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