@devalok/shilp-sutra 0.59.0 → 0.60.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 (75) hide show
  1. package/MIGRATION.md +72 -0
  2. package/dist/composed/avatar-group.js +3 -3
  3. package/dist/composed/avatar-group.js.map +1 -1
  4. package/dist/composed/bulk-action-bar.d.ts +9 -35
  5. package/dist/composed/bulk-action-bar.d.ts.map +1 -1
  6. package/dist/composed/bulk-action-bar.js +2 -139
  7. package/dist/composed/index.js +6 -5
  8. package/dist/composed/master-detail.d.ts.map +1 -1
  9. package/dist/composed/master-detail.js +1 -1
  10. package/dist/composed/master-detail.js.map +1 -1
  11. package/dist/composed/multi-select-popover.d.ts.map +1 -1
  12. package/dist/composed/multi-select-popover.js +1 -1
  13. package/dist/composed/multi-select-popover.js.map +1 -1
  14. package/dist/tokens/semantic.css +85 -3
  15. package/dist/ui/avatar.d.ts.map +1 -1
  16. package/dist/ui/avatar.js +3 -3
  17. package/dist/ui/avatar.js.map +1 -1
  18. package/dist/ui/bulk-action-bar.d.ts +59 -0
  19. package/dist/ui/bulk-action-bar.d.ts.map +1 -0
  20. package/dist/ui/bulk-action-bar.js +145 -0
  21. package/dist/ui/bulk-action-bar.js.map +1 -0
  22. package/dist/ui/checkbox.d.ts.map +1 -1
  23. package/dist/ui/checkbox.js +1 -1
  24. package/dist/ui/checkbox.js.map +1 -1
  25. package/dist/ui/combobox.d.ts.map +1 -1
  26. package/dist/ui/combobox.js +10 -10
  27. package/dist/ui/combobox.js.map +1 -1
  28. package/dist/ui/data-table-body.d.ts.map +1 -1
  29. package/dist/ui/data-table-body.js +2 -1
  30. package/dist/ui/data-table-body.js.map +1 -1
  31. package/dist/ui/data-table-bulk-actions.d.ts +26 -1
  32. package/dist/ui/data-table-bulk-actions.d.ts.map +1 -1
  33. package/dist/ui/data-table-bulk-actions.js +21 -46
  34. package/dist/ui/data-table-bulk-actions.js.map +1 -1
  35. package/dist/ui/data-table-context.d.ts +36 -2
  36. package/dist/ui/data-table-context.d.ts.map +1 -1
  37. package/dist/ui/data-table-context.js +25 -21
  38. package/dist/ui/data-table-context.js.map +1 -1
  39. package/dist/ui/data-table-header.d.ts.map +1 -1
  40. package/dist/ui/data-table-header.js +2 -1
  41. package/dist/ui/data-table-header.js.map +1 -1
  42. package/dist/ui/index.d.ts +1 -0
  43. package/dist/ui/index.d.ts.map +1 -1
  44. package/dist/ui/index.js +58 -57
  45. package/dist/ui/index.js.map +1 -1
  46. package/dist/ui/input.d.ts.map +1 -1
  47. package/dist/ui/input.js +6 -6
  48. package/dist/ui/input.js.map +1 -1
  49. package/dist/ui/radio.d.ts.map +1 -1
  50. package/dist/ui/radio.js +4 -4
  51. package/dist/ui/radio.js.map +1 -1
  52. package/dist/ui/select.d.ts.map +1 -1
  53. package/dist/ui/select.js +6 -6
  54. package/dist/ui/select.js.map +1 -1
  55. package/dist/ui/switch.d.ts.map +1 -1
  56. package/dist/ui/switch.js +2 -2
  57. package/dist/ui/switch.js.map +1 -1
  58. package/dist/ui/table.d.ts.map +1 -1
  59. package/dist/ui/table.js +1 -1
  60. package/dist/ui/table.js.map +1 -1
  61. package/dist/ui/textarea.d.ts.map +1 -1
  62. package/dist/ui/textarea.js +6 -6
  63. package/dist/ui/textarea.js.map +1 -1
  64. package/dist/ui/toast.d.ts.map +1 -1
  65. package/dist/ui/toast.js +1 -1
  66. package/dist/ui/toast.js.map +1 -1
  67. package/docs/components/composed/bulk-action-bar.md +14 -7
  68. package/docs/components/ui/bulk-action-bar.md +72 -0
  69. package/llms.txt +2 -2
  70. package/make-kit/foundations/color.md +3 -1
  71. package/mcp-manifest.json +178 -150
  72. package/package.json +6 -1
  73. package/skill/SKILL.md +1 -1
  74. package/skill/references/components.md +2 -2
  75. package/dist/composed/bulk-action-bar.js.map +0 -1
package/MIGRATION.md CHANGED
@@ -4,6 +4,78 @@ This page indexes all breaking changes across `@devalok/shilp-sutra` versions. F
4
4
 
5
5
  > **Upgrading from < 0.36?** Start here, then read each intermediate version section. Breaking changes stack — skipping versions means stacking migrations.
6
6
 
7
+ ## v0.60.0 — no code changes needed, but several controls look different
8
+
9
+ **Nothing breaks at the TypeScript level.** No prop removed, no type narrowed,
10
+ no export dropped. You do not have to change any code to take this release.
11
+
12
+ It is not "additive" either, and the distinction matters if you keep visual
13
+ snapshots: several controls change appearance, and one screen-reader
14
+ announcement changes. Everything below happens on upgrade, with no opt-out.
15
+
16
+ ### Form controls take a distinct ground
17
+
18
+ Inputs, Textareas, Selects and Comboboxes now sit on their own `--color-field`
19
+ token rather than a surface value — `neutral-1` in light, `neutral-2` in dark.
20
+ Hover moves with them. Validation edges move from colour step 7 to step 8, which
21
+ takes them **over** the WCAG 1.4.11 3:1 bar for the first time (error
22
+ 2.75 → 3.82:1, success 2.49 → 3.36:1).
23
+
24
+ If you override field backgrounds, check them against the new token.
25
+
26
+ ### Switch loses its resting stroke
27
+
28
+ An unchecked Switch is now carried by its fill alone. Validation states still
29
+ paint the border — the 2px is reserved and transparent, so the thumb does not
30
+ move when a field errors.
31
+
32
+ If you relied on the resting outline to find an off switch on a busy surface,
33
+ it is now 1.639:1 against the panel rather than 2.006:1. Recorded as
34
+ `SWITCH-RESTING-STROKE-REMOVED` in `docs/deviations.md`.
35
+
36
+ ### Avatar role rings are fainter
37
+
38
+ Step 7 → 6 on `lead`, `admin` and `client`. Measured on white the lead ring goes
39
+ 2.865 → 2.095:1. **The ring is the only thing conveying role** — no label, no
40
+ icon, no tooltip — so if role matters in your UI, add a text affordance. This is
41
+ a known, accepted shortfall (`AVATAR-ROLE-RING-STEP-6`), not an oversight.
42
+
43
+ ### Disabled elements are slightly more legible
44
+
45
+ `--action-disabled-opacity` 38% → 45%, globally. Every `opacity-action-disabled`
46
+ consumer moves. This improves every case measured; the worst
47
+ (`surface-fg-subtle`) goes 1.673 → 1.859:1.
48
+
49
+ ### DataTable's bulk-action bar changed announcement and emphasis
50
+
51
+ It now uses the shared `BulkActionBar` internally. `bulkActions`,
52
+ `bulkActionsPosition` and `onClick(selectedRows)` are unchanged — nothing to
53
+ migrate — but:
54
+
55
+ - the toolbar's accessible name is the stable `"Bulk actions"` rather than
56
+ `"N items selected"`, which was re-announced on every selection change;
57
+ - non-destructive actions render `ghost` rather than `outline`; destructive
58
+ (`color: 'error'`) actions stay `solid`;
59
+ - it gains roving focus (Arrow / Home / End), inline confirmation via
60
+ `requiresConfirmation`, per-action `loading`, and reduced-motion support —
61
+ props `BulkAction` already declared and the previous implementation ignored.
62
+
63
+ `BulkActionBar`'s implementation moved from `composed/bulk-action-bar` to
64
+ `ui/bulk-action-bar`. **The old import path still works**; a new one is also
65
+ available.
66
+
67
+ ### Shadows
68
+
69
+ Light shadows are 1/255 lighter — invisible, done to match the Figma library
70
+ exactly. Dark shadows changed more: they now render as shadows rather than
71
+ faint glows on the page background, and the pressed ring is visible in dark for
72
+ the first time (it measured 1.012:1, i.e. nothing).
73
+
74
+ ### If you keep visual snapshots
75
+
76
+ Expect diffs on: any form control, Switch, Avatar with a role ring, anything
77
+ disabled, DataTable with bulk actions, and anything elevated in dark mode.
78
+
7
79
  ## v0.59.0 — additive (no migration required)
8
80
 
9
81
  Nothing breaks at the TypeScript level; this release is additive.
@@ -9,9 +9,9 @@ import { jsx as d, jsxs as f } from "react/jsx-runtime";
9
9
  import { motion as p, useReducedMotion as m } from "framer-motion";
10
10
  //#region src/composed/avatar-group.tsx
