From 9768c9bbcaf68ec0462f3ca880a261a83f4a3fd9 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 13 Sep 2025 17:36:04 +0200 Subject: [PATCH] bui-themer: refactor the conversion to style object first Signed-off-by: Patrik Oldsberg --- .../BuiThemerPage/BuiThemerPage.tsx | 19 ++--- .../convertMuiToBuiTheme.test.ts | 84 +++++++++++-------- .../BuiThemerPage/convertMuiToBuiTheme.ts | 69 ++++++++------- 3 files changed, 91 insertions(+), 81 deletions(-) diff --git a/plugins/bui-themer/src/components/BuiThemerPage/BuiThemerPage.tsx b/plugins/bui-themer/src/components/BuiThemerPage/BuiThemerPage.tsx index bb07eae892..43cea38026 100644 --- a/plugins/bui-themer/src/components/BuiThemerPage/BuiThemerPage.tsx +++ b/plugins/bui-themer/src/components/BuiThemerPage/BuiThemerPage.tsx @@ -49,10 +49,10 @@ interface ThemeContentProps { interface IsolatedPreviewProps { mode: 'light' | 'dark'; - css: string; + styleObject: Record; } -function BuiThemePreview({ mode, css }: IsolatedPreviewProps) { +function BuiThemePreview({ mode, styleObject }: IsolatedPreviewProps) { return (
line.trim().startsWith('--bui-')) - .map(line => { - const [key, value] = line.trim().split(':'); - return [key?.trim(), value?.replace(';', '').trim()]; - }) - .filter(([key, value]) => key && value), - ), + ...styleObject, width: '100%', backgroundColor: 'var(--bui-bg-surface-2)', padding: 'var(--bui-space-3)', @@ -178,7 +169,7 @@ function ThemeContent({ const [generatedCss, setGeneratedCss] = useState(''); const [activeTab, setActiveTab] = useState('css'); - const css = useMemo(() => { + const { css, styleObject } = useMemo(() => { return convertMuiToBuiTheme(muiTheme); }, [muiTheme]); @@ -258,7 +249,7 @@ function ThemeContent({ - + diff --git a/plugins/bui-themer/src/components/BuiThemerPage/convertMuiToBuiTheme.test.ts b/plugins/bui-themer/src/components/BuiThemerPage/convertMuiToBuiTheme.test.ts index da87642e8f..60cb2830ce 100644 --- a/plugins/bui-themer/src/components/BuiThemerPage/convertMuiToBuiTheme.test.ts +++ b/plugins/bui-themer/src/components/BuiThemerPage/convertMuiToBuiTheme.test.ts @@ -52,16 +52,24 @@ describe('convertMuiToBuiTheme', () => { const result = convertMuiToBuiTheme(theme); - expect(result).toContain(':root {'); - expect(result).toContain('--bui-font-regular: Roboto, sans-serif;'); + expect(result.css).toContain(':root {'); + expect(result.css).toContain('--bui-font-regular: Roboto, sans-serif;'); // Spacing is skipped for default 8px - expect(result).not.toContain('--bui-space:'); - expect(result).toContain('--bui-radius-3: 4px;'); - expect(result).toContain('--bui-bg: #f5f5f5;'); - expect(result).toContain('--bui-bg-surface-1: #ffffff;'); - expect(result).toContain('--bui-fg-primary: #000000;'); - expect(result).toContain('--bui-fg-secondary: #666666;'); - expect(result).toContain('--bui-bg-solid: #1976d2;'); + expect(result.css).not.toContain('--bui-space:'); + expect(result.css).toContain('--bui-radius-3: 4px;'); + expect(result.css).toContain('--bui-bg: #f5f5f5;'); + expect(result.css).toContain('--bui-bg-surface-1: #ffffff;'); + expect(result.css).toContain('--bui-fg-primary: #000000;'); + expect(result.css).toContain('--bui-fg-secondary: #666666;'); + expect(result.css).toContain('--bui-bg-solid: #1976d2;'); + + // Test style object + expect(result.styleObject).toHaveProperty( + '--bui-font-regular', + 'Roboto, sans-serif', + ); + expect(result.styleObject).toHaveProperty('--bui-bg-solid', '#1976d2'); + expect(result.styleObject).toHaveProperty('--bui-fg-primary', '#000000'); }); it('should generate CSS for dark theme', () => { @@ -85,11 +93,15 @@ describe('convertMuiToBuiTheme', () => { const result = convertMuiToBuiTheme(theme); - expect(result).toContain("[data-theme-mode='dark'] {"); - expect(result).toContain('--bui-bg: #121212;'); - expect(result).toContain('--bui-bg-surface-1: #1e1e1e;'); - expect(result).toContain('--bui-fg-primary: #ffffff;'); - expect(result).toContain('--bui-fg-secondary: #b3b3b3;'); + expect(result.css).toContain("[data-theme-mode='dark'] {"); + expect(result.css).toContain('--bui-bg: #121212;'); + expect(result.css).toContain('--bui-bg-surface-1: #1e1e1e;'); + expect(result.css).toContain('--bui-fg-primary: #ffffff;'); + expect(result.css).toContain('--bui-fg-secondary: #b3b3b3;'); + + // Test style object + expect(result.styleObject).toHaveProperty('--bui-bg-surface-1', '#1e1e1e'); + expect(result.styleObject).toHaveProperty('--bui-fg-primary', '#ffffff'); }); it('should handle missing theme properties gracefully', () => { @@ -97,12 +109,12 @@ describe('convertMuiToBuiTheme', () => { const result = convertMuiToBuiTheme(theme); - expect(result).toContain(':root {'); - expect(result).toContain('--bui-font-weight-regular: 400;'); - expect(result).toContain('--bui-font-weight-bold: 700;'); + expect(result.css).toContain(':root {'); + expect(result.css).toContain('--bui-font-weight-regular: 400;'); + expect(result.css).toContain('--bui-font-weight-bold: 700;'); // Should have default values even when theme properties are missing - expect(result).toContain('--bui-black: #000;'); - expect(result).toContain('--bui-white: #fff;'); + expect(result.css).toContain('--bui-black: #000;'); + expect(result.css).toContain('--bui-white: #fff;'); }); it('should generate surface colors correctly', () => { @@ -130,11 +142,11 @@ describe('convertMuiToBuiTheme', () => { const result = convertMuiToBuiTheme(theme); - expect(result).toContain('--bui-bg-solid: #1976d2;'); - expect(result).toContain('--bui-bg-solid-hover: rgb(21, 100, 179);'); - expect(result).toContain('--bui-bg-danger: #ffcdd2;'); - expect(result).toContain('--bui-bg-warning: #ffe0b2;'); - expect(result).toContain('--bui-bg-success: #c8e6c9;'); + expect(result.css).toContain('--bui-bg-solid: #1976d2;'); + expect(result.css).toContain('--bui-bg-solid-hover: rgb(21, 100, 179);'); + expect(result.css).toContain('--bui-bg-danger: #ffcdd2;'); + expect(result.css).toContain('--bui-bg-warning: #ffe0b2;'); + expect(result.css).toContain('--bui-bg-success: #c8e6c9;'); }); it('should generate foreground colors correctly', () => { @@ -162,12 +174,12 @@ describe('convertMuiToBuiTheme', () => { const result = convertMuiToBuiTheme(theme); - expect(result).toContain('--bui-fg-link: #1976d2;'); - expect(result).toContain('--bui-fg-link-hover: #115293;'); - expect(result).toContain('--bui-fg-disabled: #cccccc;'); - expect(result).toContain('--bui-fg-danger: #f44336;'); - expect(result).toContain('--bui-fg-warning: #ff9800;'); - expect(result).toContain('--bui-fg-success: #4caf50;'); + expect(result.css).toContain('--bui-fg-link: #1976d2;'); + expect(result.css).toContain('--bui-fg-link-hover: #115293;'); + expect(result.css).toContain('--bui-fg-disabled: #cccccc;'); + expect(result.css).toContain('--bui-fg-danger: #f44336;'); + expect(result.css).toContain('--bui-fg-warning: #ff9800;'); + expect(result.css).toContain('--bui-fg-success: #4caf50;'); }); it('should generate border colors correctly', () => { @@ -189,9 +201,9 @@ describe('convertMuiToBuiTheme', () => { const result = convertMuiToBuiTheme(theme); // Base border colors are no longer generated - expect(result).toContain('--bui-border-danger: #f44336;'); - expect(result).toContain('--bui-border-warning: #ff9800;'); - expect(result).toContain('--bui-border-success: #4caf50;'); + expect(result.css).toContain('--bui-border-danger: #f44336;'); + expect(result.css).toContain('--bui-border-warning: #ff9800;'); + expect(result.css).toContain('--bui-border-success: #4caf50;'); }); it('should handle function-based spacing', () => { @@ -201,7 +213,7 @@ describe('convertMuiToBuiTheme', () => { const result = convertMuiToBuiTheme(theme); - expect(result).toContain('--bui-space: calc(4px * 0.5);'); + expect(result.css).toContain('--bui-space: calc(4px * 0.5);'); }); it('should handle string-based spacing', () => { @@ -211,7 +223,7 @@ describe('convertMuiToBuiTheme', () => { const result = convertMuiToBuiTheme(theme); - expect(result).toContain('--bui-space: calc(calc(1 * 8px) * 0.5);'); + expect(result.css).toContain('--bui-space: calc(calc(1 * 8px) * 0.5);'); }); it('should handle string-based border radius', () => { @@ -223,6 +235,6 @@ describe('convertMuiToBuiTheme', () => { const result = convertMuiToBuiTheme(theme); - expect(result).toContain('--bui-radius-3: 8px;'); + expect(result.css).toContain('--bui-radius-3: 8px;'); }); }); diff --git a/plugins/bui-themer/src/components/BuiThemerPage/convertMuiToBuiTheme.ts b/plugins/bui-themer/src/components/BuiThemerPage/convertMuiToBuiTheme.ts index 36a9322289..7981825866 100644 --- a/plugins/bui-themer/src/components/BuiThemerPage/convertMuiToBuiTheme.ts +++ b/plugins/bui-themer/src/components/BuiThemerPage/convertMuiToBuiTheme.ts @@ -17,39 +17,48 @@ import { Theme as Mui5Theme } from '@mui/material/styles'; import { blend, alpha } from '@mui/system/colorManipulator'; +export interface ConvertMuiToBuiThemeResult { + css: string; + styleObject: Record; +} + /** * Converts a MUI v5 Theme to BUI CSS variables * @param theme - The MUI v5 theme to convert - * @returns CSS string with BUI variables + * @returns Object containing CSS string and style object with BUI variables */ -export function convertMuiToBuiTheme(theme: Mui5Theme): string { - const variables = generateBuiVariables(theme); +export function convertMuiToBuiTheme( + theme: Mui5Theme, +): ConvertMuiToBuiThemeResult { + const styleObject = generateBuiVariables(theme); const selector = theme.palette.mode === 'dark' ? "[data-theme-mode='dark']" : ':root'; - return `${selector} {\n${variables}\n}`; + const css = `${selector} {\n${Object.entries(styleObject) + .map(([key, value]) => ` ${key}: ${value};`) + .join('\n')}\n}`; + + return { css, styleObject }; } /** * Generates BUI CSS variables from MUI theme */ -function generateBuiVariables(theme: Mui5Theme): string { - const variables: string[] = []; +function generateBuiVariables(theme: Mui5Theme): Record { + const styleObject: Record = {}; // Font families if (theme.typography.fontFamily) { - variables.push(` --bui-font-regular: ${theme.typography.fontFamily};`); + styleObject['--bui-font-regular'] = theme.typography.fontFamily; } // Font weights - variables.push( - ` --bui-font-weight-regular: ${ - theme.typography.fontWeightRegular || 400 - };`, + styleObject['--bui-font-weight-regular'] = String( + theme.typography.fontWeightRegular || 400, ); - variables.push( - ` --bui-font-weight-bold: ${theme.typography.fontWeightBold || 600};`, + styleObject['--bui-font-weight-bold'] = String( + theme.typography.fontWeightBold || 600, ); // Font sizes - map MUI typography scale to BUI scale @@ -74,21 +83,22 @@ function generateBuiVariables(theme: Mui5Theme): string { ? typographyVariant.fontSize : undefined; if (fontSize) { - variables.push(` ${buiVar}: ${fontSize};`); + styleObject[buiVar] = + typeof fontSize === 'string' ? fontSize : `${fontSize}px`; } }); const spacing = theme.spacing(1); // Skip spacing if the theme is using the default if (spacing !== '8px') { - variables.push(` --bui-space: calc(${spacing} * 0.5);`); + styleObject['--bui-space'] = `calc(${spacing} * 0.5)`; } // Border radius if (theme.shape?.borderRadius) { const radius = theme.shape.borderRadius; const radiusValue = typeof radius === 'number' ? `${radius}px` : radius; - variables.push(` --bui-radius-3: ${radiusValue};`); + styleObject['--bui-radius-3'] = radiusValue; } // Colors - map MUI palette to BUI color tokens @@ -96,24 +106,21 @@ function generateBuiVariables(theme: Mui5Theme): string { // Base colors if (palette.common?.black) { - variables.push(` --bui-black: ${palette.common.black};`); + styleObject['--bui-black'] = palette.common.black; } if (palette.common?.white) { - variables.push(` --bui-white: ${palette.common.white};`); + styleObject['--bui-white'] = palette.common.white; } // Background colors if (palette.background?.default) { - variables.push(` --bui-bg: ${palette.background.default};`); - } - if (palette.background?.paper) { - variables.push(` --bui-bg-surface-1: ${palette.background.paper};`); + styleObject['--bui-bg'] = palette.background.default; } // Generate surface colors Object.entries({ - 'surface-1': palette.background.default, - 'surface-2': palette.background.paper, + 'surface-1': palette.background.paper, + 'surface-2': palette.background.default, solid: palette.primary.main, 'solid-hover': blend(palette.primary.main, palette.primary.dark, 0.5), 'solid-pressed': palette.primary.dark, @@ -126,15 +133,15 @@ function generateBuiVariables(theme: Mui5Theme): string { warning: palette.warning.light, success: palette.success.light, }).forEach(([key, value]) => { - variables.push(` --bui-bg-${key}: ${value};`); + styleObject[`--bui-bg-${key}`] = value; }); // Foreground colors if (palette.text?.primary) { - variables.push(` --bui-fg-primary: ${palette.text.primary};`); + styleObject['--bui-fg-primary'] = palette.text.primary; } if (palette.text?.secondary) { - variables.push(` --bui-fg-secondary: ${palette.text.secondary};`); + styleObject['--bui-fg-secondary'] = palette.text.secondary; } // Generate foreground colors @@ -150,7 +157,7 @@ function generateBuiVariables(theme: Mui5Theme): string { warning: palette.warning.main, success: palette.success.main, }).forEach(([key, value]) => { - variables.push(` --bui-fg-${key}: ${value};`); + styleObject[`--bui-fg-${key}`] = value; }); // Border colors @@ -159,13 +166,13 @@ function generateBuiVariables(theme: Mui5Theme): string { warning: palette.warning.main, success: palette.success.main, }).forEach(([key, value]) => { - variables.push(` --bui-border${key ? `-${key}` : ''}: ${value};`); + styleObject[`--bui-border${key ? `-${key}` : ''}`] = value; }); // Special colors if (palette.primary?.main) { - variables.push(` --bui-ring: ${palette.primary.main};`); + styleObject['--bui-ring'] = palette.primary.main; } - return variables.join('\n'); + return styleObject; }