From 2324523971960cec467f5c12aa6edf6d5014112c Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Mon, 29 Jun 2026 15:13:03 +0200 Subject: [PATCH 1/3] feat: add theme url param for dev page --- pages/app/app-context.tsx | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/pages/app/app-context.tsx b/pages/app/app-context.tsx index e27477b..4beeb98 100644 --- a/pages/app/app-context.tsx +++ b/pages/app/app-context.tsx @@ -7,10 +7,29 @@ import mapValues from "lodash/mapValues"; import { applyDensity, applyMode, Density, disableMotion, Mode } from "@cloudscape-design/global-styles"; +export enum Theme { + Default = "default", + OneTheme = "one-theme", +} + +const themeClassNames: Record = { + [Theme.Default]: "", + [Theme.OneTheme]: "awsui-one-theme", +}; + +function applyThemeClass(activeTheme: Theme) { + for (const [theme, className] of Object.entries(themeClassNames)) { + if (className) { + document.body.classList.toggle(className, theme === activeTheme); + } + } +} + interface AppUrlParams { mode: Mode; density: Density; direction: "ltr" | "rtl"; + theme: Theme; motionDisabled: boolean; i18n: boolean; screenshotMode: boolean; @@ -28,6 +47,7 @@ const appContextDefaults: AppContextType = { mode: Mode.Light, density: Density.Comfortable, direction: "ltr", + theme: Theme.Default, motionDisabled: false, i18n: true, screenshotMode: false, @@ -43,7 +63,12 @@ function parseQuery(urlParams: URLSearchParams) { const queryParams: Record = { ...appContextDefaults.urlParams }; urlParams.forEach((value, key) => (queryParams[key] = value)); - return mapValues(queryParams, (value) => { + const themeValues = Object.values(Theme) as string[]; + + return mapValues(queryParams, (value, key) => { + if (key === "theme") { + return themeValues.includes(value) ? value : Theme.Default; + } if (value === "true" || value === "false") { return value === "true"; } @@ -86,6 +111,10 @@ export function AppContextProvider({ children }: { children: React.ReactNode }) disableMotion(urlParams.motionDisabled); }, [urlParams.motionDisabled]); + useEffect(() => { + applyThemeClass(urlParams.theme); + }, [urlParams.theme]); + document.documentElement.setAttribute("dir", urlParams.direction); return {children}; From a0b9723363df851d1a346c8a08ebf6319e51029f Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Thu, 2 Jul 2026 11:05:29 +0200 Subject: [PATCH 2/3] refactor: replace url parser with shared util --- pages/app/app-context.tsx | 121 ++++++-------------------------------- 1 file changed, 18 insertions(+), 103 deletions(-) diff --git a/pages/app/app-context.tsx b/pages/app/app-context.tsx index 4beeb98..0e3ad4b 100644 --- a/pages/app/app-context.tsx +++ b/pages/app/app-context.tsx @@ -1,121 +1,36 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { createContext, useEffect } from "react"; +import { createContext, useEffect, useMemo } from "react"; import { useSearchParams } from "react-router-dom"; -import mapValues from "lodash/mapValues"; -import { applyDensity, applyMode, Density, disableMotion, Mode } from "@cloudscape-design/global-styles"; - -export enum Theme { - Default = "default", - OneTheme = "one-theme", -} - -const themeClassNames: Record = { - [Theme.Default]: "", - [Theme.OneTheme]: "awsui-one-theme", -}; - -function applyThemeClass(activeTheme: Theme) { - for (const [theme, className] of Object.entries(themeClassNames)) { - if (className) { - document.body.classList.toggle(className, theme === activeTheme); - } - } -} - -interface AppUrlParams { - mode: Mode; - density: Density; - direction: "ltr" | "rtl"; - theme: Theme; - motionDisabled: boolean; - i18n: boolean; - screenshotMode: boolean; -} - -export interface AppContextType { - pageId?: string; - urlParams: AppUrlParams & T; - setUrlParams: (newParams: Partial) => void; -} - -const appContextDefaults: AppContextType = { - pageId: undefined, - urlParams: { - mode: Mode.Light, - density: Density.Comfortable, - direction: "ltr", - theme: Theme.Default, - motionDisabled: false, - i18n: true, - screenshotMode: false, - }, +import { + type AppContextType, + applyAppModes, + appModesDefaults, + type AppUrlParams, + parseAppModes, + updateAppModes, +} from "@cloudscape-design/build-tools/lib/dev-pages-utils"; + +const AppContext = createContext({ + urlParams: appModesDefaults, setUrlParams: () => {}, -}; - -const AppContext = createContext(appContextDefaults); +}); export default AppContext; -function parseQuery(urlParams: URLSearchParams) { - const queryParams: Record = { ...appContextDefaults.urlParams }; - urlParams.forEach((value, key) => (queryParams[key] = value)); - - const themeValues = Object.values(Theme) as string[]; - - return mapValues(queryParams, (value, key) => { - if (key === "theme") { - return themeValues.includes(value) ? value : Theme.Default; - } - if (value === "true" || value === "false") { - return value === "true"; - } - return value; - }); -} - -function formatQuery(params: AppUrlParams) { - const query: Record = {}; - for (const [key, value] of Object.entries(params)) { - if (value === appContextDefaults.urlParams[key as keyof AppUrlParams]) { - continue; - } - query[key as keyof AppUrlParams] = String(value); - } - return query; -} - export function AppContextProvider({ children }: { children: React.ReactNode }) { const [searchParams, setSearchParams] = useSearchParams(); - const urlParams = parseQuery(searchParams) as AppUrlParams; + const urlParams = useMemo(() => parseAppModes(searchParams), [searchParams]); function setUrlParams(newParams: Partial) { - setSearchParams(formatQuery({ ...urlParams, ...newParams })); - - if ((newParams.direction ?? "ltr") !== (urlParams.direction ?? "ltr")) { - window.location.reload(); - } + updateAppModes(urlParams, newParams, setSearchParams); } useEffect(() => { - applyMode(urlParams.mode); - }, [urlParams.mode]); - - useEffect(() => { - applyDensity(urlParams.density); - }, [urlParams.density]); - - useEffect(() => { - disableMotion(urlParams.motionDisabled); - }, [urlParams.motionDisabled]); - - useEffect(() => { - applyThemeClass(urlParams.theme); - }, [urlParams.theme]); - - document.documentElement.setAttribute("dir", urlParams.direction); + applyAppModes(urlParams); + }, [urlParams]); - return {children}; + return {children}; } From 52593be9de52910b8d7aafc85ef57093679e0e11 Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Mon, 6 Jul 2026 10:34:49 +0200 Subject: [PATCH 3/3] refactor: use shared AppModesProvider --- pages/app/app-context.tsx | 36 ----------------------------------- pages/app/index.tsx | 12 ++++++------ pages/app/screenshot-area.tsx | 6 +++--- 3 files changed, 9 insertions(+), 45 deletions(-) delete mode 100644 pages/app/app-context.tsx diff --git a/pages/app/app-context.tsx b/pages/app/app-context.tsx deleted file mode 100644 index 0e3ad4b..0000000 --- a/pages/app/app-context.tsx +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { createContext, useEffect, useMemo } from "react"; -import { useSearchParams } from "react-router-dom"; - -import { - type AppContextType, - applyAppModes, - appModesDefaults, - type AppUrlParams, - parseAppModes, - updateAppModes, -} from "@cloudscape-design/build-tools/lib/dev-pages-utils"; - -const AppContext = createContext({ - urlParams: appModesDefaults, - setUrlParams: () => {}, -}); - -export default AppContext; - -export function AppContextProvider({ children }: { children: React.ReactNode }) { - const [searchParams, setSearchParams] = useSearchParams(); - const urlParams = useMemo(() => parseAppModes(searchParams), [searchParams]); - - function setUrlParams(newParams: Partial) { - updateAppModes(urlParams, newParams, setSearchParams); - } - - useEffect(() => { - applyAppModes(urlParams); - }, [urlParams]); - - return {children}; -} diff --git a/pages/app/index.tsx b/pages/app/index.tsx index 1020a3c..4fcd70c 100644 --- a/pages/app/index.tsx +++ b/pages/app/index.tsx @@ -1,9 +1,10 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { Suspense, useContext } from "react"; +import { Suspense } from "react"; import { HashRouter, Route, Routes, useHref, useLocation, useNavigate, useSearchParams } from "react-router-dom"; +import { AppModesProvider, useAppModes } from "@cloudscape-design/build-tools/lib/dev-pages-utils"; import Alert from "@cloudscape-design/components/alert"; import AppLayout from "@cloudscape-design/components/app-layout"; import Box from "@cloudscape-design/components/box"; @@ -14,7 +15,6 @@ import Spinner from "@cloudscape-design/components/spinner"; import TopNavigation from "@cloudscape-design/components/top-navigation"; import { Density, Mode } from "@cloudscape-design/global-styles"; -import AppContext, { AppContextProvider } from "./app-context"; import { pages, pagesMap } from "./pages"; import "@cloudscape-design/global-styles/index.css"; @@ -22,15 +22,15 @@ import "@cloudscape-design/global-styles/index.css"; export default function App() { return ( - + - + ); } function AppBody() { - const { urlParams } = useContext(AppContext); + const { urlParams } = useAppModes(); const routes = ( <> @@ -70,7 +70,7 @@ function Page({ pageId }: { pageId: string }) { function Navigation() { const navigate = useNavigate(); const [searchParams] = useSearchParams(); - const { urlParams, setUrlParams } = useContext(AppContext); + const { urlParams, setUrlParams } = useAppModes(); const isDarkMode = urlParams.mode === Mode.Dark; const isCompactMode = urlParams.density === Density.Compact; const isRtl = urlParams.direction === "rtl"; diff --git a/pages/app/screenshot-area.tsx b/pages/app/screenshot-area.tsx index ec3208b..717ef0c 100644 --- a/pages/app/screenshot-area.tsx +++ b/pages/app/screenshot-area.tsx @@ -1,14 +1,14 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { ReactNode, useContext } from "react"; +import { ReactNode } from "react"; import clsx from "clsx"; -import AppContext from "./app-context"; +import { useAppModes } from "@cloudscape-design/build-tools/lib/dev-pages-utils"; import styles from "./screenshot-area.module.css"; export function ScreenshotArea({ children }: { children: ReactNode }) { - const { urlParams } = useContext(AppContext); + const { urlParams } = useAppModes(); return
{children}
; }