@usevyre/ai-context 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/anti-patterns.json +186 -4
- package/dist/cheat-sheets/calendar.md +5 -3
- package/dist/cheat-sheets/conversation.md +63 -0
- package/dist/cheat-sheets/datepicker.md +36 -0
- package/dist/cheat-sheets/daterangepicker.md +37 -0
- package/dist/cheat-sheets/field.md +19 -1
- package/dist/cheat-sheets/index.md +9 -2
- package/dist/cheat-sheets/input.md +2 -0
- package/dist/cheat-sheets/item.md +53 -0
- package/dist/cheat-sheets/kanban.md +59 -0
- package/dist/cheat-sheets/radiogroup.md +47 -0
- package/dist/cheat-sheets/richtexteditor.md +41 -0
- package/dist/cheat-sheets/sidebar.md +16 -0
- package/dist/claude-context.md +337 -5
- package/dist/copilot-instructions.md +337 -5
- package/dist/cursor-rules.md +96 -4
- package/dist/full-context.md +336 -4
- package/dist/index.js +2390 -151
- package/dist/schema.json +584 -8
- package/dist/tokens.json +1 -1
- package/dist/tokens.md +1 -1
- package/dist/version-info.json +92 -38
- package/dist/windsurf-rules.md +337 -5
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# useVyre Copilot Instructions
|
|
2
|
-
# Version: 1.
|
|
2
|
+
# Version: 1.2.0
|
|
3
3
|
|
|
4
4
|
When generating UI code in this project, follow the useVyre design system rules below.
|
|
5
5
|
|
|
@@ -310,7 +310,7 @@ import { Button } from "@usevyre/react"
|
|
|
310
310
|
|
|
311
311
|
### Calendar
|
|
312
312
|
|
|
313
|
-
|
|
313
|
+
Inline date-grid widget (always visible, no input). mode: single | range | multiple, optional time picker. For an input + popover use DatePicker; for start/end ranges with presets use DateRangePicker.
|
|
314
314
|
|
|
315
315
|
```tsx
|
|
316
316
|
import { Calendar } from "@usevyre/react"
|
|
@@ -319,6 +319,7 @@ import { Calendar } from "@usevyre/react"
|
|
|
319
319
|
// value = Date | null
|
|
320
320
|
// onChange = function
|
|
321
321
|
// disabled = boolean (default: false)
|
|
322
|
+
// defaultMonth = Date
|
|
322
323
|
|
|
323
324
|
// Examples:
|
|
324
325
|
const [date, setDate] = useState(null);
|
|
@@ -326,7 +327,39 @@ const [date, setDate] = useState(null);
|
|
|
326
327
|
```
|
|
327
328
|
|
|
328
329
|
**Common mistakes:**
|
|
329
|
-
- ❌ `
|
|
330
|
+
- ❌ `Calendar for an input field that opens a popover` → Use <DatePicker /> (single date) or <DateRangePicker /> (range)
|
|
331
|
+
- ❌ `value as tuple for mode="single"` → Pass value matching mode; use mode="range" for [start,end]
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
### DatePicker
|
|
336
|
+
|
|
337
|
+
Input trigger that opens a Calendar in a popover. Same modes as Calendar (single | range | multiple) plus a placeholder. Use this for a compact date field; use Calendar for an always-visible grid, or DateRangePicker for start/end ranges with presets.
|
|
338
|
+
|
|
339
|
+
```tsx
|
|
340
|
+
import { DatePicker } from "@usevyre/react"
|
|
341
|
+
|
|
342
|
+
// Props:
|
|
343
|
+
// value = Date | [Date, Date] | Date[] | null
|
|
344
|
+
// onChange = function
|
|
345
|
+
// mode = "single" | "range" | "multiple" (default: single)
|
|
346
|
+
// placeholder = string (default: Pick a date)
|
|
347
|
+
// showTime = boolean (default: false)
|
|
348
|
+
// minDate = Date
|
|
349
|
+
// maxDate = Date
|
|
350
|
+
// disabled = function
|
|
351
|
+
// weekStartsOn = "0" | "1" (default: 1)
|
|
352
|
+
// inputClassName = string
|
|
353
|
+
|
|
354
|
+
// Examples:
|
|
355
|
+
const [date, setDate] = useState(null);
|
|
356
|
+
<DatePicker value={date} onChange={setDate} placeholder="Pick a date" />
|
|
357
|
+
<DatePicker value={date} onChange={setDate} showTime />
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**Common mistakes:**
|
|
361
|
+
- ❌ `DatePicker mode="range" for { from, to } object` → Use <DateRangePicker /> for the { from, to } object API + presets + dual month
|
|
362
|
+
- ❌ `DatePicker without value/onChange` → Provide value and onChange (e.g. from useState)
|
|
330
363
|
|
|
331
364
|
---
|
|
332
365
|
|
|
@@ -386,6 +419,78 @@ import { Checkbox } from "@usevyre/react"
|
|
|
386
419
|
|
|
387
420
|
---
|
|
388
421
|
|
|
422
|
+
### RadioGroup
|
|
423
|
+
|
|
424
|
+
Controlled single-choice group. RadioGroup owns the selected value; render it data-driven via the options array OR with composable <Radio> children for custom content. role=radiogroup with proper labelling. For multi-select use Checkbox; for a compact dropdown use Select.
|
|
425
|
+
|
|
426
|
+
```tsx
|
|
427
|
+
import { RadioGroup, Radio } from "@usevyre/react"
|
|
428
|
+
|
|
429
|
+
// Props:
|
|
430
|
+
// value = string
|
|
431
|
+
// defaultValue = string
|
|
432
|
+
// onChange = function
|
|
433
|
+
// name = string
|
|
434
|
+
// disabled = boolean (default: false)
|
|
435
|
+
// size = "sm" | "md" (default: md)
|
|
436
|
+
// orientation = "vertical" | "horizontal" (default: vertical)
|
|
437
|
+
// options = { value: string; label?: string; description?: string; disabled?: boolean }[]
|
|
438
|
+
|
|
439
|
+
// Examples:
|
|
440
|
+
<RadioGroup
|
|
441
|
+
value={plan}
|
|
442
|
+
onChange={setPlan}
|
|
443
|
+
options={[
|
|
444
|
+
{ value: "free", label: "Free", description: "For hobby projects" },
|
|
445
|
+
{ value: "pro", label: "Pro", description: "For teams" },
|
|
446
|
+
]}
|
|
447
|
+
/>
|
|
448
|
+
<RadioGroup value={plan} onChange={setPlan} orientation="horizontal">
|
|
449
|
+
<Radio value="free" label="Free" />
|
|
450
|
+
<Radio value="pro" label="Pro" />
|
|
451
|
+
</RadioGroup>
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
**Common mistakes:**
|
|
455
|
+
- ❌ `<Radio> used outside a <RadioGroup>` → Always wrap <Radio> in <RadioGroup>
|
|
456
|
+
- ❌ `RadioGroup without value/onChange (React) or v-model (Vue)` → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
|
|
457
|
+
- ❌ `Using Checkbox for mutually-exclusive choices` → Use RadioGroup + Radio (or options) for one-of-many
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
### RichTextEditor
|
|
462
|
+
|
|
463
|
+
Controlled WYSIWYG editor. value is an HTML string; you own it in state and set it in onChange (React) / v-model (Vue). Native contentEditable + execCommand — zero dependencies. Toolbar: bold, italic, underline, strike, h1-h3, ordered/unordered lists, quote, code block, link, clear formatting.
|
|
464
|
+
|
|
465
|
+
```tsx
|
|
466
|
+
import { RichTextEditor } from "@usevyre/react"
|
|
467
|
+
|
|
468
|
+
// Props:
|
|
469
|
+
// value = string
|
|
470
|
+
// onChange = function
|
|
471
|
+
// placeholder = string (default: Write something…)
|
|
472
|
+
// disabled = boolean (default: false)
|
|
473
|
+
// readOnly = boolean (default: false)
|
|
474
|
+
// toolbar = RichTextTool[]
|
|
475
|
+
// minHeight = string (default: 10rem)
|
|
476
|
+
|
|
477
|
+
// Examples:
|
|
478
|
+
const [html, setHtml] = useState("<p>Hello <strong>world</strong></p>");
|
|
479
|
+
<RichTextEditor value={html} onChange={setHtml} placeholder="Write…" />
|
|
480
|
+
<RichTextEditor
|
|
481
|
+
value={html}
|
|
482
|
+
onChange={setHtml}
|
|
483
|
+
toolbar={["bold", "italic", "link"]}
|
|
484
|
+
/>
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
**Common mistakes:**
|
|
488
|
+
- ❌ `RichTextEditor without value/onChange (React) or v-model (Vue)` → Keep the HTML string in state and update it in onChange / v-model
|
|
489
|
+
- ❌ `Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising` → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
|
|
490
|
+
- ❌ `toolbar="bold" (string)` → Pass an array, e.g. toolbar={["bold","italic","link"]}
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
389
494
|
### Command
|
|
390
495
|
|
|
391
496
|
Command palette / search dialog. Use for search-first navigation or quick actions.
|
|
@@ -437,7 +542,7 @@ import { DropdownMenu, DropdownItem, DropdownSeparator, DropdownCheckboxItem, Dr
|
|
|
437
542
|
|
|
438
543
|
### Field
|
|
439
544
|
|
|
440
|
-
Form field wrapper
|
|
545
|
+
Form field wrapper. Two ways to use it (both supported): (1) props-based — pass label/hint/state/required for the common case; (2) composable — use the parts FieldLabel, FieldDescription, FieldError, FieldGroup, FieldSet for richer layouts (multiple controls, custom error placement). The props-based API is unchanged and still works.
|
|
441
546
|
|
|
442
547
|
```tsx
|
|
443
548
|
import { Field, Input, Textarea } from "@usevyre/react"
|
|
@@ -455,10 +560,23 @@ import { Field, Input, Textarea } from "@usevyre/react"
|
|
|
455
560
|
<Field label="Search">
|
|
456
561
|
<Input leftElement={<SearchIcon />} placeholder="Search..." />
|
|
457
562
|
</Field>
|
|
563
|
+
<Field>
|
|
564
|
+
<FieldLabel required htmlFor="email">Email</FieldLabel>
|
|
565
|
+
<Input id="email" type="email" />
|
|
566
|
+
<FieldDescription>We\u2019ll never share it.</FieldDescription>
|
|
567
|
+
<FieldError>{errors.email}</FieldError>
|
|
568
|
+
</Field>
|
|
569
|
+
|
|
570
|
+
// Two controls side by side
|
|
571
|
+
<FieldGroup orientation="horizontal">
|
|
572
|
+
<Field label="First name"><Input /></Field>
|
|
573
|
+
<Field label="Last name"><Input /></Field>
|
|
574
|
+
</FieldGroup>
|
|
458
575
|
```
|
|
459
576
|
|
|
460
577
|
**Common mistakes:**
|
|
461
578
|
- ❌ `Applying state prop directly to Input` → Wrap Input in <Field state="error"> to apply validation styling
|
|
579
|
+
- ❌ `Mixing props label/hint AND FieldLabel/FieldError for the same field` → Pick one: either props-based (label/hint/state) OR composable parts
|
|
462
580
|
|
|
463
581
|
---
|
|
464
582
|
|
|
@@ -470,6 +588,7 @@ Text input field. Wrap in Field for labels and validation. Use leftElement/right
|
|
|
470
588
|
import { Input } from "@usevyre/react"
|
|
471
589
|
|
|
472
590
|
// Props:
|
|
591
|
+
// modelValue = string | number
|
|
473
592
|
// size = "sm" | "md" | "lg" (default: md)
|
|
474
593
|
// leftElement = ReactNode
|
|
475
594
|
// rightElement = ReactNode
|
|
@@ -481,6 +600,7 @@ import { Input } from "@usevyre/react"
|
|
|
481
600
|
**Common mistakes:**
|
|
482
601
|
- ❌ `size="icon"` → Use size="sm" | "md" | "lg"
|
|
483
602
|
- ❌ `type="search" for search UI` → Import Command from @usevyre/react for search palettes
|
|
603
|
+
- ❌ `Vue: binding Input/Textarea value without v-model` → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
|
|
484
604
|
|
|
485
605
|
---
|
|
486
606
|
|
|
@@ -676,6 +796,8 @@ import { AppLayout, Sidebar, SidebarHeader, SidebarContent, SidebarSection, Side
|
|
|
676
796
|
|
|
677
797
|
// Props:
|
|
678
798
|
// variant = "default" | "floating" (default: default)
|
|
799
|
+
// SidebarTrigger.icon = ReactNode
|
|
800
|
+
// SidebarTrigger.collapsedIcon = ReactNode
|
|
679
801
|
|
|
680
802
|
// Examples:
|
|
681
803
|
<AppLayout>
|
|
@@ -691,8 +813,18 @@ import { AppLayout, Sidebar, SidebarHeader, SidebarContent, SidebarSection, Side
|
|
|
691
813
|
</Sidebar>
|
|
692
814
|
<main>Page content</main>
|
|
693
815
|
</AppLayout>
|
|
816
|
+
<SidebarTrigger icon={<PanelLeftClose />} collapsedIcon={<PanelLeftOpen />} />
|
|
817
|
+
|
|
818
|
+
// Vue:
|
|
819
|
+
// <SidebarTrigger>
|
|
820
|
+
// <template #icon><PanelLeftClose /></template>
|
|
821
|
+
// <template #collapsed-icon><PanelLeftOpen /></template>
|
|
822
|
+
// </SidebarTrigger>
|
|
694
823
|
```
|
|
695
824
|
|
|
825
|
+
**Common mistakes:**
|
|
826
|
+
- ❌ `Vue: passing icon/collapsedIcon as props on SidebarTrigger` → Use <template #icon> and <template #collapsed-icon>; React uses icon / collapsedIcon props
|
|
827
|
+
|
|
696
828
|
---
|
|
697
829
|
|
|
698
830
|
### Skeleton
|
|
@@ -1068,6 +1200,180 @@ import { TagGroup, Tag } from "@usevyre/react"
|
|
|
1068
1200
|
|
|
1069
1201
|
---
|
|
1070
1202
|
|
|
1203
|
+
### Item
|
|
1204
|
+
|
|
1205
|
+
Layout primitive for list rows, settings rows, and notification rows. Denser than Card — use Item (not Card) for repeated list rows.
|
|
1206
|
+
|
|
1207
|
+
```tsx
|
|
1208
|
+
import { Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions, ItemGroup } from "@usevyre/react"
|
|
1209
|
+
|
|
1210
|
+
// Props:
|
|
1211
|
+
// variant = "default" | "outlined" | "muted" | "plain" (default: default)
|
|
1212
|
+
// size = "sm" | "md" | "lg" (default: md)
|
|
1213
|
+
// clickable = boolean (default: false)
|
|
1214
|
+
|
|
1215
|
+
// Examples:
|
|
1216
|
+
<Item>
|
|
1217
|
+
<ItemMedia><BellIcon /></ItemMedia>
|
|
1218
|
+
<ItemContent>
|
|
1219
|
+
<ItemTitle>Notifications</ItemTitle>
|
|
1220
|
+
<ItemDescription>Receive an email when someone mentions you.</ItemDescription>
|
|
1221
|
+
</ItemContent>
|
|
1222
|
+
<ItemActions>
|
|
1223
|
+
<Switch defaultChecked />
|
|
1224
|
+
</ItemActions>
|
|
1225
|
+
</Item>
|
|
1226
|
+
<ItemGroup separated>
|
|
1227
|
+
<Item clickable>
|
|
1228
|
+
<ItemContent><ItemTitle>Profile</ItemTitle></ItemContent>
|
|
1229
|
+
</Item>
|
|
1230
|
+
<Item clickable>
|
|
1231
|
+
<ItemContent><ItemTitle>Billing</ItemTitle></ItemContent>
|
|
1232
|
+
</Item>
|
|
1233
|
+
</ItemGroup>
|
|
1234
|
+
```
|
|
1235
|
+
|
|
1236
|
+
**Common mistakes:**
|
|
1237
|
+
- ❌ `Card used for repeated list rows` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
|
|
1238
|
+
- ❌ `Item variant="primary"` → Use variant="default" | "outlined" | "muted"
|
|
1239
|
+
- ❌ `raw text directly inside Item` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
|
|
1240
|
+
|
|
1241
|
+
---
|
|
1242
|
+
|
|
1243
|
+
### Kanban
|
|
1244
|
+
|
|
1245
|
+
Drag-and-drop board: cards move between columns (or reorder within a column). CONTROLLED & data-driven like DataGrid. While dragging, a placeholder shows the exact drop position. Each card is wrapped in a Card (variant="outlined"); renderCard (React) / #card slot (Vue) can render ANY content incl. complex components (Avatar/Badge/Progress). Columns and cards accept an optional semantic color tint. Native HTML5 DnD, zero deps.
|
|
1246
|
+
|
|
1247
|
+
```tsx
|
|
1248
|
+
import { Kanban } from "@usevyre/react"
|
|
1249
|
+
|
|
1250
|
+
// Props:
|
|
1251
|
+
// value = KanbanColumn[]
|
|
1252
|
+
// onChange = function
|
|
1253
|
+
// renderCard = function
|
|
1254
|
+
// onCardClick = function
|
|
1255
|
+
|
|
1256
|
+
// Examples:
|
|
1257
|
+
const [columns, setColumns] = useState([
|
|
1258
|
+
{ id: "todo", title: "To Do", cards: [{ id: "1", title: "Spec API" }] },
|
|
1259
|
+
{ id: "doing", title: "In Progress", cards: [] },
|
|
1260
|
+
{ id: "done", title: "Done", cards: [{ id: "2", title: "Kickoff" }] },
|
|
1261
|
+
]);
|
|
1262
|
+
<Kanban value={columns} onChange={setColumns} />
|
|
1263
|
+
<Kanban
|
|
1264
|
+
value={columns}
|
|
1265
|
+
onChange={setColumns}
|
|
1266
|
+
onCardClick={(card) => openDetail(card.id)}
|
|
1267
|
+
renderCard={(card) => (
|
|
1268
|
+
<><strong>{card.title}</strong><Badge>{card.id}</Badge></>
|
|
1269
|
+
)}
|
|
1270
|
+
/>
|
|
1271
|
+
const [cols, setCols] = useState([
|
|
1272
|
+
{ id: "doing", title: "In Progress", color: "teal", cards: [
|
|
1273
|
+
{ id: "t1", title: "OAuth", assignee: "AK", progress: 60, color: "warning" },
|
|
1274
|
+
]},
|
|
1275
|
+
]);
|
|
1276
|
+
<Kanban
|
|
1277
|
+
value={cols}
|
|
1278
|
+
onChange={setCols}
|
|
1279
|
+
renderCard={(card) => (
|
|
1280
|
+
<><strong>{card.title}</strong><Progress value={card.progress} /></>
|
|
1281
|
+
)}
|
|
1282
|
+
/>
|
|
1283
|
+
```
|
|
1284
|
+
|
|
1285
|
+
**Common mistakes:**
|
|
1286
|
+
- ❌ `Kanban without onChange (or ignoring it)` → Store columns in state and setColumns in onChange (v-model in Vue)
|
|
1287
|
+
- ❌ `Duplicate card ids across columns` → Use globally-unique card ids across the entire board
|
|
1288
|
+
- ❌ `Mutating value in place then calling onChange` → Pass the new array Kanban gives you straight to setState / v-model
|
|
1289
|
+
- ❌ `color="blue" (or any non-semantic value)` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
|
|
1290
|
+
|
|
1291
|
+
---
|
|
1292
|
+
|
|
1293
|
+
### Conversation
|
|
1294
|
+
|
|
1295
|
+
Chat / inbox message thread. CONTROLLED & data-driven like Kanban — you own `value` (messages array) and append in your own send handler; Conversation holds no message state. Consecutive messages from the same author are grouped (avatar + name shown once), day separators are inserted on date change, and outgoing messages (authorId === currentUserId) align right.
|
|
1296
|
+
|
|
1297
|
+
```tsx
|
|
1298
|
+
import { Conversation } from "@usevyre/react"
|
|
1299
|
+
|
|
1300
|
+
// Props:
|
|
1301
|
+
// value = ConversationMessage[]
|
|
1302
|
+
// currentUserId = string
|
|
1303
|
+
// composer = boolean (default: false)
|
|
1304
|
+
// onSend = function
|
|
1305
|
+
// placeholder = string (default: Write a message…)
|
|
1306
|
+
// typing = boolean | string (default: false)
|
|
1307
|
+
// allowAttachments = boolean (default: false)
|
|
1308
|
+
// accept = string
|
|
1309
|
+
// renderMessage = function
|
|
1310
|
+
// renderComposer = function
|
|
1311
|
+
|
|
1312
|
+
// Examples:
|
|
1313
|
+
const [messages, setMessages] = useState([
|
|
1314
|
+
{ id: "1", authorId: "sam", authorName: "Sam", text: "Hey!" },
|
|
1315
|
+
{ id: "2", authorId: "me", text: "Hi \ud83d\udc4b", status: "read" },
|
|
1316
|
+
]);
|
|
1317
|
+
<Conversation
|
|
1318
|
+
value={messages}
|
|
1319
|
+
currentUserId="me"
|
|
1320
|
+
composer
|
|
1321
|
+
onSend={(t) => setMessages((m) => [...m, { id: crypto.randomUUID(), authorId: "me", text: t }])}
|
|
1322
|
+
/>
|
|
1323
|
+
<Conversation
|
|
1324
|
+
value={messages}
|
|
1325
|
+
currentUserId="me"
|
|
1326
|
+
typing="Sam is typing"
|
|
1327
|
+
renderMessage={(m) => <strong>{m.text}</strong>}
|
|
1328
|
+
/>
|
|
1329
|
+
const messages = [
|
|
1330
|
+
{ id: "1", authorId: "sam", authorName: "Sam", text: "Moodboard \ud83d\udc47",
|
|
1331
|
+
attachments: [{ kind: "image", url: "/board.png", name: "board.png" }] },
|
|
1332
|
+
{ id: "2", authorId: "me", text: "Specs:", status: "read",
|
|
1333
|
+
attachments: [{ kind: "file", url: "/spec.pdf", name: "spec.pdf", size: "2.4 MB" }] },
|
|
1334
|
+
];
|
|
1335
|
+
<Conversation value={messages} currentUserId="me" />
|
|
1336
|
+
```
|
|
1337
|
+
|
|
1338
|
+
**Common mistakes:**
|
|
1339
|
+
- ❌ `Conversation without currentUserId` → Always pass currentUserId matching one of the message authorId values
|
|
1340
|
+
- ❌ `Expecting Conversation to store/append messages` → Append to your own state in onSend (or @send) and pass it back via value
|
|
1341
|
+
- ❌ `composer without onSend (React) / @send (Vue)` → Provide onSend / @send to append the message to value
|
|
1342
|
+
- ❌ `Treating onSend as (text) only when using allowAttachments` → Handle onSend(text, files) — map files to message attachments and append
|
|
1343
|
+
|
|
1344
|
+
---
|
|
1345
|
+
|
|
1346
|
+
### DateRangePicker
|
|
1347
|
+
|
|
1348
|
+
Start/end date range picker. Built on Calendar (mode=range) with a friendlier { from, to } object API, a two-month side-by-side view, and preset shortcuts. Use this for report/filter date ranges; use DatePicker for a single date.
|
|
1349
|
+
|
|
1350
|
+
```tsx
|
|
1351
|
+
import { DateRangePicker } from "@usevyre/react"
|
|
1352
|
+
|
|
1353
|
+
// Props:
|
|
1354
|
+
// value = { from: Date | null; to: Date | null } | null
|
|
1355
|
+
// onChange = function
|
|
1356
|
+
// placeholder = string (default: Pick a date range)
|
|
1357
|
+
// numberOfMonths = "1" | "2" (default: 2)
|
|
1358
|
+
// presets = boolean | DateRangePreset[] (default: false)
|
|
1359
|
+
// minDate = Date
|
|
1360
|
+
// maxDate = Date
|
|
1361
|
+
// disabled = function
|
|
1362
|
+
// weekStartsOn = "0" | "1" (default: 1)
|
|
1363
|
+
|
|
1364
|
+
// Examples:
|
|
1365
|
+
const [range, setRange] = useState({ from: null, to: null });
|
|
1366
|
+
<DateRangePicker value={range} onChange={setRange} presets />
|
|
1367
|
+
<DateRangePicker value={range} onChange={setRange} numberOfMonths={1} />
|
|
1368
|
+
```
|
|
1369
|
+
|
|
1370
|
+
**Common mistakes:**
|
|
1371
|
+
- ❌ `value={[from, to]}` → Use value={{ from, to }} and read range.from / range.to
|
|
1372
|
+
- ❌ `DateRangePicker for a single date` → Use <DatePicker /> for a single date
|
|
1373
|
+
- ❌ `presets="true" (string)` → Use the bare prop: presets (or presets={true})
|
|
1374
|
+
|
|
1375
|
+
---
|
|
1376
|
+
|
|
1071
1377
|
## Hallucination Guard — Common AI Mistakes
|
|
1072
1378
|
|
|
1073
1379
|
The following prop values and patterns do NOT exist in useVyre.
|
|
@@ -1087,18 +1393,30 @@ If you generate these, you are hallucinating.
|
|
|
1087
1393
|
- ❌ `<Button color="...">` → Use variant prop instead
|
|
1088
1394
|
- ❌ `<Button icon={...}>` → Use leftIcon={...} or rightIcon={...}
|
|
1089
1395
|
- ❌ `<Button size="icon" without aria-label>` → Add aria-label describing the action
|
|
1090
|
-
- ❌ `<Calendar
|
|
1396
|
+
- ❌ `<Calendar Calendar for an input field that opens a popover>` → Use <DatePicker /> (single date) or <DateRangePicker /> (range)
|
|
1397
|
+
- ❌ `<Calendar value as tuple for mode="single">` → Pass value matching mode; use mode="range" for [start,end]
|
|
1398
|
+
- ❌ `<DatePicker DatePicker mode="range" for { from, to } object>` → Use <DateRangePicker /> for the { from, to } object API + presets + dual month
|
|
1399
|
+
- ❌ `<DatePicker DatePicker without value/onChange>` → Provide value and onChange (e.g. from useState)
|
|
1091
1400
|
- ❌ `<Card variant="primary">` → Use variant="elevated" | "outlined" | "ghost" | "accent"
|
|
1092
1401
|
- ❌ `<Checkbox size="lg">` → Use size="md"
|
|
1402
|
+
- ❌ `<RadioGroup <Radio> used outside a <RadioGroup>>` → Always wrap <Radio> in <RadioGroup>
|
|
1403
|
+
- ❌ `<RadioGroup RadioGroup without value/onChange (React) or v-model (Vue)>` → Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
|
|
1404
|
+
- ❌ `<RadioGroup Using Checkbox for mutually-exclusive choices>` → Use RadioGroup + Radio (or options) for one-of-many
|
|
1405
|
+
- ❌ `<RichTextEditor RichTextEditor without value/onChange (React) or v-model (Vue)>` → Keep the HTML string in state and update it in onChange / v-model
|
|
1406
|
+
- ❌ `<RichTextEditor Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising>` → Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
|
|
1407
|
+
- ❌ `<RichTextEditor toolbar="bold" (string)>` → Pass an array, e.g. toolbar={["bold","italic","link"]}
|
|
1093
1408
|
- ❌ `<Command Using Input type="search" for search UI>` → Use Command + CommandInput + CommandList + CommandItem
|
|
1094
1409
|
- ❌ `<DropdownMenu DropdownItem variant="primary">` → Use variant="danger" for destructive items only
|
|
1095
1410
|
- ❌ `<Field Applying state prop directly to Input>` → Wrap Input in <Field state="error"> to apply validation styling
|
|
1411
|
+
- ❌ `<Field Mixing props label/hint AND FieldLabel/FieldError for the same field>` → Pick one: either props-based (label/hint/state) OR composable parts
|
|
1096
1412
|
- ❌ `<Input size="icon">` → Use size="sm" | "md" | "lg"
|
|
1097
1413
|
- ❌ `<Input type="search" for search UI>` → Import Command from @usevyre/react for search palettes
|
|
1414
|
+
- ❌ `<Input Vue: binding Input/Textarea value without v-model>` → Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
|
|
1098
1415
|
- ❌ `<Modal size="xl">` → Use size="lg" or size="full"
|
|
1099
1416
|
- ❌ `<Popover placement="top-center">` → Use placement="top" for centered placement
|
|
1100
1417
|
- ❌ `<Progress value > 100>` → Normalize your value to 0–100 range before passing
|
|
1101
1418
|
- ❌ `<Select Passing strings directly as children>` → Pass options={[{ value: 'a', label: 'Option A' }]}
|
|
1419
|
+
- ❌ `<Sidebar Vue: passing icon/collapsedIcon as props on SidebarTrigger>` → Use <template #icon> and <template #collapsed-icon>; React uses icon / collapsedIcon props
|
|
1102
1420
|
- ❌ `<Toast Rendering <Toast> directly in JSX>` → Use: const { toast } = useToast(); then toast({ title, variant })
|
|
1103
1421
|
- ❌ `<Toast variant="error">` → Use variant="danger"
|
|
1104
1422
|
- ❌ `<Toast variant="info">` → Use variant="default"
|
|
@@ -1119,6 +1437,20 @@ If you generate these, you are hallucinating.
|
|
|
1119
1437
|
- ❌ `<Tag Tag size="xl">` → Use size="lg"
|
|
1120
1438
|
- ❌ `<TagGroup TagGroup without Tag children>` → Place <Tag> elements as direct children
|
|
1121
1439
|
- ❌ `<TagGroup Using TagGroup for tag input>` → Use TagsInput for an editable tag field
|
|
1440
|
+
- ❌ `<Item Card used for repeated list rows>` → Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
|
|
1441
|
+
- ❌ `<Item Item variant="primary">` → Use variant="default" | "outlined" | "muted"
|
|
1442
|
+
- ❌ `<Item raw text directly inside Item>` → Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
|
|
1443
|
+
- ❌ `<Kanban Kanban without onChange (or ignoring it)>` → Store columns in state and setColumns in onChange (v-model in Vue)
|
|
1444
|
+
- ❌ `<Kanban Duplicate card ids across columns>` → Use globally-unique card ids across the entire board
|
|
1445
|
+
- ❌ `<Kanban Mutating value in place then calling onChange>` → Pass the new array Kanban gives you straight to setState / v-model
|
|
1446
|
+
- ❌ `<Kanban color="blue" (or any non-semantic value)>` → Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
|
|
1447
|
+
- ❌ `<Conversation Conversation without currentUserId>` → Always pass currentUserId matching one of the message authorId values
|
|
1448
|
+
- ❌ `<Conversation Expecting Conversation to store/append messages>` → Append to your own state in onSend (or @send) and pass it back via value
|
|
1449
|
+
- ❌ `<Conversation composer without onSend (React) / @send (Vue)>` → Provide onSend / @send to append the message to value
|
|
1450
|
+
- ❌ `<Conversation Treating onSend as (text) only when using allowAttachments>` → Handle onSend(text, files) — map files to message attachments and append
|
|
1451
|
+
- ❌ `<DateRangePicker value={[from, to]}>` → Use value={{ from, to }} and read range.from / range.to
|
|
1452
|
+
- ❌ `<DateRangePicker DateRangePicker for a single date>` → Use <DatePicker /> for a single date
|
|
1453
|
+
- ❌ `<DateRangePicker presets="true" (string)>` → Use the bare prop: presets (or presets={true})
|
|
1122
1454
|
|
|
1123
1455
|
---
|
|
1124
1456
|
|
package/dist/cursor-rules.md
CHANGED
|
@@ -4,7 +4,7 @@ alwaysApply: true
|
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# useVyre Design System — Cursor Rules
|
|
7
|
-
# Version: 1.
|
|
7
|
+
# Version: 1.2.0
|
|
8
8
|
|
|
9
9
|
You are working in a project using the useVyre design system (@usevyre/react).
|
|
10
10
|
Follow these rules strictly when generating any UI code.
|
|
@@ -83,13 +83,26 @@ Never do:
|
|
|
83
83
|
- ❌ size="icon" without aria-label → ✅ Add aria-label describing the action
|
|
84
84
|
|
|
85
85
|
## Calendar
|
|
86
|
-
|
|
86
|
+
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.
|
|
87
87
|
Import: `import { Calendar } from "@usevyre/react"`
|
|
88
88
|
|
|
89
89
|
Valid props:
|
|
90
90
|
|
|
91
91
|
Never do:
|
|
92
|
-
- ❌
|
|
92
|
+
- ❌ Calendar for an input field that opens a popover → ✅ Use <DatePicker /> (single date) or <DateRangePicker /> (range)
|
|
93
|
+
- ❌ value as tuple for mode="single" → ✅ Pass value matching mode; use mode="range" for [start,end]
|
|
94
|
+
|
|
95
|
+
## DatePicker
|
|
96
|
+
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.
|
|
97
|
+
Import: `import { DatePicker } from "@usevyre/react"`
|
|
98
|
+
|
|
99
|
+
Valid props:
|
|
100
|
+
- mode: "single" | "range" | "multiple" [default: single]
|
|
101
|
+
- weekStartsOn: "0" | "1" [default: 1]
|
|
102
|
+
|
|
103
|
+
Never do:
|
|
104
|
+
- ❌ DatePicker mode="range" for { from, to } object → ✅ Use <DateRangePicker /> for the { from, to } object API + presets + dual month
|
|
105
|
+
- ❌ DatePicker without value/onChange → ✅ Provide value and onChange (e.g. from useState)
|
|
93
106
|
|
|
94
107
|
## Card
|
|
95
108
|
Content container with optional header, body, and footer sections.
|
|
@@ -111,6 +124,30 @@ Valid props:
|
|
|
111
124
|
Never do:
|
|
112
125
|
- ❌ size="lg" → ✅ Use size="md"
|
|
113
126
|
|
|
127
|
+
## RadioGroup
|
|
128
|
+
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.
|
|
129
|
+
Import: `import { RadioGroup, Radio } from "@usevyre/react"`
|
|
130
|
+
|
|
131
|
+
Valid props:
|
|
132
|
+
- size: "sm" | "md" [default: md]
|
|
133
|
+
- orientation: "vertical" | "horizontal" [default: vertical]
|
|
134
|
+
|
|
135
|
+
Never do:
|
|
136
|
+
- ❌ <Radio> used outside a <RadioGroup> → ✅ Always wrap <Radio> in <RadioGroup>
|
|
137
|
+
- ❌ RadioGroup without value/onChange (React) or v-model (Vue) → ✅ Bind value + onChange (React) or v-model (Vue); or defaultValue for uncontrolled in React
|
|
138
|
+
- ❌ Using Checkbox for mutually-exclusive choices → ✅ Use RadioGroup + Radio (or options) for one-of-many
|
|
139
|
+
|
|
140
|
+
## RichTextEditor
|
|
141
|
+
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.
|
|
142
|
+
Import: `import { RichTextEditor } from "@usevyre/react"`
|
|
143
|
+
|
|
144
|
+
Valid props:
|
|
145
|
+
|
|
146
|
+
Never do:
|
|
147
|
+
- ❌ RichTextEditor without value/onChange (React) or v-model (Vue) → ✅ Keep the HTML string in state and update it in onChange / v-model
|
|
148
|
+
- ❌ Rendering value as text or with dangerouslySetInnerHTML elsewhere without sanitising → ✅ Sanitise (e.g. DOMPurify) before re-rendering untrusted RTE output
|
|
149
|
+
- ❌ toolbar="bold" (string) → ✅ Pass an array, e.g. toolbar={["bold","italic","link"]}
|
|
150
|
+
|
|
114
151
|
## Command
|
|
115
152
|
Command palette / search dialog. Use for search-first navigation or quick actions.
|
|
116
153
|
Import: `import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandDialog } from "@usevyre/react"`
|
|
@@ -128,7 +165,7 @@ Never do:
|
|
|
128
165
|
- ❌ DropdownItem variant="primary" → ✅ Use variant="danger" for destructive items only
|
|
129
166
|
|
|
130
167
|
## Field
|
|
131
|
-
Form field wrapper
|
|
168
|
+
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.
|
|
132
169
|
Import: `import { Field, Input, Textarea } from "@usevyre/react"`
|
|
133
170
|
|
|
134
171
|
Valid props:
|
|
@@ -136,6 +173,7 @@ Valid props:
|
|
|
136
173
|
|
|
137
174
|
Never do:
|
|
138
175
|
- ❌ Applying state prop directly to Input → ✅ Wrap Input in <Field state="error"> to apply validation styling
|
|
176
|
+
- ❌ Mixing props label/hint AND FieldLabel/FieldError for the same field → ✅ Pick one: either props-based (label/hint/state) OR composable parts
|
|
139
177
|
|
|
140
178
|
## Input
|
|
141
179
|
Text input field. Wrap in Field for labels and validation. Use leftElement/rightElement for icons.
|
|
@@ -147,6 +185,7 @@ Valid props:
|
|
|
147
185
|
Never do:
|
|
148
186
|
- ❌ size="icon" → ✅ Use size="sm" | "md" | "lg"
|
|
149
187
|
- ❌ type="search" for search UI → ✅ Import Command from @usevyre/react for search palettes
|
|
188
|
+
- ❌ Vue: binding Input/Textarea value without v-model → ✅ Use v-model on <Input>/<Textarea> in Vue; in React use value + onChange
|
|
150
189
|
|
|
151
190
|
## Label
|
|
152
191
|
Accessible form label. Associate with input via htmlFor.
|
|
@@ -223,6 +262,9 @@ Import: `import { AppLayout, Sidebar, SidebarHeader, SidebarContent, SidebarSect
|
|
|
223
262
|
Valid props:
|
|
224
263
|
- variant: "default" | "floating" [default: default]
|
|
225
264
|
|
|
265
|
+
Never do:
|
|
266
|
+
- ❌ Vue: passing icon/collapsedIcon as props on SidebarTrigger → ✅ Use <template #icon> and <template #collapsed-icon>; React uses icon / collapsedIcon props
|
|
267
|
+
|
|
226
268
|
## Skeleton
|
|
227
269
|
Loading placeholder that mimics the shape of content while data loads.
|
|
228
270
|
Import: `import { Skeleton } from "@usevyre/react"`
|
|
@@ -354,6 +396,56 @@ Never do:
|
|
|
354
396
|
- ❌ TagGroup without Tag children → ✅ Place <Tag> elements as direct children
|
|
355
397
|
- ❌ Using TagGroup for tag input → ✅ Use TagsInput for an editable tag field
|
|
356
398
|
|
|
399
|
+
## Item
|
|
400
|
+
Layout primitive for list rows, settings rows, and notification rows. Denser than Card — use Item (not Card) for repeated list rows.
|
|
401
|
+
Import: `import { Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions, ItemGroup } from "@usevyre/react"`
|
|
402
|
+
|
|
403
|
+
Valid props:
|
|
404
|
+
- variant: "default" | "outlined" | "muted" | "plain" [default: default]
|
|
405
|
+
- size: "sm" | "md" | "lg" [default: md]
|
|
406
|
+
|
|
407
|
+
Never do:
|
|
408
|
+
- ❌ Card used for repeated list rows → ✅ Use <Item> (optionally inside <ItemGroup separated>) for list/settings rows
|
|
409
|
+
- ❌ Item variant="primary" → ✅ Use variant="default" | "outlined" | "muted"
|
|
410
|
+
- ❌ raw text directly inside Item → ✅ Wrap text in <ItemContent><ItemTitle>…</ItemTitle></ItemContent>
|
|
411
|
+
|
|
412
|
+
## Kanban
|
|
413
|
+
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.
|
|
414
|
+
Import: `import { Kanban } from "@usevyre/react"`
|
|
415
|
+
|
|
416
|
+
Valid props:
|
|
417
|
+
|
|
418
|
+
Never do:
|
|
419
|
+
- ❌ Kanban without onChange (or ignoring it) → ✅ Store columns in state and setColumns in onChange (v-model in Vue)
|
|
420
|
+
- ❌ Duplicate card ids across columns → ✅ Use globally-unique card ids across the entire board
|
|
421
|
+
- ❌ Mutating value in place then calling onChange → ✅ Pass the new array Kanban gives you straight to setState / v-model
|
|
422
|
+
- ❌ color="blue" (or any non-semantic value) → ✅ Use one of: "default" | "accent" | "teal" | "success" | "warning" | "danger"
|
|
423
|
+
|
|
424
|
+
## Conversation
|
|
425
|
+
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.
|
|
426
|
+
Import: `import { Conversation } from "@usevyre/react"`
|
|
427
|
+
|
|
428
|
+
Valid props:
|
|
429
|
+
|
|
430
|
+
Never do:
|
|
431
|
+
- ❌ Conversation without currentUserId → ✅ Always pass currentUserId matching one of the message authorId values
|
|
432
|
+
- ❌ Expecting Conversation to store/append messages → ✅ Append to your own state in onSend (or @send) and pass it back via value
|
|
433
|
+
- ❌ composer without onSend (React) / @send (Vue) → ✅ Provide onSend / @send to append the message to value
|
|
434
|
+
- ❌ Treating onSend as (text) only when using allowAttachments → ✅ Handle onSend(text, files) — map files to message attachments and append
|
|
435
|
+
|
|
436
|
+
## DateRangePicker
|
|
437
|
+
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.
|
|
438
|
+
Import: `import { DateRangePicker } from "@usevyre/react"`
|
|
439
|
+
|
|
440
|
+
Valid props:
|
|
441
|
+
- numberOfMonths: "1" | "2" [default: 2]
|
|
442
|
+
- weekStartsOn: "0" | "1" [default: 1]
|
|
443
|
+
|
|
444
|
+
Never do:
|
|
445
|
+
- ❌ value={[from, to]} → ✅ Use value={{ from, to }} and read range.from / range.to
|
|
446
|
+
- ❌ DateRangePicker for a single date → ✅ Use <DatePicker /> for a single date
|
|
447
|
+
- ❌ presets="true" (string) → ✅ Use the bare prop: presets (or presets={true})
|
|
448
|
+
|
|
357
449
|
## Token Rules
|
|
358
450
|
|
|
359
451
|
Use --vyre-color-semantic-* for all colors. Never use primitive tokens.
|