@usevyre/ai-context 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/anti-patterns.json +179 -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/claude-context.md +324 -5
- package/dist/copilot-instructions.md +324 -5
- package/dist/cursor-rules.md +93 -4
- package/dist/full-context.md +323 -4
- package/dist/index.js +2263 -106
- package/dist/schema.json +563 -7
- package/dist/tokens.json +1 -1
- package/dist/tokens.md +1 -1
- package/dist/version-info.json +92 -38
- package/dist/windsurf-rules.md +324 -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
|
|
|
@@ -1068,6 +1188,180 @@ import { TagGroup, Tag } from "@usevyre/react"
|
|
|
1068
1188
|
|
|
1069
1189
|
---
|
|
1070
1190
|
|
|
1191
|
+
### Item
|
|
1192
|
+
|
|
1193
|
+
Layout primitive for list rows, settings rows, and notification rows. Denser than Card — use Item (not Card) for repeated list rows.
|
|
1194
|
+
|
|
1195
|
+
\`\`\`tsx
|
|
1196
|
+
import { Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions, ItemGroup } from "@usevyre/react"
|
|
1197
|
+
|
|
1198
|
+
// Props:
|
|
1199
|
+
// variant = "default" | "outlined" | "muted" | "plain" (default: default)
|
|
1200
|
+
// size = "sm" | "md" | "lg" (default: md)
|
|
1201
|
+
// clickable = boolean (default: false)
|
|
1202
|
+
|
|
1203
|
+
// Examples:
|
|
1204
|
+
<Item>
|
|
1205
|
+
<ItemMedia><BellIcon /></ItemMedia>
|
|
1206
|
+
<ItemContent>
|
|
1207
|
+
<ItemTitle>Notifications</ItemTitle>
|
|
1208
|
+
<ItemDescription>Receive an email when someone mentions you.</ItemDescription>
|
|
1209
|
+
</ItemContent>
|
|
1210
|
+
<ItemActions>
|
|
1211
|
+
<Switch defaultChecked />
|
|
1212
|
+
</ItemActions>
|
|
1213
|
+
</Item>
|
|
1214
|
+
<ItemGroup separated>
|
|
1215
|
+
<Item clickable>
|
|
1216
|
+
<ItemContent><ItemTitle>Profile</ItemTitle></ItemContent>
|
|
1217
|
+
</Item>
|
|
1218
|
+
<Item clickable>
|
|
1219
|
+
<ItemContent><ItemTitle>Billing</ItemTitle></ItemContent>
|
|
1220
|
+
</Item>
|
|
1221
|
+
</ItemGroup>
|
|
1222
|
+
\`\`\`
|
|
1223
|
+
|
|
1224
|
+
**Common mistakes:**
|
|
1225
|
+
- ❌ \`Card used for repeated list rows\` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
|
|
1226
|
+
- ❌ \`Item variant="primary"\` → Use variant="default" | "outlined" | "muted"
|
|
1227
|
+
- ❌ \`raw text directly inside Item\` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
|
|
1228
|
+
|
|
1229
|
+
---
|
|
1230
|
+
|
|
1231
|
+
### Kanban
|
|
1232
|
+
|
|
1233
|
+
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.
|
|
1234
|
+
|
|
1235
|
+
\`\`\`tsx
|
|
1236
|
+
import { Kanban } from "@usevyre/react"
|
|
1237
|
+
|
|
1238
|
+
// Props:
|
|
1239
|
+
// value = KanbanColumn[]
|
|
1240
|
+
// onChange = function
|
|
1241
|
+
// renderCard = function
|
|
1242
|
+
// onCardClick = function
|
|
1243
|
+
|
|
1244
|
+
// Examples:
|
|
1245
|
+
const [columns, setColumns] = useState([
|
|
1246
|
+
{ id: "todo", title: "To Do", cards: [{ id: "1", title: "Spec API" }] },
|
|
1247
|
+
{ id: "doing", title: "In Progress", cards: [] },
|
|
1248
|
+
{ id: "done", title: "Done", cards: [{ id: "2", title: "Kickoff" }] },
|
|
1249
|
+
]);
|
|
1250
|
+
<Kanban value={columns} onChange={setColumns} />
|
|
1251
|
+
<Kanban
|
|
1252
|
+
value={columns}
|
|
1253
|
+
onChange={setColumns}
|
|
1254
|
+
onCardClick={(card) => openDetail(card.id)}
|
|
1255
|
+
renderCard={(card) => (
|
|
1256
|
+
<><strong>{card.title}</strong><Badge>{card.id}</Badge></>
|
|
1257
|
+
)}
|
|
1258
|
+
/>
|
|
1259
|
+
const [cols, setCols] = useState([
|
|
1260
|
+
{ id: "doing", title: "In Progress", color: "teal", cards: [
|
|
1261
|
+
{ id: "t1", title: "OAuth", assignee: "AK", progress: 60, color: "warning" },
|
|
1262
|
+
]},
|
|
1263
|
+
]);
|
|
1264
|
+
<Kanban
|
|
1265
|
+
value={cols}
|
|
1266
|
+
onChange={setCols}
|
|
1267
|
+
renderCard={(card) => (
|
|
1268
|
+
<><strong>{card.title}</strong><Progress value={card.progress} /></>
|
|
1269
|
+
)}
|
|
1270
|
+
/>
|
|
1271
|
+
\`\`\`
|
|
1272
|
+
|
|
1273
|
+
**Common mistakes:**
|
|
1274
|
+
- ❌ \`Kanban without onChange (or ignoring it)\` → Store columns in state and setColumns in onChange (v-model in Vue)
|
|
1275
|
+
- ❌ \`Duplicate card ids across columns\` → Use globally-unique card ids across the entire board
|
|
1276
|
+
- ❌ \`Mutating value in place then calling onChange\` → Pass the new array Kanban gives you straight to setState / v-model
|
|
1277
|
+
- ❌ \`color="blue" (or any non-semantic value)\` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
|
|
1278
|
+
|
|
1279
|
+
---
|
|
1280
|
+
|
|
1281
|
+
### Conversation
|
|
1282
|
+
|
|
1283
|
+
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.
|
|
1284
|
+
|
|
1285
|
+
\`\`\`tsx
|
|
1286
|
+
import { Conversation } from "@usevyre/react"
|
|
1287
|
+
|
|
1288
|
+
// Props:
|
|
1289
|
+
// value = ConversationMessage[]
|
|
1290
|
+
// currentUserId = string
|
|
1291
|
+
// composer = boolean (default: false)
|
|
1292
|
+
// onSend = function
|
|
1293
|
+
// placeholder = string (default: Write a message…)
|
|
1294
|
+
// typing = boolean | string (default: false)
|
|
1295
|
+
// allowAttachments = boolean (default: false)
|
|
1296
|
+
// accept = string
|
|
1297
|
+
// renderMessage = function
|
|
1298
|
+
// renderComposer = function
|
|
1299
|
+
|
|
1300
|
+
// Examples:
|
|
1301
|
+
const [messages, setMessages] = useState([
|
|
1302
|
+
{ id: "1", authorId: "sam", authorName: "Sam", text: "Hey!" },
|
|
1303
|
+
{ id: "2", authorId: "me", text: "Hi \ud83d\udc4b", status: "read" },
|
|
1304
|
+
]);
|
|
1305
|
+
<Conversation
|
|
1306
|
+
value={messages}
|
|
1307
|
+
currentUserId="me"
|
|
1308
|
+
composer
|
|
1309
|
+
onSend={(t) => setMessages((m) => [...m, { id: crypto.randomUUID(), authorId: "me", text: t }])}
|
|
1310
|
+
/>
|
|
1311
|
+
<Conversation
|
|
1312
|
+
value={messages}
|
|
1313
|
+
currentUserId="me"
|
|
1314
|
+
typing="Sam is typing"
|
|
1315
|
+
renderMessage={(m) => <strong>{m.text}</strong>}
|
|
1316
|
+
/>
|
|
1317
|
+
const messages = [
|
|
1318
|
+
{ id: "1", authorId: "sam", authorName: "Sam", text: "Moodboard \ud83d\udc47",
|
|
1319
|
+
attachments: [{ kind: "image", url: "/board.png", name: "board.png" }] },
|
|
1320
|
+
{ id: "2", authorId: "me", text: "Specs:", status: "read",
|
|
1321
|
+
attachments: [{ kind: "file", url: "/spec.pdf", name: "spec.pdf", size: "2.4 MB" }] },
|
|
1322
|
+
];
|
|
1323
|
+
<Conversation value={messages} currentUserId="me" />
|
|
1324
|
+
\`\`\`
|
|
1325
|
+
|
|
1326
|
+
**Common mistakes:**
|
|
1327
|
+
- ❌ \`Conversation without currentUserId\` → Always pass currentUserId matching one of the message authorId values
|
|
1328
|
+
- ❌ \`Expecting Conversation to store/append messages\` → Append to your own state in onSend (or @send) and pass it back via value
|
|
1329
|
+
- ❌ \`composer without onSend (React) / @send (Vue)\` → Provide onSend / @send to append the message to value
|
|
1330
|
+
- ❌ \`Treating onSend as (text) only when using allowAttachments\` → Handle onSend(text, files) — map files to message attachments and append
|
|
1331
|
+
|
|
1332
|
+
---
|
|
1333
|
+
|
|
1334
|
+
### DateRangePicker
|
|
1335
|
+
|
|
1336
|
+
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.
|
|
1337
|
+
|
|
1338
|
+
\`\`\`tsx
|
|
1339
|
+
import { DateRangePicker } from "@usevyre/react"
|
|
1340
|
+
|
|
1341
|
+
// Props:
|
|
1342
|
+
// value = { from: Date | null; to: Date | null } | null
|
|
1343
|
+
// onChange = function
|
|
1344
|
+
// placeholder = string (default: Pick a date range)
|
|
1345
|
+
// numberOfMonths = "1" | "2" (default: 2)
|
|
1346
|
+
// presets = boolean | DateRangePreset[] (default: false)
|
|
1347
|
+
// minDate = Date
|
|
1348
|
+
// maxDate = Date
|
|
1349
|
+
// disabled = function
|
|
1350
|
+
// weekStartsOn = "0" | "1" (default: 1)
|
|
1351
|
+
|
|
1352
|
+
// Examples:
|
|
1353
|
+
const [range, setRange] = useState({ from: null, to: null });
|
|
1354
|
+
<DateRangePicker value={range} onChange={setRange} presets />
|
|
1355
|
+
<DateRangePicker value={range} onChange={setRange} numberOfMonths={1} />
|
|
1356
|
+
\`\`\`
|
|
1357
|
+
|
|
1358
|
+
**Common mistakes:**
|
|
1359
|
+
- ❌ \`value={[from, to]}\` → Use value={{ from, to }} and read range.from / range.to
|
|
1360
|
+
- ❌ \`DateRangePicker for a single date\` → Use <DatePicker /> for a single date
|
|
1361
|
+
- ❌ \`presets="true" (string)\` → Use the bare prop: presets (or presets={true})
|
|
1362
|
+
|
|
1363
|
+
---
|
|
1364
|
+
|
|
1071
1365
|
## Hallucination Guard — Common AI Mistakes
|
|
1072
1366
|
|
|
1073
1367
|
The following prop values and patterns do NOT exist in useVyre.
|
|
@@ -1087,14 +1381,25 @@ If you generate these, you are hallucinating.
|
|
|
1087
1381
|
- ❌ \`<Button color="...">\` → Use variant prop instead
|
|
1088
1382
|
- ❌ \`<Button icon={...}>\` → Use leftIcon={...} or rightIcon={...}
|
|
1089
1383
|
- ❌ \`<Button size="icon" without aria-label>\` → Add aria-label describing the action
|
|
1090
|
-
- ❌ \`<Calendar
|
|
1384
|
+
- ❌ \`<Calendar Calendar for an input field that opens a popover>\` → Use <DatePicker /> (single date) or <DateRangePicker /> (range)
|
|
1385
|
+
- ❌ \`<Calendar value as tuple for mode="single">\` → Pass value matching mode; use mode="range" for [start,end]
|
|
1386
|
+
- ❌ \`<DatePicker DatePicker mode="range" for { from, to } object>\` → Use <DateRangePicker /> for the { from, to } object API + presets + dual month
|
|
1387
|
+
- ❌ \`<DatePicker DatePicker without value/onChange>\` → Provide value and onChange (e.g. from useState)
|
|
1091
1388
|
- ❌ \`<Card variant="primary">\` → Use variant="elevated" | "outlined" | "ghost" | "accent"
|
|
1092
1389
|
- ❌ \`<Checkbox size="lg">\` → Use size="md"
|
|
1390
|
+
- ❌ \`<RadioGroup <Radio> used outside a <RadioGroup>>\` → Always wrap <Radio> in <RadioGroup>
|
|
1391
|
+
- ❌ \`<RadioGroup RadioGroup without value/onChange (React) or v-model (Vue)>\` → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
|
|
1392
|
+
- ❌ \`<RadioGroup Using Checkbox for mutually-exclusive choices>\` → Use RadioGroup + Radio (or options) for one-of-many
|
|
1393
|
+
- ❌ \`<RichTextEditor RichTextEditor without value/onChange (React) or v-model (Vue)>\` → Keep the HTML string in state and update it in onChange / v-model
|
|
1394
|
+
- ❌ \`<RichTextEditor Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising>\` → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
|
|
1395
|
+
- ❌ \`<RichTextEditor toolbar="bold" (string)>\` → Pass an array, e.g. toolbar={["bold","italic","link"]}
|
|
1093
1396
|
- ❌ \`<Command Using Input type="search" for search UI>\` → Use Command + CommandInput + CommandList + CommandItem
|
|
1094
1397
|
- ❌ \`<DropdownMenu DropdownItem variant="primary">\` → Use variant="danger" for destructive items only
|
|
1095
1398
|
- ❌ \`<Field Applying state prop directly to Input>\` → Wrap Input in <Field state="error"> to apply validation styling
|
|
1399
|
+
- ❌ \`<Field Mixing props label/hint AND FieldLabel/FieldError for the same field>\` → Pick one: either props-based (label/hint/state) OR composable parts
|
|
1096
1400
|
- ❌ \`<Input size="icon">\` → Use size="sm" | "md" | "lg"
|
|
1097
1401
|
- ❌ \`<Input type="search" for search UI>\` → Import Command from @usevyre/react for search palettes
|
|
1402
|
+
- ❌ \`<Input Vue: binding Input/Textarea value without v-model>\` → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
|
|
1098
1403
|
- ❌ \`<Modal size="xl">\` → Use size="lg" or size="full"
|
|
1099
1404
|
- ❌ \`<Popover placement="top-center">\` → Use placement="top" for centered placement
|
|
1100
1405
|
- ❌ \`<Progress value > 100>\` → Normalize your value to 0–100 range before passing
|
|
@@ -1119,6 +1424,20 @@ If you generate these, you are hallucinating.
|
|
|
1119
1424
|
- ❌ \`<Tag Tag size="xl">\` → Use size="lg"
|
|
1120
1425
|
- ❌ \`<TagGroup TagGroup without Tag children>\` → Place <Tag> elements as direct children
|
|
1121
1426
|
- ❌ \`<TagGroup Using TagGroup for tag input>\` → Use TagsInput for an editable tag field
|
|
1427
|
+
- ❌ \`<Item Card used for repeated list rows>\` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
|
|
1428
|
+
- ❌ \`<Item Item variant="primary">\` → Use variant="default" | "outlined" | "muted"
|
|
1429
|
+
- ❌ \`<Item raw text directly inside Item>\` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
|
|
1430
|
+
- ❌ \`<Kanban Kanban without onChange (or ignoring it)>\` → Store columns in state and setColumns in onChange (v-model in Vue)
|
|
1431
|
+
- ❌ \`<Kanban Duplicate card ids across columns>\` → Use globally-unique card ids across the entire board
|
|
1432
|
+
- ❌ \`<Kanban Mutating value in place then calling onChange>\` → Pass the new array Kanban gives you straight to setState / v-model
|
|
1433
|
+
- ❌ \`<Kanban color="blue" (or any non-semantic value)>\` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
|
|
1434
|
+
- ❌ \`<Conversation Conversation without currentUserId>\` → Always pass currentUserId matching one of the message authorId values
|
|
1435
|
+
- ❌ \`<Conversation Expecting Conversation to store/append messages>\` → Append to your own state in onSend (or @send) and pass it back via value
|
|
1436
|
+
- ❌ \`<Conversation composer without onSend (React) / @send (Vue)>\` → Provide onSend / @send to append the message to value
|
|
1437
|
+
- ❌ \`<Conversation Treating onSend as (text) only when using allowAttachments>\` → Handle onSend(text, files) — map files to message attachments and append
|
|
1438
|
+
- ❌ \`<DateRangePicker value={[from, to]}>\` → Use value={{ from, to }} and read range.from / range.to
|
|
1439
|
+
- ❌ \`<DateRangePicker DateRangePicker for a single date>\` → Use <DatePicker /> for a single date
|
|
1440
|
+
- ❌ \`<DateRangePicker presets="true" (string)>\` → Use the bare prop: presets (or presets={true})
|
|
1122
1441
|
|
|
1123
1442
|
---
|
|
1124
1443
|
|
|
@@ -1173,7 +1492,7 @@ alwaysApply: true
|
|
|
1173
1492
|
---
|
|
1174
1493
|
|
|
1175
1494
|
# useVyre Design System — Cursor Rules
|
|
1176
|
-
# Version: 1.
|
|
1495
|
+
# Version: 1.2.0
|
|
1177
1496
|
|
|
1178
1497
|
You are working in a project using the useVyre design system (@usevyre/react).
|
|
1179
1498
|
Follow these rules strictly when generating any UI code.
|
|
@@ -1252,13 +1571,26 @@ Never do:
|
|
|
1252
1571
|
- ❌ size="icon" without aria-label → ✅ Add aria-label describing the action
|
|
1253
1572
|
|
|
1254
1573
|
## Calendar
|
|
1255
|
-
|
|
1574
|
+
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
1575
|
Import: \`import { Calendar } from "@usevyre/react"\`
|
|
1257
1576
|
|
|
1258
1577
|
Valid props:
|
|
1259
1578
|
|
|
1260
1579
|
Never do:
|
|
1261
|
-
- ❌
|
|
1580
|
+
- ❌ Calendar for an input field that opens a popover → ✅ Use <DatePicker /> (single date) or <DateRangePicker /> (range)
|
|
1581
|
+
- ❌ value as tuple for mode="single" → ✅ Pass value matching mode; use mode="range" for [start,end]
|
|
1582
|
+
|
|
1583
|
+
## DatePicker
|
|
1584
|
+
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.
|
|
1585
|
+
Import: \`import { DatePicker } from "@usevyre/react"\`
|
|
1586
|
+
|
|
1587
|
+
Valid props:
|
|
1588
|
+
- mode: "single" | "range" | "multiple" [default: single]
|
|
1589
|
+
- weekStartsOn: "0" | "1" [default: 1]
|
|
1590
|
+
|
|
1591
|
+
Never do:
|
|
1592
|
+
- ❌ DatePicker mode="range" for { from, to } object → ✅ Use <DateRangePicker /> for the { from, to } object API + presets + dual month
|
|
1593
|
+
- ❌ DatePicker without value/onChange → ✅ Provide value and onChange (e.g. from useState)
|
|
1262
1594
|
|
|
1263
1595
|
## Card
|
|
1264
1596
|
Content container with optional header, body, and footer sections.
|
|
@@ -1280,6 +1612,30 @@ Valid props:
|
|
|
1280
1612
|
Never do:
|
|
1281
1613
|
- ❌ size="lg" → ✅ Use size="md"
|
|
1282
1614
|
|
|
1615
|
+
## RadioGroup
|
|
1616
|
+
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.
|
|
1617
|
+
Import: \`import { RadioGroup, Radio } from "@usevyre/react"\`
|
|
1618
|
+
|
|
1619
|
+
Valid props:
|
|
1620
|
+
- size: "sm" | "md" [default: md]
|
|
1621
|
+
- orientation: "vertical" | "horizontal" [default: vertical]
|
|
1622
|
+
|
|
1623
|
+
Never do:
|
|
1624
|
+
- ❌ <Radio> used outside a <RadioGroup> → ✅ Always wrap <Radio> in <RadioGroup>
|
|
1625
|
+
- ❌ RadioGroup without value/onChange (React) or v-model (Vue) → ✅ Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
|
|
1626
|
+
- ❌ Using Checkbox for mutually-exclusive choices → ✅ Use RadioGroup + Radio (or options) for one-of-many
|
|
1627
|
+
|
|
1628
|
+
## RichTextEditor
|
|
1629
|
+
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.
|
|
1630
|
+
Import: \`import { RichTextEditor } from "@usevyre/react"\`
|
|
1631
|
+
|
|
1632
|
+
Valid props:
|
|
1633
|
+
|
|
1634
|
+
Never do:
|
|
1635
|
+
- ❌ RichTextEditor without value/onChange (React) or v-model (Vue) → ✅ Keep the HTML string in state and update it in onChange / v-model
|
|
1636
|
+
- ❌ Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising → ✅ Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
|
|
1637
|
+
- ❌ toolbar="bold" (string) → ✅ Pass an array, e.g. toolbar={["bold","italic","link"]}
|
|
1638
|
+
|
|
1283
1639
|
## Command
|
|
1284
1640
|
Command palette / search dialog. Use for search-first navigation or quick actions.
|
|
1285
1641
|
Import: \`import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandDialog } from "@usevyre/react"\`
|
|
@@ -1297,7 +1653,7 @@ Never do:
|
|
|
1297
1653
|
- ❌ DropdownItem variant="primary" → ✅ Use variant="danger" for destructive items only
|
|
1298
1654
|
|
|
1299
1655
|
## Field
|
|
1300
|
-
Form field wrapper
|
|
1656
|
+
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
1657
|
Import: \`import { Field, Input, Textarea } from "@usevyre/react"\`
|
|
1302
1658
|
|
|
1303
1659
|
Valid props:
|
|
@@ -1305,6 +1661,7 @@ Valid props:
|
|
|
1305
1661
|
|
|
1306
1662
|
Never do:
|
|
1307
1663
|
- ❌ Applying state prop directly to Input → ✅ Wrap Input in <Field state="error"> to apply validation styling
|
|
1664
|
+
- ❌ Mixing props label/hint AND FieldLabel/FieldError for the same field → ✅ Pick one: either props-based (label/hint/state) OR composable parts
|
|
1308
1665
|
|
|
1309
1666
|
## Input
|
|
1310
1667
|
Text input field. Wrap in Field for labels and validation. Use leftElement/rightElement for icons.
|
|
@@ -1316,6 +1673,7 @@ Valid props:
|
|
|
1316
1673
|
Never do:
|
|
1317
1674
|
- ❌ size="icon" → ✅ Use size="sm" | "md" | "lg"
|
|
1318
1675
|
- ❌ type="search" for search UI → ✅ Import Command from @usevyre/react for search palettes
|
|
1676
|
+
- ❌ Vue: binding Input/Textarea value without v-model → ✅ Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
|
|
1319
1677
|
|
|
1320
1678
|
## Label
|
|
1321
1679
|
Accessible form label. Associate with input via htmlFor.
|
|
@@ -1523,13 +1881,63 @@ Never do:
|
|
|
1523
1881
|
- ❌ TagGroup without Tag children → ✅ Place <Tag> elements as direct children
|
|
1524
1882
|
- ❌ Using TagGroup for tag input → ✅ Use TagsInput for an editable tag field
|
|
1525
1883
|
|
|
1884
|
+
## Item
|
|
1885
|
+
Layout primitive for list rows, settings rows, and notification rows. Denser than Card — use Item (not Card) for repeated list rows.
|
|
1886
|
+
Import: \`import { Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions, ItemGroup } from "@usevyre/react"\`
|
|
1887
|
+
|
|
1888
|
+
Valid props:
|
|
1889
|
+
- variant: "default" | "outlined" | "muted" | "plain" [default: default]
|
|
1890
|
+
- size: "sm" | "md" | "lg" [default: md]
|
|
1891
|
+
|
|
1892
|
+
Never do:
|
|
1893
|
+
- ❌ Card used for repeated list rows → ✅ Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
|
|
1894
|
+
- ❌ Item variant="primary" → ✅ Use variant="default" | "outlined" | "muted"
|
|
1895
|
+
- ❌ raw text directly inside Item → ✅ Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
|
|
1896
|
+
|
|
1897
|
+
## Kanban
|
|
1898
|
+
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.
|
|
1899
|
+
Import: \`import { Kanban } from "@usevyre/react"\`
|
|
1900
|
+
|
|
1901
|
+
Valid props:
|
|
1902
|
+
|
|
1903
|
+
Never do:
|
|
1904
|
+
- ❌ Kanban without onChange (or ignoring it) → ✅ Store columns in state and setColumns in onChange (v-model in Vue)
|
|
1905
|
+
- ❌ Duplicate card ids across columns → ✅ Use globally-unique card ids across the entire board
|
|
1906
|
+
- ❌ Mutating value in place then calling onChange → ✅ Pass the new array Kanban gives you straight to setState / v-model
|
|
1907
|
+
- ❌ color="blue" (or any non-semantic value) → ✅ Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
|
|
1908
|
+
|
|
1909
|
+
## Conversation
|
|
1910
|
+
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.
|
|
1911
|
+
Import: \`import { Conversation } from "@usevyre/react"\`
|
|
1912
|
+
|
|
1913
|
+
Valid props:
|
|
1914
|
+
|
|
1915
|
+
Never do:
|
|
1916
|
+
- ❌ Conversation without currentUserId → ✅ Always pass currentUserId matching one of the message authorId values
|
|
1917
|
+
- ❌ Expecting Conversation to store/append messages → ✅ Append to your own state in onSend (or @send) and pass it back via value
|
|
1918
|
+
- ❌ composer without onSend (React) / @send (Vue) → ✅ Provide onSend / @send to append the message to value
|
|
1919
|
+
- ❌ Treating onSend as (text) only when using allowAttachments → ✅ Handle onSend(text, files) — map files to message attachments and append
|
|
1920
|
+
|
|
1921
|
+
## DateRangePicker
|
|
1922
|
+
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.
|
|
1923
|
+
Import: \`import { DateRangePicker } from "@usevyre/react"\`
|
|
1924
|
+
|
|
1925
|
+
Valid props:
|
|
1926
|
+
- numberOfMonths: "1" | "2" [default: 2]
|
|
1927
|
+
- weekStartsOn: "0" | "1" [default: 1]
|
|
1928
|
+
|
|
1929
|
+
Never do:
|
|
1930
|
+
- ❌ value={[from, to]} → ✅ Use value={{ from, to }} and read range.from / range.to
|
|
1931
|
+
- ❌ DateRangePicker for a single date → ✅ Use <DatePicker /> for a single date
|
|
1932
|
+
- ❌ presets="true" (string) → ✅ Use the bare prop: presets (or presets={true})
|
|
1933
|
+
|
|
1526
1934
|
## Token Rules
|
|
1527
1935
|
|
|
1528
1936
|
Use --vyre-color-semantic-* for all colors. Never use primitive tokens.
|
|
1529
1937
|
Use --vyre-spacing-* for all spacing. Never use raw px in component code.
|
|
1530
1938
|
Use --vyre-border-radius-* for border radius.`;
|
|
1531
1939
|
export const claudeContext = `# useVyre Design System Context
|
|
1532
|
-
# Version: 1.
|
|
1940
|
+
# Version: 1.2.0
|
|
1533
1941
|
|
|
1534
1942
|
You are working in a codebase that uses the useVyre design system.
|
|
1535
1943
|
Follow the rules below strictly when writing any UI code.
|
|
@@ -1841,7 +2249,7 @@ import { Button } from "@usevyre/react"
|
|
|
1841
2249
|
|
|
1842
2250
|
### Calendar
|
|
1843
2251
|
|
|
1844
|
-
|
|
2252
|
+
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
2253
|
|
|
1846
2254
|
\`\`\`tsx
|
|
1847
2255
|
import { Calendar } from "@usevyre/react"
|
|
@@ -1850,6 +2258,7 @@ import { Calendar } from "@usevyre/react"
|
|
|
1850
2258
|
// value = Date | null
|
|
1851
2259
|
// onChange = function
|
|
1852
2260
|
// disabled = boolean (default: false)
|
|
2261
|
+
// defaultMonth = Date
|
|
1853
2262
|
|
|
1854
2263
|
// Examples:
|
|
1855
2264
|
const [date, setDate] = useState(null);
|
|
@@ -1857,7 +2266,39 @@ const [date, setDate] = useState(null);
|
|
|
1857
2266
|
\`\`\`
|
|
1858
2267
|
|
|
1859
2268
|
**Common mistakes:**
|
|
1860
|
-
- ❌ \`
|
|
2269
|
+
- ❌ \`Calendar for an input field that opens a popover\` → Use <DatePicker /> (single date) or <DateRangePicker /> (range)
|
|
2270
|
+
- ❌ \`value as tuple for mode="single"\` → Pass value matching mode; use mode="range" for [start,end]
|
|
2271
|
+
|
|
2272
|
+
---
|
|
2273
|
+
|
|
2274
|
+
### DatePicker
|
|
2275
|
+
|
|
2276
|
+
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.
|
|
2277
|
+
|
|
2278
|
+
\`\`\`tsx
|
|
2279
|
+
import { DatePicker } from "@usevyre/react"
|
|
2280
|
+
|
|
2281
|
+
// Props:
|
|
2282
|
+
// value = Date | [Date, Date] | Date[] | null
|
|
2283
|
+
// onChange = function
|
|
2284
|
+
// mode = "single" | "range" | "multiple" (default: single)
|
|
2285
|
+
// placeholder = string (default: Pick a date)
|
|
2286
|
+
// showTime = boolean (default: false)
|
|
2287
|
+
// minDate = Date
|
|
2288
|
+
// maxDate = Date
|
|
2289
|
+
// disabled = function
|
|
2290
|
+
// weekStartsOn = "0" | "1" (default: 1)
|
|
2291
|
+
// inputClassName = string
|
|
2292
|
+
|
|
2293
|
+
// Examples:
|
|
2294
|
+
const [date, setDate] = useState(null);
|
|
2295
|
+
<DatePicker value={date} onChange={setDate} placeholder="Pick a date" />
|
|
2296
|
+
<DatePicker value={date} onChange={setDate} showTime />
|
|
2297
|
+
\`\`\`
|
|
2298
|
+
|
|
2299
|
+
**Common mistakes:**
|
|
2300
|
+
- ❌ \`DatePicker mode="range" for { from, to } object\` → Use <DateRangePicker /> for the { from, to } object API + presets + dual month
|
|
2301
|
+
- ❌ \`DatePicker without value/onChange\` → Provide value and onChange (e.g. from useState)
|
|
1861
2302
|
|
|
1862
2303
|
---
|
|
1863
2304
|
|
|
@@ -1917,6 +2358,78 @@ import { Checkbox } from "@usevyre/react"
|
|
|
1917
2358
|
|
|
1918
2359
|
---
|
|
1919
2360
|
|
|
2361
|
+
### RadioGroup
|
|
2362
|
+
|
|
2363
|
+
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.
|
|
2364
|
+
|
|
2365
|
+
\`\`\`tsx
|
|
2366
|
+
import { RadioGroup, Radio } from "@usevyre/react"
|
|
2367
|
+
|
|
2368
|
+
// Props:
|
|
2369
|
+
// value = string
|
|
2370
|
+
// defaultValue = string
|
|
2371
|
+
// onChange = function
|
|
2372
|
+
// name = string
|
|
2373
|
+
// disabled = boolean (default: false)
|
|
2374
|
+
// size = "sm" | "md" (default: md)
|
|
2375
|
+
// orientation = "vertical" | "horizontal" (default: vertical)
|
|
2376
|
+
// options = { value: string; label?: string; description?: string; disabled?: boolean }[]
|
|
2377
|
+
|
|
2378
|
+
// Examples:
|
|
2379
|
+
<RadioGroup
|
|
2380
|
+
value={plan}
|
|
2381
|
+
onChange={setPlan}
|
|
2382
|
+
options={[
|
|
2383
|
+
{ value: "free", label: "Free", description: "For hobby projects" },
|
|
2384
|
+
{ value: "pro", label: "Pro", description: "For teams" },
|
|
2385
|
+
]}
|
|
2386
|
+
/>
|
|
2387
|
+
<RadioGroup value={plan} onChange={setPlan} orientation="horizontal">
|
|
2388
|
+
<Radio value="free" label="Free" />
|
|
2389
|
+
<Radio value="pro" label="Pro" />
|
|
2390
|
+
</RadioGroup>
|
|
2391
|
+
\`\`\`
|
|
2392
|
+
|
|
2393
|
+
**Common mistakes:**
|
|
2394
|
+
- ❌ \`<Radio> used outside a <RadioGroup>\` → Always wrap <Radio> in <RadioGroup>
|
|
2395
|
+
- ❌ \`RadioGroup without value/onChange (React) or v-model (Vue)\` → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
|
|
2396
|
+
- ❌ \`Using Checkbox for mutually-exclusive choices\` → Use RadioGroup + Radio (or options) for one-of-many
|
|
2397
|
+
|
|
2398
|
+
---
|
|
2399
|
+
|
|
2400
|
+
### RichTextEditor
|
|
2401
|
+
|
|
2402
|
+
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.
|
|
2403
|
+
|
|
2404
|
+
\`\`\`tsx
|
|
2405
|
+
import { RichTextEditor } from "@usevyre/react"
|
|
2406
|
+
|
|
2407
|
+
// Props:
|
|
2408
|
+
// value = string
|
|
2409
|
+
// onChange = function
|
|
2410
|
+
// placeholder = string (default: Write something…)
|
|
2411
|
+
// disabled = boolean (default: false)
|
|
2412
|
+
// readOnly = boolean (default: false)
|
|
2413
|
+
// toolbar = RichTextTool[]
|
|
2414
|
+
// minHeight = string (default: 10rem)
|
|
2415
|
+
|
|
2416
|
+
// Examples:
|
|
2417
|
+
const [html, setHtml] = useState("<p>Hello <strong>world</strong></p>");
|
|
2418
|
+
<RichTextEditor value={html} onChange={setHtml} placeholder="Write…" />
|
|
2419
|
+
<RichTextEditor
|
|
2420
|
+
value={html}
|
|
2421
|
+
onChange={setHtml}
|
|
2422
|
+
toolbar={["bold", "italic", "link"]}
|
|
2423
|
+
/>
|
|
2424
|
+
\`\`\`
|
|
2425
|
+
|
|
2426
|
+
**Common mistakes:**
|
|
2427
|
+
- ❌ \`RichTextEditor without value/onChange (React) or v-model (Vue)\` → Keep the HTML string in state and update it in onChange / v-model
|
|
2428
|
+
- ❌ \`Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising\` → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
|
|
2429
|
+
- ❌ \`toolbar="bold" (string)\` → Pass an array, e.g. toolbar={["bold","italic","link"]}
|
|
2430
|
+
|
|
2431
|
+
---
|
|
2432
|
+
|
|
1920
2433
|
### Command
|
|
1921
2434
|
|
|
1922
2435
|
Command palette / search dialog. Use for search-first navigation or quick actions.
|
|
@@ -1968,7 +2481,7 @@ import { DropdownMenu, DropdownItem, DropdownSeparator, DropdownCheckboxItem, Dr
|
|
|
1968
2481
|
|
|
1969
2482
|
### Field
|
|
1970
2483
|
|
|
1971
|
-
Form field wrapper
|
|
2484
|
+
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
2485
|
|
|
1973
2486
|
\`\`\`tsx
|
|
1974
2487
|
import { Field, Input, Textarea } from "@usevyre/react"
|
|
@@ -1986,10 +2499,23 @@ import { Field, Input, Textarea } from "@usevyre/react"
|
|
|
1986
2499
|
<Field label="Search">
|
|
1987
2500
|
<Input leftElement={<SearchIcon />} placeholder="Search..." />
|
|
1988
2501
|
</Field>
|
|
2502
|
+
<Field>
|
|
2503
|
+
<FieldLabel required htmlFor="email">Email</FieldLabel>
|
|
2504
|
+
<Input id="email" type="email" />
|
|
2505
|
+
<FieldDescription>We\u2019ll never share it.</FieldDescription>
|
|
2506
|
+
<FieldError>{errors.email}</FieldError>
|
|
2507
|
+
</Field>
|
|
2508
|
+
|
|
2509
|
+
// Two controls side by side
|
|
2510
|
+
<FieldGroup orientation="horizontal">
|
|
2511
|
+
<Field label="First name"><Input /></Field>
|
|
2512
|
+
<Field label="Last name"><Input /></Field>
|
|
2513
|
+
</FieldGroup>
|
|
1989
2514
|
\`\`\`
|
|
1990
2515
|
|
|
1991
2516
|
**Common mistakes:**
|
|
1992
2517
|
- ❌ \`Applying state prop directly to Input\` → Wrap Input in <Field state="error"> to apply validation styling
|
|
2518
|
+
- ❌ \`Mixing props label/hint AND FieldLabel/FieldError for the same field\` → Pick one: either props-based (label/hint/state) OR composable parts
|
|
1993
2519
|
|
|
1994
2520
|
---
|
|
1995
2521
|
|
|
@@ -2001,6 +2527,7 @@ Text input field. Wrap in Field for labels and validation. Use leftElement/right
|
|
|
2001
2527
|
import { Input } from "@usevyre/react"
|
|
2002
2528
|
|
|
2003
2529
|
// Props:
|
|
2530
|
+
// modelValue = string | number
|
|
2004
2531
|
// size = "sm" | "md" | "lg" (default: md)
|
|
2005
2532
|
// leftElement = ReactNode
|
|
2006
2533
|
// rightElement = ReactNode
|
|
@@ -2012,6 +2539,7 @@ import { Input } from "@usevyre/react"
|
|
|
2012
2539
|
**Common mistakes:**
|
|
2013
2540
|
- ❌ \`size="icon"\` → Use size="sm" | "md" | "lg"
|
|
2014
2541
|
- ❌ \`type="search" for search UI\` → Import Command from @usevyre/react for search palettes
|
|
2542
|
+
- ❌ \`Vue: binding Input/Textarea value without v-model\` → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
|
|
2015
2543
|
|
|
2016
2544
|
---
|
|
2017
2545
|
|
|
@@ -2599,6 +3127,180 @@ import { TagGroup, Tag } from "@usevyre/react"
|
|
|
2599
3127
|
|
|
2600
3128
|
---
|
|
2601
3129
|
|
|
3130
|
+
### Item
|
|
3131
|
+
|
|
3132
|
+
Layout primitive for list rows, settings rows, and notification rows. Denser than Card — use Item (not Card) for repeated list rows.
|
|
3133
|
+
|
|
3134
|
+
\`\`\`tsx
|
|
3135
|
+
import { Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions, ItemGroup } from "@usevyre/react"
|
|
3136
|
+
|
|
3137
|
+
// Props:
|
|
3138
|
+
// variant = "default" | "outlined" | "muted" | "plain" (default: default)
|
|
3139
|
+
// size = "sm" | "md" | "lg" (default: md)
|
|
3140
|
+
// clickable = boolean (default: false)
|
|
3141
|
+
|
|
3142
|
+
// Examples:
|
|
3143
|
+
<Item>
|
|
3144
|
+
<ItemMedia><BellIcon /></ItemMedia>
|
|
3145
|
+
<ItemContent>
|
|
3146
|
+
<ItemTitle>Notifications</ItemTitle>
|
|
3147
|
+
<ItemDescription>Receive an email when someone mentions you.</ItemDescription>
|
|
3148
|
+
</ItemContent>
|
|
3149
|
+
<ItemActions>
|
|
3150
|
+
<Switch defaultChecked />
|
|
3151
|
+
</ItemActions>
|
|
3152
|
+
</Item>
|
|
3153
|
+
<ItemGroup separated>
|
|
3154
|
+
<Item clickable>
|
|
3155
|
+
<ItemContent><ItemTitle>Profile</ItemTitle></ItemContent>
|
|
3156
|
+
</Item>
|
|
3157
|
+
<Item clickable>
|
|
3158
|
+
<ItemContent><ItemTitle>Billing</ItemTitle></ItemContent>
|
|
3159
|
+
</Item>
|
|
3160
|
+
</ItemGroup>
|
|
3161
|
+
\`\`\`
|
|
3162
|
+
|
|
3163
|
+
**Common mistakes:**
|
|
3164
|
+
- ❌ \`Card used for repeated list rows\` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
|
|
3165
|
+
- ❌ \`Item variant="primary"\` → Use variant="default" | "outlined" | "muted"
|
|
3166
|
+
- ❌ \`raw text directly inside Item\` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
|
|
3167
|
+
|
|
3168
|
+
---
|
|
3169
|
+
|
|
3170
|
+
### Kanban
|
|
3171
|
+
|
|
3172
|
+
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.
|
|
3173
|
+
|
|
3174
|
+
\`\`\`tsx
|
|
3175
|
+
import { Kanban } from "@usevyre/react"
|
|
3176
|
+
|
|
3177
|
+
// Props:
|
|
3178
|
+
// value = KanbanColumn[]
|
|
3179
|
+
// onChange = function
|
|
3180
|
+
// renderCard = function
|
|
3181
|
+
// onCardClick = function
|
|
3182
|
+
|
|
3183
|
+
// Examples:
|
|
3184
|
+
const [columns, setColumns] = useState([
|
|
3185
|
+
{ id: "todo", title: "To Do", cards: [{ id: "1", title: "Spec API" }] },
|
|
3186
|
+
{ id: "doing", title: "In Progress", cards: [] },
|
|
3187
|
+
{ id: "done", title: "Done", cards: [{ id: "2", title: "Kickoff" }] },
|
|
3188
|
+
]);
|
|
3189
|
+
<Kanban value={columns} onChange={setColumns} />
|
|
3190
|
+
<Kanban
|
|
3191
|
+
value={columns}
|
|
3192
|
+
onChange={setColumns}
|
|
3193
|
+
onCardClick={(card) => openDetail(card.id)}
|
|
3194
|
+
renderCard={(card) => (
|
|
3195
|
+
<><strong>{card.title}</strong><Badge>{card.id}</Badge></>
|
|
3196
|
+
)}
|
|
3197
|
+
/>
|
|
3198
|
+
const [cols, setCols] = useState([
|
|
3199
|
+
{ id: "doing", title: "In Progress", color: "teal", cards: [
|
|
3200
|
+
{ id: "t1", title: "OAuth", assignee: "AK", progress: 60, color: "warning" },
|
|
3201
|
+
]},
|
|
3202
|
+
]);
|
|
3203
|
+
<Kanban
|
|
3204
|
+
value={cols}
|
|
3205
|
+
onChange={setCols}
|
|
3206
|
+
renderCard={(card) => (
|
|
3207
|
+
<><strong>{card.title}</strong><Progress value={card.progress} /></>
|
|
3208
|
+
)}
|
|
3209
|
+
/>
|
|
3210
|
+
\`\`\`
|
|
3211
|
+
|
|
3212
|
+
**Common mistakes:**
|
|
3213
|
+
- ❌ \`Kanban without onChange (or ignoring it)\` → Store columns in state and setColumns in onChange (v-model in Vue)
|
|
3214
|
+
- ❌ \`Duplicate card ids across columns\` → Use globally-unique card ids across the entire board
|
|
3215
|
+
- ❌ \`Mutating value in place then calling onChange\` → Pass the new array Kanban gives you straight to setState / v-model
|
|
3216
|
+
- ❌ \`color="blue" (or any non-semantic value)\` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
|
|
3217
|
+
|
|
3218
|
+
---
|
|
3219
|
+
|
|
3220
|
+
### Conversation
|
|
3221
|
+
|
|
3222
|
+
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.
|
|
3223
|
+
|
|
3224
|
+
\`\`\`tsx
|
|
3225
|
+
import { Conversation } from "@usevyre/react"
|
|
3226
|
+
|
|
3227
|
+
// Props:
|
|
3228
|
+
// value = ConversationMessage[]
|
|
3229
|
+
// currentUserId = string
|
|
3230
|
+
// composer = boolean (default: false)
|
|
3231
|
+
// onSend = function
|
|
3232
|
+
// placeholder = string (default: Write a message…)
|
|
3233
|
+
// typing = boolean | string (default: false)
|
|
3234
|
+
// allowAttachments = boolean (default: false)
|
|
3235
|
+
// accept = string
|
|
3236
|
+
// renderMessage = function
|
|
3237
|
+
// renderComposer = function
|
|
3238
|
+
|
|
3239
|
+
// Examples:
|
|
3240
|
+
const [messages, setMessages] = useState([
|
|
3241
|
+
{ id: "1", authorId: "sam", authorName: "Sam", text: "Hey!" },
|
|
3242
|
+
{ id: "2", authorId: "me", text: "Hi \ud83d\udc4b", status: "read" },
|
|
3243
|
+
]);
|
|
3244
|
+
<Conversation
|
|
3245
|
+
value={messages}
|
|
3246
|
+
currentUserId="me"
|
|
3247
|
+
composer
|
|
3248
|
+
onSend={(t) => setMessages((m) => [...m, { id: crypto.randomUUID(), authorId: "me", text: t }])}
|
|
3249
|
+
/>
|
|
3250
|
+
<Conversation
|
|
3251
|
+
value={messages}
|
|
3252
|
+
currentUserId="me"
|
|
3253
|
+
typing="Sam is typing"
|
|
3254
|
+
renderMessage={(m) => <strong>{m.text}</strong>}
|
|
3255
|
+
/>
|
|
3256
|
+
const messages = [
|
|
3257
|
+
{ id: "1", authorId: "sam", authorName: "Sam", text: "Moodboard \ud83d\udc47",
|
|
3258
|
+
attachments: [{ kind: "image", url: "/board.png", name: "board.png" }] },
|
|
3259
|
+
{ id: "2", authorId: "me", text: "Specs:", status: "read",
|
|
3260
|
+
attachments: [{ kind: "file", url: "/spec.pdf", name: "spec.pdf", size: "2.4 MB" }] },
|
|
3261
|
+
];
|
|
3262
|
+
<Conversation value={messages} currentUserId="me" />
|
|
3263
|
+
\`\`\`
|
|
3264
|
+
|
|
3265
|
+
**Common mistakes:**
|
|
3266
|
+
- ❌ \`Conversation without currentUserId\` → Always pass currentUserId matching one of the message authorId values
|
|
3267
|
+
- ❌ \`Expecting Conversation to store/append messages\` → Append to your own state in onSend (or @send) and pass it back via value
|
|
3268
|
+
- ❌ \`composer without onSend (React) / @send (Vue)\` → Provide onSend / @send to append the message to value
|
|
3269
|
+
- ❌ \`Treating onSend as (text) only when using allowAttachments\` → Handle onSend(text, files) — map files to message attachments and append
|
|
3270
|
+
|
|
3271
|
+
---
|
|
3272
|
+
|
|
3273
|
+
### DateRangePicker
|
|
3274
|
+
|
|
3275
|
+
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.
|
|
3276
|
+
|
|
3277
|
+
\`\`\`tsx
|
|
3278
|
+
import { DateRangePicker } from "@usevyre/react"
|
|
3279
|
+
|
|
3280
|
+
// Props:
|
|
3281
|
+
// value = { from: Date | null; to: Date | null } | null
|
|
3282
|
+
// onChange = function
|
|
3283
|
+
// placeholder = string (default: Pick a date range)
|
|
3284
|
+
// numberOfMonths = "1" | "2" (default: 2)
|
|
3285
|
+
// presets = boolean | DateRangePreset[] (default: false)
|
|
3286
|
+
// minDate = Date
|
|
3287
|
+
// maxDate = Date
|
|
3288
|
+
// disabled = function
|
|
3289
|
+
// weekStartsOn = "0" | "1" (default: 1)
|
|
3290
|
+
|
|
3291
|
+
// Examples:
|
|
3292
|
+
const [range, setRange] = useState({ from: null, to: null });
|
|
3293
|
+
<DateRangePicker value={range} onChange={setRange} presets />
|
|
3294
|
+
<DateRangePicker value={range} onChange={setRange} numberOfMonths={1} />
|
|
3295
|
+
\`\`\`
|
|
3296
|
+
|
|
3297
|
+
**Common mistakes:**
|
|
3298
|
+
- ❌ \`value={[from, to]}\` → Use value={{ from, to }} and read range.from / range.to
|
|
3299
|
+
- ❌ \`DateRangePicker for a single date\` → Use <DatePicker /> for a single date
|
|
3300
|
+
- ❌ \`presets="true" (string)\` → Use the bare prop: presets (or presets={true})
|
|
3301
|
+
|
|
3302
|
+
---
|
|
3303
|
+
|
|
2602
3304
|
## Hallucination Guard — Common AI Mistakes
|
|
2603
3305
|
|
|
2604
3306
|
The following prop values and patterns do NOT exist in useVyre.
|
|
@@ -2618,14 +3320,25 @@ If you generate these, you are hallucinating.
|
|
|
2618
3320
|
- ❌ \`<Button color="...">\` → Use variant prop instead
|
|
2619
3321
|
- ❌ \`<Button icon={...}>\` → Use leftIcon={...} or rightIcon={...}
|
|
2620
3322
|
- ❌ \`<Button size="icon" without aria-label>\` → Add aria-label describing the action
|
|
2621
|
-
- ❌ \`<Calendar
|
|
3323
|
+
- ❌ \`<Calendar Calendar for an input field that opens a popover>\` → Use <DatePicker /> (single date) or <DateRangePicker /> (range)
|
|
3324
|
+
- ❌ \`<Calendar value as tuple for mode="single">\` → Pass value matching mode; use mode="range" for [start,end]
|
|
3325
|
+
- ❌ \`<DatePicker DatePicker mode="range" for { from, to } object>\` → Use <DateRangePicker /> for the { from, to } object API + presets + dual month
|
|
3326
|
+
- ❌ \`<DatePicker DatePicker without value/onChange>\` → Provide value and onChange (e.g. from useState)
|
|
2622
3327
|
- ❌ \`<Card variant="primary">\` → Use variant="elevated" | "outlined" | "ghost" | "accent"
|
|
2623
3328
|
- ❌ \`<Checkbox size="lg">\` → Use size="md"
|
|
3329
|
+
- ❌ \`<RadioGroup <Radio> used outside a <RadioGroup>>\` → Always wrap <Radio> in <RadioGroup>
|
|
3330
|
+
- ❌ \`<RadioGroup RadioGroup without value/onChange (React) or v-model (Vue)>\` → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
|
|
3331
|
+
- ❌ \`<RadioGroup Using Checkbox for mutually-exclusive choices>\` → Use RadioGroup + Radio (or options) for one-of-many
|
|
3332
|
+
- ❌ \`<RichTextEditor RichTextEditor without value/onChange (React) or v-model (Vue)>\` → Keep the HTML string in state and update it in onChange / v-model
|
|
3333
|
+
- ❌ \`<RichTextEditor Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising>\` → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
|
|
3334
|
+
- ❌ \`<RichTextEditor toolbar="bold" (string)>\` → Pass an array, e.g. toolbar={["bold","italic","link"]}
|
|
2624
3335
|
- ❌ \`<Command Using Input type="search" for search UI>\` → Use Command + CommandInput + CommandList + CommandItem
|
|
2625
3336
|
- ❌ \`<DropdownMenu DropdownItem variant="primary">\` → Use variant="danger" for destructive items only
|
|
2626
3337
|
- ❌ \`<Field Applying state prop directly to Input>\` → Wrap Input in <Field state="error"> to apply validation styling
|
|
3338
|
+
- ❌ \`<Field Mixing props label/hint AND FieldLabel/FieldError for the same field>\` → Pick one: either props-based (label/hint/state) OR composable parts
|
|
2627
3339
|
- ❌ \`<Input size="icon">\` → Use size="sm" | "md" | "lg"
|
|
2628
3340
|
- ❌ \`<Input type="search" for search UI>\` → Import Command from @usevyre/react for search palettes
|
|
3341
|
+
- ❌ \`<Input Vue: binding Input/Textarea value without v-model>\` → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
|
|
2629
3342
|
- ❌ \`<Modal size="xl">\` → Use size="lg" or size="full"
|
|
2630
3343
|
- ❌ \`<Popover placement="top-center">\` → Use placement="top" for centered placement
|
|
2631
3344
|
- ❌ \`<Progress value > 100>\` → Normalize your value to 0–100 range before passing
|
|
@@ -2650,6 +3363,20 @@ If you generate these, you are hallucinating.
|
|
|
2650
3363
|
- ❌ \`<Tag Tag size="xl">\` → Use size="lg"
|
|
2651
3364
|
- ❌ \`<TagGroup TagGroup without Tag children>\` → Place <Tag> elements as direct children
|
|
2652
3365
|
- ❌ \`<TagGroup Using TagGroup for tag input>\` → Use TagsInput for an editable tag field
|
|
3366
|
+
- ❌ \`<Item Card used for repeated list rows>\` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
|
|
3367
|
+
- ❌ \`<Item Item variant="primary">\` → Use variant="default" | "outlined" | "muted"
|
|
3368
|
+
- ❌ \`<Item raw text directly inside Item>\` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
|
|
3369
|
+
- ❌ \`<Kanban Kanban without onChange (or ignoring it)>\` → Store columns in state and setColumns in onChange (v-model in Vue)
|
|
3370
|
+
- ❌ \`<Kanban Duplicate card ids across columns>\` → Use globally-unique card ids across the entire board
|
|
3371
|
+
- ❌ \`<Kanban Mutating value in place then calling onChange>\` → Pass the new array Kanban gives you straight to setState / v-model
|
|
3372
|
+
- ❌ \`<Kanban color="blue" (or any non-semantic value)>\` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
|
|
3373
|
+
- ❌ \`<Conversation Conversation without currentUserId>\` → Always pass currentUserId matching one of the message authorId values
|
|
3374
|
+
- ❌ \`<Conversation Expecting Conversation to store/append messages>\` → Append to your own state in onSend (or @send) and pass it back via value
|
|
3375
|
+
- ❌ \`<Conversation composer without onSend (React) / @send (Vue)>\` → Provide onSend / @send to append the message to value
|
|
3376
|
+
- ❌ \`<Conversation Treating onSend as (text) only when using allowAttachments>\` → Handle onSend(text, files) — map files to message attachments and append
|
|
3377
|
+
- ❌ \`<DateRangePicker value={[from, to]}>\` → Use value={{ from, to }} and read range.from / range.to
|
|
3378
|
+
- ❌ \`<DateRangePicker DateRangePicker for a single date>\` → Use <DatePicker /> for a single date
|
|
3379
|
+
- ❌ \`<DateRangePicker presets="true" (string)>\` → Use the bare prop: presets (or presets={true})
|
|
2653
3380
|
|
|
2654
3381
|
---
|
|
2655
3382
|
|
|
@@ -2699,7 +3426,7 @@ If you generate these, you are hallucinating.
|
|
|
2699
3426
|
\`\`\`
|
|
2700
3427
|
`;
|
|
2701
3428
|
export const windsurfRules = `# useVyre Rules for Windsurf
|
|
2702
|
-
# Version: 1.
|
|
3429
|
+
# Version: 1.2.0
|
|
2703
3430
|
|
|
2704
3431
|
# useVyre Design System — AI Context
|
|
2705
3432
|
# Version: 0.2.0
|
|
@@ -3008,7 +3735,7 @@ import { Button } from "@usevyre/react"
|
|
|
3008
3735
|
|
|
3009
3736
|
### Calendar
|
|
3010
3737
|
|
|
3011
|
-
|
|
3738
|
+
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
3739
|
|
|
3013
3740
|
\`\`\`tsx
|
|
3014
3741
|
import { Calendar } from "@usevyre/react"
|
|
@@ -3017,6 +3744,7 @@ import { Calendar } from "@usevyre/react"
|
|
|
3017
3744
|
// value = Date | null
|
|
3018
3745
|
// onChange = function
|
|
3019
3746
|
// disabled = boolean (default: false)
|
|
3747
|
+
// defaultMonth = Date
|
|
3020
3748
|
|
|
3021
3749
|
// Examples:
|
|
3022
3750
|
const [date, setDate] = useState(null);
|
|
@@ -3024,16 +3752,48 @@ const [date, setDate] = useState(null);
|
|
|
3024
3752
|
\`\`\`
|
|
3025
3753
|
|
|
3026
3754
|
**Common mistakes:**
|
|
3027
|
-
- ❌ \`
|
|
3755
|
+
- ❌ \`Calendar for an input field that opens a popover\` → Use <DatePicker /> (single date) or <DateRangePicker /> (range)
|
|
3756
|
+
- ❌ \`value as tuple for mode="single"\` → Pass value matching mode; use mode="range" for [start,end]
|
|
3028
3757
|
|
|
3029
3758
|
---
|
|
3030
3759
|
|
|
3031
|
-
###
|
|
3760
|
+
### DatePicker
|
|
3032
3761
|
|
|
3033
|
-
|
|
3762
|
+
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.
|
|
3034
3763
|
|
|
3035
3764
|
\`\`\`tsx
|
|
3036
|
-
import {
|
|
3765
|
+
import { DatePicker } from "@usevyre/react"
|
|
3766
|
+
|
|
3767
|
+
// Props:
|
|
3768
|
+
// value = Date | [Date, Date] | Date[] | null
|
|
3769
|
+
// onChange = function
|
|
3770
|
+
// mode = "single" | "range" | "multiple" (default: single)
|
|
3771
|
+
// placeholder = string (default: Pick a date)
|
|
3772
|
+
// showTime = boolean (default: false)
|
|
3773
|
+
// minDate = Date
|
|
3774
|
+
// maxDate = Date
|
|
3775
|
+
// disabled = function
|
|
3776
|
+
// weekStartsOn = "0" | "1" (default: 1)
|
|
3777
|
+
// inputClassName = string
|
|
3778
|
+
|
|
3779
|
+
// Examples:
|
|
3780
|
+
const [date, setDate] = useState(null);
|
|
3781
|
+
<DatePicker value={date} onChange={setDate} placeholder="Pick a date" />
|
|
3782
|
+
<DatePicker value={date} onChange={setDate} showTime />
|
|
3783
|
+
\`\`\`
|
|
3784
|
+
|
|
3785
|
+
**Common mistakes:**
|
|
3786
|
+
- ❌ \`DatePicker mode="range" for { from, to } object\` → Use <DateRangePicker /> for the { from, to } object API + presets + dual month
|
|
3787
|
+
- ❌ \`DatePicker without value/onChange\` → Provide value and onChange (e.g. from useState)
|
|
3788
|
+
|
|
3789
|
+
---
|
|
3790
|
+
|
|
3791
|
+
### Card
|
|
3792
|
+
|
|
3793
|
+
Content container with optional header, body, and footer sections.
|
|
3794
|
+
|
|
3795
|
+
\`\`\`tsx
|
|
3796
|
+
import { Card, CardHeader, CardBody, CardFooter } from "@usevyre/react"
|
|
3037
3797
|
|
|
3038
3798
|
// Props:
|
|
3039
3799
|
// variant = "default" | "elevated" | "outlined" | "ghost" | "accent" (default: default)
|
|
@@ -3084,6 +3844,78 @@ import { Checkbox } from "@usevyre/react"
|
|
|
3084
3844
|
|
|
3085
3845
|
---
|
|
3086
3846
|
|
|
3847
|
+
### RadioGroup
|
|
3848
|
+
|
|
3849
|
+
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.
|
|
3850
|
+
|
|
3851
|
+
\`\`\`tsx
|
|
3852
|
+
import { RadioGroup, Radio } from "@usevyre/react"
|
|
3853
|
+
|
|
3854
|
+
// Props:
|
|
3855
|
+
// value = string
|
|
3856
|
+
// defaultValue = string
|
|
3857
|
+
// onChange = function
|
|
3858
|
+
// name = string
|
|
3859
|
+
// disabled = boolean (default: false)
|
|
3860
|
+
// size = "sm" | "md" (default: md)
|
|
3861
|
+
// orientation = "vertical" | "horizontal" (default: vertical)
|
|
3862
|
+
// options = { value: string; label?: string; description?: string; disabled?: boolean }[]
|
|
3863
|
+
|
|
3864
|
+
// Examples:
|
|
3865
|
+
<RadioGroup
|
|
3866
|
+
value={plan}
|
|
3867
|
+
onChange={setPlan}
|
|
3868
|
+
options={[
|
|
3869
|
+
{ value: "free", label: "Free", description: "For hobby projects" },
|
|
3870
|
+
{ value: "pro", label: "Pro", description: "For teams" },
|
|
3871
|
+
]}
|
|
3872
|
+
/>
|
|
3873
|
+
<RadioGroup value={plan} onChange={setPlan} orientation="horizontal">
|
|
3874
|
+
<Radio value="free" label="Free" />
|
|
3875
|
+
<Radio value="pro" label="Pro" />
|
|
3876
|
+
</RadioGroup>
|
|
3877
|
+
\`\`\`
|
|
3878
|
+
|
|
3879
|
+
**Common mistakes:**
|
|
3880
|
+
- ❌ \`<Radio> used outside a <RadioGroup>\` → Always wrap <Radio> in <RadioGroup>
|
|
3881
|
+
- ❌ \`RadioGroup without value/onChange (React) or v-model (Vue)\` → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
|
|
3882
|
+
- ❌ \`Using Checkbox for mutually-exclusive choices\` → Use RadioGroup + Radio (or options) for one-of-many
|
|
3883
|
+
|
|
3884
|
+
---
|
|
3885
|
+
|
|
3886
|
+
### RichTextEditor
|
|
3887
|
+
|
|
3888
|
+
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.
|
|
3889
|
+
|
|
3890
|
+
\`\`\`tsx
|
|
3891
|
+
import { RichTextEditor } from "@usevyre/react"
|
|
3892
|
+
|
|
3893
|
+
// Props:
|
|
3894
|
+
// value = string
|
|
3895
|
+
// onChange = function
|
|
3896
|
+
// placeholder = string (default: Write something…)
|
|
3897
|
+
// disabled = boolean (default: false)
|
|
3898
|
+
// readOnly = boolean (default: false)
|
|
3899
|
+
// toolbar = RichTextTool[]
|
|
3900
|
+
// minHeight = string (default: 10rem)
|
|
3901
|
+
|
|
3902
|
+
// Examples:
|
|
3903
|
+
const [html, setHtml] = useState("<p>Hello <strong>world</strong></p>");
|
|
3904
|
+
<RichTextEditor value={html} onChange={setHtml} placeholder="Write…" />
|
|
3905
|
+
<RichTextEditor
|
|
3906
|
+
value={html}
|
|
3907
|
+
onChange={setHtml}
|
|
3908
|
+
toolbar={["bold", "italic", "link"]}
|
|
3909
|
+
/>
|
|
3910
|
+
\`\`\`
|
|
3911
|
+
|
|
3912
|
+
**Common mistakes:**
|
|
3913
|
+
- ❌ \`RichTextEditor without value/onChange (React) or v-model (Vue)\` → Keep the HTML string in state and update it in onChange / v-model
|
|
3914
|
+
- ❌ \`Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising\` → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
|
|
3915
|
+
- ❌ \`toolbar="bold" (string)\` → Pass an array, e.g. toolbar={["bold","italic","link"]}
|
|
3916
|
+
|
|
3917
|
+
---
|
|
3918
|
+
|
|
3087
3919
|
### Command
|
|
3088
3920
|
|
|
3089
3921
|
Command palette / search dialog. Use for search-first navigation or quick actions.
|
|
@@ -3135,7 +3967,7 @@ import { DropdownMenu, DropdownItem, DropdownSeparator, DropdownCheckboxItem, Dr
|
|
|
3135
3967
|
|
|
3136
3968
|
### Field
|
|
3137
3969
|
|
|
3138
|
-
Form field wrapper
|
|
3970
|
+
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
3971
|
|
|
3140
3972
|
\`\`\`tsx
|
|
3141
3973
|
import { Field, Input, Textarea } from "@usevyre/react"
|
|
@@ -3153,10 +3985,23 @@ import { Field, Input, Textarea } from "@usevyre/react"
|
|
|
3153
3985
|
<Field label="Search">
|
|
3154
3986
|
<Input leftElement={<SearchIcon />} placeholder="Search..." />
|
|
3155
3987
|
</Field>
|
|
3988
|
+
<Field>
|
|
3989
|
+
<FieldLabel required htmlFor="email">Email</FieldLabel>
|
|
3990
|
+
<Input id="email" type="email" />
|
|
3991
|
+
<FieldDescription>We\u2019ll never share it.</FieldDescription>
|
|
3992
|
+
<FieldError>{errors.email}</FieldError>
|
|
3993
|
+
</Field>
|
|
3994
|
+
|
|
3995
|
+
// Two controls side by side
|
|
3996
|
+
<FieldGroup orientation="horizontal">
|
|
3997
|
+
<Field label="First name"><Input /></Field>
|
|
3998
|
+
<Field label="Last name"><Input /></Field>
|
|
3999
|
+
</FieldGroup>
|
|
3156
4000
|
\`\`\`
|
|
3157
4001
|
|
|
3158
4002
|
**Common mistakes:**
|
|
3159
4003
|
- ❌ \`Applying state prop directly to Input\` → Wrap Input in <Field state="error"> to apply validation styling
|
|
4004
|
+
- ❌ \`Mixing props label/hint AND FieldLabel/FieldError for the same field\` → Pick one: either props-based (label/hint/state) OR composable parts
|
|
3160
4005
|
|
|
3161
4006
|
---
|
|
3162
4007
|
|
|
@@ -3168,6 +4013,7 @@ Text input field. Wrap in Field for labels and validation. Use leftElement/right
|
|
|
3168
4013
|
import { Input } from "@usevyre/react"
|
|
3169
4014
|
|
|
3170
4015
|
// Props:
|
|
4016
|
+
// modelValue = string | number
|
|
3171
4017
|
// size = "sm" | "md" | "lg" (default: md)
|
|
3172
4018
|
// leftElement = ReactNode
|
|
3173
4019
|
// rightElement = ReactNode
|
|
@@ -3179,6 +4025,7 @@ import { Input } from "@usevyre/react"
|
|
|
3179
4025
|
**Common mistakes:**
|
|
3180
4026
|
- ❌ \`size="icon"\` → Use size="sm" | "md" | "lg"
|
|
3181
4027
|
- ❌ \`type="search" for search UI\` → Import Command from @usevyre/react for search palettes
|
|
4028
|
+
- ❌ \`Vue: binding Input/Textarea value without v-model\` → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
|
|
3182
4029
|
|
|
3183
4030
|
---
|
|
3184
4031
|
|
|
@@ -3766,6 +4613,180 @@ import { TagGroup, Tag } from "@usevyre/react"
|
|
|
3766
4613
|
|
|
3767
4614
|
---
|
|
3768
4615
|
|
|
4616
|
+
### Item
|
|
4617
|
+
|
|
4618
|
+
Layout primitive for list rows, settings rows, and notification rows. Denser than Card — use Item (not Card) for repeated list rows.
|
|
4619
|
+
|
|
4620
|
+
\`\`\`tsx
|
|
4621
|
+
import { Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions, ItemGroup } from "@usevyre/react"
|
|
4622
|
+
|
|
4623
|
+
// Props:
|
|
4624
|
+
// variant = "default" | "outlined" | "muted" | "plain" (default: default)
|
|
4625
|
+
// size = "sm" | "md" | "lg" (default: md)
|
|
4626
|
+
// clickable = boolean (default: false)
|
|
4627
|
+
|
|
4628
|
+
// Examples:
|
|
4629
|
+
<Item>
|
|
4630
|
+
<ItemMedia><BellIcon /></ItemMedia>
|
|
4631
|
+
<ItemContent>
|
|
4632
|
+
<ItemTitle>Notifications</ItemTitle>
|
|
4633
|
+
<ItemDescription>Receive an email when someone mentions you.</ItemDescription>
|
|
4634
|
+
</ItemContent>
|
|
4635
|
+
<ItemActions>
|
|
4636
|
+
<Switch defaultChecked />
|
|
4637
|
+
</ItemActions>
|
|
4638
|
+
</Item>
|
|
4639
|
+
<ItemGroup separated>
|
|
4640
|
+
<Item clickable>
|
|
4641
|
+
<ItemContent><ItemTitle>Profile</ItemTitle></ItemContent>
|
|
4642
|
+
</Item>
|
|
4643
|
+
<Item clickable>
|
|
4644
|
+
<ItemContent><ItemTitle>Billing</ItemTitle></ItemContent>
|
|
4645
|
+
</Item>
|
|
4646
|
+
</ItemGroup>
|
|
4647
|
+
\`\`\`
|
|
4648
|
+
|
|
4649
|
+
**Common mistakes:**
|
|
4650
|
+
- ❌ \`Card used for repeated list rows\` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
|
|
4651
|
+
- ❌ \`Item variant="primary"\` → Use variant="default" | "outlined" | "muted"
|
|
4652
|
+
- ❌ \`raw text directly inside Item\` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
|
|
4653
|
+
|
|
4654
|
+
---
|
|
4655
|
+
|
|
4656
|
+
### Kanban
|
|
4657
|
+
|
|
4658
|
+
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.
|
|
4659
|
+
|
|
4660
|
+
\`\`\`tsx
|
|
4661
|
+
import { Kanban } from "@usevyre/react"
|
|
4662
|
+
|
|
4663
|
+
// Props:
|
|
4664
|
+
// value = KanbanColumn[]
|
|
4665
|
+
// onChange = function
|
|
4666
|
+
// renderCard = function
|
|
4667
|
+
// onCardClick = function
|
|
4668
|
+
|
|
4669
|
+
// Examples:
|
|
4670
|
+
const [columns, setColumns] = useState([
|
|
4671
|
+
{ id: "todo", title: "To Do", cards: [{ id: "1", title: "Spec API" }] },
|
|
4672
|
+
{ id: "doing", title: "In Progress", cards: [] },
|
|
4673
|
+
{ id: "done", title: "Done", cards: [{ id: "2", title: "Kickoff" }] },
|
|
4674
|
+
]);
|
|
4675
|
+
<Kanban value={columns} onChange={setColumns} />
|
|
4676
|
+
<Kanban
|
|
4677
|
+
value={columns}
|
|
4678
|
+
onChange={setColumns}
|
|
4679
|
+
onCardClick={(card) => openDetail(card.id)}
|
|
4680
|
+
renderCard={(card) => (
|
|
4681
|
+
<><strong>{card.title}</strong><Badge>{card.id}</Badge></>
|
|
4682
|
+
)}
|
|
4683
|
+
/>
|
|
4684
|
+
const [cols, setCols] = useState([
|
|
4685
|
+
{ id: "doing", title: "In Progress", color: "teal", cards: [
|
|
4686
|
+
{ id: "t1", title: "OAuth", assignee: "AK", progress: 60, color: "warning" },
|
|
4687
|
+
]},
|
|
4688
|
+
]);
|
|
4689
|
+
<Kanban
|
|
4690
|
+
value={cols}
|
|
4691
|
+
onChange={setCols}
|
|
4692
|
+
renderCard={(card) => (
|
|
4693
|
+
<><strong>{card.title}</strong><Progress value={card.progress} /></>
|
|
4694
|
+
)}
|
|
4695
|
+
/>
|
|
4696
|
+
\`\`\`
|
|
4697
|
+
|
|
4698
|
+
**Common mistakes:**
|
|
4699
|
+
- ❌ \`Kanban without onChange (or ignoring it)\` → Store columns in state and setColumns in onChange (v-model in Vue)
|
|
4700
|
+
- ❌ \`Duplicate card ids across columns\` → Use globally-unique card ids across the entire board
|
|
4701
|
+
- ❌ \`Mutating value in place then calling onChange\` → Pass the new array Kanban gives you straight to setState / v-model
|
|
4702
|
+
- ❌ \`color="blue" (or any non-semantic value)\` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
|
|
4703
|
+
|
|
4704
|
+
---
|
|
4705
|
+
|
|
4706
|
+
### Conversation
|
|
4707
|
+
|
|
4708
|
+
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.
|
|
4709
|
+
|
|
4710
|
+
\`\`\`tsx
|
|
4711
|
+
import { Conversation } from "@usevyre/react"
|
|
4712
|
+
|
|
4713
|
+
// Props:
|
|
4714
|
+
// value = ConversationMessage[]
|
|
4715
|
+
// currentUserId = string
|
|
4716
|
+
// composer = boolean (default: false)
|
|
4717
|
+
// onSend = function
|
|
4718
|
+
// placeholder = string (default: Write a message…)
|
|
4719
|
+
// typing = boolean | string (default: false)
|
|
4720
|
+
// allowAttachments = boolean (default: false)
|
|
4721
|
+
// accept = string
|
|
4722
|
+
// renderMessage = function
|
|
4723
|
+
// renderComposer = function
|
|
4724
|
+
|
|
4725
|
+
// Examples:
|
|
4726
|
+
const [messages, setMessages] = useState([
|
|
4727
|
+
{ id: "1", authorId: "sam", authorName: "Sam", text: "Hey!" },
|
|
4728
|
+
{ id: "2", authorId: "me", text: "Hi \ud83d\udc4b", status: "read" },
|
|
4729
|
+
]);
|
|
4730
|
+
<Conversation
|
|
4731
|
+
value={messages}
|
|
4732
|
+
currentUserId="me"
|
|
4733
|
+
composer
|
|
4734
|
+
onSend={(t) => setMessages((m) => [...m, { id: crypto.randomUUID(), authorId: "me", text: t }])}
|
|
4735
|
+
/>
|
|
4736
|
+
<Conversation
|
|
4737
|
+
value={messages}
|
|
4738
|
+
currentUserId="me"
|
|
4739
|
+
typing="Sam is typing"
|
|
4740
|
+
renderMessage={(m) => <strong>{m.text}</strong>}
|
|
4741
|
+
/>
|
|
4742
|
+
const messages = [
|
|
4743
|
+
{ id: "1", authorId: "sam", authorName: "Sam", text: "Moodboard \ud83d\udc47",
|
|
4744
|
+
attachments: [{ kind: "image", url: "/board.png", name: "board.png" }] },
|
|
4745
|
+
{ id: "2", authorId: "me", text: "Specs:", status: "read",
|
|
4746
|
+
attachments: [{ kind: "file", url: "/spec.pdf", name: "spec.pdf", size: "2.4 MB" }] },
|
|
4747
|
+
];
|
|
4748
|
+
<Conversation value={messages} currentUserId="me" />
|
|
4749
|
+
\`\`\`
|
|
4750
|
+
|
|
4751
|
+
**Common mistakes:**
|
|
4752
|
+
- ❌ \`Conversation without currentUserId\` → Always pass currentUserId matching one of the message authorId values
|
|
4753
|
+
- ❌ \`Expecting Conversation to store/append messages\` → Append to your own state in onSend (or @send) and pass it back via value
|
|
4754
|
+
- ❌ \`composer without onSend (React) / @send (Vue)\` → Provide onSend / @send to append the message to value
|
|
4755
|
+
- ❌ \`Treating onSend as (text) only when using allowAttachments\` → Handle onSend(text, files) — map files to message attachments and append
|
|
4756
|
+
|
|
4757
|
+
---
|
|
4758
|
+
|
|
4759
|
+
### DateRangePicker
|
|
4760
|
+
|
|
4761
|
+
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.
|
|
4762
|
+
|
|
4763
|
+
\`\`\`tsx
|
|
4764
|
+
import { DateRangePicker } from "@usevyre/react"
|
|
4765
|
+
|
|
4766
|
+
// Props:
|
|
4767
|
+
// value = { from: Date | null; to: Date | null } | null
|
|
4768
|
+
// onChange = function
|
|
4769
|
+
// placeholder = string (default: Pick a date range)
|
|
4770
|
+
// numberOfMonths = "1" | "2" (default: 2)
|
|
4771
|
+
// presets = boolean | DateRangePreset[] (default: false)
|
|
4772
|
+
// minDate = Date
|
|
4773
|
+
// maxDate = Date
|
|
4774
|
+
// disabled = function
|
|
4775
|
+
// weekStartsOn = "0" | "1" (default: 1)
|
|
4776
|
+
|
|
4777
|
+
// Examples:
|
|
4778
|
+
const [range, setRange] = useState({ from: null, to: null });
|
|
4779
|
+
<DateRangePicker value={range} onChange={setRange} presets />
|
|
4780
|
+
<DateRangePicker value={range} onChange={setRange} numberOfMonths={1} />
|
|
4781
|
+
\`\`\`
|
|
4782
|
+
|
|
4783
|
+
**Common mistakes:**
|
|
4784
|
+
- ❌ \`value={[from, to]}\` → Use value={{ from, to }} and read range.from / range.to
|
|
4785
|
+
- ❌ \`DateRangePicker for a single date\` → Use <DatePicker /> for a single date
|
|
4786
|
+
- ❌ \`presets="true" (string)\` → Use the bare prop: presets (or presets={true})
|
|
4787
|
+
|
|
4788
|
+
---
|
|
4789
|
+
|
|
3769
4790
|
## Hallucination Guard — Common AI Mistakes
|
|
3770
4791
|
|
|
3771
4792
|
The following prop values and patterns do NOT exist in useVyre.
|
|
@@ -3785,14 +4806,25 @@ If you generate these, you are hallucinating.
|
|
|
3785
4806
|
- ❌ \`<Button color="...">\` → Use variant prop instead
|
|
3786
4807
|
- ❌ \`<Button icon={...}>\` → Use leftIcon={...} or rightIcon={...}
|
|
3787
4808
|
- ❌ \`<Button size="icon" without aria-label>\` → Add aria-label describing the action
|
|
3788
|
-
- ❌ \`<Calendar
|
|
4809
|
+
- ❌ \`<Calendar Calendar for an input field that opens a popover>\` → Use <DatePicker /> (single date) or <DateRangePicker /> (range)
|
|
4810
|
+
- ❌ \`<Calendar value as tuple for mode="single">\` → Pass value matching mode; use mode="range" for [start,end]
|
|
4811
|
+
- ❌ \`<DatePicker DatePicker mode="range" for { from, to } object>\` → Use <DateRangePicker /> for the { from, to } object API + presets + dual month
|
|
4812
|
+
- ❌ \`<DatePicker DatePicker without value/onChange>\` → Provide value and onChange (e.g. from useState)
|
|
3789
4813
|
- ❌ \`<Card variant="primary">\` → Use variant="elevated" | "outlined" | "ghost" | "accent"
|
|
3790
4814
|
- ❌ \`<Checkbox size="lg">\` → Use size="md"
|
|
4815
|
+
- ❌ \`<RadioGroup <Radio> used outside a <RadioGroup>>\` → Always wrap <Radio> in <RadioGroup>
|
|
4816
|
+
- ❌ \`<RadioGroup RadioGroup without value/onChange (React) or v-model (Vue)>\` → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
|
|
4817
|
+
- ❌ \`<RadioGroup Using Checkbox for mutually-exclusive choices>\` → Use RadioGroup + Radio (or options) for one-of-many
|
|
4818
|
+
- ❌ \`<RichTextEditor RichTextEditor without value/onChange (React) or v-model (Vue)>\` → Keep the HTML string in state and update it in onChange / v-model
|
|
4819
|
+
- ❌ \`<RichTextEditor Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising>\` → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
|
|
4820
|
+
- ❌ \`<RichTextEditor toolbar="bold" (string)>\` → Pass an array, e.g. toolbar={["bold","italic","link"]}
|
|
3791
4821
|
- ❌ \`<Command Using Input type="search" for search UI>\` → Use Command + CommandInput + CommandList + CommandItem
|
|
3792
4822
|
- ❌ \`<DropdownMenu DropdownItem variant="primary">\` → Use variant="danger" for destructive items only
|
|
3793
4823
|
- ❌ \`<Field Applying state prop directly to Input>\` → Wrap Input in <Field state="error"> to apply validation styling
|
|
4824
|
+
- ❌ \`<Field Mixing props label/hint AND FieldLabel/FieldError for the same field>\` → Pick one: either props-based (label/hint/state) OR composable parts
|
|
3794
4825
|
- ❌ \`<Input size="icon">\` → Use size="sm" | "md" | "lg"
|
|
3795
4826
|
- ❌ \`<Input type="search" for search UI>\` → Import Command from @usevyre/react for search palettes
|
|
4827
|
+
- ❌ \`<Input Vue: binding Input/Textarea value without v-model>\` → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
|
|
3796
4828
|
- ❌ \`<Modal size="xl">\` → Use size="lg" or size="full"
|
|
3797
4829
|
- ❌ \`<Popover placement="top-center">\` → Use placement="top" for centered placement
|
|
3798
4830
|
- ❌ \`<Progress value > 100>\` → Normalize your value to 0–100 range before passing
|
|
@@ -3817,6 +4849,20 @@ If you generate these, you are hallucinating.
|
|
|
3817
4849
|
- ❌ \`<Tag Tag size="xl">\` → Use size="lg"
|
|
3818
4850
|
- ❌ \`<TagGroup TagGroup without Tag children>\` → Place <Tag> elements as direct children
|
|
3819
4851
|
- ❌ \`<TagGroup Using TagGroup for tag input>\` → Use TagsInput for an editable tag field
|
|
4852
|
+
- ❌ \`<Item Card used for repeated list rows>\` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
|
|
4853
|
+
- ❌ \`<Item Item variant="primary">\` → Use variant="default" | "outlined" | "muted"
|
|
4854
|
+
- ❌ \`<Item raw text directly inside Item>\` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
|
|
4855
|
+
- ❌ \`<Kanban Kanban without onChange (or ignoring it)>\` → Store columns in state and setColumns in onChange (v-model in Vue)
|
|
4856
|
+
- ❌ \`<Kanban Duplicate card ids across columns>\` → Use globally-unique card ids across the entire board
|
|
4857
|
+
- ❌ \`<Kanban Mutating value in place then calling onChange>\` → Pass the new array Kanban gives you straight to setState / v-model
|
|
4858
|
+
- ❌ \`<Kanban color="blue" (or any non-semantic value)>\` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
|
|
4859
|
+
- ❌ \`<Conversation Conversation without currentUserId>\` → Always pass currentUserId matching one of the message authorId values
|
|
4860
|
+
- ❌ \`<Conversation Expecting Conversation to store/append messages>\` → Append to your own state in onSend (or @send) and pass it back via value
|
|
4861
|
+
- ❌ \`<Conversation composer without onSend (React) / @send (Vue)>\` → Provide onSend / @send to append the message to value
|
|
4862
|
+
- ❌ \`<Conversation Treating onSend as (text) only when using allowAttachments>\` → Handle onSend(text, files) — map files to message attachments and append
|
|
4863
|
+
- ❌ \`<DateRangePicker value={[from, to]}>\` → Use value={{ from, to }} and read range.from / range.to
|
|
4864
|
+
- ❌ \`<DateRangePicker DateRangePicker for a single date>\` → Use <DatePicker /> for a single date
|
|
4865
|
+
- ❌ \`<DateRangePicker presets="true" (string)>\` → Use the bare prop: presets (or presets={true})
|
|
3820
4866
|
|
|
3821
4867
|
---
|
|
3822
4868
|
|
|
@@ -3866,7 +4912,7 @@ If you generate these, you are hallucinating.
|
|
|
3866
4912
|
\`\`\`
|
|
3867
4913
|
`;
|
|
3868
4914
|
export const copilotInstructions = `# useVyre Copilot Instructions
|
|
3869
|
-
# Version: 1.
|
|
4915
|
+
# Version: 1.2.0
|
|
3870
4916
|
|
|
3871
4917
|
When generating UI code in this project, follow the useVyre design system rules below.
|
|
3872
4918
|
|
|
@@ -4177,7 +5223,7 @@ import { Button } from "@usevyre/react"
|
|
|
4177
5223
|
|
|
4178
5224
|
### Calendar
|
|
4179
5225
|
|
|
4180
|
-
|
|
5226
|
+
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
5227
|
|
|
4182
5228
|
\`\`\`tsx
|
|
4183
5229
|
import { Calendar } from "@usevyre/react"
|
|
@@ -4186,6 +5232,7 @@ import { Calendar } from "@usevyre/react"
|
|
|
4186
5232
|
// value = Date | null
|
|
4187
5233
|
// onChange = function
|
|
4188
5234
|
// disabled = boolean (default: false)
|
|
5235
|
+
// defaultMonth = Date
|
|
4189
5236
|
|
|
4190
5237
|
// Examples:
|
|
4191
5238
|
const [date, setDate] = useState(null);
|
|
@@ -4193,7 +5240,39 @@ const [date, setDate] = useState(null);
|
|
|
4193
5240
|
\`\`\`
|
|
4194
5241
|
|
|
4195
5242
|
**Common mistakes:**
|
|
4196
|
-
- ❌ \`
|
|
5243
|
+
- ❌ \`Calendar for an input field that opens a popover\` → Use <DatePicker /> (single date) or <DateRangePicker /> (range)
|
|
5244
|
+
- ❌ \`value as tuple for mode="single"\` → Pass value matching mode; use mode="range" for [start,end]
|
|
5245
|
+
|
|
5246
|
+
---
|
|
5247
|
+
|
|
5248
|
+
### DatePicker
|
|
5249
|
+
|
|
5250
|
+
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.
|
|
5251
|
+
|
|
5252
|
+
\`\`\`tsx
|
|
5253
|
+
import { DatePicker } from "@usevyre/react"
|
|
5254
|
+
|
|
5255
|
+
// Props:
|
|
5256
|
+
// value = Date | [Date, Date] | Date[] | null
|
|
5257
|
+
// onChange = function
|
|
5258
|
+
// mode = "single" | "range" | "multiple" (default: single)
|
|
5259
|
+
// placeholder = string (default: Pick a date)
|
|
5260
|
+
// showTime = boolean (default: false)
|
|
5261
|
+
// minDate = Date
|
|
5262
|
+
// maxDate = Date
|
|
5263
|
+
// disabled = function
|
|
5264
|
+
// weekStartsOn = "0" | "1" (default: 1)
|
|
5265
|
+
// inputClassName = string
|
|
5266
|
+
|
|
5267
|
+
// Examples:
|
|
5268
|
+
const [date, setDate] = useState(null);
|
|
5269
|
+
<DatePicker value={date} onChange={setDate} placeholder="Pick a date" />
|
|
5270
|
+
<DatePicker value={date} onChange={setDate} showTime />
|
|
5271
|
+
\`\`\`
|
|
5272
|
+
|
|
5273
|
+
**Common mistakes:**
|
|
5274
|
+
- ❌ \`DatePicker mode="range" for { from, to } object\` → Use <DateRangePicker /> for the { from, to } object API + presets + dual month
|
|
5275
|
+
- ❌ \`DatePicker without value/onChange\` → Provide value and onChange (e.g. from useState)
|
|
4197
5276
|
|
|
4198
5277
|
---
|
|
4199
5278
|
|
|
@@ -4253,6 +5332,78 @@ import { Checkbox } from "@usevyre/react"
|
|
|
4253
5332
|
|
|
4254
5333
|
---
|
|
4255
5334
|
|
|
5335
|
+
### RadioGroup
|
|
5336
|
+
|
|
5337
|
+
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.
|
|
5338
|
+
|
|
5339
|
+
\`\`\`tsx
|
|
5340
|
+
import { RadioGroup, Radio } from "@usevyre/react"
|
|
5341
|
+
|
|
5342
|
+
// Props:
|
|
5343
|
+
// value = string
|
|
5344
|
+
// defaultValue = string
|
|
5345
|
+
// onChange = function
|
|
5346
|
+
// name = string
|
|
5347
|
+
// disabled = boolean (default: false)
|
|
5348
|
+
// size = "sm" | "md" (default: md)
|
|
5349
|
+
// orientation = "vertical" | "horizontal" (default: vertical)
|
|
5350
|
+
// options = { value: string; label?: string; description?: string; disabled?: boolean }[]
|
|
5351
|
+
|
|
5352
|
+
// Examples:
|
|
5353
|
+
<RadioGroup
|
|
5354
|
+
value={plan}
|
|
5355
|
+
onChange={setPlan}
|
|
5356
|
+
options={[
|
|
5357
|
+
{ value: "free", label: "Free", description: "For hobby projects" },
|
|
5358
|
+
{ value: "pro", label: "Pro", description: "For teams" },
|
|
5359
|
+
]}
|
|
5360
|
+
/>
|
|
5361
|
+
<RadioGroup value={plan} onChange={setPlan} orientation="horizontal">
|
|
5362
|
+
<Radio value="free" label="Free" />
|
|
5363
|
+
<Radio value="pro" label="Pro" />
|
|
5364
|
+
</RadioGroup>
|
|
5365
|
+
\`\`\`
|
|
5366
|
+
|
|
5367
|
+
**Common mistakes:**
|
|
5368
|
+
- ❌ \`<Radio> used outside a <RadioGroup>\` → Always wrap <Radio> in <RadioGroup>
|
|
5369
|
+
- ❌ \`RadioGroup without value/onChange (React) or v-model (Vue)\` → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
|
|
5370
|
+
- ❌ \`Using Checkbox for mutually-exclusive choices\` → Use RadioGroup + Radio (or options) for one-of-many
|
|
5371
|
+
|
|
5372
|
+
---
|
|
5373
|
+
|
|
5374
|
+
### RichTextEditor
|
|
5375
|
+
|
|
5376
|
+
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.
|
|
5377
|
+
|
|
5378
|
+
\`\`\`tsx
|
|
5379
|
+
import { RichTextEditor } from "@usevyre/react"
|
|
5380
|
+
|
|
5381
|
+
// Props:
|
|
5382
|
+
// value = string
|
|
5383
|
+
// onChange = function
|
|
5384
|
+
// placeholder = string (default: Write something…)
|
|
5385
|
+
// disabled = boolean (default: false)
|
|
5386
|
+
// readOnly = boolean (default: false)
|
|
5387
|
+
// toolbar = RichTextTool[]
|
|
5388
|
+
// minHeight = string (default: 10rem)
|
|
5389
|
+
|
|
5390
|
+
// Examples:
|
|
5391
|
+
const [html, setHtml] = useState("<p>Hello <strong>world</strong></p>");
|
|
5392
|
+
<RichTextEditor value={html} onChange={setHtml} placeholder="Write…" />
|
|
5393
|
+
<RichTextEditor
|
|
5394
|
+
value={html}
|
|
5395
|
+
onChange={setHtml}
|
|
5396
|
+
toolbar={["bold", "italic", "link"]}
|
|
5397
|
+
/>
|
|
5398
|
+
\`\`\`
|
|
5399
|
+
|
|
5400
|
+
**Common mistakes:**
|
|
5401
|
+
- ❌ \`RichTextEditor without value/onChange (React) or v-model (Vue)\` → Keep the HTML string in state and update it in onChange / v-model
|
|
5402
|
+
- ❌ \`Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising\` → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
|
|
5403
|
+
- ❌ \`toolbar="bold" (string)\` → Pass an array, e.g. toolbar={["bold","italic","link"]}
|
|
5404
|
+
|
|
5405
|
+
---
|
|
5406
|
+
|
|
4256
5407
|
### Command
|
|
4257
5408
|
|
|
4258
5409
|
Command palette / search dialog. Use for search-first navigation or quick actions.
|
|
@@ -4304,7 +5455,7 @@ import { DropdownMenu, DropdownItem, DropdownSeparator, DropdownCheckboxItem, Dr
|
|
|
4304
5455
|
|
|
4305
5456
|
### Field
|
|
4306
5457
|
|
|
4307
|
-
Form field wrapper
|
|
5458
|
+
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
5459
|
|
|
4309
5460
|
\`\`\`tsx
|
|
4310
5461
|
import { Field, Input, Textarea } from "@usevyre/react"
|
|
@@ -4322,10 +5473,23 @@ import { Field, Input, Textarea } from "@usevyre/react"
|
|
|
4322
5473
|
<Field label="Search">
|
|
4323
5474
|
<Input leftElement={<SearchIcon />} placeholder="Search..." />
|
|
4324
5475
|
</Field>
|
|
5476
|
+
<Field>
|
|
5477
|
+
<FieldLabel required htmlFor="email">Email</FieldLabel>
|
|
5478
|
+
<Input id="email" type="email" />
|
|
5479
|
+
<FieldDescription>We\u2019ll never share it.</FieldDescription>
|
|
5480
|
+
<FieldError>{errors.email}</FieldError>
|
|
5481
|
+
</Field>
|
|
5482
|
+
|
|
5483
|
+
// Two controls side by side
|
|
5484
|
+
<FieldGroup orientation="horizontal">
|
|
5485
|
+
<Field label="First name"><Input /></Field>
|
|
5486
|
+
<Field label="Last name"><Input /></Field>
|
|
5487
|
+
</FieldGroup>
|
|
4325
5488
|
\`\`\`
|
|
4326
5489
|
|
|
4327
5490
|
**Common mistakes:**
|
|
4328
5491
|
- ❌ \`Applying state prop directly to Input\` → Wrap Input in <Field state="error"> to apply validation styling
|
|
5492
|
+
- ❌ \`Mixing props label/hint AND FieldLabel/FieldError for the same field\` → Pick one: either props-based (label/hint/state) OR composable parts
|
|
4329
5493
|
|
|
4330
5494
|
---
|
|
4331
5495
|
|
|
@@ -4337,6 +5501,7 @@ Text input field. Wrap in Field for labels and validation. Use leftElement/right
|
|
|
4337
5501
|
import { Input } from "@usevyre/react"
|
|
4338
5502
|
|
|
4339
5503
|
// Props:
|
|
5504
|
+
// modelValue = string | number
|
|
4340
5505
|
// size = "sm" | "md" | "lg" (default: md)
|
|
4341
5506
|
// leftElement = ReactNode
|
|
4342
5507
|
// rightElement = ReactNode
|
|
@@ -4348,6 +5513,7 @@ import { Input } from "@usevyre/react"
|
|
|
4348
5513
|
**Common mistakes:**
|
|
4349
5514
|
- ❌ \`size="icon"\` → Use size="sm" | "md" | "lg"
|
|
4350
5515
|
- ❌ \`type="search" for search UI\` → Import Command from @usevyre/react for search palettes
|
|
5516
|
+
- ❌ \`Vue: binding Input/Textarea value without v-model\` → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
|
|
4351
5517
|
|
|
4352
5518
|
---
|
|
4353
5519
|
|
|
@@ -4935,6 +6101,180 @@ import { TagGroup, Tag } from "@usevyre/react"
|
|
|
4935
6101
|
|
|
4936
6102
|
---
|
|
4937
6103
|
|
|
6104
|
+
### Item
|
|
6105
|
+
|
|
6106
|
+
Layout primitive for list rows, settings rows, and notification rows. Denser than Card — use Item (not Card) for repeated list rows.
|
|
6107
|
+
|
|
6108
|
+
\`\`\`tsx
|
|
6109
|
+
import { Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions, ItemGroup } from "@usevyre/react"
|
|
6110
|
+
|
|
6111
|
+
// Props:
|
|
6112
|
+
// variant = "default" | "outlined" | "muted" | "plain" (default: default)
|
|
6113
|
+
// size = "sm" | "md" | "lg" (default: md)
|
|
6114
|
+
// clickable = boolean (default: false)
|
|
6115
|
+
|
|
6116
|
+
// Examples:
|
|
6117
|
+
<Item>
|
|
6118
|
+
<ItemMedia><BellIcon /></ItemMedia>
|
|
6119
|
+
<ItemContent>
|
|
6120
|
+
<ItemTitle>Notifications</ItemTitle>
|
|
6121
|
+
<ItemDescription>Receive an email when someone mentions you.</ItemDescription>
|
|
6122
|
+
</ItemContent>
|
|
6123
|
+
<ItemActions>
|
|
6124
|
+
<Switch defaultChecked />
|
|
6125
|
+
</ItemActions>
|
|
6126
|
+
</Item>
|
|
6127
|
+
<ItemGroup separated>
|
|
6128
|
+
<Item clickable>
|
|
6129
|
+
<ItemContent><ItemTitle>Profile</ItemTitle></ItemContent>
|
|
6130
|
+
</Item>
|
|
6131
|
+
<Item clickable>
|
|
6132
|
+
<ItemContent><ItemTitle>Billing</ItemTitle></ItemContent>
|
|
6133
|
+
</Item>
|
|
6134
|
+
</ItemGroup>
|
|
6135
|
+
\`\`\`
|
|
6136
|
+
|
|
6137
|
+
**Common mistakes:**
|
|
6138
|
+
- ❌ \`Card used for repeated list rows\` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
|
|
6139
|
+
- ❌ \`Item variant="primary"\` → Use variant="default" | "outlined" | "muted"
|
|
6140
|
+
- ❌ \`raw text directly inside Item\` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
|
|
6141
|
+
|
|
6142
|
+
---
|
|
6143
|
+
|
|
6144
|
+
### Kanban
|
|
6145
|
+
|
|
6146
|
+
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.
|
|
6147
|
+
|
|
6148
|
+
\`\`\`tsx
|
|
6149
|
+
import { Kanban } from "@usevyre/react"
|
|
6150
|
+
|
|
6151
|
+
// Props:
|
|
6152
|
+
// value = KanbanColumn[]
|
|
6153
|
+
// onChange = function
|
|
6154
|
+
// renderCard = function
|
|
6155
|
+
// onCardClick = function
|
|
6156
|
+
|
|
6157
|
+
// Examples:
|
|
6158
|
+
const [columns, setColumns] = useState([
|
|
6159
|
+
{ id: "todo", title: "To Do", cards: [{ id: "1", title: "Spec API" }] },
|
|
6160
|
+
{ id: "doing", title: "In Progress", cards: [] },
|
|
6161
|
+
{ id: "done", title: "Done", cards: [{ id: "2", title: "Kickoff" }] },
|
|
6162
|
+
]);
|
|
6163
|
+
<Kanban value={columns} onChange={setColumns} />
|
|
6164
|
+
<Kanban
|
|
6165
|
+
value={columns}
|
|
6166
|
+
onChange={setColumns}
|
|
6167
|
+
onCardClick={(card) => openDetail(card.id)}
|
|
6168
|
+
renderCard={(card) => (
|
|
6169
|
+
<><strong>{card.title}</strong><Badge>{card.id}</Badge></>
|
|
6170
|
+
)}
|
|
6171
|
+
/>
|
|
6172
|
+
const [cols, setCols] = useState([
|
|
6173
|
+
{ id: "doing", title: "In Progress", color: "teal", cards: [
|
|
6174
|
+
{ id: "t1", title: "OAuth", assignee: "AK", progress: 60, color: "warning" },
|
|
6175
|
+
]},
|
|
6176
|
+
]);
|
|
6177
|
+
<Kanban
|
|
6178
|
+
value={cols}
|
|
6179
|
+
onChange={setCols}
|
|
6180
|
+
renderCard={(card) => (
|
|
6181
|
+
<><strong>{card.title}</strong><Progress value={card.progress} /></>
|
|
6182
|
+
)}
|
|
6183
|
+
/>
|
|
6184
|
+
\`\`\`
|
|
6185
|
+
|
|
6186
|
+
**Common mistakes:**
|
|
6187
|
+
- ❌ \`Kanban without onChange (or ignoring it)\` → Store columns in state and setColumns in onChange (v-model in Vue)
|
|
6188
|
+
- ❌ \`Duplicate card ids across columns\` → Use globally-unique card ids across the entire board
|
|
6189
|
+
- ❌ \`Mutating value in place then calling onChange\` → Pass the new array Kanban gives you straight to setState / v-model
|
|
6190
|
+
- ❌ \`color="blue" (or any non-semantic value)\` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
|
|
6191
|
+
|
|
6192
|
+
---
|
|
6193
|
+
|
|
6194
|
+
### Conversation
|
|
6195
|
+
|
|
6196
|
+
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.
|
|
6197
|
+
|
|
6198
|
+
\`\`\`tsx
|
|
6199
|
+
import { Conversation } from "@usevyre/react"
|
|
6200
|
+
|
|
6201
|
+
// Props:
|
|
6202
|
+
// value = ConversationMessage[]
|
|
6203
|
+
// currentUserId = string
|
|
6204
|
+
// composer = boolean (default: false)
|
|
6205
|
+
// onSend = function
|
|
6206
|
+
// placeholder = string (default: Write a message…)
|
|
6207
|
+
// typing = boolean | string (default: false)
|
|
6208
|
+
// allowAttachments = boolean (default: false)
|
|
6209
|
+
// accept = string
|
|
6210
|
+
// renderMessage = function
|
|
6211
|
+
// renderComposer = function
|
|
6212
|
+
|
|
6213
|
+
// Examples:
|
|
6214
|
+
const [messages, setMessages] = useState([
|
|
6215
|
+
{ id: "1", authorId: "sam", authorName: "Sam", text: "Hey!" },
|
|
6216
|
+
{ id: "2", authorId: "me", text: "Hi \ud83d\udc4b", status: "read" },
|
|
6217
|
+
]);
|
|
6218
|
+
<Conversation
|
|
6219
|
+
value={messages}
|
|
6220
|
+
currentUserId="me"
|
|
6221
|
+
composer
|
|
6222
|
+
onSend={(t) => setMessages((m) => [...m, { id: crypto.randomUUID(), authorId: "me", text: t }])}
|
|
6223
|
+
/>
|
|
6224
|
+
<Conversation
|
|
6225
|
+
value={messages}
|
|
6226
|
+
currentUserId="me"
|
|
6227
|
+
typing="Sam is typing"
|
|
6228
|
+
renderMessage={(m) => <strong>{m.text}</strong>}
|
|
6229
|
+
/>
|
|
6230
|
+
const messages = [
|
|
6231
|
+
{ id: "1", authorId: "sam", authorName: "Sam", text: "Moodboard \ud83d\udc47",
|
|
6232
|
+
attachments: [{ kind: "image", url: "/board.png", name: "board.png" }] },
|
|
6233
|
+
{ id: "2", authorId: "me", text: "Specs:", status: "read",
|
|
6234
|
+
attachments: [{ kind: "file", url: "/spec.pdf", name: "spec.pdf", size: "2.4 MB" }] },
|
|
6235
|
+
];
|
|
6236
|
+
<Conversation value={messages} currentUserId="me" />
|
|
6237
|
+
\`\`\`
|
|
6238
|
+
|
|
6239
|
+
**Common mistakes:**
|
|
6240
|
+
- ❌ \`Conversation without currentUserId\` → Always pass currentUserId matching one of the message authorId values
|
|
6241
|
+
- ❌ \`Expecting Conversation to store/append messages\` → Append to your own state in onSend (or @send) and pass it back via value
|
|
6242
|
+
- ❌ \`composer without onSend (React) / @send (Vue)\` → Provide onSend / @send to append the message to value
|
|
6243
|
+
- ❌ \`Treating onSend as (text) only when using allowAttachments\` → Handle onSend(text, files) — map files to message attachments and append
|
|
6244
|
+
|
|
6245
|
+
---
|
|
6246
|
+
|
|
6247
|
+
### DateRangePicker
|
|
6248
|
+
|
|
6249
|
+
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.
|
|
6250
|
+
|
|
6251
|
+
\`\`\`tsx
|
|
6252
|
+
import { DateRangePicker } from "@usevyre/react"
|
|
6253
|
+
|
|
6254
|
+
// Props:
|
|
6255
|
+
// value = { from: Date | null; to: Date | null } | null
|
|
6256
|
+
// onChange = function
|
|
6257
|
+
// placeholder = string (default: Pick a date range)
|
|
6258
|
+
// numberOfMonths = "1" | "2" (default: 2)
|
|
6259
|
+
// presets = boolean | DateRangePreset[] (default: false)
|
|
6260
|
+
// minDate = Date
|
|
6261
|
+
// maxDate = Date
|
|
6262
|
+
// disabled = function
|
|
6263
|
+
// weekStartsOn = "0" | "1" (default: 1)
|
|
6264
|
+
|
|
6265
|
+
// Examples:
|
|
6266
|
+
const [range, setRange] = useState({ from: null, to: null });
|
|
6267
|
+
<DateRangePicker value={range} onChange={setRange} presets />
|
|
6268
|
+
<DateRangePicker value={range} onChange={setRange} numberOfMonths={1} />
|
|
6269
|
+
\`\`\`
|
|
6270
|
+
|
|
6271
|
+
**Common mistakes:**
|
|
6272
|
+
- ❌ \`value={[from, to]}\` → Use value={{ from, to }} and read range.from / range.to
|
|
6273
|
+
- ❌ \`DateRangePicker for a single date\` → Use <DatePicker /> for a single date
|
|
6274
|
+
- ❌ \`presets="true" (string)\` → Use the bare prop: presets (or presets={true})
|
|
6275
|
+
|
|
6276
|
+
---
|
|
6277
|
+
|
|
4938
6278
|
## Hallucination Guard — Common AI Mistakes
|
|
4939
6279
|
|
|
4940
6280
|
The following prop values and patterns do NOT exist in useVyre.
|
|
@@ -4954,14 +6294,25 @@ If you generate these, you are hallucinating.
|
|
|
4954
6294
|
- ❌ \`<Button color="...">\` → Use variant prop instead
|
|
4955
6295
|
- ❌ \`<Button icon={...}>\` → Use leftIcon={...} or rightIcon={...}
|
|
4956
6296
|
- ❌ \`<Button size="icon" without aria-label>\` → Add aria-label describing the action
|
|
4957
|
-
- ❌ \`<Calendar
|
|
6297
|
+
- ❌ \`<Calendar Calendar for an input field that opens a popover>\` → Use <DatePicker /> (single date) or <DateRangePicker /> (range)
|
|
6298
|
+
- ❌ \`<Calendar value as tuple for mode="single">\` → Pass value matching mode; use mode="range" for [start,end]
|
|
6299
|
+
- ❌ \`<DatePicker DatePicker mode="range" for { from, to } object>\` → Use <DateRangePicker /> for the { from, to } object API + presets + dual month
|
|
6300
|
+
- ❌ \`<DatePicker DatePicker without value/onChange>\` → Provide value and onChange (e.g. from useState)
|
|
4958
6301
|
- ❌ \`<Card variant="primary">\` → Use variant="elevated" | "outlined" | "ghost" | "accent"
|
|
4959
6302
|
- ❌ \`<Checkbox size="lg">\` → Use size="md"
|
|
6303
|
+
- ❌ \`<RadioGroup <Radio> used outside a <RadioGroup>>\` → Always wrap <Radio> in <RadioGroup>
|
|
6304
|
+
- ❌ \`<RadioGroup RadioGroup without value/onChange (React) or v-model (Vue)>\` → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
|
|
6305
|
+
- ❌ \`<RadioGroup Using Checkbox for mutually-exclusive choices>\` → Use RadioGroup + Radio (or options) for one-of-many
|
|
6306
|
+
- ❌ \`<RichTextEditor RichTextEditor without value/onChange (React) or v-model (Vue)>\` → Keep the HTML string in state and update it in onChange / v-model
|
|
6307
|
+
- ❌ \`<RichTextEditor Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising>\` → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
|
|
6308
|
+
- ❌ \`<RichTextEditor toolbar="bold" (string)>\` → Pass an array, e.g. toolbar={["bold","italic","link"]}
|
|
4960
6309
|
- ❌ \`<Command Using Input type="search" for search UI>\` → Use Command + CommandInput + CommandList + CommandItem
|
|
4961
6310
|
- ❌ \`<DropdownMenu DropdownItem variant="primary">\` → Use variant="danger" for destructive items only
|
|
4962
6311
|
- ❌ \`<Field Applying state prop directly to Input>\` → Wrap Input in <Field state="error"> to apply validation styling
|
|
6312
|
+
- ❌ \`<Field Mixing props label/hint AND FieldLabel/FieldError for the same field>\` → Pick one: either props-based (label/hint/state) OR composable parts
|
|
4963
6313
|
- ❌ \`<Input size="icon">\` → Use size="sm" | "md" | "lg"
|
|
4964
6314
|
- ❌ \`<Input type="search" for search UI>\` → Import Command from @usevyre/react for search palettes
|
|
6315
|
+
- ❌ \`<Input Vue: binding Input/Textarea value without v-model>\` → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
|
|
4965
6316
|
- ❌ \`<Modal size="xl">\` → Use size="lg" or size="full"
|
|
4966
6317
|
- ❌ \`<Popover placement="top-center">\` → Use placement="top" for centered placement
|
|
4967
6318
|
- ❌ \`<Progress value > 100>\` → Normalize your value to 0–100 range before passing
|
|
@@ -4986,6 +6337,20 @@ If you generate these, you are hallucinating.
|
|
|
4986
6337
|
- ❌ \`<Tag Tag size="xl">\` → Use size="lg"
|
|
4987
6338
|
- ❌ \`<TagGroup TagGroup without Tag children>\` → Place <Tag> elements as direct children
|
|
4988
6339
|
- ❌ \`<TagGroup Using TagGroup for tag input>\` → Use TagsInput for an editable tag field
|
|
6340
|
+
- ❌ \`<Item Card used for repeated list rows>\` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
|
|
6341
|
+
- ❌ \`<Item Item variant="primary">\` → Use variant="default" | "outlined" | "muted"
|
|
6342
|
+
- ❌ \`<Item raw text directly inside Item>\` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
|
|
6343
|
+
- ❌ \`<Kanban Kanban without onChange (or ignoring it)>\` → Store columns in state and setColumns in onChange (v-model in Vue)
|
|
6344
|
+
- ❌ \`<Kanban Duplicate card ids across columns>\` → Use globally-unique card ids across the entire board
|
|
6345
|
+
- ❌ \`<Kanban Mutating value in place then calling onChange>\` → Pass the new array Kanban gives you straight to setState / v-model
|
|
6346
|
+
- ❌ \`<Kanban color="blue" (or any non-semantic value)>\` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
|
|
6347
|
+
- ❌ \`<Conversation Conversation without currentUserId>\` → Always pass currentUserId matching one of the message authorId values
|
|
6348
|
+
- ❌ \`<Conversation Expecting Conversation to store/append messages>\` → Append to your own state in onSend (or @send) and pass it back via value
|
|
6349
|
+
- ❌ \`<Conversation composer without onSend (React) / @send (Vue)>\` → Provide onSend / @send to append the message to value
|
|
6350
|
+
- ❌ \`<Conversation Treating onSend as (text) only when using allowAttachments>\` → Handle onSend(text, files) — map files to message attachments and append
|
|
6351
|
+
- ❌ \`<DateRangePicker value={[from, to]}>\` → Use value={{ from, to }} and read range.from / range.to
|
|
6352
|
+
- ❌ \`<DateRangePicker DateRangePicker for a single date>\` → Use <DatePicker /> for a single date
|
|
6353
|
+
- ❌ \`<DateRangePicker presets="true" (string)>\` → Use the bare prop: presets (or presets={true})
|
|
4989
6354
|
|
|
4990
6355
|
---
|
|
4991
6356
|
|
|
@@ -5037,8 +6402,8 @@ If you generate these, you are hallucinating.
|
|
|
5037
6402
|
|
|
5038
6403
|
export const schema = {
|
|
5039
6404
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
5040
|
-
"version": "1.
|
|
5041
|
-
"generatedAt": "2026-05-
|
|
6405
|
+
"version": "1.2.0",
|
|
6406
|
+
"generatedAt": "2026-05-16",
|
|
5042
6407
|
"package": "@usevyre/react",
|
|
5043
6408
|
"packageVersion": "1.1.0",
|
|
5044
6409
|
"validFor": [
|
|
@@ -5046,6 +6411,11 @@ export const schema = {
|
|
|
5046
6411
|
"@usevyre/vue@1.1.0+"
|
|
5047
6412
|
],
|
|
5048
6413
|
"changelog": {
|
|
6414
|
+
"1.2.0": {
|
|
6415
|
+
"date": "2026-05-16",
|
|
6416
|
+
"breaking": false,
|
|
6417
|
+
"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"
|
|
6418
|
+
},
|
|
5049
6419
|
"1.1.0": {
|
|
5050
6420
|
"date": "2026-05-15",
|
|
5051
6421
|
"breaking": false,
|
|
@@ -5386,7 +6756,7 @@ export const schema = {
|
|
|
5386
6756
|
]
|
|
5387
6757
|
},
|
|
5388
6758
|
"Calendar": {
|
|
5389
|
-
"description": "
|
|
6759
|
+
"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
6760
|
"import": "import { Calendar } from \"@usevyre/react\"",
|
|
5391
6761
|
"props": {
|
|
5392
6762
|
"value": {
|
|
@@ -5400,13 +6770,22 @@ export const schema = {
|
|
|
5400
6770
|
"disabled": {
|
|
5401
6771
|
"type": "boolean",
|
|
5402
6772
|
"default": false
|
|
6773
|
+
},
|
|
6774
|
+
"defaultMonth": {
|
|
6775
|
+
"type": "Date",
|
|
6776
|
+
"description": "Month to display initially when value is empty (uncontrolled view). Used by DateRangePicker for the second month."
|
|
5403
6777
|
}
|
|
5404
6778
|
},
|
|
5405
6779
|
"antiPatterns": [
|
|
5406
6780
|
{
|
|
5407
|
-
"pattern": "
|
|
5408
|
-
"reason": "Calendar
|
|
5409
|
-
"fix": "
|
|
6781
|
+
"pattern": "Calendar for an input field that opens a popover",
|
|
6782
|
+
"reason": "Calendar renders an always-visible grid, not a trigger",
|
|
6783
|
+
"fix": "Use <DatePicker /> (single date) or <DateRangePicker /> (range)"
|
|
6784
|
+
},
|
|
6785
|
+
{
|
|
6786
|
+
"pattern": "value as tuple for mode=\"single\"",
|
|
6787
|
+
"reason": "value type must match mode: Date for single, [Date,Date] for range, Date[] for multiple",
|
|
6788
|
+
"fix": "Pass value matching mode; use mode=\"range\" for [start,end]"
|
|
5410
6789
|
}
|
|
5411
6790
|
],
|
|
5412
6791
|
"examples": [
|
|
@@ -5416,39 +6795,120 @@ export const schema = {
|
|
|
5416
6795
|
}
|
|
5417
6796
|
]
|
|
5418
6797
|
},
|
|
5419
|
-
"
|
|
5420
|
-
"description": "
|
|
5421
|
-
"import": "import {
|
|
5422
|
-
"subcomponents": [
|
|
5423
|
-
"CardHeader",
|
|
5424
|
-
"CardBody",
|
|
5425
|
-
"CardFooter"
|
|
5426
|
-
],
|
|
6798
|
+
"DatePicker": {
|
|
6799
|
+
"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.",
|
|
6800
|
+
"import": "import { DatePicker } from \"@usevyre/react\"",
|
|
5427
6801
|
"props": {
|
|
5428
|
-
"
|
|
6802
|
+
"value": {
|
|
6803
|
+
"type": "Date | [Date, Date] | Date[] | null",
|
|
6804
|
+
"description": "Selected value (controlled). Type must match mode."
|
|
6805
|
+
},
|
|
6806
|
+
"onChange": {
|
|
6807
|
+
"type": "function",
|
|
6808
|
+
"description": "Callback when the selection changes; argument shape matches mode"
|
|
6809
|
+
},
|
|
6810
|
+
"mode": {
|
|
5429
6811
|
"type": "enum",
|
|
5430
6812
|
"values": [
|
|
5431
|
-
"
|
|
5432
|
-
"
|
|
5433
|
-
"
|
|
5434
|
-
"ghost",
|
|
5435
|
-
"accent"
|
|
6813
|
+
"single",
|
|
6814
|
+
"range",
|
|
6815
|
+
"multiple"
|
|
5436
6816
|
],
|
|
5437
|
-
"default": "
|
|
5438
|
-
"description": "
|
|
6817
|
+
"default": "single",
|
|
6818
|
+
"description": "Selection mode"
|
|
5439
6819
|
},
|
|
5440
|
-
"
|
|
5441
|
-
"type": "
|
|
5442
|
-
"default":
|
|
5443
|
-
"description": "
|
|
6820
|
+
"placeholder": {
|
|
6821
|
+
"type": "string",
|
|
6822
|
+
"default": "Pick a date",
|
|
6823
|
+
"description": "Trigger text when nothing is selected"
|
|
5444
6824
|
},
|
|
5445
|
-
"
|
|
6825
|
+
"showTime": {
|
|
5446
6826
|
"type": "boolean",
|
|
5447
6827
|
"default": false,
|
|
5448
|
-
"description": "Adds
|
|
5449
|
-
}
|
|
5450
|
-
|
|
5451
|
-
|
|
6828
|
+
"description": "Adds an HH:MM time picker (single mode)"
|
|
6829
|
+
},
|
|
6830
|
+
"minDate": {
|
|
6831
|
+
"type": "Date",
|
|
6832
|
+
"description": "Earliest selectable date"
|
|
6833
|
+
},
|
|
6834
|
+
"maxDate": {
|
|
6835
|
+
"type": "Date",
|
|
6836
|
+
"description": "Latest selectable date"
|
|
6837
|
+
},
|
|
6838
|
+
"disabled": {
|
|
6839
|
+
"type": "function",
|
|
6840
|
+
"description": "(date: Date) => boolean — disable specific dates"
|
|
6841
|
+
},
|
|
6842
|
+
"weekStartsOn": {
|
|
6843
|
+
"type": "enum",
|
|
6844
|
+
"values": [
|
|
6845
|
+
0,
|
|
6846
|
+
1
|
|
6847
|
+
],
|
|
6848
|
+
"default": 1,
|
|
6849
|
+
"description": "0 = Sunday, 1 = Monday"
|
|
6850
|
+
},
|
|
6851
|
+
"inputClassName": {
|
|
6852
|
+
"type": "string",
|
|
6853
|
+
"description": "Class on the trigger button"
|
|
6854
|
+
}
|
|
6855
|
+
},
|
|
6856
|
+
"antiPatterns": [
|
|
6857
|
+
{
|
|
6858
|
+
"pattern": "DatePicker mode=\"range\" for { from, to } object",
|
|
6859
|
+
"reason": "DatePicker range mode emits a [Date, Date] tuple, not a { from, to } object",
|
|
6860
|
+
"fix": "Use <DateRangePicker /> for the { from, to } object API + presets + dual month"
|
|
6861
|
+
},
|
|
6862
|
+
{
|
|
6863
|
+
"pattern": "DatePicker without value/onChange",
|
|
6864
|
+
"reason": "DatePicker is controlled — it has no internal selected state",
|
|
6865
|
+
"fix": "Provide value and onChange (e.g. from useState)"
|
|
6866
|
+
}
|
|
6867
|
+
],
|
|
6868
|
+
"examples": [
|
|
6869
|
+
{
|
|
6870
|
+
"description": "Single date field",
|
|
6871
|
+
"code": "const [date, setDate] = useState(null);\n<DatePicker value={date} onChange={setDate} placeholder=\"Pick a date\" />"
|
|
6872
|
+
},
|
|
6873
|
+
{
|
|
6874
|
+
"description": "Date + time",
|
|
6875
|
+
"code": "<DatePicker value={date} onChange={setDate} showTime />"
|
|
6876
|
+
}
|
|
6877
|
+
]
|
|
6878
|
+
},
|
|
6879
|
+
"Card": {
|
|
6880
|
+
"description": "Content container with optional header, body, and footer sections.",
|
|
6881
|
+
"import": "import { Card, CardHeader, CardBody, CardFooter } from \"@usevyre/react\"",
|
|
6882
|
+
"subcomponents": [
|
|
6883
|
+
"CardHeader",
|
|
6884
|
+
"CardBody",
|
|
6885
|
+
"CardFooter"
|
|
6886
|
+
],
|
|
6887
|
+
"props": {
|
|
6888
|
+
"variant": {
|
|
6889
|
+
"type": "enum",
|
|
6890
|
+
"values": [
|
|
6891
|
+
"default",
|
|
6892
|
+
"elevated",
|
|
6893
|
+
"outlined",
|
|
6894
|
+
"ghost",
|
|
6895
|
+
"accent"
|
|
6896
|
+
],
|
|
6897
|
+
"default": "default",
|
|
6898
|
+
"description": "Visual style of the card"
|
|
6899
|
+
},
|
|
6900
|
+
"hoverable": {
|
|
6901
|
+
"type": "boolean",
|
|
6902
|
+
"default": false,
|
|
6903
|
+
"description": "Adds hover lift effect"
|
|
6904
|
+
},
|
|
6905
|
+
"clickable": {
|
|
6906
|
+
"type": "boolean",
|
|
6907
|
+
"default": false,
|
|
6908
|
+
"description": "Adds cursor pointer and active press feedback"
|
|
6909
|
+
}
|
|
6910
|
+
},
|
|
6911
|
+
"antiPatterns": [
|
|
5452
6912
|
{
|
|
5453
6913
|
"pattern": "variant=\"primary\"",
|
|
5454
6914
|
"reason": "Card has no 'primary' variant",
|
|
@@ -5506,6 +6966,150 @@ export const schema = {
|
|
|
5506
6966
|
}
|
|
5507
6967
|
]
|
|
5508
6968
|
},
|
|
6969
|
+
"RadioGroup": {
|
|
6970
|
+
"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.",
|
|
6971
|
+
"import": "import { RadioGroup, Radio } from \"@usevyre/react\"",
|
|
6972
|
+
"subcomponents": [
|
|
6973
|
+
"Radio"
|
|
6974
|
+
],
|
|
6975
|
+
"props": {
|
|
6976
|
+
"value": {
|
|
6977
|
+
"type": "string",
|
|
6978
|
+
"description": "Selected value (controlled). Vue: v-model."
|
|
6979
|
+
},
|
|
6980
|
+
"defaultValue": {
|
|
6981
|
+
"type": "string",
|
|
6982
|
+
"description": "Initial value (uncontrolled, React only)"
|
|
6983
|
+
},
|
|
6984
|
+
"onChange": {
|
|
6985
|
+
"type": "function",
|
|
6986
|
+
"description": "(value: string) => void. Vue: update:modelValue / v-model."
|
|
6987
|
+
},
|
|
6988
|
+
"name": {
|
|
6989
|
+
"type": "string",
|
|
6990
|
+
"description": "Radio input name; auto-generated if omitted"
|
|
6991
|
+
},
|
|
6992
|
+
"disabled": {
|
|
6993
|
+
"type": "boolean",
|
|
6994
|
+
"default": false,
|
|
6995
|
+
"description": "Disable the whole group"
|
|
6996
|
+
},
|
|
6997
|
+
"size": {
|
|
6998
|
+
"type": "enum",
|
|
6999
|
+
"values": [
|
|
7000
|
+
"sm",
|
|
7001
|
+
"md"
|
|
7002
|
+
],
|
|
7003
|
+
"default": "md",
|
|
7004
|
+
"description": "Control size"
|
|
7005
|
+
},
|
|
7006
|
+
"orientation": {
|
|
7007
|
+
"type": "enum",
|
|
7008
|
+
"values": [
|
|
7009
|
+
"vertical",
|
|
7010
|
+
"horizontal"
|
|
7011
|
+
],
|
|
7012
|
+
"default": "vertical",
|
|
7013
|
+
"description": "Layout direction"
|
|
7014
|
+
},
|
|
7015
|
+
"options": {
|
|
7016
|
+
"type": "{ value: string; label?: string; description?: string; disabled?: boolean }[]",
|
|
7017
|
+
"description": "Data-driven options. Omit to pass <Radio> children instead."
|
|
7018
|
+
}
|
|
7019
|
+
},
|
|
7020
|
+
"antiPatterns": [
|
|
7021
|
+
{
|
|
7022
|
+
"pattern": "<Radio> used outside a <RadioGroup>",
|
|
7023
|
+
"reason": "Radio reads selection + name from RadioGroup context; it throws without a provider",
|
|
7024
|
+
"fix": "Always wrap <Radio> in <RadioGroup>"
|
|
7025
|
+
},
|
|
7026
|
+
{
|
|
7027
|
+
"pattern": "RadioGroup without value/onChange (React) or v-model (Vue)",
|
|
7028
|
+
"reason": "RadioGroup is controlled; selection won't update",
|
|
7029
|
+
"fix": "Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React"
|
|
7030
|
+
},
|
|
7031
|
+
{
|
|
7032
|
+
"pattern": "Using Checkbox for mutually-exclusive choices",
|
|
7033
|
+
"reason": "Radio expresses single-choice semantics + keyboard model",
|
|
7034
|
+
"fix": "Use RadioGroup + Radio (or options) for one-of-many"
|
|
7035
|
+
}
|
|
7036
|
+
],
|
|
7037
|
+
"examples": [
|
|
7038
|
+
{
|
|
7039
|
+
"description": "Data-driven",
|
|
7040
|
+
"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/>"
|
|
7041
|
+
},
|
|
7042
|
+
{
|
|
7043
|
+
"description": "Composable children",
|
|
7044
|
+
"code": "<RadioGroup value={plan} onChange={setPlan} orientation=\"horizontal\">\n <Radio value=\"free\" label=\"Free\" />\n <Radio value=\"pro\" label=\"Pro\" />\n</RadioGroup>"
|
|
7045
|
+
}
|
|
7046
|
+
]
|
|
7047
|
+
},
|
|
7048
|
+
"RichTextEditor": {
|
|
7049
|
+
"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.",
|
|
7050
|
+
"import": "import { RichTextEditor } from \"@usevyre/react\"",
|
|
7051
|
+
"props": {
|
|
7052
|
+
"value": {
|
|
7053
|
+
"type": "string",
|
|
7054
|
+
"description": "HTML content (controlled). React: value + onChange. Vue: v-model."
|
|
7055
|
+
},
|
|
7056
|
+
"onChange": {
|
|
7057
|
+
"type": "function",
|
|
7058
|
+
"description": "(html: string) => void. Vue: update:modelValue / v-model."
|
|
7059
|
+
},
|
|
7060
|
+
"placeholder": {
|
|
7061
|
+
"type": "string",
|
|
7062
|
+
"default": "Write something…",
|
|
7063
|
+
"description": "Shown when the editor is empty"
|
|
7064
|
+
},
|
|
7065
|
+
"disabled": {
|
|
7066
|
+
"type": "boolean",
|
|
7067
|
+
"default": false,
|
|
7068
|
+
"description": "Not editable, dimmed; toolbar disabled"
|
|
7069
|
+
},
|
|
7070
|
+
"readOnly": {
|
|
7071
|
+
"type": "boolean",
|
|
7072
|
+
"default": false,
|
|
7073
|
+
"description": "Not editable; toolbar hidden entirely"
|
|
7074
|
+
},
|
|
7075
|
+
"toolbar": {
|
|
7076
|
+
"type": "RichTextTool[]",
|
|
7077
|
+
"description": "Which buttons to show. Default = all. Tools: \"bold\"|\"italic\"|\"underline\"|\"strike\"|\"h1\"|\"h2\"|\"h3\"|\"ul\"|\"ol\"|\"quote\"|\"code\"|\"link\"|\"clear\""
|
|
7078
|
+
},
|
|
7079
|
+
"minHeight": {
|
|
7080
|
+
"type": "string",
|
|
7081
|
+
"default": "10rem",
|
|
7082
|
+
"description": "CSS min-height of the editable area"
|
|
7083
|
+
}
|
|
7084
|
+
},
|
|
7085
|
+
"antiPatterns": [
|
|
7086
|
+
{
|
|
7087
|
+
"pattern": "RichTextEditor without value/onChange (React) or v-model (Vue)",
|
|
7088
|
+
"reason": "It is fully controlled; edits won't persist",
|
|
7089
|
+
"fix": "Keep the HTML string in state and update it in onChange / v-model"
|
|
7090
|
+
},
|
|
7091
|
+
{
|
|
7092
|
+
"pattern": "Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising",
|
|
7093
|
+
"reason": "value is raw HTML the user typed",
|
|
7094
|
+
"fix": "Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output"
|
|
7095
|
+
},
|
|
7096
|
+
{
|
|
7097
|
+
"pattern": "toolbar=\"bold\" (string)",
|
|
7098
|
+
"reason": "toolbar is an array of tool ids",
|
|
7099
|
+
"fix": "Pass an array, e.g. toolbar={[\"bold\",\"italic\",\"link\"]}"
|
|
7100
|
+
}
|
|
7101
|
+
],
|
|
7102
|
+
"examples": [
|
|
7103
|
+
{
|
|
7104
|
+
"description": "Controlled editor",
|
|
7105
|
+
"code": "const [html, setHtml] = useState(\"<p>Hello <strong>world</strong></p>\");\n<RichTextEditor value={html} onChange={setHtml} placeholder=\"Write…\" />"
|
|
7106
|
+
},
|
|
7107
|
+
{
|
|
7108
|
+
"description": "Minimal toolbar",
|
|
7109
|
+
"code": "<RichTextEditor\n value={html}\n onChange={setHtml}\n toolbar={[\"bold\", \"italic\", \"link\"]}\n/>"
|
|
7110
|
+
}
|
|
7111
|
+
]
|
|
7112
|
+
},
|
|
5509
7113
|
"Command": {
|
|
5510
7114
|
"description": "Command palette / search dialog. Use for search-first navigation or quick actions.",
|
|
5511
7115
|
"import": "import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandDialog } from \"@usevyre/react\"",
|
|
@@ -5575,7 +7179,7 @@ export const schema = {
|
|
|
5575
7179
|
]
|
|
5576
7180
|
},
|
|
5577
7181
|
"Field": {
|
|
5578
|
-
"description": "Form field wrapper
|
|
7182
|
+
"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
7183
|
"import": "import { Field, Input, Textarea } from \"@usevyre/react\"",
|
|
5580
7184
|
"props": {
|
|
5581
7185
|
"label": {
|
|
@@ -5608,6 +7212,11 @@ export const schema = {
|
|
|
5608
7212
|
"pattern": "Applying state prop directly to Input",
|
|
5609
7213
|
"reason": "state belongs on Field, not on the Input itself",
|
|
5610
7214
|
"fix": "Wrap Input in <Field state=\"error\"> to apply validation styling"
|
|
7215
|
+
},
|
|
7216
|
+
{
|
|
7217
|
+
"pattern": "Mixing props label/hint AND FieldLabel/FieldError for the same field",
|
|
7218
|
+
"reason": "Duplicates the label / message",
|
|
7219
|
+
"fix": "Pick one: either props-based (label/hint/state) OR composable parts"
|
|
5611
7220
|
}
|
|
5612
7221
|
],
|
|
5613
7222
|
"examples": [
|
|
@@ -5618,13 +7227,28 @@ export const schema = {
|
|
|
5618
7227
|
{
|
|
5619
7228
|
"description": "Search field with left icon",
|
|
5620
7229
|
"code": "<Field label=\"Search\">\n <Input leftElement={<SearchIcon />} placeholder=\"Search...\" />\n</Field>"
|
|
7230
|
+
},
|
|
7231
|
+
{
|
|
7232
|
+
"description": "Composable field with explicit parts",
|
|
7233
|
+
"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
7234
|
}
|
|
7235
|
+
],
|
|
7236
|
+
"subcomponents": [
|
|
7237
|
+
"FieldLabel",
|
|
7238
|
+
"FieldDescription",
|
|
7239
|
+
"FieldError",
|
|
7240
|
+
"FieldGroup",
|
|
7241
|
+
"FieldSet"
|
|
5622
7242
|
]
|
|
5623
7243
|
},
|
|
5624
7244
|
"Input": {
|
|
5625
7245
|
"description": "Text input field. Wrap in Field for labels and validation. Use leftElement/rightElement for icons.",
|
|
5626
7246
|
"import": "import { Input } from \"@usevyre/react\"",
|
|
5627
7247
|
"props": {
|
|
7248
|
+
"modelValue": {
|
|
7249
|
+
"type": "string | number",
|
|
7250
|
+
"description": "Vue only: enables v-model (two-way binding). React: use native value + onChange. Native input attrs (type, placeholder, disabled…) work in both."
|
|
7251
|
+
},
|
|
5628
7252
|
"size": {
|
|
5629
7253
|
"type": "enum",
|
|
5630
7254
|
"values": [
|
|
@@ -5654,6 +7278,11 @@ export const schema = {
|
|
|
5654
7278
|
"pattern": "type=\"search\" for search UI",
|
|
5655
7279
|
"reason": "Use Command component for search-first interfaces",
|
|
5656
7280
|
"fix": "Import Command from @usevyre/react for search palettes"
|
|
7281
|
+
},
|
|
7282
|
+
{
|
|
7283
|
+
"pattern": "Vue: binding Input/Textarea value without v-model",
|
|
7284
|
+
"reason": "Vue Input & Textarea support v-model (modelValue); manual :value alone won't update",
|
|
7285
|
+
"fix": "Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange"
|
|
5657
7286
|
}
|
|
5658
7287
|
],
|
|
5659
7288
|
"examples": [
|
|
@@ -6678,12 +8307,304 @@ export const schema = {
|
|
|
6678
8307
|
"code": "<TagGroup gap=\"sm\">\n <Tag>React</Tag>\n <Tag>Vue</Tag>\n <Tag variant=\"accent\">TypeScript</Tag>\n</TagGroup>"
|
|
6679
8308
|
}
|
|
6680
8309
|
]
|
|
8310
|
+
},
|
|
8311
|
+
"Item": {
|
|
8312
|
+
"description": "Layout primitive for list rows, settings rows, and notification rows. Denser than Card — use Item (not Card) for repeated list rows.",
|
|
8313
|
+
"import": "import { Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions, ItemGroup } from \"@usevyre/react\"",
|
|
8314
|
+
"subcomponents": [
|
|
8315
|
+
"ItemGroup",
|
|
8316
|
+
"ItemMedia",
|
|
8317
|
+
"ItemContent",
|
|
8318
|
+
"ItemTitle",
|
|
8319
|
+
"ItemDescription",
|
|
8320
|
+
"ItemActions"
|
|
8321
|
+
],
|
|
8322
|
+
"props": {
|
|
8323
|
+
"variant": {
|
|
8324
|
+
"type": "enum",
|
|
8325
|
+
"values": [
|
|
8326
|
+
"default",
|
|
8327
|
+
"outlined",
|
|
8328
|
+
"muted",
|
|
8329
|
+
"plain"
|
|
8330
|
+
],
|
|
8331
|
+
"default": "default",
|
|
8332
|
+
"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)."
|
|
8333
|
+
},
|
|
8334
|
+
"size": {
|
|
8335
|
+
"type": "enum",
|
|
8336
|
+
"values": [
|
|
8337
|
+
"sm",
|
|
8338
|
+
"md",
|
|
8339
|
+
"lg"
|
|
8340
|
+
],
|
|
8341
|
+
"default": "md",
|
|
8342
|
+
"description": "Vertical density / text size of the row"
|
|
8343
|
+
},
|
|
8344
|
+
"clickable": {
|
|
8345
|
+
"type": "boolean",
|
|
8346
|
+
"default": false,
|
|
8347
|
+
"description": "Adds pointer cursor, hover background, focus ring, and role=button"
|
|
8348
|
+
}
|
|
8349
|
+
},
|
|
8350
|
+
"antiPatterns": [
|
|
8351
|
+
{
|
|
8352
|
+
"pattern": "Card used for repeated list rows",
|
|
8353
|
+
"reason": "Card is a content container; Item is the dense list-row primitive",
|
|
8354
|
+
"fix": "Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows"
|
|
8355
|
+
},
|
|
8356
|
+
{
|
|
8357
|
+
"pattern": "Item variant=\"primary\"",
|
|
8358
|
+
"reason": "Item has no 'primary' variant",
|
|
8359
|
+
"fix": "Use variant=\"default\" | \"outlined\" | \"muted\""
|
|
8360
|
+
},
|
|
8361
|
+
{
|
|
8362
|
+
"pattern": "raw text directly inside Item",
|
|
8363
|
+
"reason": "Item lays out media/content/actions columns; bare text breaks alignment",
|
|
8364
|
+
"fix": "Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>"
|
|
8365
|
+
}
|
|
8366
|
+
],
|
|
8367
|
+
"examples": [
|
|
8368
|
+
{
|
|
8369
|
+
"description": "Settings row with media, content and a trailing switch",
|
|
8370
|
+
"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>"
|
|
8371
|
+
},
|
|
8372
|
+
{
|
|
8373
|
+
"description": "Grouped clickable list with dividers",
|
|
8374
|
+
"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>"
|
|
8375
|
+
}
|
|
8376
|
+
]
|
|
8377
|
+
},
|
|
8378
|
+
"Kanban": {
|
|
8379
|
+
"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.",
|
|
8380
|
+
"import": "import { Kanban } from \"@usevyre/react\"",
|
|
8381
|
+
"props": {
|
|
8382
|
+
"value": {
|
|
8383
|
+
"type": "KanbanColumn[]",
|
|
8384
|
+
"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."
|
|
8385
|
+
},
|
|
8386
|
+
"onChange": {
|
|
8387
|
+
"type": "function",
|
|
8388
|
+
"description": "(next: KanbanColumn[]) => void — called with the next columns array after a drag move. Required; set it back into your state."
|
|
8389
|
+
},
|
|
8390
|
+
"renderCard": {
|
|
8391
|
+
"type": "function",
|
|
8392
|
+
"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."
|
|
8393
|
+
},
|
|
8394
|
+
"onCardClick": {
|
|
8395
|
+
"type": "function",
|
|
8396
|
+
"description": "(card, column) => void — fired on click/Enter/Space (not after a drag). Vue: @card-click."
|
|
8397
|
+
}
|
|
8398
|
+
},
|
|
8399
|
+
"antiPatterns": [
|
|
8400
|
+
{
|
|
8401
|
+
"pattern": "Kanban without onChange (or ignoring it)",
|
|
8402
|
+
"reason": "Kanban holds no internal data state; dropped cards won't move unless you apply onChange",
|
|
8403
|
+
"fix": "Store columns in state and setColumns in onChange (v-model in Vue)"
|
|
8404
|
+
},
|
|
8405
|
+
{
|
|
8406
|
+
"pattern": "Duplicate card ids across columns",
|
|
8407
|
+
"reason": "Card moves are resolved by id; duplicates corrupt the move",
|
|
8408
|
+
"fix": "Use globally-unique card ids across the entire board"
|
|
8409
|
+
},
|
|
8410
|
+
{
|
|
8411
|
+
"pattern": "Mutating value in place then calling onChange",
|
|
8412
|
+
"reason": "React/Vue need a new array reference to re-render reliably",
|
|
8413
|
+
"fix": "Pass the new array Kanban gives you straight to setState / v-model"
|
|
8414
|
+
},
|
|
8415
|
+
{
|
|
8416
|
+
"pattern": "color=\"blue\" (or any non-semantic value)",
|
|
8417
|
+
"reason": "color is a fixed semantic set, not an arbitrary CSS color",
|
|
8418
|
+
"fix": "Use one of: \"default\" | \"accent\" | \"teal\" | \"success\" | \"warning\" | \"danger\""
|
|
8419
|
+
}
|
|
8420
|
+
],
|
|
8421
|
+
"examples": [
|
|
8422
|
+
{
|
|
8423
|
+
"description": "Controlled board",
|
|
8424
|
+
"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} />"
|
|
8425
|
+
},
|
|
8426
|
+
{
|
|
8427
|
+
"description": "Custom card body + click handler",
|
|
8428
|
+
"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/>"
|
|
8429
|
+
},
|
|
8430
|
+
{
|
|
8431
|
+
"description": "Tinted columns/cards + complex card content",
|
|
8432
|
+
"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/>"
|
|
8433
|
+
}
|
|
8434
|
+
]
|
|
8435
|
+
},
|
|
8436
|
+
"Conversation": {
|
|
8437
|
+
"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.",
|
|
8438
|
+
"import": "import { Conversation } from \"@usevyre/react\"",
|
|
8439
|
+
"props": {
|
|
8440
|
+
"value": {
|
|
8441
|
+
"type": "ConversationMessage[]",
|
|
8442
|
+
"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."
|
|
8443
|
+
},
|
|
8444
|
+
"currentUserId": {
|
|
8445
|
+
"type": "string",
|
|
8446
|
+
"description": "Whose messages are outgoing (aligned right). Required — match against message.authorId."
|
|
8447
|
+
},
|
|
8448
|
+
"composer": {
|
|
8449
|
+
"type": "boolean",
|
|
8450
|
+
"default": false,
|
|
8451
|
+
"description": "Show the built-in input + Send button. Pair with onSend."
|
|
8452
|
+
},
|
|
8453
|
+
"onSend": {
|
|
8454
|
+
"type": "function",
|
|
8455
|
+
"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."
|
|
8456
|
+
},
|
|
8457
|
+
"placeholder": {
|
|
8458
|
+
"type": "string",
|
|
8459
|
+
"default": "Write a message…",
|
|
8460
|
+
"description": "Composer input placeholder."
|
|
8461
|
+
},
|
|
8462
|
+
"typing": {
|
|
8463
|
+
"type": "boolean | string",
|
|
8464
|
+
"default": false,
|
|
8465
|
+
"description": "Show an incoming typing indicator. Pass a string to label it."
|
|
8466
|
+
},
|
|
8467
|
+
"allowAttachments": {
|
|
8468
|
+
"type": "boolean",
|
|
8469
|
+
"default": false,
|
|
8470
|
+
"description": "Show a 📎 attach button + staged-file chips in the built-in composer. Files come back via onSend's second arg."
|
|
8471
|
+
},
|
|
8472
|
+
"accept": {
|
|
8473
|
+
"type": "string",
|
|
8474
|
+
"description": "Forwarded to the file input's accept attribute, e.g. \"image/*\"."
|
|
8475
|
+
},
|
|
8476
|
+
"renderMessage": {
|
|
8477
|
+
"type": "function",
|
|
8478
|
+
"description": "(message, meta) => ReactNode — custom bubble body. meta = { outgoing, isGroupStart, isGroupEnd }. Vue: #message scoped slot."
|
|
8479
|
+
},
|
|
8480
|
+
"renderComposer": {
|
|
8481
|
+
"type": "function",
|
|
8482
|
+
"description": "(api) => ReactNode — replace the composer entirely. api = { value, setValue, files, setFiles, send }. Vue: #composer scoped slot."
|
|
8483
|
+
}
|
|
8484
|
+
},
|
|
8485
|
+
"antiPatterns": [
|
|
8486
|
+
{
|
|
8487
|
+
"pattern": "Conversation without currentUserId",
|
|
8488
|
+
"reason": "Alignment (incoming vs outgoing) is decided by authorId === currentUserId",
|
|
8489
|
+
"fix": "Always pass currentUserId matching one of the message authorId values"
|
|
8490
|
+
},
|
|
8491
|
+
{
|
|
8492
|
+
"pattern": "Expecting Conversation to store/append messages",
|
|
8493
|
+
"reason": "Conversation is controlled & stateless for data, like Kanban/DataGrid",
|
|
8494
|
+
"fix": "Append to your own state in onSend (or @send) and pass it back via value"
|
|
8495
|
+
},
|
|
8496
|
+
{
|
|
8497
|
+
"pattern": "composer without onSend (React) / @send (Vue)",
|
|
8498
|
+
"reason": "The built-in composer emits text; nothing happens unless you handle it",
|
|
8499
|
+
"fix": "Provide onSend / @send to append the message to value"
|
|
8500
|
+
},
|
|
8501
|
+
{
|
|
8502
|
+
"pattern": "Treating onSend as (text) only when using allowAttachments",
|
|
8503
|
+
"reason": "With allowAttachments, staged files arrive as the 2nd arg; ignoring it drops attachments",
|
|
8504
|
+
"fix": "Handle onSend(text, files) — map files to message attachments and append"
|
|
8505
|
+
}
|
|
8506
|
+
],
|
|
8507
|
+
"examples": [
|
|
8508
|
+
{
|
|
8509
|
+
"description": "Controlled thread with built-in composer",
|
|
8510
|
+
"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/>"
|
|
8511
|
+
},
|
|
8512
|
+
{
|
|
8513
|
+
"description": "Typing indicator + custom bubble",
|
|
8514
|
+
"code": "<Conversation\n value={messages}\n currentUserId=\"me\"\n typing=\"Sam is typing\"\n renderMessage={(m) => <strong>{m.text}</strong>}\n/>"
|
|
8515
|
+
},
|
|
8516
|
+
{
|
|
8517
|
+
"description": "Message with image + file attachments",
|
|
8518
|
+
"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\" />"
|
|
8519
|
+
}
|
|
8520
|
+
]
|
|
8521
|
+
},
|
|
8522
|
+
"DateRangePicker": {
|
|
8523
|
+
"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.",
|
|
8524
|
+
"import": "import { DateRangePicker } from \"@usevyre/react\"",
|
|
8525
|
+
"props": {
|
|
8526
|
+
"value": {
|
|
8527
|
+
"type": "{ from: Date | null; to: Date | null } | null",
|
|
8528
|
+
"description": "Selected range (controlled). Pass an OBJECT, not a tuple."
|
|
8529
|
+
},
|
|
8530
|
+
"onChange": {
|
|
8531
|
+
"type": "function",
|
|
8532
|
+
"description": "Callback (range: { from, to }) => void when the range changes"
|
|
8533
|
+
},
|
|
8534
|
+
"placeholder": {
|
|
8535
|
+
"type": "string",
|
|
8536
|
+
"default": "Pick a date range",
|
|
8537
|
+
"description": "Trigger text when no range is selected"
|
|
8538
|
+
},
|
|
8539
|
+
"numberOfMonths": {
|
|
8540
|
+
"type": "enum",
|
|
8541
|
+
"values": [
|
|
8542
|
+
1,
|
|
8543
|
+
2
|
|
8544
|
+
],
|
|
8545
|
+
"default": 2,
|
|
8546
|
+
"description": "How many month calendars to show side-by-side"
|
|
8547
|
+
},
|
|
8548
|
+
"presets": {
|
|
8549
|
+
"type": "boolean | DateRangePreset[]",
|
|
8550
|
+
"default": false,
|
|
8551
|
+
"description": "true shows built-in presets (Today, Yesterday, Last 7/30 days, This/Last month); or pass a custom array of { label, range() }"
|
|
8552
|
+
},
|
|
8553
|
+
"minDate": {
|
|
8554
|
+
"type": "Date",
|
|
8555
|
+
"description": "Earliest selectable date"
|
|
8556
|
+
},
|
|
8557
|
+
"maxDate": {
|
|
8558
|
+
"type": "Date",
|
|
8559
|
+
"description": "Latest selectable date"
|
|
8560
|
+
},
|
|
8561
|
+
"disabled": {
|
|
8562
|
+
"type": "function",
|
|
8563
|
+
"description": "(date: Date) => boolean — disable specific dates"
|
|
8564
|
+
},
|
|
8565
|
+
"weekStartsOn": {
|
|
8566
|
+
"type": "enum",
|
|
8567
|
+
"values": [
|
|
8568
|
+
0,
|
|
8569
|
+
1
|
|
8570
|
+
],
|
|
8571
|
+
"default": 1,
|
|
8572
|
+
"description": "0 = Sunday, 1 = Monday"
|
|
8573
|
+
}
|
|
8574
|
+
},
|
|
8575
|
+
"antiPatterns": [
|
|
8576
|
+
{
|
|
8577
|
+
"pattern": "value={[from, to]}",
|
|
8578
|
+
"reason": "DateRangePicker uses a { from, to } object, not a [Date, Date] tuple like Calendar mode=range",
|
|
8579
|
+
"fix": "Use value={{ from, to }} and read range.from / range.to"
|
|
8580
|
+
},
|
|
8581
|
+
{
|
|
8582
|
+
"pattern": "DateRangePicker for a single date",
|
|
8583
|
+
"reason": "DateRangePicker always selects a start AND end",
|
|
8584
|
+
"fix": "Use <DatePicker /> for a single date"
|
|
8585
|
+
},
|
|
8586
|
+
{
|
|
8587
|
+
"pattern": "presets=\"true\" (string)",
|
|
8588
|
+
"reason": "presets is a boolean or an array, not a string",
|
|
8589
|
+
"fix": "Use the bare prop: presets (or presets={true})"
|
|
8590
|
+
}
|
|
8591
|
+
],
|
|
8592
|
+
"examples": [
|
|
8593
|
+
{
|
|
8594
|
+
"description": "Range picker with built-in presets",
|
|
8595
|
+
"code": "const [range, setRange] = useState({ from: null, to: null });\n<DateRangePicker value={range} onChange={setRange} presets />"
|
|
8596
|
+
},
|
|
8597
|
+
{
|
|
8598
|
+
"description": "Single month, no presets",
|
|
8599
|
+
"code": "<DateRangePicker value={range} onChange={setRange} numberOfMonths={1} />"
|
|
8600
|
+
}
|
|
8601
|
+
]
|
|
6681
8602
|
}
|
|
6682
8603
|
}
|
|
6683
8604
|
};
|
|
6684
8605
|
|
|
6685
8606
|
export const antiPatterns = {
|
|
6686
|
-
"version": "1.
|
|
8607
|
+
"version": "1.2.0",
|
|
6687
8608
|
"rules": [
|
|
6688
8609
|
{
|
|
6689
8610
|
"component": "Accordion",
|
|
@@ -6785,9 +8706,30 @@ export const antiPatterns = {
|
|
|
6785
8706
|
},
|
|
6786
8707
|
{
|
|
6787
8708
|
"component": "Calendar",
|
|
6788
|
-
"pattern": "
|
|
6789
|
-
"reason": "Calendar
|
|
6790
|
-
"fix": "
|
|
8709
|
+
"pattern": "Calendar for an input field that opens a popover",
|
|
8710
|
+
"reason": "Calendar renders an always-visible grid, not a trigger",
|
|
8711
|
+
"fix": "Use <DatePicker /> (single date) or <DateRangePicker /> (range)",
|
|
8712
|
+
"severity": "error"
|
|
8713
|
+
},
|
|
8714
|
+
{
|
|
8715
|
+
"component": "Calendar",
|
|
8716
|
+
"pattern": "value as tuple for mode=\"single\"",
|
|
8717
|
+
"reason": "value type must match mode: Date for single, [Date,Date] for range, Date[] for multiple",
|
|
8718
|
+
"fix": "Pass value matching mode; use mode=\"range\" for [start,end]",
|
|
8719
|
+
"severity": "error"
|
|
8720
|
+
},
|
|
8721
|
+
{
|
|
8722
|
+
"component": "DatePicker",
|
|
8723
|
+
"pattern": "DatePicker mode=\"range\" for { from, to } object",
|
|
8724
|
+
"reason": "DatePicker range mode emits a [Date, Date] tuple, not a { from, to } object",
|
|
8725
|
+
"fix": "Use <DateRangePicker /> for the { from, to } object API + presets + dual month",
|
|
8726
|
+
"severity": "error"
|
|
8727
|
+
},
|
|
8728
|
+
{
|
|
8729
|
+
"component": "DatePicker",
|
|
8730
|
+
"pattern": "DatePicker without value/onChange",
|
|
8731
|
+
"reason": "DatePicker is controlled — it has no internal selected state",
|
|
8732
|
+
"fix": "Provide value and onChange (e.g. from useState)",
|
|
6791
8733
|
"severity": "error"
|
|
6792
8734
|
},
|
|
6793
8735
|
{
|
|
@@ -6804,6 +8746,48 @@ export const antiPatterns = {
|
|
|
6804
8746
|
"fix": "Use size=\"md\"",
|
|
6805
8747
|
"severity": "error"
|
|
6806
8748
|
},
|
|
8749
|
+
{
|
|
8750
|
+
"component": "RadioGroup",
|
|
8751
|
+
"pattern": "<Radio> used outside a <RadioGroup>",
|
|
8752
|
+
"reason": "Radio reads selection + name from RadioGroup context; it throws without a provider",
|
|
8753
|
+
"fix": "Always wrap <Radio> in <RadioGroup>",
|
|
8754
|
+
"severity": "error"
|
|
8755
|
+
},
|
|
8756
|
+
{
|
|
8757
|
+
"component": "RadioGroup",
|
|
8758
|
+
"pattern": "RadioGroup without value/onChange (React) or v-model (Vue)",
|
|
8759
|
+
"reason": "RadioGroup is controlled; selection won't update",
|
|
8760
|
+
"fix": "Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React",
|
|
8761
|
+
"severity": "error"
|
|
8762
|
+
},
|
|
8763
|
+
{
|
|
8764
|
+
"component": "RadioGroup",
|
|
8765
|
+
"pattern": "Using Checkbox for mutually-exclusive choices",
|
|
8766
|
+
"reason": "Radio expresses single-choice semantics + keyboard model",
|
|
8767
|
+
"fix": "Use RadioGroup + Radio (or options) for one-of-many",
|
|
8768
|
+
"severity": "error"
|
|
8769
|
+
},
|
|
8770
|
+
{
|
|
8771
|
+
"component": "RichTextEditor",
|
|
8772
|
+
"pattern": "RichTextEditor without value/onChange (React) or v-model (Vue)",
|
|
8773
|
+
"reason": "It is fully controlled; edits won't persist",
|
|
8774
|
+
"fix": "Keep the HTML string in state and update it in onChange / v-model",
|
|
8775
|
+
"severity": "error"
|
|
8776
|
+
},
|
|
8777
|
+
{
|
|
8778
|
+
"component": "RichTextEditor",
|
|
8779
|
+
"pattern": "Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising",
|
|
8780
|
+
"reason": "value is raw HTML the user typed",
|
|
8781
|
+
"fix": "Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output",
|
|
8782
|
+
"severity": "error"
|
|
8783
|
+
},
|
|
8784
|
+
{
|
|
8785
|
+
"component": "RichTextEditor",
|
|
8786
|
+
"pattern": "toolbar=\"bold\" (string)",
|
|
8787
|
+
"reason": "toolbar is an array of tool ids",
|
|
8788
|
+
"fix": "Pass an array, e.g. toolbar={[\"bold\",\"italic\",\"link\"]}",
|
|
8789
|
+
"severity": "error"
|
|
8790
|
+
},
|
|
6807
8791
|
{
|
|
6808
8792
|
"component": "Command",
|
|
6809
8793
|
"pattern": "Using Input type=\"search\" for search UI",
|
|
@@ -6825,6 +8809,13 @@ export const antiPatterns = {
|
|
|
6825
8809
|
"fix": "Wrap Input in <Field state=\"error\"> to apply validation styling",
|
|
6826
8810
|
"severity": "error"
|
|
6827
8811
|
},
|
|
8812
|
+
{
|
|
8813
|
+
"component": "Field",
|
|
8814
|
+
"pattern": "Mixing props label/hint AND FieldLabel/FieldError for the same field",
|
|
8815
|
+
"reason": "Duplicates the label / message",
|
|
8816
|
+
"fix": "Pick one: either props-based (label/hint/state) OR composable parts",
|
|
8817
|
+
"severity": "error"
|
|
8818
|
+
},
|
|
6828
8819
|
{
|
|
6829
8820
|
"component": "Input",
|
|
6830
8821
|
"pattern": "size=\"icon\"",
|
|
@@ -6839,6 +8830,13 @@ export const antiPatterns = {
|
|
|
6839
8830
|
"fix": "Import Command from @usevyre/react for search palettes",
|
|
6840
8831
|
"severity": "error"
|
|
6841
8832
|
},
|
|
8833
|
+
{
|
|
8834
|
+
"component": "Input",
|
|
8835
|
+
"pattern": "Vue: binding Input/Textarea value without v-model",
|
|
8836
|
+
"reason": "Vue Input & Textarea support v-model (modelValue); manual :value alone won't update",
|
|
8837
|
+
"fix": "Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange",
|
|
8838
|
+
"severity": "error"
|
|
8839
|
+
},
|
|
6842
8840
|
{
|
|
6843
8841
|
"component": "Modal",
|
|
6844
8842
|
"pattern": "size=\"xl\"",
|
|
@@ -7006,19 +9004,122 @@ export const antiPatterns = {
|
|
|
7006
9004
|
"reason": "TagGroup is display-only",
|
|
7007
9005
|
"fix": "Use TagsInput for an editable tag field",
|
|
7008
9006
|
"severity": "error"
|
|
9007
|
+
},
|
|
9008
|
+
{
|
|
9009
|
+
"component": "Item",
|
|
9010
|
+
"pattern": "Card used for repeated list rows",
|
|
9011
|
+
"reason": "Card is a content container; Item is the dense list-row primitive",
|
|
9012
|
+
"fix": "Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows",
|
|
9013
|
+
"severity": "error"
|
|
9014
|
+
},
|
|
9015
|
+
{
|
|
9016
|
+
"component": "Item",
|
|
9017
|
+
"pattern": "Item variant=\"primary\"",
|
|
9018
|
+
"reason": "Item has no 'primary' variant",
|
|
9019
|
+
"fix": "Use variant=\"default\" | \"outlined\" | \"muted\"",
|
|
9020
|
+
"severity": "error"
|
|
9021
|
+
},
|
|
9022
|
+
{
|
|
9023
|
+
"component": "Item",
|
|
9024
|
+
"pattern": "raw text directly inside Item",
|
|
9025
|
+
"reason": "Item lays out media/content/actions columns; bare text breaks alignment",
|
|
9026
|
+
"fix": "Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>",
|
|
9027
|
+
"severity": "error"
|
|
9028
|
+
},
|
|
9029
|
+
{
|
|
9030
|
+
"component": "Kanban",
|
|
9031
|
+
"pattern": "Kanban without onChange (or ignoring it)",
|
|
9032
|
+
"reason": "Kanban holds no internal data state; dropped cards won't move unless you apply onChange",
|
|
9033
|
+
"fix": "Store columns in state and setColumns in onChange (v-model in Vue)",
|
|
9034
|
+
"severity": "error"
|
|
9035
|
+
},
|
|
9036
|
+
{
|
|
9037
|
+
"component": "Kanban",
|
|
9038
|
+
"pattern": "Duplicate card ids across columns",
|
|
9039
|
+
"reason": "Card moves are resolved by id; duplicates corrupt the move",
|
|
9040
|
+
"fix": "Use globally-unique card ids across the entire board",
|
|
9041
|
+
"severity": "error"
|
|
9042
|
+
},
|
|
9043
|
+
{
|
|
9044
|
+
"component": "Kanban",
|
|
9045
|
+
"pattern": "Mutating value in place then calling onChange",
|
|
9046
|
+
"reason": "React/Vue need a new array reference to re-render reliably",
|
|
9047
|
+
"fix": "Pass the new array Kanban gives you straight to setState / v-model",
|
|
9048
|
+
"severity": "error"
|
|
9049
|
+
},
|
|
9050
|
+
{
|
|
9051
|
+
"component": "Kanban",
|
|
9052
|
+
"pattern": "color=\"blue\" (or any non-semantic value)",
|
|
9053
|
+
"reason": "color is a fixed semantic set, not an arbitrary CSS color",
|
|
9054
|
+
"fix": "Use one of: \"default\" | \"accent\" | \"teal\" | \"success\" | \"warning\" | \"danger\"",
|
|
9055
|
+
"severity": "error"
|
|
9056
|
+
},
|
|
9057
|
+
{
|
|
9058
|
+
"component": "Conversation",
|
|
9059
|
+
"pattern": "Conversation without currentUserId",
|
|
9060
|
+
"reason": "Alignment (incoming vs outgoing) is decided by authorId === currentUserId",
|
|
9061
|
+
"fix": "Always pass currentUserId matching one of the message authorId values",
|
|
9062
|
+
"severity": "error"
|
|
9063
|
+
},
|
|
9064
|
+
{
|
|
9065
|
+
"component": "Conversation",
|
|
9066
|
+
"pattern": "Expecting Conversation to store/append messages",
|
|
9067
|
+
"reason": "Conversation is controlled & stateless for data, like Kanban/DataGrid",
|
|
9068
|
+
"fix": "Append to your own state in onSend (or @send) and pass it back via value",
|
|
9069
|
+
"severity": "error"
|
|
9070
|
+
},
|
|
9071
|
+
{
|
|
9072
|
+
"component": "Conversation",
|
|
9073
|
+
"pattern": "composer without onSend (React) / @send (Vue)",
|
|
9074
|
+
"reason": "The built-in composer emits text; nothing happens unless you handle it",
|
|
9075
|
+
"fix": "Provide onSend / @send to append the message to value",
|
|
9076
|
+
"severity": "error"
|
|
9077
|
+
},
|
|
9078
|
+
{
|
|
9079
|
+
"component": "Conversation",
|
|
9080
|
+
"pattern": "Treating onSend as (text) only when using allowAttachments",
|
|
9081
|
+
"reason": "With allowAttachments, staged files arrive as the 2nd arg; ignoring it drops attachments",
|
|
9082
|
+
"fix": "Handle onSend(text, files) — map files to message attachments and append",
|
|
9083
|
+
"severity": "error"
|
|
9084
|
+
},
|
|
9085
|
+
{
|
|
9086
|
+
"component": "DateRangePicker",
|
|
9087
|
+
"pattern": "value={[from, to]}",
|
|
9088
|
+
"reason": "DateRangePicker uses a { from, to } object, not a [Date, Date] tuple like Calendar mode=range",
|
|
9089
|
+
"fix": "Use value={{ from, to }} and read range.from / range.to",
|
|
9090
|
+
"severity": "error"
|
|
9091
|
+
},
|
|
9092
|
+
{
|
|
9093
|
+
"component": "DateRangePicker",
|
|
9094
|
+
"pattern": "DateRangePicker for a single date",
|
|
9095
|
+
"reason": "DateRangePicker always selects a start AND end",
|
|
9096
|
+
"fix": "Use <DatePicker /> for a single date",
|
|
9097
|
+
"severity": "error"
|
|
9098
|
+
},
|
|
9099
|
+
{
|
|
9100
|
+
"component": "DateRangePicker",
|
|
9101
|
+
"pattern": "presets=\"true\" (string)",
|
|
9102
|
+
"reason": "presets is a boolean or an array, not a string",
|
|
9103
|
+
"fix": "Use the bare prop: presets (or presets={true})",
|
|
9104
|
+
"severity": "error"
|
|
7009
9105
|
}
|
|
7010
9106
|
]
|
|
7011
9107
|
};
|
|
7012
9108
|
|
|
7013
9109
|
export const versionInfo = {
|
|
7014
|
-
"version": "1.
|
|
9110
|
+
"version": "1.2.0",
|
|
7015
9111
|
"packageVersion": "1.1.0",
|
|
7016
|
-
"generatedAt": "2026-05-
|
|
9112
|
+
"generatedAt": "2026-05-17T04:15:52.069Z",
|
|
7017
9113
|
"validFor": [
|
|
7018
9114
|
"@usevyre/react@1.1.0+",
|
|
7019
9115
|
"@usevyre/vue@1.1.0+"
|
|
7020
9116
|
],
|
|
7021
9117
|
"changelog": {
|
|
9118
|
+
"1.2.0": {
|
|
9119
|
+
"date": "2026-05-16",
|
|
9120
|
+
"breaking": false,
|
|
9121
|
+
"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"
|
|
9122
|
+
},
|
|
7022
9123
|
"1.1.0": {
|
|
7023
9124
|
"date": "2026-05-15",
|
|
7024
9125
|
"breaking": false,
|
|
@@ -7043,252 +9144,301 @@ export const versionInfo = {
|
|
|
7043
9144
|
"components": {
|
|
7044
9145
|
"Accordion": {
|
|
7045
9146
|
"version": "1.1.0",
|
|
7046
|
-
"lastUpdated": "2026-05-
|
|
9147
|
+
"lastUpdated": "2026-05-16",
|
|
7047
9148
|
"breaking": false,
|
|
7048
9149
|
"stable": true,
|
|
7049
9150
|
"changelog": null
|
|
7050
9151
|
},
|
|
7051
9152
|
"Alert": {
|
|
7052
9153
|
"version": "1.1.0",
|
|
7053
|
-
"lastUpdated": "2026-05-
|
|
9154
|
+
"lastUpdated": "2026-05-16",
|
|
7054
9155
|
"breaking": false,
|
|
7055
9156
|
"stable": true,
|
|
7056
9157
|
"changelog": null
|
|
7057
9158
|
},
|
|
7058
9159
|
"Avatar": {
|
|
7059
9160
|
"version": "1.1.0",
|
|
7060
|
-
"lastUpdated": "2026-05-
|
|
9161
|
+
"lastUpdated": "2026-05-16",
|
|
7061
9162
|
"breaking": false,
|
|
7062
9163
|
"stable": true,
|
|
7063
9164
|
"changelog": null
|
|
7064
9165
|
},
|
|
7065
9166
|
"Badge": {
|
|
7066
9167
|
"version": "1.1.0",
|
|
7067
|
-
"lastUpdated": "2026-05-
|
|
9168
|
+
"lastUpdated": "2026-05-16",
|
|
7068
9169
|
"breaking": false,
|
|
7069
9170
|
"stable": true,
|
|
7070
9171
|
"changelog": null
|
|
7071
9172
|
},
|
|
7072
9173
|
"Breadcrumb": {
|
|
7073
9174
|
"version": "1.1.0",
|
|
7074
|
-
"lastUpdated": "2026-05-
|
|
9175
|
+
"lastUpdated": "2026-05-16",
|
|
7075
9176
|
"breaking": false,
|
|
7076
9177
|
"stable": true,
|
|
7077
9178
|
"changelog": null
|
|
7078
9179
|
},
|
|
7079
9180
|
"Button": {
|
|
7080
9181
|
"version": "1.1.0",
|
|
7081
|
-
"lastUpdated": "2026-05-
|
|
9182
|
+
"lastUpdated": "2026-05-16",
|
|
7082
9183
|
"breaking": false,
|
|
7083
9184
|
"stable": true,
|
|
7084
9185
|
"changelog": null
|
|
7085
9186
|
},
|
|
7086
9187
|
"Calendar": {
|
|
7087
9188
|
"version": "1.1.0",
|
|
7088
|
-
"lastUpdated": "2026-05-
|
|
9189
|
+
"lastUpdated": "2026-05-16",
|
|
9190
|
+
"breaking": false,
|
|
9191
|
+
"stable": true,
|
|
9192
|
+
"changelog": null
|
|
9193
|
+
},
|
|
9194
|
+
"DatePicker": {
|
|
9195
|
+
"version": "1.1.0",
|
|
9196
|
+
"lastUpdated": "2026-05-16",
|
|
7089
9197
|
"breaking": false,
|
|
7090
9198
|
"stable": true,
|
|
7091
9199
|
"changelog": null
|
|
7092
9200
|
},
|
|
7093
9201
|
"Card": {
|
|
7094
9202
|
"version": "1.1.0",
|
|
7095
|
-
"lastUpdated": "2026-05-
|
|
9203
|
+
"lastUpdated": "2026-05-16",
|
|
7096
9204
|
"breaking": false,
|
|
7097
9205
|
"stable": true,
|
|
7098
9206
|
"changelog": null
|
|
7099
9207
|
},
|
|
7100
9208
|
"Checkbox": {
|
|
7101
9209
|
"version": "1.1.0",
|
|
7102
|
-
"lastUpdated": "2026-05-
|
|
9210
|
+
"lastUpdated": "2026-05-16",
|
|
9211
|
+
"breaking": false,
|
|
9212
|
+
"stable": true,
|
|
9213
|
+
"changelog": null
|
|
9214
|
+
},
|
|
9215
|
+
"RadioGroup": {
|
|
9216
|
+
"version": "1.1.0",
|
|
9217
|
+
"lastUpdated": "2026-05-16",
|
|
9218
|
+
"breaking": false,
|
|
9219
|
+
"stable": true,
|
|
9220
|
+
"changelog": null
|
|
9221
|
+
},
|
|
9222
|
+
"RichTextEditor": {
|
|
9223
|
+
"version": "1.1.0",
|
|
9224
|
+
"lastUpdated": "2026-05-16",
|
|
7103
9225
|
"breaking": false,
|
|
7104
9226
|
"stable": true,
|
|
7105
9227
|
"changelog": null
|
|
7106
9228
|
},
|
|
7107
9229
|
"Command": {
|
|
7108
9230
|
"version": "1.1.0",
|
|
7109
|
-
"lastUpdated": "2026-05-
|
|
9231
|
+
"lastUpdated": "2026-05-16",
|
|
7110
9232
|
"breaking": false,
|
|
7111
9233
|
"stable": true,
|
|
7112
9234
|
"changelog": null
|
|
7113
9235
|
},
|
|
7114
9236
|
"DropdownMenu": {
|
|
7115
9237
|
"version": "1.1.0",
|
|
7116
|
-
"lastUpdated": "2026-05-
|
|
9238
|
+
"lastUpdated": "2026-05-16",
|
|
7117
9239
|
"breaking": false,
|
|
7118
9240
|
"stable": true,
|
|
7119
9241
|
"changelog": null
|
|
7120
9242
|
},
|
|
7121
9243
|
"Field": {
|
|
7122
9244
|
"version": "1.1.0",
|
|
7123
|
-
"lastUpdated": "2026-05-
|
|
9245
|
+
"lastUpdated": "2026-05-16",
|
|
7124
9246
|
"breaking": false,
|
|
7125
9247
|
"stable": true,
|
|
7126
9248
|
"changelog": null
|
|
7127
9249
|
},
|
|
7128
9250
|
"Input": {
|
|
7129
9251
|
"version": "1.1.0",
|
|
7130
|
-
"lastUpdated": "2026-05-
|
|
9252
|
+
"lastUpdated": "2026-05-16",
|
|
7131
9253
|
"breaking": false,
|
|
7132
9254
|
"stable": true,
|
|
7133
9255
|
"changelog": null
|
|
7134
9256
|
},
|
|
7135
9257
|
"Label": {
|
|
7136
9258
|
"version": "1.1.0",
|
|
7137
|
-
"lastUpdated": "2026-05-
|
|
9259
|
+
"lastUpdated": "2026-05-16",
|
|
7138
9260
|
"breaking": false,
|
|
7139
9261
|
"stable": true,
|
|
7140
9262
|
"changelog": null
|
|
7141
9263
|
},
|
|
7142
9264
|
"Modal": {
|
|
7143
9265
|
"version": "1.1.0",
|
|
7144
|
-
"lastUpdated": "2026-05-
|
|
9266
|
+
"lastUpdated": "2026-05-16",
|
|
7145
9267
|
"breaking": false,
|
|
7146
9268
|
"stable": true,
|
|
7147
9269
|
"changelog": null
|
|
7148
9270
|
},
|
|
7149
9271
|
"Pagination": {
|
|
7150
9272
|
"version": "1.1.0",
|
|
7151
|
-
"lastUpdated": "2026-05-
|
|
9273
|
+
"lastUpdated": "2026-05-16",
|
|
7152
9274
|
"breaking": false,
|
|
7153
9275
|
"stable": true,
|
|
7154
9276
|
"changelog": null
|
|
7155
9277
|
},
|
|
7156
9278
|
"Popover": {
|
|
7157
9279
|
"version": "1.1.0",
|
|
7158
|
-
"lastUpdated": "2026-05-
|
|
9280
|
+
"lastUpdated": "2026-05-16",
|
|
7159
9281
|
"breaking": false,
|
|
7160
9282
|
"stable": true,
|
|
7161
9283
|
"changelog": null
|
|
7162
9284
|
},
|
|
7163
9285
|
"Progress": {
|
|
7164
9286
|
"version": "1.1.0",
|
|
7165
|
-
"lastUpdated": "2026-05-
|
|
9287
|
+
"lastUpdated": "2026-05-16",
|
|
7166
9288
|
"breaking": false,
|
|
7167
9289
|
"stable": true,
|
|
7168
9290
|
"changelog": null
|
|
7169
9291
|
},
|
|
7170
9292
|
"Select": {
|
|
7171
9293
|
"version": "1.1.0",
|
|
7172
|
-
"lastUpdated": "2026-05-
|
|
9294
|
+
"lastUpdated": "2026-05-16",
|
|
7173
9295
|
"breaking": false,
|
|
7174
9296
|
"stable": true,
|
|
7175
9297
|
"changelog": null
|
|
7176
9298
|
},
|
|
7177
9299
|
"Separator": {
|
|
7178
9300
|
"version": "1.1.0",
|
|
7179
|
-
"lastUpdated": "2026-05-
|
|
9301
|
+
"lastUpdated": "2026-05-16",
|
|
7180
9302
|
"breaking": false,
|
|
7181
9303
|
"stable": true,
|
|
7182
9304
|
"changelog": null
|
|
7183
9305
|
},
|
|
7184
9306
|
"Sheet": {
|
|
7185
9307
|
"version": "1.1.0",
|
|
7186
|
-
"lastUpdated": "2026-05-
|
|
9308
|
+
"lastUpdated": "2026-05-16",
|
|
7187
9309
|
"breaking": false,
|
|
7188
9310
|
"stable": true,
|
|
7189
9311
|
"changelog": null
|
|
7190
9312
|
},
|
|
7191
9313
|
"Sidebar": {
|
|
7192
9314
|
"version": "1.1.0",
|
|
7193
|
-
"lastUpdated": "2026-05-
|
|
9315
|
+
"lastUpdated": "2026-05-16",
|
|
7194
9316
|
"breaking": false,
|
|
7195
9317
|
"stable": true,
|
|
7196
9318
|
"changelog": null
|
|
7197
9319
|
},
|
|
7198
9320
|
"Skeleton": {
|
|
7199
9321
|
"version": "1.1.0",
|
|
7200
|
-
"lastUpdated": "2026-05-
|
|
9322
|
+
"lastUpdated": "2026-05-16",
|
|
7201
9323
|
"breaking": false,
|
|
7202
9324
|
"stable": true,
|
|
7203
9325
|
"changelog": null
|
|
7204
9326
|
},
|
|
7205
9327
|
"Slider": {
|
|
7206
9328
|
"version": "1.1.0",
|
|
7207
|
-
"lastUpdated": "2026-05-
|
|
9329
|
+
"lastUpdated": "2026-05-16",
|
|
7208
9330
|
"breaking": false,
|
|
7209
9331
|
"stable": true,
|
|
7210
9332
|
"changelog": null
|
|
7211
9333
|
},
|
|
7212
9334
|
"Switch": {
|
|
7213
9335
|
"version": "1.1.0",
|
|
7214
|
-
"lastUpdated": "2026-05-
|
|
9336
|
+
"lastUpdated": "2026-05-16",
|
|
7215
9337
|
"breaking": false,
|
|
7216
9338
|
"stable": true,
|
|
7217
9339
|
"changelog": null
|
|
7218
9340
|
},
|
|
7219
9341
|
"Table": {
|
|
7220
9342
|
"version": "1.1.0",
|
|
7221
|
-
"lastUpdated": "2026-05-
|
|
9343
|
+
"lastUpdated": "2026-05-16",
|
|
7222
9344
|
"breaking": false,
|
|
7223
9345
|
"stable": true,
|
|
7224
9346
|
"changelog": null
|
|
7225
9347
|
},
|
|
7226
9348
|
"Tabs": {
|
|
7227
9349
|
"version": "1.1.0",
|
|
7228
|
-
"lastUpdated": "2026-05-
|
|
9350
|
+
"lastUpdated": "2026-05-16",
|
|
7229
9351
|
"breaking": false,
|
|
7230
9352
|
"stable": true,
|
|
7231
9353
|
"changelog": null
|
|
7232
9354
|
},
|
|
7233
9355
|
"Toast": {
|
|
7234
9356
|
"version": "1.1.0",
|
|
7235
|
-
"lastUpdated": "2026-05-
|
|
9357
|
+
"lastUpdated": "2026-05-16",
|
|
7236
9358
|
"breaking": false,
|
|
7237
9359
|
"stable": true,
|
|
7238
9360
|
"changelog": null
|
|
7239
9361
|
},
|
|
7240
9362
|
"Tooltip": {
|
|
7241
9363
|
"version": "1.1.0",
|
|
7242
|
-
"lastUpdated": "2026-05-
|
|
9364
|
+
"lastUpdated": "2026-05-16",
|
|
7243
9365
|
"breaking": false,
|
|
7244
9366
|
"stable": true,
|
|
7245
9367
|
"changelog": null
|
|
7246
9368
|
},
|
|
7247
9369
|
"Typography": {
|
|
7248
9370
|
"version": "1.1.0",
|
|
7249
|
-
"lastUpdated": "2026-05-
|
|
9371
|
+
"lastUpdated": "2026-05-16",
|
|
7250
9372
|
"breaking": false,
|
|
7251
9373
|
"stable": true,
|
|
7252
9374
|
"changelog": null
|
|
7253
9375
|
},
|
|
7254
9376
|
"ButtonGroup": {
|
|
7255
9377
|
"version": "1.1.0",
|
|
7256
|
-
"lastUpdated": "2026-05-
|
|
9378
|
+
"lastUpdated": "2026-05-16",
|
|
7257
9379
|
"breaking": false,
|
|
7258
9380
|
"stable": true,
|
|
7259
9381
|
"changelog": null
|
|
7260
9382
|
},
|
|
7261
9383
|
"TagsInput": {
|
|
7262
9384
|
"version": "1.1.0",
|
|
7263
|
-
"lastUpdated": "2026-05-
|
|
9385
|
+
"lastUpdated": "2026-05-16",
|
|
7264
9386
|
"breaking": false,
|
|
7265
9387
|
"stable": true,
|
|
7266
9388
|
"changelog": null
|
|
7267
9389
|
},
|
|
7268
9390
|
"Combobox": {
|
|
7269
9391
|
"version": "1.1.0",
|
|
7270
|
-
"lastUpdated": "2026-05-
|
|
9392
|
+
"lastUpdated": "2026-05-16",
|
|
7271
9393
|
"breaking": false,
|
|
7272
9394
|
"stable": true,
|
|
7273
9395
|
"changelog": null
|
|
7274
9396
|
},
|
|
7275
9397
|
"DataGrid": {
|
|
7276
9398
|
"version": "1.1.0",
|
|
7277
|
-
"lastUpdated": "2026-05-
|
|
9399
|
+
"lastUpdated": "2026-05-16",
|
|
7278
9400
|
"breaking": false,
|
|
7279
9401
|
"stable": true,
|
|
7280
9402
|
"changelog": null
|
|
7281
9403
|
},
|
|
7282
9404
|
"Tag": {
|
|
7283
9405
|
"version": "1.1.0",
|
|
7284
|
-
"lastUpdated": "2026-05-
|
|
9406
|
+
"lastUpdated": "2026-05-16",
|
|
7285
9407
|
"breaking": false,
|
|
7286
9408
|
"stable": true,
|
|
7287
9409
|
"changelog": null
|
|
7288
9410
|
},
|
|
7289
9411
|
"TagGroup": {
|
|
7290
9412
|
"version": "1.1.0",
|
|
7291
|
-
"lastUpdated": "2026-05-
|
|
9413
|
+
"lastUpdated": "2026-05-16",
|
|
9414
|
+
"breaking": false,
|
|
9415
|
+
"stable": true,
|
|
9416
|
+
"changelog": null
|
|
9417
|
+
},
|
|
9418
|
+
"Item": {
|
|
9419
|
+
"version": "1.1.0",
|
|
9420
|
+
"lastUpdated": "2026-05-16",
|
|
9421
|
+
"breaking": false,
|
|
9422
|
+
"stable": true,
|
|
9423
|
+
"changelog": null
|
|
9424
|
+
},
|
|
9425
|
+
"Kanban": {
|
|
9426
|
+
"version": "1.1.0",
|
|
9427
|
+
"lastUpdated": "2026-05-16",
|
|
9428
|
+
"breaking": false,
|
|
9429
|
+
"stable": true,
|
|
9430
|
+
"changelog": null
|
|
9431
|
+
},
|
|
9432
|
+
"Conversation": {
|
|
9433
|
+
"version": "1.1.0",
|
|
9434
|
+
"lastUpdated": "2026-05-16",
|
|
9435
|
+
"breaking": false,
|
|
9436
|
+
"stable": true,
|
|
9437
|
+
"changelog": null
|
|
9438
|
+
},
|
|
9439
|
+
"DateRangePicker": {
|
|
9440
|
+
"version": "1.1.0",
|
|
9441
|
+
"lastUpdated": "2026-05-16",
|
|
7292
9442
|
"breaking": false,
|
|
7293
9443
|
"stable": true,
|
|
7294
9444
|
"changelog": null
|
|
@@ -7303,13 +9453,16 @@ export const cheatSheets = {
|
|
|
7303
9453
|
"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
9454
|
"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
9455
|
"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**
|
|
9456
|
+
"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",
|
|
9457
|
+
"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
9458
|
"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
9459
|
"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",
|
|
9460
|
+
"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",
|
|
9461
|
+
"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
9462
|
"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
9463
|
"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",
|
|
9464
|
+
"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",
|
|
9465
|
+
"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
9466
|
"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
9467
|
"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
9468
|
"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",
|
|
@@ -7332,5 +9485,9 @@ export const cheatSheets = {
|
|
|
7332
9485
|
"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
9486
|
"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
9487
|
"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"
|
|
9488
|
+
"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",
|
|
9489
|
+
"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",
|
|
9490
|
+
"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",
|
|
9491
|
+
"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",
|
|
9492
|
+
"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
9493
|
};
|