@devalok/shilp-sutra 0.37.0 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/MIGRATION.md +67 -0
  2. package/dist/_chunks/tiptap.js +977 -968
  3. package/dist/_chunks/tiptap.js.map +1 -1
  4. package/dist/composed/index.d.ts +0 -2
  5. package/dist/composed/index.d.ts.map +1 -1
  6. package/dist/composed/index.js +5 -6
  7. package/dist/composed/inline-edit.d.ts.map +1 -1
  8. package/dist/composed/inline-edit.js +42 -40
  9. package/dist/composed/inline-edit.js.map +1 -1
  10. package/dist/hooks/index.d.ts +2 -2
  11. package/dist/hooks/index.d.ts.map +1 -1
  12. package/dist/hooks/index.js +0 -1
  13. package/dist/test-utils/conformance.d.ts +37 -0
  14. package/dist/test-utils/conformance.d.ts.map +1 -0
  15. package/dist/ui/alert.d.ts +1 -2
  16. package/dist/ui/alert.d.ts.map +1 -1
  17. package/dist/ui/alert.js +1 -27
  18. package/dist/ui/alert.js.map +1 -1
  19. package/dist/ui/banner.d.ts +3 -5
  20. package/dist/ui/banner.d.ts.map +1 -1
  21. package/dist/ui/banner.js +13 -13
  22. package/dist/ui/banner.js.map +1 -1
  23. package/dist/ui/index.d.ts +1 -1
  24. package/dist/ui/index.d.ts.map +1 -1
  25. package/dist/ui/index.js +2 -2
  26. package/dist/ui/index.js.map +1 -1
  27. package/dist/ui/input.d.ts +1 -9
  28. package/dist/ui/input.d.ts.map +1 -1
  29. package/dist/ui/input.js +26 -27
  30. package/dist/ui/input.js.map +1 -1
  31. package/dist/ui/segmented-control.d.ts +1 -1
  32. package/dist/ui/segmented-control.d.ts.map +1 -1
  33. package/dist/ui/segmented-control.js +2 -4
  34. package/dist/ui/segmented-control.js.map +1 -1
  35. package/docs/components/_header.md +1 -1
  36. package/docs/components/composed/activity-feed.md +6 -0
  37. package/docs/components/composed/avatar-group.md +8 -0
  38. package/docs/components/composed/bulk-action-bar.md +6 -0
  39. package/docs/components/composed/command-palette.md +7 -0
  40. package/docs/components/composed/confirm-dialog.md +6 -0
  41. package/docs/components/composed/content-card.md +6 -0
  42. package/docs/components/composed/date-picker.md +10 -0
  43. package/docs/components/composed/deadline-indicator.md +7 -0
  44. package/docs/components/composed/emoji-picker.md +8 -0
  45. package/docs/components/composed/empty-state.md +8 -0
  46. package/docs/components/composed/error-boundary.md +7 -0
  47. package/docs/components/composed/file-preview.md +8 -0
  48. package/docs/components/composed/filter-bar.md +8 -0
  49. package/docs/components/composed/form-section.md +6 -0
  50. package/docs/components/composed/global-loading.md +6 -0
  51. package/docs/components/composed/inline-edit.md +8 -0
  52. package/docs/components/composed/loading-skeleton.md +7 -0
  53. package/docs/components/composed/markdown-viewer.md +9 -0
  54. package/docs/components/composed/master-detail.md +7 -0
  55. package/docs/components/composed/member-picker.md +7 -0
  56. package/docs/components/composed/multi-select-popover.md +11 -0
  57. package/docs/components/composed/page-header.md +7 -0
  58. package/docs/components/composed/page-skeletons.md +10 -0
  59. package/docs/components/composed/priority-indicator.md +7 -0
  60. package/docs/components/composed/rich-chat-input.md +12 -0
  61. package/docs/components/composed/rich-text-editor.md +9 -0
  62. package/docs/components/composed/schedule-view.md +8 -0
  63. package/docs/components/composed/simple-tooltip.md +7 -0
  64. package/docs/components/composed/status-badge.md +10 -0
  65. package/docs/components/shell/app-command-palette.md +8 -0
  66. package/docs/components/shell/bottom-navbar.md +12 -0
  67. package/docs/components/shell/command-registry.md +7 -0
  68. package/docs/components/shell/link-context.md +15 -0
  69. package/docs/components/shell/notification-center.md +10 -0
  70. package/docs/components/shell/notification-preferences.md +7 -0
  71. package/docs/components/shell/sidebar.md +11 -0
  72. package/docs/components/shell/top-bar.md +11 -0
  73. package/docs/components/ui/accordion.md +10 -0
  74. package/docs/components/ui/alert-dialog.md +10 -1
  75. package/docs/components/ui/alert.md +13 -2
  76. package/docs/components/ui/aspect-ratio.md +7 -0
  77. package/docs/components/ui/autocomplete.md +7 -0
  78. package/docs/components/ui/avatar.md +10 -0
  79. package/docs/components/ui/badge-group.md +7 -0
  80. package/docs/components/ui/badge-indicator.md +7 -0
  81. package/docs/components/ui/badge.md +12 -0
  82. package/docs/components/ui/banner.md +10 -1
  83. package/docs/components/ui/breadcrumb.md +8 -0
  84. package/docs/components/ui/button-group.md +24 -4
  85. package/docs/components/ui/button-processing.md +5 -0
  86. package/docs/components/ui/button.md +9 -0
  87. package/docs/components/ui/card.md +16 -4
  88. package/docs/components/ui/charts.md +9 -0
  89. package/docs/components/ui/chat.md +9 -0
  90. package/docs/components/ui/checkbox.md +15 -1
  91. package/docs/components/ui/chip.md +9 -0
  92. package/docs/components/ui/code.md +7 -0
  93. package/docs/components/ui/collapsible.md +9 -0
  94. package/docs/components/ui/color-input.md +8 -0
  95. package/docs/components/ui/color-swatch.md +7 -0
  96. package/docs/components/ui/combobox.md +16 -3
  97. package/docs/components/ui/container.md +11 -0
  98. package/docs/components/ui/context-menu.md +8 -0
  99. package/docs/components/ui/data-table-body.md +4 -0
  100. package/docs/components/ui/data-table-bulk-actions.md +4 -0
  101. package/docs/components/ui/data-table-card.md +4 -0
  102. package/docs/components/ui/data-table-context.md +4 -0
  103. package/docs/components/ui/data-table-header.md +4 -0
  104. package/docs/components/ui/data-table-pagination.md +4 -0
  105. package/docs/components/ui/data-table-toolbar.md +7 -0
  106. package/docs/components/ui/data-table.md +25 -0
  107. package/docs/components/ui/devalok-grain.md +8 -0
  108. package/docs/components/ui/dialog.md +10 -0
  109. package/docs/components/ui/dropdown-menu.md +10 -0
  110. package/docs/components/ui/file-upload.md +8 -0
  111. package/docs/components/ui/form.md +21 -0
  112. package/docs/components/ui/hover-card.md +9 -0
  113. package/docs/components/ui/icon-button.md +7 -0
  114. package/docs/components/ui/icon-context.md +7 -0
  115. package/docs/components/ui/icon-group.md +9 -0
  116. package/docs/components/ui/icon.md +7 -0
  117. package/docs/components/ui/input-otp.md +29 -7
  118. package/docs/components/ui/input.md +11 -2
  119. package/docs/components/ui/label.md +7 -1
  120. package/docs/components/ui/link.md +7 -0
  121. package/docs/components/ui/menubar.md +9 -0
  122. package/docs/components/ui/navigation-menu.md +9 -0
  123. package/docs/components/ui/number-input.md +10 -0
  124. package/docs/components/ui/pagination.md +10 -0
  125. package/docs/components/ui/popover.md +9 -0
  126. package/docs/components/ui/progress-ring.md +7 -0
  127. package/docs/components/ui/progress.md +7 -0
  128. package/docs/components/ui/radio.md +27 -2
  129. package/docs/components/ui/search-input.md +8 -0
  130. package/docs/components/ui/segmented-control.md +21 -6
  131. package/docs/components/ui/select.md +14 -3
  132. package/docs/components/ui/separator.md +7 -0
  133. package/docs/components/ui/sheet.md +10 -0
  134. package/docs/components/ui/sidebar.md +30 -0
  135. package/docs/components/ui/skeleton.md +7 -0
  136. package/docs/components/ui/slider.md +15 -1
  137. package/docs/components/ui/spinner.md +7 -0
  138. package/docs/components/ui/split-button.md +11 -0
  139. package/docs/components/ui/stack.md +8 -0
  140. package/docs/components/ui/stat-card.md +9 -0
  141. package/docs/components/ui/status-dot.md +9 -0
  142. package/docs/components/ui/stepper.md +8 -0
  143. package/docs/components/ui/switch.md +8 -0
  144. package/docs/components/ui/table.md +8 -0
  145. package/docs/components/ui/tabs.md +10 -2
  146. package/docs/components/ui/text.md +9 -4
  147. package/docs/components/ui/textarea.md +7 -1
  148. package/docs/components/ui/toast.md +9 -0
  149. package/docs/components/ui/toaster.md +8 -0
  150. package/docs/components/ui/toggle-group.md +13 -4
  151. package/docs/components/ui/toggle.md +10 -2
  152. package/docs/components/ui/tooltip.md +11 -1
  153. package/docs/components/ui/tree-view.md +12 -0
  154. package/docs/components/ui/visually-hidden.md +7 -0
  155. package/docs/recipes/customize-brand.md +216 -0
  156. package/docs/recipes/index.md +51 -0
  157. package/docs/recipes/install-astro.md +178 -0
  158. package/docs/recipes/install-next-app-router.md +230 -0
  159. package/docs/recipes/install-next-pages.md +123 -0
  160. package/docs/recipes/install-remix.md +171 -0
  161. package/docs/recipes/install-tanstack-start.md +143 -0
  162. package/docs/recipes/install-vite.md +170 -0
  163. package/docs/recipes/server-components.md +209 -0
  164. package/docs/recipes/troubleshoot.md +217 -0
  165. package/llms-full.txt +1133 -88
  166. package/llms.txt +33 -16
  167. package/package.json +45 -35
  168. package/dist/composed/responsive-overlay.d.ts +0 -23
  169. package/dist/composed/responsive-overlay.d.ts.map +0 -1
  170. package/dist/composed/responsive-overlay.js +0 -40
  171. package/dist/composed/responsive-overlay.js.map +0 -1
  172. package/dist/hooks/use-toast.d.ts +0 -17
  173. package/dist/hooks/use-toast.d.ts.map +0 -1
  174. package/dist/hooks/use-toast.js +0 -3
  175. package/dist/tailwind/index.cjs +0 -41
  176. package/dist/tailwind/index.d.ts +0 -2
  177. package/dist/tailwind/index.d.ts.map +0 -1
  178. package/dist/tailwind/index.js +0 -2
  179. package/dist/tailwind/preset.d.ts +0 -25
  180. package/dist/tailwind/preset.d.ts.map +0 -1
  181. package/dist/tailwind/preset.js +0 -17
  182. package/dist/tailwind/preset.js.map +0 -1
  183. package/docs/components/composed/responsive-overlay.md +0 -34
package/llms-full.txt CHANGED
@@ -5,7 +5,7 @@
5
5
  > All variant values and props verified from source CVA definitions.
6
6
  >
7
7
  > Package: @devalok/shilp-sutra
8
- > Version: 0.37.0
8
+ > Version: 0.38.0
9
9
  >
10
10
  > **If you are an AI agent reading this file top-to-bottom:** the Setup
11
11
  > section below is authoritative. If any later per-component doc or a
@@ -39,7 +39,7 @@ pnpm add sonner
39
39
  transpilePackages: ['@devalok/shilp-sutra', '@devalok/shilp-sutra-brand'],
40
40
  ```
41
41
 
42
- **Do NOT** import `@devalok/shilp-sutra/tailwind` in a `tailwind.config.ts`. That export is a deprecated no-op stub and logs a dev-mode `console.warn`. It is scheduled for removal in 0.38.
42
+ **Do NOT** import `@devalok/shilp-sutra/tailwind`. That export was removed in 0.38.0. Use the CSS-first setup (`@import "tailwindcss"; @import "@devalok/shilp-sutra/css";`) — no `tailwind.config.ts` is required.
43
43
 
44
44
  ### Peer dependencies (0.37.0)
45
45
 
@@ -220,9 +220,19 @@ Note: getFormFieldA11y() was removed in favor of useFormField() hook.
220
220
  </Accordion>
221
221
  ```
222
222
 
223
+ ## Composability
224
+ - Built on Radix Accordion — accepts `value`/`defaultValue`/`onValueChange` matching the `type` discriminated union.
225
+ - **`type="single"`** — value is a string (which item is open). Only one item open at a time. Pair with `collapsible` to allow closing the current item by re-clicking it.
226
+ - **`type="multiple"`** — value is a string[] (which items are open). Multiple can be open at once.
227
+ - **AccordionTrigger chevron:** Inline SVG that auto-rotates via the `data-state` attribute Radix sets (`open` / `closed`). The `chevronPosition` prop controls render order inside the trigger (left / right) — rotation still works from either position.
228
+ - **AccordionContent:** Uses Radix CSS custom properties (`--radix-accordion-content-height`) for open/close height animation. Framer Motion handles the fade. Don't wrap AccordionContent children in additional motion components — doubles the animation.
229
+ - **Inline, not portalled** — parent `overflow: hidden` WILL clip an open accordion. Keep accordions out of tight overflow contexts or use `overflow-visible` on the container.
230
+
223
231
  ## Gotchas
224
232
  - type is REQUIRED — omitting it causes runtime error
225
233
  - collapsible only works with type="single"
234
+ - Each AccordionItem needs a unique `value` — duplicates silently break toggling
235
+ - Don't put focusable elements inside AccordionTrigger — the trigger IS the focusable button, nested focusables break screen reader navigation
226
236
 
227
237
  ## Changes
228
238
  ### v0.29.0
@@ -246,14 +256,15 @@ Note: getFormFieldA11y() was removed in favor of useFormField() hook.
246
256
  - Category: ui
247
257
 
248
258
  ## Props
249
- variant: "subtle" | "filled" | "outline"
259
+ variant: "subtle" | "solid" | "outline"
250
260
  color: "info" | "success" | "warning" | "error" | "neutral"
261
+ size: "sm" | "md" | "lg"
251
262
  title: string (optional)
252
263
  onDismiss: () => void (optional, shows X button when provided)
253
264
  children: ReactNode (body text)
254
265
 
255
266
  ## Defaults
256
- variant="subtle", color="info"
267
+ variant="subtle", color="info", size="md"
257
268
 
258
269
  ## Example
259
270
  ```jsx
@@ -262,6 +273,13 @@ Note: getFormFieldA11y() was removed in favor of useFormField() hook.
262
273
  </Alert>
263
274
  ```
264
275
 
276
+ ## Composability
277
+ - **Flat, not compound** — unlike Dialog/Card, there's no `<AlertTitle>` / `<AlertDescription>`. Use the `title` prop for the heading and `children` for the body text. This simplifies the API and prevents composition traps (missing title, out-of-order header parts).
278
+ - **Auto-icon by color:** Icon is selected automatically from color (info→circle, success→check, warning→triangle, error→alert). Pass a custom `icon` prop to override.
279
+ - **Role=alert:** Announces assertively to screen readers. Don't stack multiple Alerts in a row — the last one wins.
280
+ - **Dismissal pattern:** Pass `onDismiss` for user-dismissable alerts; the library tracks the exit animation before calling back. For persistent alerts (system state), omit `onDismiss`.
281
+ - **Not for transient messages** — use Toast for transient/time-limited notifications and Banner for page-level announcements. Alert is inline, in-flow, block-level.
282
+
265
283
  ## Gotchas
266
284
  - NOT a compound component — use title prop, NOT <AlertTitle>
267
285
  - DO NOT use variant="destructive" — use color="error"
@@ -269,6 +287,9 @@ Note: getFormFieldA11y() was removed in favor of useFormField() hook.
269
287
  - Icon is auto-selected by color (info=circle, success=check, warning=triangle, error=alert)
270
288
 
271
289
  ## Changes
290
+ ### v0.38.0
291
+ - **Removed** (BREAKING) `variant="filled"` deprecated alias. Use `variant="solid"`.
292
+
272
293
  ### v0.31.0
273
294
  - **Added** `size` prop: `sm | md | lg`. Default `md` (non-breaking).
274
295
 
@@ -329,8 +350,17 @@ Note: getFormFieldA11y() was removed in favor of useFormField() hook.
329
350
  </AlertDialog>
330
351
  ```
331
352
 
353
+ ## Composability
354
+ - Built on Radix AlertDialog — like Dialog but **non-dismissible** by clicking outside or pressing Escape. The user must choose Cancel or Action.
355
+ - Same portal + trigger + asChild pattern as Dialog.
356
+ - `AlertDialogAction` and `AlertDialogCancel` are semantically distinct from generic buttons: they auto-close the dialog on click. Use them even if you wrap them around a styled Button via `asChild` so the close behavior stays wired.
357
+ - **Focus management:** Initial focus lands on `AlertDialogCancel` (the safe default) — destructive confirmation is always one tab away.
358
+ - Use AlertDialog for destructive / irreversible actions; use Dialog for everything else.
359
+
332
360
  ## Gotchas
333
- - AlertDialogAction does NOT have color="error" styling — add it yourself via className or wrap a Button
361
+ - AlertDialogAction does NOT have color="error" styling — add it yourself via className or wrap a Button with `asChild`
362
+ - Do NOT add a close-on-outside-click handler — the non-dismissible behavior is the whole point
363
+ - AlertDialogCancel receives initial focus; don't flip the convention
334
364
 
335
365
  ## Changes
336
366
  ### v0.19.1
@@ -365,8 +395,15 @@ Note: getFormFieldA11y() was removed in favor of useFormField() hook.
365
395
  </AspectRatio>
366
396
  ```
367
397
 
398
+ ## Composability
399
+ - Radix AspectRatio primitive — pure layout wrapper, no context, no cascade.
400
+ - **Child fill pattern:** Its child is absolutely positioned to fill. Pair with `object-cover` / `object-contain` on `<img>`/`<video>` and `w-full h-full` for anything else.
401
+ - Useful as a responsive-image container, a chart area (pair with Chart components), or a placeholder frame for media skeletons.
402
+ - Works inside Card, any layout component, or flex/grid containers — nothing to configure.
403
+
368
404
  ## Gotchas
369
405
  - ratio is a number, not a string — use `16/9` not `"16/9"`
406
+ - The child must be size-fluid (`w-full h-full` or absolutely positioned) — elements with intrinsic size escape the aspect-ratio box
370
407
 
371
408
  ## Changes
372
409
  ### v0.1.0
@@ -400,6 +437,13 @@ Note: getFormFieldA11y() was removed in favor of useFormField() hook.
400
437
  />
401
438
  ```
402
439
 
440
+ ## Composability
441
+ - **Autocomplete vs Combobox:** Autocomplete allows free-text input (users can type anything); Combobox enforces selection from the list. Pick by whether "off-list" values are valid (e.g. city field that accepts typos → Autocomplete; tag picker from a fixed vocabulary → Combobox).
442
+ - **Value shape is an object** (`{ value, label }`), not a plain string — this preserves label/value decoupling for display-vs-storage.
443
+ - **FormField:** Does NOT auto-consume FormField state. Set explicit error styling via className if needed.
444
+ - **Portal rendering:** Dropdown portals to body with z-popover (1400) — stacks above Dialog/Sheet.
445
+ - **Keyboard:** ArrowDown/Up navigate suggestions, Enter selects, Esc closes. Typeahead is the input's native filtering.
446
+
403
447
  ## Gotchas
404
448
  - Allows free-text input (no forced selection) — use Combobox for forced selection
405
449
  - value is an object { value, label }, NOT just a string
@@ -452,10 +496,20 @@ Note: getFormFieldA11y() was removed in favor of useFormField() hook.
452
496
  </Avatar>
453
497
  ```
454
498
 
499
+ ## Composability
500
+ - **Two internal contexts cascade to AvatarFallback:**
501
+ - `AvatarSizeContext` — carries the Avatar's `size` so AvatarFallback's font size scales without re-specifying (xs → `text-[9px]`, sm → `text-ds-xs`, md → `text-ds-sm`, lg → `text-ds-md`, xl → `text-ds-lg`)
502
+ - `AvatarShapeContext` — carries `shape` so AvatarFallback inherits the same border-radius (critical for `shape="square"` / `shape="rounded"` — without context the fallback would always be `rounded-ds-full` regardless of parent)
503
+ - **Status + Ring + Badge are independent** — you can stack all three on one Avatar. They render in separate layers so they don't conflict visually.
504
+ - **Use with AvatarGroup:** AvatarGroup applies its own `size` to children. If your Avatar has an explicit `size`, it overrides AvatarGroup's default — keep it consistent across the group.
505
+ - **AvatarFallback colorSeed:** Deterministic background color from the seed string — identical user IDs produce identical backgrounds across renders. Useful for user-identifying avatars without photos.
506
+ - **Ring offset color:** The ring renders with `ring-offset` matching the current surface — it assumes the Avatar sits on `bg-surface-raised`. On other backgrounds, add a ring-offset className to compensate.
507
+
455
508
  ## Gotchas
456
509
  - Status dot renders with role="img" and aria-label (accessible, not decorative)
457
510
  - Dot size scales automatically with avatar size
458
511
  - AvatarFallback font size scales automatically with avatar size (via AvatarSizeContext)
512
+ - AvatarFallback shape inherits from Avatar (via AvatarShapeContext) — setting `rounded-*` on AvatarFallback directly conflicts with the context
459
513
  - Ring offset color matches the surface context — ensure it sits on the expected background
460
514
  - Badge is hidden when value is `0` (falsy); use `"dot"` for presence without a count
461
515
  - Online status dot pulses with a CSS animation
@@ -513,6 +567,18 @@ Note: getFormFieldA11y() was removed in favor of useFormField() hook.
513
567
  <Badge color="custom" style={{ '--badge-color': '#8b5cf6' }}>Custom</Badge>
514
568
  ```
515
569
 
570
+ ## Composability
571
+ - **Badge.Group** (BadgeGroup) wraps a set of badges with overflow collapse (`max` + "+N" indicator) and gap control. Use it for filter chips, tag lists, user cluster avatars-equivalent.
572
+ - **Badge.Indicator** (BadgeIndicator) attaches a count / dot overlay to ANY child (icons, avatars, buttons). It's positioning-only — doesn't style the child.
573
+ - **Interactive modes:**
574
+ - `onClick` alone → renders as `<button>` (standard interactive badge)
575
+ - `onDismiss` alone → renders as `<span>` with an inner X button
576
+ - Both → renders as `<div role="button">` (avoids invalid nested buttons) — the X button inside handles its own click isolation
577
+ - **asChild:** Use with router Links for nav-style badges (`<Badge asChild><Link href="/tags/react">React</Link></Badge>`).
578
+ - **Custom colors via CSS variables:** Set `color="custom"` + inline style `style={{ '--badge-color': '#...' }}`. For solid variant, also set `--badge-fg-color` for the foreground.
579
+ - **Icon slots auto-size via IconProvider** — same cascade as Button. Don't set explicit size on nested `<Icon>`.
580
+ - **Chip (deprecated)** was merged into Badge — use Badge with `onClick` and `selected` for the old Chip use case.
581
+
516
582
  ## Gotchas
517
583
  - DO NOT use variant="destructive" — use variant="solid" color="error"
518
584
  - Badge is now interactive when `onClick` is provided (renders as `<button>`)
@@ -587,6 +653,13 @@ Note: getFormFieldA11y() was removed in favor of useFormField() hook.
587
653
  {/* Renders: React, TypeScript, Tailwind, +2 */}
588
654
  ```
589
655
 
656
+ ## Composability
657
+ - **Wrapper for Badge children** — doesn't style children, just lays them out and optionally collapses overflow into a "+N" indicator.
658
+ - **Server-safe** — can render in RSC trees (children still need to be server-safe themselves).
659
+ - **Overflow indicator is a Badge** — picks `variant="outline" color="neutral"` and the `size` prop on BadgeGroup. Child badges keep their own variant/color/size.
660
+ - **Click-to-reveal pattern:** Pair `onOverflowClick` with a Popover / Sheet / Dialog that shows the full list. Without the handler, the overflow badge is inert (decorative count).
661
+ - Doesn't accept arbitrary children — only Badges. Non-Badge children render but may look off (no gap rhythm matching).
662
+
590
663
  ## Gotchas
