Skip to content

Commit

Permalink
Merge pull request #531 from omnifed/526-feat-update-usetheme-context…
Browse files Browse the repository at this point in the history
…-to-use-new-theme-selector

526 feat update usetheme context to use new theme selector
  • Loading branch information
caseybaggz authored Oct 2, 2024
2 parents 32b3154 + 527976e commit cf82128
Show file tree
Hide file tree
Showing 20 changed files with 524 additions and 101 deletions.
17 changes: 17 additions & 0 deletions docs/app/actions/cookies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use server'

import { cookies } from 'next/headers'

export async function getCookie(cName: string) {
return cookies().get(cName)?.value ?? ''
}

export async function setCookie(cName: string, cValue: any) {
let date = new Date()
date.setTime(date.getTime() + 7 * 24 * 60 * 60 * 1000)
cookies().set(cName, cValue, { expires: date })
}

export async function deleteCookie(cName: string) {
cookies().delete(cName)
}
10 changes: 8 additions & 2 deletions docs/app/components/ModeToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,28 @@ import { AnimatingMoonIcon } from './icons/AnimatingMoonIcon'
import { AnimatingSunIcon } from './icons/AnimatingSunIcon'
import { Show, useThemeContext } from '@cerberus-design/react'
import { focusStates } from '@cerberus-design/panda-preset'
import { useMemo } from 'react'
import { useCallback, useMemo, type MouseEvent } from 'react'

