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 e18d5201d0173bb1a428ed0ea3e0b7542d5f069f Author: Maxime Beauchemin <[email protected]> AuthorDate: Tue Apr 1 00:25:50 2025 -0700 apply theme now works --- .../src/components/ThemeEditor/index.tsx | 141 ++++++++++++--------- 1 file changed, 79 insertions(+), 62 deletions(-) diff --git a/superset-frontend/src/components/ThemeEditor/index.tsx b/superset-frontend/src/components/ThemeEditor/index.tsx index cba265c10a..85708ab41f 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 } from 'react'; +import { useState, useEffect } from 'react'; import { Modal, Tooltip, @@ -39,18 +39,15 @@ import { mergeWith } from 'lodash'; const { Title } = Typography; const { Panel } = Collapse; -// Manually curated list of seed tokens (excluding animation-related ones) -const seedTokenCategories: Record< - string, - { token: string; type: 'color' | 'number' | 'string' }[] -> = { +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: 'colorBgBase', type: 'color' }, + { token: 'colorLink', type: 'color' }, ], Typography: [ { token: 'fontFamily', type: 'string' }, @@ -69,37 +66,64 @@ const seedTokenCategories: Record< }; export default function ThemeEditor() { - const initialTheme = themeObject.toSerializedConfig(); - const filteredKeys = Object.values(seedTokenCategories) - .flat() - .map(entry => entry.token); - const initialTokens = filteredKeys.reduce( - (acc, key) => { + const [tokens, setTokens] = useState({}); + const [jsonOverrides, setJsonOverrides] = useState('{}'); + const [isModalOpen, setIsModalOpen] = useState(false); + const [isDark, setIsDark] = useState(false); + const [isCompact, setIsCompact] = useState(false); + + useEffect(() => { + if (!isModalOpen) return; + + const initialTheme = themeObject.toSerializedConfig(); + const { algorithm } = initialTheme; + + const filteredKeys = Object.values(seedTokenCategories) + .flat() + .map(entry => entry.token); + + const initialTokens = filteredKeys.reduce((acc, key) => { acc[key] = themeObject.theme[key]; return acc; - }, - {} as Record<string, any>, - ); + }, {}); - const { algorithm } = initialTheme; - const [tokens, setTokens] = useState<Record<string, any>>(initialTokens); - const [jsonOverrides, setJsonOverrides] = useState<string>('{}'); - const [isModalOpen, setIsModalOpen] = useState(false); - const [isDark, setIsDark] = useState(algorithm?.includes('dark')); - const [isCompact, setIsCompact] = useState(algorithm?.includes('compact')); + setTokens(initialTokens); + setIsDark(algorithm?.includes('dark')); + setIsCompact(algorithm?.includes('compact')); + }, [isModalOpen]); + + const setToken = (key, value) => { + setTokens(prev => ({ ...prev, [key]: value })); + }; + + const updateColorBgBase = dark => { + setToken('colorBgBase', dark ? '#141414' : '#ffffff'); + }; const getMergedTheme = () => { + let overrides = {}; + try { + overrides = JSON.parse(jsonOverrides); + } catch (e) { + console.log('Invalid JSON in overrides:', e); + } + const algorithm = [ + isDark ? 'dark' : 'default', + ...(isCompact ? ['compact'] : []), + ]; + return { + tokens: { ...tokens, ...overrides }, + algorithm, + }; + }; + + const applyTheme = () => { try { - const overrides = JSON.parse(jsonOverrides); - const merged = mergeWith({}, tokens, overrides); - merged.algorithm = [ - isDark ? 'dark' : 'default', - ...(isCompact ? ['compact'] : []), - ]; - return merged; + themeObject.setConfig(getMergedTheme()); } catch (e) { - return tokens; + console.error('Failed to apply theme overrides:', e); } + setIsModalOpen(false); }; return ( @@ -117,7 +141,7 @@ export default function ThemeEditor() { title={t('Theme Editor')} open={isModalOpen} onCancel={() => setIsModalOpen(false)} - onOk={() => setIsModalOpen(false)} + onOk={applyTheme} width={800} centered > @@ -127,7 +151,10 @@ export default function ThemeEditor() { <Form.Item label={t('Dark Mode')}> <Switch checked={isDark} - onChange={setIsDark} + onChange={val => { + setIsDark(val); + updateColorBgBase(val); + }} checkedChildren={t('Dark')} unCheckedChildren={t('Light')} /> @@ -152,7 +179,7 @@ export default function ThemeEditor() { token={token} type={type} tokens={tokens} - setTokens={setTokens} + setToken={setToken} /> ))} </ThemeSection> @@ -186,13 +213,8 @@ export default function ThemeEditor() { </> ); } -function ThemeSection({ - children, - layout, -}: { - children: React.ReactNode; - layout?: 'horizontal' | 'default'; -}) { + +function ThemeSection({ children, layout }) { return layout === 'horizontal' ? ( <Form layout="horizontal"> <div style={{ display: 'flex', flexWrap: 'wrap', gap: 16 }}> @@ -207,37 +229,29 @@ function ThemeSection({ </Form> ); } +function ThemeToken({ token, type, tokens, setToken }) { + const value = tokens[token]; -function ThemeToken({ - token, - type, - tokens, - setTokens, -}: { - token: string; - type: 'color' | 'number' | 'string'; - tokens: Record<string, any>; - setTokens: React.Dispatch<React.SetStateAction<Record<string, any>>>; -}) { - const initialValue = tokens[token]; - const [value, setValue] = useState(initialValue); - - const handleChange = (val: any) => { + const handleChange = val => { const normalized = type === 'number' ? typeof val === 'number' && !Number.isNaN(val) ? val : null : val; - setValue(normalized); - setTokens(prev => ({ ...prev, [token]: normalized })); + setToken(token, normalized); }; const renderInput = () => { + const commonProps = { + id: `token-input-${token}`, + }; + switch (type) { case 'color': return ( <ColorPicker + {...commonProps} value={value} onChange={(_, hex) => handleChange(hex)} format="hex" @@ -246,6 +260,7 @@ function ThemeToken({ case 'number': return ( <InputNumber + {...commonProps} style={{ width: '100%' }} value={value} onChange={handleChange} @@ -254,7 +269,11 @@ function ThemeToken({ case 'string': default: return ( - <Input value={value} onChange={e => handleChange(e.target.value)} /> + <Input + {...commonProps} + value={value} + onChange={e => handleChange(e.target.value)} + /> ); } }; @@ -267,10 +286,8 @@ function ThemeToken({ : 160; return ( - <div style={{ width, marginBottom: 0 }}> - <label style={{ display: 'block', fontSize: 12, marginBottom: 4 }}> - {token} - </label> + <div style={{ width }}> + <div style={{ fontSize: 12, marginBottom: 4 }}>{token}</div> {renderInput()} </div> );
