feat(theming): add opt-in SQL Lab grid tokens and fix modal border-radius clipping (#41031)

Co-authored-by: aadhikari <aadhikari@apple.com>
This commit is contained in:
anamitraadhikari
2026-07-07 00:54:58 -07:00
committed by GitHub
parent de5a233ccf
commit ec4e5deb01
10 changed files with 208 additions and 21 deletions

View File

@@ -258,6 +258,16 @@ export interface SupersetSpecificTokens {
dashboardTileBorder?: string;
dashboardTileBorderRadius?: number;
dashboardTileBoxShadow?: string;
/**
* Results grid customization tokens.
* Control the appearance of AG Grid-backed result tables (e.g. SQL Lab).
*/
resultsGridRowHeight?: number;
resultsGridHeaderFontWeight?: number;
resultsGridHeaderFontSize?: number;
resultsGridBorderRadius?: number;
resultsGridNoStriping?: boolean;
}
/**

View File

@@ -0,0 +1,94 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import type { SupersetTheme } from '@apache-superset/core/theme';
import { buildResultsGridThemeOverrides } from './buildResultsGridThemeOverrides';
test('returns undefined when no resultsGrid tokens are set', () => {
const theme = {} as SupersetTheme;
expect(buildResultsGridThemeOverrides(theme)).toBeUndefined();
});
test('maps resultsGridHeaderFontSize to headerFontSize', () => {
const theme = { resultsGridHeaderFontSize: 14 } as SupersetTheme;
expect(buildResultsGridThemeOverrides(theme)).toEqual({
headerFontSize: 14,
});
});
test('maps resultsGridHeaderFontWeight to headerFontWeight', () => {
const theme = { resultsGridHeaderFontWeight: 700 } as SupersetTheme;
expect(buildResultsGridThemeOverrides(theme)).toEqual({
headerFontWeight: 700,
});
});
test('maps resultsGridRowHeight to both rowHeight and headerHeight', () => {
const theme = { resultsGridRowHeight: 40 } as SupersetTheme;
expect(buildResultsGridThemeOverrides(theme)).toEqual({
rowHeight: 40,
headerHeight: 40,
});
});
test('maps resultsGridBorderRadius to both borderRadius and wrapperBorderRadius', () => {
const theme = { resultsGridBorderRadius: 8 } as SupersetTheme;
expect(buildResultsGridThemeOverrides(theme)).toEqual({
borderRadius: 8,
wrapperBorderRadius: 8,
});
});
test('maps resultsGridNoStriping to oddRowBackgroundColor transparent', () => {
const theme = { resultsGridNoStriping: true } as SupersetTheme;
expect(buildResultsGridThemeOverrides(theme)).toEqual({
oddRowBackgroundColor: 'transparent',
});
});
test('does not map resultsGridNoStriping when false', () => {
const theme = { resultsGridNoStriping: false } as SupersetTheme;
expect(buildResultsGridThemeOverrides(theme)).toBeUndefined();
});
test('maps all tokens together when all are set', () => {
const theme = {
resultsGridHeaderFontSize: 12,
resultsGridHeaderFontWeight: 600,
resultsGridRowHeight: 36,
resultsGridBorderRadius: 4,
resultsGridNoStriping: true,
} as SupersetTheme;
expect(buildResultsGridThemeOverrides(theme)).toEqual({
headerFontSize: 12,
headerFontWeight: 600,
rowHeight: 36,
headerHeight: 36,
borderRadius: 4,
wrapperBorderRadius: 4,
oddRowBackgroundColor: 'transparent',
});
});
test('ignores non-number values for numeric tokens', () => {
const theme = {
resultsGridRowHeight: '40',
resultsGridHeaderFontSize: undefined,
} as unknown as SupersetTheme;
expect(buildResultsGridThemeOverrides(theme)).toBeUndefined();
});

View File

@@ -0,0 +1,46 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import type { SupersetTheme } from '@apache-superset/core/theme';
import type { GridThemeOverrides } from 'src/components/GridTable/types';
export function buildResultsGridThemeOverrides(
theme: SupersetTheme,
): GridThemeOverrides | undefined {
const overrides: GridThemeOverrides = {};
if (typeof theme.resultsGridHeaderFontSize === 'number') {
overrides.headerFontSize = theme.resultsGridHeaderFontSize;
}
if (typeof theme.resultsGridHeaderFontWeight === 'number') {
overrides.headerFontWeight = theme.resultsGridHeaderFontWeight;
}
if (typeof theme.resultsGridRowHeight === 'number') {
overrides.rowHeight = theme.resultsGridRowHeight;
overrides.headerHeight = theme.resultsGridRowHeight;
}
if (typeof theme.resultsGridBorderRadius === 'number') {
overrides.borderRadius = theme.resultsGridBorderRadius;
overrides.wrapperBorderRadius = theme.resultsGridBorderRadius;
}
if (theme.resultsGridNoStriping === true) {
overrides.oddRowBackgroundColor = 'transparent';
}
return Object.keys(overrides).length > 0 ? overrides : undefined;
}

View File

@@ -44,6 +44,8 @@ import {
FilterableTable,
ErrorMessageWithStackTrace,
} from 'src/components';
import type { GridThemeOverrides } from 'src/components/GridTable/types';
import { buildResultsGridThemeOverrides } from './buildResultsGridThemeOverrides';
import { nanoid } from 'nanoid';
import { t } from '@apache-superset/core/translation';
import {
@@ -214,6 +216,12 @@ const ResultSet = ({
extensionsRegistry.get('sqleditor.extension.resultTable') ??
FilterableTable;
const theme = useTheme();
const resultsGridThemeOverrides = useMemo<GridThemeOverrides | undefined>(
() => buildResultsGridThemeOverrides(theme),
[theme],
);
const [searchText, setSearchText] = useState('');
const [cachedData, setCachedData] = useState<Record<string, unknown>[]>([]);
const [showSaveDatasetModal, setShowSaveDatasetModal] = useState(false);
@@ -708,6 +716,7 @@ const ResultSet = ({
filterText: searchText,
expandedColumns,
allowHTML,
themeOverrides: resultsGridThemeOverrides,
};
return (

View File

@@ -44,40 +44,40 @@ const StyledEditableTabs = styled(EditableTabs)`
height: 100%;
display: flex;
flex-direction: column;
& .ant-tabs-nav::before {
border-color: ${({ theme }) => theme.colorBorder} !important;
&& .ant-tabs-nav::before {
border-color: ${({ theme }) => theme.colorBorder};
}
& .ant-tabs-nav-add {
border-color: ${({ theme }) => theme.colorBorder} !important;
&& .ant-tabs-nav-add {
border-color: ${({ theme }) => theme.colorBorder};
height: 34px;
}
& .ant-tabs-nav-list {
&& .ant-tabs-nav-list {
align-items: end;
padding-top: 1px;
column-gap: ${({ theme }) => theme.sizeUnit}px;
}
& .ant-tabs-tab-active {
border-left-color: ${({ theme }) => theme.colorPrimaryActive} !important;
border-top-color: ${({ theme }) => theme.colorPrimaryActive} !important;
border-right-color: ${({ theme }) => theme.colorPrimaryActive} !important;
box-shadow: 0 0 2px ${({ theme }) => theme.colorPrimaryActive} !important;
&& .ant-tabs-tab-active {
border-left-color: ${({ theme }) => theme.colorPrimaryActive};
border-top-color: ${({ theme }) => theme.colorPrimaryActive};
border-right-color: ${({ theme }) => theme.colorPrimaryActive};
box-shadow: 0 0 2px ${({ theme }) => theme.colorPrimaryActive};
border-top: 2px;
}
& .ant-tabs-tab {
border-radius: 2px 2px 0px 0px !important;
&& .ant-tabs-tab {
border-radius: 2px 2px 0px 0px;
padding: ${({ theme }) => theme.sizeUnit}px
${({ theme }) => theme.sizeUnit * 2}px !important;
${({ theme }) => theme.sizeUnit * 2}px;
& + .ant-tabs-nav-add {
margin-right: ${({ theme }) => theme.sizeUnit * 4}px;
}
&:not(.ant-tabs-tab-active) {
border-color: ${({ theme }) => theme.colorBorder} !important;
box-shadow: inset 0 0 1px ${({ theme }) => theme.colorBorder} !important;
border-color: ${({ theme }) => theme.colorBorder};
box-shadow: inset 0 0 1px ${({ theme }) => theme.colorBorder};
}
}
& .ant-tabs-nav-add {
border-radius: 2px 2px 0px 0px !important;
min-height: auto !important;
&& .ant-tabs-nav-add {
border-radius: 2px 2px 0px 0px;
min-height: auto;
align-self: flex-end;
}
`;

View File

@@ -65,6 +65,7 @@ export const FilterableTable = ({
expandedColumns = [],
allowHTML = true,
striped,
themeOverrides,
}: FilterableTableProps) => {
const getCellContent = useCellContentParser({
columnKeys: orderedColumnKeys,
@@ -131,6 +132,7 @@ export const FilterableTable = ({
striped={striped}
enableActions
columnReorderable
themeOverrides={themeOverrides}
/>
</div>
);

View File

@@ -16,6 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
import type { GridThemeOverrides } from '../GridTable/types';
export type CellDataType = string | number | null;
export type Datum = Record<string, CellDataType>;
@@ -31,4 +33,5 @@ export interface FilterableTableProps {
striped?: boolean;
expandedColumns?: string[];
allowHTML?: boolean;
themeOverrides?: GridThemeOverrides;
}

View File

@@ -46,6 +46,7 @@ export function GridTable<RecordType extends object>({
enableActions,
size = GridSize.Middle,
striped,
themeOverrides,
}: TableProps<RecordType>) {
const theme = useTheme();
const isExternalFilterPresent = useCallback(
@@ -138,18 +139,20 @@ export function GridTable<RecordType extends object>({
[columnReorderable, enableActions, sortable],
);
const rowHeight = theme.sizeUnit * (size === GridSize.Middle ? 9 : 7);
const defaultRowHeight = theme.sizeUnit * (size === GridSize.Middle ? 9 : 7);
const rowHeight = themeOverrides?.rowHeight ?? defaultRowHeight;
const headerHeight = themeOverrides?.headerHeight ?? rowHeight;
const gridOptions = useMemo<GridOptions>(
() => ({
enableCellTextSelection: true,
ensureDomOrder: true,
suppressFieldDotNotation: true,
headerHeight: rowHeight,
headerHeight,
rowSelection: 'multiple',
rowHeight,
}),
[rowHeight],
[rowHeight, headerHeight],
);
return (
@@ -200,6 +203,7 @@ export function GridTable<RecordType extends object>({
components={gridComponents}
gridOptions={gridOptions}
onCellKeyDown={onKeyDown}
themeOverrides={themeOverrides}
/>
</div>
);

View File

@@ -26,6 +26,16 @@ export type ColDef = {
field: string;
};
export interface GridThemeOverrides {
headerFontSize?: number;
headerFontWeight?: number;
rowHeight?: number;
headerHeight?: number;
borderRadius?: number;
wrapperBorderRadius?: number;
oddRowBackgroundColor?: string;
}
export interface TableProps<RecordType> {
/**
* Data that will populate the each row and map to the column key.
@@ -59,4 +69,6 @@ export interface TableProps<RecordType> {
usePagination?: boolean;
striped?: boolean;
themeOverrides?: GridThemeOverrides;
}

View File

@@ -100,6 +100,13 @@ const SUPERSET_CUSTOM_TOKENS: Set<string> = new Set([
'dashboardTileBorder',
'dashboardTileBorderRadius',
'dashboardTileBoxShadow',
// Results grid tokens
'resultsGridRowHeight',
'resultsGridHeaderFontWeight',
'resultsGridHeaderFontSize',
'resultsGridBorderRadius',
'resultsGridNoStriping',
]);
/**