Skip to content

Commit

Permalink
[DST-379]: Docs: Persist Theme (#3865)
Browse files Browse the repository at this point in the history
Co-authored-by: aromko <aromkoapps@gmail.com>
Co-authored-by: Marcel Köhler <77496890+aromko@users.noreply.github.com>
  • Loading branch information
3 people committed May 13, 2024
1 parent 20fb4f4 commit 5eafe50
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 50 deletions.
5 changes: 5 additions & 0 deletions .changeset/small-pillows-exist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@marigold/docs": patch
---

[DST-379]: Docs: The selected theme on marigold docs is now persisted in the local storage.
25 changes: 16 additions & 9 deletions docs/app/_components/NavLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import Link, { LinkProps } from 'next/link';

import { type VariantProps, cn, cva } from '@marigold/system';

import { useThemeSwitch } from '@/ui/ThemeSwitch';

const styles = cva([], {
variants: {
variant: {
Expand Down Expand Up @@ -54,13 +56,18 @@ export const NavLink = ({
current,
className,
children,
href,
...props
}: NavLinkProps) => (
<Link
{...props}
className={cn(styles({ variant, current, className }))}
aria-current={current ? 'page' : undefined}
>
{children}
</Link>
);
}: NavLinkProps) => {
const { current: currentTheme } = useThemeSwitch();
return (
<Link
{...props}
className={cn(styles({ variant, current, className }))}
aria-current={current ? 'page' : undefined}
href={`${href}${currentTheme ? `?theme=${currentTheme}` : ''}`}
>
{children}
</Link>
);
};
4 changes: 2 additions & 2 deletions docs/app/_components/ThemeMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import { useThemeSwitch } from '@/ui/ThemeSwitch';
import { Theme } from '@/ui/icons/Theme';

export const ThemeMenu = () => {
const { themes, setTheme } = useThemeSwitch();
const { themes, updateTheme } = useThemeSwitch();

return (
<Menu
label={<Theme className="text-secondary-600" />}
onAction={current => setTheme(current)}
onAction={current => updateTheme(current)}
placement="bottom end"
>
{Object.keys(themes).map(name => (
Expand Down
64 changes: 35 additions & 29 deletions docs/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { siteConfig } from '@/lib/config';
import { b2bTheme, coreTheme, theme } from '@/theme';
import { MarigoldProvider } from '@/ui';
import { Suspense } from 'react';

import '@marigold/theme-b2b/styles.css';
import '@marigold/theme-core/styles.css';
Expand Down Expand Up @@ -47,35 +48,40 @@ const Layout = ({ children }: { children: React.ReactNode }) => {
return (
<html lang="en">
<body className={`${fontSans.className} min-h-screen`}>
<MarigoldThemeSwitch themes={themes} initial={siteConfig.defaultTheme}>
<MarigoldProvider theme={theme}>
<SiteHeader />
<aside
className={[
'top-[--page-header-height]',
'py-[--page-sub-nav-padding] xl:py-[--page-sub-nav-padding-xl]',
'pl-[--page-padding-md] xl:pl-[--page-padding-xl]',
'h-[calc(100vh-var(--page-header-height))] w-[--page-sub-nav-width] xl:w-[--page-sub-nav-width-xl]',
'fixed z-10 hidden overflow-hidden hover:overflow-y-auto md:block',
'scrollbar-thin scrollbar-thumb-secondary-400 scrollbar-thumb-rounded-full scrollbar-track-transparent',
'border-secondary-200 border-r',
].join(' ')}
>
{/* current section navigation sidebar */}
<SectionNavigation />
</aside>
<main
className={[
'pt-[--page-main-padding] xl:pt-[--page-main-padding-xl]',
'px-[--page-padding] md:px-[--page-padding-md] xl:pr-[--page-padding-xl]',
'md:pl-[calc(var(--page-sub-nav-width)+var(--page-main-padding))] xl:pl-[calc(var(--page-sub-nav-width-xl)+var(--page-main-padding-xl))]',
].join(' ')}
>
{children}
<SiteFooter />
</main>
</MarigoldProvider>
</MarigoldThemeSwitch>
<Suspense>
<MarigoldThemeSwitch
themes={themes}
initial={siteConfig.defaultTheme}
>
<MarigoldProvider theme={theme}>
<SiteHeader />
<aside
className={[
'top-[--page-header-height]',
'py-[--page-sub-nav-padding] xl:py-[--page-sub-nav-padding-xl]',
'pl-[--page-padding-md] xl:pl-[--page-padding-xl]',
'h-[calc(100vh-var(--page-header-height))] w-[--page-sub-nav-width] xl:w-[--page-sub-nav-width-xl]',
'fixed z-10 hidden overflow-hidden hover:overflow-y-auto md:block',
'scrollbar-thin scrollbar-thumb-secondary-400 scrollbar-thumb-rounded-full scrollbar-track-transparent',
'border-secondary-200 border-r',
].join(' ')}
>
{/* current section navigation sidebar */}
<SectionNavigation />
</aside>
<main
className={[
'pt-[--page-main-padding] xl:pt-[--page-main-padding-xl]',
'px-[--page-padding] md:px-[--page-padding-md] xl:pr-[--page-padding-xl]',
'md:pl-[calc(var(--page-sub-nav-width)+var(--page-main-padding))] xl:pl-[calc(var(--page-sub-nav-width-xl)+var(--page-main-padding-xl))]',
].join(' ')}
>
{children}
<SiteFooter />
</main>
</MarigoldProvider>
</MarigoldThemeSwitch>
</Suspense>
<Analytics />
</body>
</html>
Expand Down
30 changes: 25 additions & 5 deletions docs/ui/ThemeSwitch.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,45 @@ import { b2bTheme, coreTheme } from '../theme';
import { MarigoldThemeSwitch, useThemeSwitch } from './ThemeSwitch';

const themes = {
b2bTheme: b2bTheme,
coreTheme: coreTheme,
b2bTheme,
coreTheme,
};

const useRouter = require('next/navigation').useRouter;

jest.mock('next/navigation', () => ({
useRouter: jest.fn(),
useSearchParams: jest.fn(),
}));

beforeEach(() => {
useRouter.mockReturnValue({ replace: jest.fn() });
});

afterEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});

const wrapper = ({ children }: { children?: ReactNode }) => (
<MarigoldThemeSwitch themes={themes} initial="b2bTheme">
<MarigoldThemeSwitch themes={themes} initial="b2b">
{children}
</MarigoldThemeSwitch>
);

test('returns the theme', () => {
const { result } = renderHook(() => useThemeSwitch(), { wrapper });

expect(result.current.current).toMatchInlineSnapshot(`"b2bTheme"`);
expect(result.current.current).toMatchInlineSnapshot(`"b2b"`);
});

test('switches the theme', async () => {
// useRouter.mockReturnValue({ replace: jest.fn() });

const { result } = renderHook(() => useThemeSwitch(), { wrapper });

act(() => {
result.current.setTheme(coreTheme);
result.current.updateTheme(coreTheme);
});

// @ts-ignore
Expand Down
61 changes: 56 additions & 5 deletions docs/ui/ThemeSwitch.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
'use client';

import { type Theme } from '@/ui';
import React, { ReactNode, useContext, useEffect, useState } from 'react';
import React, {
ReactNode,
useCallback,
useContext,
useEffect,
useRef,
useState,
} from 'react';

import { useRouter, useSearchParams } from 'next/navigation';

// Context
// ---------------
export interface ThemeSwitchContextType {
current: string | undefined;
themes: { [name: string]: Theme };
setTheme: Function;
updateTheme: Function;
}

export const Context = React.createContext({
Expand All @@ -33,11 +42,53 @@ export const MarigoldThemeSwitch = ({
initial,
children,
}: MarigoldThemeSwitchProps) => {
const [theme, setTheme] = useState(initial);
useEffect(() => setTheme(theme), [theme]);
let localTheme: string = '';

const searchParams = useSearchParams();
const themeQueryParam = searchParams?.get('theme');

const [theme, setTheme] = useState<string>(() => initial);
const router = useRouter();

if (typeof localStorage !== 'undefined') {
localTheme = localStorage.getItem('theme') as string;
}

const isInitialMount = useRef(true); // Ref to track initial mount

const updateTheme = useCallback(
(theme: string) => {
setTheme(theme);
localStorage.setItem('theme', theme);

const currentUrl = new URL(window.location.href);
const searchParams = new URLSearchParams(currentUrl.search);

searchParams.set('theme', theme);
const newUrl = `${currentUrl.pathname}?${searchParams.toString()}${currentUrl.hash}`;

router.replace(newUrl, {
scroll: false,
});
},
[router]
);

useEffect(() => {
if (isInitialMount.current) {
isInitialMount.current = false;
if (themeQueryParam) {
updateTheme(themeQueryParam);
} else if (localTheme) {
updateTheme(localTheme);
} else {
updateTheme(initial);
}
}
}, [localTheme, themeQueryParam, updateTheme, initial]);

return (
<Context.Provider value={{ current: theme, themes, setTheme }}>
<Context.Provider value={{ current: theme, themes, updateTheme }}>
{children}
</Context.Provider>
);
Expand Down

0 comments on commit 5eafe50

Please sign in to comment.