feat(theming): land Ant Design v5 overhaul — dynamic themes, real dark mode + massive styling refactor (#31590)

Co-authored-by: Enzo Martellucci <52219496+EnxDev@users.noreply.github.com>
Co-authored-by: Diego Pucci <diegopucci.me@gmail.com>
Co-authored-by: Mehmet Salih Yavuz <salih.yavuz@proton.me>
Co-authored-by: Geido <60598000+geido@users.noreply.github.com>
Co-authored-by: Alexandru Soare <37236580+alexandrusoare@users.noreply.github.com>
Co-authored-by: Damian Pendrak <dpendrak@gmail.com>
Co-authored-by: Pius Iniobong <67148161+payose@users.noreply.github.com>
Co-authored-by: Enzo Martellucci <enzomartellucci@gmail.com>
Co-authored-by: Kamil Gabryjelski <kamil.gabryjelski@gmail.com>
This commit is contained in:
Maxime Beauchemin
2025-06-20 13:38:58 -07:00
committed by GitHub
parent 2cc1ef88c8
commit dd129fa403
1267 changed files with 32958 additions and 23592 deletions

View File

@@ -27,7 +27,7 @@ import {
DragEvent,
useEffect,
} from 'react';
import { styled, typedMemo, usePrevious } from '@superset-ui/core';
import {
useTable,
usePagination,
@@ -41,9 +41,8 @@ import {
Row,
} from 'react-table';
import { matchSorter, rankings } from 'match-sorter';
import { styled, typedMemo, usePrevious } from '@superset-ui/core';
import { isEqual } from 'lodash';
import { Space } from 'antd';
import { Space } from '@superset-ui/core/components';
import GlobalFilter, { GlobalFilterProps } from './components/GlobalFilter';
import SelectPageSize, {
SelectPageSizeProps,

View File

@@ -22,8 +22,10 @@ import {
ChangeEventHandler,
useRef,
useEffect,
Ref,
} from 'react';
import { Row, FilterValue } from 'react-table';
import { Input, type InputRef, Space } from '@superset-ui/core/components';
import useAsyncState from '../utils/useAsyncState';
export interface SearchInputProps {
@@ -31,7 +33,7 @@ export interface SearchInputProps {
value: string;
onChange: ChangeEventHandler<HTMLInputElement>;
onBlur?: () => void;
inputRef?: React.RefObject<HTMLInputElement>;
inputRef?: Ref<InputRef>;
}
const isSearchFocused = new Map();
@@ -56,17 +58,18 @@ function DefaultSearchInput({
inputRef,
}: SearchInputProps) {
return (
<span className="dt-global-filter">
Search{' '}
<input
<Space direction="horizontal" size={4} className="dt-global-filter">
Search
<Input
size="small"
ref={inputRef}
className="form-control input-sm"
placeholder={`${count} records...`}
value={value}
onChange={onChange}
onBlur={onBlur}
className="form-control input-sm"
/>
</span>
</Space>
);
}
@@ -82,7 +85,7 @@ export default (memo as <T>(fn: T) => T)(function GlobalFilter<
rowCount,
}: GlobalFilterProps<D>) {
const count = serverPagination ? rowCount : preGlobalFilteredRows.length;
const inputRef = useRef<HTMLInputElement>(null);
const inputRef = useRef<InputRef>(null);
const [value, setValue] = useAsyncState(
filterValue,

View File

@@ -18,10 +18,10 @@
*/
/* eslint-disable import/no-extraneous-dependencies */
import { styled } from '@superset-ui/core';
import { Select } from 'antd';
import { RawAntdSelect } from '@superset-ui/core/components';
import { SearchOption } from '../../types';
const StyledSelect = styled(Select)`
const StyledSelect = styled(RawAntdSelect)`
width: 120px;
margin-right: 8px;
`;
@@ -29,7 +29,7 @@ const StyledSelect = styled(Select)`
interface SearchSelectDropdownProps {
/** The currently selected search column value */
value?: string;
/** Callback triggered when a new search column is selected */
/** Function triggered when a new search column is selected */
onChange: (searchCol: string) => void;
/** Available search column options to populate the dropdown */
searchOptions: SearchOption[];

View File

@@ -17,8 +17,9 @@
* under the License.
*/
import { memo } from 'react';
import { t } from '@superset-ui/core';
import { css, t } from '@superset-ui/core';
import { formatSelectOptions } from '@superset-ui/chart-controls';
import { RawAntdSelect } from '@superset-ui/core/components';
export type SizeOption = [number, string];
@@ -33,16 +34,18 @@ function DefaultSelectRenderer({
options,
onChange,
}: SelectPageSizeRendererProps) {
const { Option } = RawAntdSelect;
return (
<span className="dt-select-page-size form-inline">
{t('Show')}{' '}
<select
className="form-control input-sm"
<RawAntdSelect<number>
value={current}
onBlur={() => {}}
onChange={e => {
onChange(Number((e.target as HTMLSelectElement).value));
}}
onChange={value => onChange(value)}
size="small"
css={theme => css`
width: ${theme.sizeUnit * 18}px;
`}
>
{options.map(option => {
const [size, text] = Array.isArray(option)
@@ -50,16 +53,16 @@ function DefaultSelectRenderer({
: [option, option];
const sizeLabel = size === 0 ? t('all') : size;
return (
<option
aria-label={t('Show %s entries', sizeLabel)}
<Option
key={size}
value={size}
value={Number(size)}
aria-label={t('Show %s entries', sizeLabel)}
>
{text}
</option>
</Option>
);
})}
</select>{' '}
</RawAntdSelect>{' '}
{t('entries')}
</span>
);

View File

@@ -16,36 +16,50 @@
* specific language governing permissions and limitations
* under the License.
*/
import { css, styled } from '@superset-ui/core';
export default styled.div`
${({ theme }) => css`
/* Base table styles */
table {
width: 100%;
min-width: auto;
max-width: none;
margin: 0;
border-collapse: collapse;
}
/* Cell styling */
th,
td {
min-width: 4.3em;
padding: 0.75rem;
vertical-align: top;
}
/* Header styling */
thead > tr > th {
padding-right: 0;
position: relative;
background: ${theme.colors.grayscale.light5};
background-color: ${theme.colorBgBase};
text-align: left;
border-bottom: 2px solid ${theme.colorSplit};
color: ${theme.colorText};
vertical-align: bottom;
}
/* Icons in header */
th svg {
color: ${theme.colors.grayscale.light2};
margin: ${theme.gridUnit / 2}px;
margin: 1px ${theme.sizeUnit / 2}px;
fill-opacity: 0.2;
}
th.is-sorted svg {
color: ${theme.colors.grayscale.base};
color: ${theme.colorText};
fill-opacity: 1;
}
/* Table body styling */
.table > tbody > tr:first-of-type > td,
.table > tbody > tr:first-of-type > th {
border-top: 0;
@@ -53,58 +67,113 @@ export default styled.div`
.table > tbody tr td {
font-feature-settings: 'tnum' 1;
border-top: 1px solid ${theme.colorSplit};
}
/* Bootstrap-like condensed table styles */
table.table-condensed,
table.table-sm {
font-size: ${theme.fontSizeSM}px;
}
table.table-condensed th,
table.table-condensed td,
table.table-sm th,
table.table-sm td {
padding: 0.3rem;
}
/* Bootstrap-like bordered table styles */
table.table-bordered {
border: 1px solid ${theme.colorSplit};
}
table.table-bordered th,
table.table-bordered td {
border: 1px solid ${theme.colorSplit};
}
/* Bootstrap-like striped table styles */
table.table-striped tbody tr:nth-of-type(odd) {
background-color: ${theme.colorBgLayout};
}
/* Controls and metrics */
.dt-controls {
padding-bottom: 0.65em;
}
.dt-metric {
text-align: right;
}
.dt-totals {
font-weight: ${theme.typography.weights.bold};
font-weight: ${theme.fontWeightStrong};
}
.dt-is-null {
color: ${theme.colors.grayscale.light1};
color: ${theme.colorTextTertiary};
}
td.dt-is-filter {
cursor: pointer;
}
td.dt-is-filter:hover {
background-color: ${theme.colors.secondary.light4};
background-color: ${theme.colorPrimaryBgHover};
}
td.dt-is-active-filter,
td.dt-is-active-filter:hover {
background-color: ${theme.colors.secondary.light3};
background-color: ${theme.colorPrimaryBgHover};
}
.dt-global-filter {
float: right;
}
/* Cell truncation */
.dt-truncate-cell {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dt-truncate-cell:hover {
overflow: visible;
white-space: normal;
height: auto;
}
/* Pagination styling */
.dt-pagination {
text-align: right;
/* use padding instead of margin so clientHeight can capture it */
padding-top: 0.5em;
}
.dt-pagination .pagination {
margin: 0;
.dt-pagination .pagination > li > a,
.dt-pagination .pagination > li > span {
background-color: ${theme.colorBgBase};
color: ${theme.colorText};
border-color: ${theme.colorBorderSecondary};
}
.dt-pagination .pagination > li.active > a,
.dt-pagination .pagination > li.active > span,
.dt-pagination .pagination > li.active > a:focus,
.dt-pagination .pagination > li.active > a:hover,
.dt-pagination .pagination > li.active > span:focus,
.dt-pagination .pagination > li.active > span:hover {
background-color: ${theme.colorPrimary};
color: ${theme.colorBgContainer};
border-color: ${theme.colorBorderSecondary};
}
.pagination > li > span.dt-pagination-ellipsis:focus,
.pagination > li > span.dt-pagination-ellipsis:hover {
background: ${theme.colors.grayscale.light5};
background: ${theme.colorBgLayout};
border-color: ${theme.colorBorderSecondary};
}
.dt-no-results {
@@ -113,8 +182,9 @@ export default styled.div`
}
.right-border-only {
border-right: 2px solid ${theme.colors.grayscale.light2};
border-right: 2px solid ${theme.colorSplit};
}
table .right-border-only:last-child {
border-right: none;
}

View File

@@ -52,8 +52,16 @@ import {
t,
tn,
useTheme,
SupersetTheme,
} from '@superset-ui/core';
import { Dropdown, Menu, Tooltip } from '@superset-ui/chart-controls';
import {
Input,
Space,
RawAntdSelect as Select,
Dropdown,
Menu,
Tooltip,
} from '@superset-ui/core/components';
import {
CheckOutlined,
InfoCircleOutlined,
@@ -62,7 +70,7 @@ import {
PlusCircleOutlined,
TableOutlined,
} from '@ant-design/icons';
import { debounce, isEmpty, isEqual } from 'lodash';
import { isEmpty, debounce, isEqual } from 'lodash';
import {
ColorSchemeEnum,
DataColumnMeta,
@@ -179,49 +187,50 @@ function SortIcon<D extends object>({ column }: { column: ColumnInstance<D> }) {
return sortIcon;
}
const SearchInput = ({
function SearchInput({
count,
value,
onChange,
onBlur,
inputRef,
}: SearchInputProps) => (
<span className="dt-global-filter">
{t('Search')}{' '}
<input
ref={inputRef}
aria-label={t('Search %s records', count)}
className="form-control input-sm"
placeholder={tn('search.num_records', count)}
value={value}
onChange={onChange}
onBlur={onBlur}
/>
</span>
);
}: SearchInputProps) {
return (
<Space direction="horizontal" size={4} className="dt-global-filter">
{t('Search')}
<Input
size="small"
aria-label={t('Search %s records', count)}
placeholder={tn('search.num_records', count)}
value={value}
onChange={onChange}
onBlur={onBlur}
ref={inputRef}
/>
</Space>
);
}
function SelectPageSize({
options,
current,
onChange,
}: SelectPageSizeRendererProps) {
const { Option } = Select;
return (
<span
className="dt-select-page-size form-inline"
role="group"
aria-label={t('Select page size')}
>
<>
<label htmlFor="pageSizeSelect" className="sr-only">
{t('Select page size')}
</label>
{t('Show')}{' '}
<select
<Select<number>
id="pageSizeSelect"
className="form-control input-sm"
value={current}
onChange={e => {
onChange(Number((e.target as HTMLSelectElement).value));
}}
onChange={value => onChange(value)}
size="small"
css={(theme: SupersetTheme) => css`
width: ${theme.sizeUnit * 18}px;
`}
aria-label={t('Show entries per page')}
>
{options.map(option => {
@@ -229,14 +238,14 @@ function SelectPageSize({
? option
: [option, option];
return (
<option key={size} value={size}>
<Option key={size} value={Number(size)}>
{text}
</option>
</Option>
);
})}
</select>{' '}
</Select>{' '}
{t('entries per page')}
</span>
</>
);
}
@@ -569,9 +578,9 @@ export default function TableChart<D extends DataRecord = DataRecord>(
<div
css={css`
max-width: 242px;
padding: 0 ${theme.gridUnit * 2}px;
color: ${theme.colors.grayscale.base};
font-size: ${theme.typography.sizes.s}px;
padding: 0 ${theme.sizeUnit * 2}px;
color: ${theme.colorText};
font-size: ${theme.fontSizeSM}px;
`}
>
{t(
@@ -582,7 +591,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
<Menu.Item key={column.key}>
<span
css={css`
color: ${theme.colors.grayscale.dark2};
color: ${theme.colorText};
`}
>
{column.label}
@@ -590,7 +599,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
<span
css={css`
float: right;
font-size: ${theme.typography.sizes.s}px;
font-size: ${theme.fontSizeSM}px;
`}
>
{selectedComparisonColumns.includes(column.key) && (
@@ -641,7 +650,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
css={css`
float: right;
& svg {
color: ${theme.colors.grayscale.base} !important;
color: ${theme.colorIcon} !important;
}
`}
>
@@ -672,7 +681,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
<tr
css={css`
th {
border-right: 2px solid ${theme.colors.grayscale.light2};
border-right: 1px solid ${theme.colorSplit};
}
th:first-child {
border-left: none;
@@ -755,7 +764,6 @@ export default function TableChart<D extends DataRecord = DataRecord>(
isUsingTimeComparison &&
Array.isArray(basicColorFormatters) &&
basicColorFormatters.length > 0;
const valueRange =
!hasBasicColorFormatters &&
!hasColumnColorFormatters &&
@@ -829,15 +837,15 @@ export default function TableChart<D extends DataRecord = DataRecord>(
? basicColorColumnFormatters[row.index][column.key]?.mainArrow
: '';
}
const StyledCell = styled.td`
color: ${theme.colorText};
text-align: ${sharedStyle.textAlign};
white-space: ${value instanceof Date ? 'nowrap' : undefined};
position: relative;
background: ${backgroundColor || undefined};
padding-left: ${column.isChildColumn
? `${theme.gridUnit * 5}px`
: `${theme.gridUnit}px`};
? `${theme.sizeUnit * 5}px`
: `${theme.sizeUnit}px`};
`;
const cellBarStyles = css`
@@ -868,9 +876,9 @@ export default function TableChart<D extends DataRecord = DataRecord>(
color: ${basicColorFormatters &&
basicColorFormatters[row.index][originKey]?.arrowColor ===
ColorSchemeEnum.Green
? theme.colors.success.base
: theme.colors.error.base};
margin-right: ${theme.gridUnit}px;
? theme.colorSuccess
: theme.colorError};
margin-right: ${theme.sizeUnit}px;
`;
if (
@@ -880,9 +888,9 @@ export default function TableChart<D extends DataRecord = DataRecord>(
arrowStyles = css`
color: ${basicColorColumnFormatters[row.index][column.key]
?.arrowColor === ColorSchemeEnum.Green
? theme.colors.success.base
: theme.colors.error.base};
margin-right: ${theme.gridUnit}px;
? theme.colorSuccess
: theme.colorError};
margin-right: ${theme.sizeUnit}px;
`;
}
@@ -971,7 +979,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
},
Header: ({ column: col, onClick, style, onDragStart, onDrop }) => (
<th
id={`header-${column.key}`}
id={`header-${column.originalLabel}`}
title={t('Shift + Click to sort by multiple columns')}
className={[className, col.isSorted ? 'is-sorted' : ''].join(' ')}
style={{
@@ -1027,8 +1035,8 @@ export default function TableChart<D extends DataRecord = DataRecord>(
display: flex;
align-items: center;
& svg {
margin-left: ${theme.gridUnit}px;
color: ${theme.colors.grayscale.dark1} !important;
margin-left: ${theme.sizeUnit}px;
color: ${theme.colorBorder} !important;
}
`}
>

View File

@@ -18,17 +18,18 @@
*/
import {
AdhocColumn,
BuildQuery,
PostProcessingRule,
QueryFormOrderBy,
QueryMode,
QueryObject,
buildQueryContext,
ensureIsArray,
getMetricLabel,
isPhysicalColumn,
QueryFormOrderBy,
QueryMode,
QueryObject,
removeDuplicates,
} from '@superset-ui/core';
import { PostProcessingRule } from '@superset-ui/core/src/query/types/PostProcessing';
import { BuildQuery } from '@superset-ui/core/src/chart/registries/ChartBuildQueryRegistrySingleton';
import {
isTimeComparison,
timeCompareOperator,

View File

@@ -17,8 +17,7 @@
* under the License.
*/
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import { ThemeProvider, supersetTheme } from '@superset-ui/core';
import { render, screen } from '@superset-ui/core/spec';
import TableChart from '../src/TableChart';
import transformProps from '../src/transformProps';
import DateWithFormatter from '../src/utils/DateWithFormatter';
@@ -268,9 +267,7 @@ describe('plugin-chart-table', () => {
describe('TableChart', () => {
it('render basic data', () => {
render(
<ThemeProvider theme={supersetTheme}>
<TableChart {...transformProps(testData.basic)} sticky={false} />,
</ThemeProvider>,
<TableChart {...transformProps(testData.basic)} sticky={false} />,
);
const firstDataRow = screen.getAllByRole('rowgroup')[1];
@@ -289,10 +286,10 @@ describe('plugin-chart-table', () => {
it('render advanced data', () => {
render(
<ThemeProvider theme={supersetTheme}>
<>
<TableChart {...transformProps(testData.advanced)} sticky={false} />
,
</ThemeProvider>,
</>,
);
const secondColumnHeader = screen.getByText('Sum of Num');
expect(secondColumnHeader).toBeInTheDocument();
@@ -413,9 +410,7 @@ describe('plugin-chart-table', () => {
it('render empty data', () => {
render(
<ThemeProvider theme={supersetTheme}>
<TableChart {...transformProps(testData.empty)} sticky={false} />,
</ThemeProvider>,
<TableChart {...transformProps(testData.empty)} sticky={false} />,
);
expect(screen.getByText('No records found')).toBeInTheDocument();
});
@@ -491,14 +486,10 @@ describe('plugin-chart-table', () => {
);
expect(getComputedStyle(screen.getByText('N/A')).background).toBe('');
});
it('should display originalLabel in grouped headers', () => {
it('should display original label in grouped headers', () => {
const props = transformProps(testData.comparison);
render(
<ThemeProvider theme={supersetTheme}>
<TableChart {...props} sticky={false} />
</ThemeProvider>,
);
render(<TableChart {...props} sticky={false} />);
const groupHeaders = screen.getAllByRole('columnheader');
expect(groupHeaders.length).toBeGreaterThan(0);
const hasMetricHeaders = groupHeaders.some(

View File

@@ -1,18 +1,18 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"declarationDir": "lib",
"composite": true,
"rootDir": "src",
"outDir": "lib",
"rootDir": "src"
},
"exclude": ["lib", "test"],
"extends": "../../tsconfig.json",
"include": ["src/**/*", "types/**/*", "../../types/**/*"],
"references": [
{
"path": "../../packages/superset-ui-chart-controls"
},
{
"path": "../../packages/superset-ui-core"
"baseUrl": ".",
"paths": {
"@superset-ui/core/components": ["../../packages/superset-ui-core/src/components"]
}
},
"include": ["src/**/*", "types/**/*"],
"exclude": ["lib", "test"],
"references": [
{ "path": "../../packages/superset-ui-core" },
{ "path": "../../packages/superset-ui-chart-controls" }
]
}