diff --git a/plugins/bui-themer/package.json b/plugins/bui-themer/package.json index f6f5faffd4..ff97c92ecb 100644 --- a/plugins/bui-themer/package.json +++ b/plugins/bui-themer/package.json @@ -30,9 +30,11 @@ "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@backstage/theme": "workspace:^", + "@backstage/ui": "workspace:^", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.61", + "@mui/material": "^7.3.2", "react-use": "^17.2.4" }, "peerDependencies": { diff --git a/plugins/bui-themer/src/components/BuiThemerPage/BuiThemerPage.tsx b/plugins/bui-themer/src/components/BuiThemerPage/BuiThemerPage.tsx index 2da27cad36..b36c550081 100644 --- a/plugins/bui-themer/src/components/BuiThemerPage/BuiThemerPage.tsx +++ b/plugins/bui-themer/src/components/BuiThemerPage/BuiThemerPage.tsx @@ -14,43 +14,276 @@ * limitations under the License. */ -/* +import { useMemo, useState, useCallback, useEffect } from 'react'; +import { useApi } from '@backstage/core-plugin-api'; +import { appThemeApiRef } from '@backstage/core-plugin-api'; +import { useTheme } from '@mui/material/styles'; +import { + Box, + Button, + Card, + Container, + Flex, + Grid, + HeaderPage, + Text, + TextField, + Switch, +} from '@backstage/ui'; +import { + convertMuiToBuiTheme, + ConvertMuiToBuiThemeOptions, +} from './convertMuiToBuiTheme'; -IMPLEMENTATION PLAN: +interface ThemePreviewProps { + themeId: string; + themeTitle: string; + variant: 'light' | 'dark'; + Provider: React.ComponentType<{ children: React.ReactNode }>; +} -- Create a converter utility that maps a MUI v5 `Theme` to BUI CSS variables, - covering palette, typography, spacing, and shape tokens to match - `packages/ui/src/css/core.css`. -- Place it in an adjacent `convertMuiToBuiTheme.ts` file exporting - `convertMuiToBuiTheme(theme: Theme, options?): string`, returning - deterministic CSS text. -- Scope output blocks as: - - `:root { ... }` for light themes - - `[data-theme-mode='dark'] { ... }` for dark themes - Optionally prefix by theme id (e.g. `[data-app-theme='']`) so multiple - theme blocks can coexist without conflicts. -- Do not modify `packages/ui/src/css/core.css`; generate CSS for copy/download - only. -- Define a mapping from MUI fields to `--bui-*` tokens with sensible fallbacks - aligning with core defaults. Handle success/warning/error/background/text - surfaces, borders, and link/hover states. -- Add tests for light/dark and fallback behavior using MUI `createTheme` and - snapshot the generated CSS. -- Build the page to list all installed themes via `useApi(appThemeApiRef)`, - render a minimal child under each theme `Provider`, and read `useTheme()` to - get the runtime theme instance. Provide Copy and Download actions and an - optional live preview applying the generated variables. -- Performance: memoize generated CSS by theme id and regenerate when the - installed theme list changes. Observing `activeThemeId$()` is optional unless - previewing the active theme. +// Memoization cache for generated CSS +const cssCache = new Map(); -REQUIREMENTS: +interface ThemeContentProps { + themeId: string; + themeTitle: string; + variant: 'light' | 'dark'; +} -- No changes can be made outside the plugins/bui-themer/src directory -- Only components from the `@backstage/ui` package at `packages/ui` can be used +function ThemeContent({ themeId, themeTitle, variant }: ThemeContentProps) { + const [generatedCss, setGeneratedCss] = useState(''); + const [isPreviewMode, setIsPreviewMode] = useState(false); + const [includeThemeId, setIncludeThemeId] = useState(false); + const muiTheme = useTheme(); -*/ + const css = useMemo(() => { + // Create cache key based on theme properties and options + const cacheKey = `${themeId}-${includeThemeId}-${JSON.stringify({ + palette: muiTheme.palette, + typography: muiTheme.typography, + spacing: muiTheme.spacing, + shape: muiTheme.shape, + })}`; + + // Check cache first + if (cssCache.has(cacheKey)) { + return cssCache.get(cacheKey)!; + } + + const options: ConvertMuiToBuiThemeOptions = { + themeId, + includeThemeId, + }; + const result = convertMuiToBuiTheme(muiTheme, options); + + // Cache the result + cssCache.set(cacheKey, result); + + // Clean up old cache entries (keep only last 50) + if (cssCache.size > 50) { + const firstKey = cssCache.keys().next().value; + if (firstKey) { + cssCache.delete(firstKey); + } + } + + return result; + }, [muiTheme, themeId, includeThemeId]); + + useEffect(() => { + setGeneratedCss(css); + }, [css]); + + const handleCopy = useCallback(() => { + window.navigator.clipboard.writeText(generatedCss); + }, [generatedCss]); + + const handleDownload = useCallback(() => { + const blob = new Blob([generatedCss], { type: 'text/css' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `bui-theme-${themeId}.css`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, [generatedCss, themeId]); + + const handlePreviewToggle = useCallback(() => { + setIsPreviewMode(!isPreviewMode); + if (!isPreviewMode) { + // Apply the generated CSS to a style element + const styleId = `bui-theme-preview-${themeId}`; + let styleElement = document.getElementById(styleId); + if (!styleElement) { + styleElement = document.createElement('style'); + styleElement.id = styleId; + document.head.appendChild(styleElement); + } + styleElement.textContent = generatedCss; + } else { + // Remove the preview styles + const styleElement = document.getElementById( + `bui-theme-preview-${themeId}`, + ); + if (styleElement) { + styleElement.remove(); + } + } + }, [isPreviewMode, generatedCss, themeId]); + + return ( + + + + + {themeTitle} + + {variant} theme + + + + + + + + + + + + + + + + Generated CSS: + + + + + {isPreviewMode && ( + + + Live Preview: + + + Solid Button + + + Tint Button + + + Surface Card + + + )} + + + + ); +} + +function ThemePreview({ + themeId, + themeTitle, + variant, + Provider, +}: ThemePreviewProps) { + return ( + + + + ); +} export function BuiThemerPage() { - return
TODO
; + const appThemeApi = useApi(appThemeApiRef); + const installedThemes = appThemeApi.getInstalledThemes(); + + return ( + + + + + Convert MUI v5 themes to BUI CSS variables. Select a theme to generate + the corresponding BUI CSS variables that can be copied or downloaded. + + + + + {installedThemes.length === 0 ? ( + + + + No themes found. Please install some themes in your Backstage + app. + + + + ) : ( + + {installedThemes.map(theme => ( + + + + ))} + + )} + + + ); } diff --git a/plugins/bui-themer/src/components/BuiThemerPage/convertMuiToBuiTheme.test.ts b/plugins/bui-themer/src/components/BuiThemerPage/convertMuiToBuiTheme.test.ts new file mode 100644 index 0000000000..e45f48f3a0 --- /dev/null +++ b/plugins/bui-themer/src/components/BuiThemerPage/convertMuiToBuiTheme.test.ts @@ -0,0 +1,278 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createTheme } from '@mui/material/styles'; +import { convertMuiToBuiTheme } from './convertMuiToBuiTheme'; + +describe('convertMuiToBuiTheme', () => { + it('should generate CSS for light theme', () => { + const theme = createTheme({ + palette: { + mode: 'light', + primary: { + main: '#1976d2', + dark: '#115293', + }, + background: { + default: '#f5f5f5', + paper: '#ffffff', + }, + text: { + primary: '#000000', + secondary: '#666666', + }, + }, + typography: { + fontFamily: 'Roboto, sans-serif', + h1: { + fontSize: '2.5rem', + }, + body1: { + fontSize: '1rem', + }, + }, + spacing: 8, + shape: { + borderRadius: 4, + }, + }); + + const result = convertMuiToBuiTheme(theme); + + expect(result).toContain(':root {'); + expect(result).toContain('--bui-font-regular: Roboto, sans-serif;'); + expect(result).toContain('--bui-space: 8px;'); + 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;'); + }); + + it('should generate CSS for dark theme', () => { + const theme = createTheme({ + palette: { + mode: 'dark', + primary: { + main: '#90caf9', + dark: '#42a5f5', + }, + background: { + default: '#121212', + paper: '#1e1e1e', + }, + text: { + primary: '#ffffff', + secondary: '#b3b3b3', + }, + }, + }); + + 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;'); + }); + + it('should include theme ID scoping when requested', () => { + const theme = createTheme({ + palette: { + mode: 'light', + primary: { + main: '#1976d2', + }, + }, + }); + + const result = convertMuiToBuiTheme(theme, { + themeId: 'my-theme', + includeThemeId: true, + }); + + expect(result).toContain("[data-app-theme='my-theme'] :root {"); + }); + + it('should handle missing theme properties gracefully', () => { + const theme = createTheme({}); + + const result = convertMuiToBuiTheme(theme); + + expect(result).toContain(':root {'); + expect(result).toContain('--bui-font-weight-regular: 400;'); + expect(result).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;'); + }); + + it('should generate proper gray scale for light theme', () => { + const theme = createTheme({ + palette: { + mode: 'light', + primary: { + main: '#1976d2', + }, + }, + }); + + const result = convertMuiToBuiTheme(theme); + + expect(result).toContain('--bui-gray-1: #f8f8f8;'); + expect(result).toContain('--bui-gray-8: #595959;'); + }); + + it('should generate proper gray scale for dark theme', () => { + const theme = createTheme({ + palette: { + mode: 'dark', + primary: { + main: '#90caf9', + }, + }, + }); + + const result = convertMuiToBuiTheme(theme); + + expect(result).toContain('--bui-gray-1: #191919;'); + expect(result).toContain('--bui-gray-8: #b4b4b4;'); + }); + + it('should generate surface colors correctly', () => { + const theme = createTheme({ + palette: { + mode: 'light', + primary: { + main: '#1976d2', + dark: '#115293', + }, + error: { + main: '#f44336', + light: '#ffcdd2', + }, + warning: { + main: '#ff9800', + light: '#ffe0b2', + }, + success: { + main: '#4caf50', + light: '#c8e6c9', + }, + }, + }); + + const result = convertMuiToBuiTheme(theme); + + expect(result).toContain('--bui-bg-solid: #1976d2;'); + expect(result).toContain('--bui-bg-solid-hover: #115293;'); + expect(result).toContain('--bui-bg-danger: #ffcdd2;'); + expect(result).toContain('--bui-bg-warning: #ffe0b2;'); + expect(result).toContain('--bui-bg-success: #c8e6c9;'); + }); + + it('should generate foreground colors correctly', () => { + const theme = createTheme({ + palette: { + mode: 'light', + primary: { + main: '#1976d2', + dark: '#115293', + }, + error: { + main: '#f44336', + }, + warning: { + main: '#ff9800', + }, + success: { + main: '#4caf50', + }, + text: { + disabled: '#cccccc', + }, + }, + }); + + 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;'); + }); + + it('should generate border colors correctly', () => { + const theme = createTheme({ + palette: { + mode: 'light', + error: { + main: '#f44336', + }, + warning: { + main: '#ff9800', + }, + success: { + main: '#4caf50', + }, + }, + }); + + const result = convertMuiToBuiTheme(theme); + + expect(result).toContain('--bui-border: rgba(0, 0, 0, 0.1);'); + expect(result).toContain('--bui-border-hover: rgba(0, 0, 0, 0.2);'); + expect(result).toContain('--bui-border-danger: #f44336;'); + expect(result).toContain('--bui-border-warning: #ff9800;'); + expect(result).toContain('--bui-border-success: #4caf50;'); + }); + + it('should handle function-based spacing', () => { + const theme = createTheme({ + spacing: (factor: number) => `${factor * 4}px`, + }); + + const result = convertMuiToBuiTheme(theme); + + expect(result).toContain('--bui-space: 4px;'); + }); + + it('should handle string-based spacing', () => { + const theme = createTheme({ + spacing: '8px', + }); + + const result = convertMuiToBuiTheme(theme); + + expect(result).toContain('--bui-space: calc(1 * 8px);'); + }); + + it('should handle string-based border radius', () => { + const theme = createTheme({ + shape: { + borderRadius: '8px', + }, + }); + + const result = convertMuiToBuiTheme(theme); + + expect(result).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 new file mode 100644 index 0000000000..e1f1618760 --- /dev/null +++ b/plugins/bui-themer/src/components/BuiThemerPage/convertMuiToBuiTheme.ts @@ -0,0 +1,286 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Theme as Mui5Theme } from '@mui/material/styles'; + +export interface ConvertMuiToBuiThemeOptions { + /** + * Theme ID to use for scoping CSS variables + */ + themeId?: string; + /** + * Whether to include theme ID scoping in the CSS + */ + includeThemeId?: boolean; +} + +/** + * Converts a MUI v5 Theme to BUI CSS variables + * @param theme - The MUI v5 theme to convert + * @param options - Conversion options + * @returns CSS string with BUI variables + */ +export function convertMuiToBuiTheme( + theme: Mui5Theme, + options: ConvertMuiToBuiThemeOptions = {}, +): string { + const { themeId, includeThemeId = false } = options; + const isDark = theme.palette.mode === 'dark'; + + // Generate CSS variables based on theme + const variables = generateBuiVariables(theme); + + // Create CSS selector based on theme mode and ID + let selector = isDark ? "[data-theme-mode='dark']" : ':root'; + if (includeThemeId && themeId) { + selector = `[data-app-theme='${themeId}'] ${selector}`; + } + + return `${selector} {\n${variables}\n}`; +} + +/** + * Generates BUI CSS variables from MUI theme + */ +function generateBuiVariables(theme: Mui5Theme): string { + const variables: string[] = []; + + // Font families + if (theme.typography.fontFamily) { + variables.push(` --bui-font-regular: ${theme.typography.fontFamily};`); + } + + // Font weights + variables.push( + ` --bui-font-weight-regular: ${ + theme.typography.fontWeightRegular || 400 + };`, + ); + variables.push( + ` --bui-font-weight-bold: ${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) { + variables.push(` ${buiVar}: ${fontSize};`); + } + }); + + // Spacing - map MUI spacing to BUI spacing scale + if (theme.spacing) { + const spacingUnit = + typeof theme.spacing === 'function' ? theme.spacing(1) : theme.spacing; + const spacingValue = + typeof spacingUnit === 'number' ? `${spacingUnit}px` : spacingUnit; + variables.push(` --bui-space: ${spacingValue};`); + } + + // 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};`); + } + + // Colors - map MUI palette to BUI color tokens + const palette = theme.palette; + + // Base colors + if (palette.common?.black) { + variables.push(` --bui-black: ${palette.common.black};`); + } + if (palette.common?.white) { + variables.push(` --bui-white: ${palette.common.white};`); + } + + // Gray scale - generate from primary color or use defaults + const grayScale = generateGrayScale(palette.mode === 'dark'); + Object.entries(grayScale).forEach(([key, value]) => { + variables.push(` --bui-gray-${key}: ${value};`); + }); + + // 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};`); + } + + // Generate surface colors + const surfaceColors = generateSurfaceColors(palette); + Object.entries(surfaceColors).forEach(([key, value]) => { + variables.push(` --bui-bg-${key}: ${value};`); + }); + + // Foreground colors + if (palette.text?.primary) { + variables.push(` --bui-fg-primary: ${palette.text.primary};`); + } + if (palette.text?.secondary) { + variables.push(` --bui-fg-secondary: ${palette.text.secondary};`); + } + + // Generate foreground colors + const foregroundColors = generateForegroundColors(palette); + Object.entries(foregroundColors).forEach(([key, value]) => { + variables.push(` --bui-fg-${key}: ${value};`); + }); + + // Border colors + const borderColors = generateBorderColors(palette); + Object.entries(borderColors).forEach(([key, value]) => { + variables.push(` --bui-border${key ? `-${key}` : ''}: ${value};`); + }); + + // Special colors + if (palette.primary?.main) { + variables.push(` --bui-ring: ${palette.primary.main};`); + } + + return variables.join('\n'); +} + +/** + * Generates gray scale colors + */ +function generateGrayScale(isDark: boolean): Record { + if (isDark) { + return { + '1': '#191919', + '2': '#242424', + '3': '#373737', + '4': '#464646', + '5': '#575757', + '6': '#7b7b7b', + '7': '#9e9e9e', + '8': '#b4b4b4', + }; + } + + return { + '1': '#f8f8f8', + '2': '#ececec', + '3': '#d9d9d9', + '4': '#c1c1c1', + '5': '#9e9e9e', + '6': '#8c8c8c', + '7': '#757575', + '8': '#595959', + }; +} + +/** + * Generates surface background colors + */ +function generateSurfaceColors( + palette: Mui5Theme['palette'], +): Record { + const isDark = palette.mode === 'dark'; + + // Helper function to get tint colors with proper fallbacks + const getTintColor = (opacity: string) => { + if (palette.primary?.main) { + return `${palette.primary.main}${opacity}`; + } + return isDark + ? `rgba(156, 201, 255, ${opacity === '40' ? '0.12' : '0.16'})` + : `rgba(31, 84, 147, ${opacity === '40' ? '0.4' : '0.6'})`; + }; + + // Helper function to get fallback colors based on theme mode + const getFallbackColor = (lightColor: string, darkColor: string) => { + return isDark ? darkColor : lightColor; + }; + + return { + 'surface-2': getFallbackColor('#ececec', '#242424'), + solid: palette.primary?.main || getFallbackColor('#1f5493', '#9cc9ff'), + 'solid-hover': + palette.primary?.dark || getFallbackColor('#163a66', '#83b9fd'), + 'solid-pressed': + palette.primary?.dark || getFallbackColor('#0f2b4e', '#83b9fd'), + 'solid-disabled': getFallbackColor('#ebebeb', '#222222'), + tint: 'transparent', + 'tint-hover': getTintColor('40'), + 'tint-pressed': getTintColor('60'), + 'tint-disabled': getFallbackColor('#ebebeb', 'transparent'), + danger: palette.error?.light || getFallbackColor('#feebe7', '#3b1219'), + warning: palette.warning?.light || getFallbackColor('#fff2b2', '#302008'), + success: palette.success?.light || getFallbackColor('#e6f6eb', '#132d21'), + }; +} + +/** + * Generates foreground colors + */ +function generateForegroundColors( + palette: Mui5Theme['palette'], +): Record { + const isDark = palette.mode === 'dark'; + + return { + link: palette.primary?.main || (isDark ? '#9cc9ff' : '#1f5493'), + 'link-hover': palette.primary?.dark || (isDark ? '#7eb5f7' : '#1f2d5c'), + disabled: palette.text?.disabled || (isDark ? '#9e9e9e' : '#9e9e9e'), + solid: isDark ? '#101821' : '#ffffff', + 'solid-disabled': isDark ? '#575757' : '#9c9c9c', + tint: palette.primary?.main || (isDark ? '#9cc9ff' : '#1f5493'), + 'tint-disabled': isDark ? '#575757' : '#9e9e9e', + danger: palette.error?.main || '#e22b2b', + warning: palette.warning?.main || '#e36d05', + success: palette.success?.main || '#1db954', + }; +} + +/** + * Generates border colors + */ +function generateBorderColors( + palette: Mui5Theme['palette'], +): Record { + const isDark = palette.mode === 'dark'; + + return { + '': isDark ? 'rgba(255, 255, 255, 0.12)' : 'rgba(0, 0, 0, 0.1)', + hover: isDark ? 'rgba(255, 255, 255, 0.4)' : 'rgba(0, 0, 0, 0.2)', + pressed: isDark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.4)', + disabled: isDark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.1)', + danger: palette.error?.main || '#f87a7a', + warning: palette.warning?.main || '#e36d05', + success: palette.success?.main || '#53db83', + }; +} diff --git a/yarn.lock b/yarn.lock index f22d88ee20..41f7dc6d64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2435,6 +2435,13 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.28.3": + version: 7.28.4 + resolution: "@babel/runtime@npm:7.28.4" + checksum: 10/6c9a70452322ea80b3c9b2a412bcf60771819213a67576c8cec41e88a95bb7bf01fc983754cda35dc19603eef52df22203ccbf7777b9d6316932f9fb77c25163 + languageName: node + linkType: hard + "@babel/template@npm:^7.27.2, @babel/template@npm:^7.3.3": version: 7.27.2 resolution: "@babel/template@npm:7.27.2" @@ -4336,9 +4343,11 @@ __metadata: "@backstage/frontend-plugin-api": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" + "@backstage/ui": "workspace:^" "@material-ui/core": "npm:^4.9.13" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:^4.0.0-alpha.61" + "@mui/material": "npm:^7.3.2" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^14.0.0" "@testing-library/user-event": "npm:^14.0.0" @@ -8184,6 +8193,19 @@ __metadata: languageName: node linkType: hard +"@emotion/cache@npm:^11.14.0": + version: 11.14.0 + resolution: "@emotion/cache@npm:11.14.0" + dependencies: + "@emotion/memoize": "npm:^0.9.0" + "@emotion/sheet": "npm:^1.4.0" + "@emotion/utils": "npm:^1.4.2" + "@emotion/weak-memoize": "npm:^0.4.0" + stylis: "npm:4.2.0" + checksum: 10/52336b28a27b07dde8fcdfd80851cbd1487672bbd4db1e24cca1440c95d8a6a968c57b0453c2b7c88d9b432b717f99554dbecc05b5cdef27933299827e69fd8e + languageName: node + linkType: hard + "@emotion/hash@npm:^0.8.0": version: 0.8.0 resolution: "@emotion/hash@npm:0.8.0" @@ -11170,6 +11192,13 @@ __metadata: languageName: node linkType: hard +"@mui/core-downloads-tracker@npm:^7.3.2": + version: 7.3.2 + resolution: "@mui/core-downloads-tracker@npm:7.3.2" + checksum: 10/01c0976e5fb6a8f0b59ebd58019af5452d7761e97daf0f2ef121bc3323508edf2fea1b13ea1a54b0098d6ee5eef70041c9722fe43291b237b989b6813a24b71e + languageName: node + linkType: hard + "@mui/material@npm:^5.12.2": version: 5.16.14 resolution: "@mui/material@npm:5.16.14" @@ -11203,6 +11232,42 @@ __metadata: languageName: node linkType: hard +"@mui/material@npm:^7.3.2": + version: 7.3.2 + resolution: "@mui/material@npm:7.3.2" + dependencies: + "@babel/runtime": "npm:^7.28.3" + "@mui/core-downloads-tracker": "npm:^7.3.2" + "@mui/system": "npm:^7.3.2" + "@mui/types": "npm:^7.4.6" + "@mui/utils": "npm:^7.3.2" + "@popperjs/core": "npm:^2.11.8" + "@types/react-transition-group": "npm:^4.4.12" + clsx: "npm:^2.1.1" + csstype: "npm:^3.1.3" + prop-types: "npm:^15.8.1" + react-is: "npm:^19.1.1" + react-transition-group: "npm:^4.4.5" + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@mui/material-pigment-css": ^7.3.2 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@mui/material-pigment-css": + optional: true + "@types/react": + optional: true + checksum: 10/e9a5348060d6e6e6cb1ce0fe299a582fa9b37b56dff46b62f72d029d5078acefd56a258e559b29bb3d12e89d0a81fd4f1194ce07cc136bbf03fdb4878fd9b3c0 + languageName: node + linkType: hard + "@mui/private-theming@npm:^5.16.14": version: 5.16.14 resolution: "@mui/private-theming@npm:5.16.14" @@ -11220,6 +11285,23 @@ __metadata: languageName: node linkType: hard +"@mui/private-theming@npm:^7.3.2": + version: 7.3.2 + resolution: "@mui/private-theming@npm:7.3.2" + dependencies: + "@babel/runtime": "npm:^7.28.3" + "@mui/utils": "npm:^7.3.2" + prop-types: "npm:^15.8.1" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/5b733fc9e09e5b056af3e448899c665c6d1560ec56f593811fc88b9c5499ad42d8365551b182473880de6141a206da4f1f9e5b2ebcea12461dc6d8954e2a3ed7 + languageName: node + linkType: hard + "@mui/styled-engine@npm:^5.16.14": version: 5.16.14 resolution: "@mui/styled-engine@npm:5.16.14" @@ -11241,6 +11323,29 @@ __metadata: languageName: node linkType: hard +"@mui/styled-engine@npm:^7.3.2": + version: 7.3.2 + resolution: "@mui/styled-engine@npm:7.3.2" + dependencies: + "@babel/runtime": "npm:^7.28.3" + "@emotion/cache": "npm:^11.14.0" + "@emotion/serialize": "npm:^1.3.3" + "@emotion/sheet": "npm:^1.4.0" + csstype: "npm:^3.1.3" + prop-types: "npm:^15.8.1" + peerDependencies: + "@emotion/react": ^11.4.1 + "@emotion/styled": ^11.3.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + checksum: 10/f31a6080b950cc66edd19a5285d30e0557fc16ef07a5e9027d3479f18dda4a569a62976c0fa396a99b077c01000c4fced3c8ab3ea0f6bcd82d6db06b821503b2 + languageName: node + linkType: hard + "@mui/styles@npm:^5.14.18": version: 5.16.14 resolution: "@mui/styles@npm:5.16.14" @@ -11300,6 +11405,34 @@ __metadata: languageName: node linkType: hard +"@mui/system@npm:^7.3.2": + version: 7.3.2 + resolution: "@mui/system@npm:7.3.2" + dependencies: + "@babel/runtime": "npm:^7.28.3" + "@mui/private-theming": "npm:^7.3.2" + "@mui/styled-engine": "npm:^7.3.2" + "@mui/types": "npm:^7.4.6" + "@mui/utils": "npm:^7.3.2" + clsx: "npm:^2.1.1" + csstype: "npm:^3.1.3" + prop-types: "npm:^15.8.1" + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + checksum: 10/1e157027a693877a246c36e5c98dd988172daedb3430f95843a508fa3f8a707c394fe216078a17868d5d25690ea2f4c5af007dffff639436c36d982fb16e920a + languageName: node + linkType: hard + "@mui/types@npm:^7.2.15": version: 7.2.19 resolution: "@mui/types@npm:7.2.19" @@ -11312,6 +11445,20 @@ __metadata: languageName: node linkType: hard +"@mui/types@npm:^7.4.6": + version: 7.4.6 + resolution: "@mui/types@npm:7.4.6" + dependencies: + "@babel/runtime": "npm:^7.28.3" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/6263ef077332b75e01c1b347339f100f8743d8135efdee4a6c167ce70c69ca29d9face54c53395cbc1c8a8f5453fe8ef3f4457842cf0c05318f6ef6cde44456e + languageName: node + linkType: hard + "@mui/utils@npm:^5.14.15, @mui/utils@npm:^5.16.14": version: 5.16.14 resolution: "@mui/utils@npm:5.16.14" @@ -11332,6 +11479,26 @@ __metadata: languageName: node linkType: hard +"@mui/utils@npm:^7.3.2": + version: 7.3.2 + resolution: "@mui/utils@npm:7.3.2" + dependencies: + "@babel/runtime": "npm:^7.28.3" + "@mui/types": "npm:^7.4.6" + "@types/prop-types": "npm:^15.7.15" + clsx: "npm:^2.1.1" + prop-types: "npm:^15.8.1" + react-is: "npm:^19.1.1" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/defcebcb8e49cdbdf271e49066d78046d2d25d4c8177176a6c5d3368fe85e6a280c7be71dbf356b434863d96d69236956afe6a4a47bc394de6aeecfa8bc24fda + languageName: node + linkType: hard + "@n1ru4l/push-pull-async-iterable-iterator@npm:^3.1.0": version: 3.1.0 resolution: "@n1ru4l/push-pull-async-iterable-iterator@npm:3.1.0" @@ -20765,7 +20932,7 @@ __metadata: languageName: node linkType: hard -"@types/prop-types@npm:*, @types/prop-types@npm:^15.0.0, @types/prop-types@npm:^15.7.12, @types/prop-types@npm:^15.7.3": +"@types/prop-types@npm:*, @types/prop-types@npm:^15.0.0, @types/prop-types@npm:^15.7.12, @types/prop-types@npm:^15.7.15, @types/prop-types@npm:^15.7.3": version: 15.7.15 resolution: "@types/prop-types@npm:15.7.15" checksum: 10/31aa2f59b28f24da6fb4f1d70807dae2aedfce090ec63eaf9ea01727a9533ef6eaf017de5bff99fbccad7d1c9e644f52c6c2ba30869465dd22b1a7221c29f356 @@ -20879,6 +21046,15 @@ __metadata: languageName: node linkType: hard +"@types/react-transition-group@npm:^4.4.12": + version: 4.4.12 + resolution: "@types/react-transition-group@npm:4.4.12" + peerDependencies: + "@types/react": "*" + checksum: 10/ea14bc84f529a3887f9954b753843820ac8a3c49fcdfec7840657ecc6a8800aad98afdbe4b973eb96c7252286bde38476fcf64b1c09527354a9a9366e516d9a2 + languageName: node + linkType: hard + "@types/react-virtualized-auto-sizer@npm:^1.0.1": version: 1.0.4 resolution: "@types/react-virtualized-auto-sizer@npm:1.0.4" @@ -42751,6 +42927,13 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^19.1.1": + version: 19.1.1 + resolution: "react-is@npm:19.1.1" + checksum: 10/44e0937da1f0da1d5dbd4f01972870768ef207f8a49717f4491f5022454f34c956cb66be560aee5286387169853b0283a81e1f419b51dc62654c8710dc98065a + languageName: node + linkType: hard + "react-json-view@npm:^1.21.3": version: 1.21.3 resolution: "react-json-view@npm:1.21.3"