@devalok/shilp-sutra 0.55.1 → 0.57.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 (270) hide show
  1. package/BREAKING.json +13 -0
  2. package/MIGRATION.md +163 -0
  3. package/README.md +16 -1
  4. package/dist/_chunks/badge-group.js +92 -78
  5. package/dist/_chunks/badge-group.js.map +1 -1
  6. package/dist/_chunks/chat.js +91 -75
  7. package/dist/_chunks/chat.js.map +1 -1
  8. package/dist/_chunks/document-preview.js +1 -1
  9. package/dist/_chunks/document-preview.js.map +1 -1
  10. package/dist/_chunks/emoji-suggestion.js +32 -30
  11. package/dist/_chunks/emoji-suggestion.js.map +1 -1
  12. package/dist/_chunks/mention-suggestion.js +36 -35
  13. package/dist/_chunks/mention-suggestion.js.map +1 -1
  14. package/dist/_chunks/oauth-button.js +1 -1
  15. package/dist/_chunks/oauth-button.js.map +1 -1
  16. package/dist/_chunks/primitives-internal.js +437 -537
  17. package/dist/_chunks/primitives-internal.js.map +1 -1
  18. package/dist/_chunks/success.js +2 -2
  19. package/dist/_chunks/success.js.map +1 -1
  20. package/dist/_chunks/tree-view.js +1 -1
  21. package/dist/_chunks/tree-view.js.map +1 -1
  22. package/dist/_chunks/use-calendar.js +4 -4
  23. package/dist/_chunks/use-calendar.js.map +1 -1
  24. package/dist/ai/blocks/text.js +1 -1
  25. package/dist/ai/blocks/text.js.map +1 -1
  26. package/dist/ai/command-bar.js +9 -9
  27. package/dist/ai/command-bar.js.map +1 -1
  28. package/dist/ai/conversation.js +2 -2
  29. package/dist/ai/conversation.js.map +1 -1
  30. package/dist/composed/activity-feed.js +1 -1
  31. package/dist/composed/activity-feed.js.map +1 -1
  32. package/dist/composed/avatar-group.d.ts +7 -2
  33. package/dist/composed/avatar-group.d.ts.map +1 -1
  34. package/dist/composed/avatar-group.js +3 -3
  35. package/dist/composed/avatar-group.js.map +1 -1
  36. package/dist/composed/command-palette.js +7 -7
  37. package/dist/composed/command-palette.js.map +1 -1
  38. package/dist/composed/content-card.js +20 -20
  39. package/dist/composed/content-card.js.map +1 -1
  40. package/dist/composed/date-picker/index.js +4 -4
  41. package/dist/composed/date-picker/index.js.map +1 -1
  42. package/dist/composed/diff.d.ts +23 -2
  43. package/dist/composed/diff.d.ts.map +1 -1
  44. package/dist/composed/diff.js +341 -260
  45. package/dist/composed/diff.js.map +1 -1
  46. package/dist/composed/emoji-picker.js +3 -3
  47. package/dist/composed/emoji-picker.js.map +1 -1
  48. package/dist/composed/error-boundary.js +2 -2
  49. package/dist/composed/error-boundary.js.map +1 -1
  50. package/dist/composed/extensions/emoji-node.js +8 -7
  51. package/dist/composed/extensions/emoji-node.js.map +1 -1
  52. package/dist/composed/file-preview.js +1 -1
  53. package/dist/composed/file-preview.js.map +1 -1
  54. package/dist/composed/filter-bar.js +1 -1
  55. package/dist/composed/filter-bar.js.map +1 -1
  56. package/dist/composed/inline-edit.js +1 -1
  57. package/dist/composed/inline-edit.js.map +1 -1
  58. package/dist/composed/loading-skeleton.js +3 -3
  59. package/dist/composed/loading-skeleton.js.map +1 -1
  60. package/dist/composed/master-detail.js +1 -1
  61. package/dist/composed/master-detail.js.map +1 -1
  62. package/dist/composed/member-picker.js +1 -1
  63. package/dist/composed/member-picker.js.map +1 -1
  64. package/dist/composed/multi-select-popover.js +1 -1
  65. package/dist/composed/multi-select-popover.js.map +1 -1
  66. package/dist/composed/page-skeletons.js +4 -4
  67. package/dist/composed/page-skeletons.js.map +1 -1
  68. package/dist/composed/responsive-modal.js +1 -1
  69. package/dist/composed/responsive-modal.js.map +1 -1
  70. package/dist/composed/rich-chat-input.js +549 -540
  71. package/dist/composed/rich-chat-input.js.map +1 -1
  72. package/dist/composed/rich-text-editor.js +206 -198
  73. package/dist/composed/rich-text-editor.js.map +1 -1
  74. package/dist/composed/schedule-view.js +5 -5
  75. package/dist/composed/schedule-view.js.map +1 -1
  76. package/dist/hooks/index.d.ts +1 -0
  77. package/dist/hooks/index.d.ts.map +1 -1
  78. package/dist/hooks/index.js +4 -3
  79. package/dist/hooks/use-container-size.d.ts +18 -0
  80. package/dist/hooks/use-container-size.d.ts.map +1 -0
  81. package/dist/hooks/use-container-size.js +28 -0
  82. package/dist/hooks/use-container-size.js.map +1 -0
  83. package/dist/shell/app-shell.d.ts +101 -0
  84. package/dist/shell/app-shell.d.ts.map +1 -0
  85. package/dist/shell/app-shell.js +76 -0
  86. package/dist/shell/app-shell.js.map +1 -0
  87. package/dist/shell/bottom-navbar.js +2 -2
  88. package/dist/shell/bottom-navbar.js.map +1 -1
  89. package/dist/shell/index.d.ts +1 -0
  90. package/dist/shell/index.d.ts.map +1 -1
  91. package/dist/shell/index.js +6 -5
  92. package/dist/shell/notification-center.js +4 -4
  93. package/dist/shell/notification-center.js.map +1 -1
  94. package/dist/shell/notification-preferences.js +1 -1
  95. package/dist/shell/notification-preferences.js.map +1 -1
  96. package/dist/shell/top-bar.js +7 -7
  97. package/dist/shell/top-bar.js.map +1 -1
  98. package/dist/tokens/generate-scale.d.ts +49 -0
  99. package/dist/tokens/generate-scale.d.ts.map +1 -1
  100. package/dist/tokens/primitives.css +29 -26
  101. package/dist/tokens/semantic.css +98 -39
  102. package/dist/tokens/variants.css +1 -1
  103. package/dist/ui/accordion.js +1 -1
  104. package/dist/ui/accordion.js.map +1 -1
  105. package/dist/ui/alert-dialog.js +1 -1
  106. package/dist/ui/alert-dialog.js.map +1 -1
  107. package/dist/ui/alert.d.ts.map +1 -1
  108. package/dist/ui/alert.js +36 -36
  109. package/dist/ui/alert.js.map +1 -1
  110. package/dist/ui/avatar.js +44 -44
  111. package/dist/ui/avatar.js.map +1 -1
  112. package/dist/ui/badge-indicator.js +1 -1
  113. package/dist/ui/badge-indicator.js.map +1 -1
  114. package/dist/ui/badge.d.ts +47 -33
  115. package/dist/ui/badge.d.ts.map +1 -1
  116. package/dist/ui/banner.js +26 -26
  117. package/dist/ui/banner.js.map +1 -1
  118. package/dist/ui/button.js +35 -35
  119. package/dist/ui/button.js.map +1 -1
  120. package/dist/ui/card.js +42 -42
  121. package/dist/ui/card.js.map +1 -1
  122. package/dist/ui/chat/date-separator.d.ts +8 -1
  123. package/dist/ui/chat/date-separator.d.ts.map +1 -1
  124. package/dist/ui/checkbox.js +1 -1
  125. package/dist/ui/checkbox.js.map +1 -1
  126. package/dist/ui/code.js +2 -2
  127. package/dist/ui/code.js.map +1 -1
  128. package/dist/ui/color-input.js +3 -3
  129. package/dist/ui/color-input.js.map +1 -1
  130. package/dist/ui/combobox.js +139 -139
  131. package/dist/ui/combobox.js.map +1 -1
  132. package/dist/ui/context-menu.js +4 -4
  133. package/dist/ui/context-menu.js.map +1 -1
  134. package/dist/ui/data-table-body.d.ts +3 -1
  135. package/dist/ui/data-table-body.d.ts.map +1 -1
  136. package/dist/ui/data-table-body.js +93 -92
  137. package/dist/ui/data-table-body.js.map +1 -1
  138. package/dist/ui/data-table-bulk-actions.d.ts +7 -1
  139. package/dist/ui/data-table-bulk-actions.d.ts.map +1 -1
  140. package/dist/ui/data-table-bulk-actions.js +14 -8
  141. package/dist/ui/data-table-bulk-actions.js.map +1 -1
  142. package/dist/ui/data-table-card.d.ts.map +1 -1
  143. package/dist/ui/data-table-card.js +66 -47
  144. package/dist/ui/data-table-card.js.map +1 -1
  145. package/dist/ui/data-table-context.d.ts +4 -0
  146. package/dist/ui/data-table-context.d.ts.map +1 -1
  147. package/dist/ui/data-table-context.js.map +1 -1
  148. package/dist/ui/data-table-header.d.ts.map +1 -1
  149. package/dist/ui/data-table-header.js +18 -15
  150. package/dist/ui/data-table-header.js.map +1 -1
  151. package/dist/ui/data-table-pagination.js +3 -3
  152. package/dist/ui/data-table-pagination.js.map +1 -1
  153. package/dist/ui/data-table-toolbar.d.ts +11 -1
  154. package/dist/ui/data-table-toolbar.d.ts.map +1 -1
  155. package/dist/ui/data-table-toolbar.js +7 -7
  156. package/dist/ui/data-table-toolbar.js.map +1 -1
  157. package/dist/ui/data-table.d.ts +52 -5
  158. package/dist/ui/data-table.d.ts.map +1 -1
  159. package/dist/ui/data-table.js +181 -169
  160. package/dist/ui/data-table.js.map +1 -1
  161. package/dist/ui/dialog.js +1 -1
  162. package/dist/ui/dialog.js.map +1 -1
  163. package/dist/ui/dot.js +13 -13
  164. package/dist/ui/dot.js.map +1 -1
  165. package/dist/ui/dropdown-menu.js +4 -4
  166. package/dist/ui/dropdown-menu.js.map +1 -1
  167. package/dist/ui/file-upload.js +3 -3
  168. package/dist/ui/file-upload.js.map +1 -1
  169. package/dist/ui/input-otp.js +1 -1
  170. package/dist/ui/input-otp.js.map +1 -1
  171. package/dist/ui/input.js +30 -30
  172. package/dist/ui/input.js.map +1 -1
  173. package/dist/ui/lib/utils.js +2 -1
  174. package/dist/ui/lib/utils.js.map +1 -1
  175. package/dist/ui/menubar.js +5 -5
  176. package/dist/ui/menubar.js.map +1 -1
  177. package/dist/ui/navigation-menu.js +1 -1
  178. package/dist/ui/navigation-menu.js.map +1 -1
  179. package/dist/ui/number-input.js +36 -36
  180. package/dist/ui/number-input.js.map +1 -1
  181. package/dist/ui/pagination.js +1 -1
  182. package/dist/ui/pagination.js.map +1 -1
  183. package/dist/ui/progress-ring.js +2 -2
  184. package/dist/ui/progress-ring.js.map +1 -1
  185. package/dist/ui/progress.d.ts.map +1 -1
  186. package/dist/ui/progress.js +66 -62
  187. package/dist/ui/progress.js.map +1 -1
  188. package/dist/ui/radio.js +1 -1
  189. package/dist/ui/radio.js.map +1 -1
  190. package/dist/ui/select.js +61 -61
  191. package/dist/ui/select.js.map +1 -1
  192. package/dist/ui/sheet.js +47 -47
  193. package/dist/ui/sheet.js.map +1 -1
  194. package/dist/ui/sidebar.js +132 -132
  195. package/dist/ui/sidebar.js.map +1 -1
  196. package/dist/ui/skeleton.js +39 -39
  197. package/dist/ui/slider.js +18 -18
  198. package/dist/ui/slider.js.map +1 -1
  199. package/dist/ui/split-button.js +3 -3
  200. package/dist/ui/split-button.js.map +1 -1
  201. package/dist/ui/stat-card.js +1 -1
  202. package/dist/ui/stat-card.js.map +1 -1
  203. package/dist/ui/stepper.js +2 -2
  204. package/dist/ui/stepper.js.map +1 -1
  205. package/dist/ui/surface.d.ts.map +1 -1
  206. package/dist/ui/surface.js +11 -11
  207. package/dist/ui/surface.js.map +1 -1
  208. package/dist/ui/switch.js +1 -1
  209. package/dist/ui/switch.js.map +1 -1
  210. package/dist/ui/table.js +1 -1
  211. package/dist/ui/table.js.map +1 -1
  212. package/dist/ui/tabs.d.ts.map +1 -1
  213. package/dist/ui/tabs.js +48 -48
  214. package/dist/ui/tabs.js.map +1 -1
  215. package/dist/ui/text.js +8 -8
  216. package/dist/ui/textarea.js +17 -17
  217. package/dist/ui/textarea.js.map +1 -1
  218. package/dist/ui/toast.js +1 -1
  219. package/dist/ui/toast.js.map +1 -1
  220. package/dist/ui/toggle.js +15 -15
  221. package/dist/ui/toggle.js.map +1 -1
  222. package/docs/components/composed/avatar-group.md +4 -4
  223. package/docs/components/composed/diff.md +10 -2
  224. package/docs/components/composed/schedule-view.md +1 -1
  225. package/docs/components/shell/app-shell.md +132 -0
  226. package/docs/components/ui/avatar.md +1 -1
  227. package/docs/components/ui/button.md +1 -1
  228. package/docs/components/ui/chat.md +7 -1
  229. package/docs/components/ui/color-swatch.md +1 -1
  230. package/docs/components/ui/data-table-toolbar.md +6 -2
  231. package/docs/components/ui/data-table.md +41 -7
  232. package/docs/components/ui/dot.md +1 -1
  233. package/docs/components/ui/progress.md +1 -0
  234. package/docs/components/ui/surface.md +3 -3
  235. package/docs/components/ui/table.md +2 -2
  236. package/docs/recipes/install-astro.md +3 -2
  237. package/docs/recipes/install-next-app-router.md +3 -2
  238. package/docs/recipes/install-remix.md +3 -2
  239. package/docs/recipes/install-tanstack-start.md +3 -2
  240. package/docs/recipes/install-vite.md +3 -2
  241. package/docs/recipes/troubleshoot.md +26 -5
  242. package/llms.txt +2 -1
  243. package/make-kit/Guidelines.md +2 -2
  244. package/make-kit/components/button.md +1 -1
  245. package/make-kit/components/card.md +6 -6
  246. package/make-kit/components/surface.md +4 -4
  247. package/make-kit/foundations/color.md +4 -4
  248. package/make-kit/foundations/dark-mode.md +1 -1
  249. package/make-kit/foundations/radius.md +1 -1
  250. package/make-kit/foundations/surfaces.md +9 -9
  251. package/mcp-manifest.json +373 -34
  252. package/package.json +43 -13
  253. package/skill/SKILL.md +12 -6
  254. package/skill/references/components.md +2 -1
  255. package/skill/references/setup-astro.md +3 -2
  256. package/skill/references/setup-next-app-router.md +3 -2
  257. package/skill/references/setup-remix.md +3 -2
  258. package/skill/references/setup-tanstack-start.md +3 -2
  259. package/skill/references/setup-vite.md +3 -2
  260. package/skill/references/troubleshoot.md +26 -5
  261. package/dist/_chunks/tiptap.js +0 -18809
  262. package/dist/_chunks/tiptap.js.map +0 -1
  263. package/dist/_chunks/vendor-utils.js +0 -1766
  264. package/dist/_chunks/vendor-utils.js.map +0 -1
  265. package/dist/primitives/react-toast.d.ts +0 -120
  266. package/dist/ui/charts/_internal/scales.d.ts +0 -5
  267. package/dist/ui/charts/_internal/scales.d.ts.map +0 -1
  268. package/dist/ui/lib/slot.d.ts +0 -8
  269. package/dist/ui/lib/slot.d.ts.map +0 -1
  270. package/scripts/welcome.mjs +0 -312
