bui-themer: refactor the conversion to style object first
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -49,10 +49,10 @@ interface ThemeContentProps {
|
||||
|
||||
interface IsolatedPreviewProps {
|
||||
mode: 'light' | 'dark';
|
||||
css: string;
|
||||
styleObject: Record<string, string>;
|
||||
}
|
||||
|
||||
function BuiThemePreview({ mode, css }: IsolatedPreviewProps) {
|
||||
function BuiThemePreview({ mode, styleObject }: IsolatedPreviewProps) {
|
||||
return (
|
||||
<div
|
||||
// Setting the theme mode ensures that the correct defaults are picked up from the BUI theme
|
||||
@@ -60,16 +60,7 @@ function BuiThemePreview({ mode, css }: IsolatedPreviewProps) {
|
||||
style={{
|
||||
// Apply the generated CSS variables directly to this container
|
||||
// This creates a scoped context where the variables take precedence
|
||||
...Object.fromEntries(
|
||||
css
|
||||
.split('\n')
|
||||
.filter(line => 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<string>('');
|
||||
const [activeTab, setActiveTab] = useState<string>('css');
|
||||
|
||||
const css = useMemo(() => {
|
||||
const { css, styleObject } = useMemo(() => {
|
||||
return convertMuiToBuiTheme(muiTheme);
|
||||
}, [muiTheme]);
|
||||
|
||||
@@ -258,7 +249,7 @@ function ThemeContent({
|
||||
|
||||
<TabPanel id="preview">
|
||||
<Box>
|
||||
<BuiThemePreview mode={variant} css={generatedCss} />
|
||||
<BuiThemePreview mode={variant} styleObject={styleObject} />
|
||||
</Box>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
@@ -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;');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<string, string> {
|
||||
const styleObject: Record<string, string> = {};
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user