This is an automated email from the ASF dual-hosted git repository.
maximebeauchemin pushed a commit to branch template_less
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/template_less by this push:
new f5b680699f ThemeEditor
f5b680699f is described below
commit f5b680699f35ba0fbbdec9657485a16b221a005e
Author: Maxime Beauchemin <[email protected]>
AuthorDate: Thu Mar 27 12:07:18 2025 -0700
ThemeEditor
---
superset-frontend/.storybook/preview.jsx | 8 +-
.../packages/superset-ui-core/src/theme/Theme.tsx | 4 +
.../superset-ui-core/src/theme/exampleThemes.ts} | 16 ++-
.../packages/superset-ui-core/src/theme/index.tsx | 3 +-
.../src/components/Icons/AntdEnhanced.tsx | 1 +
.../src/components/ThemeEditor/index.tsx | 111 ++++++++++++++++-----
superset-frontend/src/features/home/RightMenu.tsx | 7 --
7 files changed, 107 insertions(+), 43 deletions(-)
diff --git a/superset-frontend/.storybook/preview.jsx
b/superset-frontend/.storybook/preview.jsx
index 40d0e68fa9..dfbe752eef 100644
--- a/superset-frontend/.storybook/preview.jsx
+++ b/superset-frontend/.storybook/preview.jsx
@@ -17,15 +17,13 @@
* under the License.
*/
import { withJsx } from '@mihkeleidast/storybook-addon-source';
-import { themeObject } from '@superset-ui/core';
+import { themeObject, css, exampleThemes } from '@superset-ui/core';
import { combineReducers, createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import reducerIndex from 'spec/helpers/reducerIndex';
import { GlobalStyles } from '../src/GlobalStyles';
import { Global } from '@emotion/react';
-import { css } from '@superset-ui/core';
-import { themes } from './themes';
import { App, Layout, Space, Content } from 'antd-v5';
import 'src/theme.ts';
@@ -62,14 +60,14 @@ export const globalTypes = {
defaultValue: 'superset',
toolbar: {
icon: 'paintbrush',
- items: Object.keys(themes),
+ items: Object.keys(exampleThemes),
},
},
};
const themeDecorator = (Story, context) => {
const themeKey = context.globals.theme || 'superset';
- themeObject.setConfig(themes[themeKey]);
+ themeObject.setConfig(exampleThemes[themeKey]);
return (
<themeObject.SupersetThemeProvider>
diff --git a/superset-frontend/packages/superset-ui-core/src/theme/Theme.tsx
b/superset-frontend/packages/superset-ui-core/src/theme/Theme.tsx
index a893b1e71a..5c9280a069 100644
--- a/superset-frontend/packages/superset-ui-core/src/theme/Theme.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/theme/Theme.tsx
@@ -220,6 +220,10 @@ export class Theme {
this.setConfig(newConfig);
}
+ json(): string {
+ return JSON.stringify(serializeThemeConfig(this.antdConfig), null, 2);
+ }
+
getColorVariants(color: string): ColorVariants {
const firstLetterCapped = color.charAt(0).toUpperCase() + color.slice(1);
if (color === 'default' || color === 'grayscale') {
diff --git a/superset-frontend/.storybook/themes.js
b/superset-frontend/packages/superset-ui-core/src/theme/exampleThemes.ts
similarity index 82%
rename from superset-frontend/.storybook/themes.js
rename to superset-frontend/packages/superset-ui-core/src/theme/exampleThemes.ts
index 1a7a48b1d9..9be9b9976e 100644
--- a/superset-frontend/.storybook/themes.js
+++ b/superset-frontend/packages/superset-ui-core/src/theme/exampleThemes.ts
@@ -16,14 +16,19 @@
* specific language governing permissions and limitations
* under the License.
*/
-export const themes = {
- superset: {
- algorithm: 'light',
- },
+/* eslint-disable theme-colors/no-literal-colors */
+import { SerializableThemeConfig } from './types';
+
+const exampleThemes: Record<string, SerializableThemeConfig> = {
+ superset: {},
supersetDark: {
token: {},
algorithm: 'dark',
},
+ supersetCompact: {
+ token: {},
+ algorithm: 'compact',
+ },
funky: {
token: {
colorPrimary: '#f759ab', // hot pink
@@ -34,7 +39,7 @@ export const themes = {
borderRadius: 12,
fontFamily: 'Comic Sans MS, cursive',
},
- algorithm: 'light',
+ algorithm: 'default',
},
funkyDark: {
token: {
@@ -49,3 +54,4 @@ export const themes = {
algorithm: 'dark',
},
};
+export default exampleThemes;
diff --git a/superset-frontend/packages/superset-ui-core/src/theme/index.tsx
b/superset-frontend/packages/superset-ui-core/src/theme/index.tsx
index 56fccafb55..4556b63594 100644
--- a/superset-frontend/packages/superset-ui-core/src/theme/index.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/theme/index.tsx
@@ -31,6 +31,7 @@ export {
withTheme,
} from '@emotion/react';
export { default as createEmotionCache } from '@emotion/cache';
+export { default as exampleThemes } from './exampleThemes';
declare module '@emotion/react' {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
@@ -51,7 +52,7 @@ export function useTheme() {
const styled = emotionStyled;
// launching in in dark mode for now while iterating
-const themeObject = Theme.fromConfig({});
+const themeObject = Theme.fromConfig({ algorithm: 'dark' });
const { theme } = themeObject;
const supersetTheme = theme;
diff --git a/superset-frontend/src/components/Icons/AntdEnhanced.tsx
b/superset-frontend/src/components/Icons/AntdEnhanced.tsx
index f2909531e9..6c2e338e62 100644
--- a/superset-frontend/src/components/Icons/AntdEnhanced.tsx
+++ b/superset-frontend/src/components/Icons/AntdEnhanced.tsx
@@ -124,6 +124,7 @@ const AntdIcons = {
AreaChartOutlined,
ArrowRightOutlined,
BarChartOutlined,
+ BgColorsOutlined,
BellOutlined,
BookOutlined,
CaretUpOutlined,
diff --git a/superset-frontend/src/components/ThemeEditor/index.tsx
b/superset-frontend/src/components/ThemeEditor/index.tsx
index c2ef6e7983..aa8978e008 100644
--- a/superset-frontend/src/components/ThemeEditor/index.tsx
+++ b/superset-frontend/src/components/ThemeEditor/index.tsx
@@ -1,26 +1,66 @@
-import { Modal, Button, Tooltip, App, Flex, Typography } from 'antd-v5';
-import { t, themeObject } from '@superset-ui/core';
+/**
+ * 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 { Modal, Tooltip, Flex, Select } from 'antd-v5';
+import Button from 'src/components/Button';
+import {
+ themeObject,
+ exampleThemes,
+ SerializableThemeConfig,
+ SupersetTheme,
+} from '@superset-ui/core';
import { useState } from 'react';
import Icons from 'src/components/Icons';
import { JsonEditor } from 'src/components/AsyncAceEditor';
-const ThemeEditor = ({
+interface ThemeEditorProps {
+ initialTheme?: SupersetTheme;
+ tooltipTitle?: string;
+ modalTitle?: string;
+}
+
+const ThemeEditor: React.FC<ThemeEditorProps> = ({
initialTheme = {},
tooltipTitle = 'Edit Theme',
modalTitle = 'Theme Editor',
}) => {
- const [isModalOpen, setIsModalOpen] = useState(false);
- const [jsonMetadata, setJsonMetadata] = useState('{}');
+ const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
+ const jsonTheme: string = themeObject.json();
+ const [jsonMetadata, setJsonMetadata] = useState<string>(jsonTheme);
+ const [selectedTheme, setSelectedTheme] = useState<string | null>(null);
+
+ // Get theme names for the Select options
+ const themeOptions: { value: string; label: string }[] = Object.keys(
+ exampleThemes,
+ ).map(key => ({
+ value: key,
+ label: key,
+ }));
- const handleOpenModal = () => {
+ const handleOpenModal = (): void => {
setIsModalOpen(true);
};
- const handleCancel = () => {
+ const handleCancel = (): void => {
setIsModalOpen(false);
};
- const handleSave = () => {
+ const handleSave = (): void => {
try {
const parsedTheme = JSON.parse(jsonMetadata);
console.log('Parsed theme:', parsedTheme);
@@ -32,24 +72,31 @@ const ThemeEditor = ({
}
};
- const handleEditorChange = newValue => {
- setThemeContent(newValue);
+ const handleThemeChange = (value: string): void => {
+ setSelectedTheme(value);
+ // When a theme is selected, update the JSON editor with the theme
definition
+ const themeData = exampleThemes[value] || ({} as SerializableThemeConfig);
+ setJsonMetadata(JSON.stringify(themeData, null, 2));
};
return (
<>
<Tooltip title={tooltipTitle} placement="bottom">
<Button
- type="text"
- icon={<Icons.EditOutlined />}
+ buttonStyle="link"
+ icon={
+ <Icons.BgColorsOutlined
+ iconSize="l"
+ iconColor={themeObject.theme.colorPrimary}
+ />
+ }
onClick={handleOpenModal}
aria-label="Edit theme"
size="large"
/>
</Tooltip>
-
<Modal
- title="Theme Editor"
+ title={modalTitle}
open={isModalOpen}
onCancel={handleCancel}
width={800}
@@ -61,23 +108,37 @@ const ThemeEditor = ({
}}
footer={
<Flex justify="end" gap="small">
- <Button onClick={handleCancel}>Cancel</Button>
+ <Button onClick={handleCancel} buttonStyle="secondary">
+ Cancel
+ </Button>
<Button type="primary" onClick={handleSave}>
Apply Theme
</Button>
</Flex>
}
>
- <JsonEditor
- showLoadingForImport
- name="json_metadata"
- value={jsonMetadata}
- onChange={setJsonMetadata}
- tabSize={2}
- width="100%"
- height="200px"
- wrapEnabled
- />
+ <Flex vertical gap="middle">
+ <div>
+ Select a theme template:
+ <Select
+ placeholder="Choose a theme"
+ style={{ width: '100%', marginTop: '8px' }}
+ options={themeOptions}
+ onChange={handleThemeChange}
+ value={selectedTheme}
+ />
+ </div>
+ <JsonEditor
+ showLoadingForImport
+ name="json_metadata"
+ value={jsonMetadata}
+ onChange={setJsonMetadata}
+ tabSize={2}
+ width="100%"
+ height="200px"
+ wrapEnabled
+ />
+ </Flex>
</Modal>
</>
);
diff --git a/superset-frontend/src/features/home/RightMenu.tsx
b/superset-frontend/src/features/home/RightMenu.tsx
index c3110c5ee6..d6dfb1270c 100644
--- a/superset-frontend/src/features/home/RightMenu.tsx
+++ b/superset-frontend/src/features/home/RightMenu.tsx
@@ -25,7 +25,6 @@ import { useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import { useQueryParams, BooleanParam } from 'use-query-params';
import { get, isEmpty } from 'lodash';
-import { Switch } from 'src/components/Switch';
import ThemeEditor from 'src/components/ThemeEditor';
import {
@@ -38,7 +37,6 @@ import {
useTheme,
isFeatureEnabled,
FeatureFlag,
- themeObject,
} from '@superset-ui/core';
import { Menu } from 'src/components/Menu';
import { Tooltip } from 'src/components/Tooltip';
@@ -494,11 +492,6 @@ const RightMenu = ({
)}
{(isFeatureEnabled(FeatureFlag.DarkThemeSwitch) || true) && (
<span>
- <Switch
- onChange={(checked: boolean) => {
- themeObject.toggleDarkMode(checked);
- }}
- />
<ThemeEditor />
</span>
)}