@usevyre/ai-context 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -305,7 +305,7 @@ import { Button } from "@usevyre/react"
305
305
 
306
306
  ### Calendar
307
307
 
308
- Date picker calendar widget for selecting single dates or ranges.
308
+ 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.
309
309
 
310
310
  ```tsx
311
311
  import { Calendar } from "@usevyre/react"
@@ -314,6 +314,7 @@ import { Calendar } from "@usevyre/react"
314
314
  // value = Date | null
315
315
  // onChange = function
316
316
  // disabled = boolean (default: false)
317
+ // defaultMonth = Date
317
318
 
318
319
  // Examples:
319
320
  const [date, setDate] = useState(null);
@@ -321,7 +322,39 @@ const [date, setDate] = useState(null);
321
322
  ```
322
323
 
323
324
  **Common mistakes:**
324
- - ❌ `Using Calendar for time selection` Combine with a separate time Input if time selection is needed
325
+ - ❌ `Calendar for an input field that opens a popover` Use <DatePicker /> (single date) or <DateRangePicker /> (range)
326
+ - ❌ `value as tuple for mode="single"` → Pass value matching mode; use mode="range" for [start,end]
327
+
328
+ ---
329
+
330
+ ### DatePicker
331
+
332
+ 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.
333
+
334
+ ```tsx
335
+ import { DatePicker } from "@usevyre/react"
336
+
337
+ // Props:
338
+ // value = Date | [Date, Date] | Date[] | null
339
+ // onChange = function
340
+ // mode = "single" | "range" | "multiple" (default: single)
341
+ // placeholder = string (default: Pick a date)
342
+ // showTime = boolean (default: false)
343
+ // minDate = Date
344
+ // maxDate = Date
345
+ // disabled = function
346
+ // weekStartsOn = "0" | "1" (default: 1)
347
+ // inputClassName = string
348
+
349
+ // Examples:
350
+ const [date, setDate] = useState(null);
351
+ <DatePicker value={date} onChange={setDate} placeholder="Pick a date" />
352
+ <DatePicker value={date} onChange={setDate} showTime />
353
+ ```
354
+
355
+ **Common mistakes:**
356
+ - ❌ `DatePicker mode="range" for { from, to } object` → Use <DateRangePicker /> for the { from, to } object API + presets + dual month
357
+ - ❌ `DatePicker without value/onChange` → Provide value and onChange (e.g. from useState)
325
358
 
326
359
  ---
327
360
 
@@ -381,6 +414,78 @@ import { Checkbox } from "@usevyre/react"
381
414
 
382
415
  ---
383
416
 
