mirror of
https://github.com/apache/superset.git
synced 2026-04-18 23:55:00 +00:00
feat(theming): Custom label tokens (#38679)
This commit is contained in:
committed by
GitHub
parent
fdcb942f3c
commit
86a260e39b
@@ -110,6 +110,26 @@ export interface ColorVariants {
|
||||
}
|
||||
|
||||
export interface SupersetSpecificTokens {
|
||||
// Label variant tokens — Published/Draft (dashboard status)
|
||||
labelPublishedColor?: string;
|
||||
labelPublishedBg?: string;
|
||||
labelPublishedBorderColor?: string;
|
||||
labelPublishedIconColor?: string;
|
||||
labelDraftColor?: string;
|
||||
labelDraftBg?: string;
|
||||
labelDraftBorderColor?: string;
|
||||
labelDraftIconColor?: string;
|
||||
|
||||
// Label variant tokens — Dataset type (Physical/Virtual)
|
||||
labelDatasetPhysicalColor?: string;
|
||||
labelDatasetPhysicalBg?: string;
|
||||
labelDatasetPhysicalBorderColor?: string;
|
||||
labelDatasetPhysicalIconColor?: string;
|
||||
labelDatasetVirtualColor?: string;
|
||||
labelDatasetVirtualBg?: string;
|
||||
labelDatasetVirtualBorderColor?: string;
|
||||
labelDatasetVirtualIconColor?: string;
|
||||
|
||||
// Font-related
|
||||
fontSizeXS: string;
|
||||
fontSizeXXL: string;
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* 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 { screen } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { supersetTheme } from '@apache-superset/core/theme';
|
||||
import { DatasetTypeLabel } from './DatasetTypeLabel';
|
||||
import { renderWithTheme } from './testUtils';
|
||||
|
||||
test('renders "Physical" text for physical dataset', () => {
|
||||
renderWithTheme(<DatasetTypeLabel datasetType="physical" />);
|
||||
expect(screen.getByText('Physical')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders "Virtual" text for virtual dataset', () => {
|
||||
renderWithTheme(<DatasetTypeLabel datasetType="virtual" />);
|
||||
expect(screen.getByText('Virtual')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('uses default primary color for physical label', () => {
|
||||
renderWithTheme(<DatasetTypeLabel datasetType="physical" />);
|
||||
const tag = screen
|
||||
.getByText('Physical')
|
||||
.closest('[data-test="dataset-type-label"]');
|
||||
expect(tag).toHaveStyle({ color: supersetTheme.colorPrimaryText });
|
||||
});
|
||||
|
||||
test('uses default color for virtual label', () => {
|
||||
renderWithTheme(<DatasetTypeLabel datasetType="virtual" />);
|
||||
const tag = screen
|
||||
.getByText('Virtual')
|
||||
.closest('[data-test="dataset-type-label"]');
|
||||
expect(tag).toHaveStyle({ color: supersetTheme.colorPrimary });
|
||||
});
|
||||
|
||||
test('applies custom labelDatasetPhysical tokens when set', () => {
|
||||
renderWithTheme(<DatasetTypeLabel datasetType="physical" />, {
|
||||
labelDatasetPhysicalColor: '#111111',
|
||||
labelDatasetPhysicalBg: '#222222',
|
||||
labelDatasetPhysicalBorderColor: '#333333',
|
||||
});
|
||||
const tag = screen
|
||||
.getByText('Physical')
|
||||
.closest('[data-test="dataset-type-label"]');
|
||||
expect(tag).toHaveStyle({
|
||||
color: '#111111',
|
||||
backgroundColor: '#222222',
|
||||
borderColor: '#333333',
|
||||
});
|
||||
});
|
||||
|
||||
test('applies custom labelDatasetVirtual tokens when set', () => {
|
||||
renderWithTheme(<DatasetTypeLabel datasetType="virtual" />, {
|
||||
labelDatasetVirtualColor: '#444444',
|
||||
labelDatasetVirtualBg: '#555555',
|
||||
labelDatasetVirtualBorderColor: '#666666',
|
||||
});
|
||||
const tag = screen
|
||||
.getByText('Virtual')
|
||||
.closest('[data-test="dataset-type-label"]');
|
||||
expect(tag).toHaveStyle({
|
||||
color: '#444444',
|
||||
backgroundColor: '#555555',
|
||||
borderColor: '#666666',
|
||||
});
|
||||
});
|
||||
|
||||
test('applies custom labelDatasetPhysicalIconColor to icon', () => {
|
||||
const { container } = renderWithTheme(
|
||||
<DatasetTypeLabel datasetType="physical" />,
|
||||
{ labelDatasetPhysicalIconColor: '#aabbcc' },
|
||||
);
|
||||
const svg = container.querySelector('[role="img"]');
|
||||
expect(svg).toHaveStyle({ color: '#aabbcc' });
|
||||
});
|
||||
|
||||
test('applies custom labelDatasetVirtualIconColor to icon', () => {
|
||||
const { container } = renderWithTheme(
|
||||
<DatasetTypeLabel datasetType="virtual" />,
|
||||
{ labelDatasetVirtualIconColor: '#ddeeff' },
|
||||
);
|
||||
const svg = container.querySelector('[role="img"]');
|
||||
expect(svg).toHaveStyle({ color: '#ddeeff' });
|
||||
});
|
||||
|
||||
test('uses default colorPrimary for physical dataset icon', () => {
|
||||
const { container } = renderWithTheme(
|
||||
<DatasetTypeLabel datasetType="physical" />,
|
||||
);
|
||||
const svg = container.querySelector('[role="img"]');
|
||||
expect(svg).toHaveStyle({ color: supersetTheme.colorPrimary });
|
||||
});
|
||||
|
||||
test('virtual dataset icon has no explicit icon color by default', () => {
|
||||
const { container } = renderWithTheme(
|
||||
<DatasetTypeLabel datasetType="virtual" />,
|
||||
);
|
||||
const svg = container.querySelector('[role="img"]') as HTMLElement;
|
||||
expect(svg.style.color).toBe('');
|
||||
});
|
||||
|
||||
test('partial token override uses custom bg with default color fallback', () => {
|
||||
renderWithTheme(<DatasetTypeLabel datasetType="physical" />, {
|
||||
labelDatasetPhysicalBg: '#ff0000',
|
||||
});
|
||||
const tag = screen
|
||||
.getByText('Physical')
|
||||
.closest('[data-test="dataset-type-label"]');
|
||||
expect(tag).toHaveStyle({
|
||||
backgroundColor: '#ff0000',
|
||||
color: supersetTheme.colorPrimaryText,
|
||||
});
|
||||
});
|
||||
@@ -32,28 +32,41 @@ export const DatasetTypeLabel: React.FC<DatasetTypeLabelProps> = ({
|
||||
datasetType,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const label: string =
|
||||
datasetType === 'physical' ? t('Physical') : t('Virtual');
|
||||
const icon =
|
||||
datasetType === 'physical' ? (
|
||||
<Icons.InsertRowAboveOutlined
|
||||
iconSize={SIZE}
|
||||
iconColor={theme.colorPrimary}
|
||||
/>
|
||||
) : (
|
||||
<Icons.ConsoleSqlOutlined iconSize={SIZE} />
|
||||
);
|
||||
const labelType = datasetType === 'physical' ? 'primary' : 'default';
|
||||
const isPhysical = datasetType === 'physical';
|
||||
const label: string = isPhysical ? t('Physical') : t('Virtual');
|
||||
const labelType = isPhysical ? 'primary' : 'default';
|
||||
|
||||
const color = isPhysical
|
||||
? (theme.labelDatasetPhysicalColor ?? theme.colorPrimaryText)
|
||||
: (theme.labelDatasetVirtualColor ?? theme.colorPrimary);
|
||||
const bg = isPhysical
|
||||
? theme.labelDatasetPhysicalBg
|
||||
: theme.labelDatasetVirtualBg;
|
||||
const borderColor = isPhysical
|
||||
? theme.labelDatasetPhysicalBorderColor
|
||||
: theme.labelDatasetVirtualBorderColor;
|
||||
const iconColor = isPhysical
|
||||
? (theme.labelDatasetPhysicalIconColor ?? theme.colorPrimary)
|
||||
: theme.labelDatasetVirtualIconColor;
|
||||
|
||||
const icon = isPhysical ? (
|
||||
<Icons.InsertRowAboveOutlined iconSize={SIZE} iconColor={iconColor} />
|
||||
) : (
|
||||
<Icons.ConsoleSqlOutlined
|
||||
iconSize={SIZE}
|
||||
{...(iconColor && { iconColor })}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Label
|
||||
icon={icon}
|
||||
type={labelType}
|
||||
data-test="dataset-type-label"
|
||||
style={{
|
||||
color:
|
||||
datasetType === 'physical'
|
||||
? theme.colorPrimaryText
|
||||
: theme.colorPrimary,
|
||||
color,
|
||||
...(bg && { backgroundColor: bg }),
|
||||
...(borderColor && { borderColor }),
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 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 { screen, fireEvent } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { supersetTheme } from '@apache-superset/core/theme';
|
||||
import { PublishedLabel } from './PublishedLabel';
|
||||
import { renderWithTheme } from './testUtils';
|
||||
|
||||
test('renders "Published" text when isPublished is true', () => {
|
||||
renderWithTheme(<PublishedLabel isPublished />);
|
||||
expect(screen.getByText('Published')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders "Draft" text when isPublished is false', () => {
|
||||
renderWithTheme(<PublishedLabel isPublished={false} />);
|
||||
expect(screen.getByText('Draft')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('uses default success color for published label', () => {
|
||||
renderWithTheme(<PublishedLabel isPublished />);
|
||||
const tag = screen.getByText('Published').closest('.ant-tag');
|
||||
expect(tag).toHaveStyle({ color: supersetTheme.colorSuccessText });
|
||||
});
|
||||
|
||||
test('uses default primary color for draft label', () => {
|
||||
renderWithTheme(<PublishedLabel isPublished={false} />);
|
||||
const tag = screen.getByText('Draft').closest('.ant-tag');
|
||||
expect(tag).toHaveStyle({ color: supersetTheme.colorPrimaryText });
|
||||
});
|
||||
|
||||
test('applies custom labelPublished tokens when set', () => {
|
||||
renderWithTheme(<PublishedLabel isPublished />, {
|
||||
labelPublishedColor: '#111111',
|
||||
labelPublishedBg: '#222222',
|
||||
labelPublishedBorderColor: '#333333',
|
||||
});
|
||||
const tag = screen.getByText('Published').closest('.ant-tag');
|
||||
expect(tag).toHaveStyle({
|
||||
color: '#111111',
|
||||
backgroundColor: '#222222',
|
||||
borderColor: '#333333',
|
||||
});
|
||||
});
|
||||
|
||||
test('applies custom labelDraft tokens when set', () => {
|
||||
renderWithTheme(<PublishedLabel isPublished={false} />, {
|
||||
labelDraftColor: '#444444',
|
||||
labelDraftBg: '#555555',
|
||||
labelDraftBorderColor: '#666666',
|
||||
});
|
||||
const tag = screen.getByText('Draft').closest('.ant-tag');
|
||||
expect(tag).toHaveStyle({
|
||||
color: '#444444',
|
||||
backgroundColor: '#555555',
|
||||
borderColor: '#666666',
|
||||
});
|
||||
});
|
||||
|
||||
test('applies custom labelPublishedIconColor to icon', () => {
|
||||
const { container } = renderWithTheme(<PublishedLabel isPublished />, {
|
||||
labelPublishedIconColor: '#aabbcc',
|
||||
});
|
||||
const svg = container.querySelector('[role="img"]');
|
||||
expect(svg).toHaveStyle({ color: '#aabbcc' });
|
||||
});
|
||||
|
||||
test('applies custom labelDraftIconColor to icon', () => {
|
||||
const { container } = renderWithTheme(
|
||||
<PublishedLabel isPublished={false} />,
|
||||
{ labelDraftIconColor: '#ddeeff' },
|
||||
);
|
||||
const svg = container.querySelector('[role="img"]');
|
||||
expect(svg).toHaveStyle({ color: '#ddeeff' });
|
||||
});
|
||||
|
||||
test('uses default colorSuccess for published icon', () => {
|
||||
const { container } = renderWithTheme(<PublishedLabel isPublished />);
|
||||
const svg = container.querySelector('[role="img"]');
|
||||
expect(svg).toHaveStyle({ color: supersetTheme.colorSuccess });
|
||||
});
|
||||
|
||||
test('uses default colorPrimary for draft icon', () => {
|
||||
const { container } = renderWithTheme(<PublishedLabel isPublished={false} />);
|
||||
const svg = container.querySelector('[role="img"]');
|
||||
expect(svg).toHaveStyle({ color: supersetTheme.colorPrimary });
|
||||
});
|
||||
|
||||
test('calls onClick handler when clicked', () => {
|
||||
const handleClick = jest.fn();
|
||||
renderWithTheme(<PublishedLabel isPublished onClick={handleClick} />);
|
||||
fireEvent.click(screen.getByText('Published'));
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('partial token override uses custom bg with default color fallback', () => {
|
||||
renderWithTheme(<PublishedLabel isPublished />, {
|
||||
labelPublishedBg: '#ff0000',
|
||||
});
|
||||
const tag = screen.getByText('Published').closest('.ant-tag');
|
||||
expect(tag).toHaveStyle({
|
||||
backgroundColor: '#ff0000',
|
||||
color: supersetTheme.colorSuccessText,
|
||||
});
|
||||
});
|
||||
@@ -33,20 +33,34 @@ export const PublishedLabel: React.FC<PublishedLabelProps> = ({
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const label = isPublished ? t('Published') : t('Draft');
|
||||
const icon = isPublished ? (
|
||||
<Icons.CheckCircleOutlined iconSize="s" iconColor={theme.colorSuccess} />
|
||||
) : (
|
||||
<Icons.MinusCircleOutlined iconSize="s" iconColor={theme.colorPrimary} />
|
||||
);
|
||||
const labelType = isPublished ? 'success' : 'primary';
|
||||
|
||||
const color = isPublished
|
||||
? (theme.labelPublishedColor ?? theme.colorSuccessText)
|
||||
: (theme.labelDraftColor ?? theme.colorPrimaryText);
|
||||
const bg = isPublished ? theme.labelPublishedBg : theme.labelDraftBg;
|
||||
const borderColor = isPublished
|
||||
? theme.labelPublishedBorderColor
|
||||
: theme.labelDraftBorderColor;
|
||||
const iconColor = isPublished
|
||||
? (theme.labelPublishedIconColor ?? theme.colorSuccess)
|
||||
: (theme.labelDraftIconColor ?? theme.colorPrimary);
|
||||
|
||||
const icon = isPublished ? (
|
||||
<Icons.CheckCircleOutlined iconSize="s" iconColor={iconColor} />
|
||||
) : (
|
||||
<Icons.MinusCircleOutlined iconSize="s" iconColor={iconColor} />
|
||||
);
|
||||
|
||||
return (
|
||||
<Label
|
||||
type={labelType}
|
||||
icon={icon}
|
||||
onClick={onClick}
|
||||
style={{
|
||||
color: isPublished ? theme.colorSuccessText : theme.colorPrimaryText,
|
||||
color,
|
||||
...(bg && { backgroundColor: bg }),
|
||||
...(borderColor && { borderColor }),
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 ReactElement } from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { ThemeProvider } from '@emotion/react';
|
||||
import { Theme, supersetTheme } from '@apache-superset/core/theme';
|
||||
|
||||
export function renderWithTheme(
|
||||
ui: ReactElement,
|
||||
tokenOverrides?: Record<string, string>,
|
||||
) {
|
||||
const theme = tokenOverrides
|
||||
? Theme.fromConfig({ token: tokenOverrides }).theme
|
||||
: supersetTheme;
|
||||
return render(<ThemeProvider theme={theme}>{ui}</ThemeProvider>);
|
||||
}
|
||||
@@ -106,3 +106,31 @@ test('getAllValidTokenNames includes known Ant Design tokens', () => {
|
||||
expect(result.antdTokens).toContain('fontSize');
|
||||
expect(result.antdTokens).toContain('padding');
|
||||
});
|
||||
|
||||
test('label variant tokens are recognized as valid Superset custom tokens', () => {
|
||||
const labelTokens = [
|
||||
// Published/Draft
|
||||
'labelPublishedColor',
|
||||
'labelPublishedBg',
|
||||
'labelPublishedBorderColor',
|
||||
'labelPublishedIconColor',
|
||||
'labelDraftColor',
|
||||
'labelDraftBg',
|
||||
'labelDraftBorderColor',
|
||||
'labelDraftIconColor',
|
||||
// Dataset type
|
||||
'labelDatasetPhysicalColor',
|
||||
'labelDatasetPhysicalBg',
|
||||
'labelDatasetPhysicalBorderColor',
|
||||
'labelDatasetPhysicalIconColor',
|
||||
'labelDatasetVirtualColor',
|
||||
'labelDatasetVirtualBg',
|
||||
'labelDatasetVirtualBorderColor',
|
||||
'labelDatasetVirtualIconColor',
|
||||
];
|
||||
|
||||
labelTokens.forEach(token => {
|
||||
expect(isValidTokenName(token)).toBe(true);
|
||||
expect(isSupersetCustomToken(token)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -48,6 +48,26 @@ const SUPERSET_CUSTOM_TOKENS: Set<string> = new Set([
|
||||
// Font loading
|
||||
'fontUrls',
|
||||
|
||||
// Label variant tokens — Published/Draft (dashboard status)
|
||||
'labelPublishedColor',
|
||||
'labelPublishedBg',
|
||||
'labelPublishedBorderColor',
|
||||
'labelPublishedIconColor',
|
||||
'labelDraftColor',
|
||||
'labelDraftBg',
|
||||
'labelDraftBorderColor',
|
||||
'labelDraftIconColor',
|
||||
|
||||
// Label variant tokens — Dataset type (Physical/Virtual)
|
||||
'labelDatasetPhysicalColor',
|
||||
'labelDatasetPhysicalBg',
|
||||
'labelDatasetPhysicalBorderColor',
|
||||
'labelDatasetPhysicalIconColor',
|
||||
'labelDatasetVirtualColor',
|
||||
'labelDatasetVirtualBg',
|
||||
'labelDatasetVirtualBorderColor',
|
||||
'labelDatasetVirtualIconColor',
|
||||
|
||||
// Editor tokens
|
||||
'colorEditorSelection',
|
||||
|
||||
|
||||
Reference in New Issue
Block a user