Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ jobs:
grep -q 'rounded-menu' components/ui/attachment.tsx
bunx shadcn@latest add http://localhost:8099/r/file-upload.json -y -o
grep -q 'matchesAccept' components/file-upload.tsx
# Every other item too, so a new one cannot ship without being
# installed and built at least once.
items=$(python3 -c 'import json; print(" ".join(f"http://localhost:8099/r/{i["name"]}.json" for i in json.load(open("../../registry/r/registry.json"))["items"] if i["type"] in ("registry:ui", "registry:component", "registry:block")))')
bunx shadcn@latest add $items -y -o
bun run build
- if: always()
run: docker compose logs
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ npx shadcn@latest add @winlab/button
| `alert-dialog` | `registry:ui` | Confirmation with Cancel and an action |
| `select` | `registry:ui` | Single-choice dropdown, same height as `input` |
| `table` | `registry:ui` | Data table with muted headers; add `tabular-nums` and `text-right` to number columns |
| `popover` | `registry:ui` | Frosted menu surface anchored to a trigger; `w-(--anchor-width)` matches the trigger |
| `popover` | `registry:ui` | Frosted menu surface anchored to a trigger, 288px wide |
| `command` | `registry:ui` | Searchable list for a combobox inside a `popover` |
| `combobox` | `registry:ui` | `ComboboxTrigger` (same field look as `select`) and `ComboboxContent` (as wide as the trigger) around a `command` |
| `combobox` | `registry:ui` | `ComboboxTrigger` (same field look as `select`) and `ComboboxContent` (at least as wide as the trigger) around a `command` |
| `dropdown-menu` | `registry:ui` | Action menu: items, labels, separators and submenus |
| `calendar` | `registry:ui` | One month of 40px days to pick a single date; weeks start on Monday, captions read 2026 年 10 月 |
| `attachment` | `registry:ui` | One file per row: 40px icon or thumbnail, name, a detail line and actions; states `idle` (dashed), `uploading`, `error`, `done` |
Expand All @@ -67,6 +67,8 @@ npx shadcn@latest add @winlab/button
| `separator` | `registry:ui` | Hairline divider |
| `collapsible` | `registry:ui` | Unstyled show / hide region |
| `sonner` | `registry:ui` | Toasts: frosted surface, status-colored icons, action and cancel buttons |
| `number-ticker` | `registry:ui` | A whole number whose digits roll to their new value |
| `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 and the page layouts `column`, `spotlight` and `wide`; set `NEXT_PUBLIC_BUILD_SHA`, `_TIME` and `_URL` at build for the version corner |
| `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 |
Expand All @@ -79,6 +81,11 @@ npx shadcn@latest add @winlab/button
| `date-picker` | `registry:block` | A date field with the same closed look as `select`; opens a `calendar`, closes on choice, can block days, and posts yyyy-mm-dd under `name` for `form-dialog` |
| `action-panel` | `registry:block` | A page whose job is one action, such as opening the door: one 192px round button that locks while it runs, turns `success` for a moment, and says why when it cannot be pressed |
| `file-upload` | `registry:block` | Drop, paste or pick files; chosen files list as `attachment` rows to remove, files outside `accept` or `maxSize` show as errors with the reason, and `name` posts them inside `form-dialog` |
| `signature-pad` | `registry:block` | Draw or upload a handwritten signature in a dialog; black ink on white in every theme, exported as a cropped transparent PNG |
| `focus` | `registry:block` | The one loud thing on a page, under its header: a label, a title with a highlight and a row of facts |
| `avatar-stack` | `registry:block` | Several people in the space of one; the avatars spread apart when pointed at |
| `countdown` | `registry:block` | Time left until a moment, to the minute, with rolling digits |
| `expand-row` | `registry:block` | A list row that opens in place to show its details |
| `badge` | `registry:ui` | Status label; variants `default`, `muted`, `outline`, `destructive`, `success`, `warning` |

Text falls back in that order: `font-sans` is Inter, then Noto Sans JP, then Noto Sans TC. `font-mono` puts JetBrains Mono in front of the same CJK fonts. Use Inter's `tabular-nums` for amounts and table figures, not `font-mono`.
Expand Down
13 changes: 13 additions & 0 deletions scripts/check-theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,18 @@ for (const [mode, cssVars, registryVars] of pairs) {
}
}

// Utilities and keyframes the registry ships must exist here too, or the
// site renders an item differently from a project that installed it.
for (const item of registry.items) {
const rules = "css" in item && item.css ? Object.keys(item.css) : []
for (const rule of rules) {
if (!rule.startsWith("@utility") && !rule.startsWith("@keyframes")) continue
if (!css.includes(`${rule} {`)) {
console.error(`globals.css is missing ${rule} (from ${item.name})`)
failed = true
}
}
}

if (failed) process.exit(1)
console.log("theme in sync")
Loading