This is an automated email from the ASF dual-hosted git repository.

maximebeauchemin pushed a commit to branch template_less_preset_theme_editor
in repository https://gitbox.apache.org/repos/asf/superset.git

commit a2f59b6e48304f5cffdf9418c7f5840f4cc4da76
Author: Maxime Beauchemin <[email protected]>
AuthorDate: Tue Apr 1 09:26:09 2025 -0700

    help text and json error state
---
 .../src/components/ThemeEditor/index.tsx           | 237 +++++++++++++++------
 1 file changed, 175 insertions(+), 62 deletions(-)

diff --git a/superset-frontend/src/components/ThemeEditor/index.tsx 
b/superset-frontend/src/components/ThemeEditor/index.tsx
index 672fced5c3..a6ccf4df41 100644
--- a/superset-frontend/src/components/ThemeEditor/index.tsx
+++ b/superset-frontend/src/components/ThemeEditor/index.tsx
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useMemo } from 'react';
 import {
   Modal,
   Tooltip,
@@ -35,33 +35,133 @@ import { Icons } from 'src/components/Icons';
 import { JsonEditor } from 'src/components/AsyncAceEditor';
 import { themeObject, t } from '@superset-ui/core';
 import { mergeWith } from 'lodash';
+import tinycolor from 'tinycolor2';
+import InfoTooltip from '../InfoTooltip';
 
-const { Title } = Typography;
+const { Title, Text } = Typography;
 const { Panel } = Collapse;
 
 const seedTokenCategories = {
   Colors: [
-    { token: 'colorBgBase', type: 'color' },
-    { token: 'colorPrimary', type: 'color' },
-    { token: 'colorSuccess', type: 'color' },
-    { token: 'colorWarning', type: 'color' },
-    { token: 'colorError', type: 'color' },
-    { token: 'colorInfo', type: 'color' },
-    { token: 'colorLink', type: 'color' },
+    {
+      token: 'colorBgBase',
+      type: 'color',
+      help: t(
+        'Used to derive the base variable of the background color gradient. In 
v5, we ' +
+          'added a layer of background color derivation algorithm to produce 
map token of ' +
+          'background color. But PLEASE DO NOT USE this Seed Token directly in 
the code!',
+      ),
+    },
+    {
+      token: 'colorPrimary',
+      type: 'color',
+      help: t(
+        'Brand color is one of the most direct visual elements to reflect the 
' +
+          'characteristics and communication of the product. After you have 
selected ' +
+          'the brand color, we will automatically generate a complete color 
palette ' +
+          'and assign it effective design semantics.',
+      ),
+    },
+    {
+      token: 'colorSuccess',
+      type: 'color',
+      help: t(
+        'Used to represent the token sequence of operation success, such as 
Result, Progress ' +
+          'and other components will use these map tokens.',
+      ),
+    },
+    {
+      token: 'colorWarning',
+      type: 'color',
+      help: t(
+        'Used to represent the warning map token, such as Notification, Alert, 
etc. Alert or ' +
+          'Control component(like Input) will use these map tokens.',
+      ),
+    },
+    {
+      token: 'colorError',
+      type: 'color',
+      help: t(
+        'Used to represent the visual elements of the operation failure, such 
as the error ' +
+          'Button, error Result component, etc.',
+      ),
+    },
+    {
+      token: 'colorInfo',
+      type: 'color',
+      help: t(
+        'Used to represent the operation information of the Token sequence, 
such as Alert, Tag, Progress, and other components use these map tokens.',
+      ),
+    },
+    {
+      token: 'colorLink',
+      type: 'color',
+      help: t('Control the color of hyperlink.'),
+    },
   ],
   Typography: [
-    { token: 'fontFamily', type: 'string' },
-    { token: 'fontFamilyCode', type: 'string' },
-    { token: 'fontSize', type: 'number' },
+    {
+      token: 'fontFamily',
+      type: 'string',
+      help: t(
+        'The font family of Ant Design prioritizes the default interface font 
of the system, ' +
+          'and provides a set of alternative font libraries that are suitable 
for screen display ' +
+          'to maintain the readability and readability of the font under 
different platforms ' +
+          'and browsers, reflecting the friendly, stable and professional 
characteristics.',
+      ),
+    },
+    {
+      token: 'fontFamilyCode',
+      type: 'string',
+      help: t('Code font, used for code, pre and kbd elements in Typography'),
+    },
+    {
+      token: 'fontSize',
+      type: 'number',
+      help: t(
+        'The most widely used font size in the design system, from which the 
text gradient ' +
+          'will be derived.',
+      ),
+    },
     { token: 'fontWeightStrong', type: 'number' },
     { token: 'lineHeight', type: 'number' },
   ],
   Layout: [
-    { token: 'borderRadius', type: 'number' },
-    { token: 'sizeUnit', type: 'number' },
-    { token: 'controlHeight', type: 'number' },
-    { token: 'zIndexBase', type: 'number' },
-    { token: 'zIndexPopupBase', type: 'number' },
+    {
+      token: 'borderRadius',
+      type: 'number',
+      help: t('Border radius of base components'),
+    },
+    {
+      token: 'sizeUnit',
+      type: 'number',
+      help: t(
+        'The unit of size change, in Ant Design, our base unit is 4, which is 
more ' +
+          'fine-grained control of the size step',
+      ),
+    },
+    {
+      token: 'controlHeight',
+      type: 'number',
+      help: t(
+        'The height of the basic controls such as buttons and input boxes in 
Ant Design',
+      ),
+    },
+    {
+      token: 'zIndexBase',
+      type: 'number',
+      help: t(
+        'The base Z axis value of all components, which can be used to control 
the level ' +
+          'of some floating components based on the Z axis value, such as 
BackTop, Affix, etc.',
+      ),
+    },
+    {
+      token: 'zIndexPopupBase',
+      type: 'number',
+      help: t(
+        'Base zIndex of component like FloatButton, Affix which can be cover 
by large popup',
+      ),
+    },
   ],
 };
 
@@ -71,6 +171,7 @@ export default function ThemeEditor() {
   const [isModalOpen, setIsModalOpen] = useState(false);
   const [isDark, setIsDark] = useState(false);
   const [isCompact, setIsCompact] = useState(false);
+  const [isJsonParsable, setIsJsonParsable] = useState(true);
 
   useEffect(() => {
     if (!isModalOpen) return;
@@ -91,30 +192,37 @@ export default function ThemeEditor() {
     setIsDark(algorithm?.includes('dark'));
     setIsCompact(algorithm?.includes('compact'));
   }, [isModalOpen]);
+  useEffect(() => {
+    try {
+      JSON.parse(jsonOverrides);
+      setIsJsonParsable(true);
+    } catch {
+      setIsJsonParsable(false);
+    }
+  }, [jsonOverrides]);
 
   const setToken = (key, value) => {
     setTokens(prev => ({ ...prev, [key]: value }));
   };
 
   const updateColorBgBase = dark => {
-    setToken('colorBgBase', dark ? '#000' : '#fff');
+    setToken('colorBgBase', dark ? '#141414' : '#ffffff');
   };
 
   const getMergedTheme = () => {
     let overrides = {};
     try {
       overrides = JSON.parse(jsonOverrides);
-    } catch (e) {
-      console.error('Invalid JSON in overrides:', e);
+    } catch {
+      // fallback to empty overrides
     }
-    const algorithm = [
+
+    const merged = mergeWith({}, tokens, overrides);
+    merged.algorithm = [
       isDark ? 'dark' : 'default',
       ...(isCompact ? ['compact'] : []),
     ];
-    return {
-      token: { ...tokens, ...overrides },
-      algorithm,
-    };
+    return merged;
   };
 
   const applyTheme = () => {
@@ -124,9 +232,7 @@ export default function ThemeEditor() {
     } catch (e) {
       console.error('Failed to apply theme overrides:', e);
     }
-    setIsModalOpen(false);
   };
-
   return (
     <>
       <Tooltip title={t('Edit Theme')} placement="bottom">
@@ -143,12 +249,17 @@ export default function ThemeEditor() {
         open={isModalOpen}
         onCancel={() => setIsModalOpen(false)}
         onOk={applyTheme}
-        width={800}
+        width={600}
         centered
       >
         <Collapse defaultActiveKey={['algorithms', 'Colors']}>
           <Panel header={t('Algorithms')} key="algorithms">
-            <Form layout="horizontal">
+            <Form
+              layout="horizontal"
+              labelAlign="right"
+              labelCol={{ span: 8 }}
+              wrapperCol={{ span: 16 }}
+            >
               <Form.Item label={t('Dark Mode')}>
                 <Switch
                   checked={isDark}
@@ -171,24 +282,45 @@ export default function ThemeEditor() {
 
           {Object.entries(seedTokenCategories).map(([section, items]) => (
             <Panel header={t(section)} key={section}>
-              <ThemeSection
-                layout={section === 'Colors' ? 'horizontal' : 'default'}
+              <Form
+                layout="horizontal"
+                labelAlign="right"
+                labelCol={{ span: 8 }}
+                wrapperCol={{ span: 16 }}
               >
-                {items.map(({ token, type }) => (
-                  <ThemeToken
+                {items.map(({ token, type, help }) => (
+                  <Form.Item
                     key={token}
-                    token={token}
-                    type={type}
-                    tokens={tokens}
-                    setToken={setToken}
-                  />
+                    label={
+                      help ? (
+                        <>
+                          {token} <InfoTooltip tooltip={help} />
+                        </>
+                      ) : (
+                        token
+                      )
+                    }
+                    style={{ marginBottom: 16 }}
+                  >
+                    <ThemeToken
+                      token={token}
+                      type={type}
+                      tokens={tokens}
+                      setToken={setToken}
+                    />
+                  </Form.Item>
                 ))}
-              </ThemeSection>
+              </Form>
             </Panel>
           ))}
 
           <Panel header={t('Raw JSON Overrides')} key="overrides">
             <Card bodyStyle={{ padding: 0 }}>
+              {!isJsonParsable && (
+                <Text type="danger" style={{ padding: 12, display: 'block' }}>
+                  {t('Invalid JSON. Please correct it to preview the theme.')}
+                </Text>
+              )}
               <JsonEditor
                 showLoadingForImport
                 name="json_overrides"
@@ -215,15 +347,6 @@ export default function ThemeEditor() {
   );
 }
 
-function ThemeSection({ children, layout }) {
-  return (
-    <Form layout={layout}>
-      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 16 }}>
-        {children}
-      </div>
-    </Form>
-  );
-}
 function ThemeToken({ token, type, tokens, setToken }) {
   const value = tokens[token];
 
@@ -243,14 +366,16 @@ function ThemeToken({ token, type, tokens, setToken }) {
     };
 
     switch (type) {
-      case 'color':
+      case 'color': {
         return (
           <ColorPicker
             {...commonProps}
             value={value}
             onChange={(_, hex) => handleChange(hex)}
+            format="hex"
           />
         );
+      }
       case 'number':
         return (
           <InputNumber
@@ -272,17 +397,5 @@ function ThemeToken({ token, type, tokens, setToken }) {
     }
   };
 
-  const width =
-    type === 'string'
-      ? token === 'fontFamily' || token === 'fontFamilyCode'
-        ? 280
-        : 240
-      : 160;
-
-  return (
-    <div style={{ width }}>
-      <div style={{ fontSize: 12, marginBottom: 4 }}>{token}</div>
-      {renderInput()}
-    </div>
-  );
+  return renderInput();
 }

Reply via email to