11
11
  var h = {
12
- lead: "ring-2 ring-accent-7",
13
- admin: "ring-2 ring-warning-7",
14
- client: "ring-2 ring-info-7"
12
+ lead: "ring-2 ring-accent-6",
13
+ admin: "ring-2 ring-warning-6",
14
+ client: "ring-2 ring-info-6"
15
15
  }, g = {
16
16
  xs: "-ml-ds-02",
17
17
  sm: "-ml-ds-02b",
@@ -1 +1 @@
1
- {"version":3,"file":"avatar-group.js","names":[],"sources":["../../src/composed/avatar-group.tsx"],"sourcesContent":["'use client'\n\nimport { type VariantProps } from 'class-variance-authority'\nimport { motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\n\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n avatarVariants,\n type AvatarRing,\n} from '../ui/avatar'\nimport { springs } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '../ui/tooltip'\nimport { getInitials } from './lib/string-utils'\n\ntype Size = NonNullable<VariantProps<typeof avatarVariants>['size']>\n\n// Group-level ring. Applied here (not via Avatar's `ring` prop) because that adds\n// a ring-offset that gets clipped by the group's overflow; the offset surface is\n// derived from `borderColor` so it never seams against a surface-base blend.\nconst groupRingMap: Record<Exclude<AvatarRing, 'none'>, string> = {\n lead: 'ring-2 ring-accent-7',\n admin: 'ring-2 ring-warning-7',\n client: 'ring-2 ring-info-7',\n}\n\nexport interface AvatarUser {\n name: string\n image?: string | null\n ring?: AvatarRing\n /** Small top-right marker. `lead` = accent dot, `admin` = warning dot, or any node. */\n indicator?: 'lead' | 'admin' | React.ReactNode\n}\n\nexport interface AvatarGroupProps extends React.HTMLAttributes<HTMLDivElement> {\n users: AvatarUser[]\n max?: number\n size?: Size\n showTooltip?: boolean\n /**\n * Border color for the group avatars. @default 'surface-panel'\n *\n * `'surface-raised'` is the pre-0.57 name for `'surface-panel'` and still\n * works — a widening, so nothing breaks. It is removed next major.\n */\n borderColor?: 'surface-base' | 'surface-panel' | 'surface-raised'\n /** Callback when the \"+N\" overflow badge is clicked (renders it as a button). */\n onOverflowClick?: () => void\n /** Custom render function for each avatar (consumer owns the Avatar). */\n renderAvatar?: (user: AvatarUser, index: number) => React.ReactNode\n /** Direction avatars expand on hover/focus. @default 'right' */\n expandDirection?: 'left' | 'right'\n /** How far avatars spread on hover/focus. @default 'default' */\n expandAmount?: 'compact' | 'default' | 'wide'\n /** Accessible label for the group. @default `${count} team members` */\n label?: string\n}\n\n// Overlap (negative margin) + spread distance, keyed off the same per-size scale.\nconst overlapMap: Record<Size, string> = {\n xs: '-ml-ds-02',\n sm: '-ml-ds-02b',\n md: '-ml-ds-03',\n lg: '-ml-ds-04',\n xl: '-ml-ds-05',\n}\nconst spreadPxMap: Record<Size, number> = {\n xs: 8, sm: 10, md: 12, lg: 16, xl: 20,\n}\nconst indicatorDotMap: Record<Size, string> = {\n xs: 'size-1', sm: 'size-1.5', md: 'size-2', lg: 'size-2.5', xl: 'size-3',\n}\n\nconst AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(\n (\n {\n users,\n max = 4,\n size = 'md',\n showTooltip = true,\n borderColor = 'surface-panel',\n onOverflowClick,\n renderAvatar,\n expandDirection = 'right',\n expandAmount = 'default',\n label,\n className,\n onMouseEnter,\n onMouseLeave,\n onFocus,\n onBlur,\n ...props\n },\n ref,\n ) => {\n const reduced = useReducedMotion()\n const [active, setActive] = React.useState(false)\n\n // Empty group is not a focusable \"0 members\" target — render nothing.\n if (users.length === 0) return null\n\n const cap = Math.max(1, max)\n const displayed = users.slice(0, cap)\n const overflow = users.length - cap\n const overlapClass = overlapMap[size]\n const borderClass = borderColor === 'surface-base' ? 'border-surface-base' : 'border-surface-panel'\n const ringOffsetClass = borderColor === 'surface-base'\n ? 'ring-offset-1 ring-offset-surface-base'\n : 'ring-offset-1 ring-offset-surface-panel'\n\n const spreadPx = spreadPxMap[size]\n const spreadMult = { compact: 0.5, default: 1, wide: 1.5 }[expandAmount]\n const totalVisible = displayed.length + (overflow > 0 ? 1 : 0)\n\n // Spread offset per index. Zero under reduced-motion (no positional animation).\n const xFor = (index: number): number => {\n if (!active || reduced) return 0\n const shift = spreadPx * spreadMult\n return expandDirection === 'left' ? -(totalVisible - 1 - index) * shift : index * shift\n }\n\n // framer governs the transition; reduced-motion collapses it to instant.\n const spreadTransition = reduced ? { duration: 0 } : springs.snappy\n // Gentle dim of non-focused peers; instant under reduced-motion.\n const spotlight =\n 'transition-opacity duration-fast-02 ease-productive-standard motion-reduce:transition-none hover:z-30 focus-visible:z-30 group-hover:[&:not(:hover)]:opacity-80'\n\n const onEnter = (e: React.MouseEvent<HTMLDivElement>) => { setActive(true); onMouseEnter?.(e) }\n const onLeave = (e: React.MouseEvent<HTMLDivElement>) => { setActive(false); onMouseLeave?.(e) }\n const onGroupFocus = (e: React.FocusEvent<HTMLDivElement>) => { setActive(true); onFocus?.(e) }\n const onGroupBlur = (e: React.FocusEvent<HTMLDivElement>) => {\n if (!e.currentTarget.contains(e.relatedTarget)) setActive(false)\n onBlur?.(e)\n }\n\n return (\n <TooltipProvider>\n <div\n ref={ref}\n role=\"group\"\n aria-label={label ?? `${users.length} team members`}\n className={cn('group flex items-center', className)}\n onMouseEnter={onEnter}\n onMouseLeave={onLeave}\n onFocus={onGroupFocus}\n onBlur={onGroupBlur}\n {...props}\n >\n {displayed.map((user, index) => {\n const key = `${user.name}-${index}`\n const extraRing = user.ring && user.ring !== 'none'\n ? cn(groupRingMap[user.ring], ringOffsetClass)\n : undefined\n\n const inner = renderAvatar ? (\n renderAvatar(user, index)\n ) : (\n <Avatar size={size} className={borderClass}>\n {user.image && <AvatarImage src={user.image} alt=\"\" />}\n <AvatarFallback colorSeed={user.name}>{getInitials(user.name)}</AvatarFallback>\n {user.indicator && (\n <span\n aria-hidden=\"true\"\n className={cn(\n 'absolute top-0 right-0 rounded-pill ring-1 ring-surface-panel',\n indicatorDotMap[size],\n user.indicator === 'lead' ? 'bg-accent-9'\n : user.indicator === 'admin' ? 'bg-warning-9' : '',\n )}\n >\n {typeof user.indicator !== 'string' && user.indicator}\n </span>\n )}\n </Avatar>\n )\n\n // Each avatar is a focusable, labelled trigger so keyboard/AT users reach\n // the member name (via the tooltip) and get a visible focus ring.\n const node = (\n <motion.button\n type=\"button\"\n aria-label={user.name}\n animate={{ x: xFor(index) }}\n transition={spreadTransition}\n style={{ zIndex: displayed.length - index }}\n className={cn(\n 'relative shrink-0 rounded-pill focus-ring',\n index > 0 && overlapClass,\n spotlight,\n extraRing,\n )}\n >\n {inner}\n </motion.button>\n )\n\n if (!showTooltip) return <React.Fragment key={key}>{node}</React.Fragment>\n\n return (\n <Tooltip key={key}>\n <TooltipTrigger asChild>{node}</TooltipTrigger>\n <TooltipContent sideOffset={6}>\n <p className=\"text-body-sm\">{user.name}</p>\n </TooltipContent>\n </Tooltip>\n )\n })}\n\n {overflow > 0 && (\n <Tooltip>\n <TooltipTrigger asChild>\n <motion.button\n type=\"button\"\n onClick={onOverflowClick}\n aria-label={`${overflow} more ${overflow === 1 ? 'member' : 'members'}`}\n animate={{ x: xFor(displayed.length) }}\n transition={spreadTransition}\n style={{ zIndex: 0 }}\n className={cn(\n 'relative shrink-0 rounded-pill focus-ring',\n overlapClass,\n spotlight,\n !onOverflowClick && 'cursor-default',\n )}\n >\n <Avatar size={size} className={borderClass}>\n <AvatarFallback className=\"bg-accent-2 font-semibold text-accent-11\">\n +{overflow}\n </AvatarFallback>\n </Avatar>\n </motion.button>\n </TooltipTrigger>\n <TooltipContent sideOffset={6}>\n <div className=\"flex flex-col gap-ds-01\">\n {users.slice(cap).map((user, i) => (\n <p key={i} className=\"text-body-sm\">{user.name}</p>\n ))}\n </div>\n </TooltipContent>\n </Tooltip>\n )}\n </div>\n </TooltipProvider>\n )\n },\n)\nAvatarGroup.displayName = 'AvatarGroup'\n\nexport { AvatarGroup }\n"],"mappings":"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"}
1
+ {"version":3,"file":"avatar-group.js","names":[],"sources":["../../src/composed/avatar-group.tsx"],"sourcesContent":["'use client'\n\nimport { type VariantProps } from 'class-variance-authority'\nimport { motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\n\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n avatarVariants,\n type AvatarRing,\n} from '../ui/avatar'\nimport { springs } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '../ui/tooltip'\nimport { getInitials } from './lib/string-utils'\n\ntype Size = NonNullable<VariantProps<typeof avatarVariants>['size']>\n\n// Group-level ring. Applied here (not via Avatar's `ring` prop) because that adds\n// a ring-offset that gets clipped by the group's overflow; the offset surface is\n// derived from `borderColor` so it never seams against a surface-base blend.\nconst groupRingMap: Record<Exclude<AvatarRing, 'none'>, string> = {\n lead: 'ring-2 ring-accent-6',\n admin: 'ring-2 ring-warning-6',\n client: 'ring-2 ring-info-6',\n}\n\nexport interface AvatarUser {\n name: string\n image?: string | null\n ring?: AvatarRing\n /** Small top-right marker. `lead` = accent dot, `admin` = warning dot, or any node. */\n indicator?: 'lead' | 'admin' | React.ReactNode\n}\n\nexport interface AvatarGroupProps extends React.HTMLAttributes<HTMLDivElement> {\n users: AvatarUser[]\n max?: number\n size?: Size\n showTooltip?: boolean\n /**\n * Border color for the group avatars. @default 'surface-panel'\n *\n * `'surface-raised'` is the pre-0.57 name for `'surface-panel'` and still\n * works — a widening, so nothing breaks. It is removed next major.\n */\n borderColor?: 'surface-base' | 'surface-panel' | 'surface-raised'\n /** Callback when the \"+N\" overflow badge is clicked (renders it as a button). */\n onOverflowClick?: () => void\n /** Custom render function for each avatar (consumer owns the Avatar). */\n renderAvatar?: (user: AvatarUser, index: number) => React.ReactNode\n /** Direction avatars expand on hover/focus. @default 'right' */\n expandDirection?: 'left' | 'right'\n /** How far avatars spread on hover/focus. @default 'default' */\n expandAmount?: 'compact' | 'default' | 'wide'\n /** Accessible label for the group. @default `${count} team members` */\n label?: string\n}\n\n// Overlap (negative margin) + spread distance, keyed off the same per-size scale.\nconst overlapMap: Record<Size, string> = {\n xs: '-ml-ds-02',\n sm: '-ml-ds-02b',\n md: '-ml-ds-03',\n lg: '-ml-ds-04',\n xl: '-ml-ds-05',\n}\nconst spreadPxMap: Record<Size, number> = {\n xs: 8, sm: 10, md: 12, lg: 16, xl: 20,\n}\nconst indicatorDotMap: Record<Size, string> = {\n xs: 'size-1', sm: 'size-1.5', md: 'size-2', lg: 'size-2.5', xl: 'size-3',\n}\n\nconst AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(\n (\n {\n users,\n max = 4,\n size = 'md',\n showTooltip = true,\n borderColor = 'surface-panel',\n onOverflowClick,\n renderAvatar,\n expandDirection = 'right',\n expandAmount = 'default',\n label,\n className,\n onMouseEnter,\n onMouseLeave,\n onFocus,\n onBlur,\n ...props\n },\n ref,\n ) => {\n const reduced = useReducedMotion()\n const [active, setActive] = React.useState(false)\n\n // Empty group is not a focusable \"0 members\" target — render nothing.\n if (users.length === 0) return null\n\n const cap = Math.max(1, max)\n const displayed = users.slice(0, cap)\n const overflow = users.length - cap\n const overlapClass = overlapMap[size]\n const borderClass = borderColor === 'surface-base' ? 'border-surface-base' : 'border-surface-panel'\n const ringOffsetClass = borderColor === 'surface-base'\n ? 'ring-offset-1 ring-offset-surface-base'\n : 'ring-offset-1 ring-offset-surface-panel'\n\n const spreadPx = spreadPxMap[size]\n const spreadMult = { compact: 0.5, default: 1, wide: 1.5 }[expandAmount]\n const totalVisible = displayed.length + (overflow > 0 ? 1 : 0)\n\n // Spread offset per index. Zero under reduced-motion (no positional animation).\n const xFor = (index: number): number => {\n if (!active || reduced) return 0\n const shift = spreadPx * spreadMult\n return expandDirection === 'left' ? -(totalVisible - 1 - index) * shift : index * shift\n }\n\n // framer governs the transition; reduced-motion collapses it to instant.\n const spreadTransition = reduced ? { duration: 0 } : springs.snappy\n // Gentle dim of non-focused peers; instant under reduced-motion.\n const spotlight =\n 'transition-opacity duration-fast-02 ease-productive-standard motion-reduce:transition-none hover:z-30 focus-visible:z-30 group-hover:[&:not(:hover)]:opacity-80'\n\n const onEnter = (e: React.MouseEvent<HTMLDivElement>) => { setActive(true); onMouseEnter?.(e) }\n const onLeave = (e: React.MouseEvent<HTMLDivElement>) => { setActive(false); onMouseLeave?.(e) }\n const onGroupFocus = (e: React.FocusEvent<HTMLDivElement>) => { setActive(true); onFocus?.(e) }\n const onGroupBlur = (e: React.FocusEvent<HTMLDivElement>) => {\n if (!e.currentTarget.contains(e.relatedTarget)) setActive(false)\n onBlur?.(e)\n }\n\n return (\n <TooltipProvider>\n <div\n ref={ref}\n role=\"group\"\n aria-label={label ?? `${users.length} team members`}\n className={cn('group flex items-center', className)}\n onMouseEnter={onEnter}\n onMouseLeave={onLeave}\n onFocus={onGroupFocus}\n onBlur={onGroupBlur}\n {...props}\n >\n {displayed.map((user, index) => {\n const key = `${user.name}-${index}`\n const extraRing = user.ring && user.ring !== 'none'\n ? cn(groupRingMap[user.ring], ringOffsetClass)\n : undefined\n\n const inner = renderAvatar ? (\n renderAvatar(user, index)\n ) : (\n <Avatar size={size} className={borderClass}>\n {user.image && <AvatarImage src={user.image} alt=\"\" />}\n <AvatarFallback colorSeed={user.name}>{getInitials(user.name)}</AvatarFallback>\n {user.indicator && (\n <span\n aria-hidden=\"true\"\n className={cn(\n 'absolute top-0 right-0 rounded-pill ring-1 ring-surface-panel',\n indicatorDotMap[size],\n user.indicator === 'lead' ? 'bg-accent-9'\n : user.indicator === 'admin' ? 'bg-warning-9' : '',\n )}\n >\n {typeof user.indicator !== 'string' && user.indicator}\n </span>\n )}\n </Avatar>\n )\n\n // Each avatar is a focusable, labelled trigger so keyboard/AT users reach\n // the member name (via the tooltip) and get a visible focus ring.\n const node = (\n <motion.button\n type=\"button\"\n aria-label={user.name}\n animate={{ x: xFor(index) }}\n transition={spreadTransition}\n style={{ zIndex: displayed.length - index }}\n className={cn(\n 'relative shrink-0 rounded-pill focus-ring',\n index > 0 && overlapClass,\n spotlight,\n extraRing,\n )}\n >\n {inner}\n </motion.button>\n )\n\n if (!showTooltip) return <React.Fragment key={key}>{node}</React.Fragment>\n\n return (\n <Tooltip key={key}>\n <TooltipTrigger asChild>{node}</TooltipTrigger>\n <TooltipContent sideOffset={6}>\n <p className=\"text-body-sm\">{user.name}</p>\n </TooltipContent>\n </Tooltip>\n )\n })}\n\n {overflow > 0 && (\n <Tooltip>\n <TooltipTrigger asChild>\n <motion.button\n type=\"button\"\n onClick={onOverflowClick}\n aria-label={`${overflow} more ${overflow === 1 ? 'member' : 'members'}`}\n animate={{ x: xFor(displayed.length) }}\n transition={spreadTransition}\n style={{ zIndex: 0 }}\n className={cn(\n 'relative shrink-0 rounded-pill focus-ring',\n overlapClass,\n spotlight,\n !onOverflowClick && 'cursor-default',\n )}\n >\n <Avatar size={size} className={borderClass}>\n <AvatarFallback className=\"bg-accent-2 font-semibold text-accent-11\">\n +{overflow}\n </AvatarFallback>\n </Avatar>\n </motion.button>\n </TooltipTrigger>\n <TooltipContent sideOffset={6}>\n <div className=\"flex flex-col gap-ds-01\">\n {users.slice(cap).map((user, i) => (\n <p key={i} className=\"text-body-sm\">{user.name}</p>\n ))}\n </div>\n </TooltipContent>\n </Tooltip>\n )}\n </div>\n </TooltipProvider>\n )\n },\n)\nAvatarGroup.displayName = 'AvatarGroup'\n\nexport { AvatarGroup }\n"],"mappings":"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"}
@@ -1,38 +1,12 @@
1
- import { Button } from '../ui/button.js';
2
- import { IconInput } from '../ui/lib/icon-input.js';
3
- import * as React from 'react';
4
- type ButtonColor = React.ComponentProps<typeof Button>['color'];
5
- export interface BulkActionBarAction {
6
- label: string;
7
- icon?: IconInput;
8
- onClick: () => void;
9
- /** Any Button color. @default 'accent' (or 'error' for destructive). */
10
- color?: ButtonColor;
11
- disabled?: boolean;
12
- /** Show a pending spinner on the action (e.g. slow bulk op in flight). */
13
- loading?: boolean;
14
- /** Show inline confirmation before executing the action. */
15
- requiresConfirmation?: boolean;
16
- /** Custom confirmation message. @default 'Are you sure?' */
17
- confirmMessage?: string;
18
- }
19
- export interface BulkActionBarProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children' | 'color' | 'onDrag' | 'onDragStart' | 'onDragEnd' | 'onAnimationStart' | 'onAnimationEnd' | 'onAnimationIteration' | 'style'> {
20
- show: boolean;
21
- count: number;
22
- onClearSelection: () => void;
23
- actions: BulkActionBarAction[];
24
- /** Total items available for selection (enables "Select all"). */
25
- totalCount?: number;
26
- /** Called when the user clicks "Select all". */
27
- onSelectAll?: () => void;
28
- }
29
1
  /**
30
- * BulkActionBar floating selection toolbar. Follows the ARIA toolbar model:
31
- * a single tab stop with roving focus (Arrow keys / Home / End) across ALL
32
- * controls (Select-all, actions, Clear), focus landing on the real buttons so
33
- * Enter/Space activate them. Arrow direction mirrors under `dir="rtl"`. Escape
34
- * clears the selection (or cancels a pending confirmation).
2
+ * Re-export. The implementation moved to `ui/bulk-action-bar` so that
3
+ * `DataTable` can use it `ui/` is forbidden from importing `composed/`
4
+ * (see the module-boundary rules in eslint.config.js), and DataTable lives in
5
+ * `ui/`. Rather than keep two bulk bars, the shared one moved down a layer.
6
+ *
7
+ * This file exists so `@devalok/shilp-sutra/composed/bulk-action-bar` keeps
8
+ * resolving. It is the documented path and consumers import it; moving the
9
+ * file should not move the import.
35
10
  */