417
+ ### RadioGroup
418
+
419
+ 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.
420
+
421
+ ```tsx
422
+ import { RadioGroup, Radio } from "@usevyre/react"
423
+
424
+ // Props:
425
+ // value = string
426
+ // defaultValue = string
427
+ // onChange = function
428
+ // name = string
429
+ // disabled = boolean (default: false)
430
+ // size = "sm" | "md" (default: md)
431
+ // orientation = "vertical" | "horizontal" (default: vertical)
432
+ // options = { value: string; label?: string; description?: string; disabled?: boolean }[]
433
+
434
+ // Examples:
435
+ <RadioGroup
436
+ value={plan}
437
+ onChange={setPlan}
438
+ options={[
439
+ { value: "free", label: "Free", description: "For hobby projects" },
440
+ { value: "pro", label: "Pro", description: "For teams" },
441
+ ]}
442
+ />
443
+ <RadioGroup value={plan} onChange={setPlan} orientation="horizontal">
444
+ <Radio value="free" label="Free" />
445
+ <Radio value="pro" label="Pro" />
446
+ </RadioGroup>
447
+ ```
448
+
449
+ **Common mistakes:**
450
+ - ❌ `<Radio> used outside a <RadioGroup>` → Always wrap <Radio> in <RadioGroup>
451
+ - ❌ `RadioGroup without value/onChange (React) or v-model (Vue)` → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
452
+ - ❌ `Using Checkbox for mutually-exclusive choices` → Use RadioGroup + Radio (or options) for one-of-many
453
+
454
+ ---
455
+
456
+ ### RichTextEditor
457
+
458
+ 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.
459
+
460
+ ```tsx
461
+ import { RichTextEditor } from "@usevyre/react"
462
+
463
+ // Props:
464
+ // value = string
465
+ // onChange = function
466
+ // placeholder = string (default: Write something…)
467
+ // disabled = boolean (default: false)
468
+ // readOnly = boolean (default: false)
469
+ // toolbar = RichTextTool[]
470
+ // minHeight = string (default: 10rem)
471
+
472
+ // Examples:
473
+ const [html, setHtml] = useState("<p>Hello <strong>world</strong></p>");
474
+ <RichTextEditor value={html} onChange={setHtml} placeholder="Write…" />
475
+ <RichTextEditor
476
+ value={html}
477
+ onChange={setHtml}
478
+ toolbar={["bold", "italic", "link"]}
479
+ />
480
+ ```
481
+
482
+ **Common mistakes:**
483
+ - ❌ `RichTextEditor without value/onChange (React) or v-model (Vue)` → Keep the HTML string in state and update it in onChange / v-model
484
+ - ❌ `Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising` → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
485
+ - ❌ `toolbar="bold" (string)` → Pass an array, e.g. toolbar={["bold","italic","link"]}
486
+
487
+ ---
488
+
384
489
  ### Command
385
490
 
386
491
  Command palette / search dialog. Use for search-first navigation or quick actions.
