@usevyre/ai-context 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  # useVyre Design System Context
2
- # Version: 1.1.0
2
+ # Version: 1.2.0
3
3
 
4
4
  You are working in a codebase that uses the useVyre design system.
5
5
  Follow the rules below strictly when writing any UI code.
@@ -311,7 +311,7 @@ import { Button } from "@usevyre/react"
311
311
 
312
312
  ### Calendar
313
313
 
314
- Date picker calendar widget for selecting single dates or ranges.
314
+ 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.
315
315
 
316
316
  ```tsx
317
317
  import { Calendar } from "@usevyre/react"
@@ -320,6 +320,7 @@ import { Calendar } from "@usevyre/react"
320
320
  // value = Date | null
321
321
  // onChange = function
322
322
  // disabled = boolean (default: false)
323
+ // defaultMonth = Date
323
324
 
324
325
  // Examples:
325
326
  const [date, setDate] = useState(null);
@@ -327,7 +328,39 @@ const [date, setDate] = useState(null);
327
328
  ```
328
329
 
329
330
  **Common mistakes:**
330
- - ❌ `Using Calendar for time selection` Combine with a separate time Input if time selection is needed
331
+ - ❌ `Calendar for an input field that opens a popover` Use <DatePicker /> (single date) or <DateRangePicker /> (range)
332
+ - ❌ `value as tuple for mode="single"` → Pass value matching mode; use mode="range" for [start,end]
333
+
334
+ ---
335
+
336
+ ### DatePicker
337
+
338
+ 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.
339
+
340
+ ```tsx
341
+ import { DatePicker } from "@usevyre/react"
342
+
343
+ // Props:
344
+ // value = Date | [Date, Date] | Date[] | null
345
+ // onChange = function
346
+ // mode = "single" | "range" | "multiple" (default: single)
347
+ // placeholder = string (default: Pick a date)
348
+ // showTime = boolean (default: false)
349
+ // minDate = Date
350
+ // maxDate = Date
351
+ // disabled = function
352
+ // weekStartsOn = "0" | "1" (default: 1)
353
+ // inputClassName = string
354
+
355
+ // Examples:
356
+ const [date, setDate] = useState(null);
357
+ <DatePicker value={date} onChange={setDate} placeholder="Pick a date" />
358
+ <DatePicker value={date} onChange={setDate} showTime />
359
+ ```
360
+
361
+ **Common mistakes:**
362
+ - ❌ `DatePicker mode="range" for { from, to } object` → Use <DateRangePicker /> for the { from, to } object API + presets + dual month
363
+ - ❌ `DatePicker without value/onChange` → Provide value and onChange (e.g. from useState)
331
364
 
332
365
  ---
333
366
 
@@ -387,6 +420,78 @@ import { Checkbox } from "@usevyre/react"
387
420
 
388
421
  ---
389
422
 
423
+ ### RadioGroup
424
+
425
+ 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.
426
+
427
+ ```tsx
428
+ import { RadioGroup, Radio } from "@usevyre/react"
429
+
430
+ // Props:
431
+ // value = string
432
+ // defaultValue = string
433
+ // onChange = function
434
+ // name = string
435
+ // disabled = boolean (default: false)
436
+ // size = "sm" | "md" (default: md)
437
+ // orientation = "vertical" | "horizontal" (default: vertical)
438
+ // options = { value: string; label?: string; description?: string; disabled?: boolean }[]
439
+
440
+ // Examples:
441
+ <RadioGroup
442
+ value={plan}
443
+ onChange={setPlan}
444
+ options={[
445
+ { value: "free", label: "Free", description: "For hobby projects" },
446
+ { value: "pro", label: "Pro", description: "For teams" },
447
+ ]}
448
+ />
449
+ <RadioGroup value={plan} onChange={setPlan} orientation="horizontal">
450
+ <Radio value="free" label="Free" />
451
+ <Radio value="pro" label="Pro" />
452
+ </RadioGroup>
453
+ ```
454
+
455
+ **Common mistakes:**
456
+ - ❌ `<Radio> used outside a <RadioGroup>` → Always wrap <Radio> in <RadioGroup>
457
+ - ❌ `RadioGroup without value/onChange (React) or v-model (Vue)` → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
458
+ - ❌ `Using Checkbox for mutually-exclusive choices` → Use RadioGroup + Radio (or options) for one-of-many
459
+
460
+ ---
461
+
462
+ ### RichTextEditor
463
+
464
+ 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.
465
+
466
+ ```tsx
467
+ import { RichTextEditor } from "@usevyre/react"
468
+
469
+ // Props:
470
+ // value = string
471
+ // onChange = function
472
+ // placeholder = string (default: Write something…)
473
+ // disabled = boolean (default: false)
474
+ // readOnly = boolean (default: false)
475
+ // toolbar = RichTextTool[]
476
+ // minHeight = string (default: 10rem)
477
+
478
+ // Examples:
479
+ const [html, setHtml] = useState("<p>Hello <strong>world</strong></p>");
480
+ <RichTextEditor value={html} onChange={setHtml} placeholder="Write…" />
481
+ <RichTextEditor
482
+ value={html}
483
+ onChange={setHtml}
484
+ toolbar={["bold", "italic", "link"]}
485
+ />
486
+ ```
487
+
488
+ **Common mistakes:**
489
+ - ❌ `RichTextEditor without value/onChange (React) or v-model (Vue)` → Keep the HTML string in state and update it in onChange / v-model
490
+ - ❌ `Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising` → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
491
+ - ❌ `toolbar="bold" (string)` → Pass an array, e.g. toolbar={["bold","italic","link"]}
492
+
493
+ ---
494
+
390
495
  ### Command
