diff --git a/examples/with-cache-components/.gitignore b/examples/with-cache-components/.gitignore new file mode 100644 index 00000000..aa5ebcde --- /dev/null +++ b/examples/with-cache-components/.gitignore @@ -0,0 +1,11 @@ +# dependencies +node_modules + +# next.js +.next +.turbo + +# misc +.DS_Store +*.tsbuildinfo +next-env.d.ts diff --git a/examples/with-cache-components/README.md b/examples/with-cache-components/README.md new file mode 100644 index 00000000..cce72d04 --- /dev/null +++ b/examples/with-cache-components/README.md @@ -0,0 +1,11 @@ +# next-themes with Cache Components + +Demonstrates next-themes working with Next.js [Cache Components](https://nextjs.org/docs/app/guides/cache-components) (`cacheComponents: true`). + +When Cache Components keeps multiple routes mounted in the DOM, theme providers that store state in React can go stale. The `useSyncExternalStore` approach in next-themes ensures all mounted routes share the same theme. + +## Running + +```bash +pnpm dev +``` diff --git a/examples/with-cache-components/app/[locale]/event-log.tsx b/examples/with-cache-components/app/[locale]/event-log.tsx new file mode 100644 index 00000000..29ad8367 --- /dev/null +++ b/examples/with-cache-components/app/[locale]/event-log.tsx @@ -0,0 +1,74 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; + +export function EventLog() { + const [entries, setEntries] = useState([]); + const scrollRef = useRef(null); + + useEffect(() => { + const html = document.documentElement; + + function log(msg: string) { + const time = new Date().toLocaleTimeString("en", { + hour12: false, + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }); + setEntries((prev) => [...prev.slice(-19), `${time} ${msg}`]); + } + + log(`initial data-theme="${html.getAttribute("data-theme")}"`); + + const observer = new MutationObserver(() => { + log(`data-theme changed to "${html.getAttribute("data-theme")}"`); + }); + + observer.observe(html, { + attributes: true, + attributeFilter: ["data-theme"], + }); + + return () => observer.disconnect(); + }, []); + + useEffect(() => { + scrollRef.current?.scrollTo(0, scrollRef.current.scrollHeight); + }, [entries]); + + return ( +
+
+ Event log (watching data-theme attribute) +
+
+ {entries.length === 0 ? ( +

+ No events yet... +

+ ) : ( + entries.map((entry, i) =>
{entry}
) + )} +
+
+ ); +} + +const styles = { + header: { + borderBottom: "1px solid var(--border)", + padding: "0.5rem 0.75rem", + fontSize: "0.75rem", + fontWeight: 500, + color: "var(--muted)", + }, + log: { + height: "10rem", + overflowY: "auto" as const, + padding: "0.75rem", + fontFamily: "monospace", + fontSize: "0.75rem", + color: "var(--muted)", + }, +} satisfies Record; diff --git a/examples/with-cache-components/app/[locale]/layout.tsx b/examples/with-cache-components/app/[locale]/layout.tsx new file mode 100644 index 00000000..a32d2ef5 --- /dev/null +++ b/examples/with-cache-components/app/[locale]/layout.tsx @@ -0,0 +1,47 @@ +import { ThemeProvider } from "next-themes"; + +export function generateStaticParams() { + return [{ locale: "en" }, { locale: "es" }]; +} + +const labels: Record = { + en: "English", + es: "Español", +}; + +export default async function LocaleLayout({ + children, + params, +}: { + children: React.ReactNode; + params: Promise<{ locale: string }>; +}) { + const { locale } = await params; + + return ( + +
+

+ Locale: {labels[locale] ?? locale} +

+ {children} +
+
+ ); +} + +const styles = { + container: { + maxWidth: "36rem", + margin: "0 auto", + padding: "2rem 1.5rem", + }, + badge: { + marginBottom: "1.5rem", + background: "var(--surface)", + padding: "0.5rem 0.75rem", + borderRadius: "0.375rem", + fontFamily: "monospace", + fontSize: "0.75rem", + }, +} satisfies Record; diff --git a/examples/with-cache-components/app/[locale]/page.tsx b/examples/with-cache-components/app/[locale]/page.tsx new file mode 100644 index 00000000..fe807d76 --- /dev/null +++ b/examples/with-cache-components/app/[locale]/page.tsx @@ -0,0 +1,81 @@ +import { ThemeToggle } from "./theme-toggle"; +import { EventLog } from "./event-log"; + +const content: Record = { + en: { + title: "Hello", + description: "Toggle the theme, switch locale, and see what happens.", + }, + es: { + title: "Hola", + description: "Cambia el tema, cambia de idioma, y mira lo que pasa.", + }, +}; + +export default async function Page({ + params, +}: { + params: Promise<{ locale: string }>; +}) { + const { locale } = await params; + const { title, description } = content[locale] ?? content.en; + + return ( +
+
+

{title}

+

+ {description} +

+
+ + + +
+

How it works

+
    +
  1. + Each locale (/en, /es) is a separate + route. +
  2. +
  3. + With cacheComponents enabled, navigating between + locales keeps both routes mounted in the DOM. +
  4. +
  5. + next-themes uses useSyncExternalStore{" "} + backed by localStorage so the theme stays consistent + across all mounted routes. +
  6. +
  7. + Toggle the theme, switch locale, and check the event log below. +
  8. +
+
+ + +
+ ); +} + +const styles = { + card: { + border: "1px solid var(--border)", + borderRadius: "0.5rem", + padding: "1rem", + }, + cardTitle: { + marginBottom: "0.5rem", + fontSize: "0.875rem", + fontWeight: 500, + color: "var(--muted)", + }, + list: { + paddingLeft: "1.25rem", + fontSize: "0.875rem", + color: "var(--muted)", + display: "flex", + flexDirection: "column" as const, + gap: "0.25rem", + }, +} satisfies Record; diff --git a/examples/with-cache-components/app/[locale]/theme-toggle.tsx b/examples/with-cache-components/app/[locale]/theme-toggle.tsx new file mode 100644 index 00000000..208a0ba0 --- /dev/null +++ b/examples/with-cache-components/app/[locale]/theme-toggle.tsx @@ -0,0 +1,32 @@ +"use client"; + +import { useTheme } from "next-themes"; + +export function ThemeToggle() { + const { resolvedTheme, setTheme } = useTheme(); + + return ( +
+ + Current theme: + + +
+ ); +} diff --git a/examples/with-cache-components/app/globals.css b/examples/with-cache-components/app/globals.css new file mode 100644 index 00000000..08b590b3 --- /dev/null +++ b/examples/with-cache-components/app/globals.css @@ -0,0 +1,39 @@ +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +:root { + --bg: #ffffff; + --fg: #171717; + --border: rgba(0, 0, 0, 0.1); + --muted: rgba(0, 0, 0, 0.5); + --surface: rgba(0, 0, 0, 0.04); +} + +[data-theme="dark"] { + --bg: #0a0a0a; + --fg: #ededed; + --border: rgba(255, 255, 255, 0.1); + --muted: rgba(255, 255, 255, 0.5); + --surface: rgba(255, 255, 255, 0.06); +} + +body { + background: var(--bg); + color: var(--fg); + font-family: system-ui, -apple-system, sans-serif; + line-height: 1.5; +} + +a { + color: inherit; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} diff --git a/examples/with-cache-components/app/layout.tsx b/examples/with-cache-components/app/layout.tsx new file mode 100644 index 00000000..49f574db --- /dev/null +++ b/examples/with-cache-components/app/layout.tsx @@ -0,0 +1,39 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import "./globals.css"; + +export const metadata: Metadata = { + title: "next-themes + Cache Components", +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + + {children} + + + ); +} + +const styles = { + nav: { + display: "flex", + alignItems: "center", + gap: "1.5rem", + borderBottom: "1px solid var(--border)", + padding: "1rem 1.5rem", + }, + title: { + fontWeight: 600, + }, +} satisfies Record; diff --git a/examples/with-cache-components/app/page.tsx b/examples/with-cache-components/app/page.tsx new file mode 100644 index 00000000..37fbf412 --- /dev/null +++ b/examples/with-cache-components/app/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from "next/navigation"; + +export default function Home() { + redirect("/en"); +} diff --git a/examples/with-cache-components/next.config.ts b/examples/with-cache-components/next.config.ts new file mode 100644 index 00000000..584cbfce --- /dev/null +++ b/examples/with-cache-components/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + cacheComponents: true, +}; + +export default nextConfig; diff --git a/examples/with-cache-components/package.json b/examples/with-cache-components/package.json new file mode 100644 index 00000000..bd1250a4 --- /dev/null +++ b/examples/with-cache-components/package.json @@ -0,0 +1,21 @@ +{ + "name": "with-cache-components", + "private": true, + "scripts": { + "dev": "next dev --turbopack", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "next": "^16", + "next-themes": "workspace:*", + "react": "^19", + "react-dom": "^19" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "typescript": "^5" + } +} diff --git a/examples/with-cache-components/tsconfig.json b/examples/with-cache-components/tsconfig.json new file mode 100644 index 00000000..e7ff3a26 --- /dev/null +++ b/examples/with-cache-components/tsconfig.json @@ -0,0 +1,41 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": [ + "./*" + ] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/playwright.config.ts b/playwright.config.ts index 6d6a308c..455c61c2 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -5,20 +5,33 @@ const config: PlaywrightTestConfig = { retries: process.env.CI ? 2 : 0, reporter: process.env.CI ? 'github' : 'list', testDir: './test', - webServer: { - command: 'pnpm start --filter=example...', - port: 3000, - reuseExistingServer: !process.env.CI, - timeout: 120 * 1000 - }, + webServer: [ + { + command: 'pnpm start --filter=example...', + port: 3000, + reuseExistingServer: !process.env.CI, + timeout: 120 * 1000 + }, + { + command: 'pnpm --filter=with-cache-components build && pnpm --filter=with-cache-components exec next start --port 3001', + port: 3001, + reuseExistingServer: !process.env.CI, + timeout: 120 * 1000 + } + ], use: { - trace: 'on-first-retry', - baseURL: 'http://localhost:3000' + trace: 'on-first-retry' }, projects: [ { name: 'chromium', - use: { ...devices['Desktop Chrome'] } + use: { ...devices['Desktop Chrome'], baseURL: 'http://localhost:3000' }, + testIgnore: /cache-components/ + }, + { + name: 'cache-components', + use: { ...devices['Desktop Chrome'], baseURL: 'http://localhost:3001' }, + testMatch: /cache-components/ } ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c20d90b..83749b24 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ importers: version: 1.51.0 '@testing-library/react': specifier: ^14.2.1 - version: 14.2.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 14.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@types/node': specifier: 20.5.7 version: 20.5.7 @@ -143,6 +143,34 @@ importers: specifier: 3.3.3 version: 3.3.3 + examples/with-cache-components: + dependencies: + next: + specifier: ^16 + version: 16.1.6(@playwright/test@1.51.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + next-themes: + specifier: workspace:* + version: link:../../next-themes + react: + specifier: ^19 + version: 19.2.4 + react-dom: + specifier: ^19 + version: 19.2.4(react@19.2.4) + devDependencies: + '@types/node': + specifier: ^20 + version: 20.5.7 + '@types/react': + specifier: ^19 + version: 19.2.14 + '@types/react-dom': + specifier: ^19 + version: 19.2.3(@types/react@19.2.14) + typescript: + specifier: ^5 + version: 5.4.3 + next-themes: devDependencies: react: @@ -174,6 +202,9 @@ packages: resolution: {integrity: sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==} engines: {node: '>=6.9.0'} + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -462,6 +493,159 @@ packages: cpu: [x64] os: [win32] + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -494,41 +678,88 @@ packages: '@next/env@14.2.21': resolution: {integrity: sha512-lXcwcJd5oR01tggjWJ6SrNNYFGuOOMB9c251wUNkjCpkoXOPkDeF/15c3mnVlBqrW4JJXb2kVxDFhC4GduJt2A==} + '@next/env@16.1.6': + resolution: {integrity: sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==} + '@next/swc-darwin-arm64@14.2.21': resolution: {integrity: sha512-HwEjcKsXtvszXz5q5Z7wCtrHeTTDSTgAbocz45PHMUjU3fBYInfvhR+ZhavDRUYLonm53aHZbB09QtJVJj8T7g==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] + '@next/swc-darwin-arm64@16.1.6': + resolution: {integrity: sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + '@next/swc-darwin-x64@14.2.21': resolution: {integrity: sha512-TSAA2ROgNzm4FhKbTbyJOBrsREOMVdDIltZ6aZiKvCi/v0UwFmwigBGeqXDA97TFMpR3LNNpw52CbVelkoQBxA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] + '@next/swc-darwin-x64@16.1.6': + resolution: {integrity: sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + '@next/swc-linux-arm64-gnu@14.2.21': resolution: {integrity: sha512-0Dqjn0pEUz3JG+AImpnMMW/m8hRtl1GQCNbO66V1yp6RswSTiKmnHf3pTX6xMdJYSemf3O4Q9ykiL0jymu0TuA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] + + '@next/swc-linux-arm64-gnu@16.1.6': + resolution: {integrity: sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] '@next/swc-linux-arm64-musl@14.2.21': resolution: {integrity: sha512-Ggfw5qnMXldscVntwnjfaQs5GbBbjioV4B4loP+bjqNEb42fzZlAaK+ldL0jm2CTJga9LynBMhekNfV8W4+HBw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] + + '@next/swc-linux-arm64-musl@16.1.6': + resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] '@next/swc-linux-x64-gnu@14.2.21': resolution: {integrity: sha512-uokj0lubN1WoSa5KKdThVPRffGyiWlm/vCc/cMkWOQHw69Qt0X1o3b2PyLLx8ANqlefILZh1EdfLRz9gVpG6tg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] + + '@next/swc-linux-x64-gnu@16.1.6': + resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] '@next/swc-linux-x64-musl@14.2.21': resolution: {integrity: sha512-iAEBPzWNbciah4+0yI4s7Pce6BIoxTQ0AGCkxn/UBuzJFkYyJt71MadYQkjPqCQCJAFQ26sYh7MOKdU+VQFgPg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] + + '@next/swc-linux-x64-musl@16.1.6': + resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] '@next/swc-win32-arm64-msvc@14.2.21': resolution: {integrity: sha512-plykgB3vL2hB4Z32W3ktsfqyuyGAPxqwiyrAi2Mr8LlEUhNn9VgkiAl5hODSBpzIfWweX3er1f5uNpGDygfQVQ==} @@ -536,6 +767,12 @@ packages: cpu: [arm64] os: [win32] + '@next/swc-win32-arm64-msvc@16.1.6': + resolution: {integrity: sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + '@next/swc-win32-ia32-msvc@14.2.21': resolution: {integrity: sha512-w5bacz4Vxqrh06BjWgua3Yf7EMDb8iMcVhNrNx8KnJXt8t+Uu0Zg4JHLDL/T7DkTCEEfKXO/Er1fcfWxn2xfPA==} engines: {node: '>= 10'} @@ -548,6 +785,12 @@ packages: cpu: [x64] os: [win32] + '@next/swc-win32-x64-msvc@16.1.6': + resolution: {integrity: sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -603,51 +846,61 @@ packages: resolution: {integrity: sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.35.0': resolution: {integrity: sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.35.0': resolution: {integrity: sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.35.0': resolution: {integrity: sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.35.0': resolution: {integrity: sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.35.0': resolution: {integrity: sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.35.0': resolution: {integrity: sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.35.0': resolution: {integrity: sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.35.0': resolution: {integrity: sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.35.0': resolution: {integrity: sha512-QysqXzYiDvQWfUiTm8XmJNO2zm9yC9P/2Gkrwg2dH9cxotQzunBHYr6jk4SujCTqnfGxduOmQcI7c2ryuW8XVg==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.35.0': resolution: {integrity: sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg==} @@ -670,6 +923,9 @@ packages: '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + '@swc/helpers@0.5.5': resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} @@ -699,9 +955,17 @@ packages: '@types/react-dom@18.2.7': resolution: {integrity: sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==} + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + '@types/react@18.2.72': resolution: {integrity: sha512-/e7GWxGzXQF7OJAua7UAYqYi/4VpXEfbGtmYQcAQwP3SjjjAXfybTf/JK5S+SaetB/ChXl8Y2g1hCsj7jDXxcg==} + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@vitest/expect@1.6.1': resolution: {integrity: sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==} @@ -791,6 +1055,11 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + baseline-browser-mapping@2.10.0: + resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} + engines: {node: '>=6.0.0'} + hasBin: true + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -905,6 +1174,9 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} @@ -941,6 +1213,10 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -1373,6 +1649,27 @@ packages: sass: optional: true + next@16.1.6: + resolution: {integrity: sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==} + engines: {node: '>=20.9.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} @@ -1582,6 +1879,11 @@ packages: peerDependencies: react: ^18.2.0 + react-dom@19.2.4: + resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + peerDependencies: + react: ^19.2.4 + react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} @@ -1592,6 +1894,10 @@ packages: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} + react@19.2.4: + resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + engines: {node: '>=0.10.0'} + read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} @@ -1646,6 +1952,14 @@ packages: scheduler@0.23.0: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -1654,6 +1968,10 @@ packages: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1735,6 +2053,19 @@ packages: babel-plugin-macros: optional: true + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -2057,6 +2388,11 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.21.5': optional: true @@ -2201,6 +2537,103 @@ snapshots: '@esbuild/win32-x64@0.24.2': optional: true + '@img/colour@1.0.0': + optional: true + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.8.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -2235,33 +2668,59 @@ snapshots: '@next/env@14.2.21': {} + '@next/env@16.1.6': {} + '@next/swc-darwin-arm64@14.2.21': optional: true + '@next/swc-darwin-arm64@16.1.6': + optional: true + '@next/swc-darwin-x64@14.2.21': optional: true + '@next/swc-darwin-x64@16.1.6': + optional: true + '@next/swc-linux-arm64-gnu@14.2.21': optional: true + '@next/swc-linux-arm64-gnu@16.1.6': + optional: true + '@next/swc-linux-arm64-musl@14.2.21': optional: true + '@next/swc-linux-arm64-musl@16.1.6': + optional: true + '@next/swc-linux-x64-gnu@14.2.21': optional: true + '@next/swc-linux-x64-gnu@16.1.6': + optional: true + '@next/swc-linux-x64-musl@14.2.21': optional: true + '@next/swc-linux-x64-musl@16.1.6': + optional: true + '@next/swc-win32-arm64-msvc@14.2.21': optional: true + '@next/swc-win32-arm64-msvc@16.1.6': + optional: true + '@next/swc-win32-ia32-msvc@14.2.21': optional: true '@next/swc-win32-x64-msvc@14.2.21': optional: true + '@next/swc-win32-x64-msvc@16.1.6': + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2342,6 +2801,10 @@ snapshots: '@swc/counter@0.1.3': {} + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + '@swc/helpers@0.5.5': dependencies: '@swc/counter': 0.1.3 @@ -2358,13 +2821,13 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 - '@testing-library/react@14.2.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@testing-library/react@14.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@babel/runtime': 7.24.1 '@testing-library/dom': 9.3.4 '@types/react-dom': 18.2.7 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) '@types/aria-query@5.0.4': {} @@ -2378,11 +2841,19 @@ snapshots: dependencies: '@types/react': 18.2.72 + '@types/react-dom@19.2.3(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + '@types/react@18.2.72': dependencies: '@types/prop-types': 15.7.12 csstype: 3.1.3 + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + '@vitest/expect@1.6.1': dependencies: '@vitest/spy': 1.6.1 @@ -2478,6 +2949,8 @@ snapshots: balanced-match@1.0.2: {} + baseline-browser-mapping@2.10.0: {} + binary-extensions@2.3.0: {} brace-expansion@2.0.1: @@ -2599,6 +3072,8 @@ snapshots: csstype@3.1.3: {} + csstype@3.2.3: {} + data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 @@ -2649,6 +3124,9 @@ snapshots: delayed-stream@1.0.0: {} + detect-libc@2.1.2: + optional: true + didyoumean@1.2.2: {} diff-sequences@29.6.3: {} @@ -3118,6 +3596,31 @@ snapshots: - '@babel/core' - babel-plugin-macros + next@16.1.6(@playwright/test@1.51.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + '@next/env': 16.1.6 + '@swc/helpers': 0.5.15 + baseline-browser-mapping: 2.10.0 + caniuse-lite: 1.0.30001703 + postcss: 8.4.31 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + styled-jsx: 5.1.6(react@19.2.4) + optionalDependencies: + '@next/swc-darwin-arm64': 16.1.6 + '@next/swc-darwin-x64': 16.1.6 + '@next/swc-linux-arm64-gnu': 16.1.6 + '@next/swc-linux-arm64-musl': 16.1.6 + '@next/swc-linux-x64-gnu': 16.1.6 + '@next/swc-linux-x64-musl': 16.1.6 + '@next/swc-win32-arm64-msvc': 16.1.6 + '@next/swc-win32-x64-msvc': 16.1.6 + '@playwright/test': 1.51.0 + sharp: 0.34.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + node-releases@2.0.14: {} normalize-path@3.0.0: {} @@ -3285,6 +3788,11 @@ snapshots: react: 18.2.0 scheduler: 0.23.0 + react-dom@19.2.4(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + react-is@17.0.2: {} react-is@18.3.1: {} @@ -3293,6 +3801,8 @@ snapshots: dependencies: loose-envify: 1.4.0 + react@19.2.4: {} + read-cache@1.0.0: dependencies: pify: 2.3.0 @@ -3365,6 +3875,11 @@ snapshots: dependencies: loose-envify: 1.4.0 + scheduler@0.27.0: {} + + semver@7.7.4: + optional: true + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -3381,6 +3896,38 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 + sharp@0.34.5: + dependencies: + '@img/colour': 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + optional: true + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -3447,6 +3994,11 @@ snapshots: client-only: 0.0.1 react: 18.2.0 + styled-jsx@5.1.6(react@19.2.4): + dependencies: + client-only: 0.0.1 + react: 19.2.4 + sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.5 diff --git a/test/cache-components.test.ts b/test/cache-components.test.ts new file mode 100644 index 00000000..122b0495 --- /dev/null +++ b/test/cache-components.test.ts @@ -0,0 +1,85 @@ +import { test, expect } from '@playwright/test' +import { makeBrowserContext } from './util' + +test.describe('cache-components test-suite', () => { + test('should keep theme consistent when navigating between locale routes', async ({ + browser, + baseURL + }) => { + const context = await makeBrowserContext(browser, { + baseURL, + localStorage: [{ name: 'theme', value: 'light' }] + }) + const page = await context.newPage() + + await page.goto('/en') + await expect(page.locator('html')).toHaveAttribute('data-theme', 'light') + + // Switch to dark + await page.getByRole('button', { name: /click to toggle/i }).click() + await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark') + + // Navigate to other locale + await page.getByRole('link', { name: 'Español' }).click() + await page.waitForURL('**/es') + + // Theme should still be dark + await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark') + + // Switch back to light on /es + await page.getByRole('button', { name: /click to toggle/i }).click() + await expect(page.locator('html')).toHaveAttribute('data-theme', 'light') + + // Navigate back to /en + await page.getByRole('link', { name: 'English' }).click() + await page.waitForURL('**/en') + + // Theme should still be light (not stale dark from the hidden /en route) + await expect(page.locator('html')).toHaveAttribute('data-theme', 'light') + }) + + test('should persist theme across locale navigations with multiple round-trips', async ({ + browser, + baseURL + }) => { + const context = await makeBrowserContext(browser, { + baseURL, + localStorage: [{ name: 'theme', value: 'dark' }] + }) + const page = await context.newPage() + + await page.goto('/en') + await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark') + + // Navigate back and forth several times + await page.getByRole('link', { name: 'Español' }).click() + await page.waitForURL('**/es') + await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark') + + await page.getByRole('link', { name: 'English' }).click() + await page.waitForURL('**/en') + await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark') + + // Now toggle and navigate again + await page.getByRole('button', { name: /click to toggle/i }).click() + await expect(page.locator('html')).toHaveAttribute('data-theme', 'light') + + await page.getByRole('link', { name: 'Español' }).click() + await page.waitForURL('**/es') + await expect(page.locator('html')).toHaveAttribute('data-theme', 'light') + }) + + test('should sync localStorage when theme is toggled', async ({ browser, baseURL }) => { + const context = await makeBrowserContext(browser, { + baseURL, + localStorage: [{ name: 'theme', value: 'light' }] + }) + const page = await context.newPage() + + await page.goto('/en') + await page.getByRole('button', { name: /click to toggle/i }).click() + + const stored = await page.evaluate(() => localStorage.getItem('theme')) + expect(stored).toBe('dark') + }) +})