@@ -432,7 +537,7 @@ import { DropdownMenu, DropdownItem, DropdownSeparator, DropdownCheckboxItem, Dr
432
537
 
433
538
  ### Field
434
539
 
435
- Form field wrapper providing label, hint text, and validation state for Input or Textarea.
540
+ 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.
436
541
 
437
542
  ```tsx
438
543
  import { Field, Input, Textarea } from "@usevyre/react"
@@ -450,10 +555,23 @@ import { Field, Input, Textarea } from "@usevyre/react"
450
555
  <Field label="Search">
451
556
  <Input leftElement={<SearchIcon />} placeholder="Search..." />
452
557
  </Field>
558
+ <Field>
559
+ <FieldLabel required htmlFor="email">Email</FieldLabel>
560
+ <Input id="email" type="email" />
561
+ <FieldDescription>We\u2019ll never share it.</FieldDescription>
562
+ <FieldError>{errors.email}</FieldError>
563
+ </Field>
564
+
565
+ // Two controls side by side
566
+ <FieldGroup orientation="horizontal">
567
+ <Field label="First name"><Input /></Field>
568
+ <Field label="Last name"><Input /></Field>
569
+ </FieldGroup>
453
570
  ```
454
571
 
455
572
  **Common mistakes:**
456
573
  - ❌ `Applying state prop directly to Input` → Wrap Input in <Field state="error"> to apply validation styling
574
+ - ❌ `Mixing props label/hint AND FieldLabel/FieldError for the same field` → Pick one: either props-based (label/hint/state) OR composable parts
457
575
 
458
576
  ---
459
577
 
@@ -465,6 +583,7 @@ Text input field. Wrap in Field for labels and validation. Use leftElement/right
465
583
  import { Input } from "@usevyre/react"
466
584
 
467
585
  // Props:
586
+ // modelValue = string | number
468
587
  // size = "sm" | "md" | "lg" (default: md)
469
588
  // leftElement = ReactNode
470
589
  // rightElement = ReactNode
@@ -476,6 +595,7 @@ import { Input } from "@usevyre/react"
476
595
  **Common mistakes:**
477
596
  - ❌ `size="icon"` → Use size="sm" | "md" | "lg"
478
597
  - ❌ `type="search" for search UI` → Import Command from @usevyre/react for search palettes
598
+ - ❌ `Vue: binding Input/Textarea value without v-model` → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
479
599
 
480
600
  ---
481
601
 
@@ -881,6 +1001,362 @@ import { Text, Heading, Lead, Code, Blockquote } from "@usevyre/react"
881
1001
 
882
1002
  ---
883
1003
 
1004
+ ### ButtonGroup
1005
+
1006
+ Groups multiple Button components into one visual unit (toolbar, segmented control). Pure layout — no internal state.
1007
+
1008
+ ```tsx
1009
+ import { ButtonGroup, Button } from "@usevyre/react"
1010
+
1011
+ // Props:
1012
+ // orientation = "horizontal" | "vertical" (default: horizontal)
1013
+ // attached = boolean (default: false)
1014
+ // size = "sm" | "md" | "lg" | "icon"
1015
+
1016
+ // Examples:
1017
+ <ButtonGroup attached>
1018
+ <Button variant="secondary">Day</Button>
1019
+ <Button variant="secondary">Week</Button>
1020
+ <Button variant="secondary">Month</Button>
1021
+ </ButtonGroup>
1022
+ <ButtonGroup orientation="vertical" attached>
1023
+ <Button variant="secondary">Top</Button>
1024
+ <Button variant="secondary">Bottom</Button>
1025
+ </ButtonGroup>
1026
+ ```
1027
+
1028
+ **Common mistakes:**
1029
+ - ❌ `ButtonGroup variant="..."` → Set variant on each <Button> inside the group
1030
+ - ❌ `ButtonGroup without Button children` → Place <Button> elements as direct children
1031
+
1032
+ ---
1033
+
1034
+ ### TagsInput
1035
+
1036
+ Multi-tag input. Type and press Enter or comma to add a tag, click x to remove, Backspace on empty input removes the last tag. Controlled.
1037
+
1038
+ ```tsx
1039
+ import { TagsInput } from "@usevyre/react"
1040
+
1041
+ // Props:
1042
+ // value = string[]
1043
+ // onChange = (tags: string[]) => void
1044
+ // placeholder = string
1045
+ // disabled = boolean (default: false)
1046
+ // max = number
1047
+ // size = "sm" | "md" | "lg" (default: md)
1048
+
1049
+ // Examples:
1050
+ const [tags, setTags] = useState<string[]>([]);
1051
+ <TagsInput value={tags} onChange={setTags} placeholder="Add a tag…" />
1052
+ <TagsInput value={tags} onChange={setTags} max={5} />
1053
+ ```
1054
+
1055
+ **Common mistakes:**
1056
+ - ❌ `TagsInput value={string}` → Pass an array: value={['react','vue']}
1057
+ - ❌ `TagsInput without onChange` → Provide value and onChange (React) or v-model (Vue)
1058
+
1059
+ ---
1060
+
1061
+ ### Combobox
1062
+
1063
+ 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).
1064
+
1065
+ ```tsx
1066
+ import { Combobox } from "@usevyre/react"
1067
+
1068
+ // Props:
1069
+ // options = { value: string; label: string; disabled?: boolean }[]
1070
+ // value = string | null
1071
+ // onChange = (value: string | null) => void
1072
+ // placeholder = string (default: "Search…")
1073
+ // disabled = boolean (default: false)
1074
+ // size = "sm" | "md" | "lg" (default: md)
1075
+ // emptyText = string (default: "No results")
1076
+
1077
+ // Examples:
1078
+ const [lang, setLang] = useState<string | null>(null);
1079
+ <Combobox
1080
+ options={[{ value: "ts", label: "TypeScript" }, { value: "go", label: "Go" }]}
1081
+ value={lang}
1082
+ onChange={setLang}
1083
+ placeholder="Search language…"
1084
+ />
1085
+ ```
1086
+
1087
+ **Common mistakes:**
1088
+ - ❌ `Combobox value=""` → Use value={null} for no selection
1089
+ - ❌ `Combobox options={string[]}` → Use [{ value: 'ts', label: 'TypeScript' }]
1090
+ - ❌ `Using Combobox for command palette` → Use Command for command palettes
1091
+
1092
+ ---
1093
+
1094
+ ### DataGrid
1095
+
1096
+ Table with built-in column sorting, loading skeletons, and empty state. Filtering and pagination are out of scope — compose with the Pagination component.
1097
+
1098
+ ```tsx
1099
+ import { DataGrid } from "@usevyre/react"
1100
+
1101
+ // Props:
1102
+ // columns = { key: string; label: string; sortable?: boolean; width?: string }[]
1103
+ // rows = Record<string, unknown>[]
1104
+ // sortKey = string
1105
+ // sortDir = "asc" | "desc"
1106
+ // onSort = (key: string, dir: 'asc' | 'desc') => void
1107
+ // loading = boolean (default: false)
1108
+ // emptyText = string (default: "No data")
1109
+ // stickyHeader = boolean (default: false)
1110
+
1111
+ // Examples:
1112
+ const cols = [{ key: "name", label: "Name", sortable: true }];
1113
+ <DataGrid
1114
+ columns={cols}
1115
+ rows={people}
1116
+ sortKey={sortKey}
1117
+ sortDir={sortDir}
1118
+ onSort={(k, d) => { setSortKey(k); setSortDir(d); }}
1119
+ />
1120
+ <DataGrid columns={cols} rows={[]} loading />
1121
+ ```
1122
+
1123
+ **Common mistakes:**
1124
+ - ❌ `DataGrid expecting built-in pagination` → Slice rows yourself and use the Pagination component
1125
+ - ❌ `DataGrid expecting built-in filtering` → Filter the rows array before passing it in
1126
+ - ❌ `sortable without onSort` → Handle onSort and sort the rows array in your state
1127
+
1128
+ ---
1129
+
1130
+ ### Tag
1131
+
1132
+ Standalone display tag/chip for categories, labels, or filter chips. NOT an input — for tag input use TagsInput. Group multiple with TagGroup.
1133
+
1134
+ ```tsx
1135
+ import { Tag } from "@usevyre/react"
1136
+
1137
+ // Props:
1138
+ // variant = "default" | "accent" | "danger" (default: default)
1139
+ // size = "sm" | "md" | "lg" (default: md)
1140
+ // onRemove = () => void
1141
+ // onClick = () => void
1142
+ // disabled = boolean (default: false)
1143
+
1144
+ // Examples:
1145
+ <TagGroup>
1146
+ <Tag>Design</Tag>
1147
+ <Tag variant="accent">Featured</Tag>
1148
+ <Tag>Engineering</Tag>
1149
+ </TagGroup>
1150
+ <Tag onRemove={() => removeFilter("react")}>react</Tag>
1151
+ <Tag onClick={() => toggleFilter("vue")}>vue</Tag>
1152
+ ```
1153
+
1154
+ **Common mistakes:**
1155
+ - ❌ `Tag variant="success"` → Use Badge for success/warning/teal status colors; Tag is for categories/filters
1156
+ - ❌ `Using Tag for tag input` → Use TagsInput for adding/removing tags via keyboard
1157
+ - ❌ `Tag size="xl"` → Use size="lg"
1158
+
1159
+ ---
1160
+
1161
+ ### TagGroup
1162
+
1163
+ Read-only container that lays out multiple Tag elements with automatic wrapping and consistent spacing. For tag input use TagsInput.
1164
+
1165
+ ```tsx
1166
+ import { TagGroup, Tag } from "@usevyre/react"
1167
+
1168
+ // Props:
1169
+ // gap = "sm" | "md" | "lg" (default: md)
1170
+ // wrap = boolean (default: true)
1171
+
1172
+ // Examples:
1173
+ <TagGroup gap="sm">
1174
+ <Tag>React</Tag>
1175
+ <Tag>Vue</Tag>
1176
+ <Tag variant="accent">TypeScript</Tag>
1177
+ </TagGroup>
1178
+ ```
1179
+
1180
+ **Common mistakes:**
1181
+ - ❌ `TagGroup without Tag children` → Place <Tag> elements as direct children
1182
+ - ❌ `Using TagGroup for tag input` → Use TagsInput for an editable tag field
1183
+
1184
+ ---
1185
+
1186
+ ### Item
1187
+
1188
+ Layout primitive for list rows, settings rows, and notification rows. Denser than Card — use Item (not Card) for repeated list rows.
1189
+
1190
+ ```tsx
1191
+ import { Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions, ItemGroup } from "@usevyre/react"
1192
+
1193
+ // Props:
1194
+ // variant = "default" | "outlined" | "muted" | "plain" (default: default)
1195
+ // size = "sm" | "md" | "lg" (default: md)
1196
+ // clickable = boolean (default: false)
1197
+
1198
+ // Examples:
1199
+ <Item>
1200
+ <ItemMedia><BellIcon /></ItemMedia>
1201
+ <ItemContent>
1202
+ <ItemTitle>Notifications</ItemTitle>
1203
+ <ItemDescription>Receive an email when someone mentions you.</ItemDescription>
1204
+ </ItemContent>
1205
+ <ItemActions>
1206
+ <Switch defaultChecked />
1207
+ </ItemActions>
1208
+ </Item>
1209
+ <ItemGroup separated>
1210
+ <Item clickable>
1211
+ <ItemContent><ItemTitle>Profile</ItemTitle></ItemContent>
1212
+ </Item>
1213
+ <Item clickable>
1214
+ <ItemContent><ItemTitle>Billing</ItemTitle></ItemContent>
1215
+ </Item>
1216
+ </ItemGroup>
1217
+ ```
1218
+
1219
+ **Common mistakes:**
1220
+ - ❌ `Card used for repeated list rows` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
1221
+ - ❌ `Item variant="primary"` → Use variant="default" | "outlined" | "muted"
1222
+ - ❌ `raw text directly inside Item` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
1223
+
1224
+ ---
1225
+
1226
+ ### Kanban
1227
+
1228
+ 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.
1229
+
1230
+ ```tsx
1231
+ import { Kanban } from "@usevyre/react"
1232
+
1233
+ // Props:
1234
+ // value = KanbanColumn[]
1235
+ // onChange = function
1236
+ // renderCard = function
1237
+ // onCardClick = function
1238
+
1239
+ // Examples:
1240
+ const [columns, setColumns] = useState([
1241
+ { id: "todo", title: "To Do", cards: [{ id: "1", title: "Spec API" }] },
1242
+ { id: "doing", title: "In Progress", cards: [] },
1243
+ { id: "done", title: "Done", cards: [{ id: "2", title: "Kickoff" }] },
1244
+ ]);
1245
+ <Kanban value={columns} onChange={setColumns} />
1246
+ <Kanban
1247
+ value={columns}
1248
+ onChange={setColumns}
1249
+ onCardClick={(card) => openDetail(card.id)}
1250
+ renderCard={(card) => (
1251
+ <><strong>{card.title}</strong><Badge>{card.id}</Badge></>
1252
+ )}
1253
+ />
1254
+ const [cols, setCols] = useState([
1255
+ { id: "doing", title: "In Progress", color: "teal", cards: [
1256
+ { id: "t1", title: "OAuth", assignee: "AK", progress: 60, color: "warning" },
1257
+ ]},
1258
+ ]);
1259
+ <Kanban
1260
+ value={cols}
1261
+ onChange={setCols}
1262
+ renderCard={(card) => (
1263
+ <><strong>{card.title}</strong><Progress value={card.progress} /></>
1264
+ )}
1265
+ />
1266
+ ```
1267
+
1268
+ **Common mistakes:**
1269
+ - ❌ `Kanban without onChange (or ignoring it)` → Store columns in state and setColumns in onChange (v-model in Vue)
1270
+ - ❌ `Duplicate card ids across columns` → Use globally-unique card ids across the entire board
1271
+ - ❌ `Mutating value in place then calling onChange` → Pass the new array Kanban gives you straight to setState / v-model
1272
+ - ❌ `color="blue" (or any non-semantic value)` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
1273
+
1274
+ ---
1275
+
1276
+ ### Conversation
1277
+
1278
+ 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.
1279
+
1280
+ ```tsx
1281
+ import { Conversation } from "@usevyre/react"
1282
+
1283
+ // Props:
1284
+ // value = ConversationMessage[]
1285
+ // currentUserId = string
1286
+ // composer = boolean (default: false)
1287
+ // onSend = function
1288
+ // placeholder = string (default: Write a message…)
1289
+ // typing = boolean | string (default: false)
1290
+ // allowAttachments = boolean (default: false)
1291
+ // accept = string
1292
+ // renderMessage = function
1293
+ // renderComposer = function
1294
+
1295
+ // Examples:
1296
+ const [messages, setMessages] = useState([
1297
+ { id: "1", authorId: "sam", authorName: "Sam", text: "Hey!" },
1298
+ { id: "2", authorId: "me", text: "Hi \ud83d\udc4b", status: "read" },
1299
+ ]);
1300
+ <Conversation
1301
+ value={messages}
1302
+ currentUserId="me"
1303
+ composer
1304
+ onSend={(t) => setMessages((m) => [...m, { id: crypto.randomUUID(), authorId: "me", text: t }])}
1305
+ />
1306
+ <Conversation
1307
+ value={messages}
1308
+ currentUserId="me"
1309
+ typing="Sam is typing"
1310
+ renderMessage={(m) => <strong>{m.text}</strong>}
1311
+ />
1312
+ const messages = [
1313
+ { id: "1", authorId: "sam", authorName: "Sam", text: "Moodboard \ud83d\udc47",
1314
+ attachments: [{ kind: "image", url: "/board.png", name: "board.png" }] },
1315
+ { id: "2", authorId: "me", text: "Specs:", status: "read",
1316
+ attachments: [{ kind: "file", url: "/spec.pdf", name: "spec.pdf", size: "2.4 MB" }] },
1317
+ ];
1318
+ <Conversation value={messages} currentUserId="me" />
1319
+ ```
1320
+
1321
+ **Common mistakes:**
1322
+ - ❌ `Conversation without currentUserId` → Always pass currentUserId matching one of the message authorId values
1323
+ - ❌ `Expecting Conversation to store/append messages` → Append to your own state in onSend (or @send) and pass it back via value
1324
+ - ❌ `composer without onSend (React) / @send (Vue)` → Provide onSend / @send to append the message to value
1325
+ - ❌ `Treating onSend as (text) only when using allowAttachments` → Handle onSend(text, files) — map files to message attachments and append
1326
+
1327
+ ---
1328
+
1329
+ ### DateRangePicker
1330
+
1331
+ 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.
1332
+
1333
+ ```tsx
1334
+ import { DateRangePicker } from "@usevyre/react"
1335
+
1336
+ // Props:
1337
+ // value = { from: Date | null; to: Date | null } | null
1338
+ // onChange = function
1339
+ // placeholder = string (default: Pick a date range)
1340
+ // numberOfMonths = "1" | "2" (default: 2)
1341
+ // presets = boolean | DateRangePreset[] (default: false)
1342
+ // minDate = Date
1343
+ // maxDate = Date
1344
+ // disabled = function
1345
+ // weekStartsOn = "0" | "1" (default: 1)
1346
+
1347
+ // Examples:
1348
+ const [range, setRange] = useState({ from: null, to: null });
1349
+ <DateRangePicker value={range} onChange={setRange} presets />
1350
+ <DateRangePicker value={range} onChange={setRange} numberOfMonths={1} />
1351
+ ```
1352
+
1353
+ **Common mistakes:**
1354
+ - ❌ `value={[from, to]}` → Use value={{ from, to }} and read range.from / range.to
1355
+ - ❌ `DateRangePicker for a single date` → Use <DatePicker /> for a single date
1356
+ - ❌ `presets="true" (string)` → Use the bare prop: presets (or presets={true})
1357
+
1358
+ ---
1359
+
884
1360
  ## Hallucination Guard — Common AI Mistakes
