@gradeui/ui 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,146 @@
1
+ ---
2
+ name: Banner
3
+ import: "@gradeui/ui"
4
+ variants: [default, info, success, warning, destructive, announcement]
5
+ props:
6
+ - variant? (default | info | success | warning | destructive | announcement) — intent + tonal direction. `default` is a calm muted strip; `announcement` is a low-alpha brand tint for "new feature" messaging; status variants pick up the soft+deep token pairs.
7
+ - surface? (solid | translucent | glass | glass-strong) — material applied over the variant tint. `glass` for banners that sit over imagery / generative backdrops.
8
+ - align? (start | center | between) — justify behaviour of the inner flex row. Defaults to `between` so the action / dismiss button right-align.
9
+ - sticky?: boolean — stick to the top of the scroll container.
10
+ - dismissible?: boolean — render the trailing X close button. Pair with `onDismiss` to react.
11
+ - onDismiss?: () => void
12
+ - icon?: ReactNode — leading icon slot. NOT inferred from variant; pass what fits the message.
13
+ - action?: ReactNode — trailing slot before dismiss. Usually a `<Button size="sm">` or `<a>`.
14
+ - role?: string — overrides the automatic role mapping (warning/destructive → alert, others → status).
15
+ when_to_use: A full-width horizontal strip surfacing system-level state, announcements, or first-run guidance — "you're previewing a draft", "investigating incident", "new feature available", "send your design to Figma". Distinct from Callout (inline boxed message in the layout flow), Toast (transient floating notification), Dialog (modal interrupt). Banner is what lives at the TOP of an AppShellHeader, page, or panel.
16
+ composes_with: [AppShellHeader (most common host — banner sits ABOVE the header content), Button (in the action slot), Link (inside the content), Lucide icons (in the icon slot)]
17
+ aliases: [banner, notification banner, system banner, header banner, announcement bar, top bar, status bar, promo banner, incident banner, draft banner, first run banner, glass banner, sticky banner]
18
+ ---
19
+
20
+ Banner is the "horizontal strip across the top of something" primitive. The shape difference from Callout matters: Callout is an inline boxed message inside layout flow; Banner is full-bleed and meant to anchor at the top of a page, panel, or AppShellHeader.
21
+
22
+ ---
23
+
24
+ ### Scenario 1 — First-run guidance (default)
25
+
26
+ A one-line hint surfaced the first time a user lands on a tab or screen. Calm muted tint, dismissible, lives above the main content.
27
+
28
+ ```jsx
29
+ <Banner
30
+ variant="default"
31
+ dismissible
32
+ onDismiss={dismiss}
33
+ action={
34
+ <a href={pluginUrl} target="_blank" rel="noreferrer" className="text-sm font-medium underline underline-offset-4">
35
+ Get the Grade plugin →
36
+ </a>
37
+ }
38
+ >
39
+ Send your design to Figma as live components.
40
+ </Banner>
41
+ ```
42
+
43
+ This is the canonical replacement for the inline-style `FigmaIntroBanner` that motivated this primitive — same content, but the tint inherits properly from the active theme and the dismiss button gets the same focus-ring treatment as every other interactive element.
44
+
45
+ ---
46
+
47
+ ### Scenario 2 — Incident / warning banner at AppShellHeader top
48
+
49
+ You need to tell users something is going wrong without interrupting them. Banner with `variant="warning"` (or `destructive` if it's worse) sits at the very top of the AppShell.
50
+
51
+ ```jsx
52
+ <AppShell>
53
+ <Banner
54
+ variant="warning"
55
+ sticky
56
+ icon={<AlertTriangle className="h-4 w-4" />}
57
+ action={
58
+ <Button asChild variant="outline" size="sm">
59
+ <a href="/status">Status page</a>
60
+ </Button>
61
+ }
62
+ >
63
+ We're investigating an incident affecting search results. Comments and edits are unaffected.
64
+ </Banner>
65
+ <AppShellHeader>...</AppShellHeader>
66
+ <AppShellMain>...</AppShellMain>
67
+ </AppShell>
68
+ ```
69
+
70
+ `sticky` so it doesn't scroll away (incidents stay visible). `variant="warning"` gets `role="alert"` automatically — screen readers interrupt to announce it.
71
+
72
+ ---
73
+
74
+ ### Scenario 3 — Announcement banner (brand tint, low-key)
75
+
76
+ New feature announcement. You want to be noticed but not alarming. The `announcement` variant uses a low-alpha brand tint so the banner reads as "we have news" without competing with the page.
77
+
78
+ ```jsx
79
+ <Banner
80
+ variant="announcement"
81
+ dismissible
82
+ onDismiss={dismissAnnouncement}
83
+ icon={<Sparkles className="h-4 w-4" />}
84
+ action={
85
+ <Button asChild size="sm">
86
+ <a href="/components/code">See how →</a>
87
+ </Button>
88
+ }
89
+ >
90
+ <strong className="font-medium">New —</strong> Code component lands with diff hero and scroll-triggered reveals.
91
+ </Banner>
92
+ ```
93
+
94
+ ---
95
+
96
+ ### Scenario 4 — Glass banner over a hero image
97
+
98
+ The marketing site has a hero image and a top banner promoting an event. A solid banner would punch a stripe through the imagery. Glass keeps the image visible.
99
+
100
+ ```jsx
101
+ <div className="relative h-screen" style={{ backgroundImage: "url(/hero/teams-shipping.jpg)", backgroundSize: "cover" }}>
102
+ <Banner
103
+ surface="glass"
104
+ sticky
105
+ align="center"
106
+ action={
107
+ <Button size="sm" variant="outline" asChild>
108
+ <a href="/launchweek">Watch the launch →</a>
109
+ </Button>
110
+ }
111
+ >
112
+ GradeUI launch week kicks off 14 June.
113
+ </Banner>
114
+ ...
115
+ </div>
116
+ ```
117
+
118
+ `surface="glass"` + the default `variant="default"` gives a frosted strip with `--surface-blur-glass` worth of blur. Pair with `align="center"` when the banner has no leading icon — keeps the message visually centered.
119
+
120
+ ---
121
+
122
+ ### Anti-patterns
123
+
124
+ **DO NOT roll a banner with inline styles or Tailwind soup.**
125
+
126
+ ```jsx
127
+ {/* ❌ Wrong token names, no dismiss focus ring, no role mapping, no theme inheritance. */}
128
+ <div style={{ background: "oklch(var(--gds-primary) / 0.06)", color: "var(--gds-foreground)" }}>
129
+ Send your design to Figma. <a>Get the Grade plugin →</a>
130
+ </div>
131
+
132
+ {/* ✅ */}
133
+ <Banner variant="announcement" dismissible onDismiss={dismiss} action={...}>
134
+ Send your design to Figma.
135
+ </Banner>
136
+ ```
137
+
138
+ The inline-style original this primitive replaced was effectively invisible because it reached for `--gds-primary` / `--gds-foreground` tokens that don't exist. The fallback values kicked in and the banner washed out completely. Banner exists so this category of mistake is impossible.
139
+
140
+ **DO NOT use Banner for inline form-level validation.** That's Callout's job — it's a boxed message inside the layout flow. Banner is full-bleed chrome.
141
+
142
+ **DO NOT use Banner for transient confirmation ("Saved").** That's Toast (Sonner). Banner is persistent until dismissed.
143
+
144
+ **DO NOT stack multiple Banners.** Two banners reading at the same time fight for attention. If you genuinely need two messages, surface the highest-priority one and queue the second for after the first is dismissed.
145
+
146
+ **DO NOT pass `role="alert"` on a calm `variant="info"` Banner.** The variant→role mapping is intentional. Info/success/announcement are polite; warning/destructive interrupt. Overriding makes assistive tech behaviour inconsistent with the visual signal.
@@ -3,14 +3,26 @@ name: Card
3
3
  import: "@gradeui/ui"
4
4
  subcomponents: [CardHeader, CardTitle, CardDescription, CardContent, CardFooter]
5
5
  props:
6
+ - surface? (solid | translucent | glass | glass-strong) — what the card surface is *made of*. `solid` is the default opaque `bg-card`. `translucent` is ~82% opacity for menu sheets. `glass` is ~58% opacity + 14px blur + edge highlight for floating panels. `glass-strong` is ~42% + 24px blur for full-page overlays. Composes with `shadow-elevation-*` (depth) and `gds-aura-*` (state signal).
6
7
  - Each subcomponent accepts native div HTML attrs (className, etc.)
7
- - No variants Card is a flexible container surface; shape via data-card-style and depth via shadow-elevation-* utilities
8
- when_to_use: Grouped content with a distinct surface — settings panels, dashboard tiles, list-of-cards layouts. Pair CardHeader (title + description) with CardContent and optional CardFooter (actions).
9
- composes_with: [Button (in CardFooter), Badge, Separator, Avatar, any form controls]
10
- aliases: [card, group box, groupbox, panel, tile, surface]
8
+ when_to_use: Grouped content with a distinct surface settings panels, dashboard tiles, list-of-cards layouts, marketing hero containers, AI suggestion overlays. Pair CardHeader (title + description) with CardContent and optional CardFooter (actions). Reach for `surface="glass"` whenever the card sits over a busy backdrop (gradient mesh, dot grid, generative art, image hero).
9
+ composes_with: [Button (in CardFooter), Badge, Separator, Avatar, Code, MediaSurface, any form controls]
10
+ aliases: [card, group box, groupbox, panel, tile, surface, glass card, frosted card, floating panel, hero card, ai suggestion card, dashboard tile, settings panel]
11
11
  ---
12
12
 
13
- Canonical structure do NOT skip CardHeader if the card has a title:
13
+ Card is the most common host for the **Presence** system (PRESENCE.md). Three independent axes layer on top of every card:
14
+
15
+ - **Surface** — what it's made of (`surface` prop: solid / translucent / glass / glass-strong)
16
+ - **Elevation** — how high it sits (`shadow-elevation-1..5` utility)
17
+ - **Aura** — what it's radiating (`gds-aura-ring`, `gds-aura-gradient`, `gds-aura-shimmer`)
18
+
19
+ The four scenarios below are the canonical recipes. Match the scenario to the screen you're building.
20
+
21
+ ---
22
+
23
+ ### Scenario 1 — Settings panel (default opaque)
24
+
25
+ You want a grouped content surface on a normal page: a settings panel, a list-of-cards tile, a dashboard widget. The page background is calm; the card just needs to sit cleanly on it.
14
26
 
15
27
  ```jsx
16
28
  <Card>
@@ -18,23 +30,166 @@ Canonical structure — do NOT skip CardHeader if the card has a title:
18
30
  <CardTitle>Billing</CardTitle>
19
31
  <CardDescription>Manage your subscription.</CardDescription>
20
32
  </CardHeader>
21
- <CardContent>…</CardContent>
33
+ <CardContent>
34
+ <Stack gap="md">
35
+ <Row justify="between">
36
+ <span className="text-sm">Plan</span>
37
+ <Badge>Pro</Badge>
38
+ </Row>
39
+ <Row justify="between">
40
+ <span className="text-sm">Renews</span>
41
+ <span className="text-sm text-muted-foreground">12 Jun 2026</span>
42
+ </Row>
43
+ </Stack>
44
+ </CardContent>
22
45
  <CardFooter>
23
- <Button>Save</Button>
46
+ <Button variant="outline">Cancel plan</Button>
47
+ <Button>Update payment</Button>
24
48
  </CardFooter>
25
49
  </Card>
26
50
  ```
27
51
 
28
- Card is the most common host for Presence affordances. Three independent axes:
52
+ No `surface` prop — the default `solid` is the right answer for almost every in-page card. Reach for glass only when there's something behind worth blurring.
53
+
54
+ ---
55
+
56
+ ### Scenario 2 — Glass card over a busy backdrop (marketing hero)
57
+
58
+ You're building a marketing hero. There's a gradient mesh, a dot grid, generative art, or a hero image behind the card. The card should read as **floating chrome** — translucent enough to let the backdrop breathe through, but with a defined edge so the content stays legible.
29
59
 
30
60
  ```jsx
31
- // Elevation — pick a depth level (1=minimal, 3=raised, 4=popover, 5=dialog).
32
- <Card className="shadow-elevation-4">…</Card>
61
+ <SectionBlock background="gradient" padding="xl">
62
+ <Grid cols="2" gap="md">
63
+ <Card surface="glass" className="shadow-elevation-4">
64
+ <CardHeader>
65
+ <CardTitle>v0 — sidebar component</CardTitle>
66
+ <CardDescription>~300 lines</CardDescription>
67
+ </CardHeader>
68
+ <CardContent className="p-0">
69
+ <Code source={v0Code} language="tsx" bare className="p-4 text-xs max-h-72" />
70
+ </CardContent>
71
+ </Card>
72
+
73
+ <Card surface="glass" className="shadow-elevation-4 gds-aura-ring">
74
+ <CardHeader>
75
+ <CardTitle>GradeUI — sidebar component</CardTitle>
76
+ <CardDescription>6 lines</CardDescription>
77
+ </CardHeader>
78
+ <CardContent className="p-0">
79
+ <Code source={gradeCode} language="tsx" bare className="p-4 text-xs max-h-72" />
80
+ </CardContent>
81
+ </Card>
82
+ </Grid>
83
+ </SectionBlock>
84
+ ```
85
+
86
+ `surface="glass"` does five things at once: 58% opacity `bg-card`, 14px backdrop blur, an inner edge highlight (the "wet" rim that gives glass its boundary), a faint border, and it drops the base `bg-card` so the alpha actually shows. Layering `shadow-elevation-4` adds the floating-popover drop shadow; `gds-aura-ring` makes the second card pulse with a blue halo to signal "this is the recommended path".
87
+
88
+ ---
89
+
90
+ ### Scenario 3 — Translucent menu sheet (floating chrome with structure)
33
91
 
34
- // Surfaceopt into glass / translucent backgrounds.
35
- <Card className="gds-surface-glass shadow-elevation-4">…</Card>
92
+ You want a floating panel a command palette, a notification drawer, an AI suggestion overlay — that's visibly distinct from the canvas but doesn't need full glass blur. Translucent is for "I want presence without drama".
36
93
 
37
- // Aura — radiate AI-attention state. Combinable.
38
- <Card className="gds-aura-ring">Studio is reviewing this</Card>
39
- <Card className="gds-aura-ring gds-aura-shimmer">Generating…</Card>
94
+ ```jsx
95
+ <Card surface="translucent" className="shadow-elevation-5 w-80">
96
+ <CardHeader>
97
+ <CardTitle>Suggested action</CardTitle>
98
+ <CardDescription>Studio noticed a layout opportunity.</CardDescription>
99
+ </CardHeader>
100
+ <CardContent>
101
+ <p className="text-sm">
102
+ Three buttons in your toolbar would line up edge-to-edge with the
103
+ tabs below if their size matched. Apply <code>size="sm"</code>?
104
+ </p>
105
+ </CardContent>
106
+ <CardFooter>
107
+ <Button variant="ghost" size="sm">Dismiss</Button>
108
+ <Button size="sm">Apply</Button>
109
+ </CardFooter>
110
+ </Card>
40
111
  ```
112
+
113
+ 82% opacity is enough to feel layered but not enough to need backdrop blur — works equally well over a busy or a calm background. `shadow-elevation-5` (dialog tier) plus `translucent` is the "floating but not glass" signature.
114
+
115
+ ---
116
+
117
+ ### Scenario 4 — AI is generating (aura + surface composition)
118
+
119
+ You want to signal that Studio (or any AI agent) is actively working on this card. Aura is the right axis for state signals. It composes with any surface.
120
+
121
+ ```jsx
122
+ <Card
123
+ surface="glass"
124
+ className="shadow-elevation-4 gds-aura-ring gds-aura-shimmer"
125
+ style={{ "--aura-color": "var(--selected-glow)" }}
126
+ >
127
+ <CardHeader>
128
+ <CardTitle>Generating layout</CardTitle>
129
+ <CardDescription>About 4 seconds remaining.</CardDescription>
130
+ </CardHeader>
131
+ <CardContent>
132
+ <Stack gap="xs">
133
+ <Skeleton className="h-4 w-3/4" />
134
+ <Skeleton className="h-4 w-1/2" />
135
+ <Skeleton className="h-4 w-2/3" />
136
+ </Stack>
137
+ </CardContent>
138
+ </Card>
139
+ ```
140
+
141
+ Ring (pulsing halo) + shimmer (diagonal sweep) together = "actively generating". For "Studio is reviewing this", use ring alone. For "ready to ship", swap tone to `--success`. The skeletons inside are the content's own loading state — orthogonal to the card-level aura.
142
+
143
+ ---
144
+
145
+ ### Scenario 5 — Glass-strong for a full-page overlay backdrop
146
+
147
+ `surface="glass-strong"` is tuned for a different job than the other three: it's the **backdrop** behind a modal sheet, not the modal itself. Heavy blur (24px), 42% opacity. Use it to de-emphasise the page underneath while keeping it readable.
148
+
149
+ ```jsx
150
+ <Card surface="glass-strong" className="fixed inset-4 z-50">
151
+ <CardContent className="grid place-items-center h-full">
152
+ <Stack gap="md" align="center">
153
+ <Spinner />
154
+ <span className="text-lg">Saving your theme…</span>
155
+ </Stack>
156
+ </CardContent>
157
+ </Card>
158
+ ```
159
+
160
+ Almost always wrong for in-flow content — at 42% opacity the card reads as washed out. If you find yourself reaching for glass-strong for a regular card, you probably want `glass`.
161
+
162
+ ---
163
+
164
+ ### Anti-patterns
165
+
166
+ **DO NOT roll glass by hand with Tailwind utilities.** The wrong path:
167
+
168
+ ```jsx
169
+ {/* ❌ Tailwind soup — misses edge highlight, locks blur to a fixed step,
170
+ bypasses theme tuning, no Studio inspector knob. */}
171
+ <Card className="overflow-hidden border-border bg-card/40 backdrop-blur-md">
172
+ ```
173
+
174
+ The right path:
175
+
176
+ ```jsx
177
+ {/* ✅ Theme-aware bg, tuned blur, edge highlight, knob-discoverable. */}
178
+ <Card surface="glass">
179
+ ```
180
+
181
+ This is the single most common mistake. The model reaches for `bg-card/40 backdrop-blur-md` because every other DS leaves glass at the utility layer. Ours doesn't.
182
+
183
+ **DO NOT layer a solid `bg-card` className over `surface="glass"`.** The opaque fill defeats the blur. Card already drops `bg-card` when `surface` is set to anything other than `solid` — don't undo that by tacking `bg-card` back on via className. If you want a tinted glass, override `--card` on the element:
184
+
185
+ ```jsx
186
+ <Card surface="glass" style={{ "--card": "0.99 0.04 250" }}>
187
+ ...
188
+ </Card>
189
+ ```
190
+
191
+ **DO NOT use `surface="glass"` over a solid background.** Glass needs something behind it to blur. Over plain `bg-background` it reads as a slightly washed-out card and you pay for backdrop-filter for no gain. If the page is calm, use `solid`.
192
+
193
+ **DO NOT use `surface="glass-strong"` for in-flow content.** It's a full-page overlay material. At 42% opacity, regular cards read as washed out. Reach for `glass`.
194
+
195
+ **DO NOT skip CardHeader if the card has a title.** The header is the semantic anchor for the title + description pair. Inline `<h3>` inside CardContent breaks the visual rhythm and harms screen-reader navigation.
@@ -0,0 +1,132 @@
1
+ ---
2
+ name: Code
3
+ import: "@gradeui/ui"
4
+ variants: []
5
+ props:
6
+ - source: string — the code to render
7
+ - language? (tsx | jsx | ts | js | html | css | json | bash | md | py | go | rust) — Prism language id; defaults to `tsx`
8
+ - highlight? — 1-indexed line number, array of numbers, or array of `[start, end]` ranges to emphasise
9
+ - diff? — `{ added?: number[]; removed?: number[] }` — 1-indexed lines for diff hero / changelog mode
10
+ - reveal? (none | lines | typewriter | diff) — entrance animation; defaults to `none`
11
+ - trigger? (mount | inView | manual) — what kicks the reveal off; defaults to `mount`
12
+ - play?: boolean — for `trigger="manual"`, set true to play
13
+ - speed? (slow | normal | fast) — animation feel preset. `normal` (default) maps to the canonical 50ms/22ms staggers + 180ms pre-delay. Pick a feel; don't tune individual numbers unless you have to.
14
+ - delay?: number — explicit delay before reveal starts (ms) — overrides the `speed` preset
15
+ - stagger?: number — explicit per-line stagger for `lines`/`diff`, per-token for `typewriter` (ms) — overrides the `speed` preset
16
+ - prompt?: string — string prepended to each line. Use for terminal emulation: `prompt="$ "` for bash, `prompt="> "` for PowerShell, `prompt=">>> "` for Python REPL. Prompt characters render in muted token colour, don't pick up the typewriter stagger, and are hidden from screen readers.
17
+ - showLineNumbers?: boolean
18
+ - filename?: string — optional label rendered in the header chrome
19
+ - wrap?: boolean — wrap long lines instead of horizontal scroll
20
+ - bare?: boolean — drop chrome (border, header, padding) — for inline use
21
+ - height? (auto | number | string) — container sizing. `auto` (default) grows with content. Number = pixels (`300` → `300px`). String passes through as CSS (`"20rem"`, `"50vh"`).
22
+ - maxLines?: number — cap the visible line count at exactly N line-heights. Wins over `height`. Use for terminal windows, code-tour cards, and surfaces that need a stable vertical rhythm regardless of snippet length.
23
+ when_to_use: Read-only code surface for marketing heroes, docs, changelog entries, AI-output displays. Use `diff` for the "diff hero" pattern (before/after side-by-side or stacked). Use `reveal="lines"` with `trigger="inView"` for scroll-driven marketing pages. Use `reveal="typewriter"` for AI-output / chat-style displays. Use `bare` for inline code inside prose. NOT a code editor — for editable code, reach for an external editor primitive (CodeMirror / Monaco).
24
+ composes_with: [SectionBlock, Card, Tabs (for multi-file examples), Carousel (slide-to-slide code progression)]
25
+ aliases: [code block, code, code snippet, code surface, syntax highlighted code, diff hero, diff view, diff block, changelog code, before after code, scroll-triggered code, typewriter code]
26
+ ---
27
+
28
+ Token palette is driven by `--gds-code-*` CSS variables in `styles/globals.css`. The component itself is presentation-agnostic: prism renders tokens, the variables colour them, motion handles the reveal. Override per-instance via inline `style` to retune one block without touching the theme.
29
+
30
+ The engine is `prism-react-renderer` (already used by Studio's CodeView). Sync, ~6kb, render-prop API — no async hydration flash, no bundle bloat from lang files.
31
+
32
+ ```jsx
33
+ // Plain block.
34
+ <Code language="tsx" source={`function greet(name) {
35
+ return \`Hello, \${name}\`;
36
+ }`} />
37
+ ```
38
+
39
+ ```jsx
40
+ // Line highlight — emphasis lines accept a number, an array, or
41
+ // [start, end] ranges. Composes cleanly with diff (diff colours win).
42
+ <Code
43
+ language="tsx"
44
+ highlight={[2, [4, 6]]}
45
+ source={`<Button>Save</Button>
46
+ <Button variant="raised">Ship it</Button>
47
+ <Button variant="raised" style={{ "--btn-glow": "var(--warning)" }}>
48
+ Iterate
49
+ </Button>`}
50
+ />
51
+ ```
52
+
53
+ ```jsx
54
+ // Diff hero — the marketing "before / after" pattern. Added lines get
55
+ // the success-tinted bg + `+` gutter; removed lines get destructive-
56
+ // tinted bg + `-` gutter. `showLineNumbers` is opt-in.
57
+ <Code
58
+ language="tsx"
59
+ filename="button.tsx"
60
+ diff={{ removed: [1], added: [2, 3, 4] }}
61
+ source={`<button className="px-4 py-2 rounded-md bg-blue-600 text-white shadow-md">
62
+ <Button variant="raised">
63
+ Ship it
64
+ </Button>`}
65
+ />
66
+ ```
67
+
68
+ ```jsx
69
+ // Scroll-triggered reveal — marketing hero. The block waits until the
70
+ // reader scrolls it into view, then staggers each line in. `once: true`
71
+ // is baked in: the reveal doesn't replay when the user scrolls away
72
+ // and back.
73
+ <Code
74
+ language="tsx"
75
+ reveal="lines"
76
+ trigger="inView"
77
+ stagger={50}
78
+ source={`<AppShell nav="three-pane">
79
+ <AppShellHeader>...</AppShellHeader>
80
+ <AppShellNav>...</AppShellNav>
81
+ <AppShellAside>...</AppShellAside>
82
+ <AppShellMain>...</AppShellMain>
83
+ </AppShell>`}
84
+ />
85
+ ```
86
+
87
+ ```jsx
88
+ // Typewriter — token-by-token reveal. Good for AI-output displays
89
+ // and "watch it generate" demos. Whitespace tokens are free (no
90
+ // delay) so leading indent doesn't feel like dead time. `speed`
91
+ // keeps it ergonomic — pick "slow" / "normal" / "fast" instead of
92
+ // tuning stagger by hand.
93
+ <Code
94
+ language="tsx"
95
+ reveal="typewriter"
96
+ trigger="inView"
97
+ speed="normal"
98
+ source={`const theme = await ai.generate({
99
+ brand: "Acme",
100
+ mood: "calm",
101
+ });`}
102
+ />
103
+ ```
104
+
105
+ ```jsx
106
+ // Terminal emulation — `prompt` prepends a static prompt string to
107
+ // each line. Combine with `reveal="typewriter"` for a scripted CLI
108
+ // session feel. Prompt chars are chrome (muted, aria-hidden, no
109
+ // animation), so the typewriter only stages the actual command.
110
+ <Code
111
+ language="bash"
112
+ prompt="$ "
113
+ reveal="typewriter"
114
+ trigger="inView"
115
+ speed="slow"
116
+ source={`pnpm add @gradeui/ui
117
+ pnpm gradeui init
118
+ pnpm dev`}
119
+ />
120
+ ```
121
+
122
+ ### Anti-patterns
123
+
124
+ DO NOT use `<Code>` as a code editor. It's read-only by design — the prism renderer doesn't take input. For editable code, compose your own surface around CodeMirror or Monaco.
125
+
126
+ DO NOT reach for a separate library to render code elsewhere in the app — Studio's CodeView, docs blocks, and marketing heroes should all share this primitive so the token palette stays single-source.
127
+
128
+ DO NOT pass `highlight` AND `diff` for the same line — diff wins, and the highlight emphasis is silently dropped. Use one signal per line.
129
+
130
+ DO NOT use `reveal="typewriter"` for long blocks (50+ lines). It works but feels laboured; use `reveal="lines"` instead.
131
+
132
+ DO NOT override the prism `theme` prop — the component intentionally hides it. Restyle via the `--gds-code-*` CSS variables so every Code block in the app shifts together with the active theme.
@@ -5,36 +5,129 @@ subcomponents: [DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogD
5
5
  props:
6
6
  - Dialog: open?, onOpenChange? — Radix controlled/uncontrolled pattern
7
7
  - DialogTrigger: asChild? (wrap a Button)
8
+ - DialogContent: surface? (solid | translucent | glass | glass-strong) — what the modal panel is *made of*. Defaults to `solid` (opaque `bg-background`). `glass` lets the page show through softly — pairs with rich backdrops or AI-suggestion modals.
8
9
  - DialogContent: accepts native div HTML attrs
9
10
  - DialogFooter: used for action rows
10
- when_to_use: Modal interruptions — confirmations, focused forms, detail views. Dialog is the right primitive for Apple HIG / React Native "Alert" (modal) semantics. For non-blocking inline messaging use Callout; for transient notifications use Toaster (Sonner). Always include DialogTitle (a11y requirement).
11
- composes_with: [Button (as DialogTrigger asChild, and inside DialogFooter), Input/Textarea/Select inside DialogContent]
12
- aliases: [modal, popup, overlay, alert, system alert, alert dialog, modal dialog, confirm dialog, react native modal, rn alert]
11
+ when_to_use: Modal interruptions — confirmations, focused forms, detail views, AI suggestion sheets. Dialog is the right primitive for Apple HIG / React Native "Alert" (modal) semantics. For non-blocking inline messaging use Callout; for transient notifications use Toaster (Sonner). Always include DialogTitle (a11y requirement).
12
+ composes_with: [Button (as DialogTrigger asChild, and inside DialogFooter), Input/Textarea/Select inside DialogContent, Code (for changelog / diff modals), MediaSurface (for image / preview modals)]
13
+ aliases: [modal, popup, overlay, alert, system alert, alert dialog, modal dialog, confirm dialog, react native modal, rn alert, glass modal, frosted modal, ai suggestion modal]
13
14
  ---
14
15
 
16
+ DialogContent sits at elevation-5 (the dialog tier). The Presence axes still apply: `surface` picks the material, `gds-aura-*` adds radiating state, the overlay scrim handles dimming the page.
17
+
18
+ ---
19
+
20
+ ### Scenario 1 — Destructive confirmation (default opaque)
21
+
22
+ You're confirming a destructive action: delete, discard, revoke. Keep the dialog opaque — the user should focus on the decision, not the page behind it. The raised Button + tonal `--btn-glow` keeps the destructive action visually heavy without going red-everywhere.
23
+
15
24
  ```jsx
16
25
  <Dialog>
17
- <DialogTrigger asChild><Button>Delete</Button></DialogTrigger>
26
+ <DialogTrigger asChild><Button variant="outline">Delete project</Button></DialogTrigger>
18
27
  <DialogContent>
19
28
  <DialogHeader>
20
29
  <DialogTitle>Delete project?</DialogTitle>
21
- <DialogDescription>This cannot be undone.</DialogDescription>
30
+ <DialogDescription>
31
+ This will remove the project, its screens, and all comments. This cannot be undone.
32
+ </DialogDescription>
22
33
  </DialogHeader>
23
34
  <DialogFooter>
24
35
  <Button variant="outline">Cancel</Button>
25
- <Button variant="destructive">Delete</Button>
36
+ <Button variant="raised" style={{ "--btn-glow": "var(--destructive)" }}>
37
+ Delete forever
38
+ </Button>
26
39
  </DialogFooter>
27
40
  </DialogContent>
28
41
  </Dialog>
29
42
  ```
30
43
 
31
- DialogContent ships at elevation-5. Reach for the raised Button variant inside DialogFooter when the action carries weight ("Delete", "Publish", "Ship"):
44
+ No `surface` prop `solid` is the right answer for high-stakes confirmations. The opacity reinforces "stop and decide".
45
+
46
+ ---
47
+
48
+ ### Scenario 2 — Glass modal over a rich canvas (creative-tool aesthetic)
49
+
50
+ You're building a creative tool — Studio, a presentation builder, a photo editor. The canvas behind the dialog is visually rich (a layout in progress, an image, generative art). A solid dialog cuts a hole through the work. Glass keeps the work visible while focusing attention.
32
51
 
33
52
  ```jsx
34
- <DialogFooter>
35
- <Button variant="outline">Cancel</Button>
36
- <Button variant="raised" style={{ "--btn-glow": "var(--destructive)" }}>
37
- Delete forever
38
- </Button>
39
- </DialogFooter>
53
+ <Dialog>
54
+ <DialogTrigger asChild><Button>Add a comment</Button></DialogTrigger>
55
+ <DialogContent surface="glass" className="shadow-elevation-5">
56
+ <DialogHeader>
57
+ <DialogTitle>Comment on Hero section</DialogTitle>
58
+ <DialogDescription>
59
+ Visible to your team and to Studio when it next regenerates this screen.
60
+ </DialogDescription>
61
+ </DialogHeader>
62
+ <Textarea placeholder="What should change about this section?" />
63
+ <DialogFooter>
64
+ <Button variant="ghost">Cancel</Button>
65
+ <Button>Post comment</Button>
66
+ </DialogFooter>
67
+ </DialogContent>
68
+ </Dialog>
40
69
  ```
70
+
71
+ `surface="glass"` is the canvas-tool signature. The user keeps spatial awareness of what they were just looking at; the dialog feels like a layer above the work, not a separate page.
72
+
73
+ ---
74
+
75
+ ### Scenario 3 — AI suggestion sheet (translucent + aura)
76
+
77
+ Studio is offering a suggestion. It shouldn't feel as heavy as a destructive confirmation — it's a recommendation, not a demand. Translucent (no blur) is lighter than glass; the aura ring announces "this is from an AI agent".
78
+
79
+ ```jsx
80
+ <Dialog open={hasSuggestion}>
81
+ <DialogContent
82
+ surface="translucent"
83
+ className="shadow-elevation-5 gds-aura-ring"
84
+ style={{ "--aura-color": "var(--selected-glow)" }}
85
+ >
86
+ <DialogHeader>
87
+ <DialogTitle>Three buttons could align</DialogTitle>
88
+ <DialogDescription>
89
+ Toolbar buttons match TabsList height when size="sm". Apply across all three?
90
+ </DialogDescription>
91
+ </DialogHeader>
92
+
93
+ <Card surface="glass" className="shadow-elevation-2">
94
+ <CardContent>
95
+ <Code source={suggestedDiff} language="tsx" diff={{ added: [2, 3, 4] }} bare />
96
+ </CardContent>
97
+ </Card>
98
+
99
+ <DialogFooter>
100
+ <Button variant="ghost">Dismiss</Button>
101
+ <Button>Apply suggestion</Button>
102
+ </DialogFooter>
103
+ </DialogContent>
104
+ </Dialog>
105
+ ```
106
+
107
+ Three Presence axes layered: `surface="translucent"` (material), `shadow-elevation-5` (depth), `gds-aura-ring` (state signal). The inner Card uses `surface="glass"` for a different reason — to read as a nested floating preview rather than a flat content block.
108
+
109
+ ---
110
+
111
+ ### Anti-patterns
112
+
113
+ **DO NOT use `surface="glass"` for destructive confirmations.** Glass implies "the page is still alive behind this" — users will be less decisive. Opaque is the right material for high-stakes choices.
114
+
115
+ **DO NOT roll glass by hand on DialogContent.**
116
+
117
+ ```jsx
118
+ {/* ❌ Misses edge highlight, no theme tuning, no inspector knob. */}
119
+ <DialogContent className="bg-background/50 backdrop-blur-md">
120
+
121
+ {/* ✅ */}
122
+ <DialogContent surface="glass">
123
+ ```
124
+
125
+ **DO NOT skip DialogTitle.** Screen readers announce the title on open — without it the dialog reads as "[unlabeled dialog]". If the design has no visible title, wrap a visually-hidden title:
126
+
127
+ ```jsx
128
+ <DialogHeader>
129
+ <DialogTitle className="sr-only">Image preview</DialogTitle>
130
+ </DialogHeader>
131
+ ```
132
+
133
+ **DO NOT use Dialog for ambient messaging.** Toast for transient ("Saved"), Callout for inline ("3 unread comments"), Dialog only when the user MUST respond before continuing.