@gradeui/ui 0.10.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.
- package/components/ui/accordion.md +1 -1
- package/components/ui/ai-chat-composer.md +37 -0
- package/components/ui/ai-chat.md +68 -22
- package/components/ui/alert.md +0 -21
- package/components/ui/app-shell.md +135 -18
- package/components/ui/avatar.md +12 -1
- package/components/ui/badge.md +2 -2
- package/components/ui/banner.md +146 -0
- package/components/ui/breadcrumb.md +49 -2
- package/components/ui/button.md +35 -3
- package/components/ui/calendar.md +1 -1
- package/components/ui/callout.md +45 -0
- package/components/ui/card.md +176 -6
- package/components/ui/carousel.md +56 -0
- package/components/ui/chart.md +1 -1
- package/components/ui/checkbox.md +1 -0
- package/components/ui/code.md +132 -0
- package/components/ui/collapsible.md +1 -1
- package/components/ui/command.md +1 -1
- package/components/ui/date-picker.md +1 -1
- package/components/ui/dialog.md +110 -6
- package/components/ui/dropdown-menu.md +97 -2
- package/components/ui/flex.md +1 -1
- package/components/ui/grid.md +1 -1
- package/components/ui/hover-card.md +98 -4
- package/components/ui/input.md +1 -1
- package/components/ui/label.md +1 -0
- package/components/ui/map.md +2 -2
- package/components/ui/media-surface.md +50 -7
- package/components/ui/multi-select.md +114 -0
- package/components/ui/popover.md +123 -4
- package/components/ui/progress.md +1 -0
- package/components/ui/radio-group.md +1 -1
- package/components/ui/resizable.md +1 -1
- package/components/ui/row.md +1 -1
- package/components/ui/scroll-area.md +1 -1
- package/components/ui/section-block.md +153 -0
- package/components/ui/select.md +1 -1
- package/components/ui/separator.md +1 -1
- package/components/ui/sheet.md +102 -4
- package/components/ui/side-menu.md +0 -40
- package/components/ui/sidebar.md +121 -0
- package/components/ui/simple-tabs.md +0 -27
- package/components/ui/skeleton.md +1 -1
- package/components/ui/slider.md +1 -1
- package/components/ui/sortable.md +101 -0
- package/components/ui/stack.md +19 -1
- package/components/ui/switch.md +1 -1
- package/components/ui/table.md +1 -0
- package/components/ui/tabs.md +19 -2
- package/components/ui/textarea.md +1 -1
- package/components/ui/toast.md +2 -2
- package/components/ui/toggle-group.md +12 -5
- package/components/ui/toolbar.md +167 -0
- package/components/ui/tooltip.md +1 -1
- package/components/ui/video-player.md +2 -2
- package/dist/contracts.d.mts +14 -0
- package/dist/contracts.d.ts +14 -0
- package/dist/contracts.js +63 -0
- package/dist/contracts.js.map +1 -0
- package/dist/contracts.mjs +63 -0
- package/dist/contracts.mjs.map +1 -0
- package/dist/index.d.mts +1651 -185
- package/dist/index.d.ts +1651 -185
- package/dist/index.js +123 -52
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +123 -52
- package/dist/index.mjs.map +1 -1
- package/dist/map/google.js +1 -0
- package/dist/map/google.js.map +1 -1
- package/dist/map/google.mjs +1 -0
- package/dist/map/google.mjs.map +1 -1
- package/dist/map/mapbox.js +1 -0
- package/dist/map/mapbox.js.map +1 -1
- package/dist/map/mapbox.mjs +1 -0
- package/dist/map/mapbox.mjs.map +1 -1
- package/dist/map/maplibre.js +1 -0
- package/dist/map/maplibre.js.map +1 -1
- package/dist/map/maplibre.mjs +1 -0
- package/dist/map/maplibre.mjs.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/tailwind-preset.js +1 -1
- package/dist/tailwind-preset.js.map +1 -1
- package/dist/tailwind-preset.mjs +1 -1
- package/dist/tailwind-preset.mjs.map +1 -1
- package/package.json +28 -9
package/components/ui/popover.md
CHANGED
|
@@ -6,18 +6,26 @@ props:
|
|
|
6
6
|
- Popover: open?, defaultOpen?, onOpenChange?, modal? (default false)
|
|
7
7
|
- PopoverTrigger: asChild?: boolean — usually a Button
|
|
8
8
|
- PopoverContent: side? "top" | "right" | "bottom" | "left"; align? "start" | "center" | "end"; sideOffset?, alignOffset?, collisionPadding?, className?
|
|
9
|
+
- PopoverContent: surface? (solid | translucent | glass | glass-strong) — what the popover surface is *made of*. `solid` is the default opaque `bg-popover`. `translucent` is the Apple HIG menu-sheet feel. `glass` for floating panels over rich canvases (Studio inspector, image-tool palette).
|
|
9
10
|
- PopoverAnchor: asChild?: boolean — pin the popover to a different element than the trigger
|
|
10
11
|
when_to_use: A floating panel anchored to a trigger that contains interactive content — date pickers, color pickers, filter pickers, "more info" panels, inline forms. Differs from Tooltip (hover-only, no focusable content) and Dialog (modal, blocks the page). DatePicker, DateRangePicker, and the Combobox pattern all compose Popover internally.
|
|
11
|
-
composes_with: [Button (as trigger), Calendar (date picker), Command (combobox), Form controls (inline edit popover)]
|
|
12
|
-
aliases: [popover, dropdown panel, floating panel, inline editor, attached panel, filter pop]
|
|
12
|
+
composes_with: [Button (as trigger), Calendar (date picker), Command (combobox), Form controls (inline edit popover), Code (code-detail popovers)]
|
|
13
|
+
aliases: [popover, dropdown panel, floating panel, inline editor, attached panel, filter pop, popover view, popoverpresentation, attached popover, glass popover, frosted popover, inspector popover]
|
|
13
14
|
---
|
|
14
15
|
|
|
16
|
+
PopoverContent sits at elevation-4. Three scenario recipes — match the material to the canvas the popover floats over.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
### Scenario 1 — Filter popover (default opaque)
|
|
21
|
+
|
|
22
|
+
You're attaching a filter picker to a button in a list/table header. The page behind is mostly white space and a table — there's nothing visually important to preserve through the popover. Opaque is the right default.
|
|
23
|
+
|
|
15
24
|
```jsx
|
|
16
|
-
// Filter popover anchored to a Button trigger.
|
|
17
25
|
<Popover>
|
|
18
26
|
<PopoverTrigger asChild>
|
|
19
27
|
<Button variant="outline" size="sm">
|
|
20
|
-
<Filter /> Filters
|
|
28
|
+
<Filter className="h-4 w-4" /> Filters
|
|
21
29
|
</Button>
|
|
22
30
|
</PopoverTrigger>
|
|
23
31
|
<PopoverContent className="w-72" align="end">
|
|
@@ -30,7 +38,118 @@ aliases: [popover, dropdown panel, floating panel, inline editor, attached panel
|
|
|
30
38
|
<Label>Status</Label>
|
|
31
39
|
<Select>{/* … */}</Select>
|
|
32
40
|
</Stack>
|
|
41
|
+
<Row justify="end" gap="xs">
|
|
42
|
+
<Button variant="ghost" size="sm">Clear</Button>
|
|
43
|
+
<Button size="sm">Apply</Button>
|
|
44
|
+
</Row>
|
|
33
45
|
</Stack>
|
|
34
46
|
</PopoverContent>
|
|
35
47
|
</Popover>
|
|
36
48
|
```
|
|
49
|
+
|
|
50
|
+
`solid` keeps the form fields maximally legible. Filter popovers are read-heavy; legibility wins over aesthetic.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
### Scenario 2 — Glass inspector popover (creative tool aesthetic)
|
|
55
|
+
|
|
56
|
+
You're building Studio, a presentation editor, or a vector tool. The user clicked a selected layer and a popover offers per-element knobs. The canvas behind is the work — they need to keep spatial awareness of what they just clicked. Glass is the canonical signal.
|
|
57
|
+
|
|
58
|
+
```jsx
|
|
59
|
+
<Popover>
|
|
60
|
+
<PopoverTrigger asChild>
|
|
61
|
+
<Button variant="ghost" size="icon"><Palette className="h-4 w-4" /></Button>
|
|
62
|
+
</PopoverTrigger>
|
|
63
|
+
<PopoverContent
|
|
64
|
+
surface="glass"
|
|
65
|
+
className="w-80 shadow-elevation-4"
|
|
66
|
+
align="end"
|
|
67
|
+
sideOffset={8}
|
|
68
|
+
>
|
|
69
|
+
<Stack gap="md">
|
|
70
|
+
<Row justify="between" align="center">
|
|
71
|
+
<span className="text-sm font-medium">Button — selected</span>
|
|
72
|
+
<Badge variant="outline">raised</Badge>
|
|
73
|
+
</Row>
|
|
74
|
+
|
|
75
|
+
<Stack gap="xs">
|
|
76
|
+
<Label>Tone</Label>
|
|
77
|
+
<Row gap="xs">
|
|
78
|
+
<Button size="sm" variant="raised" style={{ "--btn-glow": "var(--selected-glow)" }} />
|
|
79
|
+
<Button size="sm" variant="raised" style={{ "--btn-glow": "var(--success)" }} />
|
|
80
|
+
<Button size="sm" variant="raised" style={{ "--btn-glow": "var(--warning)" }} />
|
|
81
|
+
<Button size="sm" variant="raised" style={{ "--btn-glow": "var(--destructive)" }} />
|
|
82
|
+
</Row>
|
|
83
|
+
</Stack>
|
|
84
|
+
|
|
85
|
+
<Stack gap="xs">
|
|
86
|
+
<Label>Size</Label>
|
|
87
|
+
<ToggleGroup type="single" defaultValue="md">
|
|
88
|
+
<ToggleGroupItem value="sm">sm</ToggleGroupItem>
|
|
89
|
+
<ToggleGroupItem value="md">md</ToggleGroupItem>
|
|
90
|
+
<ToggleGroupItem value="lg">lg</ToggleGroupItem>
|
|
91
|
+
</ToggleGroup>
|
|
92
|
+
</Stack>
|
|
93
|
+
</Stack>
|
|
94
|
+
</PopoverContent>
|
|
95
|
+
</Popover>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`surface="glass"` + `shadow-elevation-4` is the Studio-inspector signature. The user's eye stays on the canvas; the popover reads as chrome layered above it.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
### Scenario 3 — AI suggestion popover (translucent + aura)
|
|
103
|
+
|
|
104
|
+
A different shape from the destructive Dialog confirmation: an inline AI suggestion that surfaces while the user keeps working. Translucent stays light; aura announces the AI origin.
|
|
105
|
+
|
|
106
|
+
```jsx
|
|
107
|
+
<Popover open={hasSuggestion}>
|
|
108
|
+
<PopoverAnchor>
|
|
109
|
+
<Code source={selectedSnippet} language="tsx" highlight={[3]} bare />
|
|
110
|
+
</PopoverAnchor>
|
|
111
|
+
<PopoverContent
|
|
112
|
+
surface="translucent"
|
|
113
|
+
className="w-96 shadow-elevation-4 gds-aura-ring"
|
|
114
|
+
style={{ "--aura-color": "var(--selected-glow)" }}
|
|
115
|
+
side="bottom"
|
|
116
|
+
align="start"
|
|
117
|
+
>
|
|
118
|
+
<Stack gap="sm">
|
|
119
|
+
<Row gap="xs" align="center">
|
|
120
|
+
<Sparkles className="h-4 w-4" />
|
|
121
|
+
<span className="text-sm font-medium">Studio suggestion</span>
|
|
122
|
+
</Row>
|
|
123
|
+
<p className="text-sm">
|
|
124
|
+
This Toolbar would line up edge-to-edge with the TabsList below if it used <code>size="sm"</code>. Apply?
|
|
125
|
+
</p>
|
|
126
|
+
<Row justify="end" gap="xs">
|
|
127
|
+
<Button variant="ghost" size="sm">Dismiss</Button>
|
|
128
|
+
<Button size="sm">Apply</Button>
|
|
129
|
+
</Row>
|
|
130
|
+
</Stack>
|
|
131
|
+
</PopoverContent>
|
|
132
|
+
</Popover>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Note `PopoverAnchor` — the popover is pinned to the selected snippet, not to a trigger button. This is the "annotation surfaces next to the thing it annotates" pattern.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
### Anti-patterns
|
|
140
|
+
|
|
141
|
+
**DO NOT roll glass by hand on PopoverContent.**
|
|
142
|
+
|
|
143
|
+
```jsx
|
|
144
|
+
{/* ❌ Misses edge highlight, fixed-step blur. */}
|
|
145
|
+
<PopoverContent className="bg-popover/50 backdrop-blur-md">
|
|
146
|
+
|
|
147
|
+
{/* ✅ */}
|
|
148
|
+
<PopoverContent surface="glass">
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**DO NOT use Popover for content that needs a modal interaction.** Popover is non-modal — pointer-down outside dismisses it. If the user must decide before continuing, reach for Dialog.
|
|
152
|
+
|
|
153
|
+
**DO NOT use `surface="glass-strong"` on PopoverContent.** It's tuned for full-page overlays; on a 288px popover it just reads as washed out.
|
|
154
|
+
|
|
155
|
+
**DO NOT use Popover when the trigger is a hover target with no focusable content.** That's Tooltip's job — Popover requires focus, Tooltip dismisses on hover-out.
|
|
@@ -7,6 +7,7 @@ props:
|
|
|
7
7
|
- className?: string
|
|
8
8
|
when_to_use: Determinate progress — file uploads, multi-step forms, quota meters. Indeterminate state → use Skeleton or animated Loader icon.
|
|
9
9
|
composes_with: [Card (as a section), Badge (showing % next to it), Label (describing what's loading)]
|
|
10
|
+
aliases: [progress, progress view, progress indicator, progress bar, determinate progress, loading bar, completion bar]
|
|
10
11
|
---
|
|
11
12
|
|
|
12
13
|
```jsx
|
|
@@ -14,7 +14,7 @@ props:
|
|
|
14
14
|
- RadioGroupItem: disabled?: boolean
|
|
15
15
|
when_to_use: A small set of mutually-exclusive options where the user needs to SEE all of them at once — pricing tiers (3-4 options), shipping speed, payment method radio cards. For 5+ options use Select. For a segmented control as part of a toolbar use ToggleGroup. For yes/no use Switch.
|
|
16
16
|
composes_with: [Label (paired with each item via htmlFor), Stack (vertical list), Card (radio card pattern)]
|
|
17
|
-
aliases: [radio group, radio buttons, single-choice, pricing options, payment method]
|
|
17
|
+
aliases: [radio group, radio buttons, single-choice, pricing options, payment method, radio buttons, radio control, single-select]
|
|
18
18
|
---
|
|
19
19
|
|
|
20
20
|
```jsx
|
|
@@ -13,7 +13,7 @@ props:
|
|
|
13
13
|
- ResizableHandle: withHandle?: boolean — show a visible drag affordance (default just a hit-zone)
|
|
14
14
|
when_to_use: A multi-pane layout where the user wants to drag the divider — Slack/Mail-style list+detail, IDE editor+terminal, side-by-side compare view. Static layouts shouldn't use this — reach for AppShell with nav="three-pane" (fixed widths) or Grid (responsive ladder). Built on react-resizable-panels under the hood.
|
|
15
15
|
composes_with: [AppShellMain (host the splitter inside main), ScrollArea (each panel's content), Card]
|
|
16
|
-
aliases: [resizable, splitter, split pane, drag divider, adjustable panels, resizer]
|
|
16
|
+
aliases: [resizable, splitter, split pane, drag divider, adjustable panels, resizer, split view, draggable divider, split pane resizer, ns split view]
|
|
17
17
|
---
|
|
18
18
|
|
|
19
19
|
```jsx
|
package/components/ui/row.md
CHANGED
|
@@ -12,7 +12,7 @@ props:
|
|
|
12
12
|
- children: React.ReactNode
|
|
13
13
|
when_to_use: Horizontal composition — button groups, inline form rows, logo + nav rows, anything on one line. Reach for Row instead of `flex items-center gap-*` so the alignment and spacing are editable through the settings panel. For two-pane layouts with an explicit ratio (sidebar + content, 1/3 + 2/3) use Split instead — Row evenly flows whatever children it holds.
|
|
14
14
|
composes_with: [Button, Input, NavItem, Stack (can wrap a Row), any content component]
|
|
15
|
-
aliases: [row, hstack, horizontal, inline, horizontal layout]
|
|
15
|
+
aliases: [row, hstack, horizontal, inline, horizontal layout, hstack, h-stack, horizontal stack, lazyhstack]
|
|
16
16
|
---
|
|
17
17
|
|
|
18
18
|
```jsx
|
|
@@ -10,7 +10,7 @@ props:
|
|
|
10
10
|
- ScrollBar: orientation? "vertical" | "horizontal" (default vertical)
|
|
11
11
|
when_to_use: Bounded content that needs custom scroll chrome — sidebars with long item lists, chat transcripts, table panels inside a dashboard, anywhere the OS scrollbar would feel out of place against the design tokens. The wrapping element has to have a height constraint (`h-`, `max-h-`, or grid row sizing) or nothing scrolls — scroll-area can't infer a bound on its own. For body-level scrolling, leave the document to the browser.
|
|
12
12
|
composes_with: [Card (long card body), AppShellNav (long sidebar), Sheet (long modal body), Table (sticky-header scrolling list)]
|
|
13
|
-
aliases: [scroll area, scroll container, custom scrollbar, sidebar scroll, panel scroll]
|
|
13
|
+
aliases: [scroll area, scroll container, custom scrollbar, sidebar scroll, panel scroll, scroll view, scrollview, react native scrollview]
|
|
14
14
|
---
|
|
15
15
|
|
|
16
16
|
```jsx
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: SectionBlock
|
|
3
|
+
import: "@gradeui/ui"
|
|
4
|
+
props:
|
|
5
|
+
- padding? (none | sm | md | lg | xl) — vertical rhythm. Defaults to `lg`.
|
|
6
|
+
- background? (transparent | muted | card | primary | gradient) — tonal direction of the section bg.
|
|
7
|
+
- surface? (solid | translucent | glass | glass-strong) — what the section is *made of*. Orthogonal to `background`. Use `glass` for hero sections that float over a generative backdrop / image / dot grid.
|
|
8
|
+
- container? (default | wide | narrow | full) — max-width of the inner content.
|
|
9
|
+
- alignment? (left | center | right) — header / CTA alignment.
|
|
10
|
+
- titleSize? (sm | md | lg | xl)
|
|
11
|
+
- title?: string
|
|
12
|
+
- subtitle?: string
|
|
13
|
+
- cta1? / cta2? — string or `{ text, variant, href, onClick }` config
|
|
14
|
+
- backgroundImage?: string — direct CSS background image url
|
|
15
|
+
- as? "section" | "div" | "article" — semantic root
|
|
16
|
+
- fullBleed?: boolean
|
|
17
|
+
when_to_use: The top-level container for a marketing page section — hero, feature row, pricing table, testimonial strip, FAQ section. Always reach for SectionBlock over a hand-rolled `<section>` so vertical rhythm, container width, and tonal background stay consistent across the page. Pair `background="gradient"` + `surface="glass"` inner Cards for the "modern marketing hero" pattern.
|
|
18
|
+
composes_with: [Card (the most common child — especially with surface="glass"), Grid (feature rows), Stack (hero column), MediaSurface (hero imagery), Code (developer hero), Carousel (logo strips)]
|
|
19
|
+
aliases: [section, section block, hero section, marketing section, page section, content section, container section, feature section, hero, page hero, marketing hero, glass section, gradient section, mesh hero]
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
SectionBlock is the **container axis** of a marketing page; Card is the **content axis** inside it. Three Presence axes still apply to SectionBlock: `background` (tonal direction), `surface` (material), `padding` (depth of vertical rhythm).
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
### Scenario 1 — Standard feature row (default)
|
|
27
|
+
|
|
28
|
+
You're laying out a feature section on a marketing page — a row of cards explaining capabilities. Calm tonal background, generous padding, default container width.
|
|
29
|
+
|
|
30
|
+
```jsx
|
|
31
|
+
<SectionBlock
|
|
32
|
+
padding="lg"
|
|
33
|
+
background="muted"
|
|
34
|
+
title="Built for production"
|
|
35
|
+
subtitle="The hard primitives every team eventually needs."
|
|
36
|
+
alignment="center"
|
|
37
|
+
>
|
|
38
|
+
<Grid cols="3" gap="md">
|
|
39
|
+
<Card>
|
|
40
|
+
<CardHeader>
|
|
41
|
+
<Database className="h-5 w-5" />
|
|
42
|
+
<CardTitle>Data tables</CardTitle>
|
|
43
|
+
<CardDescription>Sorting, filtering, virtualisation.</CardDescription>
|
|
44
|
+
</CardHeader>
|
|
45
|
+
</Card>
|
|
46
|
+
<Card>
|
|
47
|
+
<CardHeader>
|
|
48
|
+
<Map className="h-5 w-5" />
|
|
49
|
+
<CardTitle>Maps</CardTitle>
|
|
50
|
+
<CardDescription>MapLibre default. Mapbox + Google adapters.</CardDescription>
|
|
51
|
+
</CardHeader>
|
|
52
|
+
</Card>
|
|
53
|
+
<Card>
|
|
54
|
+
<CardHeader>
|
|
55
|
+
<MoveVertical className="h-5 w-5" />
|
|
56
|
+
<CardTitle>Drag and drop</CardTitle>
|
|
57
|
+
<CardDescription>dnd-kit underneath, themed against tokens.</CardDescription>
|
|
58
|
+
</CardHeader>
|
|
59
|
+
</Card>
|
|
60
|
+
</Grid>
|
|
61
|
+
</SectionBlock>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
No `surface` prop. The default `solid` is the right answer for in-flow feature rows — the muted background sets the section apart from neighbouring sections cleanly.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
### Scenario 2 — Gradient hero with glass cards (modern marketing pattern)
|
|
69
|
+
|
|
70
|
+
The canonical "shadcn-killer marketing hero" pattern. SectionBlock supplies the gradient mesh; Card children opt into glass; the two compose without either having to know about the other.
|
|
71
|
+
|
|
72
|
+
```jsx
|
|
73
|
+
<SectionBlock
|
|
74
|
+
padding="xl"
|
|
75
|
+
background="gradient"
|
|
76
|
+
alignment="center"
|
|
77
|
+
title="Open the markup. Tell me which one you would merge."
|
|
78
|
+
subtitle="GradeUI produces code you would actually integrate."
|
|
79
|
+
cta1={{ text: "Open Studio", href: "/studio" }}
|
|
80
|
+
cta2={{ text: "Install the library", variant: "outline" }}
|
|
81
|
+
>
|
|
82
|
+
<Grid cols="2" gap="md" className="mt-8">
|
|
83
|
+
<Card surface="glass" className="shadow-elevation-4">
|
|
84
|
+
<CardHeader>
|
|
85
|
+
<CardTitle>v0 — sidebar component</CardTitle>
|
|
86
|
+
<CardDescription>~300 lines</CardDescription>
|
|
87
|
+
</CardHeader>
|
|
88
|
+
<CardContent className="p-0">
|
|
89
|
+
<Code source={v0Code} language="tsx" bare className="p-4 text-xs max-h-72" />
|
|
90
|
+
</CardContent>
|
|
91
|
+
</Card>
|
|
92
|
+
|
|
93
|
+
<Card surface="glass" className="shadow-elevation-4 gds-aura-ring">
|
|
94
|
+
<CardHeader>
|
|
95
|
+
<CardTitle>GradeUI — sidebar component</CardTitle>
|
|
96
|
+
<CardDescription>6 lines</CardDescription>
|
|
97
|
+
</CardHeader>
|
|
98
|
+
<CardContent className="p-0">
|
|
99
|
+
<Code source={gradeCode} language="tsx" bare className="p-4 text-xs max-h-72" />
|
|
100
|
+
</CardContent>
|
|
101
|
+
</Card>
|
|
102
|
+
</Grid>
|
|
103
|
+
</SectionBlock>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
This is the pattern the home-diff-hero scaffold uses. `background="gradient"` paints the mesh; the Cards float through it via `surface="glass"`; `gds-aura-ring` on the second card draws the eye to the recommended path. No Tailwind soup anywhere.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
### Scenario 3 — Glass section over a backgroundImage (image hero)
|
|
111
|
+
|
|
112
|
+
You're using a hero image as the section background. A solid section panel over it would defeat the image. A glass section keeps the image visible while focusing the eye on the content overlay.
|
|
113
|
+
|
|
114
|
+
```jsx
|
|
115
|
+
<SectionBlock
|
|
116
|
+
padding="xl"
|
|
117
|
+
surface="glass"
|
|
118
|
+
backgroundImage="/hero/teams-shipping.jpg"
|
|
119
|
+
alignment="center"
|
|
120
|
+
title="For teams shipping software"
|
|
121
|
+
subtitle="The primitive layer modern product teams actually use."
|
|
122
|
+
cta1={{ text: "Open Studio" }}
|
|
123
|
+
container="narrow"
|
|
124
|
+
>
|
|
125
|
+
<Row justify="center" gap="lg" className="text-sm text-muted-foreground">
|
|
126
|
+
<span>Linear</span><span>Vercel</span><span>Stripe</span><span>Anthropic</span>
|
|
127
|
+
</Row>
|
|
128
|
+
</SectionBlock>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`background` stays at the default `transparent` so the image shows through; `surface="glass"` paints the frosted overlay on top with edge highlight + theme-tuned blur. The narrow container caps content width so the hero stays readable over the image.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
### Anti-patterns
|
|
136
|
+
|
|
137
|
+
**DO NOT roll glass by hand at the section level.**
|
|
138
|
+
|
|
139
|
+
```jsx
|
|
140
|
+
{/* ❌ */}
|
|
141
|
+
<section className="py-20 bg-card/40 backdrop-blur-md">
|
|
142
|
+
|
|
143
|
+
{/* ✅ */}
|
|
144
|
+
<SectionBlock surface="glass" padding="xl">
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**DO NOT use `background="primary"` + `surface="glass"`.** The primary fill is intentionally opaque (it's a brand statement). Layering glass on top makes the brand colour read as washed-out. Pick one signal.
|
|
148
|
+
|
|
149
|
+
**DO NOT skip SectionBlock for marketing rows.** Hand-rolling `<section className="py-20">` means every section gets a slightly different vertical rhythm and container width — the page reads as drift. SectionBlock is the rhythm primitive.
|
|
150
|
+
|
|
151
|
+
**DO NOT use `padding="xl"` for in-app sections.** xl padding is marketing-page territory. In-app section breaks should use `sm` or `md` — anything more and your dashboard reads as a marketing page.
|
|
152
|
+
|
|
153
|
+
**DO NOT use `surface="glass-strong"` on SectionBlock unless the section is acting as a full-page overlay.** It's tuned for very heavy de-emphasis of what's underneath; on a regular section it just looks washed-out.
|
package/components/ui/select.md
CHANGED
|
@@ -10,7 +10,7 @@ props:
|
|
|
10
10
|
- SelectItem: value: string — required; content is the label
|
|
11
11
|
when_to_use: Single-choice from 3+ known options. Fewer than 3 → RadioGroup. Huge list with search → use a Combobox (not in DS yet). Multi-select → not supported by this primitive.
|
|
12
12
|
composes_with: [Label (above SelectTrigger), Form, Card]
|
|
13
|
-
aliases: [dropdown, combobox, picker]
|
|
13
|
+
aliases: [dropdown, combobox, picker, select, pop-up button, popup button, popup picker, picker view, rnpickerselect, react native picker, native picker]
|
|
14
14
|
---
|
|
15
15
|
|
|
16
16
|
```jsx
|
|
@@ -7,7 +7,7 @@ props:
|
|
|
7
7
|
- className?: string
|
|
8
8
|
when_to_use: Light divider between sibling blocks in a Card, list, or header. For section-level partition use extra spacing instead.
|
|
9
9
|
composes_with: [Card (between CardHeader/Content/Footer), navigation menus, any vertical stacks]
|
|
10
|
-
aliases: [divider, rule, hr]
|
|
10
|
+
aliases: [divider, rule, hr, line, horizontal rule]
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
```jsx
|
package/components/ui/sheet.md
CHANGED
|
@@ -6,16 +6,24 @@ props:
|
|
|
6
6
|
- Sheet: open?, defaultOpen?, onOpenChange?, modal? (default true)
|
|
7
7
|
- SheetTrigger: asChild?: boolean
|
|
8
8
|
- SheetContent: side? "top" | "right" | "bottom" | "left" (default "right")
|
|
9
|
+
- SheetContent: surface? (solid | translucent | glass | glass-strong) — what the sheet panel is *made of*. `solid` is the default opaque `bg-background`. Reach for `glass` whenever the canvas behind the sheet (a layout in progress, a media gallery, a dashboard) should remain visible.
|
|
9
10
|
- SheetContent: className?: string — usually set a width (right/left) or height (top/bottom)
|
|
10
11
|
- SheetTitle / SheetDescription: identify the sheet to screen readers; required for accessibility even if visually styled differently
|
|
11
12
|
- SheetClose: asChild? — usually wraps a Button labelled Cancel or Done
|
|
12
|
-
when_to_use: A panel that slides in from a screen edge — mobile nav drawers, side panels for editing a single record without leaving the list, filter trays on small viewports. For a centered focus modal use Dialog. For a transient announcement use Toast (Sonner). For inline reveals use Collapsible.
|
|
13
|
-
composes_with: [Form controls (an inline edit sheet), Button (trigger + close), AppShellNav (mobile-only swap)]
|
|
14
|
-
aliases: [sheet, drawer, side panel, slide-in, nav drawer, mobile drawer, slide-over]
|
|
13
|
+
when_to_use: A panel that slides in from a screen edge — mobile nav drawers, side panels for editing a single record without leaving the list, filter trays on small viewports, Studio-style inspector panels. For a centered focus modal use Dialog. For a transient announcement use Toast (Sonner). For inline reveals use Collapsible.
|
|
14
|
+
composes_with: [Form controls (an inline edit sheet), Button (trigger + close), AppShellNav (mobile-only swap), Code (changelog drawers), MediaSurface (image-detail sheets)]
|
|
15
|
+
aliases: [sheet, drawer, side panel, slide-in, nav drawer, mobile drawer, slide-over, action sheet, modal sheet, bottom sheet, side sheet, react native modal sheet, bottom-sheet, ios action sheet, inspector panel, glass sheet, frosted drawer]
|
|
15
16
|
---
|
|
16
17
|
|
|
18
|
+
SheetContent sits at elevation-5. The `surface` axis controls material independently of `side` (which controls layout direction) — every combination is valid.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
### Scenario 1 — Edit-record drawer (default opaque)
|
|
23
|
+
|
|
24
|
+
A right-edge drawer that lets a user edit one record without losing their place in a list. The list is the user's context — the drawer doesn't need to blur it; it just needs to be visibly distinct.
|
|
25
|
+
|
|
17
26
|
```jsx
|
|
18
|
-
// Edit-record drawer from the right edge.
|
|
19
27
|
<Sheet>
|
|
20
28
|
<SheetTrigger asChild>
|
|
21
29
|
<Button variant="outline">Edit user</Button>
|
|
@@ -44,3 +52,93 @@ aliases: [sheet, drawer, side panel, slide-in, nav drawer, mobile drawer, slide-
|
|
|
44
52
|
</SheetContent>
|
|
45
53
|
</Sheet>
|
|
46
54
|
```
|
|
55
|
+
|
|
56
|
+
`solid` is the right default for editing workflows. Form fields need maximum legibility; blur behind them works against that.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
### Scenario 2 — Glass inspector panel (creative tool aesthetic)
|
|
61
|
+
|
|
62
|
+
You're building a creative tool. The canvas is the work — a Studio layout, an image being annotated, a presentation slide. The inspector panel needs to live alongside the work without obscuring it. Glass is the canonical "I am chrome, not content" signal.
|
|
63
|
+
|
|
64
|
+
```jsx
|
|
65
|
+
<Sheet open={hasSelection} modal={false}>
|
|
66
|
+
<SheetContent
|
|
67
|
+
side="right"
|
|
68
|
+
surface="glass"
|
|
69
|
+
className="w-96 shadow-elevation-5"
|
|
70
|
+
>
|
|
71
|
+
<SheetHeader>
|
|
72
|
+
<SheetTitle>Selection</SheetTitle>
|
|
73
|
+
<SheetDescription>Button — Toolbar > trailing</SheetDescription>
|
|
74
|
+
</SheetHeader>
|
|
75
|
+
|
|
76
|
+
<Stack gap="md" className="py-4">
|
|
77
|
+
<Stack gap="xs">
|
|
78
|
+
<Label>Variant</Label>
|
|
79
|
+
<Select defaultValue="raised">{/* … */}</Select>
|
|
80
|
+
</Stack>
|
|
81
|
+
<Stack gap="xs">
|
|
82
|
+
<Label>Size</Label>
|
|
83
|
+
<ToggleGroup type="single" defaultValue="md">
|
|
84
|
+
<ToggleGroupItem value="sm">sm</ToggleGroupItem>
|
|
85
|
+
<ToggleGroupItem value="md">md</ToggleGroupItem>
|
|
86
|
+
<ToggleGroupItem value="lg">lg</ToggleGroupItem>
|
|
87
|
+
</ToggleGroup>
|
|
88
|
+
</Stack>
|
|
89
|
+
</Stack>
|
|
90
|
+
</SheetContent>
|
|
91
|
+
</Sheet>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Three things to notice: `modal={false}` so the user keeps interacting with the canvas while the inspector is open; `surface="glass"` so the canvas reads through; `shadow-elevation-5` to lift the panel cleanly off the canvas. This is the Studio inspector pattern.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
### Scenario 3 — Bottom action sheet (mobile, glass for iOS feel)
|
|
99
|
+
|
|
100
|
+
The iOS-native action sheet has glass behind it. Matching that material on mobile flows is "feels like a native app" by default.
|
|
101
|
+
|
|
102
|
+
```jsx
|
|
103
|
+
<Sheet open={pickerOpen} onOpenChange={setPickerOpen}>
|
|
104
|
+
<SheetContent
|
|
105
|
+
side="bottom"
|
|
106
|
+
surface="glass"
|
|
107
|
+
className="rounded-t-2xl"
|
|
108
|
+
>
|
|
109
|
+
<SheetHeader className="text-center">
|
|
110
|
+
<SheetTitle>Share screen</SheetTitle>
|
|
111
|
+
</SheetHeader>
|
|
112
|
+
<Stack gap="xs" className="py-4">
|
|
113
|
+
<Button variant="ghost" className="justify-start"><Mail /> Email</Button>
|
|
114
|
+
<Button variant="ghost" className="justify-start"><MessageCircle /> Message</Button>
|
|
115
|
+
<Button variant="ghost" className="justify-start"><Copy /> Copy link</Button>
|
|
116
|
+
</Stack>
|
|
117
|
+
<SheetClose asChild>
|
|
118
|
+
<Button variant="outline" className="w-full">Cancel</Button>
|
|
119
|
+
</SheetClose>
|
|
120
|
+
</SheetContent>
|
|
121
|
+
</Sheet>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`side="bottom"` + `surface="glass"` + `rounded-t-2xl` is the iOS action-sheet recipe. The rounded top corners signal "this can be dismissed by dragging down" even before any gesture handler is wired up.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
### Anti-patterns
|
|
129
|
+
|
|
130
|
+
**DO NOT roll glass by hand on SheetContent.**
|
|
131
|
+
|
|
132
|
+
```jsx
|
|
133
|
+
{/* ❌ Tailwind soup — no edge highlight, blur isn't theme-tuned. */}
|
|
134
|
+
<SheetContent className="bg-background/60 backdrop-blur-md">
|
|
135
|
+
|
|
136
|
+
{/* ✅ */}
|
|
137
|
+
<SheetContent surface="glass">
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**DO NOT use `surface="glass"` for a modal sheet that contains a long form.** Form legibility wins over aesthetic. If the user is going to spend 30 seconds in this sheet, give them an opaque background.
|
|
141
|
+
|
|
142
|
+
**DO NOT pair `surface="glass"` with `modal={true}` and the default scrim.** The scrim already dims the canvas — adding glass on top of a dimmed canvas reads as "two competing layers of de-emphasis". Either turn off the scrim (`modal={false}`), or use `surface="solid"`.
|
|
143
|
+
|
|
144
|
+
**DO NOT skip SheetTitle.** Screen readers announce it on open. If the design has no visible title, wrap a `sr-only` one.
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: SideMenu
|
|
3
|
-
import: "@gradeui/ui"
|
|
4
|
-
props:
|
|
5
|
-
- sections: SideMenuSection[] — top-level groups; each section has `{ title?, items }`
|
|
6
|
-
- sections[].title?: string — optional group heading
|
|
7
|
-
- sections[].items: SideMenuItem[] — `{ label, href?, icon?, active?, badge?, onClick? }`
|
|
8
|
-
- activeHref?: string — auto-derives `active` on matching items; falls back to per-item `active`
|
|
9
|
-
- onItemClick?: (item) => void — fires for client-side routing; complements per-item onClick
|
|
10
|
-
- className?: string
|
|
11
|
-
when_to_use: The primary navigation rail inside an AppShell — Admin/Settings/Billing, Inbox/Sent/Archive, file-tree-ish sidebars. Always sits inside <AppShellNav placement="side">. For top horizontal nav, compose Row + Button/Link directly — Side Menu is vertical-stack-of-items by design. For palette-style jump-to, use Command.
|
|
12
|
-
composes_with: [AppShellNav, AppShell, Avatar (header above the menu), Badge (item counts)]
|
|
13
|
-
aliases: [side menu, sidebar nav, side nav, vertical nav, sidebar items, rail, side bar]
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
```jsx
|
|
17
|
-
// Inbox/Sent/Archive rail inside AppShellNav.
|
|
18
|
-
<AppShellNav placement="side">
|
|
19
|
-
<SideMenu
|
|
20
|
-
activeHref="/inbox"
|
|
21
|
-
sections={[
|
|
22
|
-
{
|
|
23
|
-
title: "Mail",
|
|
24
|
-
items: [
|
|
25
|
-
{ label: "Inbox", href: "/inbox", icon: <Inbox />, badge: "12" },
|
|
26
|
-
{ label: "Sent", href: "/sent", icon: <Send /> },
|
|
27
|
-
{ label: "Archive", href: "/archive", icon: <Archive /> },
|
|
28
|
-
],
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
title: "Labels",
|
|
32
|
-
items: [
|
|
33
|
-
{ label: "Work", href: "/label/work", icon: <Tag /> },
|
|
34
|
-
{ label: "Personal", href: "/label/personal", icon: <Tag /> },
|
|
35
|
-
],
|
|
36
|
-
},
|
|
37
|
-
]}
|
|
38
|
-
/>
|
|
39
|
-
</AppShellNav>
|
|
40
|
-
```
|