@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.
- package/AGENTS.md +7 -1
- package/BREAKING.json +29 -0
- package/MIGRATION.md +61 -0
- package/dist/_chunks/motion-provider.js +7 -6
- package/dist/_chunks/motion-provider.js.map +1 -1
- package/dist/_chunks/success.js +53 -53
- package/dist/ai/command-bar.js +158 -158
- package/dist/ai/command-bar.js.map +1 -1
- package/dist/ai/conversation.js +5 -5
- package/dist/composed/avatar-group.d.ts +11 -9
- package/dist/composed/avatar-group.d.ts.map +1 -1
- package/dist/composed/avatar-group.js +108 -140
- package/dist/composed/avatar-group.js.map +1 -1
- package/dist/composed/bulk-action-bar.d.ts +19 -11
- package/dist/composed/bulk-action-bar.d.ts.map +1 -1
- package/dist/composed/bulk-action-bar.js +81 -110
- package/dist/composed/bulk-action-bar.js.map +1 -1
- package/dist/composed/command-palette.js +17 -17
- package/dist/composed/command-palette.js.map +1 -1
- package/dist/composed/error-boundary.d.ts +15 -3
- package/dist/composed/error-boundary.d.ts.map +1 -1
- package/dist/composed/error-boundary.js +43 -26
- package/dist/composed/error-boundary.js.map +1 -1
- package/dist/composed/loading-skeleton.d.ts +2 -0
- package/dist/composed/loading-skeleton.d.ts.map +1 -1
- package/dist/composed/loading-skeleton.js +79 -56
- package/dist/composed/loading-skeleton.js.map +1 -1
- package/dist/composed/master-detail.d.ts +21 -8
- package/dist/composed/master-detail.d.ts.map +1 -1
- package/dist/composed/master-detail.js +91 -77
- package/dist/composed/master-detail.js.map +1 -1
- package/dist/composed/page-skeletons.d.ts +6 -0
- package/dist/composed/page-skeletons.d.ts.map +1 -1
- package/dist/composed/page-skeletons.js +93 -71
- package/dist/composed/page-skeletons.js.map +1 -1
- package/dist/composed/priority-indicator.d.ts +15 -6
- package/dist/composed/priority-indicator.d.ts.map +1 -1
- package/dist/composed/priority-indicator.js +37 -88
- package/dist/composed/priority-indicator.js.map +1 -1
- package/dist/composed/schedule-view.d.ts +17 -2
- package/dist/composed/schedule-view.d.ts.map +1 -1
- package/dist/composed/schedule-view.js +163 -64
- package/dist/composed/schedule-view.js.map +1 -1
- package/dist/motion/motion-provider.d.ts.map +1 -1
- package/dist/shell/bottom-navbar.d.ts +32 -6
- package/dist/shell/bottom-navbar.d.ts.map +1 -1
- package/dist/shell/bottom-navbar.js +156 -129
- package/dist/shell/bottom-navbar.js.map +1 -1
- package/dist/shell/index.d.ts +5 -3
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +2 -3
- package/dist/shell/notification-preferences.d.ts.map +1 -1
- package/dist/shell/notification-preferences.js +2 -0
- package/dist/shell/notification-preferences.js.map +1 -1
- package/dist/tokens/primitives.css +6 -2
- package/dist/tokens/utilities.css +6 -0
- package/dist/ui/autocomplete.d.ts +34 -32
- package/dist/ui/autocomplete.d.ts.map +1 -1
- package/dist/ui/autocomplete.js +115 -110
- package/dist/ui/autocomplete.js.map +1 -1
- package/dist/ui/combobox.d.ts +1 -1
- package/dist/ui/combobox.d.ts.map +1 -1
- package/dist/ui/combobox.js +133 -128
- package/dist/ui/combobox.js.map +1 -1
- package/dist/ui/file-upload.d.ts.map +1 -1
- package/dist/ui/file-upload.js +124 -138
- package/dist/ui/file-upload.js.map +1 -1
- package/dist/ui/form.d.ts +7 -0
- package/dist/ui/form.d.ts.map +1 -1
- package/dist/ui/form.js +4 -2
- package/dist/ui/form.js.map +1 -1
- package/dist/ui/icon-button.d.ts +2 -1
- package/dist/ui/icon-button.d.ts.map +1 -1
- package/dist/ui/icon-button.js +1 -1
- package/dist/ui/icon-button.js.map +1 -1
- package/dist/ui/index.js +30 -30
- package/dist/ui/label.d.ts.map +1 -1
- package/dist/ui/label.js +7 -6
- package/dist/ui/label.js.map +1 -1
- package/dist/ui/search-input.js +4 -4
- package/dist/ui/sidebar.js +7 -7
- package/dist/ui/spinner.d.ts.map +1 -1
- package/dist/ui/spinner.js +50 -44
- package/dist/ui/spinner.js.map +1 -1
- package/dist/ui/switch.d.ts.map +1 -1
- package/dist/ui/switch.js +24 -24
- package/dist/ui/switch.js.map +1 -1
- package/dist/ui/table.js +1 -1
- package/dist/ui/table.js.map +1 -1
- package/docs/components/composed/bulk-action-bar.md +9 -3
- package/docs/components/composed/error-boundary.md +18 -2
- package/docs/components/composed/master-detail.md +12 -2
- package/docs/components/composed/page-skeletons.md +2 -2
- package/docs/components/composed/priority-indicator.md +22 -11
- package/docs/components/composed/rich-chat-input.md +18 -6
- package/docs/components/composed/schedule-view.md +20 -5
- package/docs/components/composed/simple-tooltip.md +1 -1
- package/docs/components/shell/app-command-palette.md +2 -1
- package/docs/components/shell/bottom-navbar.md +28 -9
- package/docs/components/shell/command-registry.md +2 -2
- package/docs/components/shell/link-context.md +2 -2
- package/docs/components/shell/top-bar.md +1 -1
- package/docs/components/ui/autocomplete.md +26 -10
- package/docs/components/ui/search-input.md +2 -2
- package/docs/components/ui/slider.md +2 -2
- package/docs/components/ui/split-button.md +3 -3
- package/llms.txt +2 -2
- package/make-kit/Guidelines.md +1 -1
- package/make-kit/components/overview.md +2 -2
- package/make-kit/foundations/surfaces.md +1 -1
- package/mcp-manifest.json +581 -172
- package/package.json +1 -6
- package/skill/SKILL.md +1 -1
- package/skill/chat-skill/README.md +18 -0
- package/skill/chat-skill/SKILL.md +78 -0
- package/skill/chat-skill/chatgpt-instructions.md +26 -0
- package/skill/chat-skill/gemini-instructions.md +25 -0
- package/skill/references/components.md +2 -2
- package/dist/shell/sidebar.d.ts +0 -113
- package/dist/shell/sidebar.d.ts.map +0 -1
- package/dist/shell/sidebar.js +0 -293
- package/dist/shell/sidebar.js.map +0 -1
- package/docs/components/shell/sidebar.md +0 -103
package/dist/ui/switch.js.map
CHANGED
|
@@ -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,
|
|
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";
|
package/dist/ui/table.js.map
CHANGED
|
@@ -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,
|
|
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:
|
|
19
|
+
icon: IconInput (optional — any icon component or element)
|
|
17
20
|
onClick: () => void
|
|
18
|
-
color: "
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
- **
|
|
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
|
|
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:
|
|
4
|
+
- Server-safe: No
|
|
5
5
|
- Category: composed
|
|
6
6
|
|
|
7
7
|
## Props
|
|
8
8
|
priority: Priority
|
|
9
|
-
|
|
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
|
-
|
|
16
|
+
iconOnly: false
|
|
15
17
|
|
|
16
18
|
## Example
|
|
17
19
|
```jsx
|
|
18
20
|
<PriorityIndicator priority="HIGH" />
|
|
19
|
-
<PriorityIndicator priority="low"
|
|
21
|
+
<PriorityIndicator priority="low" iconOnly />
|
|
22
|
+
<PriorityIndicator priority="URGENT">Critical</PriorityIndicator>
|
|
20
23
|
```
|
|
21
24
|
|
|
22
25
|
## Composability
|
|
23
|
-
- **
|
|
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
|
-
-
|
|
26
|
-
- **
|
|
27
|
-
-
|
|
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
|
-
-
|
|
32
|
-
- `
|
|
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: (
|
|
12
|
+
onSubmit: (message: RichChatInputMessage) => void (REQUIRED)
|
|
13
13
|
placeholder: string (default: "Type a message...")
|
|
14
14
|
disabled: boolean (default: false)
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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={(
|
|
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
|
|
14
|
-
endHour
|
|
15
|
-
slotDuration
|
|
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
|
-
- **
|
|
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: '
|
|
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"
|
|
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:
|
|
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
|
-
|
|
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
|
|
33
|
-
- **Responsive switch pattern:** Use `useIsMobile()` hook to conditionally render
|
|
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 ... /> : <
|
|
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+ (
|
|
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
|
|
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:
|
|
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** —
|
|
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
|
-
<
|
|
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,
|
|
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
|
|
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
|
|
9
|
+
value?: AutocompleteOption | null (controlled)
|
|
10
|
+
defaultValue?: AutocompleteOption | null (uncontrolled initial)
|
|
10
11
|
onValueChange?: (option: AutocompleteOption) => void
|
|
11
|
-
placeholder
|
|
12
|
-
emptyText
|
|
13
|
-
disabled
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
- **
|
|
32
|
-
- **
|
|
33
|
-
- **
|
|
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
|
|
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
|
|
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
|