885
1361
 
886
1362
  The following prop values and patterns do NOT exist in useVyre.
@@ -900,14 +1376,25 @@ If you generate these, you are hallucinating.
900
1376
  - ❌ `<Button color="...">` → Use variant prop instead
901
1377
  - ❌ `<Button icon={...}>` → Use leftIcon={...} or rightIcon={...}
902
1378
  - ❌ `<Button size="icon" without aria-label>` → Add aria-label describing the action
903
- - ❌ `<Calendar Using Calendar for time selection>` Combine with a separate time Input if time selection is needed
1379
+ - ❌ `<Calendar Calendar for an input field that opens a popover>` Use <DatePicker /> (single date) or <DateRangePicker /> (range)
1380
+ - ❌ `<Calendar value as tuple for mode="single">` → Pass value matching mode; use mode="range" for [start,end]
1381
+ - ❌ `<DatePicker DatePicker mode="range" for { from, to } object>` → Use <DateRangePicker /> for the { from, to } object API + presets + dual month
1382
+ - ❌ `<DatePicker DatePicker without value/onChange>` → Provide value and onChange (e.g. from useState)
904
1383
  - ❌ `<Card variant="primary">` → Use variant="elevated" | "outlined" | "ghost" | "accent"
905
1384
  - ❌ `<Checkbox size="lg">` → Use size="md"
