diff --git a/dev/index.html b/dev/index.html index e76e076b..12343278 100644 --- a/dev/index.html +++ b/dev/index.html @@ -6,6 +6,14 @@ SciReactUI Dev +
diff --git a/src/components/controls/ColourSchemeButton.test.tsx b/src/components/controls/ColourSchemeButton.test.tsx index d3e11070..c3281d5b 100644 --- a/src/components/controls/ColourSchemeButton.test.tsx +++ b/src/components/controls/ColourSchemeButton.test.tsx @@ -1,29 +1,42 @@ import { fireEvent } from "@testing-library/react"; +import { useColorScheme } from "@mui/material/styles"; import { ColourSchemeButton } from "./ColourSchemeButton"; -import { ColourSchemes } from "../../utils/globals"; import { renderWithProviders } from "../../__test-utils__/helpers"; const mockSetColorScheme = vi.fn(); -vi.mock("@mui/material", async () => { - const actualEnums = await vi.importActual("../../utils/globals"); +vi.mock("@mui/material/styles", async () => { + const actual = await vi.importActual( + "@mui/material/styles", + ); return { - ...(await vi.importActual("@mui/material")), - useColorScheme: vi.fn().mockReturnValue({ - // @ts-expect-error module doesn't have a type - colorScheme: actualEnums.ColourSchemes.Dark, - setColorScheme: (scheme: ColourSchemes) => mockSetColorScheme(scheme), - }), + ...actual, + useColorScheme: vi.fn(), }; }); +const mockUseColorScheme = vi.mocked(useColorScheme); + describe("ColourSchemeButton", () => { + beforeEach(() => { + vi.clearAllMocks(); + + mockUseColorScheme.mockReturnValue({ + mode: "dark", + systemMode: undefined, + setMode: mockSetColorScheme, + colorScheme: "dark", + allColorSchemes: ["light", "dark"], + setColorScheme: vi.fn(), + }); + }); + it("should render without errors", () => { renderWithProviders(); }); - it("should show dark icon and button", () => { + it("should show button and light mode icon when current mode is dark", () => { const { getByTestId, getByRole } = renderWithProviders( , ); @@ -31,7 +44,7 @@ describe("ColourSchemeButton", () => { const button = getByRole("button"); expect(button).toBeInTheDocument(); - const icon = getByTestId("BedtimeIcon"); + const icon = getByTestId("LightModeIcon"); expect(icon).toBeInTheDocument(); }); @@ -41,7 +54,7 @@ describe("ColourSchemeButton", () => { const button = getByRole("button"); fireEvent.click(button); - //expect(mockSetColorScheme).toHaveBeenCalledWith(ColourSchemes.Light); + expect(mockSetColorScheme).toHaveBeenCalledWith("light"); }); it("should call local onclick when button clicked", () => { @@ -55,4 +68,19 @@ describe("ColourSchemeButton", () => { expect(mockOnClick).toHaveBeenCalled(); }); + + it("should not render while colour scheme is unresolved", () => { + mockUseColorScheme.mockReturnValue({ + mode: undefined, + systemMode: undefined, + setMode: mockSetColorScheme, + colorScheme: undefined, + allColorSchemes: ["light", "dark"], + setColorScheme: vi.fn(), + }); + + const { queryByRole } = renderWithProviders(); + + expect(queryByRole("button")).not.toBeInTheDocument(); + }); }); diff --git a/src/components/controls/ColourSchemeButton.tsx b/src/components/controls/ColourSchemeButton.tsx index 68c97dfc..6f80ddc3 100644 --- a/src/components/controls/ColourSchemeButton.tsx +++ b/src/components/controls/ColourSchemeButton.tsx @@ -9,6 +9,10 @@ export const ColourSchemeButton = (props: IconButtonProps) => { const resolvedMode = mode === "system" ? systemMode : mode; const isDark = resolvedMode === "dark"; + if (resolvedMode === undefined) { + return null; + } + return ( -
-
- ### Avoiding Dark Mode Flicker in SSR Apps - - To prevent a flash of incorrect theme (e.g. light mode before switching to dark), include the `InitColorSchemeScript` in your app code. - - This ensures the correct colour scheme is applied immediately, based on system preference or saved user choice. - - - ```tsx filename="main.tsx" - import { InitColorSchemeScript } from "@mui/material"; - - function Root() { - return ( - <> - - - - ); - } - ``` - - If your app renders into `index.html`, place the script at the top of the body so the mode is set before React mounts. - - This helps avoid the brief light-mode flash when the user prefers dark mode. - - For more info see MUI's documentation on [InitColorSchemeScript](https://v7.mui.com/material-ui/react-init-color-scheme-script/). - -
+
+
+ + ### Avoiding Dark Mode Flicker + + Applications that support dark mode may briefly render the default colour scheme before the user's preferred mode is applied. + + #### SSR Applications + + To prevent a flash of incorrect theme in SSR applications, use MUI's `InitColorSchemeScript` and place it before your application content so the correct colour scheme is applied before hydration. + For example, for Next.js App Router: + ```tsx filename="layout.tsx" + import InitColorSchemeScript from '@mui/material/InitColorSchemeScript'; + + export default function RootLayout(props: { children: React.ReactNode }) { + return ( + + + + {props.children} + + + ); + } + ``` + If your app renders into `index.html`, place the script at the top of the body so the mode is set before React mounts. + For more info see MUI's documentation on [InitColorSchemeScript](https://v7.mui.com/material-ui/react-init-color-scheme-script/). + + #### Client-Side Applications (Vite etc.) + + For client-side applications, initialise the mode before React mounts: + ``` + + +
+ + + ``` + + This ensures the correct DiamondDS token set is applied before the first paint, avoiding a flash of the default colour scheme when a saved preference exists. + + DiamondDS uses: + ``` + + ``` + or + ``` + + ``` + and should be configured with + ``` + colorSchemeSelector: '[data-mode="%s"]' + ```
+