theme: stack old theme attributes
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -96,6 +96,7 @@ describe('createPublicSignInApp', () => {
|
||||
<body
|
||||
data-theme-mode="light"
|
||||
data-theme-name="backstage"
|
||||
data-unified-theme-stack="[{"mode":"light","name":"backstage"}]"
|
||||
>
|
||||
<div>
|
||||
<form
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ReactNode, useEffect } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||
import {
|
||||
ThemeProvider,
|
||||
@@ -58,6 +58,8 @@ const generateV4ClassName = createGenerateClassName({
|
||||
productionPrefix: 'jss4-',
|
||||
});
|
||||
|
||||
import { useApplyThemeAttributes } from './useApplyThemeAttributes';
|
||||
|
||||
/**
|
||||
* Provides themes for all Material UI versions supported by the provided unified theme.
|
||||
*
|
||||
@@ -70,28 +72,11 @@ export function UnifiedThemeProvider(
|
||||
|
||||
const v4Theme = theme.getTheme('v4') as Mui4Theme;
|
||||
const v5Theme = theme.getTheme('v5') as Mui5Theme;
|
||||
const themeMode = v4Theme ? v4Theme.palette.type : v5Theme?.palette.mode;
|
||||
const themeName = 'backstage';
|
||||
|
||||
useEffect(() => {
|
||||
const oldMode = document.body.getAttribute('data-theme-mode');
|
||||
const oldName = document.body.getAttribute('data-theme-name');
|
||||
document.body.setAttribute('data-theme-mode', themeMode);
|
||||
document.body.setAttribute('data-theme-name', themeName);
|
||||
|
||||
return () => {
|
||||
if (oldMode) {
|
||||
document.body.setAttribute('data-theme-mode', oldMode);
|
||||
} else {
|
||||
document.body.removeAttribute('data-theme-mode');
|
||||
}
|
||||
if (oldName) {
|
||||
document.body.setAttribute('data-theme-name', oldName);
|
||||
} else {
|
||||
document.body.removeAttribute('data-theme-name');
|
||||
}
|
||||
};
|
||||
}, [themeMode, themeName]);
|
||||
useApplyThemeAttributes(
|
||||
v4Theme ? v4Theme.palette.type : v5Theme?.palette.mode,
|
||||
'backstage',
|
||||
);
|
||||
|
||||
let cssBaseline: JSX.Element | undefined = undefined;
|
||||
if (!noCssBaseline) {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 '@testing-library/jest-dom';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useApplyThemeAttributes } from './useApplyThemeAttributes';
|
||||
|
||||
describe('useApplyThemeAttributes', () => {
|
||||
beforeEach(() => {
|
||||
document.body.removeAttribute('data-unified-theme-stack');
|
||||
document.body.removeAttribute('data-theme-mode');
|
||||
document.body.removeAttribute('data-theme-name');
|
||||
});
|
||||
|
||||
it('pushes attributes on mount and pops on unmount', () => {
|
||||
const { unmount } = renderHook(() =>
|
||||
useApplyThemeAttributes('light', 'one'),
|
||||
);
|
||||
expect(document.body.getAttribute('data-theme-mode')).toBe('light');
|
||||
expect(document.body.getAttribute('data-theme-name')).toBe('one');
|
||||
expect(
|
||||
JSON.parse(document.body.getAttribute('data-unified-theme-stack') || '[]')
|
||||
.length,
|
||||
).toBe(1);
|
||||
|
||||
unmount();
|
||||
expect(document.body.getAttribute('data-theme-mode')).toBeNull();
|
||||
expect(document.body.getAttribute('data-theme-name')).toBeNull();
|
||||
expect(document.body.getAttribute('data-unified-theme-stack')).toBeNull();
|
||||
});
|
||||
|
||||
it('stacks multiple mounts and applies top-most', () => {
|
||||
const r1 = renderHook(() => useApplyThemeAttributes('light', 'one'));
|
||||
const r2 = renderHook(() => useApplyThemeAttributes('dark', 'two'));
|
||||
|
||||
expect(document.body.getAttribute('data-theme-mode')).toBe('dark');
|
||||
expect(document.body.getAttribute('data-theme-name')).toBe('two');
|
||||
|
||||
r2.unmount();
|
||||
expect(document.body.getAttribute('data-theme-mode')).toBe('light');
|
||||
expect(document.body.getAttribute('data-theme-name')).toBe('one');
|
||||
|
||||
r1.unmount();
|
||||
expect(document.body.getAttribute('data-theme-mode')).toBeNull();
|
||||
expect(document.body.getAttribute('data-theme-name')).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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 { useEffect } from 'react';
|
||||
|
||||
type ThemeFrame = { mode?: string; name?: string };
|
||||
|
||||
const STACK_ATTR = 'data-unified-theme-stack';
|
||||
const MODE_ATTR = 'data-theme-mode';
|
||||
const NAME_ATTR = 'data-theme-name';
|
||||
|
||||
function mutateAttrStack(mutator: (stack: ThemeFrame[]) => void) {
|
||||
const stack = (() => {
|
||||
const raw = document.body.getAttribute(STACK_ATTR);
|
||||
if (!raw) {
|
||||
return [] as ThemeFrame[];
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
return Array.isArray(parsed) ? (parsed as ThemeFrame[]) : [];
|
||||
} catch {
|
||||
return [] as ThemeFrame[];
|
||||
}
|
||||
})();
|
||||
|
||||
mutator(stack);
|
||||
|
||||
if (stack.length === 0) {
|
||||
document.body.removeAttribute(STACK_ATTR);
|
||||
} else {
|
||||
document.body.setAttribute(STACK_ATTR, JSON.stringify(stack));
|
||||
}
|
||||
|
||||
const top = stack[stack.length - 1];
|
||||
if (top?.mode) {
|
||||
document.body.setAttribute(MODE_ATTR, String(top.mode));
|
||||
} else {
|
||||
document.body.removeAttribute(MODE_ATTR);
|
||||
}
|
||||
if (top?.name) {
|
||||
document.body.setAttribute(NAME_ATTR, String(top.name));
|
||||
} else {
|
||||
document.body.removeAttribute(NAME_ATTR);
|
||||
}
|
||||
}
|
||||
|
||||
export function useApplyThemeAttributes(themeMode: string, themeName: string) {
|
||||
useEffect(() => {
|
||||
mutateAttrStack(stack => {
|
||||
stack.push({ mode: themeMode, name: themeName });
|
||||
});
|
||||
|
||||
return () => {
|
||||
mutateAttrStack(stack => {
|
||||
stack.pop();
|
||||
});
|
||||
};
|
||||
}, [themeMode, themeName]);
|
||||
}
|
||||
@@ -99,6 +99,7 @@ describe('appModulePublicSignIn', () => {
|
||||
<body
|
||||
data-theme-mode="light"
|
||||
data-theme-name="backstage"
|
||||
data-unified-theme-stack="[{"mode":"light","name":"backstage"}]"
|
||||
>
|
||||
<div>
|
||||
<form
|
||||
|
||||
Reference in New Issue
Block a user