@gradeui/ui 1.1.0 → 1.3.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/background-fill.md +109 -0
- package/components/ui/checkbox-card.md +43 -0
- package/components/ui/checkbox.md +2 -2
- package/components/ui/code.md +2 -1
- package/components/ui/composer.md +226 -0
- package/components/ui/dropdown-menu.md +2 -0
- package/components/ui/field.md +26 -0
- package/components/ui/fill-picker.md +36 -0
- package/components/ui/input.md +27 -2
- package/components/ui/label.md +2 -1
- package/components/ui/logo.md +57 -0
- package/components/ui/message.md +263 -0
- package/components/ui/radio-card.md +41 -0
- package/components/ui/radio-group.md +2 -2
- package/components/ui/select.md +16 -4
- package/components/ui/switch-card.md +30 -0
- package/components/ui/switch.md +2 -2
- package/components/ui/textarea.md +2 -1
- package/components/ui/three-scene.md +22 -1
- 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 +1255 -39
- package/dist/index.d.ts +1255 -39
- package/dist/index.js +122 -47
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +122 -47
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +3 -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 +11 -1
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Message
|
|
3
|
+
import: "@gradeui/ui"
|
|
4
|
+
props:
|
|
5
|
+
- author: string — display name of the message author
|
|
6
|
+
- timestamp?: ReactNode — string ("11:24", "2 hours ago") or any node for custom formatting
|
|
7
|
+
- avatar?: ReactNode — slot for any `<Avatar>` composition; omit for grouped messages from the same author
|
|
8
|
+
- badge?: ReactNode — small chip(s) next to the author name (OP, Bot, Admin, role tag)
|
|
9
|
+
- edited?: boolean | string — renders "(edited)" hint next to timestamp; pass a string to customise ("(edited 2 minutes ago)")
|
|
10
|
+
- pinned?: boolean — renders a pin glyph + "Pinned" label above the header row for sticky / pinned messages
|
|
11
|
+
- actions?: ReactNode — end-of-header slot, typically hover-revealed icon buttons (reply / react / more)
|
|
12
|
+
- reactions?: ReactNode — slot below the body, typically a Row of reaction chips (emoji + count)
|
|
13
|
+
- threadCount?: number — renders a "N replies" link affordance below the body
|
|
14
|
+
- onThreadClick?: () => void — handler for the threadCount affordance
|
|
15
|
+
- align?: "start" | "end" — `start` (default) puts the avatar on the left; `end` mirrors for "your messages" in DM threads
|
|
16
|
+
- density?: "default" | "compact" — `default` is the canonical chat / channel-feed rhythm; `compact` tightens text sizes + gaps for dense side panels (Studio comments, activity feeds). Pair with `Avatar size="xs"` for the tightest stack.
|
|
17
|
+
- children: ReactNode — body content (plain text or rich nodes)
|
|
18
|
+
- className?: string
|
|
19
|
+
when_to_use: |
|
|
20
|
+
The canonical "avatar + author + timestamp + body" row. THE PRIMITIVE
|
|
21
|
+
for any chat surface, comment thread, post-reply, activity log, or
|
|
22
|
+
notification feed that follows the people-and-text shape.
|
|
23
|
+
|
|
24
|
+
CONCRETE TEST — if you find yourself composing an `<Avatar>` followed
|
|
25
|
+
by a `<Row>` of author name + timestamp, with a `<p>` or `<span>`
|
|
26
|
+
body below, STOP. That is `<Message>`. Reach for it directly.
|
|
27
|
+
|
|
28
|
+
Slack-style channel feed, Discord messages, Teams chat, Linear /
|
|
29
|
+
GitHub / Jira comments, Reddit replies, Twitter/X posts in a thread,
|
|
30
|
+
Notion comment sidebars, in-app activity logs, notification rows —
|
|
31
|
+
every one of these IS `<Message>`. Do not roll the layout inline.
|
|
32
|
+
|
|
33
|
+
For non-people activity (system events, log lines, status pings) use
|
|
34
|
+
Callout or a plain Row instead — Message implies a human author.
|
|
35
|
+
composes_with: [Avatar (in the avatar slot — pair with AvatarFallback tone="..." for stable per-author colour), Badge (in the badge slot for role / OP / bot tags), Button (in actions, typically size="icon" + variant="ghost"), Stack (host multiple Messages in a thread), Card (wrap a Stack of Messages for a comment-thread block)]
|
|
36
|
+
aliases: [
|
|
37
|
+
message, chat message, comment, post, reply, activity row, notification row,
|
|
38
|
+
thread row, channel message, dm message, slack message, discord message,
|
|
39
|
+
teams message, channel feed message, feed item, feed row, message row,
|
|
40
|
+
user message, user post, conversation message, conversation row,
|
|
41
|
+
inline comment, threaded reply, message bubble, chat bubble, talk bubble
|
|
42
|
+
]
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
```jsx
|
|
46
|
+
// Comment thread shape — avatar left, body below the author row.
|
|
47
|
+
<Stack gap="md">
|
|
48
|
+
<Message
|
|
49
|
+
author="alice"
|
|
50
|
+
timestamp="2 hours ago"
|
|
51
|
+
avatar={
|
|
52
|
+
<Avatar size="sm">
|
|
53
|
+
<AvatarFallback tone="violet">A</AvatarFallback>
|
|
54
|
+
</Avatar>
|
|
55
|
+
}
|
|
56
|
+
>
|
|
57
|
+
Splitting this into two PRs makes the review tractable.
|
|
58
|
+
</Message>
|
|
59
|
+
<Message
|
|
60
|
+
author="ben"
|
|
61
|
+
timestamp="1 hour ago"
|
|
62
|
+
badge={<Badge variant="outline" className="text-[10px]">OP</Badge>}
|
|
63
|
+
avatar={
|
|
64
|
+
<Avatar size="sm">
|
|
65
|
+
<AvatarFallback tone="amber">B</AvatarFallback>
|
|
66
|
+
</Avatar>
|
|
67
|
+
}
|
|
68
|
+
>
|
|
69
|
+
Agreed. I'll take the schema PR.
|
|
70
|
+
</Message>
|
|
71
|
+
</Stack>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
```jsx
|
|
75
|
+
// Chat shape — your messages right-aligned via align="end".
|
|
76
|
+
<Stack gap="md">
|
|
77
|
+
<Message
|
|
78
|
+
author="alice"
|
|
79
|
+
timestamp="11:24"
|
|
80
|
+
avatar={
|
|
81
|
+
<Avatar size="xs">
|
|
82
|
+
<AvatarFallback tone="violet">A</AvatarFallback>
|
|
83
|
+
</Avatar>
|
|
84
|
+
}
|
|
85
|
+
>
|
|
86
|
+
Hey, how's the launch going?
|
|
87
|
+
</Message>
|
|
88
|
+
<Message
|
|
89
|
+
author="you"
|
|
90
|
+
timestamp="11:26"
|
|
91
|
+
align="end"
|
|
92
|
+
avatar={
|
|
93
|
+
<Avatar size="xs">
|
|
94
|
+
<AvatarFallback tone="emerald">Y</AvatarFallback>
|
|
95
|
+
</Avatar>
|
|
96
|
+
}
|
|
97
|
+
>
|
|
98
|
+
Launch image is in, scheduling now.
|
|
99
|
+
</Message>
|
|
100
|
+
</Stack>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```jsx
|
|
104
|
+
// Full Slack-style message — edited indicator, pinned flag, reactions
|
|
105
|
+
// row, threaded reply count, role badge, hover actions.
|
|
106
|
+
<Message
|
|
107
|
+
author="alice"
|
|
108
|
+
timestamp="11:24"
|
|
109
|
+
edited
|
|
110
|
+
pinned
|
|
111
|
+
badge={<Badge variant="secondary" className="text-[10px]">Designer</Badge>}
|
|
112
|
+
avatar={
|
|
113
|
+
<Avatar size="md">
|
|
114
|
+
<AvatarFallback tone="violet">A</AvatarFallback>
|
|
115
|
+
</Avatar>
|
|
116
|
+
}
|
|
117
|
+
reactions={
|
|
118
|
+
<>
|
|
119
|
+
<Badge variant="outline" className="gap-1 cursor-pointer">👍 4</Badge>
|
|
120
|
+
<Badge variant="outline" className="gap-1 cursor-pointer">🎉 2</Badge>
|
|
121
|
+
</>
|
|
122
|
+
}
|
|
123
|
+
threadCount={3}
|
|
124
|
+
onThreadClick={() => openThread(messageId)}
|
|
125
|
+
>
|
|
126
|
+
Updated the token spec — review when you have a chance.
|
|
127
|
+
</Message>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```jsx
|
|
131
|
+
// Slack / Discord channel feed — with role badge + hover-revealed actions.
|
|
132
|
+
<Stack gap="lg">
|
|
133
|
+
{messages.map((m) => (
|
|
134
|
+
<Message
|
|
135
|
+
key={m.id}
|
|
136
|
+
author={m.user}
|
|
137
|
+
timestamp={m.time}
|
|
138
|
+
badge={<Badge variant="secondary" className="text-[10px]">{m.role}</Badge>}
|
|
139
|
+
avatar={
|
|
140
|
+
<Avatar size="md">
|
|
141
|
+
<AvatarImage src={m.avatar} />
|
|
142
|
+
<AvatarFallback tone="sky">{m.user.charAt(0)}</AvatarFallback>
|
|
143
|
+
</Avatar>
|
|
144
|
+
}
|
|
145
|
+
actions={
|
|
146
|
+
<Row gap="xs" className="opacity-0 group-hover:opacity-100 transition-opacity">
|
|
147
|
+
<Button size="icon" variant="ghost" className="h-6 w-6"><Smile className="h-3 w-3" /></Button>
|
|
148
|
+
<Button size="icon" variant="ghost" className="h-6 w-6"><Reply className="h-3 w-3" /></Button>
|
|
149
|
+
<Button size="icon" variant="ghost" className="h-6 w-6"><MoreHorizontal className="h-3 w-3" /></Button>
|
|
150
|
+
</Row>
|
|
151
|
+
}
|
|
152
|
+
className="group"
|
|
153
|
+
>
|
|
154
|
+
{m.text}
|
|
155
|
+
</Message>
|
|
156
|
+
))}
|
|
157
|
+
</Stack>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```jsx
|
|
161
|
+
// Compact density — for narrow side panels (Studio Comments tab,
|
|
162
|
+
// activity feeds, notification rows). Notice the smaller Avatar size
|
|
163
|
+
// pairs naturally with density="compact".
|
|
164
|
+
<Stack gap="sm">
|
|
165
|
+
<Message
|
|
166
|
+
density="compact"
|
|
167
|
+
author="alice"
|
|
168
|
+
timestamp="2m ago"
|
|
169
|
+
edited="· edited 1m ago"
|
|
170
|
+
avatar={
|
|
171
|
+
<Avatar size="xs">
|
|
172
|
+
<AvatarFallback tone="violet">A</AvatarFallback>
|
|
173
|
+
</Avatar>
|
|
174
|
+
}
|
|
175
|
+
>
|
|
176
|
+
Splitting this into two PRs makes the review tractable.
|
|
177
|
+
</Message>
|
|
178
|
+
<Message
|
|
179
|
+
density="compact"
|
|
180
|
+
author="ben"
|
|
181
|
+
timestamp="1m ago"
|
|
182
|
+
avatar={
|
|
183
|
+
<Avatar size="xs">
|
|
184
|
+
<AvatarFallback tone="amber">B</AvatarFallback>
|
|
185
|
+
</Avatar>
|
|
186
|
+
}
|
|
187
|
+
>
|
|
188
|
+
Agreed. I'll take the schema PR.
|
|
189
|
+
</Message>
|
|
190
|
+
</Stack>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Anti-patterns
|
|
194
|
+
|
|
195
|
+
```jsx
|
|
196
|
+
// ❌ Rolling the message layout by hand from Avatar + Row + Badge + spans.
|
|
197
|
+
// This is the EXACT shape Message exists to consolidate — caught in
|
|
198
|
+
// the wild on a "Slack clone" prompt where the model assembled this
|
|
199
|
+
// inline instead of reaching for Message. The result loses the
|
|
200
|
+
// align="end" knob, the actions slot, the data-gds-part hooks, and
|
|
201
|
+
// duplicates the same flex template across every consumer.
|
|
202
|
+
{messages.map((msg) => (
|
|
203
|
+
<div className="group flex gap-4">
|
|
204
|
+
<Avatar className="w-9 h-9 shrink-0">
|
|
205
|
+
<AvatarImage src={msg.avatar} />
|
|
206
|
+
<AvatarFallback>{msg.user.charAt(0)}</AvatarFallback>
|
|
207
|
+
</Avatar>
|
|
208
|
+
<div className="flex-1 min-w-0">
|
|
209
|
+
<Row gap="sm" align="baseline">
|
|
210
|
+
<span className="font-semibold text-sm">{msg.user}</span>
|
|
211
|
+
<Badge variant="secondary" className="text-[10px]">{msg.role}</Badge>
|
|
212
|
+
<span className="text-[10px] text-muted-foreground">{msg.time}</span>
|
|
213
|
+
</Row>
|
|
214
|
+
<p className="text-sm mt-1">{msg.text}</p>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
))}
|
|
218
|
+
|
|
219
|
+
// ✅ The Grade way.
|
|
220
|
+
{messages.map((msg) => (
|
|
221
|
+
<Message
|
|
222
|
+
key={msg.id}
|
|
223
|
+
author={msg.user}
|
|
224
|
+
timestamp={msg.time}
|
|
225
|
+
badge={<Badge variant="secondary" className="text-[10px]">{msg.role}</Badge>}
|
|
226
|
+
avatar={
|
|
227
|
+
<Avatar size="md">
|
|
228
|
+
<AvatarImage src={msg.avatar} />
|
|
229
|
+
<AvatarFallback>{msg.user.charAt(0)}</AvatarFallback>
|
|
230
|
+
</Avatar>
|
|
231
|
+
}
|
|
232
|
+
>
|
|
233
|
+
{msg.text}
|
|
234
|
+
</Message>
|
|
235
|
+
))}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
```jsx
|
|
239
|
+
// ❌ Building a custom "AuthorDot" or "MessageRow" component inline as
|
|
240
|
+
// a one-off helper inside a scaffold. Three scaffolds did this before
|
|
241
|
+
// Message landed; the pattern is always identical.
|
|
242
|
+
function MessageRow({ user, body, time }) {
|
|
243
|
+
return (
|
|
244
|
+
<div className="flex gap-3 items-start">
|
|
245
|
+
<div className="h-7 w-7 rounded-full bg-violet-500/20 ...">{user[0]}</div>
|
|
246
|
+
<div>
|
|
247
|
+
<Row><strong>{user}</strong> <small>{time}</small></Row>
|
|
248
|
+
<p>{body}</p>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ✅ Use Message. The colored-initials avatar pattern is covered by
|
|
255
|
+
// Avatar + AvatarFallback tone="...".
|
|
256
|
+
<Message
|
|
257
|
+
author={user}
|
|
258
|
+
timestamp={time}
|
|
259
|
+
avatar={<Avatar size="sm"><AvatarFallback tone="violet">{user[0]}</AvatarFallback></Avatar>}
|
|
260
|
+
>
|
|
261
|
+
{body}
|
|
262
|
+
</Message>
|
|
263
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: RadioCard
|
|
3
|
+
import: "@gradeui/ui"
|
|
4
|
+
props:
|
|
5
|
+
- value: string (required) — the radio value
|
|
6
|
+
- label?: ReactNode — title line
|
|
7
|
+
- description?: ReactNode — secondary line
|
|
8
|
+
- aside?: ReactNode — slot before the indicator (a Badge, price, hint)
|
|
9
|
+
- hideIndicator?: boolean — hide the dot; selection shown by the card border + background
|
|
10
|
+
- indicatorPosition?: "leading" | "trailing" — default trailing
|
|
11
|
+
- children?: ReactNode — arbitrary static content (image, custom layout) instead of label/description
|
|
12
|
+
when_to_use: Single-select where each option is a whole selectable card (shipping options, plan picker, onboarding choices). The whole card is the control, so focus and the checked state live on the card surface and the entire card is clickable. MUST sit inside a RadioGroup (keeps roving focus + single-select). Static content only — never nest an interactive control (Slider/Input/Button/link) inside. For a plain radio + label row use Field instead.
|
|
13
|
+
composes_with: [RadioGroup (required parent), Badge (in aside), MediaSurface (custom children)]
|
|
14
|
+
aliases: [radio card, selectable card, option card, plan picker, choice card, pricing tier, segmented choice card]
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
```jsx
|
|
18
|
+
<RadioGroup defaultValue="standard" className="grid gap-3">
|
|
19
|
+
<RadioCard value="standard" label="Standard" description="4–10 business days" />
|
|
20
|
+
<RadioCard value="fast" label="Fast" description="2–5 business days" />
|
|
21
|
+
<RadioCard value="next-day" label="Next day" description="1 business day" />
|
|
22
|
+
</RadioGroup>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Indicator on the leading edge instead of trailing:
|
|
26
|
+
|
|
27
|
+
```jsx
|
|
28
|
+
<RadioGroup defaultValue="standard" className="grid gap-3">
|
|
29
|
+
<RadioCard value="standard" indicatorPosition="leading" label="Standard" description="4–10 business days" />
|
|
30
|
+
<RadioCard value="fast" indicatorPosition="leading" label="Fast" description="2–5 business days" />
|
|
31
|
+
</RadioGroup>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
No visible dot (selection reads from the card border + background), laid out in a grid via className on the group:
|
|
35
|
+
|
|
36
|
+
```jsx
|
|
37
|
+
<RadioGroup defaultValue="m" className="grid grid-cols-2 gap-3">
|
|
38
|
+
<RadioCard value="s" hideIndicator label="Small" description="Up to 10 seats" />
|
|
39
|
+
<RadioCard value="m" hideIndicator label="Medium" description="Up to 50 seats" />
|
|
40
|
+
</RadioGroup>
|
|
41
|
+
```
|
|
@@ -12,8 +12,8 @@ props:
|
|
|
12
12
|
- RadioGroupItem: value: string — what the group emits when this item is picked
|
|
13
13
|
- RadioGroupItem: id?: string — pair with a <Label htmlFor> for click-on-label
|
|
14
14
|
- RadioGroupItem: disabled?: boolean
|
|
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
|
-
composes_with: [Label (paired with each item via htmlFor),
|
|
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. When each option should be a whole clickable card (label + description, selected state on the card), use RadioCard inside the RadioGroup instead of a Card with a radio in the corner. For a plain label + description row, wrap RadioGroupItem in Field. For 5+ options use Select. For a segmented control as part of a toolbar use ToggleGroup. For yes/no use Switch.
|
|
16
|
+
composes_with: [Label (paired with each item via htmlFor), Field (label + description row), RadioCard (whole-card selectable option), Stack (vertical list)]
|
|
17
17
|
aliases: [radio group, radio buttons, single-choice, pricing options, payment method, radio buttons, radio control, single-select]
|
|
18
18
|
---
|
|
19
19
|
|
package/components/ui/select.md
CHANGED
|
@@ -4,11 +4,11 @@ import: "@gradeui/ui"
|
|
|
4
4
|
subcomponents: [SelectTrigger, SelectValue, SelectContent, SelectItem, SelectGroup, SelectLabel, SelectSeparator]
|
|
5
5
|
props:
|
|
6
6
|
- Select: value?, onValueChange?, defaultValue?, disabled? — Radix root
|
|
7
|
-
- SelectTrigger: wraps the clickable control
|
|
7
|
+
- SelectTrigger: size?: "default" | "sm" | "xs" — control density; wraps the clickable control, nest SelectValue inside
|
|
8
8
|
- SelectValue: placeholder?: string — text when nothing is selected
|
|
9
|
-
- SelectContent:
|
|
10
|
-
- SelectItem: value: string — required; content is the label
|
|
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.
|
|
9
|
+
- SelectContent: size?: "default" | "sm" | "xs" — menu density; cascades to every SelectItem inside via context so a compact trigger gets a compact menu. Accepts items via children.
|
|
10
|
+
- SelectItem: value: string — required; content is the label. Inherits density from SelectContent.
|
|
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. In dense tool panels, set size="xs" on BOTH the trigger and the content so the closed control and open menu match.
|
|
12
12
|
composes_with: [Label (above SelectTrigger), Form, Card]
|
|
13
13
|
aliases: [dropdown, combobox, picker, select, pop-up button, popup button, popup picker, picker view, rnpickerselect, react native picker, native picker]
|
|
14
14
|
---
|
|
@@ -22,3 +22,15 @@ aliases: [dropdown, combobox, picker, select, pop-up button, popup button, popup
|
|
|
22
22
|
</SelectContent>
|
|
23
23
|
</Select>
|
|
24
24
|
```
|
|
25
|
+
|
|
26
|
+
Compact, for dense panels — match the trigger and menu density:
|
|
27
|
+
|
|
28
|
+
```jsx
|
|
29
|
+
<Select defaultValue="md">
|
|
30
|
+
<SelectTrigger size="xs"><SelectValue /></SelectTrigger>
|
|
31
|
+
<SelectContent size="xs">
|
|
32
|
+
<SelectItem value="sm">Small</SelectItem>
|
|
33
|
+
<SelectItem value="md">Medium</SelectItem>
|
|
34
|
+
</SelectContent>
|
|
35
|
+
</Select>
|
|
36
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: SwitchCard
|
|
3
|
+
import: "@gradeui/ui"
|
|
4
|
+
props:
|
|
5
|
+
- checked? / defaultChecked? / onCheckedChange? — standard switch state
|
|
6
|
+
- label?: ReactNode — title line
|
|
7
|
+
- description?: ReactNode — secondary line
|
|
8
|
+
- aside?: ReactNode — slot before the indicator (a Badge, price, hint)
|
|
9
|
+
- hideIndicator?: boolean — hide the switch glyph; state shown by the card border + background
|
|
10
|
+
- indicatorPosition?: "leading" | "trailing" — default trailing
|
|
11
|
+
- children?: ReactNode — arbitrary static content instead of label/description
|
|
12
|
+
when_to_use: A prominent on/off setting presented as a whole selectable card. The whole card is the switch, so the toggled state lives on the card surface. Standalone. For a row of compact settings (label left, small Switch right) use Field layout="setting" instead — SwitchCard is for the heavier, card-sized toggle.
|
|
13
|
+
composes_with: [Badge (in aside), Stack (stacking several)]
|
|
14
|
+
aliases: [switch card, toggle card, setting card, feature toggle card]
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
```jsx
|
|
18
|
+
<SwitchCard label="Auto-renew" description="Renew this plan automatically each month" defaultChecked />
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Indicator on the leading edge:
|
|
22
|
+
|
|
23
|
+
```jsx
|
|
24
|
+
<SwitchCard
|
|
25
|
+
indicatorPosition="leading"
|
|
26
|
+
label="Auto-renew"
|
|
27
|
+
description="Renew this plan automatically each month"
|
|
28
|
+
defaultChecked
|
|
29
|
+
/>
|
|
30
|
+
```
|
package/components/ui/switch.md
CHANGED
|
@@ -7,8 +7,8 @@ props:
|
|
|
7
7
|
- defaultChecked?: boolean
|
|
8
8
|
- disabled?: boolean
|
|
9
9
|
- id?: string
|
|
10
|
-
when_to_use: Instant on/off setting ("Enable notifications", "Dark mode"). Commits on toggle — no submit button needed. For selecting-from-a-list use Checkbox.
|
|
11
|
-
composes_with: [Label (via htmlFor), Card (settings rows)]
|
|
10
|
+
when_to_use: Instant on/off setting ("Enable notifications", "Dark mode"). Commits on toggle — no submit button needed. For selecting-from-a-list use Checkbox. For a settings row (label + description on the left, Switch on the right) use Field layout="setting". For a prominent on/off presented as a whole selectable card, use SwitchCard.
|
|
11
|
+
composes_with: [Label (via htmlFor), Field (layout="setting" settings row), SwitchCard (whole-card toggle), Card (settings rows)]
|
|
12
12
|
aliases: [toggle, switch, on/off switch, ios toggle, toggle switch, switch control, react native switch]
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
name: Textarea
|
|
3
3
|
import: "@gradeui/ui"
|
|
4
4
|
props:
|
|
5
|
+
- size?: "default" | "sm" | "xs" — control density, mirrors Input. default = min-h-80 / text-sm; sm and xs shrink the min-height + padding for dense panels.
|
|
5
6
|
- All native textarea HTML attrs (rows, value, onChange, placeholder, disabled)
|
|
6
|
-
when_to_use: Multi-line text entry (descriptions, messages, comments). Pair with a Label. Single-line input → use Input instead.
|
|
7
|
+
when_to_use: Multi-line text entry (descriptions, messages, comments). Pair with a Label. Single-line input → use Input instead. Use size="sm"/"xs" in dense tool panels.
|
|
7
8
|
composes_with: [Label, Form, Card (in CardContent)]
|
|
8
9
|
aliases: [text area, multiline, comment box, message field, text editor, multi-line text, multiline input, multiline text field, comments box, multiline textinput]
|
|
9
10
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: ThreeScene
|
|
3
3
|
import: "@gradeui/ui"
|
|
4
4
|
props:
|
|
5
|
-
- preset?: "space" | "plasma" | "voronoi" | "synthwave" — shader preset id from the registry
|
|
5
|
+
- preset?: "mesh" | "waves" | "space" | "plasma" | "voronoi" | "synthwave" — shader preset id from the registry
|
|
6
6
|
- fragmentShader?: string — user-authored GLSL body; takes precedence over preset
|
|
7
7
|
- onShaderError?: (error: ShaderCompileError) => void — fires on compile failure; scene falls back to `preset="space"`
|
|
8
8
|
- postPreset?: "none" | "vhs" | "cinematic" | "synthwave" | "crt" (default "vhs") — post-processing pass
|
|
@@ -23,6 +23,8 @@ notes: |
|
|
|
23
23
|
## Path 1 — `preset` (pick one, fastest, highest quality)
|
|
24
24
|
|
|
25
25
|
Valid `preset` ids (complete list — do NOT invent any others):
|
|
26
|
+
- "mesh" — smooth moving blobs of primary/secondary/accent over the background; soft, theme-reactive. THE default soft background. Default post: "none".
|
|
27
|
+
- "waves" — flowing banded ribbons rippling across the surface; clean motion for headers/heroes. Default post: "none".
|
|
26
28
|
- "space" — Hyperspace starfield, streaking stars. Default post: "vhs".
|
|
27
29
|
- "plasma" — soft rolling colour clouds, ambient/abstract. Default post: "synthwave".
|
|
28
30
|
- "voronoi" — jittered cellular grid with glowing edges. Default post: "crt".
|
|
@@ -118,6 +120,25 @@ notes: |
|
|
|
118
120
|
## Fullscreen backgrounds
|
|
119
121
|
|
|
120
122
|
Surface defaults to `aspect="video"` (16:9). For a full-bleed hero background using `className="absolute inset-0"`, ALWAYS also pass `aspect="auto"` — otherwise the aspect-ratio constraint fights the absolute positioning and you get letterboxing.
|
|
123
|
+
|
|
124
|
+
## Layering & tweakable params (direction)
|
|
125
|
+
|
|
126
|
+
Shaders are composable, not monolithic. A rendered visual is a BASE
|
|
127
|
+
layer (the generative scene — gradient, dots, waves, space…) plus a
|
|
128
|
+
stack of EFFECT layers applied on top (grain, dither, vignette,
|
|
129
|
+
chromatic…). This is the same model as the post-FX composer — an
|
|
130
|
+
effect is independent of the base it sits over, so e.g. `grain`
|
|
131
|
+
applies to ALL bases (mix-and-match).
|
|
132
|
+
|
|
133
|
+
Every layer — base and effect — declares a `params: ParamSpec[]`
|
|
134
|
+
schema (see lib/three/types.ts): `range` (slider + number),
|
|
135
|
+
`segmented`, `select`, `toggle`, `color`, `colorList`. A param's
|
|
136
|
+
`key` doubles as the GLSL uniform name, so values map to uniforms
|
|
137
|
+
generically. The same `ParamSpec` shape is what a controls panel
|
|
138
|
+
renders from — the Paper-style "Presets + sliders + swatches" panel —
|
|
139
|
+
and is the canonical "this section is a form" descriptor shared with
|
|
140
|
+
the inspector controls kit (Input slots, sized Select, segmented
|
|
141
|
+
control, slider+number).
|
|
121
142
|
---
|
|
122
143
|
|
|
123
144
|
```jsx
|