@usevyre/ai-context 1.1.0 → 1.2.1
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/dist/anti-patterns.json +186 -4
- package/dist/cheat-sheets/calendar.md +5 -3
- package/dist/cheat-sheets/conversation.md +63 -0
- package/dist/cheat-sheets/datepicker.md +36 -0
- package/dist/cheat-sheets/daterangepicker.md +37 -0
- package/dist/cheat-sheets/field.md +19 -1
- package/dist/cheat-sheets/index.md +9 -2
- package/dist/cheat-sheets/input.md +2 -0
- package/dist/cheat-sheets/item.md +53 -0
- package/dist/cheat-sheets/kanban.md +59 -0
- package/dist/cheat-sheets/radiogroup.md +47 -0
- package/dist/cheat-sheets/richtexteditor.md +41 -0
- package/dist/cheat-sheets/sidebar.md +16 -0
- package/dist/claude-context.md +337 -5
- package/dist/copilot-instructions.md +337 -5
- package/dist/cursor-rules.md +96 -4
- package/dist/full-context.md +336 -4
- package/dist/index.js +2390 -151
- package/dist/schema.json +584 -8
- package/dist/tokens.json +1 -1
- package/dist/tokens.md +1 -1
- package/dist/version-info.json +92 -38
- package/dist/windsurf-rules.md +337 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// @usevyre/ai-context v1.
|
|
1
|
+
// @usevyre/ai-context v1.2.0
|
|
2
2
|
// Auto-generated — do not edit directly. Edit src/schema/components.json instead.
|
|
3
3
|
|
|
4
|
-
export const version = "1.
|
|
4
|
+
export const version = "1.2.0";
|
|
5
5
|
|
|
6
6
|
export const fullContext = `# useVyre Design System — AI Context
|
|
7
7
|
# Version: 0.2.0
|
|
@@ -310,7 +310,7 @@ import { Button } from "@usevyre/react"
|
|
|
310
310
|
|
|
311
311
|
### Calendar
|
|
312
312
|
|
|
313
|
-
|
|
313
|
+
Inline date-grid widget (always visible, no input). mode: single | range | multiple, optional time picker. For an input + popover use DatePicker; for start/end ranges with presets use DateRangePicker.
|
|
314
314
|
|
|
315
315
|
\`\`\`tsx
|
|
316
316
|
import { Calendar } from "@usevyre/react"
|
|
@@ -319,6 +319,7 @@ import { Calendar } from "@usevyre/react"
|
|
|
319
319
|
// value = Date | null
|
|
320
320
|
// onChange = function
|
|
321
321
|
// disabled = boolean (default: false)
|
|
322
|
+
// defaultMonth = Date
|
|
322
323
|
|
|
323
324
|
// Examples:
|
|
324
325
|
const [date, setDate] = useState(null);
|
|
@@ -326,7 +327,39 @@ const [date, setDate] = useState(null);
|
|
|
326
327
|
\`\`\`
|
|
327
328
|
|
|
328
329
|
**Common mistakes:**
|
|
329
|
-
- ❌ \`
|
|
330
|
+
- ❌ \`Calendar for an input field that opens a popover\` → Use <DatePicker /> (single date) or <DateRangePicker /> (range)
|
|
331
|
+
- ❌ \`value as tuple for mode="single"\` → Pass value matching mode; use mode="range" for [start,end]
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
### DatePicker
|
|
336
|
+
|
|
337
|
+
Input trigger that opens a Calendar in a popover. Same modes as Calendar (single | range | multiple) plus a placeholder. Use this for a compact date field; use Calendar for an always-visible grid, or DateRangePicker for start/end ranges with presets.
|
|
338
|
+
|
|
339
|
+
\`\`\`tsx
|
|
340
|
+
import { DatePicker } from "@usevyre/react"
|
|
341
|
+
|
|
342
|
+
// Props:
|
|
343
|
+
// value = Date | [Date, Date] | Date[] | null
|
|
344
|
+
// onChange = function
|
|
345
|
+
// mode = "single" | "range" | "multiple" (default: single)
|
|
346
|
+
// placeholder = string (default: Pick a date)
|
|
347
|
+
// showTime = boolean (default: false)
|
|
348
|
+
// minDate = Date
|
|
349
|
+
// maxDate = Date
|
|
350
|
+
// disabled = function
|
|
351
|
+
// weekStartsOn = "0" | "1" (default: 1)
|
|
352
|
+
// inputClassName = string
|
|
353
|
+
|
|
354
|
+
// Examples:
|
|
355
|
+
const [date, setDate] = useState(null);
|
|
356
|
+
<DatePicker value={date} onChange={setDate} placeholder="Pick a date" />
|
|
357
|
+
<DatePicker value={date} onChange={setDate} showTime />
|
|
358
|
+
\`\`\`
|
|
359
|
+
|
|
360
|
+
**Common mistakes:**
|
|
361
|
+
- ❌ \`DatePicker mode="range" for { from, to } object\` → Use <DateRangePicker /> for the { from, to } object API + presets + dual month
|
|
362
|
+
- ❌ \`DatePicker without value/onChange\` → Provide value and onChange (e.g. from useState)
|
|
330
363
|
|
|
331
364
|
---
|
|
332
365
|
|
|
@@ -386,6 +419,78 @@ import { Checkbox } from "@usevyre/react"
|
|
|
386
419
|
|
|
387
420
|
---
|
|
388
421
|
|
|
422
|
+
### RadioGroup
|
|
423
|
+
|
|
424
|
+
Controlled single-choice group. RadioGroup owns the selected value; render it data-driven via the options array OR with composable <Radio> children for custom content. role=radiogroup with proper labelling. For multi-select use Checkbox; for a compact dropdown use Select.
|
|
425
|
+
|
|
426
|
+
\`\`\`tsx
|
|
427
|
+
import { RadioGroup, Radio } from "@usevyre/react"
|
|
428
|
+
|
|
429
|
+
// Props:
|
|
430
|
+
// value = string
|
|
431
|
+
// defaultValue = string
|
|
432
|
+
// onChange = function
|
|
433
|
+
// name = string
|
|
434
|
+
// disabled = boolean (default: false)
|
|
435
|
+
// size = "sm" | "md" (default: md)
|
|
436
|
+
// orientation = "vertical" | "horizontal" (default: vertical)
|
|
437
|
+
// options = { value: string; label?: string; description?: string; disabled?: boolean }[]
|
|
438
|
+
|
|
439
|
+
// Examples:
|
|
440
|
+
<RadioGroup
|
|
441
|
+
value={plan}
|
|
442
|
+
onChange={setPlan}
|
|
443
|
+
options={[
|
|
444
|
+
{ value: "free", label: "Free", description: "For hobby projects" },
|
|
445
|
+
{ value: "pro", label: "Pro", description: "For teams" },
|
|
446
|
+
]}
|
|
447
|
+
/>
|
|
448
|
+
<RadioGroup value={plan} onChange={setPlan} orientation="horizontal">
|
|
449
|
+
<Radio value="free" label="Free" />
|
|
450
|
+
<Radio value="pro" label="Pro" />
|
|
451
|
+
</RadioGroup>
|
|
452
|
+
\`\`\`
|
|
453
|
+
|
|
454
|
+
**Common mistakes:**
|
|
455
|
+
- ❌ \`<Radio> used outside a <RadioGroup>\` → Always wrap <Radio> in <RadioGroup>
|
|
456
|
+
- ❌ \`RadioGroup without value/onChange (React) or v-model (Vue)\` → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
|
|
457
|
+
- ❌ \`Using Checkbox for mutually-exclusive choices\` → Use RadioGroup + Radio (or options) for one-of-many
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
### RichTextEditor
|
|
462
|
+
|
|
463
|
+
Controlled WYSIWYG editor. value is an HTML string; you own it in state and set it in onChange (React) / v-model (Vue). Native contentEditable + execCommand — zero dependencies. Toolbar: bold, italic, underline, strike, h1-h3, ordered/unordered lists, quote, code block, link, clear formatting.
|
|
464
|
+
|
|
465
|
+
\`\`\`tsx
|
|
466
|
+
import { RichTextEditor } from "@usevyre/react"
|
|
467
|
+
|
|
468
|
+
// Props:
|
|
469
|
+
// value = string
|
|
470
|
+
// onChange = function
|
|
471
|
+
// placeholder = string (default: Write something…)
|
|
472
|
+
// disabled = boolean (default: false)
|
|
473
|
+
// readOnly = boolean (default: false)
|
|
474
|
+
// toolbar = RichTextTool[]
|
|
475
|
+
// minHeight = string (default: 10rem)
|
|
476
|
+
|
|
477
|
+
// Examples:
|
|
478
|
+
const [html, setHtml] = useState("<p>Hello <strong>world</strong></p>");
|
|
479
|
+
<RichTextEditor value={html} onChange={setHtml} placeholder="Write…" />
|
|
480
|
+
<RichTextEditor
|
|
481
|
+
value={html}
|
|
482
|
+
onChange={setHtml}
|
|
483
|
+
toolbar={["bold", "italic", "link"]}
|
|
484
|
+
/>
|
|
485
|
+
\`\`\`
|
|
486
|
+
|
|
487
|
+
**Common mistakes:**
|
|
488
|
+
- ❌ \`RichTextEditor without value/onChange (React) or v-model (Vue)\` → Keep the HTML string in state and update it in onChange / v-model
|
|
489
|
+
- ❌ \`Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising\` → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
|
|
490
|
+
- ❌ \`toolbar="bold" (string)\` → Pass an array, e.g. toolbar={["bold","italic","link"]}
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
389
494
|
### Command
|
|
390
495
|
|
|
391
496
|
Command palette / search dialog. Use for search-first navigation or quick actions.
|
|
@@ -437,7 +542,7 @@ import { DropdownMenu, DropdownItem, DropdownSeparator, DropdownCheckboxItem, Dr
|
|
|
437
542
|
|
|
438
543
|
### Field
|
|
439
544
|
|
|
440
|
-
Form field wrapper
|
|
545
|
+
Form field wrapper. Two ways to use it (both supported): (1) props-based — pass label/hint/state/required for the common case; (2) composable — use the parts FieldLabel, FieldDescription, FieldError, FieldGroup, FieldSet for richer layouts (multiple controls, custom error placement). The props-based API is unchanged and still works.
|
|
441
546
|
|
|
442
547
|
\`\`\`tsx
|
|
443
548
|
import { Field, Input, Textarea } from "@usevyre/react"
|
|
@@ -455,10 +560,23 @@ import { Field, Input, Textarea } from "@usevyre/react"
|
|
|
455
560
|
<Field label="Search">
|
|
456
561
|
<Input leftElement={<SearchIcon />} placeholder="Search..." />
|
|
457
562
|
</Field>
|
|
563
|
+
<Field>
|
|
564
|
+
<FieldLabel required htmlFor="email">Email</FieldLabel>
|
|
565
|
+
<Input id="email" type="email" />
|
|
566
|
+
<FieldDescription>We\u2019ll never share it.</FieldDescription>
|
|
567
|
+
<FieldError>{errors.email}</FieldError>
|
|
568
|
+
</Field>
|
|
569
|
+
|
|
570
|
+
// Two controls side by side
|
|
571
|
+
<FieldGroup orientation="horizontal">
|
|
572
|
+
<Field label="First name"><Input /></Field>
|
|
573
|
+
<Field label="Last name"><Input /></Field>
|
|
574
|
+
</FieldGroup>
|
|
458
575
|
\`\`\`
|
|
459
576
|
|
|
460
577
|
**Common mistakes:**
|
|
461
578
|
- ❌ \`Applying state prop directly to Input\` → Wrap Input in <Field state="error"> to apply validation styling
|
|
579
|
+
- ❌ \`Mixing props label/hint AND FieldLabel/FieldError for the same field\` → Pick one: either props-based (label/hint/state) OR composable parts
|
|
462
580
|
|
|
463
581
|
---
|
|
464
582
|
|
|
@@ -470,6 +588,7 @@ Text input field. Wrap in Field for labels and validation. Use leftElement/right
|
|
|
470
588
|
import { Input } from "@usevyre/react"
|
|
471
589
|
|
|
472
590
|
// Props:
|
|
591
|
+
// modelValue = string | number
|
|
473
592
|
// size = "sm" | "md" | "lg" (default: md)
|
|
474
593
|
// leftElement = ReactNode
|
|
475
594
|
// rightElement = ReactNode
|
|
@@ -481,6 +600,7 @@ import { Input } from "@usevyre/react"
|
|
|
481
600
|
**Common mistakes:**
|
|
482
601
|
- ❌ \`size="icon"\` → Use size="sm" | "md" | "lg"
|
|
483
602
|
- ❌ \`type="search" for search UI\` → Import Command from @usevyre/react for search palettes
|
|
603
|
+
- ❌ \`Vue: binding Input/Textarea value without v-model\` → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
|
|
484
604
|
|
|
485
605
|
---
|
|
486
606
|
|
|
@@ -676,6 +796,8 @@ import { AppLayout, Sidebar, SidebarHeader, SidebarContent, SidebarSection, Side
|
|
|
676
796
|
|
|
677
797
|
// Props:
|
|
678
798
|
// variant = "default" | "floating" (default: default)
|
|
799
|
+
// SidebarTrigger.icon = ReactNode
|
|
800
|
+
// SidebarTrigger.collapsedIcon = ReactNode
|
|
679
801
|
|
|
680
802
|
// Examples:
|
|
681
803
|
<AppLayout>
|
|
@@ -691,8 +813,18 @@ import { AppLayout, Sidebar, SidebarHeader, SidebarContent, SidebarSection, Side
|
|
|
691
813
|
</Sidebar>
|
|
692
814
|
<main>Page content</main>
|
|
693
815
|
</AppLayout>
|
|
816
|
+
<SidebarTrigger icon={<PanelLeftClose />} collapsedIcon={<PanelLeftOpen />} />
|
|
817
|
+
|
|
818
|
+
// Vue:
|
|
819
|
+
// <SidebarTrigger>
|
|
820
|
+
// <template #icon><PanelLeftClose /></template>
|
|
821
|
+
// <template #collapsed-icon><PanelLeftOpen /></template>
|
|
822
|
+
// </SidebarTrigger>
|
|
694
823
|
\`\`\`
|
|
695
824
|
|
|
825
|
+
**Common mistakes:**
|
|
826
|
+
- ❌ \`Vue: passing icon/collapsedIcon as props on SidebarTrigger\` → Use <template #icon> and <template #collapsed-icon>; React uses icon / collapsedIcon props
|
|
827
|
+
|
|
696
828
|
---
|
|
697
829
|
|
|
698
830
|
### Skeleton
|
|
@@ -1068,6 +1200,180 @@ import { TagGroup, Tag } from "@usevyre/react"
|
|
|
1068
1200
|
|
|
1069
1201
|
---
|
|
1070
1202
|
|
|
1203
|
+
### Item
|
|
1204
|
+
|
|
1205
|
+
Layout primitive for list rows, settings rows, and notification rows. Denser than Card — use Item (not Card) for repeated list rows.
|
|
1206
|
+
|
|
1207
|
+
\`\`\`tsx
|
|
1208
|
+
import { Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions, ItemGroup } from "@usevyre/react"
|
|
1209
|
+
|
|
1210
|
+
// Props:
|
|
1211
|
+
// variant = "default" | "outlined" | "muted" | "plain" (default: default)
|
|
1212
|
+
// size = "sm" | "md" | "lg" (default: md)
|
|
1213
|
+
// clickable = boolean (default: false)
|
|
1214
|
+
|
|
1215
|
+
// Examples:
|
|
1216
|
+
<Item>
|
|
1217
|
+
<ItemMedia><BellIcon /></ItemMedia>
|
|
1218
|
+
<ItemContent>
|
|
1219
|
+
<ItemTitle>Notifications</ItemTitle>
|
|
1220
|
+
<ItemDescription>Receive an email when someone mentions you.</ItemDescription>
|
|
1221
|
+
</ItemContent>
|
|
1222
|
+
<ItemActions>
|
|
1223
|
+
<Switch defaultChecked />
|
|
1224
|
+
</ItemActions>
|
|
1225
|
+
</Item>
|
|
1226
|
+
<ItemGroup separated>
|
|
1227
|
+
<Item clickable>
|
|
1228
|
+
<ItemContent><ItemTitle>Profile</ItemTitle></ItemContent>
|
|
1229
|
+
</Item>
|
|
1230
|
+
<Item clickable>
|
|
1231
|
+
<ItemContent><ItemTitle>Billing</ItemTitle></ItemContent>
|
|
1232
|
+
</Item>
|
|
1233
|
+
</ItemGroup>
|
|
1234
|
+
\`\`\`
|
|
1235
|
+
|
|
1236
|
+
**Common mistakes:**
|
|
1237
|
+
- ❌ \`Card used for repeated list rows\` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
|
|
1238
|
+
- ❌ \`Item variant="primary"\` → Use variant="default" | "outlined" | "muted"
|
|
1239
|
+
- ❌ \`raw text directly inside Item\` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
|
|
1240
|
+
|
|
1241
|
+
---
|
|
1242
|
+
|
|
1243
|
+
### Kanban
|
|
1244
|
+
|
|
1245
|
+
Drag-and-drop board: cards move between columns (or reorder within a column). CONTROLLED & data-driven like DataGrid. While dragging, a placeholder shows the exact drop position. Each card is wrapped in a Card (variant="outlined"); renderCard (React) / #card slot (Vue) can render ANY content incl. complex components (Avatar/Badge/Progress). Columns and cards accept an optional semantic color tint. Native HTML5 DnD, zero deps.
|
|
1246
|
+
|
|
1247
|
+
\`\`\`tsx
|
|
1248
|
+
import { Kanban } from "@usevyre/react"
|
|
1249
|
+
|
|
1250
|
+
// Props:
|
|
1251
|
+
// value = KanbanColumn[]
|
|
1252
|
+
// onChange = function
|
|
1253
|
+
// renderCard = function
|
|
1254
|
+
// onCardClick = function
|
|
1255
|
+
|
|
1256
|
+
// Examples:
|
|
1257
|
+
const [columns, setColumns] = useState([
|
|
1258
|
+
{ id: "todo", title: "To Do", cards: [{ id: "1", title: "Spec API" }] },
|
|
1259
|
+
{ id: "doing", title: "In Progress", cards: [] },
|
|
1260
|
+
{ id: "done", title: "Done", cards: [{ id: "2", title: "Kickoff" }] },
|
|
1261
|
+
]);
|
|
1262
|
+
<Kanban value={columns} onChange={setColumns} />
|
|
1263
|
+
<Kanban
|
|
1264
|
+
value={columns}
|
|
1265
|
+
onChange={setColumns}
|
|
1266
|
+
onCardClick={(card) => openDetail(card.id)}
|
|
1267
|
+
renderCard={(card) => (
|
|
1268
|
+
<><strong>{card.title}</strong><Badge>{card.id}</Badge></>
|
|
1269
|
+
)}
|
|
1270
|
+
/>
|
|
1271
|
+
const [cols, setCols] = useState([
|
|
1272
|
+
{ id: "doing", title: "In Progress", color: "teal", cards: [
|
|
1273
|
+
{ id: "t1", title: "OAuth", assignee: "AK", progress: 60, color: "warning" },
|
|
1274
|
+
]},
|
|
1275
|
+
]);
|
|
1276
|
+
<Kanban
|
|
1277
|
+
value={cols}
|
|
1278
|
+
onChange={setCols}
|
|
1279
|
+
renderCard={(card) => (
|
|
1280
|
+
<><strong>{card.title}</strong><Progress value={card.progress} /></>
|
|
1281
|
+
)}
|
|
1282
|
+
/>
|
|
1283
|
+
\`\`\`
|
|
1284
|
+
|
|
1285
|
+
**Common mistakes:**
|
|
1286
|
+
- ❌ \`Kanban without onChange (or ignoring it)\` → Store columns in state and setColumns in onChange (v-model in Vue)
|
|
1287
|
+
- ❌ \`Duplicate card ids across columns\` → Use globally-unique card ids across the entire board
|
|
1288
|
+
- ❌ \`Mutating value in place then calling onChange\` → Pass the new array Kanban gives you straight to setState / v-model
|
|
1289
|
+
- ❌ \`color="blue" (or any non-semantic value)\` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
|
|
1290
|
+
|
|
1291
|
+
---
|
|
1292
|
+
|
|
1293
|
+
### Conversation
|
|
1294
|
+
|
|
1295
|
+
Chat / inbox message thread. CONTROLLED & data-driven like Kanban — you own \`value\` (messages array) and append in your own send handler; Conversation holds no message state. Consecutive messages from the same author are grouped (avatar + name shown once), day separators are inserted on date change, and outgoing messages (authorId === currentUserId) align right.
|
|
1296
|
+
|
|
1297
|
+
\`\`\`tsx
|
|
1298
|
+
import { Conversation } from "@usevyre/react"
|
|
1299
|
+
|
|
1300
|
+
// Props:
|
|
1301
|
+
// value = ConversationMessage[]
|
|
1302
|
+
// currentUserId = string
|
|
1303
|
+
// composer = boolean (default: false)
|
|
1304
|
+
// onSend = function
|
|
1305
|
+
// placeholder = string (default: Write a message…)
|
|
1306
|
+
// typing = boolean | string (default: false)
|
|
1307
|
+
// allowAttachments = boolean (default: false)
|
|
1308
|
+
// accept = string
|
|
1309
|
+
// renderMessage = function
|
|
1310
|
+
// renderComposer = function
|
|
1311
|
+
|
|
1312
|
+
// Examples:
|
|
1313
|
+
const [messages, setMessages] = useState([
|
|
1314
|
+
{ id: "1", authorId: "sam", authorName: "Sam", text: "Hey!" },
|
|
1315
|
+
{ id: "2", authorId: "me", text: "Hi \ud83d\udc4b", status: "read" },
|
|
1316
|
+
]);
|
|
1317
|
+
<Conversation
|
|
1318
|
+
value={messages}
|
|
1319
|
+
currentUserId="me"
|
|
1320
|
+
composer
|
|
1321
|
+
onSend={(t) => setMessages((m) => [...m, { id: crypto.randomUUID(), authorId: "me", text: t }])}
|
|
1322
|
+
/>
|
|
1323
|
+
<Conversation
|
|
1324
|
+
value={messages}
|
|
1325
|
+
currentUserId="me"
|
|
1326
|
+
typing="Sam is typing"
|
|
1327
|
+
renderMessage={(m) => <strong>{m.text}</strong>}
|
|
1328
|
+
/>
|
|
1329
|
+
const messages = [
|
|
1330
|
+
{ id: "1", authorId: "sam", authorName: "Sam", text: "Moodboard \ud83d\udc47",
|
|
1331
|
+
attachments: [{ kind: "image", url: "/board.png", name: "board.png" }] },
|
|
1332
|
+
{ id: "2", authorId: "me", text: "Specs:", status: "read",
|
|
1333
|
+
attachments: [{ kind: "file", url: "/spec.pdf", name: "spec.pdf", size: "2.4 MB" }] },
|
|
1334
|
+
];
|
|
1335
|
+
<Conversation value={messages} currentUserId="me" />
|
|
1336
|
+
\`\`\`
|
|
1337
|
+
|
|
1338
|
+
**Common mistakes:**
|
|
1339
|
+
- ❌ \`Conversation without currentUserId\` → Always pass currentUserId matching one of the message authorId values
|
|
1340
|
+
- ❌ \`Expecting Conversation to store/append messages\` → Append to your own state in onSend (or @send) and pass it back via value
|
|
1341
|
+
- ❌ \`composer without onSend (React) / @send (Vue)\` → Provide onSend / @send to append the message to value
|
|
1342
|
+
- ❌ \`Treating onSend as (text) only when using allowAttachments\` → Handle onSend(text, files) — map files to message attachments and append
|
|
1343
|
+
|
|
1344
|
+
---
|
|
1345
|
+
|
|
1346
|
+
### DateRangePicker
|
|
1347
|
+
|
|
1348
|
+
Start/end date range picker. Built on Calendar (mode=range) with a friendlier { from, to } object API, a two-month side-by-side view, and preset shortcuts. Use this for report/filter date ranges; use DatePicker for a single date.
|
|
1349
|
+
|
|
1350
|
+
\`\`\`tsx
|
|
1351
|
+
import { DateRangePicker } from "@usevyre/react"
|
|
1352
|
+
|
|
1353
|
+
// Props:
|
|
1354
|
+
// value = { from: Date | null; to: Date | null } | null
|
|
1355
|
+
// onChange = function
|
|
1356
|
+
// placeholder = string (default: Pick a date range)
|
|
1357
|
+
// numberOfMonths = "1" | "2" (default: 2)
|
|
1358
|
+
// presets = boolean | DateRangePreset[] (default: false)
|
|
1359
|
+
// minDate = Date
|
|
1360
|
+
// maxDate = Date
|
|
1361
|
+
// disabled = function
|
|
1362
|
+
// weekStartsOn = "0" | "1" (default: 1)
|
|
1363
|
+
|
|
1364
|
+
// Examples:
|
|
1365
|
+
const [range, setRange] = useState({ from: null, to: null });
|
|
1366
|
+
<DateRangePicker value={range} onChange={setRange} presets />
|
|
1367
|
+
<DateRangePicker value={range} onChange={setRange} numberOfMonths={1} />
|
|
1368
|
+
\`\`\`
|
|
1369
|
+
|
|
1370
|
+
**Common mistakes:**
|
|
1371
|
+
- ❌ \`value={[from, to]}\` → Use value={{ from, to }} and read range.from / range.to
|
|
1372
|
+
- ❌ \`DateRangePicker for a single date\` → Use <DatePicker /> for a single date
|
|
1373
|
+
- ❌ \`presets="true" (string)\` → Use the bare prop: presets (or presets={true})
|
|
1374
|
+
|
|
1375
|
+
---
|
|
1376
|
+
|
|
1071
1377
|
## Hallucination Guard — Common AI Mistakes
|
|
1072
1378
|
|
|
1073
1379
|
The following prop values and patterns do NOT exist in useVyre.
|
|
@@ -1087,18 +1393,30 @@ If you generate these, you are hallucinating.
|
|
|
1087
1393
|
- ❌ \`<Button color="...">\` → Use variant prop instead
|
|
1088
1394
|
- ❌ \`<Button icon={...}>\` → Use leftIcon={...} or rightIcon={...}
|
|
1089
1395
|
- ❌ \`<Button size="icon" without aria-label>\` → Add aria-label describing the action
|
|
1090
|
-
- ❌ \`<Calendar
|
|
1396
|
+
- ❌ \`<Calendar Calendar for an input field that opens a popover>\` → Use <DatePicker /> (single date) or <DateRangePicker /> (range)
|
|
1397
|
+
- ❌ \`<Calendar value as tuple for mode="single">\` → Pass value matching mode; use mode="range" for [start,end]
|
|
1398
|
+
- ❌ \`<DatePicker DatePicker mode="range" for { from, to } object>\` → Use <DateRangePicker /> for the { from, to } object API + presets + dual month
|
|
1399
|
+
- ❌ \`<DatePicker DatePicker without value/onChange>\` → Provide value and onChange (e.g. from useState)
|
|
1091
1400
|
- ❌ \`<Card variant="primary">\` → Use variant="elevated" | "outlined" | "ghost" | "accent"
|
|
1092
1401
|
- ❌ \`<Checkbox size="lg">\` → Use size="md"
|
|
1402
|
+
- ❌ \`<RadioGroup <Radio> used outside a <RadioGroup>>\` → Always wrap <Radio> in <RadioGroup>
|
|
1403
|
+
- ❌ \`<RadioGroup RadioGroup without value/onChange (React) or v-model (Vue)>\` → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
|
|
1404
|
+
- ❌ \`<RadioGroup Using Checkbox for mutually-exclusive choices>\` → Use RadioGroup + Radio (or options) for one-of-many
|
|
1405
|
+
- ❌ \`<RichTextEditor RichTextEditor without value/onChange (React) or v-model (Vue)>\` → Keep the HTML string in state and update it in onChange / v-model
|
|
1406
|
+
- ❌ \`<RichTextEditor Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising>\` → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
|
|
1407
|
+
- ❌ \`<RichTextEditor toolbar="bold" (string)>\` → Pass an array, e.g. toolbar={["bold","italic","link"]}
|
|
1093
1408
|
- ❌ \`<Command Using Input type="search" for search UI>\` → Use Command + CommandInput + CommandList + CommandItem
|
|
1094
1409
|
- ❌ \`<DropdownMenu DropdownItem variant="primary">\` → Use variant="danger" for destructive items only
|
|
1095
1410
|
- ❌ \`<Field Applying state prop directly to Input>\` → Wrap Input in <Field state="error"> to apply validation styling
|
|
1411
|
+
- ❌ \`<Field Mixing props label/hint AND FieldLabel/FieldError for the same field>\` → Pick one: either props-based (label/hint/state) OR composable parts
|
|
1096
1412
|
- ❌ \`<Input size="icon">\` → Use size="sm" | "md" | "lg"
|
|
1097
1413
|
- ❌ \`<Input type="search" for search UI>\` → Import Command from @usevyre/react for search palettes
|
|
1414
|
+
- ❌ \`<Input Vue: binding Input/Textarea value without v-model>\` → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
|
|
1098
1415
|
- ❌ \`<Modal size="xl">\` → Use size="lg" or size="full"
|
|
1099
1416
|
- ❌ \`<Popover placement="top-center">\` → Use placement="top" for centered placement
|
|
1100
1417
|
- ❌ \`<Progress value > 100>\` → Normalize your value to 0–100 range before passing
|
|
1101
1418
|
- ❌ \`<Select Passing strings directly as children>\` → Pass options={[{ value: 'a', label: 'Option A' }]}
|
|
1419
|
+
- ❌ \`<Sidebar Vue: passing icon/collapsedIcon as props on SidebarTrigger>\` → Use <template #icon> and <template #collapsed-icon>; React uses icon / collapsedIcon props
|
|
1102
1420
|
- ❌ \`<Toast Rendering <Toast> directly in JSX>\` → Use: const { toast } = useToast(); then toast({ title, variant })
|
|
1103
1421
|
- ❌ \`<Toast variant="error">\` → Use variant="danger"
|
|
1104
1422
|
- ❌ \`<Toast variant="info">\` → Use variant="default"
|
|
@@ -1119,6 +1437,20 @@ If you generate these, you are hallucinating.
|
|
|
1119
1437
|
- ❌ \`<Tag Tag size="xl">\` → Use size="lg"
|
|
1120
1438
|
- ❌ \`<TagGroup TagGroup without Tag children>\` → Place <Tag> elements as direct children
|
|
1121
1439
|
- ❌ \`<TagGroup Using TagGroup for tag input>\` → Use TagsInput for an editable tag field
|
|
1440
|
+
- ❌ \`<Item Card used for repeated list rows>\` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
|
|
1441
|
+
- ❌ \`<Item Item variant="primary">\` → Use variant="default" | "outlined" | "muted"
|
|
1442
|
+
- ❌ \`<Item raw text directly inside Item>\` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
|
|
1443
|
+
- ❌ \`<Kanban Kanban without onChange (or ignoring it)>\` → Store columns in state and setColumns in onChange (v-model in Vue)
|
|
1444
|
+
- ❌ \`<Kanban Duplicate card ids across columns>\` → Use globally-unique card ids across the entire board
|
|
1445
|
+
- ❌ \`<Kanban Mutating value in place then calling onChange>\` → Pass the new array Kanban gives you straight to setState / v-model
|
|
1446
|
+
- ❌ \`<Kanban color="blue" (or any non-semantic value)>\` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
|
|
1447
|
+
- ❌ \`<Conversation Conversation without currentUserId>\` → Always pass currentUserId matching one of the message authorId values
|
|
1448
|
+
- ❌ \`<Conversation Expecting Conversation to store/append messages>\` → Append to your own state in onSend (or @send) and pass it back via value
|
|
1449
|
+
- ❌ \`<Conversation composer without onSend (React) / @send (Vue)>\` → Provide onSend / @send to append the message to value
|
|
1450
|
+
- ❌ \`<Conversation Treating onSend as (text) only when using allowAttachments>\` → Handle onSend(text, files) — map files to message attachments and append
|
|
1451
|
+
- ❌ \`<DateRangePicker value={[from, to]}>\` → Use value={{ from, to }} and read range.from / range.to
|
|
1452
|
+
- ❌ \`<DateRangePicker DateRangePicker for a single date>\` → Use <DatePicker /> for a single date
|
|
1453
|
+
- ❌ \`<DateRangePicker presets="true" (string)>\` → Use the bare prop: presets (or presets={true})
|
|
1122
1454
|
|
|
1123
1455
|
---
|
|
1124
1456
|
|
|
@@ -1173,7 +1505,7 @@ alwaysApply: true
|
|
|
1173
1505
|
---
|
|
1174
1506
|
|
|
1175
1507
|
# useVyre Design System — Cursor Rules
|
|
1176
|
-
# Version: 1.
|
|
1508
|
+
# Version: 1.2.0
|
|
1177
1509
|
|
|
1178
1510
|
You are working in a project using the useVyre design system (@usevyre/react).
|
|
1179
1511
|
Follow these rules strictly when generating any UI code.
|
|
@@ -1252,13 +1584,26 @@ Never do:
|
|
|
1252
1584
|
- ❌ size="icon" without aria-label → ✅ Add aria-label describing the action
|
|
1253
1585
|
|
|
1254
1586
|
## Calendar
|
|
1255
|
-
|
|
1587
|
+
Inline date-grid widget (always visible, no input). mode: single | range | multiple, optional time picker. For an input + popover use DatePicker; for start/end ranges with presets use DateRangePicker.
|
|
1256
1588
|
Import: \`import { Calendar } from "@usevyre/react"\`
|
|
1257
1589
|
|
|
1258
1590
|
Valid props:
|
|
1259
1591
|
|
|
1260
1592
|
Never do:
|
|
1261
|
-
- ❌
|
|
1593
|
+
- ❌ Calendar for an input field that opens a popover → ✅ Use <DatePicker /> (single date) or <DateRangePicker /> (range)
|
|
1594
|
+
- ❌ value as tuple for mode="single" → ✅ Pass value matching mode; use mode="range" for [start,end]
|
|
1595
|
+
|
|
1596
|
+
## DatePicker
|
|
1597
|
+
Input trigger that opens a Calendar in a popover. Same modes as Calendar (single | range | multiple) plus a placeholder. Use this for a compact date field; use Calendar for an always-visible grid, or DateRangePicker for start/end ranges with presets.
|
|
1598
|
+
Import: \`import { DatePicker } from "@usevyre/react"\`
|
|
1599
|
+
|
|
1600
|
+
Valid props:
|
|
1601
|
+
- mode: "single" | "range" | "multiple" [default: single]
|
|
1602
|
+
- weekStartsOn: "0" | "1" [default: 1]
|
|
1603
|
+
|
|
1604
|
+
Never do:
|
|
1605
|
+
- ❌ DatePicker mode="range" for { from, to } object → ✅ Use <DateRangePicker /> for the { from, to } object API + presets + dual month
|
|
1606
|
+
- ❌ DatePicker without value/onChange → ✅ Provide value and onChange (e.g. from useState)
|
|
1262
1607
|
|
|
1263
1608
|
## Card
|
|
1264
1609
|
Content container with optional header, body, and footer sections.
|
|
@@ -1280,6 +1625,30 @@ Valid props:
|
|
|
1280
1625
|
Never do:
|
|
1281
1626
|
- ❌ size="lg" → ✅ Use size="md"
|
|
1282
1627
|
|
|
1628
|
+
## RadioGroup
|
|
1629
|
+
Controlled single-choice group. RadioGroup owns the selected value; render it data-driven via the options array OR with composable <Radio> children for custom content. role=radiogroup with proper labelling. For multi-select use Checkbox; for a compact dropdown use Select.
|
|
1630
|
+
Import: \`import { RadioGroup, Radio } from "@usevyre/react"\`
|
|
1631
|
+
|
|
1632
|
+
Valid props:
|
|
1633
|
+
- size: "sm" | "md" [default: md]
|
|
1634
|
+
- orientation: "vertical" | "horizontal" [default: vertical]
|
|
1635
|
+
|
|
1636
|
+
Never do:
|
|
1637
|
+
- ❌ <Radio> used outside a <RadioGroup> → ✅ Always wrap <Radio> in <RadioGroup>
|
|
1638
|
+
- ❌ RadioGroup without value/onChange (React) or v-model (Vue) → ✅ Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
|
|
1639
|
+
- ❌ Using Checkbox for mutually-exclusive choices → ✅ Use RadioGroup + Radio (or options) for one-of-many
|
|
1640
|
+
|
|
1641
|
+
## RichTextEditor
|
|
1642
|
+
Controlled WYSIWYG editor. value is an HTML string; you own it in state and set it in onChange (React) / v-model (Vue). Native contentEditable + execCommand — zero dependencies. Toolbar: bold, italic, underline, strike, h1-h3, ordered/unordered lists, quote, code block, link, clear formatting.
|
|
1643
|
+
Import: \`import { RichTextEditor } from "@usevyre/react"\`
|
|
1644
|
+
|
|
1645
|
+
Valid props:
|
|
1646
|
+
|
|
1647
|
+
Never do:
|
|
1648
|
+
- ❌ RichTextEditor without value/onChange (React) or v-model (Vue) → ✅ Keep the HTML string in state and update it in onChange / v-model
|
|
1649
|
+
- ❌ Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising → ✅ Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
|
|
1650
|
+
- ❌ toolbar="bold" (string) → ✅ Pass an array, e.g. toolbar={["bold","italic","link"]}
|
|
1651
|
+
|
|
1283
1652
|
## Command
|
|
1284
1653
|
Command palette / search dialog. Use for search-first navigation or quick actions.
|
|
1285
1654
|
Import: \`import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandDialog } from "@usevyre/react"\`
|
|
@@ -1297,7 +1666,7 @@ Never do:
|
|
|
1297
1666
|
- ❌ DropdownItem variant="primary" → ✅ Use variant="danger" for destructive items only
|
|
1298
1667
|
|
|
1299
1668
|
## Field
|
|
1300
|
-
Form field wrapper
|
|
1669
|
+
Form field wrapper. Two ways to use it (both supported): (1) props-based — pass label/hint/state/required for the common case; (2) composable — use the parts FieldLabel, FieldDescription, FieldError, FieldGroup, FieldSet for richer layouts (multiple controls, custom error placement). The props-based API is unchanged and still works.
|
|
1301
1670
|
Import: \`import { Field, Input, Textarea } from "@usevyre/react"\`
|
|
1302
1671
|
|
|
1303
1672
|
Valid props:
|
|
@@ -1305,6 +1674,7 @@ Valid props:
|
|
|
1305
1674
|
|
|
1306
1675
|
Never do:
|
|
1307
1676
|
- ❌ Applying state prop directly to Input → ✅ Wrap Input in <Field state="error"> to apply validation styling
|
|
1677
|
+
- ❌ Mixing props label/hint AND FieldLabel/FieldError for the same field → ✅ Pick one: either props-based (label/hint/state) OR composable parts
|
|
1308
1678
|
|
|
1309
1679
|
## Input
|
|
1310
1680
|
Text input field. Wrap in Field for labels and validation. Use leftElement/rightElement for icons.
|
|
@@ -1316,6 +1686,7 @@ Valid props:
|
|
|
1316
1686
|
Never do:
|
|
1317
1687
|
- ❌ size="icon" → ✅ Use size="sm" | "md" | "lg"
|
|
1318
1688
|
- ❌ type="search" for search UI → ✅ Import Command from @usevyre/react for search palettes
|
|
1689
|
+
- ❌ Vue: binding Input/Textarea value without v-model → ✅ Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
|
|
1319
1690
|
|
|
1320
1691
|
## Label
|
|
1321
1692
|
Accessible form label. Associate with input via htmlFor.
|
|
@@ -1392,6 +1763,9 @@ Import: \`import { AppLayout, Sidebar, SidebarHeader, SidebarContent, SidebarSec
|
|
|
1392
1763
|
Valid props:
|
|
1393
1764
|
- variant: "default" | "floating" [default: default]
|
|
1394
1765
|
|
|
1766
|
+
Never do:
|
|
1767
|
+
- ❌ Vue: passing icon/collapsedIcon as props on SidebarTrigger → ✅ Use <template #icon> and <template #collapsed-icon>; React uses icon / collapsedIcon props
|
|
1768
|
+
|
|
1395
1769
|
## Skeleton
|
|
1396
1770
|
Loading placeholder that mimics the shape of content while data loads.
|
|
1397
1771
|
Import: \`import { Skeleton } from "@usevyre/react"\`
|
|
@@ -1523,13 +1897,63 @@ Never do:
|
|
|
1523
1897
|
- ❌ TagGroup without Tag children → ✅ Place <Tag> elements as direct children
|
|
1524
1898
|
- ❌ Using TagGroup for tag input → ✅ Use TagsInput for an editable tag field
|
|
1525
1899
|
|
|
1900
|
+
## Item
|
|
1901
|
+
Layout primitive for list rows, settings rows, and notification rows. Denser than Card — use Item (not Card) for repeated list rows.
|
|
1902
|
+
Import: \`import { Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions, ItemGroup } from "@usevyre/react"\`
|
|
1903
|
+
|
|
1904
|
+
Valid props:
|
|
1905
|
+
- variant: "default" | "outlined" | "muted" | "plain" [default: default]
|
|
1906
|
+
- size: "sm" | "md" | "lg" [default: md]
|
|
1907
|
+
|
|
1908
|
+
Never do:
|
|
1909
|
+
- ❌ Card used for repeated list rows → ✅ Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
|
|
1910
|
+
- ❌ Item variant="primary" → ✅ Use variant="default" | "outlined" | "muted"
|
|
1911
|
+
- ❌ raw text directly inside Item → ✅ Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
|
|
1912
|
+
|
|
1913
|
+
## Kanban
|
|
1914
|
+
Drag-and-drop board: cards move between columns (or reorder within a column). CONTROLLED & data-driven like DataGrid. While dragging, a placeholder shows the exact drop position. Each card is wrapped in a Card (variant="outlined"); renderCard (React) / #card slot (Vue) can render ANY content incl. complex components (Avatar/Badge/Progress). Columns and cards accept an optional semantic color tint. Native HTML5 DnD, zero deps.
|
|
1915
|
+
Import: \`import { Kanban } from "@usevyre/react"\`
|
|
1916
|
+
|
|
1917
|
+
Valid props:
|
|
1918
|
+
|
|
1919
|
+
Never do:
|
|
1920
|
+
- ❌ Kanban without onChange (or ignoring it) → ✅ Store columns in state and setColumns in onChange (v-model in Vue)
|
|
1921
|
+
- ❌ Duplicate card ids across columns → ✅ Use globally-unique card ids across the entire board
|
|
1922
|
+
- ❌ Mutating value in place then calling onChange → ✅ Pass the new array Kanban gives you straight to setState / v-model
|
|
1923
|
+
- ❌ color="blue" (or any non-semantic value) → ✅ Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
|
|
1924
|
+
|
|
1925
|
+
## Conversation
|
|
1926
|
+
Chat / inbox message thread. CONTROLLED & data-driven like Kanban — you own \`value\` (messages array) and append in your own send handler; Conversation holds no message state. Consecutive messages from the same author are grouped (avatar + name shown once), day separators are inserted on date change, and outgoing messages (authorId === currentUserId) align right.
|
|
1927
|
+
Import: \`import { Conversation } from "@usevyre/react"\`
|
|
1928
|
+
|
|
1929
|
+
Valid props:
|
|
1930
|
+
|
|
1931
|
+
Never do:
|
|
1932
|
+
- ❌ Conversation without currentUserId → ✅ Always pass currentUserId matching one of the message authorId values
|
|
1933
|
+
- ❌ Expecting Conversation to store/append messages → ✅ Append to your own state in onSend (or @send) and pass it back via value
|
|
1934
|
+
- ❌ composer without onSend (React) / @send (Vue) → ✅ Provide onSend / @send to append the message to value
|
|
1935
|
+
- ❌ Treating onSend as (text) only when using allowAttachments → ✅ Handle onSend(text, files) — map files to message attachments and append
|
|
1936
|
+
|
|
1937
|
+
## DateRangePicker
|
|
1938
|
+
Start/end date range picker. Built on Calendar (mode=range) with a friendlier { from, to } object API, a two-month side-by-side view, and preset shortcuts. Use this for report/filter date ranges; use DatePicker for a single date.
|
|
1939
|
+
Import: \`import { DateRangePicker } from "@usevyre/react"\`
|
|
1940
|
+
|
|
1941
|
+
Valid props:
|
|
1942
|
+
- numberOfMonths: "1" | "2" [default: 2]
|
|
1943
|
+
- weekStartsOn: "0" | "1" [default: 1]
|
|
1944
|
+
|
|
1945
|
+
Never do:
|
|
1946
|
+
- ❌ value={[from, to]} → ✅ Use value={{ from, to }} and read range.from / range.to
|
|
1947
|
+
- ❌ DateRangePicker for a single date → ✅ Use <DatePicker /> for a single date
|
|
1948
|
+
- ❌ presets="true" (string) → ✅ Use the bare prop: presets (or presets={true})
|
|
1949
|
+
|
|
1526
1950
|
## Token Rules
|
|
1527
1951
|
|
|
1528
1952
|
Use --vyre-color-semantic-* for all colors. Never use primitive tokens.
|
|
1529
1953
|
Use --vyre-spacing-* for all spacing. Never use raw px in component code.
|
|
1530
1954
|
Use --vyre-border-radius-* for border radius.`;
|
|
1531
1955
|
export const claudeContext = `# useVyre Design System Context
|
|
1532
|
-
# Version: 1.
|
|
1956
|
+
# Version: 1.2.0
|
|
1533
1957
|
|
|
1534
1958
|
You are working in a codebase that uses the useVyre design system.
|
|
1535
1959
|
Follow the rules below strictly when writing any UI code.
|
|
@@ -1841,7 +2265,7 @@ import { Button } from "@usevyre/react"
|
|
|
1841
2265
|
|
|
1842
2266
|
### Calendar
|
|
1843
2267
|
|
|
1844
|
-
|
|
2268
|
+
Inline date-grid widget (always visible, no input). mode: single | range | multiple, optional time picker. For an input + popover use DatePicker; for start/end ranges with presets use DateRangePicker.
|
|
1845
2269
|
|
|
1846
2270
|
\`\`\`tsx
|
|
1847
2271
|
import { Calendar } from "@usevyre/react"
|
|
@@ -1850,6 +2274,7 @@ import { Calendar } from "@usevyre/react"
|
|
|
1850
2274
|
// value = Date | null
|
|
1851
2275
|
// onChange = function
|
|
1852
2276
|
// disabled = boolean (default: false)
|
|
2277
|
+
// defaultMonth = Date
|
|
1853
2278
|
|
|
1854
2279
|
// Examples:
|
|
1855
2280
|
const [date, setDate] = useState(null);
|
|
@@ -1857,7 +2282,39 @@ const [date, setDate] = useState(null);
|
|
|
1857
2282
|
\`\`\`
|
|
1858
2283
|
|
|
1859
2284
|
**Common mistakes:**
|
|
1860
|
-
- ❌ \`
|
|
2285
|
+
- ❌ \`Calendar for an input field that opens a popover\` → Use <DatePicker /> (single date) or <DateRangePicker /> (range)
|
|
2286
|
+
- ❌ \`value as tuple for mode="single"\` → Pass value matching mode; use mode="range" for [start,end]
|
|
2287
|
+
|
|
2288
|
+
---
|
|
2289
|
+
|
|
2290
|
+
### DatePicker
|
|
2291
|
+
|
|
2292
|
+
Input trigger that opens a Calendar in a popover. Same modes as Calendar (single | range | multiple) plus a placeholder. Use this for a compact date field; use Calendar for an always-visible grid, or DateRangePicker for start/end ranges with presets.
|
|
2293
|
+
|
|
2294
|
+
\`\`\`tsx
|
|
2295
|
+
import { DatePicker } from "@usevyre/react"
|
|
2296
|
+
|
|
2297
|
+
// Props:
|
|
2298
|
+
// value = Date | [Date, Date] | Date[] | null
|
|
2299
|
+
// onChange = function
|
|
2300
|
+
// mode = "single" | "range" | "multiple" (default: single)
|
|
2301
|
+
// placeholder = string (default: Pick a date)
|
|
2302
|
+
// showTime = boolean (default: false)
|
|
2303
|
+
// minDate = Date
|
|
2304
|
+
// maxDate = Date
|
|
2305
|
+
// disabled = function
|
|
2306
|
+
// weekStartsOn = "0" | "1" (default: 1)
|
|
2307
|
+
// inputClassName = string
|
|
2308
|
+
|
|
2309
|
+
// Examples:
|
|
2310
|
+
const [date, setDate] = useState(null);
|
|
2311
|
+
<DatePicker value={date} onChange={setDate} placeholder="Pick a date" />
|
|
2312
|
+
<DatePicker value={date} onChange={setDate} showTime />
|
|
2313
|
+
\`\`\`
|
|
2314
|
+
|
|
2315
|
+
**Common mistakes:**
|
|
2316
|
+
- ❌ \`DatePicker mode="range" for { from, to } object\` → Use <DateRangePicker /> for the { from, to } object API + presets + dual month
|
|
2317
|
+
- ❌ \`DatePicker without value/onChange\` → Provide value and onChange (e.g. from useState)
|
|
1861
2318
|
|
|
1862
2319
|
---
|
|
1863
2320
|
|
|
@@ -1917,6 +2374,78 @@ import { Checkbox } from "@usevyre/react"
|
|
|
1917
2374
|
|
|
1918
2375
|
---
|
|
1919
2376
|
|
|
2377
|
+
### RadioGroup
|
|
2378
|
+
|
|
2379
|
+
Controlled single-choice group. RadioGroup owns the selected value; render it data-driven via the options array OR with composable <Radio> children for custom content. role=radiogroup with proper labelling. For multi-select use Checkbox; for a compact dropdown use Select.
|
|
2380
|
+
|
|
2381
|
+
\`\`\`tsx
|
|
2382
|
+
import { RadioGroup, Radio } from "@usevyre/react"
|
|
2383
|
+
|
|
2384
|
+
// Props:
|
|
2385
|
+
// value = string
|
|
2386
|
+
// defaultValue = string
|
|
2387
|
+
// onChange = function
|
|
2388
|
+
// name = string
|
|
2389
|
+
// disabled = boolean (default: false)
|
|
2390
|
+
// size = "sm" | "md" (default: md)
|
|
2391
|
+
// orientation = "vertical" | "horizontal" (default: vertical)
|
|
2392
|
+
// options = { value: string; label?: string; description?: string; disabled?: boolean }[]
|
|
2393
|
+
|
|
2394
|
+
// Examples:
|
|
2395
|
+
<RadioGroup
|
|
2396
|
+
value={plan}
|
|
2397
|
+
onChange={setPlan}
|
|
2398
|
+
options={[
|
|
2399
|
+
{ value: "free", label: "Free", description: "For hobby projects" },
|
|
2400
|
+
{ value: "pro", label: "Pro", description: "For teams" },
|
|
2401
|
+
]}
|
|
2402
|
+
/>
|
|
2403
|
+
<RadioGroup value={plan} onChange={setPlan} orientation="horizontal">
|
|
2404
|
+
<Radio value="free" label="Free" />
|
|
2405
|
+
<Radio value="pro" label="Pro" />
|
|
2406
|
+
</RadioGroup>
|
|
2407
|
+
\`\`\`
|
|
2408
|
+
|
|
2409
|
+
**Common mistakes:**
|
|
2410
|
+
- ❌ \`<Radio> used outside a <RadioGroup>\` → Always wrap <Radio> in <RadioGroup>
|
|
2411
|
+
- ❌ \`RadioGroup without value/onChange (React) or v-model (Vue)\` → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
|
|
2412
|
+
- ❌ \`Using Checkbox for mutually-exclusive choices\` → Use RadioGroup + Radio (or options) for one-of-many
|
|
2413
|
+
|
|
2414
|
+
---
|
|
2415
|
+
|
|
2416
|
+
### RichTextEditor
|
|
2417
|
+
|
|
2418
|
+
Controlled WYSIWYG editor. value is an HTML string; you own it in state and set it in onChange (React) / v-model (Vue). Native contentEditable + execCommand — zero dependencies. Toolbar: bold, italic, underline, strike, h1-h3, ordered/unordered lists, quote, code block, link, clear formatting.
|
|
2419
|
+
|
|
2420
|
+
\`\`\`tsx
|
|
2421
|
+
import { RichTextEditor } from "@usevyre/react"
|
|
2422
|
+
|
|
2423
|
+
// Props:
|
|
2424
|
+
// value = string
|
|
2425
|
+
// onChange = function
|
|
2426
|
+
// placeholder = string (default: Write something…)
|
|
2427
|
+
// disabled = boolean (default: false)
|
|
2428
|
+
// readOnly = boolean (default: false)
|
|
2429
|
+
// toolbar = RichTextTool[]
|
|
2430
|
+
// minHeight = string (default: 10rem)
|
|
2431
|
+
|
|
2432
|
+
// Examples:
|
|
2433
|
+
const [html, setHtml] = useState("<p>Hello <strong>world</strong></p>");
|
|
2434
|
+
<RichTextEditor value={html} onChange={setHtml} placeholder="Write…" />
|
|
2435
|
+
<RichTextEditor
|
|
2436
|
+
value={html}
|
|
2437
|
+
onChange={setHtml}
|
|
2438
|
+
toolbar={["bold", "italic", "link"]}
|
|
2439
|
+
/>
|
|
2440
|
+
\`\`\`
|
|
2441
|
+
|
|
2442
|
+
**Common mistakes:**
|
|
2443
|
+
- ❌ \`RichTextEditor without value/onChange (React) or v-model (Vue)\` → Keep the HTML string in state and update it in onChange / v-model
|
|
2444
|
+
- ❌ \`Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising\` → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
|
|
2445
|
+
- ❌ \`toolbar="bold" (string)\` → Pass an array, e.g. toolbar={["bold","italic","link"]}
|
|
2446
|
+
|
|
2447
|
+
---
|
|
2448
|
+
|
|
1920
2449
|
### Command
|
|
1921
2450
|
|
|
1922
2451
|
Command palette / search dialog. Use for search-first navigation or quick actions.
|
|
@@ -1968,7 +2497,7 @@ import { DropdownMenu, DropdownItem, DropdownSeparator, DropdownCheckboxItem, Dr
|
|
|
1968
2497
|
|
|
1969
2498
|
### Field
|
|
1970
2499
|
|
|
1971
|
-
Form field wrapper
|
|
2500
|
+
Form field wrapper. Two ways to use it (both supported): (1) props-based — pass label/hint/state/required for the common case; (2) composable — use the parts FieldLabel, FieldDescription, FieldError, FieldGroup, FieldSet for richer layouts (multiple controls, custom error placement). The props-based API is unchanged and still works.
|
|
1972
2501
|
|
|
1973
2502
|
\`\`\`tsx
|
|
1974
2503
|
import { Field, Input, Textarea } from "@usevyre/react"
|
|
@@ -1986,10 +2515,23 @@ import { Field, Input, Textarea } from "@usevyre/react"
|
|
|
1986
2515
|
<Field label="Search">
|
|
1987
2516
|
<Input leftElement={<SearchIcon />} placeholder="Search..." />
|
|
1988
2517
|
</Field>
|
|
2518
|
+
<Field>
|
|
2519
|
+
<FieldLabel required htmlFor="email">Email</FieldLabel>
|
|
2520
|
+
<Input id="email" type="email" />
|
|
2521
|
+
<FieldDescription>We\u2019ll never share it.</FieldDescription>
|
|
2522
|
+
<FieldError>{errors.email}</FieldError>
|
|
2523
|
+
</Field>
|
|
2524
|
+
|
|
2525
|
+
// Two controls side by side
|
|
2526
|
+
<FieldGroup orientation="horizontal">
|
|
2527
|
+
<Field label="First name"><Input /></Field>
|
|
2528
|
+
<Field label="Last name"><Input /></Field>
|
|
2529
|
+
</FieldGroup>
|
|
1989
2530
|
\`\`\`
|
|
1990
2531
|
|
|
1991
2532
|
**Common mistakes:**
|
|
1992
2533
|
- ❌ \`Applying state prop directly to Input\` → Wrap Input in <Field state="error"> to apply validation styling
|
|
2534
|
+
- ❌ \`Mixing props label/hint AND FieldLabel/FieldError for the same field\` → Pick one: either props-based (label/hint/state) OR composable parts
|
|
1993
2535
|
|
|
1994
2536
|
---
|
|
1995
2537
|
|
|
@@ -2001,6 +2543,7 @@ Text input field. Wrap in Field for labels and validation. Use leftElement/right
|
|
|
2001
2543
|
import { Input } from "@usevyre/react"
|
|
2002
2544
|
|
|
2003
2545
|
// Props:
|
|
2546
|
+
// modelValue = string | number
|
|
2004
2547
|
// size = "sm" | "md" | "lg" (default: md)
|
|
2005
2548
|
// leftElement = ReactNode
|
|
2006
2549
|
// rightElement = ReactNode
|
|
@@ -2012,6 +2555,7 @@ import { Input } from "@usevyre/react"
|
|
|
2012
2555
|
**Common mistakes:**
|
|
2013
2556
|
- ❌ \`size="icon"\` → Use size="sm" | "md" | "lg"
|
|
2014
2557
|
- ❌ \`type="search" for search UI\` → Import Command from @usevyre/react for search palettes
|
|
2558
|
+
- ❌ \`Vue: binding Input/Textarea value without v-model\` → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
|
|
2015
2559
|
|
|
2016
2560
|
---
|
|
2017
2561
|
|
|
@@ -2207,6 +2751,8 @@ import { AppLayout, Sidebar, SidebarHeader, SidebarContent, SidebarSection, Side
|
|
|
2207
2751
|
|
|
2208
2752
|
// Props:
|
|
2209
2753
|
// variant = "default" | "floating" (default: default)
|
|
2754
|
+
// SidebarTrigger.icon = ReactNode
|
|
2755
|
+
// SidebarTrigger.collapsedIcon = ReactNode
|
|
2210
2756
|
|
|
2211
2757
|
// Examples:
|
|
2212
2758
|
<AppLayout>
|
|
@@ -2222,8 +2768,18 @@ import { AppLayout, Sidebar, SidebarHeader, SidebarContent, SidebarSection, Side
|
|
|
2222
2768
|
</Sidebar>
|
|
2223
2769
|
<main>Page content</main>
|
|
2224
2770
|
</AppLayout>
|
|
2771
|
+
<SidebarTrigger icon={<PanelLeftClose />} collapsedIcon={<PanelLeftOpen />} />
|
|
2772
|
+
|
|
2773
|
+
// Vue:
|
|
2774
|
+
// <SidebarTrigger>
|
|
2775
|
+
// <template #icon><PanelLeftClose /></template>
|
|
2776
|
+
// <template #collapsed-icon><PanelLeftOpen /></template>
|
|
2777
|
+
// </SidebarTrigger>
|
|
2225
2778
|
\`\`\`
|
|
2226
2779
|
|
|
2780
|
+
**Common mistakes:**
|
|
2781
|
+
- ❌ \`Vue: passing icon/collapsedIcon as props on SidebarTrigger\` → Use <template #icon> and <template #collapsed-icon>; React uses icon / collapsedIcon props
|
|
2782
|
+
|
|
2227
2783
|
---
|
|
2228
2784
|
|
|
2229
2785
|
### Skeleton
|
|
@@ -2599,59 +3155,259 @@ import { TagGroup, Tag } from "@usevyre/react"
|
|
|
2599
3155
|
|
|
2600
3156
|
---
|
|
2601
3157
|
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
The following prop values and patterns do NOT exist in useVyre.
|
|
2605
|
-
If you generate these, you are hallucinating.
|
|
3158
|
+
### Item
|
|
2606
3159
|
|
|
2607
|
-
|
|
2608
|
-
- ❌ \`<Alert variant="error">\` → Use variant="danger"
|
|
2609
|
-
- ❌ \`<Alert variant="primary">\` → Use variant="info" | "success" | "warning" | "danger"
|
|
2610
|
-
- ❌ \`<Avatar size="xs">\` → Use size="sm"
|
|
2611
|
-
- ❌ \`<Avatar size="2xl">\` → Use size="xl"
|
|
2612
|
-
- ❌ \`<Badge variant="primary">\` → Use variant="accent" for brand color
|
|
2613
|
-
- ❌ \`<Badge variant="error">\` → Use variant="danger"
|
|
2614
|
-
- ❌ \`<Badge variant="info">\` → Use variant="teal" for info-like styling
|
|
2615
|
-
- ❌ \`<Breadcrumb Using plain <a> tags inside Breadcrumb>\` → Use BreadcrumbItem > BreadcrumbLink for each crumb
|
|
2616
|
-
- ❌ \`<Button variant="blue">\` → Use variant="accent" for brand amber, or variant="teal" for teal
|
|
2617
|
-
- ❌ \`<Button size="xl">\` → Use size="lg"
|
|
2618
|
-
- ❌ \`<Button color="...">\` → Use variant prop instead
|
|
2619
|
-
- ❌ \`<Button icon={...}>\` → Use leftIcon={...} or rightIcon={...}
|
|
2620
|
-
- ❌ \`<Button size="icon" without aria-label>\` → Add aria-label describing the action
|
|
2621
|
-
- ❌ \`<Calendar Using Calendar for time selection>\` → Combine with a separate time Input if time selection is needed
|
|
2622
|
-
- ❌ \`<Card variant="primary">\` → Use variant="elevated" | "outlined" | "ghost" | "accent"
|
|
2623
|
-
- ❌ \`<Checkbox size="lg">\` → Use size="md"
|
|
2624
|
-
- ❌ \`<Command Using Input type="search" for search UI>\` → Use Command + CommandInput + CommandList + CommandItem
|
|
2625
|
-
- ❌ \`<DropdownMenu DropdownItem variant="primary">\` → Use variant="danger" for destructive items only
|
|
2626
|
-
- ❌ \`<Field Applying state prop directly to Input>\` → Wrap Input in <Field state="error"> to apply validation styling
|
|
2627
|
-
- ❌ \`<Input size="icon">\` → Use size="sm" | "md" | "lg"
|
|
2628
|
-
- ❌ \`<Input type="search" for search UI>\` → Import Command from @usevyre/react for search palettes
|
|
2629
|
-
- ❌ \`<Modal size="xl">\` → Use size="lg" or size="full"
|
|
2630
|
-
- ❌ \`<Popover placement="top-center">\` → Use placement="top" for centered placement
|
|
2631
|
-
- ❌ \`<Progress value > 100>\` → Normalize your value to 0–100 range before passing
|
|
2632
|
-
- ❌ \`<Select Passing strings directly as children>\` → Pass options={[{ value: 'a', label: 'Option A' }]}
|
|
2633
|
-
- ❌ \`<Toast Rendering <Toast> directly in JSX>\` → Use: const { toast } = useToast(); then toast({ title, variant })
|
|
2634
|
-
- ❌ \`<Toast variant="error">\` → Use variant="danger"
|
|
2635
|
-
- ❌ \`<Toast variant="info">\` → Use variant="default"
|
|
2636
|
-
- ❌ \`<Tooltip Using Tooltip for rich content (forms, buttons, etc.)>\` → Use Popover for rich interactive content
|
|
2637
|
-
- ❌ \`<Typography Using raw <h1>, <p> tags instead of Typography components>\` → Use <Heading>, <Text>, <Lead> from @usevyre/react
|
|
2638
|
-
- ❌ \`<ButtonGroup ButtonGroup variant="...">\` → Set variant on each <Button> inside the group
|
|
2639
|
-
- ❌ \`<ButtonGroup ButtonGroup without Button children>\` → Place <Button> elements as direct children
|
|
2640
|
-
- ❌ \`<TagsInput TagsInput value={string}>\` → Pass an array: value={['react','vue']}
|
|
2641
|
-
- ❌ \`<TagsInput TagsInput without onChange>\` → Provide value and onChange (React) or v-model (Vue)
|
|
2642
|
-
- ❌ \`<Combobox Combobox value="">\` → Use value={null} for no selection
|
|
2643
|
-
- ❌ \`<Combobox Combobox options={string[]}>\` → Use [{ value: 'ts', label: 'TypeScript' }]
|
|
2644
|
-
- ❌ \`<Combobox Using Combobox for command palette>\` → Use Command for command palettes
|
|
2645
|
-
- ❌ \`<DataGrid DataGrid expecting built-in pagination>\` → Slice rows yourself and use the Pagination component
|
|
2646
|
-
- ❌ \`<DataGrid DataGrid expecting built-in filtering>\` → Filter the rows array before passing it in
|
|
2647
|
-
- ❌ \`<DataGrid sortable without onSort>\` → Handle onSort and sort the rows array in your state
|
|
2648
|
-
- ❌ \`<Tag Tag variant="success">\` → Use Badge for success/warning/teal status colors; Tag is for categories/filters
|
|
2649
|
-
- ❌ \`<Tag Using Tag for tag input>\` → Use TagsInput for adding/removing tags via keyboard
|
|
2650
|
-
- ❌ \`<Tag Tag size="xl">\` → Use size="lg"
|
|
2651
|
-
- ❌ \`<TagGroup TagGroup without Tag children>\` → Place <Tag> elements as direct children
|
|
2652
|
-
- ❌ \`<TagGroup Using TagGroup for tag input>\` → Use TagsInput for an editable tag field
|
|
3160
|
+
Layout primitive for list rows, settings rows, and notification rows. Denser than Card — use Item (not Card) for repeated list rows.
|
|
2653
3161
|
|
|
2654
|
-
|
|
3162
|
+
\`\`\`tsx
|
|
3163
|
+
import { Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions, ItemGroup } from "@usevyre/react"
|
|
3164
|
+
|
|
3165
|
+
// Props:
|
|
3166
|
+
// variant = "default" | "outlined" | "muted" | "plain" (default: default)
|
|
3167
|
+
// size = "sm" | "md" | "lg" (default: md)
|
|
3168
|
+
// clickable = boolean (default: false)
|
|
3169
|
+
|
|
3170
|
+
// Examples:
|
|
3171
|
+
<Item>
|
|
3172
|
+
<ItemMedia><BellIcon /></ItemMedia>
|
|
3173
|
+
<ItemContent>
|
|
3174
|
+
<ItemTitle>Notifications</ItemTitle>
|
|
3175
|
+
<ItemDescription>Receive an email when someone mentions you.</ItemDescription>
|
|
3176
|
+
</ItemContent>
|
|
3177
|
+
<ItemActions>
|
|
3178
|
+
<Switch defaultChecked />
|
|
3179
|
+
</ItemActions>
|
|
3180
|
+
</Item>
|
|
3181
|
+
<ItemGroup separated>
|
|
3182
|
+
<Item clickable>
|
|
3183
|
+
<ItemContent><ItemTitle>Profile</ItemTitle></ItemContent>
|
|
3184
|
+
</Item>
|
|
3185
|
+
<Item clickable>
|
|
3186
|
+
<ItemContent><ItemTitle>Billing</ItemTitle></ItemContent>
|
|
3187
|
+
</Item>
|
|
3188
|
+
</ItemGroup>
|
|
3189
|
+
\`\`\`
|
|
3190
|
+
|
|
3191
|
+
**Common mistakes:**
|
|
3192
|
+
- ❌ \`Card used for repeated list rows\` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
|
|
3193
|
+
- ❌ \`Item variant="primary"\` → Use variant="default" | "outlined" | "muted"
|
|
3194
|
+
- ❌ \`raw text directly inside Item\` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
|
|
3195
|
+
|
|
3196
|
+
---
|
|
3197
|
+
|
|
3198
|
+
### Kanban
|
|
3199
|
+
|
|
3200
|
+
Drag-and-drop board: cards move between columns (or reorder within a column). CONTROLLED & data-driven like DataGrid. While dragging, a placeholder shows the exact drop position. Each card is wrapped in a Card (variant="outlined"); renderCard (React) / #card slot (Vue) can render ANY content incl. complex components (Avatar/Badge/Progress). Columns and cards accept an optional semantic color tint. Native HTML5 DnD, zero deps.
|
|
3201
|
+
|
|
3202
|
+
\`\`\`tsx
|
|
3203
|
+
import { Kanban } from "@usevyre/react"
|
|
3204
|
+
|
|
3205
|
+
// Props:
|
|
3206
|
+
// value = KanbanColumn[]
|
|
3207
|
+
// onChange = function
|
|
3208
|
+
// renderCard = function
|
|
3209
|
+
// onCardClick = function
|
|
3210
|
+
|
|
3211
|
+
// Examples:
|
|
3212
|
+
const [columns, setColumns] = useState([
|
|
3213
|
+
{ id: "todo", title: "To Do", cards: [{ id: "1", title: "Spec API" }] },
|
|
3214
|
+
{ id: "doing", title: "In Progress", cards: [] },
|
|
3215
|
+
{ id: "done", title: "Done", cards: [{ id: "2", title: "Kickoff" }] },
|
|
3216
|
+
]);
|
|
3217
|
+
<Kanban value={columns} onChange={setColumns} />
|
|
3218
|
+
<Kanban
|
|
3219
|
+
value={columns}
|
|
3220
|
+
onChange={setColumns}
|
|
3221
|
+
onCardClick={(card) => openDetail(card.id)}
|
|
3222
|
+
renderCard={(card) => (
|
|
3223
|
+
<><strong>{card.title}</strong><Badge>{card.id}</Badge></>
|
|
3224
|
+
)}
|
|
3225
|
+
/>
|
|
3226
|
+
const [cols, setCols] = useState([
|
|
3227
|
+
{ id: "doing", title: "In Progress", color: "teal", cards: [
|
|
3228
|
+
{ id: "t1", title: "OAuth", assignee: "AK", progress: 60, color: "warning" },
|
|
3229
|
+
]},
|
|
3230
|
+
]);
|
|
3231
|
+
<Kanban
|
|
3232
|
+
value={cols}
|
|
3233
|
+
onChange={setCols}
|
|
3234
|
+
renderCard={(card) => (
|
|
3235
|
+
<><strong>{card.title}</strong><Progress value={card.progress} /></>
|
|
3236
|
+
)}
|
|
3237
|
+
/>
|
|
3238
|
+
\`\`\`
|
|
3239
|
+
|
|
3240
|
+
**Common mistakes:**
|
|
3241
|
+
- ❌ \`Kanban without onChange (or ignoring it)\` → Store columns in state and setColumns in onChange (v-model in Vue)
|
|
3242
|
+
- ❌ \`Duplicate card ids across columns\` → Use globally-unique card ids across the entire board
|
|
3243
|
+
- ❌ \`Mutating value in place then calling onChange\` → Pass the new array Kanban gives you straight to setState / v-model
|
|
3244
|
+
- ❌ \`color="blue" (or any non-semantic value)\` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
|
|
3245
|
+
|
|
3246
|
+
---
|
|
3247
|
+
|
|
3248
|
+
### Conversation
|
|
3249
|
+
|
|
3250
|
+
Chat / inbox message thread. CONTROLLED & data-driven like Kanban — you own \`value\` (messages array) and append in your own send handler; Conversation holds no message state. Consecutive messages from the same author are grouped (avatar + name shown once), day separators are inserted on date change, and outgoing messages (authorId === currentUserId) align right.
|
|
3251
|
+
|
|
3252
|
+
\`\`\`tsx
|
|
3253
|
+
import { Conversation } from "@usevyre/react"
|
|
3254
|
+
|
|
3255
|
+
// Props:
|
|
3256
|
+
// value = ConversationMessage[]
|
|
3257
|
+
// currentUserId = string
|
|
3258
|
+
// composer = boolean (default: false)
|
|
3259
|
+
// onSend = function
|
|
3260
|
+
// placeholder = string (default: Write a message…)
|
|
3261
|
+
// typing = boolean | string (default: false)
|
|
3262
|
+
// allowAttachments = boolean (default: false)
|
|
3263
|
+
// accept = string
|
|
3264
|
+
// renderMessage = function
|
|
3265
|
+
// renderComposer = function
|
|
3266
|
+
|
|
3267
|
+
// Examples:
|
|
3268
|
+
const [messages, setMessages] = useState([
|
|
3269
|
+
{ id: "1", authorId: "sam", authorName: "Sam", text: "Hey!" },
|
|
3270
|
+
{ id: "2", authorId: "me", text: "Hi \ud83d\udc4b", status: "read" },
|
|
3271
|
+
]);
|
|
3272
|
+
<Conversation
|
|
3273
|
+
value={messages}
|
|
3274
|
+
currentUserId="me"
|
|
3275
|
+
composer
|
|
3276
|
+
onSend={(t) => setMessages((m) => [...m, { id: crypto.randomUUID(), authorId: "me", text: t }])}
|
|
3277
|
+
/>
|
|
3278
|
+
<Conversation
|
|
3279
|
+
value={messages}
|
|
3280
|
+
currentUserId="me"
|
|
3281
|
+
typing="Sam is typing"
|
|
3282
|
+
renderMessage={(m) => <strong>{m.text}</strong>}
|
|
3283
|
+
/>
|
|
3284
|
+
const messages = [
|
|
3285
|
+
{ id: "1", authorId: "sam", authorName: "Sam", text: "Moodboard \ud83d\udc47",
|
|
3286
|
+
attachments: [{ kind: "image", url: "/board.png", name: "board.png" }] },
|
|
3287
|
+
{ id: "2", authorId: "me", text: "Specs:", status: "read",
|
|
3288
|
+
attachments: [{ kind: "file", url: "/spec.pdf", name: "spec.pdf", size: "2.4 MB" }] },
|
|
3289
|
+
];
|
|
3290
|
+
<Conversation value={messages} currentUserId="me" />
|
|
3291
|
+
\`\`\`
|
|
3292
|
+
|
|
3293
|
+
**Common mistakes:**
|
|
3294
|
+
- ❌ \`Conversation without currentUserId\` → Always pass currentUserId matching one of the message authorId values
|
|
3295
|
+
- ❌ \`Expecting Conversation to store/append messages\` → Append to your own state in onSend (or @send) and pass it back via value
|
|
3296
|
+
- ❌ \`composer without onSend (React) / @send (Vue)\` → Provide onSend / @send to append the message to value
|
|
3297
|
+
- ❌ \`Treating onSend as (text) only when using allowAttachments\` → Handle onSend(text, files) — map files to message attachments and append
|
|
3298
|
+
|
|
3299
|
+
---
|
|
3300
|
+
|
|
3301
|
+
### DateRangePicker
|
|
3302
|
+
|
|
3303
|
+
Start/end date range picker. Built on Calendar (mode=range) with a friendlier { from, to } object API, a two-month side-by-side view, and preset shortcuts. Use this for report/filter date ranges; use DatePicker for a single date.
|
|
3304
|
+
|
|
3305
|
+
\`\`\`tsx
|
|
3306
|
+
import { DateRangePicker } from "@usevyre/react"
|
|
3307
|
+
|
|
3308
|
+
// Props:
|
|
3309
|
+
// value = { from: Date | null; to: Date | null } | null
|
|
3310
|
+
// onChange = function
|
|
3311
|
+
// placeholder = string (default: Pick a date range)
|
|
3312
|
+
// numberOfMonths = "1" | "2" (default: 2)
|
|
3313
|
+
// presets = boolean | DateRangePreset[] (default: false)
|
|
3314
|
+
// minDate = Date
|
|
3315
|
+
// maxDate = Date
|
|
3316
|
+
// disabled = function
|
|
3317
|
+
// weekStartsOn = "0" | "1" (default: 1)
|
|
3318
|
+
|
|
3319
|
+
// Examples:
|
|
3320
|
+
const [range, setRange] = useState({ from: null, to: null });
|
|
3321
|
+
<DateRangePicker value={range} onChange={setRange} presets />
|
|
3322
|
+
<DateRangePicker value={range} onChange={setRange} numberOfMonths={1} />
|
|
3323
|
+
\`\`\`
|
|
3324
|
+
|
|
3325
|
+
**Common mistakes:**
|
|
3326
|
+
- ❌ \`value={[from, to]}\` → Use value={{ from, to }} and read range.from / range.to
|
|
3327
|
+
- ❌ \`DateRangePicker for a single date\` → Use <DatePicker /> for a single date
|
|
3328
|
+
- ❌ \`presets="true" (string)\` → Use the bare prop: presets (or presets={true})
|
|
3329
|
+
|
|
3330
|
+
---
|
|
3331
|
+
|
|
3332
|
+
## Hallucination Guard — Common AI Mistakes
|
|
3333
|
+
|
|
3334
|
+
The following prop values and patterns do NOT exist in useVyre.
|
|
3335
|
+
If you generate these, you are hallucinating.
|
|
3336
|
+
|
|
3337
|
+
- ❌ \`<Accordion Accordion without AccordionItem>\` → Always compose: Accordion > AccordionItem > AccordionTrigger + AccordionContent
|
|
3338
|
+
- ❌ \`<Alert variant="error">\` → Use variant="danger"
|
|
3339
|
+
- ❌ \`<Alert variant="primary">\` → Use variant="info" | "success" | "warning" | "danger"
|
|
3340
|
+
- ❌ \`<Avatar size="xs">\` → Use size="sm"
|
|
3341
|
+
- ❌ \`<Avatar size="2xl">\` → Use size="xl"
|
|
3342
|
+
- ❌ \`<Badge variant="primary">\` → Use variant="accent" for brand color
|
|
3343
|
+
- ❌ \`<Badge variant="error">\` → Use variant="danger"
|
|
3344
|
+
- ❌ \`<Badge variant="info">\` → Use variant="teal" for info-like styling
|
|
3345
|
+
- ❌ \`<Breadcrumb Using plain <a> tags inside Breadcrumb>\` → Use BreadcrumbItem > BreadcrumbLink for each crumb
|
|
3346
|
+
- ❌ \`<Button variant="blue">\` → Use variant="accent" for brand amber, or variant="teal" for teal
|
|
3347
|
+
- ❌ \`<Button size="xl">\` → Use size="lg"
|
|
3348
|
+
- ❌ \`<Button color="...">\` → Use variant prop instead
|
|
3349
|
+
- ❌ \`<Button icon={...}>\` → Use leftIcon={...} or rightIcon={...}
|
|
3350
|
+
- ❌ \`<Button size="icon" without aria-label>\` → Add aria-label describing the action
|
|
3351
|
+
- ❌ \`<Calendar Calendar for an input field that opens a popover>\` → Use <DatePicker /> (single date) or <DateRangePicker /> (range)
|
|
3352
|
+
- ❌ \`<Calendar value as tuple for mode="single">\` → Pass value matching mode; use mode="range" for [start,end]
|
|
3353
|
+
- ❌ \`<DatePicker DatePicker mode="range" for { from, to } object>\` → Use <DateRangePicker /> for the { from, to } object API + presets + dual month
|
|
3354
|
+
- ❌ \`<DatePicker DatePicker without value/onChange>\` → Provide value and onChange (e.g. from useState)
|
|
3355
|
+
- ❌ \`<Card variant="primary">\` → Use variant="elevated" | "outlined" | "ghost" | "accent"
|
|
3356
|
+
- ❌ \`<Checkbox size="lg">\` → Use size="md"
|
|
3357
|
+
- ❌ \`<RadioGroup <Radio> used outside a <RadioGroup>>\` → Always wrap <Radio> in <RadioGroup>
|
|
3358
|
+
- ❌ \`<RadioGroup RadioGroup without value/onChange (React) or v-model (Vue)>\` → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
|
|
3359
|
+
- ❌ \`<RadioGroup Using Checkbox for mutually-exclusive choices>\` → Use RadioGroup + Radio (or options) for one-of-many
|
|
3360
|
+
- ❌ \`<RichTextEditor RichTextEditor without value/onChange (React) or v-model (Vue)>\` → Keep the HTML string in state and update it in onChange / v-model
|
|
3361
|
+
- ❌ \`<RichTextEditor Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising>\` → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
|
|
3362
|
+
- ❌ \`<RichTextEditor toolbar="bold" (string)>\` → Pass an array, e.g. toolbar={["bold","italic","link"]}
|
|
3363
|
+
- ❌ \`<Command Using Input type="search" for search UI>\` → Use Command + CommandInput + CommandList + CommandItem
|
|
3364
|
+
- ❌ \`<DropdownMenu DropdownItem variant="primary">\` → Use variant="danger" for destructive items only
|
|
3365
|
+
- ❌ \`<Field Applying state prop directly to Input>\` → Wrap Input in <Field state="error"> to apply validation styling
|
|
3366
|
+
- ❌ \`<Field Mixing props label/hint AND FieldLabel/FieldError for the same field>\` → Pick one: either props-based (label/hint/state) OR composable parts
|
|
3367
|
+
- ❌ \`<Input size="icon">\` → Use size="sm" | "md" | "lg"
|
|
3368
|
+
- ❌ \`<Input type="search" for search UI>\` → Import Command from @usevyre/react for search palettes
|
|
3369
|
+
- ❌ \`<Input Vue: binding Input/Textarea value without v-model>\` → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
|
|
3370
|
+
- ❌ \`<Modal size="xl">\` → Use size="lg" or size="full"
|
|
3371
|
+
- ❌ \`<Popover placement="top-center">\` → Use placement="top" for centered placement
|
|
3372
|
+
- ❌ \`<Progress value > 100>\` → Normalize your value to 0–100 range before passing
|
|
3373
|
+
- ❌ \`<Select Passing strings directly as children>\` → Pass options={[{ value: 'a', label: 'Option A' }]}
|
|
3374
|
+
- ❌ \`<Sidebar Vue: passing icon/collapsedIcon as props on SidebarTrigger>\` → Use <template #icon> and <template #collapsed-icon>; React uses icon / collapsedIcon props
|
|
3375
|
+
- ❌ \`<Toast Rendering <Toast> directly in JSX>\` → Use: const { toast } = useToast(); then toast({ title, variant })
|
|
3376
|
+
- ❌ \`<Toast variant="error">\` → Use variant="danger"
|
|
3377
|
+
- ❌ \`<Toast variant="info">\` → Use variant="default"
|
|
3378
|
+
- ❌ \`<Tooltip Using Tooltip for rich content (forms, buttons, etc.)>\` → Use Popover for rich interactive content
|
|
3379
|
+
- ❌ \`<Typography Using raw <h1>, <p> tags instead of Typography components>\` → Use <Heading>, <Text>, <Lead> from @usevyre/react
|
|
3380
|
+
- ❌ \`<ButtonGroup ButtonGroup variant="...">\` → Set variant on each <Button> inside the group
|
|
3381
|
+
- ❌ \`<ButtonGroup ButtonGroup without Button children>\` → Place <Button> elements as direct children
|
|
3382
|
+
- ❌ \`<TagsInput TagsInput value={string}>\` → Pass an array: value={['react','vue']}
|
|
3383
|
+
- ❌ \`<TagsInput TagsInput without onChange>\` → Provide value and onChange (React) or v-model (Vue)
|
|
3384
|
+
- ❌ \`<Combobox Combobox value="">\` → Use value={null} for no selection
|
|
3385
|
+
- ❌ \`<Combobox Combobox options={string[]}>\` → Use [{ value: 'ts', label: 'TypeScript' }]
|
|
3386
|
+
- ❌ \`<Combobox Using Combobox for command palette>\` → Use Command for command palettes
|
|
3387
|
+
- ❌ \`<DataGrid DataGrid expecting built-in pagination>\` → Slice rows yourself and use the Pagination component
|
|
3388
|
+
- ❌ \`<DataGrid DataGrid expecting built-in filtering>\` → Filter the rows array before passing it in
|
|
3389
|
+
- ❌ \`<DataGrid sortable without onSort>\` → Handle onSort and sort the rows array in your state
|
|
3390
|
+
- ❌ \`<Tag Tag variant="success">\` → Use Badge for success/warning/teal status colors; Tag is for categories/filters
|
|
3391
|
+
- ❌ \`<Tag Using Tag for tag input>\` → Use TagsInput for adding/removing tags via keyboard
|
|
3392
|
+
- ❌ \`<Tag Tag size="xl">\` → Use size="lg"
|
|
3393
|
+
- ❌ \`<TagGroup TagGroup without Tag children>\` → Place <Tag> elements as direct children
|
|
3394
|
+
- ❌ \`<TagGroup Using TagGroup for tag input>\` → Use TagsInput for an editable tag field
|
|
3395
|
+
- ❌ \`<Item Card used for repeated list rows>\` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
|
|
3396
|
+
- ❌ \`<Item Item variant="primary">\` → Use variant="default" | "outlined" | "muted"
|
|
3397
|
+
- ❌ \`<Item raw text directly inside Item>\` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
|
|
3398
|
+
- ❌ \`<Kanban Kanban without onChange (or ignoring it)>\` → Store columns in state and setColumns in onChange (v-model in Vue)
|
|
3399
|
+
- ❌ \`<Kanban Duplicate card ids across columns>\` → Use globally-unique card ids across the entire board
|
|
3400
|
+
- ❌ \`<Kanban Mutating value in place then calling onChange>\` → Pass the new array Kanban gives you straight to setState / v-model
|
|
3401
|
+
- ❌ \`<Kanban color="blue" (or any non-semantic value)>\` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
|
|
3402
|
+
- ❌ \`<Conversation Conversation without currentUserId>\` → Always pass currentUserId matching one of the message authorId values
|
|
3403
|
+
- ❌ \`<Conversation Expecting Conversation to store/append messages>\` → Append to your own state in onSend (or @send) and pass it back via value
|
|
3404
|
+
- ❌ \`<Conversation composer without onSend (React) / @send (Vue)>\` → Provide onSend / @send to append the message to value
|
|
3405
|
+
- ❌ \`<Conversation Treating onSend as (text) only when using allowAttachments>\` → Handle onSend(text, files) — map files to message attachments and append
|
|
3406
|
+
- ❌ \`<DateRangePicker value={[from, to]}>\` → Use value={{ from, to }} and read range.from / range.to
|
|
3407
|
+
- ❌ \`<DateRangePicker DateRangePicker for a single date>\` → Use <DatePicker /> for a single date
|
|
3408
|
+
- ❌ \`<DateRangePicker presets="true" (string)>\` → Use the bare prop: presets (or presets={true})
|
|
3409
|
+
|
|
3410
|
+
---
|
|
2655
3411
|
|
|
2656
3412
|
## Styling Rules for AI Agents
|
|
2657
3413
|
|
|
@@ -2699,7 +3455,7 @@ If you generate these, you are hallucinating.
|
|
|
2699
3455
|
\`\`\`
|
|
2700
3456
|
`;
|
|
2701
3457
|
export const windsurfRules = `# useVyre Rules for Windsurf
|
|
2702
|
-
# Version: 1.
|
|
3458
|
+
# Version: 1.2.0
|
|
2703
3459
|
|
|
2704
3460
|
# useVyre Design System — AI Context
|
|
2705
3461
|
# Version: 0.2.0
|
|
@@ -3008,7 +3764,7 @@ import { Button } from "@usevyre/react"
|
|
|
3008
3764
|
|
|
3009
3765
|
### Calendar
|
|
3010
3766
|
|
|
3011
|
-
|
|
3767
|
+
Inline date-grid widget (always visible, no input). mode: single | range | multiple, optional time picker. For an input + popover use DatePicker; for start/end ranges with presets use DateRangePicker.
|
|
3012
3768
|
|
|
3013
3769
|
\`\`\`tsx
|
|
3014
3770
|
import { Calendar } from "@usevyre/react"
|
|
@@ -3017,6 +3773,7 @@ import { Calendar } from "@usevyre/react"
|
|
|
3017
3773
|
// value = Date | null
|
|
3018
3774
|
// onChange = function
|
|
3019
3775
|
// disabled = boolean (default: false)
|
|
3776
|
+
// defaultMonth = Date
|
|
3020
3777
|
|
|
3021
3778
|
// Examples:
|
|
3022
3779
|
const [date, setDate] = useState(null);
|
|
@@ -3024,7 +3781,39 @@ const [date, setDate] = useState(null);
|
|
|
3024
3781
|
\`\`\`
|
|
3025
3782
|
|
|
3026
3783
|
**Common mistakes:**
|
|
3027
|
-
- ❌ \`
|
|
3784
|
+
- ❌ \`Calendar for an input field that opens a popover\` → Use <DatePicker /> (single date) or <DateRangePicker /> (range)
|
|
3785
|
+
- ❌ \`value as tuple for mode="single"\` → Pass value matching mode; use mode="range" for [start,end]
|
|
3786
|
+
|
|
3787
|
+
---
|
|
3788
|
+
|
|
3789
|
+
### DatePicker
|
|
3790
|
+
|
|
3791
|
+
Input trigger that opens a Calendar in a popover. Same modes as Calendar (single | range | multiple) plus a placeholder. Use this for a compact date field; use Calendar for an always-visible grid, or DateRangePicker for start/end ranges with presets.
|
|
3792
|
+
|
|
3793
|
+
\`\`\`tsx
|
|
3794
|
+
import { DatePicker } from "@usevyre/react"
|
|
3795
|
+
|
|
3796
|
+
// Props:
|
|
3797
|
+
// value = Date | [Date, Date] | Date[] | null
|
|
3798
|
+
// onChange = function
|
|
3799
|
+
// mode = "single" | "range" | "multiple" (default: single)
|
|
3800
|
+
// placeholder = string (default: Pick a date)
|
|
3801
|
+
// showTime = boolean (default: false)
|
|
3802
|
+
// minDate = Date
|
|
3803
|
+
// maxDate = Date
|
|
3804
|
+
// disabled = function
|
|
3805
|
+
// weekStartsOn = "0" | "1" (default: 1)
|
|
3806
|
+
// inputClassName = string
|
|
3807
|
+
|
|
3808
|
+
// Examples:
|
|
3809
|
+
const [date, setDate] = useState(null);
|
|
3810
|
+
<DatePicker value={date} onChange={setDate} placeholder="Pick a date" />
|
|
3811
|
+
<DatePicker value={date} onChange={setDate} showTime />
|
|
3812
|
+
\`\`\`
|
|
3813
|
+
|
|
3814
|
+
**Common mistakes:**
|
|
3815
|
+
- ❌ \`DatePicker mode="range" for { from, to } object\` → Use <DateRangePicker /> for the { from, to } object API + presets + dual month
|
|
3816
|
+
- ❌ \`DatePicker without value/onChange\` → Provide value and onChange (e.g. from useState)
|
|
3028
3817
|
|
|
3029
3818
|
---
|
|
3030
3819
|
|
|
@@ -3084,6 +3873,78 @@ import { Checkbox } from "@usevyre/react"
|
|
|
3084
3873
|
|
|
3085
3874
|
---
|
|
3086
3875
|
|
|
3876
|
+
### RadioGroup
|
|
3877
|
+
|
|
3878
|
+
Controlled single-choice group. RadioGroup owns the selected value; render it data-driven via the options array OR with composable <Radio> children for custom content. role=radiogroup with proper labelling. For multi-select use Checkbox; for a compact dropdown use Select.
|
|
3879
|
+
|
|
3880
|
+
\`\`\`tsx
|
|
3881
|
+
import { RadioGroup, Radio } from "@usevyre/react"
|
|
3882
|
+
|
|
3883
|
+
// Props:
|
|
3884
|
+
// value = string
|
|
3885
|
+
// defaultValue = string
|
|
3886
|
+
// onChange = function
|
|
3887
|
+
// name = string
|
|
3888
|
+
// disabled = boolean (default: false)
|
|
3889
|
+
// size = "sm" | "md" (default: md)
|
|
3890
|
+
// orientation = "vertical" | "horizontal" (default: vertical)
|
|
3891
|
+
// options = { value: string; label?: string; description?: string; disabled?: boolean }[]
|
|
3892
|
+
|
|
3893
|
+
// Examples:
|
|
3894
|
+
<RadioGroup
|
|
3895
|
+
value={plan}
|
|
3896
|
+
onChange={setPlan}
|
|
3897
|
+
options={[
|
|
3898
|
+
{ value: "free", label: "Free", description: "For hobby projects" },
|
|
3899
|
+
{ value: "pro", label: "Pro", description: "For teams" },
|
|
3900
|
+
]}
|
|
3901
|
+
/>
|
|
3902
|
+
<RadioGroup value={plan} onChange={setPlan} orientation="horizontal">
|
|
3903
|
+
<Radio value="free" label="Free" />
|
|
3904
|
+
<Radio value="pro" label="Pro" />
|
|
3905
|
+
</RadioGroup>
|
|
3906
|
+
\`\`\`
|
|
3907
|
+
|
|
3908
|
+
**Common mistakes:**
|
|
3909
|
+
- ❌ \`<Radio> used outside a <RadioGroup>\` → Always wrap <Radio> in <RadioGroup>
|
|
3910
|
+
- ❌ \`RadioGroup without value/onChange (React) or v-model (Vue)\` → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
|
|
3911
|
+
- ❌ \`Using Checkbox for mutually-exclusive choices\` → Use RadioGroup + Radio (or options) for one-of-many
|
|
3912
|
+
|
|
3913
|
+
---
|
|
3914
|
+
|
|
3915
|
+
### RichTextEditor
|
|
3916
|
+
|
|
3917
|
+
Controlled WYSIWYG editor. value is an HTML string; you own it in state and set it in onChange (React) / v-model (Vue). Native contentEditable + execCommand — zero dependencies. Toolbar: bold, italic, underline, strike, h1-h3, ordered/unordered lists, quote, code block, link, clear formatting.
|
|
3918
|
+
|
|
3919
|
+
\`\`\`tsx
|
|
3920
|
+
import { RichTextEditor } from "@usevyre/react"
|
|
3921
|
+
|
|
3922
|
+
// Props:
|
|
3923
|
+
// value = string
|
|
3924
|
+
// onChange = function
|
|
3925
|
+
// placeholder = string (default: Write something…)
|
|
3926
|
+
// disabled = boolean (default: false)
|
|
3927
|
+
// readOnly = boolean (default: false)
|
|
3928
|
+
// toolbar = RichTextTool[]
|
|
3929
|
+
// minHeight = string (default: 10rem)
|
|
3930
|
+
|
|
3931
|
+
// Examples:
|
|
3932
|
+
const [html, setHtml] = useState("<p>Hello <strong>world</strong></p>");
|
|
3933
|
+
<RichTextEditor value={html} onChange={setHtml} placeholder="Write…" />
|
|
3934
|
+
<RichTextEditor
|
|
3935
|
+
value={html}
|
|
3936
|
+
onChange={setHtml}
|
|
3937
|
+
toolbar={["bold", "italic", "link"]}
|
|
3938
|
+
/>
|
|
3939
|
+
\`\`\`
|
|
3940
|
+
|
|
3941
|
+
**Common mistakes:**
|
|
3942
|
+
- ❌ \`RichTextEditor without value/onChange (React) or v-model (Vue)\` → Keep the HTML string in state and update it in onChange / v-model
|
|
3943
|
+
- ❌ \`Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising\` → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
|
|
3944
|
+
- ❌ \`toolbar="bold" (string)\` → Pass an array, e.g. toolbar={["bold","italic","link"]}
|
|
3945
|
+
|
|
3946
|
+
---
|
|
3947
|
+
|
|
3087
3948
|
### Command
|
|
3088
3949
|
|
|
3089
3950
|
Command palette / search dialog. Use for search-first navigation or quick actions.
|
|
@@ -3135,7 +3996,7 @@ import { DropdownMenu, DropdownItem, DropdownSeparator, DropdownCheckboxItem, Dr
|
|
|
3135
3996
|
|
|
3136
3997
|
### Field
|
|
3137
3998
|
|
|
3138
|
-
Form field wrapper
|
|
3999
|
+
Form field wrapper. Two ways to use it (both supported): (1) props-based — pass label/hint/state/required for the common case; (2) composable — use the parts FieldLabel, FieldDescription, FieldError, FieldGroup, FieldSet for richer layouts (multiple controls, custom error placement). The props-based API is unchanged and still works.
|
|
3139
4000
|
|
|
3140
4001
|
\`\`\`tsx
|
|
3141
4002
|
import { Field, Input, Textarea } from "@usevyre/react"
|
|
@@ -3153,10 +4014,23 @@ import { Field, Input, Textarea } from "@usevyre/react"
|
|
|
3153
4014
|
<Field label="Search">
|
|
3154
4015
|
<Input leftElement={<SearchIcon />} placeholder="Search..." />
|
|
3155
4016
|
</Field>
|
|
4017
|
+
<Field>
|
|
4018
|
+
<FieldLabel required htmlFor="email">Email</FieldLabel>
|
|
4019
|
+
<Input id="email" type="email" />
|
|
4020
|
+
<FieldDescription>We\u2019ll never share it.</FieldDescription>
|
|
4021
|
+
<FieldError>{errors.email}</FieldError>
|
|
4022
|
+
</Field>
|
|
4023
|
+
|
|
4024
|
+
// Two controls side by side
|
|
4025
|
+
<FieldGroup orientation="horizontal">
|
|
4026
|
+
<Field label="First name"><Input /></Field>
|
|
4027
|
+
<Field label="Last name"><Input /></Field>
|
|
4028
|
+
</FieldGroup>
|
|
3156
4029
|
\`\`\`
|
|
3157
4030
|
|
|
3158
4031
|
**Common mistakes:**
|
|
3159
4032
|
- ❌ \`Applying state prop directly to Input\` → Wrap Input in <Field state="error"> to apply validation styling
|
|
4033
|
+
- ❌ \`Mixing props label/hint AND FieldLabel/FieldError for the same field\` → Pick one: either props-based (label/hint/state) OR composable parts
|
|
3160
4034
|
|
|
3161
4035
|
---
|
|
3162
4036
|
|
|
@@ -3168,6 +4042,7 @@ Text input field. Wrap in Field for labels and validation. Use leftElement/right
|
|
|
3168
4042
|
import { Input } from "@usevyre/react"
|
|
3169
4043
|
|
|
3170
4044
|
// Props:
|
|
4045
|
+
// modelValue = string | number
|
|
3171
4046
|
// size = "sm" | "md" | "lg" (default: md)
|
|
3172
4047
|
// leftElement = ReactNode
|
|
3173
4048
|
// rightElement = ReactNode
|
|
@@ -3179,6 +4054,7 @@ import { Input } from "@usevyre/react"
|
|
|
3179
4054
|
**Common mistakes:**
|
|
3180
4055
|
- ❌ \`size="icon"\` → Use size="sm" | "md" | "lg"
|
|
3181
4056
|
- ❌ \`type="search" for search UI\` → Import Command from @usevyre/react for search palettes
|
|
4057
|
+
- ❌ \`Vue: binding Input/Textarea value without v-model\` → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
|
|
3182
4058
|
|
|
3183
4059
|
---
|
|
3184
4060
|
|
|
@@ -3374,6 +4250,8 @@ import { AppLayout, Sidebar, SidebarHeader, SidebarContent, SidebarSection, Side
|
|
|
3374
4250
|
|
|
3375
4251
|
// Props:
|
|
3376
4252
|
// variant = "default" | "floating" (default: default)
|
|
4253
|
+
// SidebarTrigger.icon = ReactNode
|
|
4254
|
+
// SidebarTrigger.collapsedIcon = ReactNode
|
|
3377
4255
|
|
|
3378
4256
|
// Examples:
|
|
3379
4257
|
<AppLayout>
|
|
@@ -3389,8 +4267,18 @@ import { AppLayout, Sidebar, SidebarHeader, SidebarContent, SidebarSection, Side
|
|
|
3389
4267
|
</Sidebar>
|
|
3390
4268
|
<main>Page content</main>
|
|
3391
4269
|
</AppLayout>
|
|
4270
|
+
<SidebarTrigger icon={<PanelLeftClose />} collapsedIcon={<PanelLeftOpen />} />
|
|
4271
|
+
|
|
4272
|
+
// Vue:
|
|
4273
|
+
// <SidebarTrigger>
|
|
4274
|
+
// <template #icon><PanelLeftClose /></template>
|
|
4275
|
+
// <template #collapsed-icon><PanelLeftOpen /></template>
|
|
4276
|
+
// </SidebarTrigger>
|
|
3392
4277
|
\`\`\`
|
|
3393
4278
|
|
|
4279
|
+
**Common mistakes:**
|
|
4280
|
+
- ❌ \`Vue: passing icon/collapsedIcon as props on SidebarTrigger\` → Use <template #icon> and <template #collapsed-icon>; React uses icon / collapsedIcon props
|
|
4281
|
+
|
|
3394
4282
|
---
|
|
3395
4283
|
|
|
3396
4284
|
### Skeleton
|
|
@@ -3766,6 +4654,180 @@ import { TagGroup, Tag } from "@usevyre/react"
|
|
|
3766
4654
|
|
|
3767
4655
|
---
|
|
3768
4656
|
|
|
4657
|
+
### Item
|
|
4658
|
+
|
|
4659
|
+
Layout primitive for list rows, settings rows, and notification rows. Denser than Card — use Item (not Card) for repeated list rows.
|
|
4660
|
+
|
|
4661
|
+
\`\`\`tsx
|
|
4662
|
+
import { Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions, ItemGroup } from "@usevyre/react"
|
|
4663
|
+
|
|
4664
|
+
// Props:
|
|
4665
|
+
// variant = "default" | "outlined" | "muted" | "plain" (default: default)
|
|
4666
|
+
// size = "sm" | "md" | "lg" (default: md)
|
|
4667
|
+
// clickable = boolean (default: false)
|
|
4668
|
+
|
|
4669
|
+
// Examples:
|
|
4670
|
+
<Item>
|
|
4671
|
+
<ItemMedia><BellIcon /></ItemMedia>
|
|
4672
|
+
<ItemContent>
|
|
4673
|
+
<ItemTitle>Notifications</ItemTitle>
|
|
4674
|
+
<ItemDescription>Receive an email when someone mentions you.</ItemDescription>
|
|
4675
|
+
</ItemContent>
|
|
4676
|
+
<ItemActions>
|
|
4677
|
+
<Switch defaultChecked />
|
|
4678
|
+
</ItemActions>
|
|
4679
|
+
</Item>
|
|
4680
|
+
<ItemGroup separated>
|
|
4681
|
+
<Item clickable>
|
|
4682
|
+
<ItemContent><ItemTitle>Profile</ItemTitle></ItemContent>
|
|
4683
|
+
</Item>
|
|
4684
|
+
<Item clickable>
|
|
4685
|
+
<ItemContent><ItemTitle>Billing</ItemTitle></ItemContent>
|
|
4686
|
+
</Item>
|
|
4687
|
+
</ItemGroup>
|
|
4688
|
+
\`\`\`
|
|
4689
|
+
|
|
4690
|
+
**Common mistakes:**
|
|
4691
|
+
- ❌ \`Card used for repeated list rows\` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
|
|
4692
|
+
- ❌ \`Item variant="primary"\` → Use variant="default" | "outlined" | "muted"
|
|
4693
|
+
- ❌ \`raw text directly inside Item\` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
|
|
4694
|
+
|
|
4695
|
+
---
|
|
4696
|
+
|
|
4697
|
+
### Kanban
|
|
4698
|
+
|
|
4699
|
+
Drag-and-drop board: cards move between columns (or reorder within a column). CONTROLLED & data-driven like DataGrid. While dragging, a placeholder shows the exact drop position. Each card is wrapped in a Card (variant="outlined"); renderCard (React) / #card slot (Vue) can render ANY content incl. complex components (Avatar/Badge/Progress). Columns and cards accept an optional semantic color tint. Native HTML5 DnD, zero deps.
|
|
4700
|
+
|
|
4701
|
+
\`\`\`tsx
|
|
4702
|
+
import { Kanban } from "@usevyre/react"
|
|
4703
|
+
|
|
4704
|
+
// Props:
|
|
4705
|
+
// value = KanbanColumn[]
|
|
4706
|
+
// onChange = function
|
|
4707
|
+
// renderCard = function
|
|
4708
|
+
// onCardClick = function
|
|
4709
|
+
|
|
4710
|
+
// Examples:
|
|
4711
|
+
const [columns, setColumns] = useState([
|
|
4712
|
+
{ id: "todo", title: "To Do", cards: [{ id: "1", title: "Spec API" }] },
|
|
4713
|
+
{ id: "doing", title: "In Progress", cards: [] },
|
|
4714
|
+
{ id: "done", title: "Done", cards: [{ id: "2", title: "Kickoff" }] },
|
|
4715
|
+
]);
|
|
4716
|
+
<Kanban value={columns} onChange={setColumns} />
|
|
4717
|
+
<Kanban
|
|
4718
|
+
value={columns}
|
|
4719
|
+
onChange={setColumns}
|
|
4720
|
+
onCardClick={(card) => openDetail(card.id)}
|
|
4721
|
+
renderCard={(card) => (
|
|
4722
|
+
<><strong>{card.title}</strong><Badge>{card.id}</Badge></>
|
|
4723
|
+
)}
|
|
4724
|
+
/>
|
|
4725
|
+
const [cols, setCols] = useState([
|
|
4726
|
+
{ id: "doing", title: "In Progress", color: "teal", cards: [
|
|
4727
|
+
{ id: "t1", title: "OAuth", assignee: "AK", progress: 60, color: "warning" },
|
|
4728
|
+
]},
|
|
4729
|
+
]);
|
|
4730
|
+
<Kanban
|
|
4731
|
+
value={cols}
|
|
4732
|
+
onChange={setCols}
|
|
4733
|
+
renderCard={(card) => (
|
|
4734
|
+
<><strong>{card.title}</strong><Progress value={card.progress} /></>
|
|
4735
|
+
)}
|
|
4736
|
+
/>
|
|
4737
|
+
\`\`\`
|
|
4738
|
+
|
|
4739
|
+
**Common mistakes:**
|
|
4740
|
+
- ❌ \`Kanban without onChange (or ignoring it)\` → Store columns in state and setColumns in onChange (v-model in Vue)
|
|
4741
|
+
- ❌ \`Duplicate card ids across columns\` → Use globally-unique card ids across the entire board
|
|
4742
|
+
- ❌ \`Mutating value in place then calling onChange\` → Pass the new array Kanban gives you straight to setState / v-model
|
|
4743
|
+
- ❌ \`color="blue" (or any non-semantic value)\` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
|
|
4744
|
+
|
|
4745
|
+
---
|
|
4746
|
+
|
|
4747
|
+
### Conversation
|
|
4748
|
+
|
|
4749
|
+
Chat / inbox message thread. CONTROLLED & data-driven like Kanban — you own \`value\` (messages array) and append in your own send handler; Conversation holds no message state. Consecutive messages from the same author are grouped (avatar + name shown once), day separators are inserted on date change, and outgoing messages (authorId === currentUserId) align right.
|
|
4750
|
+
|
|
4751
|
+
\`\`\`tsx
|
|
4752
|
+
import { Conversation } from "@usevyre/react"
|
|
4753
|
+
|
|
4754
|
+
// Props:
|
|
4755
|
+
// value = ConversationMessage[]
|
|
4756
|
+
// currentUserId = string
|
|
4757
|
+
// composer = boolean (default: false)
|
|
4758
|
+
// onSend = function
|
|
4759
|
+
// placeholder = string (default: Write a message…)
|
|
4760
|
+
// typing = boolean | string (default: false)
|
|
4761
|
+
// allowAttachments = boolean (default: false)
|
|
4762
|
+
// accept = string
|
|
4763
|
+
// renderMessage = function
|
|
4764
|
+
// renderComposer = function
|
|
4765
|
+
|
|
4766
|
+
// Examples:
|
|
4767
|
+
const [messages, setMessages] = useState([
|
|
4768
|
+
{ id: "1", authorId: "sam", authorName: "Sam", text: "Hey!" },
|
|
4769
|
+
{ id: "2", authorId: "me", text: "Hi \ud83d\udc4b", status: "read" },
|
|
4770
|
+
]);
|
|
4771
|
+
<Conversation
|
|
4772
|
+
value={messages}
|
|
4773
|
+
currentUserId="me"
|
|
4774
|
+
composer
|
|
4775
|
+
onSend={(t) => setMessages((m) => [...m, { id: crypto.randomUUID(), authorId: "me", text: t }])}
|
|
4776
|
+
/>
|
|
4777
|
+
<Conversation
|
|
4778
|
+
value={messages}
|
|
4779
|
+
currentUserId="me"
|
|
4780
|
+
typing="Sam is typing"
|
|
4781
|
+
renderMessage={(m) => <strong>{m.text}</strong>}
|
|
4782
|
+
/>
|
|
4783
|
+
const messages = [
|
|
4784
|
+
{ id: "1", authorId: "sam", authorName: "Sam", text: "Moodboard \ud83d\udc47",
|
|
4785
|
+
attachments: [{ kind: "image", url: "/board.png", name: "board.png" }] },
|
|
4786
|
+
{ id: "2", authorId: "me", text: "Specs:", status: "read",
|
|
4787
|
+
attachments: [{ kind: "file", url: "/spec.pdf", name: "spec.pdf", size: "2.4 MB" }] },
|
|
4788
|
+
];
|
|
4789
|
+
<Conversation value={messages} currentUserId="me" />
|
|
4790
|
+
\`\`\`
|
|
4791
|
+
|
|
4792
|
+
**Common mistakes:**
|
|
4793
|
+
- ❌ \`Conversation without currentUserId\` → Always pass currentUserId matching one of the message authorId values
|
|
4794
|
+
- ❌ \`Expecting Conversation to store/append messages\` → Append to your own state in onSend (or @send) and pass it back via value
|
|
4795
|
+
- ❌ \`composer without onSend (React) / @send (Vue)\` → Provide onSend / @send to append the message to value
|
|
4796
|
+
- ❌ \`Treating onSend as (text) only when using allowAttachments\` → Handle onSend(text, files) — map files to message attachments and append
|
|
4797
|
+
|
|
4798
|
+
---
|
|
4799
|
+
|
|
4800
|
+
### DateRangePicker
|
|
4801
|
+
|
|
4802
|
+
Start/end date range picker. Built on Calendar (mode=range) with a friendlier { from, to } object API, a two-month side-by-side view, and preset shortcuts. Use this for report/filter date ranges; use DatePicker for a single date.
|
|
4803
|
+
|
|
4804
|
+
\`\`\`tsx
|
|
4805
|
+
import { DateRangePicker } from "@usevyre/react"
|
|
4806
|
+
|
|
4807
|
+
// Props:
|
|
4808
|
+
// value = { from: Date | null; to: Date | null } | null
|
|
4809
|
+
// onChange = function
|
|
4810
|
+
// placeholder = string (default: Pick a date range)
|
|
4811
|
+
// numberOfMonths = "1" | "2" (default: 2)
|
|
4812
|
+
// presets = boolean | DateRangePreset[] (default: false)
|
|
4813
|
+
// minDate = Date
|
|
4814
|
+
// maxDate = Date
|
|
4815
|
+
// disabled = function
|
|
4816
|
+
// weekStartsOn = "0" | "1" (default: 1)
|
|
4817
|
+
|
|
4818
|
+
// Examples:
|
|
4819
|
+
const [range, setRange] = useState({ from: null, to: null });
|
|
4820
|
+
<DateRangePicker value={range} onChange={setRange} presets />
|
|
4821
|
+
<DateRangePicker value={range} onChange={setRange} numberOfMonths={1} />
|
|
4822
|
+
\`\`\`
|
|
4823
|
+
|
|
4824
|
+
**Common mistakes:**
|
|
4825
|
+
- ❌ \`value={[from, to]}\` → Use value={{ from, to }} and read range.from / range.to
|
|
4826
|
+
- ❌ \`DateRangePicker for a single date\` → Use <DatePicker /> for a single date
|
|
4827
|
+
- ❌ \`presets="true" (string)\` → Use the bare prop: presets (or presets={true})
|
|
4828
|
+
|
|
4829
|
+
---
|
|
4830
|
+
|
|
3769
4831
|
## Hallucination Guard — Common AI Mistakes
|
|
3770
4832
|
|
|
3771
4833
|
The following prop values and patterns do NOT exist in useVyre.
|
|
@@ -3785,18 +4847,30 @@ If you generate these, you are hallucinating.
|
|
|
3785
4847
|
- ❌ \`<Button color="...">\` → Use variant prop instead
|
|
3786
4848
|
- ❌ \`<Button icon={...}>\` → Use leftIcon={...} or rightIcon={...}
|
|
3787
4849
|
- ❌ \`<Button size="icon" without aria-label>\` → Add aria-label describing the action
|
|
3788
|
-
- ❌ \`<Calendar
|
|
4850
|
+
- ❌ \`<Calendar Calendar for an input field that opens a popover>\` → Use <DatePicker /> (single date) or <DateRangePicker /> (range)
|
|
4851
|
+
- ❌ \`<Calendar value as tuple for mode="single">\` → Pass value matching mode; use mode="range" for [start,end]
|
|
4852
|
+
- ❌ \`<DatePicker DatePicker mode="range" for { from, to } object>\` → Use <DateRangePicker /> for the { from, to } object API + presets + dual month
|
|
4853
|
+
- ❌ \`<DatePicker DatePicker without value/onChange>\` → Provide value and onChange (e.g. from useState)
|
|
3789
4854
|
- ❌ \`<Card variant="primary">\` → Use variant="elevated" | "outlined" | "ghost" | "accent"
|
|
3790
4855
|
- ❌ \`<Checkbox size="lg">\` → Use size="md"
|
|
4856
|
+
- ❌ \`<RadioGroup <Radio> used outside a <RadioGroup>>\` → Always wrap <Radio> in <RadioGroup>
|
|
4857
|
+
- ❌ \`<RadioGroup RadioGroup without value/onChange (React) or v-model (Vue)>\` → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
|
|
4858
|
+
- ❌ \`<RadioGroup Using Checkbox for mutually-exclusive choices>\` → Use RadioGroup + Radio (or options) for one-of-many
|
|
4859
|
+
- ❌ \`<RichTextEditor RichTextEditor without value/onChange (React) or v-model (Vue)>\` → Keep the HTML string in state and update it in onChange / v-model
|
|
4860
|
+
- ❌ \`<RichTextEditor Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising>\` → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
|
|
4861
|
+
- ❌ \`<RichTextEditor toolbar="bold" (string)>\` → Pass an array, e.g. toolbar={["bold","italic","link"]}
|
|
3791
4862
|
- ❌ \`<Command Using Input type="search" for search UI>\` → Use Command + CommandInput + CommandList + CommandItem
|
|
3792
4863
|
- ❌ \`<DropdownMenu DropdownItem variant="primary">\` → Use variant="danger" for destructive items only
|
|
3793
4864
|
- ❌ \`<Field Applying state prop directly to Input>\` → Wrap Input in <Field state="error"> to apply validation styling
|
|
4865
|
+
- ❌ \`<Field Mixing props label/hint AND FieldLabel/FieldError for the same field>\` → Pick one: either props-based (label/hint/state) OR composable parts
|
|
3794
4866
|
- ❌ \`<Input size="icon">\` → Use size="sm" | "md" | "lg"
|
|
3795
4867
|
- ❌ \`<Input type="search" for search UI>\` → Import Command from @usevyre/react for search palettes
|
|
4868
|
+
- ❌ \`<Input Vue: binding Input/Textarea value without v-model>\` → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
|
|
3796
4869
|
- ❌ \`<Modal size="xl">\` → Use size="lg" or size="full"
|
|
3797
4870
|
- ❌ \`<Popover placement="top-center">\` → Use placement="top" for centered placement
|
|
3798
4871
|
- ❌ \`<Progress value > 100>\` → Normalize your value to 0–100 range before passing
|
|
3799
4872
|
- ❌ \`<Select Passing strings directly as children>\` → Pass options={[{ value: 'a', label: 'Option A' }]}
|
|
4873
|
+
- ❌ \`<Sidebar Vue: passing icon/collapsedIcon as props on SidebarTrigger>\` → Use <template #icon> and <template #collapsed-icon>; React uses icon / collapsedIcon props
|
|
3800
4874
|
- ❌ \`<Toast Rendering <Toast> directly in JSX>\` → Use: const { toast } = useToast(); then toast({ title, variant })
|
|
3801
4875
|
- ❌ \`<Toast variant="error">\` → Use variant="danger"
|
|
3802
4876
|
- ❌ \`<Toast variant="info">\` → Use variant="default"
|
|
@@ -3817,6 +4891,20 @@ If you generate these, you are hallucinating.
|
|
|
3817
4891
|
- ❌ \`<Tag Tag size="xl">\` → Use size="lg"
|
|
3818
4892
|
- ❌ \`<TagGroup TagGroup without Tag children>\` → Place <Tag> elements as direct children
|
|
3819
4893
|
- ❌ \`<TagGroup Using TagGroup for tag input>\` → Use TagsInput for an editable tag field
|
|
4894
|
+
- ❌ \`<Item Card used for repeated list rows>\` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
|
|
4895
|
+
- ❌ \`<Item Item variant="primary">\` → Use variant="default" | "outlined" | "muted"
|
|
4896
|
+
- ❌ \`<Item raw text directly inside Item>\` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
|
|
4897
|
+
- ❌ \`<Kanban Kanban without onChange (or ignoring it)>\` → Store columns in state and setColumns in onChange (v-model in Vue)
|
|
4898
|
+
- ❌ \`<Kanban Duplicate card ids across columns>\` → Use globally-unique card ids across the entire board
|
|
4899
|
+
- ❌ \`<Kanban Mutating value in place then calling onChange>\` → Pass the new array Kanban gives you straight to setState / v-model
|
|
4900
|
+
- ❌ \`<Kanban color="blue" (or any non-semantic value)>\` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
|
|
4901
|
+
- ❌ \`<Conversation Conversation without currentUserId>\` → Always pass currentUserId matching one of the message authorId values
|
|
4902
|
+
- ❌ \`<Conversation Expecting Conversation to store/append messages>\` → Append to your own state in onSend (or @send) and pass it back via value
|
|
4903
|
+
- ❌ \`<Conversation composer without onSend (React) / @send (Vue)>\` → Provide onSend / @send to append the message to value
|
|
4904
|
+
- ❌ \`<Conversation Treating onSend as (text) only when using allowAttachments>\` → Handle onSend(text, files) — map files to message attachments and append
|
|
4905
|
+
- ❌ \`<DateRangePicker value={[from, to]}>\` → Use value={{ from, to }} and read range.from / range.to
|
|
4906
|
+
- ❌ \`<DateRangePicker DateRangePicker for a single date>\` → Use <DatePicker /> for a single date
|
|
4907
|
+
- ❌ \`<DateRangePicker presets="true" (string)>\` → Use the bare prop: presets (or presets={true})
|
|
3820
4908
|
|
|
3821
4909
|
---
|
|
3822
4910
|
|
|
@@ -3866,7 +4954,7 @@ If you generate these, you are hallucinating.
|
|
|
3866
4954
|
\`\`\`
|
|
3867
4955
|
`;
|
|
3868
4956
|
export const copilotInstructions = `# useVyre Copilot Instructions
|
|
3869
|
-
# Version: 1.
|
|
4957
|
+
# Version: 1.2.0
|
|
3870
4958
|
|
|
3871
4959
|
When generating UI code in this project, follow the useVyre design system rules below.
|
|
3872
4960
|
|
|
@@ -4177,7 +5265,7 @@ import { Button } from "@usevyre/react"
|
|
|
4177
5265
|
|
|
4178
5266
|
### Calendar
|
|
4179
5267
|
|
|
4180
|
-
|
|
5268
|
+
Inline date-grid widget (always visible, no input). mode: single | range | multiple, optional time picker. For an input + popover use DatePicker; for start/end ranges with presets use DateRangePicker.
|
|
4181
5269
|
|
|
4182
5270
|
\`\`\`tsx
|
|
4183
5271
|
import { Calendar } from "@usevyre/react"
|
|
@@ -4186,6 +5274,7 @@ import { Calendar } from "@usevyre/react"
|
|
|
4186
5274
|
// value = Date | null
|
|
4187
5275
|
// onChange = function
|
|
4188
5276
|
// disabled = boolean (default: false)
|
|
5277
|
+
// defaultMonth = Date
|
|
4189
5278
|
|
|
4190
5279
|
// Examples:
|
|
4191
5280
|
const [date, setDate] = useState(null);
|
|
@@ -4193,7 +5282,39 @@ const [date, setDate] = useState(null);
|
|
|
4193
5282
|
\`\`\`
|
|
4194
5283
|
|
|
4195
5284
|
**Common mistakes:**
|
|
4196
|
-
- ❌ \`
|
|
5285
|
+
- ❌ \`Calendar for an input field that opens a popover\` → Use <DatePicker /> (single date) or <DateRangePicker /> (range)
|
|
5286
|
+
- ❌ \`value as tuple for mode="single"\` → Pass value matching mode; use mode="range" for [start,end]
|
|
5287
|
+
|
|
5288
|
+
---
|
|
5289
|
+
|
|
5290
|
+
### DatePicker
|
|
5291
|
+
|
|
5292
|
+
Input trigger that opens a Calendar in a popover. Same modes as Calendar (single | range | multiple) plus a placeholder. Use this for a compact date field; use Calendar for an always-visible grid, or DateRangePicker for start/end ranges with presets.
|
|
5293
|
+
|
|
5294
|
+
\`\`\`tsx
|
|
5295
|
+
import { DatePicker } from "@usevyre/react"
|
|
5296
|
+
|
|
5297
|
+
// Props:
|
|
5298
|
+
// value = Date | [Date, Date] | Date[] | null
|
|
5299
|
+
// onChange = function
|
|
5300
|
+
// mode = "single" | "range" | "multiple" (default: single)
|
|
5301
|
+
// placeholder = string (default: Pick a date)
|
|
5302
|
+
// showTime = boolean (default: false)
|
|
5303
|
+
// minDate = Date
|
|
5304
|
+
// maxDate = Date
|
|
5305
|
+
// disabled = function
|
|
5306
|
+
// weekStartsOn = "0" | "1" (default: 1)
|
|
5307
|
+
// inputClassName = string
|
|
5308
|
+
|
|
5309
|
+
// Examples:
|
|
5310
|
+
const [date, setDate] = useState(null);
|
|
5311
|
+
<DatePicker value={date} onChange={setDate} placeholder="Pick a date" />
|
|
5312
|
+
<DatePicker value={date} onChange={setDate} showTime />
|
|
5313
|
+
\`\`\`
|
|
5314
|
+
|
|
5315
|
+
**Common mistakes:**
|
|
5316
|
+
- ❌ \`DatePicker mode="range" for { from, to } object\` → Use <DateRangePicker /> for the { from, to } object API + presets + dual month
|
|
5317
|
+
- ❌ \`DatePicker without value/onChange\` → Provide value and onChange (e.g. from useState)
|
|
4197
5318
|
|
|
4198
5319
|
---
|
|
4199
5320
|
|
|
@@ -4253,40 +5374,112 @@ import { Checkbox } from "@usevyre/react"
|
|
|
4253
5374
|
|
|
4254
5375
|
---
|
|
4255
5376
|
|
|
4256
|
-
###
|
|
5377
|
+
### RadioGroup
|
|
4257
5378
|
|
|
4258
|
-
|
|
5379
|
+
Controlled single-choice group. RadioGroup owns the selected value; render it data-driven via the options array OR with composable <Radio> children for custom content. role=radiogroup with proper labelling. For multi-select use Checkbox; for a compact dropdown use Select.
|
|
4259
5380
|
|
|
4260
5381
|
\`\`\`tsx
|
|
4261
|
-
import {
|
|
5382
|
+
import { RadioGroup, Radio } from "@usevyre/react"
|
|
5383
|
+
|
|
5384
|
+
// Props:
|
|
5385
|
+
// value = string
|
|
5386
|
+
// defaultValue = string
|
|
5387
|
+
// onChange = function
|
|
5388
|
+
// name = string
|
|
5389
|
+
// disabled = boolean (default: false)
|
|
5390
|
+
// size = "sm" | "md" (default: md)
|
|
5391
|
+
// orientation = "vertical" | "horizontal" (default: vertical)
|
|
5392
|
+
// options = { value: string; label?: string; description?: string; disabled?: boolean }[]
|
|
4262
5393
|
|
|
4263
5394
|
// Examples:
|
|
4264
|
-
<
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
5395
|
+
<RadioGroup
|
|
5396
|
+
value={plan}
|
|
5397
|
+
onChange={setPlan}
|
|
5398
|
+
options={[
|
|
5399
|
+
{ value: "free", label: "Free", description: "For hobby projects" },
|
|
5400
|
+
{ value: "pro", label: "Pro", description: "For teams" },
|
|
5401
|
+
]}
|
|
5402
|
+
/>
|
|
5403
|
+
<RadioGroup value={plan} onChange={setPlan} orientation="horizontal">
|
|
5404
|
+
<Radio value="free" label="Free" />
|
|
5405
|
+
<Radio value="pro" label="Pro" />
|
|
5406
|
+
</RadioGroup>
|
|
4274
5407
|
\`\`\`
|
|
4275
5408
|
|
|
4276
5409
|
**Common mistakes:**
|
|
4277
|
-
- ❌
|
|
5410
|
+
- ❌ \`<Radio> used outside a <RadioGroup>\` → Always wrap <Radio> in <RadioGroup>
|
|
5411
|
+
- ❌ \`RadioGroup without value/onChange (React) or v-model (Vue)\` → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
|
|
5412
|
+
- ❌ \`Using Checkbox for mutually-exclusive choices\` → Use RadioGroup + Radio (or options) for one-of-many
|
|
4278
5413
|
|
|
4279
5414
|
---
|
|
4280
5415
|
|
|
4281
|
-
###
|
|
5416
|
+
### RichTextEditor
|
|
4282
5417
|
|
|
4283
|
-
|
|
5418
|
+
Controlled WYSIWYG editor. value is an HTML string; you own it in state and set it in onChange (React) / v-model (Vue). Native contentEditable + execCommand — zero dependencies. Toolbar: bold, italic, underline, strike, h1-h3, ordered/unordered lists, quote, code block, link, clear formatting.
|
|
4284
5419
|
|
|
4285
5420
|
\`\`\`tsx
|
|
4286
|
-
import {
|
|
5421
|
+
import { RichTextEditor } from "@usevyre/react"
|
|
4287
5422
|
|
|
4288
5423
|
// Props:
|
|
4289
|
-
//
|
|
5424
|
+
// value = string
|
|
5425
|
+
// onChange = function
|
|
5426
|
+
// placeholder = string (default: Write something…)
|
|
5427
|
+
// disabled = boolean (default: false)
|
|
5428
|
+
// readOnly = boolean (default: false)
|
|
5429
|
+
// toolbar = RichTextTool[]
|
|
5430
|
+
// minHeight = string (default: 10rem)
|
|
5431
|
+
|
|
5432
|
+
// Examples:
|
|
5433
|
+
const [html, setHtml] = useState("<p>Hello <strong>world</strong></p>");
|
|
5434
|
+
<RichTextEditor value={html} onChange={setHtml} placeholder="Write…" />
|
|
5435
|
+
<RichTextEditor
|
|
5436
|
+
value={html}
|
|
5437
|
+
onChange={setHtml}
|
|
5438
|
+
toolbar={["bold", "italic", "link"]}
|
|
5439
|
+
/>
|
|
5440
|
+
\`\`\`
|
|
5441
|
+
|
|
5442
|
+
**Common mistakes:**
|
|
5443
|
+
- ❌ \`RichTextEditor without value/onChange (React) or v-model (Vue)\` → Keep the HTML string in state and update it in onChange / v-model
|
|
5444
|
+
- ❌ \`Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising\` → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
|
|
5445
|
+
- ❌ \`toolbar="bold" (string)\` → Pass an array, e.g. toolbar={["bold","italic","link"]}
|
|
5446
|
+
|
|
5447
|
+
---
|
|
5448
|
+
|
|
5449
|
+
### Command
|
|
5450
|
+
|
|
5451
|
+
Command palette / search dialog. Use for search-first navigation or quick actions.
|
|
5452
|
+
|
|
5453
|
+
\`\`\`tsx
|
|
5454
|
+
import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandDialog } from "@usevyre/react"
|
|
5455
|
+
|
|
5456
|
+
// Examples:
|
|
5457
|
+
<Command>
|
|
5458
|
+
<CommandInput placeholder="Search..." />
|
|
5459
|
+
<CommandList>
|
|
5460
|
+
<CommandEmpty>No results found.</CommandEmpty>
|
|
5461
|
+
<CommandGroup heading="Suggestions">
|
|
5462
|
+
<CommandItem onSelect={() => handleSelect('dashboard')}>Dashboard</CommandItem>
|
|
5463
|
+
<CommandItem onSelect={() => handleSelect('settings')}>Settings</CommandItem>
|
|
5464
|
+
</CommandGroup>
|
|
5465
|
+
</CommandList>
|
|
5466
|
+
</Command>
|
|
5467
|
+
\`\`\`
|
|
5468
|
+
|
|
5469
|
+
**Common mistakes:**
|
|
5470
|
+
- ❌ \`Using Input type="search" for search UI\` → Use Command + CommandInput + CommandList + CommandItem
|
|
5471
|
+
|
|
5472
|
+
---
|
|
5473
|
+
|
|
5474
|
+
### DropdownMenu
|
|
5475
|
+
|
|
5476
|
+
Contextual menu triggered by a button. Supports items, separators, checkbox items, radio groups, and sub-menus.
|
|
5477
|
+
|
|
5478
|
+
\`\`\`tsx
|
|
5479
|
+
import { DropdownMenu, DropdownItem, DropdownSeparator, DropdownCheckboxItem, DropdownRadioGroup, DropdownRadioItem, DropdownSub } from "@usevyre/react"
|
|
5480
|
+
|
|
5481
|
+
// Props:
|
|
5482
|
+
// trigger = ReactElement
|
|
4290
5483
|
|
|
4291
5484
|
// Examples:
|
|
4292
5485
|
<DropdownMenu trigger={<Button variant="secondary">Options</Button>}>
|
|
@@ -4304,7 +5497,7 @@ import { DropdownMenu, DropdownItem, DropdownSeparator, DropdownCheckboxItem, Dr
|
|
|
4304
5497
|
|
|
4305
5498
|
### Field
|
|
4306
5499
|
|
|
4307
|
-
Form field wrapper
|
|
5500
|
+
Form field wrapper. Two ways to use it (both supported): (1) props-based — pass label/hint/state/required for the common case; (2) composable — use the parts FieldLabel, FieldDescription, FieldError, FieldGroup, FieldSet for richer layouts (multiple controls, custom error placement). The props-based API is unchanged and still works.
|
|
4308
5501
|
|
|
4309
5502
|
\`\`\`tsx
|
|
4310
5503
|
import { Field, Input, Textarea } from "@usevyre/react"
|
|
@@ -4322,10 +5515,23 @@ import { Field, Input, Textarea } from "@usevyre/react"
|
|
|
4322
5515
|
<Field label="Search">
|
|
4323
5516
|
<Input leftElement={<SearchIcon />} placeholder="Search..." />
|
|
4324
5517
|
</Field>
|
|
5518
|
+
<Field>
|
|
5519
|
+
<FieldLabel required htmlFor="email">Email</FieldLabel>
|
|
5520
|
+
<Input id="email" type="email" />
|
|
5521
|
+
<FieldDescription>We\u2019ll never share it.</FieldDescription>
|
|
5522
|
+
<FieldError>{errors.email}</FieldError>
|
|
5523
|
+
</Field>
|
|
5524
|
+
|
|
5525
|
+
// Two controls side by side
|
|
5526
|
+
<FieldGroup orientation="horizontal">
|
|
5527
|
+
<Field label="First name"><Input /></Field>
|
|
5528
|
+
<Field label="Last name"><Input /></Field>
|
|
5529
|
+
</FieldGroup>
|
|
4325
5530
|
\`\`\`
|
|
4326
5531
|
|
|
4327
5532
|
**Common mistakes:**
|
|
4328
5533
|
- ❌ \`Applying state prop directly to Input\` → Wrap Input in <Field state="error"> to apply validation styling
|
|
5534
|
+
- ❌ \`Mixing props label/hint AND FieldLabel/FieldError for the same field\` → Pick one: either props-based (label/hint/state) OR composable parts
|
|
4329
5535
|
|
|
4330
5536
|
---
|
|
4331
5537
|
|
|
@@ -4337,6 +5543,7 @@ Text input field. Wrap in Field for labels and validation. Use leftElement/right
|
|
|
4337
5543
|
import { Input } from "@usevyre/react"
|
|
4338
5544
|
|
|
4339
5545
|
// Props:
|
|
5546
|
+
// modelValue = string | number
|
|
4340
5547
|
// size = "sm" | "md" | "lg" (default: md)
|
|
4341
5548
|
// leftElement = ReactNode
|
|
4342
5549
|
// rightElement = ReactNode
|
|
@@ -4348,6 +5555,7 @@ import { Input } from "@usevyre/react"
|
|
|
4348
5555
|
**Common mistakes:**
|
|
4349
5556
|
- ❌ \`size="icon"\` → Use size="sm" | "md" | "lg"
|
|
4350
5557
|
- ❌ \`type="search" for search UI\` → Import Command from @usevyre/react for search palettes
|
|
5558
|
+
- ❌ \`Vue: binding Input/Textarea value without v-model\` → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
|
|
4351
5559
|
|
|
4352
5560
|
---
|
|
4353
5561
|
|
|
@@ -4543,6 +5751,8 @@ import { AppLayout, Sidebar, SidebarHeader, SidebarContent, SidebarSection, Side
|
|
|
4543
5751
|
|
|
4544
5752
|
// Props:
|
|
4545
5753
|
// variant = "default" | "floating" (default: default)
|
|
5754
|
+
// SidebarTrigger.icon = ReactNode
|
|
5755
|
+
// SidebarTrigger.collapsedIcon = ReactNode
|
|
4546
5756
|
|
|
4547
5757
|
// Examples:
|
|
4548
5758
|
<AppLayout>
|
|
@@ -4558,8 +5768,18 @@ import { AppLayout, Sidebar, SidebarHeader, SidebarContent, SidebarSection, Side
|
|
|
4558
5768
|
</Sidebar>
|
|
4559
5769
|
<main>Page content</main>
|
|
4560
5770
|
</AppLayout>
|
|
5771
|
+
<SidebarTrigger icon={<PanelLeftClose />} collapsedIcon={<PanelLeftOpen />} />
|
|
5772
|
+
|
|
5773
|
+
// Vue:
|
|
5774
|
+
// <SidebarTrigger>
|
|
5775
|
+
// <template #icon><PanelLeftClose /></template>
|
|
5776
|
+
// <template #collapsed-icon><PanelLeftOpen /></template>
|
|
5777
|
+
// </SidebarTrigger>
|
|
4561
5778
|
\`\`\`
|
|
4562
5779
|
|
|
5780
|
+
**Common mistakes:**
|
|
5781
|
+
- ❌ \`Vue: passing icon/collapsedIcon as props on SidebarTrigger\` → Use <template #icon> and <template #collapsed-icon>; React uses icon / collapsedIcon props
|
|
5782
|
+
|
|
4563
5783
|
---
|
|
4564
5784
|
|
|
4565
5785
|
### Skeleton
|
|
@@ -4935,6 +6155,180 @@ import { TagGroup, Tag } from "@usevyre/react"
|
|
|
4935
6155
|
|
|
4936
6156
|
---
|
|
4937
6157
|
|
|
6158
|
+
### Item
|
|
6159
|
+
|
|
6160
|
+
Layout primitive for list rows, settings rows, and notification rows. Denser than Card — use Item (not Card) for repeated list rows.
|
|
6161
|
+
|
|
6162
|
+
\`\`\`tsx
|
|
6163
|
+
import { Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions, ItemGroup } from "@usevyre/react"
|
|
6164
|
+
|
|
6165
|
+
// Props:
|
|
6166
|
+
// variant = "default" | "outlined" | "muted" | "plain" (default: default)
|
|
6167
|
+
// size = "sm" | "md" | "lg" (default: md)
|
|
6168
|
+
// clickable = boolean (default: false)
|
|
6169
|
+
|
|
6170
|
+
// Examples:
|
|
6171
|
+
<Item>
|
|
6172
|
+
<ItemMedia><BellIcon /></ItemMedia>
|
|
6173
|
+
<ItemContent>
|
|
6174
|
+
<ItemTitle>Notifications</ItemTitle>
|
|
6175
|
+
<ItemDescription>Receive an email when someone mentions you.</ItemDescription>
|
|
6176
|
+
</ItemContent>
|
|
6177
|
+
<ItemActions>
|
|
6178
|
+
<Switch defaultChecked />
|
|
6179
|
+
</ItemActions>
|
|
6180
|
+
</Item>
|
|
6181
|
+
<ItemGroup separated>
|
|
6182
|
+
<Item clickable>
|
|
6183
|
+
<ItemContent><ItemTitle>Profile</ItemTitle></ItemContent>
|
|
6184
|
+
</Item>
|
|
6185
|
+
<Item clickable>
|
|
6186
|
+
<ItemContent><ItemTitle>Billing</ItemTitle></ItemContent>
|
|
6187
|
+
</Item>
|
|
6188
|
+
</ItemGroup>
|
|
6189
|
+
\`\`\`
|
|
6190
|
+
|
|
6191
|
+
**Common mistakes:**
|
|
6192
|
+
- ❌ \`Card used for repeated list rows\` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
|
|
6193
|
+
- ❌ \`Item variant="primary"\` → Use variant="default" | "outlined" | "muted"
|
|
6194
|
+
- ❌ \`raw text directly inside Item\` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
|
|
6195
|
+
|
|
6196
|
+
---
|
|
6197
|
+
|
|
6198
|
+
### Kanban
|
|
6199
|
+
|
|
6200
|
+
Drag-and-drop board: cards move between columns (or reorder within a column). CONTROLLED & data-driven like DataGrid. While dragging, a placeholder shows the exact drop position. Each card is wrapped in a Card (variant="outlined"); renderCard (React) / #card slot (Vue) can render ANY content incl. complex components (Avatar/Badge/Progress). Columns and cards accept an optional semantic color tint. Native HTML5 DnD, zero deps.
|
|
6201
|
+
|
|
6202
|
+
\`\`\`tsx
|
|
6203
|
+
import { Kanban } from "@usevyre/react"
|
|
6204
|
+
|
|
6205
|
+
// Props:
|
|
6206
|
+
// value = KanbanColumn[]
|
|
6207
|
+
// onChange = function
|
|
6208
|
+
// renderCard = function
|
|
6209
|
+
// onCardClick = function
|
|
6210
|
+
|
|
6211
|
+
// Examples:
|
|
6212
|
+
const [columns, setColumns] = useState([
|
|
6213
|
+
{ id: "todo", title: "To Do", cards: [{ id: "1", title: "Spec API" }] },
|
|
6214
|
+
{ id: "doing", title: "In Progress", cards: [] },
|
|
6215
|
+
{ id: "done", title: "Done", cards: [{ id: "2", title: "Kickoff" }] },
|
|
6216
|
+
]);
|
|
6217
|
+
<Kanban value={columns} onChange={setColumns} />
|
|
6218
|
+
<Kanban
|
|
6219
|
+
value={columns}
|
|
6220
|
+
onChange={setColumns}
|
|
6221
|
+
onCardClick={(card) => openDetail(card.id)}
|
|
6222
|
+
renderCard={(card) => (
|
|
6223
|
+
<><strong>{card.title}</strong><Badge>{card.id}</Badge></>
|
|
6224
|
+
)}
|
|
6225
|
+
/>
|
|
6226
|
+
const [cols, setCols] = useState([
|
|
6227
|
+
{ id: "doing", title: "In Progress", color: "teal", cards: [
|
|
6228
|
+
{ id: "t1", title: "OAuth", assignee: "AK", progress: 60, color: "warning" },
|
|
6229
|
+
]},
|
|
6230
|
+
]);
|
|
6231
|
+
<Kanban
|
|
6232
|
+
value={cols}
|
|
6233
|
+
onChange={setCols}
|
|
6234
|
+
renderCard={(card) => (
|
|
6235
|
+
<><strong>{card.title}</strong><Progress value={card.progress} /></>
|
|
6236
|
+
)}
|
|
6237
|
+
/>
|
|
6238
|
+
\`\`\`
|
|
6239
|
+
|
|
6240
|
+
**Common mistakes:**
|
|
6241
|
+
- ❌ \`Kanban without onChange (or ignoring it)\` → Store columns in state and setColumns in onChange (v-model in Vue)
|
|
6242
|
+
- ❌ \`Duplicate card ids across columns\` → Use globally-unique card ids across the entire board
|
|
6243
|
+
- ❌ \`Mutating value in place then calling onChange\` → Pass the new array Kanban gives you straight to setState / v-model
|
|
6244
|
+
- ❌ \`color="blue" (or any non-semantic value)\` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
|
|
6245
|
+
|
|
6246
|
+
---
|
|
6247
|
+
|
|
6248
|
+
### Conversation
|
|
6249
|
+
|
|
6250
|
+
Chat / inbox message thread. CONTROLLED & data-driven like Kanban — you own \`value\` (messages array) and append in your own send handler; Conversation holds no message state. Consecutive messages from the same author are grouped (avatar + name shown once), day separators are inserted on date change, and outgoing messages (authorId === currentUserId) align right.
|
|
6251
|
+
|
|
6252
|
+
\`\`\`tsx
|
|
6253
|
+
import { Conversation } from "@usevyre/react"
|
|
6254
|
+
|
|
6255
|
+
// Props:
|
|
6256
|
+
// value = ConversationMessage[]
|
|
6257
|
+
// currentUserId = string
|
|
6258
|
+
// composer = boolean (default: false)
|
|
6259
|
+
// onSend = function
|
|
6260
|
+
// placeholder = string (default: Write a message…)
|
|
6261
|
+
// typing = boolean | string (default: false)
|
|
6262
|
+
// allowAttachments = boolean (default: false)
|
|
6263
|
+
// accept = string
|
|
6264
|
+
// renderMessage = function
|
|
6265
|
+
// renderComposer = function
|
|
6266
|
+
|
|
6267
|
+
// Examples:
|
|
6268
|
+
const [messages, setMessages] = useState([
|
|
6269
|
+
{ id: "1", authorId: "sam", authorName: "Sam", text: "Hey!" },
|
|
6270
|
+
{ id: "2", authorId: "me", text: "Hi \ud83d\udc4b", status: "read" },
|
|
6271
|
+
]);
|
|
6272
|
+
<Conversation
|
|
6273
|
+
value={messages}
|
|
6274
|
+
currentUserId="me"
|
|
6275
|
+
composer
|
|
6276
|
+
onSend={(t) => setMessages((m) => [...m, { id: crypto.randomUUID(), authorId: "me", text: t }])}
|
|
6277
|
+
/>
|
|
6278
|
+
<Conversation
|
|
6279
|
+
value={messages}
|
|
6280
|
+
currentUserId="me"
|
|
6281
|
+
typing="Sam is typing"
|
|
6282
|
+
renderMessage={(m) => <strong>{m.text}</strong>}
|
|
6283
|
+
/>
|
|
6284
|
+
const messages = [
|
|
6285
|
+
{ id: "1", authorId: "sam", authorName: "Sam", text: "Moodboard \ud83d\udc47",
|
|
6286
|
+
attachments: [{ kind: "image", url: "/board.png", name: "board.png" }] },
|
|
6287
|
+
{ id: "2", authorId: "me", text: "Specs:", status: "read",
|
|
6288
|
+
attachments: [{ kind: "file", url: "/spec.pdf", name: "spec.pdf", size: "2.4 MB" }] },
|
|
6289
|
+
];
|
|
6290
|
+
<Conversation value={messages} currentUserId="me" />
|
|
6291
|
+
\`\`\`
|
|
6292
|
+
|
|
6293
|
+
**Common mistakes:**
|
|
6294
|
+
- ❌ \`Conversation without currentUserId\` → Always pass currentUserId matching one of the message authorId values
|
|
6295
|
+
- ❌ \`Expecting Conversation to store/append messages\` → Append to your own state in onSend (or @send) and pass it back via value
|
|
6296
|
+
- ❌ \`composer without onSend (React) / @send (Vue)\` → Provide onSend / @send to append the message to value
|
|
6297
|
+
- ❌ \`Treating onSend as (text) only when using allowAttachments\` → Handle onSend(text, files) — map files to message attachments and append
|
|
6298
|
+
|
|
6299
|
+
---
|
|
6300
|
+
|
|
6301
|
+
### DateRangePicker
|
|
6302
|
+
|
|
6303
|
+
Start/end date range picker. Built on Calendar (mode=range) with a friendlier { from, to } object API, a two-month side-by-side view, and preset shortcuts. Use this for report/filter date ranges; use DatePicker for a single date.
|
|
6304
|
+
|
|
6305
|
+
\`\`\`tsx
|
|
6306
|
+
import { DateRangePicker } from "@usevyre/react"
|
|
6307
|
+
|
|
6308
|
+
// Props:
|
|
6309
|
+
// value = { from: Date | null; to: Date | null } | null
|
|
6310
|
+
// onChange = function
|
|
6311
|
+
// placeholder = string (default: Pick a date range)
|
|
6312
|
+
// numberOfMonths = "1" | "2" (default: 2)
|
|
6313
|
+
// presets = boolean | DateRangePreset[] (default: false)
|
|
6314
|
+
// minDate = Date
|
|
6315
|
+
// maxDate = Date
|
|
6316
|
+
// disabled = function
|
|
6317
|
+
// weekStartsOn = "0" | "1" (default: 1)
|
|
6318
|
+
|
|
6319
|
+
// Examples:
|
|
6320
|
+
const [range, setRange] = useState({ from: null, to: null });
|
|
6321
|
+
<DateRangePicker value={range} onChange={setRange} presets />
|
|
6322
|
+
<DateRangePicker value={range} onChange={setRange} numberOfMonths={1} />
|
|
6323
|
+
\`\`\`
|
|
6324
|
+
|
|
6325
|
+
**Common mistakes:**
|
|
6326
|
+
- ❌ \`value={[from, to]}\` → Use value={{ from, to }} and read range.from / range.to
|
|
6327
|
+
- ❌ \`DateRangePicker for a single date\` → Use <DatePicker /> for a single date
|
|
6328
|
+
- ❌ \`presets="true" (string)\` → Use the bare prop: presets (or presets={true})
|
|
6329
|
+
|
|
6330
|
+
---
|
|
6331
|
+
|
|
4938
6332
|
## Hallucination Guard — Common AI Mistakes
|
|
4939
6333
|
|
|
4940
6334
|
The following prop values and patterns do NOT exist in useVyre.
|
|
@@ -4954,18 +6348,30 @@ If you generate these, you are hallucinating.
|
|
|
4954
6348
|
- ❌ \`<Button color="...">\` → Use variant prop instead
|
|
4955
6349
|
- ❌ \`<Button icon={...}>\` → Use leftIcon={...} or rightIcon={...}
|
|
4956
6350
|
- ❌ \`<Button size="icon" without aria-label>\` → Add aria-label describing the action
|
|
4957
|
-
- ❌ \`<Calendar
|
|
6351
|
+
- ❌ \`<Calendar Calendar for an input field that opens a popover>\` → Use <DatePicker /> (single date) or <DateRangePicker /> (range)
|
|
6352
|
+
- ❌ \`<Calendar value as tuple for mode="single">\` → Pass value matching mode; use mode="range" for [start,end]
|
|
6353
|
+
- ❌ \`<DatePicker DatePicker mode="range" for { from, to } object>\` → Use <DateRangePicker /> for the { from, to } object API + presets + dual month
|
|
6354
|
+
- ❌ \`<DatePicker DatePicker without value/onChange>\` → Provide value and onChange (e.g. from useState)
|
|
4958
6355
|
- ❌ \`<Card variant="primary">\` → Use variant="elevated" | "outlined" | "ghost" | "accent"
|
|
4959
6356
|
- ❌ \`<Checkbox size="lg">\` → Use size="md"
|
|
6357
|
+
- ❌ \`<RadioGroup <Radio> used outside a <RadioGroup>>\` → Always wrap <Radio> in <RadioGroup>
|
|
6358
|
+
- ❌ \`<RadioGroup RadioGroup without value/onChange (React) or v-model (Vue)>\` → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
|
|
6359
|
+
- ❌ \`<RadioGroup Using Checkbox for mutually-exclusive choices>\` → Use RadioGroup + Radio (or options) for one-of-many
|
|
6360
|
+
- ❌ \`<RichTextEditor RichTextEditor without value/onChange (React) or v-model (Vue)>\` → Keep the HTML string in state and update it in onChange / v-model
|
|
6361
|
+
- ❌ \`<RichTextEditor Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising>\` → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
|
|
6362
|
+
- ❌ \`<RichTextEditor toolbar="bold" (string)>\` → Pass an array, e.g. toolbar={["bold","italic","link"]}
|
|
4960
6363
|
- ❌ \`<Command Using Input type="search" for search UI>\` → Use Command + CommandInput + CommandList + CommandItem
|
|
4961
6364
|
- ❌ \`<DropdownMenu DropdownItem variant="primary">\` → Use variant="danger" for destructive items only
|
|
4962
6365
|
- ❌ \`<Field Applying state prop directly to Input>\` → Wrap Input in <Field state="error"> to apply validation styling
|
|
6366
|
+
- ❌ \`<Field Mixing props label/hint AND FieldLabel/FieldError for the same field>\` → Pick one: either props-based (label/hint/state) OR composable parts
|
|
4963
6367
|
- ❌ \`<Input size="icon">\` → Use size="sm" | "md" | "lg"
|
|
4964
6368
|
- ❌ \`<Input type="search" for search UI>\` → Import Command from @usevyre/react for search palettes
|
|
6369
|
+
- ❌ \`<Input Vue: binding Input/Textarea value without v-model>\` → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
|
|
4965
6370
|
- ❌ \`<Modal size="xl">\` → Use size="lg" or size="full"
|
|
4966
6371
|
- ❌ \`<Popover placement="top-center">\` → Use placement="top" for centered placement
|
|
4967
6372
|
- ❌ \`<Progress value > 100>\` → Normalize your value to 0–100 range before passing
|
|
4968
6373
|
- ❌ \`<Select Passing strings directly as children>\` → Pass options={[{ value: 'a', label: 'Option A' }]}
|
|
6374
|
+
- ❌ \`<Sidebar Vue: passing icon/collapsedIcon as props on SidebarTrigger>\` → Use <template #icon> and <template #collapsed-icon>; React uses icon / collapsedIcon props
|
|
4969
6375
|
- ❌ \`<Toast Rendering <Toast> directly in JSX>\` → Use: const { toast } = useToast(); then toast({ title, variant })
|
|
4970
6376
|
- ❌ \`<Toast variant="error">\` → Use variant="danger"
|
|
4971
6377
|
- ❌ \`<Toast variant="info">\` → Use variant="default"
|
|
@@ -4986,6 +6392,20 @@ If you generate these, you are hallucinating.
|
|
|
4986
6392
|
- ❌ \`<Tag Tag size="xl">\` → Use size="lg"
|
|
4987
6393
|
- ❌ \`<TagGroup TagGroup without Tag children>\` → Place <Tag> elements as direct children
|
|
4988
6394
|
- ❌ \`<TagGroup Using TagGroup for tag input>\` → Use TagsInput for an editable tag field
|
|
6395
|
+
- ❌ \`<Item Card used for repeated list rows>\` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
|
|
6396
|
+
- ❌ \`<Item Item variant="primary">\` → Use variant="default" | "outlined" | "muted"
|
|
6397
|
+
- ❌ \`<Item raw text directly inside Item>\` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
|
|
6398
|
+
- ❌ \`<Kanban Kanban without onChange (or ignoring it)>\` → Store columns in state and setColumns in onChange (v-model in Vue)
|
|
6399
|
+
- ❌ \`<Kanban Duplicate card ids across columns>\` → Use globally-unique card ids across the entire board
|
|
6400
|
+
- ❌ \`<Kanban Mutating value in place then calling onChange>\` → Pass the new array Kanban gives you straight to setState / v-model
|
|
6401
|
+
- ❌ \`<Kanban color="blue" (or any non-semantic value)>\` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
|
|
6402
|
+
- ❌ \`<Conversation Conversation without currentUserId>\` → Always pass currentUserId matching one of the message authorId values
|
|
6403
|
+
- ❌ \`<Conversation Expecting Conversation to store/append messages>\` → Append to your own state in onSend (or @send) and pass it back via value
|
|
6404
|
+
- ❌ \`<Conversation composer without onSend (React) / @send (Vue)>\` → Provide onSend / @send to append the message to value
|
|
6405
|
+
- ❌ \`<Conversation Treating onSend as (text) only when using allowAttachments>\` → Handle onSend(text, files) — map files to message attachments and append
|
|
6406
|
+
- ❌ \`<DateRangePicker value={[from, to]}>\` → Use value={{ from, to }} and read range.from / range.to
|
|
6407
|
+
- ❌ \`<DateRangePicker DateRangePicker for a single date>\` → Use <DatePicker /> for a single date
|
|
6408
|
+
- ❌ \`<DateRangePicker presets="true" (string)>\` → Use the bare prop: presets (or presets={true})
|
|
4989
6409
|
|
|
4990
6410
|
---
|
|
4991
6411
|
|
|
@@ -5037,8 +6457,8 @@ If you generate these, you are hallucinating.
|
|
|
5037
6457
|
|
|
5038
6458
|
export const schema = {
|
|
5039
6459
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
5040
|
-
"version": "1.
|
|
5041
|
-
"generatedAt": "2026-05-
|
|
6460
|
+
"version": "1.2.0",
|
|
6461
|
+
"generatedAt": "2026-05-16",
|
|
5042
6462
|
"package": "@usevyre/react",
|
|
5043
6463
|
"packageVersion": "1.1.0",
|
|
5044
6464
|
"validFor": [
|
|
@@ -5046,6 +6466,11 @@ export const schema = {
|
|
|
5046
6466
|
"@usevyre/vue@1.1.0+"
|
|
5047
6467
|
],
|
|
5048
6468
|
"changelog": {
|
|
6469
|
+
"1.2.0": {
|
|
6470
|
+
"date": "2026-05-16",
|
|
6471
|
+
"breaking": false,
|
|
6472
|
+
"summary": "Added Item layout primitive, DateRangePicker (dual-month range picker with presets, built on Calendar) and Kanban (controlled drag-and-drop board); Calendar gained additive defaultMonth prop; DatePicker split into its own documented component/entry"
|
|
6473
|
+
},
|
|
5049
6474
|
"1.1.0": {
|
|
5050
6475
|
"date": "2026-05-15",
|
|
5051
6476
|
"breaking": false,
|
|
@@ -5386,7 +6811,7 @@ export const schema = {
|
|
|
5386
6811
|
]
|
|
5387
6812
|
},
|
|
5388
6813
|
"Calendar": {
|
|
5389
|
-
"description": "
|
|
6814
|
+
"description": "Inline date-grid widget (always visible, no input). mode: single | range | multiple, optional time picker. For an input + popover use DatePicker; for start/end ranges with presets use DateRangePicker.",
|
|
5390
6815
|
"import": "import { Calendar } from \"@usevyre/react\"",
|
|
5391
6816
|
"props": {
|
|
5392
6817
|
"value": {
|
|
@@ -5400,13 +6825,22 @@ export const schema = {
|
|
|
5400
6825
|
"disabled": {
|
|
5401
6826
|
"type": "boolean",
|
|
5402
6827
|
"default": false
|
|
6828
|
+
},
|
|
6829
|
+
"defaultMonth": {
|
|
6830
|
+
"type": "Date",
|
|
6831
|
+
"description": "Month to display initially when value is empty (uncontrolled view). Used by DateRangePicker for the second month."
|
|
5403
6832
|
}
|
|
5404
6833
|
},
|
|
5405
6834
|
"antiPatterns": [
|
|
5406
6835
|
{
|
|
5407
|
-
"pattern": "
|
|
5408
|
-
"reason": "Calendar
|
|
5409
|
-
"fix": "
|
|
6836
|
+
"pattern": "Calendar for an input field that opens a popover",
|
|
6837
|
+
"reason": "Calendar renders an always-visible grid, not a trigger",
|
|
6838
|
+
"fix": "Use <DatePicker /> (single date) or <DateRangePicker /> (range)"
|
|
6839
|
+
},
|
|
6840
|
+
{
|
|
6841
|
+
"pattern": "value as tuple for mode=\"single\"",
|
|
6842
|
+
"reason": "value type must match mode: Date for single, [Date,Date] for range, Date[] for multiple",
|
|
6843
|
+
"fix": "Pass value matching mode; use mode=\"range\" for [start,end]"
|
|
5410
6844
|
}
|
|
5411
6845
|
],
|
|
5412
6846
|
"examples": [
|
|
@@ -5416,6 +6850,87 @@ export const schema = {
|
|
|
5416
6850
|
}
|
|
5417
6851
|
]
|
|
5418
6852
|
},
|
|
6853
|
+
"DatePicker": {
|
|
6854
|
+
"description": "Input trigger that opens a Calendar in a popover. Same modes as Calendar (single | range | multiple) plus a placeholder. Use this for a compact date field; use Calendar for an always-visible grid, or DateRangePicker for start/end ranges with presets.",
|
|
6855
|
+
"import": "import { DatePicker } from \"@usevyre/react\"",
|
|
6856
|
+
"props": {
|
|
6857
|
+
"value": {
|
|
6858
|
+
"type": "Date | [Date, Date] | Date[] | null",
|
|
6859
|
+
"description": "Selected value (controlled). Type must match mode."
|
|
6860
|
+
},
|
|
6861
|
+
"onChange": {
|
|
6862
|
+
"type": "function",
|
|
6863
|
+
"description": "Callback when the selection changes; argument shape matches mode"
|
|
6864
|
+
},
|
|
6865
|
+
"mode": {
|
|
6866
|
+
"type": "enum",
|
|
6867
|
+
"values": [
|
|
6868
|
+
"single",
|
|
6869
|
+
"range",
|
|
6870
|
+
"multiple"
|
|
6871
|
+
],
|
|
6872
|
+
"default": "single",
|
|
6873
|
+
"description": "Selection mode"
|
|
6874
|
+
},
|
|
6875
|
+
"placeholder": {
|
|
6876
|
+
"type": "string",
|
|
6877
|
+
"default": "Pick a date",
|
|
6878
|
+
"description": "Trigger text when nothing is selected"
|
|
6879
|
+
},
|
|
6880
|
+
"showTime": {
|
|
6881
|
+
"type": "boolean",
|
|
6882
|
+
"default": false,
|
|
6883
|
+
"description": "Adds an HH:MM time picker (single mode)"
|
|
6884
|
+
},
|
|
6885
|
+
"minDate": {
|
|
6886
|
+
"type": "Date",
|
|
6887
|
+
"description": "Earliest selectable date"
|
|
6888
|
+
},
|
|
6889
|
+
"maxDate": {
|
|
6890
|
+
"type": "Date",
|
|
6891
|
+
"description": "Latest selectable date"
|
|
6892
|
+
},
|
|
6893
|
+
"disabled": {
|
|
6894
|
+
"type": "function",
|
|
6895
|
+
"description": "(date: Date) => boolean — disable specific dates"
|
|
6896
|
+
},
|
|
6897
|
+
"weekStartsOn": {
|
|
6898
|
+
"type": "enum",
|
|
6899
|
+
"values": [
|
|
6900
|
+
0,
|
|
6901
|
+
1
|
|
6902
|
+
],
|
|
6903
|
+
"default": 1,
|
|
6904
|
+
"description": "0 = Sunday, 1 = Monday"
|
|
6905
|
+
},
|
|
6906
|
+
"inputClassName": {
|
|
6907
|
+
"type": "string",
|
|
6908
|
+
"description": "Class on the trigger button"
|
|
6909
|
+
}
|
|
6910
|
+
},
|
|
6911
|
+
"antiPatterns": [
|
|
6912
|
+
{
|
|
6913
|
+
"pattern": "DatePicker mode=\"range\" for { from, to } object",
|
|
6914
|
+
"reason": "DatePicker range mode emits a [Date, Date] tuple, not a { from, to } object",
|
|
6915
|
+
"fix": "Use <DateRangePicker /> for the { from, to } object API + presets + dual month"
|
|
6916
|
+
},
|
|
6917
|
+
{
|
|
6918
|
+
"pattern": "DatePicker without value/onChange",
|
|
6919
|
+
"reason": "DatePicker is controlled — it has no internal selected state",
|
|
6920
|
+
"fix": "Provide value and onChange (e.g. from useState)"
|
|
6921
|
+
}
|
|
6922
|
+
],
|
|
6923
|
+
"examples": [
|
|
6924
|
+
{
|
|
6925
|
+
"description": "Single date field",
|
|
6926
|
+
"code": "const [date, setDate] = useState(null);\n<DatePicker value={date} onChange={setDate} placeholder=\"Pick a date\" />"
|
|
6927
|
+
},
|
|
6928
|
+
{
|
|
6929
|
+
"description": "Date + time",
|
|
6930
|
+
"code": "<DatePicker value={date} onChange={setDate} showTime />"
|
|
6931
|
+
}
|
|
6932
|
+
]
|
|
6933
|
+
},
|
|
5419
6934
|
"Card": {
|
|
5420
6935
|
"description": "Content container with optional header, body, and footer sections.",
|
|
5421
6936
|
"import": "import { Card, CardHeader, CardBody, CardFooter } from \"@usevyre/react\"",
|
|
@@ -5506,6 +7021,150 @@ export const schema = {
|
|
|
5506
7021
|
}
|
|
5507
7022
|
]
|
|
5508
7023
|
},
|
|
7024
|
+
"RadioGroup": {
|
|
7025
|
+
"description": "Controlled single-choice group. RadioGroup owns the selected value; render it data-driven via the options array OR with composable <Radio> children for custom content. role=radiogroup with proper labelling. For multi-select use Checkbox; for a compact dropdown use Select.",
|
|
7026
|
+
"import": "import { RadioGroup, Radio } from \"@usevyre/react\"",
|
|
7027
|
+
"subcomponents": [
|
|
7028
|
+
"Radio"
|
|
7029
|
+
],
|
|
7030
|
+
"props": {
|
|
7031
|
+
"value": {
|
|
7032
|
+
"type": "string",
|
|
7033
|
+
"description": "Selected value (controlled). Vue: v-model."
|
|
7034
|
+
},
|
|
7035
|
+
"defaultValue": {
|
|
7036
|
+
"type": "string",
|
|
7037
|
+
"description": "Initial value (uncontrolled, React only)"
|
|
7038
|
+
},
|
|
7039
|
+
"onChange": {
|
|
7040
|
+
"type": "function",
|
|
7041
|
+
"description": "(value: string) => void. Vue: update:modelValue / v-model."
|
|
7042
|
+
},
|
|
7043
|
+
"name": {
|
|
7044
|
+
"type": "string",
|
|
7045
|
+
"description": "Radio input name; auto-generated if omitted"
|
|
7046
|
+
},
|
|
7047
|
+
"disabled": {
|
|
7048
|
+
"type": "boolean",
|
|
7049
|
+
"default": false,
|
|
7050
|
+
"description": "Disable the whole group"
|
|
7051
|
+
},
|
|
7052
|
+
"size": {
|
|
7053
|
+
"type": "enum",
|
|
7054
|
+
"values": [
|
|
7055
|
+
"sm",
|
|
7056
|
+
"md"
|
|
7057
|
+
],
|
|
7058
|
+
"default": "md",
|
|
7059
|
+
"description": "Control size"
|
|
7060
|
+
},
|
|
7061
|
+
"orientation": {
|
|
7062
|
+
"type": "enum",
|
|
7063
|
+
"values": [
|
|
7064
|
+
"vertical",
|
|
7065
|
+
"horizontal"
|
|
7066
|
+
],
|
|
7067
|
+
"default": "vertical",
|
|
7068
|
+
"description": "Layout direction"
|
|
7069
|
+
},
|
|
7070
|
+
"options": {
|
|
7071
|
+
"type": "{ value: string; label?: string; description?: string; disabled?: boolean }[]",
|
|
7072
|
+
"description": "Data-driven options. Omit to pass <Radio> children instead."
|
|
7073
|
+
}
|
|
7074
|
+
},
|
|
7075
|
+
"antiPatterns": [
|
|
7076
|
+
{
|
|
7077
|
+
"pattern": "<Radio> used outside a <RadioGroup>",
|
|
7078
|
+
"reason": "Radio reads selection + name from RadioGroup context; it throws without a provider",
|
|
7079
|
+
"fix": "Always wrap <Radio> in <RadioGroup>"
|
|
7080
|
+
},
|
|
7081
|
+
{
|
|
7082
|
+
"pattern": "RadioGroup without value/onChange (React) or v-model (Vue)",
|
|
7083
|
+
"reason": "RadioGroup is controlled; selection won't update",
|
|
7084
|
+
"fix": "Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React"
|
|
7085
|
+
},
|
|
7086
|
+
{
|
|
7087
|
+
"pattern": "Using Checkbox for mutually-exclusive choices",
|
|
7088
|
+
"reason": "Radio expresses single-choice semantics + keyboard model",
|
|
7089
|
+
"fix": "Use RadioGroup + Radio (or options) for one-of-many"
|
|
7090
|
+
}
|
|
7091
|
+
],
|
|
7092
|
+
"examples": [
|
|
7093
|
+
{
|
|
7094
|
+
"description": "Data-driven",
|
|
7095
|
+
"code": "<RadioGroup\n value={plan}\n onChange={setPlan}\n options={[\n { value: \"free\", label: \"Free\", description: \"For hobby projects\" },\n { value: \"pro\", label: \"Pro\", description: \"For teams\" },\n ]}\n/>"
|
|
7096
|
+
},
|
|
7097
|
+
{
|
|
7098
|
+
"description": "Composable children",
|
|
7099
|
+
"code": "<RadioGroup value={plan} onChange={setPlan} orientation=\"horizontal\">\n <Radio value=\"free\" label=\"Free\" />\n <Radio value=\"pro\" label=\"Pro\" />\n</RadioGroup>"
|
|
7100
|
+
}
|
|
7101
|
+
]
|
|
7102
|
+
},
|
|
7103
|
+
"RichTextEditor": {
|
|
7104
|
+
"description": "Controlled WYSIWYG editor. value is an HTML string; you own it in state and set it in onChange (React) / v-model (Vue). Native contentEditable + execCommand — zero dependencies. Toolbar: bold, italic, underline, strike, h1-h3, ordered/unordered lists, quote, code block, link, clear formatting.",
|
|
7105
|
+
"import": "import { RichTextEditor } from \"@usevyre/react\"",
|
|
7106
|
+
"props": {
|
|
7107
|
+
"value": {
|
|
7108
|
+
"type": "string",
|
|
7109
|
+
"description": "HTML content (controlled). React: value + onChange. Vue: v-model."
|
|
7110
|
+
},
|
|
7111
|
+
"onChange": {
|
|
7112
|
+
"type": "function",
|
|
7113
|
+
"description": "(html: string) => void. Vue: update:modelValue / v-model."
|
|
7114
|
+
},
|
|
7115
|
+
"placeholder": {
|
|
7116
|
+
"type": "string",
|
|
7117
|
+
"default": "Write something…",
|
|
7118
|
+
"description": "Shown when the editor is empty"
|
|
7119
|
+
},
|
|
7120
|
+
"disabled": {
|
|
7121
|
+
"type": "boolean",
|
|
7122
|
+
"default": false,
|
|
7123
|
+
"description": "Not editable, dimmed; toolbar disabled"
|
|
7124
|
+
},
|
|
7125
|
+
"readOnly": {
|
|
7126
|
+
"type": "boolean",
|
|
7127
|
+
"default": false,
|
|
7128
|
+
"description": "Not editable; toolbar hidden entirely"
|
|
7129
|
+
},
|
|
7130
|
+
"toolbar": {
|
|
7131
|
+
"type": "RichTextTool[]",
|
|
7132
|
+
"description": "Which buttons to show. Default = all. Tools: \"bold\"|\"italic\"|\"underline\"|\"strike\"|\"h1\"|\"h2\"|\"h3\"|\"ul\"|\"ol\"|\"quote\"|\"code\"|\"link\"|\"clear\""
|
|
7133
|
+
},
|
|
7134
|
+
"minHeight": {
|
|
7135
|
+
"type": "string",
|
|
7136
|
+
"default": "10rem",
|
|
7137
|
+
"description": "CSS min-height of the editable area"
|
|
7138
|
+
}
|
|
7139
|
+
},
|
|
7140
|
+
"antiPatterns": [
|
|
7141
|
+
{
|
|
7142
|
+
"pattern": "RichTextEditor without value/onChange (React) or v-model (Vue)",
|
|
7143
|
+
"reason": "It is fully controlled; edits won't persist",
|
|
7144
|
+
"fix": "Keep the HTML string in state and update it in onChange / v-model"
|
|
7145
|
+
},
|
|
7146
|
+
{
|
|
7147
|
+
"pattern": "Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising",
|
|
7148
|
+
"reason": "value is raw HTML the user typed",
|
|
7149
|
+
"fix": "Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output"
|
|
7150
|
+
},
|
|
7151
|
+
{
|
|
7152
|
+
"pattern": "toolbar=\"bold\" (string)",
|
|
7153
|
+
"reason": "toolbar is an array of tool ids",
|
|
7154
|
+
"fix": "Pass an array, e.g. toolbar={[\"bold\",\"italic\",\"link\"]}"
|
|
7155
|
+
}
|
|
7156
|
+
],
|
|
7157
|
+
"examples": [
|
|
7158
|
+
{
|
|
7159
|
+
"description": "Controlled editor",
|
|
7160
|
+
"code": "const [html, setHtml] = useState(\"<p>Hello <strong>world</strong></p>\");\n<RichTextEditor value={html} onChange={setHtml} placeholder=\"Write…\" />"
|
|
7161
|
+
},
|
|
7162
|
+
{
|
|
7163
|
+
"description": "Minimal toolbar",
|
|
7164
|
+
"code": "<RichTextEditor\n value={html}\n onChange={setHtml}\n toolbar={[\"bold\", \"italic\", \"link\"]}\n/>"
|
|
7165
|
+
}
|
|
7166
|
+
]
|
|
7167
|
+
},
|
|
5509
7168
|
"Command": {
|
|
5510
7169
|
"description": "Command palette / search dialog. Use for search-first navigation or quick actions.",
|
|
5511
7170
|
"import": "import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandDialog } from \"@usevyre/react\"",
|
|
@@ -5575,7 +7234,7 @@ export const schema = {
|
|
|
5575
7234
|
]
|
|
5576
7235
|
},
|
|
5577
7236
|
"Field": {
|
|
5578
|
-
"description": "Form field wrapper
|
|
7237
|
+
"description": "Form field wrapper. Two ways to use it (both supported): (1) props-based — pass label/hint/state/required for the common case; (2) composable — use the parts FieldLabel, FieldDescription, FieldError, FieldGroup, FieldSet for richer layouts (multiple controls, custom error placement). The props-based API is unchanged and still works.",
|
|
5579
7238
|
"import": "import { Field, Input, Textarea } from \"@usevyre/react\"",
|
|
5580
7239
|
"props": {
|
|
5581
7240
|
"label": {
|
|
@@ -5608,6 +7267,11 @@ export const schema = {
|
|
|
5608
7267
|
"pattern": "Applying state prop directly to Input",
|
|
5609
7268
|
"reason": "state belongs on Field, not on the Input itself",
|
|
5610
7269
|
"fix": "Wrap Input in <Field state=\"error\"> to apply validation styling"
|
|
7270
|
+
},
|
|
7271
|
+
{
|
|
7272
|
+
"pattern": "Mixing props label/hint AND FieldLabel/FieldError for the same field",
|
|
7273
|
+
"reason": "Duplicates the label / message",
|
|
7274
|
+
"fix": "Pick one: either props-based (label/hint/state) OR composable parts"
|
|
5611
7275
|
}
|
|
5612
7276
|
],
|
|
5613
7277
|
"examples": [
|
|
@@ -5618,13 +7282,28 @@ export const schema = {
|
|
|
5618
7282
|
{
|
|
5619
7283
|
"description": "Search field with left icon",
|
|
5620
7284
|
"code": "<Field label=\"Search\">\n <Input leftElement={<SearchIcon />} placeholder=\"Search...\" />\n</Field>"
|
|
7285
|
+
},
|
|
7286
|
+
{
|
|
7287
|
+
"description": "Composable field with explicit parts",
|
|
7288
|
+
"code": "<Field>\n <FieldLabel required htmlFor=\"email\">Email</FieldLabel>\n <Input id=\"email\" type=\"email\" />\n <FieldDescription>We\\u2019ll never share it.</FieldDescription>\n <FieldError>{errors.email}</FieldError>\n</Field>\n\n// Two controls side by side\n<FieldGroup orientation=\"horizontal\">\n <Field label=\"First name\"><Input /></Field>\n <Field label=\"Last name\"><Input /></Field>\n</FieldGroup>"
|
|
5621
7289
|
}
|
|
7290
|
+
],
|
|
7291
|
+
"subcomponents": [
|
|
7292
|
+
"FieldLabel",
|
|
7293
|
+
"FieldDescription",
|
|
7294
|
+
"FieldError",
|
|
7295
|
+
"FieldGroup",
|
|
7296
|
+
"FieldSet"
|
|
5622
7297
|
]
|
|
5623
7298
|
},
|
|
5624
7299
|
"Input": {
|
|
5625
7300
|
"description": "Text input field. Wrap in Field for labels and validation. Use leftElement/rightElement for icons.",
|
|
5626
7301
|
"import": "import { Input } from \"@usevyre/react\"",
|
|
5627
7302
|
"props": {
|
|
7303
|
+
"modelValue": {
|
|
7304
|
+
"type": "string | number",
|
|
7305
|
+
"description": "Vue only: enables v-model (two-way binding). React: use native value + onChange. Native input attrs (type, placeholder, disabled…) work in both."
|
|
7306
|
+
},
|
|
5628
7307
|
"size": {
|
|
5629
7308
|
"type": "enum",
|
|
5630
7309
|
"values": [
|
|
@@ -5654,6 +7333,11 @@ export const schema = {
|
|
|
5654
7333
|
"pattern": "type=\"search\" for search UI",
|
|
5655
7334
|
"reason": "Use Command component for search-first interfaces",
|
|
5656
7335
|
"fix": "Import Command from @usevyre/react for search palettes"
|
|
7336
|
+
},
|
|
7337
|
+
{
|
|
7338
|
+
"pattern": "Vue: binding Input/Textarea value without v-model",
|
|
7339
|
+
"reason": "Vue Input & Textarea support v-model (modelValue); manual :value alone won't update",
|
|
7340
|
+
"fix": "Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange"
|
|
5657
7341
|
}
|
|
5658
7342
|
],
|
|
5659
7343
|
"examples": [
|
|
@@ -5984,7 +7668,8 @@ export const schema = {
|
|
|
5984
7668
|
"SidebarContent",
|
|
5985
7669
|
"SidebarSection",
|
|
5986
7670
|
"SidebarItem",
|
|
5987
|
-
"SidebarFooter"
|
|
7671
|
+
"SidebarFooter",
|
|
7672
|
+
"SidebarTrigger"
|
|
5988
7673
|
],
|
|
5989
7674
|
"props": {
|
|
5990
7675
|
"variant": {
|
|
@@ -5994,16 +7679,35 @@ export const schema = {
|
|
|
5994
7679
|
"floating"
|
|
5995
7680
|
],
|
|
5996
7681
|
"default": "default"
|
|
7682
|
+
},
|
|
7683
|
+
"SidebarTrigger.icon": {
|
|
7684
|
+
"type": "ReactNode",
|
|
7685
|
+
"description": "Icon shown when the sidebar is expanded. Optional — defaults to the built-in menu icon. Vue: #icon slot."
|
|
7686
|
+
},
|
|
7687
|
+
"SidebarTrigger.collapsedIcon": {
|
|
7688
|
+
"type": "ReactNode",
|
|
7689
|
+
"description": "Icon shown when the sidebar is collapsed. Optional — falls back to icon, then the built-in icon. Vue: #collapsed-icon slot. Use a different glyph here to signal collapsed state (common UX pattern)."
|
|
5997
7690
|
}
|
|
5998
7691
|
},
|
|
5999
7692
|
"examples": [
|
|
6000
7693
|
{
|
|
6001
7694
|
"description": "App layout with sidebar",
|
|
6002
7695
|
"code": "<AppLayout>\n <Sidebar>\n <SidebarHeader>Logo</SidebarHeader>\n <SidebarContent>\n <SidebarSection heading=\"Main\">\n <SidebarItem href=\"/\" active>Dashboard</SidebarItem>\n <SidebarItem href=\"/settings\">Settings</SidebarItem>\n </SidebarSection>\n </SidebarContent>\n <SidebarFooter><Avatar fallback=\"JD\" size=\"sm\" /></SidebarFooter>\n </Sidebar>\n <main>Page content</main>\n</AppLayout>"
|
|
7696
|
+
},
|
|
7697
|
+
{
|
|
7698
|
+
"description": "SidebarTrigger with distinct open/collapsed icons",
|
|
7699
|
+
"code": "<SidebarTrigger icon={<PanelLeftClose />} collapsedIcon={<PanelLeftOpen />} />\n\n// Vue:\n// <SidebarTrigger>\n// <template #icon><PanelLeftClose /></template>\n// <template #collapsed-icon><PanelLeftOpen /></template>\n// </SidebarTrigger>"
|
|
6003
7700
|
}
|
|
6004
|
-
]
|
|
6005
|
-
|
|
6006
|
-
|
|
7701
|
+
],
|
|
7702
|
+
"antiPatterns": [
|
|
7703
|
+
{
|
|
7704
|
+
"pattern": "Vue: passing icon/collapsedIcon as props on SidebarTrigger",
|
|
7705
|
+
"reason": "Vue SidebarTrigger customises icons via slots, not props (consistent with SidebarItem)",
|
|
7706
|
+
"fix": "Use <template #icon> and <template #collapsed-icon>; React uses icon / collapsedIcon props"
|
|
7707
|
+
}
|
|
7708
|
+
]
|
|
7709
|
+
},
|
|
7710
|
+
"Skeleton": {
|
|
6007
7711
|
"description": "Loading placeholder that mimics the shape of content while data loads.",
|
|
6008
7712
|
"import": "import { Skeleton } from \"@usevyre/react\"",
|
|
6009
7713
|
"props": {
|
|
@@ -6678,12 +8382,304 @@ export const schema = {
|
|
|
6678
8382
|
"code": "<TagGroup gap=\"sm\">\n <Tag>React</Tag>\n <Tag>Vue</Tag>\n <Tag variant=\"accent\">TypeScript</Tag>\n</TagGroup>"
|
|
6679
8383
|
}
|
|
6680
8384
|
]
|
|
8385
|
+
},
|
|
8386
|
+
"Item": {
|
|
8387
|
+
"description": "Layout primitive for list rows, settings rows, and notification rows. Denser than Card — use Item (not Card) for repeated list rows.",
|
|
8388
|
+
"import": "import { Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions, ItemGroup } from \"@usevyre/react\"",
|
|
8389
|
+
"subcomponents": [
|
|
8390
|
+
"ItemGroup",
|
|
8391
|
+
"ItemMedia",
|
|
8392
|
+
"ItemContent",
|
|
8393
|
+
"ItemTitle",
|
|
8394
|
+
"ItemDescription",
|
|
8395
|
+
"ItemActions"
|
|
8396
|
+
],
|
|
8397
|
+
"props": {
|
|
8398
|
+
"variant": {
|
|
8399
|
+
"type": "enum",
|
|
8400
|
+
"values": [
|
|
8401
|
+
"default",
|
|
8402
|
+
"outlined",
|
|
8403
|
+
"muted",
|
|
8404
|
+
"plain"
|
|
8405
|
+
],
|
|
8406
|
+
"default": "default",
|
|
8407
|
+
"description": "Visual style of the row. plain = transparent + no border (use when composing Item inside another surface like Card or a Kanban card to avoid a double background)."
|
|
8408
|
+
},
|
|
8409
|
+
"size": {
|
|
8410
|
+
"type": "enum",
|
|
8411
|
+
"values": [
|
|
8412
|
+
"sm",
|
|
8413
|
+
"md",
|
|
8414
|
+
"lg"
|
|
8415
|
+
],
|
|
8416
|
+
"default": "md",
|
|
8417
|
+
"description": "Vertical density / text size of the row"
|
|
8418
|
+
},
|
|
8419
|
+
"clickable": {
|
|
8420
|
+
"type": "boolean",
|
|
8421
|
+
"default": false,
|
|
8422
|
+
"description": "Adds pointer cursor, hover background, focus ring, and role=button"
|
|
8423
|
+
}
|
|
8424
|
+
},
|
|
8425
|
+
"antiPatterns": [
|
|
8426
|
+
{
|
|
8427
|
+
"pattern": "Card used for repeated list rows",
|
|
8428
|
+
"reason": "Card is a content container; Item is the dense list-row primitive",
|
|
8429
|
+
"fix": "Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows"
|
|
8430
|
+
},
|
|
8431
|
+
{
|
|
8432
|
+
"pattern": "Item variant=\"primary\"",
|
|
8433
|
+
"reason": "Item has no 'primary' variant",
|
|
8434
|
+
"fix": "Use variant=\"default\" | \"outlined\" | \"muted\""
|
|
8435
|
+
},
|
|
8436
|
+
{
|
|
8437
|
+
"pattern": "raw text directly inside Item",
|
|
8438
|
+
"reason": "Item lays out media/content/actions columns; bare text breaks alignment",
|
|
8439
|
+
"fix": "Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>"
|
|
8440
|
+
}
|
|
8441
|
+
],
|
|
8442
|
+
"examples": [
|
|
8443
|
+
{
|
|
8444
|
+
"description": "Settings row with media, content and a trailing switch",
|
|
8445
|
+
"code": "<Item>\n <ItemMedia><BellIcon /></ItemMedia>\n <ItemContent>\n <ItemTitle>Notifications</ItemTitle>\n <ItemDescription>Receive an email when someone mentions you.</ItemDescription>\n </ItemContent>\n <ItemActions>\n <Switch defaultChecked />\n </ItemActions>\n</Item>"
|
|
8446
|
+
},
|
|
8447
|
+
{
|
|
8448
|
+
"description": "Grouped clickable list with dividers",
|
|
8449
|
+
"code": "<ItemGroup separated>\n <Item clickable>\n <ItemContent><ItemTitle>Profile</ItemTitle></ItemContent>\n </Item>\n <Item clickable>\n <ItemContent><ItemTitle>Billing</ItemTitle></ItemContent>\n </Item>\n</ItemGroup>"
|
|
8450
|
+
}
|
|
8451
|
+
]
|
|
8452
|
+
},
|
|
8453
|
+
"Kanban": {
|
|
8454
|
+
"description": "Drag-and-drop board: cards move between columns (or reorder within a column). CONTROLLED & data-driven like DataGrid. While dragging, a placeholder shows the exact drop position. Each card is wrapped in a Card (variant=\"outlined\"); renderCard (React) / #card slot (Vue) can render ANY content incl. complex components (Avatar/Badge/Progress). Columns and cards accept an optional semantic color tint. Native HTML5 DnD, zero deps.",
|
|
8455
|
+
"import": "import { Kanban } from \"@usevyre/react\"",
|
|
8456
|
+
"props": {
|
|
8457
|
+
"value": {
|
|
8458
|
+
"type": "KanbanColumn[]",
|
|
8459
|
+
"description": "Controlled board data. KanbanColumn = { id, title, cards: KanbanCard[], color? }; KanbanCard = { id, title, description?, color? }. Extra app fields may ride along on cards and be read in renderCard. color = \"default\"|\"accent\"|\"teal\"|\"success\"|\"warning\"|\"danger\" (token-based tint). Card ids must be unique across the whole board."
|
|
8460
|
+
},
|
|
8461
|
+
"onChange": {
|
|
8462
|
+
"type": "function",
|
|
8463
|
+
"description": "(next: KanbanColumn[]) => void — called with the next columns array after a drag move. Required; set it back into your state."
|
|
8464
|
+
},
|
|
8465
|
+
"renderCard": {
|
|
8466
|
+
"type": "function",
|
|
8467
|
+
"description": "(card, column) => ReactNode — custom card body, rendered inside the wrapping <Card>. Vue: #card scoped slot. Can return any components (Avatar, Badge, Progress, etc.); attach extra fields to the card object and read them here."
|
|
8468
|
+
},
|
|
8469
|
+
"onCardClick": {
|
|
8470
|
+
"type": "function",
|
|
8471
|
+
"description": "(card, column) => void — fired on click/Enter/Space (not after a drag). Vue: @card-click."
|
|
8472
|
+
}
|
|
8473
|
+
},
|
|
8474
|
+
"antiPatterns": [
|
|
8475
|
+
{
|
|
8476
|
+
"pattern": "Kanban without onChange (or ignoring it)",
|
|
8477
|
+
"reason": "Kanban holds no internal data state; dropped cards won't move unless you apply onChange",
|
|
8478
|
+
"fix": "Store columns in state and setColumns in onChange (v-model in Vue)"
|
|
8479
|
+
},
|
|
8480
|
+
{
|
|
8481
|
+
"pattern": "Duplicate card ids across columns",
|
|
8482
|
+
"reason": "Card moves are resolved by id; duplicates corrupt the move",
|
|
8483
|
+
"fix": "Use globally-unique card ids across the entire board"
|
|
8484
|
+
},
|
|
8485
|
+
{
|
|
8486
|
+
"pattern": "Mutating value in place then calling onChange",
|
|
8487
|
+
"reason": "React/Vue need a new array reference to re-render reliably",
|
|
8488
|
+
"fix": "Pass the new array Kanban gives you straight to setState / v-model"
|
|
8489
|
+
},
|
|
8490
|
+
{
|
|
8491
|
+
"pattern": "color=\"blue\" (or any non-semantic value)",
|
|
8492
|
+
"reason": "color is a fixed semantic set, not an arbitrary CSS color",
|
|
8493
|
+
"fix": "Use one of: \"default\" | \"accent\" | \"teal\" | \"success\" | \"warning\" | \"danger\""
|
|
8494
|
+
}
|
|
8495
|
+
],
|
|
8496
|
+
"examples": [
|
|
8497
|
+
{
|
|
8498
|
+
"description": "Controlled board",
|
|
8499
|
+
"code": "const [columns, setColumns] = useState([\n { id: \"todo\", title: \"To Do\", cards: [{ id: \"1\", title: \"Spec API\" }] },\n { id: \"doing\", title: \"In Progress\", cards: [] },\n { id: \"done\", title: \"Done\", cards: [{ id: \"2\", title: \"Kickoff\" }] },\n]);\n<Kanban value={columns} onChange={setColumns} />"
|
|
8500
|
+
},
|
|
8501
|
+
{
|
|
8502
|
+
"description": "Custom card body + click handler",
|
|
8503
|
+
"code": "<Kanban\n value={columns}\n onChange={setColumns}\n onCardClick={(card) => openDetail(card.id)}\n renderCard={(card) => (\n <><strong>{card.title}</strong><Badge>{card.id}</Badge></>\n )}\n/>"
|
|
8504
|
+
},
|
|
8505
|
+
{
|
|
8506
|
+
"description": "Tinted columns/cards + complex card content",
|
|
8507
|
+
"code": "const [cols, setCols] = useState([\n { id: \"doing\", title: \"In Progress\", color: \"teal\", cards: [\n { id: \"t1\", title: \"OAuth\", assignee: \"AK\", progress: 60, color: \"warning\" },\n ]},\n]);\n<Kanban\n value={cols}\n onChange={setCols}\n renderCard={(card) => (\n <><strong>{card.title}</strong><Progress value={card.progress} /></>\n )}\n/>"
|
|
8508
|
+
}
|
|
8509
|
+
]
|
|
8510
|
+
},
|
|
8511
|
+
"Conversation": {
|
|
8512
|
+
"description": "Chat / inbox message thread. CONTROLLED & data-driven like Kanban — you own `value` (messages array) and append in your own send handler; Conversation holds no message state. Consecutive messages from the same author are grouped (avatar + name shown once), day separators are inserted on date change, and outgoing messages (authorId === currentUserId) align right.",
|
|
8513
|
+
"import": "import { Conversation } from \"@usevyre/react\"",
|
|
8514
|
+
"props": {
|
|
8515
|
+
"value": {
|
|
8516
|
+
"type": "ConversationMessage[]",
|
|
8517
|
+
"description": "Ordered messages. ConversationMessage = { id, authorId, text?, authorName?, authorAvatar?, timestamp?: Date|string|number, status?: \"sending\"|\"sent\"|\"delivered\"|\"read\", attachments?: ConversationAttachment[] }. ConversationAttachment = { kind: \"image\"|\"audio\"|\"video\"|\"file\", url, name?, size? } — rendered inside the bubble. Required & controlled."
|
|
8518
|
+
},
|
|
8519
|
+
"currentUserId": {
|
|
8520
|
+
"type": "string",
|
|
8521
|
+
"description": "Whose messages are outgoing (aligned right). Required — match against message.authorId."
|
|
8522
|
+
},
|
|
8523
|
+
"composer": {
|
|
8524
|
+
"type": "boolean",
|
|
8525
|
+
"default": false,
|
|
8526
|
+
"description": "Show the built-in input + Send button. Pair with onSend."
|
|
8527
|
+
},
|
|
8528
|
+
"onSend": {
|
|
8529
|
+
"type": "function",
|
|
8530
|
+
"description": "(text: string, files: File[]) => void — called when the built-in composer submits. files holds anything staged via the attach button (empty array unless allowAttachments). You own the upload + turning staged files into message attachments. Vue: @send."
|
|
8531
|
+
},
|
|
8532
|
+
"placeholder": {
|
|
8533
|
+
"type": "string",
|
|
8534
|
+
"default": "Write a message…",
|
|
8535
|
+
"description": "Composer input placeholder."
|
|
8536
|
+
},
|
|
8537
|
+
"typing": {
|
|
8538
|
+
"type": "boolean | string",
|
|
8539
|
+
"default": false,
|
|
8540
|
+
"description": "Show an incoming typing indicator. Pass a string to label it."
|
|
8541
|
+
},
|
|
8542
|
+
"allowAttachments": {
|
|
8543
|
+
"type": "boolean",
|
|
8544
|
+
"default": false,
|
|
8545
|
+
"description": "Show a 📎 attach button + staged-file chips in the built-in composer. Files come back via onSend's second arg."
|
|
8546
|
+
},
|
|
8547
|
+
"accept": {
|
|
8548
|
+
"type": "string",
|
|
8549
|
+
"description": "Forwarded to the file input's accept attribute, e.g. \"image/*\"."
|
|
8550
|
+
},
|
|
8551
|
+
"renderMessage": {
|
|
8552
|
+
"type": "function",
|
|
8553
|
+
"description": "(message, meta) => ReactNode — custom bubble body. meta = { outgoing, isGroupStart, isGroupEnd }. Vue: #message scoped slot."
|
|
8554
|
+
},
|
|
8555
|
+
"renderComposer": {
|
|
8556
|
+
"type": "function",
|
|
8557
|
+
"description": "(api) => ReactNode — replace the composer entirely. api = { value, setValue, files, setFiles, send }. Vue: #composer scoped slot."
|
|
8558
|
+
}
|
|
8559
|
+
},
|
|
8560
|
+
"antiPatterns": [
|
|
8561
|
+
{
|
|
8562
|
+
"pattern": "Conversation without currentUserId",
|
|
8563
|
+
"reason": "Alignment (incoming vs outgoing) is decided by authorId === currentUserId",
|
|
8564
|
+
"fix": "Always pass currentUserId matching one of the message authorId values"
|
|
8565
|
+
},
|
|
8566
|
+
{
|
|
8567
|
+
"pattern": "Expecting Conversation to store/append messages",
|
|
8568
|
+
"reason": "Conversation is controlled & stateless for data, like Kanban/DataGrid",
|
|
8569
|
+
"fix": "Append to your own state in onSend (or @send) and pass it back via value"
|
|
8570
|
+
},
|
|
8571
|
+
{
|
|
8572
|
+
"pattern": "composer without onSend (React) / @send (Vue)",
|
|
8573
|
+
"reason": "The built-in composer emits text; nothing happens unless you handle it",
|
|
8574
|
+
"fix": "Provide onSend / @send to append the message to value"
|
|
8575
|
+
},
|
|
8576
|
+
{
|
|
8577
|
+
"pattern": "Treating onSend as (text) only when using allowAttachments",
|
|
8578
|
+
"reason": "With allowAttachments, staged files arrive as the 2nd arg; ignoring it drops attachments",
|
|
8579
|
+
"fix": "Handle onSend(text, files) — map files to message attachments and append"
|
|
8580
|
+
}
|
|
8581
|
+
],
|
|
8582
|
+
"examples": [
|
|
8583
|
+
{
|
|
8584
|
+
"description": "Controlled thread with built-in composer",
|
|
8585
|
+
"code": "const [messages, setMessages] = useState([\n { id: \"1\", authorId: \"sam\", authorName: \"Sam\", text: \"Hey!\" },\n { id: \"2\", authorId: \"me\", text: \"Hi \\ud83d\\udc4b\", status: \"read\" },\n]);\n<Conversation\n value={messages}\n currentUserId=\"me\"\n composer\n onSend={(t) => setMessages((m) => [...m, { id: crypto.randomUUID(), authorId: \"me\", text: t }])}\n/>"
|
|
8586
|
+
},
|
|
8587
|
+
{
|
|
8588
|
+
"description": "Typing indicator + custom bubble",
|
|
8589
|
+
"code": "<Conversation\n value={messages}\n currentUserId=\"me\"\n typing=\"Sam is typing\"\n renderMessage={(m) => <strong>{m.text}</strong>}\n/>"
|
|
8590
|
+
},
|
|
8591
|
+
{
|
|
8592
|
+
"description": "Message with image + file attachments",
|
|
8593
|
+
"code": "const messages = [\n { id: \"1\", authorId: \"sam\", authorName: \"Sam\", text: \"Moodboard \\ud83d\\udc47\",\n attachments: [{ kind: \"image\", url: \"/board.png\", name: \"board.png\" }] },\n { id: \"2\", authorId: \"me\", text: \"Specs:\", status: \"read\",\n attachments: [{ kind: \"file\", url: \"/spec.pdf\", name: \"spec.pdf\", size: \"2.4 MB\" }] },\n];\n<Conversation value={messages} currentUserId=\"me\" />"
|
|
8594
|
+
}
|
|
8595
|
+
]
|
|
8596
|
+
},
|
|
8597
|
+
"DateRangePicker": {
|
|
8598
|
+
"description": "Start/end date range picker. Built on Calendar (mode=range) with a friendlier { from, to } object API, a two-month side-by-side view, and preset shortcuts. Use this for report/filter date ranges; use DatePicker for a single date.",
|
|
8599
|
+
"import": "import { DateRangePicker } from \"@usevyre/react\"",
|
|
8600
|
+
"props": {
|
|
8601
|
+
"value": {
|
|
8602
|
+
"type": "{ from: Date | null; to: Date | null } | null",
|
|
8603
|
+
"description": "Selected range (controlled). Pass an OBJECT, not a tuple."
|
|
8604
|
+
},
|
|
8605
|
+
"onChange": {
|
|
8606
|
+
"type": "function",
|
|
8607
|
+
"description": "Callback (range: { from, to }) => void when the range changes"
|
|
8608
|
+
},
|
|
8609
|
+
"placeholder": {
|
|
8610
|
+
"type": "string",
|
|
8611
|
+
"default": "Pick a date range",
|
|
8612
|
+
"description": "Trigger text when no range is selected"
|
|
8613
|
+
},
|
|
8614
|
+
"numberOfMonths": {
|
|
8615
|
+
"type": "enum",
|
|
8616
|
+
"values": [
|
|
8617
|
+
1,
|
|
8618
|
+
2
|
|
8619
|
+
],
|
|
8620
|
+
"default": 2,
|
|
8621
|
+
"description": "How many month calendars to show side-by-side"
|
|
8622
|
+
},
|
|
8623
|
+
"presets": {
|
|
8624
|
+
"type": "boolean | DateRangePreset[]",
|
|
8625
|
+
"default": false,
|
|
8626
|
+
"description": "true shows built-in presets (Today, Yesterday, Last 7/30 days, This/Last month); or pass a custom array of { label, range() }"
|
|
8627
|
+
},
|
|
8628
|
+
"minDate": {
|
|
8629
|
+
"type": "Date",
|
|
8630
|
+
"description": "Earliest selectable date"
|
|
8631
|
+
},
|
|
8632
|
+
"maxDate": {
|
|
8633
|
+
"type": "Date",
|
|
8634
|
+
"description": "Latest selectable date"
|
|
8635
|
+
},
|
|
8636
|
+
"disabled": {
|
|
8637
|
+
"type": "function",
|
|
8638
|
+
"description": "(date: Date) => boolean — disable specific dates"
|
|
8639
|
+
},
|
|
8640
|
+
"weekStartsOn": {
|
|
8641
|
+
"type": "enum",
|
|
8642
|
+
"values": [
|
|
8643
|
+
0,
|
|
8644
|
+
1
|
|
8645
|
+
],
|
|
8646
|
+
"default": 1,
|
|
8647
|
+
"description": "0 = Sunday, 1 = Monday"
|
|
8648
|
+
}
|
|
8649
|
+
},
|
|
8650
|
+
"antiPatterns": [
|
|
8651
|
+
{
|
|
8652
|
+
"pattern": "value={[from, to]}",
|
|
8653
|
+
"reason": "DateRangePicker uses a { from, to } object, not a [Date, Date] tuple like Calendar mode=range",
|
|
8654
|
+
"fix": "Use value={{ from, to }} and read range.from / range.to"
|
|
8655
|
+
},
|
|
8656
|
+
{
|
|
8657
|
+
"pattern": "DateRangePicker for a single date",
|
|
8658
|
+
"reason": "DateRangePicker always selects a start AND end",
|
|
8659
|
+
"fix": "Use <DatePicker /> for a single date"
|
|
8660
|
+
},
|
|
8661
|
+
{
|
|
8662
|
+
"pattern": "presets=\"true\" (string)",
|
|
8663
|
+
"reason": "presets is a boolean or an array, not a string",
|
|
8664
|
+
"fix": "Use the bare prop: presets (or presets={true})"
|
|
8665
|
+
}
|
|
8666
|
+
],
|
|
8667
|
+
"examples": [
|
|
8668
|
+
{
|
|
8669
|
+
"description": "Range picker with built-in presets",
|
|
8670
|
+
"code": "const [range, setRange] = useState({ from: null, to: null });\n<DateRangePicker value={range} onChange={setRange} presets />"
|
|
8671
|
+
},
|
|
8672
|
+
{
|
|
8673
|
+
"description": "Single month, no presets",
|
|
8674
|
+
"code": "<DateRangePicker value={range} onChange={setRange} numberOfMonths={1} />"
|
|
8675
|
+
}
|
|
8676
|
+
]
|
|
6681
8677
|
}
|
|
6682
8678
|
}
|
|
6683
8679
|
};
|
|
6684
8680
|
|
|
6685
8681
|
export const antiPatterns = {
|
|
6686
|
-
"version": "1.
|
|
8682
|
+
"version": "1.2.0",
|
|
6687
8683
|
"rules": [
|
|
6688
8684
|
{
|
|
6689
8685
|
"component": "Accordion",
|
|
@@ -6785,9 +8781,30 @@ export const antiPatterns = {
|
|
|
6785
8781
|
},
|
|
6786
8782
|
{
|
|
6787
8783
|
"component": "Calendar",
|
|
6788
|
-
"pattern": "
|
|
6789
|
-
"reason": "Calendar
|
|
6790
|
-
"fix": "
|
|
8784
|
+
"pattern": "Calendar for an input field that opens a popover",
|
|
8785
|
+
"reason": "Calendar renders an always-visible grid, not a trigger",
|
|
8786
|
+
"fix": "Use <DatePicker /> (single date) or <DateRangePicker /> (range)",
|
|
8787
|
+
"severity": "error"
|
|
8788
|
+
},
|
|
8789
|
+
{
|
|
8790
|
+
"component": "Calendar",
|
|
8791
|
+
"pattern": "value as tuple for mode=\"single\"",
|
|
8792
|
+
"reason": "value type must match mode: Date for single, [Date,Date] for range, Date[] for multiple",
|
|
8793
|
+
"fix": "Pass value matching mode; use mode=\"range\" for [start,end]",
|
|
8794
|
+
"severity": "error"
|
|
8795
|
+
},
|
|
8796
|
+
{
|
|
8797
|
+
"component": "DatePicker",
|
|
8798
|
+
"pattern": "DatePicker mode=\"range\" for { from, to } object",
|
|
8799
|
+
"reason": "DatePicker range mode emits a [Date, Date] tuple, not a { from, to } object",
|
|
8800
|
+
"fix": "Use <DateRangePicker /> for the { from, to } object API + presets + dual month",
|
|
8801
|
+
"severity": "error"
|
|
8802
|
+
},
|
|
8803
|
+
{
|
|
8804
|
+
"component": "DatePicker",
|
|
8805
|
+
"pattern": "DatePicker without value/onChange",
|
|
8806
|
+
"reason": "DatePicker is controlled — it has no internal selected state",
|
|
8807
|
+
"fix": "Provide value and onChange (e.g. from useState)",
|
|
6791
8808
|
"severity": "error"
|
|
6792
8809
|
},
|
|
6793
8810
|
{
|
|
@@ -6804,6 +8821,48 @@ export const antiPatterns = {
|
|
|
6804
8821
|
"fix": "Use size=\"md\"",
|
|
6805
8822
|
"severity": "error"
|
|
6806
8823
|
},
|
|
8824
|
+
{
|
|
8825
|
+
"component": "RadioGroup",
|
|
8826
|
+
"pattern": "<Radio> used outside a <RadioGroup>",
|
|
8827
|
+
"reason": "Radio reads selection + name from RadioGroup context; it throws without a provider",
|
|
8828
|
+
"fix": "Always wrap <Radio> in <RadioGroup>",
|
|
8829
|
+
"severity": "error"
|
|
8830
|
+
},
|
|
8831
|
+
{
|
|
8832
|
+
"component": "RadioGroup",
|
|
8833
|
+
"pattern": "RadioGroup without value/onChange (React) or v-model (Vue)",
|
|
8834
|
+
"reason": "RadioGroup is controlled; selection won't update",
|
|
8835
|
+
"fix": "Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React",
|
|
8836
|
+
"severity": "error"
|
|
8837
|
+
},
|
|
8838
|
+
{
|
|
8839
|
+
"component": "RadioGroup",
|
|
8840
|
+
"pattern": "Using Checkbox for mutually-exclusive choices",
|
|
8841
|
+
"reason": "Radio expresses single-choice semantics + keyboard model",
|
|
8842
|
+
"fix": "Use RadioGroup + Radio (or options) for one-of-many",
|
|
8843
|
+
"severity": "error"
|
|
8844
|
+
},
|
|
8845
|
+
{
|
|
8846
|
+
"component": "RichTextEditor",
|
|
8847
|
+
"pattern": "RichTextEditor without value/onChange (React) or v-model (Vue)",
|
|
8848
|
+
"reason": "It is fully controlled; edits won't persist",
|
|
8849
|
+
"fix": "Keep the HTML string in state and update it in onChange / v-model",
|
|
8850
|
+
"severity": "error"
|
|
8851
|
+
},
|
|
8852
|
+
{
|
|
8853
|
+
"component": "RichTextEditor",
|
|
8854
|
+
"pattern": "Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising",
|
|
8855
|
+
"reason": "value is raw HTML the user typed",
|
|
8856
|
+
"fix": "Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output",
|
|
8857
|
+
"severity": "error"
|
|
8858
|
+
},
|
|
8859
|
+
{
|
|
8860
|
+
"component": "RichTextEditor",
|
|
8861
|
+
"pattern": "toolbar=\"bold\" (string)",
|
|
8862
|
+
"reason": "toolbar is an array of tool ids",
|
|
8863
|
+
"fix": "Pass an array, e.g. toolbar={[\"bold\",\"italic\",\"link\"]}",
|
|
8864
|
+
"severity": "error"
|
|
8865
|
+
},
|
|
6807
8866
|
{
|
|
6808
8867
|
"component": "Command",
|
|
6809
8868
|
"pattern": "Using Input type=\"search\" for search UI",
|
|
@@ -6825,6 +8884,13 @@ export const antiPatterns = {
|
|
|
6825
8884
|
"fix": "Wrap Input in <Field state=\"error\"> to apply validation styling",
|
|
6826
8885
|
"severity": "error"
|
|
6827
8886
|
},
|
|
8887
|
+
{
|
|
8888
|
+
"component": "Field",
|
|
8889
|
+
"pattern": "Mixing props label/hint AND FieldLabel/FieldError for the same field",
|
|
8890
|
+
"reason": "Duplicates the label / message",
|
|
8891
|
+
"fix": "Pick one: either props-based (label/hint/state) OR composable parts",
|
|
8892
|
+
"severity": "error"
|
|
8893
|
+
},
|
|
6828
8894
|
{
|
|
6829
8895
|
"component": "Input",
|
|
6830
8896
|
"pattern": "size=\"icon\"",
|
|
@@ -6839,6 +8905,13 @@ export const antiPatterns = {
|
|
|
6839
8905
|
"fix": "Import Command from @usevyre/react for search palettes",
|
|
6840
8906
|
"severity": "error"
|
|
6841
8907
|
},
|
|
8908
|
+
{
|
|
8909
|
+
"component": "Input",
|
|
8910
|
+
"pattern": "Vue: binding Input/Textarea value without v-model",
|
|
8911
|
+
"reason": "Vue Input & Textarea support v-model (modelValue); manual :value alone won't update",
|
|
8912
|
+
"fix": "Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange",
|
|
8913
|
+
"severity": "error"
|
|
8914
|
+
},
|
|
6842
8915
|
{
|
|
6843
8916
|
"component": "Modal",
|
|
6844
8917
|
"pattern": "size=\"xl\"",
|
|
@@ -6867,6 +8940,13 @@ export const antiPatterns = {
|
|
|
6867
8940
|
"fix": "Pass options={[{ value: 'a', label: 'Option A' }]}",
|
|
6868
8941
|
"severity": "error"
|
|
6869
8942
|
},
|
|
8943
|
+
{
|
|
8944
|
+
"component": "Sidebar",
|
|
8945
|
+
"pattern": "Vue: passing icon/collapsedIcon as props on SidebarTrigger",
|
|
8946
|
+
"reason": "Vue SidebarTrigger customises icons via slots, not props (consistent with SidebarItem)",
|
|
8947
|
+
"fix": "Use <template #icon> and <template #collapsed-icon>; React uses icon / collapsedIcon props",
|
|
8948
|
+
"severity": "error"
|
|
8949
|
+
},
|
|
6870
8950
|
{
|
|
6871
8951
|
"component": "Toast",
|
|
6872
8952
|
"pattern": "Rendering <Toast> directly in JSX",
|
|
@@ -7006,19 +9086,122 @@ export const antiPatterns = {
|
|
|
7006
9086
|
"reason": "TagGroup is display-only",
|
|
7007
9087
|
"fix": "Use TagsInput for an editable tag field",
|
|
7008
9088
|
"severity": "error"
|
|
9089
|
+
},
|
|
9090
|
+
{
|
|
9091
|
+
"component": "Item",
|
|
9092
|
+
"pattern": "Card used for repeated list rows",
|
|
9093
|
+
"reason": "Card is a content container; Item is the dense list-row primitive",
|
|
9094
|
+
"fix": "Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows",
|
|
9095
|
+
"severity": "error"
|
|
9096
|
+
},
|
|
9097
|
+
{
|
|
9098
|
+
"component": "Item",
|
|
9099
|
+
"pattern": "Item variant=\"primary\"",
|
|
9100
|
+
"reason": "Item has no 'primary' variant",
|
|
9101
|
+
"fix": "Use variant=\"default\" | \"outlined\" | \"muted\"",
|
|
9102
|
+
"severity": "error"
|
|
9103
|
+
},
|
|
9104
|
+
{
|
|
9105
|
+
"component": "Item",
|
|
9106
|
+
"pattern": "raw text directly inside Item",
|
|
9107
|
+
"reason": "Item lays out media/content/actions columns; bare text breaks alignment",
|
|
9108
|
+
"fix": "Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>",
|
|
9109
|
+
"severity": "error"
|
|
9110
|
+
},
|
|
9111
|
+
{
|
|
9112
|
+
"component": "Kanban",
|
|
9113
|
+
"pattern": "Kanban without onChange (or ignoring it)",
|
|
9114
|
+
"reason": "Kanban holds no internal data state; dropped cards won't move unless you apply onChange",
|
|
9115
|
+
"fix": "Store columns in state and setColumns in onChange (v-model in Vue)",
|
|
9116
|
+
"severity": "error"
|
|
9117
|
+
},
|
|
9118
|
+
{
|
|
9119
|
+
"component": "Kanban",
|
|
9120
|
+
"pattern": "Duplicate card ids across columns",
|
|
9121
|
+
"reason": "Card moves are resolved by id; duplicates corrupt the move",
|
|
9122
|
+
"fix": "Use globally-unique card ids across the entire board",
|
|
9123
|
+
"severity": "error"
|
|
9124
|
+
},
|
|
9125
|
+
{
|
|
9126
|
+
"component": "Kanban",
|
|
9127
|
+
"pattern": "Mutating value in place then calling onChange",
|
|
9128
|
+
"reason": "React/Vue need a new array reference to re-render reliably",
|
|
9129
|
+
"fix": "Pass the new array Kanban gives you straight to setState / v-model",
|
|
9130
|
+
"severity": "error"
|
|
9131
|
+
},
|
|
9132
|
+
{
|
|
9133
|
+
"component": "Kanban",
|
|
9134
|
+
"pattern": "color=\"blue\" (or any non-semantic value)",
|
|
9135
|
+
"reason": "color is a fixed semantic set, not an arbitrary CSS color",
|
|
9136
|
+
"fix": "Use one of: \"default\" | \"accent\" | \"teal\" | \"success\" | \"warning\" | \"danger\"",
|
|
9137
|
+
"severity": "error"
|
|
9138
|
+
},
|
|
9139
|
+
{
|
|
9140
|
+
"component": "Conversation",
|
|
9141
|
+
"pattern": "Conversation without currentUserId",
|
|
9142
|
+
"reason": "Alignment (incoming vs outgoing) is decided by authorId === currentUserId",
|
|
9143
|
+
"fix": "Always pass currentUserId matching one of the message authorId values",
|
|
9144
|
+
"severity": "error"
|
|
9145
|
+
},
|
|
9146
|
+
{
|
|
9147
|
+
"component": "Conversation",
|
|
9148
|
+
"pattern": "Expecting Conversation to store/append messages",
|
|
9149
|
+
"reason": "Conversation is controlled & stateless for data, like Kanban/DataGrid",
|
|
9150
|
+
"fix": "Append to your own state in onSend (or @send) and pass it back via value",
|
|
9151
|
+
"severity": "error"
|
|
9152
|
+
},
|
|
9153
|
+
{
|
|
9154
|
+
"component": "Conversation",
|
|
9155
|
+
"pattern": "composer without onSend (React) / @send (Vue)",
|
|
9156
|
+
"reason": "The built-in composer emits text; nothing happens unless you handle it",
|
|
9157
|
+
"fix": "Provide onSend / @send to append the message to value",
|
|
9158
|
+
"severity": "error"
|
|
9159
|
+
},
|
|
9160
|
+
{
|
|
9161
|
+
"component": "Conversation",
|
|
9162
|
+
"pattern": "Treating onSend as (text) only when using allowAttachments",
|
|
9163
|
+
"reason": "With allowAttachments, staged files arrive as the 2nd arg; ignoring it drops attachments",
|
|
9164
|
+
"fix": "Handle onSend(text, files) — map files to message attachments and append",
|
|
9165
|
+
"severity": "error"
|
|
9166
|
+
},
|
|
9167
|
+
{
|
|
9168
|
+
"component": "DateRangePicker",
|
|
9169
|
+
"pattern": "value={[from, to]}",
|
|
9170
|
+
"reason": "DateRangePicker uses a { from, to } object, not a [Date, Date] tuple like Calendar mode=range",
|
|
9171
|
+
"fix": "Use value={{ from, to }} and read range.from / range.to",
|
|
9172
|
+
"severity": "error"
|
|
9173
|
+
},
|
|
9174
|
+
{
|
|
9175
|
+
"component": "DateRangePicker",
|
|
9176
|
+
"pattern": "DateRangePicker for a single date",
|
|
9177
|
+
"reason": "DateRangePicker always selects a start AND end",
|
|
9178
|
+
"fix": "Use <DatePicker /> for a single date",
|
|
9179
|
+
"severity": "error"
|
|
9180
|
+
},
|
|
9181
|
+
{
|
|
9182
|
+
"component": "DateRangePicker",
|
|
9183
|
+
"pattern": "presets=\"true\" (string)",
|
|
9184
|
+
"reason": "presets is a boolean or an array, not a string",
|
|
9185
|
+
"fix": "Use the bare prop: presets (or presets={true})",
|
|
9186
|
+
"severity": "error"
|
|
7009
9187
|
}
|
|
7010
9188
|
]
|
|
7011
9189
|
};
|
|
7012
9190
|
|
|
7013
9191
|
export const versionInfo = {
|
|
7014
|
-
"version": "1.
|
|
9192
|
+
"version": "1.2.0",
|
|
7015
9193
|
"packageVersion": "1.1.0",
|
|
7016
|
-
"generatedAt": "2026-05-
|
|
9194
|
+
"generatedAt": "2026-05-17T13:24:18.828Z",
|
|
7017
9195
|
"validFor": [
|
|
7018
9196
|
"@usevyre/react@1.1.0+",
|
|
7019
9197
|
"@usevyre/vue@1.1.0+"
|
|
7020
9198
|
],
|
|
7021
9199
|
"changelog": {
|
|
9200
|
+
"1.2.0": {
|
|
9201
|
+
"date": "2026-05-16",
|
|
9202
|
+
"breaking": false,
|
|
9203
|
+
"summary": "Added Item layout primitive, DateRangePicker (dual-month range picker with presets, built on Calendar) and Kanban (controlled drag-and-drop board); Calendar gained additive defaultMonth prop; DatePicker split into its own documented component/entry"
|
|
9204
|
+
},
|
|
7022
9205
|
"1.1.0": {
|
|
7023
9206
|
"date": "2026-05-15",
|
|
7024
9207
|
"breaking": false,
|
|
@@ -7043,252 +9226,301 @@ export const versionInfo = {
|
|
|
7043
9226
|
"components": {
|
|
7044
9227
|
"Accordion": {
|
|
7045
9228
|
"version": "1.1.0",
|
|
7046
|
-
"lastUpdated": "2026-05-
|
|
9229
|
+
"lastUpdated": "2026-05-16",
|
|
7047
9230
|
"breaking": false,
|
|
7048
9231
|
"stable": true,
|
|
7049
9232
|
"changelog": null
|
|
7050
9233
|
},
|
|
7051
9234
|
"Alert": {
|
|
7052
9235
|
"version": "1.1.0",
|
|
7053
|
-
"lastUpdated": "2026-05-
|
|
9236
|
+
"lastUpdated": "2026-05-16",
|
|
7054
9237
|
"breaking": false,
|
|
7055
9238
|
"stable": true,
|
|
7056
9239
|
"changelog": null
|
|
7057
9240
|
},
|
|
7058
9241
|
"Avatar": {
|
|
7059
9242
|
"version": "1.1.0",
|
|
7060
|
-
"lastUpdated": "2026-05-
|
|
9243
|
+
"lastUpdated": "2026-05-16",
|
|
7061
9244
|
"breaking": false,
|
|
7062
9245
|
"stable": true,
|
|
7063
9246
|
"changelog": null
|
|
7064
9247
|
},
|
|
7065
9248
|
"Badge": {
|
|
7066
9249
|
"version": "1.1.0",
|
|
7067
|
-
"lastUpdated": "2026-05-
|
|
9250
|
+
"lastUpdated": "2026-05-16",
|
|
7068
9251
|
"breaking": false,
|
|
7069
9252
|
"stable": true,
|
|
7070
9253
|
"changelog": null
|
|
7071
9254
|
},
|
|
7072
9255
|
"Breadcrumb": {
|
|
7073
9256
|
"version": "1.1.0",
|
|
7074
|
-
"lastUpdated": "2026-05-
|
|
9257
|
+
"lastUpdated": "2026-05-16",
|
|
7075
9258
|
"breaking": false,
|
|
7076
9259
|
"stable": true,
|
|
7077
9260
|
"changelog": null
|
|
7078
9261
|
},
|
|
7079
9262
|
"Button": {
|
|
7080
9263
|
"version": "1.1.0",
|
|
7081
|
-
"lastUpdated": "2026-05-
|
|
9264
|
+
"lastUpdated": "2026-05-16",
|
|
7082
9265
|
"breaking": false,
|
|
7083
9266
|
"stable": true,
|
|
7084
9267
|
"changelog": null
|
|
7085
9268
|
},
|
|
7086
9269
|
"Calendar": {
|
|
7087
9270
|
"version": "1.1.0",
|
|
7088
|
-
"lastUpdated": "2026-05-
|
|
9271
|
+
"lastUpdated": "2026-05-16",
|
|
9272
|
+
"breaking": false,
|
|
9273
|
+
"stable": true,
|
|
9274
|
+
"changelog": null
|
|
9275
|
+
},
|
|
9276
|
+
"DatePicker": {
|
|
9277
|
+
"version": "1.1.0",
|
|
9278
|
+
"lastUpdated": "2026-05-16",
|
|
7089
9279
|
"breaking": false,
|
|
7090
9280
|
"stable": true,
|
|
7091
9281
|
"changelog": null
|
|
7092
9282
|
},
|
|
7093
9283
|
"Card": {
|
|
7094
9284
|
"version": "1.1.0",
|
|
7095
|
-
"lastUpdated": "2026-05-
|
|
9285
|
+
"lastUpdated": "2026-05-16",
|
|
7096
9286
|
"breaking": false,
|
|
7097
9287
|
"stable": true,
|
|
7098
9288
|
"changelog": null
|
|
7099
9289
|
},
|
|
7100
9290
|
"Checkbox": {
|
|
7101
9291
|
"version": "1.1.0",
|
|
7102
|
-
"lastUpdated": "2026-05-
|
|
9292
|
+
"lastUpdated": "2026-05-16",
|
|
9293
|
+
"breaking": false,
|
|
9294
|
+
"stable": true,
|
|
9295
|
+
"changelog": null
|
|
9296
|
+
},
|
|
9297
|
+
"RadioGroup": {
|
|
9298
|
+
"version": "1.1.0",
|
|
9299
|
+
"lastUpdated": "2026-05-16",
|
|
9300
|
+
"breaking": false,
|
|
9301
|
+
"stable": true,
|
|
9302
|
+
"changelog": null
|
|
9303
|
+
},
|
|
9304
|
+
"RichTextEditor": {
|
|
9305
|
+
"version": "1.1.0",
|
|
9306
|
+
"lastUpdated": "2026-05-16",
|
|
7103
9307
|
"breaking": false,
|
|
7104
9308
|
"stable": true,
|
|
7105
9309
|
"changelog": null
|
|
7106
9310
|
},
|
|
7107
9311
|
"Command": {
|
|
7108
9312
|
"version": "1.1.0",
|
|
7109
|
-
"lastUpdated": "2026-05-
|
|
9313
|
+
"lastUpdated": "2026-05-16",
|
|
7110
9314
|
"breaking": false,
|
|
7111
9315
|
"stable": true,
|
|
7112
9316
|
"changelog": null
|
|
7113
9317
|
},
|
|
7114
9318
|
"DropdownMenu": {
|
|
7115
9319
|
"version": "1.1.0",
|
|
7116
|
-
"lastUpdated": "2026-05-
|
|
9320
|
+
"lastUpdated": "2026-05-16",
|
|
7117
9321
|
"breaking": false,
|
|
7118
9322
|
"stable": true,
|
|
7119
9323
|
"changelog": null
|
|
7120
9324
|
},
|
|
7121
9325
|
"Field": {
|
|
7122
9326
|
"version": "1.1.0",
|
|
7123
|
-
"lastUpdated": "2026-05-
|
|
9327
|
+
"lastUpdated": "2026-05-16",
|
|
7124
9328
|
"breaking": false,
|
|
7125
9329
|
"stable": true,
|
|
7126
9330
|
"changelog": null
|
|
7127
9331
|
},
|
|
7128
9332
|
"Input": {
|
|
7129
9333
|
"version": "1.1.0",
|
|
7130
|
-
"lastUpdated": "2026-05-
|
|
9334
|
+
"lastUpdated": "2026-05-16",
|
|
7131
9335
|
"breaking": false,
|
|
7132
9336
|
"stable": true,
|
|
7133
9337
|
"changelog": null
|
|
7134
9338
|
},
|
|
7135
9339
|
"Label": {
|
|
7136
9340
|
"version": "1.1.0",
|
|
7137
|
-
"lastUpdated": "2026-05-
|
|
9341
|
+
"lastUpdated": "2026-05-16",
|
|
7138
9342
|
"breaking": false,
|
|
7139
9343
|
"stable": true,
|
|
7140
9344
|
"changelog": null
|
|
7141
9345
|
},
|
|
7142
9346
|
"Modal": {
|
|
7143
9347
|
"version": "1.1.0",
|
|
7144
|
-
"lastUpdated": "2026-05-
|
|
9348
|
+
"lastUpdated": "2026-05-16",
|
|
7145
9349
|
"breaking": false,
|
|
7146
9350
|
"stable": true,
|
|
7147
9351
|
"changelog": null
|
|
7148
9352
|
},
|
|
7149
9353
|
"Pagination": {
|
|
7150
9354
|
"version": "1.1.0",
|
|
7151
|
-
"lastUpdated": "2026-05-
|
|
9355
|
+
"lastUpdated": "2026-05-16",
|
|
7152
9356
|
"breaking": false,
|
|
7153
9357
|
"stable": true,
|
|
7154
9358
|
"changelog": null
|
|
7155
9359
|
},
|
|
7156
9360
|
"Popover": {
|
|
7157
9361
|
"version": "1.1.0",
|
|
7158
|
-
"lastUpdated": "2026-05-
|
|
9362
|
+
"lastUpdated": "2026-05-16",
|
|
7159
9363
|
"breaking": false,
|
|
7160
9364
|
"stable": true,
|
|
7161
9365
|
"changelog": null
|
|
7162
9366
|
},
|
|
7163
9367
|
"Progress": {
|
|
7164
9368
|
"version": "1.1.0",
|
|
7165
|
-
"lastUpdated": "2026-05-
|
|
9369
|
+
"lastUpdated": "2026-05-16",
|
|
7166
9370
|
"breaking": false,
|
|
7167
9371
|
"stable": true,
|
|
7168
9372
|
"changelog": null
|
|
7169
9373
|
},
|
|
7170
9374
|
"Select": {
|
|
7171
9375
|
"version": "1.1.0",
|
|
7172
|
-
"lastUpdated": "2026-05-
|
|
9376
|
+
"lastUpdated": "2026-05-16",
|
|
7173
9377
|
"breaking": false,
|
|
7174
9378
|
"stable": true,
|
|
7175
9379
|
"changelog": null
|
|
7176
9380
|
},
|
|
7177
9381
|
"Separator": {
|
|
7178
9382
|
"version": "1.1.0",
|
|
7179
|
-
"lastUpdated": "2026-05-
|
|
9383
|
+
"lastUpdated": "2026-05-16",
|
|
7180
9384
|
"breaking": false,
|
|
7181
9385
|
"stable": true,
|
|
7182
9386
|
"changelog": null
|
|
7183
9387
|
},
|
|
7184
9388
|
"Sheet": {
|
|
7185
9389
|
"version": "1.1.0",
|
|
7186
|
-
"lastUpdated": "2026-05-
|
|
9390
|
+
"lastUpdated": "2026-05-16",
|
|
7187
9391
|
"breaking": false,
|
|
7188
9392
|
"stable": true,
|
|
7189
9393
|
"changelog": null
|
|
7190
9394
|
},
|
|
7191
9395
|
"Sidebar": {
|
|
7192
9396
|
"version": "1.1.0",
|
|
7193
|
-
"lastUpdated": "2026-05-
|
|
9397
|
+
"lastUpdated": "2026-05-16",
|
|
7194
9398
|
"breaking": false,
|
|
7195
9399
|
"stable": true,
|
|
7196
9400
|
"changelog": null
|
|
7197
9401
|
},
|
|
7198
9402
|
"Skeleton": {
|
|
7199
9403
|
"version": "1.1.0",
|
|
7200
|
-
"lastUpdated": "2026-05-
|
|
9404
|
+
"lastUpdated": "2026-05-16",
|
|
7201
9405
|
"breaking": false,
|
|
7202
9406
|
"stable": true,
|
|
7203
9407
|
"changelog": null
|
|
7204
9408
|
},
|
|
7205
9409
|
"Slider": {
|
|
7206
9410
|
"version": "1.1.0",
|
|
7207
|
-
"lastUpdated": "2026-05-
|
|
9411
|
+
"lastUpdated": "2026-05-16",
|
|
7208
9412
|
"breaking": false,
|
|
7209
9413
|
"stable": true,
|
|
7210
9414
|
"changelog": null
|
|
7211
9415
|
},
|
|
7212
9416
|
"Switch": {
|
|
7213
9417
|
"version": "1.1.0",
|
|
7214
|
-
"lastUpdated": "2026-05-
|
|
9418
|
+
"lastUpdated": "2026-05-16",
|
|
7215
9419
|
"breaking": false,
|
|
7216
9420
|
"stable": true,
|
|
7217
9421
|
"changelog": null
|
|
7218
9422
|
},
|
|
7219
9423
|
"Table": {
|
|
7220
9424
|
"version": "1.1.0",
|
|
7221
|
-
"lastUpdated": "2026-05-
|
|
9425
|
+
"lastUpdated": "2026-05-16",
|
|
7222
9426
|
"breaking": false,
|
|
7223
9427
|
"stable": true,
|
|
7224
9428
|
"changelog": null
|
|
7225
9429
|
},
|
|
7226
9430
|
"Tabs": {
|
|
7227
9431
|
"version": "1.1.0",
|
|
7228
|
-
"lastUpdated": "2026-05-
|
|
9432
|
+
"lastUpdated": "2026-05-16",
|
|
7229
9433
|
"breaking": false,
|
|
7230
9434
|
"stable": true,
|
|
7231
9435
|
"changelog": null
|
|
7232
9436
|
},
|
|
7233
9437
|
"Toast": {
|
|
7234
9438
|
"version": "1.1.0",
|
|
7235
|
-
"lastUpdated": "2026-05-
|
|
9439
|
+
"lastUpdated": "2026-05-16",
|
|
7236
9440
|
"breaking": false,
|
|
7237
9441
|
"stable": true,
|
|
7238
9442
|
"changelog": null
|
|
7239
9443
|
},
|
|
7240
9444
|
"Tooltip": {
|
|
7241
9445
|
"version": "1.1.0",
|
|
7242
|
-
"lastUpdated": "2026-05-
|
|
9446
|
+
"lastUpdated": "2026-05-16",
|
|
7243
9447
|
"breaking": false,
|
|
7244
9448
|
"stable": true,
|
|
7245
9449
|
"changelog": null
|
|
7246
9450
|
},
|
|
7247
9451
|
"Typography": {
|
|
7248
9452
|
"version": "1.1.0",
|
|
7249
|
-
"lastUpdated": "2026-05-
|
|
9453
|
+
"lastUpdated": "2026-05-16",
|
|
7250
9454
|
"breaking": false,
|
|
7251
9455
|
"stable": true,
|
|
7252
9456
|
"changelog": null
|
|
7253
9457
|
},
|
|
7254
9458
|
"ButtonGroup": {
|
|
7255
9459
|
"version": "1.1.0",
|
|
7256
|
-
"lastUpdated": "2026-05-
|
|
9460
|
+
"lastUpdated": "2026-05-16",
|
|
7257
9461
|
"breaking": false,
|
|
7258
9462
|
"stable": true,
|
|
7259
9463
|
"changelog": null
|
|
7260
9464
|
},
|
|
7261
9465
|
"TagsInput": {
|
|
7262
9466
|
"version": "1.1.0",
|
|
7263
|
-
"lastUpdated": "2026-05-
|
|
9467
|
+
"lastUpdated": "2026-05-16",
|
|
7264
9468
|
"breaking": false,
|
|
7265
9469
|
"stable": true,
|
|
7266
9470
|
"changelog": null
|
|
7267
9471
|
},
|
|
7268
9472
|
"Combobox": {
|
|
7269
9473
|
"version": "1.1.0",
|
|
7270
|
-
"lastUpdated": "2026-05-
|
|
9474
|
+
"lastUpdated": "2026-05-16",
|
|
7271
9475
|
"breaking": false,
|
|
7272
9476
|
"stable": true,
|
|
7273
9477
|
"changelog": null
|
|
7274
9478
|
},
|
|
7275
9479
|
"DataGrid": {
|
|
7276
9480
|
"version": "1.1.0",
|
|
7277
|
-
"lastUpdated": "2026-05-
|
|
9481
|
+
"lastUpdated": "2026-05-16",
|
|
7278
9482
|
"breaking": false,
|
|
7279
9483
|
"stable": true,
|
|
7280
9484
|
"changelog": null
|
|
7281
9485
|
},
|
|
7282
9486
|
"Tag": {
|
|
7283
9487
|
"version": "1.1.0",
|
|
7284
|
-
"lastUpdated": "2026-05-
|
|
9488
|
+
"lastUpdated": "2026-05-16",
|
|
7285
9489
|
"breaking": false,
|
|
7286
9490
|
"stable": true,
|
|
7287
9491
|
"changelog": null
|
|
7288
9492
|
},
|
|
7289
9493
|
"TagGroup": {
|
|
7290
9494
|
"version": "1.1.0",
|
|
7291
|
-
"lastUpdated": "2026-05-
|
|
9495
|
+
"lastUpdated": "2026-05-16",
|
|
9496
|
+
"breaking": false,
|
|
9497
|
+
"stable": true,
|
|
9498
|
+
"changelog": null
|
|
9499
|
+
},
|
|
9500
|
+
"Item": {
|
|
9501
|
+
"version": "1.1.0",
|
|
9502
|
+
"lastUpdated": "2026-05-16",
|
|
9503
|
+
"breaking": false,
|
|
9504
|
+
"stable": true,
|
|
9505
|
+
"changelog": null
|
|
9506
|
+
},
|
|
9507
|
+
"Kanban": {
|
|
9508
|
+
"version": "1.1.0",
|
|
9509
|
+
"lastUpdated": "2026-05-16",
|
|
9510
|
+
"breaking": false,
|
|
9511
|
+
"stable": true,
|
|
9512
|
+
"changelog": null
|
|
9513
|
+
},
|
|
9514
|
+
"Conversation": {
|
|
9515
|
+
"version": "1.1.0",
|
|
9516
|
+
"lastUpdated": "2026-05-16",
|
|
9517
|
+
"breaking": false,
|
|
9518
|
+
"stable": true,
|
|
9519
|
+
"changelog": null
|
|
9520
|
+
},
|
|
9521
|
+
"DateRangePicker": {
|
|
9522
|
+
"version": "1.1.0",
|
|
9523
|
+
"lastUpdated": "2026-05-16",
|
|
7292
9524
|
"breaking": false,
|
|
7293
9525
|
"stable": true,
|
|
7294
9526
|
"changelog": null
|
|
@@ -7303,13 +9535,16 @@ export const cheatSheets = {
|
|
|
7303
9535
|
"Badge": "# Badge — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Small label for status, category, or count. Use dot prop for live status indicator.**\n\n```ts\nimport { Badge } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `variant` | `\"default\"` \\| `\"accent\"` \\| `\"teal\"` \\| `\"success\"` \\| `\"warning\"` \\| `\"danger\"` | `default` |\n| `dot` | `true` \\| `false` | `false` |\n\n## Common AI Mistakes\n\n- ❌ `variant=\"primary\"`\n → Use variant=\"accent\" for brand color\n- ❌ `variant=\"error\"`\n → Use variant=\"danger\"\n- ❌ `variant=\"info\"`\n → Use variant=\"teal\" for info-like styling\n\n## Examples\n\n**Live status with dot**\n```tsx\n<Badge variant=\"success\" dot>Online</Badge>\n```\n\n**Warning badge**\n```tsx\n<Badge variant=\"warning\">Beta</Badge>\n```\n\n**Danger badge**\n```tsx\n<Badge variant=\"danger\">Error</Badge>\n```\n",
|
|
7304
9536
|
"Breadcrumb": "# Breadcrumb — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Navigation trail showing current page location in hierarchy.**\n\n```ts\nimport { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbSeparator } from \"@usevyre/react\"\n```\n\n## Common AI Mistakes\n\n- ❌ `Using plain <a> tags inside Breadcrumb`\n → Use BreadcrumbItem > BreadcrumbLink for each crumb\n\n## Examples\n\n**Basic breadcrumb**\n```tsx\n<Breadcrumb>\n <BreadcrumbItem><BreadcrumbLink href=\"/\">Home</BreadcrumbLink></BreadcrumbItem>\n <BreadcrumbSeparator />\n <BreadcrumbItem><BreadcrumbLink href=\"/docs\">Docs</BreadcrumbLink></BreadcrumbItem>\n <BreadcrumbSeparator />\n <BreadcrumbItem aria-current=\"page\">Button</BreadcrumbItem>\n</Breadcrumb>\n```\n",
|
|
7305
9537
|
"Button": "# Button — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Triggers actions and navigation. The most commonly used interactive element.**\n\n```ts\nimport { Button } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `variant` | `\"primary\"` \\| `\"secondary\"` \\| `\"ghost\"` \\| `\"accent\"` \\| `\"teal\"` \\| `\"danger\"` | `secondary` |\n| `size` | `\"sm\"` \\| `\"md\"` \\| `\"lg\"` \\| `\"icon\"` | `md` |\n| `loading` | `true` \\| `false` | `false` |\n| `disabled` | `true` \\| `false` | `false` |\n\n## Common AI Mistakes\n\n- ❌ `variant=\"blue\"`\n → Use variant=\"accent\" for brand amber, or variant=\"teal\" for teal\n- ❌ `size=\"xl\"`\n → Use size=\"lg\"\n- ❌ `color=\"...\"`\n → Use variant prop instead\n- ❌ `icon={...}`\n → Use leftIcon={...} or rightIcon={...}\n- ❌ `size=\"icon\" without aria-label`\n → Add aria-label describing the action\n\n## Examples\n\n**Primary CTA**\n```tsx\n<Button variant=\"primary\">Get Started</Button>\n```\n\n**Large accent button**\n```tsx\n<Button variant=\"accent\" size=\"lg\">Launch App</Button>\n```\n\n**Loading state**\n```tsx\n<Button variant=\"danger\" loading>Deleting...</Button>\n```\n\n**Link button**\n```tsx\n<Button as=\"a\" href=\"/docs\" variant=\"secondary\">Read Docs</Button>\n```\n\n**Icon-only button**\n```tsx\n<Button variant=\"ghost\" size=\"icon\" aria-label=\"Close\">\n <X size={16} />\n</Button>\n```\n\n## Accessibility\n\n- Always add aria-label when using size='icon' (no visible text)\n",
|
|
7306
|
-
"Calendar": "# Calendar — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**
|
|
9538
|
+
"Calendar": "# Calendar — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Inline date-grid widget (always visible, no input). mode: single | range | multiple, optional time picker. For an input + popover use DatePicker; for start/end ranges with presets use DateRangePicker.**\n\n```ts\nimport { Calendar } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `disabled` | `true` \\| `false` | `false` |\n\n## Common AI Mistakes\n\n- ❌ `Calendar for an input field that opens a popover`\n → Use <DatePicker /> (single date) or <DateRangePicker /> (range)\n- ❌ `value as tuple for mode=\"single\"`\n → Pass value matching mode; use mode=\"range\" for [start,end]\n\n## Examples\n\n**Controlled date picker**\n```tsx\nconst [date, setDate] = useState(null);\n<Calendar value={date} onChange={setDate} />\n```\n",
|
|
9539
|
+
"DatePicker": "# DatePicker — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Input trigger that opens a Calendar in a popover. Same modes as Calendar (single | range | multiple) plus a placeholder. Use this for a compact date field; use Calendar for an always-visible grid, or DateRangePicker for start/end ranges with presets.**\n\n```ts\nimport { DatePicker } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `mode` | `\"single\"` \\| `\"range\"` \\| `\"multiple\"` | `single` |\n| `weekStartsOn` | `\"0\"` \\| `\"1\"` | `1` |\n| `showTime` | `true` \\| `false` | `false` |\n\n## Common AI Mistakes\n\n- ❌ `DatePicker mode=\"range\" for { from, to } object`\n → Use <DateRangePicker /> for the { from, to } object API + presets + dual month\n- ❌ `DatePicker without value/onChange`\n → Provide value and onChange (e.g. from useState)\n\n## Examples\n\n**Single date field**\n```tsx\nconst [date, setDate] = useState(null);\n<DatePicker value={date} onChange={setDate} placeholder=\"Pick a date\" />\n```\n\n**Date + time**\n```tsx\n<DatePicker value={date} onChange={setDate} showTime />\n```\n",
|
|
7307
9540
|
"Card": "# Card — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Content container with optional header, body, and footer sections.**\n\n```ts\nimport { Card, CardHeader, CardBody, CardFooter } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `variant` | `\"default\"` \\| `\"elevated\"` \\| `\"outlined\"` \\| `\"ghost\"` \\| `\"accent\"` | `default` |\n| `hoverable` | `true` \\| `false` | `false` |\n| `clickable` | `true` \\| `false` | `false` |\n\n## Common AI Mistakes\n\n- ❌ `variant=\"primary\"`\n → Use variant=\"elevated\" | \"outlined\" | \"ghost\" | \"accent\"\n\n## Examples\n\n**Elevated card with sections**\n```tsx\n<Card variant=\"elevated\">\n <CardHeader><Badge variant=\"teal\">New</Badge></CardHeader>\n <CardBody>\n <h3>Card Title</h3>\n <p>Description text.</p>\n </CardBody>\n <CardFooter>\n <Button variant=\"ghost\" size=\"sm\">Learn more</Button>\n </CardFooter>\n</Card>\n```\n",
|
|
7308
9541
|
"Checkbox": "# Checkbox — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Binary toggle for boolean form values.**\n\n```ts\nimport { Checkbox } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `size` | `\"sm\"` \\| `\"md\"` | `md` |\n| `checked` | `true` \\| `false` | — |\n| `disabled` | `true` \\| `false` | `false` |\n| `indeterminate` | `true` \\| `false` | `false` |\n\n## Common AI Mistakes\n\n- ❌ `size=\"lg\"`\n → Use size=\"md\"\n\n## Examples\n\n**Labeled checkbox**\n```tsx\n<label style={{ display: 'flex', alignItems: 'center', gap: 'var(--vyre-spacing-2)' }}>\n <Checkbox checked={agreed} onChange={e => setAgreed(e.target.checked)} />\n I agree to the terms\n</label>\n```\n",
|
|
9542
|
+
"RadioGroup": "# RadioGroup — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Controlled single-choice group. RadioGroup owns the selected value; render it data-driven via the options array OR with composable <Radio> children for custom content. role=radiogroup with proper labelling. For multi-select use Checkbox; for a compact dropdown use Select.**\n\n```ts\nimport { RadioGroup, Radio } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `size` | `\"sm\"` \\| `\"md\"` | `md` |\n| `orientation` | `\"vertical\"` \\| `\"horizontal\"` | `vertical` |\n| `disabled` | `true` \\| `false` | `false` |\n\n## Common AI Mistakes\n\n- ❌ `<Radio> used outside a <RadioGroup>`\n → Always wrap <Radio> in <RadioGroup>\n- ❌ `RadioGroup without value/onChange (React) or v-model (Vue)`\n → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React\n- ❌ `Using Checkbox for mutually-exclusive choices`\n → Use RadioGroup + Radio (or options) for one-of-many\n\n## Examples\n\n**Data-driven**\n```tsx\n<RadioGroup\n value={plan}\n onChange={setPlan}\n options={[\n { value: \"free\", label: \"Free\", description: \"For hobby projects\" },\n { value: \"pro\", label: \"Pro\", description: \"For teams\" },\n ]}\n/>\n```\n\n**Composable children**\n```tsx\n<RadioGroup value={plan} onChange={setPlan} orientation=\"horizontal\">\n <Radio value=\"free\" label=\"Free\" />\n <Radio value=\"pro\" label=\"Pro\" />\n</RadioGroup>\n```\n",
|
|
9543
|
+
"RichTextEditor": "# RichTextEditor — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Controlled WYSIWYG editor. value is an HTML string; you own it in state and set it in onChange (React) / v-model (Vue). Native contentEditable + execCommand — zero dependencies. Toolbar: bold, italic, underline, strike, h1-h3, ordered/unordered lists, quote, code block, link, clear formatting.**\n\n```ts\nimport { RichTextEditor } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `disabled` | `true` \\| `false` | `false` |\n| `readOnly` | `true` \\| `false` | `false` |\n\n## Common AI Mistakes\n\n- ❌ `RichTextEditor without value/onChange (React) or v-model (Vue)`\n → Keep the HTML string in state and update it in onChange / v-model\n- ❌ `Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising`\n → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output\n- ❌ `toolbar=\"bold\" (string)`\n → Pass an array, e.g. toolbar={[\"bold\",\"italic\",\"link\"]}\n\n## Examples\n\n**Controlled editor**\n```tsx\nconst [html, setHtml] = useState(\"<p>Hello <strong>world</strong></p>\");\n<RichTextEditor value={html} onChange={setHtml} placeholder=\"Write…\" />\n```\n\n**Minimal toolbar**\n```tsx\n<RichTextEditor\n value={html}\n onChange={setHtml}\n toolbar={[\"bold\", \"italic\", \"link\"]}\n/>\n```\n",
|
|
7309
9544
|
"Command": "# Command — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Command palette / search dialog. Use for search-first navigation or quick actions.**\n\n```ts\nimport { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandDialog } from \"@usevyre/react\"\n```\n\n## Common AI Mistakes\n\n- ❌ `Using Input type=\"search\" for search UI`\n → Use Command + CommandInput + CommandList + CommandItem\n\n## Examples\n\n**Basic command palette**\n```tsx\n<Command>\n <CommandInput placeholder=\"Search...\" />\n <CommandList>\n <CommandEmpty>No results found.</CommandEmpty>\n <CommandGroup heading=\"Suggestions\">\n <CommandItem onSelect={() => handleSelect('dashboard')}>Dashboard</CommandItem>\n <CommandItem onSelect={() => handleSelect('settings')}>Settings</CommandItem>\n </CommandGroup>\n </CommandList>\n</Command>\n```\n",
|
|
7310
9545
|
"DropdownMenu": "# DropdownMenu — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Contextual menu triggered by a button. Supports items, separators, checkbox items, radio groups, and sub-menus.**\n\n```ts\nimport { DropdownMenu, DropdownItem, DropdownSeparator, DropdownCheckboxItem, DropdownRadioGroup, DropdownRadioItem, DropdownSub } from \"@usevyre/react\"\n```\n\n## Common AI Mistakes\n\n- ❌ `DropdownItem variant=\"primary\"`\n → Use variant=\"danger\" for destructive items only\n\n## Examples\n\n**Dropdown with danger item**\n```tsx\n<DropdownMenu trigger={<Button variant=\"secondary\">Options</Button>}>\n <DropdownItem onSelect={() => handleEdit()}>Edit</DropdownItem>\n <DropdownItem onSelect={() => handleDuplicate()}>Duplicate</DropdownItem>\n <DropdownSeparator />\n <DropdownItem variant=\"danger\" onSelect={() => handleDelete()}>Delete</DropdownItem>\n</DropdownMenu>\n```\n",
|
|
7311
|
-
"Field": "# Field — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Form field wrapper
|
|
7312
|
-
"Input": "# Input — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Text input field. Wrap in Field for labels and validation. Use leftElement/rightElement for icons.**\n\n```ts\nimport { Input } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `size` | `\"sm\"` \\| `\"md\"` \\| `\"lg\"` | `md` |\n\n## Common AI Mistakes\n\n- ❌ `size=\"icon\"`\n → Use size=\"sm\" | \"md\" | \"lg\"\n- ❌ `type=\"search\" for search UI`\n → Import Command from @usevyre/react for search palettes\n\n## Examples\n\n**Password input with icon**\n```tsx\n<Input type=\"password\" rightElement={<EyeIcon />} placeholder=\"Password\" />\n```\n",
|
|
9546
|
+
"Field": "# Field — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Form field wrapper. Two ways to use it (both supported): (1) props-based — pass label/hint/state/required for the common case; (2) composable — use the parts FieldLabel, FieldDescription, FieldError, FieldGroup, FieldSet for richer layouts (multiple controls, custom error placement). The props-based API is unchanged and still works.**\n\n```ts\nimport { Field, Input, Textarea } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `state` | `\"idle\"` \\| `\"error\"` \\| `\"success\"` \\| `\"warning\"` | `idle` |\n| `required` | `true` \\| `false` | `false` |\n\n## Common AI Mistakes\n\n- ❌ `Applying state prop directly to Input`\n → Wrap Input in <Field state=\"error\"> to apply validation styling\n- ❌ `Mixing props label/hint AND FieldLabel/FieldError for the same field`\n → Pick one: either props-based (label/hint/state) OR composable parts\n\n## Examples\n\n**Error state field**\n```tsx\n<Field label=\"Email\" state=\"error\" hint=\"Invalid email format\">\n <Input type=\"email\" placeholder=\"you@example.com\" />\n</Field>\n```\n\n**Search field with left icon**\n```tsx\n<Field label=\"Search\">\n <Input leftElement={<SearchIcon />} placeholder=\"Search...\" />\n</Field>\n```\n\n**Composable field with explicit parts**\n```tsx\n<Field>\n <FieldLabel required htmlFor=\"email\">Email</FieldLabel>\n <Input id=\"email\" type=\"email\" />\n <FieldDescription>We\\u2019ll never share it.</FieldDescription>\n <FieldError>{errors.email}</FieldError>\n</Field>\n\n// Two controls side by side\n<FieldGroup orientation=\"horizontal\">\n <Field label=\"First name\"><Input /></Field>\n <Field label=\"Last name\"><Input /></Field>\n</FieldGroup>\n```\n",
|
|
9547
|
+
"Input": "# Input — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Text input field. Wrap in Field for labels and validation. Use leftElement/rightElement for icons.**\n\n```ts\nimport { Input } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `size` | `\"sm\"` \\| `\"md\"` \\| `\"lg\"` | `md` |\n\n## Common AI Mistakes\n\n- ❌ `size=\"icon\"`\n → Use size=\"sm\" | \"md\" | \"lg\"\n- ❌ `type=\"search\" for search UI`\n → Import Command from @usevyre/react for search palettes\n- ❌ `Vue: binding Input/Textarea value without v-model`\n → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange\n\n## Examples\n\n**Password input with icon**\n```tsx\n<Input type=\"password\" rightElement={<EyeIcon />} placeholder=\"Password\" />\n```\n",
|
|
7313
9548
|
"Label": "# Label — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Accessible form label. Associate with input via htmlFor.**\n\n```ts\nimport { Label } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `required` | `true` \\| `false` | `false` |\n\n## Examples\n\n**Label with input**\n```tsx\n<Label htmlFor=\"email\">Email address</Label>\n<Input id=\"email\" type=\"email\" />\n```\n",
|
|
7314
9549
|
"Modal": "# Modal — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Dialog overlay for confirmations, forms, or focused content.**\n\n```ts\nimport { Modal, ModalHeader, ModalBody, ModalFooter } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `size` | `\"sm\"` \\| `\"md\"` \\| `\"lg\"` \\| `\"full\"` | `md` |\n| `open` | `true` \\| `false` | — |\n\n## Common AI Mistakes\n\n- ❌ `size=\"xl\"`\n → Use size=\"lg\" or size=\"full\"\n\n## Examples\n\n**Confirmation modal**\n```tsx\n<Modal open={isOpen} onClose={() => setIsOpen(false)} title=\"Confirm Delete\" size=\"sm\">\n <ModalBody>Are you sure you want to delete this item?</ModalBody>\n <ModalFooter>\n <Button variant=\"ghost\" onClick={() => setIsOpen(false)}>Cancel</Button>\n <Button variant=\"danger\" onClick={handleDelete}>Delete</Button>\n </ModalFooter>\n</Modal>\n```\n",
|
|
7315
9550
|
"Pagination": "# Pagination — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Page navigation for paginated lists or tables.**\n\n```ts\nimport { Pagination } from \"@usevyre/react\"\n```\n\n## Examples\n\n**Basic pagination**\n```tsx\n<Pagination page={currentPage} total={totalPages} onChange={setCurrentPage} />\n```\n",
|
|
@@ -7318,7 +9553,7 @@ export const cheatSheets = {
|
|
|
7318
9553
|
"Select": "# Select — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Dropdown for selecting one option from a list.**\n\n```ts\nimport { Select } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `size` | `\"sm\"` \\| `\"md\"` \\| `\"lg\"` | `md` |\n| `disabled` | `true` \\| `false` | `false` |\n\n## Common AI Mistakes\n\n- ❌ `Passing strings directly as children`\n → Pass options={[{ value: 'a', label: 'Option A' }]}\n\n## Examples\n\n**Controlled select**\n```tsx\n<Select\n options={[{ value: 'react', label: 'React' }, { value: 'vue', label: 'Vue' }]}\n value={framework}\n onChange={setFramework}\n placeholder=\"Choose framework\"\n/>\n```\n",
|
|
7319
9554
|
"Separator": "# Separator — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Horizontal or vertical divider line.**\n\n```ts\nimport { Separator } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `orientation` | `\"horizontal\"` \\| `\"vertical\"` | `horizontal` |\n\n## Examples\n\n**Horizontal separator**\n```tsx\n<Separator />\n```\n\n**Vertical separator**\n```tsx\n<Separator orientation=\"vertical\" />\n```\n",
|
|
7320
9555
|
"Sheet": "# Sheet — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Side panel (drawer) that slides in from the edge. For forms, detail views, or settings.**\n\n```ts\nimport { Sheet, SheetHeader, SheetBody, SheetFooter } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `size` | `\"sm\"` \\| `\"md\"` \\| `\"lg\"` \\| `\"full\"` | `md` |\n| `side` | `\"left\"` \\| `\"right\"` | `right` |\n| `open` | `true` \\| `false` | — |\n\n## Examples\n\n**Settings sheet from the right**\n```tsx\n<Sheet open={isOpen} onClose={() => setIsOpen(false)} title=\"Settings\" side=\"right\">\n <SheetBody>Settings content here.</SheetBody>\n <SheetFooter>\n <Button variant=\"accent\">Save</Button>\n </SheetFooter>\n</Sheet>\n```\n",
|
|
7321
|
-
"Sidebar": "# Sidebar — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**App navigation sidebar. Use AppLayout as the root layout wrapper.**\n\n```ts\nimport { AppLayout, Sidebar, SidebarHeader, SidebarContent, SidebarSection, SidebarItem, SidebarFooter } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `variant` | `\"default\"` \\| `\"floating\"` | `default` |\n\n## Examples\n\n**App layout with sidebar**\n```tsx\n<AppLayout>\n <Sidebar>\n <SidebarHeader>Logo</SidebarHeader>\n <SidebarContent>\n <SidebarSection heading=\"Main\">\n <SidebarItem href=\"/\" active>Dashboard</SidebarItem>\n <SidebarItem href=\"/settings\">Settings</SidebarItem>\n </SidebarSection>\n </SidebarContent>\n <SidebarFooter><Avatar fallback=\"JD\" size=\"sm\" /></SidebarFooter>\n </Sidebar>\n <main>Page content</main>\n</AppLayout>\n```\n",
|
|
9556
|
+
"Sidebar": "# Sidebar — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**App navigation sidebar. Use AppLayout as the root layout wrapper.**\n\n```ts\nimport { AppLayout, Sidebar, SidebarHeader, SidebarContent, SidebarSection, SidebarItem, SidebarFooter } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `variant` | `\"default\"` \\| `\"floating\"` | `default` |\n\n## Common AI Mistakes\n\n- ❌ `Vue: passing icon/collapsedIcon as props on SidebarTrigger`\n → Use <template #icon> and <template #collapsed-icon>; React uses icon / collapsedIcon props\n\n## Examples\n\n**App layout with sidebar**\n```tsx\n<AppLayout>\n <Sidebar>\n <SidebarHeader>Logo</SidebarHeader>\n <SidebarContent>\n <SidebarSection heading=\"Main\">\n <SidebarItem href=\"/\" active>Dashboard</SidebarItem>\n <SidebarItem href=\"/settings\">Settings</SidebarItem>\n </SidebarSection>\n </SidebarContent>\n <SidebarFooter><Avatar fallback=\"JD\" size=\"sm\" /></SidebarFooter>\n </Sidebar>\n <main>Page content</main>\n</AppLayout>\n```\n\n**SidebarTrigger with distinct open/collapsed icons**\n```tsx\n<SidebarTrigger icon={<PanelLeftClose />} collapsedIcon={<PanelLeftOpen />} />\n\n// Vue:\n// <SidebarTrigger>\n// <template #icon><PanelLeftClose /></template>\n// <template #collapsed-icon><PanelLeftOpen /></template>\n// </SidebarTrigger>\n```\n",
|
|
7322
9557
|
"Skeleton": "# Skeleton — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Loading placeholder that mimics the shape of content while data loads.**\n\n```ts\nimport { Skeleton } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `variant` | `\"rect\"` \\| `\"circle\"` \\| `\"text\"` | `rect` |\n\n## Examples\n\n**Avatar skeleton**\n```tsx\n<Skeleton variant=\"circle\" width={40} height={40} />\n```\n\n**Text line skeletons**\n```tsx\n<Skeleton variant=\"text\" width=\"100%\" />\n<Skeleton variant=\"text\" width=\"60%\" />\n```\n",
|
|
7323
9558
|
"Slider": "# Slider — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Range input for selecting a numeric value within a range.**\n\n```ts\nimport { Slider } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `size` | `\"sm\"` \\| `\"md\"` | `md` |\n| `disabled` | `true` \\| `false` | `false` |\n\n## Examples\n\n**Volume slider**\n```tsx\n<Slider value={volume} onChange={setVolume} min={0} max={100} step={5} />\n```\n",
|
|
7324
9559
|
"Switch": "# Switch — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Toggle switch for boolean on/off settings.**\n\n```ts\nimport { Switch } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `size` | `\"sm\"` \\| `\"md\"` | `md` |\n| `checked` | `true` \\| `false` | — |\n| `disabled` | `true` \\| `false` | `false` |\n\n## Examples\n\n**Notifications toggle**\n```tsx\n<label style={{ display: 'flex', alignItems: 'center', gap: 'var(--vyre-spacing-2)' }}>\n <Switch checked={notifications} onChange={setNotifications} />\n Enable notifications\n</label>\n```\n",
|
|
@@ -7332,5 +9567,9 @@ export const cheatSheets = {
|
|
|
7332
9567
|
"Combobox": "# Combobox — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Searchable single-select dropdown with typeahead filtering and keyboard navigation. Use when the list is long enough to need search. Differs from Select (no search) and Command (palette).**\n\n```ts\nimport { Combobox } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `size` | `\"sm\"` \\| `\"md\"` \\| `\"lg\"` | `md` |\n| `disabled` | `true` \\| `false` | `false` |\n\n## Common AI Mistakes\n\n- ❌ `Combobox value=\"\"`\n → Use value={null} for no selection\n- ❌ `Combobox options={string[]}`\n → Use [{ value: 'ts', label: 'TypeScript' }]\n- ❌ `Using Combobox for command palette`\n → Use Command for command palettes\n\n## Examples\n\n**Searchable language picker**\n```tsx\nconst [lang, setLang] = useState<string | null>(null);\n<Combobox\n options={[{ value: \"ts\", label: \"TypeScript\" }, { value: \"go\", label: \"Go\" }]}\n value={lang}\n onChange={setLang}\n placeholder=\"Search language…\"\n/>\n```\n",
|
|
7333
9568
|
"DataGrid": "# DataGrid — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Table with built-in column sorting, loading skeletons, and empty state. Filtering and pagination are out of scope — compose with the Pagination component.**\n\n```ts\nimport { DataGrid } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `sortDir` | `\"asc\"` \\| `\"desc\"` | — |\n| `loading` | `true` \\| `false` | `false` |\n| `stickyHeader` | `true` \\| `false` | `false` |\n\n## Common AI Mistakes\n\n- ❌ `DataGrid expecting built-in pagination`\n → Slice rows yourself and use the Pagination component\n- ❌ `DataGrid expecting built-in filtering`\n → Filter the rows array before passing it in\n- ❌ `sortable without onSort`\n → Handle onSort and sort the rows array in your state\n\n## Examples\n\n**Sortable grid**\n```tsx\nconst cols = [{ key: \"name\", label: \"Name\", sortable: true }];\n<DataGrid\n columns={cols}\n rows={people}\n sortKey={sortKey}\n sortDir={sortDir}\n onSort={(k, d) => { setSortKey(k); setSortDir(d); }}\n/>\n```\n\n**Loading state**\n```tsx\n<DataGrid columns={cols} rows={[]} loading />\n```\n",
|
|
7334
9569
|
"Tag": "# Tag — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Standalone display tag/chip for categories, labels, or filter chips. NOT an input — for tag input use TagsInput. Group multiple with TagGroup.**\n\n```ts\nimport { Tag } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `variant` | `\"default\"` \\| `\"accent\"` \\| `\"danger\"` | `default` |\n| `size` | `\"sm\"` \\| `\"md\"` \\| `\"lg\"` | `md` |\n| `disabled` | `true` \\| `false` | `false` |\n\n## Common AI Mistakes\n\n- ❌ `Tag variant=\"success\"`\n → Use Badge for success/warning/teal status colors; Tag is for categories/filters\n- ❌ `Using Tag for tag input`\n → Use TagsInput for adding/removing tags via keyboard\n- ❌ `Tag size=\"xl\"`\n → Use size=\"lg\"\n\n## Examples\n\n**Category tags in a group**\n```tsx\n<TagGroup>\n <Tag>Design</Tag>\n <Tag variant=\"accent\">Featured</Tag>\n <Tag>Engineering</Tag>\n</TagGroup>\n```\n\n**Removable filter chip (React)**\n```tsx\n<Tag onRemove={() => removeFilter(\"react\")}>react</Tag>\n```\n\n**Clickable toggle tag (React)**\n```tsx\n<Tag onClick={() => toggleFilter(\"vue\")}>vue</Tag>\n```\n",
|
|
7335
|
-
"TagGroup": "# TagGroup — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Read-only container that lays out multiple Tag elements with automatic wrapping and consistent spacing. For tag input use TagsInput.**\n\n```ts\nimport { TagGroup, Tag } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `gap` | `\"sm\"` \\| `\"md\"` \\| `\"lg\"` | `md` |\n| `wrap` | `true` \\| `false` | `true` |\n\n## Common AI Mistakes\n\n- ❌ `TagGroup without Tag children`\n → Place <Tag> elements as direct children\n- ❌ `Using TagGroup for tag input`\n → Use TagsInput for an editable tag field\n\n## Examples\n\n**Tag group with mixed variants**\n```tsx\n<TagGroup gap=\"sm\">\n <Tag>React</Tag>\n <Tag>Vue</Tag>\n <Tag variant=\"accent\">TypeScript</Tag>\n</TagGroup>\n```\n"
|
|
9570
|
+
"TagGroup": "# TagGroup — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Read-only container that lays out multiple Tag elements with automatic wrapping and consistent spacing. For tag input use TagsInput.**\n\n```ts\nimport { TagGroup, Tag } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `gap` | `\"sm\"` \\| `\"md\"` \\| `\"lg\"` | `md` |\n| `wrap` | `true` \\| `false` | `true` |\n\n## Common AI Mistakes\n\n- ❌ `TagGroup without Tag children`\n → Place <Tag> elements as direct children\n- ❌ `Using TagGroup for tag input`\n → Use TagsInput for an editable tag field\n\n## Examples\n\n**Tag group with mixed variants**\n```tsx\n<TagGroup gap=\"sm\">\n <Tag>React</Tag>\n <Tag>Vue</Tag>\n <Tag variant=\"accent\">TypeScript</Tag>\n</TagGroup>\n```\n",
|
|
9571
|
+
"Item": "# Item — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Layout primitive for list rows, settings rows, and notification rows. Denser than Card — use Item (not Card) for repeated list rows.**\n\n```ts\nimport { Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions, ItemGroup } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `variant` | `\"default\"` \\| `\"outlined\"` \\| `\"muted\"` \\| `\"plain\"` | `default` |\n| `size` | `\"sm\"` \\| `\"md\"` \\| `\"lg\"` | `md` |\n| `clickable` | `true` \\| `false` | `false` |\n\n## Common AI Mistakes\n\n- ❌ `Card used for repeated list rows`\n → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows\n- ❌ `Item variant=\"primary\"`\n → Use variant=\"default\" | \"outlined\" | \"muted\"\n- ❌ `raw text directly inside Item`\n → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>\n\n## Examples\n\n**Settings row with media, content and a trailing switch**\n```tsx\n<Item>\n <ItemMedia><BellIcon /></ItemMedia>\n <ItemContent>\n <ItemTitle>Notifications</ItemTitle>\n <ItemDescription>Receive an email when someone mentions you.</ItemDescription>\n </ItemContent>\n <ItemActions>\n <Switch defaultChecked />\n </ItemActions>\n</Item>\n```\n\n**Grouped clickable list with dividers**\n```tsx\n<ItemGroup separated>\n <Item clickable>\n <ItemContent><ItemTitle>Profile</ItemTitle></ItemContent>\n </Item>\n <Item clickable>\n <ItemContent><ItemTitle>Billing</ItemTitle></ItemContent>\n </Item>\n</ItemGroup>\n```\n",
|
|
9572
|
+
"Kanban": "# Kanban — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Drag-and-drop board: cards move between columns (or reorder within a column). CONTROLLED & data-driven like DataGrid. While dragging, a placeholder shows the exact drop position. Each card is wrapped in a Card (variant=\"outlined\"); renderCard (React) / #card slot (Vue) can render ANY content incl. complex components (Avatar/Badge/Progress). Columns and cards accept an optional semantic color tint. Native HTML5 DnD, zero deps.**\n\n```ts\nimport { Kanban } from \"@usevyre/react\"\n```\n\n## Common AI Mistakes\n\n- ❌ `Kanban without onChange (or ignoring it)`\n → Store columns in state and setColumns in onChange (v-model in Vue)\n- ❌ `Duplicate card ids across columns`\n → Use globally-unique card ids across the entire board\n- ❌ `Mutating value in place then calling onChange`\n → Pass the new array Kanban gives you straight to setState / v-model\n- ❌ `color=\"blue\" (or any non-semantic value)`\n → Use one of: \"default\" | \"accent\" | \"teal\" | \"success\" | \"warning\" | \"danger\"\n\n## Examples\n\n**Controlled board**\n```tsx\nconst [columns, setColumns] = useState([\n { id: \"todo\", title: \"To Do\", cards: [{ id: \"1\", title: \"Spec API\" }] },\n { id: \"doing\", title: \"In Progress\", cards: [] },\n { id: \"done\", title: \"Done\", cards: [{ id: \"2\", title: \"Kickoff\" }] },\n]);\n<Kanban value={columns} onChange={setColumns} />\n```\n\n**Custom card body + click handler**\n```tsx\n<Kanban\n value={columns}\n onChange={setColumns}\n onCardClick={(card) => openDetail(card.id)}\n renderCard={(card) => (\n <><strong>{card.title}</strong><Badge>{card.id}</Badge></>\n )}\n/>\n```\n\n**Tinted columns/cards + complex card content**\n```tsx\nconst [cols, setCols] = useState([\n { id: \"doing\", title: \"In Progress\", color: \"teal\", cards: [\n { id: \"t1\", title: \"OAuth\", assignee: \"AK\", progress: 60, color: \"warning\" },\n ]},\n]);\n<Kanban\n value={cols}\n onChange={setCols}\n renderCard={(card) => (\n <><strong>{card.title}</strong><Progress value={card.progress} /></>\n )}\n/>\n```\n",
|
|
9573
|
+
"Conversation": "# Conversation — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Chat / inbox message thread. CONTROLLED & data-driven like Kanban — you own `value` (messages array) and append in your own send handler; Conversation holds no message state. Consecutive messages from the same author are grouped (avatar + name shown once), day separators are inserted on date change, and outgoing messages (authorId === currentUserId) align right.**\n\n```ts\nimport { Conversation } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `composer` | `true` \\| `false` | `false` |\n| `allowAttachments` | `true` \\| `false` | `false` |\n\n## Common AI Mistakes\n\n- ❌ `Conversation without currentUserId`\n → Always pass currentUserId matching one of the message authorId values\n- ❌ `Expecting Conversation to store/append messages`\n → Append to your own state in onSend (or @send) and pass it back via value\n- ❌ `composer without onSend (React) / @send (Vue)`\n → Provide onSend / @send to append the message to value\n- ❌ `Treating onSend as (text) only when using allowAttachments`\n → Handle onSend(text, files) — map files to message attachments and append\n\n## Examples\n\n**Controlled thread with built-in composer**\n```tsx\nconst [messages, setMessages] = useState([\n { id: \"1\", authorId: \"sam\", authorName: \"Sam\", text: \"Hey!\" },\n { id: \"2\", authorId: \"me\", text: \"Hi \\ud83d\\udc4b\", status: \"read\" },\n]);\n<Conversation\n value={messages}\n currentUserId=\"me\"\n composer\n onSend={(t) => setMessages((m) => [...m, { id: crypto.randomUUID(), authorId: \"me\", text: t }])}\n/>\n```\n\n**Typing indicator + custom bubble**\n```tsx\n<Conversation\n value={messages}\n currentUserId=\"me\"\n typing=\"Sam is typing\"\n renderMessage={(m) => <strong>{m.text}</strong>}\n/>\n```\n\n**Message with image + file attachments**\n```tsx\nconst messages = [\n { id: \"1\", authorId: \"sam\", authorName: \"Sam\", text: \"Moodboard \\ud83d\\udc47\",\n attachments: [{ kind: \"image\", url: \"/board.png\", name: \"board.png\" }] },\n { id: \"2\", authorId: \"me\", text: \"Specs:\", status: \"read\",\n attachments: [{ kind: \"file\", url: \"/spec.pdf\", name: \"spec.pdf\", size: \"2.4 MB\" }] },\n];\n<Conversation value={messages} currentUserId=\"me\" />\n```\n",
|
|
9574
|
+
"DateRangePicker": "# DateRangePicker — AI Cheat Sheet\n> Quick reference for Claude / Cursor / Copilot\n\n**Start/end date range picker. Built on Calendar (mode=range) with a friendlier { from, to } object API, a two-month side-by-side view, and preset shortcuts. Use this for report/filter date ranges; use DatePicker for a single date.**\n\n```ts\nimport { DateRangePicker } from \"@usevyre/react\"\n```\n\n## Valid Props\n\n| Prop | Values | Default |\n|------|--------|---------|\n| `numberOfMonths` | `\"1\"` \\| `\"2\"` | `2` |\n| `weekStartsOn` | `\"0\"` \\| `\"1\"` | `1` |\n\n## Common AI Mistakes\n\n- ❌ `value={[from, to]}`\n → Use value={{ from, to }} and read range.from / range.to\n- ❌ `DateRangePicker for a single date`\n → Use <DatePicker /> for a single date\n- ❌ `presets=\"true\" (string)`\n → Use the bare prop: presets (or presets={true})\n\n## Examples\n\n**Range picker with built-in presets**\n```tsx\nconst [range, setRange] = useState({ from: null, to: null });\n<DateRangePicker value={range} onChange={setRange} presets />\n```\n\n**Single month, no presets**\n```tsx\n<DateRangePicker value={range} onChange={setRange} numberOfMonths={1} />\n```\n"
|
|
7336
9575
|
};
|