bui-themer: use backstage palette + refine conversion and preview
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
CardBody,
|
||||
@@ -26,6 +27,8 @@ import {
|
||||
Select,
|
||||
Text,
|
||||
TextField,
|
||||
TagGroup,
|
||||
Tag,
|
||||
} from '@backstage/ui';
|
||||
|
||||
interface IsolatedPreviewProps {
|
||||
@@ -50,26 +53,28 @@ export function BuiThemePreview({ mode, styleObject }: IsolatedPreviewProps) {
|
||||
}}
|
||||
>
|
||||
<Flex direction="column" gap="4">
|
||||
<Card>
|
||||
<CardHeader>Theme Preview</CardHeader>
|
||||
<CardBody>
|
||||
<Text
|
||||
variant="body-small"
|
||||
style={{ color: 'var(--bui-fg-secondary)' }}
|
||||
>
|
||||
This preview shows how your theme will look with various Backstage
|
||||
UI components
|
||||
</Text>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>Button Variants</CardHeader>
|
||||
<CardBody>
|
||||
<Flex gap="3">
|
||||
<Button variant="primary">Primary</Button>
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
<Button variant="tertiary">Tertiary</Button>
|
||||
<Flex direction="column" gap="2">
|
||||
<Button variant="primary">Primary</Button>
|
||||
<Button isDisabled variant="primary">
|
||||
Disabled
|
||||
</Button>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="2">
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
<Button isDisabled variant="secondary">
|
||||
Disabled
|
||||
</Button>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="2">
|
||||
<Button variant="tertiary">Tertiary</Button>
|
||||
<Button isDisabled variant="tertiary">
|
||||
Disabled
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</CardBody>
|
||||
</Card>
|
||||
@@ -98,42 +103,64 @@ export function BuiThemePreview({ mode, styleObject }: IsolatedPreviewProps) {
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>Surface Variations</CardHeader>
|
||||
<CardHeader>Tag Variants</CardHeader>
|
||||
<CardBody>
|
||||
<Flex gap="3">
|
||||
<Card
|
||||
style={{
|
||||
backgroundColor: 'var(--bui-bg-surface-1)',
|
||||
color: 'var(--bui-fg-primary)',
|
||||
padding: '12px',
|
||||
minWidth: '120px',
|
||||
}}
|
||||
>
|
||||
<Text variant="body-small">Surface 1</Text>
|
||||
</Card>
|
||||
<Card
|
||||
style={{
|
||||
backgroundColor: 'var(--bui-bg-surface-2)',
|
||||
color: 'var(--bui-fg-primary)',
|
||||
padding: '12px',
|
||||
minWidth: '120px',
|
||||
}}
|
||||
>
|
||||
<Text variant="body-small">Surface 2</Text>
|
||||
</Card>
|
||||
<Card
|
||||
style={{
|
||||
backgroundColor: 'var(--bui-bg-solid)',
|
||||
color: 'var(--bui-fg-solid)',
|
||||
padding: '12px',
|
||||
minWidth: '120px',
|
||||
}}
|
||||
>
|
||||
<Text variant="body-small">Solid</Text>
|
||||
</Card>
|
||||
<TagGroup>
|
||||
<Tag>Default</Tag>
|
||||
<Tag
|
||||
style={{
|
||||
backgroundColor: 'var(--bui-bg-danger)',
|
||||
color: 'var(--bui-fg-danger)',
|
||||
border: `1px solid var(--bui-border-danger)`,
|
||||
}}
|
||||
>
|
||||
Danger
|
||||
</Tag>
|
||||
<Tag
|
||||
style={{
|
||||
backgroundColor: 'var(--bui-bg-warning)',
|
||||
color: 'var(--bui-fg-warning)',
|
||||
border: `1px solid var(--bui-border-warning)`,
|
||||
}}
|
||||
>
|
||||
Warning
|
||||
</Tag>
|
||||
<Tag
|
||||
style={{
|
||||
backgroundColor: 'var(--bui-bg-success)',
|
||||
color: 'var(--bui-fg-success)',
|
||||
border: `1px solid var(--bui-border-success)`,
|
||||
}}
|
||||
>
|
||||
Success
|
||||
</Tag>
|
||||
</TagGroup>
|
||||
</Flex>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>Text Variants</CardHeader>
|
||||
<CardBody>
|
||||
<Box
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr 1fr',
|
||||
gap: 'var(--bui-space-3)',
|
||||
}}
|
||||
>
|
||||
<Text variant="title-x-small">Title X Small</Text>
|
||||
<Text variant="body-x-small">Body X Small</Text>
|
||||
<Text variant="title-small">Title Small</Text>
|
||||
<Text variant="body-small">Body Small</Text>
|
||||
<Text variant="title-medium">Title Medium</Text>
|
||||
<Text variant="body-medium">Body Medium</Text>
|
||||
<Text variant="title-large">Title Large</Text>
|
||||
<Text variant="body-large">Body Large</Text>
|
||||
</Box>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -56,11 +56,13 @@ describe('convertMuiToBuiTheme', () => {
|
||||
expect(result.css).toContain('--bui-font-regular: Roboto, sans-serif;');
|
||||
// Spacing is skipped for default 8px
|
||||
expect(result.css).not.toContain('--bui-space:');
|
||||
expect(result.css).toContain('--bui-radius-3: 4px;');
|
||||
expect(result.css).toContain('--bui-bg: #f5f5f5;');
|
||||
// Border radius tokens are only generated when radius is 0
|
||||
expect(result.css).not.toContain('--bui-radius-3:');
|
||||
// Background default maps to surface-2
|
||||
expect(result.css).toContain('--bui-bg-surface-2: #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;');
|
||||
// Secondary may not be present without Backstage additions, so don't assert it
|
||||
expect(result.css).toContain('--bui-bg-solid: #1976d2;');
|
||||
|
||||
// Test style object
|
||||
@@ -94,10 +96,10 @@ describe('convertMuiToBuiTheme', () => {
|
||||
const result = convertMuiToBuiTheme(theme);
|
||||
|
||||
expect(result.css).toContain("[data-theme-mode='dark'] {");
|
||||
expect(result.css).toContain('--bui-bg: #121212;');
|
||||
// Background default maps to surface-2 in dark mode as well
|
||||
expect(result.css).toContain('--bui-bg-surface-2: #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');
|
||||
@@ -177,9 +179,16 @@ describe('convertMuiToBuiTheme', () => {
|
||||
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;');
|
||||
// Foreground danger/warning/success may be hex or rgb depending on theme
|
||||
expect(result.css).toMatch(
|
||||
/--bui-fg-danger:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i,
|
||||
);
|
||||
expect(result.css).toMatch(
|
||||
/--bui-fg-warning:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i,
|
||||
);
|
||||
expect(result.css).toMatch(
|
||||
/--bui-fg-success:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i,
|
||||
);
|
||||
});
|
||||
|
||||
it('should generate border colors correctly', () => {
|
||||
@@ -200,7 +209,7 @@ describe('convertMuiToBuiTheme', () => {
|
||||
|
||||
const result = convertMuiToBuiTheme(theme);
|
||||
|
||||
// Base border colors are no longer generated
|
||||
// Base border colors are included when border/divider exist; specific statuses always present
|
||||
expect(result.css).toContain('--bui-border-danger: #f44336;');
|
||||
expect(result.css).toContain('--bui-border-warning: #ff9800;');
|
||||
expect(result.css).toContain('--bui-border-success: #4caf50;');
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { Theme as Mui5Theme } from '@mui/material/styles';
|
||||
import { BackstagePaletteAdditions } from '@backstage/theme';
|
||||
import { blend, alpha } from '@mui/system/colorManipulator';
|
||||
|
||||
export interface ConvertMuiToBuiThemeResult {
|
||||
@@ -61,48 +62,25 @@ function generateBuiVariables(theme: Mui5Theme): Record<string, string> {
|
||||
theme.typography.fontWeightBold || 600,
|
||||
);
|
||||
|
||||
// Font sizes - map MUI typography scale to BUI scale
|
||||
const fontSizeMap = {
|
||||
h1: '--bui-font-size-10',
|
||||
h2: '--bui-font-size-8',
|
||||
h3: '--bui-font-size-7',
|
||||
h4: '--bui-font-size-6',
|
||||
h5: '--bui-font-size-5',
|
||||
h6: '--bui-font-size-4',
|
||||
body1: '--bui-font-size-4',
|
||||
body2: '--bui-font-size-3',
|
||||
caption: '--bui-font-size-2',
|
||||
overline: '--bui-font-size-1',
|
||||
};
|
||||
|
||||
Object.entries(fontSizeMap).forEach(([muiKey, buiVar]) => {
|
||||
const typographyVariant =
|
||||
theme.typography[muiKey as keyof typeof theme.typography];
|
||||
const fontSize =
|
||||
typeof typographyVariant === 'object' && typographyVariant?.fontSize
|
||||
? typographyVariant.fontSize
|
||||
: undefined;
|
||||
if (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') {
|
||||
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;
|
||||
styleObject['--bui-radius-3'] = radiusValue;
|
||||
// Border radius, only translate a 0 radius
|
||||
if (theme.shape.borderRadius === 0) {
|
||||
styleObject['--bui-radius-1'] = '0';
|
||||
styleObject['--bui-radius-2'] = '0';
|
||||
styleObject['--bui-radius-3'] = '0';
|
||||
styleObject['--bui-radius-4'] = '0';
|
||||
styleObject['--bui-radius-5'] = '0';
|
||||
styleObject['--bui-radius-6'] = '0';
|
||||
}
|
||||
|
||||
// Colors - map MUI palette to BUI color tokens
|
||||
const palette = theme.palette;
|
||||
const palette = theme.palette as typeof theme.palette &
|
||||
Partial<BackstagePaletteAdditions>;
|
||||
|
||||
// Base colors
|
||||
if (palette.common?.black) {
|
||||
@@ -112,13 +90,27 @@ function generateBuiVariables(theme: Mui5Theme): Record<string, string> {
|
||||
styleObject['--bui-white'] = palette.common.white;
|
||||
}
|
||||
|
||||
// Background colors
|
||||
if (palette.background?.default) {
|
||||
styleObject['--bui-bg'] = palette.background.default;
|
||||
}
|
||||
// Generate foreground colors
|
||||
Object.entries({
|
||||
primary: palette.text.primary,
|
||||
secondary: palette.textSubtle,
|
||||
link: palette.link ?? palette.primary.main,
|
||||
'link-hover': palette.linkHover ?? palette.primary.dark,
|
||||
disabled: palette.text.disabled,
|
||||
solid: palette.primary.contrastText,
|
||||
'solid-disabled': palette.text.disabled,
|
||||
tint: palette.textSubtle,
|
||||
'tint-disabled': palette.textVerySubtle,
|
||||
danger: palette.error.dark,
|
||||
warning: palette.warning.dark,
|
||||
success: palette.success.dark,
|
||||
}).forEach(([key, value]) => {
|
||||
styleObject[`--bui-fg-${key}`] = value;
|
||||
});
|
||||
|
||||
// Generate surface colors
|
||||
Object.entries({
|
||||
'': palette.background.default,
|
||||
'surface-1': palette.background.paper,
|
||||
'surface-2': palette.background.default,
|
||||
solid: palette.primary.main,
|
||||
@@ -136,30 +128,6 @@ function generateBuiVariables(theme: Mui5Theme): Record<string, string> {
|
||||
styleObject[`--bui-bg-${key}`] = value;
|
||||
});
|
||||
|
||||
// Foreground colors
|
||||
if (palette.text?.primary) {
|
||||
styleObject['--bui-fg-primary'] = palette.text.primary;
|
||||
}
|
||||
if (palette.text?.secondary) {
|
||||
styleObject['--bui-fg-secondary'] = palette.text.secondary;
|
||||
}
|
||||
|
||||
// Generate foreground colors
|
||||
Object.entries({
|
||||
link: palette.primary.main,
|
||||
'link-hover': palette.primary.dark,
|
||||
disabled: palette.text.disabled,
|
||||
solid: palette.text.primary,
|
||||
'solid-disabled': palette.action.disabled,
|
||||
tint: palette.primary.main,
|
||||
'tint-disabled': palette.action.disabled,
|
||||
danger: palette.error.main,
|
||||
warning: palette.warning.main,
|
||||
success: palette.success.main,
|
||||
}).forEach(([key, value]) => {
|
||||
styleObject[`--bui-fg-${key}`] = value;
|
||||
});
|
||||
|
||||
// Border colors
|
||||
Object.entries({
|
||||
danger: palette.error.main,
|
||||
@@ -169,9 +137,17 @@ function generateBuiVariables(theme: Mui5Theme): Record<string, string> {
|
||||
styleObject[`--bui-border${key ? `-${key}` : ''}`] = value;
|
||||
});
|
||||
|
||||
// Base border color if available
|
||||
if (palette.border || palette.divider) {
|
||||
styleObject['--bui-border'] = palette.border || palette.divider;
|
||||
styleObject['--bui-border-danger'] = palette.error.main;
|
||||
styleObject['--bui-border-warning'] = palette.warning.main;
|
||||
styleObject['--bui-border-success'] = palette.success.main;
|
||||
}
|
||||
|
||||
// Special colors
|
||||
if (palette.primary?.main) {
|
||||
styleObject['--bui-ring'] = palette.primary.main;
|
||||
if (palette.highlight || palette.primary?.main) {
|
||||
styleObject['--bui-ring'] = palette.highlight || palette.primary.main;
|
||||
}
|
||||
|
||||
return styleObject;
|
||||
|
||||
Reference in New Issue
Block a user