@gradeui/ui 1.0.0 → 1.2.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.
- package/components/ui/banner.md +146 -0
- package/components/ui/card.md +170 -15
- package/components/ui/code.md +133 -0
- package/components/ui/composer.md +226 -0
- package/components/ui/dialog.md +106 -13
- package/components/ui/dropdown-menu.md +93 -4
- package/components/ui/hover-card.md +98 -4
- package/components/ui/message.md +229 -0
- package/components/ui/popover.md +119 -7
- package/components/ui/section-block.md +153 -0
- package/components/ui/sheet.md +98 -6
- package/dist/contracts.js +48 -4
- package/dist/contracts.js.map +1 -1
- package/dist/contracts.mjs +48 -4
- package/dist/contracts.mjs.map +1 -1
- package/dist/index.d.mts +1027 -29
- package/dist/index.d.ts +1027 -29
- package/dist/index.js +102 -49
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +102 -49
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +15 -1
|
@@ -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.
|
package/components/ui/card.md
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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
|
|
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>
|
|
46
|
+
<Button variant="outline">Cancel plan</Button>
|
|
47
|
+
<Button>Update payment</Button>
|
|
24
48
|
</CardFooter>
|
|
25
49
|
</Card>
|
|
26
50
|
```
|
|
27
51
|
|
|
28
|
-
|
|
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
|
-
|
|
32
|
-
<
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
<Card className="
|
|
39
|
-
<
|
|
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,133 @@
|
|
|
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
|
+
- size? (xs | sm | md) — type-scale preset. `xs` (12px) for dense changelog cards / inline blocks; `sm` (14px, default) for marketing heroes and docs; `md` (16px) for focal-point displays.
|
|
22
|
+
- height? (auto | number | string) — container sizing. `auto` (default) grows with content. Number = pixels (`300` → `300px`). String passes through as CSS (`"20rem"`, `"50vh"`).
|
|
23
|
+
- maxLines?: number — cap the visible line count at exactly N line-heights. Wins over `height`. Inherits the current size's line-height automatically.
|
|
24
|
+
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).
|
|
25
|
+
composes_with: [SectionBlock, Card, Tabs (for multi-file examples), Carousel (slide-to-slide code progression)]
|
|
26
|
+
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]
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
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.
|
|
30
|
+
|
|
31
|
+
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.
|
|
32
|
+
|
|
33
|
+
```jsx
|
|
34
|
+
// Plain block.
|
|
35
|
+
<Code language="tsx" source={`function greet(name) {
|
|
36
|
+
return \`Hello, \${name}\`;
|
|
37
|
+
}`} />
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```jsx
|
|
41
|
+
// Line highlight — emphasis lines accept a number, an array, or
|
|
42
|
+
// [start, end] ranges. Composes cleanly with diff (diff colours win).
|
|
43
|
+
<Code
|
|
44
|
+
language="tsx"
|
|
45
|
+
highlight={[2, [4, 6]]}
|
|
46
|
+
source={`<Button>Save</Button>
|
|
47
|
+
<Button variant="raised">Ship it</Button>
|
|
48
|
+
<Button variant="raised" style={{ "--btn-glow": "var(--warning)" }}>
|
|
49
|
+
Iterate
|
|
50
|
+
</Button>`}
|
|
51
|
+
/>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```jsx
|
|
55
|
+
// Diff hero — the marketing "before / after" pattern. Added lines get
|
|
56
|
+
// the success-tinted bg + `+` gutter; removed lines get destructive-
|
|
57
|
+
// tinted bg + `-` gutter. `showLineNumbers` is opt-in.
|
|
58
|
+
<Code
|
|
59
|
+
language="tsx"
|
|
60
|
+
filename="button.tsx"
|
|
61
|
+
diff={{ removed: [1], added: [2, 3, 4] }}
|
|
62
|
+
source={`<button className="px-4 py-2 rounded-md bg-blue-600 text-white shadow-md">
|
|
63
|
+
<Button variant="raised">
|
|
64
|
+
Ship it
|
|
65
|
+
</Button>`}
|
|
66
|
+
/>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```jsx
|
|
70
|
+
// Scroll-triggered reveal — marketing hero. The block waits until the
|
|
71
|
+
// reader scrolls it into view, then staggers each line in. `once: true`
|
|
72
|
+
// is baked in: the reveal doesn't replay when the user scrolls away
|
|
73
|
+
// and back.
|
|
74
|
+
<Code
|
|
75
|
+
language="tsx"
|
|
76
|
+
reveal="lines"
|
|
77
|
+
trigger="inView"
|
|
78
|
+
stagger={50}
|
|
79
|
+
source={`<AppShell nav="three-pane">
|
|
80
|
+
<AppShellHeader>...</AppShellHeader>
|
|
81
|
+
<AppShellNav>...</AppShellNav>
|
|
82
|
+
<AppShellAside>...</AppShellAside>
|
|
83
|
+
<AppShellMain>...</AppShellMain>
|
|
84
|
+
</AppShell>`}
|
|
85
|
+
/>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```jsx
|
|
89
|
+
// Typewriter — token-by-token reveal. Good for AI-output displays
|
|
90
|
+
// and "watch it generate" demos. Whitespace tokens are free (no
|
|
91
|
+
// delay) so leading indent doesn't feel like dead time. `speed`
|
|
92
|
+
// keeps it ergonomic — pick "slow" / "normal" / "fast" instead of
|
|
93
|
+
// tuning stagger by hand.
|
|
94
|
+
<Code
|
|
95
|
+
language="tsx"
|
|
96
|
+
reveal="typewriter"
|
|
97
|
+
trigger="inView"
|
|
98
|
+
speed="normal"
|
|
99
|
+
source={`const theme = await ai.generate({
|
|
100
|
+
brand: "Acme",
|
|
101
|
+
mood: "calm",
|
|
102
|
+
});`}
|
|
103
|
+
/>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```jsx
|
|
107
|
+
// Terminal emulation — `prompt` prepends a static prompt string to
|
|
108
|
+
// each line. Combine with `reveal="typewriter"` for a scripted CLI
|
|
109
|
+
// session feel. Prompt chars are chrome (muted, aria-hidden, no
|
|
110
|
+
// animation), so the typewriter only stages the actual command.
|
|
111
|
+
<Code
|
|
112
|
+
language="bash"
|
|
113
|
+
prompt="$ "
|
|
114
|
+
reveal="typewriter"
|
|
115
|
+
trigger="inView"
|
|
116
|
+
speed="slow"
|
|
117
|
+
source={`pnpm add @gradeui/ui
|
|
118
|
+
pnpm gradeui init
|
|
119
|
+
pnpm dev`}
|
|
120
|
+
/>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Anti-patterns
|
|
124
|
+
|
|
125
|
+
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.
|
|
126
|
+
|
|
127
|
+
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.
|
|
128
|
+
|
|
129
|
+
DO NOT pass `highlight` AND `diff` for the same line — diff wins, and the highlight emphasis is silently dropped. Use one signal per line.
|
|
130
|
+
|
|
131
|
+
DO NOT use `reveal="typewriter"` for long blocks (50+ lines). It works but feels laboured; use `reveal="lines"` instead.
|
|
132
|
+
|
|
133
|
+
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.
|