From 10cec3b638348fe14539769d3ec890ed3816aacb Mon Sep 17 00:00:00 2001 From: Loki Date: Wed, 14 Oct 2026 16:27:43 +0800 Subject: [PATCH] feat(theme-provider): ship the system-theme provider as a block Co-Authored-By: Claude Opus 5.5 --- DESIGN.md | 2 +- README.md | 1 + app/layout.tsx | 2 +- registry.json | 15 +++++++++++++++ .../blocks/theme-provider}/theme-provider.tsx | 4 ++++ 5 files changed, 22 insertions(+), 2 deletions(-) rename {components => registry/winlab/blocks/theme-provider}/theme-provider.tsx (83%) diff --git a/DESIGN.md b/DESIGN.md index 4ab4f8f..dc71d40 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -14,7 +14,7 @@ Every WinLab app is framed by four fixed corners, 24px in from the viewport. `ap | Bottom right | Whose it is | © year; its tip says NYCU WinLab | - Actions are not navigation: a "新增訂單" button goes in the page header next to the content it changes, never in a corner. -- There is no theme toggle: the theme follows the system. +- There is no theme toggle: the theme follows the system (`theme-provider`). D switches themes, outside text fields, so a page can be checked in both. - CD passes `BUILD_SHA`, `BUILD_TIME` and `BUILD_URL` to the image build (`NEXT_PUBLIC_BUILD_*`); without them the version reads `dev`. - A corner with nothing to show stays empty. - Every corner item has a tip (`tip` is required in `app-shell`, so a missing one fails the type check) that says what its label leaves out (where a link goes, what a page holds, who owns the site). A tip never repeats the label. Tips open at once, toward the page, lined up with the corner's outer edge; touch never opens them, so nothing depends on one. diff --git a/README.md b/README.md index d2b5db6..5bf37d3 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ npx shadcn@latest add @winlab/button | `progress` | `registry:ui` | How much of a whole is used: an 8px muted track with a primary fill | | `chart` | `registry:ui` | Recharts in WinLab colors: series take `primary`, `muted-foreground` or a state color; inverted tooltip and a legend | | `app-shell` | `registry:block` | The four corners of every WinLab app (pages fold into a menu on narrow screens; the account can be a link or a menu) and the page layouts `column`, `spotlight` and `wide`; set `NEXT_PUBLIC_BUILD_SHA`, `_TIME` and `_URL` at build for the version corner | +| `theme-provider` | `registry:block` | Wrap the app in it: the theme follows the system (no toggle), D switches themes while checking a page; give `` `suppressHydrationWarning` | | `confirm-dialog` | `registry:block` | Asks before an action runs; the confirm button names the verb, both buttons lock while it runs, and it closes only when the action finishes | | `form-dialog` | `registry:block` | A short form in a dialog; `FormField` puts a label over its control, fields and buttons lock while it submits, and it closes only when the submit finishes | | `empty-state` | `registry:block` | What a list shows with no rows: "還沒有{noun}" or "找不到符合「{query}」的{noun}", with the next action; `TableEmpty` is the same sentence as a table row | diff --git a/app/layout.tsx b/app/layout.tsx index 0612688..305b9f5 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -7,7 +7,7 @@ import { } from "next/font/google" import "./globals.css" -import { ThemeProvider } from "@/components/theme-provider" +import { ThemeProvider } from "@/registry/winlab/blocks/theme-provider/theme-provider" import { cn } from "@/lib/utils" import { Toaster } from "@/registry/winlab/ui/sonner" diff --git a/registry.json b/registry.json index fbc4548..8707acd 100644 --- a/registry.json +++ b/registry.json @@ -978,6 +978,21 @@ } ] }, + { + "name": "theme-provider", + "type": "registry:block", + "title": "Theme Provider", + "description": "The theme follows the system, with D to switch while checking a page; wrap the app in it.", + "dependencies": [ + "next-themes" + ], + "files": [ + { + "path": "registry/winlab/blocks/theme-provider/theme-provider.tsx", + "type": "registry:component" + } + ] + }, { "name": "confirm-dialog", "type": "registry:block", diff --git a/components/theme-provider.tsx b/registry/winlab/blocks/theme-provider/theme-provider.tsx similarity index 83% rename from components/theme-provider.tsx rename to registry/winlab/blocks/theme-provider/theme-provider.tsx index 3d44f48..4732734 100644 --- a/components/theme-provider.tsx +++ b/registry/winlab/blocks/theme-provider/theme-provider.tsx @@ -3,6 +3,10 @@ import * as React from "react" import { ThemeProvider as NextThemesProvider, useTheme } from "next-themes" +// The theme follows the system; there is no toggle in the interface. D +// switches between light and dark (outside text fields) so a page can be +// checked in both. next-themes puts .dark on , which the base's dark +// variant reads; give suppressHydrationWarning. function ThemeProvider({ children, ...props