591
664
  - Overflow badge is always `variant="outline" color="neutral"`
592
665
  - `size` only applies to the overflow badge — child badges keep their own size
@@ -627,6 +700,13 @@ Note: getFormFieldA11y() was removed in favor of useFormField() hook.
627
700
  </Badge.Indicator>
628
701
  ```
629
702
 
703
+ ## Composability
704
+ - **Overlay wrapper** — takes any child and overlays a positioned count/dot at the specified corner. The child must be inline-friendly (anything that renders in a span-wrapped position: relative context).
705
+ - **Common pairings:** IconButton (notification bell with count), Avatar (online presence dot), Button (cart with item count), tabs (unread indicator).
706
+ - **Invisible for layout stability:** Use `invisible={true}` to keep the child's position consistent when the badge should disappear — avoids layout shift as counts appear/disappear.
707
+ - **showZero vs hide-on-zero:** By default `count={0}` hides the indicator. Set `showZero={true}` for use cases where "zero" is meaningful (scores, pending items that should display 0 explicitly).
708
+ - **Dot vs count:** `dot={true}` is a presence-only pulse indicator (online, new activity); count is a numeric badge. They're mutually exclusive — setting both uses dot.
709
+
630
710
  ## Gotchas
631
711
  - Wraps children in `position: relative` span — the indicator is absolutely positioned
632
712
  - Animation uses spring physics, respects `prefers-reduced-motion`
@@ -645,7 +725,6 @@ Note: getFormFieldA11y() was removed in favor of useFormField() hook.
645
725
  ## Props
646
726
  color: "info" | "success" | "warning" | "error" | "neutral"
647
727
  actions: ReactNode (optional action slot for one or more buttons; wraps on narrow viewports)
648
- action: ReactNode (DEPRECATED — use `actions` instead)
649
728
  onDismiss: () => void (optional, shows X button)
650
729
  children: ReactNode (message text)
651
730
 
@@ -659,12 +738,22 @@ Note: getFormFieldA11y() was removed in favor of useFormField() hook.
659
738
  </Banner>
660
739
  ```
661
740
 
741
+ ## Composability
742
+ - **Banner vs Alert vs Toast:** Banner is full-width, top-of-page / top-of-region announcements (maintenance, quota, trial-expiring). Alert is inline in-flow. Toast is transient floating notifications. Pick by where the message lives in the visual hierarchy.
743
+ - **actions slot:** Pass one or more `<Button>` elements — the container flex-wraps on narrow viewports so actions drop below the message rather than squeezing horizontally.
744
+ - **Role=alert** announces assertively. Same constraint as Alert — don't stack multiple Banners.
745
+ - **Dismissal timing:** `onDismiss` fires AFTER the exit animation completes, not on click. Use it to remove the banner from state only after it's visually gone.
746
+ - Works inside Shell layouts (TopBar / Sidebar / main content) — usually sits above main content, below the TopBar.
747
+
662
748
  ## Gotchas
663
749
  - Banner is full-width (spans container). Alert is inline.
664
750
  - Renders role="alert" automatically
665
751
  - `onDismiss` fires after the exit animation completes, not immediately on dismiss button click
666
752
 
667
753
  ## Changes
754
+ ### v0.38.0
755
+ - **Removed** (BREAKING) deprecated singular `action` prop. Use `actions={[...]}` (plural).
756
+
668
757
  ### v0.20.0
669
758
  - **Added** `actions` prop (plural) for multiple action buttons with mobile-friendly flex-wrap
670
759
  - **Deprecated** `action` prop — use `actions` instead (both still work; `actions` takes priority)
@@ -706,8 +795,16 @@ Note: getFormFieldA11y() was removed in favor of useFormField() hook.
706
795
  </Breadcrumb>
707
796
  ```
708
797
 
798
+ ## Composability
799
+ - **Compound structure only** — no context cascade, each part is standalone. The semantic shape (`<nav>` → `<ol>` → `<li>`) is what matters for a11y, not any internal state.
800
+ - **BreadcrumbLink vs BreadcrumbPage:** Use BreadcrumbLink for everything except the current page; BreadcrumbPage for the current page (not clickable, renders without href, announced as "current page" via aria-current).
801
+ - **Router integration:** Use `<BreadcrumbLink asChild><NextLink href="/x">...</NextLink></BreadcrumbLink>` (Radix Slot pattern).
802
+ - **Truncation:** For long paths, use BreadcrumbEllipsis between the first and last items instead of showing all. Pair with a DropdownMenu (open the ellipsis to show hidden intermediate items) if you need to expose them.
803
+ - **PageHeader (composed) auto-builds Breadcrumb** from its `breadcrumbs` prop — use that higher-level component for page-top breadcrumbs and save this component for inline or custom positioning.
804
+
709
805
  ## Gotchas
710
806
  - Use BreadcrumbPage for the current (non-clickable) page, BreadcrumbLink for navigable items
807
+ - Don't wrap BreadcrumbPage in a link — it's the current location by definition
711
808
 
712
809
  ## Changes
713
810
  ### v0.18.0
@@ -751,6 +848,15 @@ Note: getFormFieldA11y() was removed in favor of useFormField() hook.
751
848
  <Button variant="soft" color="warning" size="compact-sm" shape="pill">Overdue</Button>
752
849
  ```
753
850
 
851
+ ## Composability
852
+ - **ButtonGroup context consumption:** When nested inside `<ButtonGroup>`, Button auto-inherits variant/color/size/weight/shape/disabled. Explicit props on the individual Button override. The context also drives position-aware border-radius (first/middle/last within an attached group).
853
+ - **IconProvider cascade:** Icons in `startIcon`/`endIcon` auto-size via IconProvider per the button size (xs→sm, sm→sm, md→md, lg→md, icon-xs→xs, icon-lg→lg). Don't pass explicit `size` to `<Icon>` inside Button.
854
+ - **asChild for router links:** `<Button asChild><Link href="/foo">...</Link></Button>` transfers Button's styling to the Link while preserving navigation semantics. Required for Next.js `<Link>` / react-router `<Link>`.
855
+ - **onClickAsync state machine:** Overrides onClick. Auto-cycles `idle → loading (aria-busy, spinner) → success (checkmark) → idle` on resolve; `loading → error (X mark) → idle` on reject. Duration controlled by `asyncFeedbackDuration` (1500ms default). Auto-activates `processing='working'` during loading — marching-ants border keeps users visually aware.
856
+ - **Processing vs loading:** `loading` is a short async state (shows spinner, blocks clicks). `processing` is a longer-running state (marching ants border, may or may not block clicks based on `processingDisabled`). Use onClickAsync for simple request cases; use processing explicitly for long-running background operations.
857
+ - **DevalokGrain children:** Grain elements are auto-extracted and rendered as direct button children for absolute positioning — lets you layer grain texture on solid-variant buttons without breaking layout.
858
+ - **Prefer `variant="soft"` over `variant="outline"` for secondary actions** (see Gotchas for details). This is a design-system-wide convention.
859
+
754
860
  ## Gotchas
755
861
  - **Prefer `variant="soft"` over `variant="outline"` for secondary actions.** Soft (tinted step-3 bg, step-11 text) is the Devalok-recommended default — it feels warmer and brand-consistent. Use `outline` only when soft's tint would disappear (on colored/surface-raised bg), in toolbar/icon-dense contexts, or when you need outline's stronger hierarchy next to a primary action.
756
862
  - DO NOT use variant="destructive" — use variant="solid" color="error"
@@ -816,24 +922,44 @@ Note: getFormFieldA11y() was removed in favor of useFormField() hook.
816
922
  - Category: ui
817
923
 
818
924
  ## Props
819
- variant: ButtonProps['variant'] (propagated to children)
925
+ variant: ButtonProps['variant'] (propagated to children via context)
820
926
  color: ButtonProps['color'] (propagated to children)
821
927
  size: ButtonProps['size'] (propagated to children)
822
- orientation: "horizontal" | "vertical" (default: "horizontal")
928
+ weight: ButtonProps['weight'] (propagated to children)
929
+ shape: ButtonProps['shape'] (propagated to children)
930
+ disabled: boolean (propagates to all children)
931
+ orientation: "horizontal" | "vertical"
932
+ attached: boolean (true = buttons visually merge with shared borders; false = spaced apart via gap)
933
+ fullWidth: boolean (group stretches to parent width; children stretch equally)
823
934
 
824
935
  ## Defaults
825
- orientation="horizontal"
936
+ orientation="horizontal", attached=true
826
937
 
827
938
  ## Example
828
939
  ```jsx
829
940
  <ButtonGroup variant="outline" size="sm">
830
941
  <Button>Bold</Button>
831
942
  <Button>Italic</Button>
943
+ <Button>Underline</Button>
944
+ </ButtonGroup>
945
+
946
+ {/* Spaced, not attached */}
947
+ <ButtonGroup attached={false} variant="soft">
948
+ <Button>Save</Button>
949
+ <Button>Cancel</Button>
832
950
  </ButtonGroup>
833
951
  ```
834
952
 
953
+ ## Composability
954
+ - Every Button child reads variant/color/size/weight/shape/disabled from ButtonGroup context. Explicit props on individual children override context.
955
+ - **Position-aware border radius:** Child Buttons read their position in the group (first / middle / last) and apply appropriate corner radii inline. Works for both horizontal and vertical orientations.
956
+ - **Focus z-index isolation:** The focused button rises above its siblings so the focus ring isn't clipped by adjacent borders.
957
+ - **Tonal dividers:** For solid/soft/ghost variants without visible borders, ButtonGroup injects subtle divider elements between children.
958
+ - **Works with SplitButton:** `<SplitButton>` inside a `<ButtonGroup>` inherits the same context and position rules.
959
+
835
960
  ## Gotchas
836
- - Children can override variant/size individually
961
+ - Children can override variant/size individually — context is a default, not a lock
962
+ - `attached={false}` disables the position-aware border radius (children use their default corners)
837
963
 
838
964
  ## Changes
839
965
  ### v0.33.0
@@ -862,6 +988,11 @@ Internal component that renders the processing animation overlay for Button. Con
862
988
 
863
989
  See `Button` docs for the public API.
864
990
 
991
+ ## Composability
992
+ - **Internal only — do NOT import or use directly.** The Button component owns the lifecycle of this overlay.
993
+ - Drives Button's `processing` prop rendering. If you're building a custom button and want similar marching-ants feedback, copy the SVG/animation pattern — don't reach into this component.
994
+ - Listed here for reference so consumers grep'ing for "processing" find the behavior origin.
995
+
865
996
  ## Changes
866
997
  ### v0.29.0
867
998
  - **Added** Initial release — marching ants processing overlay (SVG dashed rect with animated stroke-dashoffset). Speed tiers: ambient (3s), working (2s), urgent (1s). Color maps to step-11 tokens for visibility on all variants.
@@ -873,18 +1004,22 @@ See `Button` docs for the public API.
873
1004
 
874
1005
  ## Props
875
1006
  variant: "default" | "elevated" | "outline" | "flat"
1007
+ color: "default" | "accent" | "error" | "success" | "warning" | "info" | "neutral" (border accent color)
1008
+ size: "sm" | "md" | "lg" (padding — propagated to CardHeader/CardContent/CardFooter via context)
876
1009
  interactive: boolean (enables hover shadow lift + pointer cursor)
1010
+ accent: "left" | "top" | "right" | "bottom" (render a colored accent bar on the specified edge)
1011
+ accentColor: "default" | "accent" | "error" | "success" | "warning" | "info" (color of the accent bar; default maps to accent-9)
877
1012
 
878
1013
  ## Compound Components
879
1014
  Card (root)
880
- CardHeader
1015
+ CardHeader ← inherits size from Card context
881
1016
  CardTitle
882
1017
  CardDescription
883
- CardContent
884
- CardFooter
1018
+ CardContent ← inherits size from Card context
1019
+ CardFooter ← inherits size from Card context
885
1020
 
886
1021
  ## Defaults
887
- variant="default"
1022
+ variant="default", color="default", size="md"
888
1023
 
889
1024
  ## Example
890
1025
  ```jsx
@@ -897,8 +1032,16 @@ See `Button` docs for the public API.
897
1032
  </Card>
898
1033
  ```
899
1034
 
1035
+ ## Composability
1036
+ - **Size cascades through context** — Card's `size` prop sets padding on CardHeader, CardContent, and CardFooter via `CardSizeContext`. Don't set padding classes on sub-components directly; override via `className` if needed.
1037
+ - **Not a compound state machine** — Card, CardHeader, CardTitle, etc. are purely structural. No open/close state.
1038
+ - **Accent bar is independent:** The `accent` / `accentColor` props render a decorative colored edge bar (absolutely positioned, `aria-hidden`). Works alongside `color` (which tints the border) — the two can stack for layered emphasis.
1039
+ - **Interactive cards:** Set `interactive={true}` + `onClick` for clickable cards (entire surface becomes the button). Add `aria-label` on the Card root when there's no visible heading. For complex multi-action cards, prefer standard Card with explicit buttons inside.
1040
+ - **ContentCard (composed)** is a higher-level wrapper with built-in header/footer slots and title/actions — use it for list-row-style cards; use Card directly for custom layouts.
1041
+
900
1042
  ## Gotchas
901
1043
  - Use `interactive` prop for clickable cards — adds hover lift and pointer cursor
1044
+ - Don't override CardHeader/CardContent/CardFooter padding via className if you want the size cascade to work — set size on Card instead
902
1045
 
903
1046
  ## Changes
904
1047
  ### v0.31.0
@@ -947,9 +1090,18 @@ import { BarChart } from '@devalok/shilp-sutra/ui/charts'
947
1090
  <BarChart data={salesData} />
948
1091
  ```
949
1092
 
1093
+ ## Composability
1094
+ - **ChartContainer wraps all chart primitives** — provides responsive sizing (fills parent width, configurable aspect ratio) and a consistent background/padding. Always render charts inside a ChartContainer unless you're building a custom container.
1095
+ - **Legend is a sibling, not a child** — render `<Legend items={...} />` next to your chart (above/below), not nested. Use `onHover` / `onClick` handlers in Legend + chart to sync hover highlights.
1096
+ - **Data shape is chart-specific** — BarChart wants `[{ label, value }]`, LineChart wants `[{ x, y, series }]`, etc. Each chart's props doc spells out the shape; don't assume cross-chart compatibility.
1097
+ - **Sparkline is the only chart safe to use inline** — it's a minimal 1-line SVG, no axes, no legend. Use it inside StatCard, tables, tight cells. For anything larger, reach for the full chart types.
1098
+ - **Entrance animations** — all charts use Framer Motion spring-based entry. Animations respect `prefers-reduced-motion`.
1099
+ - **Accessibility:** Charts render role="img" with aria-label from the chart title. For dense data, provide a hidden accessible table fallback manually (WCAG requirement for complex charts).
1100
+
950
1101
  ## Gotchas
951
1102
  - Barrel-isolated since v0.5.0 — must use `@devalok/shilp-sutra/ui/charts`, NOT the `ui` barrel
952
1103
  - Requires D3 as an optional peer dependency
1104
+ - Chart components do NOT auto-generate accessible data tables — add one manually for WCAG compliance on complex charts
953
1105
 
954
1106
  ## Changes
955
1107
  ### v0.18.0
@@ -1161,6 +1313,15 @@ Animated bouncing dots with a text description of who is typing.
1161
1313
 
1162
1314
  ---
1163
1315
 
1316
+ ## Composability
1317
+ - **Chat is a kit of 7 primitives, not a monolithic ChatWindow.** Compose MessageList (scroll container) + Message/SystemMessage (row variants) + DateSeparator/UnreadSeparator (visual dividers) + MessageInput (composer) + TypingIndicator (presence).
1318
+ - **Message is a compound component** — `Message.Avatar`, `Message.Content`, `Message.Author`, `Message.Body`, `Message.Reactions`, `Message.Actions`, `Message.Action`. Arrange them to match your design; most layouts only need Avatar + Content (with Author + Body inside).
1319
+ - **Message.Actions reveal on hover** via `group-hover/message` — ONLY works when Actions is nested inside a Message root. If you want a custom action bar, do it differently.
1320
+ - **MessageList ARIA:** `role="log" + aria-live="polite"` — screen readers announce new messages automatically. Don't manually add aria-live to children.
1321
+ - **grouped mode:** For consecutive messages from the same user, set `grouped={true}` to hide the avatar and author (typical chat UI pattern). Logic for "same user as previous" is consumer-side.
1322
+ - **MessageInput is specifically for chat** — auto-resizing textarea (up to 160px), Enter-to-send, Shift+Enter-for-newline, streaming support. For richer composition (formatting, attachments), use RichChatInput from composed (built on top of this + TipTap).
1323
+ - **TypingIndicator** accepts multiple users — handles pluralization of the "is/are typing" label automatically.
1324
+
1164
1325
  ## Gotchas
1165
1326
  - MessageList uses `role="log"` with `aria-live="polite"` — screen readers announce new messages
1166
1327
  - Message entrance animations use Framer Motion springs — AnimatePresence wraps children in MessageList
@@ -1183,20 +1344,34 @@ Animated bouncing dots with a text description of who is typing.
1183
1344
  ## Props
1184
1345
  checked: boolean | "indeterminate"
1185
1346
  onCheckedChange: (checked: boolean | "indeterminate") => void
1347
+ size: "sm" | "md" | "lg"
1186
1348
  error: boolean (shows red border)
1187
1349
  indeterminate: boolean (overrides checked, shows dash icon)
1188
1350
  disabled: boolean
1189
1351
 
1190
1352
  ## Defaults
1191
- none
1353
+ size="md"
1192
1354
 
1193
1355
  ## Example
1194
1356
  ```jsx
1195
1357
  <Checkbox checked={agreed} onCheckedChange={(v) => setAgreed(v === true)} />
1358
+
1359
+ {/* With label */}
1360
+ <label htmlFor="terms" className="flex items-center gap-ds-02">
1361
+ <Checkbox id="terms" checked={agreed} onCheckedChange={(v) => setAgreed(v === true)} />
1362
+ I agree to the terms
1363
+ </label>
1196
1364
  ```
1197
1365
 
1366
+ ## Composability
1367
+ - **FormField integration:** Unlike Input/Textarea, Checkbox does NOT auto-consume FormField state — the `error` prop must be set explicitly. (Form-library convention differs for checkboxes since they often don't share the same visual grouping.)
1368
+ - **Labels:** Checkbox doesn't carry its own label — pair it with `<Label htmlFor="x" />` + `<Checkbox id="x" />`, or wrap both in a `<label>` element. Screen readers rely on that association.
1369
+ - **Controlled vs uncontrolled:** Pass `checked` + `onCheckedChange` for controlled, `defaultChecked` for uncontrolled. Don't mix.
1370
+
1198
1371
  ## Gotchas
1199
1372
  - indeterminate overrides checked visually
1373
+ - Checkbox does NOT auto-consume FormField error state — pass `error` explicitly if using inside a FormField
1374
+ - WCAG 2.5.8 minimum target size met at md (24px); sm (20px) may fail if not paired with enough label hit area
1200
1375
 
1201
1376
  ## Changes
1202
1377
  ### v0.22.0
@@ -1229,8 +1404,15 @@ Animated bouncing dots with a text description of who is typing.
1229
1404
  <Code variant="block">{`const x = 1;\nconsole.log(x);`}</Code>
1230
1405
  ```
1231
1406
 
1407
+ ## Composability
1408
+ - **Server-safe.** No context, no cascade.
1409
+ - Inline variant composes inside Text, paragraphs, list items, table cells — wherever body text goes.
1410
+ - Block variant is a `<pre><code>` pair with overflow-x scroll; pair with a dark background Card or MarkdownViewer for long snippets.
1411
+ - Not a syntax-highlighted code viewer — for that, use RichTextEditor's code block or a dedicated library. Code here is typographic emphasis, not lexer-aware rendering.
1412
+
1232
1413
  ## Gotchas
1233
1414
  - "block" renders as `<pre><code>`, "inline" renders as `<code>`
1415
+ - Block variant doesn't syntax-highlight — it's a styled pre tag, nothing more
1234
1416
 
1235
1417
  ## Changes
1236
1418
  ### v0.1.1
@@ -1265,8 +1447,17 @@ Animated bouncing dots with a text description of who is typing.
1265
1447
  </Collapsible>
1266
1448
  ```
1267
1449
 
1450
+ ## Composability
1451
+ - Radix Collapsible primitive — smaller than Accordion (no grouping/value matching, just open/close).
1452
+ - **When to use:** A single show/hide section where the state is local (e.g. "Advanced options" inside a form, expandable row detail, show-more). For multi-section with mutual exclusion, use Accordion.
1453
+ - **Inline rendering:** CollapsibleContent is NOT portalled — it expands inline and pushes sibling content down. Respects `overflow` rules of the parent.
1454
+ - **No built-in chevron** — unlike AccordionTrigger, CollapsibleTrigger is bare. Add your own icon + rotate via `data-state` attribute Radix sets (`[data-state=open]:rotate-180`).
1455
+ - **Animation:** Height transition via `animate-collapsible-down/up` keyframes + Radix `--radix-collapsible-content-height` CSS custom property.
1456
+
1268
1457
  ## Gotchas
1269
1458
  - Standard Radix Collapsible API
1459
+ - No auto-chevron — add one manually if you want the affordance
1460
+ - Not portalled — clipped by parent `overflow: hidden`
1270
1461
 
1271
1462
  ## Changes
1272
1463
  ### v0.13.0
@@ -1341,6 +1532,14 @@ Animated bouncing dots with a text description of who is typing.
1341
1532
  <ColorInput value={color} onChange={setColor} showPicker={false} />
1342
1533
  ```
1343
1534
 
1535
+ ## Composability
1536
+ - **Built on Popover internally** — trigger opens a portal-rendered picker panel. z-popover (1400) stacking.
1537
+ - **variant="default" vs "inline":** Default variant is an input-style trigger with gradient swatch + hex label (fits in forms). Inline variant IS the selected color — the entire trigger takes the color as its background with contrast-aware text. Use inline for color-tags in chat, lists, or tight toolbar UIs.
1538
+ - **Controlled or uncontrolled:** Works both ways — pass `value` + `onChange` for controlled, or omit and let internal state track.
1539
+ - **Presets are compositional:** Pass `{ hex, label }[]` for named brand colors (accessible, keyboard-navigable). Pass `false` to hide the preset strip entirely (picker-only mode).
1540
+ - **react-colorful** is bundled (2.8KB gzipped) — zero additional setup. Picker itself is pointer-based; keyboard users edit via the HEX/RGB/HSL format inputs below.
1541
+ - **FormField:** Not auto-consumed (no `state` prop). Wrap in FormField for label + helper text; style error visuals via className.
1542
+
1344
1543
  ## Gotchas
1345
1544
  - Value must be a 6-character hex string (e.g. "#d33163")
1346
1545
  - Presets accept both `string[]` (backward-compatible) and `{ hex, label }[]` (recommended for accessibility)
@@ -1391,6 +1590,13 @@ Animated bouncing dots with a text description of who is typing.
1391
1590
  <ColorSwatch color="oklch(0.7 0.15 200)" shape="rounded" />
1392
1591
  ```
1393
1592
 
1593
+ ## Composability
1594
+ - **Server-safe, decorative-only primitive.** Accepts any valid CSS color string — hex, rgb, oklch, hsl, named. Pure presentation, no context.
1595
+ - **Usage patterns:** Inline color marker next to a label, preview dot in lists, color-indicator in a category chip, legend swatch for charts.
1596
+ - **`ring={true}`** adds a subtle outline — essential for white/very-light colors that would otherwise disappear on surface-raised backgrounds.
1597
+ - **Interactive color picking:** Use ColorInput (which opens a full picker). ColorSwatch is display-only.
1598
+ - No IconProvider cascade, no FormField consumption — composes freely with anything.
1599
+
1394
1600
  ## Gotchas
1395
1601
  - Color is applied via inline `backgroundColor` style, not a token class — accepts any runtime CSS color string
1396
1602
  - Renders `role="presentation"` — purely decorative, not interactive
@@ -1402,6 +1608,7 @@ Animated bouncing dots with a text description of who is typing.
1402
1608
 
1403
1609
  ## Props
1404
1610
  options: ComboboxOption[] (REQUIRED) — { value: string, label: string, description?: string, icon?: ReactNode, disabled?: boolean }
1611
+ size: "xs" | "sm" | "md" | "lg" (trigger height)
1405
1612
  DISCRIMINATED UNION — type depends on `multiple` flag:
1406
1613
  Single (default): multiple?: false, value?: string, onValueChange: (value: string) => void
1407
1614
  Multiple: multiple: true, value?: string[], onValueChange: (value: string[]) => void
@@ -1409,13 +1616,14 @@ Animated bouncing dots with a text description of who is typing.
1409
1616
  searchPlaceholder: string (default: "Search...")
1410
1617
  emptyMessage: string (default: "No results found")
1411
1618
  disabled: boolean
1412
- triggerClassName: string
1619
+ className: string (wrapper div — the positioning container, NOT the trigger)
1620
+ triggerClassName: string (the actual Popover trigger button)
1413
1621
  accessibleLabel: string (custom aria-label for trigger, falls back to placeholder)
1414
1622
  maxVisible: number (default: 6, max dropdown items before scroll)
1415
1623
  renderOption: (option, selected) => ReactNode
1416
1624
 
1417
1625
  ## Defaults
1418
- placeholder="Select...", searchPlaceholder="Search...", emptyMessage="No results found", maxVisible=6
1626
+ size="md", placeholder="Select...", searchPlaceholder="Search...", emptyMessage="No results found", maxVisible=6
1419
1627
 
1420
1628
  ## Example
1421
1629
  ```jsx