1385
+ - ❌ `<RadioGroup <Radio> used outside a <RadioGroup>>` → Always wrap <Radio> in <RadioGroup>
1386
+ - ❌ `<RadioGroup RadioGroup without value/onChange (React) or v-model (Vue)>` → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
1387
+ - ❌ `<RadioGroup Using Checkbox for mutually-exclusive choices>` → Use RadioGroup + Radio (or options) for one-of-many
1388
+ - ❌ `<RichTextEditor RichTextEditor without value/onChange (React) or v-model (Vue)>` → Keep the HTML string in state and update it in onChange / v-model
1389
+ - ❌ `<RichTextEditor Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising>` → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
1390
+ - ❌ `<RichTextEditor toolbar="bold" (string)>` → Pass an array, e.g. toolbar={["bold","italic","link"]}
906
1391
  - ❌ `<Command Using Input type="search" for search UI>` → Use Command + CommandInput + CommandList + CommandItem
907
1392
  - ❌ `<DropdownMenu DropdownItem variant="primary">` → Use variant="danger" for destructive items only
908
1393
  - ❌ `<Field Applying state prop directly to Input>` → Wrap Input in <Field state="error"> to apply validation styling
1394
+ - ❌ `<Field Mixing props label/hint AND FieldLabel/FieldError for the same field>` → Pick one: either props-based (label/hint/state) OR composable parts
909
1395
  - ❌ `<Input size="icon">` → Use size="sm" | "md" | "lg"
