Use theme ID context instead default 'backstage' value

Signed-off-by: gaelgoth <gothuey.gael@gmail.com>
This commit is contained in:
gaelgoth
2025-10-21 09:59:23 +02:00
parent 43b9f8fd90
commit fa06f6bd61
9 changed files with 55 additions and 13 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/core-app-api': patch
'@backstage/theme': patch
---
Added support for the `data-theme-name` attribute to dynamically update based on the active theme. Previously, this attribute was statically set to `backstage` and did not reflect theme changes.
+1
View File
@@ -48,6 +48,7 @@
"dependencies": {
"@backstage/config": "workspace:^",
"@backstage/core-plugin-api": "workspace:^",
"@backstage/theme": "workspace:^",
"@backstage/types": "workspace:^",
"@backstage/version-bridge": "workspace:^",
"@types/prop-types": "^15.7.3",
@@ -16,6 +16,7 @@
import { useMemo, useEffect, useState, PropsWithChildren } from 'react';
import { useApi, appThemeApiRef, AppTheme } from '@backstage/core-plugin-api';
import { AppThemeIdContext } from '@backstage/theme';
import useObservable from 'react-use/esm/useObservable';
// This tries to find the most accurate match, but also falls back to less
@@ -88,5 +89,9 @@ export function AppThemeProvider({ children }: PropsWithChildren<{}>) {
throw new Error('App has no themes');
}
return <appTheme.Provider children={children} />;
return (
<AppThemeIdContext.Provider value={appTheme.id}>
<appTheme.Provider children={children} />
</AppThemeIdContext.Provider>
);
}
@@ -95,8 +95,8 @@ describe('createPublicSignInApp', () => {
expect(baseElement).toMatchInlineSnapshot(`
<body
data-theme-mode="light"
data-theme-name="backstage"
data-unified-theme-stack="[{"mode":"light","name":"backstage"}]"
data-theme-name="light"
data-unified-theme-stack="[{"mode":"light","name":"light"}]"
>
<div>
<form
@@ -20,11 +20,20 @@ import {
} from '@material-ui/core/styles';
import { useTheme as useV5Theme } from '@mui/material/styles';
import { makeStyles as makeV5Styles } from '@mui/styles';
import { render, screen } from '@testing-library/react';
import { UnifiedThemeProvider } from './UnifiedThemeProvider';
import { render, screen, waitFor } from '@testing-library/react';
import {
UnifiedThemeProvider,
AppThemeIdContext,
} from './UnifiedThemeProvider';
import { themes } from './themes';
describe('UnifiedThemeProvider', () => {
afterEach(() => {
document.body.removeAttribute('data-theme-name');
document.body.removeAttribute('data-theme-mode');
document.body.removeAttribute('data-unified-theme-stack');
});
it('provides a themes for v4 and v5 directly', () => {
function MyV4Component() {
const theme = useV4Theme();
@@ -81,4 +90,19 @@ describe('UnifiedThemeProvider', () => {
'rgb(158, 158, 158)',
);
});
it('applies theme attributes based on the provided theme id', async () => {
render(
<AppThemeIdContext.Provider value="aperture">
<UnifiedThemeProvider theme={themes.light}>
<span>theme</span>
</UnifiedThemeProvider>
</AppThemeIdContext.Provider>,
);
await waitFor(() =>
expect(document.body.getAttribute('data-theme-name')).toBe('aperture'),
);
expect(document.body.getAttribute('data-theme-mode')).toBe('light');
});
});
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ReactNode } from 'react';
import { ReactNode, createContext, useContext } from 'react';
import {
ThemeProvider,
StylesProvider,
@@ -29,6 +29,8 @@ import {
import { UnifiedTheme } from './types';
import { unstable_ClassNameGenerator as ClassNameGenerator } from '@mui/material/className';
export const AppThemeIdContext = createContext<string | undefined>(undefined);
/**
* Props for {@link UnifiedThemeProvider}.
*
@@ -71,10 +73,10 @@ export function UnifiedThemeProvider(
const v4Theme = theme.getTheme('v4') as Mui4Theme;
const v5Theme = theme.getTheme('v5') as Mui5Theme;
useApplyThemeAttributes(
v4Theme ? v4Theme.palette.type : v5Theme?.palette.mode,
'backstage',
);
const themeMode = v4Theme ? v4Theme.palette.type : v5Theme?.palette.mode;
const themeId = useContext(AppThemeIdContext) ?? 'backstage';
useApplyThemeAttributes(themeMode, themeId);
let result = children as JSX.Element;
+4 -1
View File
@@ -18,6 +18,9 @@ export { transformV5ComponentThemesToV4 } from './overrides';
export { createUnifiedTheme, createUnifiedThemeFromV4 } from './UnifiedTheme';
export type { UnifiedThemeOptions } from './UnifiedTheme';
export { themes } from './themes';
export { UnifiedThemeProvider } from './UnifiedThemeProvider';
export {
UnifiedThemeProvider,
AppThemeIdContext,
} from './UnifiedThemeProvider';
export type { UnifiedThemeProviderProps } from './UnifiedThemeProvider';
export type { UnifiedTheme, SupportedThemes, SupportedVersions } from './types';
@@ -98,8 +98,8 @@ describe('appModulePublicSignIn', () => {
expect(baseElement).toMatchInlineSnapshot(`
<body
data-theme-mode="light"
data-theme-name="backstage"
data-unified-theme-stack="[{"mode":"light","name":"backstage"}]"
data-theme-name="light"
data-unified-theme-stack="[{"mode":"light","name":"light"}]"
>
<div>
<form
+1
View File
@@ -3493,6 +3493,7 @@ __metadata:
"@backstage/config": "workspace:^"
"@backstage/core-plugin-api": "workspace:^"
"@backstage/test-utils": "workspace:^"
"@backstage/theme": "workspace:^"
"@backstage/types": "workspace:^"
"@backstage/version-bridge": "workspace:^"
"@testing-library/dom": "npm:^10.0.0"