@usevyre/ai-context 1.1.0 → 1.2.1

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