Skip to content

Commit 5fd0216

Browse files
committed
Implement theme management and enhance accessibility
- Added ThemeProvider to manage theme context in the application. - Introduced a script to initialize the theme based on user preference before rendering. - Removed the deprecated ThemeSync component from various pages. - Updated button styles for improved focus visibility and accessibility. - Enhanced the DocsSidebar with ARIA attributes for better navigation.
1 parent 034e732 commit 5fd0216

12 files changed

Lines changed: 159 additions & 53 deletions

app/error.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"use client"; // Error boundaries must be Client Components
2+
3+
import Link from "next/link";
4+
import { useEffect } from "react";
5+
6+
export default function Error({
7+
error,
8+
unstable_retry,
9+
}: {
10+
error: Error & { digest?: string };
11+
unstable_retry: () => void;
12+
}) {
13+
useEffect(() => {
14+
console.error(error);
15+
}, [error]);
16+
17+
return (
18+
<main className="flex min-h-0 flex-1 flex-col items-center justify-center gap-6 bg-background px-6 py-16 text-center text-foreground">
19+
<div className="space-y-3">
20+
<p className="font-mono text-xs uppercase tracking-[0.25em] text-muted-foreground">
21+
Something broke
22+
</p>
23+
<h1 className="font-doto text-4xl font-black tracking-tighter">
24+
An evil error appeared.
25+
</h1>
26+
<p className="mx-auto max-w-md text-sm text-muted-foreground">
27+
Something went wrong while rendering this page. You can try again, or
28+
head back to safety.
29+
</p>
30+
{error.digest ? (
31+
<p className="font-mono text-[11px] text-muted-foreground/70">
32+
Error ID: {error.digest}
33+
</p>
34+
) : null}
35+
</div>
36+
37+
<div className="flex flex-wrap items-center justify-center gap-2">
38+
<button
39+
type="button"
40+
onClick={() => unstable_retry()}
41+
className="inline-flex h-9 items-center justify-center bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
42+
>
43+
Try again
44+
</button>
45+
<Link
46+
href="/"
47+
className="inline-flex h-9 items-center justify-center border border-border bg-background px-4 text-sm font-medium text-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
48+
>
49+
Go home
50+
</Link>
51+
</div>
52+
</main>
53+
);
54+
}