391
496
 
392
497
  Command palette / search dialog. Use for search-first navigation or quick actions.
@@ -438,7 +543,7 @@ import { DropdownMenu, DropdownItem, DropdownSeparator, DropdownCheckboxItem, Dr
438
543
 
439
544
  ### Field
440
545
 
441
- Form field wrapper providing label, hint text, and validation state for Input or Textarea.
546
+ 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.
442
547
 
443
548
  ```tsx
444
549
  import { Field, Input, Textarea } from "@usevyre/react"
@@ -456,10 +561,23 @@ import { Field, Input, Textarea } from "@usevyre/react"
456
561
  <Field label="Search">
457
562
  <Input leftElement={<SearchIcon />} placeholder="Search..." />
458
563
  </Field>
564
+ <Field>
565
+ <FieldLabel required htmlFor="email">Email</FieldLabel>
566
+ <Input id="email" type="email" />
567
+ <FieldDescription>We\u2019ll never share it.</FieldDescription>
568
+ <FieldError>{errors.email}</FieldError>
569
+ </Field>
570
+
571
+ // Two controls side by side
572
+ <FieldGroup orientation="horizontal">
573
+ <Field label="First name"><Input /></Field>
574
+ <Field label="Last name"><Input /></Field>
575
+ </FieldGroup>
459
576
  ```
460
577
 
461
578
  **Common mistakes:**
462
579
  - ❌ `Applying state prop directly to Input` → Wrap Input in <Field state="error"> to apply validation styling
580
+ - ❌ `Mixing props label/hint AND FieldLabel/FieldError for the same field` → Pick one: either props-based (label/hint/state) OR composable parts
463
581
 
464
582
  ---
465
583
 
@@ -471,6 +589,7 @@ Text input field. Wrap in Field for labels and validation. Use leftElement/right
471
589
  import { Input } from "@usevyre/react"
472
590
 
473
591
  // Props:
592
+ // modelValue = string | number
474
593
  // size = "sm" | "md" | "lg" (default: md)
475
594
  // leftElement = ReactNode
476
595
  // rightElement = ReactNode
@@ -482,6 +601,7 @@ import { Input } from "@usevyre/react"
482
601
  **Common mistakes:**
483
602
  - ❌ `size="icon"` → Use size="sm" | "md" | "lg"
484
603
  - ❌ `type="search" for search UI` → Import Command from @usevyre/react for search palettes
604
+ - ❌ `Vue: binding Input/Textarea value without v-model` → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
485
605
 
486
606
  ---
487
607
 
@@ -1069,6 +1189,180 @@ import { TagGroup, Tag } from "@usevyre/react"
1069
1189
 
1070
1190
  ---
1071
1191
 
1192
+ ### Item
1193
+
1194
+ Layout primitive for list rows, settings rows, and notification rows. Denser than Card — use Item (not Card) for repeated list rows.
1195
+
1196
+ ```tsx
1197
+ import { Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions, ItemGroup } from "@usevyre/react"
1198
+
1199
+ // Props:
1200
+ // variant = "default" | "outlined" | "muted" | "plain" (default: default)
1201
+ // size = "sm" | "md" | "lg" (default: md)
1202
+ // clickable = boolean (default: false)
1203
+
1204
+ // Examples:
1205
+ <Item>
1206
+ <ItemMedia><BellIcon /></ItemMedia>
1207
+ <ItemContent>
1208
+ <ItemTitle>Notifications</ItemTitle>
1209
+ <ItemDescription>Receive an email when someone mentions you.</ItemDescription>
1210
+ </ItemContent>
1211
+ <ItemActions>
1212
+ <Switch defaultChecked />
1213
+ </ItemActions>
1214
+ </Item>
1215
+ <ItemGroup separated>
1216
+ <Item clickable>
1217
+ <ItemContent><ItemTitle>Profile</ItemTitle></ItemContent>
1218
+ </Item>
1219
+ <Item clickable>
1220
+ <ItemContent><ItemTitle>Billing</ItemTitle></ItemContent>
1221
+ </Item>
1222
+ </ItemGroup>
1223
+ ```
1224
+
1225
+ **Common mistakes:**
1226
+ - ❌ `Card used for repeated list rows` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
1227
+ - ❌ `Item variant="primary"` → Use variant="default" | "outlined" | "muted"
1228
+ - ❌ `raw text directly inside Item` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
1229
+
1230
+ ---
1231
+
1232
+ ### Kanban
1233
+
1234
+ 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.
1235
+
1236
+ ```tsx
1237
+ import { Kanban } from "@usevyre/react"
1238
+
1239
+ // Props:
1240
+ // value = KanbanColumn[]
1241
+ // onChange = function
1242
+ // renderCard = function
1243
+ // onCardClick = function
1244
+
1245
+ // Examples:
1246
+ const [columns, setColumns] = useState([
1247
+ { id: "todo", title: "To Do", cards: [{ id: "1", title: "Spec API" }] },
1248
+ { id: "doing", title: "In Progress", cards: [] },
1249
+ { id: "done", title: "Done", cards: [{ id: "2", title: "Kickoff" }] },
1250
+ ]);
1251
+ <Kanban value={columns} onChange={setColumns} />
1252
+ <Kanban
1253
+ value={columns}
1254
+ onChange={setColumns}
1255
+ onCardClick={(card) => openDetail(card.id)}
1256
+ renderCard={(card) => (
1257
+ <><strong>{card.title}</strong><Badge>{card.id}</Badge></>
1258
+ )}
1259
+ />
1260
+ const [cols, setCols] = useState([
1261
+ { id: "doing", title: "In Progress", color: "teal", cards: [
1262
+ { id: "t1", title: "OAuth", assignee: "AK", progress: 60, color: "warning" },
1263
+ ]},
1264
+ ]);
1265
+ <Kanban
1266
+ value={cols}
1267
+ onChange={setCols}
1268
+ renderCard={(card) => (
1269
+ <><strong>{card.title}</strong><Progress value={card.progress} /></>
1270
+ )}
1271
+ />
1272
+ ```
1273
+
1274
+ **Common mistakes:**
1275
+ - ❌ `Kanban without onChange (or ignoring it)` → Store columns in state and setColumns in onChange (v-model in Vue)
1276
+ - ❌ `Duplicate card ids across columns` → Use globally-unique card ids across the entire board
1277
+ - ❌ `Mutating value in place then calling onChange` → Pass the new array Kanban gives you straight to setState / v-model
1278
+ - ❌ `color="blue" (or any non-semantic value)` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
1279
+
1280
+ ---
1281
+
1282
+ ### Conversation
1283
+
1284
+ 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.
1285
+
1286
+ ```tsx
1287
+ import { Conversation } from "@usevyre/react"
1288
+
1289
+ // Props:
1290
+ // value = ConversationMessage[]
1291
+ // currentUserId = string
1292
+ // composer = boolean (default: false)
1293
+ // onSend = function
1294
+ // placeholder = string (default: Write a message…)
1295
+ // typing = boolean | string (default: false)
1296
+ // allowAttachments = boolean (default: false)
1297
+ // accept = string
1298
+ // renderMessage = function
1299
+ // renderComposer = function
1300
+
1301
+ // Examples:
1302
+ const [messages, setMessages] = useState([
1303
+ { id: "1", authorId: "sam", authorName: "Sam", text: "Hey!" },
1304
+ { id: "2", authorId: "me", text: "Hi \ud83d\udc4b", status: "read" },
1305
+ ]);
1306
+ <Conversation
1307
+ value={messages}
1308
+ currentUserId="me"
1309
+ composer
1310
+ onSend={(t) => setMessages((m) => [...m, { id: crypto.randomUUID(), authorId: "me", text: t }])}
1311
+ />
1312
+ <Conversation
1313
+ value={messages}
1314
+ currentUserId="me"
1315
+ typing="Sam is typing"
1316
+ renderMessage={(m) => <strong>{m.text}</strong>}
1317
+ />
1318
+ const messages = [
1319
+ { id: "1", authorId: "sam", authorName: "Sam", text: "Moodboard \ud83d\udc47",
1320
+ attachments: [{ kind: "image", url: "/board.png", name: "board.png" }] },
1321
+ { id: "2", authorId: "me", text: "Specs:", status: "read",
1322
+ attachments: [{ kind: "file", url: "/spec.pdf", name: "spec.pdf", size: "2.4 MB" }] },
1323
+ ];
1324
+ <Conversation value={messages} currentUserId="me" />
1325
+ ```
1326
+
1327
+ **Common mistakes:**
1328
+ - ❌ `Conversation without currentUserId` → Always pass currentUserId matching one of the message authorId values
1329
+ - ❌ `Expecting Conversation to store/append messages` → Append to your own state in onSend (or @send) and pass it back via value
1330
+ - ❌ `composer without onSend (React) / @send (Vue)` → Provide onSend / @send to append the message to value
1331
+ - ❌ `Treating onSend as (text) only when using allowAttachments` → Handle onSend(text, files) — map files to message attachments and append
1332
+
1333
+ ---
1334
+
1335
+ ### DateRangePicker
1336
+
1337
+ 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.
1338
+
1339
+ ```tsx
1340
+ import { DateRangePicker } from "@usevyre/react"
1341
+
1342
+ // Props:
1343
+ // value = { from: Date | null; to: Date | null } | null
1344
+ // onChange = function
1345
+ // placeholder = string (default: Pick a date range)
1346
+ // numberOfMonths = "1" | "2" (default: 2)
1347
+ // presets = boolean | DateRangePreset[] (default: false)
1348
+ // minDate = Date
1349
+ // maxDate = Date
1350
+ // disabled = function
1351
+ // weekStartsOn = "0" | "1" (default: 1)
1352
+
1353
+ // Examples:
1354
+ const [range, setRange] = useState({ from: null, to: null });
1355
+ <DateRangePicker value={range} onChange={setRange} presets />
1356
+ <DateRangePicker value={range} onChange={setRange} numberOfMonths={1} />
1357
+ ```
1358
+
1359
+ **Common mistakes:**
1360
+ - ❌ `value={[from, to]}` → Use value={{ from, to }} and read range.from / range.to
1361
+ - ❌ `DateRangePicker for a single date` → Use <DatePicker /> for a single date
1362
+ - ❌ `presets="true" (string)` → Use the bare prop: presets (or presets={true})
1363
+
1364
+ ---
1365
+
1072
1366
  ## Hallucination Guard — Common AI Mistakes
1073
1367
 
1074
1368
  The following prop values and patterns do NOT exist in useVyre.
@@ -1088,14 +1382,25 @@ If you generate these, you are hallucinating.
1088
1382
  - ❌ `<Button color="...">` → Use variant prop instead
1089
1383
  - ❌ `<Button icon={...}>` → Use leftIcon={...} or rightIcon={...}
1090
1384
  - ❌ `<Button size="icon" without aria-label>` → Add aria-label describing the action
1091
- - ❌ `<Calendar Using Calendar for time selection>` Combine with a separate time Input if time selection is needed
1385
+ - ❌ `<Calendar Calendar for an input field that opens a popover>` Use <DatePicker /> (single date) or <DateRangePicker /> (range)
1386
+ - ❌ `<Calendar value as tuple for mode="single">` → Pass value matching mode; use mode="range" for [start,end]
1387
+ - ❌ `<DatePicker DatePicker mode="range" for { from, to } object>` → Use <DateRangePicker /> for the { from, to } object API + presets + dual month
1388
+ - ❌ `<DatePicker DatePicker without value/onChange>` → Provide value and onChange (e.g. from useState)
1092
1389
  - ❌ `<Card variant="primary">` → Use variant="elevated" | "outlined" | "ghost" | "accent"
1093
1390
  - ❌ `<Checkbox size="lg">` → Use size="md"
1391
+ - ❌ `<RadioGroup <Radio> used outside a <RadioGroup>>` → Always wrap <Radio> in <RadioGroup>
1392
+ - ❌ `<RadioGroup RadioGroup without value/onChange (React) or v-model (Vue)>` → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
1393
+ - ❌ `<RadioGroup Using Checkbox for mutually-exclusive choices>` → Use RadioGroup + Radio (or options) for one-of-many
1394
+ - ❌ `<RichTextEditor RichTextEditor without value/onChange (React) or v-model (Vue)>` → Keep the HTML string in state and update it in onChange / v-model
1395
+ - ❌ `<RichTextEditor Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising>` → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
1396
+ - ❌ `<RichTextEditor toolbar="bold" (string)>` → Pass an array, e.g. toolbar={["bold","italic","link"]}
1094
1397
  - ❌ `<Command Using Input type="search" for search UI>` → Use Command + CommandInput + CommandList + CommandItem
1095
1398
  - ❌ `<DropdownMenu DropdownItem variant="primary">` → Use variant="danger" for destructive items only
1096
1399
  - ❌ `<Field Applying state prop directly to Input>` → Wrap Input in <Field state="error"> to apply validation styling
1400
+ - ❌ `<Field Mixing props label/hint AND FieldLabel/FieldError for the same field>` → Pick one: either props-based (label/hint/state) OR composable parts
1097
1401
  - ❌ `<Input size="icon">` → Use size="sm" | "md" | "lg"
1098
1402
  - ❌ `<Input type="search" for search UI>` → Import Command from @usevyre/react for search palettes
1403
+ - ❌ `<Input Vue: binding Input/Textarea value without v-model>` → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
1099
1404
  - ❌ `<Modal size="xl">` → Use size="lg" or size="full"
1100
1405
  - ❌ `<Popover placement="top-center">` → Use placement="top" for centered placement
1101
1406
  - ❌ `<Progress value > 100>` → Normalize your value to 0–100 range before passing
@@ -1120,6 +1425,20 @@ If you generate these, you are hallucinating.
1120
1425
  - ❌ `<Tag Tag size="xl">` → Use size="lg"
1121
1426
  - ❌ `<TagGroup TagGroup without Tag children>` → Place <Tag> elements as direct children
1122
1427
  - ❌ `<TagGroup Using TagGroup for tag input>` → Use TagsInput for an editable tag field
1428
+ - ❌ `<Item Card used for repeated list rows>` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
1429
+ - ❌ `<Item Item variant="primary">` → Use variant="default" | "outlined" | "muted"
1430
+ - ❌ `<Item raw text directly inside Item>` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
1431
+ - ❌ `<Kanban Kanban without onChange (or ignoring it)>` → Store columns in state and setColumns in onChange (v-model in Vue)
1432
+ - ❌ `<Kanban Duplicate card ids across columns>` → Use globally-unique card ids across the entire board
1433
+ - ❌ `<Kanban Mutating value in place then calling onChange>` → Pass the new array Kanban gives you straight to setState / v-model
1434
+ - ❌ `<Kanban color="blue" (or any non-semantic value)>` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
1435
+ - ❌ `<Conversation Conversation without currentUserId>` → Always pass currentUserId matching one of the message authorId values
1436
+ - ❌ `<Conversation Expecting Conversation to store/append messages>` → Append to your own state in onSend (or @send) and pass it back via value
1437
+ - ❌ `<Conversation composer without onSend (React) / @send (Vue)>` → Provide onSend / @send to append the message to value
1438
+ - ❌ `<Conversation Treating onSend as (text) only when using allowAttachments>` → Handle onSend(text, files) — map files to message attachments and append
1439
+ - ❌ `<DateRangePicker value={[from, to]}>` → Use value={{ from, to }} and read range.from / range.to
1440
+ - ❌ `<DateRangePicker DateRangePicker for a single date>` → Use <DatePicker /> for a single date
1441
+ - ❌ `<DateRangePicker presets="true" (string)>` → Use the bare prop: presets (or presets={true})
1123
1442
 
1124
1443
  ---
1125
1444