@@ -1428,9 +1636,20 @@ Animated bouncing dots with a text description of who is typing.
1428
1636
  />
1429
1637
  ```
1430
1638
 
1639
+ ## Composability
1640
+ - **Combobox vs Autocomplete vs Select:** Combobox = typeahead search + forced selection (the user picks from filtered options). Autocomplete = typeahead + free text allowed. Select = no typeahead, click-to-open with fixed options. Pick by user behavior, not visual style.
1641
+ - **Single vs multi mode** is a discriminated union — `multiple: true` changes the shape of value (`string[]`) and onValueChange. TypeScript enforces the pairing.
1642
+ - **Multi-select pills:** Capped at 2 visible + "+N more" overflow regardless of `maxVisible`. Clicking the +N opens a popover list of all selected items (handled by the component).
1643
+ - **className vs triggerClassName:** className lands on the wrapper (positioning); triggerClassName lands on the trigger button (styling the control). Know which you need.
1644
+ - **Portal + z-popover (1400):** content stacks above Dialog/Sheet; works inside scrolling containers without clipping.
1645
+ - **renderOption:** For complex option rendering (avatar + label + description), pass `renderOption: (option, selected) => <YourCustom />`. The selected state is a boolean flag.
1646
+ - **FormField:** Does NOT auto-consume FormField state. Wrap in FormField for label + helper text, but style error manually.
1647
+
1431
1648
  ## Gotchas
1432
1649
  - Enforces selection from list (unlike Autocomplete which allows free text)
1433
- - In multi mode, selected items appear as pills with "+N more" overflow
1650
+ - In multi mode, selected items appear as pills with "+N more" overflow (capped at 2 visible pills regardless of `maxVisible`)
1651
+ - `className` vs `triggerClassName`: `className` lands on the wrapper div (useful for width/positioning); `triggerClassName` lands on the actual Popover trigger button (useful for styling the control itself)
1652
+ - Dropdown content is portalled to document.body — parent styles like `overflow: hidden` don't clip it, and container-scoped test queries won't find it
1434
1653
 
1435
1654
  ## Changes
1436
1655
  ### v0.18.0
@@ -1468,8 +1687,19 @@ Animated bouncing dots with a text description of who is typing.
1468
1687
  </Container>
1469
1688
  ```
1470
1689
 