@@ -1 +1 @@
1
- {"version":3,"file":"command-bar.js","names":[],"sources":["../../src/ai/command-bar.tsx"],"sourcesContent":["'use client'\n\n/**\n * CommandBar -- Unified AI command interface.\n *\n * Three variants:\n * - hero: Full-featured inline bar with greeting, hints, placeholder rotation\n * - inline: Compact inline bar for embedding in panels/toolbars\n * - floating: Modal overlay (same pattern as CommandPalette) with global keybinding\n *\n * Supports both command-palette filtering (when `groups` provided) and\n * AI natural-language submission via `onSubmit`.\n */\nimport {\n IconArrowDown,\n IconArrowUp,\n IconCornerDownLeft,\n IconLoader2,\n IconSearch,\n IconX,\n} from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport type { CommandGroup, CommandItem } from '../composed/command-palette'\nimport { useMotion } from '../motion/motion-provider'\nimport {\n Dialog,\n DialogContentRaw,\n DialogDescription,\n DialogOverlay,\n DialogPortal,\n DialogTitle,\n} from '../ui/dialog'\nimport { Icon } from '../ui/icon'\nimport { IconProvider } from '../ui/icon-context'\nimport { getIsMac, getModifierDisplay,matchesKeybinding } from '../ui/lib/keybinding'\nimport { springs,tweens } from '../ui/lib/motion'\nimport { normalizeIcon } from '../ui/lib/normalize-icon'\nimport { cn } from '../ui/lib/utils'\nimport { VisuallyHidden } from '../ui/visually-hidden'\n\n// -----------------------------------------------------------------------\n// GradientBorderWrap — animated gradient border during processing\n// -----------------------------------------------------------------------\n\n/**\n * Wraps the input row with an animated gradient border during processing.\n * Uses the Devalok brand palette (pink → purple → magenta) flowing around\n * the border. When inactive, renders children directly with no wrapper overhead.\n *\n * Technique: outer div with gradient background + padding-[1.5px] creates\n * a visible gradient border. The inner content covers the center, leaving\n * only the edge visible as a \"border\".\n */\nfunction GradientBorderWrap({\n active,\n rounded,\n reducedMotion,\n children,\n}: {\n active: boolean\n rounded: string\n reducedMotion: boolean\n children: React.ReactNode\n}) {\n if (!active) return <>{children}</>\n\n if (reducedMotion) {\n return (\n <div className={cn('p-[1.5px] bg-accent-9', rounded)}>\n {children}\n </div>\n )\n }\n\n return (\n <motion.div\n className={cn('relative p-[1.5px]', rounded)}\n style={{\n background: 'linear-gradient(var(--gradient-angle, 0deg), #D33163, #9B5DE5, #C850C0, #D33163)',\n backgroundSize: '300% 300%',\n }}\n animate={{\n // Rotate the gradient angle — creates the flowing border effect\n // Using CSS custom property animation via backgroundPosition as proxy\n backgroundPosition: ['0% 0%', '100% 100%', '0% 0%'],\n }}\n transition={{\n backgroundPosition: {\n duration: 4,\n repeat: Infinity,\n ease: 'linear',\n },\n }}\n >\n {children}\n </motion.div>\n )\n}\n\n// -----------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------\n\n/**\n * Unified AI command interface with three visual variants:\n * - `hero`: full-featured inline bar with greeting, animated placeholder rotation, and hints\n * - `inline`: compact bar for embedding in panels or toolbars\n * - `floating`: modal overlay with global keybinding (same pattern as CommandPalette)\n *\n * Supports both command-palette filtering (when `groups` provided) and\n * natural-language AI submission via `onSubmit`.\n */\nexport interface CommandBarProps\n extends Omit<React.ComponentPropsWithRef<'div'>, 'onSubmit'> {\n // -- AI submission --\n /** Called when the user submits a query (Enter key). */\n onSubmit?: (query: string) => void\n /** Current interaction state. Controls visual feedback (gradient border, placeholder). */\n state?: 'idle' | 'typing' | 'processing' | 'responded'\n\n // -- Command palette mode (optional) --\n /** When provided, enables command-palette filtering alongside AI submission. */\n groups?: CommandGroup[]\n onSearch?: (query: string) => void\n emptyMessage?: string\n emptyState?: React.ReactNode\n\n // -- Visual --\n /** Layout variant. @default 'hero' */\n variant?: 'hero' | 'inline' | 'floating'\n /** Placeholder text, or an array of strings that rotate on an interval. */\n placeholder?: string | string[]\n /** Rotation interval in ms when `placeholder` is an array. @default 4000 */\n placeholderInterval?: number\n /** Greeting text shown above the input in `hero` variant. */\n greeting?: string\n /** Hint strings shown below the input in `hero` variant. */\n hints?: string[]\n agentName?: string\n agentIcon?: React.ReactNode\n\n // -- Floating variant (modal) --\n open?: boolean\n defaultOpen?: boolean\n onOpenChange?: (open: boolean) => void\n /** Keybinding to toggle the floating bar. @default 'mod+k' */\n keybinding?: string | string[] | false\n\n // -- Interaction --\n disabled?: boolean\n maxHeight?: string | number\n\n // -- Composition --\n children?: React.ReactNode\n}\n\n// -----------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------\n\nfunction getFilterValue(item: CommandItem): string {\n if (item.filterValue) return item.filterValue\n if (typeof item.label === 'string') return item.label\n return ''\n}\n\nfunction getFilterDescription(item: CommandItem): string {\n if (typeof item.description === 'string') return item.description\n return ''\n}\n\n// -----------------------------------------------------------------------\n// Rotating Placeholder\n// -----------------------------------------------------------------------\n\nfunction RotatingPlaceholder({\n placeholders,\n interval,\n paused,\n}: {\n placeholders: string[]\n interval: number\n paused: boolean\n}) {\n const [currentIndex, setCurrentIndex] = React.useState(0)\n const { reducedMotion } = useMotion()\n\n React.useEffect(() => {\n if (paused || placeholders.length <= 1) return\n const timer = setInterval(() => {\n setCurrentIndex((prev) => (prev + 1) % placeholders.length)\n }, interval)\n return () => clearInterval(timer)\n }, [paused, placeholders.length, interval])\n\n if (reducedMotion) {\n return (\n <span className=\"pointer-events-none absolute text-surface-fg-subtle\">\n {placeholders[currentIndex]}\n </span>\n )\n }\n\n return (\n <AnimatePresence mode=\"wait\">\n <motion.span\n key={placeholders[currentIndex]}\n initial={{ opacity: 0, y: -4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 4 }}\n transition={tweens.fade}\n className=\"pointer-events-none absolute text-surface-fg-subtle\"\n >\n {placeholders[currentIndex]}\n </motion.span>\n </AnimatePresence>\n )\n}\n\n// -----------------------------------------------------------------------\n// CommandBar\n// -----------------------------------------------------------------------\n\nconst CommandBar = React.forwardRef<HTMLDivElement, CommandBarProps>(\n function CommandBar(\n {\n onSubmit,\n state = 'idle',\n groups = [],\n onSearch,\n emptyMessage = 'No results found.',\n emptyState,\n variant = 'hero',\n placeholder = 'Ask anything...',\n placeholderInterval = 5000,\n greeting,\n hints,\n agentName,\n agentIcon,\n open: openProp,\n defaultOpen,\n onOpenChange,\n keybinding = 'mod+j',\n disabled = false,\n maxHeight = '320px',\n children,\n className,\n ...props\n },\n ref,\n ) {\n // -- Controlled/uncontrolled open state (floating) --\n const isControlled = openProp !== undefined\n const [internalOpen, setInternalOpen] = React.useState(defaultOpen ?? false)\n const open = isControlled ? openProp : internalOpen\n\n const openRef = React.useRef(open)\n openRef.current = open\n\n const setOpen = React.useCallback(\n (nextOpen: boolean | ((prev: boolean) => boolean)) => {\n const resolved =\n typeof nextOpen === 'function' ? nextOpen(openRef.current) : nextOpen\n if (!isControlled) {\n setInternalOpen(resolved)\n }\n onOpenChange?.(resolved)\n },\n [isControlled, onOpenChange],\n )\n\n // -- Core state --\n const [query, setQuery] = React.useState('')\n const [activeIndex, setActiveIndex] = React.useState(-1)\n const [lastQuery, setLastQuery] = React.useState('')\n const [isFocused, setIsFocused] = React.useState(false)\n const [shake, setShake] = React.useState(false)\n const inputRef = React.useRef<HTMLInputElement>(null)\n const listRef = React.useRef<HTMLDivElement>(null)\n const instanceId = React.useId()\n const listboxId = `command-bar-listbox-${instanceId}`\n\n // -- Motion --\n const { reducedMotion: isReduced } = useMotion()\n const noMotionTransition = { duration: 0 }\n\n // -- Platform detection --\n const isMac = React.useMemo(() => getIsMac(), [])\n\n // -- Placeholders --\n const placeholders = React.useMemo(\n () => (Array.isArray(placeholder) ? placeholder : [placeholder]),\n [placeholder],\n )\n\n // -- Has groups --\n const hasGroups = groups.length > 0\n\n // -- Filter groups --\n const filteredGroups = React.useMemo(() => {\n if (!hasGroups) return []\n if (!query.trim()) return groups\n const q = query.toLowerCase()\n return groups\n .map((group) => ({\n ...group,\n items: group.items.filter(\n (item) =>\n getFilterValue(item).toLowerCase().includes(q) ||\n getFilterDescription(item).toLowerCase().includes(q),\n ),\n }))\n .filter((group) => group.items.length > 0)\n }, [groups, query, hasGroups])\n\n const filteredItems = React.useMemo(\n () => filteredGroups.flatMap((g) => g.items),\n [filteredGroups],\n )\n\n // Build item index map for keyboard navigation\n const itemIndexMap = React.useMemo(() => {\n const map = new Map<string, number>()\n let idx = 0\n for (const group of filteredGroups) {\n for (const item of group.items) {\n map.set(item.id, idx++)\n }\n }\n return map\n }, [filteredGroups])\n\n // -- Max height CSS --\n const maxHeightValue =\n typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight\n\n // -- Global keybinding (floating variant) --\n React.useEffect(() => {\n if (variant !== 'floating' || keybinding === false) return\n\n const bindings = Array.isArray(keybinding) ? keybinding : [keybinding]\n\n function handleKeyDown(e: KeyboardEvent) {\n for (const binding of bindings) {\n if (matchesKeybinding(e, binding)) {\n e.preventDefault()\n setOpen((prev) => !prev)\n return\n }\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n }, [variant, keybinding, setOpen])\n\n // -- Reset on floating open --\n React.useEffect(() => {\n if (variant === 'floating' && open) {\n setQuery('')\n setActiveIndex(-1)\n requestAnimationFrame(() => {\n inputRef.current?.focus()\n })\n }\n }, [variant, open])\n\n // -- Scroll active item into view --\n React.useEffect(() => {\n if (activeIndex < 0) return\n const activeEl = listRef.current?.querySelector(\n `[data-command-index=\"${activeIndex}\"]`,\n )\n activeEl?.scrollIntoView({ block: 'nearest' })\n }, [activeIndex])\n\n // -- Query change handler --\n const handleQueryChange = (value: string) => {\n setQuery(value)\n setActiveIndex(hasGroups && value.trim() ? 0 : -1)\n onSearch?.(value)\n }\n\n // -- Submit handler --\n const handleSubmit = React.useCallback(() => {\n if (!query.trim() || disabled) return\n setLastQuery(query)\n onSubmit?.(query)\n }, [query, disabled, onSubmit])\n\n // -- Keyboard handler --\n const handleKeyDown = (e: React.KeyboardEvent) => {\n switch (e.key) {\n case 'ArrowDown': {\n if (!hasGroups || filteredItems.length === 0) return\n e.preventDefault()\n setActiveIndex((prev) =>\n prev < filteredItems.length - 1 ? prev + 1 : 0,\n )\n break\n }\n case 'ArrowUp': {\n if (hasGroups && filteredItems.length > 0) {\n e.preventDefault()\n setActiveIndex((prev) =>\n prev > 0 ? prev - 1 : filteredItems.length - 1,\n )\n } else if (!hasGroups && !query && lastQuery) {\n // Recall last query\n e.preventDefault()\n setQuery(lastQuery)\n }\n break\n }\n case 'Enter': {\n e.preventDefault()\n const isCmdEnter = e.metaKey || e.ctrlKey\n\n if (isCmdEnter) {\n // Cmd+Enter always submits\n handleSubmit()\n } else if (\n hasGroups &&\n activeIndex >= 0 &&\n filteredItems[activeIndex]\n ) {\n // Enter selects active command item\n filteredItems[activeIndex].onSelect()\n if (variant === 'floating') setOpen(false)\n } else {\n // Enter submits query\n handleSubmit()\n }\n break\n }\n case 'Escape': {\n e.preventDefault()\n if (state === 'responded') {\n setQuery('')\n } else if (variant === 'floating') {\n setOpen(false)\n } else {\n inputRef.current?.blur()\n }\n break\n }\n }\n }\n\n // -- Clear handler --\n const handleClear = () => {\n setQuery('')\n setActiveIndex(-1)\n inputRef.current?.focus()\n }\n\n // -- Shake animation reset --\n React.useEffect(() => {\n if (shake) {\n const timer = setTimeout(() => setShake(false), 500)\n return () => clearTimeout(timer)\n }\n }, [shake])\n\n // -- Shared spring/tween for reduced motion --\n const springSnappy = isReduced ? noMotionTransition : springs.snappy\n const tweenFade = isReduced ? noMotionTransition : tweens.fade\n const noInit = isReduced ? { opacity: 1, scale: 1, y: 0 } : undefined\n\n // =====================================================================\n // Shared Input Row\n // =====================================================================\n const isCompact = variant === 'inline'\n const isProcessing = state === 'processing'\n const isResponded = state === 'responded'\n\n const renderInputRow = () => (\n <GradientBorderWrap\n active={isProcessing}\n rounded={isCompact ? 'rounded-control' : 'rounded-surface'}\n reducedMotion={isReduced}\n >\n <div\n className={cn(\n 'flex items-center gap-ds-04 border bg-surface-overlay transition-colors transition-shadow duration-fast-02 ease-productive-standard',\n isCompact\n ? 'rounded-control px-ds-04'\n : 'rounded-surface px-ds-05',\n isProcessing\n ? 'border-transparent'\n : 'border-surface-border-strong',\n isFocused && !isProcessing && 'border-accent-7 shadow-ring',\n shake && 'animate-shake',\n )}\n >\n {/* Search icon */}\n <Icon\n icon={IconSearch}\n size={isCompact ? 'xs' : 'sm'}\n stroke=\"light\"\n className={cn(\n 'shrink-0 transition-colors duration-fast-02 ease-productive-standard',\n isFocused ? 'text-accent-9' : 'text-surface-fg-subtle',\n )}\n />\n\n {/* Input wrapper */}\n <div className=\"relative flex flex-1 items-center\">\n {!query && !isFocused && placeholders.length > 1 && (\n <RotatingPlaceholder\n placeholders={placeholders}\n interval={placeholderInterval}\n paused={isFocused || !!query}\n />\n )}\n <input\n ref={inputRef}\n value={query}\n onChange={(e) => handleQueryChange(e.target.value)}\n onFocus={() => setIsFocused(true)}\n onBlur={() => setIsFocused(false)}\n onKeyDown={handleKeyDown}\n placeholder={\n placeholders.length === 1 || isFocused ? placeholders[0] : ''\n }\n readOnly={isProcessing}\n disabled={disabled}\n role=\"combobox\"\n aria-expanded={hasGroups && filteredItems.length > 0}\n aria-controls={hasGroups ? listboxId : undefined}\n aria-activedescendant={\n hasGroups && activeIndex >= 0 && filteredItems[activeIndex]\n ? `command-bar-item-${instanceId}-${filteredItems[activeIndex].id}`\n : undefined\n }\n aria-autocomplete={hasGroups ? 'list' : undefined}\n aria-label={agentName ? `Ask ${agentName}` : 'AI Command Bar'}\n className={cn(\n 'flex-1 bg-transparent text-surface-fg outline-hidden',\n 'placeholder:text-surface-fg-subtle',\n isCompact ? 'h-9 text-body-sm' : 'h-12 text-body-lg',\n isProcessing && 'cursor-wait',\n disabled && 'cursor-not-allowed opacity-50',\n )}\n autoComplete=\"off\"\n autoCorrect=\"off\"\n spellCheck={false}\n />\n </div>\n\n {/* Right side: spinner / clear / shortcut badge */}\n {isProcessing ? (\n <span data-testid=\"command-bar-spinner\" className=\"shrink-0\">\n <Icon\n icon={IconLoader2}\n size={isCompact ? 'xs' : 'sm'}\n stroke=\"light\"\n animate=\"spin\"\n className=\"text-accent-9\"\n />\n </span>\n ) : isResponded ? (\n <button\n type=\"button\"\n onClick={handleClear}\n className=\"shrink-0 rounded-control-inner p-ds-01 text-surface-fg-subtle transition-colors duration-fast-01 hover:bg-surface-raised-hover hover:text-surface-fg-muted focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9\"\n aria-label=\"Clear\"\n title=\"Clear\"\n >\n <Icon\n icon={IconX}\n size={isCompact ? 'xs' : 'sm'}\n stroke=\"light\"\n />\n </button>\n ) : variant === 'hero' ? (\n <AnimatePresence>\n {!isFocused && (\n <motion.kbd\n initial={noInit ?? { opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={isReduced ? undefined : { opacity: 0 }}\n transition={tweenFade}\n className=\"hidden shrink-0 select-none rounded-control border border-card-strong bg-surface-raised px-ds-02b py-ds-01 text-body-sm font-medium text-surface-fg-subtle shadow-kbd sm:inline-flex\"\n >\n {getModifierDisplay(isMac)}J\n </motion.kbd>\n )}\n </AnimatePresence>\n ) : null}\n </div>\n </GradientBorderWrap>\n )\n\n // =====================================================================\n // Command Results (shared between variants)\n // =====================================================================\n const renderCommandResults = () => {\n if (!hasGroups) return null\n\n const showResults =\n filteredItems.length > 0 || (query.trim() && filteredGroups.length === 0)\n\n if (!showResults && !query.trim()) return null\n\n return (\n <div\n ref={listRef}\n id={listboxId}\n role=\"listbox\"\n aria-label=\"Command results\"\n className=\"overflow-y-auto px-ds-03 py-ds-03\"\n style={{ maxHeight: maxHeightValue }}\n >\n {filteredGroups.length === 0 && query.trim() && (\n <motion.div\n initial={noInit ?? { opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={tweenFade}\n className=\"flex items-center justify-center py-ds-07\"\n >\n {emptyState ?? (\n <p className=\"text-body-md text-surface-fg-subtle\">\n {emptyMessage}\n </p>\n )}\n </motion.div>\n )}\n\n {filteredGroups.map((group, groupIdx) => (\n <motion.div\n key={group.label}\n initial={noInit ?? { opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={\n isReduced\n ? noMotionTransition\n : { ...tweens.fade, delay: groupIdx * 0.06 }\n }\n className=\"mb-ds-02\"\n >\n <div className=\"px-ds-03 pb-ds-02 pt-ds-03\">\n <span className=\"text-label-xs font-semibold uppercase tracking-wider text-surface-fg-subtle\">\n {group.label}\n </span>\n </div>\n\n {group.items.map((item) => {\n const itemIndex = itemIndexMap.get(item.id) ?? 0\n const isActive = itemIndex === activeIndex\n return (\n <motion.button\n key={item.id}\n id={`command-bar-item-${instanceId}-${item.id}`}\n type=\"button\"\n role=\"option\"\n aria-selected={isActive}\n data-command-index={itemIndex}\n initial={noInit ?? { opacity: 0, y: 4 }}\n animate={{ opacity: 1, y: 0 }}\n transition={\n isReduced\n ? noMotionTransition\n : { ...springs.snappy, delay: itemIndex * 0.03 }\n }\n onClick={() => {\n item.onSelect()\n if (variant === 'floating') setOpen(false)\n }}\n onMouseEnter={() => setActiveIndex(itemIndex)}\n className={cn(\n 'flex w-full items-center gap-ds-04 rounded-surface px-ds-03 py-ds-03 text-left transition-[color,background-color] duration-fast-02 ease-productive-standard',\n isActive\n ? 'bg-surface-raised-hover text-surface-fg'\n : 'text-surface-fg-muted hover:bg-surface-raised',\n )}\n >\n {item.icon && (\n <span\n className={cn(\n '[&>svg]:h-ico-sm [&>svg]:w-ico-sm shrink-0 transition-colors duration-fast-02 ease-productive-standard',\n isActive\n ? 'text-accent-11'\n : 'text-surface-fg-subtle',\n )}\n aria-hidden=\"true\"\n >\n <IconProvider size=\"sm\">{normalizeIcon(item.icon)}</IconProvider>\n </span>\n )}\n <div className=\"flex flex-1 flex-col\">\n <span className=\"text-body-md\">\n {item.renderLabel\n ? item.renderLabel(query)\n : item.label}\n </span>\n {item.description && (\n <span className=\"text-body-sm text-surface-fg-subtle\">\n {item.description}\n </span>\n )}\n </div>\n <AnimatePresence>\n {isActive && (\n <motion.span\n initial={isReduced ? undefined : { opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={isReduced ? undefined : { opacity: 0 }}\n transition={tweenFade}\n className=\"inline-flex shrink-0\"\n >\n <Icon\n icon={IconCornerDownLeft}\n size=\"sm\"\n stroke=\"light\"\n className=\"text-surface-fg-subtle\"\n />\n </motion.span>\n )}\n </AnimatePresence>\n </motion.button>\n )\n })}\n </motion.div>\n ))}\n </div>\n )\n }\n\n // =====================================================================\n // Hero Variant\n // =====================================================================\n if (variant === 'hero') {\n return (\n <div\n ref={ref}\n role=\"search\"\n className={cn(\n 'bg-surface-raised rounded-overlay-lg shadow-raised p-ds-07',\n className,\n )}\n {...props}\n >\n {greeting && (\n <p className=\"text-heading-xs text-surface-fg-muted mb-ds-04\">\n {greeting}\n </p>\n )}\n\n {renderInputRow()}\n {renderCommandResults()}\n\n {/* Hints */}\n {hints && hints.length > 0 && (\n <div className=\"mt-ds-04 flex flex-wrap gap-ds-02b\">\n {hints.map((hint) => (\n <button\n key={hint}\n type=\"button\"\n onClick={() => {\n setQuery(hint)\n onSearch?.(hint)\n inputRef.current?.focus()\n }}\n className=\"rounded-control border border-card-strong bg-surface-overlay px-ds-03 py-ds-01 text-body-sm text-surface-fg-subtle transition-colors duration-fast-02 ease-productive-standard hover:bg-surface-raised-hover hover:text-surface-fg-muted\"\n >\n {hint}\n </button>\n ))}\n </div>\n )}\n\n {/* Response area (children) */}\n {children && <div className=\"mt-ds-05\">{children}</div>}\n </div>\n )\n }\n\n // =====================================================================\n // Inline Variant\n // =====================================================================\n if (variant === 'inline') {\n return (\n <div\n ref={ref}\n role=\"search\"\n className={cn('w-full', className)}\n {...props}\n >\n {renderInputRow()}\n {renderCommandResults()}\n {children && <div className=\"mt-ds-03\">{children}</div>}\n </div>\n )\n }\n\n // =====================================================================\n // Floating Variant\n // =====================================================================\n const handleDialogOpenChange = (nextOpen: boolean) => {\n setOpen(nextOpen)\n }\n\n return (\n <Dialog open={open} onOpenChange={handleDialogOpenChange}>\n <DialogPortal>\n <DialogOverlay className=\"fixed inset-0 z-overlay bg-overlay data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\" />\n <DialogContentRaw\n ref={ref}\n {...props}\n className={cn(\n 'fixed left-1/2 top-[20%] z-modal w-full max-w-[560px] -translate-x-1/2',\n 'overflow-hidden rounded-overlay-lg bg-surface-overlay shadow-overlay',\n 'duration-moderate-02 data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\n 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',\n 'data-[state=closed]:slide-out-to-left-1/2 data-[state=open]:slide-in-from-left-1/2',\n 'data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2',\n className,\n )}\n >\n <VisuallyHidden>\n <DialogTitle>AI Command Bar</DialogTitle>\n <DialogDescription>\n Search commands or ask a question\n </DialogDescription>\n </VisuallyHidden>\n\n <div className=\"p-ds-04\">{renderInputRow()}</div>\n\n {renderCommandResults()}\n\n {children && (\n <div className=\"border-t border-surface-border-strong p-ds-04\">\n {children}\n </div>\n )}\n\n {/* Footer hints */}\n <motion.div\n initial={noInit ?? { opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={tweenFade}\n className=\"flex items-center gap-ds-05 border-t border-surface-border-strong px-ds-05 py-ds-03\"\n >\n {hasGroups && (\n <div className=\"flex items-center gap-ds-02b\">\n <div className=\"flex items-center gap-ds-01\">\n <kbd className=\"inline-flex h-ico-md w-ico-md items-center justify-center rounded border border-card-strong bg-surface-raised shadow-kbd\">\n <Icon icon={IconArrowUp} size=\"xs\" className=\"text-surface-fg-subtle\" />\n </kbd>\n <kbd className=\"inline-flex h-ico-md w-ico-md items-center justify-center rounded border border-card-strong bg-surface-raised shadow-kbd\">\n <Icon icon={IconArrowDown} size=\"xs\" className=\"text-surface-fg-subtle\" />\n </kbd>\n </div>\n <span className=\"text-caption text-surface-fg-subtle\">\n Navigate\n </span>\n </div>\n )}\n <div className=\"flex items-center gap-ds-02b\">\n <kbd className=\"inline-flex h-[20px] items-center justify-center rounded-control border border-card-strong bg-surface-raised px-ds-02b shadow-kbd\">\n <Icon icon={IconCornerDownLeft} size=\"xs\" className=\"text-surface-fg-subtle\" />\n </kbd>\n <span className=\"text-caption text-surface-fg-subtle\">\n Submit\n </span>\n </div>\n <div className=\"flex items-center gap-ds-02b\">\n <kbd className=\"inline-flex h-[20px] items-center justify-center rounded-control border border-card-strong bg-surface-raised px-ds-02b text-caption font-medium text-surface-fg-subtle shadow-kbd\">\n Esc\n </kbd>\n <span className=\"text-caption text-surface-fg-subtle\">\n Close\n </span>\n </div>\n </motion.div>\n </DialogContentRaw>\n </DialogPortal>\n </Dialog>\n )\n },\n)\n\nCommandBar.displayName = 'CommandBar'\n\nexport { CommandBar }\nexport type { CommandGroup, CommandItem }\n"],"mappings":"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"}
1
+ {"version":3,"file":"command-bar.js","names":[],"sources":["../../src/ai/command-bar.tsx"],"sourcesContent":["'use client'\n\n/**\n * CommandBar -- Unified AI command interface.\n *\n * Three variants:\n * - hero: Full-featured inline bar with greeting, hints, placeholder rotation\n * - inline: Compact inline bar for embedding in panels/toolbars\n * - floating: Modal overlay (same pattern as CommandPalette) with global keybinding\n *\n * Supports both command-palette filtering (when `groups` provided) and\n * AI natural-language submission via `onSubmit`.\n */\nimport {\n IconArrowDown,\n IconArrowUp,\n IconCornerDownLeft,\n IconLoader2,\n IconSearch,\n IconX,\n} from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport type { CommandGroup, CommandItem } from '../composed/command-palette'\nimport { useMotion } from '../motion/motion-provider'\nimport {\n Dialog,\n DialogContentRaw,\n DialogDescription,\n DialogOverlay,\n DialogPortal,\n DialogTitle,\n} from '../ui/dialog'\nimport { Icon } from '../ui/icon'\nimport { IconProvider } from '../ui/icon-context'\nimport { getIsMac, getModifierDisplay,matchesKeybinding } from '../ui/lib/keybinding'\nimport { springs,tweens } from '../ui/lib/motion'\nimport { normalizeIcon } from '../ui/lib/normalize-icon'\nimport { cn } from '../ui/lib/utils'\nimport { VisuallyHidden } from '../ui/visually-hidden'\n\n// -----------------------------------------------------------------------\n// GradientBorderWrap — animated gradient border during processing\n// -----------------------------------------------------------------------\n\n/**\n * Wraps the input row with an animated gradient border during processing.\n * Uses the Devalok brand palette (pink → purple → magenta) flowing around\n * the border. When inactive, renders children directly with no wrapper overhead.\n *\n * Technique: outer div with gradient background + padding-[1.5px] creates\n * a visible gradient border. The inner content covers the center, leaving\n * only the edge visible as a \"border\".\n */\nfunction GradientBorderWrap({\n active,\n rounded,\n reducedMotion,\n children,\n}: {\n active: boolean\n rounded: string\n reducedMotion: boolean\n children: React.ReactNode\n}) {\n if (!active) return <>{children}</>\n\n if (reducedMotion) {\n return (\n <div className={cn('p-[1.5px] bg-accent-9', rounded)}>\n {children}\n </div>\n )\n }\n\n return (\n <motion.div\n className={cn('relative p-[1.5px]', rounded)}\n style={{\n background: 'linear-gradient(var(--gradient-angle, 0deg), #D33163, #9B5DE5, #C850C0, #D33163)',\n backgroundSize: '300% 300%',\n }}\n animate={{\n // Rotate the gradient angle — creates the flowing border effect\n // Using CSS custom property animation via backgroundPosition as proxy\n backgroundPosition: ['0% 0%', '100% 100%', '0% 0%'],\n }}\n transition={{\n backgroundPosition: {\n duration: 4,\n repeat: Infinity,\n ease: 'linear',\n },\n }}\n >\n {children}\n </motion.div>\n )\n}\n\n// -----------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------\n\n/**\n * Unified AI command interface with three visual variants:\n * - `hero`: full-featured inline bar with greeting, animated placeholder rotation, and hints\n * - `inline`: compact bar for embedding in panels or toolbars\n * - `floating`: modal overlay with global keybinding (same pattern as CommandPalette)\n *\n * Supports both command-palette filtering (when `groups` provided) and\n * natural-language AI submission via `onSubmit`.\n */\nexport interface CommandBarProps\n extends Omit<React.ComponentPropsWithRef<'div'>, 'onSubmit'> {\n // -- AI submission --\n /** Called when the user submits a query (Enter key). */\n onSubmit?: (query: string) => void\n /** Current interaction state. Controls visual feedback (gradient border, placeholder). */\n state?: 'idle' | 'typing' | 'processing' | 'responded'\n\n // -- Command palette mode (optional) --\n /** When provided, enables command-palette filtering alongside AI submission. */\n groups?: CommandGroup[]\n onSearch?: (query: string) => void\n emptyMessage?: string\n emptyState?: React.ReactNode\n\n // -- Visual --\n /** Layout variant. @default 'hero' */\n variant?: 'hero' | 'inline' | 'floating'\n /** Placeholder text, or an array of strings that rotate on an interval. */\n placeholder?: string | string[]\n /** Rotation interval in ms when `placeholder` is an array. @default 4000 */\n placeholderInterval?: number\n /** Greeting text shown above the input in `hero` variant. */\n greeting?: string\n /** Hint strings shown below the input in `hero` variant. */\n hints?: string[]\n agentName?: string\n agentIcon?: React.ReactNode\n\n // -- Floating variant (modal) --\n open?: boolean\n defaultOpen?: boolean\n onOpenChange?: (open: boolean) => void\n /** Keybinding to toggle the floating bar. @default 'mod+k' */\n keybinding?: string | string[] | false\n\n // -- Interaction --\n disabled?: boolean\n maxHeight?: string | number\n\n // -- Composition --\n children?: React.ReactNode\n}\n\n// -----------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------\n\nfunction getFilterValue(item: CommandItem): string {\n if (item.filterValue) return item.filterValue\n if (typeof item.label === 'string') return item.label\n return ''\n}\n\nfunction getFilterDescription(item: CommandItem): string {\n if (typeof item.description === 'string') return item.description\n return ''\n}\n\n// -----------------------------------------------------------------------\n// Rotating Placeholder\n// -----------------------------------------------------------------------\n\nfunction RotatingPlaceholder({\n placeholders,\n interval,\n paused,\n}: {\n placeholders: string[]\n interval: number\n paused: boolean\n}) {\n const [currentIndex, setCurrentIndex] = React.useState(0)\n const { reducedMotion } = useMotion()\n\n React.useEffect(() => {\n if (paused || placeholders.length <= 1) return\n const timer = setInterval(() => {\n setCurrentIndex((prev) => (prev + 1) % placeholders.length)\n }, interval)\n return () => clearInterval(timer)\n }, [paused, placeholders.length, interval])\n\n if (reducedMotion) {\n return (\n <span className=\"pointer-events-none absolute text-surface-fg-subtle\">\n {placeholders[currentIndex]}\n </span>\n )\n }\n\n return (\n <AnimatePresence mode=\"wait\">\n <motion.span\n key={placeholders[currentIndex]}\n initial={{ opacity: 0, y: -4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 4 }}\n transition={tweens.fade}\n className=\"pointer-events-none absolute text-surface-fg-subtle\"\n >\n {placeholders[currentIndex]}\n </motion.span>\n </AnimatePresence>\n )\n}\n\n// -----------------------------------------------------------------------\n// CommandBar\n// -----------------------------------------------------------------------\n\nconst CommandBar = React.forwardRef<HTMLDivElement, CommandBarProps>(\n function CommandBar(\n {\n onSubmit,\n state = 'idle',\n groups = [],\n onSearch,\n emptyMessage = 'No results found.',\n emptyState,\n variant = 'hero',\n placeholder = 'Ask anything...',\n placeholderInterval = 5000,\n greeting,\n hints,\n agentName,\n agentIcon,\n open: openProp,\n defaultOpen,\n onOpenChange,\n keybinding = 'mod+j',\n disabled = false,\n maxHeight = '320px',\n children,\n className,\n ...props\n },\n ref,\n ) {\n // -- Controlled/uncontrolled open state (floating) --\n const isControlled = openProp !== undefined\n const [internalOpen, setInternalOpen] = React.useState(defaultOpen ?? false)\n const open = isControlled ? openProp : internalOpen\n\n const openRef = React.useRef(open)\n openRef.current = open\n\n const setOpen = React.useCallback(\n (nextOpen: boolean | ((prev: boolean) => boolean)) => {\n const resolved =\n typeof nextOpen === 'function' ? nextOpen(openRef.current) : nextOpen\n if (!isControlled) {\n setInternalOpen(resolved)\n }\n onOpenChange?.(resolved)\n },\n [isControlled, onOpenChange],\n )\n\n // -- Core state --\n const [query, setQuery] = React.useState('')\n const [activeIndex, setActiveIndex] = React.useState(-1)\n const [lastQuery, setLastQuery] = React.useState('')\n const [isFocused, setIsFocused] = React.useState(false)\n const [shake, setShake] = React.useState(false)\n const inputRef = React.useRef<HTMLInputElement>(null)\n const listRef = React.useRef<HTMLDivElement>(null)\n const instanceId = React.useId()\n const listboxId = `command-bar-listbox-${instanceId}`\n\n // -- Motion --\n const { reducedMotion: isReduced } = useMotion()\n const noMotionTransition = { duration: 0 }\n\n // -- Platform detection --\n const isMac = React.useMemo(() => getIsMac(), [])\n\n // -- Placeholders --\n const placeholders = React.useMemo(\n () => (Array.isArray(placeholder) ? placeholder : [placeholder]),\n [placeholder],\n )\n\n // -- Has groups --\n const hasGroups = groups.length > 0\n\n // -- Filter groups --\n const filteredGroups = React.useMemo(() => {\n if (!hasGroups) return []\n if (!query.trim()) return groups\n const q = query.toLowerCase()\n return groups\n .map((group) => ({\n ...group,\n items: group.items.filter(\n (item) =>\n getFilterValue(item).toLowerCase().includes(q) ||\n getFilterDescription(item).toLowerCase().includes(q),\n ),\n }))\n .filter((group) => group.items.length > 0)\n }, [groups, query, hasGroups])\n\n const filteredItems = React.useMemo(\n () => filteredGroups.flatMap((g) => g.items),\n [filteredGroups],\n )\n\n // Build item index map for keyboard navigation\n const itemIndexMap = React.useMemo(() => {\n const map = new Map<string, number>()\n let idx = 0\n for (const group of filteredGroups) {\n for (const item of group.items) {\n map.set(item.id, idx++)\n }\n }\n return map\n }, [filteredGroups])\n\n // -- Max height CSS --\n const maxHeightValue =\n typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight\n\n // -- Global keybinding (floating variant) --\n React.useEffect(() => {\n if (variant !== 'floating' || keybinding === false) return\n\n const bindings = Array.isArray(keybinding) ? keybinding : [keybinding]\n\n function handleKeyDown(e: KeyboardEvent) {\n for (const binding of bindings) {\n if (matchesKeybinding(e, binding)) {\n e.preventDefault()\n setOpen((prev) => !prev)\n return\n }\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n }, [variant, keybinding, setOpen])\n\n // -- Reset on floating open --\n React.useEffect(() => {\n if (variant === 'floating' && open) {\n setQuery('')\n setActiveIndex(-1)\n requestAnimationFrame(() => {\n inputRef.current?.focus()\n })\n }\n }, [variant, open])\n\n // -- Scroll active item into view --\n React.useEffect(() => {\n if (activeIndex < 0) return\n const activeEl = listRef.current?.querySelector(\n `[data-command-index=\"${activeIndex}\"]`,\n )\n activeEl?.scrollIntoView({ block: 'nearest' })\n }, [activeIndex])\n\n // -- Query change handler --\n const handleQueryChange = (value: string) => {\n setQuery(value)\n setActiveIndex(hasGroups && value.trim() ? 0 : -1)\n onSearch?.(value)\n }\n\n // -- Submit handler --\n const handleSubmit = React.useCallback(() => {\n if (!query.trim() || disabled) return\n setLastQuery(query)\n onSubmit?.(query)\n }, [query, disabled, onSubmit])\n\n // -- Keyboard handler --\n const handleKeyDown = (e: React.KeyboardEvent) => {\n switch (e.key) {\n case 'ArrowDown': {\n if (!hasGroups || filteredItems.length === 0) return\n e.preventDefault()\n setActiveIndex((prev) =>\n prev < filteredItems.length - 1 ? prev + 1 : 0,\n )\n break\n }\n case 'ArrowUp': {\n if (hasGroups && filteredItems.length > 0) {\n e.preventDefault()\n setActiveIndex((prev) =>\n prev > 0 ? prev - 1 : filteredItems.length - 1,\n )\n } else if (!hasGroups && !query && lastQuery) {\n // Recall last query\n e.preventDefault()\n setQuery(lastQuery)\n }\n break\n }\n case 'Enter': {\n e.preventDefault()\n const isCmdEnter = e.metaKey || e.ctrlKey\n\n if (isCmdEnter) {\n // Cmd+Enter always submits\n handleSubmit()\n } else if (\n hasGroups &&\n activeIndex >= 0 &&\n filteredItems[activeIndex]\n ) {\n // Enter selects active command item\n filteredItems[activeIndex].onSelect()\n if (variant === 'floating') setOpen(false)\n } else {\n // Enter submits query\n handleSubmit()\n }\n break\n }\n case 'Escape': {\n e.preventDefault()\n if (state === 'responded') {\n setQuery('')\n } else if (variant === 'floating') {\n setOpen(false)\n } else {\n inputRef.current?.blur()\n }\n break\n }\n }\n }\n\n // -- Clear handler --\n const handleClear = () => {\n setQuery('')\n setActiveIndex(-1)\n inputRef.current?.focus()\n }\n\n // -- Shake animation reset --\n React.useEffect(() => {\n if (shake) {\n const timer = setTimeout(() => setShake(false), 500)\n return () => clearTimeout(timer)\n }\n }, [shake])\n\n // -- Shared spring/tween for reduced motion --\n const springSnappy = isReduced ? noMotionTransition : springs.snappy\n const tweenFade = isReduced ? noMotionTransition : tweens.fade\n const noInit = isReduced ? { opacity: 1, scale: 1, y: 0 } : undefined\n\n // =====================================================================\n // Shared Input Row\n // =====================================================================\n const isCompact = variant === 'inline'\n const isProcessing = state === 'processing'\n const isResponded = state === 'responded'\n\n const renderInputRow = () => (\n <GradientBorderWrap\n active={isProcessing}\n rounded={isCompact ? 'rounded-control' : 'rounded-surface'}\n reducedMotion={isReduced}\n >\n <div\n className={cn(\n 'flex items-center gap-ds-04 border bg-surface-overlay transition-colors transition-shadow duration-fast-02 ease-productive-standard',\n isCompact\n ? 'rounded-control px-ds-04'\n : 'rounded-surface px-ds-05',\n isProcessing\n ? 'border-transparent'\n : 'border-surface-border-strong',\n isFocused && !isProcessing && 'border-accent-7 shadow-ring',\n shake && 'animate-shake',\n )}\n >\n {/* Search icon */}\n <Icon\n icon={IconSearch}\n size={isCompact ? 'xs' : 'sm'}\n stroke=\"light\"\n className={cn(\n 'shrink-0 transition-colors duration-fast-02 ease-productive-standard',\n isFocused ? 'text-accent-9' : 'text-surface-fg-subtle',\n )}\n />\n\n {/* Input wrapper */}\n <div className=\"relative flex flex-1 items-center\">\n {!query && !isFocused && placeholders.length > 1 && (\n <RotatingPlaceholder\n placeholders={placeholders}\n interval={placeholderInterval}\n paused={isFocused || !!query}\n />\n )}\n <input\n ref={inputRef}\n value={query}\n onChange={(e) => handleQueryChange(e.target.value)}\n onFocus={() => setIsFocused(true)}\n onBlur={() => setIsFocused(false)}\n onKeyDown={handleKeyDown}\n placeholder={\n placeholders.length === 1 || isFocused ? placeholders[0] : ''\n }\n readOnly={isProcessing}\n disabled={disabled}\n role=\"combobox\"\n aria-expanded={hasGroups && filteredItems.length > 0}\n aria-controls={hasGroups ? listboxId : undefined}\n aria-activedescendant={\n hasGroups && activeIndex >= 0 && filteredItems[activeIndex]\n ? `command-bar-item-${instanceId}-${filteredItems[activeIndex].id}`\n : undefined\n }\n aria-autocomplete={hasGroups ? 'list' : undefined}\n aria-label={agentName ? `Ask ${agentName}` : 'AI Command Bar'}\n className={cn(\n 'flex-1 bg-transparent text-surface-fg outline-hidden',\n 'placeholder:text-surface-fg-subtle',\n isCompact ? 'h-9 text-body-sm' : 'h-12 text-body-lg',\n isProcessing && 'cursor-wait',\n disabled && 'cursor-not-allowed opacity-50',\n )}\n autoComplete=\"off\"\n autoCorrect=\"off\"\n spellCheck={false}\n />\n </div>\n\n {/* Right side: spinner / clear / shortcut badge */}\n {isProcessing ? (\n <span data-testid=\"command-bar-spinner\" className=\"shrink-0\">\n <Icon\n icon={IconLoader2}\n size={isCompact ? 'xs' : 'sm'}\n stroke=\"light\"\n animate=\"spin\"\n className=\"text-accent-9\"\n />\n </span>\n ) : isResponded ? (\n <button\n type=\"button\"\n onClick={handleClear}\n className=\"shrink-0 rounded-control-inner p-ds-01 text-surface-fg-subtle transition-colors duration-fast-01 hover:bg-surface-panel-hover hover:text-surface-fg-muted focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9\"\n aria-label=\"Clear\"\n title=\"Clear\"\n >\n <Icon\n icon={IconX}\n size={isCompact ? 'xs' : 'sm'}\n stroke=\"light\"\n />\n </button>\n ) : variant === 'hero' ? (\n <AnimatePresence>\n {!isFocused && (\n <motion.kbd\n initial={noInit ?? { opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={isReduced ? undefined : { opacity: 0 }}\n transition={tweenFade}\n className=\"hidden shrink-0 select-none rounded-control border border-card-strong bg-surface-panel px-ds-02b py-ds-01 text-body-sm font-medium text-surface-fg-subtle shadow-kbd sm:inline-flex\"\n >\n {getModifierDisplay(isMac)}J\n </motion.kbd>\n )}\n </AnimatePresence>\n ) : null}\n </div>\n </GradientBorderWrap>\n )\n\n // =====================================================================\n // Command Results (shared between variants)\n // =====================================================================\n const renderCommandResults = () => {\n if (!hasGroups) return null\n\n const showResults =\n filteredItems.length > 0 || (query.trim() && filteredGroups.length === 0)\n\n if (!showResults && !query.trim()) return null\n\n return (\n <div\n ref={listRef}\n id={listboxId}\n role=\"listbox\"\n aria-label=\"Command results\"\n className=\"overflow-y-auto px-ds-03 py-ds-03\"\n style={{ maxHeight: maxHeightValue }}\n >\n {filteredGroups.length === 0 && query.trim() && (\n <motion.div\n initial={noInit ?? { opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={tweenFade}\n className=\"flex items-center justify-center py-ds-07\"\n >\n {emptyState ?? (\n <p className=\"text-body-md text-surface-fg-subtle\">\n {emptyMessage}\n </p>\n )}\n </motion.div>\n )}\n\n {filteredGroups.map((group, groupIdx) => (\n <motion.div\n key={group.label}\n initial={noInit ?? { opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={\n isReduced\n ? noMotionTransition\n : { ...tweens.fade, delay: groupIdx * 0.06 }\n }\n className=\"mb-ds-02\"\n >\n <div className=\"px-ds-03 pb-ds-02 pt-ds-03\">\n <span className=\"text-label-xs font-semibold uppercase tracking-wider text-surface-fg-subtle\">\n {group.label}\n </span>\n </div>\n\n {group.items.map((item) => {\n const itemIndex = itemIndexMap.get(item.id) ?? 0\n const isActive = itemIndex === activeIndex\n return (\n <motion.button\n key={item.id}\n id={`command-bar-item-${instanceId}-${item.id}`}\n type=\"button\"\n role=\"option\"\n aria-selected={isActive}\n data-command-index={itemIndex}\n initial={noInit ?? { opacity: 0, y: 4 }}\n animate={{ opacity: 1, y: 0 }}\n transition={\n isReduced\n ? noMotionTransition\n : { ...springs.snappy, delay: itemIndex * 0.03 }\n }\n onClick={() => {\n item.onSelect()\n if (variant === 'floating') setOpen(false)\n }}\n onMouseEnter={() => setActiveIndex(itemIndex)}\n className={cn(\n 'flex w-full items-center gap-ds-04 rounded-surface px-ds-03 py-ds-03 text-left transition-[color,background-color] duration-fast-02 ease-productive-standard',\n isActive\n ? 'bg-surface-panel-hover text-surface-fg'\n : 'text-surface-fg-muted hover:bg-surface-panel-hover',\n )}\n >\n {item.icon && (\n <span\n className={cn(\n '[&>svg]:h-ico-sm [&>svg]:w-ico-sm shrink-0 transition-colors duration-fast-02 ease-productive-standard',\n isActive\n ? 'text-accent-11'\n : 'text-surface-fg-subtle',\n )}\n aria-hidden=\"true\"\n >\n <IconProvider size=\"sm\">{normalizeIcon(item.icon)}</IconProvider>\n </span>\n )}\n <div className=\"flex flex-1 flex-col\">\n <span className=\"text-body-md\">\n {item.renderLabel\n ? item.renderLabel(query)\n : item.label}\n </span>\n {item.description && (\n <span className=\"text-body-sm text-surface-fg-subtle\">\n {item.description}\n </span>\n )}\n </div>\n <AnimatePresence>\n {isActive && (\n <motion.span\n initial={isReduced ? undefined : { opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={isReduced ? undefined : { opacity: 0 }}\n transition={tweenFade}\n className=\"inline-flex shrink-0\"\n >\n <Icon\n icon={IconCornerDownLeft}\n size=\"sm\"\n stroke=\"light\"\n className=\"text-surface-fg-subtle\"\n />\n </motion.span>\n )}\n </AnimatePresence>\n </motion.button>\n )\n })}\n </motion.div>\n ))}\n </div>\n )\n }\n\n // =====================================================================\n // Hero Variant\n // =====================================================================\n if (variant === 'hero') {\n return (\n <div\n ref={ref}\n role=\"search\"\n className={cn(\n 'bg-surface-panel rounded-overlay-lg shadow-raised p-ds-07',\n className,\n )}\n {...props}\n >\n {greeting && (\n <p className=\"text-heading-xs text-surface-fg-muted mb-ds-04\">\n {greeting}\n </p>\n )}\n\n {renderInputRow()}\n {renderCommandResults()}\n\n {/* Hints */}\n {hints && hints.length > 0 && (\n <div className=\"mt-ds-04 flex flex-wrap gap-ds-02b\">\n {hints.map((hint) => (\n <button\n key={hint}\n type=\"button\"\n onClick={() => {\n setQuery(hint)\n onSearch?.(hint)\n inputRef.current?.focus()\n }}\n className=\"rounded-control border border-card-strong bg-surface-overlay px-ds-03 py-ds-01 text-body-sm text-surface-fg-subtle transition-colors duration-fast-02 ease-productive-standard hover:bg-surface-panel-hover hover:text-surface-fg-muted\"\n >\n {hint}\n </button>\n ))}\n </div>\n )}\n\n {/* Response area (children) */}\n {children && <div className=\"mt-ds-05\">{children}</div>}\n </div>\n )\n }\n\n // =====================================================================\n // Inline Variant\n // =====================================================================\n if (variant === 'inline') {\n return (\n <div\n ref={ref}\n role=\"search\"\n className={cn('w-full', className)}\n {...props}\n >\n {renderInputRow()}\n {renderCommandResults()}\n {children && <div className=\"mt-ds-03\">{children}</div>}\n </div>\n )\n }\n\n // =====================================================================\n // Floating Variant\n // =====================================================================\n const handleDialogOpenChange = (nextOpen: boolean) => {\n setOpen(nextOpen)\n }\n\n return (\n <Dialog open={open} onOpenChange={handleDialogOpenChange}>\n <DialogPortal>\n <DialogOverlay className=\"fixed inset-0 z-overlay bg-overlay data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\" />\n <DialogContentRaw\n ref={ref}\n {...props}\n className={cn(\n 'fixed left-1/2 top-[20%] z-modal w-full max-w-[560px] -translate-x-1/2',\n 'overflow-hidden rounded-overlay-lg bg-surface-overlay shadow-overlay',\n 'duration-moderate-02 data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\n 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',\n 'data-[state=closed]:slide-out-to-left-1/2 data-[state=open]:slide-in-from-left-1/2',\n 'data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2',\n className,\n )}\n >\n <VisuallyHidden>\n <DialogTitle>AI Command Bar</DialogTitle>\n <DialogDescription>\n Search commands or ask a question\n </DialogDescription>\n </VisuallyHidden>\n\n <div className=\"p-ds-04\">{renderInputRow()}</div>\n\n {renderCommandResults()}\n\n {children && (\n <div className=\"border-t border-surface-border-strong p-ds-04\">\n {children}\n </div>\n )}\n\n {/* Footer hints */}\n <motion.div\n initial={noInit ?? { opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={tweenFade}\n className=\"flex items-center gap-ds-05 border-t border-surface-border-strong px-ds-05 py-ds-03\"\n >\n {hasGroups && (\n <div className=\"flex items-center gap-ds-02b\">\n <div className=\"flex items-center gap-ds-01\">\n <kbd className=\"inline-flex h-ico-md w-ico-md items-center justify-center rounded border border-card-strong bg-surface-panel shadow-kbd\">\n <Icon icon={IconArrowUp} size=\"xs\" className=\"text-surface-fg-subtle\" />\n </kbd>\n <kbd className=\"inline-flex h-ico-md w-ico-md items-center justify-center rounded border border-card-strong bg-surface-panel shadow-kbd\">\n <Icon icon={IconArrowDown} size=\"xs\" className=\"text-surface-fg-subtle\" />\n </kbd>\n </div>\n <span className=\"text-caption text-surface-fg-subtle\">\n Navigate\n </span>\n </div>\n )}\n <div className=\"flex items-center gap-ds-02b\">\n <kbd className=\"inline-flex h-[20px] items-center justify-center rounded-control border border-card-strong bg-surface-panel px-ds-02b shadow-kbd\">\n <Icon icon={IconCornerDownLeft} size=\"xs\" className=\"text-surface-fg-subtle\" />\n </kbd>\n <span className=\"text-caption text-surface-fg-subtle\">\n Submit\n </span>\n </div>\n <div className=\"flex items-center gap-ds-02b\">\n <kbd className=\"inline-flex h-[20px] items-center justify-center rounded-control border border-card-strong bg-surface-panel px-ds-02b text-caption font-medium text-surface-fg-subtle shadow-kbd\">\n Esc\n </kbd>\n <span className=\"text-caption text-surface-fg-subtle\">\n Close\n </span>\n </div>\n </motion.div>\n </DialogContentRaw>\n </DialogPortal>\n </Dialog>\n )\n },\n)\n\nCommandBar.displayName = 'CommandBar'\n\nexport { CommandBar }\nexport type { CommandGroup, CommandItem }\n"],"mappings":"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"}
@@ -62,13 +62,13 @@ function y({ name: t, icon: n }) {
62
62
  }
