From c32692aa10e09bf07711f1b7df2f2b1ac6143ad2 Mon Sep 17 00:00:00 2001 From: Loki Date: Wed, 7 Oct 2026 23:33:24 +0800 Subject: [PATCH] feat!: drop description slots from blocks; copy rules Co-Authored-By: Claude Opus 5.5 --- DESIGN.md | 9 ++++++++ README.md | 2 +- app/_docs/demos/confirm-dialog.tsx | 2 -- app/_docs/demos/page-header.tsx | 6 +----- app/_docs/demos/sonner.tsx | 19 ++++------------- app/_docs/demos/status-page.tsx | 1 - app/_docs/docs.ts | 4 ++-- app/not-found.tsx | 1 - registry.json | 2 +- .../blocks/confirm-dialog/confirm-dialog.tsx | 4 ---- .../winlab/blocks/form-dialog/form-dialog.tsx | 6 ------ .../winlab/blocks/page-header/page-header.tsx | 21 +++++++------------ .../winlab/blocks/status-page/status-page.tsx | 16 ++++---------- 13 files changed, 29 insertions(+), 64 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index 0399675..b15464e 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -82,6 +82,15 @@ Color means state: something that changes as work moves on. A category (a kind, - Each app maps its status values to these variants in one place, next to the status labels, and every page reads that map. - A status that only applies to some rows shows nothing on the others; do not add a "normal" badge. +## Copy + +Say it once, in as few words as the thing needs. No sentence explains what the screen already shows. + +- Buttons are the verb: "登入", "刪除", "允許". While it runs, the verb + "中…". +- Titles name the thing or ask the question: "收據", "刪除這張收據?", "找不到這個頁面". There is no subtitle; blocks have no description slot, so a page cannot grow one. +- Toasts are the outcome or the reason, a few words: "已送出", "檔案超過 10 MB". +- Field labels carry what a field needs; there is no helper text (see Two layers). + ## Type Two sizes, by role: `text-title` (24px) for page and dialog titles, `text-body` (16px) for everything else. Weight and the muted color carry the rest: section titles `font-semibold`, labels and buttons `font-medium`, secondary text `text-muted-foreground`. diff --git a/README.md b/README.md index 3699d5c..b23e63a 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ npx shadcn@latest add @winlab/button | `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 | -| `page-header` | `registry:block` | The top of a page: `text-title` title, one line on what it holds, and the page's own actions on the right (under the title on a phone); `SectionHeader` is the same one level down | +| `page-header` | `registry:block` | The top of a page: `text-title` title, and the page's own actions on the right (under the title on a phone); `SectionHeader` is the same one level down | | `list-skeleton` | `registry:block` | Placeholder rows for a list (`ListSkeleton`) or a table body (`TableSkeleton`) on first load only, split by the same dividers as the real rows | | `member-combobox` | `registry:block` | Pick lab members by name or email; one member closes on choice, `multiple` keeps the menu open and toggles | | `field-list` | `registry:block` | One record's fields as a `dl`: muted names in a left column, values beside them (under them on a phone), split by dividers | diff --git a/app/_docs/demos/confirm-dialog.tsx b/app/_docs/demos/confirm-dialog.tsx index 9c373a8..a741ce2 100644 --- a/app/_docs/demos/confirm-dialog.tsx +++ b/app/_docs/demos/confirm-dialog.tsx @@ -17,14 +17,12 @@ export function Demo() { } title="刪除這張收據?" - description="刪除後無法復原。" confirmLabel="刪除" onConfirm={wait} /> 關閉訂單} title="關閉這筆訂單?" - description="關閉後成員就不能再點餐,之後可以重新開啟。" confirmLabel="關閉" variant="default" onConfirm={wait} diff --git a/app/_docs/demos/page-header.tsx b/app/_docs/demos/page-header.tsx index 108eea0..0414d86 100644 --- a/app/_docs/demos/page-header.tsx +++ b/app/_docs/demos/page-header.tsx @@ -7,11 +7,7 @@ import { Button } from "@/registry/winlab/ui/button" export function Demo() { return (
- 上傳收據} - /> + 上傳收據} /> 全部下載} diff --git a/app/_docs/demos/sonner.tsx b/app/_docs/demos/sonner.tsx index b8437b5..07c1aa0 100644 --- a/app/_docs/demos/sonner.tsx +++ b/app/_docs/demos/sonner.tsx @@ -7,34 +7,23 @@ import { Button } from "@/registry/winlab/ui/button" export function Demo() { return (
- - -
diff --git a/app/_docs/demos/status-page.tsx b/app/_docs/demos/status-page.tsx index 2732934..12517bd 100644 --- a/app/_docs/demos/status-page.tsx +++ b/app/_docs/demos/status-page.tsx @@ -7,7 +7,6 @@ export function Demo() { return ( window.location.reload()}>重試} /> ) diff --git a/app/_docs/docs.ts b/app/_docs/docs.ts index 44afed0..630fb49 100644 --- a/app/_docs/docs.ts +++ b/app/_docs/docs.ts @@ -58,7 +58,7 @@ export const docs: Doc[] = [ group: "區塊", slug: "page-header", title: "頁首", - description: "頁面標題、一句說明,與這頁的動作按鈕。", + description: "頁面標題與這頁的動作按鈕。", item: "page-header", }, { @@ -86,7 +86,7 @@ export const docs: Doc[] = [ group: "區塊", slug: "status-page", title: "狀態頁", - description: "找不到、出錯、沒有權限時,說明發生什麼事和下一步。", + description: "找不到、出錯、沒有權限時的標題與下一步。", item: "status-page", }, { diff --git a/app/not-found.tsx b/app/not-found.tsx index 7a81884..686272f 100644 --- a/app/not-found.tsx +++ b/app/not-found.tsx @@ -9,7 +9,6 @@ export default function NotFound() { 回到首頁 diff --git a/registry.json b/registry.json index 282f90f..7032aae 100644 --- a/registry.json +++ b/registry.json @@ -752,7 +752,7 @@ "name": "page-header", "type": "registry:block", "title": "Page Header", - "description": "The top of a page: title, one line on what it holds, and the page's own actions.", + "description": "The top of a page: its title and the page's own actions.", "dependencies": [], "files": [ { diff --git a/registry/winlab/blocks/confirm-dialog/confirm-dialog.tsx b/registry/winlab/blocks/confirm-dialog/confirm-dialog.tsx index 77e882b..a5b803a 100644 --- a/registry/winlab/blocks/confirm-dialog/confirm-dialog.tsx +++ b/registry/winlab/blocks/confirm-dialog/confirm-dialog.tsx @@ -6,7 +6,6 @@ import { AlertDialog, AlertDialogCancel, AlertDialogContent, - AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, @@ -21,7 +20,6 @@ type ConfirmDialogProps = { open?: boolean onOpenChange?: (open: boolean) => void title: string - description: React.ReactNode /** The verb on the confirm button: "刪除", "撤回". Never "確定". */ confirmLabel: string /** Shown while onConfirm runs; defaults to the verb + "中…". */ @@ -40,7 +38,6 @@ function ConfirmDialog({ open: openProp, onOpenChange, title, - description, confirmLabel, pendingLabel = `${confirmLabel}中…`, variant = "destructive", @@ -77,7 +74,6 @@ function ConfirmDialog({ {title} - {description} 取消 diff --git a/registry/winlab/blocks/form-dialog/form-dialog.tsx b/registry/winlab/blocks/form-dialog/form-dialog.tsx index a44445d..b4200b0 100644 --- a/registry/winlab/blocks/form-dialog/form-dialog.tsx +++ b/registry/winlab/blocks/form-dialog/form-dialog.tsx @@ -7,7 +7,6 @@ import { Dialog, DialogClose, DialogContent, - DialogDescription, DialogFooter, DialogHeader, DialogTitle, @@ -22,7 +21,6 @@ type FormDialogProps = { open?: boolean onOpenChange?: (open: boolean) => void title: string - description?: React.ReactNode size?: "default" | "wide" /** The verb on the submit button: "新增", "儲存". */ submitLabel: string @@ -43,7 +41,6 @@ function FormDialog({ open: openProp, onOpenChange, title, - description, size, submitLabel, pendingLabel = `${submitLabel}中…`, @@ -84,9 +81,6 @@ function FormDialog({
{title} - {description && ( - {description} - )}
{children} diff --git a/registry/winlab/blocks/page-header/page-header.tsx b/registry/winlab/blocks/page-header/page-header.tsx index 07b692e..2f91dfa 100644 --- a/registry/winlab/blocks/page-header/page-header.tsx +++ b/registry/winlab/blocks/page-header/page-header.tsx @@ -2,41 +2,34 @@ import * as React from "react" type HeaderProps = { title: React.ReactNode - /** One line under the title saying what the page holds. */ - description?: React.ReactNode /** Buttons that act on this page's content, such as 新增. Navigation goes * in the app shell's corners, not here. */ actions?: React.ReactNode } -// The top of a page: its title and what it holds on the left, the page's -// own actions on the right. On a phone the actions drop under the title. -function PageHeader({ title, description, actions }: HeaderProps) { +// The top of a page: its title on the left, the page's own actions on the +// right. No subtitle: the title says what the page is. On a phone the +// actions drop under the title. +function PageHeader({ title, actions }: HeaderProps) { return (
-
-

{title}

- {description &&

{description}

} -
+

{title}

{actions &&
{actions}
}
) } // A section title inside a page, one step below the page title by weight. -function SectionHeader({ title, description, actions }: HeaderProps) { +function SectionHeader({ title, actions }: HeaderProps) { return (
-
-

{title}

- {description &&

{description}

} -
+

{title}

{actions &&
{actions}
}
) diff --git a/registry/winlab/blocks/status-page/status-page.tsx b/registry/winlab/blocks/status-page/status-page.tsx index a07abe9..f064037 100644 --- a/registry/winlab/blocks/status-page/status-page.tsx +++ b/registry/winlab/blocks/status-page/status-page.tsx @@ -1,18 +1,13 @@ import * as React from "react" -// A page that only says what happened and what to do next: not found, an -// error, no access, signed out. Put it in app-shell's spotlight layout. -// Say it in Chinese, as a sentence: "找不到這個頁面", "這個頁面出了問題", -// "你沒有這個頁面的權限". +// A page that only says what happened and the way out: not found, an error, +// no access, signed out. Put it in app-shell's spotlight layout. The title +// says it all ("找不到這個頁面"); the action is a verb ("回到首頁", "重試"). function StatusPage({ title, - description, action, }: { title: string - /** Why it happened, in one sentence. */ - description: React.ReactNode - /** The way out: 回到首頁, 重試, 登入. */ action?: React.ReactNode }) { return ( @@ -20,10 +15,7 @@ function StatusPage({ data-slot="status-page" className="flex flex-col items-center gap-12 text-center" > -
-

{title}

-

{description}

-
+

{title}

{action}
)