@devalok/shilp-sutra 0.6.0 → 0.6.2

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/llms-full.txt ADDED
@@ -0,0 +1,1753 @@
1
+ # @devalok/shilp-sutra — Full Component Reference
2
+
3
+ > Exhaustive API reference for AI coding agents.
4
+ > For a concise cheatsheet, see llms.txt instead.
5
+ > All variant values and props verified from source CVA definitions.
6
+ >
7
+ > Package: @devalok/shilp-sutra
8
+ > Version: 0.6.1
9
+
10
+ ---
11
+
12
+ ## Architecture Notes
13
+
14
+ ### The Two-Axis Variant System
15
+
16
+ Many components use TWO props where shadcn/ui uses one:
17
+ - `variant` controls SHAPE/SURFACE: solid, outline, ghost, subtle, filled, etc.
18
+ - `color` controls INTENT/SEMANTICS: default, error, success, warning, info, etc.
19
+
20
+ Components with two-axis system: Button, Badge, Alert, Chip, Toast, Banner, Progress, StatusBadge
21
+
22
+ ### Server-Safe Components (no "use client")
23
+
24
+ These can be imported directly in Next.js Server Components:
25
+ - UI: Text, Skeleton, Spinner, Stack, Container, Table (and sub-components), Code, VisuallyHidden
26
+ - Composed: ContentCard, EmptyState, PageHeader, LoadingSkeleton, PageSkeletons, PriorityIndicator, StatusBadge
27
+
28
+ Use per-component imports for server components:
29
+ import { Text } from '@devalok/shilp-sutra/ui/text'
30
+ import { PageHeader } from '@devalok/shilp-sutra/composed/page-header'
31
+
32
+ DO NOT use barrel imports in Server Components — they include "use client" components.
33
+
34
+ ### Toast Setup Pattern
35
+
36
+ 1. Mount <Toaster /> once at your root layout.
37
+ 2. Call useToast() or the imperative toast() function from any component.
38
+ 3. Valid colors: 'neutral' | 'success' | 'warning' | 'error' | 'info'
39
+
40
+ ### Form Accessibility Pattern
41
+
42
+ Use <FormField> + useFormField() hook:
43
+ <FormField state="error">
44
+ <Label htmlFor="email">Email</Label>
45
+ <Input id="email" state="error" />
46
+ <FormHelperText>Error message here.</FormHelperText>
47
+ </FormField>
48
+
49
+ useFormField() returns { state, helperTextId, required } from context.
50
+ Wire manually: <Input aria-describedby={helperTextId} aria-invalid={state === 'error'} />
51
+
52
+ Note: getFormFieldA11y() was removed in favor of useFormField() hook.
53
+
54
+ ---
55
+
56
+ # UI COMPONENTS
57
+ # Alphabetical within this section.
58
+ # Import from: @devalok/shilp-sutra/ui/<kebab-name>
59
+
60
+ ---
61
+
62
+ ## Accordion
63
+ - Import: @devalok/shilp-sutra/ui/accordion
64
+ - Server-safe: No
65
+ - Props (Accordion root):
66
+ type: "single" | "multiple" (REQUIRED)
67
+ defaultValue: string (single) | string[] (multiple)
68
+ value: string | string[] (controlled)
69
+ onValueChange: (value) => void
70
+ collapsible: boolean (only valid when type="single")
71
+ - Compound:
72
+ Accordion (root)
73
+ AccordionItem (value: string, REQUIRED)
74
+ AccordionTrigger (clickable header, chevron auto-renders)
75
+ AccordionContent (collapsible body)
76
+ - Defaults: none (type is required)
77
+ - Example:
78
+ <Accordion type="single" defaultValue="item-1" collapsible>
79
+ <AccordionItem value="item-1">
80
+ <AccordionTrigger>Question?</AccordionTrigger>
81
+ <AccordionContent>Answer.</AccordionContent>
82
+ </AccordionItem>
83
+ </Accordion>
84
+ - Gotchas:
85
+ - type is REQUIRED — omitting it causes runtime error
86
+ - collapsible only works with type="single"
87
+
88
+ ## Alert
89
+ - Import: @devalok/shilp-sutra/ui/alert
90
+ - Server-safe: No
91
+ - Props:
92
+ variant: "subtle" | "filled" | "outline"
93
+ color: "info" | "success" | "warning" | "error" | "neutral"
94
+ title: string (optional)
95
+ onDismiss: () => void (optional, shows X button when provided)
96
+ children: ReactNode (body text)
97
+ - Defaults: variant="subtle", color="info"
98
+ - Example:
99
+ <Alert color="error" title="Save failed" onDismiss={() => setError(null)}>
100
+ Your changes could not be saved.
101
+ </Alert>
102
+ - Gotchas:
103
+ - NOT a compound component — use title prop, NOT <AlertTitle>
104
+ - DO NOT use variant="destructive" — use color="error"
105
+ - Renders role="alert" automatically
106
+ - Icon is auto-selected by color (info=circle, success=check, warning=triangle, error=alert)
107
+
108
+ ## AlertDialog
109
+ - Import: @devalok/shilp-sutra/ui/alert-dialog
110
+ - Server-safe: No
111
+ - Props: Standard Radix AlertDialog props (open, onOpenChange, defaultOpen)
112
+ - Compound:
113
+ AlertDialog (root)
114
+ AlertDialogTrigger
115
+ AlertDialogContent
116
+ AlertDialogHeader
117
+ AlertDialogTitle
118
+ AlertDialogDescription
119
+ AlertDialogFooter
120
+ AlertDialogCancel
121
+ AlertDialogAction
122
+ - Example:
123
+ <AlertDialog>
124
+ <AlertDialogTrigger asChild>
125
+ <Button color="error">Delete</Button>
126
+ </AlertDialogTrigger>
127
+ <AlertDialogContent>
128
+ <AlertDialogHeader>
129
+ <AlertDialogTitle>Are you sure?</AlertDialogTitle>
130
+ <AlertDialogDescription>This cannot be undone.</AlertDialogDescription>
131
+ </AlertDialogHeader>
132
+ <AlertDialogFooter>
133
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
134
+ <AlertDialogAction>Delete</AlertDialogAction>
135
+ </AlertDialogFooter>
136
+ </AlertDialogContent>
137
+ </AlertDialog>
138
+ - Gotchas: AlertDialogAction does NOT have color="error" styling — add it yourself via className or wrap a Button
139
+
140
+ ## AspectRatio
141
+ - Import: @devalok/shilp-sutra/ui/aspect-ratio
142
+ - Server-safe: No
143
+ - Props:
144
+ ratio: number (e.g. 16/9, 4/3, 1)
145
+ - Example:
146
+ <AspectRatio ratio={16 / 9}>
147
+ <img src="/photo.jpg" alt="Photo" className="object-cover w-full h-full" />
148
+ </AspectRatio>
149
+
150
+ ## Autocomplete
151
+ - Import: @devalok/shilp-sutra/ui/autocomplete
152
+ - Server-safe: No
153
+ - Props:
154
+ options: AutocompleteOption[] (REQUIRED) — { value: string, label: string }
155
+ value: AutocompleteOption | null
156
+ onValueChange: (option: AutocompleteOption) => void
157
+ placeholder: string
158
+ emptyText: string (default: "No options")
159
+ disabled: boolean
160
+ className: string
161
+ id: string
162
+ - Example:
163
+ <Autocomplete
164
+ options={[{ value: 'mumbai', label: 'Mumbai' }]}
165
+ value={selectedCity}
166
+ onValueChange={setSelectedCity}
167
+ placeholder="Search cities..."
168
+ />
169
+ - Gotchas:
170
+ - Allows free-text input (no forced selection) — use Combobox for forced selection
171
+ - value is an object { value, label }, NOT just a string
172
+
173
+ ## Avatar
174
+ - Import: @devalok/shilp-sutra/ui/avatar
175
+ - Server-safe: No
176
+ - Props (Avatar root):
177
+ size: "xs" | "sm" | "md" | "lg" | "xl"
178
+ shape: "circle" | "square" | "rounded"
179
+ status: "online" | "offline" | "busy" | "away"
180
+ - Defaults: size="md", shape="circle"
181
+ - Compound:
182
+ Avatar (root with size/shape/status)
183
+ AvatarImage (src, alt)
184
+ AvatarFallback (children = initials text)
185
+ - Example:
186
+ <Avatar size="lg" status="online">
187
+ <AvatarImage src={user.photoUrl} alt={user.name} />
188
+ <AvatarFallback>JD</AvatarFallback>
189
+ </Avatar>
190
+ - Gotchas:
191
+ - Status dot renders with role="img" and aria-label (accessible, not decorative)
192
+ - Dot size scales automatically with avatar size
193
+
194
+ ## Badge
195
+ - Import: @devalok/shilp-sutra/ui/badge
196
+ - Server-safe: No
197
+ - Props:
198
+ variant: "subtle" | "solid" | "outline" | "secondary" (alias->subtle) | "destructive" (alias->solid+error)
199
+ color: "default" | "info" | "success" | "error" | "warning" | "brand" | "accent" | "teal" | "amber" | "slate" | "indigo" | "cyan" | "orange" | "emerald"
200
+ size: "sm" | "md" | "lg"
201
+ dot: boolean (shows leading dot indicator)
202
+ onDismiss: () => void (shows X button when provided)
203
+ children: ReactNode
204
+ - Defaults: variant="subtle", color="default", size="md"
205
+ - Example:
206
+ <Badge variant="solid" color="success">Active</Badge>
207
+ <Badge color="teal" onDismiss={() => removeFilter('teal')}>Teal team</Badge>
208
+ - Gotchas:
209
+ - DO NOT use variant="destructive" — use variant="solid" color="error"
210
+ - Badge is display-only; for interactive tags use Chip
211
+
212
+ ## Banner
213
+ - Import: @devalok/shilp-sutra/ui/banner
214
+ - Server-safe: No
215
+ - Props:
216
+ color: "info" | "success" | "warning" | "error" | "neutral"
217
+ action: ReactNode (optional action slot, typically a ghost Button)
218
+ onDismiss: () => void (optional, shows X button)
219
+ children: ReactNode (message text)
220
+ - Defaults: color="info"
221
+ - Example:
222
+ <Banner color="warning" onDismiss={() => dismiss()}>
223
+ Scheduled maintenance on Sunday.
224
+ </Banner>
225
+ - Gotchas:
226
+ - Banner is full-width (spans container). Alert is inline.
227
+ - Renders role="alert" automatically
228
+
229
+ ## Breadcrumb
230
+ - Import: @devalok/shilp-sutra/ui/breadcrumb
231
+ - Server-safe: No
232
+ - Compound:
233
+ Breadcrumb (root nav)
234
+ BreadcrumbList (ol)
235
+ BreadcrumbItem (li)
236
+ BreadcrumbLink (for clickable items) | BreadcrumbPage (for current page)
237
+ BreadcrumbSeparator (auto-rendered or custom)
238
+ BreadcrumbEllipsis (for collapsed items)
239
+ - Example:
240
+ <Breadcrumb>
241
+ <BreadcrumbList>
242
+ <BreadcrumbItem><BreadcrumbLink href="/">Home</BreadcrumbLink></BreadcrumbItem>
243
+ <BreadcrumbSeparator />
244
+ <BreadcrumbItem><BreadcrumbPage>Settings</BreadcrumbPage></BreadcrumbItem>
245
+ </BreadcrumbList>
246
+ </Breadcrumb>
247
+
248
+ ## Button
249
+ - Import: @devalok/shilp-sutra/ui/button
250
+ - Server-safe: No
251
+ - Props:
252
+ variant: "solid" | "default" (alias->solid) | "outline" | "ghost" | "link" | "destructive" (alias->solid+error)
253
+ color: "default" | "error"
254
+ size: "sm" | "md" | "lg" | "icon" | "icon-sm" | "icon-md" | "icon-lg"
255
+ startIcon: ReactNode
256
+ endIcon: ReactNode
257
+ loading: boolean (disables button, shows spinner)
258
+ loadingPosition: "start" | "end" | "center" (default: "start")
259
+ fullWidth: boolean
260
+ asChild: boolean
261
+ - Defaults: variant="solid", color="default", size="md"
262
+ - Example:
263
+ <Button variant="solid" color="error" startIcon={<IconTrash />} loading={isDeleting}>
264
+ Delete project
265
+ </Button>
266
+ - Gotchas:
267
+ - DO NOT use variant="destructive" — use variant="solid" color="error"
268
+ - DO NOT use variant="secondary" — use variant="outline" or variant="ghost"
269
+ - DO NOT use size="default" — use size="md"
270
+ - DO NOT use color="danger" — use color="error"
271
+ - Inherits variant/color/size from ButtonGroup context if present
272
+
273
+ ## ButtonGroup
274
+ - Import: @devalok/shilp-sutra/ui/button-group
275
+ - Server-safe: No
276
+ - Props:
277
+ variant: ButtonProps['variant'] (propagated to children)
278
+ color: ButtonProps['color'] (propagated to children)
279
+ size: ButtonProps['size'] (propagated to children)
280
+ orientation: "horizontal" | "vertical" (default: "horizontal")
281
+ - Example:
282
+ <ButtonGroup variant="outline" size="sm">
283
+ <Button>Bold</Button>
284
+ <Button>Italic</Button>
285
+ </ButtonGroup>
286
+ - Gotchas: Children can override variant/size individually
287
+
288
+ ## Card
289
+ - Import: @devalok/shilp-sutra/ui/card
290
+ - Server-safe: No
291
+ - Props (Card root):
292
+ variant: "default" | "elevated" | "outline" | "flat"
293
+ interactive: boolean (enables hover shadow lift + pointer cursor)
294
+ - Defaults: variant="default"
295
+ - Compound:
296
+ Card (root)
297
+ CardHeader
298
+ CardTitle
299
+ CardDescription
300
+ CardContent
301
+ CardFooter
302
+ - Example:
303
+ <Card variant="elevated" interactive onClick={() => navigate(url)}>
304
+ <CardHeader>
305
+ <CardTitle>Project</CardTitle>
306
+ <CardDescription>Last updated 2h ago</CardDescription>
307
+ </CardHeader>
308
+ <CardContent><p>Content here</p></CardContent>
309
+ </Card>
310
+
311
+ ## Checkbox
312
+ - Import: @devalok/shilp-sutra/ui/checkbox
313
+ - Server-safe: No
314
+ - Props:
315
+ checked: boolean | "indeterminate"
316
+ onCheckedChange: (checked: boolean | "indeterminate") => void
317
+ error: boolean (shows red border)
318
+ indeterminate: boolean (overrides checked, shows dash icon)
319
+ disabled: boolean
320
+ - Example:
321
+ <Checkbox checked={agreed} onCheckedChange={(v) => setAgreed(v === true)} />
322
+ - Gotchas: indeterminate overrides checked visually
323
+
324
+ ## Chip
325
+ - Import: @devalok/shilp-sutra/ui/chip
326
+ - Server-safe: No
327
+ - Props:
328
+ label: string (REQUIRED — use this, NOT children)
329
+ variant: "subtle" | "outline"
330
+ color: "default" | "primary" | "success" | "error" | "warning" | "info" | "teal" | "amber" | "slate" | "indigo" | "cyan" | "orange" | "emerald"
331
+ size: "sm" | "md" | "lg"
332
+ icon: ReactNode
333
+ onClick: MouseEventHandler (renders as <button> when provided)
334
+ onDismiss: () => void (shows X button)
335
+ disabled: boolean
336
+ - Defaults: variant="subtle", size="md", color="default"
337
+ - Example:
338
+ <Chip label="In Progress" color="warning" />
339
+ <Chip label="React" color="info" onDismiss={() => removeFilter('react')} />
340
+ - Gotchas:
341
+ - MUST use label prop — children are NOT rendered
342
+ - <Chip>text</Chip> is WRONG — use <Chip label="text" />
343
+
344
+ ## Code
345
+ - Import: @devalok/shilp-sutra/ui/code
346
+ - Server-safe: Yes
347
+ - Props:
348
+ variant: "inline" | "block"
349
+ children: ReactNode
350
+ - Defaults: variant="inline"
351
+ - Example:
352
+ <Code>onClick</Code>
353
+ <Code variant="block">{`const x = 1;\nconsole.log(x);`}</Code>
354
+ - Gotchas: "block" renders as <pre><code>, "inline" renders as <code>
355
+
356
+ ## Collapsible
357
+ - Import: @devalok/shilp-sutra/ui/collapsible
358
+ - Server-safe: No
359
+ - Props: Standard Radix Collapsible props (open, onOpenChange, defaultOpen)
360
+ - Compound:
361
+ Collapsible (root)
362
+ CollapsibleTrigger
363
+ CollapsibleContent
364
+ - Example:
365
+ <Collapsible>
366
+ <CollapsibleTrigger>Toggle</CollapsibleTrigger>
367
+ <CollapsibleContent>Hidden content</CollapsibleContent>
368
+ </Collapsible>
369
+
370
+ ## Combobox
371
+ - Import: @devalok/shilp-sutra/ui/combobox
372
+ - Server-safe: No
373
+ - Props:
374
+ options: ComboboxOption[] (REQUIRED) — { value: string, label: string, description?: string, icon?: ReactNode, disabled?: boolean }
375
+ value: string | string[]
376
+ onValueChange: (value: string | string[]) => void (REQUIRED)
377
+ placeholder: string (default: "Select...")
378
+ searchPlaceholder: string (default: "Search...")
379
+ emptyMessage: string (default: "No results found")
380
+ multiple: boolean (default: false)
381
+ disabled: boolean
382
+ triggerClassName: string
383
+ maxVisible: number (default: 6, max dropdown items before scroll)
384
+ renderOption: (option, selected) => ReactNode
385
+ - Example:
386
+ <Combobox
387
+ multiple
388
+ options={tagOptions}
389
+ value={selectedTags}
390
+ onValueChange={(v) => setSelectedTags(v as string[])}
391
+ placeholder="Select tags..."
392
+ />
393
+ - Gotchas:
394
+ - Enforces selection from list (unlike Autocomplete which allows free text)
395
+ - In multi mode, selected items appear as pills with "+N more" overflow
396
+
397
+ ## Container
398
+ - Import: @devalok/shilp-sutra/ui/container
399
+ - Server-safe: Yes
400
+ - Props:
401
+ maxWidth: "default" | "body" | "full"
402
+ as: ElementType (default: "div")
403
+ - Defaults: maxWidth="default"
404
+ - Example:
405
+ <Container maxWidth="body">
406
+ <p>Centered content</p>
407
+ </Container>
408
+
409
+ ## ContextMenu
410
+ - Import: @devalok/shilp-sutra/ui/context-menu
411
+ - Server-safe: No
412
+ - Compound:
413
+ ContextMenu (root)
414
+ ContextMenuTrigger (right-click target)
415
+ ContextMenuContent
416
+ ContextMenuItem
417
+ ContextMenuCheckboxItem
418
+ ContextMenuRadioGroup > ContextMenuRadioItem
419
+ ContextMenuLabel
420
+ ContextMenuSeparator
421
+ ContextMenuSub > ContextMenuSubTrigger, ContextMenuSubContent
422
+ - Example:
423
+ <ContextMenu>
424
+ <ContextMenuTrigger>Right-click me</ContextMenuTrigger>
425
+ <ContextMenuContent>
426
+ <ContextMenuItem>Edit</ContextMenuItem>
427
+ <ContextMenuItem>Delete</ContextMenuItem>
428
+ </ContextMenuContent>
429
+ </ContextMenu>
430
+
431
+ ## Dialog
432
+ - Import: @devalok/shilp-sutra/ui/dialog
433
+ - Server-safe: No
434
+ - Compound:
435
+ Dialog (root)
436
+ DialogTrigger
437
+ DialogContent
438
+ DialogHeader
439
+ DialogTitle
440
+ DialogDescription
441
+ [body content]
442
+ DialogFooter
443
+ DialogClose
444
+ - Example:
445
+ <Dialog>
446
+ <DialogTrigger asChild><Button>Open</Button></DialogTrigger>
447
+ <DialogContent>
448
+ <DialogHeader>
449
+ <DialogTitle>Edit Profile</DialogTitle>
450
+ <DialogDescription>Make changes to your profile.</DialogDescription>
451
+ </DialogHeader>
452
+ <div>Form fields here</div>
453
+ <DialogFooter>
454
+ <Button>Save</Button>
455
+ </DialogFooter>
456
+ </DialogContent>
457
+ </Dialog>
458
+
459
+ ## DropdownMenu
460
+ - Import: @devalok/shilp-sutra/ui/dropdown-menu
461
+ - Server-safe: No
462
+ - Compound:
463
+ DropdownMenu (root)
464
+ DropdownMenuTrigger
465
+ DropdownMenuContent
466
+ DropdownMenuLabel
467
+ DropdownMenuSeparator
468
+ DropdownMenuItem (+ DropdownMenuShortcut for keyboard hints)
469
+ DropdownMenuCheckboxItem
470
+ DropdownMenuRadioGroup > DropdownMenuRadioItem
471
+ DropdownMenuGroup
472
+ DropdownMenuSub > DropdownMenuSubTrigger, DropdownMenuSubContent
473
+ - Example:
474
+ <DropdownMenu>
475
+ <DropdownMenuTrigger asChild><Button variant="ghost">Menu</Button></DropdownMenuTrigger>
476
+ <DropdownMenuContent>
477
+ <DropdownMenuItem>Profile</DropdownMenuItem>
478
+ <DropdownMenuSeparator />
479
+ <DropdownMenuItem>Logout</DropdownMenuItem>
480
+ </DropdownMenuContent>
481
+ </DropdownMenu>
482
+
483
+ ## FileUpload
484
+ - Import: @devalok/shilp-sutra/ui/file-upload
485
+ - Server-safe: No
486
+ - Props:
487
+ onFiles: (files: File[]) => void (REQUIRED)
488
+ accept: string (MIME or extension, e.g. "image/*", ".pdf,.doc")
489
+ maxSize: number (bytes, default: 10MB)
490
+ multiple: boolean
491
+ uploading: boolean
492
+ progress: number (0-100)
493
+ error: string
494
+ compact: boolean (inline button mode vs drop zone)
495
+ disabled: boolean
496
+ label: string
497
+ sublabel: string
498
+ - Example:
499
+ <FileUpload
500
+ accept="image/*"
501
+ maxSize={2 * 1024 * 1024}
502
+ onFiles={(files) => uploadAvatar(files[0])}
503
+ label="Upload profile photo"
504
+ sublabel="PNG, JPG up to 2MB"
505
+ />
506
+ - Gotchas:
507
+ - compact=true renders a small inline button; false (default) renders a large drag-and-drop zone
508
+ - Client-side validation: invalid files are rejected before onFiles is called
509
+
510
+ ## FormField / FormHelperText / useFormField
511
+ - Import: @devalok/shilp-sutra/ui/form
512
+ - Server-safe: No
513
+ - Props (FormField):
514
+ state: "helper" | "error" | "warning" | "success" (default: "helper")
515
+ helperTextId: string (auto-generated if omitted)
516
+ required: boolean
517
+ - Props (FormHelperText):
518
+ state: "helper" | "error" | "warning" | "success" (inherits from FormField context)
519
+ - Hook: useFormField() => { state, helperTextId, required }
520
+ - Example:
521
+ <FormField state="error">
522
+ <Label htmlFor="email">Email</Label>
523
+ <Input id="email" state="error" />
524
+ <FormHelperText>Please enter a valid email.</FormHelperText>
525
+ </FormField>
526
+ - Gotchas:
527
+ - getFormFieldA11y() was REMOVED — use useFormField() hook instead
528
+ - FormHelperText auto-reads state and id from FormField context
529
+ - FormHelperText renders role="alert" when state="error"
530
+
531
+ ## HoverCard
532
+ - Import: @devalok/shilp-sutra/ui/hover-card
533
+ - Server-safe: No
534
+ - Compound:
535
+ HoverCard (root)
536
+ HoverCardTrigger
537
+ HoverCardContent
538
+ - Example:
539
+ <HoverCard>
540
+ <HoverCardTrigger asChild><span>Hover me</span></HoverCardTrigger>
541
+ <HoverCardContent>Preview content</HoverCardContent>
542
+ </HoverCard>
543
+
544
+ ## IconButton
545
+ - Import: @devalok/shilp-sutra/ui/icon-button
546
+ - Server-safe: No
547
+ - Props:
548
+ icon: ReactNode (REQUIRED)
549
+ aria-label: string (REQUIRED — WCAG AA mandatory)
550
+ shape: "square" | "circle" (default: "square")
551
+ size: "sm" | "md" | "lg" (default: "md")
552
+ variant: same as Button (solid, outline, ghost, link)
553
+ color: same as Button (default, error)
554
+ loading: boolean
555
+ disabled: boolean
556
+ - Example:
557
+ <IconButton icon={<IconEdit />} variant="ghost" aria-label="Edit item" />
558
+ <IconButton icon={<IconX />} shape="circle" variant="ghost" size="sm" aria-label="Close" />
559
+ - Gotchas:
560
+ - aria-label is enforced by TypeScript — you MUST provide it
561
+ - Prefer IconButton over Button with size="icon-*" for icon-only buttons
562
+
563
+ ## Input
564
+ - Import: @devalok/shilp-sutra/ui/input
565
+ - Server-safe: No
566
+ - Props:
567
+ size: "sm" | "md" | "lg"
568
+ state: "default" | "error" | "warning" | "success"
569
+ startIcon: ReactNode
570
+ endIcon: ReactNode
571
+ (plus all standard HTML input attributes except native "size")
572
+ - Defaults: size="md"
573
+ - Example:
574
+ <Input type="email" placeholder="you@example.com" state="error" startIcon={<IconMail />} />
575
+ - Gotchas:
576
+ - HTML native "size" attribute is excluded — use CSS width instead
577
+ - state="error" sets aria-invalid automatically
578
+
579
+ ## InputOTP
580
+ - Import: @devalok/shilp-sutra/ui/input-otp
581
+ - Server-safe: No
582
+ - Props (InputOTP): Standard input-otp props (maxLength, pattern, etc.)
583
+ - Compound:
584
+ InputOTP (root)
585
+ InputOTPGroup
586
+ InputOTPSlot (index: number, REQUIRED)
587
+ InputOTPSeparator
588
+ - Example:
589
+ <InputOTP maxLength={6}>
590
+ <InputOTPGroup>
591
+ <InputOTPSlot index={0} />
592
+ <InputOTPSlot index={1} />
593
+ <InputOTPSlot index={2} />
594
+ </InputOTPGroup>
595
+ <InputOTPSeparator />
596
+ <InputOTPGroup>
597
+ <InputOTPSlot index={3} />
598
+ <InputOTPSlot index={4} />
599
+ <InputOTPSlot index={5} />
600
+ </InputOTPGroup>
601
+ </InputOTP>
602
+
603
+ ## Label
604
+ - Import: @devalok/shilp-sutra/ui/label
605
+ - Server-safe: No
606
+ - Props:
607
+ required: boolean (shows red asterisk)
608
+ htmlFor: string
609
+ (plus standard Radix Label props)
610
+ - Example:
611
+ <Label htmlFor="email" required>Email Address</Label>
612
+
613
+ ## Link
614
+ - Import: @devalok/shilp-sutra/ui/link
615
+ - Server-safe: No
616
+ - Props:
617
+ inline: boolean (default: true — "inline" or "block" display)
618
+ asChild: boolean (merge with child element, e.g. Next.js Link)
619
+ (plus standard anchor attributes)
620
+ - Example:
621
+ <Link href="/docs">Documentation</Link>
622
+ <Link asChild><NextLink href="/about">About</NextLink></Link>
623
+
624
+ ## Menubar
625
+ - Import: @devalok/shilp-sutra/ui/menubar
626
+ - Server-safe: No
627
+ - Compound:
628
+ Menubar (root)
629
+ MenubarMenu
630
+ MenubarTrigger
631
+ MenubarContent
632
+ MenubarItem (+ MenubarShortcut)
633
+ MenubarCheckboxItem
634
+ MenubarRadioGroup > MenubarRadioItem
635
+ MenubarLabel
636
+ MenubarSeparator
637
+ MenubarSub > MenubarSubTrigger, MenubarSubContent
638
+ - Example:
639
+ <Menubar>
640
+ <MenubarMenu>
641
+ <MenubarTrigger>File</MenubarTrigger>
642
+ <MenubarContent>
643
+ <MenubarItem>New<MenubarShortcut>Ctrl+N</MenubarShortcut></MenubarItem>
644
+ <MenubarSeparator />
645
+ <MenubarItem>Exit</MenubarItem>
646
+ </MenubarContent>
647
+ </MenubarMenu>
648
+ </Menubar>
649
+
650
+ ## NavigationMenu
651
+ - Import: @devalok/shilp-sutra/ui/navigation-menu
652
+ - Server-safe: No
653
+ - Compound:
654
+ NavigationMenu (root)
655
+ NavigationMenuList
656
+ NavigationMenuItem
657
+ NavigationMenuTrigger (for items with content panels)
658
+ NavigationMenuContent (dropdown panel)
659
+ NavigationMenuLink (for direct links)
660
+ NavigationMenuIndicator
661
+ NavigationMenuViewport
662
+ - Example:
663
+ <NavigationMenu>
664
+ <NavigationMenuList>
665
+ <NavigationMenuItem>
666
+ <NavigationMenuTrigger>Products</NavigationMenuTrigger>
667
+ <NavigationMenuContent>
668
+ <ul>...</ul>
669
+ </NavigationMenuContent>
670
+ </NavigationMenuItem>
671
+ <NavigationMenuItem>
672
+ <NavigationMenuLink href="/about">About</NavigationMenuLink>
673
+ </NavigationMenuItem>
674
+ </NavigationMenuList>
675
+ </NavigationMenu>
676
+
677
+ ## NumberInput
678
+ - Import: @devalok/shilp-sutra/ui/number-input
679
+ - Server-safe: No
680
+ - Props:
681
+ value: number (default: 0)
682
+ onValueChange: (value: number) => void
683
+ min: number
684
+ max: number
685
+ step: number (default: 1)
686
+ disabled: boolean
687
+ - Example:
688
+ <NumberInput value={qty} onValueChange={setQty} min={1} max={99} />
689
+ - Gotchas: Controlled only — buttons won't work without onValueChange
690
+
691
+ ## Pagination
692
+ - Import: @devalok/shilp-sutra/ui/pagination
693
+ - Server-safe: No
694
+ - Compound:
695
+ PaginationRoot (nav)
696
+ PaginationContent (ul)
697
+ PaginationItem (li)
698
+ PaginationLink (isActive: boolean, asChild: boolean)
699
+ PaginationPrevious
700
+ PaginationNext
701
+ PaginationEllipsis
702
+ PaginationNav (convenience wrapper)
703
+ - Utility: generatePagination(current: number, total: number, siblingCount: number) => (number | 'ellipsis')[]
704
+ - Example:
705
+ <PaginationRoot>
706
+ <PaginationContent>
707
+ <PaginationItem><PaginationPrevious onClick={() => setPage(p - 1)} /></PaginationItem>
708
+ {generatePagination(page, totalPages, 1).map((item, i) =>
709
+ item === 'ellipsis'
710
+ ? <PaginationItem key={i}><PaginationEllipsis /></PaginationItem>
711
+ : <PaginationItem key={i}><PaginationLink isActive={item === page} onClick={() => setPage(item)}>{item}</PaginationLink></PaginationItem>
712
+ )}
713
+ <PaginationItem><PaginationNext onClick={() => setPage(p + 1)} /></PaginationItem>
714
+ </PaginationContent>
715
+ </PaginationRoot>
716
+ - Gotchas: Root component is PaginationRoot (NOT Pagination)
717
+
718
+ ## Popover
719
+ - Import: @devalok/shilp-sutra/ui/popover
720
+ - Server-safe: No
721
+ - Compound:
722
+ Popover (root)
723
+ PopoverTrigger
724
+ PopoverContent
725
+ PopoverAnchor (optional anchor point)
726
+ - Example:
727
+ <Popover>
728
+ <PopoverTrigger asChild><Button>Open</Button></PopoverTrigger>
729
+ <PopoverContent>Content here</PopoverContent>
730
+ </Popover>
731
+
732
+ ## Progress
733
+ - Import: @devalok/shilp-sutra/ui/progress
734
+ - Server-safe: No
735
+ - Props:
736
+ value: number (0-100) — omit for indeterminate
737
+ size: "sm" | "md" | "lg" (track height)
738
+ color: "default" | "success" | "warning" | "error" (indicator color)
739
+ showLabel: boolean (shows percentage text)
740
+ indicatorClassName: string
741
+ - Defaults: size="md", color="default"
742
+ - Example:
743
+ <Progress value={75} color="success" showLabel />
744
+ <Progress size="sm" /> {/* indeterminate */}
745
+ - Gotchas: Omit value (or pass undefined) for indeterminate animation
746
+
747
+ ## RadioGroup
748
+ - Import: @devalok/shilp-sutra/ui/radio
749
+ - Server-safe: No
750
+ - Compound:
751
+ RadioGroup (root, value, onValueChange, defaultValue)
752
+ RadioGroupItem (value: string, REQUIRED)
753
+ - Example:
754
+ <RadioGroup defaultValue="option-1" onValueChange={setValue}>
755
+ <div className="flex items-center gap-2">
756
+ <RadioGroupItem value="option-1" id="r1" />
757
+ <Label htmlFor="r1">Option 1</Label>
758
+ </div>
759
+ <div className="flex items-center gap-2">
760
+ <RadioGroupItem value="option-2" id="r2" />
761
+ <Label htmlFor="r2">Option 2</Label>
762
+ </div>
763
+ </RadioGroup>
764
+
765
+ ## ScrollArea
766
+ - Import: @devalok/shilp-sutra/ui/scroll-area
767
+ - Server-safe: No
768
+ - Props: Standard Radix ScrollArea props
769
+ - Note: Provides custom-styled scrollbars
770
+
771
+ ## SearchInput
772
+ - Import: @devalok/shilp-sutra/ui/search-input
773
+ - Server-safe: No
774
+ - Props:
775
+ size: "sm" | "md" | "lg" (default: "md")
776
+ loading: boolean (shows spinner instead of clear button)
777
+ onClear: () => void (shows X button when value is non-empty)
778
+ value: string
779
+ onChange: ChangeEventHandler
780
+ (plus standard input attributes except native "size")
781
+ - Example:
782
+ <SearchInput
783
+ value={query}
784
+ onChange={(e) => setQuery(e.target.value)}
785
+ onClear={() => setQuery('')}
786
+ placeholder="Search tasks..."
787
+ loading={isSearching}
788
+ />
789
+
790
+ ## SegmentedControl
791
+ - Import: @devalok/shilp-sutra/ui/segmented-control
792
+ - Server-safe: No
793
+ - Props (SegmentedControl root):
794
+ size: "sm" | "md" | "lg" (REQUIRED) — also accepts legacy "small" | "medium" | "big"
795
+ variant: "filled" | "tonal" (REQUIRED)
796
+ options: SegmentedControlOption[] (REQUIRED) — { id: string, text: string, icon?: ComponentType }
797
+ selectedId: string (REQUIRED)
798
+ onSelect: (id: string) => void (REQUIRED)
799
+ disabled: boolean
800
+ - Defaults: None — size and variant are required
801
+ - Example:
802
+ <SegmentedControl
803
+ size="md"
804
+ variant="tonal"
805
+ options={[
806
+ { id: 'list', text: 'List' },
807
+ { id: 'grid', text: 'Grid' },
808
+ ]}
809
+ selectedId={viewMode}
810
+ onSelect={setViewMode}
811
+ />
812
+ - Gotchas:
813
+ - Controlled only — selectedId + onSelect are required
814
+ - Uses data-driven API (options prop), not compound children
815
+
816
+ ## Select
817
+ - Import: @devalok/shilp-sutra/ui/select
818
+ - Server-safe: No
819
+ - Props (SelectTrigger):
820
+ size: "sm" | "md" | "lg"
821
+ - Defaults: size="md" (on SelectTrigger)
822
+ - Compound:
823
+ Select (root — value, onValueChange, defaultValue)
824
+ SelectTrigger (size goes HERE, not on Select root)
825
+ SelectValue (placeholder)
826
+ SelectContent
827
+ SelectGroup (optional grouping)
828
+ SelectLabel (group header)
829
+ SelectItem (value: string, REQUIRED)
830
+ SelectSeparator
831
+ - Example:
832
+ <Select onValueChange={setValue}>
833
+ <SelectTrigger size="lg">
834
+ <SelectValue placeholder="Choose..." />
835
+ </SelectTrigger>
836
+ <SelectContent>
837
+ <SelectItem value="a">Option A</SelectItem>
838
+ <SelectItem value="b">Option B</SelectItem>
839
+ </SelectContent>
840
+ </Select>
841
+ - Gotchas:
842
+ - Size goes on SelectTrigger, NOT on Select root
843
+ - <Select size="lg"> is silently ignored (no TypeScript error)
844
+
845
+ ## Separator
846
+ - Import: @devalok/shilp-sutra/ui/separator
847
+ - Server-safe: No
848
+ - Props:
849
+ orientation: "horizontal" | "vertical" (default: "horizontal")
850
+ decorative: boolean (default: true)
851
+ - Example:
852
+ <Separator />
853
+ <Separator orientation="vertical" className="h-6" />
854
+
855
+ ## Sheet
856
+ - Import: @devalok/shilp-sutra/ui/sheet
857
+ - Server-safe: No
858
+ - Props (SheetContent):
859
+ side: "top" | "bottom" | "left" | "right"
860
+ - Compound:
861
+ Sheet (root)
862
+ SheetTrigger
863
+ SheetContent (side prop)
864
+ SheetHeader
865
+ SheetTitle
866
+ SheetDescription
867
+ [body content]
868
+ SheetFooter
869
+ SheetClose
870
+ - Example:
871
+ <Sheet>
872
+ <SheetTrigger asChild><Button>Open</Button></SheetTrigger>
873
+ <SheetContent side="right">
874
+ <SheetHeader>
875
+ <SheetTitle>Settings</SheetTitle>
876
+ </SheetHeader>
877
+ <div>Content</div>
878
+ </SheetContent>
879
+ </Sheet>
880
+
881
+ ## Sidebar
882
+ - Import: @devalok/shilp-sutra/ui/sidebar
883
+ - Server-safe: No
884
+ - Compound (full tree):
885
+ SidebarProvider (context provider — must wrap everything)
886
+ Sidebar (root panel)
887
+ SidebarHeader
888
+ SidebarContent
889
+ SidebarGroup
890
+ SidebarGroupLabel
891
+ SidebarGroupAction
892
+ SidebarGroupContent
893
+ SidebarMenu
894
+ SidebarMenuItem
895
+ SidebarMenuButton (tooltip, isActive)
896
+ SidebarMenuAction
897
+ SidebarMenuBadge
898
+ SidebarMenuSub
899
+ SidebarMenuSubItem
900
+ SidebarMenuSubButton (isActive)
901
+ SidebarFooter
902
+ SidebarSeparator
903
+ SidebarRail
904
+ SidebarInset (main content area)
905
+ SidebarTrigger (hamburger button)
906
+ SidebarInput (search input in sidebar)
907
+ SidebarMenuSkeleton (loading placeholder)
908
+ - Hook: useSidebar() => { state, open, setOpen, openMobile, setOpenMobile, isMobile, toggleSidebar }
909
+ - Example:
910
+ <SidebarProvider>
911
+ <Sidebar>
912
+ <SidebarHeader>Logo</SidebarHeader>
913
+ <SidebarContent>
914
+ <SidebarGroup>
915
+ <SidebarGroupLabel>Navigation</SidebarGroupLabel>
916
+ <SidebarGroupContent>
917
+ <SidebarMenu>
918
+ <SidebarMenuItem>
919
+ <SidebarMenuButton isActive>
920
+ <IconHome /> Dashboard
921
+ </SidebarMenuButton>
922
+ </SidebarMenuItem>
923
+ </SidebarMenu>
924
+ </SidebarGroupContent>
925
+ </SidebarGroup>
926
+ </SidebarContent>
927
+ </Sidebar>
928
+ <SidebarInset>
929
+ <SidebarTrigger />
930
+ <main>Page content</main>
931
+ </SidebarInset>
932
+ </SidebarProvider>
933
+ - Gotchas:
934
+ - SidebarProvider MUST wrap both Sidebar and SidebarInset
935
+ - Use SidebarMenuButton for nav items (supports tooltip in collapsed state)
936
+
937
+ ## Skeleton
938
+ - Import: @devalok/shilp-sutra/ui/skeleton
939
+ - Server-safe: Yes
940
+ - Props:
941
+ variant: "rectangle" | "circle" | "text"
942
+ animation: "pulse" | "shimmer" | "none"
943
+ - Defaults: variant="rectangle", animation="pulse"
944
+ - Example:
945
+ <Skeleton variant="text" className="w-3/4" />
946
+ <Skeleton variant="circle" className="h-12 w-12" />
947
+ <Skeleton variant="rectangle" animation="shimmer" className="h-48 w-full" />
948
+ - Gotchas: shimmer respects prefers-reduced-motion
949
+
950
+ ## Slider
951
+ - Import: @devalok/shilp-sutra/ui/slider
952
+ - Server-safe: No
953
+ - Props: Standard Radix Slider props (value, onValueChange, defaultValue, min, max, step, aria-label)
954
+ - Example:
955
+ <Slider defaultValue={[50]} max={100} step={1} aria-label="Volume" />
956
+ - Gotchas: value is number[] (array), not a single number
957
+
958
+ ## Spinner
959
+ - Import: @devalok/shilp-sutra/ui/spinner
960
+ - Server-safe: Yes
961
+ - Props:
962
+ size: "sm" | "md" | "lg"
963
+ - Defaults: size="md"
964
+ - Example:
965
+ <Spinner size="lg" />
966
+ - Gotchas:
967
+ - Renders role="status" with sr-only "Loading..." text — no need for aria-label
968
+ - Button has built-in loading prop — prefer that over manual Spinner composition
969
+
970
+ ## Stack
971
+ - Import: @devalok/shilp-sutra/ui/stack
972
+ - Server-safe: Yes
973
+ - Props:
974
+ direction: "vertical" | "horizontal" | "row" | "column" (default: "vertical")
975
+ gap: SpacingToken | number — tokens: "ds-01".."ds-13", or numbers 0-13
976
+ align: "start" | "center" | "end" | "stretch" | "baseline"
977
+ justify: "start" | "center" | "end" | "between" | "around" | "evenly"
978
+ wrap: boolean
979
+ as: ElementType (default: "div")
980
+ - Example:
981
+ <Stack direction="horizontal" gap="ds-04" align="center">
982
+ <Avatar size="sm" />
983
+ <Text variant="body-md">User Name</Text>
984
+ </Stack>
985
+
986
+ ## StatCard
987
+ - Import: @devalok/shilp-sutra/ui/stat-card
988
+ - Server-safe: No
989
+ - Props:
990
+ label: string (heading text) — alias: title
991
+ title: string (alias for label)
992
+ value: string | number (REQUIRED)
993
+ delta: { value: string, direction: "up" | "down" | "neutral" }
994
+ icon: ReactNode | ComponentType<{ className?: string }>
995
+ loading: boolean (renders skeleton)
996
+ - Example:
997
+ <StatCard
998
+ label="Revenue"
999
+ value="$48,200"
1000
+ delta={{ value: "+12%", direction: "up" }}
1001
+ icon={<IconCurrencyDollar />}
1002
+ />
1003
+ - Gotchas: delta.direction "up" = green, "down" = red, "neutral" = grey
1004
+
1005
+ ## Stepper / Step
1006
+ - Import: @devalok/shilp-sutra/ui/stepper
1007
+ - Server-safe: No
1008
+ - Props (Stepper):
1009
+ activeStep: number (REQUIRED, 0-indexed)
1010
+ orientation: "horizontal" | "vertical" (default: "horizontal")
1011
+ children: <Step> elements
1012
+ - Props (Step):
1013
+ label: string (REQUIRED)
1014
+ description: string
1015
+ icon: ReactNode (overrides default number/checkmark)
1016
+ - Example:
1017
+ <Stepper activeStep={1}>
1018
+ <Step label="Account" description="Create credentials" />
1019
+ <Step label="Profile" description="Add details" />
1020
+ <Step label="Review" />
1021
+ </Stepper>
1022
+ - Gotchas: Steps before activeStep are "completed", at activeStep is "active", after is "pending"
1023
+
1024
+ ## Switch
1025
+ - Import: @devalok/shilp-sutra/ui/switch
1026
+ - Server-safe: No
1027
+ - Props:
1028
+ checked: boolean
1029
+ onCheckedChange: (checked: boolean) => void
1030
+ error: boolean (shows red border/bg)
1031
+ disabled: boolean
1032
+ - Example:
1033
+ <Switch checked={enabled} onCheckedChange={setEnabled} />
1034
+
1035
+ ## Table
1036
+ - Import: @devalok/shilp-sutra/ui/table
1037
+ - Server-safe: Yes
1038
+ - Compound:
1039
+ Table (root <table>)
1040
+ TableHeader (<thead>)
1041
+ TableRow (<tr>)
1042
+ TableHead (<th>)
1043
+ TableBody (<tbody>)
1044
+ TableRow (<tr>)
1045
+ TableCell (<td>)
1046
+ TableFooter (<tfoot>)
1047
+ TableCaption (<caption>)
1048
+ - Example:
1049
+ <Table>
1050
+ <TableHeader>
1051
+ <TableRow>
1052
+ <TableHead>Name</TableHead>
1053
+ <TableHead>Status</TableHead>
1054
+ </TableRow>
1055
+ </TableHeader>
1056
+ <TableBody>
1057
+ <TableRow>
1058
+ <TableCell>Project Alpha</TableCell>
1059
+ <TableCell><Badge color="success">Active</Badge></TableCell>
1060
+ </TableRow>
1061
+ </TableBody>
1062
+ </Table>
1063
+
1064
+ ## Tabs
1065
+ - Import: @devalok/shilp-sutra/ui/tabs
1066
+ - Server-safe: No
1067
+ - Props (Tabs root): defaultValue, value, onValueChange
1068
+ - Props (TabsList): variant: "line" | "contained" (default: "line")
1069
+ - Props (TabsTrigger): value: string (REQUIRED), variant (inherits from TabsList)
1070
+ - Props (TabsContent): value: string (REQUIRED)
1071
+ - Compound:
1072
+ Tabs (root)
1073
+ TabsList (variant)
1074
+ TabsTrigger (value)
1075
+ TabsContent (value)
1076
+ - Defaults: TabsList variant="line"
1077
+ - Example:
1078
+ <Tabs defaultValue="overview">
1079
+ <TabsList variant="contained">
1080
+ <TabsTrigger value="overview">Overview</TabsTrigger>
1081
+ <TabsTrigger value="activity">Activity</TabsTrigger>
1082
+ </TabsList>
1083
+ <TabsContent value="overview">Overview content</TabsContent>
1084
+ <TabsContent value="activity">Activity content</TabsContent>
1085
+ </Tabs>
1086
+ - Gotchas:
1087
+ - variant goes on TabsList, NOT on individual TabsTrigger (propagates via context)
1088
+ - DO NOT put variant on TabsTrigger — it inherits from TabsList
1089
+
1090
+ ## Text
1091
+ - Import: @devalok/shilp-sutra/ui/text
1092
+ - Server-safe: Yes
1093
+ - Props:
1094
+ variant: "heading-2xl" | "heading-xl" | "heading-lg" | "heading-md" | "heading-sm" | "heading-xs" | "body-lg" | "body-md" | "body-sm" | "body-xs" | "label-lg" | "label-md" | "label-sm" | "label-xs" | "caption" | "overline"
1095
+ as: ElementType (override the auto-selected HTML element)
1096
+ - Defaults: variant="body-md"
1097
+ - Default element mapping:
1098
+ heading-2xl -> h1, heading-xl -> h2, heading-lg -> h3, heading-md -> h4, heading-sm -> h5, heading-xs -> h6
1099
+ body-* -> p, label-* -> span, caption -> span, overline -> span
1100
+ - Example:
1101
+ <Text variant="heading-2xl">Page Title</Text>
1102
+ <Text variant="body-sm" as="span">Inline text</Text>
1103
+ <Text variant="label-sm" className="text-text-secondary">SECTION LABEL</Text>
1104
+ - Gotchas:
1105
+ - label-* and overline variants are automatically uppercase
1106
+ - Use "as" prop to override the HTML element when needed
1107
+
1108
+ ## Textarea
1109
+ - Import: @devalok/shilp-sutra/ui/textarea
1110
+ - Server-safe: No
1111
+ - Props:
1112
+ size: "sm" | "md" | "lg"
1113
+ state: "default" | "error" | "warning" | "success"
1114
+ (plus standard textarea attributes except native "size")
1115
+ - Defaults: size="md"
1116
+ - Example:
1117
+ <Textarea size="lg" state="error" placeholder="Describe the issue..." />
1118
+ - Gotchas: state="error" sets aria-invalid automatically; all sizes are vertically resizable
1119
+
1120
+ ## Toast / Toaster
1121
+ - Import: @devalok/shilp-sutra/ui/toast, @devalok/shilp-sutra/ui/toaster
1122
+ - Server-safe: No
1123
+ - Props (Toast):
1124
+ color: "neutral" | "success" | "warning" | "error" | "info"
1125
+ - Defaults: color="neutral"
1126
+ - Compound (declarative — for tests/Storybook only):
1127
+ ToastProvider
1128
+ Toast (color)
1129
+ ToastTitle
1130
+ ToastDescription
1131
+ ToastAction (altText required)
1132
+ ToastClose
1133
+ ToastViewport
1134
+ - Imperative usage (recommended):
1135
+ // 1. Mount <Toaster /> once at root layout
1136
+ <Toaster />
1137
+ // 2. Call from anywhere:
1138
+ const { toast } = useToast()
1139
+ toast({ title: 'Saved!', description: 'Changes saved.', color: 'success' })
1140
+ // Or use the direct function (no hook):
1141
+ import { toast } from '@devalok/shilp-sutra/hooks/use-toast'
1142
+ toast({ title: 'Deleted', color: 'error' })
1143
+ - Gotchas:
1144
+ - DO NOT use color="destructive" — use color="error"
1145
+ - DO NOT use variant="destructive" — Toast has NO variant prop, only color
1146
+ - Max 2 toasts visible simultaneously (TOAST_LIMIT=2)
1147
+ - Auto-dismisses after 5 seconds (TOAST_REMOVE_DELAY=5000)
1148
+
1149
+ ## Toggle
1150
+ - Import: @devalok/shilp-sutra/ui/toggle
1151
+ - Server-safe: No
1152
+ - Props:
1153
+ variant: "default" | "outline"
1154
+ size: "sm" | "md" | "lg"
1155
+ pressed: boolean
1156
+ onPressedChange: (pressed: boolean) => void
1157
+ defaultPressed: boolean
1158
+ - Defaults: variant="default", size="md"
1159
+ - Example:
1160
+ <Toggle aria-label="Toggle bold" pressed={isBold} onPressedChange={setIsBold}>
1161
+ <IconBold />
1162
+ </Toggle>
1163
+
1164
+ ## ToggleGroup
1165
+ - Import: @devalok/shilp-sutra/ui/toggle-group
1166
+ - Server-safe: No
1167
+ - Props (ToggleGroup):
1168
+ type: "single" | "multiple"
1169
+ variant: "default" | "outline" (propagated to items)
1170
+ size: "sm" | "md" | "lg" (propagated to items)
1171
+ value: string | string[]
1172
+ onValueChange: (value) => void
1173
+ - Compound:
1174
+ ToggleGroup (root)
1175
+ ToggleGroupItem (value: string)
1176
+ - Example:
1177
+ <ToggleGroup type="single" variant="outline" size="sm" value={alignment} onValueChange={setAlignment}>
1178
+ <ToggleGroupItem value="left"><IconAlignLeft /></ToggleGroupItem>
1179
+ <ToggleGroupItem value="center"><IconAlignCenter /></ToggleGroupItem>
1180
+ <ToggleGroupItem value="right"><IconAlignRight /></ToggleGroupItem>
1181
+ </ToggleGroup>
1182
+
1183
+ ## Tooltip
1184
+ - Import: @devalok/shilp-sutra/ui/tooltip
1185
+ - Server-safe: No
1186
+ - Compound:
1187
+ TooltipProvider (REQUIRED at layout root or wrapping tooltip usage, controls delay)
1188
+ Tooltip (root)
1189
+ TooltipTrigger
1190
+ TooltipContent
1191
+ - Example:
1192
+ <TooltipProvider>
1193
+ <Tooltip>
1194
+ <TooltipTrigger asChild><Button>Hover me</Button></TooltipTrigger>
1195
+ <TooltipContent>Tooltip text</TooltipContent>
1196
+ </Tooltip>
1197
+ </TooltipProvider>
1198
+ - Gotchas: TooltipProvider is REQUIRED — without it, tooltips won't show
1199
+
1200
+ ## Transitions (Fade, Collapse, Grow, Slide)
1201
+ - Import: @devalok/shilp-sutra/ui/transitions
1202
+ - Server-safe: No
1203
+ - Props (shared TransitionProps):
1204
+ open: boolean (REQUIRED)
1205
+ duration: string (CSS duration, default: "var(--duration-moderate-02)")
1206
+ unmountOnClose: boolean (Fade, Grow, Slide only)
1207
+ children: ReactNode
1208
+ - Props (Slide only):
1209
+ direction: "up" | "down" | "left" | "right" (default: "up")
1210
+ - Example:
1211
+ <Fade open={isVisible}><div>Fading content</div></Fade>
1212
+ <Collapse open={isExpanded}><div>Collapsible content</div></Collapse>
1213
+ <Grow open={isVisible}><div>Scaling content</div></Grow>
1214
+ <Slide open={isVisible} direction="left"><div>Sliding panel</div></Slide>
1215
+ - Gotchas: All transitions respect prefers-reduced-motion (duration set to 0ms)
1216
+
1217
+ ## TreeView / TreeItem
1218
+ - Import: @devalok/shilp-sutra/ui/tree-view
1219
+ - Server-safe: No
1220
+ - Props (TreeView):
1221
+ items: TreeNode[] (data-driven mode) — { id, label, icon?, disabled?, children? }
1222
+ defaultExpanded: string[]
1223
+ defaultSelected: string[]
1224
+ multiSelect: boolean
1225
+ checkboxes: boolean
1226
+ onSelect: (ids: string[]) => void
1227
+ onExpand: (ids: string[]) => void
1228
+ children: ReactNode (declarative mode)
1229
+ - Props (TreeItem):
1230
+ itemId: string (REQUIRED)
1231
+ label: string
1232
+ icon: ReactNode
1233
+ disabled: boolean
1234
+ depth: number
1235
+ children: ReactNode (nested TreeItems)
1236
+ - Hook: useTree({ defaultExpanded, defaultSelected, multiSelect, onSelect, onExpand })
1237
+ - Example (data-driven):
1238
+ <TreeView
1239
+ items={[
1240
+ { id: '1', label: 'Folder', children: [
1241
+ { id: '1.1', label: 'File A' },
1242
+ { id: '1.2', label: 'File B' },
1243
+ ]},
1244
+ ]}
1245
+ defaultExpanded={['1']}
1246
+ onSelect={(ids) => console.log(ids)}
1247
+ />
1248
+ - Example (declarative):
1249
+ <TreeView>
1250
+ <TreeItem itemId="1" label="Folder">
1251
+ <TreeItem itemId="1.1" label="File A" />
1252
+ </TreeItem>
1253
+ </TreeView>
1254
+
1255
+ ## VisuallyHidden
1256
+ - Import: @devalok/shilp-sutra/ui/visually-hidden
1257
+ - Server-safe: Yes
1258
+ - Props: standard span attributes
1259
+ - Example:
1260
+ <VisuallyHidden>Screen reader only text</VisuallyHidden>
1261
+
1262
+ ---
1263
+
1264
+ # COMPOSED COMPONENTS
1265
+ # Alphabetical within this section.
1266
+ # Import from: @devalok/shilp-sutra/composed/<kebab-name>
1267
+
1268
+ ---
1269
+
1270
+ ## AvatarGroup
1271
+ - Import: @devalok/shilp-sutra/composed/avatar-group
1272
+ - Server-safe: No
1273
+ - Props:
1274
+ users: AvatarUser[] (REQUIRED) — { name: string, image?: string | null }
1275
+ max: number (default: 4, overflow shows "+N" badge)
1276
+ size: "sm" | "md" | "lg"
1277
+ showTooltip: boolean (default: true)
1278
+ - Defaults: size="md", max=4, showTooltip=true
1279
+ - Example:
1280
+ <AvatarGroup
1281
+ users={[{ name: 'Alice', image: '/alice.jpg' }, { name: 'Bob' }]}
1282
+ max={3}
1283
+ size="md"
1284
+ />
1285
+ - Gotchas: Wraps TooltipProvider internally — no need to add one
1286
+
1287
+ ## CommandPalette
1288
+ - Import: @devalok/shilp-sutra/composed/command-palette
1289
+ - Server-safe: No
1290
+ - Props:
1291
+ groups: CommandGroup[] — { label: string, items: CommandItem[] }
1292
+ placeholder: string (default: "Search or jump to...")
1293
+ onSearch: (query: string) => void
1294
+ emptyMessage: string (default: "No results found.")
1295
+ - CommandItem shape: { id, label, description?, icon?, shortcut?, onSelect: () => void }
1296
+ - Example:
1297
+ <CommandPalette
1298
+ groups={[{
1299
+ label: 'Navigation',
1300
+ items: [{ id: 'dash', label: 'Dashboard', onSelect: () => navigate('/') }],
1301
+ }]}
1302
+ />
1303
+ - Gotchas: Opens with Ctrl+K / Cmd+K by default
1304
+
1305
+ ## ContentCard
1306
+ - Import: @devalok/shilp-sutra/composed/content-card
1307
+ - Server-safe: Yes
1308
+ - Props:
1309
+ variant: "default" | "outline" | "ghost"
1310
+ padding: "default" | "compact" | "spacious" | "none"
1311
+ header: ReactNode (custom header content)
1312
+ headerTitle: string (simple text header)
1313
+ headerActions: ReactNode (actions in header area)
1314
+ footer: ReactNode
1315
+ children: ReactNode (body)
1316
+ - Defaults: variant="default", padding="default"
1317
+ - Example:
1318
+ <ContentCard headerTitle="Team Members" headerActions={<Button size="sm">Add</Button>}>
1319
+ <p>Member list here</p>
1320
+ </ContentCard>
1321
+
1322
+ ## DatePicker
1323
+ - Import: @devalok/shilp-sutra/composed/date-picker
1324
+ - Server-safe: No
1325
+ - Props:
1326
+ value: Date | null
1327
+ onChange: (date: Date | null) => void
1328
+ placeholder: string (default: "Pick a date")
1329
+ formatStr: string (default: "MMM d, yyyy")
1330
+ minDate: Date
1331
+ maxDate: Date
1332
+ disabledDates: (date: Date) => boolean
1333
+ className: string
1334
+ - Example:
1335
+ <DatePicker value={date} onChange={setDate} placeholder="Select date" />
1336
+
1337
+ ## DateRangePicker
1338
+ - Import: @devalok/shilp-sutra/composed/date-picker
1339
+ - Server-safe: No
1340
+ - Props:
1341
+ startDate: Date | null
1342
+ endDate: Date | null
1343
+ onChange: (range: { start: Date | null, end: Date | null }) => void
1344
+ placeholder: string (default: "Pick a date range")
1345
+ formatStr: string (default: "MMM d, yyyy")
1346
+ minDate: Date
1347
+ maxDate: Date
1348
+ disabledDates: (date: Date) => boolean
1349
+ presets: PresetKey[] (shows quick-select sidebar)
1350
+ numberOfMonths: number (default: 1)
1351
+ - Example:
1352
+ <DateRangePicker
1353
+ startDate={start}
1354
+ endDate={end}
1355
+ onChange={({ start, end }) => { setStart(start); setEnd(end); }}
1356
+ />
1357
+
1358
+ ## DateTimePicker
1359
+ - Import: @devalok/shilp-sutra/composed/date-picker
1360
+ - Server-safe: No
1361
+ - Props:
1362
+ value: Date | null
1363
+ onChange: (date: Date | null) => void
1364
+ minDate: Date
1365
+ maxDate: Date
1366
+ disabledDates: (date: Date) => boolean
1367
+ timeFormat: "12h" | "24h"
1368
+ minuteStep: number
1369
+ placeholder: string
1370
+ className: string
1371
+ - Example:
1372
+ <DateTimePicker value={dateTime} onChange={setDateTime} timeFormat="12h" minuteStep={15} />
1373
+
1374
+ ## EmptyState
1375
+ - Import: @devalok/shilp-sutra/composed/empty-state
1376
+ - Server-safe: Yes
1377
+ - Props:
1378
+ title: string (REQUIRED)
1379
+ description: string
1380
+ icon: ReactNode (default: Devalok chakra icon)
1381
+ action: ReactNode (e.g. a Button)
1382
+ compact: boolean (smaller layout)
1383
+ - Example:
1384
+ <EmptyState
1385
+ title="No tasks found"
1386
+ description="Create your first task to get started."
1387
+ action={<Button>Create Task</Button>}
1388
+ />
1389
+
1390
+ ## ErrorDisplay
1391
+ - Import: @devalok/shilp-sutra/composed/error-boundary
1392
+ - Server-safe: No
1393
+ - Props:
1394
+ error: unknown (REQUIRED — Error object, status object, or string)
1395
+ onReset: () => void (optional retry button)
1396
+ - Example:
1397
+ <ErrorDisplay error={error} onReset={() => refetch()} />
1398
+ - Gotchas:
1399
+ - Auto-detects HTTP status codes (404, 403, 500) and shows appropriate icon/message
1400
+ - Shows stack trace in development mode only
1401
+
1402
+ ## GlobalLoading
1403
+ - Import: @devalok/shilp-sutra/composed/global-loading
1404
+ - Server-safe: No
1405
+ - Props:
1406
+ isLoading: boolean (REQUIRED)
1407
+ - Example:
1408
+ <GlobalLoading isLoading={isNavigating} />
1409
+ - Gotchas: Fixed-position bar at top of viewport (z-toast layer)
1410
+
1411
+ ## LoadingSkeleton (CardSkeleton, TableSkeleton, BoardSkeleton, ListSkeleton)
1412
+ - Import: @devalok/shilp-sutra/composed/loading-skeleton
1413
+ - Server-safe: Yes
1414
+ - Props (CardSkeleton): className
1415
+ - Props (TableSkeleton): rows (default: 5), columns (default: 4), className
1416
+ - Props (BoardSkeleton): columns (default: 4), cardsPerColumn (default: 3), className
1417
+ - Props (ListSkeleton): rows (default: 6), showAvatar (default: true), className
1418
+ - Example:
1419
+ <CardSkeleton />
1420
+ <TableSkeleton rows={8} columns={5} />
1421
+ <BoardSkeleton columns={3} cardsPerColumn={4} />
1422
+ <ListSkeleton rows={10} showAvatar={false} />
1423
+
1424
+ ## MemberPicker
1425
+ - Import: @devalok/shilp-sutra/composed/member-picker
1426
+ - Server-safe: No
1427
+ - Props:
1428
+ members: MemberPickerMember[] (REQUIRED) — { id, name, avatar? }
1429
+ selectedIds: string[] (REQUIRED)
1430
+ onSelect: (memberId: string) => void (REQUIRED)
1431
+ multiple: boolean (default: false)
1432
+ placeholder: string (default: "Search members...")
1433
+ children: ReactNode (trigger element)
1434
+ - Example:
1435
+ <MemberPicker members={teamMembers} selectedIds={assignees} onSelect={toggleAssignee} multiple>
1436
+ <Button variant="outline">Assign Members</Button>
1437
+ </MemberPicker>
1438
+
1439
+ ## PageHeader
1440
+ - Import: @devalok/shilp-sutra/composed/page-header
1441
+ - Server-safe: Yes
1442
+ - Props:
1443
+ title: string (falls back to last breadcrumb label if omitted)
1444
+ subtitle: string
1445
+ actions: ReactNode (action buttons)
1446
+ breadcrumbs: Breadcrumb[] — { label: string, href?: string }
1447
+ titleClassName: string
1448
+ - Example:
1449
+ <PageHeader
1450
+ title="Project Settings"
1451
+ subtitle="Configure your project preferences"
1452
+ breadcrumbs={[
1453
+ { label: 'Home', href: '/' },
1454
+ { label: 'Projects', href: '/projects' },
1455
+ { label: 'Settings' },
1456
+ ]}
1457
+ actions={<Button>Save</Button>}
1458
+ />
1459
+
1460
+ ## PageSkeletons (DashboardSkeleton, ProjectListSkeleton, TaskDetailSkeleton)
1461
+ - Import: @devalok/shilp-sutra/composed/page-skeletons
1462
+ - Server-safe: Yes
1463
+ - Props: None (no configurable props)
1464
+ - Example:
1465
+ <DashboardSkeleton />
1466
+ <ProjectListSkeleton />
1467
+ <TaskDetailSkeleton />
1468
+
1469
+ ## PriorityIndicator
1470
+ - Import: @devalok/shilp-sutra/composed/priority-indicator
1471
+ - Server-safe: Yes
1472
+ - Props:
1473
+ priority: "LOW" | "MEDIUM" | "HIGH" | "URGENT" (case-insensitive)
1474
+ display: "compact" | "full" (default: "full")
1475
+ - Example:
1476
+ <PriorityIndicator priority="HIGH" />
1477
+ <PriorityIndicator priority="low" display="compact" />
1478
+ - Gotchas: Case-insensitive — "low" and "LOW" both work
1479
+
1480
+ ## RichTextEditor / RichTextViewer
1481
+ - Import: @devalok/shilp-sutra/composed/rich-text-editor
1482
+ - Server-safe: No
1483
+ - Props (RichTextEditor):
1484
+ content: string (HTML string)
1485
+ placeholder: string (default: "Start writing...")
1486
+ onChange: (html: string) => void
1487
+ className: string
1488
+ editable: boolean (default: true)
1489
+ - Props (RichTextViewer):
1490
+ content: string (REQUIRED, HTML string)
1491
+ className: string
1492
+ - Example:
1493
+ <RichTextEditor content={html} onChange={setHtml} placeholder="Write your message..." />
1494
+ <RichTextViewer content={savedHtml} />
1495
+ - Gotchas: Requires @tiptap/react, @tiptap/starter-kit, @tiptap/extension-placeholder as peer deps
1496
+
1497
+ ## ScheduleView
1498
+ - Import: @devalok/shilp-sutra/composed/schedule-view
1499
+ - Server-safe: No
1500
+ - Props:
1501
+ view: "day" | "week" (REQUIRED)
1502
+ date: Date (REQUIRED — current day or any date in target week)
1503
+ events: ScheduleEvent[] (REQUIRED) — { id, title, start: Date, end: Date, color? }
1504
+ onEventClick: (event: ScheduleEvent) => void
1505
+ onSlotClick: (start: Date, end: Date) => void
1506
+ startHour: number (default: 8)
1507
+ endHour: number (default: 18, exclusive)
1508
+ slotDuration: number (minutes, default: 30)
1509
+ - Event colors: "primary" | "success" | "warning" | "error" | "info" | "neutral"
1510
+ - Example:
1511
+ <ScheduleView
1512
+ view="week"
1513
+ date={new Date()}
1514
+ events={calendarEvents}
1515
+ onEventClick={(e) => openEvent(e.id)}
1516
+ />
1517
+
1518
+ ## SimpleTooltip
1519
+ - Import: @devalok/shilp-sutra/composed/simple-tooltip
1520
+ - Server-safe: No
1521
+ - Props:
1522
+ content: ReactNode (REQUIRED — tooltip content)
1523
+ side: "top" | "right" | "bottom" | "left" (default: "top")
1524
+ align: "start" | "center" | "end" (default: "center")
1525
+ delayDuration: number (ms, default: 300)
1526
+ children: ReactNode (trigger element)
1527
+ - Example:
1528
+ <SimpleTooltip content="Edit this item">
1529
+ <IconButton icon={<IconEdit />} aria-label="Edit" />
1530
+ </SimpleTooltip>
1531
+ - Gotchas: Wraps the full Tooltip compound (Provider + Tooltip + Trigger + Content) into one component
1532
+
1533
+ ## StatusBadge
1534
+ - Import: @devalok/shilp-sutra/composed/status-badge
1535
+ - Server-safe: Yes
1536
+ - Props:
1537
+ status: "active" | "pending" | "approved" | "rejected" | "completed" | "blocked" | "cancelled" | "draft"
1538
+ color: "success" | "warning" | "error" | "info" | "neutral" (overrides status styling when set)
1539
+ size: "sm" | "md"
1540
+ label: string (auto-derived from status/color if omitted)
1541
+ hideDot: boolean (default: false)
1542
+ - Defaults: size="md"
1543
+ - Example:
1544
+ <StatusBadge status="active" />
1545
+ <StatusBadge color="warning" label="In Review" size="sm" />
1546
+ - Gotchas: When color is set, it takes priority over status for styling
1547
+
1548
+ ---
1549
+
1550
+ # SHELL COMPONENTS
1551
+ # Application-level layout components.
1552
+ # Import from: @devalok/shilp-sutra/shell/<kebab-name>
1553
+
1554
+ ---
1555
+
1556
+ ## AppCommandPalette
1557
+ - Import: @devalok/shilp-sutra/shell/app-command-palette
1558
+ - Server-safe: No
1559
+ - Props:
1560
+ user: AppCommandPaletteUser | null — { name, role? }
1561
+ extraGroups: CommandGroup[]
1562
+ onNavigate: (path: string) => void
1563
+ onSearch: (query: string) => void
1564
+ searchResults: SearchResult[] — { id, title, snippet?, entityType, projectId?, metadata? }
1565
+ onSelectResult: (result: SearchResult) => void
1566
+ - Example:
1567
+ <AppCommandPalette
1568
+ user={{ name: 'John', role: 'admin' }}
1569
+ onNavigate={(path) => router.push(path)}
1570
+ searchResults={results}
1571
+ onSelectResult={(r) => router.push(`/${r.entityType}/${r.id}`)}
1572
+ />
1573
+
1574
+ ## AppSidebar
1575
+ - Import: @devalok/shilp-sutra/shell/sidebar
1576
+ - Server-safe: No
1577
+ - Props:
1578
+ currentPath: string (highlights active nav item)
1579
+ user: SidebarUser | null — { name, email?, image?, designation?, role? }
1580
+ navGroups: NavGroup[] — { label: string, items: NavItem[] }
1581
+ logo: ReactNode
1582
+ footerLinks: Array<{ label: string, href: string }>
1583
+ className: string
1584
+ - NavItem shape: { title, href, icon: ReactNode, exact?: boolean }
1585
+ - Example:
1586
+ <AppSidebar
1587
+ currentPath="/dashboard"
1588
+ user={{ name: 'Jane', email: 'jane@example.com' }}
1589
+ navGroups={[{
1590
+ label: 'Main',
1591
+ items: [
1592
+ { title: 'Dashboard', href: '/dashboard', icon: <IconHome /> },
1593
+ { title: 'Projects', href: '/projects', icon: <IconFolder /> },
1594
+ ],
1595
+ }]}
1596
+ />
1597
+ - Gotchas: Must be wrapped in SidebarProvider (from ui/sidebar)
1598
+
1599
+ ## BottomNavbar
1600
+ - Import: @devalok/shilp-sutra/shell/bottom-navbar
1601
+ - Server-safe: No
1602
+ - Props:
1603
+ currentPath: string
1604
+ user: BottomNavbarUser | null — { name, role? }
1605
+ primaryItems: BottomNavItem[] (max 4 recommended) — { title, href, icon, exact? }
1606
+ moreItems: BottomNavItem[] (overflow items in "More" menu)
1607
+ className: string
1608
+ - Example:
1609
+ <BottomNavbar
1610
+ currentPath="/dashboard"
1611
+ primaryItems={[
1612
+ { title: 'Home', href: '/', icon: <IconHome /> },
1613
+ { title: 'Tasks', href: '/tasks', icon: <IconChecklist /> },
1614
+ ]}
1615
+ />
1616
+
1617
+ ## LinkProvider
1618
+ - Import: @devalok/shilp-sutra/shell/link-context
1619
+ - Server-safe: No
1620
+ - Props:
1621
+ component: ForwardRefComponent (e.g. Next.js Link, Remix Link)
1622
+ children: ReactNode
1623
+ - Hook: useLink() => LinkComponent
1624
+ - Example:
1625
+ import Link from 'next/link'
1626
+ <LinkProvider component={Link}>
1627
+ <AppSidebar ... />
1628
+ <BottomNavbar ... />
1629
+ </LinkProvider>
1630
+ - Gotchas: Without LinkProvider, shell components render plain <a> tags
1631
+
1632
+ ## NotificationCenter
1633
+ - Import: @devalok/shilp-sutra/shell/notification-center
1634
+ - Server-safe: No
1635
+ - Props:
1636
+ notifications: Notification[] — { id, title, body?, tier: "INFO"|"IMPORTANT"|"CRITICAL", isRead, createdAt, entityType?, entityId?, projectId?, project? }
1637
+ unreadCount: number (derived from notifications if not provided)
1638
+ open: boolean (controlled mode)
1639
+ onOpenChange: (open: boolean) => void
1640
+ isLoading: boolean
1641
+ hasMore: boolean
1642
+ onFetchMore: () => void
1643
+ onMarkRead: (id: string) => void
1644
+ onMarkAllRead: () => void
1645
+ onNotificationClick: (notification: Notification) => void
1646
+ - Example:
1647
+ <NotificationCenter
1648
+ notifications={notifications}
1649
+ onMarkRead={markAsRead}
1650
+ onMarkAllRead={markAllAsRead}
1651
+ onNotificationClick={(n) => router.push(`/notifications/${n.id}`)}
1652
+ />
1653
+
1654
+ ## NotificationPreferences
1655
+ - Import: @devalok/shilp-sutra/shell/notification-preferences
1656
+ - Server-safe: No
1657
+ - Props:
1658
+ preferences: NotificationPreference[] — { id, userId?, projectId, channel, minTier, muted }
1659
+ projects: NotificationProject[] — { id, title }
1660
+ isLoading: boolean
1661
+ onSave: (preference: { projectId, channel, minTier, muted }) => void
1662
+ onDelete: (id: string) => void
1663
+ - Example:
1664
+ <NotificationPreferences
1665
+ preferences={prefs}
1666
+ projects={projectList}
1667
+ onSave={handleSavePref}
1668
+ onDelete={handleDeletePref}
1669
+ />
1670
+
1671
+ ## TopBar
1672
+ - Import: @devalok/shilp-sutra/shell/top-bar
1673
+ - Server-safe: No
1674
+ - Props:
1675
+ pageTitle: string
1676
+ user: TopBarUser | null — { name, email?, image? }
1677
+ onNavigate: (path: string) => void
1678
+ onLogout: () => void
1679
+ onSearchClick: () => void
1680
+ onAiChatClick: () => void
1681
+ mobileLogo: ReactNode
1682
+ notificationSlot: ReactNode (render NotificationCenter here)
1683
+ className: string
1684
+ - Example:
1685
+ <TopBar
1686
+ pageTitle="Dashboard"
1687
+ user={{ name: 'John', email: 'john@example.com' }}
1688
+ onNavigate={(p) => router.push(p)}
1689
+ onLogout={handleLogout}
1690
+ notificationSlot={<NotificationCenter notifications={notifications} />}
1691
+ />
1692
+
1693
+ ---
1694
+
1695
+ # HOOKS
1696
+ # Import from: @devalok/shilp-sutra/hooks/<hook-name>
1697
+
1698
+ ---
1699
+
1700
+ ## useToast
1701
+ - Import: @devalok/shilp-sutra/hooks/use-toast
1702
+ - Returns: { toasts, toast, dismiss }
1703
+ - toast(options): options = { title?, description?, color?, action? }
1704
+ color: "neutral" | "success" | "warning" | "error" | "info"
1705
+ Returns: { id, dismiss, update }
1706
+ - dismiss(toastId?: string): dismiss specific or all toasts
1707
+ - Example:
1708
+ const { toast } = useToast()
1709
+ toast({ title: 'Saved!', color: 'success' })
1710
+ toast({ title: 'Error', description: 'Something went wrong.', color: 'error' })
1711
+ - Also available: import { toast } from '@devalok/shilp-sutra/hooks/use-toast' (imperative, no hook)
1712
+
1713
+ ## useColorMode
1714
+ - Import: @devalok/shilp-sutra/hooks/use-color-mode
1715
+ - Returns: { colorMode, setColorMode, toggleColorMode }
1716
+ - colorMode: "light" | "dark" | "system"
1717
+ - setColorMode(mode: "light" | "dark" | "system"): sets .dark class on html, persists to localStorage + cookie
1718
+ - toggleColorMode(): toggles between light and dark
1719
+ - Example:
1720
+ const { colorMode, toggleColorMode } = useColorMode()
1721
+ <Button onClick={toggleColorMode}>{colorMode === 'dark' ? 'Light' : 'Dark'}</Button>
1722
+
1723
+ ## useIsMobile
1724
+ - Import: @devalok/shilp-sutra/hooks/use-mobile
1725
+ - Returns: boolean (true if viewport width < 768px)
1726
+ - Example:
1727
+ const isMobile = useIsMobile()
1728
+ {isMobile ? <BottomNavbar /> : <AppSidebar />}
1729
+
1730
+ ---
1731
+
1732
+ # UTILITIES
1733
+ # Import from: @devalok/shilp-sutra (barrel) or per-component path
1734
+
1735
+ ---
1736
+
1737
+ ## cn (className merge utility)
1738
+ - Import: @devalok/shilp-sutra (barrel export)
1739
+ - Usage: cn('base-class', condition && 'conditional-class', className)
1740
+ - Merges Tailwind classes with clsx + twMerge
1741
+
1742
+ ## getInitials
1743
+ - Import: @devalok/shilp-sutra/composed (barrel export)
1744
+ - Usage: getInitials("John Doe") => "JD"
1745
+
1746
+ ## generatePagination
1747
+ - Import: @devalok/shilp-sutra/ui/pagination
1748
+ - Usage: generatePagination(currentPage, totalPages, siblingCount)
1749
+ - Returns: (number | 'ellipsis')[]
1750
+
1751
+ ## motion / duration / easings / durations
1752
+ - Import: @devalok/shilp-sutra (barrel export)
1753
+ - Design system motion tokens for custom animations