910
1396
  - ❌ `<Input type="search" for search UI>` → Import Command from @usevyre/react for search palettes
1397
+ - ❌ `<Input Vue: binding Input/Textarea value without v-model>` → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
911
1398
  - ❌ `<Modal size="xl">` → Use size="lg" or size="full"
912
1399
  - ❌ `<Popover placement="top-center">` → Use placement="top" for centered placement
913
1400
  - ❌ `<Progress value > 100>` → Normalize your value to 0–100 range before passing
@@ -917,6 +1404,35 @@ If you generate these, you are hallucinating.
917
1404
  - ❌ `<Toast variant="info">` → Use variant="default"
918
1405
  - ❌ `<Tooltip Using Tooltip for rich content (forms, buttons, etc.)>` → Use Popover for rich interactive content
919
1406
  - ❌ `<Typography Using raw <h1>, <p> tags instead of Typography components>` → Use <Heading>, <Text>, <Lead> from @usevyre/react
1407
+ - ❌ `<ButtonGroup ButtonGroup variant="...">` → Set variant on each <Button> inside the group
1408
+ - ❌ `<ButtonGroup ButtonGroup without Button children>` → Place <Button> elements as direct children
1409
+ - ❌ `<TagsInput TagsInput value={string}>` → Pass an array: value={['react','vue']}
1410
+ - ❌ `<TagsInput TagsInput without onChange>` → Provide value and onChange (React) or v-model (Vue)
1411
+ - ❌ `<Combobox Combobox value="">` → Use value={null} for no selection
1412
+ - ❌ `<Combobox Combobox options={string[]}>` → Use [{ value: 'ts', label: 'TypeScript' }]
1413
+ - ❌ `<Combobox Using Combobox for command palette>` → Use Command for command palettes
1414
+ - ❌ `<DataGrid DataGrid expecting built-in pagination>` → Slice rows yourself and use the Pagination component
1415
+ - ❌ `<DataGrid DataGrid expecting built-in filtering>` → Filter the rows array before passing it in
1416
+ - ❌ `<DataGrid sortable without onSort>` → Handle onSort and sort the rows array in your state
1417
+ - ❌ `<Tag Tag variant="success">` → Use Badge for success/warning/teal status colors; Tag is for categories/filters
1418
+ - ❌ `<Tag Using Tag for tag input>` → Use TagsInput for adding/removing tags via keyboard
1419
+ - ❌ `<Tag Tag size="xl">` → Use size="lg"
1420
+ - ❌ `<TagGroup TagGroup without Tag children>` → Place <Tag> elements as direct children
1421
+ - ❌ `<TagGroup Using TagGroup for tag input>` → Use TagsInput for an editable tag field
1422
+ - ❌ `<Item Card used for repeated list rows>` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
1423
+ - ❌ `<Item Item variant="primary">` → Use variant="default" | "outlined" | "muted"
1424
+ - ❌ `<Item raw text directly inside Item>` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
1425
+ - ❌ `<Kanban Kanban without onChange (or ignoring it)>` → Store columns in state and setColumns in onChange (v-model in Vue)
1426
+ - ❌ `<Kanban Duplicate card ids across columns>` → Use globally-unique card ids across the entire board
1427
+ - ❌ `<Kanban Mutating value in place then calling onChange>` → Pass the new array Kanban gives you straight to setState / v-model
1428
+ - ❌ `<Kanban color="blue" (or any non-semantic value)>` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
1429
+ - ❌ `<Conversation Conversation without currentUserId>` → Always pass currentUserId matching one of the message authorId values
1430
+ - ❌ `<Conversation Expecting Conversation to store/append messages>` → Append to your own state in onSend (or @send) and pass it back via value
1431
+ - ❌ `<Conversation composer without onSend (React) / @send (Vue)>` → Provide onSend / @send to append the message to value
1432
+ - ❌ `<Conversation Treating onSend as (text) only when using allowAttachments>` → Handle onSend(text, files) — map files to message attachments and append
1433
+ - ❌ `<DateRangePicker value={[from, to]}>` → Use value={{ from, to }} and read range.from / range.to
1434
+ - ❌ `<DateRangePicker DateRangePicker for a single date>` → Use <DatePicker /> for a single date
1435
+ - ❌ `<DateRangePicker presets="true" (string)>` → Use the bare prop: presets (or presets={true})
920
1436
 
921
1437
  ---
922
1438