mirror of
https://github.com/apache/superset.git
synced 2026-07-13 10:15:58 +00:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 (
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -100,6 +100,13 @@ const SUPERSET_CUSTOM_TOKENS: Set<string> = new Set([
|
||||
'dashboardTileBorder',
|
||||
'dashboardTileBorderRadius',
|
||||
'dashboardTileBoxShadow',
|
||||
|
||||
// Results grid tokens
|
||||
'resultsGridRowHeight',
|
||||
'resultsGridHeaderFontWeight',
|
||||
'resultsGridHeaderFontSize',
|
||||
'resultsGridBorderRadius',
|
||||
'resultsGridNoStriping',
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user