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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ jobs:
test -f lib/field.ts
bunx shadcn@latest add http://localhost:8099/r/app-shell.json -y -o
grep -q 'NYCU WinLab' components/app-shell.tsx
grep -q 'layout === "wide"' components/app-shell.tsx
bunx shadcn@latest add http://localhost:8099/r/confirm-dialog.json -y -o
grep -q 'useTransition' components/confirm-dialog.tsx
bunx shadcn@latest add http://localhost:8099/r/form-dialog.json -y -o
Expand Down
5 changes: 4 additions & 1 deletion DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ Every WinLab app is framed by four fixed corners, 24px in from the viewport. `ap

## Page layouts

Two, chosen per page with `app-shell`'s `layout`:
Three, chosen per page with `app-shell`'s `layout`:

| Layout | For | Shape |
|--------|-----|-------|
| `column` | Pages read top to bottom: home, lists, tables, long forms | One centered column (`max-w-4xl`) starting under the top corners |
| `wide` | A grid that needs the whole screen: a two-week timetable, a room schedule | The full width between the corners' outer edges (24px in from the viewport), starting under the top corners |
| `spotlight` | Pages about one thing: a single item, sign-in, a result, an empty state, a not-found, a component's doc page | The content centered horizontally and vertically between the corners (`max-w-2xl`); taller content scrolls as a column |

Use `wide` only when the content is a grid that loses meaning when squeezed; text and lists stay in `column`, so lines never run across a whole monitor.

In a spotlight, the title and description center above the focal content; text that is the focus itself stays left-aligned when it runs to more than one line.

## Two layers
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ 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 |
| `app-shell` | `registry:block` | The four corners of every WinLab app; set `NEXT_PUBLIC_BUILD_SHA`, `_TIME` and `_URL` at build for the version corner |
| `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 |
| `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 |
Expand Down
4 changes: 2 additions & 2 deletions app/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ export default async function DocPage({
return (
<SiteShell
page={{ label: doc.title, href: `/${doc.slug}` }}
layout="spotlight"
layout={doc.layout ?? "spotlight"}
>
<div className="flex flex-col items-center gap-12">
<div className="flex w-full flex-col items-center gap-12">
<header className="flex flex-col items-center gap-2 text-center">
<h1 className="text-title font-medium">{doc.title}</h1>
<p className="text-muted-foreground">{doc.description}</p>
Expand Down
2 changes: 2 additions & 0 deletions app/_docs/demos/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Demo as MemberComboboxDemo } from "@/app/_docs/demos/member-combobox"
import { Demo as FieldListDemo } from "@/app/_docs/demos/field-list"
import { Demo as StatusPageDemo } from "@/app/_docs/demos/status-page"
import { Demo as DatePickerDemo } from "@/app/_docs/demos/date-picker"
import { Demo as Wide } from "@/app/_docs/demos/wide"
import { Demo as Button } from "@/app/_docs/demos/button"
import { Demo as Badge } from "@/app/_docs/demos/badge"
import { Demo as Input } from "@/app/_docs/demos/input"
Expand Down Expand Up @@ -38,6 +39,7 @@ import { Demo as Sonner } from "@/app/_docs/demos/sonner"
export const demos: Record<string, ComponentType> = {
colors: Colors,
"app-shell": AppShell,
wide: Wide,
"confirm-dialog": ConfirmDialog,
"form-dialog": FormDialogDemo,
"empty-state": EmptyStateDemo,
Expand Down
36 changes: 36 additions & 0 deletions app/_docs/demos/wide.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Fourteen days of room slots: the grid that wide exists for.
const days = Array.from({ length: 14 }, (_, index) => index + 5)
const hours = ["09", "10", "11", "13", "14", "15", "16"]
const booked = new Set(["7-10", "8-14", "12-09", "15-15", "16-11", "18-13"])

export function Demo() {
return (
<div className="w-full overflow-x-auto">
<div className="grid min-w-max grid-cols-15 gap-1 tabular-nums">
<span />
{days.map((day) => (
<span key={day} className="text-center text-muted-foreground">
10/{day}
</span>
))}
{hours.map((hour) => (
<div key={hour} className="contents">
<span className="pr-2 text-right text-muted-foreground">
{hour}:00
</span>
{days.map((day) => (
<span
key={day}
className={
booked.has(`${day}-${hour}`)
? "h-10 rounded-control bg-primary"
: "h-10 rounded-control bg-muted"
}
/>
))}
</div>
))}
</div>
</div>
)
}
9 changes: 9 additions & 0 deletions app/_docs/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export type Doc = {
description: string
/** Registry item to install, if the page is about one. */
item?: string
/** The page's app-shell layout; doc pages default to spotlight. */
layout?: "spotlight" | "wide"
}

export const docs: Doc[] = [
Expand All @@ -24,6 +26,13 @@ export const docs: Doc[] = [
"每個 WinLab app 的四個角,預設是麵包屑、導覽、使用者與版本、版權,也可以換成別的內容。",
item: "app-shell",
},
{
group: "版面",
slug: "wide",
title: "寬版面",
description: "時段表這類格子用滿四角之間的寬度,文字和清單不用。",
layout: "wide",
},
{
group: "區塊",
slug: "confirm-dialog",
Expand Down
2 changes: 1 addition & 1 deletion app/_docs/site-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export function SiteShell({
children,
}: {
page?: { label: string; href: string }
layout?: "column" | "spotlight"
layout?: "column" | "spotlight" | "wide"
children: React.ReactNode
}) {
return (
Expand Down
8 changes: 6 additions & 2 deletions registry/winlab/blocks/app-shell/app-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@ type User = { name: string; href: string; image?: string; tip?: string }
// bottom right the copyright year; the tip names the owner
// Every item can carry a tip, opening toward the page and lined up with the
// corner's outer edge so it never leaves the viewport.
// Two page layouts:
// Three page layouts:
// column read top to bottom: home, lists, tables, long forms
// spotlight one thing, centered between the corners: a detail, sign-in,
// a result, an empty state; taller content scrolls as a column
// wide a grid that needs every pixel, such as a two-week timetable:
// the full width between the corners' outer edges
function AppShell({
breadcrumb = [],
nav = [],
Expand All @@ -55,7 +57,7 @@ function AppShell({
React.ReactNode
>
>
layout?: "column" | "spotlight"
layout?: "column" | "spotlight" | "wide"
children: React.ReactNode
}) {
const pathname = usePathname()
Expand Down Expand Up @@ -157,6 +159,8 @@ function AppShell({
<main className="flex min-h-svh w-full items-center justify-center px-6 py-24">
<div className="w-full max-w-2xl">{children}</div>
</main>
) : layout === "wide" ? (
<main className="w-full px-6 pt-24 pb-24">{children}</main>
) : (
<main className="mx-auto w-full max-w-4xl px-6 pt-24 pb-24">
{children}
Expand Down
Loading