36
- declare const BulkActionBar: React.ForwardRefExoticComponent<BulkActionBarProps & React.RefAttributes<HTMLDivElement>>;
37
- export { BulkActionBar };
11
+ export { BulkActionBar, type BulkActionBarAction, type BulkActionBarProps, type BulkActionsPlacement, } from '../ui/bulk-action-bar.js';
38
12
  //# sourceMappingURL=bulk-action-bar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bulk-action-bar.d.ts","sourceRoot":"","sources":["../../src/composed/bulk-action-bar.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAErC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAIrD,KAAK,WAAW,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,CAAC,OAAO,CAAC,CAAA;AAE/D,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,wEAAwE;IACxE,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,4DAA4D;IAC5D,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,4DAA4D;IAC5D,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,kBACf,SAAQ,IAAI,CACV,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAEpC,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,aAAa,GAAG,WAAW,GAAG,kBAAkB,GAAG,gBAAgB,GAAG,sBAAsB,GAAG,OAAO,CACzI;IACD,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,gBAAgB,EAAE,MAAM,IAAI,CAAA;IAC5B,OAAO,EAAE,mBAAmB,EAAE,CAAA;IAC9B,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;CACzB;AAED;;;;;;GAMG;AACH,QAAA,MAAM,aAAa,2FAwJlB,CAAA;AAGD,OAAO,EAAE,aAAa,EAAE,CAAA"}