app/layout.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Geist, Geist_Mono, Inter, Doto } from "next/font/google";
22
import "./globals.css";
33
import { JsonLd } from "@/components/seo/json-ld";
4+
import { ThemeProvider } from "@/components/theme-provider";
5+
import { THEME_STORAGE_KEY } from "@/lib/theme-preference";
46
import { cn } from "@/lib/utils";
57
import {
68
createOrganizationJsonLd,
@@ -29,6 +31,13 @@ const dotoVar = Doto({
2931

3032
export const metadata = rootMetadata;
3133

34+
// Runs before paint so the persisted/system theme is applied without a flash of
35+
// the wrong color scheme. Kept dependency-free and rendered as the first node in
36+
// <body> so it executes during HTML parse, before the app content is painted.
37+
const themeInitScript = `(function(){try{var t=localStorage.getItem(${JSON.stringify(
38+
THEME_STORAGE_KEY,
39+
)});if(t!=="light"&&t!=="dark"){t=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";}document.documentElement.classList.toggle("dark",t==="dark");}catch(e){}})();`;
40+
3241
export default function RootLayout({
3342
children,
3443
}: Readonly<{
@@ -37,6 +46,7 @@ export default function RootLayout({
3746
return (
3847
<html
3948
lang="en"
49+
suppressHydrationWarning
4050
className={cn(
4151
geistSans.variable,
4252
geistMono.variable,
@@ -46,6 +56,7 @@ export default function RootLayout({
4656
)}
4757
>
4858
<body className="flex h-full flex-col overflow-hidden">
59+
<script dangerouslySetInnerHTML={{ __html: themeInitScript }} />
4960
<JsonLd
5061
data={[
5162
createWebSiteJsonLd(),
@@ -55,7 +66,7 @@ export default function RootLayout({
5566
/>
5667
<Analytics />
5768
<SpeedInsights />
58-
{children}
69+
<ThemeProvider>{children}</ThemeProvider>
5970
</body>
6071
</html>
6172
);

app/not-found.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import Link from "next/link";
2+
3+
export default function NotFound() {
4+
return (
5+
<main className="flex min-h-0 flex-1 flex-col items-center justify-center gap-6 bg-background px-6 py-16 text-center text-foreground">
6+
<div className="space-y-3">
7+
<p className="font-mono text-xs uppercase tracking-[0.25em] text-muted-foreground">
8+
404
9+
</p>
10+
<h1 className="font-doto text-4xl font-black tracking-tighter">
11+
This page pressed the wrong button.
12+
</h1>
13+
<p className="mx-auto max-w-md text-sm text-muted-foreground">
14+
The page you are looking for does not exist or has been moved.
15+
</p>
16+
</div>
17+
18+
<div className="flex flex-wrap items-center justify-center gap-2">
19+
<Link
20+
href="/"
21+
className="inline-flex h-9 items-center justify-center bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
22+
>
23+
Go home
24+
</Link>
25+
<Link
26+
href="/docs"
27+
className="inline-flex h-9 items-center justify-center border border-border bg-background px-4 text-sm font-medium text-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
28+
>
29+
Browse docs
30+
</Link>
31+
</div>
32+
</main>
33+
);
34+
}

components/docs-page-actions.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ export function DocsPageActions({
193193
href={link.href}
194194
target="_blank"
195195
rel="noreferrer noopener"
196-
className="flex cursor-pointer items-center gap-2.5 px-2.5 py-2 text-sm text-muted-foreground outline-none transition-colors data-highlighted:bg-muted data-highlighted:text-foreground"
196+
className="flex cursor-pointer items-center gap-2.5 px-2.5 py-2 text-sm text-muted-foreground outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:bg-muted focus-visible:text-foreground data-highlighted:bg-muted data-highlighted:text-foreground"
197197
>
198198
<span className="flex size-4 shrink-0 items-center justify-center text-foreground">
199199
{link.icon}

components/docs-shell.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import Link from "next/link";
22
import Image from "next/image";
33
import type { ReactNode } from "react";
44
import { DocsSidebar } from "@/components/docs-sidebar";
5-
import { ThemeSync } from "@/components/theme-sync";
65

76
type DocsNavPage = {
87
title: string;
@@ -20,7 +19,6 @@ export function DocsShell({
2019
}: DocsShellProps) {
2120
return (
2221
<div className="flex h-dvh overflow-hidden bg-background text-foreground">
23-
<ThemeSync />
2422
<DocsSidebar
2523
componentPages={componentPages}
2624
brand={

components/docs-sidebar.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export function DocsSidebar({
9292
return (
9393
<>
9494
<aside
95+
id="docs-mobile-nav"
9596
className={cn(
9697
"docs-scroll fixed inset-y-0 left-0 z-50 w-64 shrink-0 overflow-y-auto bg-sidebar p-4 text-sidebar-foreground transition-transform duration-200 ease-out md:static md:z-auto md:h-full md:w-52 md:translate-x-0 md:border-r md:border-sidebar-border",
9798
open ? "translate-x-0" : "-translate-x-full md:translate-x-0",
@@ -159,6 +160,7 @@ export function DocsSidebar({
159160
className="inline-flex size-8 items-center justify-center rounded-md bg-sidebar text-sidebar-foreground/70 shadow-sm ring-1 ring-sidebar-border transition-colors hover:text-sidebar-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sidebar-ring"
160161
onClick={() => setOpen((value) => !value)}
161162
aria-expanded={open}
163+
aria-controls="docs-mobile-nav"
162164
aria-label="Open menu"
163165
>
164166
<ListIcon size={16} />

components/landing/hero-section.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import Gravity, { MatterBody } from "@/components/physics/gravity";
44
import { ButtonPreview } from "@/components/landing/previews";
55
import { showcase } from "@/components/landing/showcase";
6-
import { ThemeSync } from "@/components/theme-sync";
76
import { ThemeToggle } from "@/components/theme-toggle";
87
import { siteConfig } from "@/lib/seo";
98
import { ArrowUpRight } from "@phosphor-icons/react";
@@ -34,8 +33,6 @@ type HeroSectionProps = {
3433
export function HeroSection({ trapped = false }: HeroSectionProps) {
3534
return (
3635
<div className="relative h-dvh overflow-hidden bg-background text-foreground">
37-
<ThemeSync />
38-
3936
<div className="pointer-events-none relative z-10 flex flex-col gap-4 px-4 py-4 sm:px-6 sm:py-5">
4037
<div className="flex items-start justify-between gap-4">
4138
<div className="space-y-2">

components/landing/landing-page.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import { DeferredMount } from "@/components/landing/deferred-mount";
44
import { FitToContainer } from "@/components/landing/fit-to-container";
5-
import { ThemeSync } from "@/components/theme-sync";
65
import { siteConfig } from "@/lib/seo";
76
import { ArrowUpRight, Copy } from "@phosphor-icons/react";
87
import Link from "next/link";
@@ -323,8 +322,6 @@ export function LandingPage() {
323322

324323
return (
325324
<div className="flex h-dvh flex-col overflow-hidden bg-background text-foreground xl:flex-row">
326-
<ThemeSync />
327-
328325
<header className="shrink-0 border-b border-border px-6 py-6 xl:flex xl:w-[340px] xl:shrink-0 xl:flex-col xl:justify-between xl:border-b-0 xl:px-8 xl:py-10">
329326
<div>
330327
<Link
@@ -339,9 +336,9 @@ export function LandingPage() {
339336
height={192}
340337
className="h-auto w-14 sm:w-16"
341338
/>
342-
<h1 className="text-2xl font-doto font-black tracking-tighter leading-5">
339+
<span className="text-2xl font-doto font-black tracking-tighter leading-5">
343340
Evil <br /> Buttons
344-
</h1>
341+
</span>
345342
</div>
346343
</Link>
347344
<h1 className="mt-4 max-w-2xl text-2xl font-semibold tracking-tight text-balance leading-tight xl:mt-8 xl:text-3xl">

components/landing/playground-page.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { AppDialRoot } from "@/components/dial-root";
44
import { FitToContainer } from "@/components/landing/fit-to-container";
55
import { ButtonPreview } from "@/components/landing/previews";
66
import { showcase } from "@/components/landing/showcase";
7-
import { ThemeSync } from "@/components/theme-sync";
87
import { ThemeToggle } from "@/components/theme-toggle";
98
import { useAppTheme } from "@/hooks/use-app-theme";
109
import { siteConfig } from "@/lib/seo";
@@ -28,8 +27,6 @@ export function PlaygroundPage() {
2827

2928
return (
3029
<div className="flex h-dvh flex-col overflow-hidden bg-background text-foreground">
31-
<ThemeSync />
32-
3330
<header className="grid shrink-0 gap-3 px-4 py-3 sm:flex sm:items-center sm:justify-between sm:gap-4 sm:px-6 sm:py-4">
3431
<Link
3532
href="/"
@@ -50,7 +47,10 @@ export function PlaygroundPage() {
5047
<div className="flex w-full min-w-0 items-center gap-2 sm:w-auto sm:gap-3">
5148
<ThemeToggle />
5249
<Select value={selected} onValueChange={setSelected}>
53-
<SelectTrigger className="h-8 w-full min-w-0 rounded-none sm:w-52">
50+
<SelectTrigger
51+
aria-label="Pick a button"
52+
className="h-8 w-full min-w-0 rounded-none sm:w-52"
53+
>
5454
<SelectValue placeholder="Pick a button" />
5555
</SelectTrigger>
5656
<SelectContent className="rounded-none">

components/mdx.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,16 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents {
5454
p: ({ className, ...props }: ComponentPropsWithoutRef<"p">) => (
5555
<p className={cn("mt-4 text-base leading-8 text-muted-foreground", className)} {...props} />
5656
),
57-
a: ({ className, ...props }: ComponentPropsWithoutRef<"a">) => (
57+
a: ({ className, children, ...props }: ComponentPropsWithoutRef<"a">) => (
5858
<a
5959
className={cn(
6060
"font-medium text-foreground underline decoration-border underline-offset-4 transition-colors hover:text-primary",
6161
className,
6262
)}
6363
{...props}
64-
/>
64+
>
65+
{children}
66+
</a>
6567
),
6668
ul: ({ className, ...props }: ComponentPropsWithoutRef<"ul">) => (
6769
<ul className={cn("mt-4 ml-6 flex list-disc flex-col gap-2 text-muted-foreground", className)} {...props} />

0 commit comments

Comments
 (0)