diff --git a/pages/app/page.tsx b/pages/app/page.tsx index b78bf30..911b622 100644 --- a/pages/app/page.tsx +++ b/pages/app/page.tsx @@ -1,13 +1,13 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import { Suspense } from "react"; -import { useEffect, useState } from "react"; import { SpaceBetween, Toggle } from "@cloudscape-design/components"; -import { applyDensity, applyMode, Density, Mode } from "@cloudscape-design/global-styles"; +import { Density, Mode } from "@cloudscape-design/global-styles"; import { pagesMap } from "../pages"; import PageLayout from "./page-layout"; +import useModes from "./use-modes"; export interface PageProps { pageId: string; @@ -15,16 +15,7 @@ export interface PageProps { export default function Page({ pageId }: PageProps) { const Component = pagesMap[pageId]; - const [dark, setDark] = useState(false); - const [compact, setCompact] = useState(false); - - useEffect(() => { - applyMode(dark ? Mode.Dark : Mode.Light, document.documentElement); - }, [dark]); - - useEffect(() => { - applyDensity(compact ? Density.Compact : Density.Comfortable, document.documentElement); - }, [compact]); + const { mode, density, setParams } = useModes(); return ( @@ -32,10 +23,16 @@ export default function Page({ pageId }: PageProps) { Back to all pages - setDark(event.detail.checked)}> + setParams({ mode: event.detail.checked ? Mode.Dark : Mode.Light })} + > Dark mode - setCompact(event.detail.checked)}> + setParams({ density: event.detail.checked ? Density.Compact : Density.Comfortable })} + > Compact mode diff --git a/pages/app/use-modes.tsx b/pages/app/use-modes.tsx new file mode 100644 index 0000000..d3b7aa4 --- /dev/null +++ b/pages/app/use-modes.tsx @@ -0,0 +1,72 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { useEffect } from "react"; +import { useSearchParams } from "react-router-dom"; + +import { applyDensity, applyMode, Density, disableMotion, Mode } from "@cloudscape-design/global-styles"; + +export interface AppUrlParams { + mode: Mode; + density: Density; + direction: "ltr" | "rtl"; + motionDisabled: boolean; +} + +export const defaults: AppUrlParams = { + mode: Mode.Light, + density: Density.Comfortable, + direction: "ltr", + motionDisabled: false, +}; + +function castToBoolean(s: string) { + if (s === "true" || s === "false") { + return s === "true"; + } + return s; +} + +export function parseQuery(urlParams: URLSearchParams) { + const queryParams: Record = { ...defaults }; + urlParams.forEach((value, key) => (queryParams[key] = castToBoolean(value))); + return queryParams as AppUrlParams; +} + +export function formatQuery(params: AppUrlParams) { + const query: Record = {}; + for (const [key, value] of Object.entries(params)) { + if (value === defaults[key as keyof AppUrlParams]) { + continue; + } + query[key] = String(value); + } + return query; +} + +export default function useModes() { + const [searchParams, setSearchParams] = useSearchParams(); + const urlParams = parseQuery(searchParams); + const { density, direction, mode, motionDisabled } = urlParams; + + function setParams(newParams: Partial) { + setSearchParams(formatQuery({ ...urlParams, ...newParams })); + } + + useEffect(() => { + applyMode(mode); + }, [mode]); + + useEffect(() => { + applyDensity(density); + }, [density]); + + useEffect(() => { + disableMotion(motionDisabled); + }, [motionDisabled]); + + useEffect(() => { + document.documentElement.setAttribute("dir", direction); + }, [direction]); + + return { density, direction, mode, motionDisabled, setParams }; +}