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>
         )}

Reply via email to