63
63
  function b({ message: e, reducedMotion: t }) {
64
64
  return t ? /* @__PURE__ */ u("div", {
65
- className: "bg-surface-raised rounded-surface px-ds-05 py-ds-04",
65
+ className: "bg-surface-panel rounded-surface px-ds-05 py-ds-04",
66
66
  children: /* @__PURE__ */ u("p", {
67
67
  className: "text-body-sm text-surface-fg",
68
68
  children: e.content
69
69
  })
70
70
  }) : /* @__PURE__ */ u(_.div, {
71
- className: "bg-surface-raised rounded-surface px-ds-05 py-ds-04",
71
+ className: "bg-surface-panel rounded-surface px-ds-05 py-ds-04",
72
72
  initial: { y: 8 },
73
73
  animate: {
74
74
  opacity: 1,
@@ -1 +1 @@
1
- {"version":3,"file":"conversation.js","names":[],"sources":["../../src/ai/conversation.tsx"],"sourcesContent":["'use client'\n\nimport {\n IconCircle,\n IconCircleCheck,\n IconCircleX,\n IconLoader2,\n} from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { useMotion } from '../motion/motion-provider'\nimport { Icon } from '../ui/icon'\nimport { IconProvider } from '../ui/icon-context'\nimport type { IconInput } from '../ui/lib/icon-input'\nimport { durations,springs } from '../ui/lib/motion'\nimport { normalizeIcon } from '../ui/lib/normalize-icon'\nimport { cn } from '../ui/lib/utils'\nimport { useAICommand } from './ai-command-provider'\nimport { BlockRenderer } from './block-renderer'\nimport type {\n BlockComponentProps,\n ConversationMessage,\n ProcessingStep,\n} from './types'\n\n// ── Props ────────────────────────────────────────────────────────────────────\n\n/**\n * Renders a scrollable AI conversation thread with support for streaming\n * processing steps, custom content blocks, and action confirmations.\n */\nexport interface AIConversationProps {\n /** Ordered conversation messages (user + assistant turns). */\n messages: ConversationMessage[]\n /** When true, shows a live processing indicator below the last message. */\n isProcessing?: boolean\n /** Granular processing steps shown during the processing state. */\n processingSteps?: ProcessingStep[]\n /** Agent identity displayed as a header on assistant messages. */\n agent?: { name: string; icon?: IconInput }\n /** Called when user interacts with an action block (confirm/cancel/undo). */\n onAction?: (actionId: string, type: 'confirm' | 'cancel' | 'undo') => void\n /** Map of custom block type names to React components for extensible rendering. */\n customBlocks?: Record<string, React.ComponentType<BlockComponentProps<any>>>\n /** Max height of the conversation container (CSS value). Enables scrolling. */\n maxHeight?: string | number\n /** Auto-scroll to bottom on new messages. @default true */\n autoScroll?: boolean\n className?: string\n}\n\n// ── Step status icon ─────────────────────────────────────────────────────────\n\nfunction StepStatusIcon({ status }: { status: ProcessingStep['status'] }) {\n switch (status) {\n case 'done':\n return (\n <Icon icon={IconCircleCheck} size=\"sm\" className=\"text-success-11\" />\n )\n case 'active':\n return (\n <motion.span\n className=\"inline-flex items-center justify-center text-accent-9\"\n animate={{ rotate: 360 }}\n transition={{ repeat: Infinity, duration: 1, ease: 'linear' }}\n aria-hidden=\"true\"\n >\n <Icon icon={IconLoader2} size=\"sm\" />\n </motion.span>\n )\n case 'error':\n return (\n <Icon icon={IconCircleX} size=\"sm\" className=\"text-error-11\" />\n )\n case 'pending':\n default:\n return (\n <Icon icon={IconCircle} size=\"sm\" className=\"text-surface-fg-subtle opacity-50\" />\n )\n }\n}\n\n// ── Agent header ─────────────────────────────────────────────────────────────\n\nfunction AgentHeader({\n name,\n icon,\n}: {\n name: string\n icon?: IconInput\n}) {\n return (\n <div className=\"flex items-center gap-ds-02b mb-ds-03\">\n {icon && <span className=\"h-4 w-4 flex items-center justify-center\"><IconProvider size=\"sm\">{normalizeIcon(icon)}</IconProvider></span>}\n <span className=\"text-label-xs font-semibold uppercase tracking-wider text-surface-fg-subtle\">\n {name}\n </span>\n </div>\n )\n}\n\n// ── User message ─────────────────────────────────────────────────────────────\n\nfunction UserMessage({\n message,\n reducedMotion,\n}: {\n message: ConversationMessage\n reducedMotion: boolean\n}) {\n if (reducedMotion) {\n return (\n <div className=\"bg-surface-raised rounded-surface px-ds-05 py-ds-04\">\n <p className=\"text-body-sm text-surface-fg\">{message.content}</p>\n </div>\n )\n }\n\n return (\n <motion.div\n className=\"bg-surface-raised rounded-surface px-ds-05 py-ds-04\"\n initial={{ y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n transition={springs.snappy}\n >\n <p className=\"text-body-sm text-surface-fg\">{message.content}</p>\n </motion.div>\n )\n}\n\n// ── Assistant message ────────────────────────────────────────────────────────\n\nfunction AssistantMessage({\n message,\n agent,\n onAction,\n customBlocks,\n}: {\n message: ConversationMessage\n agent: { name: string; icon?: IconInput }\n onAction?: (actionId: string, type: 'confirm' | 'cancel' | 'undo') => void\n customBlocks?: Record<string, React.ComponentType<BlockComponentProps<any>>>\n}) {\n return (\n <div>\n <AgentHeader name={agent.name} icon={agent.icon} />\n {message.blocks && (\n <BlockRenderer\n blocks={message.blocks}\n onAction={onAction}\n customBlocks={customBlocks}\n />\n )}\n </div>\n )\n}\n\n// ── Processing indicator ─────────────────────────────────────────────────────\n\nfunction ProcessingIndicator({\n steps,\n agent,\n reducedMotion,\n}: {\n steps?: ProcessingStep[]\n agent: { name: string; icon?: IconInput }\n reducedMotion: boolean\n}) {\n // Step visualization\n if (steps && steps.length > 0) {\n return (\n <div role=\"status\" aria-busy=\"true\" aria-label=\"Processing\">\n <AgentHeader name={agent.name} icon={agent.icon} />\n <div className=\"flex flex-col gap-ds-02b\">\n {steps.map((step, i) => (\n <motion.div\n key={step.id}\n className=\"flex items-center gap-ds-02b\"\n initial={reducedMotion ? undefined : { opacity: 0, x: -8 }}\n animate={{ opacity: 1, x: 0 }}\n transition={\n reducedMotion\n ? { duration: 0 }\n : { duration: durations.moderate01b, delay: i * 0.05 }\n }\n >\n <StepStatusIcon status={step.status} />\n <span\n className={cn(\n 'text-body-sm',\n step.status === 'pending'\n ? 'text-surface-fg-subtle'\n : 'text-surface-fg',\n )}\n >\n {step.label}\n </span>\n </motion.div>\n ))}\n </div>\n </div>\n )\n }\n\n // Breathing dots\n return (\n <div\n role=\"status\"\n aria-busy=\"true\"\n aria-label={`${agent.name} is processing`}\n >\n <AgentHeader name={agent.name} icon={agent.icon} />\n <div className=\"flex items-center gap-ds-02\">\n {!reducedMotion &&\n [0, 1, 2].map((i) => (\n <motion.span\n key={i}\n className=\"h-1.5 w-1.5 rounded-pill bg-accent-9\"\n animate={{ scale: [1, 1.4, 1], opacity: [0.4, 1, 0.4] }}\n transition={{\n duration: 1.4,\n repeat: Infinity,\n ease: 'easeInOut',\n delay: i * 0.16,\n }}\n />\n ))}\n <span className=\"ml-ds-02b text-body-sm text-surface-fg-subtle\">\n {agent.name} is thinking...\n </span>\n </div>\n </div>\n )\n}\n\n// ── Scroll-to-bottom pill ────────────────────────────────────────────────────\n\nfunction ScrollToBottomPill({ onClick }: { onClick: () => void }) {\n return (\n <motion.button\n role=\"button\"\n aria-label=\"Scroll to latest response\"\n className=\"absolute bottom-ds-04 left-1/2 -translate-x-1/2 z-10 flex items-center gap-ds-02 bg-accent-9 text-accent-fg text-body-xs font-medium rounded-pill px-ds-04 py-ds-02 shadow-floating\"\n initial={{ y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 8 }}\n transition={springs.snappy}\n onClick={onClick}\n >\n {'\\u2193'} New response\n </motion.button>\n )\n}\n\n// ── Default agent ────────────────────────────────────────────────────────────\n\nconst DEFAULT_AGENT = { name: 'Assistant' }\n\n// ── AIConversation ───────────────────────────────────────────────────────────\n\nconst AIConversation = React.forwardRef<HTMLDivElement, AIConversationProps>(({\n messages,\n isProcessing = false,\n processingSteps,\n agent: agentProp,\n onAction,\n customBlocks,\n maxHeight,\n autoScroll = true,\n className,\n}, ref) => {\n const ctx = useAICommand()\n const { reducedMotion } = useMotion()\n\n // Resolve agent: prop > context > default\n const agent = agentProp ?? ctx?.agent ?? DEFAULT_AGENT\n\n // Resolve onAction: prop > context\n const resolvedOnAction = onAction ?? ctx?.onAction\n\n // Resolve customBlocks: prop wins over context\n const resolvedCustomBlocks = React.useMemo(() => {\n const contextBlocks = ctx?.customBlocks ?? {}\n return { ...contextBlocks, ...customBlocks }\n }, [ctx?.customBlocks, customBlocks])\n\n // ── Auto-scroll ──────────────────────────────────────────────────────────\n\n const scrollRef = React.useRef<HTMLDivElement>(null)\n const sentinelRef = React.useRef<HTMLDivElement>(null)\n const [isAtBottom, setIsAtBottom] = React.useState(true)\n const [hasNewContent, setHasNewContent] = React.useState(false)\n const prevMessageCount = React.useRef(messages.length)\n const prevProcessing = React.useRef(isProcessing)\n\n // Track if user is scrolled to bottom via IntersectionObserver\n React.useEffect(() => {\n if (!autoScroll || !sentinelRef.current) return\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n setIsAtBottom(entry.isIntersecting)\n if (entry.isIntersecting) {\n setHasNewContent(false)\n }\n },\n {\n root: scrollRef.current,\n threshold: 0.1,\n },\n )\n\n observer.observe(sentinelRef.current)\n return () => observer.disconnect()\n }, [autoScroll])\n\n // Auto-scroll or show pill on new content\n React.useEffect(() => {\n const contentChanged =\n messages.length !== prevMessageCount.current ||\n isProcessing !== prevProcessing.current\n\n prevMessageCount.current = messages.length\n prevProcessing.current = isProcessing\n\n if (!contentChanged || !autoScroll) return\n\n if (isAtBottom) {\n sentinelRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' })\n } else {\n setHasNewContent(true)\n }\n }, [messages, isProcessing, isAtBottom, autoScroll])\n\n const scrollToBottom = React.useCallback(() => {\n sentinelRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' })\n setIsAtBottom(true)\n setHasNewContent(false)\n }, [])\n\n // ── Render ───────────────────────────────────────────────────────────────\n\n return (\n <div ref={ref} className={cn('relative', className)}>\n <div\n ref={scrollRef}\n className=\"flex flex-col gap-ds-05 overflow-y-auto\"\n aria-live=\"polite\"\n style={\n maxHeight\n ? {\n maxHeight:\n typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight,\n }\n : undefined\n }\n >\n {messages.map((msg) =>\n msg.role === 'user' ? (\n <UserMessage\n key={msg.id}\n message={msg}\n reducedMotion={reducedMotion}\n />\n ) : (\n <AssistantMessage\n key={msg.id}\n message={msg}\n agent={agent}\n onAction={resolvedOnAction}\n customBlocks={resolvedCustomBlocks}\n />\n ),\n )}\n {isProcessing && (\n <ProcessingIndicator\n steps={processingSteps}\n agent={agent}\n reducedMotion={reducedMotion}\n />\n )}\n <div ref={sentinelRef} className=\"h-px\" />\n </div>\n <AnimatePresence>\n {!isAtBottom && hasNewContent && (\n <ScrollToBottomPill onClick={scrollToBottom} />\n )}\n </AnimatePresence>\n </div>\n )\n})\nAIConversation.displayName = 'AIConversation'\n\nexport { AIConversation }\n"],"mappings":"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"}
1
+ {"version":3,"file":"conversation.js","names":[],"sources":["../../src/ai/conversation.tsx"],"sourcesContent":["'use client'\n\nimport {\n IconCircle,\n IconCircleCheck,\n IconCircleX,\n IconLoader2,\n} from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { useMotion } from '../motion/motion-provider'\nimport { Icon } from '../ui/icon'\nimport { IconProvider } from '../ui/icon-context'\nimport type { IconInput } from '../ui/lib/icon-input'\nimport { durations,springs } from '../ui/lib/motion'\nimport { normalizeIcon } from '../ui/lib/normalize-icon'\nimport { cn } from '../ui/lib/utils'\nimport { useAICommand } from './ai-command-provider'\nimport { BlockRenderer } from './block-renderer'\nimport type {\n BlockComponentProps,\n ConversationMessage,\n ProcessingStep,\n} from './types'\n\n// ── Props ────────────────────────────────────────────────────────────────────\n\n/**\n * Renders a scrollable AI conversation thread with support for streaming\n * processing steps, custom content blocks, and action confirmations.\n */\nexport interface AIConversationProps {\n /** Ordered conversation messages (user + assistant turns). */\n messages: ConversationMessage[]\n /** When true, shows a live processing indicator below the last message. */\n isProcessing?: boolean\n /** Granular processing steps shown during the processing state. */\n processingSteps?: ProcessingStep[]\n /** Agent identity displayed as a header on assistant messages. */\n agent?: { name: string; icon?: IconInput }\n /** Called when user interacts with an action block (confirm/cancel/undo). */\n onAction?: (actionId: string, type: 'confirm' | 'cancel' | 'undo') => void\n /** Map of custom block type names to React components for extensible rendering. */\n customBlocks?: Record<string, React.ComponentType<BlockComponentProps<any>>>\n /** Max height of the conversation container (CSS value). Enables scrolling. */\n maxHeight?: string | number\n /** Auto-scroll to bottom on new messages. @default true */\n autoScroll?: boolean\n className?: string\n}\n\n// ── Step status icon ─────────────────────────────────────────────────────────\n\nfunction StepStatusIcon({ status }: { status: ProcessingStep['status'] }) {\n switch (status) {\n case 'done':\n return (\n <Icon icon={IconCircleCheck} size=\"sm\" className=\"text-success-11\" />\n )\n case 'active':\n return (\n <motion.span\n className=\"inline-flex items-center justify-center text-accent-9\"\n animate={{ rotate: 360 }}\n transition={{ repeat: Infinity, duration: 1, ease: 'linear' }}\n aria-hidden=\"true\"\n >\n <Icon icon={IconLoader2} size=\"sm\" />\n </motion.span>\n )\n case 'error':\n return (\n <Icon icon={IconCircleX} size=\"sm\" className=\"text-error-11\" />\n )\n case 'pending':\n default:\n return (\n <Icon icon={IconCircle} size=\"sm\" className=\"text-surface-fg-subtle opacity-50\" />\n )\n }\n}\n\n// ── Agent header ─────────────────────────────────────────────────────────────\n\nfunction AgentHeader({\n name,\n icon,\n}: {\n name: string\n icon?: IconInput\n}) {\n return (\n <div className=\"flex items-center gap-ds-02b mb-ds-03\">\n {icon && <span className=\"h-4 w-4 flex items-center justify-center\"><IconProvider size=\"sm\">{normalizeIcon(icon)}</IconProvider></span>}\n <span className=\"text-label-xs font-semibold uppercase tracking-wider text-surface-fg-subtle\">\n {name}\n </span>\n </div>\n )\n}\n\n// ── User message ─────────────────────────────────────────────────────────────\n\nfunction UserMessage({\n message,\n reducedMotion,\n}: {\n message: ConversationMessage\n reducedMotion: boolean\n}) {\n if (reducedMotion) {\n return (\n <div className=\"bg-surface-panel rounded-surface px-ds-05 py-ds-04\">\n <p className=\"text-body-sm text-surface-fg\">{message.content}</p>\n </div>\n )\n }\n\n return (\n <motion.div\n className=\"bg-surface-panel rounded-surface px-ds-05 py-ds-04\"\n initial={{ y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n transition={springs.snappy}\n >\n <p className=\"text-body-sm text-surface-fg\">{message.content}</p>\n </motion.div>\n )\n}\n\n// ── Assistant message ────────────────────────────────────────────────────────\n\nfunction AssistantMessage({\n message,\n agent,\n onAction,\n customBlocks,\n}: {\n message: ConversationMessage\n agent: { name: string; icon?: IconInput }\n onAction?: (actionId: string, type: 'confirm' | 'cancel' | 'undo') => void\n customBlocks?: Record<string, React.ComponentType<BlockComponentProps<any>>>\n}) {\n return (\n <div>\n <AgentHeader name={agent.name} icon={agent.icon} />\n {message.blocks && (\n <BlockRenderer\n blocks={message.blocks}\n onAction={onAction}\n customBlocks={customBlocks}\n />\n )}\n </div>\n )\n}\n\n// ── Processing indicator ─────────────────────────────────────────────────────\n\nfunction ProcessingIndicator({\n steps,\n agent,\n reducedMotion,\n}: {\n steps?: ProcessingStep[]\n agent: { name: string; icon?: IconInput }\n reducedMotion: boolean\n}) {\n // Step visualization\n if (steps && steps.length > 0) {\n return (\n <div role=\"status\" aria-busy=\"true\" aria-label=\"Processing\">\n <AgentHeader name={agent.name} icon={agent.icon} />\n <div className=\"flex flex-col gap-ds-02b\">\n {steps.map((step, i) => (\n <motion.div\n key={step.id}\n className=\"flex items-center gap-ds-02b\"\n initial={reducedMotion ? undefined : { opacity: 0, x: -8 }}\n animate={{ opacity: 1, x: 0 }}\n transition={\n reducedMotion\n ? { duration: 0 }\n : { duration: durations.moderate01b, delay: i * 0.05 }\n }\n >\n <StepStatusIcon status={step.status} />\n <span\n className={cn(\n 'text-body-sm',\n step.status === 'pending'\n ? 'text-surface-fg-subtle'\n : 'text-surface-fg',\n )}\n >\n {step.label}\n </span>\n </motion.div>\n ))}\n </div>\n </div>\n )\n }\n\n // Breathing dots\n return (\n <div\n role=\"status\"\n aria-busy=\"true\"\n aria-label={`${agent.name} is processing`}\n >\n <AgentHeader name={agent.name} icon={agent.icon} />\n <div className=\"flex items-center gap-ds-02\">\n {!reducedMotion &&\n [0, 1, 2].map((i) => (\n <motion.span\n key={i}\n className=\"h-1.5 w-1.5 rounded-pill bg-accent-9\"\n animate={{ scale: [1, 1.4, 1], opacity: [0.4, 1, 0.4] }}\n transition={{\n duration: 1.4,\n repeat: Infinity,\n ease: 'easeInOut',\n delay: i * 0.16,\n }}\n />\n ))}\n <span className=\"ml-ds-02b text-body-sm text-surface-fg-subtle\">\n {agent.name} is thinking...\n </span>\n </div>\n </div>\n )\n}\n\n// ── Scroll-to-bottom pill ────────────────────────────────────────────────────\n\nfunction ScrollToBottomPill({ onClick }: { onClick: () => void }) {\n return (\n <motion.button\n role=\"button\"\n aria-label=\"Scroll to latest response\"\n className=\"absolute bottom-ds-04 left-1/2 -translate-x-1/2 z-10 flex items-center gap-ds-02 bg-accent-9 text-accent-fg text-body-xs font-medium rounded-pill px-ds-04 py-ds-02 shadow-floating\"\n initial={{ y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 8 }}\n transition={springs.snappy}\n onClick={onClick}\n >\n {'\\u2193'} New response\n </motion.button>\n )\n}\n\n// ── Default agent ────────────────────────────────────────────────────────────\n\nconst DEFAULT_AGENT = { name: 'Assistant' }\n\n// ── AIConversation ───────────────────────────────────────────────────────────\n\nconst AIConversation = React.forwardRef<HTMLDivElement, AIConversationProps>(({\n messages,\n isProcessing = false,\n processingSteps,\n agent: agentProp,\n onAction,\n customBlocks,\n maxHeight,\n autoScroll = true,\n className,\n}, ref) => {\n const ctx = useAICommand()\n const { reducedMotion } = useMotion()\n\n // Resolve agent: prop > context > default\n const agent = agentProp ?? ctx?.agent ?? DEFAULT_AGENT\n\n // Resolve onAction: prop > context\n const resolvedOnAction = onAction ?? ctx?.onAction\n\n // Resolve customBlocks: prop wins over context\n const resolvedCustomBlocks = React.useMemo(() => {\n const contextBlocks = ctx?.customBlocks ?? {}\n return { ...contextBlocks, ...customBlocks }\n }, [ctx?.customBlocks, customBlocks])\n\n // ── Auto-scroll ──────────────────────────────────────────────────────────\n\n const scrollRef = React.useRef<HTMLDivElement>(null)\n const sentinelRef = React.useRef<HTMLDivElement>(null)\n const [isAtBottom, setIsAtBottom] = React.useState(true)\n const [hasNewContent, setHasNewContent] = React.useState(false)\n const prevMessageCount = React.useRef(messages.length)\n const prevProcessing = React.useRef(isProcessing)\n\n // Track if user is scrolled to bottom via IntersectionObserver\n React.useEffect(() => {\n if (!autoScroll || !sentinelRef.current) return\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n setIsAtBottom(entry.isIntersecting)\n if (entry.isIntersecting) {\n setHasNewContent(false)\n }\n },\n {\n root: scrollRef.current,\n threshold: 0.1,\n },\n )\n\n observer.observe(sentinelRef.current)\n return () => observer.disconnect()\n }, [autoScroll])\n\n // Auto-scroll or show pill on new content\n React.useEffect(() => {\n const contentChanged =\n messages.length !== prevMessageCount.current ||\n isProcessing !== prevProcessing.current\n\n prevMessageCount.current = messages.length\n prevProcessing.current = isProcessing\n\n if (!contentChanged || !autoScroll) return\n\n if (isAtBottom) {\n sentinelRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' })\n } else {\n setHasNewContent(true)\n }\n }, [messages, isProcessing, isAtBottom, autoScroll])\n\n const scrollToBottom = React.useCallback(() => {\n sentinelRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' })\n setIsAtBottom(true)\n setHasNewContent(false)\n }, [])\n\n // ── Render ───────────────────────────────────────────────────────────────\n\n return (\n <div ref={ref} className={cn('relative', className)}>\n <div\n ref={scrollRef}\n className=\"flex flex-col gap-ds-05 overflow-y-auto\"\n aria-live=\"polite\"\n style={\n maxHeight\n ? {\n maxHeight:\n typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight,\n }\n : undefined\n }\n >\n {messages.map((msg) =>\n msg.role === 'user' ? (\n <UserMessage\n key={msg.id}\n message={msg}\n reducedMotion={reducedMotion}\n />\n ) : (\n <AssistantMessage\n key={msg.id}\n message={msg}\n agent={agent}\n onAction={resolvedOnAction}\n customBlocks={resolvedCustomBlocks}\n />\n ),\n )}\n {isProcessing && (\n <ProcessingIndicator\n steps={processingSteps}\n agent={agent}\n reducedMotion={reducedMotion}\n />\n )}\n <div ref={sentinelRef} className=\"h-px\" />\n </div>\n <AnimatePresence>\n {!isAtBottom && hasNewContent && (\n <ScrollToBottomPill onClick={scrollToBottom} />\n )}\n </AnimatePresence>\n </div>\n )\n})\nAIConversation.displayName = 'AIConversation'\n\nexport { AIConversation }\n"],"mappings":"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"}
@@ -100,7 +100,7 @@ function y({ item: e, compact: r }) {
100
100
  }),
101
101
  e.detail ? /* @__PURE__ */ d("button", {
102
102
  type: "button",
103
- className: "rounded-control text-surface-fg-muted hover:underline hover:bg-surface-raised focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9",
103
+ className: "rounded-control text-surface-fg-muted hover:underline hover:bg-surface-panel-hover focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9",
104
104
  onClick: () => {
105
105
  e.detail && m((e) => !e);
106
106
  },
@@ -1 +1 @@
1
- {"version":3,"file":"activity-feed.js","names":[],"sources":["../../src/composed/activity-feed.tsx"],"sourcesContent":["'use client'\n\nimport { IconChevronRight } from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Avatar, AvatarFallback, AvatarImage } from '../ui/avatar'\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { formatRelativeTime } from '../ui/lib/date-utils'\nimport type { IconInput } from '../ui/lib/icon-input'\nimport { tweens } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\nimport { Skeleton } from '../ui/skeleton'\nimport { getInitials } from './lib/string-utils'\n\nexport interface ActivityItem {\n id: string\n actor?: { name: string; image?: string }\n action: string | React.ReactNode\n timestamp: Date | string\n icon?: IconInput\n color?: 'default' | 'success' | 'warning' | 'error' | 'info'\n detail?: React.ReactNode\n}\n\nexport interface GroupLabels {\n today?: string\n yesterday?: string\n thisWeek?: string\n older?: string\n}\n\nexport interface ActivityFeedProps extends React.HTMLAttributes<HTMLDivElement> {\n items: ActivityItem[]\n onLoadMore?: () => void\n loading?: boolean\n hasMore?: boolean\n emptyState?: React.ReactNode\n compact?: boolean\n maxInitialItems?: number\n groupBy?: 'time' | 'none'\n groupLabels?: GroupLabels\n /** Custom renderer per item. Return ReactNode for custom rendering, undefined to use default ActivityEntry. */\n renderItem?: (item: ActivityItem, index: number) => React.ReactNode | undefined\n}\n\nconst dotColorMap = {\n default: 'bg-surface-fg-subtle',\n success: 'bg-success-9',\n warning: 'bg-warning-9',\n error: 'bg-error-9',\n info: 'bg-info-9',\n} as const\n\nconst DEFAULT_GROUP_LABELS: Required<GroupLabels> = {\n today: 'Today',\n yesterday: 'Yesterday',\n thisWeek: 'This Week',\n older: 'Older',\n}\n\n/**\n * Buckets items into time-based groups: today, yesterday, earlier this week (since Monday), older.\n * Skips empty groups.\n */\nexport function groupItemsByTime(\n items: ActivityItem[],\n labels?: GroupLabels,\n): { label: string; items: ActivityItem[] }[] {\n const l = { ...DEFAULT_GROUP_LABELS, ...labels }\n const now = new Date()\n\n // Start of today (midnight local)\n const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n // Start of yesterday\n const startOfYesterday = new Date(startOfToday.getTime() - 24 * 60 * 60 * 1000)\n // Start of this week (Monday)\n const dayOfWeek = now.getDay() // 0=Sun, 1=Mon, ...\n const daysSinceMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1\n const startOfWeek = new Date(\n startOfToday.getTime() - daysSinceMonday * 24 * 60 * 60 * 1000,\n )\n\n const today: ActivityItem[] = []\n const yesterday: ActivityItem[] = []\n const thisWeek: ActivityItem[] = []\n const older: ActivityItem[] = []\n\n for (const item of items) {\n const ts =\n typeof item.timestamp === 'string'\n ? new Date(item.timestamp)\n : item.timestamp\n const t = ts.getTime()\n\n if (t >= startOfToday.getTime()) {\n today.push(item)\n } else if (t >= startOfYesterday.getTime()) {\n yesterday.push(item)\n } else if (t >= startOfWeek.getTime()) {\n thisWeek.push(item)\n } else {\n older.push(item)\n }\n }\n\n const groups: { label: string; items: ActivityItem[] }[] = []\n if (today.length > 0) groups.push({ label: l.today, items: today })\n if (yesterday.length > 0) groups.push({ label: l.yesterday, items: yesterday })\n if (thisWeek.length > 0) groups.push({ label: l.thisWeek, items: thisWeek })\n if (older.length > 0) groups.push({ label: l.older, items: older })\n\n return groups\n}\n\nfunction LoadingSkeleton({ compact }: { compact: boolean }) {\n return (\n <div className={cn('flex flex-col', compact ? 'gap-1' : 'gap-3')}>\n {[0, 1, 2].map((i) => (\n <div key={i} className=\"flex items-center gap-ds-03\">\n <Skeleton className=\"h-2 w-2 shrink-0 rounded-pill\" />\n {!compact && <Skeleton className=\"h-4 w-4 shrink-0 rounded-pill\" />}\n <Skeleton className={cn('flex-1', compact ? 'h-3' : 'h-4')} />\n <Skeleton className=\"h-3 w-12\" />\n </div>\n ))}\n </div>\n )\n}\n\nfunction ActivityEntry({\n item,\n compact,\n}: {\n item: ActivityItem\n compact: boolean\n}) {\n const [expandedDetail, setExpandedDetail] = React.useState(false)\n const detailId = React.useId()\n const color = item.color ?? 'default'\n\n const handleActionClick = () => {\n if (item.detail) {\n setExpandedDetail((prev) => !prev)\n }\n }\n\n return (\n <div className={cn('relative flex items-start', compact ? 'gap-ds-02' : 'gap-ds-03')}>\n {/* Dot */}\n <div\n className={cn(\n 'relative z-10 mt-1.5 h-2 w-2 shrink-0 rounded-pill ring-2 ring-surface-base',\n dotColorMap[color],\n )}\n />\n\n {/* Avatar (non-compact only) */}\n {!compact && item.actor && (\n <Avatar className=\"h-5 w-5 shrink-0 text-[9px]\">\n {item.actor.image && (\n <AvatarImage src={item.actor.image} alt={item.actor.name} />\n )}\n <AvatarFallback className=\"text-[9px]\">\n {getInitials(item.actor.name)}\n </AvatarFallback>\n </Avatar>\n )}\n\n {/* Content */}\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <div className=\"flex items-center justify-between gap-ds-02\">\n <div className={cn('min-w-0 flex-1 flex items-center gap-ds-01 flex-wrap', compact ? 'text-body-xs' : 'text-body-sm')}>\n {item.actor && (\n <span className=\"font-medium text-surface-fg\">{item.actor.name} </span>\n )}\n {item.detail && (\n <Icon icon={IconChevronRight} size=\"xs\" className={cn('shrink-0 text-surface-fg-subtle transition-transform duration-fast-02 ease-productive-standard', expandedDetail && 'rotate-90')} />\n )}\n {item.detail ? (\n <button\n type=\"button\"\n className=\"rounded-control text-surface-fg-muted hover:underline hover:bg-surface-raised focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9\"\n onClick={handleActionClick}\n aria-expanded={expandedDetail}\n aria-controls={detailId}\n >\n {item.action}\n </button>\n ) : (\n <span className=\"text-surface-fg-muted\">{item.action}</span>\n )}\n </div>\n\n <time\n className={cn(\n 'shrink-0 whitespace-nowrap text-surface-fg-subtle',\n compact ? 'text-body-xs' : 'text-body-sm',\n )}\n dateTime={\n (typeof item.timestamp === 'string'\n ? new Date(item.timestamp)\n : item.timestamp\n ).toISOString()\n }\n >\n {formatRelativeTime(item.timestamp)}\n </time>\n </div>\n\n {/* Expandable detail */}\n {expandedDetail && item.detail && (\n <div id={detailId} className=\"mt-ds-02 animate-in fade-in slide-in-from-top-1 text-body-sm text-surface-fg-muted\">\n {item.detail}\n </div>\n )}\n </div>\n </div>\n )\n}\n\n/**\n * Wraps custom renderItem content with the same dot + layout container\n * that ActivityEntry uses, keeping vertical rhythm consistent.\n */\nfunction CustomEntry({\n item,\n compact,\n children,\n}: {\n item: ActivityItem\n compact: boolean\n children: React.ReactNode\n}) {\n const color = item.color ?? 'default'\n return (\n <div className={cn('relative flex items-start', compact ? 'gap-ds-02' : 'gap-ds-03')}>\n {/* Dot — same as ActivityEntry */}\n <div\n className={cn(\n 'relative z-10 mt-1.5 h-2 w-2 shrink-0 rounded-pill ring-2 ring-surface-base',\n dotColorMap[color],\n )}\n />\n {/* Custom content in the same flex slot as ActivityEntry's content area */}\n <div className=\"min-w-0 flex-1\">{children}</div>\n </div>\n )\n}\n\nfunction GroupHeader({\n label,\n isFirst,\n}: {\n label: string\n isFirst: boolean\n}) {\n return (\n <motion.div\n initial={false}\n animate={{ opacity: 1 }}\n transition={tweens.fade}\n className={cn(\n 'flex items-center gap-ds-03',\n !isFirst ? 'mt-ds-06' : '',\n 'mb-ds-03',\n )}\n >\n <hr className=\"flex-1 border-surface-border\" />\n <span className=\"bg-surface-base px-ds-03 text-label-xs font-medium uppercase tracking-wider text-surface-fg-subtle\">\n {label}\n </span>\n <hr className=\"flex-1 border-surface-border\" />\n </motion.div>\n )\n}\n\nconst ActivityFeed = React.forwardRef<HTMLDivElement, ActivityFeedProps>(\n (\n {\n items,\n onLoadMore,\n loading = false,\n hasMore = false,\n emptyState,\n compact = false,\n maxInitialItems,\n groupBy = 'none',\n groupLabels,\n renderItem,\n className,\n ...props\n },\n ref,\n ) => {\n const [showAll, setShowAll] = React.useState(false)\n\n if (loading) {\n return (\n <div ref={ref} className={cn('relative', className)} {...props}>\n <LoadingSkeleton compact={compact} />\n </div>\n )\n }\n\n if (items.length === 0) {\n if (emptyState) {\n return (\n <div ref={ref} className={className} {...props}>\n {emptyState}\n </div>\n )\n }\n return null\n }\n\n // maxInitialItems applies to the flat list BEFORE grouping\n const truncated =\n maxInitialItems != null && !showAll && items.length > maxInitialItems\n const visibleItems = truncated ? items.slice(0, maxInitialItems) : items\n\n const useGrouping = groupBy === 'time'\n\n /** Resolve what to render for a single item, respecting renderItem. */\n const resolveEntry = (item: ActivityItem, index: number) => {\n if (renderItem) {\n const custom = renderItem(item, index)\n if (custom !== undefined) {\n return <CustomEntry item={item} compact={compact}>{custom}</CustomEntry>\n }\n }\n return <ActivityEntry item={item} compact={compact} />\n }\n\n return (\n <div ref={ref} className={cn('relative', className)} {...props}>\n {/* Items */}\n {useGrouping ? (\n <div>\n {groupItemsByTime(visibleItems, groupLabels).map((group, gi) => (\n <div key={group.label}>\n <GroupHeader label={group.label} isFirst={gi === 0} />\n {/* Timeline line scoped to this group */}\n <div className={cn('relative flex flex-col', compact ? 'gap-1' : 'gap-3')}>\n <div className=\"absolute bottom-0 left-[3px] top-0 w-px bg-surface-border\" />\n {group.items.map((item, index) => (\n <motion.div key={item.id} initial={false} animate={{ opacity: 1 }} transition={{ ...tweens.fade, delay: index * 0.03 }}>\n {resolveEntry(item, index)}\n </motion.div>\n ))}\n </div>\n </div>\n ))}\n </div>\n ) : (\n <div className={cn('relative flex flex-col', compact ? 'gap-1' : 'gap-3')}>\n {/* Timeline line */}\n <div className=\"absolute bottom-0 left-[3px] top-0 w-px bg-surface-border\" />\n {visibleItems.map((item, index) => (\n <motion.div key={item.id} initial={false} animate={{ opacity: 1 }} transition={{ ...tweens.fade, delay: index * 0.03 }}>\n {resolveEntry(item, index)}\n </motion.div>\n ))}\n </div>\n )}\n\n {/* Show all button */}\n {truncated && (\n <div className=\"relative mt-ds-03 flex justify-center\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setShowAll(true)}\n >\n Show all ({items.length})\n </Button>\n </div>\n )}\n\n {/* Load more button */}\n {hasMore && onLoadMore && !truncated && (\n <div className=\"relative mt-ds-03 flex justify-center\">\n <Button variant=\"ghost\" size=\"sm\" onClick={onLoadMore}>\n Load more\n </Button>\n </div>\n )}\n </div>\n )\n },\n)\nActivityFeed.displayName = 'ActivityFeed'\n\nexport { ActivityFeed }\n"],"mappings":"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"}
1
+ {"version":3,"file":"activity-feed.js","names":[],"sources":["../../src/composed/activity-feed.tsx"],"sourcesContent":["'use client'\n\nimport { IconChevronRight } from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Avatar, AvatarFallback, AvatarImage } from '../ui/avatar'\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { formatRelativeTime } from '../ui/lib/date-utils'\nimport type { IconInput } from '../ui/lib/icon-input'\nimport { tweens } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\nimport { Skeleton } from '../ui/skeleton'\nimport { getInitials } from './lib/string-utils'\n\nexport interface ActivityItem {\n id: string\n actor?: { name: string; image?: string }\n action: string | React.ReactNode\n timestamp: Date | string\n icon?: IconInput\n color?: 'default' | 'success' | 'warning' | 'error' | 'info'\n detail?: React.ReactNode\n}\n\nexport interface GroupLabels {\n today?: string\n yesterday?: string\n thisWeek?: string\n older?: string\n}\n\nexport interface ActivityFeedProps extends React.HTMLAttributes<HTMLDivElement> {\n items: ActivityItem[]\n onLoadMore?: () => void\n loading?: boolean\n hasMore?: boolean\n emptyState?: React.ReactNode\n compact?: boolean\n maxInitialItems?: number\n groupBy?: 'time' | 'none'\n groupLabels?: GroupLabels\n /** Custom renderer per item. Return ReactNode for custom rendering, undefined to use default ActivityEntry. */\n renderItem?: (item: ActivityItem, index: number) => React.ReactNode | undefined\n}\n\nconst dotColorMap = {\n default: 'bg-surface-fg-subtle',\n success: 'bg-success-9',\n warning: 'bg-warning-9',\n error: 'bg-error-9',\n info: 'bg-info-9',\n} as const\n\nconst DEFAULT_GROUP_LABELS: Required<GroupLabels> = {\n today: 'Today',\n yesterday: 'Yesterday',\n thisWeek: 'This Week',\n older: 'Older',\n}\n\n/**\n * Buckets items into time-based groups: today, yesterday, earlier this week (since Monday), older.\n * Skips empty groups.\n */\nexport function groupItemsByTime(\n items: ActivityItem[],\n labels?: GroupLabels,\n): { label: string; items: ActivityItem[] }[] {\n const l = { ...DEFAULT_GROUP_LABELS, ...labels }\n const now = new Date()\n\n // Start of today (midnight local)\n const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n // Start of yesterday\n const startOfYesterday = new Date(startOfToday.getTime() - 24 * 60 * 60 * 1000)\n // Start of this week (Monday)\n const dayOfWeek = now.getDay() // 0=Sun, 1=Mon, ...\n const daysSinceMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1\n const startOfWeek = new Date(\n startOfToday.getTime() - daysSinceMonday * 24 * 60 * 60 * 1000,\n )\n\n const today: ActivityItem[] = []\n const yesterday: ActivityItem[] = []\n const thisWeek: ActivityItem[] = []\n const older: ActivityItem[] = []\n\n for (const item of items) {\n const ts =\n typeof item.timestamp === 'string'\n ? new Date(item.timestamp)\n : item.timestamp\n const t = ts.getTime()\n\n if (t >= startOfToday.getTime()) {\n today.push(item)\n } else if (t >= startOfYesterday.getTime()) {\n yesterday.push(item)\n } else if (t >= startOfWeek.getTime()) {\n thisWeek.push(item)\n } else {\n older.push(item)\n }\n }\n\n const groups: { label: string; items: ActivityItem[] }[] = []\n if (today.length > 0) groups.push({ label: l.today, items: today })\n if (yesterday.length > 0) groups.push({ label: l.yesterday, items: yesterday })\n if (thisWeek.length > 0) groups.push({ label: l.thisWeek, items: thisWeek })\n if (older.length > 0) groups.push({ label: l.older, items: older })\n\n return groups\n}\n\nfunction LoadingSkeleton({ compact }: { compact: boolean }) {\n return (\n <div className={cn('flex flex-col', compact ? 'gap-1' : 'gap-3')}>\n {[0, 1, 2].map((i) => (\n <div key={i} className=\"flex items-center gap-ds-03\">\n <Skeleton className=\"h-2 w-2 shrink-0 rounded-pill\" />\n {!compact && <Skeleton className=\"h-4 w-4 shrink-0 rounded-pill\" />}\n <Skeleton className={cn('flex-1', compact ? 'h-3' : 'h-4')} />\n <Skeleton className=\"h-3 w-12\" />\n </div>\n ))}\n </div>\n )\n}\n\nfunction ActivityEntry({\n item,\n compact,\n}: {\n item: ActivityItem\n compact: boolean\n}) {\n const [expandedDetail, setExpandedDetail] = React.useState(false)\n const detailId = React.useId()\n const color = item.color ?? 'default'\n\n const handleActionClick = () => {\n if (item.detail) {\n setExpandedDetail((prev) => !prev)\n }\n }\n\n return (\n <div className={cn('relative flex items-start', compact ? 'gap-ds-02' : 'gap-ds-03')}>\n {/* Dot */}\n <div\n className={cn(\n 'relative z-10 mt-1.5 h-2 w-2 shrink-0 rounded-pill ring-2 ring-surface-base',\n dotColorMap[color],\n )}\n />\n\n {/* Avatar (non-compact only) */}\n {!compact && item.actor && (\n <Avatar className=\"h-5 w-5 shrink-0 text-[9px]\">\n {item.actor.image && (\n <AvatarImage src={item.actor.image} alt={item.actor.name} />\n )}\n <AvatarFallback className=\"text-[9px]\">\n {getInitials(item.actor.name)}\n </AvatarFallback>\n </Avatar>\n )}\n\n {/* Content */}\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <div className=\"flex items-center justify-between gap-ds-02\">\n <div className={cn('min-w-0 flex-1 flex items-center gap-ds-01 flex-wrap', compact ? 'text-body-xs' : 'text-body-sm')}>\n {item.actor && (\n <span className=\"font-medium text-surface-fg\">{item.actor.name} </span>\n )}\n {item.detail && (\n <Icon icon={IconChevronRight} size=\"xs\" className={cn('shrink-0 text-surface-fg-subtle transition-transform duration-fast-02 ease-productive-standard', expandedDetail && 'rotate-90')} />\n )}\n {item.detail ? (\n <button\n type=\"button\"\n className=\"rounded-control text-surface-fg-muted hover:underline hover:bg-surface-panel-hover focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9\"\n onClick={handleActionClick}\n aria-expanded={expandedDetail}\n aria-controls={detailId}\n >\n {item.action}\n </button>\n ) : (\n <span className=\"text-surface-fg-muted\">{item.action}</span>\n )}\n </div>\n\n <time\n className={cn(\n 'shrink-0 whitespace-nowrap text-surface-fg-subtle',\n compact ? 'text-body-xs' : 'text-body-sm',\n )}\n dateTime={\n (typeof item.timestamp === 'string'\n ? new Date(item.timestamp)\n : item.timestamp\n ).toISOString()\n }\n >\n {formatRelativeTime(item.timestamp)}\n </time>\n </div>\n\n {/* Expandable detail */}\n {expandedDetail && item.detail && (\n <div id={detailId} className=\"mt-ds-02 animate-in fade-in slide-in-from-top-1 text-body-sm text-surface-fg-muted\">\n {item.detail}\n </div>\n )}\n </div>\n </div>\n )\n}\n\n/**\n * Wraps custom renderItem content with the same dot + layout container\n * that ActivityEntry uses, keeping vertical rhythm consistent.\n */\nfunction CustomEntry({\n item,\n compact,\n children,\n}: {\n item: ActivityItem\n compact: boolean\n children: React.ReactNode\n}) {\n const color = item.color ?? 'default'\n return (\n <div className={cn('relative flex items-start', compact ? 'gap-ds-02' : 'gap-ds-03')}>\n {/* Dot — same as ActivityEntry */}\n <div\n className={cn(\n 'relative z-10 mt-1.5 h-2 w-2 shrink-0 rounded-pill ring-2 ring-surface-base',\n dotColorMap[color],\n )}\n />\n {/* Custom content in the same flex slot as ActivityEntry's content area */}\n <div className=\"min-w-0 flex-1\">{children}</div>\n </div>\n )\n}\n\nfunction GroupHeader({\n label,\n isFirst,\n}: {\n label: string\n isFirst: boolean\n}) {\n return (\n <motion.div\n initial={false}\n animate={{ opacity: 1 }}\n transition={tweens.fade}\n className={cn(\n 'flex items-center gap-ds-03',\n !isFirst ? 'mt-ds-06' : '',\n 'mb-ds-03',\n )}\n >\n <hr className=\"flex-1 border-surface-border\" />\n <span className=\"bg-surface-base px-ds-03 text-label-xs font-medium uppercase tracking-wider text-surface-fg-subtle\">\n {label}\n </span>\n <hr className=\"flex-1 border-surface-border\" />\n </motion.div>\n )\n}\n\nconst ActivityFeed = React.forwardRef<HTMLDivElement, ActivityFeedProps>(\n (\n {\n items,\n onLoadMore,\n loading = false,\n hasMore = false,\n emptyState,\n compact = false,\n maxInitialItems,\n groupBy = 'none',\n groupLabels,\n renderItem,\n className,\n ...props\n },\n ref,\n ) => {\n const [showAll, setShowAll] = React.useState(false)\n\n if (loading) {\n return (\n <div ref={ref} className={cn('relative', className)} {...props}>\n <LoadingSkeleton compact={compact} />\n </div>\n )\n }\n\n if (items.length === 0) {\n if (emptyState) {\n return (\n <div ref={ref} className={className} {...props}>\n {emptyState}\n </div>\n )\n }\n return null\n }\n\n // maxInitialItems applies to the flat list BEFORE grouping\n const truncated =\n maxInitialItems != null && !showAll && items.length > maxInitialItems\n const visibleItems = truncated ? items.slice(0, maxInitialItems) : items\n\n const useGrouping = groupBy === 'time'\n\n /** Resolve what to render for a single item, respecting renderItem. */\n const resolveEntry = (item: ActivityItem, index: number) => {\n if (renderItem) {\n const custom = renderItem(item, index)\n if (custom !== undefined) {\n return <CustomEntry item={item} compact={compact}>{custom}</CustomEntry>\n }\n }\n return <ActivityEntry item={item} compact={compact} />\n }\n\n return (\n <div ref={ref} className={cn('relative', className)} {...props}>\n {/* Items */}\n {useGrouping ? (\n <div>\n {groupItemsByTime(visibleItems, groupLabels).map((group, gi) => (\n <div key={group.label}>\n <GroupHeader label={group.label} isFirst={gi === 0} />\n {/* Timeline line scoped to this group */}\n <div className={cn('relative flex flex-col', compact ? 'gap-1' : 'gap-3')}>\n <div className=\"absolute bottom-0 left-[3px] top-0 w-px bg-surface-border\" />\n {group.items.map((item, index) => (\n <motion.div key={item.id} initial={false} animate={{ opacity: 1 }} transition={{ ...tweens.fade, delay: index * 0.03 }}>\n {resolveEntry(item, index)}\n </motion.div>\n ))}\n </div>\n </div>\n ))}\n </div>\n ) : (\n <div className={cn('relative flex flex-col', compact ? 'gap-1' : 'gap-3')}>\n {/* Timeline line */}\n <div className=\"absolute bottom-0 left-[3px] top-0 w-px bg-surface-border\" />\n {visibleItems.map((item, index) => (\n <motion.div key={item.id} initial={false} animate={{ opacity: 1 }} transition={{ ...tweens.fade, delay: index * 0.03 }}>\n {resolveEntry(item, index)}\n </motion.div>\n ))}\n </div>\n )}\n\n {/* Show all button */}\n {truncated && (\n <div className=\"relative mt-ds-03 flex justify-center\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setShowAll(true)}\n >\n Show all ({items.length})\n </Button>\n </div>\n )}\n\n {/* Load more button */}\n {hasMore && onLoadMore && !truncated && (\n <div className=\"relative mt-ds-03 flex justify-center\">\n <Button variant=\"ghost\" size=\"sm\" onClick={onLoadMore}>\n Load more\n </Button>\n </div>\n )}\n </div>\n )\n },\n)\nActivityFeed.displayName = 'ActivityFeed'\n\nexport { ActivityFeed }\n"],"mappings":"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"}
@@ -14,8 +14,13 @@ export interface AvatarGroupProps extends React.HTMLAttributes<HTMLDivElement> {
14
14
  max?: number;
15
15
  size?: Size;
16
16
  showTooltip?: boolean;
17
- /** Border color for the group avatars. @default 'surface-raised' */
18
- borderColor?: 'surface-base' | 'surface-raised';
17
+ /**
18
+ * Border color for the group avatars. @default 'surface-panel'
19
+ *
20
+ * `'surface-raised'` is the pre-0.57 name for `'surface-panel'` and still
21
+ * works — a widening, so nothing breaks. It is removed next major.
22
+ */
23
+ borderColor?: 'surface-base' | 'surface-panel' | 'surface-raised';
19
24
  /** Callback when the "+N" overflow badge is clicked (renders it as a button). */
20
25
  onOverflowClick?: () => void;
21
26
  /** Custom render function for each avatar (consumer owns the Avatar). */
@@ -1 +1 @@
1
- {"version":3,"file":"avatar-group.d.ts","sourceRoot":"","sources":["../../src/composed/avatar-group.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAE5D,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAIL,cAAc,EACd,KAAK,UAAU,EAChB,MAAM,cAAc,CAAA;AAWrB,KAAK,IAAI,GAAG,WAAW,CAAC,YAAY,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;AAWpE,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC,SAAS,CAAA;CAC/C;AAED,MAAM,WAAW,gBAAiB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC5E,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,oEAAoE;IACpE,WAAW,CAAC,EAAE,cAAc,GAAG,gBAAgB,CAAA;IAC/C,iFAAiF;IACjF,eAAe,CAAC,EAAE,MAAM,IAAI,CAAA;IAC5B,yEAAyE;IACzE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAA;IACnE,gEAAgE;IAChE,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IAClC,gEAAgE;IAChE,YAAY,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAA;IAC7C,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAiBD,QAAA,MAAM,WAAW,yFA4KhB,CAAA;AAGD,OAAO,EAAE,WAAW,EAAE,CAAA"}
1
+ {"version":3,"file":"avatar-group.d.ts","sourceRoot":"","sources":["../../src/composed/avatar-group.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAE5D,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAIL,cAAc,EACd,KAAK,UAAU,EAChB,MAAM,cAAc,CAAA;AAWrB,KAAK,IAAI,GAAG,WAAW,CAAC,YAAY,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;AAWpE,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC,SAAS,CAAA;CAC/C;AAED,MAAM,WAAW,gBAAiB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC5E,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,cAAc,GAAG,eAAe,GAAG,gBAAgB,CAAA;IACjE,iFAAiF;IACjF,eAAe,CAAC,EAAE,MAAM,IAAI,CAAA;IAC5B,yEAAyE;IACzE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAA;IACnE,gEAAgE;IAChE,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IAClC,gEAAgE;IAChE,YAAY,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAA;IAC7C,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAiBD,QAAA,MAAM,WAAW,yFA4KhB,CAAA;AAGD,OAAO,EAAE,WAAW,EAAE,CAAA"}
@@ -30,10 +30,10 @@ var h = {
30
30
  md: "size-2",
31
31
  lg: "size-2.5",
32
32
  xl: "size-3"
33
- }, y = u.forwardRef(({ users: y, max: b = 4, size: x = "md", showTooltip: S = !0, borderColor: C = "surface-raised", onOverflowClick: w, renderAvatar: T, expandDirection: E = "right", expandAmount: D = "default", label: O, className: k, onMouseEnter: A, onMouseLeave: j, onFocus: M, onBlur: N, ...P }, F) => {
33
+ }, y = u.forwardRef(({ users: y, max: b = 4, size: x = "md", showTooltip: S = !0, borderColor: C = "surface-panel", onOverflowClick: w, renderAvatar: T, expandDirection: E = "right", expandAmount: D = "default", label: O, className: k, onMouseEnter: A, onMouseLeave: j, onFocus: M, onBlur: N, ...P }, F) => {
34
34
  let I = m(), [L, R] = u.useState(!1);
35
35
  if (y.length === 0) return null;
36
- let z = Math.max(1, b), B = y.slice(0, z), V = y.length - z, H = g[x], U = C === "surface-base" ? "border-surface-base" : "border-surface-raised", W = C === "surface-base" ? "ring-offset-1 ring-offset-surface-base" : "ring-offset-1 ring-offset-surface-raised", G = _[x], K = {
36
+ let z = Math.max(1, b), B = y.slice(0, z), V = y.length - z, H = g[x], U = C === "surface-base" ? "border-surface-base" : "border-surface-panel", W = C === "surface-base" ? "ring-offset-1 ring-offset-surface-base" : "ring-offset-1 ring-offset-surface-panel", G = _[x], K = {
37
37
  compact: .5,
38
38
  default: 1,
39
39
  wide: 1.5
@@ -75,7 +75,7 @@ var h = {
75
75
  }),
76
76
  e.indicator && /* @__PURE__ */ d("span", {
77
77
  "aria-hidden": "true",
78
- className: t("absolute top-0 right-0 rounded-pill ring-1 ring-surface-raised", v[x], e.indicator === "lead" ? "bg-accent-9" : e.indicator === "admin" ? "bg-warning-9" : ""),
78
+ className: t("absolute top-0 right-0 rounded-pill ring-1 ring-surface-panel", v[x], e.indicator === "lead" ? "bg-accent-9" : e.indicator === "admin" ? "bg-warning-9" : ""),
79
79
  children: typeof e.indicator != "string" && e.indicator
80
80
  })
81
81
  ]
@@ -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 /** Border color for the group avatars. @default 'surface-raised' */\n borderColor?: 'surface-base' | '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-raised',\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-raised'\n const ringOffsetClass = borderColor === 'surface-base'\n ? 'ring-offset-1 ring-offset-surface-base'\n : 'ring-offset-1 ring-offset-surface-raised'\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-raised',\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-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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"}