@devalok/shilp-sutra 0.52.0 → 0.54.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 (123) hide show
  1. package/AGENTS.md +7 -1
  2. package/BREAKING.json +29 -0
  3. package/MIGRATION.md +61 -0
  4. package/dist/_chunks/motion-provider.js +7 -6
  5. package/dist/_chunks/motion-provider.js.map +1 -1
  6. package/dist/_chunks/success.js +53 -53
  7. package/dist/ai/command-bar.js +158 -158
  8. package/dist/ai/command-bar.js.map +1 -1
  9. package/dist/ai/conversation.js +5 -5
  10. package/dist/composed/avatar-group.d.ts +11 -9
  11. package/dist/composed/avatar-group.d.ts.map +1 -1
  12. package/dist/composed/avatar-group.js +108 -140
  13. package/dist/composed/avatar-group.js.map +1 -1
  14. package/dist/composed/bulk-action-bar.d.ts +19 -11
  15. package/dist/composed/bulk-action-bar.d.ts.map +1 -1
  16. package/dist/composed/bulk-action-bar.js +81 -110
  17. package/dist/composed/bulk-action-bar.js.map +1 -1
  18. package/dist/composed/command-palette.js +17 -17
  19. package/dist/composed/command-palette.js.map +1 -1
  20. package/dist/composed/error-boundary.d.ts +15 -3
  21. package/dist/composed/error-boundary.d.ts.map +1 -1
  22. package/dist/composed/error-boundary.js +43 -26
  23. package/dist/composed/error-boundary.js.map +1 -1
  24. package/dist/composed/loading-skeleton.d.ts +2 -0
  25. package/dist/composed/loading-skeleton.d.ts.map +1 -1
  26. package/dist/composed/loading-skeleton.js +79 -56
  27. package/dist/composed/loading-skeleton.js.map +1 -1
  28. package/dist/composed/master-detail.d.ts +21 -8
  29. package/dist/composed/master-detail.d.ts.map +1 -1
  30. package/dist/composed/master-detail.js +91 -77
  31. package/dist/composed/master-detail.js.map +1 -1
  32. package/dist/composed/page-skeletons.d.ts +6 -0
  33. package/dist/composed/page-skeletons.d.ts.map +1 -1
  34. package/dist/composed/page-skeletons.js +93 -71
  35. package/dist/composed/page-skeletons.js.map +1 -1
  36. package/dist/composed/priority-indicator.d.ts +15 -6
  37. package/dist/composed/priority-indicator.d.ts.map +1 -1
  38. package/dist/composed/priority-indicator.js +37 -88
  39. package/dist/composed/priority-indicator.js.map +1 -1
  40. package/dist/composed/schedule-view.d.ts +17 -2
  41. package/dist/composed/schedule-view.d.ts.map +1 -1
  42. package/dist/composed/schedule-view.js +163 -64
  43. package/dist/composed/schedule-view.js.map +1 -1
  44. package/dist/motion/motion-provider.d.ts.map +1 -1
  45. package/dist/shell/bottom-navbar.d.ts +32 -6
  46. package/dist/shell/bottom-navbar.d.ts.map +1 -1
  47. package/dist/shell/bottom-navbar.js +156 -129
  48. package/dist/shell/bottom-navbar.js.map +1 -1
  49. package/dist/shell/index.d.ts +5 -3
  50. package/dist/shell/index.d.ts.map +1 -1
  51. package/dist/shell/index.js +2 -3
  52. package/dist/shell/notification-preferences.d.ts.map +1 -1
  53. package/dist/shell/notification-preferences.js +2 -0
  54. package/dist/shell/notification-preferences.js.map +1 -1
  55. package/dist/tokens/primitives.css +6 -2
  56. package/dist/tokens/utilities.css +6 -0
  57. package/dist/ui/autocomplete.d.ts +34 -32
  58. package/dist/ui/autocomplete.d.ts.map +1 -1
  59. package/dist/ui/autocomplete.js +115 -110
  60. package/dist/ui/autocomplete.js.map +1 -1
  61. package/dist/ui/combobox.d.ts +1 -1
  62. package/dist/ui/combobox.d.ts.map +1 -1
  63. package/dist/ui/combobox.js +133 -128
  64. package/dist/ui/combobox.js.map +1 -1
  65. package/dist/ui/file-upload.d.ts.map +1 -1
  66. package/dist/ui/file-upload.js +124 -138
  67. package/dist/ui/file-upload.js.map +1 -1
  68. package/dist/ui/form.d.ts +7 -0
  69. package/dist/ui/form.d.ts.map +1 -1
  70. package/dist/ui/form.js +4 -2
  71. package/dist/ui/form.js.map +1 -1
  72. package/dist/ui/icon-button.d.ts +2 -1
  73. package/dist/ui/icon-button.d.ts.map +1 -1
  74. package/dist/ui/icon-button.js +1 -1
  75. package/dist/ui/icon-button.js.map +1 -1
  76. package/dist/ui/index.js +30 -30
  77. package/dist/ui/label.d.ts.map +1 -1
  78. package/dist/ui/label.js +7 -6
  79. package/dist/ui/label.js.map +1 -1
  80. package/dist/ui/search-input.js +4 -4
  81. package/dist/ui/sidebar.js +7 -7
  82. package/dist/ui/spinner.d.ts.map +1 -1
  83. package/dist/ui/spinner.js +50 -44
  84. package/dist/ui/spinner.js.map +1 -1
  85. package/dist/ui/switch.d.ts.map +1 -1
  86. package/dist/ui/switch.js +24 -24
  87. package/dist/ui/switch.js.map +1 -1
  88. package/dist/ui/table.js +1 -1
  89. package/dist/ui/table.js.map +1 -1
  90. package/docs/components/composed/bulk-action-bar.md +9 -3
  91. package/docs/components/composed/error-boundary.md +18 -2
  92. package/docs/components/composed/master-detail.md +12 -2
  93. package/docs/components/composed/page-skeletons.md +2 -2
  94. package/docs/components/composed/priority-indicator.md +22 -11
  95. package/docs/components/composed/rich-chat-input.md +18 -6
  96. package/docs/components/composed/schedule-view.md +20 -5
  97. package/docs/components/composed/simple-tooltip.md +1 -1
  98. package/docs/components/shell/app-command-palette.md +2 -1
  99. package/docs/components/shell/bottom-navbar.md +28 -9
  100. package/docs/components/shell/command-registry.md +2 -2
  101. package/docs/components/shell/link-context.md +2 -2
  102. package/docs/components/shell/top-bar.md +1 -1
  103. package/docs/components/ui/autocomplete.md +26 -10
  104. package/docs/components/ui/search-input.md +2 -2
  105. package/docs/components/ui/slider.md +2 -2
  106. package/docs/components/ui/split-button.md +3 -3
  107. package/llms.txt +2 -2
  108. package/make-kit/Guidelines.md +1 -1
  109. package/make-kit/components/overview.md +2 -2
  110. package/make-kit/foundations/surfaces.md +1 -1
  111. package/mcp-manifest.json +581 -172
  112. package/package.json +1 -6
  113. package/skill/SKILL.md +1 -1
  114. package/skill/chat-skill/README.md +18 -0
  115. package/skill/chat-skill/SKILL.md +78 -0
  116. package/skill/chat-skill/chatgpt-instructions.md +26 -0
  117. package/skill/chat-skill/gemini-instructions.md +25 -0
  118. package/skill/references/components.md +2 -2
  119. package/dist/shell/sidebar.d.ts +0 -113
  120. package/dist/shell/sidebar.d.ts.map +0 -1
  121. package/dist/shell/sidebar.js +0 -293
  122. package/dist/shell/sidebar.js.map +0 -1
  123. package/docs/components/shell/sidebar.md +0 -103