1690
+ ## Composability
1691
+ - **Server-safe layout primitive.** Safe in RSC trees — no hooks, no context.
1692
+ - **maxWidth choices:**
1693
+ - `"default"` (standard page container — matches the design system's layout grid)
1694
+ - `"body"` (narrower reading width — use for article/blog content)
1695
+ - `"full"` (no max — edge-to-edge, useful for full-bleed marketing sections)
1696
+ - **Polymorphic via `as`:** Change the rendered element for semantics (`as="main"` for page content, `as="section"` for major subdivisions, `as="article"` for standalone content).
1697
+ - **mx-auto centering is automatic** — Container handles horizontal centering; it does NOT add vertical spacing. Pair with `py-*` utility classes on the Container itself or inside it.
1698
+ - Nothing cascades — nesting Containers is fine but rarely useful (max-width constraints compound).
1699
+
1471
1700
  ## Gotchas
1472
1701
  - Server-safe component — can be imported directly in Next.js Server Components
1702
+ - Container does NOT add vertical padding — add it explicitly via className if needed
1473
1703
 
1474
1704
  ## Changes
1475
1705
  ### v0.1.0
@@ -1505,8 +1735,16 @@ Animated bouncing dots with a text description of who is typing.
1505
1735
  </ContextMenu>
1506
1736
  ```
1507
1737
 
1738
+ ## Composability
1739
+ - Radix ContextMenu — same item-variant surface as DropdownMenu (checkbox, radio group, sub-menus, label, separator) with the same keyboard model. Swap `Dropdown` → `Context` in imports and everything behaves the same.
1740
+ - **Trigger is not a button** — ContextMenuTrigger is a wrapper element (defaults to `<div>`) that listens for `contextmenu` events on itself and children. Use `asChild` to pass styling to your own element.
1741
+ - **Touch support:** Radix maps long-press to right-click. On mobile, hold-to-open just works without extra code.
1742
+ - **No visible trigger affordance** — unlike DropdownMenu/Popover, the user has to *know* the element is right-clickable. Pair with a visible hint (keyboard shortcut label, menu icon elsewhere) for discoverability.
1743
+ - **Portal + z-index:** z-popover (1400).
1744
+
1508
1745
  ## Gotchas
1509
1746
  - Triggered by right-click (or long-press on touch devices)
1747
+ - ContextMenuTrigger doesn't auto-indicate it's interactive — add visual affordance elsewhere on the page
1510
1748
 
1511
1749
  ## Changes
1512
1750
  ### v0.18.0
@@ -1574,6 +1812,30 @@ import { DataTable } from '@devalok/shilp-sutra/ui/data-table'
1574
1812
  />
1575
1813
  ```
1576
1814
 
1815
+ ## Composability
1816
+ **Server vs client mode is prop-driven, not explicit.**
1817
+ - Pass `onSort` → server-side sort (manual, rows stay in data order — you're responsible for re-fetching).
1818
+ - Pass `pagination` object → server-side pagination (manual, pass total count).
1819
+ - Omit both → client-side sort/pagination via TanStack react-table.
1820
+ - Mix-and-match: `onSort` + no pagination = server sort + client pagination.
1821
+
1822
+ **Companion components:**
1823
+ - `DataTableToolbar` — enabled via `toolbar={true}`. Provides column visibility, density switcher, CSV export. Reads table state via `DataTableContext` (internal). Rendered ABOVE the table automatically.
1824
+ - `BulkActionBar` (floating) — appears when rows are selected AND `bulkActions` array is non-empty. Synced with `selectedIds`; shows count + action buttons.
1825
+ - `EmptyState` from `@devalok/shilp-sutra/composed` — pass to `emptyState` prop. Takes precedence over `noResultsText` string.
1826
+
1827
+ **Controlled selection:**
1828
+ - Pass `selectedIds` (Set<string>) + `onSelectionChange` for controlled row selection.
1829
+ - Provide `getRowId: (row) => row.id` so selection survives data refetches (otherwise TanStack uses array index, which breaks on sort/filter).
1830
+ - `selectableFilter: (row) => boolean` disables selection on specific rows (e.g. archived items).
1831
+
1832
+ **Row click model:**
1833
+ - `onRowClick` fires on row-level click BUT excludes clicks on checkboxes, buttons, links, and inputs automatically. No manual `stopPropagation` needed for standard interactive elements.
1834
+
1835
+ **Virtualization:** `virtualRows={true}` enables row virtualization via `@tanstack/react-virtual`. Turn it on for 1000+ row datasets; the scroll container must have a bounded height.
1836
+
1837
+ **Density integration:** `defaultDensity="compact"` is the Karm-style dense mode (h-9 rows). DataTableToolbar's density switcher updates this at runtime; the prop sets the initial state only.
1838
+
1577
1839
  ## Gotchas
1578
1840
  - Barrel-isolated since v0.5.0 — must use `@devalok/shilp-sutra/ui/data-table`, NOT the `ui` barrel
1579
1841
  - Requires @tanstack/react-table and @tanstack/react-virtual as peer dependencies
@@ -1582,6 +1844,7 @@ import { DataTable } from '@devalok/shilp-sutra/ui/data-table'
1582
1844
  - selectedIds syncs via useEffect — provide getRowId for custom row IDs
1583
1845
  - onRowClick does NOT fire when clicking checkboxes, buttons, links, or inputs
1584
1846
  - Use defaultDensity="compact" for Karm-style h-9 rows
1847
+ - `virtualRows={true}` requires a bounded scroll container — unbounded height silently disables virtualization
1585
1848
 
1586
1849
  ## Changes
1587
1850
  ### v0.29.0
@@ -1620,6 +1883,10 @@ import { DataTable } from '@devalok/shilp-sutra/ui/data-table'
1620
1883
  This component is used internally by `<DataTable>` and should not be imported directly.
1621
1884
  See [DataTable](./data-table.md) for the public API.
1622
1885
 
1886
+ ## Composability
1887
+ - **Internal only.** Renders the tbody + rows for DataTable. Consumes DataTableContext for row data, selection state, expandable state, loading skeletons.
1888
+ - Customization routes through DataTable props: `renderExpanded`, `onRowClick`, `getRowId`, `loading`, `emptyState`.
1889
+
1623
1890
  ## Changes
1624
1891
 
1625
1892
  ### v0.32.0
@@ -1633,6 +1900,10 @@ See [DataTable](./data-table.md) for the public API.
1633
1900
  This component is used internally by `<DataTable>` and should not be imported directly.
1634
1901
  See [DataTable](./data-table.md) for the public API.
1635
1902
 
1903
+ ## Composability
1904
+ - **Internal only.** Renders the floating bulk-action bar that appears when rows are selected. Reads selection state + bulkActions config from DataTableContext.
1905
+ - Customization goes through DataTable's `bulkActions` prop — `{ label, onClick, color?, disabled? }[]`.
1906
+
1636
1907
  ## Changes
1637
1908
 
1638
1909
  ### v0.32.0
@@ -1646,6 +1917,10 @@ See [DataTable](./data-table.md) for the public API.
1646
1917
  This component is used internally by `<DataTable>` and should not be imported directly.
1647
1918
  See [DataTable](./data-table.md) for the public API.
1648
1919
 
1920
+ ## Composability
1921
+ - **Internal only.** Renders the card wrapper around DataTable (border, shadow, rounded corners matching the Card primitive's surface treatment).
1922
+ - DataTable uses this automatically — no consumer-facing prop toggles it.
1923
+
1649
1924
  ## Changes
1650
1925
 
1651
1926
  ### v0.32.0
@@ -1659,6 +1934,10 @@ See [DataTable](./data-table.md) for the public API.
1659
1934
  This component is used internally by `<DataTable>` and should not be imported directly.
1660
1935
  See [DataTable](./data-table.md) for the public API.
1661
1936
 
1937
+ ## Composability
1938
+ - **Internal React context** — binds DataTable's sub-components (header, body, pagination, toolbar, bulk-actions) to shared state: TanStack table instance, density, selection, loading.
1939
+ - Consumer code never reads this context — all interaction goes through DataTable's props.
1940
+
1662
1941
  ## Changes
1663
1942
 
1664
1943
  ### v0.32.0
@@ -1672,6 +1951,10 @@ See [DataTable](./data-table.md) for the public API.
1672
1951
  This component is used internally by `<DataTable>` and should not be imported directly.
1673
1952
  See [DataTable](./data-table.md) for the public API.
1674
1953
 
1954
+ ## Composability
1955
+ - **Internal only.** Renders the thead + column headers for DataTable. Reads sort state + column definitions from DataTableContext.
1956
+ - Customization goes through DataTable's `columns` prop (column definitions are TanStack ColumnDef<TData> — header, sortable, filter, etc. all declared there).
1957
+
1675
1958
  ## Changes
1676
1959
 
1677
1960
  ### v0.32.0
@@ -1685,6 +1968,10 @@ See [DataTable](./data-table.md) for the public API.
1685
1968
  This component is used internally by `<DataTable>` and should not be imported directly.
1686
1969
  See [DataTable](./data-table.md) for the public API.
1687
1970
 
1971
+ ## Composability
1972
+ - **Internal only.** Renders the pagination footer for DataTable. Reads pagination state from DataTableContext.
1973
+ - Customization goes through DataTable's `pagination` prop (server-side: pass `{ page, pageSize, total, onPageChange }`) or `pageSize` + `paginated` (client-side).
1974
+
1688
1975
  ## Changes
1689
1976
 
1690
1977
  ### v0.32.0
@@ -1722,10 +2009,17 @@ import { DataTableToolbar } from '@devalok/shilp-sutra/ui/data-table-toolbar'
1722
2009
  />
1723
2010
  ```
1724
2011
 
2012
+ ## Composability
2013
+ - **Companion to DataTable** — typically enabled via DataTable's `toolbar={true}` prop (which auto-renders this internally). Use DataTableToolbar directly only if you're building a custom DataTable integration that needs the toolbar positioned/styled differently.
2014
+ - **Reads table state via the `table` instance** (TanStack react-table) — you must pass it. For the auto-rendered version inside DataTable, this wiring is automatic.
2015
+ - **Feature toggles:** `globalFilter` (search across all columns), density switcher (compact/standard/comfortable row heights), CSV export. Turn each on/off independently.
2016
+ - **Density changes are runtime** — the switcher writes to the same `defaultDensity` that DataTable's prop seeds. State lives in DataTable context.
2017
+
1725
2018
  ## Gotchas
1726
2019
  - Barrel-isolated since v0.5.0 — must use `@devalok/shilp-sutra/ui/data-table-toolbar`, NOT the `ui` barrel
1727
2020
  - Companion to DataTable — provides column visibility, density toggle, and CSV export controls
1728
2021
  - Requires @tanstack/react-table as a peer dependency
2022
+ - Prefer DataTable's `toolbar={true}` prop over rendering this directly
1729
2023
 
1730
2024
  ## Changes
1731
2025
  ### v0.5.0
@@ -1776,6 +2070,14 @@ import { DataTableToolbar } from '@devalok/shilp-sutra/ui/data-table-toolbar'
1776
2070
  </div>
1777
2071
  ```
1778
2072
 
2073
+ ## Composability
2074
+ - **Brand texture overlay** — drops into any parent with `relative overflow-hidden isolate`. Auto-inherits the parent's border radius.
2075
+ - **Button auto-extracts Grain children** — if you nest `<DevalokGrain>` inside a `<Button>`, Button auto-separates it from the label slot and positions it correctly. No extra wrapping needed.
2076
+ - **Card, custom hero sections, landing-page tiles** — wrap in a positioned container (or add the `relative overflow-hidden isolate` classes to Card explicitly) and drop Grain in as a sibling to your content.
2077
+ - **z-layer contract:** Grain renders at `z-[1]`. Content on top needs `z-[2]+`. The component doesn't boost child z-index — that's consumer responsibility.
2078
+ - **`hoverIntensify` depends on parent** — the parent must have the Tailwind `group` class so `group-hover:` selectors apply. Forgetting this silently disables the effect.
2079
+ - **Tint composes with surface:** Pass an OKLCH color (or CSS variable reference like `var(--color-accent-9)`) to `tint` for a directional gradient on top of the noise. Without tint, just noise.
2080
+
1779
2081
  ## Gotchas
1780
2082
  - Parent element MUST have `relative overflow-hidden isolate` for the grain to render correctly
1781
2083
  - The grain layers are absolute-positioned at `z-[1]` — content that should appear above must use `z-[2]` or higher
@@ -1825,8 +2127,18 @@ import { DataTableToolbar } from '@devalok/shilp-sutra/ui/data-table-toolbar'
1825
2127
  </Dialog>
1826
2128
  ```
1827
2129
 
2130
+ ## Composability
2131
+ - Built on Radix Dialog primitives — every standard Radix prop passes through (`open`, `onOpenChange`, `defaultOpen`, `modal`).
2132
+ - **Trigger composition:** `<DialogTrigger asChild>` renders the wrapped element as the trigger — typical pattern is `<DialogTrigger asChild><Button>...</Button></DialogTrigger>`. Without `asChild`, a default button is injected.
2133
+ - **Portal rendering:** DialogContent portals to `document.body`. CSS containers (`overflow: hidden`, `transform`, stacking contexts) on ancestors of the trigger don't clip it. Container-scoped test queries miss portalled content — use `screen` or the portal root.
2134
+ - **Focus management:** Radix traps focus inside Content while open and restores it to the trigger on close. First focusable element inside receives focus automatically.
2135
+ - **Imperative close** from deep children: use `<DialogClose asChild>` around your own button, or `useDialogContext` — no prop drilling.
2136
+ - **z-index:** DialogContent uses `z-dialog` (configured in Tailwind theme). Nested overlays (Popover inside Dialog) stack on top because Popover uses `z-popover` (1400) higher than `z-dialog`.
2137
+
1828
2138
  ## Gotchas
1829
2139
  - DialogTitle is required for accessibility — screen readers announce it when the dialog opens
2140
+ - If your layout suppresses the title visually, use `<VisuallyHidden>` around DialogTitle — don't omit it
2141
+ - Focus returns to the trigger on close — if the trigger is conditionally unmounted, focus lands on body; handle manually if that matters
1830
2142
 
1831
2143
  ## Changes
1832
2144
  ### v0.19.1
@@ -1875,8 +2187,18 @@ import { DataTableToolbar } from '@devalok/shilp-sutra/ui/data-table-toolbar'
1875
2187
  </DropdownMenu>
1876
2188
  ```
1877
2189
 
2190
+ ## Composability
2191
+ - Radix DropdownMenu underneath — same prop surface (`open`, `onOpenChange`, `defaultOpen`, `modal`).
2192
+ - **Item variants** stack cleanly: `DropdownMenuCheckboxItem` for multi-select toggles, `DropdownMenuRadioGroup` + `DropdownMenuRadioItem` for single-select, `DropdownMenuSub` + `DropdownMenuSubTrigger/SubContent` for nested submenus. Each has its own keyboard model pre-wired.
2193
+ - **Keyboard:** Arrow keys navigate items, Enter/Space activates, Esc closes, typeahead jumps to first letter. All handled by Radix — don't re-implement.
2194
+ - **Trigger:** `<DropdownMenuTrigger asChild>` around any button. IconButton is the common pairing.
2195
+ - **Shortcut hints:** `<DropdownMenuShortcut>` inside an item renders a right-aligned `⌘K`-style kbd. Visual only — does NOT bind the shortcut globally.
2196
+ - **Closing from a handler:** Item onSelect auto-closes the menu by default. Pass `event.preventDefault()` inside the handler to keep it open (e.g. for checkbox items that shouldn't close on toggle).
2197
+
1878
2198
  ## Gotchas
1879
2199
  - Use `asChild` on DropdownMenuTrigger to render your own button element
2200
+ - DropdownMenuShortcut is decorative — bind keyboard shortcuts separately (e.g. with `useHotkeys`)
2201
+ - Sub-menus need BOTH DropdownMenuSubTrigger (visible item) and DropdownMenuSubContent (the submenu panel) — missing either silently breaks the hover-open behavior
1880
2202
 
1881
2203
  ## Changes
1882
2204
  ### v0.22.0
@@ -1924,9 +2246,17 @@ import { DataTableToolbar } from '@devalok/shilp-sutra/ui/data-table-toolbar'
1924
2246
  />
1925
2247
  ```
1926
2248
 
2249
+ ## Composability
2250
+ - **Two modes, one component:** `compact={false}` (default) = drag-and-drop zone with visible affordance; `compact={true}` = inline "Upload" button. Swap by prop, same callback.
2251
+ - **Progress integration:** Drive `uploading` + `progress` from your upload logic. The component renders an embedded Progress bar when uploading=true.
2252
+ - **Validation is built-in:** `accept` (MIME types / extensions) + `maxSize` are enforced before `onFiles` fires. Invalid files produce `error` state instead of calling through. Your onFiles never has to re-validate.
2253
+ - **Multi-file:** `multiple={true}` accepts File[] (array always, even for single-file mode — use `files[0]`).
2254
+ - **Composes with Toast for feedback:** Pair with toast.success on upload complete, toast.error on failure.
2255
+
1927
2256
  ## Gotchas
1928
2257
  - compact=true renders a small inline button; false (default) renders a large drag-and-drop zone
1929
2258
  - Client-side validation: invalid files are rejected before onFiles is called
2259
+ - `onFiles` always receives an array — use `files[0]` even when `multiple={false}`
1930
2260
 
1931
2261
  ## Changes
1932
2262
  ### v0.1.0
@@ -1964,10 +2294,31 @@ import { DataTableToolbar } from '@devalok/shilp-sutra/ui/data-table-toolbar'
1964
2294
  </FormField>
1965
2295
  ```
1966
2296
 
2297
+ ## Composability
2298
+ The FormField/useFormField pair is the central a11y wiring pattern. Components that consume it automatically become accessible inside a FormField without any explicit ARIA work on your end.
2299
+
2300
+ **FormField cascades through context to:**
2301
+ - **Input, Textarea, NumberInput, InputOTP** — auto-receive `aria-describedby` (wired to FormHelperText's id), `aria-invalid` (when state="error"), and `aria-required` (when required=true).
2302
+ - **FormHelperText** — auto-reads `state` and `helperTextId` from context; renders `role="alert"` on error so screen readers interrupt.
2303
+ - **Label** — pair it with an Input via `htmlFor` / `id` in the normal way; FormField doesn't auto-wire labels (labels need the explicit association to satisfy screen readers reliably).
2304
+
2305
+ **Explicit props always override context.** Setting `state="error"` on an Input inside a FormField with `state="helper"` makes only that Input look errored.
2306
+
2307
+ **Nesting is NOT supported.** Don't nest FormField inside FormField — only the outermost context wins, and some a11y wiring silently breaks.
2308
+
2309
+ **Consuming context in your own components:**
2310
+ ```tsx
2311
+ const field = useFormField() // returns { state, helperTextId, required } or undefined
2312
+ // Thread field?.state onto your control's state prop,
2313
+ // and field?.helperTextId onto aria-describedby.
2314
+ ```
2315
+
1967
2316
  ## Gotchas
1968
2317
  - getFormFieldA11y() was REMOVED — use useFormField() hook instead
1969
2318
  - FormHelperText auto-reads state and id from FormField context
1970
2319
  - FormHelperText renders role="alert" when state="error"
2320
+ - Don't nest FormField components — one FormField per field
2321
+ - FormField does NOT auto-wire Label→Input association; use `<Label htmlFor="x" />` + `<Input id="x" />` explicitly
1971
2322
 
1972
2323
  ## Changes
1973
2324
  ### v0.18.0
@@ -2001,8 +2352,17 @@ import { DataTableToolbar } from '@devalok/shilp-sutra/ui/data-table-toolbar'
2001
2352
  </HoverCard>
2002
2353
  ```
2003
2354
 
2355
+ ## Composability
2356
+ - Radix HoverCard — accepts `open`, `onOpenChange`, `defaultOpen`, `openDelay` (ms before show), `closeDelay` (ms before hide).
2357
+ - **Distinction from Tooltip:** HoverCardContent can contain interactive elements (links, buttons, form fields). Tooltip is for inert text labels. If you need a rich hover preview — user card, link preview, product card — use HoverCard.
2358
+ - **Distinction from Popover:** Popover opens on click/focus; HoverCard opens on hover/focus. Use HoverCard when the intent is a passive preview, Popover when the user must explicitly invoke the panel.
2359
+ - **Trigger:** `<HoverCardTrigger asChild>` wraps any element. Works on `<span>`, `<a>`, `<button>` — the trigger doesn't need to be interactive itself (unlike Popover where click matters).
2360
+ - **Accessibility:** Hover-only interactions are invisible on touch. Pair with a focus-visible state or use Popover instead for critical content.
2361
+ - **Portal + z-index:** z-popover (1400), same as DropdownMenu/Popover.
2362
+
2004
2363
  ## Gotchas
2005
2364
  - Overlay component — uses Framer Motion for enter/exit animations (v0.18.0)
2365
+ - Don't nest another HoverCard inside one — both open on the same hover and conflict
2006
2366
 
2007
2367
  ## Changes
2008
2368
  ### v0.18.0
@@ -2046,6 +2406,13 @@ import { DataTableToolbar } from '@devalok/shilp-sutra/ui/data-table-toolbar'
2046
2406
  <Icon icon={IconPlus} state="success" />
2047
2407
  ```
2048
2408
 
2409
+ ## Composability
2410
+ - **IconContext consumption** — Icon auto-reads `size` and `stroke` from the nearest IconProvider. Button, IconButton, IconGroup, Input (startSection/endSection), Badge, NumberInput, and more all wrap their children in IconProvider so nested Icons auto-size. Explicit props always override context.
2411
+ - **Use Tabler icon components** — the `icon` prop expects a ForwardRef SVG component (the shape Tabler React exports). Any icon lib following that shape works, but Tabler is the standard.
2412
+ - **Label for a11y:** Without `label`, Icon is `aria-hidden="true"` (decorative — appropriate inside labeled buttons). With `label`, renders `role="img"` + aria-label + `<title>` for standalone icons.
2413
+ - **State overrides animate:** When both are set, state wins. `state="loading"` renders a bare Spinner; success/error render animated glyphs via Framer Motion.
2414
+ - **Reduced motion:** All animations respect `prefers-reduced-motion` — animate props fall back to static render.
2415
+
2049
2416
  ## Gotchas
2050
2417
  - Without `label`, the icon renders `aria-hidden="true"` (decorative)
2051
2418
  - With `label`, the icon renders `role="img"` with `aria-label` and a `<title>` element
@@ -2088,6 +2455,13 @@ import { DataTableToolbar } from '@devalok/shilp-sutra/ui/data-table-toolbar'
2088
2455
  <IconButton icon={<Icon icon={IconTrash} />} variant="solid" color="error" aria-label="Delete" />
2089
2456
  ```
2090
2457
 
2458
+ ## Composability
2459
+ - **Built on Button internally** — inherits ALL Button features: variant, color, loading, onClickAsync, processing, ButtonGroup context consumption, asChild. Use IconButton when your button is icon-only; use Button for label-plus-icon.
2460
+ - **aria-label is TypeScript-enforced** — the library makes it impossible to forget. This is the key accessibility constraint for icon-only buttons.
2461
+ - **IconProvider cascade:** The `icon` prop expects `<Icon icon={...} />` — Icon auto-sizes based on IconButton's `size` via IconProvider context (no need to pass size to the nested Icon).
2462
+ - **Inside ButtonGroup:** Inherits variant/color/size/weight from ButtonGroup context just like Button does. Useful for icon-only toolbars.
2463
+ - **IconGroup vs ButtonGroup of IconButtons:** IconGroup is for static icon clusters (not interactive, or for decorative displays). ButtonGroup of IconButtons is for interactive toolbars where each icon is a button.
2464
+
2091
2465
  ## Gotchas
2092
2466
  - aria-label is enforced by TypeScript — you MUST provide it
2093
2467
  - Prefer IconButton over Button with size="icon-*" for icon-only buttons
@@ -2131,6 +2505,13 @@ import { IconProvider, useIconContext } from '@devalok/shilp-sutra/ui'
2131
2505
  const { size, stroke } = useIconContext()
2132
2506
  ```
2133
2507
 
2508
+ ## Composability
2509
+ - **Low-level context primitive** — rarely used directly. Most consumers want IconGroup (which wraps IconProvider + layout) or just nest Icons inside Button/Input/Badge (which provide their own IconProvider).
2510
+ - **When to reach for IconProvider manually:** Building a custom container that hosts many Icons (a dashboard tile row, a custom toolbar, a nav rail) and you want consistent icon sizing without repeating props. IconProvider is the escape hatch for that.
2511
+ - **Consumer contract:** `useIconContext()` returns `{}` (empty) when no provider is present — your consumer code MUST handle that (fall back to a default size/stroke). Don't assume a provider exists.
2512
+ - **Memoization:** The provider memoizes its value, so consumers don't re-render unless size or stroke actually change. Safe to use in tight loops / frequently-re-rendering trees.
2513
+ - **Composes down:** IconProvider nests — inner providers override outer ones. A `<Button size="lg"><IconProvider size="xs">...</IconProvider></Button>` overrides the button's icon sizing.
2514
+
2134
2515
  ## Gotchas
2135
2516
  - Used internally by IconGroup and Button to propagate icon sizing to children
2136
2517
  - If no provider is present, `useIconContext()` returns `{}` (empty object) — consumers should fall back to defaults
@@ -2167,6 +2548,15 @@ const { size, stroke } = useIconContext()
2167
2548
  </IconGroup>
2168
2549
  ```
2169
2550
 
2551
+ ## Composability
2552
+ - **IconProvider wrapper + layout** — IconGroup is IconProvider plus a flex container. All child `<Icon>` elements auto-size via the provider. Don't pass size to nested Icons.
2553
+ - **IconGroup vs ButtonGroup-of-IconButtons:**
2554
+ - IconGroup = static icon displays (legend rows, feature lists, decorative clusters). Not interactive.
2555
+ - ButtonGroup of IconButtons = interactive toolbar where each icon is a button.
2556
+ - If you want a formatting toolbar (interactive), use ButtonGroup or ToggleGroup, not IconGroup.
2557
+ - **Toolbar mode:** Set `role="toolbar"` + provide `label` to give the group a semantic role. Only then does `label` become `aria-label` on the container.
2558
+ - **Gap semantics:** tight=2px, default=4px, loose=8px — smaller values than ButtonGroup or Stack because icons are visually dense.
2559
+
2170
2560
  ## Gotchas
2171
2561
  - Wraps children in an IconProvider — all child Icons inherit size/stroke from the group
2172
2562
  - `label` is only applied as `aria-label` when `role="toolbar"` is set
@@ -2191,8 +2581,6 @@ const { size, stroke } = useIconContext()
2191
2581
  startSectionType: 'icon' | 'label' (section display type — auto-inferred from content)
2192
2582
  endSectionType: 'icon' | 'label' (section display type — auto-inferred from content)
2193
2583
  wrapperClassName: string (classes for the wrapper div — border, bg, ring)
2194
- startIcon: ReactNode (@deprecated — use startSection)
2195
- endIcon: ReactNode (@deprecated — use endSection)
2196
2584
  (plus all standard HTML input attributes except native "size")
2197
2585
 
2198
2586
  ## Types
@@ -2209,6 +2597,13 @@ const { size, stroke } = useIconContext()
2209
2597
  <Input endSection=".00" endSectionType="label" startSection={<Icon icon={IconCurrencyDollar} />} placeholder="0" />
2210
2598
  ```
2211
2599
 
2600
+ ## Composability
2601
+ - **FormField auto-consumption:** Inside a `<FormField>`, Input auto-inherits `state`, `aria-describedby` (linked to FormHelperText), `aria-invalid` (when state="error"), `aria-required`. Explicit props on Input override context.
2602
+ - **IconProvider cascade:** Icons in `startSection` / `endSection` are auto-sized via IconProvider per the input's `size` (xs → sm icon, sm → sm icon, md → md icon, lg → md icon). Don't pass explicit size to nested `<Icon>`.
2603
+ - **Container-first architecture (v0.29.0+):** Border, background, focus ring all live on the wrapper div (accessed via `wrapperClassName`). The raw `<input>` inside is transparent. `className` goes to the input element; `wrapperClassName` goes to the wrapper.
2604
+ - **Section types:** Strings in startSection/endSection auto-render as `label` (tinted bg + border separator); React elements auto-render as `icon` (fixed-width centered cell). Override via `startSectionType` / `endSectionType`.
2605
+ - **Label pairing:** Always pair with `<Label htmlFor="x" />` + `<Input id="x" />` or wrap both in a `<label>` — FormField doesn't auto-wire the visual label to the input.
2606
+
2212
2607
  ## Gotchas
2213
2608
  - HTML native "size" attribute is excluded — use CSS width instead
2214
2609
  - state="error" sets aria-invalid automatically
@@ -2220,6 +2615,10 @@ const { size, stroke } = useIconContext()
2220
2615
  - Section type is auto-inferred: strings default to `'label'` (tinted bg + border), React elements default to `'icon'` (fixed-width centered). Override with `startSectionType`/`endSectionType`.
2221
2616
 
2222
2617
  ## Changes
2618
+ ### v0.38.0
2619
+ - **Removed** (BREAKING) deprecated `startIcon` / `endIcon` props. Use `startSection` / `endSection`.
2620
+ - **Removed** (BREAKING) deprecated `inputVariants` export. Use `inputWrapperVariants`.
2621
+
2223
2622
  ### v0.29.0
2224
2623
  - **Changed** v2 rewrite: container-first architecture with wrapper div holding focus ring
2225
2624
  - **Added** `xs` size (28px height)
@@ -2256,17 +2655,32 @@ const { size, stroke } = useIconContext()
2256
2655
  - Category: ui
2257
2656
 
2258
2657
  ## Props
2259
- Standard input-otp props (maxLength, pattern, etc.)
2658
+ ### InputOTP
2659
+ maxLength: number (REQUIRED) — total number of slots
2660
+ value: string (controlled value; defaults to empty string)
2661
+ onChange: (value: string) => void
2662
+ onComplete: (value: string) => void — fires when all slots filled
2663
+ pattern: string | RegExp — restrict input (e.g. `REGEXP_ONLY_DIGITS`)
2664
+ state: "default" | "error" (error adds red border; auto-inherits from FormField)
2665
+ size: "sm" | "md" | "lg" (slot dimensions — propagates to InputOTPSlot via context)
2666
+ disabled: boolean
2667
+ containerClassName: string (on the outer group container — separate from inner input's className)
2668
+
2669
+ ### InputOTPSlot
2670
+ index: number (REQUIRED, 0-based) — which position this slot renders
2260
2671
 
2261
2672
  ## Compound Components
2262
- InputOTP (root)
2263
- InputOTPGroup
2264
- InputOTPSlot (index: number, REQUIRED)
2265
- InputOTPSeparator
2673
+ InputOTP (root — maxLength, value, onChange, size propagated via context)
2674
+ InputOTPGroup (visual group of slots)
2675
+ InputOTPSlot (index: number, REQUIRED — reads size from context)
2676
+ InputOTPSeparator (visual separator, e.g. between two groups of 3)
2677
+
2678
+ ## Defaults
2679
+ size="md", state="default"
2266
2680
 
2267
2681
  ## Example
2268
2682
  ```jsx
2269
- <InputOTP maxLength={6}>
2683
+ <InputOTP maxLength={6} onComplete={verifyCode}>
2270
2684
  <InputOTPGroup>
2271
2685
  <InputOTPSlot index={0} />
2272
2686
  <InputOTPSlot index={1} />
@@ -2281,8 +2695,15 @@ const { size, stroke } = useIconContext()
2281
2695
  </InputOTP>
2282
2696
  ```
2283
2697
 
2698
+ ## Composability
2699
+ - Size propagates from InputOTP → InputOTPSlot via `InputOTPSizeContext` — don't set size on individual slots.
2700
+ - Inside `<FormField>`: auto-inherits state, aria-describedby, aria-required from context. Explicit `state="error"` overrides.
2701
+ - Underlying library is `input-otp` (OTPInput) — all standard library props (pattern, inputMode, autoFocus, etc.) pass through.
2702
+
2284
2703
  ## Gotchas
2285
- - Each InputOTPSlot requires an `index` prop (0-based)
2704
+ - Each InputOTPSlot requires an `index` prop (0-based) matching its position
2705
+ - `onComplete` only fires when ALL slots are filled — use `onChange` for per-character reactivity
2706
+ - `containerClassName` (outer visual group) is distinct from `className` (the hidden input element itself)
2286
2707
 
2287
2708
  ## Changes
2288
2709
  ### v0.18.0
@@ -2309,8 +2730,14 @@ const { size, stroke } = useIconContext()
2309
2730
  <Label htmlFor="email" required>Email Address</Label>
2310
2731
  ```
2311
2732
 
2733
+ ## Composability
2734
+ - **Label is NOT auto-wired by FormField** — you must explicitly pair `<Label htmlFor="x" />` with the matching `<Input id="x" />` (or Checkbox/Radio/Switch/Select). Screen-reader label association depends on this.
2735
+ - Radix Label primitive underneath — clicking the Label focuses its associated control, which is why the `htmlFor`/`id` pairing matters.
2736
+ - `required={true}` only renders the red asterisk; it does NOT set `aria-required` on the associated control (that comes from FormField context on the control itself).
2737
+ - Works with any input-like component in the library — pair with Input, Textarea, NumberInput, Checkbox, Radio, Switch, Select, Combobox, Autocomplete.
2738
+
2312
2739
  ## Gotchas
2313
- - Use with FormField for automatic aria wiring
2740
+ - Use with FormField for automatic aria wiring on the control (but the Label-to-control association is always manual)
2314
2741
 
2315
2742
  ## Changes
2316
2743
  ### v0.2.0
@@ -2338,8 +2765,15 @@ const { size, stroke } = useIconContext()
2338
2765
  <Link asChild><NextLink href="/about">About</NextLink></Link>
2339
2766
  ```
2340
2767
 
2768
+ ## Composability
2769
+ - **Framework router integration via `asChild`:** `<Link asChild><NextLink href="/foo">...</NextLink></Link>` — Link's styling transfers to the child (NextLink/react-router Link/any `<a>`-like component) while preserving the child's navigation semantics. Don't use `asChild` for plain anchors — it's unnecessary and adds a layer.
2770
+ - **inline vs block:** Default `inline` (display: inline) so Link composes naturally inside paragraphs and mixed text. Set `inline={false}` for full-width link regions (card wrappers, list items).
2771
+ - No context, no cascade. Safe to use anywhere.
2772
+ - For icon + text links, nest Icon inside — Link doesn't auto-size via IconProvider (unlike Button), so pass an explicit `size` to the Icon.
2773
+
2341
2774
  ## Gotchas
2342
2775
  - Use asChild with framework-specific Link components (e.g. Next.js Link)
2776
+ - Don't nest interactive elements (buttons, form controls) inside a Link — that's invalid HTML and breaks screen-reader navigation
2343
2777
 
2344
2778
  ## Changes
2345
2779
  ### v0.18.0
@@ -2379,8 +2813,17 @@ const { size, stroke } = useIconContext()
2379
2813
  </Menubar>
2380
2814
  ```
2381
2815
 
2816
+ ## Composability
2817
+ - Radix Menubar — shares the item-variant vocabulary with DropdownMenu and ContextMenu (checkbox items, radio groups, sub-menus, labels, separators, shortcuts).
2818
+ - **Key difference from DropdownMenu:** Menubar is the horizontal top-bar-menu pattern (File / Edit / View / Help). Multiple MenubarMenu children sit side-by-side at the root; opening one closes the others, and arrow keys move between them.
2819
+ - **Typical use:** Desktop app-like UIs where the menu is always visible at the top (code editors, design tools). For a single collapsed trigger, use DropdownMenu instead.
2820
+ - **Controlled open:** Pass `value` + `onValueChange` to Menubar root to control which MenubarMenu is open (value = menu's `value` prop or falsy for none).
2821
+ - **Portal + z-index:** z-popover (1400).
2822
+
2382
2823
  ## Gotchas
2383
2824
  - Follows the standard Radix Menubar compound pattern
2825
+ - Don't use Menubar for a single dropdown — use DropdownMenu instead
2826
+ - MenubarShortcut is decorative (same as DropdownMenuShortcut) — bind shortcuts separately
2384
2827
 
2385
2828
  ## Changes
2386
2829
  ### v0.18.0
@@ -2424,8 +2867,17 @@ const { size, stroke } = useIconContext()
2424
2867
  </NavigationMenu>
2425
2868
  ```
2426
2869
 
2870
+ ## Composability
2871
+ - **Radix NavigationMenu** underneath — keyboard model (Tab between items, Arrow for panel navigation, Esc to close) is pre-wired.
2872
+ - **NavigationMenuLink vs NavigationMenuTrigger:** Use Link for simple nav items (no dropdown); use Trigger + Content for items with a panel of sub-links.
2873
+ - **NavigationMenuViewport** is the animated container that holds the active Content panel — it auto-positions below the triggers. NavigationMenuIndicator is the small arrow/caret pointing from trigger to viewport.
2874
+ - **Purpose:** Top-level site navigation (Products, Solutions, Resources) with rich dropdowns containing links grouped by category. Don't use for context menus (use DropdownMenu) or action toolbars (use ButtonGroup).
2875
+ - **Router integration:** Use `<NavigationMenuLink asChild><NextLink href="/...">...</NextLink></NavigationMenuLink>` for framework-specific Link components.
2876
+ - **Portal + z-popover (1400)** — content panels portal to body and stack above Dialog/Sheet.
2877
+
2427
2878
  ## Gotchas
2428
2879
  - Uses Framer Motion for enter/exit animations (v0.18.0)
2880
+ - NavigationMenu is NOT for sidebar nav — use Sidebar; NOT for mobile nav — use Sheet or BottomNavbar
2429
2881
 
2430
2882
  ## Changes
2431
2883
  ### v0.18.0
@@ -2446,6 +2898,8 @@ const { size, stroke } = useIconContext()
2446
2898
  ## Props
2447
2899
  value: number (default: 0)
2448
2900
  onValueChange: (value: number) => void
2901
+ size: "xs" | "sm" | "md" | "lg"
2902
+ state: "default" | "error" | "warning" | "success" (border color)
2449
2903
  min: number
2450
2904
  max: number
2451
2905
  step: number (default: 1)
@@ -2453,6 +2907,8 @@ const { size, stroke } = useIconContext()
2453
2907
 
2454
2908
  ## Defaults
2455
2909
  value: 0
2910
+ size: "md"
2911
+ state: "default"
2456
2912
  step: 1
2457
2913
  min: Number.MIN_SAFE_INTEGER
2458
2914
  max: Number.MAX_SAFE_INTEGER
@@ -2462,6 +2918,12 @@ const { size, stroke } = useIconContext()
2462
2918
  <NumberInput value={qty} onValueChange={setQty} min={1} max={99} />
2463
2919
  ```
2464
2920
 
2921
+ ## Composability
2922
+ - **FormField auto-consumption:** Inside `<FormField>`, inherits `aria-describedby`, `aria-invalid`, `aria-required`. `state` from FormField context drives the border color unless overridden.
2923
+ - **IconProvider cascade:** The `+` and `−` stepper icons auto-size via IconProvider per the input size (xs/sm/md/lg → xs/sm/sm/md icons).
2924
+ - **Controlled only** (design choice): no `defaultValue` — pair `value` + `onValueChange` every time. The min/max bounds disable the stepper buttons automatically when reached.
2925
+ - **Label pairing:** Manual via `<Label htmlFor="x" />` + `<NumberInput id="x" />`.
2926
+
2465
2927
  ## Gotchas
2466
2928
  - Controlled only — buttons won't work without onValueChange
2467
2929
 
@@ -2519,8 +2981,18 @@ const { size, stroke } = useIconContext()
2519
2981
  </PaginationRoot>
2520
2982
  ```
2521
2983
 
2984
+ ## Composability
2985
+ - **Two APIs in one component:**
2986
+ - Low-level compound (PaginationRoot + Content + Item + Link + Previous/Next/Ellipsis) — full control over rendering
2987
+ - High-level `PaginationNav` — pass `totalPages` + `currentPage` + `onPageChange` and it renders the whole thing using `generatePagination`
2988
+ - **Use PaginationNav for 95% of cases.** Reach for the compound API when you need custom page-button rendering (e.g. pagination with input "jump to page" or custom ellipsis handling).
2989
+ - **generatePagination** utility — pass `current`, `total`, `siblingCount` to get the `[1, ..., 5, 6, 7, ..., 20]` structure. Use it standalone if rendering pagination elsewhere (e.g. in a DataTable's footer).
2990
+ - **Router integration:** Each PaginationLink accepts `asChild` — wrap with NextLink/react-router Link for URL-based pagination.
2991
+ - **DataTable already has built-in pagination** — don't add Pagination separately; use DataTable's `pagination` prop.
2992
+
2522
2993
  ## Gotchas
2523
2994
  - Root component is PaginationRoot (NOT Pagination)
2995
+ - PaginationNav is the convenience wrapper — prefer it unless you need custom rendering
2524
2996
 
2525
2997
  ## Changes
2526
2998
  ### v0.1.1
@@ -2549,8 +3021,17 @@ const { size, stroke } = useIconContext()
2549
3021
  </Popover>
2550
3022
  ```
2551
3023
 
3024
+ ## Composability
3025
+ - Built on Radix Popover — accepts `open`, `onOpenChange`, `defaultOpen`, `modal`.
3026
+ - **Trigger:** `<PopoverTrigger asChild>` wraps any focusable element.
3027
+ - **PopoverAnchor:** Optional — decouples the visual anchor from the interactive trigger. Useful when the trigger is small (e.g. an icon button) but you want the popover to position relative to a larger surrounding element.
3028
+ - **Positioning:** PopoverContent accepts `side` (top / right / bottom / left), `align` (start / center / end), `sideOffset`, and `collisionPadding` — all forwarded to Floating UI via Radix.
3029
+ - **Portal rendering:** Content portals to body; z-index is `z-popover` (1400) — above Dialog (`z-dialog`) so nested popovers-in-dialogs work correctly.
3030
+ - **Not for tooltips** — use Tooltip for transient hover-label content; Popover is for interactive content (forms, menus, pickers).
3031
+
2552
3032
  ## Gotchas
2553
3033
  - Uses Framer Motion for enter/exit animations (v0.18.0)
3034
+ - If content needs to be modal (backdrop, focus trap), pass `modal={true}` to the root Popover
2554
3035
 
2555
3036
  ## Changes
2556
3037
  ### v0.18.0
@@ -2583,6 +3064,13 @@ const { size, stroke } = useIconContext()
2583
3064
  <Progress size="sm" /> {/* indeterminate */}
2584
3065
  ```
2585
3066
 
3067
+ ## Composability
3068
+ - **Radix Progress** underneath — `value` (determinate) or absent (indeterminate). Standard ARIA: role=progressbar, aria-valuenow/min/max.
3069
+ - **autoColor semantic signal:** Maps value thresholds to color so consumers don't have to manually compute: 0–59=default, 60–84=warning, 85–100=success, >100=error. Useful for storage meters, budget trackers, goal progress.
3070
+ - **Indeterminate:** Omit `value` to get a continuous animated indeterminate bar. Use for unknown-duration loads.
3071
+ - **Progress vs ProgressRing:** Progress is a linear bar (horizontal). ProgressRing is circular. ProgressRing offers multi-ring stacked variants; Progress doesn't.
3072
+ - **Inside Card/StatCard:** StatCard has a `progress` prop that renders a thin inline version — use that inside StatCards instead of a separate Progress.
3073
+
2586
3074
  ## Gotchas
2587
3075
  - Omit value (or pass undefined) for indeterminate animation
2588
3076
  - `autoColor` overrides `color` when `value` is set — do not pass both unless you want autoColor to win
@@ -2630,6 +3118,13 @@ const { size, stroke } = useIconContext()
2630
3118
  />
2631
3119
  ```
2632
3120
 
3121
+ ## Composability
3122
+ - **SVG-based circular progress** — pure SVG, no chart library dependency.
3123
+ - **MultiProgressRing** — concentric rings for multi-metric visualization (Apple Activity style: Move + Exercise + Stand). Don't stack more than 4-5 rings at a size; the innermost rings become too thin. Size auto-allocates ring stroke; pass `size="lg"` for more room.
3124
+ - **ProgressRing vs Progress:** Use ProgressRing for dashboard tiles, at-a-glance status, multi-metric visualizations. Use Progress for linear "percentage filled" UIs.
3125
+ - **`showValue={true}`** renders the percentage in the center (ProgressRing only). For MultiProgressRing, use external labels since center would conflict with multiple values.
3126
+ - **Label for a11y:** `label` falls back to `"{n}% progress"` so screen readers always get meaningful context.
3127
+
2633
3128
  ## Gotchas
2634
3129
  - Uses Framer Motion for the animated fill — not server-safe
2635
3130
  - Value is clamped to `[0, max]` internally
@@ -2640,9 +3135,26 @@ const { size, stroke } = useIconContext()
2640
3135
  - Server-safe: No
2641
3136
  - Category: ui
2642
3137
 
3138
+ ## Props
3139
+ ### RadioGroup
3140
+ value: string (controlled)
3141
+ onValueChange: (value: string) => void
3142
+ defaultValue: string
3143
+ disabled: boolean (propagates to all items)
3144
+ orientation: "horizontal" | "vertical"
3145
+ name: string (form name for all items)
3146
+
3147
+ ### RadioGroupItem
3148
+ value: string (REQUIRED — what's selected when this item is checked)
3149
+ size: "sm" | "md" | "lg"
3150
+ disabled: boolean (item-level override)
3151
+
2643
3152
  ## Compound Components
2644
- RadioGroup (root, value, onValueChange, defaultValue)
2645
- RadioGroupItem (value: string, REQUIRED)
3153
+ RadioGroup (root value, onValueChange, defaultValue, disabled propagated)
3154
+ RadioGroupItem (value REQUIRED, size/disabled individually overridable)
3155
+
3156
+ ## Defaults
3157
+ RadioGroupItem size="md"
2646
3158
 
2647
3159
  ## Example
2648
3160
  ```jsx
@@ -2658,9 +3170,17 @@ const { size, stroke } = useIconContext()
2658
3170
  </RadioGroup>
2659
3171
  ```
2660
3172
 
3173
+ ## Composability
3174
+ - Radix RadioGroup — keyboard navigation (arrow keys to move between items, space to select) is pre-wired.
3175
+ - **RadioGroup propagates** `disabled` to every RadioGroupItem; items can opt back in with `disabled={false}` for granular control (rare).
3176
+ - **Labels:** RadioGroupItem has no intrinsic label — pair each with `<Label htmlFor="x" />` + `<RadioGroupItem id="x" value="..." />`. Screen readers announce the group label (from FormField or aria-labelledby on RadioGroup) plus each item's label.
3177
+ - **Form libraries:** RadioGroup works with react-hook-form via Controller (onValueChange maps to field.onChange). The `name` prop puts a hidden form input per item for native form serialization.
3178
+ - **FormField integration:** RadioGroup does NOT auto-consume FormField error state. For error visuals, style the RadioGroup surround (e.g. via aria-invalid on a wrapping fieldset) — individual radios don't show red borders the way Inputs do.
3179
+
2661
3180
  ## Gotchas
2662
3181
  - Each RadioGroupItem needs a unique `value` prop
2663
3182
  - Pair each item with a Label for accessibility
3183
+ - RadioGroup does NOT auto-inherit FormField error state — handle error styling at the group level
2664
3184
 
2665
3185
  ## Changes
2666
3186
  ### v0.4.2
@@ -2696,8 +3216,16 @@ const { size, stroke } = useIconContext()
2696
3216
  />
2697
3217
  ```
2698
3218
 
3219
+ ## Composability
3220
+ - Purpose-built variant of Input for search — renders the leading search icon and a contextual clear/loading trailing slot. For anything more custom, use Input with explicit `startSection` / `endSection`.
3221
+ - **`onClear` makes the X button appear** only when `value` is non-empty. Pair them so users can reset.
3222
+ - **`loading={true}` swaps the clear button for a spinner** with `aria-busy="true"` on the input — useful for debounced/async search.
3223
+ - Doesn't auto-consume FormField (no `state` prop) — wrap a regular Input inside FormField for validated search fields.
3224
+ - Keyboard: Escape auto-triggers `onClear` when wired (handled via `type="search"`'s native behavior on most browsers).
3225
+
2699
3226
  ## Gotchas
2700
3227
  - HTML native "size" attribute is excluded — use CSS width instead
3228
+ - Clear button only appears when both `onClear` is provided AND `value` is non-empty
2701
3229
 
2702
3230
  ## Changes
2703
3231
  ### v0.15.0
@@ -2718,8 +3246,8 @@ const { size, stroke } = useIconContext()
2718
3246
  - Category: ui
2719
3247
 
2720
3248
  ## Props
2721
- size: "sm" | "md" | "lg" (REQUIRED) — also accepts legacy "small" | "medium" | "big"
2722
- variant: "filled" | "tonal" (REQUIRED)
3249
+ size: "sm" | "md" | "lg"
3250
+ variant: "default" | "solid"
2723
3251
  options: SegmentedControlOption[] (REQUIRED)
2724
3252
  selectedId: string (REQUIRED)
2725
3253
  onSelect: (id: string) => void (REQUIRED)
@@ -2727,14 +3255,18 @@ const { size, stroke } = useIconContext()
2727
3255
 
2728
3256
  ## Types
2729
3257
  SegmentedControlOption = { id: string, text: string, icon?: ComponentType<{ className?: string }> }
2730
- SegmentedControlSize = 'sm' | 'md' | 'lg' | 'small' | 'medium' | 'big' (legacy aliases)
2731
- SegmentedControlVariant = 'filled' | 'tonal'
3258
+ SegmentedControlSize = 'sm' | 'md' | 'lg'
3259
+ SegmentedControlVariant = 'default' | 'solid'
3260
+
3261
+ ## Defaults
3262
+ size: "md"
3263
+ variant: "default"
2732
3264
 
2733
3265
  ## Example
2734
3266
  ```jsx
2735
3267
  <SegmentedControl
2736
3268
  size="md"
2737
- variant="tonal"
3269
+ variant="default"
2738
3270
  options={[
2739
3271
  { id: 'list', text: 'List' },
2740
3272
  { id: 'grid', text: 'Grid' },
@@ -2744,16 +3276,27 @@ const { size, stroke } = useIconContext()
2744
3276
  />
2745
3277
  ```
2746
3278
 
3279
+ ## Composability
3280
+ - **Data-driven, not compound** — unlike Tabs/ToggleGroup, SegmentedControl takes an `options` array rather than children. This makes it easier to render from a list but harder to customize per-option styling; use Tabs if you need compound children.
3281
+ - **When to use vs Tabs:** SegmentedControl is for mutually-exclusive VIEW-MODE toggles (List/Grid/Kanban) — short labels, no associated content panel. Tabs is for content switching where each tab has a corresponding TabsContent. Both render `role="tablist"`.
3282
+ - **Option icons** auto-size based on the `size` prop — don't set explicit icon sizes.
3283
+ - Fully controlled — there's no `defaultSelectedId`. Manage state in parent.
3284
+ - Built from scratch (no Radix primitive) — standard HTML buttons with `aria-selected` and roving tabindex.
3285
+
2747
3286
  ## Gotchas
2748
3287
  - Controlled only — selectedId + onSelect are required
2749
3288
  - Uses data-driven API (options prop), not compound children
3289
+ - Use Tabs (not SegmentedControl) when you need associated content panels per option
2750
3290
 
2751
3291
  ## Changes
3292
+ ### v0.38.0
3293
+ - **Removed** (BREAKING) deprecated `variant="accent"` alias. Use `variant="solid"`.
3294
+
2752
3295
  ### v0.18.0
2753
3296
  - **Fixed** `bg-interactive` changed to `bg-accent-9`, `bg-field` changed to `bg-surface-3` (OKLCH migration)
2754
3297
 
2755
3298
  ### v0.4.2
2756
- - **Changed** (BREAKING) `color` prop renamed to `variant` (values `filled`/`tonal` unchanged)
3299
+ - **Changed** (BREAKING) `color` prop renamed to `variant`
2757
3300
 
2758
3301
  ### v0.1.1
2759
3302
  - **Fixed** `tabIndex={0}` changed to `tabIndex={-1}` on tablist wrapper — fixes double-focus keyboard navigation bug
@@ -2769,11 +3312,13 @@ const { size, stroke } = useIconContext()
2769
3312
 
2770
3313
  ## Props
2771
3314
  ### SelectTrigger
2772
- size: "sm" | "md" | "lg"
3315
+ variant: "default" | "outline" | "ghost"
3316
+ color: "default" | "error" | "success" | "warning" (sets aria-invalid when error)
3317
+ size: "xs" | "sm" | "md" | "lg"
2773
3318
 
2774
3319
  ## Compound Components
2775
3320
  Select (root — value, onValueChange, defaultValue)
2776
- SelectTrigger (size goes HERE, not on Select root)
3321
+ SelectTrigger (variant/color/size go HERE, not on Select root)
2777
3322
  SelectValue (placeholder)
2778
3323
  SelectContent
2779
3324
  SelectGroup (optional grouping)
@@ -2782,7 +3327,7 @@ const { size, stroke } = useIconContext()
2782
3327
  SelectSeparator
2783
3328
 
2784
3329
  ## Defaults
2785
- SelectTrigger size: "md"
3330
+ SelectTrigger variant="default", color="default", size="md"
2786
3331
 
2787
3332
  ## Example
2788
3333
  ```jsx
@@ -2797,6 +3342,15 @@ const { size, stroke } = useIconContext()
2797
3342
  </Select>
2798
3343
  ```
2799
3344
 
3345
+ ## Composability
3346
+ - **Radix Select** underneath — `value`/`onValueChange`/`defaultValue`/`open`/`onOpenChange` standard state.
3347
+ - **Styling props live on SelectTrigger, not Select root.** The Select root owns value/state; the Trigger owns appearance (variant/color/size). Setting `<Select size="lg">` silently does nothing — TypeScript won't catch it.
3348
+ - **SelectItem requires `value`** — unique within the Select. Labels are the children of SelectItem.
3349
+ - **SelectGroup + SelectLabel:** Organize options into sections with a non-interactive section heading.
3350
+ - **FormField integration:** Set `color="error"` on SelectTrigger for error visuals. Not auto-consumed from FormField (same as Checkbox/Radio — form-library convention for select controls).
3351
+ - **Portal + z-popover (1400):** content portals to body, stacks above Dialog/Sheet/other overlays.
3352
+ - **Select vs Combobox vs Autocomplete:** Select = short fixed list, click-to-open, no typeahead. Combobox = searchable, forced selection. Autocomplete = searchable, free text. Pick by list size and search need.
3353
+
2800
3354
  ## Gotchas
2801
3355
  - Size goes on SelectTrigger, NOT on Select root
2802
3356
  - `<Select size="lg">` is silently ignored (no TypeScript error)
@@ -2834,8 +3388,15 @@ const { size, stroke } = useIconContext()
2834
3388
  <Separator orientation="vertical" className="h-6" />
2835
3389
  ```
2836
3390
 
3391
+ ## Composability
3392
+ - Radix Separator — no context, no cascade. Drop it anywhere; it inherits its stretch dimension from its parent container (full width for horizontal, full height for vertical — but vertical needs an explicit height from the parent flexbox).
3393
+ - `decorative={true}` (default) sets `role="none"` — screen readers skip it. Set `decorative={false}` for semantic separators (e.g. between navigation sections) so screen readers announce the boundary.
3394
+ - Gradient variants compose cleanly with any surface color — they use `bg-transparent` + inline linear-gradient, so the underlying `bg-*` of the parent shows through.
3395
+ - Common inside Menu/DropdownMenu/Sheet components; their internal *Separator subcomponents already wrap this one.
3396
+
2837
3397
  ## Gotchas
2838
3398
  - When decorative is true, the separator is hidden from screen readers
3399
+ - Vertical separator needs an explicit height from the parent flex container (`h-6`, `h-full`, etc.)
2839
3400
 
2840
3401
  ## Changes
2841
3402
  ### v0.22.0
@@ -2880,8 +3441,18 @@ const { size, stroke } = useIconContext()
2880
3441
  </Sheet>
2881
3442
  ```
2882
3443
 
3444
+ ## Defaults
3445
+ side="right"
3446
+
3447
+ ## Composability
3448
+ - Same primitives as Dialog — open/onOpenChange/defaultOpen, trigger asChild, portal rendering, focus trap.
3449
+ - `side` controls both the enter-from edge and the layout: top/bottom sides full-width, left/right sides ~75% of viewport (capped at sm breakpoint).
3450
+ - Use Sheet for side-anchored drawers (settings panels, mobile navigation); use Dialog for centered modals.
3451
+ - On mobile (`isMobile` from use-mobile), consider Sheet as the mobile-friendly equivalent of a Dialog/Popover — more thumb-reachable on tall screens.
3452
+
2883
3453
  ## Gotchas
2884
3454
  - Uses Framer Motion for slide enter/exit animations (v0.18.0)
3455
+ - SheetTitle is required for accessibility (same rule as Dialog)
2885
3456
 
2886
3457
  ## Changes
2887
3458
  ### v0.18.0
@@ -2899,6 +3470,14 @@ const { size, stroke } = useIconContext()
2899
3470
  - Server-safe: No
2900
3471
  - Category: ui
2901
3472
 
3473
+ ## Props
3474
+ ### SidebarMenuButton
3475
+ variant: "default" | "outline"
3476
+ size: "sm" | "md" | "lg"
3477
+ isActive: boolean (highlights as current nav item)
3478
+ tooltip: ReactNode | string (shown when sidebar is collapsed)
3479
+ asChild: boolean (render as Slot — common with next/link)
3480
+
2902
3481
  ## Compound Components
2903
3482
  SidebarProvider (context provider — must wrap everything)
2904
3483
  Sidebar (root panel)
@@ -2927,6 +3506,9 @@ const { size, stroke } = useIconContext()
2927
3506
  ## Hook
2928
3507
  useSidebar() => { state, open, setOpen, openMobile, setOpenMobile, isMobile, toggleSidebar }
2929
3508
 
3509
+ ## Defaults
3510
+ SidebarMenuButton variant="default", size="md"
3511
+
2930
3512
  ## Example
2931
3513
  ```jsx
2932
3514
  <SidebarProvider>
@@ -2954,9 +3536,28 @@ const { size, stroke } = useIconContext()
2954
3536
  </SidebarProvider>
2955
3537
  ```
2956
3538
 
3539
+ ## Composability
3540
+ The whole Sidebar ecosystem is driven by `SidebarContext` — the Provider owns the expand/collapse state and every descendant reads from it.
3541
+
3542
+ **Who reads the context:**
3543
+ - `Sidebar` — applies collapsed / expanded / off-canvas styles based on `state`
3544
+ - `SidebarTrigger` — calls `toggleSidebar()` on click; reflects aria-expanded
3545
+ - `SidebarMenuButton` — uses `state === 'collapsed'` to show its `tooltip` (hidden label appears only when sidebar is icon-width)
3546
+ - `SidebarInset` — main content area adjusts its left offset based on sidebar width + state
3547
+ - `SidebarRail` — invisible hit target on the outer edge for click-to-toggle
3548
+ - Any user component that calls `useSidebar()` — full access to open/isMobile/toggle
3549
+
3550
+ **Desktop vs mobile:** Context tracks `isMobile` via the `use-mobile` hook and branches behavior: on desktop the sidebar collapses to an icon rail; on mobile it becomes an off-canvas drawer controlled by `openMobile`.
3551
+
3552
+ **Controlled or uncontrolled:** `SidebarProvider` accepts `open` + `onOpenChange` (controlled) or `defaultOpen` (uncontrolled). State is synced to a cookie for cross-route persistence.
3553
+
3554
+ **Next.js / router integration:** `SidebarMenuButton` with `asChild` wraps any link element (`next/link`, `react-router Link`, plain `<a>`) — it transfers its styling + isActive state to the child while preserving the link's navigation semantics.
3555
+
2957
3556
  ## Gotchas
2958
3557
  - SidebarProvider MUST wrap both Sidebar and SidebarInset
2959
3558
  - Use SidebarMenuButton for nav items (supports tooltip in collapsed state)
3559
+ - `tooltip` is only visible when sidebar is collapsed — providing one doesn't duplicate the visible label
3560
+ - Cookie-based state persistence means the sidebar defaults to its prior state on page reload — use `defaultOpen` to override if needed
2960
3561
 
2961
3562
  ## Changes
2962
3563
  ### v0.18.0
@@ -3034,6 +3635,13 @@ const { size, stroke } = useIconContext()
3034
3635
  <SkeletonImage height="h-64" />
3035
3636
  ```
3036
3637
 
3638
+ ## Composability
3639
+ - Server-safe — can render in RSC trees during server-side loading states.
3640
+ - **Pattern:** One SkeletonGroup per load region (wraps with `role="status"` + `aria-busy="true"` + announces the `label`). Inside, compose the individual shape skeletons (Avatar, Text, Button, Chart, Image) to mirror the structure that will appear when loaded.
3641
+ - Use LoadingSkeleton (composed) instead for pre-built layouts — CardSkeleton, TableSkeleton, ListSkeleton, BoardSkeleton — or PageSkeletons for full-page placeholders.
3642
+ - `animation="none"` disables animation entirely — useful when placed inside components that already have their own load animation.
3643
+ - No context cascade — each skeleton is independent. If you want to drive multiple skeletons from one "reduced motion" setting, either set `animation="none"` on each or rely on the `useReducedMotion` hook (shimmer already respects it automatically).
3644
+
3037
3645
  ## Gotchas
3038
3646
  - shimmer respects prefers-reduced-motion
3039
3647
  - SkeletonGroup adds role="status" and aria-busy="true" — wrap multiple skeletons for a11y
@@ -3049,7 +3657,12 @@ const { size, stroke } = useIconContext()
3049
3657
  - Category: ui
3050
3658
 
3051
3659
  ## Props
3052
- Standard Radix Slider props (value, onValueChange, defaultValue, min, max, step, aria-label)
3660
+ size: "sm" | "md" | "lg" (track height and thumb dimensions)
3661
+ color: "accent" | "success" | "warning" | "error" (thumb border + focus ring color)
3662
+ (plus standard Radix Slider props: value, onValueChange, defaultValue, min, max, step, aria-label)
3663
+
3664
+ ## Defaults
3665
+ size="md", color="accent"
3053
3666
 
3054
3667
  ## Example
3055
3668
  ```jsx
@@ -3057,9 +3670,18 @@ const { size, stroke } = useIconContext()
3057
3670
  <Slider defaultValue={[25, 75]} max={100} step={1} /> {/* range slider */}
3058
3671
  ```
3059
3672
 
3673
+ ## Composability
3674
+ - Radix Slider primitive — keyboard navigation (arrow keys, Home/End, PageUp/PageDown) pre-wired.
3675
+ - **Value is always an array** — single-thumb: `[50]`; range: `[25, 75]`. Don't pass a plain number.
3676
+ - **Multi-thumb range:** Pass `[start, end]` — renders two thumbs that can cross each other by default. Use `minStepsBetweenThumbs` to enforce a gap.
3677
+ - **FormField:** Slider does NOT auto-consume FormField state. No validation UX — sliders usually don't need it (values are always valid by construction).
3678
+ - **No label pairing via Label** — use `aria-label` or `aria-labelledby` directly on the Slider. The thumb is the focusable/labeled element.
3679
+ - Not portal-rendered — inline; overflow rules of parents apply.
3680
+
3060
3681
  ## Gotchas
3061
3682
  - value is number[] (array), not a single number
3062
3683
  - Multi-thumb: Pass array `defaultValue={[25, 75]}` for range sliders — renders one thumb per value
3684
+ - Slider does NOT auto-consume FormField — sliders don't have validation state visuals by design
3063
3685
 
3064
3686
  ## Changes
3065
3687
  ### v0.18.0
@@ -3097,6 +3719,13 @@ const { size, stroke } = useIconContext()
3097
3719
  <Spinner variant="bare" /> {/* uses currentColor, for embedding in buttons */}
3098
3720
  ```
3099
3721
 
3722
+ ## Composability
3723
+ - **Prefer Button's `loading` prop** for inline button spinners — Button's integration is automatic (disables, sets aria-busy, positions the spinner). Manual Spinner inside Button is more work for no gain.
3724
+ - **`variant="bare"`** uses `currentColor` — drop it into any surface (button, toolbar, badge, icon group) and it picks up the parent's text color automatically.
3725
+ - **State transitions:** `spinning → success → idle` / `spinning → error → idle`. Use the state machine for async operation feedback (the success checkmark, error X animate in). Pair with `onComplete` to trigger next-step logic after the transition.
3726
+ - **delay for flicker prevention:** Set `delay={150}` so very fast operations don't flash a spinner. The spinner mounts only after the delay elapses — if the operation completes first, the user never sees it.
3727
+ - **Icon.state="loading" uses Spinner internally** — same underlying component. If you're inside an Icon context, prefer `<Icon state="loading" />` for consistent sizing.
3728
+
3100
3729
  ## Gotchas
3101
3730
  - Renders role="status" with sr-only "Loading..." text — no need for aria-label
3102
3731
  - Button has built-in loading prop — prefer that over manual Spinner composition
@@ -3162,6 +3791,17 @@ import { SplitButton } from '@devalok/shilp-sutra'
3162
3791
  | dropdownLabel | string | 'More options' | aria-label for trigger |
3163
3792
  | dropdownIcon | ReactNode | chevron-down | Custom trigger icon |
3164
3793
 
3794
+ ## Composability
3795
+ - **Two-in-one button:** visually unified `[Action | ▼]` with the left half being the primary click and the right half opening a dropdown. Use for actions that have a most-common choice plus alternatives (Save vs. Save-As-Draft, Send vs. Schedule).
3796
+ - **Built on Button + Popover internally** — inherits Button's variant/color/size vocabulary and Popover's placement prop (`top-end` default works for most top-of-page toolbars).
3797
+ - **Dropdown content is consumer-provided** — pass any JSX via `dropdownContent` (typically a DropdownMenu, list of actions, a custom panel, or a small form). Don't try to shove a full-featured menu into the chevron; keep it focused on 2–5 alternatives.
3798
+ - **ButtonGroup compatibility:** Put SplitButton inside a `<ButtonGroup>` — it inherits variant/color/size from the group context just like Button does. Position-aware corners work too.
3799
+ - **Controlled dropdown:** Pass `open` + `onOpenChange` for controlled state; omit for uncontrolled. Useful when the dropdown must close programmatically after a selection.
3800
+
3801
+ ## Gotchas
3802
+ - Always provide `dropdownLabel` (aria-label on the chevron trigger) — defaults to "More options" but context-specific labels are better
3803
+ - `triggerSide="left"` flips the chevron to the left — rare, but useful for RTL layouts or when the primary action is the secondary emphasis
3804
+
3165
3805
  ## Changes
3166
3806
 
3167
3807
  ### v0.33.0
@@ -3191,6 +3831,14 @@ import { SplitButton } from '@devalok/shilp-sutra'
3191
3831
  </Stack>
3192
3832
  ```
3193
3833
 
3834
+ ## Composability
3835
+ - **Server-safe layout primitive.** Nothing to hydrate — works anywhere.
3836
+ - **Pattern:** Stack wraps child elements in a flex container. Swap between vertical/horizontal without rewriting the child layout.
3837
+ - **Polymorphic via `as`:** Use `as="ul"` / `as="ol"` for list semantics with flex styling; `as="section"` for major page divisions; etc.
3838
+ - **gap is design-system tokenized** — use token strings (`"ds-04"`) or numeric shortcuts (`4` → `"ds-04"`). Raw numbers map 1:1 to the `ds-0N` token set.
3839
+ - **Responsive direction:** Not built in — for responsive direction changes use Tailwind's `flex-col md:flex-row` directly on a plain div, or wrap multiple Stacks with display-toggling.
3840
+ - Container vs Stack: Container centers + caps width; Stack arranges children. Compose them: `<Container><Stack>...</Stack></Container>`.
3841
+
3194
3842
  ## Gotchas
3195
3843
  - "row" and "column" are aliases for "horizontal" and "vertical"
3196
3844
  - gap accepts both token strings and numeric values
@@ -3254,6 +3902,15 @@ import { SplitButton } from '@devalok/shilp-sutra'
3254
3902
  />
3255
3903
  ```
3256
3904
 
3905
+ ## Composability
3906
+ - **High-density metric card** — optimized for dashboards. Everything optional except `value`. Mix and match features (delta, sparkline, progress, secondary label, footer) per metric's needs.
3907
+ - **Router integration via href:** Internally uses `LinkContext` to resolve framework-specific Link components (Next.js, react-router). Set `href` in a LinkProvider-wrapped tree to get seamless client-side navigation without custom asChild wiring.
3908
+ - **Interactive modes:** `onClick` makes the entire card a button; `href` makes it a link. Mutually exclusive — href wins if both are set.
3909
+ - **Accent bar semantic:** Use `accent` to signal metric health at a glance (success for positive, warning for at-risk, error for over-target). Combine with delta.direction for layered emphasis.
3910
+ - **Sparkline:** Pure SVG, lightweight — no chart library. For rich charts use Chart components. Minimum 2 data points.
3911
+ - **Icon auto-sizing:** Accepts `ComponentType<{ className }>` OR `ReactNode`. The component prop (e.g. `icon={IconBolt}`) is preferred — icon is rendered at a consistent size.
3912
+ - **Loading state:** `loading={true}` renders the full card skeleton — use during initial data fetch.
3913
+
3257
3914
  ## Gotchas
3258
3915
  - delta.direction "up" = green, "down" = red, "neutral" = grey
3259
3916
  - `label` and `title` are aliases — use either, not both
@@ -3289,6 +3946,15 @@ import { SplitButton } from '@devalok/shilp-sutra'
3289
3946
  <StatusDot status="warning" size="lg" label="Elevated load" />
3290
3947
  ```
3291
3948
 
3949
+ ## Composability
3950
+ - **Server-safe presentational dot** — simple status indicator, pairs with inline text.
3951
+ - **Status semantics map to color:** healthy=success, warning=warning, critical=error, neutral=muted surface, inactive=dimmed. The pulse animation calls attention to "healthy" by default (active presence); you can flip it for other statuses by setting pulse explicitly.
3952
+ - **StatusDot vs BadgeIndicator vs StatusBadge:**
3953
+ - StatusDot = tiny presence/status indicator with optional inline label (e.g. "Service online")
3954
+ - BadgeIndicator = overlay on another element (notification dot on a bell icon)
3955
+ - StatusBadge (composed) = full pill-style badge with colored bg and label text (e.g. ticket status)
3956
+ - **Label composability:** If you pass `label`, it renders inline after the dot. Use `labelClassName` for custom typography (e.g. font-mono for status codes).
3957
+
3292
3958
  ## Gotchas
3293
3959
  - The `pulse` prop auto-enables for "healthy" status — pass `pulse={false}` to suppress
3294
3960
  - Status type is exported as `StatusDotStatus` if you need it in consumer code
@@ -3331,8 +3997,16 @@ import { SplitButton } from '@devalok/shilp-sutra'
3331
3997
  </Stepper>
3332
3998
  ```
3333
3999
 
4000
+ ## Composability
4001
+ - `Stepper` passes `activeStep` + `orientation` + each step's index down to every `Step` child via `StepperContext`. Each Step derives its own status (completed / active / pending) from its position vs `activeStep`.
4002
+ - Step index is assigned by position in children — order matters. Don't conditionally render Steps via `&&` or `.filter()`; it shifts the indices and breaks the active highlight.
4003
+ - `StepperContent` is separate from `Stepper` — it's the animated panel surface that shows one child per step index. Pass the same `activeStep` value to both.
4004
+ - `orientation="vertical"` swaps the Step layout (stacked with connecting line on the left) AND changes how StepperContent animates (vertical crossfade instead of horizontal slide).
4005
+ - Custom icons via `Step.icon` override the default number/checkmark — the icon slot still receives the status-based styling (muted for pending, accent for active/completed).
4006
+
3334
4007
  ## Gotchas
3335
4008
  - Steps before activeStep are "completed", at activeStep is "active", after is "pending"
4009
+ - Don't conditionally render Step children — position is the index contract
3336
4010
 
3337
4011
  ## Changes
3338
4012
  ### v0.18.0
@@ -3365,9 +4039,17 @@ import { SplitButton } from '@devalok/shilp-sutra'
3365
4039
  <Switch size="lg" color="success" thumbIcon={<IconCheck size={14} />} />
3366
4040
  ```
3367
4041
 
4042
+ ## Composability
4043
+ - Radix Switch primitive — `checked` / `onCheckedChange` / `defaultChecked` standard control model.
4044
+ - **FormField:** Switch does NOT auto-consume FormField state (same as Checkbox/Radio — form-library convention for toggles). Pass `error` explicitly when needed.
4045
+ - **thumbIcon slot:** Any ReactNode renders inside the thumb circle — commonly used for check/X glyphs that animate with the thumb position.
4046
+ - **Label pairing:** Manual — pair with `<Label htmlFor="x" />` + `<Switch id="x" />`, or wrap both in a `<label>` for click-to-toggle.
4047
+ - Pair with FormHelperText (outside FormField wiring) for custom validation messages.
4048
+
3368
4049
  ## Gotchas
3369
4050
  - Use error prop for validation states (matches Checkbox API)
3370
4051
  - `error` overrides `color` — when error is true, checked state always uses error-9
4052
+ - Switch does NOT auto-consume FormField — pass `error` explicitly inside a FormField
3371
4053
 
3372
4054
  ## Changes
3373
4055
  ### v0.29.0
@@ -3423,8 +4105,16 @@ import { SplitButton } from '@devalok/shilp-sutra'
3423
4105
  </Table>
3424
4106
  ```
3425
4107
 
4108
+ ## Composability
4109
+ - **Server-safe pure HTML wrappers** — Table and sub-components are thin semantic wrappers around `<table>`, `<thead>`, `<tbody>`, etc. No state, no context. Safe in RSC trees.
4110
+ - **Table vs DataTable:** Table is presentational — you control every row, cell, header. DataTable (from `ui/data-table`) is feature-rich — sorting, filtering, pagination, selection, virtualization built in. Pick by whether you need that machinery.
4111
+ - **Use cases for bare Table:** Static data displays, marketing comparison tables, documentation tables, small lists where DataTable would be overkill.
4112
+ - **Composes with UI primitives inside cells:** Badge for status pills, Avatar for user cells, IconButton for row actions, StatusDot for state indicators. All server-safe if the table is server-rendered.
4113
+ - **TableCaption** renders as HTML `<caption>` — useful for a summary description that screen readers announce before the table content.
4114
+
3426
4115
  ## Gotchas
3427
4116
  - Table headers automatically have scope="col" for screen reader navigation
4117
+ - For anything beyond trivial display, prefer DataTable — don't rebuild sorting/pagination/selection on top of bare Table
3428
4118
 
3429
4119
  ## Changes
3430
4120
  ### v0.18.0
@@ -3449,6 +4139,8 @@ import { SplitButton } from '@devalok/shilp-sutra'
3449
4139
 
3450
4140
  ### TabsList
3451
4141
  variant: "line" | "contained"
4142
+ size: "sm" | "md" | "lg"
4143
+ orientation: "horizontal" | "vertical"
3452
4144
 
3453
4145
  ### TabsTrigger
3454
4146
  value: string (REQUIRED)
@@ -3464,7 +4156,7 @@ import { SplitButton } from '@devalok/shilp-sutra'
3464
4156
  TabsContent (value)
3465
4157
 
3466
4158
  ## Defaults
3467
- TabsList variant: "line"
4159
+ TabsList variant="line", size="md", orientation="horizontal"
3468
4160
 
3469
4161
  ## Example
3470
4162
  ```jsx
@@ -3478,8 +4170,14 @@ import { SplitButton } from '@devalok/shilp-sutra'
3478
4170
  </Tabs>
3479
4171
  ```
3480
4172
 
4173
+ ## Composability
4174
+ - TabsList propagates `variant`, `size`, and `orientation` to every TabsTrigger child via `TabsListContext`. TabsTrigger reads all three from context; explicit props on a trigger override the inherited value.
4175
+ - `orientation="vertical"` on TabsList changes the TabsList layout (flex-col + left border) AND the `roving-tabindex` keyboard behavior (ArrowUp/Down navigate, not ArrowLeft/Right).
4176
+ - Tabs (root) is just the Radix Tabs.Root — state props (value, defaultValue, onValueChange) live there; styling props live on TabsList.
4177
+ - Tabs content is rendered inline (not portalled) — container-scoped queries work fine in tests.
4178
+
3481
4179
  ## Gotchas
3482
- - variant goes on TabsList, NOT on individual TabsTrigger (propagates via context)
4180
+ - variant/size/orientation go on TabsList, NOT on Tabs root or individual TabsTrigger
3483
4181
  - Normally omit `variant` on TabsTrigger — it inherits from TabsList via context. You CAN set it per-trigger to override.
3484
4182
 
3485
4183
  ## Changes
@@ -3502,12 +4200,9 @@ import { SplitButton } from '@devalok/shilp-sutra'
3502
4200
  - Category: ui
3503
4201
 
3504
4202
  ## Props
3505
- variant: TextVariant
4203
+ 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" | "label-plain-lg" | "label-plain-md" | "label-plain-sm" | "caption" | "overline" | "code"
3506
4204
  as: ElementType (override the auto-selected HTML element)
3507
4205
 
3508
- ## Types
3509
- TextVariant = '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'
3510
-
3511
4206
  ## Defaults
3512
4207
  variant: "body-md"
3513
4208
 
@@ -3522,9 +4217,17 @@ import { SplitButton } from '@devalok/shilp-sutra'
3522
4217
  <Text variant="label-sm" className="text-text-secondary">SECTION LABEL</Text>
3523
4218
  ```
3524
4219
 
4220
+ ## Composability
4221
+ - **Server-safe** (one of few components in the library that is). Can render in RSC trees without `"use client"`.
4222
+ - No context consumption, no context cascade — pure typography primitive.
4223
+ - **Semantic HTML by default:** Each variant maps to a meaningful HTML element (h1 through p, span, code). The `as` prop overrides for visual-only demotion: e.g. `<Text variant="heading-xl" as="div">` renders h2-sized text inside a div, useful when the element already has a heading ancestor but you want the visual weight.
4224
+ - **Underpins many components:** Card's CardTitle, Alert's title, PageHeader, EmptyState, SectionHeader all render Text internally with specific variants. Don't wrap another Text inside them — variants cascade structurally, not via context.
4225
+ - Pairs with Code for inline code spans inside body text: `<Text>Call <Code>onClick</Code> to...</Text>`.
4226
+
3525
4227
  ## Gotchas
3526
4228
  - label-* and overline variants are automatically uppercase
3527
4229
  - Use "as" prop to override the HTML element when needed
4230
+ - Don't use Text inside headings that already have semantic meaning (e.g. CardTitle) — the double-element wraps are redundant and break screen-reader heading navigation
3528
4231
 
3529
4232
  ## Changes
3530
4233
  ### v0.2.0
@@ -3539,7 +4242,7 @@ import { SplitButton } from '@devalok/shilp-sutra'
3539
4242
  - Category: ui
3540
4243
 
3541
4244
  ## Props
3542
- size: "sm" | "md" | "lg"
4245
+ size: "xs" | "sm" | "md" | "lg"
3543
4246
  state: "default" | "error" | "warning" | "success"
3544
4247
  (plus standard textarea attributes except native "size")
3545
4248
 
@@ -3551,6 +4254,12 @@ import { SplitButton } from '@devalok/shilp-sutra'
3551
4254
  <Textarea size="lg" state="error" placeholder="Describe the issue..." />
3552
4255
  ```
3553
4256
 
4257
+ ## Composability
4258
+ - **FormField auto-consumption:** Same pattern as Input — inside `<FormField>`, Textarea inherits `state`, `aria-describedby`, `aria-invalid`, `aria-required`. Explicit props override.
4259
+ - **Resize:** Vertically resizable by default (`resize-y`); override with `className="resize-none"` if needed.
4260
+ - **Label pairing:** Manual — `<Label htmlFor="x" />` + `<Textarea id="x" />`. FormField doesn't auto-wire.
4261
+ - No IconProvider cascade (no icon slots — different from Input).
4262
+
3554
4263
  ## Gotchas
3555
4264
  - state="error" sets aria-invalid automatically; all sizes are vertically resizable
3556
4265
  - Inside FormField: auto-inherits state, aria-describedby, aria-required from context (explicit props override)
@@ -3614,6 +4323,15 @@ toast.error('Upload failed', { description: 'File too large' })
3614
4323
  toast.undo('Task deleted', { onUndo: () => restoreTask(id) })
3615
4324
  ```
3616
4325
 
4326
+ ## Composability
4327
+ - **Imperative API only** — `toast.success(...)`, `toast.error(...)`, etc. NO JSX invocation. This is by design (Sonner-based) — you call from event handlers, not render.
4328
+ - **Requires Toaster mounted once** at app root. Without it, `toast.*` calls are no-ops. Render `<Toaster />` in the root layout, not inside route components.
4329
+ - **toast.promise** orchestrates async flows: `toast.promise(fetch(...), { loading: 'Saving', success: 'Saved', error: 'Failed' })`. One toast, three states. Beats manually calling `.loading()` + `.success()`/`.error()`.
4330
+ - **toast.undo** adds an inline Undo button with 8s default duration — pair with state management that supports reversal (soft-delete with restore, last-action redo).
4331
+ - **toast.upload** is specifically for file-upload progress — per-file progress bars, retry on error, remove from list. Replaces the old `UploadProgress` composed component.
4332
+ - **toast.custom** is the escape hatch — render arbitrary JSX. Use sparingly; prefer the typed methods for consistency.
4333
+ - **Distinction from Alert/Banner:** Toast = transient, auto-dismissing, non-interactive-dismissible floating notification. Alert = inline in-flow announcement. Banner = page-level strip. Pick by persistence + position.
4334
+
3617
4335
  ## Gotchas
3618
4336
  - DO NOT use useToast() hook — it is deprecated, use imperative toast.* methods
3619
4337
  - DO NOT use toast({ title, color }) object syntax — use toast.success('message') etc.
@@ -3671,9 +4389,17 @@ export default function RootLayout({ children }) {
3671
4389
  }
3672
4390
  ```
3673
4391
 
4392
+ ## Composability
4393
+ - **Mount once, use everywhere.** Render `<Toaster />` in the root layout file once. Every `toast.*` call anywhere in the tree routes to this single container.
4394
+ - **Global config lives here.** Set `position`, `duration`, `visibleToasts` on Toaster to control default behavior for all toasts. Individual `toast.*` calls can override `duration` per toast.
4395
+ - **Keyboard shortcut:** `hotkey` prop sets a global shortcut to focus the toast region (default: Alt+T). Useful for keyboard users to review recent notifications.
4396
+ - **Next.js / App Router:** Mount inside your root `app/layout.tsx`'s `<body>`. It's marked `'use client'` so it doesn't render on the server (Toaster is client-only).
4397
+ - **Portal rendering:** z-toast (highest layer) — Toaster content appears above Dialog, Popover, everything. Don't wrap it in a stacking context.
4398
+
3674
4399
  ## Gotchas
3675
4400
  - Must be mounted once at the layout root for toast notifications to work
3676
4401
  - Use the `toast` import from `@devalok/shilp-sutra/ui/toast` to trigger toasts
4402
+ - NOT server-safe — mounts with 'use client'; rendering during SSR has no visual effect
3677
4403
 
3678
4404
  ## Changes
3679
4405
  ### v0.18.0
@@ -3692,13 +4418,13 @@ export default function RootLayout({ children }) {
3692
4418
  ## Props
3693
4419
  variant: "default" | "outline"
3694
4420
  size: "sm" | "md" | "lg"
4421
+ color: "accent" | "error" | "success" | "neutral" (pressed-state bg + text color)
3695
4422
  pressed: boolean
3696
4423
  onPressedChange: (pressed: boolean) => void
3697
4424
  defaultPressed: boolean
3698
4425
 
3699
4426
  ## Defaults
3700
- variant: "default"
3701
- size: "md"
4427
+ variant="default", size="md", color="accent"
3702
4428
 
3703
4429
  ## Example
3704
4430
  ```jsx
@@ -3707,8 +4433,16 @@ export default function RootLayout({ children }) {
3707
4433
  </Toggle>
3708
4434
  ```
3709
4435
 
4436
+ ## Composability
4437
+ - Radix Toggle primitive — `pressed` / `onPressedChange` / `defaultPressed`. Not the same as Switch (boolean value doesn't map to on/off semantically — Toggle is "this action is currently active").
4438
+ - **Pairs with ToggleGroup** for mutually-exclusive or multi-select toggle clusters — ToggleGroupItem inherits variant + size from ToggleGroup via context. Don't set variant on a ToggleGroupItem directly.
4439
+ - **No context consumption as a standalone** — when rendered outside a ToggleGroup, Toggle is fully independent.
4440
+ - **Icon content:** Commonly wraps a single `<Icon>` for formatting toolbar toggles (Bold, Italic, AlignLeft). Pair with IconGroup for a horizontal cluster of independent toggles (vs. ToggleGroup for related state).
4441
+ - **aria-label is required** — icon-only toggles need an accessible name.
4442
+
3710
4443
  ## Gotchas
3711
4444
  - Always provide aria-label for accessibility
4445
+ - Toggle is not Switch — Toggle means "this action is active right now"; Switch means "this setting is on"
3712
4446
 
3713
4447
  ## Changes
3714
4448
  ### v0.18.0
@@ -3738,8 +4472,11 @@ export default function RootLayout({ children }) {
3738
4472
  value: string
3739
4473
 
3740
4474
  ## Compound Components
3741
- ToggleGroup (root)
3742
- ToggleGroupItem (value: string)
4475
+ ToggleGroup (root — variant, size propagated to items via context)
4476
+ ToggleGroupItem (value: string — reads variant/size from context)
4477
+
4478
+ ## Defaults
4479
+ variant="default", size="md"
3743
4480
 
3744
4481
  ## Example
3745
4482
  ```jsx
@@ -3750,9 +4487,15 @@ export default function RootLayout({ children }) {
3750
4487
  </ToggleGroup>
3751
4488
  ```
3752
4489
 
4490
+ ## Composability
4491
+ - ToggleGroup passes `variant` and `size` to every ToggleGroupItem via `ToggleGroupContext`. Items read both from context; explicit props on a child override.
4492
+ - `type="single"` enforces one-selected-at-a-time (value: string); `type="multiple"` allows many (value: string[]). Radix Toggle Group underpins this — it's the same role + keyboard model as Radix.
4493
+ - Built on top of the plain `Toggle` component's CVA — the `variant` and `size` axes of ToggleGroupItem match Toggle exactly (`default | outline`, `sm | md | lg`).
4494
+
3753
4495
  ## Gotchas
3754
- - type is required — "single" or "multiple"
3755
- - variant and size propagate from ToggleGroup to items
4496
+ - `type` is required — "single" or "multiple" — and drives the value shape
4497
+ - variant and size propagate from ToggleGroup to items; setting them on a ToggleGroupItem overrides that item only
4498
+ - Unlike Tabs (where TabsList is the styling surface), ToggleGroup itself is the styling surface — there's no intermediate List component
3756
4499
 
3757
4500
  ## Changes
3758
4501
  ### v0.18.0
@@ -3785,8 +4528,18 @@ export default function RootLayout({ children }) {
3785
4528
  </TooltipProvider>
3786
4529
  ```
3787
4530
 
4531
+ ## Composability
4532
+ - Built on Radix Tooltip — accepts `delayDuration`, `skipDelayDuration` (on TooltipProvider), and `open`/`onOpenChange`/`defaultOpen` (on Tooltip root).
4533
+ - **Auto-provider (v0.22.0+):** If no ancestor TooltipProvider exists, `<Tooltip>` auto-wraps itself with one. Explicit TooltipProvider is still recommended at layout root for shared `delayDuration` tuning.
4534
+ - **Trigger:** `<TooltipTrigger asChild>` around the element that should show the tooltip on hover/focus. Icon-only buttons are the canonical use case.
4535
+ - **Not interactive:** TooltipContent is for non-interactive text labels — don't put buttons/links inside. Use HoverCard or Popover when the popped content needs interaction.
4536
+ - **Positioning:** TooltipContent accepts `side`, `align`, `sideOffset`, and arrow via `<TooltipArrow>` (optional).
4537
+ - **Portal rendering:** z-index is `z-tooltip` (highest in the stack — above Popover and Dialog).
4538
+
3788
4539
  ## Gotchas
3789
- - TooltipProvider is REQUIRED — without it, tooltips won't show
4540
+ - TooltipProvider is REQUIRED at the layout level OR auto-created per-tooltip but having a single root provider is better for consistent delays
4541
+ - Don't use Tooltip for critical information — hover-triggered UI is invisible to touch users. Pair with a visible label or aria-description
4542
+ - TooltipContent children must be inert — no buttons, no links, no focusable elements
3790
4543
 
3791
4544
  ## Changes
3792
4545
  ### v0.22.0
@@ -3854,8 +4607,20 @@ export default function RootLayout({ children }) {
3854
4607
  </TreeView>
3855
4608
  ```
3856
4609
 
4610
+ ## Composability
4611
+ - **Two rendering modes:**
4612
+ - **Data-driven:** Pass `items: TreeNode[]` with nested `children`. Good for server-fetched or programmatic trees.
4613
+ - **Declarative:** Use `<TreeItem>` children directly. Good for hardcoded nav, readable JSX.
4614
+ - Don't mix — pick one per TreeView instance.
4615
+ - **useTree hook** lets you drive TreeView state externally (e.g. sync with URL, persist expanded state, drive from Redux). Pass `defaultExpanded`/`defaultSelected` initially OR manage state via the hook's return value.
4616
+ - **TreeItem composability:** Each TreeItem has `icon`, `label`, `secondaryLabel`, and `actions` slots — rich rows without custom render props. `actions` reveals on row hover (same pattern as Message.Actions).
4617
+ - **multiSelect + checkboxes:** Set both to turn TreeView into a file-picker style tree with checkboxes instead of single-select highlighting.
4618
+ - **Keyboard navigation:** Full `role="tree"` spec — Arrow Up/Down to move, Right to expand/descend, Left to collapse/ascend, Enter to select. Pre-wired; no manual key handling needed.
4619
+
3857
4620
  ## Gotchas
3858
4621
  - Supports both data-driven (items prop) and declarative (children) modes
4622
+ - Don't mix data-driven and declarative in the same TreeView — pick one
4623
+ - For a simple flat checkbox list, use RadioGroup/Checkbox + Stack — TreeView is overkill for non-hierarchical data
3859
4624
 
3860
4625
  ## Changes
3861
4626
  ### v0.4.2
@@ -3880,9 +4645,16 @@ export default function RootLayout({ children }) {
3880
4645
  <VisuallyHidden>Screen reader only text</VisuallyHidden>
3881
4646
  ```
3882
4647
 
4648
+ ## Composability
4649
+ - Server-safe. Works anywhere — no context, no cascade.
4650
+ - Canonical use: wrap a DialogTitle / SheetTitle that must exist for a11y but shouldn't show visually (`<DialogTitle asChild><VisuallyHidden>...</VisuallyHidden></DialogTitle>`).
4651
+ - Also useful inside IconButton to provide a text alternative when the icon already has an aria-label (redundant label is harmless and helps some screen readers).
4652
+ - Uses the `sr-only` CSS pattern under the hood — content is in the DOM, just positioned off-screen.
4653
+
3883
4654
  ## Gotchas
3884
4655
  - Content is visually hidden but accessible to screen readers
3885
4656
  - Useful for providing accessible labels without visual UI
4657
+ - Don't use for content you want hidden entirely — that's `hidden` or conditional rendering
3886
4658
 
3887
4659
  ## Changes
3888
4660
  ### v0.1.0
@@ -3933,6 +4705,12 @@ export default function RootLayout({ children }) {
3933
4705
  ## Exported Utilities
3934
4706
  groupItemsByTime(items: ActivityItem[], labels?: GroupLabels) — pure function that buckets items into time groups; returns { label: string, items: ActivityItem[] }[]
3935
4707
 
4708
+ ## Composability
4709
+ - **Built from ui primitives:** Avatar (actor), Button (Load more), Skeleton (loading), Text (body). Override via `renderItem` to use your own primitives per row.
4710
+ - **renderItem** is the composition hook — return your own JSX or `undefined` to fall back to default ActivityEntry. Timeline dot + layout wrapper stay consistent.
4711
+ - **groupBy="time"** wraps items in Today/Yesterday/This Week/Older buckets; `groupItemsByTime()` export reusable for custom renderers.
4712
+ - **Pagination is consumer-driven** — `hasMore` + `onLoadMore` for server-side, or `maxInitialItems` + "Show all" toggle for client-side truncation.
4713
+
3936
4714
  ## Gotchas
3937
4715
  - `items` is required — passing an empty array renders the `emptyState` content
3938
4716
  - `color` on each item controls the timeline dot color
@@ -3998,6 +4776,14 @@ export default function RootLayout({ children }) {
3998
4776
  />
3999
4777
  ```
4000
4778
 
4779
+ ## Composability
4780
+ - **Built on ui/Avatar** — each slot is an Avatar with overlap + border + optional Tooltip.
4781
+ - **Wraps TooltipProvider internally** — don't add another.
4782
+ - **renderAvatar** escape hatch for custom shapes. Wrapper handles overlap/border positioning; your renderer sets size + shape.
4783
+ - **borderColor** must match the surface the group sits on (`surface-raised` on Card, `surface-base` on page bg). Mismatch = visible seam.
4784
+ - **Overflow:** +N badge with optional `onOverflowClick` → pair with a Popover/Sheet for "show all members".
4785
+ - Per-user `ring` (lead/admin/client) surfaces Avatar's ring semantic at group level — use for role/presence hints.
4786
+
4001
4787
  ## Gotchas
4002
4788
  - Wraps TooltipProvider internally — no need to add one yourself
4003
4789
  - Users beyond `max` are collapsed into a "+N" overflow badge
@@ -4059,6 +4845,12 @@ export default function RootLayout({ children }) {
4059
4845
  />
4060
4846
  ```
4061
4847
 
4848
+ ## Composability
4849
+ - **Standalone floating toolbar** — use with DataTable, TreeView, or any selection-capable UI.
4850
+ - **DataTable auto-integration:** DataTable's `bulkActions` prop renders this internally — typically you don't render BulkActionBar directly when using DataTable.
4851
+ - **Data-driven actions:** `{ label, icon, onClick, color, disabled }[]`. For destructive confirmation, call ConfirmDialog from the onClick handler.
4852
+ - **Portal to body + fixed bottom-center z-50** — independent of parent layout. Check for other fixed elements that might overlap.
4853
+
4062
4854
  ## Gotchas
4063
4855
  - Renders via `createPortal` into `document.body` — will not appear during SSR (mounts only client-side)
4064
4856
  - Positioned fixed at bottom-center with `z-50`; ensure no other fixed elements conflict
@@ -4090,6 +4882,13 @@ CommandItem shape: { id, label, description?, icon?, shortcut?, onSelect: () =>
4090
4882
  />
4091
4883
  ```
4092
4884
 
4885
+ ## Composability
4886
+ - **Built on Dialog (portal) + cmdk-style fuzzy matching.** Global Ctrl+K / Cmd+K by default; customize via `keybinding`.
4887
+ - **Groups + items are data-driven.** Each item: `{ id, label, icon?, shortcut?, description?, onSelect }`. Labels accept ReactNode for rich rendering; pair with `filterValue` for typeahead matching against a text representation.
4888
+ - **renderLabel hook** lets you highlight matched characters in query (`(query) => ReactNode`).
4889
+ - **Controlled / uncontrolled** via `open`+`onOpenChange` or `defaultOpen`.
4890
+ - **AppCommandPalette (shell)** is the app-wide variant with CommandRegistry — use that for global palettes with dynamically registered commands.
4891
+
4093
4892
  ## Gotchas
4094
4893
  - Opens with Ctrl+K / Cmd+K by default
4095
4894
  - Items animate in with staggered slide-up (30ms delay cascade); groups fade in; active item icon/shortcut highlight in interactive color (v0.15.0)
@@ -4138,6 +4937,12 @@ const [open, setOpen] = useState(false)
4138
4937
  />
4139
4938
  ```
4140
4939
 
4940
+ ## Composability
4941
+ - **Built on AlertDialog** — non-dismissible (no click-outside, Cancel-first focus), portal-rendered.
4942
+ - **Controlled-only.** Parent owns `open` + `onOpenChange`.
4943
+ - **onConfirm can be async.** `loading=true` replaces confirm text with "Processing..." and disables both buttons. Typical: setLoading(true) → await action() → setLoading(false) + setOpen(false).
4944
+ - **Stays open after confirm** — close via onOpenChange. Keeps the modal up on errors or for chained confirmations.
4945
+
4141
4946
  ## Gotchas
4142
4947
  - Dialog stays open after confirm — consumer must close it via `onOpenChange`
4143
4948
  - Built on AlertDialog internally
@@ -4174,6 +4979,12 @@ const [open, setOpen] = useState(false)
4174
4979
  </ContentCard>
4175
4980
  ```
4176
4981
 
4982
+ ## Composability
4983
+ - **Server-safe higher-level Card** — wraps ui/Card with conventional header/title/actions/footer slots.
4984
+ - **When to use:** List rows, dashboard tiles, any "card with title + optional actions + body + optional footer" pattern. Use raw ui/Card for custom layouts or when you need accent bars.
4985
+ - **Slot precedence:** `header` beats `headerTitle`. Use `headerTitle` for simple strings, `header` for rich content.
4986
+ - **padding variants** (default/compact/spacious/none) set once on ContentCard; the underlying Card size cascade propagates.
4987
+
4177
4988
  ## Gotchas
4178
4989
  - Server-safe: can be imported directly in Next.js Server Components
4179
4990
  - Use `headerTitle` for simple text headers; use `header` for custom header content
@@ -4298,6 +5109,16 @@ PresetKey: 'today' | 'yesterday' | 'last7days' | 'last30days' | 'thisMonth' | 'l
4298
5109
  <TimePicker value={time} onChange={setTime} format="24h" minuteStep={15} />
4299
5110
  ```
4300
5111
 
5112
+ ## Composability
5113
+ - **7 sub-components in one kit:** DatePicker, DateRangePicker, DateTimePicker, TimePicker, CalendarGrid, MonthPicker, YearPicker, Presets. Built on Popover (trigger + calendar content) + date-fns.
5114
+ - **Layered abstractions:**
5115
+ - High-level: DatePicker / DateRangePicker / DateTimePicker / TimePicker (use these 95% of the time — Popover-wrapped, single-field pickers with formatted value display).
5116
+ - Low-level: CalendarGrid / MonthPicker / YearPicker (use for custom picker layouts, inline calendars, dashboards with embedded date selection).
5117
+ - **useCalendar hook** manages month navigation state — use it when building custom picker UI (e.g. inline calendar with custom header).
5118
+ - **Presets component** is standalone — drop it next to a DateRangePicker's calendar for quick-select sidebars (Today / Last 7 days / This month / etc.).
5119
+ - **Events on calendar:** CalendarGrid accepts `events: { date, color, label }[]` — renders up to 3 colored dots per day cell. Useful for availability / booked-days highlights.
5120
+ - **min/max/disabledDates** are shared contract across the whole family — pass the same bounds config to any date picker variant.
5121
+
4301
5122
  ## Gotchas
4302
5123
  - TimePicker stores time inside a Date object — only hours/minutes/seconds are meaningful
4303
5124
  - CalendarGrid is a low-level building block — prefer DatePicker/DateRangePicker for most use cases
@@ -4338,6 +5159,13 @@ PresetKey: 'today' | 'yesterday' | 'last7days' | 'last30days' | 'thisMonth' | 'l
4338
5159
  <DeadlineIndicator deadline={task.dueDate} warningThreshold={2880} criticalThreshold={480} />
4339
5160
  ```
4340
5161
 
5162
+ ## Composability
5163
+ - **Server-safe inline status** — renders a colored text label ("2d left" / "3h left" / "Overdue by 1d") with optional clock icon prefix.
5164
+ - **Threshold-driven color:** `warningThreshold` + `criticalThreshold` drive the green→yellow→red progression. Tune per use case (billing deadlines vs. task due dates have different urgency cadences).
5165
+ - **Composes inside Card, StatusBadge, DataTable cells** — anywhere a short inline deadline string fits.
5166
+ - **Doesn't live-update** — uses `Date.now()` at render time. For ticking timestamps, re-render via a parent interval or use a dedicated "time ago" library.
5167
+ - For static absolute timestamps (not relative), set `format="absolute"` — useful when you want the exact date rendered with semantic color coding.
5168
+
4341
5169
  ## Gotchas
4342
5170
  - Color is semantic: green (on-track) -> yellow (warning threshold) -> red (critical/overdue)
4343
5171
  - Overdue deadlines show bold red text with "Overdue by Xd/h/m"
@@ -4381,6 +5209,14 @@ EmojiPicker, EmojiPickerPopover
4381
5209
  <EmojiPicker onSelect={handleEmoji} theme="dark" />
4382
5210
  ```
4383
5211
 
5212
+ ## Composability
5213
+ - **Two exports:** `EmojiPicker` (inline grid, no trigger) and `EmojiPickerPopover` (trigger + popover wrapper). Use EmojiPickerPopover 95% of the time — trigger-on-click is the standard UX.
5214
+ - **Wraps @emoji-mart/react**, lazy-loaded with a Skeleton placeholder while the ~200KB bundle fetches. Don't pre-import unless you need it eagerly.
5215
+ - **Trigger composition:** EmojiPickerPopover's `children` is the trigger — wrap any Button/IconButton. Typical pairing is an icon-only IconButton with a 😀 label.
5216
+ - **TipTap integration:** Use `createEmojiSuggestion(set?)` factory to create a TipTap suggestion plugin that opens the picker on typing `:emoji`. Works with RichChatInput and RichTextEditor.
5217
+ - **Theme matching:** `theme="auto"` reads the `.dark` class on `<html>` — matches the DS dark mode toggle automatically. Override with explicit light/dark.
5218
+ - **Emoji sets:** Pass `set="apple" | "google" | ...` for consistent cross-platform emoji art (defaults to native OS glyphs).
5219
+
4384
5220
  ## Gotchas
4385
5221
  - Wraps `@emoji-mart/react` which is lazy-loaded — shows a Skeleton placeholder while loading
4386
5222
  - `theme="auto"` reads the `.dark` class on `<html>` to pick light/dark
@@ -4423,6 +5259,14 @@ Note: EmptyState was server-safe prior to v0.18.0 but is NO LONGER server-safe d
4423
5259
  />
4424
5260
  ```
4425
5261
 
5262
+ ## Composability
5263
+ - **Centered block for "no data" / "no results" states.** Renders icon + title + description + optional action slot.
5264
+ - **Icon composition:** Accepts both JSX (`<IconFolder />`) and component references (`IconFolder`). Component refs auto-instantiate with correct sizing. Default icon is the Devalok chakra.
5265
+ - **DataTable integration:** Pass an EmptyState component to DataTable's `emptyState` prop; takes precedence over `noResultsText` string.
5266
+ - **compact mode** for embedding inside Cards, sidebars, narrow sections. Defaults iconSize to 'sm' in compact mode automatically.
5267
+ - **Action slot** typically holds a single primary Button ("Create your first task", "Invite members"). For multi-action, use a div + flex with gap.
5268
+ - No longer server-safe (Framer Motion entrance animation).
5269
+
4426
5270
  ## Gotchas
4427
5271
  - `icon` accepts both JSX elements (`<IconFolder />`) and component references (`IconFolder`). Component references are auto-instantiated with correct sizing classes.
4428
5272
  - `iconSize` controls icon dimensions regardless of icon type. When `compact=true` and no `iconSize`, defaults to `'sm'`.
@@ -4465,6 +5309,13 @@ Note: EmptyState was server-safe prior to v0.18.0 but is NO LONGER server-safe d
4465
5309
  <ErrorDisplay error={error} onReset={() => refetch()} />
4466
5310
  ```
4467
5311
 
5312
+ ## Composability
5313
+ - **ErrorDisplay, not ErrorBoundary** — confusingly, the import path is `error-boundary` but the component is `ErrorDisplay`. It renders an error UI; it does NOT catch errors. Pair it with your own ErrorBoundary (from react-error-boundary, Next.js error.tsx, etc.) as the fallback UI.
5314
+ - **Auto-detects HTTP status codes** (404, 403, 500) when the `error` object has a `status` field — shows appropriate icon + message.
5315
+ - **Dev-only stack trace:** Renders the stack trace only when `process.env.NODE_ENV !== 'production'`. Production users see a clean error page.
5316
+ - **onReset for retry:** Pass a callback that re-fetches / resets state. Commonly wired to a react-query `refetch` or router `replace(...)`.
5317
+ - Composes with EmptyState for "no data" states (which aren't really errors) — use EmptyState for empty, ErrorDisplay for failed.
5318
+
4468
5319
  ## Gotchas
4469
5320
  - Auto-detects HTTP status codes (404, 403, 500) and shows appropriate icon/message
4470
5321
  - Shows stack trace in development mode only
@@ -4510,6 +5361,14 @@ Note: EmptyState was server-safe prior to v0.18.0 but is NO LONGER server-safe d
4510
5361
  <FilePreview url="https://youtube.com/watch?v=..." />
4511
5362
  ```
4512
5363
 
5364
+ ## Composability
5365
+ - **Type-auto-detection** from URL + mimeType routes to the right renderer (image / pdf / video / audio / embed). For ambiguous URLs, pass `type` explicitly.
5366
+ - **Heavy deps are lazy-loaded** — react-pdf (PDF), react-zoom-pan-pinch (Image zoom) are only fetched when first needed. Skeleton placeholder while loading. Don't pre-import.
5367
+ - **Composes inside Dialog/Sheet** — common pattern: thumbnail in a list → click to open a Dialog with `<FilePreview>` filling it.
5368
+ - **Embed URL normalization:** YouTube/Vimeo/Figma/Loom URLs auto-convert to embed format. Pass the original share URL; FilePreview handles it.
5369
+ - **PDF worker from unpkg CDN** — for offline apps, override `pdfjs.GlobalWorkerOptions.workerSrc` in your app entry.
5370
+ - **Download + error fallback** built in — if the file fails to load, users still get a download link.
5371
+
4513
5372
  ## Gotchas
4514
5373
  - Image/PDF lazy-loaded (Skeleton on first render)
4515
5374
  - PDF worker from unpkg CDN — configure workerSrc for offline apps
@@ -4569,6 +5428,14 @@ FilterBar, FilterSelect, FilterMultiSelect
4569
5428
  </FilterBar>
4570
5429
  ```
4571
5430
 
5431
+ ## Composability
5432
+ - **FilterBar + FilterSelect + FilterMultiSelect** — three-part kit. FilterBar is the toolbar container; FilterSelect/FilterMultiSelect are the individual filter controls.
5433
+ - **size propagates via context** from FilterBar to every FilterSelect/FilterMultiSelect child. Don't set size on individual filters.
5434
+ - **Children MUST be direct** — the size cascade breaks if filters are wrapped in extra divs. Use React fragments or let them be direct children.
5435
+ - **Active filter highlight:** FilterSelect/FilterMultiSelect auto-show an accent border when their value is set (non-empty array for multi, non-"all" for single).
5436
+ - **Pair with DataTable or any list:** FilterBar sits above a DataTable/list; `searchValue` + `onSearchChange` drive global filter; individual filters drive column filters via your own state management.
5437
+ - **"Clear all" convention:** Passing `onClearAll` renders a Reset button that's your escape hatch — implement it to clear all filter state in one call.
5438
+
4572
5439
  ## Gotchas
4573
5440
  - FilterSelect and FilterMultiSelect must be direct children of FilterBar to inherit the size context
4574
5441
  - FilterSelect uses `"all"` as the sentinel value for "no filter" — do not use `"all"` as a real option value
@@ -4601,6 +5468,12 @@ FilterBar, FilterSelect, FilterMultiSelect
4601
5468
  </FormSection>
4602
5469
  ```
4603
5470
 
5471
+ ## Composability
5472
+ - **Visual grouping of related FormFields.** Wraps its children with a title + description + horizontal rule separator. No state, no context.
5473
+ - **collapsible mode** wraps content in a Collapsible — useful for "Advanced settings" or optional form sections. `defaultOpen={false}` for initially-closed.
5474
+ - **Pairs with FormField:** Each form control inside should be a FormField for consistent label + helper text + validation. FormSection doesn't auto-wrap; you still structure each field yourself.
5475
+ - **Not a page-level header** — for a full form's main heading use PageHeader. FormSection is mid-form, between field groups.
5476
+
4604
5477
  ## Gotchas
4605
5478
  - `defaultOpen` only applies when `collapsible={true}` — otherwise the section is always open
4606
5479
  - Renders a horizontal rule between the header and content automatically
@@ -4621,6 +5494,12 @@ FilterBar, FilterSelect, FilterMultiSelect
4621
5494
  <GlobalLoading isLoading={isNavigating} />
4622
5495
  ```
4623
5496
 
5497
+ ## Composability
5498
+ - **Thin top-of-viewport progress bar** — for route-level navigation indicators (NProgress-style). Fixed at the top; z-toast layer.
5499
+ - **Wire to router events:** Pair with Next.js `useRouter` + navigation events, or react-router's `useNavigation`, or a custom global loading state in Redux/Zustand. Set `isLoading={true}` during transit, `false` when landed.
5500
+ - **Not for in-page loading** — use Spinner, Skeleton, or LoadingSkeleton for component-level loading states. GlobalLoading is strictly for cross-route / full-page transitions.
5501
+ - **Auto-unmounts when isLoading=false** — no need to manage visibility via classes or conditional rendering.
5502
+
4624
5503
  ## Gotchas
4625
5504
  - Fixed-position bar at top of viewport (z-toast layer)
4626
5505
  - Renders nothing when `isLoading` is false
@@ -4658,6 +5537,14 @@ FilterBar, FilterSelect, FilterMultiSelect
4658
5537
  />
4659
5538
  ```
4660
5539
 
5540
+ ## Composability
5541
+ - **contentEditable-based** — the text IS the editor (Notion / Linear / Figma layer-name pattern). No separate input field appears.
5542
+ - **Keyboard contract:** Click to focus (auto-selects text, like Finder rename). Type to edit. Enter saves, Escape reverts. Paste strips rich content.
5543
+ - **Async save:** `onSave` can return a Promise — InlineEdit shows a spinner and disables editing while pending. On rejection, text reverts to the original value automatically.
5544
+ - **Accessibility:** Accepts `aria-label` and `aria-labelledby` (forwarded to the role="textbox" span) — required when the text isn't self-descriptive. Falls back to `placeholder` as label when neither is provided.
5545
+ - **Not inside FormField** — InlineEdit is for in-place editing of existing content (task title, project name); use regular FormField + Input for traditional forms.
5546
+ - **textClassName for typography control:** Pass `"text-ds-lg font-semibold"` to make it look like a heading without changing the underlying element.
5547
+
4661
5548
  ## Gotchas
4662
5549
  - Uses contentEditable — the text IS the editor. No input field appears.
4663
5550
  - Click to focus → cursor appears in text. Type to edit. Enter saves. Escape reverts.
@@ -4707,6 +5594,13 @@ Exports: CardSkeleton, TableSkeleton, BoardSkeleton, ListSkeleton
4707
5594
  <ListSkeleton rows={10} showAvatar={false} />
4708
5595
  ```
4709
5596
 
5597
+ ## Composability
5598
+ - **Pre-composed skeleton layouts** — CardSkeleton, TableSkeleton, BoardSkeleton, ListSkeleton. Each mimics the shape of a common DS layout so users see meaningful loading placeholders.
5599
+ - **Built on ui/Skeleton** — for custom loading layouts, use Skeleton directly (rectangle/circle/text variants). These composed versions are just opinionated shape combinations.
5600
+ - **Server-safe** — use during SSR for initial-paint skeletons in Next.js app router (while server data streams in).
5601
+ - **Pairs with conditional rendering:** `{isLoading ? <TableSkeleton rows={8} /> : <DataTable data={rows} />}`.
5602
+ - **PageSkeletons (separate file)** provides full-page placeholders (DashboardSkeleton, ProjectListSkeleton, TaskDetailSkeleton) — use those for route-level loading states.
5603
+
4710
5604
  ## Gotchas
4711
5605
  - Server-safe: can be imported directly in Next.js Server Components
4712
5606
  - These are pre-composed skeleton layouts — for individual skeleton shapes, use the `Skeleton` UI component
@@ -4739,6 +5633,15 @@ Exports: CardSkeleton, TableSkeleton, BoardSkeleton, ListSkeleton
4739
5633
  <MarkdownViewer content={trustedHtml} allowHtml />
4740
5634
  ```
4741
5635
 
5636
+ ## Composability
5637
+ - **Read-only markdown renderer.** For editing, use RichTextEditor (TipTap-based). MarkdownViewer is strictly for display.
5638
+ - **Built on react-markdown + remark-gfm** — GFM tables, strikethrough, task lists supported out of the box.
5639
+ - **Syntax highlighting is lazy:** Code blocks with a language fence (```ts, ```python) lazy-load react-syntax-highlighter. First render shows a plain `<pre>` fallback. Don't pre-import.
5640
+ - **Security posture:** Raw HTML is stripped by default — `allowHtml={true}` must be explicit, and ONLY for trusted content (XSS vector otherwise).
5641
+ - **Links open external by default** (`target="_blank"` + `rel="noopener noreferrer"`). Override via `linkTarget`.
5642
+ - **compact mode** for inline use (comments, message bubbles). Default spacing is for article-body content.
5643
+ - **Pairs with Chat's Message.Body** — render markdown from user messages safely. Always keep `allowHtml={false}` for user-generated content.
5644
+
4742
5645
  ## Gotchas
4743
5646
  - Code blocks with a language fence are syntax-highlighted via `react-syntax-highlighter` (lazy-loaded) — the first render shows a plain `<pre>` fallback
4744
5647
  - GFM (tables, strikethrough, task lists) is supported via `remark-gfm`
@@ -4788,6 +5691,13 @@ MasterDetail (root), MasterDetail.List, MasterDetail.Detail, MasterDetail.ListIt
4788
5691
  </MasterDetail>
4789
5692
  ```
4790
5693
 
5694
+ ## Composability
5695
+ - **Responsive list+detail layout.** Desktop: side-by-side panels. Mobile (below `breakpoint`): stacked, mutually exclusive (list OR detail, controlled by `selected`).
5696
+ - **Compound structure:** `MasterDetail.List` contains `MasterDetail.ListItem[]` (interactive). `MasterDetail.Detail` holds the currently-selected view.
5697
+ - **onBack is required for mobile** — renders the back button in Detail pane. Omitting it leaves users stranded once they drill into an item on mobile.
5698
+ - **SSR gotcha:** Uses `window.matchMedia` — initial SSR render picks desktop mode; hydrates to mobile mode if viewport is narrow. If that causes layout shift, consider rendering this only after mount (via `useState(false)` + `useEffect`).
5699
+ - **Pairs with EmptyState** — render EmptyState inside Detail when `selected === null` on desktop ("Pick an item to get started").
5700
+
4791
5701
  ## Gotchas
4792
5702
  - On mobile (below breakpoint), List and Detail are mutually exclusive — selecting an item hides the list
4793
5703
  - The `onBack` callback is required for the mobile back button to appear in the Detail pane
@@ -4816,6 +5726,13 @@ MasterDetail (root), MasterDetail.List, MasterDetail.Detail, MasterDetail.ListIt
4816
5726
  </MemberPicker>
4817
5727
  ```
4818
5728
 
5729
+ ## Composability
5730
+ - **Trigger + Popover + search list.** Pass any button/element as `children` to act as the trigger. Typical pairing: outline Button that says "Assign" or an IconButton with avatar overflow.
5731
+ - **Single vs multi:** `multiple={true}` allows multiple selections; `false` (default) replaces the previous selection. `selectedIds` is always an array in both modes — consumer controls semantics.
5732
+ - **onSelect with a single ID:** Fires once per click with one memberId. Toggle logic in multi mode is consumer responsibility (derive the new array from the click).
5733
+ - **Avatar display:** Uses ui/Avatar internally — supply `avatar` URL in the member object, fallback to initials from `name`.
5734
+ - **For general-purpose multi-select** (non-members), use MultiSelectPopover which has the same popover+search pattern without member-specific avatar rendering.
5735
+
4819
5736
  ## Gotchas
4820
5737
  - `children` is used as the trigger element (e.g., a Button)
4821
5738
  - `onSelect` is called with a single `memberId` — toggle logic is up to the consumer
@@ -4876,6 +5793,17 @@ MasterDetail (root), MasterDetail.List, MasterDetail.Detail, MasterDetail.ListIt
4876
5793
  </MultiSelectPopover>
4877
5794
  ```
4878
5795
 
5796
+ ## Composability
5797
+ - **Generalized multi-select popover** — picks from a fixed list (items) or grouped list (groups), with search, async search, and custom rendering.
5798
+ - **Items vs groups (mutually exclusive):** Pass `items` for flat lists, `groups` for sectioned lists. Don't pass both.
5799
+ - **Async search via onSearch:** When provided, local filtering is disabled — the callback owns filtering and returns a new list. `searchDebounce` (default 300ms) throttles calls.
5800
+ - **renderItem escape hatch:** Pass `(item, selected) => ReactNode` for custom item rendering (avatar + multi-line descriptions, etc.). Built-in default renders image + label + description.
5801
+ - **maxSelections behavior:** At the limit, clicking a new item REPLACES the oldest selection (FIFO). `maxSelections={1}` effectively acts as single-select.
5802
+ - **MultiSelectPopover vs Combobox vs MemberPicker:**
5803
+ - Combobox = form-field multi-select (typeahead + selection in place)
5804
+ - MultiSelectPopover = button-triggered popup for bulk selection (good for "Assign to" / "Add tags" scenarios)
5805
+ - MemberPicker = MultiSelectPopover specialized for team-member UI
5806
+
4879
5807
  ## Gotchas
4880
5808
  - Supply either `items` (flat) or `groups` (sectioned), not both
4881
5809
  - When `onSearch` is provided, local filtering is disabled — the callback must return results
@@ -4919,6 +5847,13 @@ MasterDetail (root), MasterDetail.List, MasterDetail.Detail, MasterDetail.ListIt
4919
5847
  />
4920
5848
  ```
4921
5849
 
5850
+ ## Composability
5851
+ - **Server-safe page-top header** with title + subtitle + breadcrumbs + actions slot. Renders Breadcrumb internally from the `breadcrumbs` array.
5852
+ - **Auto-derived title:** If `title` is omitted, the last breadcrumb's `label` becomes the h1. Saves duplication when the page title matches the breadcrumb leaf.
5853
+ - **Breadcrumb contract:** Last item should NOT have an `href` (it's the current page — renders as BreadcrumbPage, not a link). Items with `href` render as BreadcrumbLink.
5854
+ - **actions slot:** Typically one to three buttons (Save, Cancel, More). For larger action groups, use ButtonGroup or a toolbar.
5855
+ - **Not for tabs / sub-navigation** — pair with Tabs rendered below PageHeader for that.
5856
+
4922
5857
  ## Gotchas
4923
5858
  - Server-safe: can be imported directly in Next.js Server Components
4924
5859
  - If `title` is omitted, the last breadcrumb's `label` is used as the page title
@@ -4951,6 +5886,16 @@ Exports: DashboardSkeleton, ProjectListSkeleton, TaskDetailSkeleton
4951
5886
  <TaskDetailSkeleton />
4952
5887
  ```
4953
5888
 
5889
+ ## Composability
5890
+ - **Full-page skeleton layouts** for route-level loading states. Each mimics a common page shape (dashboard tiles, project list with filters, task detail with sidebar).
5891
+ - **Server-safe** — use in Next.js app router `loading.tsx` files for instant route-transition feedback while data streams.
5892
+ - **Built on LoadingSkeleton + ui/Skeleton** — these just assemble the pre-built regional skeletons into page-shaped layouts.
5893
+ - **When to use which skeleton tier:**
5894
+ - `<Skeleton>` (ui) — single shape for a single element
5895
+ - `<CardSkeleton>` / `<TableSkeleton>` (LoadingSkeleton) — individual region shape
5896
+ - `<DashboardSkeleton>` / etc. (PageSkeletons) — full page placeholder
5897
+ - **Fixed layout structure** — the className prop adjusts the outer container, but internal layout isn't customizable. For custom page skeletons, compose LoadingSkeleton pieces yourself.
5898
+
4954
5899
  ## Gotchas
4955
5900
  - Server-safe: can be imported directly in Next.js Server Components
4956
5901
  - These are full-page skeleton layouts — for smaller skeleton sections, use LoadingSkeleton components
@@ -4983,6 +5928,13 @@ Priority = 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT' | 'low' | 'medium' | 'high' | 'u
4983
5928
  <PriorityIndicator priority="low" display="compact" />
4984
5929
  ```
4985
5930
 
5931
+ ## Composability
5932
+ - **Server-safe priority label** — icon + color + text for task / issue priority.
5933
+ - **Composes inside list rows, DataTable cells, Card headers, task panels** — anywhere a priority flag fits.
5934
+ - **display="compact"** shows only the icon (with priority text as title attribute for tooltip). Use in tight cells; use `display="full"` (default) in free space.
5935
+ - **Case-insensitive priority** — accepts both UPPERCASE (LOW/MEDIUM/HIGH/URGENT) and lowercase. Designed to match both backend conventions without manual coercion.
5936
+ - Color semantics: LOW=success, MEDIUM=warning, HIGH=error, URGENT=error with bolder icon.
5937
+
4986
5938
  ## Gotchas
4987
5939
  - Case-insensitive — "low" and "LOW" both work
4988
5940
  - Server-safe: can be imported directly in Next.js Server Components
@@ -4994,40 +5946,6 @@ Priority = 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT' | 'low' | 'medium' | 'high' | 'u
4994
5946
 
4995
5947
  ### v0.1.0
4996
5948
  - **Added** Initial release
4997
- # ResponsiveOverlay
4998
-
4999
- - Import: @devalok/shilp-sutra/composed/responsive-overlay
5000
- - Server-safe: No
5001
- - Category: composed
5002
-
5003
- ## Props
5004
- open: boolean
5005
- onOpenChange: (open: boolean) => void
5006
- title: string
5007
- description: string
5008
- breakpoint: "sm" | "md" (below this renders as bottom Sheet; above as Dialog)
5009
- children: ReactNode
5010
- className: string
5011
-
5012
- ## Defaults
5013
- breakpoint="md"
5014
-
5015
- ## Example
5016
- ```jsx
5017
- <ResponsiveOverlay
5018
- open={open}
5019
- onOpenChange={setOpen}
5020
- title="Edit task"
5021
- description="Update the task details"
5022
- >
5023
- <TaskForm />
5024
- </ResponsiveOverlay>
5025
- ```
5026
-
5027
- ## Gotchas
5028
- - Renders a centered Dialog on desktop and a bottom Sheet on mobile — same content, different container
5029
- - Uses `window.matchMedia` internally — SSR defaults to desktop (Dialog) until hydration
5030
- - Title and description are optional; if omitted, no header is rendered in either mode
5031
5949
  # RichChatInput
5032
5950
 
5033
5951
  - Import: @devalok/shilp-sutra/composed/rich-chat-input
@@ -5097,6 +6015,18 @@ SlashCommandGroup: { label: string; commands: SlashCommand[] }
5097
6015
  />
5098
6016
  ```
5099
6017
 
6018
+ ## Composability
6019
+ - **Chat-specific TipTap editor** — purpose-built for AI + human messaging. Built on RichTextEditor primitives but pre-configured for the chat UX (auto-resize, Enter-to-send, inline toolbar).
6020
+ - **Variant drives the UX envelope:**
6021
+ - `compact` — 2-3 line inline (chat bubble composer)
6022
+ - `expanded` — 5+ lines with always-visible toolbar (AI prompt input)
6023
+ - `minimal` — single line, toolbar on focus (reply composer, quick comment)
6024
+ - **Toolbar is opt-in per feature:** Icons only appear when their corresponding handler/prop is set. `onFileUpload` → attach button appears. `slashCommands` → slash button appears. `mentions` or `onMentionSearch` → @ button appears.
6025
+ - **Composes with Message from ui/chat:** RichChatInput is the composer; Message is the read-only render of the message after send. Use them together for a complete chat UX (RichChatInput at the bottom, MessageList above).
6026
+ - **TipTap is bundled** — no need to install `@tiptap/*` directly.
6027
+ - **For general rich text editing** (not chat — long-form docs, notes), use RichTextEditor instead.
6028
+ - **isStreaming + onCancel** — when the receiving side is streaming a response, show a stop button in place of send. Standard AI chat pattern.
6029
+
5100
6030
  ## Gotchas
5101
6031
  - Tiptap is bundled — no need to install `@tiptap/*` packages separately
5102
6032
  - Enter sends by default; use `enterBehavior="newline"` for long-form composition (Cmd/Ctrl+Enter always sends)
@@ -5165,6 +6095,15 @@ MentionItem: { id: string; label: string; avatar?: string }
5165
6095
  <RichTextViewer content={savedHtml} />
5166
6096
  ```
5167
6097
 
6098
+ ## Composability
6099
+ - **Two exports — editor + viewer.** RichTextEditor for composition; RichTextViewer for read-only rendering of saved HTML. Both share the same prose styling so round-trip display matches the editor.
6100
+ - **TipTap v3 bundled** — no `@tiptap/*` install needed. Consumers can't mix in arbitrary TipTap extensions without forking.
6101
+ - **Toolbar whitelist via `toolbar` prop:** Pass an array of ToolbarItem names to show only those buttons. Omit to show all. Dividers auto-collapse between empty groups.
6102
+ - **Image upload:** Without `onImageUpload`, pasted/dropped images are inlined as base64 (HTML bloats fast). Provide the handler to upload and return a URL.
6103
+ - **Mentions:** Static `mentions` array OR async `onMentionSearch` (which takes precedence). The viewer always renders mentions correctly from saved HTML — no mention props needed on the viewer side.
6104
+ - **For chat composition specifically** (AI + team chat with streaming / slash commands), use RichChatInput — it's built on the same foundation but pre-configured for chat UX.
6105
+ - **Pairs with MarkdownViewer** — many teams use RichTextEditor for compose (WYSIWYG), but render saved content as markdown for simpler serialization. Convert HTML ↔ markdown at the storage boundary.
6106
+
5168
6107
  ## Gotchas
5169
6108
  - Tiptap is bundled — no need to install `@tiptap/*` packages separately
5170
6109
  - Emoji picker requires `@emoji-mart/react` + `@emoji-mart/data` peers
@@ -5229,6 +6168,14 @@ Event colors: "primary" | "success" | "warning" | "error" | "info" | "neutral"
5229
6168
  />
5230
6169
  ```
5231
6170
 
6171
+ ## Composability
6172
+ - **Day / Week calendar view** for time-block display (meetings, shifts, availability). Not a full calendar app — no month view, no drag-to-create.
6173
+ - **Event data is consumer-owned:** You pass `events` as an array; ScheduleView doesn't fetch, doesn't cache, doesn't expand recurring events. All scheduling logic lives in your app.
6174
+ - **Event click + slot click** — `onEventClick` for existing events; `onSlotClick` for creating new events (fires with start/end of the empty slot).
6175
+ - **Color vocabulary matches the DS** — `primary/success/warning/error/info/neutral`. Map your event types to these at the data layer.
6176
+ - **endHour is exclusive:** `endHour=18` means the last visible slot starts at 17:30 (with 30min slots). Match your UX expectation: 9-5 typically means `startHour=9, endHour=18`.
6177
+ - **Pairs with date-picker/composed** — use DatePicker or DateRangePicker to choose which date to show; pass that as ScheduleView's `date`.
6178
+
5232
6179
  ## Gotchas
5233
6180
  - `endHour` is exclusive — `endHour=18` means the last visible slot starts at 17:30 (with default 30min slots)
5234
6181
  - `onSlotClick` fires when clicking an empty time slot — useful for creating new events
@@ -5260,6 +6207,13 @@ Event colors: "primary" | "success" | "warning" | "error" | "info" | "neutral"
5260
6207
  </SimpleTooltip>
5261
6208
  ```
5262
6209
 
6210
+ ## Composability
6211
+ - **One-liner Tooltip** — wraps TooltipProvider + Tooltip + TooltipTrigger + TooltipContent so consumers don't have to manually compose them for a simple label.
6212
+ - **When to use:** 90% of tooltip use cases (icon-only button labels, abbreviated text expansions, secondary info). Use the ui/Tooltip compound for advanced cases (controlled open, nested triggers, custom animations).
6213
+ - **Auto-provides its own TooltipProvider** — safe to drop anywhere. You can still wrap a broader TooltipProvider at layout level for shared `delayDuration`; SimpleTooltip respects it if present.
6214
+ - **Content must be inert** — same rule as ui/Tooltip. For interactive popped content, use Popover or HoverCard.
6215
+ - **Pairs with IconButton** — the canonical pattern for labeled icon buttons.
6216
+
5263
6217
  ## Gotchas
5264
6218
  - Wraps the full Tooltip compound (Provider + Tooltip + Trigger + Content) into one component — no need for TooltipProvider
5265
6219
  - Unlike the low-level Tooltip, SimpleTooltip does not require wrapping in a TooltipProvider
@@ -5300,6 +6254,16 @@ Note: StatusBadge was server-safe prior to v0.18.0 but is NO LONGER server-safe
5300
6254
  <StatusBadge status="active" onClick={() => openStatusPicker()} />
5301
6255
  ```
5302
6256
 
6257
+ ## Composability
6258
+ - **Semantic pill for workflow status.** Built on ui/Badge but with opinionated status → color mapping (active=success, pending=warning, rejected=error, etc.).
6259
+ - **status vs color discriminated union:** Pass either `status` (auto-mapped color) or `color` (explicit). Don't pass both — `color` wins if you do.
6260
+ - **Interactive mode:** Passing `onClick` renders as a `<button>` with a trailing chevron — common pattern for opening a status picker dropdown. Pair with a Popover or DropdownMenu for the status selector.
6261
+ - **Pairs with DataTable, Card headers, activity feeds** — anywhere an inline status pill fits. Use `size="sm"` for dense rows.
6262
+ - **StatusBadge vs ui/Badge vs StatusDot:**
6263
+ - StatusBadge = workflow status pill with built-in status semantics
6264
+ - ui/Badge = generic badge with full color/variant/shape control
6265
+ - StatusDot = tiny presence indicator (no label pill)
6266
+
5303
6267
  ## Gotchas
5304
6268
  - When `color` is set, it takes priority over `status` for styling
5305
6269
  - Props use a discriminated union — pass either `status` or `color`, not both
@@ -5367,6 +6331,14 @@ AppCommandPaletteUser: { name: string, role?: string }
5367
6331
  />
5368
6332
  ```
5369
6333
 
6334
+ ## Composability
6335
+ - **Shell-level wrapper around composed/CommandPalette** — adds opinionated app conventions: user-aware admin command groups, search result integration, navigation dispatch.
6336
+ - **Required setup:** Place inside `<CommandRegistryProvider>` (which owns the list of page items). Typically at app root next to TopBar.
6337
+ - **Router integration via onNavigate:** Pass `(path) => router.push(path)` (Next.js) or equivalent for your framework. All page command clicks funnel through this callback.
6338
+ - **Server-search integration:** Pass `onSearch` + `searchResults` + `onSearchResultSelect` for async search (API calls). `isSearching` drives a loading state. When these props are omitted, AppCommandPalette falls back to local filtering of registered pages.
6339
+ - **Admin gating:** `isAdmin=true` surfaces `adminPages` from CommandRegistry. Takes precedence over `user.role`-based detection so you can force admin mode during testing / impersonation.
6340
+ - **For scoped, non-app-wide palettes** (per-page command trees, custom popups), use composed/CommandPalette directly without the Registry layer.
6341
+
5370
6342
  ## Gotchas
5371
6343
  - Uses CommandRegistry context for page navigation items (see CommandRegistryProvider)
5372
6344
  - `isAdmin` takes precedence over `user.role` for showing admin command groups
@@ -5408,6 +6380,18 @@ BottomNavbarUser: { name: string, role?: string }
5408
6380
  />
5409
6381
  ```
5410
6382
 
6383
+ ## Composability
6384
+ - **Mobile-only bottom nav** — fixed to bottom of viewport. Desktop should use AppSidebar instead.
6385
+ - **Responsive switch pattern:** Use `useIsMobile()` hook to conditionally render AppSidebar (desktop) or BottomNavbar (mobile). Example:
6386
+ ```jsx
6387
+ const isMobile = useIsMobile()
6388
+ return isMobile ? <BottomNavbar ... /> : <AppSidebar ... />
6389
+ ```
6390
+ - **Primary vs overflow:** `primaryItems` (max 4) for the always-visible slots; `moreItems` go into a "More" sheet that opens on tap. Don't exceed 4 primary — the bar becomes cramped.
6391
+ - **Router integration via LinkProvider:** Each nav item is rendered using the framework-specific Link component registered in LinkProvider. Without LinkProvider, you get full-page reloads on tap.
6392
+ - **Badge numbers** cap at 99+ (same as BadgeIndicator pattern).
6393
+ - **Not for desktop:** The viewport-fixed positioning + touch-optimized sizing don't translate well to desktop. Hide behind `md:hidden`.
6394
+
5411
6395
  ## Gotchas
5412
6396
  - Designed for mobile viewports — fixed to bottom of screen
5413
6397
  - Max 4 `primaryItems` recommended; overflow goes in `moreItems` shown in a "More" sheet
@@ -5470,6 +6454,13 @@ CommandPageItem: { id: string, label: string, icon: ReactNode, path: string, key
5470
6454
  </CommandRegistryProvider>
5471
6455
  ```
5472
6456
 
6457
+ ## Composability
6458
+ - **Context provider for AppCommandPalette.** Registers page-level navigation items that the command palette surfaces as commands.
6459
+ - **Place at app root** — wrap both AppCommandPalette and the rest of the app inside `<CommandRegistryProvider>`. Positioning matters: any AppCommandPalette outside the provider gets `useCommandRegistry() === null` and falls back to minimal functionality.
6460
+ - **Separation of pages vs adminPages** — the palette filters based on user role / `isAdmin` flag. Keep admin-only routes in the adminPages array to avoid leaking them to regular users.
6461
+ - **useCommandRegistry()** is the consumer hook — returns the full registry or null. Use in your own command-aware components (e.g. a Spotlight-style keyboard-search embed elsewhere in the app).
6462
+ - **Works with LinkProvider** — CommandPaletteItems navigate via `onNavigate` prop on AppCommandPalette, which routes to your framework's Link component.
6463
+
5473
6464
  ## Gotchas
5474
6465
  - Provides the command registry context consumed by AppCommandPalette
5475
6466
  - Place at app root, wrapping both AppCommandPalette and the rest of the app
@@ -5508,6 +6499,21 @@ import Link from 'next/link'
5508
6499
  </LinkProvider>
5509
6500
  ```
5510
6501
 
6502
+ ## Composability
6503
+ - **The framework router bridge for all shell components.** Without LinkProvider, AppSidebar / BottomNavbar / TopBar.UserMenu / AppCommandPalette render plain `<a>` tags — that means full page reloads instead of client-side navigation.
6504
+ - **Required setup:**
6505
+ ```tsx
6506
+ // Next.js
6507
+ import Link from 'next/link'
6508
+ <LinkProvider component={Link}>
6509
+ <App />
6510
+ </LinkProvider>
6511
+ ```
6512
+ - **Place at app root** — above every shell component that renders nav links.
6513
+ - **component must be forwardRef** (Next.js Link, Remix Link, react-router Link all qualify). Custom components need to forward ref + forward className + forward all anchor props.
6514
+ - **Consumer components use `useLink()`** — returns the registered component or falls back to plain `<a>`. Your own components can consume this hook to integrate with the same router abstraction.
6515
+ - **ui-category components don't consume LinkProvider** — they use `asChild` for router integration (e.g. `<Button asChild><NextLink ...>...</NextLink></Button>`). LinkProvider is shell-specific because shell components embed link arrays (can't use asChild per-item without breaking the data-driven API).
6516
+
5511
6517
  ## Gotchas
5512
6518
  - Without LinkProvider, shell components render plain `<a>` tags (full page reloads)
5513
6519
  - Place at app root, wrapping all shell components that render navigation links
@@ -5565,6 +6571,16 @@ NotificationAction: { label: string, variant?: 'primary' | 'default' | 'danger',
5565
6571
  />
5566
6572
  ```
5567
6573
 
6574
+ ## Composability
6575
+ - **Bell + Popover + notification list** — renders the bell button with unread count badge and a popover list on click.
6576
+ - **Typical placement:** Inside `<TopBar.Right>` — common pattern is `<TopBar.IconButton>` for utility actions PLUS `<NotificationCenter>` for the bell.
6577
+ - **No hardcoded routes** — `getNotificationRoute` is the consumer's routing decision. Return the correct path per notification type (task → `/tasks/:id`, comment → `/threads/:id`, etc.) or null for non-routable notifications.
6578
+ - **onNavigate** fires when a notification with a route is clicked — wire to your router's push/navigate call.
6579
+ - **Pagination:** Pass `hasMore` + `onFetchMore` for infinite-scroll of older notifications.
6580
+ - **emptySlot + footerSlot + headerActions** are content slots for customization — keep the bell+popover shell, swap the inside.
6581
+ - **Pairs with NotificationPreferences** (separate page component) for letting users configure which notification tiers/channels they want to receive.
6582
+ - **onDismiss** is optional — when provided, per-notification X buttons appear. Otherwise mark-as-read is the only dismissal mechanism.
6583
+
5568
6584
  ## Gotchas
5569
6585
  - Typically rendered inside TopBar's `notificationSlot` prop
5570
6586
  - `getNotificationRoute` must be provided for clickable notifications — no hardcoded routes
@@ -5616,6 +6632,13 @@ NotificationProject: { id: string, title: string }
5616
6632
  />
5617
6633
  ```
5618
6634
 
6635
+ ## Composability
6636
+ - **Full-page preferences UI** — typically rendered on a settings page (`/settings/notifications`). Not an overlay; not a dropdown.
6637
+ - **Per-project preferences** — each preference is tied to a project + channel + tier. Users can mute/unmute and adjust tier (INFO / IMPORTANT / CRITICAL threshold) per project-channel combo.
6638
+ - **All callback props support Promise<void>** — integrate with server actions / fetch calls without manual loading state management.
6639
+ - **Pairs with NotificationCenter** — NotificationCenter shows notifications in real time (driven by these preferences); NotificationPreferences lets users tune the rules.
6640
+ - **Data ownership is consumer-side** — you pass `preferences` + `projects`; NotificationPreferences renders. No built-in persistence or sync.
6641
+
5619
6642
  ## Gotchas
5620
6643
  - Manages per-project notification preferences (channel, tier, mute)
5621
6644
  - All callback props support async (Promise<void>) for server-side operations
@@ -5674,6 +6697,17 @@ SidebarPromo: { text: string, icon?: ReactNode, action?: { label: string, href?:
5674
6697
  />
5675
6698
  ```
5676
6699
 
6700
+ ## Composability
6701
+ - **Opinionated app sidebar** — data-driven via `navGroups`, with user profile header, navigation groups (with optional labels + group actions), and a configurable footer (links + version + optional promo banner).
6702
+ - **Required setup (three providers):**
6703
+ - `SidebarProvider` (from ui/sidebar) — owns expanded/collapsed state + cookie persistence
6704
+ - `LinkProvider` — framework router integration for nav item links
6705
+ - `CommandRegistryProvider` (if pairing with AppCommandPalette) — registers the same navGroups so keyboard search finds them
6706
+ - **renderItem escape hatch:** Custom per-item rendering (e.g. custom icons per item type, status decorations, hover previews). Return `null` to fall back to default.
6707
+ - **Nested navigation:** NavItem's `children: NavSubItem[]` renders a collapsible subsection. Auto-opens if a child matches the current path.
6708
+ - **Footer as slot:** `footer` can include links, version info, a ReactNode slot (e.g. theme toggle), and a dismissable promo banner — structure it once, compose.
6709
+ - **Pairs with TopBar + BottomNavbar:** Desktop layout = AppSidebar + TopBar. Mobile layout = hide AppSidebar, show BottomNavbar.
6710
+
5677
6711
  ## Gotchas
5678
6712
  - Must be wrapped in SidebarProvider (from ui/sidebar)
5679
6713
  - Requires LinkProvider for framework-specific link components
@@ -5829,6 +6863,17 @@ UserMenuItem fields:
5829
6863
  </TopBar>
5830
6864
  ```
5831
6865
 
6866
+ ## Composability
6867
+ - **Composition-based, NOT data-driven.** Use dot-notation subcomponents (`TopBar.Left`, `TopBar.Center`, `TopBar.Right`, `TopBar.Section`, `TopBar.IconButton`, `TopBar.Title`, `TopBar.UserMenu`) to assemble. No "props config" — explicit JSX.
6868
+ - **Two-zone vs three-zone layout** — adding `TopBar.Center` flips the root from `flex` to CSS grid (`1fr auto 1fr`) for true centering. Standard apps are two-zone (Left + Right); dashboards with prominent search are three-zone.
6869
+ - **Required providers:**
6870
+ - `SidebarProvider` — for SidebarTrigger inside `TopBar.Left` to work
6871
+ - `LinkProvider` — for TopBar.UserMenu's menu items that use `href`
6872
+ - **NotificationCenter lives inside TopBar.Right** — it's a complete bell+popover component. Drop it in a TopBar.Section next to other icon buttons.
6873
+ - **UserMenu is composable:** `userMenuItems` inserts custom items between the built-in Profile and the color-mode toggle. Each item can navigate (href), run an action (onClick), or show a separator/badge/color decoration.
6874
+ - **Responsive hiding:** Actions that shouldn't appear on mobile use `className="hidden md:flex"` on the IconButton — the component doesn't enforce mobile hiding; that's layout responsibility.
6875
+ - **Pairs with AppSidebar** — desktop app shell is typically `<TopBar>` + `<AppSidebar>` + main content region.
6876
+
5832
6877
  ## Gotchas
5833
6878
  - Without `TopBar.Center`, layout is flex (two-zone). With it, layout switches to CSS grid `1fr auto 1fr` for true centering.
5834
6879
  - `TopBar.IconButton` renders any number of action buttons — no artificial limit. Use responsive hiding (`className="hidden md:flex"`) for mobile.