export function ModeToggle() {
const { mode, updateMode } = useThemeContext()
const ariaLabel = useMemo(() => {
return mode === 'light' ? 'Switch to dark mode' : 'Switch to light mode'
}, [mode])

const handleUpdateMode = useCallback((e: MouseEvent<HTMLButtonElement>) => {
const currentMode = e.currentTarget.value
updateMode(currentMode === 'light' ? 'dark' : 'light')
}, [])

return (
<button
className={css({
rounded: 'sm',
_focusVisible: focusStates._focusVisible,
})}
aria-label={ariaLabel}
onClick={updateMode}
onClick={handleUpdateMode}
value={mode}
>
<Show when={mode === 'light'} fallback={<AnimatingMoonIcon />}>
<AnimatingSunIcon />
Expand Down
77 changes: 69 additions & 8 deletions docs/app/components/Nav.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
'use client'

import { useMemo } from 'react'
import { useCallback, useMemo, type MouseEvent } from 'react'
import Link from 'next/link'
import { css } from '@cerberus/styled-system/css'
import { css, cx } from '@cerberus/styled-system/css'
import { grid, gridItem, hstack } from '@cerberus/styled-system/patterns'
import navData from '@/app/data/navLinks.json'
import { LogoGithub } from '@cerberus-design/icons'
import { Show, useThemeContext } from '@cerberus-design/react'
import { Show, useThemeContext, type ColorModes } from '@cerberus-design/react'
import { version } from '@cerberus-design/configs'
import { AnimatingSunIcon } from './icons/AnimatingSunIcon'
import { AnimatingMoonIcon } from './icons/AnimatingMoonIcon'
import { usePathname } from 'next/navigation'
import { focusStates } from '@cerberus-design/panda-preset'
import { getColorMode } from '../utils/colors'
import { button } from '@cerberus/styled-system/recipes'
import { DogIcon } from './icons/DogIcon'
import { FireIcon } from './icons/FireIcon'
import { getTheme, injectTheme, type ThemeName } from '@/styled-system/themes'
import { PAGE_BORDER_INITIAL } from '../utils/const'

const navLogoContent = (
<section
Expand Down Expand Up @@ -65,11 +71,31 @@ const navGHLogoContent = (

export function Nav() {
const pathname = usePathname()
const { mode, updateMode } = useThemeContext()
const { mode, theme, updateMode, updateTheme } = useThemeContext()
const ariaLabel = useMemo(() => {
return mode === 'light' ? 'Switch to dark mode' : 'Switch to light mode'
}, [mode])

const handleUpdateMode = useCallback(
(e: MouseEvent<HTMLButtonElement>) => {
const currentMode = e.currentTarget.value as ColorModes
const newMode = currentMode === 'light' ? 'dark' : 'light'
updateMode(newMode)
},
[updateMode],
)

const handleUpdateTheme = useCallback(
async (e: MouseEvent<HTMLButtonElement>) => {
const currentTheme = e.currentTarget.value
const newTheme = currentTheme === 'cerberus' ? 'acheron' : 'cerberus'
updateTheme(newTheme)
const pandaTheme = await getTheme(newTheme as ThemeName)
injectTheme(document.documentElement, pandaTheme)
},
[updateTheme],
)

return (
<nav
className={grid({
Expand All @@ -87,7 +113,7 @@ export function Nav() {
bgColor: 'rgba(255, 255, 255, 0.3)',
},
_darkMode: {
bgColor: 'rgba(19, 0, 36, 0.3)',
bgColor: 'page.backdrop.initial',
},
md: {
gridTemplateRows: '1fr',
Expand Down Expand Up @@ -115,11 +141,11 @@ export function Nav() {
<ul
className={hstack({
border: '1px solid',
borderColor: 'page.border.initial',
borderColor: PAGE_BORDER_INITIAL,
gap: '0',
w: 'full',
'& li:nth-child(2)': {
borderColor: 'page.border.initial',
borderColor: PAGE_BORDER_INITIAL,
borderLeft: '1px solid',
borderRight: '1px solid',
md: {
Expand Down Expand Up @@ -224,6 +250,7 @@ export function Nav() {
</p>
</li>
{navGHLogoContent}

<li
className={css({
h: '1.5rem',
Expand All @@ -235,13 +262,47 @@ export function Nav() {
_focusVisible: focusStates._focusVisible,
})}
aria-label={ariaLabel}
onClick={updateMode}
onClick={handleUpdateMode}
value={mode}
>
<Show when={mode === 'light'} fallback={<AnimatingMoonIcon />}>
<AnimatingSunIcon />
</Show>
</button>
</li>

<li>
<button
className={cx(
css({
bgColor: 'page.bg.100',
border: '1px solid',
borderColor: PAGE_BORDER_INITIAL,
fontWeight: 500,
h: '2.275rem',
rounded: 'sm',
textStyle: 'label-sm',
textTransform: 'capitalize',
_hover: {
bgColor: 'page.bg.200',
},
}),
button({
palette: 'secondaryAction',
shape: 'rounded',
size: 'sm',
usage: 'outlined',
}),
)}
onClick={handleUpdateTheme}
value={theme}
>
<Show when={theme === 'cerberus'} fallback={<FireIcon />}>
<DogIcon />
</Show>
{theme}
</button>
</li>
</ul>
</section>
</nav>
Expand Down
2 changes: 1 addition & 1 deletion docs/app/components/RootHeadline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default function RootHeadline() {
})}
>
<Show
when={mode === 'dark'}
when={mode === 'dark' || mode === 'system'}
fallback={<>The cutest protector of brand consistency in the realm.</>}
>
The{' '}
Expand Down
60 changes: 60 additions & 0 deletions docs/app/components/icons/AnimatingSystemIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { type SVGProps } from 'react'

export function AnimatingSystemIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1.5em"
height="1.5em"
viewBox="0 0 24 24"
{...props}
>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
>
<path strokeDasharray={6} strokeDashoffset={6} d="M12 21h5M12 21h-5">
<animate
fill="freeze"
attributeName="stroke-dashoffset"
dur="0.225s"
values="6;0"
></animate>
</path>
<path strokeDasharray={6} strokeDashoffset={6} d="M12 21v-4">
<animate
fill="freeze"
attributeName="stroke-dashoffset"
dur="0.225s"
values="6;0"
></animate>
</path>
<path
fill="currentColor"
fillOpacity={0}
strokeDasharray={64}
strokeDashoffset={64}
d="M12 17h-9v-12h18v12Z"
>
<animate
fill="freeze"
attributeName="fill-opacity"
begin="0.75s"
dur="0.112s"
values="0;0.3"
></animate>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.225s"
dur="0.45s"
values="64;0"
></animate>
</path>
</g>
</svg>
)
}
18 changes: 18 additions & 0 deletions docs/app/components/icons/DogIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { type SVGProps } from 'react'

export function DogIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 512 512"
{...props}
>
<path
fill="currentColor"
d="M269.4 2.9C265.2 1 260.7 0 256 0s-9.2 1-13.4 2.9L54.3 82.8c-22 9.3-38.4 31-38.3 57.2c.5 99.2 41.3 280.7 213.6 363.2c16.7 8 36.1 8 52.8 0C454.7 420.7 495.5 239.2 496 140c.1-26.2-16.3-47.9-38.3-57.2zM160.9 286.2c4.8 1.2 9.9 1.8 15.1 1.8c35.3 0 64-28.7 64-64v-64h44.2c12.1 0 23.2 6.8 28.6 17.7L320 192h64c8.8 0 16 7.2 16 16v32c0 44.2-35.8 80-80 80h-48v50.7c0 7.3-5.9 13.3-13.3 13.3c-1.8 0-3.6-.4-5.2-1.1l-98.7-42.3c-6.6-2.8-10.8-9.3-10.8-16.4c0-2.8.6-5.5 1.9-8zM160 160h48v64c0 17.7-14.3 32-32 32s-32-14.3-32-32v-48c0-8.8 7.2-16 16-16m128 48a16 16 0 1 0-32 0a16 16 0 1 0 32 0"
></path>
</svg>
)
}
18 changes: 18 additions & 0 deletions docs/app/components/icons/FireIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { type SVGProps } from 'react'

export function FireIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 16 16"
{...props}
>
<path
fill="currentColor"
d="M8 16c3.314 0 6-2 6-5.5c0-1.5-.5-4-2.5-6c.25 1.5-1.25 2-1.25 2C11 4 9 .5 6 0c.357 2 .5 4-2 6c-1.25 1-2 2.729-2 4.5C2 14 4.686 16 8 16m0-1c-1.657 0-3-1-3-2.75c0-.75.25-2 1.25-3C6.125 10 7 10.5 7 10.5c-.375-1.25.5-3.25 2-3.5c-.179 1-.25 2 1 3c.625.5 1 1.364 1 2.25C11 14 9.657 15 8 15"
></path>
</svg>
)
}
49 changes: 42 additions & 7 deletions docs/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import type { Metadata } from 'next'
import { Poppins, Recursive } from 'next/font/google'
import { Analytics } from '@vercel/analytics/react'
import type { PropsWithChildren } from 'react'
import { ThemeProvider } from '@cerberus-design/react'
import { type PropsWithChildren } from 'react'
import {
ThemeProvider,
type ColorModes,
type DefaultThemes,
} from '@cerberus-design/react'
import { css, cx } from '@cerberus/styled-system/css'
import { getTheme, type ThemeName } from '@/styled-system/themes'
import { base, openGraph } from './shared-metadata'
import { Nav } from './components/Nav'

import './globals.css'
import { base, openGraph } from './shared-metadata'
import { getCookie, setCookie } from './actions/cookies'

const poppins = Poppins({
display: 'swap',
Expand All @@ -30,22 +36,51 @@ export const metadata: Metadata = {

interface RootProps {}

export default function RootLayout(props: PropsWithChildren<RootProps>) {
export default async function RootLayout(props: PropsWithChildren<RootProps>) {
const themeName = (await getCookie('theme')) as ThemeName
const theme = themeName && (await getTheme(themeName))
const colorModeName = (await getCookie('colorMode')) as ColorModes | undefined

const handleUpdateTheme = async (theme: DefaultThemes) => {
'use server'
setCookie('theme', theme)
}

const handleUpdateMode = async (mode: ColorModes) => {
'use server'
setCookie('colorMode', mode)
}

return (
<html
className={cx(poppins.variable, recursive.variable)}
lang="en"
data-theme="cerberus"
data-color-mode="light"
data-panda-theme={themeName || 'cerberus'}
data-color-mode={colorModeName || 'light'}
>
<Analytics />
{themeName && (
<head>
<style
type="text/css"
id={theme.id}
dangerouslySetInnerHTML={{ __html: theme.css }}
/>
</head>
)}

<body
className={css({
minW: '18.75rem',
h: 'full',
})}
>
<ThemeProvider>
<ThemeProvider
defaultTheme={themeName || 'cerberus'}
defaultColorMode={colorModeName || 'light'}
updateTheme={handleUpdateTheme}
updateMode={handleUpdateMode}
>
<Nav />
{props.children}
</ThemeProvider>
Expand Down
7 changes: 1 addition & 6 deletions docs/app/preset/colors/components/AvatarSwatch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,15 @@ interface ColorSwatchProps {
}

export default function ColorSwatch(props: ColorSwatchProps) {
const { mode } = useThemeContext()
const searchParams = useSearchParams()

const modeValue = useMemo(() => {
return mode === 'dark' ? '_darkMode' : '_lightMode'
}, [mode])
const color = useMemo(() => {
const snakeCaseToken = props.tokenName
.replace(/([A-Z])/g, ' $1')
.trim()
.toLowerCase()
.replace(/\s+/g, '-')
return `var(--cerberus-colors-${snakeCaseToken})`
}, [modeValue, props.tokenName])
}, [props.tokenName])

const bgColor = {
backgroundColor: color,
Expand Down
Loading

0 comments on commit cf82128

Please sign in to comment.