diff --git a/superset-frontend/packages/superset-core/src/theme/colors.test.ts b/superset-frontend/packages/superset-core/src/theme/colors.test.ts new file mode 100644 index 00000000000..9398d6ae6a9 --- /dev/null +++ b/superset-frontend/packages/superset-core/src/theme/colors.test.ts @@ -0,0 +1,26 @@ +/** + * 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 { ColorSchemeGroup } from '@apache-superset/core/theme'; + +test('ColorSchemeGroup has the expected string values', () => { + expect(ColorSchemeGroup.Custom).toBe('custom'); + expect(ColorSchemeGroup.Featured).toBe('featured'); + expect(ColorSchemeGroup.Other).toBe('other'); +}); diff --git a/superset-frontend/packages/superset-core/src/theme/colors.ts b/superset-frontend/packages/superset-core/src/theme/colors.ts new file mode 100644 index 00000000000..f15dda2bc2a --- /dev/null +++ b/superset-frontend/packages/superset-core/src/theme/colors.ts @@ -0,0 +1,74 @@ +/** + * 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. + */ + +/** + * Grouping/tier for a color scheme — controls how it appears in the + * scheme picker UI (e.g. Featured palettes are shown first). + * + * Mirrors @superset-ui/core's ColorSchemeGroup; kept here so + * palette configs have no dependency on @superset-ui/core. + */ +export enum ColorSchemeGroup { + Custom = 'custom', + Featured = 'featured', + Other = 'other', +} + +/** Plain configuration object for a categorical color scheme. */ +export interface ColorSchemeConfig { + id: string; + label?: string; + colors: string[]; + description?: string; + isDefault?: boolean; + group?: ColorSchemeGroup; +} + +/** Extension of ColorSchemeConfig for sequential / diverging schemes. */ +export interface SequentialSchemeConfig extends ColorSchemeConfig { + isDiverging?: boolean; +} + +/** + * Minimal interface for the categorical color scheme registry. + * Mirrors the public surface of @superset-ui/core's ColorSchemeRegistry. + */ +export interface CategoricalScheme { + id: string; + label?: string; + colors: string[]; +} + +export interface CategoricalSchemeRegistryLike { + keys(): string[]; + get(name: string): CategoricalScheme | null | undefined; +} + +/** + * Returns an alphabetically sorted list of all registered categorical color + * scheme names. The host app provides the implementation via + * window.superset.theme. + */ +export declare function getCategoricalSchemeNames(): string[]; + +/** + * Returns the color array for a named scheme, or null if not found. + * The host app provides the implementation via window.superset.theme. + */ +export declare function getSchemeColors(schemeName: string): string[] | null; diff --git a/superset-frontend/packages/superset-core/src/theme/index.tsx b/superset-frontend/packages/superset-core/src/theme/index.tsx index c1dca943596..1b16556ade2 100644 --- a/superset-frontend/packages/superset-core/src/theme/index.tsx +++ b/superset-frontend/packages/superset-core/src/theme/index.tsx @@ -88,3 +88,17 @@ export type { // Export theme utility functions export * from './utils/themeUtils'; export * from './utils'; + +// Color scheme API — types, enum, and declare-function bridge for extensions. +// The host app provides the runtime implementations on window.superset.theme. +export type { + ColorSchemeConfig, + SequentialSchemeConfig, + CategoricalScheme, + CategoricalSchemeRegistryLike, +} from './colors'; +export { + ColorSchemeGroup, + getCategoricalSchemeNames, + getSchemeColors, +} from './colors'; diff --git a/superset-frontend/packages/superset-ui-core/test/color/colorSchemes.test.ts b/superset-frontend/packages/superset-ui-core/test/color/colorSchemes.test.ts index 608fccc5113..2f9c406694f 100644 --- a/superset-frontend/packages/superset-ui-core/test/color/colorSchemes.test.ts +++ b/superset-frontend/packages/superset-ui-core/test/color/colorSchemes.test.ts @@ -24,6 +24,12 @@ import { CategoricalD3, CategoricalGoogle, CategoricalLyft, + CategoricalModernSunset, + CategoricalColorsOfRainbow, + CategoricalBlueToGreen, + CategoricalRedToYellow, + CategoricalWavesOfBlue, + CategoricalPresetSuperset, SequentialCommon, SequentialD3, CategoricalScheme, @@ -41,22 +47,59 @@ describe('Color Schemes', () => { CategoricalLyft, CategoricalSuperset, CategoricalPreset, + CategoricalModernSunset, + CategoricalColorsOfRainbow, + CategoricalBlueToGreen, + CategoricalRedToYellow, + CategoricalWavesOfBlue, + CategoricalPresetSuperset, ].forEach(group => { expect(group).toBeInstanceOf(Array); + expect(group.length).toBeGreaterThan(0); group.forEach(scheme => expect(scheme).toBeInstanceOf(CategoricalScheme), ); }); }); + + test('each scheme has a non-empty id and at least one color', () => { + [ + ...CategoricalAirbnb, + ...CategoricalD3, + ...CategoricalEcharts, + ...CategoricalGoogle, + ...CategoricalLyft, + ...CategoricalPreset, + ...CategoricalSuperset, + ...CategoricalPresetSuperset, + ...CategoricalModernSunset, + ...CategoricalColorsOfRainbow, + ...CategoricalBlueToGreen, + ...CategoricalRedToYellow, + ...CategoricalWavesOfBlue, + ].forEach(scheme => { + expect(scheme.id).toBeTruthy(); + expect(scheme.colors.length).toBeGreaterThan(0); + }); + }); }); + describe('sequential', () => { test('returns an array of SequentialScheme', () => { [SequentialCommon, SequentialD3].forEach(group => { expect(group).toBeInstanceOf(Array); + expect(group.length).toBeGreaterThan(0); group.forEach(scheme => expect(scheme).toBeInstanceOf(SequentialScheme), ); }); }); + + test('each scheme has a non-empty id and at least two colors', () => { + [...SequentialCommon, ...SequentialD3].forEach(scheme => { + expect(scheme.id).toBeTruthy(); + expect(scheme.colors.length).toBeGreaterThanOrEqual(2); + }); + }); }); }); diff --git a/superset-frontend/src/core/index.ts b/superset-frontend/src/core/index.ts index ce5b7f4d505..0a341c7aca2 100644 --- a/superset-frontend/src/core/index.ts +++ b/superset-frontend/src/core/index.ts @@ -35,5 +35,6 @@ export * from './menus'; export * from './models'; export * from './navigation'; export * from './sqlLab'; +export * from './theme'; export * from './utils'; export * from './views'; diff --git a/superset-frontend/src/core/theme/colors.ts b/superset-frontend/src/core/theme/colors.ts new file mode 100644 index 00000000000..4d600e9a508 --- /dev/null +++ b/superset-frontend/src/core/theme/colors.ts @@ -0,0 +1,45 @@ +/** + * 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 { theme as themeApi } from '@apache-superset/core'; +import { getCategoricalSchemeRegistry } from '@superset-ui/core'; +import type { CategoricalSchemeRegistryLike } from '@apache-superset/core/theme'; + +const getCategoricalSchemeNames: typeof themeApi.getCategoricalSchemeNames = + () => { + const registry = + getCategoricalSchemeRegistry() as CategoricalSchemeRegistryLike | null; + return (registry?.keys() ?? []).sort(); + }; + +const getSchemeColors: typeof themeApi.getSchemeColors = schemeName => { + const registry = + getCategoricalSchemeRegistry() as CategoricalSchemeRegistryLike | null; + return registry?.get(schemeName)?.colors ?? null; +}; + +/** + * Host implementation of the @apache-superset/core/theme color API. + * Spreads the contract namespace (types, enum, styling helpers) and supplies + * the runtime implementations for the declare-only registry bridge functions. + */ +export const theme: typeof themeApi = { + ...themeApi, + getCategoricalSchemeNames, + getSchemeColors, +}; diff --git a/superset-frontend/src/core/theme/index.ts b/superset-frontend/src/core/theme/index.ts new file mode 100644 index 00000000000..948992898ec --- /dev/null +++ b/superset-frontend/src/core/theme/index.ts @@ -0,0 +1,19 @@ +/** + * 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. + */ +export * from './colors'; diff --git a/superset-frontend/src/extensions/ExtensionsStartup.tsx b/superset-frontend/src/extensions/ExtensionsStartup.tsx index dd639ec1b18..09c1e227818 100644 --- a/superset-frontend/src/extensions/ExtensionsStartup.tsx +++ b/superset-frontend/src/extensions/ExtensionsStartup.tsx @@ -31,6 +31,7 @@ import { navigation, useNavigationTracker, sqlLab, + theme, views, } from 'src/core'; import { useSelector } from 'react-redux'; @@ -64,6 +65,7 @@ const ExtensionsStartup: React.FC<{ children?: React.ReactNode }> = ({ menus, navigation, sqlLab, + theme, views, }; diff --git a/superset-frontend/src/extensions/Namespaces.ts b/superset-frontend/src/extensions/Namespaces.ts index 0e7f6a3063b..8af48e93bfc 100644 --- a/superset-frontend/src/extensions/Namespaces.ts +++ b/superset-frontend/src/extensions/Namespaces.ts @@ -36,6 +36,7 @@ import type { menus, navigation, sqlLab, + theme, views, } from 'src/core'; @@ -50,6 +51,7 @@ export interface Namespaces { menus: typeof menus; navigation: typeof navigation; sqlLab: typeof sqlLab; + theme: typeof theme; views: typeof views; }