1
+ {"version":3,"file":"bulk-action-bar.d.ts","sourceRoot":"","sources":["../../src/composed/bulk-action-bar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,aAAa,EACb,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,GAC1B,MAAM,uBAAuB,CAAA"}
@@ -1,140 +1,3 @@
1
1
  "use client";
2
- import { springs as e } from "../ui/lib/motion.js";
3
- import { cn as t } from "../ui/lib/utils.js";
4
- import { Icon as n } from "../ui/icon.js";
5
- import { BadgeCompound as r } from "../_chunks/badge-group.js";
6
- import { Button as i } from "../ui/button.js";
7
- import * as a from "react";
8
- import { jsx as o, jsxs as s } from "react/jsx-runtime";
9
- import { createPortal as c } from "react-dom";
10
- import { IconX as l } from "@tabler/icons-react";
11
- import { AnimatePresence as u, motion as d, useReducedMotion as f } from "framer-motion";
12
- //#region src/composed/bulk-action-bar.tsx
13
- var p = a.forwardRef(function({ show: p, count: m, onClearSelection: h, actions: g, totalCount: _, onSelectAll: v, className: y, ...b }, x) {
14
- let S = f(), [C, w] = a.useState(!1), [T, E] = a.useState(0), [D, O] = a.useState(null), k = a.useRef([]), A = a.useRef(null), j = _ != null && _ > m && !!v, M = +!!j, N = M + g.length + 1;
15
- a.useEffect(() => {
16
- w(!0);
17
- }, []), a.useEffect(() => {
18
- E(0), O(null);
19
- }, [g.length]), a.useEffect(() => {
20
- D != null && A.current?.focus();
21
- }, [D]);
22
- let P = (e) => {
23
- let t = (e + N) % N;
24
- E(t), k.current[t]?.focus();
25
- };
26
- function F(e) {
27
- if (e.key === "Escape") {
28
- e.preventDefault(), D == null ? h() : (O(null), P(T));
29
- return;
30
- }
31
- if (D != null) return;
32
- let t = e.currentTarget.dir === "rtl" || document.dir === "rtl", n = t ? "ArrowLeft" : "ArrowRight", r = t ? "ArrowRight" : "ArrowLeft";
33
- e.key === n ? (e.preventDefault(), P(T + 1)) : e.key === r ? (e.preventDefault(), P(T - 1)) : e.key === "Home" ? (e.preventDefault(), P(0)) : e.key === "End" && (e.preventDefault(), P(N - 1));
34
- }
35
- if (!C) return null;
36
- let I = S ? { duration: 0 } : e.smooth;
37
- return c(/* @__PURE__ */ o(u, { children: p && /* @__PURE__ */ s(d.div, {
38
- ref: x,
39
- initial: S ? { opacity: 0 } : {
40
- y: 100,
41
- opacity: 0
42
- },
43
- animate: S ? { opacity: 1 } : {
44
- y: 0,
45
- opacity: 1
46
- },
47
- exit: S ? { opacity: 0 } : {
48
- y: 100,
49
- opacity: 0
50
- },
51
- transition: I,
52
- className: t("fixed bottom-ds-06 start-1/2 z-sticky -translate-x-1/2", "flex items-center gap-ds-04 rounded-surface bg-surface-overlay px-ds-05 py-ds-03 shadow-floating", y),
53
- role: "toolbar",
54
- "aria-label": `${m} items selected`,
55
- onKeyDown: F,
56
- ...b,
57
- children: [
58
- /* @__PURE__ */ s(r, {
59
- variant: "solid",
60
- size: "sm",
61
- children: [m, " selected"]
62
- }),
63
- j && /* @__PURE__ */ s(i, {
64
- ref: (e) => {
65
- k.current[0] = e;
66
- },
67
- variant: "link",
68
- size: "xs",
69
- tabIndex: T === 0 ? 0 : -1,
70
- onClick: v,
71
- children: ["Select all ", _]
72
- }),
73
- /* @__PURE__ */ o("div", {
74
- className: "flex items-center gap-ds-02",
75
- children: g.map((e, t) => {
76
- let n = M + t;
77
- return D === t ? /* @__PURE__ */ s("div", {
78
- role: "group",
79
- "aria-live": "assertive",
80
- className: "flex items-center gap-ds-02",
81
- children: [
82
- /* @__PURE__ */ o("span", {
83
- className: "whitespace-nowrap text-body-sm text-surface-fg-muted",
84
- children: e.confirmMessage ?? "Are you sure?"
85
- }),
86
- /* @__PURE__ */ o(i, {
87
- ref: A,
88
- variant: "solid",
89
- size: "sm",
90
- color: e.color === "error" ? "error" : e.color ?? "accent",
91
- onClick: () => {
92
- O(null), e.onClick();
93
- },
94
- children: "Confirm"
95
- }),
96
- /* @__PURE__ */ o(i, {
97
- variant: "ghost",
98
- size: "sm",
99
- onClick: () => {
100
- O(null), P(n);
101
- },
102
- children: "Cancel"
103
- })
104
- ]
105
- }, e.label) : /* @__PURE__ */ o(i, {
106
- ref: (e) => {
107
- k.current[n] = e;
108
- },
109
- variant: "ghost",
110
- size: "sm",
111
- color: e.color ?? "accent",
112
- disabled: e.disabled,
113
- loading: e.loading,
114
- startIcon: e.icon ?? void 0,
115
- tabIndex: T === n ? 0 : -1,
116
- onClick: e.requiresConfirmation ? () => O(t) : e.onClick,
117
- children: e.label
118
- }, e.label);
119
- })
120
- }),
121
- /* @__PURE__ */ o(i, {
122
- ref: (e) => {
123
- k.current[N - 1] = e;
124
- },
125
- variant: "ghost",
126
- size: "icon-sm",
127
- tabIndex: T === N - 1 ? 0 : -1,
128
- onClick: h,
129
- "aria-label": "Clear selection",
130
- children: /* @__PURE__ */ o(n, {
131
- icon: l,
132
- size: "sm"
133
- })
134
- })
135
- ]
136
- }) }), document.body);
137
- });
138
- p.displayName = "BulkActionBar";
139
- //#endregion
140
- export { p as BulkActionBar };
2
+ import { BulkActionBar as e } from "../ui/bulk-action-bar.js";
3
+ export { e as BulkActionBar };
@@ -1,8 +1,9 @@
1
1
  "use client";
2
- import { getInitials as e } from "./lib/string-utils.js";
3
- import { ActivityFeed as t, groupItemsByTime as n } from "./activity-feed.js";
4
- import { AvatarGroup as r } from "./avatar-group.js";
5
- import { BulkActionBar as i } from "./bulk-action-bar.js";
2
+ import { BulkActionBar as e } from "../ui/bulk-action-bar.js";
3
+ import { getInitials as t } from "./lib/string-utils.js";
4
+ import { ActivityFeed as n, groupItemsByTime as r } from "./activity-feed.js";
5
+ import { AvatarGroup as i } from "./avatar-group.js";
6
+ import "./bulk-action-bar.js";
6
7
  import { CommandPalette as a } from "./command-palette.js";
7
8
  import { ConfirmDialog as o } from "./confirm-dialog.js";
8
9
  import { ContentCard as s, contentCardVariants as c } from "./content-card.js";
@@ -24,4 +25,4 @@ import { PriorityIndicator as j } from "./priority-indicator.js";
24
25
  import { ResponsiveModal as M, ResponsiveModalBackground as N, ResponsiveModalBody as P, ResponsiveModalClose as F, ResponsiveModalContent as I, ResponsiveModalDescription as L, ResponsiveModalFooter as R, ResponsiveModalHeader as z, ResponsiveModalOverlay as B, ResponsiveModalPortal as V, ResponsiveModalTitle as H, ResponsiveModalTrigger as U } from "./responsive-modal.js";
25
26
  import { ScheduleView as W } from "./schedule-view.js";
26
27
  import { StatusBadge as G } from "./status-badge.js";
27
- export { t as ActivityFeed, r as AvatarGroup, x as BoardSkeleton, i as BulkActionBar, S as CardSkeleton, a as CommandPalette, o as ConfirmDialog, s as ContentCard, O as DashboardSkeleton, u as DeadlineIndicator, d as EmptyState, f as ErrorBoundary, p as ErrorDisplay, h as FilterBar, g as FilterMultiSelect, _ as FilterSelect, v as FormSection, y as GlobalLoading, b as InlineEdit, C as ListSkeleton, T as MasterDetail, E as MemberPicker, m as MultiSelectPopover, D as PageHeader, j as PriorityIndicator, k as ProjectListSkeleton, M as ResponsiveModal, N as ResponsiveModalBackground, P as ResponsiveModalBody, F as ResponsiveModalClose, I as ResponsiveModalContent, L as ResponsiveModalDescription, R as ResponsiveModalFooter, z as ResponsiveModalHeader, B as ResponsiveModalOverlay, V as ResponsiveModalPortal, H as ResponsiveModalTitle, U as ResponsiveModalTrigger, W as ScheduleView, l as SimpleTooltip, G as StatusBadge, w as TableSkeleton, A as TaskDetailSkeleton, c as contentCardVariants, e as getInitials, n as groupItemsByTime };
28
+ export { n as ActivityFeed, i as AvatarGroup, x as BoardSkeleton, e as BulkActionBar, S as CardSkeleton, a as CommandPalette, o as ConfirmDialog, s as ContentCard, O as DashboardSkeleton, u as DeadlineIndicator, d as EmptyState, f as ErrorBoundary, p as ErrorDisplay, h as FilterBar, g as FilterMultiSelect, _ as FilterSelect, v as FormSection, y as GlobalLoading, b as InlineEdit, C as ListSkeleton, T as MasterDetail, E as MemberPicker, m as MultiSelectPopover, D as PageHeader, j as PriorityIndicator, k as ProjectListSkeleton, M as ResponsiveModal, N as ResponsiveModalBackground, P as ResponsiveModalBody, F as ResponsiveModalClose, I as ResponsiveModalContent, L as ResponsiveModalDescription, R as ResponsiveModalFooter, z as ResponsiveModalHeader, B as ResponsiveModalOverlay, V as ResponsiveModalPortal, H as ResponsiveModalTitle, U as ResponsiveModalTrigger, W as ScheduleView, l as SimpleTooltip, G as StatusBadge, w as TableSkeleton, A as TaskDetailSkeleton, c as contentCardVariants, t as getInitials, r as groupItemsByTime };
@@ -1 +1 @@
1
- {"version":3,"file":"master-detail.d.ts","sourceRoot":"","sources":["../../src/composed/master-detail.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAO9B;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAEf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC;IAC9D,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,sCAAsC;IACtC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mEAAmE;IACnE,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IAC/B,mEAAmE;IACnE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,GAAG,MAAM,KAAK,IAAI,CAAA;CAChD;AAED,UAAU,qBAAsB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;CAAG;AAC/E,UAAU,uBAAwB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;CAAG;AACjF,UAAU,yBAA0B,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC;IACvF,8EAA8E;IAC9E,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AA6CD,iBAAS,gBAAgB,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,qBAAqB,4BA2D1G;AAED,iBAAS,kBAAkB,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,uBAAuB,4BA4BrF;AAgDD,iBAAS,gBAAgB,CAAC,EACxB,QAAQ,EACR,eAAsB,EACtB,QAAQ,EACR,KAAe,EACf,MAAM,EACN,WAAqB,EACrB,UAAiB,EACjB,UAAU,EACV,UAAU,EACV,QAAQ,EACR,SAAS,EACT,KAAK,EACL,GAAG,KAAK,EACT,EAAE,iBAAiB,qBAmCnB;AAED,QAAA,MAAM,YAAY;;;;CAIhB,CAAA;AAEF,OAAO,EAAE,YAAY,EAAE,CAAA;AACvB,YAAY,EAAE,yBAAyB,EAAE,CAAA"}
1
+ {"version":3,"file":"master-detail.d.ts","sourceRoot":"","sources":["../../src/composed/master-detail.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAO9B;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAEf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC;IAC9D,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,sCAAsC;IACtC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mEAAmE;IACnE,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IAC/B,mEAAmE;IACnE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,GAAG,MAAM,KAAK,IAAI,CAAA;CAChD;AAED,UAAU,qBAAsB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;CAAG;AAC/E,UAAU,uBAAwB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;CAAG;AACjF,UAAU,yBAA0B,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC;IACvF,8EAA8E;IAC9E,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AA6CD,iBAAS,gBAAgB,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,qBAAqB,4BA2D1G;AAED,iBAAS,kBAAkB,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,uBAAuB,4BA4BrF;AAoDD,iBAAS,gBAAgB,CAAC,EACxB,QAAQ,EACR,eAAsB,EACtB,QAAQ,EACR,KAAe,EACf,MAAM,EACN,WAAqB,EACrB,UAAiB,EACjB,UAAU,EACV,UAAU,EACV,QAAQ,EACR,SAAS,EACT,KAAK,EACL,GAAG,KAAK,EACT,EAAE,iBAAiB,qBAmCnB;AAED,QAAA,MAAM,YAAY;;;;CAIhB,CAAA;AAEF,OAAO,EAAE,YAAY,EAAE,CAAA;AACvB,YAAY,EAAE,yBAAyB,EAAE,CAAA"}
@@ -134,7 +134,7 @@ var _ = i.forwardRef(function({ active: e, value: n, children: r, className: o,
134
134
  "data-active": m || void 0,
135
135
  onKeyDown: h,
136
136
  onClick: g,
137
- className: t("flex w-full items-center px-ds-04 py-ds-03 text-left text-body-md font-sans text-surface-fg", "transition-colors duration-fast-01 ease-productive-standard", "hover:bg-surface-panel-hover", "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-7 focus-visible:ring-inset", m && "bg-accent-4 text-accent-11 font-medium", o),
137
+ className: t("flex w-full items-center px-ds-04 py-ds-03 text-left text-body-md font-sans text-surface-fg", "transition-colors duration-fast-01 ease-productive-standard", !m && "hover:bg-surface-panel-hover", "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-7 focus-visible:ring-inset", m && "bg-accent-4 text-accent-11 font-medium hover:bg-accent-5", o),
138
138
  ...l,
139
139
  children: r
140
140
  });
@@ -1 +1 @@
1
- {"version":3,"file":"master-detail.js","names":[],"sources":["../../src/composed/master-detail.tsx"],"sourcesContent":["'use client'\n\nimport { IconArrowLeft } from '@tabler/icons-react'\nimport { AnimatePresence, motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { springs } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\n\n/**\n * A responsive master-detail layout. Desktop: fixed-width list panel + detail\n * panel. Below the breakpoint: stacked view with a back button. The list is an\n * ARIA `listbox` with roving keyboard nav (Arrow/Home/End + Enter/Space).\n *\n * Selection can be **controlled** (`selected`) or **owned** by the component\n * (`defaultSelected` + `onSelect`, deriving each row's active state from its\n * `value` — no hand-wiring `active` + `onClick` per row).\n */\nexport interface MasterDetailProps\n // Replaces the rarely-used native text-selection `onSelect` with our row-select callback.\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'> {\n /** Controlled selected item id. `null` = no selection. Omit for uncontrolled. */\n selected?: string | null\n /** Uncontrolled initial selection. */\n defaultSelected?: string | null\n /** Called with a row's `value` when it's chosen (click or Enter/Space). */\n onSelect?: (value: string) => void\n /** Accessible name for the list (the `listbox`). @default 'Items' */\n label?: string\n /** Called when mobile back button is pressed. */\n onBack?: () => void\n /** Master panel width on desktop. @default '280px' */\n masterWidth?: string\n /** Breakpoint below which stacked mode activates. @default 'md' */\n breakpoint?: 'sm' | 'md' | 'lg'\n /** Content to show in the detail pane when nothing is selected. */\n emptyState?: React.ReactNode\n /** Called when the user presses ArrowUp/ArrowDown while the list has focus. */\n onNavigate?: (direction: 'up' | 'down') => void\n}\n\ninterface MasterDetailListProps extends React.HTMLAttributes<HTMLDivElement> {}\ninterface MasterDetailDetailProps extends React.HTMLAttributes<HTMLDivElement> {}\ninterface MasterDetailListItemProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Explicit active state. Omit to derive from `value` === the selected id. */\n active?: boolean\n /** Row id — enables derived selection + auto-fires `onSelect` on activate. */\n value?: string\n}\n\nconst breakpoints: Record<string, string> = { sm: '640px', md: '768px', lg: '1024px' }\n\nfunction useMediaQuery(query: string): boolean {\n const [matches, setMatches] = React.useState(false)\n React.useEffect(() => {\n if (typeof window === 'undefined') return\n const mql = window.matchMedia(query)\n setMatches(mql.matches)\n const handler = (e: MediaQueryListEvent) => setMatches(e.matches)\n mql.addEventListener('change', handler)\n return () => mql.removeEventListener('change', handler)\n }, [query])\n return matches\n}\n\ninterface MasterDetailContextValue {\n selected: string | null\n onSelect?: (value: string) => void\n label: string\n isMobile: boolean\n onBack?: () => void\n emptyState?: React.ReactNode\n onNavigate?: (direction: 'up' | 'down') => void\n activeIndex: number\n setActiveIndex: (index: number) => void\n registerItemRef: (index: number, el: HTMLButtonElement | null) => void\n}\n\nconst MasterDetailContext = React.createContext<MasterDetailContextValue>({\n selected: null,\n label: 'Items',\n isMobile: false,\n activeIndex: 0,\n setActiveIndex: () => {},\n registerItemRef: () => {},\n})\n\nfunction itemIsActive(child: React.ReactNode, selected: string | null): boolean {\n if (!React.isValidElement(child)) return false\n const p = child.props as { active?: boolean; value?: string }\n return p.active ?? (p.value != null && p.value === selected)\n}\n\nfunction MasterDetailList({ children, className, 'aria-label': ariaLabel, ...props }: MasterDetailListProps) {\n const { selected, label, isMobile, onNavigate, activeIndex, setActiveIndex, registerItemRef } =\n React.useContext(MasterDetailContext)\n const itemRefsLocal = React.useRef<(HTMLButtonElement | null)[]>([])\n const childCount = React.Children.count(children)\n\n // Roving focus starts on the active/selected row (derived from `active` or `value`).\n const activeChildIndex = React.useMemo(() => {\n let idx = -1\n React.Children.forEach(children, (child, i) => {\n if (itemIsActive(child, selected)) idx = i\n })\n return idx\n }, [children, selected])\n React.useEffect(() => {\n if (activeChildIndex >= 0) setActiveIndex(activeChildIndex)\n }, [activeChildIndex, setActiveIndex])\n\n if (isMobile && selected) return null\n\n function handleKeyDown(e: React.KeyboardEvent) {\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n const next = Math.min(activeIndex + 1, childCount - 1)\n setActiveIndex(next); itemRefsLocal.current[next]?.focus(); onNavigate?.('down')\n } else if (e.key === 'ArrowUp') {\n e.preventDefault()\n const prev = Math.max(activeIndex - 1, 0)\n setActiveIndex(prev); itemRefsLocal.current[prev]?.focus(); onNavigate?.('up')\n } else if (e.key === 'Home') {\n e.preventDefault(); setActiveIndex(0); itemRefsLocal.current[0]?.focus()\n } else if (e.key === 'End') {\n e.preventDefault()\n const last = childCount - 1\n setActiveIndex(last); itemRefsLocal.current[last]?.focus()\n }\n }\n\n return (\n <div\n role=\"listbox\"\n aria-label={ariaLabel ?? label}\n tabIndex={-1}\n className={cn('overflow-y-auto focus-visible:outline-hidden', !isMobile && 'border-e border-surface-border', className)}\n onKeyDown={handleKeyDown}\n {...props}\n >\n {React.Children.map(children, (child, i) => {\n if (!React.isValidElement(child)) return child\n return React.cloneElement(child as React.ReactElement<any>, {\n ref: (el: HTMLButtonElement | null) => {\n itemRefsLocal.current[i] = el\n registerItemRef(i, el)\n },\n tabIndex: i === activeIndex ? 0 : -1,\n })\n })}\n </div>\n )\n}\n\nfunction MasterDetailDetail({ children, className, ...props }: MasterDetailDetailProps) {\n const { selected, isMobile, onBack, emptyState } = React.useContext(MasterDetailContext)\n const reduced = useReducedMotion()\n if (isMobile && !selected) return null\n\n return (\n // aria-live so AT users are told the detail changed when the selection swaps.\n <div role=\"region\" aria-label=\"Detail\" aria-live=\"polite\" className={cn('flex-1 overflow-y-auto', className)} {...props}>\n {isMobile && onBack && (\n <div className=\"border-b border-surface-border px-ds-04 py-ds-03\">\n <Button variant=\"ghost\" size=\"xs\" onClick={onBack} startIcon={<Icon icon={IconArrowLeft} className=\"rtl:-scale-x-100\" />}>\n Back\n </Button>\n </div>\n )}\n <AnimatePresence mode=\"wait\">\n <motion.div\n key={selected ?? 'empty'}\n initial={isMobile && !reduced ? { x: 20, opacity: 0 } : false}\n animate={{ x: 0, opacity: 1 }}\n exit={isMobile && !reduced ? { x: -20, opacity: 0 } : undefined}\n transition={reduced ? { duration: 0 } : springs.snappy}\n >\n {selected ? children : (emptyState ?? children)}\n </motion.div>\n </AnimatePresence>\n </div>\n )\n}\n\nconst MasterDetailListItem = React.forwardRef<HTMLButtonElement, MasterDetailListItemProps>(\n function MasterDetailListItem({ active, value, children, className, onKeyDown, onClick, ...props }, ref) {\n const { selected, onSelect } = React.useContext(MasterDetailContext)\n const isActive = active ?? (value != null && value === selected)\n\n function handleKeyDown(e: React.KeyboardEvent<HTMLButtonElement>) {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n ;(e.currentTarget as HTMLButtonElement).click()\n }\n onKeyDown?.(e)\n }\n function handleClick(e: React.MouseEvent<HTMLButtonElement>) {\n if (value != null) onSelect?.(value)\n onClick?.(e)\n }\n\n return (\n <button\n ref={ref}\n type=\"button\"\n role=\"option\"\n aria-selected={isActive}\n data-active={isActive || undefined}\n onKeyDown={handleKeyDown}\n onClick={handleClick}\n className={cn(\n 'flex w-full items-center px-ds-04 py-ds-03 text-left text-body-md font-sans text-surface-fg',\n 'transition-colors duration-fast-01 ease-productive-standard',\n 'hover:bg-surface-panel-hover',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-7 focus-visible:ring-inset',\n // accent-4, not accent-2: in dark the pale steps sit BELOW the panel\n // while the grey hover sits above it, so a merely hovered row read\n // stronger than the selected one (1.03:1 vs 1.30:1) and in the\n // opposite direction.\n isActive && 'bg-accent-4 text-accent-11 font-medium',\n className,\n )}\n {...props}\n >\n {children}\n </button>\n )\n },\n)\n\nfunction MasterDetailRoot({\n selected,\n defaultSelected = null,\n onSelect,\n label = 'Items',\n onBack,\n masterWidth = '280px',\n breakpoint = 'md',\n emptyState,\n onNavigate,\n children,\n className,\n style,\n ...props\n}: MasterDetailProps) {\n const isMobile = useMediaQuery(`(max-width: ${breakpoints[breakpoint]})`)\n const [activeIndex, setActiveIndex] = React.useState(0)\n const itemRefs = React.useRef<(HTMLButtonElement | null)[]>([])\n\n // Controlled when `selected` is provided; otherwise own the selection.\n const isControlled = selected !== undefined\n const [internalSelected, setInternalSelected] = React.useState<string | null>(defaultSelected)\n const resolvedSelected = isControlled ? selected ?? null : internalSelected\n\n const handleSelect = React.useCallback(\n (value: string) => {\n if (!isControlled) setInternalSelected(value)\n onSelect?.(value)\n },\n [isControlled, onSelect],\n )\n\n const registerItemRef = React.useCallback((index: number, el: HTMLButtonElement | null) => {\n itemRefs.current[index] = el\n }, [])\n\n return (\n <MasterDetailContext.Provider\n value={{ selected: resolvedSelected, onSelect: handleSelect, label, isMobile, onBack, emptyState, onNavigate, activeIndex, setActiveIndex, registerItemRef }}\n >\n <div\n className={cn('flex h-full', !isMobile && 'grid', className)}\n style={{ ...style, ...(isMobile ? {} : { gridTemplateColumns: `${masterWidth} 1fr` }) }}\n {...props}\n >\n {children}\n </div>\n </MasterDetailContext.Provider>\n )\n}\n\nconst MasterDetail = Object.assign(MasterDetailRoot, {\n List: MasterDetailList,\n Detail: MasterDetailDetail,\n ListItem: MasterDetailListItem,\n})\n\nexport { MasterDetail }\nexport type { MasterDetailListItemProps }\n"],"mappings":"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"}
1
+ {"version":3,"file":"master-detail.js","names":[],"sources":["../../src/composed/master-detail.tsx"],"sourcesContent":["'use client'\n\nimport { IconArrowLeft } from '@tabler/icons-react'\nimport { AnimatePresence, motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { springs } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\n\n/**\n * A responsive master-detail layout. Desktop: fixed-width list panel + detail\n * panel. Below the breakpoint: stacked view with a back button. The list is an\n * ARIA `listbox` with roving keyboard nav (Arrow/Home/End + Enter/Space).\n *\n * Selection can be **controlled** (`selected`) or **owned** by the component\n * (`defaultSelected` + `onSelect`, deriving each row's active state from its\n * `value` — no hand-wiring `active` + `onClick` per row).\n */\nexport interface MasterDetailProps\n // Replaces the rarely-used native text-selection `onSelect` with our row-select callback.\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'> {\n /** Controlled selected item id. `null` = no selection. Omit for uncontrolled. */\n selected?: string | null\n /** Uncontrolled initial selection. */\n defaultSelected?: string | null\n /** Called with a row's `value` when it's chosen (click or Enter/Space). */\n onSelect?: (value: string) => void\n /** Accessible name for the list (the `listbox`). @default 'Items' */\n label?: string\n /** Called when mobile back button is pressed. */\n onBack?: () => void\n /** Master panel width on desktop. @default '280px' */\n masterWidth?: string\n /** Breakpoint below which stacked mode activates. @default 'md' */\n breakpoint?: 'sm' | 'md' | 'lg'\n /** Content to show in the detail pane when nothing is selected. */\n emptyState?: React.ReactNode\n /** Called when the user presses ArrowUp/ArrowDown while the list has focus. */\n onNavigate?: (direction: 'up' | 'down') => void\n}\n\ninterface MasterDetailListProps extends React.HTMLAttributes<HTMLDivElement> {}\ninterface MasterDetailDetailProps extends React.HTMLAttributes<HTMLDivElement> {}\ninterface MasterDetailListItemProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Explicit active state. Omit to derive from `value` === the selected id. */\n active?: boolean\n /** Row id — enables derived selection + auto-fires `onSelect` on activate. */\n value?: string\n}\n\nconst breakpoints: Record<string, string> = { sm: '640px', md: '768px', lg: '1024px' }\n\nfunction useMediaQuery(query: string): boolean {\n const [matches, setMatches] = React.useState(false)\n React.useEffect(() => {\n if (typeof window === 'undefined') return\n const mql = window.matchMedia(query)\n setMatches(mql.matches)\n const handler = (e: MediaQueryListEvent) => setMatches(e.matches)\n mql.addEventListener('change', handler)\n return () => mql.removeEventListener('change', handler)\n }, [query])\n return matches\n}\n\ninterface MasterDetailContextValue {\n selected: string | null\n onSelect?: (value: string) => void\n label: string\n isMobile: boolean\n onBack?: () => void\n emptyState?: React.ReactNode\n onNavigate?: (direction: 'up' | 'down') => void\n activeIndex: number\n setActiveIndex: (index: number) => void\n registerItemRef: (index: number, el: HTMLButtonElement | null) => void\n}\n\nconst MasterDetailContext = React.createContext<MasterDetailContextValue>({\n selected: null,\n label: 'Items',\n isMobile: false,\n activeIndex: 0,\n setActiveIndex: () => {},\n registerItemRef: () => {},\n})\n\nfunction itemIsActive(child: React.ReactNode, selected: string | null): boolean {\n if (!React.isValidElement(child)) return false\n const p = child.props as { active?: boolean; value?: string }\n return p.active ?? (p.value != null && p.value === selected)\n}\n\nfunction MasterDetailList({ children, className, 'aria-label': ariaLabel, ...props }: MasterDetailListProps) {\n const { selected, label, isMobile, onNavigate, activeIndex, setActiveIndex, registerItemRef } =\n React.useContext(MasterDetailContext)\n const itemRefsLocal = React.useRef<(HTMLButtonElement | null)[]>([])\n const childCount = React.Children.count(children)\n\n // Roving focus starts on the active/selected row (derived from `active` or `value`).\n const activeChildIndex = React.useMemo(() => {\n let idx = -1\n React.Children.forEach(children, (child, i) => {\n if (itemIsActive(child, selected)) idx = i\n })\n return idx\n }, [children, selected])\n React.useEffect(() => {\n if (activeChildIndex >= 0) setActiveIndex(activeChildIndex)\n }, [activeChildIndex, setActiveIndex])\n\n if (isMobile && selected) return null\n\n function handleKeyDown(e: React.KeyboardEvent) {\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n const next = Math.min(activeIndex + 1, childCount - 1)\n setActiveIndex(next); itemRefsLocal.current[next]?.focus(); onNavigate?.('down')\n } else if (e.key === 'ArrowUp') {\n e.preventDefault()\n const prev = Math.max(activeIndex - 1, 0)\n setActiveIndex(prev); itemRefsLocal.current[prev]?.focus(); onNavigate?.('up')\n } else if (e.key === 'Home') {\n e.preventDefault(); setActiveIndex(0); itemRefsLocal.current[0]?.focus()\n } else if (e.key === 'End') {\n e.preventDefault()\n const last = childCount - 1\n setActiveIndex(last); itemRefsLocal.current[last]?.focus()\n }\n }\n\n return (\n <div\n role=\"listbox\"\n aria-label={ariaLabel ?? label}\n tabIndex={-1}\n className={cn('overflow-y-auto focus-visible:outline-hidden', !isMobile && 'border-e border-surface-border', className)}\n onKeyDown={handleKeyDown}\n {...props}\n >\n {React.Children.map(children, (child, i) => {\n if (!React.isValidElement(child)) return child\n return React.cloneElement(child as React.ReactElement<any>, {\n ref: (el: HTMLButtonElement | null) => {\n itemRefsLocal.current[i] = el\n registerItemRef(i, el)\n },\n tabIndex: i === activeIndex ? 0 : -1,\n })\n })}\n </div>\n )\n}\n\nfunction MasterDetailDetail({ children, className, ...props }: MasterDetailDetailProps) {\n const { selected, isMobile, onBack, emptyState } = React.useContext(MasterDetailContext)\n const reduced = useReducedMotion()\n if (isMobile && !selected) return null\n\n return (\n // aria-live so AT users are told the detail changed when the selection swaps.\n <div role=\"region\" aria-label=\"Detail\" aria-live=\"polite\" className={cn('flex-1 overflow-y-auto', className)} {...props}>\n {isMobile && onBack && (\n <div className=\"border-b border-surface-border px-ds-04 py-ds-03\">\n <Button variant=\"ghost\" size=\"xs\" onClick={onBack} startIcon={<Icon icon={IconArrowLeft} className=\"rtl:-scale-x-100\" />}>\n Back\n </Button>\n </div>\n )}\n <AnimatePresence mode=\"wait\">\n <motion.div\n key={selected ?? 'empty'}\n initial={isMobile && !reduced ? { x: 20, opacity: 0 } : false}\n animate={{ x: 0, opacity: 1 }}\n exit={isMobile && !reduced ? { x: -20, opacity: 0 } : undefined}\n transition={reduced ? { duration: 0 } : springs.snappy}\n >\n {selected ? children : (emptyState ?? children)}\n </motion.div>\n </AnimatePresence>\n </div>\n )\n}\n\nconst MasterDetailListItem = React.forwardRef<HTMLButtonElement, MasterDetailListItemProps>(\n function MasterDetailListItem({ active, value, children, className, onKeyDown, onClick, ...props }, ref) {\n const { selected, onSelect } = React.useContext(MasterDetailContext)\n const isActive = active ?? (value != null && value === selected)\n\n function handleKeyDown(e: React.KeyboardEvent<HTMLButtonElement>) {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n ;(e.currentTarget as HTMLButtonElement).click()\n }\n onKeyDown?.(e)\n }\n function handleClick(e: React.MouseEvent<HTMLButtonElement>) {\n if (value != null) onSelect?.(value)\n onClick?.(e)\n }\n\n return (\n <button\n ref={ref}\n type=\"button\"\n role=\"option\"\n aria-selected={isActive}\n data-active={isActive || undefined}\n onKeyDown={handleKeyDown}\n onClick={handleClick}\n className={cn(\n 'flex w-full items-center px-ds-04 py-ds-03 text-left text-body-md font-sans text-surface-fg',\n 'transition-colors duration-fast-01 ease-productive-standard',\n // Gated on !isActive. `hover:bg-*` is (0,2,0) and a plain\n // `bg-accent-4` is (0,1,0), so an ungated hover WINS over the active\n // tint and pointing at the selected row turned it grey. TreeItem and\n // TableRow already carry this guard; this was the third instance.\n !isActive && 'hover:bg-surface-panel-hover',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-7 focus-visible:ring-inset',\n // accent-4, not accent-2: in dark the pale steps sit BELOW the panel\n // while the grey hover sits above it, so a merely hovered row read\n // stronger than the selected one (1.03:1 vs 1.30:1) and in the\n // opposite direction.\n isActive && 'bg-accent-4 text-accent-11 font-medium hover:bg-accent-5',\n className,\n )}\n {...props}\n >\n {children}\n </button>\n )\n },\n)\n\nfunction MasterDetailRoot({\n selected,\n defaultSelected = null,\n onSelect,\n label = 'Items',\n onBack,\n masterWidth = '280px',\n breakpoint = 'md',\n emptyState,\n onNavigate,\n children,\n className,\n style,\n ...props\n}: MasterDetailProps) {\n const isMobile = useMediaQuery(`(max-width: ${breakpoints[breakpoint]})`)\n const [activeIndex, setActiveIndex] = React.useState(0)\n const itemRefs = React.useRef<(HTMLButtonElement | null)[]>([])\n\n // Controlled when `selected` is provided; otherwise own the selection.\n const isControlled = selected !== undefined\n const [internalSelected, setInternalSelected] = React.useState<string | null>(defaultSelected)\n const resolvedSelected = isControlled ? selected ?? null : internalSelected\n\n const handleSelect = React.useCallback(\n (value: string) => {\n if (!isControlled) setInternalSelected(value)\n onSelect?.(value)\n },\n [isControlled, onSelect],\n )\n\n const registerItemRef = React.useCallback((index: number, el: HTMLButtonElement | null) => {\n itemRefs.current[index] = el\n }, [])\n\n return (\n <MasterDetailContext.Provider\n value={{ selected: resolvedSelected, onSelect: handleSelect, label, isMobile, onBack, emptyState, onNavigate, activeIndex, setActiveIndex, registerItemRef }}\n >\n <div\n className={cn('flex h-full', !isMobile && 'grid', className)}\n style={{ ...style, ...(isMobile ? {} : { gridTemplateColumns: `${masterWidth} 1fr` }) }}\n {...props}\n >\n {children}\n </div>\n </MasterDetailContext.Provider>\n )\n}\n\nconst MasterDetail = Object.assign(MasterDetailRoot, {\n List: MasterDetailList,\n Detail: MasterDetailDetail,\n ListItem: MasterDetailListItem,\n})\n\nexport { MasterDetail }\nexport type { MasterDetailListItemProps }\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"multi-select-popover.d.ts","sourceRoot":"","sources":["../../src/composed/multi-select-popover.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAiB9B,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,eAAe,EAAE,CAAA;CACzB;AAED,MAAM,WAAW,uBAAwB,SAAQ,IAAI,CAAC,KAAK,CAAC,wBAAwB,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC;IACtG,8DAA8D;IAC9D,KAAK,CAAC,EAAE,eAAe,EAAE,CAAA;IACzB,yCAAyC;IACzC,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC3B,kCAAkC;IAClC,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,oCAAoC;IACpC,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;IACtC,2BAA2B;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,eAAe,EAAE,CAAC,CAAA;IACxD,mDAAmD;IACnD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,2BAA2B;IAC3B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAA;IAC1E,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,mCAAmC;IACnC,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,yCAAyC;IACzC,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAA;IAClC,oBAAoB;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,sBAAsB;IACtB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAMD,QAAA,MAAM,kBAAkB,gGAmPvB,CAAA;AAGD,OAAO,EAAE,kBAAkB,EAAE,CAAA"}
1
+ {"version":3,"file":"multi-select-popover.d.ts","sourceRoot":"","sources":["../../src/composed/multi-select-popover.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAiB9B,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,eAAe,EAAE,CAAA;CACzB;AAED,MAAM,WAAW,uBAAwB,SAAQ,IAAI,CAAC,KAAK,CAAC,wBAAwB,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC;IACtG,8DAA8D;IAC9D,KAAK,CAAC,EAAE,eAAe,EAAE,CAAA;IACzB,yCAAyC;IACzC,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC3B,kCAAkC;IAClC,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,oCAAoC;IACpC,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;IACtC,2BAA2B;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,eAAe,EAAE,CAAC,CAAA;IACxD,mDAAmD;IACnD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,2BAA2B;IAC3B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAA;IAC1E,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,mCAAmC;IACnC,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,yCAAyC;IACzC,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAA;IAClC,oBAAoB;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,sBAAsB;IACtB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAMD,QAAA,MAAM,kBAAkB,gGA4PvB,CAAA;AAGD,OAAO,EAAE,kBAAkB,EAAE,CAAA"}
@@ -101,7 +101,7 @@ var h = c.forwardRef(({ items: h, groups: g, value: _, onValueChange: v, searchP
101
101
  delay: o * .02
102
102
  },
103
103
  onClick: () => K(r.id),
104
- className: n("flex w-full items-center gap-ds-03 px-ds-04 py-ds-02b text-left transition-colors duration-fast-01 ease-productive-standard", "hover:bg-surface-panel-hover", "disabled:opacity-action-disabled disabled:cursor-not-allowed", a && "bg-accent-4 text-accent-11", s && "bg-surface-panel-hover"),
104
+ className: n("flex w-full items-center gap-ds-03 px-ds-04 py-ds-02b text-left transition-colors duration-fast-01 ease-productive-standard", "disabled:opacity-action-disabled disabled:cursor-not-allowed", !a && "hover:bg-surface-panel-hover", !a && s && "bg-surface-panel-hover", a && "bg-accent-4 text-accent-11 hover:bg-accent-5", a && s && "bg-accent-5"),
105
105
  children: [S ? /* @__PURE__ */ u("span", {
106
106
  className: "flex-1 min-w-0",
107
107
  children: S(r, a)
@@ -1 +1 @@
1
- {"version":3,"file":"multi-select-popover.js","names":[],"sources":["../../src/composed/multi-select-popover.tsx"],"sourcesContent":["'use client'\n\nimport { IconCheck, IconSearch } from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { MotionPreference } from '../motion/motion-preference'\nimport { Icon } from '../ui/icon'\nimport { springs } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from '../ui/popover'\nimport { Spinner } from '../ui/spinner'\n\n// ============================================================\n// Types\n// ============================================================\n\nexport interface MultiSelectItem {\n id: string\n label: string\n image?: string\n description?: string\n disabled?: boolean\n}\n\nexport interface MultiSelectGroup {\n label: string\n items: MultiSelectItem[]\n}\n\nexport interface MultiSelectPopoverProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'onSelect'> {\n /** Flat list of items (use `groups` for grouped rendering) */\n items?: MultiSelectItem[]\n /** Grouped items with section headers */\n groups?: MultiSelectGroup[]\n /** Currently selected item IDs */\n value: string[]\n /** Called when selection changes */\n onValueChange: (ids: string[]) => void\n /** @default 'Search...' */\n searchPlaceholder?: string\n /** Async search — replaces local filter with server results */\n onSearch?: (query: string) => Promise<MultiSelectItem[]>\n /** Debounce for async search in ms @default 300 */\n searchDebounce?: number\n /** Custom item renderer */\n renderItem?: (item: MultiSelectItem, selected: boolean) => React.ReactNode\n /** Message when no items match filter @default 'No results found' */\n emptyMessage?: string\n /** Maximum number of selections */\n maxSelections?: number\n /** Popover alignment @default 'start' */\n align?: 'start' | 'center' | 'end'\n /** Popover width */\n width?: string | number\n /** Trigger element */\n children: React.ReactNode\n}\n\n// ============================================================\n// MultiSelectPopover\n// ============================================================\n\nconst MultiSelectPopover = React.forwardRef<HTMLDivElement, MultiSelectPopoverProps>(\n (\n {\n items,\n groups,\n value,\n onValueChange,\n searchPlaceholder = 'Search...',\n onSearch,\n searchDebounce = 300,\n renderItem,\n emptyMessage = 'No results found',\n maxSelections,\n align = 'start',\n width,\n children,\n className,\n ...props\n },\n ref,\n ) => {\n const [open, setOpen] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [asyncItems, setAsyncItems] = React.useState<MultiSelectItem[] | null>(null)\n const [loading, setLoading] = React.useState(false)\n const [focusedIndex, setFocusedIndex] = React.useState(-1)\n const debounceRef = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n const listRef = React.useRef<HTMLDivElement>(null)\n\n // Reset search and focus when popover closes\n React.useEffect(() => {\n if (!open) {\n setSearch('')\n setAsyncItems(null)\n setFocusedIndex(-1)\n }\n }, [open])\n\n // Async search\n React.useEffect(() => {\n if (!onSearch || !open) return\n if (!search.trim()) {\n setAsyncItems(null)\n setLoading(false)\n return\n }\n setLoading(true)\n clearTimeout(debounceRef.current)\n debounceRef.current = setTimeout(() => {\n onSearch(search)\n .then((results) => setAsyncItems(results))\n .catch(() => setAsyncItems([]))\n .finally(() => setLoading(false))\n }, searchDebounce)\n return () => clearTimeout(debounceRef.current)\n }, [search, onSearch, searchDebounce, open])\n\n // Resolve visible items\n const allItems = React.useMemo(() => {\n if (asyncItems) return asyncItems\n if (groups) return groups.flatMap((g) => g.items)\n return items ?? []\n }, [items, groups, asyncItems])\n\n const filteredItems = React.useMemo(() => {\n if (onSearch) return allItems // async search handles its own filtering\n const q = search.toLowerCase()\n if (!q) return allItems\n return allItems.filter((item) => item.label.toLowerCase().includes(q))\n }, [allItems, search, onSearch])\n\n const filteredGroups = React.useMemo(() => {\n if (!groups || onSearch || asyncItems) return null\n const q = search.toLowerCase()\n return groups\n .map((g) => ({\n ...g,\n items: q ? g.items.filter((item) => item.label.toLowerCase().includes(q)) : g.items,\n }))\n .filter((g) => g.items.length > 0)\n }, [groups, search, onSearch, asyncItems])\n\n function toggle(id: string) {\n const isSelected = value.includes(id)\n if (isSelected) {\n onValueChange(value.filter((v) => v !== id))\n } else {\n if (maxSelections && value.length >= maxSelections) {\n // Replace: drop the first selected item to make room for the new one\n onValueChange([...value.slice(1), id])\n } else {\n onValueChange([...value, id])\n }\n }\n }\n\n // Reset focused index when search text changes\n \n React.useEffect(() => { setFocusedIndex(-1) }, [search])\n\n // Scroll focused item into view\n React.useEffect(() => {\n if (focusedIndex < 0) return\n const list = listRef.current\n if (!list) return\n const items = list.querySelectorAll('[data-multiselect-item]')\n items[focusedIndex]?.scrollIntoView({ block: 'nearest' })\n }, [focusedIndex])\n\n function handleSearchKeyDown(e: React.KeyboardEvent) {\n const count = filteredItems.length\n if (count === 0) return\n\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n setFocusedIndex((prev) => (prev < count - 1 ? prev + 1 : 0))\n } else if (e.key === 'ArrowUp') {\n e.preventDefault()\n setFocusedIndex((prev) => (prev <= 0 ? count - 1 : prev - 1))\n } else if (e.key === 'Enter' && focusedIndex >= 0) {\n e.preventDefault()\n const item = filteredItems[focusedIndex]\n if (item && !item.disabled) toggle(item.id)\n }\n }\n\n let itemCounter = 0\n\n function renderItemRow(item: MultiSelectItem) {\n const isSelected = value.includes(item.id)\n const index = itemCounter++\n const isFocused = index === focusedIndex\n return (\n <MotionPreference>\n <motion.button\n key={item.id}\n id={`msp-item-${index}`}\n type=\"button\"\n role=\"option\"\n aria-selected={isSelected}\n disabled={item.disabled}\n data-multiselect-item=\"\"\n data-focused={isFocused ? '' : undefined}\n initial={{ opacity: 0, x: -8 }}\n animate={{ opacity: 1, x: 0 }}\n transition={{ ...springs.snappy, delay: index * 0.02 }}\n onClick={() => toggle(item.id)}\n className={cn(\n 'flex w-full items-center gap-ds-03 px-ds-04 py-ds-02b text-left transition-colors duration-fast-01 ease-productive-standard',\n 'hover:bg-surface-panel-hover',\n 'disabled:opacity-action-disabled disabled:cursor-not-allowed',\n // accent-4 so the selection stays above the focus ring's\n // panel-hover fill in dark; accent-2 sat below the panel.\n isSelected && 'bg-accent-4 text-accent-11',\n isFocused && 'bg-surface-panel-hover',\n )}\n >\n {renderItem ? (\n <span className=\"flex-1 min-w-0\">{renderItem(item, isSelected)}</span>\n ) : (\n <>\n {item.image && (\n <img\n src={item.image}\n alt=\"\"\n className=\"h-ico-md w-ico-md rounded-pill object-cover shrink-0\"\n />\n )}\n <span className=\"flex-1 min-w-0\">\n <span className=\"block truncate text-body-md font-body text-surface-fg\">{item.label}</span>\n {item.description && (\n <span className=\"block truncate text-caption text-surface-fg-subtle\">{item.description}</span>\n )}\n </span>\n </>\n )}\n {isSelected && (\n <motion.span\n initial={{ scale: 0 }}\n animate={{ scale: 1 }}\n transition={springs.snappy}\n className=\"inline-flex shrink-0\"\n >\n <Icon icon={IconCheck} size=\"sm\" className=\"text-accent-11\" />\n </motion.span>\n )}\n </motion.button>\n </MotionPreference>\n )\n }\n\n return (\n <Popover open={open} onOpenChange={setOpen} modal={false}>\n <PopoverTrigger asChild>{children}</PopoverTrigger>\n <PopoverContent\n ref={ref}\n {...props}\n className={cn(\n 'border-surface-border-strong bg-surface-overlay p-0',\n className,\n )}\n style={{ width: width ?? 240 }}\n align={align}\n sideOffset={4}\n >\n {/* Search */}\n <div className=\"flex items-center gap-ds-03 border-b border-surface-border-strong px-ds-04 py-ds-03\">\n <Icon icon={IconSearch} size=\"sm\" stroke=\"light\" className=\"shrink-0 text-surface-fg-subtle\" />\n <input\n type=\"text\"\n placeholder={searchPlaceholder}\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n onKeyDown={handleSearchKeyDown}\n aria-label=\"Search\"\n aria-activedescendant={focusedIndex >= 0 ? `msp-item-${focusedIndex}` : undefined}\n className=\"w-full bg-transparent text-body-md font-body text-surface-fg placeholder:text-surface-fg-subtle outline-hidden\"\n />\n {loading && <Spinner size=\"sm\" />}\n </div>\n\n {/* Items */}\n <div ref={listRef} role=\"listbox\" aria-multiselectable=\"true\" className=\"max-h-[240px] overflow-y-auto py-ds-02\">\n {filteredGroups\n ? filteredGroups.map((group) => (\n <div key={group.label}>\n <div className=\"px-ds-04 py-ds-02 text-label-xs font-semibold uppercase tracking-wider text-surface-fg-subtle\">\n {group.label}\n </div>\n {group.items.map(renderItemRow)}\n </div>\n ))\n : filteredItems.map(renderItemRow)}\n {!loading && filteredItems.length === 0 && (\n <p className=\"px-ds-04 py-ds-05 text-center text-body-sm font-body text-surface-fg-subtle\">\n {emptyMessage}\n </p>\n )}\n </div>\n </PopoverContent>\n </Popover>\n )\n },\n)\nMultiSelectPopover.displayName = 'MultiSelectPopover'\n\nexport { MultiSelectPopover }\n"],"mappings":"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"}
1
+ {"version":3,"file":"multi-select-popover.js","names":[],"sources":["../../src/composed/multi-select-popover.tsx"],"sourcesContent":["'use client'\n\nimport { IconCheck, IconSearch } from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { MotionPreference } from '../motion/motion-preference'\nimport { Icon } from '../ui/icon'\nimport { springs } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from '../ui/popover'\nimport { Spinner } from '../ui/spinner'\n\n// ============================================================\n// Types\n// ============================================================\n\nexport interface MultiSelectItem {\n id: string\n label: string\n image?: string\n description?: string\n disabled?: boolean\n}\n\nexport interface MultiSelectGroup {\n label: string\n items: MultiSelectItem[]\n}\n\nexport interface MultiSelectPopoverProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'onSelect'> {\n /** Flat list of items (use `groups` for grouped rendering) */\n items?: MultiSelectItem[]\n /** Grouped items with section headers */\n groups?: MultiSelectGroup[]\n /** Currently selected item IDs */\n value: string[]\n /** Called when selection changes */\n onValueChange: (ids: string[]) => void\n /** @default 'Search...' */\n searchPlaceholder?: string\n /** Async search — replaces local filter with server results */\n onSearch?: (query: string) => Promise<MultiSelectItem[]>\n /** Debounce for async search in ms @default 300 */\n searchDebounce?: number\n /** Custom item renderer */\n renderItem?: (item: MultiSelectItem, selected: boolean) => React.ReactNode\n /** Message when no items match filter @default 'No results found' */\n emptyMessage?: string\n /** Maximum number of selections */\n maxSelections?: number\n /** Popover alignment @default 'start' */\n align?: 'start' | 'center' | 'end'\n /** Popover width */\n width?: string | number\n /** Trigger element */\n children: React.ReactNode\n}\n\n// ============================================================\n// MultiSelectPopover\n// ============================================================\n\nconst MultiSelectPopover = React.forwardRef<HTMLDivElement, MultiSelectPopoverProps>(\n (\n {\n items,\n groups,\n value,\n onValueChange,\n searchPlaceholder = 'Search...',\n onSearch,\n searchDebounce = 300,\n renderItem,\n emptyMessage = 'No results found',\n maxSelections,\n align = 'start',\n width,\n children,\n className,\n ...props\n },\n ref,\n ) => {\n const [open, setOpen] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [asyncItems, setAsyncItems] = React.useState<MultiSelectItem[] | null>(null)\n const [loading, setLoading] = React.useState(false)\n const [focusedIndex, setFocusedIndex] = React.useState(-1)\n const debounceRef = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n const listRef = React.useRef<HTMLDivElement>(null)\n\n // Reset search and focus when popover closes\n React.useEffect(() => {\n if (!open) {\n setSearch('')\n setAsyncItems(null)\n setFocusedIndex(-1)\n }\n }, [open])\n\n // Async search\n React.useEffect(() => {\n if (!onSearch || !open) return\n if (!search.trim()) {\n setAsyncItems(null)\n setLoading(false)\n return\n }\n setLoading(true)\n clearTimeout(debounceRef.current)\n debounceRef.current = setTimeout(() => {\n onSearch(search)\n .then((results) => setAsyncItems(results))\n .catch(() => setAsyncItems([]))\n .finally(() => setLoading(false))\n }, searchDebounce)\n return () => clearTimeout(debounceRef.current)\n }, [search, onSearch, searchDebounce, open])\n\n // Resolve visible items\n const allItems = React.useMemo(() => {\n if (asyncItems) return asyncItems\n if (groups) return groups.flatMap((g) => g.items)\n return items ?? []\n }, [items, groups, asyncItems])\n\n const filteredItems = React.useMemo(() => {\n if (onSearch) return allItems // async search handles its own filtering\n const q = search.toLowerCase()\n if (!q) return allItems\n return allItems.filter((item) => item.label.toLowerCase().includes(q))\n }, [allItems, search, onSearch])\n\n const filteredGroups = React.useMemo(() => {\n if (!groups || onSearch || asyncItems) return null\n const q = search.toLowerCase()\n return groups\n .map((g) => ({\n ...g,\n items: q ? g.items.filter((item) => item.label.toLowerCase().includes(q)) : g.items,\n }))\n .filter((g) => g.items.length > 0)\n }, [groups, search, onSearch, asyncItems])\n\n function toggle(id: string) {\n const isSelected = value.includes(id)\n if (isSelected) {\n onValueChange(value.filter((v) => v !== id))\n } else {\n if (maxSelections && value.length >= maxSelections) {\n // Replace: drop the first selected item to make room for the new one\n onValueChange([...value.slice(1), id])\n } else {\n onValueChange([...value, id])\n }\n }\n }\n\n // Reset focused index when search text changes\n \n React.useEffect(() => { setFocusedIndex(-1) }, [search])\n\n // Scroll focused item into view\n React.useEffect(() => {\n if (focusedIndex < 0) return\n const list = listRef.current\n if (!list) return\n const items = list.querySelectorAll('[data-multiselect-item]')\n items[focusedIndex]?.scrollIntoView({ block: 'nearest' })\n }, [focusedIndex])\n\n function handleSearchKeyDown(e: React.KeyboardEvent) {\n const count = filteredItems.length\n if (count === 0) return\n\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n setFocusedIndex((prev) => (prev < count - 1 ? prev + 1 : 0))\n } else if (e.key === 'ArrowUp') {\n e.preventDefault()\n setFocusedIndex((prev) => (prev <= 0 ? count - 1 : prev - 1))\n } else if (e.key === 'Enter' && focusedIndex >= 0) {\n e.preventDefault()\n const item = filteredItems[focusedIndex]\n if (item && !item.disabled) toggle(item.id)\n }\n }\n\n let itemCounter = 0\n\n function renderItemRow(item: MultiSelectItem) {\n const isSelected = value.includes(item.id)\n const index = itemCounter++\n const isFocused = index === focusedIndex\n return (\n <MotionPreference>\n <motion.button\n key={item.id}\n id={`msp-item-${index}`}\n type=\"button\"\n role=\"option\"\n aria-selected={isSelected}\n disabled={item.disabled}\n data-multiselect-item=\"\"\n data-focused={isFocused ? '' : undefined}\n initial={{ opacity: 0, x: -8 }}\n animate={{ opacity: 1, x: 0 }}\n transition={{ ...springs.snappy, delay: index * 0.02 }}\n onClick={() => toggle(item.id)}\n className={cn(\n 'flex w-full items-center gap-ds-03 px-ds-04 py-ds-02b text-left transition-colors duration-fast-01 ease-productive-standard',\n 'disabled:opacity-action-disabled disabled:cursor-not-allowed',\n // Both greys are gated on !isSelected, and the selected row\n // carries its own hover. Two separate faults lived here:\n // hover:bg-surface-panel-hover is (0,2,0) and beat the selected\n // (0,1,0) outright, so pointing at a selected row greyed it;\n // and isFocused is also (0,1,0) but declared LATER, so arrowing\n // onto a selected row greyed it too.\n !isSelected && 'hover:bg-surface-panel-hover',\n !isSelected && isFocused && 'bg-surface-panel-hover',\n // accent-4 so the selection stays above the focus ring's\n // panel-hover fill in dark; accent-2 sat below the panel.\n isSelected && 'bg-accent-4 text-accent-11 hover:bg-accent-5',\n // Keyboard focus on a selected row advances within the accent\n // ramp rather than dropping to grey.\n isSelected && isFocused && 'bg-accent-5',\n )}\n >\n {renderItem ? (\n <span className=\"flex-1 min-w-0\">{renderItem(item, isSelected)}</span>\n ) : (\n <>\n {item.image && (\n <img\n src={item.image}\n alt=\"\"\n className=\"h-ico-md w-ico-md rounded-pill object-cover shrink-0\"\n />\n )}\n <span className=\"flex-1 min-w-0\">\n <span className=\"block truncate text-body-md font-body text-surface-fg\">{item.label}</span>\n {item.description && (\n <span className=\"block truncate text-caption text-surface-fg-subtle\">{item.description}</span>\n )}\n </span>\n </>\n )}\n {isSelected && (\n <motion.span\n initial={{ scale: 0 }}\n animate={{ scale: 1 }}\n transition={springs.snappy}\n className=\"inline-flex shrink-0\"\n >\n <Icon icon={IconCheck} size=\"sm\" className=\"text-accent-11\" />\n </motion.span>\n )}\n </motion.button>\n </MotionPreference>\n )\n }\n\n return (\n <Popover open={open} onOpenChange={setOpen} modal={false}>\n <PopoverTrigger asChild>{children}</PopoverTrigger>\n <PopoverContent\n ref={ref}\n {...props}\n className={cn(\n 'border-surface-border-strong bg-surface-overlay p-0',\n className,\n )}\n style={{ width: width ?? 240 }}\n align={align}\n sideOffset={4}\n >\n {/* Search */}\n <div className=\"flex items-center gap-ds-03 border-b border-surface-border-strong px-ds-04 py-ds-03\">\n <Icon icon={IconSearch} size=\"sm\" stroke=\"light\" className=\"shrink-0 text-surface-fg-subtle\" />\n <input\n type=\"text\"\n placeholder={searchPlaceholder}\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n onKeyDown={handleSearchKeyDown}\n aria-label=\"Search\"\n aria-activedescendant={focusedIndex >= 0 ? `msp-item-${focusedIndex}` : undefined}\n className=\"w-full bg-transparent text-body-md font-body text-surface-fg placeholder:text-surface-fg-subtle outline-hidden\"\n />\n {loading && <Spinner size=\"sm\" />}\n </div>\n\n {/* Items */}\n <div ref={listRef} role=\"listbox\" aria-multiselectable=\"true\" className=\"max-h-[240px] overflow-y-auto py-ds-02\">\n {filteredGroups\n ? filteredGroups.map((group) => (\n <div key={group.label}>\n <div className=\"px-ds-04 py-ds-02 text-label-xs font-semibold uppercase tracking-wider text-surface-fg-subtle\">\n {group.label}\n </div>\n {group.items.map(renderItemRow)}\n </div>\n ))\n : filteredItems.map(renderItemRow)}\n {!loading && filteredItems.length === 0 && (\n <p className=\"px-ds-04 py-ds-05 text-center text-body-sm font-body text-surface-fg-subtle\">\n {emptyMessage}\n </p>\n )}\n </div>\n </PopoverContent>\n </Popover>\n )\n },\n)\nMultiSelectPopover.displayName = 'MultiSelectPopover'\n\nexport { MultiSelectPopover }\n"],"mappings":"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"}