@@ -1 +1 @@
1
- {"version":3,"file":"switch.js","names":[],"sources":["../../src/ui/switch.tsx"],"sourcesContent":["'use client'\n\nimport * as SwitchPrimitives from \"@primitives/react-switch\"\nimport { motion } from 'framer-motion'\nimport * as React from \"react\"\n\nimport { useFormField } from './form'\nimport { type FieldState, resolveFieldState } from './lib/field-state'\nimport { springs } from './lib/motion'\nimport { cn } from \"./lib/utils\"\n\nconst sizeConfig = {\n sm: { track: 'h-6 w-[38px]', thumb: 'h-5 w-5', travel: 16 },\n md: { track: 'h-6 w-11', thumb: 'h-ico-md w-ico-md', travel: 20 },\n lg: { track: 'h-7 w-[52px]', thumb: 'h-6 w-6', travel: 24 },\n} as const\n\n/** Validation border + checked-track tint per state (overrides `color` when set). */\nconst stateTintClasses: Record<Exclude<FieldState, 'default'>, string> = {\n error: 'border-error-7 data-[state=checked]:bg-error-9',\n warning: 'border-warning-7 data-[state=checked]:bg-warning-9',\n success: 'border-success-7 data-[state=checked]:bg-success-9',\n}\n\nconst colorMap = {\n accent: 'data-[state=checked]:bg-accent-9',\n success: 'data-[state=checked]:bg-success-9',\n warning: 'data-[state=checked]:bg-warning-9',\n} as const\n\nexport interface SwitchProps extends React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> {\n /** Validation/feedback state. `'error'` also sets `aria-invalid`. Inherited from `FormField` when omitted. Distinct from `color` (the ON-track tint). */\n state?: FieldState\n size?: 'sm' | 'md' | 'lg'\n color?: 'accent' | 'success' | 'warning'\n thumbIcon?: React.ReactNode\n}\n\nconst Switch = React.forwardRef<\n React.ElementRef<typeof SwitchPrimitives.Root>,\n SwitchProps\n>(({ className, state: stateProp, size = 'md', color = 'accent', thumbIcon, checked, defaultChecked, onCheckedChange, ...props }, ref) => {\n const fieldCtx = useFormField()\n const state = resolveFieldState(stateProp, fieldCtx.state)\n const ariaDescribedBy = props['aria-describedby'] ?? fieldCtx.helperTextId\n const ariaRequired = props['aria-required'] ?? fieldCtx.required\n\n // Track checked state internally to drive Framer Motion animation\n const [internalChecked, setInternalChecked] = React.useState(defaultChecked ?? false)\n const isChecked = checked !== undefined ? checked : internalChecked\n const { track, thumb, travel } = sizeConfig[size]\n\n const handleCheckedChange = React.useCallback(\n (value: boolean) => {\n if (checked === undefined) {\n setInternalChecked(value)\n }\n onCheckedChange?.(value)\n },\n [checked, onCheckedChange],\n )\n\n return (\n <SwitchPrimitives.Root\n className={cn(\n \"touch-target peer inline-flex shrink-0 cursor-pointer items-center rounded-pill border-2 border-surface-border-strong shadow-raised transition-colors duration-fast-01 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-action-disabled data-[state=checked]:border-transparent data-[state=unchecked]:bg-surface-border-strong data-[state=unchecked]:hover:bg-surface-raised-active\",\n track,\n colorMap[color],\n state && stateTintClasses[state],\n className\n )}\n checked={checked}\n defaultChecked={defaultChecked}\n onCheckedChange={handleCheckedChange}\n aria-invalid={state === 'error' || undefined}\n aria-describedby={ariaDescribedBy}\n aria-required={ariaRequired || undefined}\n {...props}\n ref={ref}\n >\n <SwitchPrimitives.Thumb asChild>\n <motion.span\n className={cn(\n \"pointer-events-none flex items-center justify-center rounded-pill bg-accent-fg shadow-raised-hover ring-0\",\n thumb\n )}\n animate={{ x: isChecked ? travel : 0 }}\n whileTap={{ scale: 0.85 }}\n transition={springs.snappy}\n >\n {thumbIcon}\n </motion.span>\n </SwitchPrimitives.Thumb>\n </SwitchPrimitives.Root>\n )\n})\nSwitch.displayName = SwitchPrimitives.Root.displayName\n\nexport { Switch }\n"],"mappings":";;;;;;;;;;AAWA,IAAM,IAAa;CACjB,IAAI;EAAE,OAAO;EAAgB,OAAO;EAAW,QAAQ;CAAG;CAC1D,IAAI;EAAE,OAAO;EAAY,OAAO;EAAqB,QAAQ;CAAG;CAChE,IAAI;EAAE,OAAO;EAAgB,OAAO;EAAW,QAAQ;CAAG;AAC5D,GAGM,IAAmE;CACvE,OAAO;CACP,SAAS;CACT,SAAS;AACX,GAEM,IAAW;CACf,QAAQ;CACR,SAAS;CACT,SAAS;AACX,GAUM,IAAS,EAAM,YAGlB,EAAE,cAAW,OAAO,GAAW,UAAO,MAAM,WAAQ,UAAU,cAAW,YAAS,mBAAgB,oBAAiB,GAAG,KAAS,MAAQ;CACxI,IAAM,IAAW,EAAa,GACxB,IAAQ,EAAkB,GAAW,EAAS,KAAK,GACnD,IAAkB,EAAM,uBAAuB,EAAS,cACxD,IAAe,EAAM,oBAAoB,EAAS,UAGlD,CAAC,GAAiB,KAAsB,EAAM,SAAS,KAAkB,EAAK,GAC9E,IAAY,MAAY,KAAA,IAAsB,IAAV,GACpC,EAAE,UAAO,UAAO,cAAW,EAAW,IAEtC,IAAsB,EAAM,aAC/B,MAAmB;EAIlB,AAHI,MAAY,KAAA,KACd,EAAmB,CAAK,GAE1B,IAAkB,CAAK;CACzB,GACA,CAAC,GAAS,CAAe,CAC3B;CAEA,OACE,kBAAC,GAAD;EACE,WAAW,EACT,+dACA,GACA,EAAS,IACT,KAAS,EAAiB,IAC1B,CACF;EACS;EACO;EAChB,iBAAiB;EACjB,gBAAc,MAAU,WAAW,KAAA;EACnC,oBAAkB;EAClB,iBAAe,KAAgB,KAAA;EAC/B,GAAI;EACC;YAEL,kBAAC,GAAD;GAAwB,SAAA;aACtB,kBAAC,EAAO,MAAR;IACE,WAAW,EACT,6GACA,CACF;IACA,SAAS,EAAE,GAAG,IAAY,IAAS,EAAE;IACrC,UAAU,EAAE,OAAO,IAAK;IACxB,YAAY,EAAQ;cAEnB;GACU,CAAA;EACS,CAAA;CACH,CAAA;AAE3B,CAAC;AACD,EAAO,cAAA,EAAoC"}
1
+ {"version":3,"file":"switch.js","names":[],"sources":["../../src/ui/switch.tsx"],"sourcesContent":["'use client'\n\nimport * as SwitchPrimitives from \"@primitives/react-switch\"\nimport { motion, useReducedMotion } from 'framer-motion'\nimport * as React from \"react\"\n\nimport { useFormField } from './form'\nimport { type FieldState, resolveFieldState } from './lib/field-state'\nimport { springs } from './lib/motion'\nimport { cn } from \"./lib/utils\"\n\nconst sizeConfig = {\n sm: { track: 'h-6 w-[38px]', thumb: 'h-5 w-5', travel: 16 },\n md: { track: 'h-6 w-11', thumb: 'h-ico-md w-ico-md', travel: 20 },\n lg: { track: 'h-7 w-[52px]', thumb: 'h-6 w-6', travel: 24 },\n} as const\n\n/** Validation border + checked-track tint per state (overrides `color` when set). */\nconst stateTintClasses: Record<Exclude<FieldState, 'default'>, string> = {\n error: 'border-error-7 data-[state=checked]:bg-error-9',\n warning: 'border-warning-7 data-[state=checked]:bg-warning-9',\n success: 'border-success-7 data-[state=checked]:bg-success-9',\n}\n\nconst colorMap = {\n accent: 'data-[state=checked]:bg-accent-9',\n success: 'data-[state=checked]:bg-success-9',\n warning: 'data-[state=checked]:bg-warning-9',\n} as const\n\nexport interface SwitchProps extends React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> {\n /** Validation/feedback state. `'error'` also sets `aria-invalid`. Inherited from `FormField` when omitted. Distinct from `color` (the ON-track tint). */\n state?: FieldState\n size?: 'sm' | 'md' | 'lg'\n color?: 'accent' | 'success' | 'warning'\n thumbIcon?: React.ReactNode\n}\n\nconst Switch = React.forwardRef<\n React.ElementRef<typeof SwitchPrimitives.Root>,\n SwitchProps\n>(({ className, state: stateProp, size = 'md', color = 'accent', thumbIcon, checked, defaultChecked, onCheckedChange, ...props }, ref) => {\n const fieldCtx = useFormField()\n const state = resolveFieldState(stateProp, fieldCtx.state)\n const ariaDescribedBy = props['aria-describedby'] ?? fieldCtx.helperTextId\n const ariaRequired = props['aria-required'] ?? fieldCtx.required\n\n // Track checked state internally to drive Framer Motion animation\n const [internalChecked, setInternalChecked] = React.useState(defaultChecked ?? false)\n const isChecked = checked !== undefined ? checked : internalChecked\n const reduced = useReducedMotion()\n // Thumb travels toward the inline-end — mirror it under RTL.\n const isRtl =\n typeof document !== 'undefined' &&\n (document.dir === 'rtl' || document.documentElement.dir === 'rtl')\n const { track, thumb, travel } = sizeConfig[size]\n\n const handleCheckedChange = React.useCallback(\n (value: boolean) => {\n if (checked === undefined) {\n setInternalChecked(value)\n }\n onCheckedChange?.(value)\n },\n [checked, onCheckedChange],\n )\n\n return (\n <SwitchPrimitives.Root\n className={cn(\n \"touch-target peer inline-flex shrink-0 cursor-pointer items-center rounded-pill border-2 border-surface-border-strong shadow-raised transition-colors duration-fast-01 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-action-disabled data-[state=checked]:border-transparent data-[state=unchecked]:bg-surface-border-strong data-[state=unchecked]:hover:bg-surface-raised-active\",\n track,\n colorMap[color],\n state && stateTintClasses[state],\n className\n )}\n checked={checked}\n defaultChecked={defaultChecked}\n onCheckedChange={handleCheckedChange}\n aria-invalid={state === 'error' || undefined}\n aria-describedby={ariaDescribedBy}\n aria-required={ariaRequired || undefined}\n {...props}\n ref={ref}\n >\n <SwitchPrimitives.Thumb asChild>\n <motion.span\n className={cn(\n \"pointer-events-none flex items-center justify-center rounded-pill bg-accent-fg shadow-raised-hover ring-0\",\n thumb\n )}\n animate={{ x: (isChecked ? travel : 0) * (isRtl ? -1 : 1) }}\n whileTap={reduced ? undefined : { scale: 0.85 }}\n transition={reduced ? { duration: 0 } : springs.snappy}\n >\n {thumbIcon}\n </motion.span>\n </SwitchPrimitives.Thumb>\n </SwitchPrimitives.Root>\n )\n})\nSwitch.displayName = SwitchPrimitives.Root.displayName\n\nexport { Switch }\n"],"mappings":";;;;;;;;;;AAWA,IAAM,IAAa;CACjB,IAAI;EAAE,OAAO;EAAgB,OAAO;EAAW,QAAQ;CAAG;CAC1D,IAAI;EAAE,OAAO;EAAY,OAAO;EAAqB,QAAQ;CAAG;CAChE,IAAI;EAAE,OAAO;EAAgB,OAAO;EAAW,QAAQ;CAAG;AAC5D,GAGM,IAAmE;CACvE,OAAO;CACP,SAAS;CACT,SAAS;AACX,GAEM,IAAW;CACf,QAAQ;CACR,SAAS;CACT,SAAS;AACX,GAUM,IAAS,EAAM,YAGlB,EAAE,cAAW,OAAO,GAAW,UAAO,MAAM,WAAQ,UAAU,cAAW,YAAS,mBAAgB,oBAAiB,GAAG,KAAS,MAAQ;CACxI,IAAM,IAAW,EAAa,GACxB,IAAQ,EAAkB,GAAW,EAAS,KAAK,GACnD,IAAkB,EAAM,uBAAuB,EAAS,cACxD,IAAe,EAAM,oBAAoB,EAAS,UAGlD,CAAC,GAAiB,KAAsB,EAAM,SAAS,KAAkB,EAAK,GAC9E,IAAY,MAAY,KAAA,IAAsB,IAAV,GACpC,IAAU,EAAiB,GAE3B,IACJ,OAAO,WAAa,QACnB,SAAS,QAAQ,SAAS,SAAS,gBAAgB,QAAQ,QACxD,EAAE,UAAO,UAAO,cAAW,EAAW,IAEtC,IAAsB,EAAM,aAC/B,MAAmB;EAIlB,AAHI,MAAY,KAAA,KACd,EAAmB,CAAK,GAE1B,IAAkB,CAAK;CACzB,GACA,CAAC,GAAS,CAAe,CAC3B;CAEA,OACE,kBAAC,GAAD;EACE,WAAW,EACT,+dACA,GACA,EAAS,IACT,KAAS,EAAiB,IAC1B,CACF;EACS;EACO;EAChB,iBAAiB;EACjB,gBAAc,MAAU,WAAW,KAAA;EACnC,oBAAkB;EAClB,iBAAe,KAAgB,KAAA;EAC/B,GAAI;EACC;YAEL,kBAAC,GAAD;GAAwB,SAAA;aACtB,kBAAC,EAAO,MAAR;IACE,WAAW,EACT,6GACA,CACF;IACA,SAAS,EAAE,IAAI,IAAY,IAAS,MAAM,IAAQ,KAAK,GAAG;IAC1D,UAAU,IAAU,KAAA,IAAY,EAAE,OAAO,IAAK;IAC9C,YAAY,IAAU,EAAE,UAAU,EAAE,IAAI,EAAQ;cAE/C;GACU,CAAA;EACS,CAAA;CACH,CAAA;AAE3B,CAAC;AACD,EAAO,cAAA,EAAoC"}
package/dist/ui/table.js CHANGED
@@ -35,7 +35,7 @@ var s = t.forwardRef(({ className: t, ...r }, i) => /* @__PURE__ */ n("tfoot", {
35
35
  s.displayName = "TableFooter";
36
36
  var c = t.forwardRef(({ className: t, ...r }, i) => /* @__PURE__ */ n("tr", {
37
37
  ref: i,
38
- className: e("group/row border-b border-surface-border-subtle transition-colors hover:bg-surface-raised-hover data-[state=selected]:bg-accent-3 data-[state=selected]:hover:bg-accent-4", "has-[[data-slot=row-link]:focus-visible]:outline-2 has-[[data-slot=row-link]:focus-visible]:outline-accent-9 has-[[data-slot=row-link]:focus-visible]:-outline-offset-2", t),
38
+ className: e("group/row border-b border-surface-border-subtle transition-colors hover:bg-surface-raised-hover data-[state=selected]:bg-accent-3 data-[state=selected]:hover:bg-accent-4 data-[state=selected]:forced-colors:outline data-[state=selected]:forced-colors:outline-1", "has-[[data-slot=row-link]:focus-visible]:outline-2 has-[[data-slot=row-link]:focus-visible]:outline-accent-9 has-[[data-slot=row-link]:focus-visible]:-outline-offset-2", t),
39
39
  ...r
40
40
  }));
41
41
  c.displayName = "TableRow";
@@ -1 +1 @@
1
- {"version":3,"file":"table.js","names":[],"sources":["../../src/ui/table.tsx"],"sourcesContent":["// @server-safe\nimport * as React from \"react\"\n\nimport { cn } from \"./lib/utils\"\n\n/** Row density — sets --table-py, which header and body cells both read. */\ntype TableDensity = 'compact' | 'standard' | 'comfortable'\n\n// One variable pair drives the table's spacing (same pattern as Card):\n// --table-py vertical cell padding per density (4 / 8 / 12px → rows ≈ 29 / 37 / 45px)\n// --table-edge first/last-cell inline padding — inherits --card-spacing when the table\n// sits inside a Card, so edge columns align with the card's slots;\n// falls back to ds-04 (12px) standalone.\nconst densityClasses: Record<TableDensity, string> = {\n compact: '[--table-py:var(--spacing-ds-02)]',\n standard: '[--table-py:var(--spacing-ds-03)]',\n comfortable: '[--table-py:var(--spacing-ds-04)]',\n}\n\nexport interface TableCellBaseProps {\n /**\n * Quantitative column: right-aligns and uses tabular figures so digits line up.\n * Keep decimal places consistent per column; identifier-numbers (dates, phones,\n * IDs) stay left-aligned — they're names, not quantities.\n */\n numeric?: boolean\n}\n\nexport interface TableProps extends React.HTMLAttributes<HTMLTableElement> {\n /** Row density — vertical cell padding for header + body. @default 'standard' */\n density?: TableDensity\n /**\n * Zebra striping (even body rows get the faintest surface step). Opt-in only —\n * for very wide/dense tables; hairline separators are the default row cue.\n */\n striped?: boolean\n}\n\nconst Table = React.forwardRef<HTMLTableElement, TableProps>(\n ({ className, density = 'standard', striped, ...props }, ref) => (\n <div className=\"relative w-full overflow-auto\">\n <table\n ref={ref}\n className={cn(\n // overflow-x-clip contains TableRowLink's 100vw stretch pseudo-element\n // without creating a horizontal scrollbar (the wrapper owns scrolling).\n \"w-full caption-bottom text-body-md overflow-x-clip [--table-edge:var(--card-spacing,var(--spacing-ds-04))]\",\n densityClasses[density],\n striped && \"[&_tbody_tr:nth-child(even)]:bg-surface-base\",\n className,\n )}\n {...props}\n />\n </div>\n ),\n)\nTable.displayName = \"Table\"\n\nconst TableHeader = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <thead ref={ref} className={cn(\"[&_tr]:border-b [&_tr]:border-surface-border-subtle\", className)} {...props} />\n))\nTableHeader.displayName = \"TableHeader\"\n\nconst TableBody = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <tbody\n ref={ref}\n className={cn(\"[&_tr:last-child]:border-0\", className)}\n {...props}\n />\n))\nTableBody.displayName = \"TableBody\"\n\nconst TableFooter = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <tfoot\n ref={ref}\n className={cn(\n // surface-base band, not raised@50% — the footer must read against the\n // card surface the table lives on (same mis-mapped shadcn muted/50 family\n // as the row-hover bug).\n \"border-t border-surface-border-subtle bg-surface-base font-medium [&>tr]:last:border-b-0\",\n className\n )}\n {...props}\n />\n))\nTableFooter.displayName = \"TableFooter\"\n\nconst TableRow = React.forwardRef<\n HTMLTableRowElement,\n React.HTMLAttributes<HTMLTableRowElement>\n>(({ className, ...props }, ref) => (\n <tr\n ref={ref}\n className={cn(\n // raised-hover, NOT raised — tables live on cards (surface-raised), so a\n // surface-raised hover would be invisible (the 0.44-era port bug).\n // selected+hover gets its own explicit step — without it the hover and\n // selected classes tie on specificity and stylesheet order decides.\n // `group/row` lets TableRowActions reveal on row hover/focus; the has-[]\n // rule draws a row-level focus ring when a TableRowLink inside is\n // keyboard-focused (the anchor itself suppresses its own ring).\n \"group/row border-b border-surface-border-subtle transition-colors hover:bg-surface-raised-hover data-[state=selected]:bg-accent-3 data-[state=selected]:hover:bg-accent-4\",\n \"has-[[data-slot=row-link]:focus-visible]:outline-2 has-[[data-slot=row-link]:focus-visible]:outline-accent-9 has-[[data-slot=row-link]:focus-visible]:-outline-offset-2\",\n className,\n )}\n {...props}\n />\n))\nTableRow.displayName = \"TableRow\"\n\nconst TableHead = React.forwardRef<\n HTMLTableCellElement,\n React.ThHTMLAttributes<HTMLTableCellElement> & TableCellBaseProps\n>(({ className, numeric, ...props }, ref) => (\n <th\n ref={ref}\n scope=\"col\"\n className={cn(\n // Header is quieter than the data: one step smaller, medium, muted.\n // Height tracks density via the same --table-py the body cells read.\n \"py-(--table-py) px-ds-04 first:pl-(--table-edge) last:pr-(--table-edge) text-left align-middle text-body-sm font-medium text-surface-fg-muted [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]\",\n numeric && \"text-right\",\n className,\n )}\n {...props}\n />\n))\nTableHead.displayName = \"TableHead\"\n\nconst TableCell = React.forwardRef<\n HTMLTableCellElement,\n React.TdHTMLAttributes<HTMLTableCellElement> & TableCellBaseProps\n>(({ className, numeric, ...props }, ref) => (\n <td\n ref={ref}\n className={cn(\n \"py-(--table-py) px-ds-04 first:pl-(--table-edge) last:pr-(--table-edge) align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]\",\n numeric && \"text-right tabular-nums\",\n className,\n )}\n {...props}\n />\n))\nTableCell.displayName = \"TableCell\"\n\nexport interface TableRowActionsProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Always show the actions instead of revealing on row hover/focus. Use for\n * tables where actions must be permanently discoverable (the GitLab stance),\n * or when the row has few columns and the density win doesn't matter.\n */\n persist?: boolean\n}\n\n/**\n * Right-aligned action cluster for a table row, revealed on row hover — and,\n * critically, on keyboard focus: the buttons stay in the tab order permanently\n * (opacity reveal, never display:none) and appear the moment focus enters the\n * row (WCAG 1.4.13). On touch devices (no hover) they are always visible.\n *\n * Give the actions column a visually-hidden header: `<TableHead><span className=\"sr-only\">Actions</span></TableHead>`.\n *\n * @example\n * <TableCell>\n * <TableRowActions>\n * <IconButton size=\"xs\" variant=\"ghost\" aria-label={`Download ${name}`} icon={<IconDownload />} />\n * <IconButton size=\"xs\" variant=\"ghost\" aria-label={`Delete ${name}`} icon={<IconTrash />} />\n * </TableRowActions>\n * </TableCell>\n */\nconst TableRowActions = React.forwardRef<HTMLDivElement, TableRowActionsProps>(\n ({ className, persist, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n \"flex items-center justify-end gap-ds-01 transition-opacity duration-fast-01 ease-productive-standard\",\n persist\n ? \"opacity-100\"\n : \"opacity-0 group-hover/row:opacity-100 group-focus-within/row:opacity-100 pointer-coarse:opacity-100\",\n className,\n )}\n {...props}\n />\n ),\n)\nTableRowActions.displayName = \"TableRowActions\"\n\nconst TableCaption = React.forwardRef<\n HTMLTableCaptionElement,\n React.HTMLAttributes<HTMLTableCaptionElement>\n>(({ className, ...props }, ref) => (\n <caption\n ref={ref}\n className={cn(\"mt-ds-05 text-body-md text-surface-fg-muted\", className)}\n {...props}\n />\n))\nTableCaption.displayName = \"TableCaption\"\n\nexport type TableRowProps = React.HTMLAttributes<HTMLTableRowElement>\nexport type TableCellProps = React.TdHTMLAttributes<HTMLTableCellElement>\nexport type { TableDensity }\n\nexport {\n Table,\n TableBody,\n TableCaption,\n TableCell,\n TableFooter,\n TableHead,\n TableHeader,\n TableRow,\n TableRowActions,\n}\n"],"mappings":";;;;AAaA,IAAM,IAA+C;CACnD,SAAS;CACT,UAAU;CACV,aAAa;AACf,GAqBM,IAAQ,EAAM,YACjB,EAAE,cAAW,aAAU,YAAY,YAAS,GAAG,KAAS,MACvD,kBAAC,OAAD;CAAK,WAAU;WACb,kBAAC,SAAD;EACO;EACL,WAAW,EAGT,8GACA,EAAe,IACf,KAAW,gDACX,CACF;EACA,GAAI;CACL,CAAA;AACE,CAAA,CAET;AACA,EAAM,cAAc;AAEpB,IAAM,IAAc,EAAM,YAGvB,EAAE,cAAW,GAAG,KAAS,MAC1B,kBAAC,SAAD;CAAY;CAAK,WAAW,EAAG,uDAAuD,CAAS;CAAG,GAAI;AAAQ,CAAA,CAC/G;AACD,EAAY,cAAc;AAE1B,IAAM,IAAY,EAAM,YAGrB,EAAE,cAAW,GAAG,KAAS,MAC1B,kBAAC,SAAD;CACO;CACL,WAAW,EAAG,8BAA8B,CAAS;CACrD,GAAI;AACL,CAAA,CACF;AACD,EAAU,cAAc;AAExB,IAAM,IAAc,EAAM,YAGvB,EAAE,cAAW,GAAG,KAAS,MAC1B,kBAAC,SAAD;CACO;CACL,WAAW,EAIT,4FACA,CACF;CACA,GAAI;AACL,CAAA,CACF;AACD,EAAY,cAAc;AAE1B,IAAM,IAAW,EAAM,YAGpB,EAAE,cAAW,GAAG,KAAS,MAC1B,kBAAC,MAAD;CACO;CACL,WAAW,EAQT,6KACA,2KACA,CACF;CACA,GAAI;AACL,CAAA,CACF;AACD,EAAS,cAAc;AAEvB,IAAM,IAAY,EAAM,YAGrB,EAAE,cAAW,YAAS,GAAG,KAAS,MACnC,kBAAC,MAAD;CACO;CACL,OAAM;CACN,WAAW,EAGT,qNACA,KAAW,cACX,CACF;CACA,GAAI;AACL,CAAA,CACF;AACD,EAAU,cAAc;AAExB,IAAM,IAAY,EAAM,YAGrB,EAAE,cAAW,YAAS,GAAG,KAAS,MACnC,kBAAC,MAAD;CACO;CACL,WAAW,EACT,4JACA,KAAW,2BACX,CACF;CACA,GAAI;AACL,CAAA,CACF;AACD,EAAU,cAAc;AA2BxB,IAAM,IAAkB,EAAM,YAC3B,EAAE,cAAW,YAAS,GAAG,KAAS,MACjC,kBAAC,OAAD;CACO;CACL,WAAW,EACT,wGACA,IACI,gBACA,uGACJ,CACF;CACA,GAAI;AACL,CAAA,CAEL;AACA,EAAgB,cAAc;AAE9B,IAAM,IAAe,EAAM,YAGxB,EAAE,cAAW,GAAG,KAAS,MAC1B,kBAAC,WAAD;CACO;CACL,WAAW,EAAG,+CAA+C,CAAS;CACtE,GAAI;AACL,CAAA,CACF;AACD,EAAa,cAAc"}
1
+ {"version":3,"file":"table.js","names":[],"sources":["../../src/ui/table.tsx"],"sourcesContent":["// @server-safe\nimport * as React from \"react\"\n\nimport { cn } from \"./lib/utils\"\n\n/** Row density — sets --table-py, which header and body cells both read. */\ntype TableDensity = 'compact' | 'standard' | 'comfortable'\n\n// One variable pair drives the table's spacing (same pattern as Card):\n// --table-py vertical cell padding per density (4 / 8 / 12px → rows ≈ 29 / 37 / 45px)\n// --table-edge first/last-cell inline padding — inherits --card-spacing when the table\n// sits inside a Card, so edge columns align with the card's slots;\n// falls back to ds-04 (12px) standalone.\nconst densityClasses: Record<TableDensity, string> = {\n compact: '[--table-py:var(--spacing-ds-02)]',\n standard: '[--table-py:var(--spacing-ds-03)]',\n comfortable: '[--table-py:var(--spacing-ds-04)]',\n}\n\nexport interface TableCellBaseProps {\n /**\n * Quantitative column: right-aligns and uses tabular figures so digits line up.\n * Keep decimal places consistent per column; identifier-numbers (dates, phones,\n * IDs) stay left-aligned — they're names, not quantities.\n */\n numeric?: boolean\n}\n\nexport interface TableProps extends React.HTMLAttributes<HTMLTableElement> {\n /** Row density — vertical cell padding for header + body. @default 'standard' */\n density?: TableDensity\n /**\n * Zebra striping (even body rows get the faintest surface step). Opt-in only —\n * for very wide/dense tables; hairline separators are the default row cue.\n */\n striped?: boolean\n}\n\nconst Table = React.forwardRef<HTMLTableElement, TableProps>(\n ({ className, density = 'standard', striped, ...props }, ref) => (\n <div className=\"relative w-full overflow-auto\">\n <table\n ref={ref}\n className={cn(\n // overflow-x-clip contains TableRowLink's 100vw stretch pseudo-element\n // without creating a horizontal scrollbar (the wrapper owns scrolling).\n \"w-full caption-bottom text-body-md overflow-x-clip [--table-edge:var(--card-spacing,var(--spacing-ds-04))]\",\n densityClasses[density],\n striped && \"[&_tbody_tr:nth-child(even)]:bg-surface-base\",\n className,\n )}\n {...props}\n />\n </div>\n ),\n)\nTable.displayName = \"Table\"\n\nconst TableHeader = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <thead ref={ref} className={cn(\"[&_tr]:border-b [&_tr]:border-surface-border-subtle\", className)} {...props} />\n))\nTableHeader.displayName = \"TableHeader\"\n\nconst TableBody = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <tbody\n ref={ref}\n className={cn(\"[&_tr:last-child]:border-0\", className)}\n {...props}\n />\n))\nTableBody.displayName = \"TableBody\"\n\nconst TableFooter = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <tfoot\n ref={ref}\n className={cn(\n // surface-base band, not raised@50% — the footer must read against the\n // card surface the table lives on (same mis-mapped shadcn muted/50 family\n // as the row-hover bug).\n \"border-t border-surface-border-subtle bg-surface-base font-medium [&>tr]:last:border-b-0\",\n className\n )}\n {...props}\n />\n))\nTableFooter.displayName = \"TableFooter\"\n\nconst TableRow = React.forwardRef<\n HTMLTableRowElement,\n React.HTMLAttributes<HTMLTableRowElement>\n>(({ className, ...props }, ref) => (\n <tr\n ref={ref}\n className={cn(\n // raised-hover, NOT raised — tables live on cards (surface-raised), so a\n // surface-raised hover would be invisible (the 0.44-era port bug).\n // selected+hover gets its own explicit step — without it the hover and\n // selected classes tie on specificity and stylesheet order decides.\n // `group/row` lets TableRowActions reveal on row hover/focus; the has-[]\n // rule draws a row-level focus ring when a TableRowLink inside is\n // keyboard-focused (the anchor itself suppresses its own ring).\n \"group/row border-b border-surface-border-subtle transition-colors hover:bg-surface-raised-hover data-[state=selected]:bg-accent-3 data-[state=selected]:hover:bg-accent-4 data-[state=selected]:forced-colors:outline data-[state=selected]:forced-colors:outline-1\",\n \"has-[[data-slot=row-link]:focus-visible]:outline-2 has-[[data-slot=row-link]:focus-visible]:outline-accent-9 has-[[data-slot=row-link]:focus-visible]:-outline-offset-2\",\n className,\n )}\n {...props}\n />\n))\nTableRow.displayName = \"TableRow\"\n\nconst TableHead = React.forwardRef<\n HTMLTableCellElement,\n React.ThHTMLAttributes<HTMLTableCellElement> & TableCellBaseProps\n>(({ className, numeric, ...props }, ref) => (\n <th\n ref={ref}\n scope=\"col\"\n className={cn(\n // Header is quieter than the data: one step smaller, medium, muted.\n // Height tracks density via the same --table-py the body cells read.\n \"py-(--table-py) px-ds-04 first:pl-(--table-edge) last:pr-(--table-edge) text-left align-middle text-body-sm font-medium text-surface-fg-muted [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]\",\n numeric && \"text-right\",\n className,\n )}\n {...props}\n />\n))\nTableHead.displayName = \"TableHead\"\n\nconst TableCell = React.forwardRef<\n HTMLTableCellElement,\n React.TdHTMLAttributes<HTMLTableCellElement> & TableCellBaseProps\n>(({ className, numeric, ...props }, ref) => (\n <td\n ref={ref}\n className={cn(\n \"py-(--table-py) px-ds-04 first:pl-(--table-edge) last:pr-(--table-edge) align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]\",\n numeric && \"text-right tabular-nums\",\n className,\n )}\n {...props}\n />\n))\nTableCell.displayName = \"TableCell\"\n\nexport interface TableRowActionsProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Always show the actions instead of revealing on row hover/focus. Use for\n * tables where actions must be permanently discoverable (the GitLab stance),\n * or when the row has few columns and the density win doesn't matter.\n */\n persist?: boolean\n}\n\n/**\n * Right-aligned action cluster for a table row, revealed on row hover — and,\n * critically, on keyboard focus: the buttons stay in the tab order permanently\n * (opacity reveal, never display:none) and appear the moment focus enters the\n * row (WCAG 1.4.13). On touch devices (no hover) they are always visible.\n *\n * Give the actions column a visually-hidden header: `<TableHead><span className=\"sr-only\">Actions</span></TableHead>`.\n *\n * @example\n * <TableCell>\n * <TableRowActions>\n * <IconButton size=\"xs\" variant=\"ghost\" aria-label={`Download ${name}`} icon={<IconDownload />} />\n * <IconButton size=\"xs\" variant=\"ghost\" aria-label={`Delete ${name}`} icon={<IconTrash />} />\n * </TableRowActions>\n * </TableCell>\n */\nconst TableRowActions = React.forwardRef<HTMLDivElement, TableRowActionsProps>(\n ({ className, persist, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n \"flex items-center justify-end gap-ds-01 transition-opacity duration-fast-01 ease-productive-standard\",\n persist\n ? \"opacity-100\"\n : \"opacity-0 group-hover/row:opacity-100 group-focus-within/row:opacity-100 pointer-coarse:opacity-100\",\n className,\n )}\n {...props}\n />\n ),\n)\nTableRowActions.displayName = \"TableRowActions\"\n\nconst TableCaption = React.forwardRef<\n HTMLTableCaptionElement,\n React.HTMLAttributes<HTMLTableCaptionElement>\n>(({ className, ...props }, ref) => (\n <caption\n ref={ref}\n className={cn(\"mt-ds-05 text-body-md text-surface-fg-muted\", className)}\n {...props}\n />\n))\nTableCaption.displayName = \"TableCaption\"\n\nexport type TableRowProps = React.HTMLAttributes<HTMLTableRowElement>\nexport type TableCellProps = React.TdHTMLAttributes<HTMLTableCellElement>\nexport type { TableDensity }\n\nexport {\n Table,\n TableBody,\n TableCaption,\n TableCell,\n TableFooter,\n TableHead,\n TableHeader,\n TableRow,\n TableRowActions,\n}\n"],"mappings":";;;;AAaA,IAAM,IAA+C;CACnD,SAAS;CACT,UAAU;CACV,aAAa;AACf,GAqBM,IAAQ,EAAM,YACjB,EAAE,cAAW,aAAU,YAAY,YAAS,GAAG,KAAS,MACvD,kBAAC,OAAD;CAAK,WAAU;WACb,kBAAC,SAAD;EACO;EACL,WAAW,EAGT,8GACA,EAAe,IACf,KAAW,gDACX,CACF;EACA,GAAI;CACL,CAAA;AACE,CAAA,CAET;AACA,EAAM,cAAc;AAEpB,IAAM,IAAc,EAAM,YAGvB,EAAE,cAAW,GAAG,KAAS,MAC1B,kBAAC,SAAD;CAAY;CAAK,WAAW,EAAG,uDAAuD,CAAS;CAAG,GAAI;AAAQ,CAAA,CAC/G;AACD,EAAY,cAAc;AAE1B,IAAM,IAAY,EAAM,YAGrB,EAAE,cAAW,GAAG,KAAS,MAC1B,kBAAC,SAAD;CACO;CACL,WAAW,EAAG,8BAA8B,CAAS;CACrD,GAAI;AACL,CAAA,CACF;AACD,EAAU,cAAc;AAExB,IAAM,IAAc,EAAM,YAGvB,EAAE,cAAW,GAAG,KAAS,MAC1B,kBAAC,SAAD;CACO;CACL,WAAW,EAIT,4FACA,CACF;CACA,GAAI;AACL,CAAA,CACF;AACD,EAAY,cAAc;AAE1B,IAAM,IAAW,EAAM,YAGpB,EAAE,cAAW,GAAG,KAAS,MAC1B,kBAAC,MAAD;CACO;CACL,WAAW,EAQT,uQACA,2KACA,CACF;CACA,GAAI;AACL,CAAA,CACF;AACD,EAAS,cAAc;AAEvB,IAAM,IAAY,EAAM,YAGrB,EAAE,cAAW,YAAS,GAAG,KAAS,MACnC,kBAAC,MAAD;CACO;CACL,OAAM;CACN,WAAW,EAGT,qNACA,KAAW,cACX,CACF;CACA,GAAI;AACL,CAAA,CACF;AACD,EAAU,cAAc;AAExB,IAAM,IAAY,EAAM,YAGrB,EAAE,cAAW,YAAS,GAAG,KAAS,MACnC,kBAAC,MAAD;CACO;CACL,WAAW,EACT,4JACA,KAAW,2BACX,CACF;CACA,GAAI;AACL,CAAA,CACF;AACD,EAAU,cAAc;AA2BxB,IAAM,IAAkB,EAAM,YAC3B,EAAE,cAAW,YAAS,GAAG,KAAS,MACjC,kBAAC,OAAD;CACO;CACL,WAAW,EACT,wGACA,IACI,gBACA,uGACJ,CACF;CACA,GAAI;AACL,CAAA,CAEL;AACA,EAAgB,cAAc;AAE9B,IAAM,IAAe,EAAM,YAGxB,EAAE,cAAW,GAAG,KAAS,MAC1B,kBAAC,WAAD;CACO;CACL,WAAW,EAAG,+CAA+C,CAAS;CACtE,GAAI;AACL,CAAA,CACF;AACD,EAAa,cAAc"}
@@ -9,14 +9,20 @@
9
9
  count: number (number of selected items — displayed in badge)
10
10
  onClearSelection: () => void
11
11
  actions: BulkActionBarAction[]
12
+ totalCount: number (optional — total selectable items; enables "Select all")
13
+ onSelectAll: () => void (optional — called by the "Select all" control)
12
14
  className: string
15
+ ...div attributes (forwardRef to the toolbar div; HTMLAttributes spread)
13
16
 
14
17
  ### BulkActionBarAction
15
18
  label: string
16
- icon: ComponentType<{ className?: string }> (optional icon component)
19
+ icon: IconInput (optional any icon component or element)
17
20
  onClick: () => void
18
- color: "default" | "error"
19
- disabled: boolean
21
+ color: "accent" | "error" | "success" | "warning" | "info" | "neutral" (default: "accent")
22
+ disabled: boolean (optional)
23
+ loading: boolean (optional — pending spinner on the action)
24
+ requiresConfirmation: boolean (optional — inline confirm before executing)
25
+ confirmMessage: string (optional — default "Are you sure?")
20
26
 
21
27
  ## Defaults
22
28
  (no optional props with defaults)
@@ -5,11 +5,27 @@
5
5
  - Category: composed
6
6
 
7
7
  ## Props
8
+
9
+ ### ErrorDisplay (the rendered UI — forwardRef<HTMLDivElement>, spreads div attrs)
8
10
  error: unknown (REQUIRED — Error object, status object, or string)
9
- onReset: () => void (optional retry button)
11
+ onReset: () => void (optional renders a "Try Again" button)
12
+ actions: ReactNode (optional — custom recovery actions; replaces the default button)
13
+ fullPage: boolean (default: true — center in a min-h-[60vh] region; false = inline)
14
+ autoFocusReset: boolean (default: false — focus the recovery button on mount)
15
+
16
+ Note: the raw `error.message` is shown ONLY in development. In production, ErrorDisplay
17
+ shows the friendly status-mapped copy (404/403/500/default); the real message stays in
18
+ the dev-only stack-trace block.
19
+
20
+ ### ErrorBoundary (class boundary — wrap a subtree)
21
+ children: ReactNode (REQUIRED)
22
+ onReset: () => void (optional — called after the boundary resets)
23
+ onError: (error: unknown, info: React.ErrorInfo) => void (optional — wire Sentry/logging)
24
+ resetKeys: unknown[] (optional — auto-reset when any value changes; react-error-boundary parity)
25
+ fallback: (props: { error: unknown; onReset: () => void }) => ReactNode (optional — defaults to ErrorDisplay)
10
26
 
11
27
  ## Defaults
12
- None
28
+ fullPage: true
13
29
 
14
30
  ## Example
15
31
  ```jsx
@@ -10,13 +10,23 @@ MasterDetail (root), MasterDetail.List, MasterDetail.Detail, MasterDetail.ListIt
10
10
  ## Props
11
11
 
12
12
  ### MasterDetail (root)
13
- selected: string | null (ID of currently selected item; null = show list on mobile)
13
+ selected: string | null (CONTROLLED selected item id; omit for uncontrolled)
14
+ defaultSelected: string | null (uncontrolled initial selection)
15
+ onSelect: (value: string) => void (called with a row's `value` when chosen)
16
+ label: string (accessible name for the listbox; default "Items")
14
17
  onBack: () => void (called when mobile back button is pressed)
15
18
  masterWidth: string (master panel width on desktop)
16
19
  breakpoint: "sm" | "md" | "lg" (below this, stacked mobile mode activates)
20
+ emptyState: ReactNode (shown in the detail pane when nothing is selected)
21
+ onNavigate: (direction: "up" | "down") => void
22
+
23
+ Selection can be controlled (`selected`) or owned by the component
24
+ (`defaultSelected` + `onSelect`). With `value` on each ListItem, `active` /
25
+ `aria-selected` derive automatically — no need to hand-wire `active` + `onClick`.
17
26
 
18
27
  ### MasterDetail.ListItem
19
- active: boolean (highlights the item)
28
+ value: string (row id — enables derived selection + auto-fires onSelect on activate)
29
+ active: boolean (explicit highlight; omit to derive from value === selected)
20
30
  (extends ButtonHTMLAttributes)
21
31
 
22
32
  ## Defaults
@@ -22,12 +22,12 @@ Exports: DashboardSkeleton, ProjectListSkeleton, TaskDetailSkeleton
22
22
  ## Composability
23
23
  - **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).
24
24
  - **Server-safe** — use in Next.js app router `loading.tsx` files for instant route-transition feedback while data streams.
25
- - **Built on LoadingSkeleton + ui/Skeleton** — these just assemble the pre-built regional skeletons into page-shaped layouts.
25
+ - **Assembled from `ui/Skeleton`** — these compose the base `Skeleton` primitive directly into page-shaped layouts (they do NOT wrap the `LoadingSkeleton` regional exports). Each root is a `role="status"` / `aria-busy` region with an sr-only label.
26
26
  - **When to use which skeleton tier:**
27
27
  - `<Skeleton>` (ui) — single shape for a single element
28
28
  - `<CardSkeleton>` / `<TableSkeleton>` (LoadingSkeleton) — individual region shape
29
29
  - `<DashboardSkeleton>` / etc. (PageSkeletons) — full page placeholder
30
- - **Fixed layout structure** — the className prop adjusts the outer container, but internal layout isn't customizable. For custom page skeletons, compose LoadingSkeleton pieces yourself.
30
+ - **Fixed layout structure** — the className prop adjusts the outer container, but internal layout isn't customizable. For custom page skeletons, compose `ui/Skeleton` (or the `LoadingSkeleton` regional exports) yourself.
31
31
 
32
32
  ## Gotchas
33
33
  - Server-safe: can be imported directly in Next.js Server Components
@@ -1,37 +1,48 @@
1
1
  # PriorityIndicator
2
2
 
3
3
  - Import: @devalok/shilp-sutra/composed/priority-indicator
4
- - Server-safe: Yes
4
+ - Server-safe: No
5
5
  - Category: composed
6
6
 
7
7
  ## Props
8
8
  priority: Priority
9
- display: "compact" | "full" (default: "full")
9
+ iconOnly?: boolean (icon-only chip, no visible text)
10
+ display?: "compact" | "full" (@deprecated — use iconOnly)
11
+ children?: ReactNode (override the label, e.g. i18n)
10
12
 
11
13
  Priority = 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT' | 'low' | 'medium' | 'high' | 'urgent'
12
14
 
13
15
  ## Defaults
14
- display="full"
16
+ iconOnly: false
15
17
 
16
18
  ## Example
17
19
  ```jsx
18
20
  <PriorityIndicator priority="HIGH" />
19
- <PriorityIndicator priority="low" display="compact" />
21
+ <PriorityIndicator priority="low" iconOnly />
22
+ <PriorityIndicator priority="URGENT">Critical</PriorityIndicator>
20
23
  ```
21
24
 
22
25
  ## Composability
23
- - **Server-safe priority label** icon + color + text for task / issue priority.
26
+ - **Composes `Badge`**radius, color semantics, a11y labelling, and reduced-motion handling all come from one place (no bespoke re-roll).
24
27
  - **Composes inside list rows, DataTable cells, Card headers, task panels** — anywhere a priority flag fits.
25
- - **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.
26
- - **Case-insensitive priority** — accepts both UPPERCASE (LOW/MEDIUM/HIGH/URGENT) and lowercase. Designed to match both backend conventions without manual coercion.
27
- - Color semantics: LOW=success, MEDIUM=warning, HIGH=error, URGENT=error with bolder icon.
28
+ - **`iconOnly`** shows only the icon with a real accessible name (`role="img"` + `aria-label`), for tight cells. Omit it (default) for icon + label.
29
+ - **Severity by weight, not motion** — URGENT renders as a solid fill so the top tier reads at a glance; the others are soft. No animation (removes the prior perpetual pulse).
30
+ - **Case-insensitive priority** accepts both UPPERCASE and lowercase; unknown values fall back to MEDIUM instead of throwing.
31
+ - **`children`** overrides the label for i18n / custom copy.
32
+ - Color semantics: LOW = slate (neutral), MEDIUM = warning, HIGH = error (soft), URGENT = error (solid).
28
33
 
29
34
  ## Gotchas
30
- - Case-insensitive — "low" and "LOW" both work
31
- - Server-safe: can be imported directly in Next.js Server Components
32
- - `compact` display shows only the icon; `full` shows icon + text label
35
+ - Case-insensitive — "low" and "LOW" both work; unknown values fall back to MEDIUM
36
+ - `iconOnly` shows only the icon (accessible-named); default shows icon + text label
37
+ - `display` is deprecated use `iconOnly`
33
38
 
34
39
  ## Changes
40
+ ### v0.53.0
41
+ - **Changed** Recomposed on the `Badge` primitive (was a bespoke re-rolled chip): inherits pill radius, color semantics, accessible labelling.
42
+ - **Changed** URGENT is now a solid static fill; the perpetual scale-pulse is removed (was unguarded infinite motion, WCAG 2.2.2). Severity reads without animation.
43
+ - **Added** `iconOnly` (replaces deprecated `display`) and `children` (label override for i18n).
44
+ - **Fixed** Icon-only chip now has a real accessible name (`role="img"` + `aria-label`), not a mouse-only `title`. Unknown priority no longer throws.
45
+
35
46
  ### v0.2.0
36
47
  - **Added** Identified as server-safe component
37
48
 
@@ -9,19 +9,24 @@ Compact rich text chat input for unified human+AI workspaces. Built on TipTap.
9
9
  ## Props
10
10
 
11
11
  ### RichChatInputProps
12
- onSubmit: (html: string, plainText: string) => void (REQUIRED)
12
+ onSubmit: (message: RichChatInputMessage) => void (REQUIRED)
13
13
  placeholder: string (default: "Type a message...")
14
14
  disabled: boolean (default: false)
15
- variant: 'compact' | 'expanded' | 'minimal' (default: 'compact')
16
- maxRows: number
15
+ content: string initial HTML (not reactive; use for message editing)
16
+ variant: 'compact' | 'expanded' | 'minimal' | 'inline' (default: 'compact')
17
17
  enterBehavior: 'send' | 'newline' (default: 'send')
18
18
  maxLength: number — enables character counter
19
+ charCountDisplay: 'always' | 'focus' | 'near-limit' | 'hidden' (default: 'near-limit')
19
20
  mentions: MentionItem[] — static list for @mention autocomplete
20
21
  onMentionSearch: (query: string) => Promise<MentionItem[]> — async search
21
22
  onMentionSelect: (item: MentionItem) => void
22
23
  onFileUpload: (file: File) => Promise<{ url: string; name: string; size: number }>
23
24
  onImageUpload: (file: File) => Promise<string>
24
25
  slashCommands: SlashCommandGroup[] — enables / command palette
26
+ onVoiceRecord: (audio: Blob, duration: number) => void
27
+ onTranscribe: (blob: Blob, duration: number) => Promise<string | null> — transcribe after recording; return null to attach as a voice note
28
+ maxDuration: number — max voice-recording seconds
29
+ replyTo: { id: string; author: string; preview: string; onDismiss: () => void } — reply banner above the input
25
30
  onTyping: (isTyping: boolean) => void — typing indicator callback
26
31
  onEmpty: (isEmpty: boolean) => void
27
32
  isStreaming: boolean (default: false) — shows stop button instead of send
@@ -29,9 +34,15 @@ Compact rich text chat input for unified human+AI workspaces. Built on TipTap.
29
34
  leadingSlot: ReactNode — rendered above the editor
30
35
  trailingSlot: ReactNode — rendered below the toolbar
31
36
  disclaimer: string — small text below the input
32
- toolbar: boolean | ChatToolbarItem[] (default: true)
37
+ toolbar: boolean | ChatToolbarItem[] | ReactNode (default: true) — true = default toolbar, array = whitelist, ReactNode = custom, false = hidden
38
+ actionButton: ReactNode | false — custom left-side button (replaces the default attach button; false hides it)
39
+ emojiSet: 'native' | 'apple' | 'google' | 'twitter' | 'facebook' (default: 'native')
40
+ onSchedule: (message: RichChatInputMessage, scheduledAt: Date) => void — if set, a schedule button appears next to send
41
+ sendOptions: Array<{ label: string; icon?: ComponentType<{ className?: string }>; onSelect: () => void }> — split-send dropdown options
33
42
 
34
- ChatToolbarItem: 'bold' | 'italic' | 'underline' | 'strike' | 'highlight' | 'code' | 'bulletList' | 'orderedList' | 'mention' | 'emoji' | 'attach' | 'slash'
43
+ RichChatInputMessage: { html: string; plainText: string; attachments?: Array<{ url: string; name: string; size: number; type: string }>; voiceNote?: { blob: Blob; duration: number } }
44
+
45
+ ChatToolbarItem: 'bold' | 'italic' | 'underline' | 'strike' | 'highlight' | 'code' | 'bulletList' | 'orderedList' | 'blockquote' | 'link' | 'mention' | 'emoji' | 'slash'
35
46
 
36
47
  MentionItem: { id: string; label: string; avatar?: string }
37
48
 
@@ -43,11 +54,12 @@ SlashCommandGroup: { label: string; commands: SlashCommand[] }
43
54
  - `compact` (default) — 2-3 lines, inline toolbar
44
55
  - `expanded` — 5+ lines, always-visible toolbar, suited for AI prompts
45
56
  - `minimal` — single line, toolbar appears on focus
57
+ - `inline` — 40px min-height, no toolbar; for tight inline composers
46
58
 
47
59
  ## Example
48
60
  ```jsx
49
61
  <RichChatInput
50
- onSubmit={(html, text) => sendMessage(html)}
62
+ onSubmit={(message) => sendMessage(message.html)}
51
63
  mentions={teamMembers}
52
64
  onFileUpload={uploadFile}
53
65
  slashCommands={[{ label: 'Actions', commands: [...] }]}
@@ -10,14 +10,19 @@
10
10
  events: ScheduleEvent[] (REQUIRED) — { id, title, start: Date, end: Date, color? }
11
11
  onEventClick?: (event: ScheduleEvent) => void
12
12
  onSlotClick?: (start: Date, end: Date) => void
13
- startHour: number (default: 8)
14
- endHour: number (default: 18, exclusive)
15
- slotDuration: number (minutes, default: 30)
13
+ startHour?: number (default: 8)
14
+ endHour?: number (default: 18, exclusive)
15
+ slotDuration?: number (minutes, default: 30)
16
+ selectedEventId?: string (rings the active event)
17
+ renderEvent?: (event) => ReactNode (custom event body)
18
+ header?: ReactNode (toolbar slot above the grid)
19
+ emptyState?: ReactNode (shown when events is empty)
20
+ height?: number | string (grid body height, default 480)
16
21
 
17
22
  Event colors: "accent" | "success" | "warning" | "error" | "info" | "neutral"
18
23
 
19
24
  ## Defaults
20
- startHour=8, endHour=18, slotDuration=30
25
+ startHour=8, endHour=18, slotDuration=30, height=480
21
26
 
22
27
  ## Example
23
28
  ```jsx
@@ -32,7 +37,12 @@ Event colors: "accent" | "success" | "warning" | "error" | "info" | "neutral"
32
37
  ## Composability
33
38
  - **Day / Week calendar view** for time-block display (meetings, shifts, availability). Not a full calendar app — no month view, no drag-to-create.
34
39
  - **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.
35
- - **Event click + slot click** — `onEventClick` for existing events; `onSlotClick` for creating new events (fires with start/end of the empty slot).
40
+ - **Event click + slot click** — `onEventClick` for existing events; `onSlotClick` for creating new events (fires with start/end of the empty slot). **Slots are only interactive (focusable + keyboard-navigable) when `onSlotClick` is set** — otherwise they render as inert grid lines, so a read-only schedule adds no keyboard/AT tab stops.
41
+ - **Keyboard (interactive slots):** roving tabindex — Arrow keys move between slots (up/down within a day, left/right across days, RTL-aware), Home/End jump within the day; only one slot is in the tab order at a time.
42
+ - **Overlapping events** are partitioned into side-by-side columns automatically so double-booked times stay legible.
43
+ - **Live now-line** ticks every minute and scrolls into view on mount.
44
+ - **`renderEvent`** customizes the event block body; **`header`** adds a toolbar; **`selectedEventId`** rings the active event; **`emptyState`** shows when there are no events.
45
+ - **Composes the DS card shell tokens** (`surface-2` + `rounded-surface` + border) and is RTL-safe (logical properties throughout).
36
46
  - **Color vocabulary matches the DS** — `accent/success/warning/error/info/neutral`. Map your event types to these at the data layer.
37
47
  - **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`.
38
48
  - **Pairs with date-picker/composed** — use DatePicker or DateRangePicker to choose which date to show; pass that as ScheduleView's `date`.
@@ -43,6 +53,11 @@ Event colors: "accent" | "success" | "warning" | "error" | "info" | "neutral"
43
53
  - Events that span outside `startHour`/`endHour` may be clipped
44
54
 
45
55
  ## Changes
56
+ ### v0.53.0
57
+ - **Changed** Read-only schedules no longer flood the tab order — slots are interactive only when `onSlotClick` is set; otherwise inert grid lines. Interactive slots use roving tabindex + Arrow/Home/End keyboard navigation (RTL-aware).
58
+ - **Changed** Overlapping events now lay out in side-by-side columns instead of stacking illegibly. The now-line ticks live (per-minute) and scrolls into view; shell uses the `surface-2` card tier (fixed the prior `surface-raised` + dead-`border-card-strong` regression); layout uses logical (RTL-safe) properties; magic-number sizes tokenized.
59
+ - **Added** `selectedEventId`, `renderEvent`, `header`, `emptyState`, and `height` props.
60
+
46
61
  ### v0.49.0
47
62
  - **BREAKING** `ScheduleEvent.color` value `"primary"` renamed `"accent"` (DS colour vocabulary). It was the default, so untyped events are unaffected.
48
63
  - **Added** keyboard focus rings on slot cells + event blocks; current-time indicator uses the shared `<Dot>`.
@@ -24,7 +24,7 @@
24
24
  ## Composability
25
25
  - **One-liner Tooltip** — wraps TooltipProvider + Tooltip + TooltipTrigger + TooltipContent so consumers don't have to manually compose them for a simple label.
26
26
  - **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).
27
- - **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.
27
+ - **Always mounts its own TooltipProvider** — safe to drop anywhere. Note it does NOT inherit an ancestor `TooltipProvider`'s `delayDuration`; its own `delayDuration` prop (default 300ms) always wins. Set it per-tooltip via the prop.
28
28
  - **Content must be inert** — same rule as ui/Tooltip. For interactive popped content, use Popover or HoverCard.
29
29
  - **Pairs with IconButton** — the canonical pattern for labeled icon buttons.
30
30
 
@@ -23,7 +23,7 @@ AppCommandPaletteUser: { name: string, role?: string }
23
23
  ## Example
24
24
  ```jsx
25
25
  <AppCommandPalette
26
- user={{ name: 'John', role: 'admin' }}
26
+ user={{ name: 'John', role: 'Admin' }}
27
27
  isAdmin={true}
28
28
  onNavigate={(path) => router.push(path)}
29
29
  searchResults={results}
@@ -42,6 +42,7 @@ AppCommandPaletteUser: { name: string, role?: string }
42
42
  ## Gotchas
43
43
  - Uses CommandRegistry context for page navigation items (see CommandRegistryProvider)
44
44
  - `isAdmin` takes precedence over `user.role` for showing admin command groups
45
+ - **Role detection is case-sensitive:** `user.role` only auto-enables admin groups when it is exactly `'Admin'` or `'SuperAdmin'`. `'admin'` (lowercase) silently shows nothing — pass `isAdmin` explicitly if your role strings differ.
45
46
  - Should be placed at the app root level, typically alongside TopBar
46
47
 
47
48
  ## Changes
@@ -6,16 +6,23 @@
6
6
 
7
7
  ## Props
8
8
  currentPath?: string (optional)
9
- user?: BottomNavbarUser | null (optional)
9
+ user?: BottomNavbarUser | null (drives per-item role gating, optional)
10
10
  primaryItems?: BottomNavItem[] (max 4 recommended, optional)
11
- moreItems?: BottomNavItem[] (overflow items in "More" menu, optional)
11
+ moreItems?: BottomNavItem[] (overflow items in "More" sheet, optional)
12
+ indicator?: 'pill' | 'underline' | 'tint' | 'none' (active-item indicator; default 'pill')
13
+ labelVisibility?: 'always' | 'selected'
12
14
  className?: string
13
15
 
14
- BottomNavItem: { title: string, href: string, icon: ReactNode, exact?: boolean, badge?: number }
16
+ BottomNavItem: { title: string, href: string, icon: IconInput, activeIcon?: IconInput, exact?: boolean, badge?: number, roles?: string[], canView?: (user: BottomNavbarUser | null) => boolean }
15
17
  BottomNavbarUser: { name: string, role?: string }
16
18
 
17
19
  ## Defaults
18
- None
20
+ currentPath: '/'
21
+ user: null
22
+ primaryItems: []
23
+ moreItems: []
24
+ indicator: 'pill'
25
+ labelVisibility: 'always'
19
26
 
20
27
  ## Example
21
28
  ```jsx
@@ -29,24 +36,36 @@ BottomNavbarUser: { name: string, role?: string }
29
36
  ```
30
37
 
31
38
  ## Composability
32
- - **Mobile-only bottom nav** — fixed to bottom of viewport. Desktop should use AppSidebar instead.
33
- - **Responsive switch pattern:** Use `useIsMobile()` hook to conditionally render AppSidebar (desktop) or BottomNavbar (mobile). Example:
39
+ - **Mobile-only bottom nav** — fixed to bottom of viewport. Desktop should use Sidebar instead.
40
+ - **Responsive switch pattern:** Use `useIsMobile()` hook to conditionally render Sidebar (desktop) or BottomNavbar (mobile). Example:
34
41
  ```jsx
35
42
  const isMobile = useIsMobile()
36
- return isMobile ? <BottomNavbar ... /> : <AppSidebar ... />
43
+ return isMobile ? <BottomNavbar ... /> : <Sidebar ... />
37
44
  ```
38
45
  - **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.
39
46
  - **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.
40
- - **Badge numbers** cap at 99+ (same as BadgeIndicator pattern).
47
+ - **Badge numbers** cap at 99+ (composes the `Badge` primitive).
48
+ - **Role gating:** each item may declare `roles: string[]` (shown only when `user.role` matches) or a `canView(user)` predicate for arbitrary logic (`canView` wins). Items with neither are always visible. Gating applies to both `primaryItems` and `moreItems`.
49
+ - **Overflow sheet:** the "More" surface is the DS `Sheet` (`side="bottom"`) — it inherits focus trap, scroll lock, return-focus, and `aria-modal`; the trigger is wired with `aria-haspopup`/`aria-controls` automatically.
50
+ - **Indicator (animated):** the active indicator slides to the selected item (shared-element `layoutId`) and fades in on first appearance. Modes: `pill` (default, Material-3 tonal pill behind the icon), `underline` (top accent bar), `tint` (subtle bg on the whole active cell), `none` (no shape — pair with `activeIcon` for the iOS filled-icon look). `labelVisibility="selected"` shows labels only for the active item.
51
+ - **Filled-when-active:** set `activeIcon` on an item (e.g. a Tabler `*Filled` variant) to swap the icon for a filled version while it's the active route; falls back to `icon`.
41
52
  - **Not for desktop:** The viewport-fixed positioning + touch-optimized sizing don't translate well to desktop. Hide behind `md:hidden`.
42
53
 
43
54
  ## Gotchas
44
55
  - Designed for mobile viewports — fixed to bottom of screen
45
56
  - Max 4 `primaryItems` recommended; overflow goes in `moreItems` shown in a "More" sheet
46
- - Use with `useIsMobile()` hook to conditionally render instead of AppSidebar
57
+ - Use with `useIsMobile()` hook to conditionally render instead of Sidebar
47
58
  - Requires LinkProvider for framework-specific link components (e.g., Next.js Link)
48
59
 
49
60
  ## Changes
61
+ ### v0.53.0
62
+ - **Changed** Overflow "More" menu re-founded on the DS `Sheet` primitive — inherits focus trap, scroll lock, return-focus, `aria-modal`, and trigger↔panel ARIA wiring (was a hand-rolled `role="dialog"` with none of these). Composes `Badge` for notification counts and the Sheet's built-in close (≥ touch target).
63
+ - **Added** Per-item role gating: `roles?: string[]` and `canView?: (user) => boolean` on `BottomNavItem`. The previously-inert `user` prop now drives it.
64
+ - **Added** `activeIcon` per item — a filled/alternate icon shown while active (falls back to `icon`). Tightened the icon lozenge padding so icon-only items read less airy.
65
+ - **Added** `indicator` (default **`pill`** — Material-3; plus `underline`, `tint`, `none`) and `labelVisibility` ('always' | 'selected'). The active indicator animates (slides) between items via a shared-element `layoutId`.
66
+ - **Added** Label truncation + logical (RTL-safe) properties; overflow grid adapts to item count.
67
+ - **Fixed** Notification badge `zoom-in` animation now reduced-motion gated.
68
+
50
69
  ### v0.19.0
51
70
  - **Changed** Background elevated from `bg-surface-1` to `bg-surface-2` for visual hierarchy above app background
52
71
  - **Changed** "More" menu and interactive items bumped accordingly
@@ -13,7 +13,7 @@ Exports: CommandRegistryProvider, useCommandRegistry
13
13
  registry: CommandRegistry (REQUIRED)
14
14
 
15
15
  CommandRegistry: { pages: CommandPageItem[], adminPages: CommandPageItem[] }
16
- CommandPageItem: { id: string, label: string, icon: ReactNode, path: string, keywords?: string[] }
16
+ CommandPageItem: { id: string, label: string, icon: IconInput, path: string, keywords?: string[] }
17
17
 
18
18
  ### useCommandRegistry hook
19
19
  Returns: CommandRegistry | null
@@ -41,7 +41,7 @@ CommandPageItem: { id: string, label: string, icon: ReactNode, path: string, key
41
41
  ## Composability
42
42
  - **Context provider for AppCommandPalette.** Registers page-level navigation items that the command palette surfaces as commands.
43
43
  - **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.
44
- - **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.
44
+ - **Separation of pages vs adminPages** — this is an ORGANIZATIONAL split, not access control. The component does NOT enforce anything: YOU populate `adminPages` conditionally (e.g. only when the signed-in user is an admin). Anything you put in `adminPages` is still shipped to the client do real authorization on the server, not here.
45
45
  - **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).
46
46
  - **Works with LinkProvider** — CommandPaletteItems navigate via `onNavigate` prop on AppCommandPalette, which routes to your framework's Link component.
47
47
 
@@ -23,13 +23,13 @@ Exports: LinkProvider, useLink
23
23
  import Link from 'next/link'
24
24
 
25
25
  <LinkProvider component={Link}>
26
- <AppSidebar ... />
26
+ <Sidebar ... />
27
27
  <BottomNavbar ... />
28
28
  </LinkProvider>
29
29
  ```
30
30
 
31
31
  ## Composability
32
- - **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.
32
+ - **The framework router bridge for all shell components.** Without LinkProvider, Sidebar / BottomNavbar / TopBar.UserMenu / AppCommandPalette render plain `<a>` tags — that means full page reloads instead of client-side navigation.
33
33
  - **Required setup:**
34
34
  ```tsx
35
35
  // Next.js
@@ -120,7 +120,7 @@ UserMenuItem fields:
120
120
  - **NotificationCenter lives inside TopBar.Right** — it's a complete bell+popover component. Drop it in a TopBar.Section next to other icon buttons.
121
121
  - **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.
122
122
  - **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.
123
- - **Pairs with AppSidebar** — desktop app shell is typically `<TopBar>` + `<AppSidebar>` + main content region.
123
+ - **Pairs with Sidebar** — desktop app shell is typically `<TopBar>` + `<Sidebar>` + main content region.
124
124
 
125
125
  ## Gotchas
126
126
  - Without `TopBar.Center`, layout is flex (two-zone). With it, layout switches to CSS grid `1fr auto 1fr` for true centering.
@@ -6,16 +6,23 @@
6
6
 
7
7
  ## Props
8
8
  options: AutocompleteOption[] (REQUIRED) — { value: string, label: string }
9
- value: AutocompleteOption | null
9
+ value?: AutocompleteOption | null (controlled)
10
+ defaultValue?: AutocompleteOption | null (uncontrolled initial)
10
11
  onValueChange?: (option: AutocompleteOption) => void
11
- placeholder: string
12
- emptyText: string (default: "No options")
13
- disabled: boolean
14
- className: string
15
- id: string
12
+ placeholder?: string
13
+ emptyText?: string (default: "No options")
14
+ disabled?: boolean
15
+ size?: (forwarded to Input)
16
+ state?: 'default' | 'error' | 'warning' | 'success' (forwarded to Input)
17
+ isLoading?: boolean
18
+ loadingText?: string (default: "Loading…")
19
+ renderOption?: (option, query) => ReactNode
20
+ className?: string
21
+ id?: string
16
22
 
17
23
  ## Defaults
18
24
  emptyText="No options"
25
+ loadingText="Loading…"
19
26
 
20
27
  ## Example
21
28
  ```jsx
@@ -28,17 +35,26 @@
28
35
  ```
29
36
 
30
37
  ## Composability
31
- - **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).
32
- - **Value shape is an object** (`{ value, label }`), not a plain string this preserves label/value decoupling for display-vs-storage.
33
- - **FormField:** Does NOT auto-consume FormField state. Set explicit error styling via className if needed.
38
+ - **Composes `Input`** the field is the DS `Input`, so it inherits `size`, error/`state` painting, read-only, hover, and FormField wiring. Autocomplete owns only the dropdown + behavior.
39
+ - **Autocomplete vs Combobox:** Autocomplete allows free-text input (users can type anything); Combobox enforces selection from the list.
40
+ - **Value shape is an object** (`{ value, label }`), not a plain string. Controlled via `value`, or uncontrolled via `defaultValue`.
41
+ - **FormField:** auto-consumes FormField state (via the composed Input) — inside a FormField, error border + `aria-invalid`/`aria-describedby`/`required` are wired automatically. Pass `state` to override.
42
+ - **Async / "type to search":** set `isLoading` to show a spinner (in the field + the listbox) with `loadingText`.
43
+ - **Matched-text highlight:** the query substring is bolded in each option by default; override the whole row with `renderOption`.
34
44
  - **Portal rendering:** Dropdown portals to body with z-popover (1400) — stacks above Dialog/Sheet.
35
- - **Keyboard:** ArrowDown/Up navigate suggestions, Enter selects, Esc closes. Typeahead is the input's native filtering.
45
+ - **Keyboard:** ArrowDown/Up/Home/End navigate, Enter selects, Esc closes.
36
46
 
37
47
  ## Gotchas
38
48
  - Allows free-text input (no forced selection) — use Combobox for forced selection
39
49
  - value is an object { value, label }, NOT just a string
50
+ - Client-side filtering only (known list). For huge/remote lists, drive `options` yourself with `isLoading` — no built-in virtualization.
40
51
 
41
52
  ## Changes
53
+ ### v0.53.0
54
+ - **Changed** Re-parented onto the DS `Input` primitive — inherits `size`, error/`state` painting, read-only, hover, and FormField auto-consumption (previously re-rolled its own `<input>` and read FormField error but never painted it).
55
+ - **Added** `defaultValue` (uncontrolled), `size`, `state`, `isLoading`/`loadingText` (async), `renderOption`, and matched-substring highlighting.
56
+ - **Fixed** Doc corrected — it DOES auto-consume FormField (via Input). Dropped a keystroke-frequency stagger animation + a dead effect.
57
+
42
58
  ### v0.18.0
43
59
  - **Fixed** Added `useEffect` to sync query when external value changes
44
60
 
@@ -5,7 +5,7 @@
5
5
  - Category: ui
6
6
 
7
7
  ## Props
8
- size: "sm" | "md" | "lg"
8
+ size: "xs" | "sm" | "md" | "lg"
9
9
  loading: boolean (shows spinner instead of clear button)
10
10
  onClear: () => void (shows X button when value is non-empty)
11
11
  value: string
@@ -31,7 +31,7 @@
31
31
  - **`onClear` makes the X button appear** only when `value` is non-empty. Pair them so users can reset.
32
32
  - **`loading={true}` swaps the clear button for a spinner** with `aria-busy="true"` on the input — useful for debounced/async search.
33
33
  - Doesn't auto-consume FormField (no `state` prop) — wrap a regular Input inside FormField for validated search fields.
34
- - Keyboard: Escape auto-triggers `onClear` when wired (handled via `type="search"`'s native behavior on most browsers).
34
+ - Keyboard: Escape is NOT wired to clear (the input is not `type="search"`). Reset via the X button, or handle Escape yourself and call your `value` setter.
35
35
 
36
36
  ## Gotchas
37
37
  - HTML native "size" attribute is excluded — use CSS width instead