@devalok/shilp-sutra 0.37.1 → 0.39.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 (260) hide show
  1. package/MIGRATION.md +185 -0
  2. package/dist/_chunks/badge-group.js +4 -4
  3. package/dist/_chunks/badge-group.js.map +1 -1
  4. package/dist/_chunks/chat.js +14 -14
  5. package/dist/_chunks/chat.js.map +1 -1
  6. package/dist/_chunks/date-picker.js +9 -9
  7. package/dist/_chunks/date-picker.js.map +1 -1
  8. package/dist/_chunks/document-preview.js +2 -2
  9. package/dist/_chunks/document-preview.js.map +1 -1
  10. package/dist/_chunks/file-preview.js +11 -11
  11. package/dist/_chunks/file-preview.js.map +1 -1
  12. package/dist/_chunks/image-preview.js +2 -2
  13. package/dist/_chunks/image-preview.js.map +1 -1
  14. package/dist/_chunks/mention-suggestion.js +5 -5
  15. package/dist/_chunks/mention-suggestion.js.map +1 -1
  16. package/dist/_chunks/popover.js +3 -3
  17. package/dist/_chunks/popover.js.map +1 -1
  18. package/dist/_chunks/rich-chat-input.js +33 -33
  19. package/dist/_chunks/rich-chat-input.js.map +1 -1
  20. package/dist/_chunks/shared.js +5 -5
  21. package/dist/_chunks/shared.js.map +1 -1
  22. package/dist/_chunks/text.js +2 -2
  23. package/dist/_chunks/text.js.map +1 -1
  24. package/dist/_chunks/tiptap.js +977 -968
  25. package/dist/_chunks/tiptap.js.map +1 -1
  26. package/dist/_chunks/tree-view.js +1 -1
  27. package/dist/_chunks/tree-view.js.map +1 -1
  28. package/dist/_chunks/use-calendar.js +5 -5
  29. package/dist/_chunks/use-calendar.js.map +1 -1
  30. package/dist/ai/command-bar.js +10 -10
  31. package/dist/ai/command-bar.js.map +1 -1
  32. package/dist/ai/conversation.js +4 -4
  33. package/dist/ai/conversation.js.map +1 -1
  34. package/dist/composed/activity-feed.js +5 -5
  35. package/dist/composed/activity-feed.js.map +1 -1
  36. package/dist/composed/avatar-group.js +4 -4
  37. package/dist/composed/avatar-group.js.map +1 -1
  38. package/dist/composed/bulk-action-bar.js +1 -1
  39. package/dist/composed/bulk-action-bar.js.map +1 -1
  40. package/dist/composed/command-palette.js +5 -5
  41. package/dist/composed/command-palette.js.map +1 -1
  42. package/dist/composed/content-card.js +1 -1
  43. package/dist/composed/content-card.js.map +1 -1
  44. package/dist/composed/emoji-picker.js +4 -4
  45. package/dist/composed/emoji-picker.js.map +1 -1
  46. package/dist/composed/empty-state.js +1 -1
  47. package/dist/composed/empty-state.js.map +1 -1
  48. package/dist/composed/error-boundary.js +3 -3
  49. package/dist/composed/error-boundary.js.map +1 -1
  50. package/dist/composed/filter-bar.js +1 -1
  51. package/dist/composed/filter-bar.js.map +1 -1
  52. package/dist/composed/index.d.ts +0 -2
  53. package/dist/composed/index.d.ts.map +1 -1
  54. package/dist/composed/index.js +5 -6
  55. package/dist/composed/inline-edit.js +1 -1
  56. package/dist/composed/inline-edit.js.map +1 -1
  57. package/dist/composed/loading-skeleton.js +9 -9
  58. package/dist/composed/loading-skeleton.js.map +1 -1
  59. package/dist/composed/markdown-viewer.js +3 -3
  60. package/dist/composed/markdown-viewer.js.map +1 -1
  61. package/dist/composed/multi-select-popover.js +1 -1
  62. package/dist/composed/multi-select-popover.js.map +1 -1
  63. package/dist/composed/page-header.js +1 -1
  64. package/dist/composed/page-header.js.map +1 -1
  65. package/dist/composed/page-skeletons.js +15 -15
  66. package/dist/composed/page-skeletons.js.map +1 -1
  67. package/dist/composed/priority-indicator.js +3 -3
  68. package/dist/composed/priority-indicator.js.map +1 -1
  69. package/dist/composed/rich-text-editor.js +11 -11
  70. package/dist/composed/rich-text-editor.js.map +1 -1
  71. package/dist/composed/schedule-view.js +3 -3
  72. package/dist/composed/schedule-view.js.map +1 -1
  73. package/dist/composed/status-badge.js +3 -3
  74. package/dist/composed/status-badge.js.map +1 -1
  75. package/dist/hooks/index.d.ts +2 -2
  76. package/dist/hooks/index.d.ts.map +1 -1
  77. package/dist/hooks/index.js +0 -1
  78. package/dist/shell/bottom-navbar.js +6 -6
  79. package/dist/shell/bottom-navbar.js.map +1 -1
  80. package/dist/shell/notification-center.js +8 -8
  81. package/dist/shell/notification-center.js.map +1 -1
  82. package/dist/shell/notification-preferences.js +1 -1
  83. package/dist/shell/notification-preferences.js.map +1 -1
  84. package/dist/shell/sidebar.js +6 -6
  85. package/dist/shell/sidebar.js.map +1 -1
  86. package/dist/shell/top-bar.js +4 -4
  87. package/dist/shell/top-bar.js.map +1 -1
  88. package/dist/tokens/semantic.css +82 -9
  89. package/dist/ui/accordion.js +1 -1
  90. package/dist/ui/accordion.js.map +1 -1
  91. package/dist/ui/alert-dialog.js +3 -3
  92. package/dist/ui/alert-dialog.js.map +1 -1
  93. package/dist/ui/alert.d.ts +1 -2
  94. package/dist/ui/alert.d.ts.map +1 -1
  95. package/dist/ui/alert.js +3 -29
  96. package/dist/ui/alert.js.map +1 -1
  97. package/dist/ui/autocomplete.js +2 -2
  98. package/dist/ui/autocomplete.js.map +1 -1
  99. package/dist/ui/avatar.js +11 -11
  100. package/dist/ui/avatar.js.map +1 -1
  101. package/dist/ui/badge-indicator.js +1 -1
  102. package/dist/ui/badge-indicator.js.map +1 -1
  103. package/dist/ui/banner.d.ts +3 -5
  104. package/dist/ui/banner.d.ts.map +1 -1
  105. package/dist/ui/banner.js +14 -14
  106. package/dist/ui/banner.js.map +1 -1
  107. package/dist/ui/breadcrumb.js +1 -1
  108. package/dist/ui/breadcrumb.js.map +1 -1
  109. package/dist/ui/button.d.ts +2 -2
  110. package/dist/ui/button.js +14 -14
  111. package/dist/ui/button.js.map +1 -1
  112. package/dist/ui/card.js +5 -5
  113. package/dist/ui/card.js.map +1 -1
  114. package/dist/ui/charts/index.js +5 -5
  115. package/dist/ui/charts/index.js.map +1 -1
  116. package/dist/ui/checkbox.js +1 -1
  117. package/dist/ui/checkbox.js.map +1 -1
  118. package/dist/ui/code.js +2 -2
  119. package/dist/ui/code.js.map +1 -1
  120. package/dist/ui/color-input.js +10 -10
  121. package/dist/ui/color-input.js.map +1 -1
  122. package/dist/ui/color-swatch.js +3 -3
  123. package/dist/ui/color-swatch.js.map +1 -1
  124. package/dist/ui/combobox.js +5 -5
  125. package/dist/ui/combobox.js.map +1 -1
  126. package/dist/ui/context-menu.js +6 -6
  127. package/dist/ui/context-menu.js.map +1 -1
  128. package/dist/ui/data-table-body.js +1 -1
  129. package/dist/ui/data-table-body.js.map +1 -1
  130. package/dist/ui/data-table-bulk-actions.js +2 -2
  131. package/dist/ui/data-table-bulk-actions.js.map +1 -1
  132. package/dist/ui/data-table-card.js +2 -2
  133. package/dist/ui/data-table-card.js.map +1 -1
  134. package/dist/ui/data-table-header.js +2 -2
  135. package/dist/ui/data-table-header.js.map +1 -1
  136. package/dist/ui/data-table-pagination.js +3 -3
  137. package/dist/ui/data-table-pagination.js.map +1 -1
  138. package/dist/ui/data-table-toolbar.js +1 -1
  139. package/dist/ui/data-table-toolbar.js.map +1 -1
  140. package/dist/ui/data-table.js +1 -1
  141. package/dist/ui/data-table.js.map +1 -1
  142. package/dist/ui/devalok-grain.d.ts +1 -1
  143. package/dist/ui/devalok-grain.js.map +1 -1
  144. package/dist/ui/dialog.js +2 -2
  145. package/dist/ui/dialog.js.map +1 -1
  146. package/dist/ui/dropdown-menu.js +6 -6
  147. package/dist/ui/dropdown-menu.js.map +1 -1
  148. package/dist/ui/file-upload.js +4 -4
  149. package/dist/ui/file-upload.js.map +1 -1
  150. package/dist/ui/hover-card.js +1 -1
  151. package/dist/ui/hover-card.js.map +1 -1
  152. package/dist/ui/icon-button.js +1 -1
  153. package/dist/ui/icon-button.js.map +1 -1
  154. package/dist/ui/index.d.ts +1 -1
  155. package/dist/ui/index.d.ts.map +1 -1
  156. package/dist/ui/index.js +2 -2
  157. package/dist/ui/index.js.map +1 -1
  158. package/dist/ui/input-otp.js +1 -1
  159. package/dist/ui/input-otp.js.map +1 -1
  160. package/dist/ui/input.d.ts +1 -9
  161. package/dist/ui/input.d.ts.map +1 -1
  162. package/dist/ui/input.js +28 -29
  163. package/dist/ui/input.js.map +1 -1
  164. package/dist/ui/link.js +1 -1
  165. package/dist/ui/link.js.map +1 -1
  166. package/dist/ui/menubar.js +8 -8
  167. package/dist/ui/menubar.js.map +1 -1
  168. package/dist/ui/navigation-menu.js +3 -3
  169. package/dist/ui/navigation-menu.js.map +1 -1
  170. package/dist/ui/number-input.js +3 -3
  171. package/dist/ui/number-input.js.map +1 -1
  172. package/dist/ui/pagination.js +1 -1
  173. package/dist/ui/pagination.js.map +1 -1
  174. package/dist/ui/progress.js +1 -1
  175. package/dist/ui/progress.js.map +1 -1
  176. package/dist/ui/radio.js +1 -1
  177. package/dist/ui/radio.js.map +1 -1
  178. package/dist/ui/segmented-control.d.ts +1 -1
  179. package/dist/ui/segmented-control.d.ts.map +1 -1
  180. package/dist/ui/segmented-control.js +5 -7
  181. package/dist/ui/segmented-control.js.map +1 -1
  182. package/dist/ui/select.js +3 -3
  183. package/dist/ui/select.js.map +1 -1
  184. package/dist/ui/sheet.js +2 -2
  185. package/dist/ui/sheet.js.map +1 -1
  186. package/dist/ui/sidebar.js +11 -11
  187. package/dist/ui/sidebar.js.map +1 -1
  188. package/dist/ui/skeleton.js +9 -9
  189. package/dist/ui/skeleton.js.map +1 -1
  190. package/dist/ui/slider.js +2 -2
  191. package/dist/ui/slider.js.map +1 -1
  192. package/dist/ui/split-button.js +7 -7
  193. package/dist/ui/split-button.js.map +1 -1
  194. package/dist/ui/stat-card.js +7 -7
  195. package/dist/ui/stat-card.js.map +1 -1
  196. package/dist/ui/status-dot.js +2 -2
  197. package/dist/ui/status-dot.js.map +1 -1
  198. package/dist/ui/stepper.js +2 -2
  199. package/dist/ui/stepper.js.map +1 -1
  200. package/dist/ui/switch.js +2 -2
  201. package/dist/ui/switch.js.map +1 -1
  202. package/dist/ui/tabs.js +3 -3
  203. package/dist/ui/tabs.js.map +1 -1
  204. package/dist/ui/textarea.js +1 -1
  205. package/dist/ui/textarea.js.map +1 -1
  206. package/dist/ui/toast.js +8 -8
  207. package/dist/ui/toast.js.map +1 -1
  208. package/dist/ui/toggle.js +1 -1
  209. package/dist/ui/toggle.js.map +1 -1
  210. package/dist/ui/tooltip.js +1 -1
  211. package/dist/ui/tooltip.js.map +1 -1
  212. package/docs/components/_header.md +91 -2
  213. package/docs/components/ui/alert.md +4 -1
  214. package/docs/components/ui/banner.md +3 -1
  215. package/docs/components/ui/input.md +4 -2
  216. package/docs/components/ui/segmented-control.md +13 -6
  217. package/docs/recipes/customize-brand.md +297 -0
  218. package/docs/recipes/index.md +51 -0
  219. package/docs/recipes/install-astro.md +178 -0
  220. package/docs/recipes/install-next-app-router.md +230 -0
  221. package/docs/recipes/install-next-pages.md +123 -0
  222. package/docs/recipes/install-remix.md +171 -0
  223. package/docs/recipes/install-tanstack-start.md +143 -0
  224. package/docs/recipes/install-vite.md +170 -0
  225. package/docs/recipes/server-components.md +209 -0
  226. package/docs/recipes/troubleshoot.md +217 -0
  227. package/llms-full.txt +116 -54
  228. package/llms.txt +42 -17
  229. package/package.json +46 -35
  230. package/skill/README.md +99 -0
  231. package/skill/SKILL.md +144 -0
  232. package/skill/install.sh +59 -0
  233. package/skill/references/components-full.md +6997 -0
  234. package/skill/references/components.md +673 -0
  235. package/skill/references/customize-brand.md +299 -0
  236. package/skill/references/server-components.md +211 -0
  237. package/skill/references/setup-astro.md +180 -0
  238. package/skill/references/setup-next-app-router.md +232 -0
  239. package/skill/references/setup-next-pages.md +125 -0
  240. package/skill/references/setup-remix.md +173 -0
  241. package/skill/references/setup-tanstack-start.md +145 -0
  242. package/skill/references/setup-vite.md +172 -0
  243. package/skill/references/troubleshoot.md +219 -0
  244. package/dist/composed/responsive-overlay.d.ts +0 -23
  245. package/dist/composed/responsive-overlay.d.ts.map +0 -1
  246. package/dist/composed/responsive-overlay.js +0 -40
  247. package/dist/composed/responsive-overlay.js.map +0 -1
  248. package/dist/hooks/use-toast.d.ts +0 -17
  249. package/dist/hooks/use-toast.d.ts.map +0 -1
  250. package/dist/hooks/use-toast.js +0 -3
  251. package/dist/tailwind/index.cjs +0 -41
  252. package/dist/tailwind/index.d.ts +0 -2
  253. package/dist/tailwind/index.d.ts.map +0 -1
  254. package/dist/tailwind/index.js +0 -2
  255. package/dist/tailwind/preset.d.ts +0 -25
  256. package/dist/tailwind/preset.d.ts.map +0 -1
  257. package/dist/tailwind/preset.js +0 -17
  258. package/dist/tailwind/preset.js.map +0 -1
  259. package/docs/components/composed/responsive-overlay.md +0 -41
  260. /package/{LICENSE → skill/LICENSE} +0 -0
@@ -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 { durations,springs } from '../ui/lib/motion'\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?: React.ReactNode }\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?: React.ReactNode\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\">{icon}</span>}\n <span className=\"text-ds-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-ds-lg px-ds-05 py-ds-04\">\n <p className=\"text-ds-sm text-surface-fg\">{message.content}</p>\n </div>\n )\n }\n\n return (\n <motion.div\n className=\"bg-surface-raised rounded-ds-lg px-ds-05 py-ds-04\"\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n transition={springs.snappy}\n >\n <p className=\"text-ds-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?: React.ReactNode }\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?: React.ReactNode }\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-ds-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-full 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-ds-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-ds-xs font-medium rounded-full px-ds-04 py-ds-02 shadow-floating\"\n initial={{ opacity: 0, 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":";;;;;;;;;;;;AAmDA,SAAS,EAAe,EAAE,aAAgD;AACxE,SAAQ,GAAR;EACE,KAAK,OACH,QACE,kBAAC,GAAD;GAAM,MAAM;GAAiB,MAAK;GAAK,WAAU;GAAoB,CAAA;EAEzE,KAAK,SACH,QACE,kBAAC,EAAO,MAAR;GACE,WAAU;GACV,SAAS,EAAE,QAAQ,KAAK;GACxB,YAAY;IAAE,QAAQ;IAAU,UAAU;IAAG,MAAM;IAAU;GAC7D,eAAY;aAEZ,kBAAC,GAAD;IAAM,MAAM;IAAa,MAAK;IAAO,CAAA;GACzB,CAAA;EAElB,KAAK,QACH,QACE,kBAAC,GAAD;GAAM,MAAM;GAAa,MAAK;GAAK,WAAU;GAAkB,CAAA;EAGnE,QACE,QACE,kBAAC,GAAD;GAAM,MAAM;GAAY,MAAK;GAAK,WAAU;GAAsC,CAAA;;;AAO1F,SAAS,EAAY,EACnB,SACA,WAIC;AACD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACG,KAAQ,kBAAC,QAAD;GAAM,WAAU;aAA4C;GAAY,CAAA,EACjF,kBAAC,QAAD;GAAM,WAAU;aACb;GACI,CAAA,CACH;;;AAMV,SAAS,EAAY,EACnB,YACA,oBAIC;AASD,QARI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,KAAD;GAAG,WAAU;aAA8B,EAAQ;GAAY,CAAA;EAC3D,CAAA,GAKR,kBAAC,EAAO,KAAR;EACE,WAAU;EACV,SAAS;GAAE,SAAS;GAAG,GAAG;GAAG;EAC7B,SAAS;GAAE,SAAS;GAAG,GAAG;GAAG;EAC7B,YAAY,EAAQ;YAEpB,kBAAC,KAAD;GAAG,WAAU;aAA8B,EAAQ;GAAY,CAAA;EACpD,CAAA;;AAMjB,SAAS,EAAiB,EACxB,YACA,UACA,aACA,mBAMC;AACD,QACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,GAAD;EAAa,MAAM,EAAM;EAAM,MAAM,EAAM;EAAQ,CAAA,EAClD,EAAQ,UACP,kBAAC,GAAD;EACE,QAAQ,EAAQ;EACN;EACI;EACd,CAAA,CAEA,EAAA,CAAA;;AAMV,SAAS,EAAoB,EAC3B,UACA,UACA,oBAKC;AAsCD,QApCI,KAAS,EAAM,SAAS,IAExB,kBAAC,OAAD;EAAK,MAAK;EAAS,aAAU;EAAO,cAAW;YAA/C,CACE,kBAAC,GAAD;GAAa,MAAM,EAAM;GAAM,MAAM,EAAM;GAAQ,CAAA,EACnD,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAM,KAAK,GAAM,MAChB,kBAAC,EAAO,KAAR;IAEE,WAAU;IACV,SAAS,IAAgB,KAAA,IAAY;KAAE,SAAS;KAAG,GAAG;KAAI;IAC1D,SAAS;KAAE,SAAS;KAAG,GAAG;KAAG;IAC7B,YACE,IACI,EAAE,UAAU,GAAG,GACf;KAAE,UAAU,EAAU;KAAa,OAAO,IAAI;KAAM;cAR5D,CAWE,kBAAC,GAAD,EAAgB,QAAQ,EAAK,QAAU,CAAA,EACvC,kBAAC,QAAD;KACE,WAAW,EACT,cACA,EAAK,WAAW,YACZ,2BACA,kBACL;eAEA,EAAK;KACD,CAAA,CACI;MArBN,EAAK,GAqBC,CACb;GACE,CAAA,CACF;MAMR,kBAAC,OAAD;EACE,MAAK;EACL,aAAU;EACV,cAAY,GAAG,EAAM,KAAK;YAH5B,CAKE,kBAAC,GAAD;GAAa,MAAM,EAAM;GAAM,MAAM,EAAM;GAAQ,CAAA,EACnD,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,CAAC,KACA;IAAC;IAAG;IAAG;IAAE,CAAC,KAAK,MACb,kBAAC,EAAO,MAAR;IAEE,WAAU;IACV,SAAS;KAAE,OAAO;MAAC;MAAG;MAAK;MAAE;KAAE,SAAS;MAAC;MAAK;MAAG;MAAI;KAAE;IACvD,YAAY;KACV,UAAU;KACV,QAAQ;KACR,MAAM;KACN,OAAO,IAAI;KACZ;IACD,EATK,EASL,CACF,EACJ,kBAAC,QAAD;IAAM,WAAU;cAAhB,CACG,EAAM,MAAK,kBACP;MACH;KACF;;;AAMV,SAAS,EAAmB,EAAE,cAAoC;AAChE,QACE,kBAAC,EAAO,QAAR;EACE,MAAK;EACL,cAAW;EACX,WAAU;EACV,SAAS;GAAE,SAAS;GAAG,GAAG;GAAG;EAC7B,SAAS;GAAE,SAAS;GAAG,GAAG;GAAG;EAC7B,MAAM;GAAE,SAAS;GAAG,GAAG;GAAG;EAC1B,YAAY,EAAQ;EACX;YARX,CAUG,KAAS,gBACI;;;AAMpB,IAAM,IAAgB,EAAE,MAAM,aAAa,EAIrC,IAAiB,EAAM,YAAiD,EAC5E,aACA,kBAAe,IACf,oBACA,OAAO,GACP,aACA,iBACA,cACA,gBAAa,IACb,gBACC,MAAQ;CACT,IAAM,IAAM,GAAc,EACpB,EAAE,qBAAkB,GAAW,EAG/B,IAAQ,KAAa,GAAK,SAAS,GAGnC,IAAmB,KAAY,GAAK,UAGpC,IAAuB,EAAM,eAE1B;EAAE,GADa,GAAK,gBAAgB,EAAE;EAClB,GAAG;EAAc,GAC3C,CAAC,GAAK,cAAc,EAAa,CAAC,EAI/B,IAAY,EAAM,OAAuB,KAAK,EAC9C,IAAc,EAAM,OAAuB,KAAK,EAChD,CAAC,GAAY,KAAiB,EAAM,SAAS,GAAK,EAClD,CAAC,GAAe,KAAoB,EAAM,SAAS,GAAM,EACzD,IAAmB,EAAM,OAAO,EAAS,OAAO,EAChD,IAAiB,EAAM,OAAO,EAAa;AAwBjD,CArBA,EAAM,gBAAgB;AACpB,MAAI,CAAC,KAAc,CAAC,EAAY,QAAS;EAEzC,IAAM,IAAW,IAAI,sBAClB,CAAC,OAAW;AAEX,GADA,EAAc,EAAM,eAAe,EAC/B,EAAM,kBACR,EAAiB,GAAM;KAG3B;GACE,MAAM,EAAU;GAChB,WAAW;GACZ,CACF;AAGD,SADA,EAAS,QAAQ,EAAY,QAAQ,QACxB,EAAS,YAAY;IACjC,CAAC,EAAW,CAAC,EAGhB,EAAM,gBAAgB;EACpB,IAAM,IACJ,EAAS,WAAW,EAAiB,WACrC,MAAiB,EAAe;AAElC,IAAiB,UAAU,EAAS,QACpC,EAAe,UAAU,GAErB,GAAC,KAAkB,CAAC,OAEpB,IACF,EAAY,SAAS,eAAe;GAAE,UAAU;GAAU,OAAO;GAAO,CAAC,GAEzE,EAAiB,GAAK;IAEvB;EAAC;EAAU;EAAc;EAAY;EAAW,CAAC;CAEpD,IAAM,IAAiB,EAAM,kBAAkB;AAG7C,EAFA,EAAY,SAAS,eAAe;GAAE,UAAU;GAAU,OAAO;GAAO,CAAC,EACzE,EAAc,GAAK,EACnB,EAAiB,GAAM;IACtB,EAAE,CAAC;AAIN,QACE,kBAAC,OAAD;EAAU;EAAK,WAAW,EAAG,YAAY,EAAU;YAAnD,CACE,kBAAC,OAAD;GACE,KAAK;GACL,WAAU;GACV,aAAU;GACV,OACE,IACI,EACE,WACE,OAAO,KAAc,WAAW,GAAG,EAAU,MAAM,GACtD,GACD,KAAA;aAVR;IAaG,EAAS,KAAK,MACb,EAAI,SAAS,SACX,kBAAC,GAAD;KAEE,SAAS;KACM;KACf,EAHK,EAAI,GAGT,GAEF,kBAAC,GAAD;KAEE,SAAS;KACF;KACP,UAAU;KACV,cAAc;KACd,EALK,EAAI,GAKT,CAEL;IACA,KACC,kBAAC,GAAD;KACE,OAAO;KACA;KACQ;KACf,CAAA;IAEJ,kBAAC,OAAD;KAAK,KAAK;KAAa,WAAU;KAAS,CAAA;IACtC;MACN,kBAAC,GAAD,EAAA,UACG,CAAC,KAAc,KACd,kBAAC,GAAD,EAAoB,SAAS,GAAkB,CAAA,EAEjC,CAAA,CACd;;EAER;AACF,EAAe,cAAc"}
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 { durations,springs } from '../ui/lib/motion'\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?: React.ReactNode }\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?: React.ReactNode\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\">{icon}</span>}\n <span className=\"text-ds-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-ds-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={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n transition={springs.snappy}\n >\n <p className=\"text-ds-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?: React.ReactNode }\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?: React.ReactNode }\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-ds-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-ds-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-ds-xs font-medium rounded-pill px-ds-04 py-ds-02 shadow-floating\"\n initial={{ opacity: 0, 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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"}
@@ -58,8 +58,8 @@ function v({ compact: e }) {
58
58
  ].map((n) => /* @__PURE__ */ f("div", {
59
59
  className: "flex items-center gap-ds-03",
60
60
  children: [
61
- /* @__PURE__ */ d(s, { className: "h-2 w-2 shrink-0 rounded-ds-full" }),
62
- !e && /* @__PURE__ */ d(s, { className: "h-4 w-4 shrink-0 rounded-ds-full" }),
61
+ /* @__PURE__ */ d(s, { className: "h-2 w-2 shrink-0 rounded-pill" }),
62
+ !e && /* @__PURE__ */ d(s, { className: "h-4 w-4 shrink-0 rounded-pill" }),
63
63
  /* @__PURE__ */ d(s, { className: t("flex-1", e ? "h-3" : "h-4") }),
64
64
  /* @__PURE__ */ d(s, { className: "h-3 w-12" })
65
65
  ]
@@ -73,7 +73,7 @@ function y({ item: e, compact: o }) {
73
73
  return /* @__PURE__ */ f("div", {
74
74
  className: t("relative flex items-start", o ? "gap-ds-02" : "gap-ds-03"),
75
75
  children: [
76
- /* @__PURE__ */ d("div", { className: t("relative z-10 mt-1.5 h-2 w-2 shrink-0 rounded-ds-full ring-2 ring-surface-base", h[g]) }),
76
+ /* @__PURE__ */ d("div", { className: t("relative z-10 mt-1.5 h-2 w-2 shrink-0 rounded-pill ring-2 ring-surface-base", h[g]) }),
77
77
  !o && e.actor && /* @__PURE__ */ f(r, {
78
78
  className: "h-5 w-5 shrink-0 text-[9px]",
79
79
  children: [e.actor.image && /* @__PURE__ */ d(a, {
@@ -101,7 +101,7 @@ function y({ item: e, compact: o }) {
101
101
  className: t("shrink-0 text-surface-fg-subtle transition-transform duration-fast-02 ease-productive-standard", s && "rotate-90")
102
102
  }),
103
103
  /* @__PURE__ */ d("span", {
104
- className: t("text-surface-fg-muted", e.detail && "cursor-pointer hover:underline hover:bg-surface-raised rounded-ds-md"),
104
+ className: t("text-surface-fg-muted", e.detail && "cursor-pointer hover:underline hover:bg-surface-raised rounded-control"),
105
105
  onClick: _,
106
106
  role: e.detail ? "button" : void 0,
107
107
  tabIndex: e.detail ? 0 : void 0,
@@ -128,7 +128,7 @@ function b({ item: e, compact: n, children: r }) {
128
128
  let i = e.color ?? "default";
129
129
  return /* @__PURE__ */ f("div", {
130
130
  className: t("relative flex items-start", n ? "gap-ds-02" : "gap-ds-03"),
131
- children: [/* @__PURE__ */ d("div", { className: t("relative z-10 mt-1.5 h-2 w-2 shrink-0 rounded-ds-full ring-2 ring-surface-base", h[i]) }), /* @__PURE__ */ d("div", {
131
+ children: [/* @__PURE__ */ d("div", { className: t("relative z-10 mt-1.5 h-2 w-2 shrink-0 rounded-pill ring-2 ring-surface-base", h[i]) }), /* @__PURE__ */ d("div", {
132
132
  className: "min-w-0 flex-1",
133
133
  children: r
134
134
  })]
@@ -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 { 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?: React.ReactNode\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-ds-full\" />\n {!compact && <Skeleton className=\"h-4 w-4 shrink-0 rounded-ds-full\" />}\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 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-ds-full 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-ds-xs' : 'text-ds-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 <span\n className={cn(\n 'text-surface-fg-muted',\n item.detail && 'cursor-pointer hover:underline hover:bg-surface-raised rounded-ds-md',\n )}\n onClick={handleActionClick}\n role={item.detail ? 'button' : undefined}\n tabIndex={item.detail ? 0 : undefined}\n onKeyDown={\n item.detail\n ? (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n handleActionClick()\n }\n }\n : undefined\n }\n >\n {item.action}\n </span>\n </div>\n\n <time\n className={cn(\n 'shrink-0 whitespace-nowrap text-surface-fg-subtle',\n compact ? 'text-ds-xs' : 'text-ds-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 className=\"mt-ds-02 animate-in fade-in slide-in-from-top-1 text-ds-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-ds-full 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={{ opacity: 0 }}\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-ds-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={{ opacity: 0 }} 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={{ opacity: 0 }} 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":";;;;;;;;;;;;;;AA8CA,IAAM,IAAc;CAClB,SAAS;CACT,SAAS;CACT,SAAS;CACT,OAAO;CACP,MAAM;CACP,EAEK,IAA8C;CAClD,OAAO;CACP,WAAW;CACX,UAAU;CACV,OAAO;CACR;AAMD,SAAgB,EACd,GACA,GAC4C;CAC5C,IAAM,IAAI;EAAE,GAAG;EAAsB,GAAG;EAAQ,EAC1C,oBAAM,IAAI,MAAM,EAGhB,IAAe,IAAI,KAAK,EAAI,aAAa,EAAE,EAAI,UAAU,EAAE,EAAI,SAAS,CAAC,EAEzE,oBAAmB,IAAI,KAAK,EAAa,SAAS,GAAG,OAAU,KAAK,IAAK,EAEzE,IAAY,EAAI,QAAQ,EACxB,IAAkB,MAAc,IAAI,IAAI,IAAY,GACpD,oBAAc,IAAI,KACtB,EAAa,SAAS,GAAG,IAAkB,KAAK,KAAK,KAAK,IAC3D,EAEK,IAAwB,EAAE,EAC1B,IAA4B,EAAE,EAC9B,IAA2B,EAAE,EAC7B,IAAwB,EAAE;AAEhC,MAAK,IAAM,KAAQ,GAAO;EAKxB,IAAM,KAHJ,OAAO,EAAK,aAAc,WACtB,IAAI,KAAK,EAAK,UAAU,GACxB,EAAK,WACE,SAAS;AAEtB,EAAI,KAAK,EAAa,SAAS,GAC7B,EAAM,KAAK,EAAK,GACP,KAAK,EAAiB,SAAS,GACxC,EAAU,KAAK,EAAK,GACX,KAAK,EAAY,SAAS,GACnC,EAAS,KAAK,EAAK,GAEnB,EAAM,KAAK,EAAK;;CAIpB,IAAM,IAAqD,EAAE;AAM7D,QALI,EAAM,SAAS,KAAG,EAAO,KAAK;EAAE,OAAO,EAAE;EAAO,OAAO;EAAO,CAAC,EAC/D,EAAU,SAAS,KAAG,EAAO,KAAK;EAAE,OAAO,EAAE;EAAW,OAAO;EAAW,CAAC,EAC3E,EAAS,SAAS,KAAG,EAAO,KAAK;EAAE,OAAO,EAAE;EAAU,OAAO;EAAU,CAAC,EACxE,EAAM,SAAS,KAAG,EAAO,KAAK;EAAE,OAAO,EAAE;EAAO,OAAO;EAAO,CAAC,EAE5D;;AAGT,SAAS,EAAgB,EAAE,cAAiC;AAC1D,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,iBAAiB,IAAU,UAAU,QAAQ;YAC7D;GAAC;GAAG;GAAG;GAAE,CAAC,KAAK,MACd,kBAAC,OAAD;GAAa,WAAU;aAAvB;IACE,kBAAC,GAAD,EAAU,WAAU,oCAAqC,CAAA;IACxD,CAAC,KAAW,kBAAC,GAAD,EAAU,WAAU,oCAAqC,CAAA;IACtE,kBAAC,GAAD,EAAU,WAAW,EAAG,UAAU,IAAU,QAAQ,MAAM,EAAI,CAAA;IAC9D,kBAAC,GAAD,EAAU,WAAU,YAAa,CAAA;IAC7B;KALI,EAKJ,CACN;EACE,CAAA;;AAIV,SAAS,EAAc,EACrB,SACA,cAIC;CACD,IAAM,CAAC,GAAgB,KAAqB,EAAM,SAAS,GAAM,EAC3D,IAAQ,EAAK,SAAS,WAEtB,UAA0B;AAC9B,EAAI,EAAK,UACP,GAAmB,MAAS,CAAC,EAAK;;AAItC,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,6BAA6B,IAAU,cAAc,YAAY;YAApF;GAEE,kBAAC,OAAD,EACE,WAAW,EACT,kFACA,EAAY,GACb,EACD,CAAA;GAGD,CAAC,KAAW,EAAK,SAChB,kBAAC,GAAD;IAAQ,WAAU;cAAlB,CACG,EAAK,MAAM,SACV,kBAAC,GAAD;KAAa,KAAK,EAAK,MAAM;KAAO,KAAK,EAAK,MAAM;KAAQ,CAAA,EAE9D,kBAAC,GAAD;KAAgB,WAAU;eACvB,EAAY,EAAK,MAAM,KAAK;KACd,CAAA,CACV;;GAIX,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAW,EAAG,wDAAwD,IAAU,eAAe,aAAa;gBAAjH;OACG,EAAK,SACJ,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CAA+C,EAAK,MAAM,MAAK,IAAQ;;OAExE,EAAK,UACJ,kBAAC,GAAD;QAAM,MAAM;QAAkB,MAAK;QAAK,WAAW,EAAG,kGAAkG,KAAkB,YAAY;QAAI,CAAA;OAE5L,kBAAC,QAAD;QACE,WAAW,EACT,yBACA,EAAK,UAAU,uEAChB;QACD,SAAS;QACT,MAAM,EAAK,SAAS,WAAW,KAAA;QAC/B,UAAU,EAAK,SAAS,IAAI,KAAA;QAC5B,WACE,EAAK,UACA,MAAM;AACL,UAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EAClB,GAAmB;YAGvB,KAAA;kBAGL,EAAK;QACD,CAAA;OACH;SAEN,kBAAC,QAAD;MACE,WAAW,EACT,qDACA,IAAU,eAAe,aAC1B;MACD,WACG,OAAO,EAAK,aAAc,WACvB,IAAI,KAAK,EAAK,UAAU,GACxB,EAAK,WACP,aAAa;gBAGhB,EAAmB,EAAK,UAAU;MAC9B,CAAA,CACH;QAGL,KAAkB,EAAK,UACtB,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAK;KACF,CAAA,CAEJ;;GACF;;;AAQV,SAAS,EAAY,EACnB,SACA,YACA,eAKC;CACD,IAAM,IAAQ,EAAK,SAAS;AAC5B,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,6BAA6B,IAAU,cAAc,YAAY;YAApF,CAEE,kBAAC,OAAD,EACE,WAAW,EACT,kFACA,EAAY,GACb,EACD,CAAA,EAEF,kBAAC,OAAD;GAAK,WAAU;GAAkB;GAAe,CAAA,CAC5C;;;AAIV,SAAS,EAAY,EACnB,UACA,cAIC;AACD,QACE,kBAAC,EAAO,KAAR;EACE,SAAS,EAAE,SAAS,GAAG;EACvB,SAAS,EAAE,SAAS,GAAG;EACvB,YAAY,EAAO;EACnB,WAAW,EACT,+BACC,IAAuB,KAAb,YACX,WACD;YARH;GAUE,kBAAC,MAAD,EAAI,WAAU,gCAAiC,CAAA;GAC/C,kBAAC,QAAD;IAAM,WAAU;cACb;IACI,CAAA;GACP,kBAAC,MAAD,EAAI,WAAU,gCAAiC,CAAA;GACpC;;;AAIjB,IAAM,IAAe,EAAM,YAEvB,EACE,UACA,eACA,aAAU,IACV,aAAU,IACV,eACA,aAAU,IACV,oBACA,aAAU,QACV,gBACA,eACA,cACA,GAAG,KAEL,MACG;CACH,IAAM,CAAC,GAAS,KAAc,EAAM,SAAS,GAAM;AAEnD,KAAI,EACF,QACE,kBAAC,OAAD;EAAU;EAAK,WAAW,EAAG,YAAY,EAAU;EAAE,GAAI;YACvD,kBAAC,GAAD,EAA0B,YAAW,CAAA;EACjC,CAAA;AAIV,KAAI,EAAM,WAAW,EAQnB,QAPI,IAEA,kBAAC,OAAD;EAAU;EAAgB;EAAW,GAAI;YACtC;EACG,CAAA,GAGH;CAIT,IAAM,IACJ,KAAmB,QAAQ,CAAC,KAAW,EAAM,SAAS,GAClD,IAAe,IAAY,EAAM,MAAM,GAAG,EAAgB,GAAG,GAE7D,IAAc,MAAY,QAG1B,KAAgB,GAAoB,MAAkB;AAC1D,MAAI,GAAY;GACd,IAAM,IAAS,EAAW,GAAM,EAAM;AACtC,OAAI,MAAW,KAAA,EACb,QAAO,kBAAC,GAAD;IAAmB;IAAe;cAAU;IAAqB,CAAA;;AAG5E,SAAO,kBAAC,GAAD;GAAqB;GAAe;GAAW,CAAA;;AAGxD,QACE,kBAAC,OAAD;EAAU;EAAK,WAAW,EAAG,YAAY,EAAU;EAAE,GAAI;YAAzD;GAEG,IACC,kBAAC,OAAD,EAAA,UACG,EAAiB,GAAc,EAAY,CAAC,KAAK,GAAO,MACvD,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,GAAD;IAAa,OAAO,EAAM;IAAO,SAAS,MAAO;IAAK,CAAA,EAEtD,kBAAC,OAAD;IAAK,WAAW,EAAG,0BAA0B,IAAU,UAAU,QAAQ;cAAzE,CACE,kBAAC,OAAD,EAAK,WAAU,6DAA8D,CAAA,EAC5E,EAAM,MAAM,KAAK,GAAM,MACtB,kBAAC,EAAO,KAAR;KAA0B,SAAS,EAAE,SAAS,GAAG;KAAE,SAAS,EAAE,SAAS,GAAG;KAAE,YAAY;MAAE,GAAG,EAAO;MAAM,OAAO,IAAQ;MAAM;eAC5H,EAAa,GAAM,EAAM;KACf,EAFI,EAAK,GAET,CACb,CACE;MACF,EAAA,EAXI,EAAM,MAWV,CACN,EACE,CAAA,GAEN,kBAAC,OAAD;IAAK,WAAW,EAAG,0BAA0B,IAAU,UAAU,QAAQ;cAAzE,CAEE,kBAAC,OAAD,EAAK,WAAU,6DAA8D,CAAA,EAC5E,EAAa,KAAK,GAAM,MACvB,kBAAC,EAAO,KAAR;KAA0B,SAAS,EAAE,SAAS,GAAG;KAAE,SAAS,EAAE,SAAS,GAAG;KAAE,YAAY;MAAE,GAAG,EAAO;MAAM,OAAO,IAAQ;MAAM;eAC5H,EAAa,GAAM,EAAM;KACf,EAFI,EAAK,GAET,CACb,CACE;;GAIP,KACC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACE,SAAQ;KACR,MAAK;KACL,eAAe,EAAW,GAAK;eAHjC;MAIC;MACY,EAAM;MAAO;MACjB;;IACL,CAAA;GAIP,KAAW,KAAc,CAAC,KACzB,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KAAQ,SAAQ;KAAQ,MAAK;KAAK,SAAS;eAAY;KAE9C,CAAA;IACL,CAAA;GAEJ;;EAGX;AACD,EAAa,cAAc"}
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 { 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?: React.ReactNode\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 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-ds-xs' : 'text-ds-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 <span\n className={cn(\n 'text-surface-fg-muted',\n item.detail && 'cursor-pointer hover:underline hover:bg-surface-raised rounded-control',\n )}\n onClick={handleActionClick}\n role={item.detail ? 'button' : undefined}\n tabIndex={item.detail ? 0 : undefined}\n onKeyDown={\n item.detail\n ? (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n handleActionClick()\n }\n }\n : undefined\n }\n >\n {item.action}\n </span>\n </div>\n\n <time\n className={cn(\n 'shrink-0 whitespace-nowrap text-surface-fg-subtle',\n compact ? 'text-ds-xs' : 'text-ds-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 className=\"mt-ds-02 animate-in fade-in slide-in-from-top-1 text-ds-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={{ opacity: 0 }}\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-ds-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={{ opacity: 0 }} 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={{ opacity: 0 }} 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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"}
@@ -13,7 +13,7 @@ var g = {
13
13
  lead: "ring-2 ring-accent-7 ring-offset-1 ring-offset-surface-raised",
14
14
  admin: "ring-2 ring-warning-7 ring-offset-1 ring-offset-surface-raised",
15
15
  client: "ring-2 ring-info-7 ring-offset-1 ring-offset-surface-raised"
16
- }, _ = t("shrink-0 overflow-hidden rounded-ds-full border-2", {
16
+ }, _ = t("shrink-0 overflow-hidden rounded-pill border-2", {
17
17
  variants: { size: {
18
18
  xs: "h-ds-xs w-ds-xs",
19
19
  sm: "h-ds-sm w-ds-sm",
@@ -74,7 +74,7 @@ var g = {
74
74
  children: [k.map((t, c) => {
75
75
  let d = u(t.name), _ = `${t.name}-${c}`;
76
76
  if (C) return /* @__PURE__ */ f("div", {
77
- className: n("shrink-0 rounded-ds-full", c > 0 && j, V, t.ring && t.ring !== "none" && g[t.ring]),
77
+ className: n("shrink-0 rounded-pill", c > 0 && j, V, t.ring && t.ring !== "none" && g[t.ring]),
78
78
  style: {
79
79
  zIndex: k.length - c,
80
80
  transform: B(c)
@@ -82,7 +82,7 @@ var g = {
82
82
  children: C(t, c)
83
83
  }, _);
84
84
  let v = /* @__PURE__ */ p("div", {
85
- className: n("relative shrink-0 rounded-ds-full", c > 0 && j, V, t.ring && t.ring !== "none" && g[t.ring]),
85
+ className: n("relative shrink-0 rounded-pill", c > 0 && j, V, t.ring && t.ring !== "none" && g[t.ring]),
86
86
  style: {
87
87
  zIndex: k.length - c,
88
88
  transform: B(c)
@@ -111,7 +111,7 @@ var g = {
111
111
  opacity: 0
112
112
  },
113
113
  transition: e.snappy,
114
- className: n("absolute top-0 right-0 rounded-ds-full ring-1 ring-surface-raised", N, t.indicator === "lead" || t.indicator === "admin" ? "bg-accent-9" : ""),
114
+ className: n("absolute top-0 right-0 rounded-pill ring-1 ring-surface-raised", N, t.indicator === "lead" || t.indicator === "admin" ? "bg-accent-9" : ""),
115
115
  children: typeof t.indicator != "string" && t.indicator
116
116
  }) })]
117
117
  }, _);
@@ -1 +1 @@
1
- {"version":3,"file":"avatar-group.js","names":[],"sources":["../../src/composed/avatar-group.tsx"],"sourcesContent":["'use client'\n\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport type { AvatarRing } from '../ui/avatar'\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\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\n// Ring classes applied at group level (not via Avatar's ring prop, which adds ring-offset-2 that gets clipped by overflow-hidden)\nconst groupRingMap: Record<string, string> = {\n lead: 'ring-2 ring-accent-7 ring-offset-1 ring-offset-surface-raised',\n admin: 'ring-2 ring-warning-7 ring-offset-1 ring-offset-surface-raised',\n client: 'ring-2 ring-info-7 ring-offset-1 ring-offset-surface-raised',\n}\n\nconst avatarSizeVariants = cva(\n 'shrink-0 overflow-hidden rounded-ds-full border-2',\n {\n variants: {\n size: {\n xs: 'h-ds-xs w-ds-xs',\n sm: 'h-ds-sm w-ds-sm',\n md: 'h-ds-md w-ds-md',\n lg: 'h-ds-lg w-ds-lg',\n xl: 'h-ds-xl w-ds-xl',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n },\n)\n\nexport interface AvatarUser {\n name: string\n image?: string | null\n ring?: AvatarRing\n indicator?: 'lead' | 'admin' | React.ReactNode\n}\n\nexport interface AvatarGroupProps\n extends React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof avatarSizeVariants> {\n users: AvatarUser[]\n max?: number\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 */\n onOverflowClick?: () => void\n /** Custom render function for each avatar */\n renderAvatar?: (user: AvatarUser, index: number) => React.ReactNode\n /** Direction avatars expand on hover. @default 'right' */\n expandDirection?: 'left' | 'right'\n /** How much avatars spread apart on hover. @default 'default' */\n expandAmount?: 'compact' | 'default' | 'wide'\n}\n\nconst AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(\n (\n {\n users,\n max = 4,\n size,\n showTooltip = true,\n borderColor = 'surface-raised',\n onOverflowClick,\n renderAvatar,\n expandDirection = 'right',\n expandAmount = 'default',\n className,\n ...props\n },\n ref,\n ) => {\n const displayed = users.slice(0, max)\n const overflow = users.length - max\n\n const overlapMap = {\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 }\n const overlapClass = overlapMap[size ?? 'md']\n\n // Match Avatar fallback font sizes so the \"+N\" badge text is consistent\n const textSizeMap: Record<string, string> = {\n xs: 'text-[9px]',\n sm: 'text-ds-xs',\n md: 'text-ds-sm',\n lg: 'text-ds-md',\n xl: 'text-ds-lg',\n }\n const textSizeClass = textSizeMap[size ?? 'md']\n\n // Indicator dot scales with avatar size (matches Avatar's statusDotSizeMap)\n const indicatorDotSizeMap: Record<string, string> = {\n xs: 'h-1 w-1',\n sm: 'h-1.5 w-1.5',\n md: 'h-2 w-2',\n lg: 'h-2.5 w-2.5',\n xl: 'h-3 w-3',\n }\n const indicatorDotClass = indicatorDotSizeMap[size ?? 'md']\n\n const borderClass =\n borderColor === 'surface-base'\n ? 'border-surface-base'\n : 'border-surface-raised'\n\n const [isHovered, setIsHovered] = React.useState(false)\n\n const overlapPxMap: Record<string, number> = {\n xs: 8, sm: 10, md: 12, lg: 16, xl: 20,\n }\n const resolvedSize = size ?? 'md'\n const overlapPx = overlapPxMap[resolvedSize]\n const expandMultiplier = { compact: 0.5, default: 1, wide: 1.5 }[expandAmount]\n const totalVisible = displayed.length + (overflow > 0 ? 1 : 0)\n\n function getExpandTransform(index: number): string {\n if (!isHovered) return 'translateX(0)'\n const shift = overlapPx * expandMultiplier\n if (expandDirection === 'left') {\n return `translateX(-${(totalVisible - 1 - index) * shift}px)`\n }\n return `translateX(${index * shift}px)`\n }\n\n const spotlightClasses =\n 'transition-[transform,opacity] duration-300 ease-out hover:z-50 hover:scale-105 group-hover:[&:not(:hover)]:opacity-85'\n\n return (\n <TooltipProvider>\n <div\n ref={ref}\n role=\"group\"\n aria-label={`${users.length} team members`}\n tabIndex={0}\n className={cn('group flex items-center', className)}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n onFocus={() => setIsHovered(true)}\n onBlur={(e) => { if (!e.currentTarget.contains(e.relatedTarget)) setIsHovered(false) }}\n {...props}\n >\n {displayed.map((user, index) => {\n const initials = getInitials(user.name)\n\n const key = `${user.name}-${index}`\n\n if (renderAvatar) {\n // When renderAvatar is provided, the consumer owns the Avatar entirely.\n // Wrapper is positioning-only (overlap, z-index, spotlight) — no size/border/clip\n // so the consumer's Avatar renders at its natural size without being clipped.\n return (\n <div\n key={key}\n className={cn(\n 'shrink-0 rounded-ds-full',\n index > 0 && overlapClass,\n spotlightClasses,\n user.ring && user.ring !== 'none' && groupRingMap[user.ring],\n )}\n style={{ zIndex: displayed.length - index, transform: getExpandTransform(index) }}\n >\n {renderAvatar(user, index)}\n </div>\n )\n }\n\n const avatarNode = (\n <div\n key={key}\n className={cn(\n 'relative shrink-0 rounded-ds-full',\n index > 0 && overlapClass,\n spotlightClasses,\n user.ring && user.ring !== 'none' && groupRingMap[user.ring],\n )}\n style={{ zIndex: displayed.length - index, transform: getExpandTransform(index) }}\n >\n <Avatar\n size={size}\n className={borderClass}\n >\n {user.image && (\n <AvatarImage src={user.image} alt={user.name} />\n )}\n <AvatarFallback\n colorSeed={user.name}\n >\n {initials}\n </AvatarFallback>\n </Avatar>\n <AnimatePresence>\n {user.indicator && (\n <motion.span\n initial={{ scale: 0, opacity: 0 }}\n animate={{ scale: 1, opacity: 1 }}\n exit={{ scale: 0, opacity: 0 }}\n transition={springs.snappy}\n className={cn(\n 'absolute top-0 right-0 rounded-ds-full ring-1 ring-surface-raised',\n indicatorDotClass,\n user.indicator === 'lead' ? 'bg-accent-9' :\n user.indicator === 'admin' ? 'bg-accent-9' : '',\n )}\n >\n {typeof user.indicator !== 'string' && user.indicator}\n </motion.span>\n )}\n </AnimatePresence>\n </div>\n )\n\n if (!showTooltip) return avatarNode\n\n return (\n <Tooltip key={key}>\n <TooltipTrigger asChild>{avatarNode}</TooltipTrigger>\n <TooltipContent\n className=\"border-surface-border-strong bg-surface-base text-surface-fg\"\n sideOffset={6}\n >\n <p className=\"text-ds-sm\">{user.name}</p>\n </TooltipContent>\n </Tooltip>\n )\n })}\n\n {overflow > 0 && (\n <Tooltip>\n <TooltipTrigger asChild>\n {onOverflowClick ? (\n <button\n type=\"button\"\n onClick={onOverflowClick}\n aria-label={`${overflow} more members`}\n className={cn(\n avatarSizeVariants({ size }),\n borderClass,\n overlapClass,\n `flex cursor-pointer items-center justify-center bg-accent-2 font-semibold text-accent-11 ${textSizeClass}`,\n 'hover:scale-105 hover:bg-accent-3 transition-[transform,background-color] duration-300 ease-out',\n )}\n style={{ zIndex: 0, transform: getExpandTransform(displayed.length) }}\n >\n +{overflow}\n </button>\n ) : (\n <div\n role=\"img\"\n aria-label={`${overflow} more members`}\n className={cn(\n avatarSizeVariants({ size }),\n borderClass,\n overlapClass,\n `flex cursor-default items-center justify-center bg-accent-2 font-semibold text-accent-11 ${textSizeClass}`,\n 'transition-[transform,opacity] duration-300 ease-out',\n )}\n style={{ zIndex: 0, transform: getExpandTransform(displayed.length) }}\n >\n +{overflow}\n </div>\n )}\n </TooltipTrigger>\n <TooltipContent\n className=\"border-surface-border-strong bg-surface-base text-surface-fg\"\n sideOffset={6}\n >\n <div className=\"flex flex-col gap-ds-01\">\n {users.slice(max).map((user, i) => (\n <p key={i} className=\"text-ds-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":";;;;;;;;;;;AAuBA,IAAM,IAAuC;CAC3C,MAAM;CACN,OAAO;CACP,QAAQ;CACT,EAEK,IAAqB,EACzB,qDACA;CACE,UAAU,EACR,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACL,EACF;CACD,iBAAiB,EACf,MAAM,MACP;CACF,CACF,EA2BK,IAAc,EAAM,YAEtB,EACE,UACA,SAAM,GACN,SACA,iBAAc,IACd,iBAAc,kBACd,oBACA,iBACA,qBAAkB,SAClB,kBAAe,WACf,cACA,GAAG,KAEL,MACG;CACH,IAAM,IAAY,EAAM,MAAM,GAAG,EAAI,EAC/B,IAAW,EAAM,SAAS,GAS1B,IAPa;EACjB,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACL,CAC+B,KAAQ,OAUlC,IAPsC;EAC1C,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACL,CACiC,KAAQ,OAUpC,IAP8C;EAClD,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACL,CAC6C,KAAQ,OAEhD,IACJ,MAAgB,iBACZ,wBACA,yBAEA,CAAC,GAAW,KAAgB,EAAM,SAAS,GAAM,EAMjD,IAJuC;EAC3C,IAAI;EAAG,IAAI;EAAI,IAAI;EAAI,IAAI;EAAI,IAAI;EACpC,CACoB,KAAQ,OAEvB,IAAmB;EAAE,SAAS;EAAK,SAAS;EAAG,MAAM;EAAK,CAAC,IAC3D,IAAe,EAAU,SAAU,MAAW;CAEpD,SAAS,EAAmB,GAAuB;AACjD,MAAI,CAAC,EAAW,QAAO;EACvB,IAAM,IAAQ,IAAY;AAI1B,SAHI,MAAoB,SACf,gBAAgB,IAAe,IAAI,KAAS,EAAM,OAEpD,cAAc,IAAQ,EAAM;;CAGrC,IAAM,IACJ;AAEF,QACE,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;EACO;EACL,MAAK;EACL,cAAY,GAAG,EAAM,OAAO;EAC5B,UAAU;EACV,WAAW,EAAG,2BAA2B,EAAU;EACnD,oBAAoB,EAAa,GAAK;EACtC,oBAAoB,EAAa,GAAM;EACvC,eAAe,EAAa,GAAK;EACjC,SAAS,MAAM;AAAE,GAAK,EAAE,cAAc,SAAS,EAAE,cAAc,IAAE,EAAa,GAAM;;EACpF,GAAI;YAVN,CAYG,EAAU,KAAK,GAAM,MAAU;GAC9B,IAAM,IAAW,EAAY,EAAK,KAAK,EAEjC,IAAM,GAAG,EAAK,KAAK,GAAG;AAE5B,OAAI,EAIF,QACE,kBAAC,OAAD;IAEE,WAAW,EACT,4BACA,IAAQ,KAAK,GACb,GACA,EAAK,QAAQ,EAAK,SAAS,UAAU,EAAa,EAAK,MACxD;IACD,OAAO;KAAE,QAAQ,EAAU,SAAS;KAAO,WAAW,EAAmB,EAAM;KAAE;cAEhF,EAAa,GAAM,EAAM;IACtB,EAVC,EAUD;GAIV,IAAM,IACJ,kBAAC,OAAD;IAEE,WAAW,EACT,qCACA,IAAQ,KAAK,GACb,GACA,EAAK,QAAQ,EAAK,SAAS,UAAU,EAAa,EAAK,MACxD;IACD,OAAO;KAAE,QAAQ,EAAU,SAAS;KAAO,WAAW,EAAmB,EAAM;KAAE;cARnF,CAUE,kBAAC,GAAD;KACQ;KACN,WAAW;eAFb,CAIG,EAAK,SACJ,kBAAC,GAAD;MAAa,KAAK,EAAK;MAAO,KAAK,EAAK;MAAQ,CAAA,EAElD,kBAAC,GAAD;MACE,WAAW,EAAK;gBAEf;MACc,CAAA,CACV;QACT,kBAAC,GAAD,EAAA,UACG,EAAK,aACJ,kBAAC,EAAO,MAAR;KACE,SAAS;MAAE,OAAO;MAAG,SAAS;MAAG;KACjC,SAAS;MAAE,OAAO;MAAG,SAAS;MAAG;KACjC,MAAM;MAAE,OAAO;MAAG,SAAS;MAAG;KAC9B,YAAY,EAAQ;KACpB,WAAW,EACT,qEACA,GACA,EAAK,cAAc,UACnB,EAAK,cAAc,UADS,gBACiB,GAC9C;eAEA,OAAO,EAAK,aAAc,YAAY,EAAK;KAChC,CAAA,EAEA,CAAA,CACd;MAxCC,EAwCD;AAKR,UAFK,IAGH,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;IAAgB,SAAA;cAAS;IAA4B,CAAA,EACrD,kBAAC,GAAD;IACE,WAAU;IACV,YAAY;cAEZ,kBAAC,KAAD;KAAG,WAAU;eAAc,EAAK;KAAS,CAAA;IAC1B,CAAA,CACT,EAAA,EARI,EAQJ,GAXa;IAazB,EAED,IAAW,KACV,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;GAAgB,SAAA;aACb,IACC,kBAAC,UAAD;IACE,MAAK;IACL,SAAS;IACT,cAAY,GAAG,EAAS;IACxB,WAAW,EACT,EAAmB,EAAE,SAAM,CAAC,EAC5B,GACA,GACA,4FAA4F,KAC5F,kGACD;IACD,OAAO;KAAE,QAAQ;KAAG,WAAW,EAAmB,EAAU,OAAO;KAAE;cAXvE,CAYC,KACG,EACK;QAET,kBAAC,OAAD;IACE,MAAK;IACL,cAAY,GAAG,EAAS;IACxB,WAAW,EACT,EAAmB,EAAE,SAAM,CAAC,EAC5B,GACA,GACA,4FAA4F,KAC5F,uDACD;IACD,OAAO;KAAE,QAAQ;KAAG,WAAW,EAAmB,EAAU,OAAO;KAAE;cAVvE,CAWC,KACG,EACE;;GAEO,CAAA,EACjB,kBAAC,GAAD;GACE,WAAU;GACV,YAAY;aAEZ,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAM,MAAM,EAAI,CAAC,KAAK,GAAM,MAC3B,kBAAC,KAAD;KAAW,WAAU;eAAc,EAAK;KAAS,EAAzC,EAAyC,CACjD;IACE,CAAA;GACS,CAAA,CACT,EAAA,CAAA,CAER;KACU,CAAA;EAGvB;AACD,EAAY,cAAc"}
1
+ {"version":3,"file":"avatar-group.js","names":[],"sources":["../../src/composed/avatar-group.tsx"],"sourcesContent":["'use client'\n\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport type { AvatarRing } from '../ui/avatar'\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\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\n// Ring classes applied at group level (not via Avatar's ring prop, which adds ring-offset-2 that gets clipped by overflow-hidden)\nconst groupRingMap: Record<string, string> = {\n lead: 'ring-2 ring-accent-7 ring-offset-1 ring-offset-surface-raised',\n admin: 'ring-2 ring-warning-7 ring-offset-1 ring-offset-surface-raised',\n client: 'ring-2 ring-info-7 ring-offset-1 ring-offset-surface-raised',\n}\n\nconst avatarSizeVariants = cva(\n 'shrink-0 overflow-hidden rounded-pill border-2',\n {\n variants: {\n size: {\n xs: 'h-ds-xs w-ds-xs',\n sm: 'h-ds-sm w-ds-sm',\n md: 'h-ds-md w-ds-md',\n lg: 'h-ds-lg w-ds-lg',\n xl: 'h-ds-xl w-ds-xl',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n },\n)\n\nexport interface AvatarUser {\n name: string\n image?: string | null\n ring?: AvatarRing\n indicator?: 'lead' | 'admin' | React.ReactNode\n}\n\nexport interface AvatarGroupProps\n extends React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof avatarSizeVariants> {\n users: AvatarUser[]\n max?: number\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 */\n onOverflowClick?: () => void\n /** Custom render function for each avatar */\n renderAvatar?: (user: AvatarUser, index: number) => React.ReactNode\n /** Direction avatars expand on hover. @default 'right' */\n expandDirection?: 'left' | 'right'\n /** How much avatars spread apart on hover. @default 'default' */\n expandAmount?: 'compact' | 'default' | 'wide'\n}\n\nconst AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(\n (\n {\n users,\n max = 4,\n size,\n showTooltip = true,\n borderColor = 'surface-raised',\n onOverflowClick,\n renderAvatar,\n expandDirection = 'right',\n expandAmount = 'default',\n className,\n ...props\n },\n ref,\n ) => {\n const displayed = users.slice(0, max)\n const overflow = users.length - max\n\n const overlapMap = {\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 }\n const overlapClass = overlapMap[size ?? 'md']\n\n // Match Avatar fallback font sizes so the \"+N\" badge text is consistent\n const textSizeMap: Record<string, string> = {\n xs: 'text-[9px]',\n sm: 'text-ds-xs',\n md: 'text-ds-sm',\n lg: 'text-ds-md',\n xl: 'text-ds-lg',\n }\n const textSizeClass = textSizeMap[size ?? 'md']\n\n // Indicator dot scales with avatar size (matches Avatar's statusDotSizeMap)\n const indicatorDotSizeMap: Record<string, string> = {\n xs: 'h-1 w-1',\n sm: 'h-1.5 w-1.5',\n md: 'h-2 w-2',\n lg: 'h-2.5 w-2.5',\n xl: 'h-3 w-3',\n }\n const indicatorDotClass = indicatorDotSizeMap[size ?? 'md']\n\n const borderClass =\n borderColor === 'surface-base'\n ? 'border-surface-base'\n : 'border-surface-raised'\n\n const [isHovered, setIsHovered] = React.useState(false)\n\n const overlapPxMap: Record<string, number> = {\n xs: 8, sm: 10, md: 12, lg: 16, xl: 20,\n }\n const resolvedSize = size ?? 'md'\n const overlapPx = overlapPxMap[resolvedSize]\n const expandMultiplier = { compact: 0.5, default: 1, wide: 1.5 }[expandAmount]\n const totalVisible = displayed.length + (overflow > 0 ? 1 : 0)\n\n function getExpandTransform(index: number): string {\n if (!isHovered) return 'translateX(0)'\n const shift = overlapPx * expandMultiplier\n if (expandDirection === 'left') {\n return `translateX(-${(totalVisible - 1 - index) * shift}px)`\n }\n return `translateX(${index * shift}px)`\n }\n\n const spotlightClasses =\n 'transition-[transform,opacity] duration-300 ease-out hover:z-50 hover:scale-105 group-hover:[&:not(:hover)]:opacity-85'\n\n return (\n <TooltipProvider>\n <div\n ref={ref}\n role=\"group\"\n aria-label={`${users.length} team members`}\n tabIndex={0}\n className={cn('group flex items-center', className)}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n onFocus={() => setIsHovered(true)}\n onBlur={(e) => { if (!e.currentTarget.contains(e.relatedTarget)) setIsHovered(false) }}\n {...props}\n >\n {displayed.map((user, index) => {\n const initials = getInitials(user.name)\n\n const key = `${user.name}-${index}`\n\n if (renderAvatar) {\n // When renderAvatar is provided, the consumer owns the Avatar entirely.\n // Wrapper is positioning-only (overlap, z-index, spotlight) — no size/border/clip\n // so the consumer's Avatar renders at its natural size without being clipped.\n return (\n <div\n key={key}\n className={cn(\n 'shrink-0 rounded-pill',\n index > 0 && overlapClass,\n spotlightClasses,\n user.ring && user.ring !== 'none' && groupRingMap[user.ring],\n )}\n style={{ zIndex: displayed.length - index, transform: getExpandTransform(index) }}\n >\n {renderAvatar(user, index)}\n </div>\n )\n }\n\n const avatarNode = (\n <div\n key={key}\n className={cn(\n 'relative shrink-0 rounded-pill',\n index > 0 && overlapClass,\n spotlightClasses,\n user.ring && user.ring !== 'none' && groupRingMap[user.ring],\n )}\n style={{ zIndex: displayed.length - index, transform: getExpandTransform(index) }}\n >\n <Avatar\n size={size}\n className={borderClass}\n >\n {user.image && (\n <AvatarImage src={user.image} alt={user.name} />\n )}\n <AvatarFallback\n colorSeed={user.name}\n >\n {initials}\n </AvatarFallback>\n </Avatar>\n <AnimatePresence>\n {user.indicator && (\n <motion.span\n initial={{ scale: 0, opacity: 0 }}\n animate={{ scale: 1, opacity: 1 }}\n exit={{ scale: 0, opacity: 0 }}\n transition={springs.snappy}\n className={cn(\n 'absolute top-0 right-0 rounded-pill ring-1 ring-surface-raised',\n indicatorDotClass,\n user.indicator === 'lead' ? 'bg-accent-9' :\n user.indicator === 'admin' ? 'bg-accent-9' : '',\n )}\n >\n {typeof user.indicator !== 'string' && user.indicator}\n </motion.span>\n )}\n </AnimatePresence>\n </div>\n )\n\n if (!showTooltip) return avatarNode\n\n return (\n <Tooltip key={key}>\n <TooltipTrigger asChild>{avatarNode}</TooltipTrigger>\n <TooltipContent\n className=\"border-surface-border-strong bg-surface-base text-surface-fg\"\n sideOffset={6}\n >\n <p className=\"text-ds-sm\">{user.name}</p>\n </TooltipContent>\n </Tooltip>\n )\n })}\n\n {overflow > 0 && (\n <Tooltip>\n <TooltipTrigger asChild>\n {onOverflowClick ? (\n <button\n type=\"button\"\n onClick={onOverflowClick}\n aria-label={`${overflow} more members`}\n className={cn(\n avatarSizeVariants({ size }),\n borderClass,\n overlapClass,\n `flex cursor-pointer items-center justify-center bg-accent-2 font-semibold text-accent-11 ${textSizeClass}`,\n 'hover:scale-105 hover:bg-accent-3 transition-[transform,background-color] duration-300 ease-out',\n )}\n style={{ zIndex: 0, transform: getExpandTransform(displayed.length) }}\n >\n +{overflow}\n </button>\n ) : (\n <div\n role=\"img\"\n aria-label={`${overflow} more members`}\n className={cn(\n avatarSizeVariants({ size }),\n borderClass,\n overlapClass,\n `flex cursor-default items-center justify-center bg-accent-2 font-semibold text-accent-11 ${textSizeClass}`,\n 'transition-[transform,opacity] duration-300 ease-out',\n )}\n style={{ zIndex: 0, transform: getExpandTransform(displayed.length) }}\n >\n +{overflow}\n </div>\n )}\n </TooltipTrigger>\n <TooltipContent\n className=\"border-surface-border-strong bg-surface-base text-surface-fg\"\n sideOffset={6}\n >\n <div className=\"flex flex-col gap-ds-01\">\n {users.slice(max).map((user, i) => (\n <p key={i} className=\"text-ds-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":";;;;;;;;;;;AAuBA,IAAM,IAAuC;CAC3C,MAAM;CACN,OAAO;CACP,QAAQ;CACT,EAEK,IAAqB,EACzB,kDACA;CACE,UAAU,EACR,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACL,EACF;CACD,iBAAiB,EACf,MAAM,MACP;CACF,CACF,EA2BK,IAAc,EAAM,YAEtB,EACE,UACA,SAAM,GACN,SACA,iBAAc,IACd,iBAAc,kBACd,oBACA,iBACA,qBAAkB,SAClB,kBAAe,WACf,cACA,GAAG,KAEL,MACG;CACH,IAAM,IAAY,EAAM,MAAM,GAAG,EAAI,EAC/B,IAAW,EAAM,SAAS,GAS1B,IAPa;EACjB,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACL,CAC+B,KAAQ,OAUlC,IAPsC;EAC1C,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACL,CACiC,KAAQ,OAUpC,IAP8C;EAClD,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACL,CAC6C,KAAQ,OAEhD,IACJ,MAAgB,iBACZ,wBACA,yBAEA,CAAC,GAAW,KAAgB,EAAM,SAAS,GAAM,EAMjD,IAJuC;EAC3C,IAAI;EAAG,IAAI;EAAI,IAAI;EAAI,IAAI;EAAI,IAAI;EACpC,CACoB,KAAQ,OAEvB,IAAmB;EAAE,SAAS;EAAK,SAAS;EAAG,MAAM;EAAK,CAAC,IAC3D,IAAe,EAAU,SAAU,MAAW;CAEpD,SAAS,EAAmB,GAAuB;AACjD,MAAI,CAAC,EAAW,QAAO;EACvB,IAAM,IAAQ,IAAY;AAI1B,SAHI,MAAoB,SACf,gBAAgB,IAAe,IAAI,KAAS,EAAM,OAEpD,cAAc,IAAQ,EAAM;;CAGrC,IAAM,IACJ;AAEF,QACE,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;EACO;EACL,MAAK;EACL,cAAY,GAAG,EAAM,OAAO;EAC5B,UAAU;EACV,WAAW,EAAG,2BAA2B,EAAU;EACnD,oBAAoB,EAAa,GAAK;EACtC,oBAAoB,EAAa,GAAM;EACvC,eAAe,EAAa,GAAK;EACjC,SAAS,MAAM;AAAE,GAAK,EAAE,cAAc,SAAS,EAAE,cAAc,IAAE,EAAa,GAAM;;EACpF,GAAI;YAVN,CAYG,EAAU,KAAK,GAAM,MAAU;GAC9B,IAAM,IAAW,EAAY,EAAK,KAAK,EAEjC,IAAM,GAAG,EAAK,KAAK,GAAG;AAE5B,OAAI,EAIF,QACE,kBAAC,OAAD;IAEE,WAAW,EACT,yBACA,IAAQ,KAAK,GACb,GACA,EAAK,QAAQ,EAAK,SAAS,UAAU,EAAa,EAAK,MACxD;IACD,OAAO;KAAE,QAAQ,EAAU,SAAS;KAAO,WAAW,EAAmB,EAAM;KAAE;cAEhF,EAAa,GAAM,EAAM;IACtB,EAVC,EAUD;GAIV,IAAM,IACJ,kBAAC,OAAD;IAEE,WAAW,EACT,kCACA,IAAQ,KAAK,GACb,GACA,EAAK,QAAQ,EAAK,SAAS,UAAU,EAAa,EAAK,MACxD;IACD,OAAO;KAAE,QAAQ,EAAU,SAAS;KAAO,WAAW,EAAmB,EAAM;KAAE;cARnF,CAUE,kBAAC,GAAD;KACQ;KACN,WAAW;eAFb,CAIG,EAAK,SACJ,kBAAC,GAAD;MAAa,KAAK,EAAK;MAAO,KAAK,EAAK;MAAQ,CAAA,EAElD,kBAAC,GAAD;MACE,WAAW,EAAK;gBAEf;MACc,CAAA,CACV;QACT,kBAAC,GAAD,EAAA,UACG,EAAK,aACJ,kBAAC,EAAO,MAAR;KACE,SAAS;MAAE,OAAO;MAAG,SAAS;MAAG;KACjC,SAAS;MAAE,OAAO;MAAG,SAAS;MAAG;KACjC,MAAM;MAAE,OAAO;MAAG,SAAS;MAAG;KAC9B,YAAY,EAAQ;KACpB,WAAW,EACT,kEACA,GACA,EAAK,cAAc,UACnB,EAAK,cAAc,UADS,gBACiB,GAC9C;eAEA,OAAO,EAAK,aAAc,YAAY,EAAK;KAChC,CAAA,EAEA,CAAA,CACd;MAxCC,EAwCD;AAKR,UAFK,IAGH,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;IAAgB,SAAA;cAAS;IAA4B,CAAA,EACrD,kBAAC,GAAD;IACE,WAAU;IACV,YAAY;cAEZ,kBAAC,KAAD;KAAG,WAAU;eAAc,EAAK;KAAS,CAAA;IAC1B,CAAA,CACT,EAAA,EARI,EAQJ,GAXa;IAazB,EAED,IAAW,KACV,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;GAAgB,SAAA;aACb,IACC,kBAAC,UAAD;IACE,MAAK;IACL,SAAS;IACT,cAAY,GAAG,EAAS;IACxB,WAAW,EACT,EAAmB,EAAE,SAAM,CAAC,EAC5B,GACA,GACA,4FAA4F,KAC5F,kGACD;IACD,OAAO;KAAE,QAAQ;KAAG,WAAW,EAAmB,EAAU,OAAO;KAAE;cAXvE,CAYC,KACG,EACK;QAET,kBAAC,OAAD;IACE,MAAK;IACL,cAAY,GAAG,EAAS;IACxB,WAAW,EACT,EAAmB,EAAE,SAAM,CAAC,EAC5B,GACA,GACA,4FAA4F,KAC5F,uDACD;IACD,OAAO;KAAE,QAAQ;KAAG,WAAW,EAAmB,EAAU,OAAO;KAAE;cAVvE,CAWC,KACG,EACE;;GAEO,CAAA,EACjB,kBAAC,GAAD;GACE,WAAU;GACV,YAAY;aAEZ,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAM,MAAM,EAAI,CAAC,KAAK,GAAM,MAC3B,kBAAC,KAAD;KAAW,WAAU;eAAc,EAAK;KAAS,EAAzC,EAAyC,CACjD;IACE,CAAA;GACS,CAAA,CACT,EAAA,CAAA,CAER;KACU,CAAA;EAGvB;AACD,EAAY,cAAc"}
@@ -122,7 +122,7 @@ function p({ show: p, count: m, onClearSelection: h, actions: g, totalCount: _,
122
122
  opacity: 0
123
123
  },
124
124
  transition: e.snappy,
125
- className: t("fixed bottom-ds-06 left-1/2 z-sticky -translate-x-1/2", "flex items-center gap-ds-04 rounded-ds-lg border border-surface-border bg-surface-overlay px-ds-05 py-ds-03 shadow-floating", y),
125
+ className: t("fixed bottom-ds-06 left-1/2 z-sticky -translate-x-1/2", "flex items-center gap-ds-04 rounded-surface border border-surface-border bg-surface-overlay px-ds-05 py-ds-03 shadow-floating", y),
126
126
  role: "toolbar",
127
127
  "aria-label": `${m} items selected`,
128
128
  onKeyDown: T,
@@ -1 +1 @@
1
- {"version":3,"file":"bulk-action-bar.js","names":[],"sources":["../../src/composed/bulk-action-bar.tsx"],"sourcesContent":["'use client'\n\nimport { IconX } from '@tabler/icons-react'\nimport { AnimatePresence, motion } from 'framer-motion'\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\n\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\nimport { Icon, type IconProps } from '../ui/icon'\nimport { springs } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\n\n// ============================================================\n// Types\n// ============================================================\n\nexport interface BulkActionBarAction {\n label: string\n icon?: IconProps['icon']\n onClick: () => void\n color?: 'accent' | 'error'\n disabled?: boolean\n /** Show inline confirmation before executing the action */\n requiresConfirmation?: boolean\n /** Custom confirmation message @default 'Are you sure?' */\n confirmMessage?: string\n}\n\nexport interface BulkActionBarProps {\n show: boolean\n count: number\n onClearSelection: () => void\n actions: BulkActionBarAction[]\n /** Total number of items available for selection */\n totalCount?: number\n /** Called when user clicks \"Select all\" */\n onSelectAll?: () => void\n className?: string\n}\n\n// ============================================================\n// ActionButton — handles inline confirmation\n// ============================================================\n\nconst ActionButton = React.forwardRef<HTMLDivElement, { action: BulkActionBarAction; tabIndex?: number }>(function ActionButton({ action, tabIndex }, ref) {\n const [confirming, setConfirming] = React.useState(false)\n\n if (confirming) {\n return (\n <motion.div\n ref={ref}\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={springs.snappy}\n className=\"flex items-center gap-ds-02\"\n tabIndex={tabIndex}\n >\n <span className=\"text-ds-sm text-surface-fg-muted whitespace-nowrap\">\n {action.confirmMessage ?? 'Are you sure?'}\n </span>\n <Button\n variant=\"solid\"\n size=\"sm\"\n color=\"error\"\n onClick={() => {\n setConfirming(false)\n action.onClick()\n }}\n >\n Confirm\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setConfirming(false)}\n >\n Cancel\n </Button>\n </motion.div>\n )\n }\n\n return (\n <motion.div\n ref={ref}\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={springs.snappy}\n tabIndex={tabIndex}\n >\n <Button\n variant=\"ghost\"\n size=\"sm\"\n color={action.color === 'error' ? 'error' : 'accent'}\n disabled={action.disabled}\n onClick={action.requiresConfirmation ? () => setConfirming(true) : action.onClick}\n startIcon={action.icon ? <Icon icon={action.icon} /> : undefined}\n tabIndex={-1}\n >\n {action.label}\n </Button>\n </motion.div>\n )\n})\n\n// ============================================================\n// BulkActionBar\n// ============================================================\n\nfunction BulkActionBar({\n show,\n count,\n onClearSelection,\n actions,\n totalCount,\n onSelectAll,\n className,\n}: BulkActionBarProps) {\n const [mounted, setMounted] = React.useState(false)\n const [focusedIndex, setFocusedIndex] = React.useState(0)\n const actionRefs = React.useRef<(HTMLDivElement | null)[]>([])\n\n // Reset focused index when actions change\n React.useEffect(() => {\n setFocusedIndex(0)\n }, [actions.length])\n\n React.useEffect(() => {\n setMounted(true)\n }, [])\n\n function handleKeyDown(e: React.KeyboardEvent) {\n if (e.key === 'Escape') {\n e.preventDefault()\n onClearSelection()\n return\n }\n\n const count = actions.length\n if (count === 0) return\n\n if (e.key === 'ArrowRight') {\n e.preventDefault()\n const next = (focusedIndex + 1) % count\n setFocusedIndex(next)\n actionRefs.current[next]?.focus()\n } else if (e.key === 'ArrowLeft') {\n e.preventDefault()\n const prev = (focusedIndex - 1 + count) % count\n setFocusedIndex(prev)\n actionRefs.current[prev]?.focus()\n } else if (e.key === 'Home') {\n e.preventDefault()\n setFocusedIndex(0)\n actionRefs.current[0]?.focus()\n } else if (e.key === 'End') {\n e.preventDefault()\n const last = count - 1\n setFocusedIndex(last)\n actionRefs.current[last]?.focus()\n }\n }\n\n if (!mounted) return null\n\n return createPortal(\n <AnimatePresence>\n {show && (\n <motion.div\n initial={{ y: 100, opacity: 0 }}\n animate={{ y: 0, opacity: 1 }}\n exit={{ y: 100, opacity: 0 }}\n transition={springs.snappy}\n className={cn(\n 'fixed bottom-ds-06 left-1/2 z-sticky -translate-x-1/2',\n 'flex items-center gap-ds-04 rounded-ds-lg border border-surface-border bg-surface-overlay px-ds-05 py-ds-03 shadow-floating',\n className,\n )}\n role=\"toolbar\"\n aria-label={`${count} items selected`}\n onKeyDown={handleKeyDown}\n >\n <Badge variant=\"solid\" size=\"sm\">\n {count} selected\n </Badge>\n\n {totalCount != null && totalCount > count && onSelectAll && (\n <Button variant=\"link\" size=\"xs\" onClick={onSelectAll}>\n Select all {totalCount}\n </Button>\n )}\n\n <div className=\"flex items-center gap-ds-02\">\n <AnimatePresence mode=\"popLayout\">\n {actions.map((action, i) => (\n <ActionButton\n key={action.label}\n ref={(el: HTMLDivElement | null) => { actionRefs.current[i] = el }}\n action={action}\n tabIndex={i === focusedIndex ? 0 : -1}\n />\n ))}\n </AnimatePresence>\n </div>\n\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={onClearSelection}\n aria-label=\"Clear selection\"\n >\n <Icon icon={IconX} size=\"sm\" />\n </Button>\n </motion.div>\n )}\n </AnimatePresence>,\n document.body,\n )\n}\n\nBulkActionBar.displayName = 'BulkActionBar'\n\nexport { BulkActionBar }\n"],"mappings":"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"}
1
+ {"version":3,"file":"bulk-action-bar.js","names":[],"sources":["../../src/composed/bulk-action-bar.tsx"],"sourcesContent":["'use client'\n\nimport { IconX } from '@tabler/icons-react'\nimport { AnimatePresence, motion } from 'framer-motion'\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\n\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\nimport { Icon, type IconProps } from '../ui/icon'\nimport { springs } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\n\n// ============================================================\n// Types\n// ============================================================\n\nexport interface BulkActionBarAction {\n label: string\n icon?: IconProps['icon']\n onClick: () => void\n color?: 'accent' | 'error'\n disabled?: boolean\n /** Show inline confirmation before executing the action */\n requiresConfirmation?: boolean\n /** Custom confirmation message @default 'Are you sure?' */\n confirmMessage?: string\n}\n\nexport interface BulkActionBarProps {\n show: boolean\n count: number\n onClearSelection: () => void\n actions: BulkActionBarAction[]\n /** Total number of items available for selection */\n totalCount?: number\n /** Called when user clicks \"Select all\" */\n onSelectAll?: () => void\n className?: string\n}\n\n// ============================================================\n// ActionButton — handles inline confirmation\n// ============================================================\n\nconst ActionButton = React.forwardRef<HTMLDivElement, { action: BulkActionBarAction; tabIndex?: number }>(function ActionButton({ action, tabIndex }, ref) {\n const [confirming, setConfirming] = React.useState(false)\n\n if (confirming) {\n return (\n <motion.div\n ref={ref}\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={springs.snappy}\n className=\"flex items-center gap-ds-02\"\n tabIndex={tabIndex}\n >\n <span className=\"text-ds-sm text-surface-fg-muted whitespace-nowrap\">\n {action.confirmMessage ?? 'Are you sure?'}\n </span>\n <Button\n variant=\"solid\"\n size=\"sm\"\n color=\"error\"\n onClick={() => {\n setConfirming(false)\n action.onClick()\n }}\n >\n Confirm\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setConfirming(false)}\n >\n Cancel\n </Button>\n </motion.div>\n )\n }\n\n return (\n <motion.div\n ref={ref}\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={springs.snappy}\n tabIndex={tabIndex}\n >\n <Button\n variant=\"ghost\"\n size=\"sm\"\n color={action.color === 'error' ? 'error' : 'accent'}\n disabled={action.disabled}\n onClick={action.requiresConfirmation ? () => setConfirming(true) : action.onClick}\n startIcon={action.icon ? <Icon icon={action.icon} /> : undefined}\n tabIndex={-1}\n >\n {action.label}\n </Button>\n </motion.div>\n )\n})\n\n// ============================================================\n// BulkActionBar\n// ============================================================\n\nfunction BulkActionBar({\n show,\n count,\n onClearSelection,\n actions,\n totalCount,\n onSelectAll,\n className,\n}: BulkActionBarProps) {\n const [mounted, setMounted] = React.useState(false)\n const [focusedIndex, setFocusedIndex] = React.useState(0)\n const actionRefs = React.useRef<(HTMLDivElement | null)[]>([])\n\n // Reset focused index when actions change\n React.useEffect(() => {\n setFocusedIndex(0)\n }, [actions.length])\n\n React.useEffect(() => {\n setMounted(true)\n }, [])\n\n function handleKeyDown(e: React.KeyboardEvent) {\n if (e.key === 'Escape') {\n e.preventDefault()\n onClearSelection()\n return\n }\n\n const count = actions.length\n if (count === 0) return\n\n if (e.key === 'ArrowRight') {\n e.preventDefault()\n const next = (focusedIndex + 1) % count\n setFocusedIndex(next)\n actionRefs.current[next]?.focus()\n } else if (e.key === 'ArrowLeft') {\n e.preventDefault()\n const prev = (focusedIndex - 1 + count) % count\n setFocusedIndex(prev)\n actionRefs.current[prev]?.focus()\n } else if (e.key === 'Home') {\n e.preventDefault()\n setFocusedIndex(0)\n actionRefs.current[0]?.focus()\n } else if (e.key === 'End') {\n e.preventDefault()\n const last = count - 1\n setFocusedIndex(last)\n actionRefs.current[last]?.focus()\n }\n }\n\n if (!mounted) return null\n\n return createPortal(\n <AnimatePresence>\n {show && (\n <motion.div\n initial={{ y: 100, opacity: 0 }}\n animate={{ y: 0, opacity: 1 }}\n exit={{ y: 100, opacity: 0 }}\n transition={springs.snappy}\n className={cn(\n 'fixed bottom-ds-06 left-1/2 z-sticky -translate-x-1/2',\n 'flex items-center gap-ds-04 rounded-surface border border-surface-border bg-surface-overlay px-ds-05 py-ds-03 shadow-floating',\n className,\n )}\n role=\"toolbar\"\n aria-label={`${count} items selected`}\n onKeyDown={handleKeyDown}\n >\n <Badge variant=\"solid\" size=\"sm\">\n {count} selected\n </Badge>\n\n {totalCount != null && totalCount > count && onSelectAll && (\n <Button variant=\"link\" size=\"xs\" onClick={onSelectAll}>\n Select all {totalCount}\n </Button>\n )}\n\n <div className=\"flex items-center gap-ds-02\">\n <AnimatePresence mode=\"popLayout\">\n {actions.map((action, i) => (\n <ActionButton\n key={action.label}\n ref={(el: HTMLDivElement | null) => { actionRefs.current[i] = el }}\n action={action}\n tabIndex={i === focusedIndex ? 0 : -1}\n />\n ))}\n </AnimatePresence>\n </div>\n\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={onClearSelection}\n aria-label=\"Clear selection\"\n >\n <Icon icon={IconX} size=\"sm\" />\n </Button>\n </motion.div>\n )}\n </AnimatePresence>,\n document.body,\n )\n}\n\nBulkActionBar.displayName = 'BulkActionBar'\n\nexport { BulkActionBar }\n"],"mappings":"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"}
@@ -103,7 +103,7 @@ var b = p.forwardRef(function({ groups: b = [], placeholder: x = "Search or jump
103
103
  children: /* @__PURE__ */ h(c, { children: [/* @__PURE__ */ m(s, { 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" }), /* @__PURE__ */ h(a, {
104
104
  ref: M,
105
105
  ...j,
106
- className: n("fixed left-1/2 top-[20%] z-modal w-full max-w-[560px] -translate-x-1/2", "overflow-hidden rounded-ds-xl border border-surface-border-strong bg-surface-overlay shadow-overlay", "duration-moderate-02 data-[state=open]:animate-in data-[state=closed]:animate-out", "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95", "data-[state=closed]:slide-out-to-left-1/2 data-[state=open]:slide-in-from-left-1/2", "data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2", A),
106
+ className: n("fixed left-1/2 top-[20%] z-modal w-full max-w-[560px] -translate-x-1/2", "overflow-hidden rounded-overlay-lg border border-surface-border-strong bg-surface-overlay shadow-overlay", "duration-moderate-02 data-[state=open]:animate-in data-[state=closed]:animate-out", "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95", "data-[state=closed]:slide-out-to-left-1/2 data-[state=open]:slide-in-from-left-1/2", "data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2", A),
107
107
  onKeyDown: le,
108
108
  children: [
109
109
  /* @__PURE__ */ h(l, { children: [/* @__PURE__ */ m(ee, { children: "Command Palette" }), /* @__PURE__ */ m(o, { children: "Search or jump to pages, projects, tasks, and actions" })] }),
@@ -144,7 +144,7 @@ var b = p.forwardRef(function({ groups: b = [], placeholder: x = "Search or jump
144
144
  spellCheck: !1
145
145
  }),
146
146
  /* @__PURE__ */ m("kbd", {
147
- className: "hidden shrink-0 select-none rounded-ds-md border border-surface-border-strong bg-surface-raised px-ds-02b py-ds-01 text-ds-sm font-medium text-surface-fg-subtle shadow-kbd sm:inline-flex",
147
+ className: "hidden shrink-0 select-none rounded-control border border-surface-border-strong bg-surface-raised px-ds-02b py-ds-01 text-ds-sm font-medium text-surface-fg-subtle shadow-kbd sm:inline-flex",
148
148
  children: "Esc"
149
149
  })
150
150
  ]
@@ -203,7 +203,7 @@ var b = p.forwardRef(function({ groups: b = [], placeholder: x = "Search or jump
203
203
  t.onSelect(), R(!1);
204
204
  },
205
205
  onMouseEnter: () => H(i),
206
- className: n("flex w-full items-center gap-ds-04 rounded-ds-lg px-ds-03 py-ds-03 text-left transition-[color,background-color] duration-fast-02 ease-productive-standard", a ? "bg-surface-raised-hover text-surface-fg" : "text-surface-fg-muted hover:bg-surface-raised"),
206
+ className: 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", a ? "bg-surface-raised-hover text-surface-fg" : "text-surface-fg-muted hover:bg-surface-raised"),
207
207
  children: [
208
208
  t.icon && /* @__PURE__ */ m("span", {
209
209
  className: n("[&>svg]:h-ico-sm [&>svg]:w-ico-sm shrink-0 transition-colors duration-fast-02 ease-productive-standard", a ? "text-accent-11" : "text-surface-fg-subtle"),
@@ -270,14 +270,14 @@ var b = p.forwardRef(function({ groups: b = [], placeholder: x = "Search or jump
270
270
  })
271
271
  })]
272
272
  }) : e.keys === "↵" ? /* @__PURE__ */ m("kbd", {
273
- className: "inline-flex h-[20px] items-center justify-center rounded-ds-md border border-surface-border-strong bg-surface-raised px-ds-02b shadow-kbd",
273
+ className: "inline-flex h-[20px] items-center justify-center rounded-control border border-surface-border-strong bg-surface-raised px-ds-02b shadow-kbd",
274
274
  children: /* @__PURE__ */ m(r, {
275
275
  icon: v,
276
276
  size: "xs",
277
277
  className: "text-surface-fg-subtle"
278
278
  })
279
279
  }) : /* @__PURE__ */ m("kbd", {
280
- className: "inline-flex h-[20px] items-center justify-center rounded-ds-md border border-surface-border-strong bg-surface-raised px-ds-02b text-ds-xs font-medium text-surface-fg-subtle shadow-kbd",
280
+ className: "inline-flex h-[20px] items-center justify-center rounded-control border border-surface-border-strong bg-surface-raised px-ds-02b text-ds-xs font-medium text-surface-fg-subtle shadow-kbd",
281
281
  children: e.keys
282
282
  }), /* @__PURE__ */ m("span", {
283
283
  className: "text-ds-xs text-surface-fg-subtle",
@@ -1 +1 @@
1
- {"version":3,"file":"command-palette.js","names":[],"sources":["../../src/composed/command-palette.tsx"],"sourcesContent":["'use client'\n\n/**\n * CommandPalette -- Keyboard-driven command palette (Ctrl+K / Cmd+K).\n *\n * Adapted from Karm V2. Uses ui/Dialog as the overlay.\n * All V1 color tokens replaced with semantic design-system tokens.\n */\nimport { IconArrowDown,IconArrowUp, IconCornerDownLeft, IconSearch } from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\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 { springs,tweens } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\nimport { VisuallyHidden } from '../ui/visually-hidden'\n\n// -----------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------\n\nexport interface CommandItem {\n id: string\n /** Display label — string or ReactNode. When ReactNode, provide `filterValue` for search filtering. */\n label: string | React.ReactNode\n /** Optional description — string or ReactNode. */\n description?: string | React.ReactNode\n icon?: React.ReactNode\n /** Keyboard shortcut hint displayed as keycap badges (e.g., \"G D\", \"Ctrl+N\"). */\n shortcut?: string\n /** Custom render override for the label. Receives the current search query for match highlighting. */\n renderLabel?: (query: string) => React.ReactNode\n /** Plain-text value used for search filtering when `label` is a ReactNode. Falls back to `label` if string. */\n filterValue?: string\n onSelect: () => void\n}\n\nexport interface CommandGroup {\n label: string\n items: CommandItem[]\n}\n\n/** A single keyboard hint shown in the footer. */\nexport interface FooterHint {\n /** Key(s) to display — rendered as text inside `<kbd>` or as an icon. */\n keys: string\n /** Human-readable label for the hint (e.g., \"Navigate\", \"Select\"). */\n label: string\n}\n\n/**\n * A searchable command palette with keyboard navigation (Ctrl+K / Cmd+K).\n * Supports grouped items, custom keybindings, controlled/uncontrolled open state,\n * and custom empty states.\n */\nexport interface CommandPaletteProps extends React.ComponentPropsWithoutRef<'div'> {\n /** Item groups displayed in the palette. Each group has a label and an array of CommandItems. */\n groups?: CommandGroup[]\n /** Placeholder text for the search input. */\n placeholder?: string\n /** Called on every search input change with the current query string. */\n onSearch?: (query: string) => void\n /** Simple text shown when no results match. Overridden by `emptyState` if provided. */\n emptyMessage?: string\n /** Full custom empty state ReactNode — overrides `emptyMessage` when provided. */\n emptyState?: React.ReactNode\n // -- Controlled/uncontrolled open state (P1 #4) --\n /** Controlled open state. */\n open?: boolean\n /** Default open state for uncontrolled usage. */\n defaultOpen?: boolean\n /** Called when the open state changes. */\n onOpenChange?: (open: boolean) => void\n // -- Keyboard shortcut customization (P1 #6) --\n /** Keybinding(s) to toggle the palette. Pass `false` to disable.\n * String format: modifier+key, e.g., 'mod+k', 'ctrl+shift+p'.\n * 'mod' maps to Meta on macOS, Ctrl otherwise. */\n keybinding?: string | string[] | false\n // -- Configurable max-height (P2 #8) --\n /** Max height of the results container. CSS value. Default '320px'. */\n maxHeight?: string | number\n // -- Custom footer hints (P2 #11) --\n /** Custom footer keyboard hints. Pass `false` to hide the footer entirely. */\n footerHints?: FooterHint[] | false\n}\n\n// -----------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------\n\nimport { getIsMac, getModifierDisplay,matchesKeybinding } from '../ui/lib/keybinding'\n\n/** Get the text-searchable value from a CommandItem. */\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\n/** Get the text-searchable description from a CommandItem. */\nfunction getFilterDescription(item: CommandItem): string {\n if (typeof item.description === 'string') return item.description\n return ''\n}\n\n/** Parse a shortcut string like \"G D\" or \"Ctrl+N\" into individual keycap segments. */\nfunction parseShortcutKeys(shortcut: string): string[] {\n // If it has \"+\" separator (Ctrl+Shift+N style), split on +\n if (shortcut.includes('+')) {\n return shortcut.split('+').map(s => s.trim()).filter(Boolean)\n }\n // Otherwise split on spaces (G D style)\n return shortcut.split(/\\s+/).filter(Boolean)\n}\n\n// -----------------------------------------------------------------------\n// CommandPalette\n// -----------------------------------------------------------------------\n\nconst CommandPalette = React.forwardRef<HTMLDivElement, CommandPaletteProps>(\n function CommandPalette(\n {\n groups = [],\n placeholder = 'Search or jump to...',\n onSearch,\n emptyMessage = 'No results found.',\n emptyState,\n open: openProp,\n defaultOpen,\n onOpenChange,\n keybinding = 'mod+k',\n maxHeight = '320px',\n footerHints,\n className,\n ...props\n },\n ref,\n ) {\n // -- Controlled/uncontrolled open state --\n const isControlled = openProp !== undefined\n const [internalOpen, setInternalOpen] = React.useState(defaultOpen ?? false)\n const open = isControlled ? openProp : internalOpen\n\n // Use a ref for the current open value to avoid stale closures in setOpen\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 = typeof nextOpen === 'function' ? nextOpen(openRef.current) : nextOpen\n if (!isControlled) {\n setInternalOpen(resolved)\n }\n onOpenChange?.(resolved)\n },\n [isControlled, onOpenChange],\n )\n\n const [query, setQuery] = React.useState('')\n const [activeIndex, setActiveIndex] = React.useState(0)\n const inputRef = React.useRef<HTMLInputElement>(null)\n const listRef = React.useRef<HTMLDivElement>(null)\n const instanceId = React.useId()\n const listboxId = `command-palette-listbox-${instanceId}`\n\n // -- Reduced motion (P2 #12) --\n const { reducedMotion: isReduced } = useMotion()\n const noMotionTransition = { duration: 0 }\n\n // -- Platform detection (P2 #13) --\n const isMac = React.useMemo(() => getIsMac(), [])\n\n // Filter groups based on query\n const filteredGroups = React.useMemo(() => {\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])\n\n const filteredItems = React.useMemo(\n () => filteredGroups.flatMap((g) => g.items),\n [filteredGroups],\n )\n\n // Global keyboard shortcut\n React.useEffect(() => {\n if (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 }, [keybinding, setOpen])\n\n // Reset state when opening\n React.useEffect(() => {\n if (open) {\n setQuery('')\n setActiveIndex(0)\n requestAnimationFrame(() => {\n inputRef.current?.focus()\n })\n }\n }, [open])\n\n // Keyboard navigation inside the palette\n const handleKeyDown = (e: React.KeyboardEvent) => {\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault()\n setActiveIndex((prev) =>\n prev < filteredItems.length - 1 ? prev + 1 : 0,\n )\n break\n }\n case 'ArrowUp': {\n e.preventDefault()\n setActiveIndex((prev) =>\n prev > 0 ? prev - 1 : filteredItems.length - 1,\n )\n break\n }\n case 'Enter': {\n e.preventDefault()\n const item = filteredItems[activeIndex]\n if (item) {\n item.onSelect()\n setOpen(false)\n }\n break\n }\n case 'Escape': {\n e.preventDefault()\n setOpen(false)\n break\n }\n }\n }\n\n // Scroll active item into view\n React.useEffect(() => {\n const activeEl = listRef.current?.querySelector(\n `[data-command-index=\"${activeIndex}\"]`,\n )\n activeEl?.scrollIntoView({ block: 'nearest' })\n }, [activeIndex])\n\n const handleQueryChange = (value: string) => {\n setQuery(value)\n setActiveIndex(0)\n onSearch?.(value)\n }\n\n // Build a map of item id -> flat index 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 // -- Resolve motion transitions --\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 // -- Resolve max height CSS value --\n const maxHeightValue = typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight\n\n // -- Resolve footer hints --\n const resolvedFooterHints: FooterHint[] | false =\n footerHints === false\n ? false\n : footerHints ?? [\n { keys: '↑↓', label: 'Navigate' },\n { keys: '↵', label: 'Select' },\n { keys: 'Esc', label: 'Close' },\n ]\n\n // Handle dialog open change (from Dialog's own close mechanisms)\n const handleDialogOpenChange = React.useCallback(\n (nextOpen: boolean) => {\n setOpen(nextOpen)\n },\n [setOpen],\n )\n\n return (\n <Dialog open={open} onOpenChange={handleDialogOpenChange}>\n <DialogPortal>\n <DialogOverlay\n 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 />\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-ds-xl border border-surface-border-strong 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 onKeyDown={handleKeyDown}\n >\n <VisuallyHidden>\n <DialogTitle>Command Palette</DialogTitle>\n <DialogDescription>\n Search or jump to pages, projects, tasks, and actions\n </DialogDescription>\n </VisuallyHidden>\n\n {/* Search input */}\n <div className=\"flex items-center gap-ds-04 border-b border-surface-border-strong px-ds-05 py-ds-04\">\n <motion.span\n initial={noInit ?? { opacity: 0, scale: 0.96 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={springSnappy}\n className=\"inline-flex shrink-0\"\n >\n <Icon icon={IconSearch} size=\"sm\" stroke=\"light\" className=\"text-surface-fg-subtle\" />\n </motion.span>\n <input\n ref={inputRef}\n value={query}\n onChange={(e) => handleQueryChange(e.target.value)}\n placeholder={placeholder}\n role=\"combobox\"\n aria-expanded={true}\n aria-controls={listboxId}\n aria-activedescendant={filteredItems[activeIndex] ? `command-item-${instanceId}-${filteredItems[activeIndex].id}` : undefined}\n aria-autocomplete=\"list\"\n className={cn(\n 'flex-1 bg-transparent text-ds-base text-surface-fg outline-hidden',\n 'placeholder:text-surface-fg-subtle',\n )}\n autoComplete=\"off\"\n autoCorrect=\"off\"\n spellCheck={false}\n />\n <kbd className=\"hidden shrink-0 select-none rounded-ds-md border border-surface-border-strong bg-surface-raised px-ds-02b py-ds-01 text-ds-sm font-medium text-surface-fg-subtle shadow-kbd sm:inline-flex\">\n Esc\n </kbd>\n </div>\n\n {/* Results */}\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 && (\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-ds-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={isReduced ? noMotionTransition : { ...tweens.fade, delay: groupIdx * 0.06 }}\n className=\"mb-ds-02\"\n >\n <div className=\"px-ds-03 pb-ds-02 pt-ds-03\">\n <span className=\"text-ds-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-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={isReduced ? noMotionTransition : { ...springs.snappy, delay: itemIndex * 0.03 }}\n onClick={() => {\n item.onSelect()\n setOpen(false)\n }}\n onMouseEnter={() => setActiveIndex(itemIndex)}\n className={cn(\n 'flex w-full items-center gap-ds-04 rounded-ds-lg 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 ? 'text-accent-11' : 'text-surface-fg-subtle',\n )}\n aria-hidden=\"true\"\n >\n {item.icon}\n </span>\n )}\n <div className=\"flex flex-1 flex-col\">\n <span className=\"text-ds-md\">\n {item.renderLabel ? item.renderLabel(query) : item.label}\n </span>\n {item.description && (\n <span className=\"text-ds-sm text-surface-fg-subtle\">\n {item.description}\n </span>\n )}\n </div>\n {item.shortcut && (\n <span className=\"flex shrink-0 items-center gap-ds-01\">\n {parseShortcutKeys(item.shortcut).map((key, i) => (\n <kbd\n key={i}\n className={cn(\n 'inline-flex min-w-[20px] items-center justify-center rounded border px-ds-02b py-ds-01 text-ds-xs font-medium shadow-kbd transition-colors duration-fast-02 ease-productive-standard',\n isActive\n ? 'bg-accent-2 text-accent-11 border-accent-6'\n : 'bg-surface-raised text-surface-fg-subtle border-surface-border-strong',\n )}\n >\n {key === 'Ctrl' || key === 'ctrl' ? getModifierDisplay(isMac) : key}\n </kbd>\n ))}\n </span>\n )}\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 icon={IconCornerDownLeft} size=\"sm\" stroke=\"light\" className=\"text-surface-fg-subtle\" />\n </motion.span>\n )}\n </AnimatePresence>\n </motion.button>\n )\n })}\n </motion.div>\n ))}\n </div>\n\n {/* Footer with keyboard hints */}\n {resolvedFooterHints !== false && (\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 {resolvedFooterHints.map((hint, i) => (\n <div key={i} className=\"flex items-center gap-ds-02b\">\n {hint.keys === '↑↓' ? (\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-surface-border-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-surface-border-strong bg-surface-raised shadow-kbd\">\n <Icon icon={IconArrowDown} size=\"xs\" className=\"text-surface-fg-subtle\" />\n </kbd>\n </div>\n ) : hint.keys === '↵' ? (\n <kbd className=\"inline-flex h-[20px] items-center justify-center rounded-ds-md border border-surface-border-strong bg-surface-raised px-ds-02b shadow-kbd\">\n <Icon icon={IconCornerDownLeft} size=\"xs\" className=\"text-surface-fg-subtle\" />\n </kbd>\n ) : (\n <kbd className=\"inline-flex h-[20px] items-center justify-center rounded-ds-md border border-surface-border-strong bg-surface-raised px-ds-02b text-ds-xs font-medium text-surface-fg-subtle shadow-kbd\">\n {hint.keys}\n </kbd>\n )}\n <span className=\"text-ds-xs text-surface-fg-subtle\">\n {hint.label}\n </span>\n </div>\n ))}\n </motion.div>\n )}\n </DialogContentRaw>\n </DialogPortal>\n </Dialog>\n )\n },\n)\n\nCommandPalette.displayName = 'CommandPalette'\n\nexport { CommandPalette }\n"],"mappings":"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"}
1
+ {"version":3,"file":"command-palette.js","names":[],"sources":["../../src/composed/command-palette.tsx"],"sourcesContent":["'use client'\n\n/**\n * CommandPalette -- Keyboard-driven command palette (Ctrl+K / Cmd+K).\n *\n * Adapted from Karm V2. Uses ui/Dialog as the overlay.\n * All V1 color tokens replaced with semantic design-system tokens.\n */\nimport { IconArrowDown,IconArrowUp, IconCornerDownLeft, IconSearch } from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\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 { springs,tweens } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\nimport { VisuallyHidden } from '../ui/visually-hidden'\n\n// -----------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------\n\nexport interface CommandItem {\n id: string\n /** Display label — string or ReactNode. When ReactNode, provide `filterValue` for search filtering. */\n label: string | React.ReactNode\n /** Optional description — string or ReactNode. */\n description?: string | React.ReactNode\n icon?: React.ReactNode\n /** Keyboard shortcut hint displayed as keycap badges (e.g., \"G D\", \"Ctrl+N\"). */\n shortcut?: string\n /** Custom render override for the label. Receives the current search query for match highlighting. */\n renderLabel?: (query: string) => React.ReactNode\n /** Plain-text value used for search filtering when `label` is a ReactNode. Falls back to `label` if string. */\n filterValue?: string\n onSelect: () => void\n}\n\nexport interface CommandGroup {\n label: string\n items: CommandItem[]\n}\n\n/** A single keyboard hint shown in the footer. */\nexport interface FooterHint {\n /** Key(s) to display — rendered as text inside `<kbd>` or as an icon. */\n keys: string\n /** Human-readable label for the hint (e.g., \"Navigate\", \"Select\"). */\n label: string\n}\n\n/**\n * A searchable command palette with keyboard navigation (Ctrl+K / Cmd+K).\n * Supports grouped items, custom keybindings, controlled/uncontrolled open state,\n * and custom empty states.\n */\nexport interface CommandPaletteProps extends React.ComponentPropsWithoutRef<'div'> {\n /** Item groups displayed in the palette. Each group has a label and an array of CommandItems. */\n groups?: CommandGroup[]\n /** Placeholder text for the search input. */\n placeholder?: string\n /** Called on every search input change with the current query string. */\n onSearch?: (query: string) => void\n /** Simple text shown when no results match. Overridden by `emptyState` if provided. */\n emptyMessage?: string\n /** Full custom empty state ReactNode — overrides `emptyMessage` when provided. */\n emptyState?: React.ReactNode\n // -- Controlled/uncontrolled open state (P1 #4) --\n /** Controlled open state. */\n open?: boolean\n /** Default open state for uncontrolled usage. */\n defaultOpen?: boolean\n /** Called when the open state changes. */\n onOpenChange?: (open: boolean) => void\n // -- Keyboard shortcut customization (P1 #6) --\n /** Keybinding(s) to toggle the palette. Pass `false` to disable.\n * String format: modifier+key, e.g., 'mod+k', 'ctrl+shift+p'.\n * 'mod' maps to Meta on macOS, Ctrl otherwise. */\n keybinding?: string | string[] | false\n // -- Configurable max-height (P2 #8) --\n /** Max height of the results container. CSS value. Default '320px'. */\n maxHeight?: string | number\n // -- Custom footer hints (P2 #11) --\n /** Custom footer keyboard hints. Pass `false` to hide the footer entirely. */\n footerHints?: FooterHint[] | false\n}\n\n// -----------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------\n\nimport { getIsMac, getModifierDisplay,matchesKeybinding } from '../ui/lib/keybinding'\n\n/** Get the text-searchable value from a CommandItem. */\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\n/** Get the text-searchable description from a CommandItem. */\nfunction getFilterDescription(item: CommandItem): string {\n if (typeof item.description === 'string') return item.description\n return ''\n}\n\n/** Parse a shortcut string like \"G D\" or \"Ctrl+N\" into individual keycap segments. */\nfunction parseShortcutKeys(shortcut: string): string[] {\n // If it has \"+\" separator (Ctrl+Shift+N style), split on +\n if (shortcut.includes('+')) {\n return shortcut.split('+').map(s => s.trim()).filter(Boolean)\n }\n // Otherwise split on spaces (G D style)\n return shortcut.split(/\\s+/).filter(Boolean)\n}\n\n// -----------------------------------------------------------------------\n// CommandPalette\n// -----------------------------------------------------------------------\n\nconst CommandPalette = React.forwardRef<HTMLDivElement, CommandPaletteProps>(\n function CommandPalette(\n {\n groups = [],\n placeholder = 'Search or jump to...',\n onSearch,\n emptyMessage = 'No results found.',\n emptyState,\n open: openProp,\n defaultOpen,\n onOpenChange,\n keybinding = 'mod+k',\n maxHeight = '320px',\n footerHints,\n className,\n ...props\n },\n ref,\n ) {\n // -- Controlled/uncontrolled open state --\n const isControlled = openProp !== undefined\n const [internalOpen, setInternalOpen] = React.useState(defaultOpen ?? false)\n const open = isControlled ? openProp : internalOpen\n\n // Use a ref for the current open value to avoid stale closures in setOpen\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 = typeof nextOpen === 'function' ? nextOpen(openRef.current) : nextOpen\n if (!isControlled) {\n setInternalOpen(resolved)\n }\n onOpenChange?.(resolved)\n },\n [isControlled, onOpenChange],\n )\n\n const [query, setQuery] = React.useState('')\n const [activeIndex, setActiveIndex] = React.useState(0)\n const inputRef = React.useRef<HTMLInputElement>(null)\n const listRef = React.useRef<HTMLDivElement>(null)\n const instanceId = React.useId()\n const listboxId = `command-palette-listbox-${instanceId}`\n\n // -- Reduced motion (P2 #12) --\n const { reducedMotion: isReduced } = useMotion()\n const noMotionTransition = { duration: 0 }\n\n // -- Platform detection (P2 #13) --\n const isMac = React.useMemo(() => getIsMac(), [])\n\n // Filter groups based on query\n const filteredGroups = React.useMemo(() => {\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])\n\n const filteredItems = React.useMemo(\n () => filteredGroups.flatMap((g) => g.items),\n [filteredGroups],\n )\n\n // Global keyboard shortcut\n React.useEffect(() => {\n if (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 }, [keybinding, setOpen])\n\n // Reset state when opening\n React.useEffect(() => {\n if (open) {\n setQuery('')\n setActiveIndex(0)\n requestAnimationFrame(() => {\n inputRef.current?.focus()\n })\n }\n }, [open])\n\n // Keyboard navigation inside the palette\n const handleKeyDown = (e: React.KeyboardEvent) => {\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault()\n setActiveIndex((prev) =>\n prev < filteredItems.length - 1 ? prev + 1 : 0,\n )\n break\n }\n case 'ArrowUp': {\n e.preventDefault()\n setActiveIndex((prev) =>\n prev > 0 ? prev - 1 : filteredItems.length - 1,\n )\n break\n }\n case 'Enter': {\n e.preventDefault()\n const item = filteredItems[activeIndex]\n if (item) {\n item.onSelect()\n setOpen(false)\n }\n break\n }\n case 'Escape': {\n e.preventDefault()\n setOpen(false)\n break\n }\n }\n }\n\n // Scroll active item into view\n React.useEffect(() => {\n const activeEl = listRef.current?.querySelector(\n `[data-command-index=\"${activeIndex}\"]`,\n )\n activeEl?.scrollIntoView({ block: 'nearest' })\n }, [activeIndex])\n\n const handleQueryChange = (value: string) => {\n setQuery(value)\n setActiveIndex(0)\n onSearch?.(value)\n }\n\n // Build a map of item id -> flat index 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 // -- Resolve motion transitions --\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 // -- Resolve max height CSS value --\n const maxHeightValue = typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight\n\n // -- Resolve footer hints --\n const resolvedFooterHints: FooterHint[] | false =\n footerHints === false\n ? false\n : footerHints ?? [\n { keys: '↑↓', label: 'Navigate' },\n { keys: '↵', label: 'Select' },\n { keys: 'Esc', label: 'Close' },\n ]\n\n // Handle dialog open change (from Dialog's own close mechanisms)\n const handleDialogOpenChange = React.useCallback(\n (nextOpen: boolean) => {\n setOpen(nextOpen)\n },\n [setOpen],\n )\n\n return (\n <Dialog open={open} onOpenChange={handleDialogOpenChange}>\n <DialogPortal>\n <DialogOverlay\n 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 />\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 border border-surface-border-strong 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 onKeyDown={handleKeyDown}\n >\n <VisuallyHidden>\n <DialogTitle>Command Palette</DialogTitle>\n <DialogDescription>\n Search or jump to pages, projects, tasks, and actions\n </DialogDescription>\n </VisuallyHidden>\n\n {/* Search input */}\n <div className=\"flex items-center gap-ds-04 border-b border-surface-border-strong px-ds-05 py-ds-04\">\n <motion.span\n initial={noInit ?? { opacity: 0, scale: 0.96 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={springSnappy}\n className=\"inline-flex shrink-0\"\n >\n <Icon icon={IconSearch} size=\"sm\" stroke=\"light\" className=\"text-surface-fg-subtle\" />\n </motion.span>\n <input\n ref={inputRef}\n value={query}\n onChange={(e) => handleQueryChange(e.target.value)}\n placeholder={placeholder}\n role=\"combobox\"\n aria-expanded={true}\n aria-controls={listboxId}\n aria-activedescendant={filteredItems[activeIndex] ? `command-item-${instanceId}-${filteredItems[activeIndex].id}` : undefined}\n aria-autocomplete=\"list\"\n className={cn(\n 'flex-1 bg-transparent text-ds-base text-surface-fg outline-hidden',\n 'placeholder:text-surface-fg-subtle',\n )}\n autoComplete=\"off\"\n autoCorrect=\"off\"\n spellCheck={false}\n />\n <kbd className=\"hidden shrink-0 select-none rounded-control border border-surface-border-strong bg-surface-raised px-ds-02b py-ds-01 text-ds-sm font-medium text-surface-fg-subtle shadow-kbd sm:inline-flex\">\n Esc\n </kbd>\n </div>\n\n {/* Results */}\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 && (\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-ds-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={isReduced ? noMotionTransition : { ...tweens.fade, delay: groupIdx * 0.06 }}\n className=\"mb-ds-02\"\n >\n <div className=\"px-ds-03 pb-ds-02 pt-ds-03\">\n <span className=\"text-ds-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-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={isReduced ? noMotionTransition : { ...springs.snappy, delay: itemIndex * 0.03 }}\n onClick={() => {\n item.onSelect()\n 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 ? 'text-accent-11' : 'text-surface-fg-subtle',\n )}\n aria-hidden=\"true\"\n >\n {item.icon}\n </span>\n )}\n <div className=\"flex flex-1 flex-col\">\n <span className=\"text-ds-md\">\n {item.renderLabel ? item.renderLabel(query) : item.label}\n </span>\n {item.description && (\n <span className=\"text-ds-sm text-surface-fg-subtle\">\n {item.description}\n </span>\n )}\n </div>\n {item.shortcut && (\n <span className=\"flex shrink-0 items-center gap-ds-01\">\n {parseShortcutKeys(item.shortcut).map((key, i) => (\n <kbd\n key={i}\n className={cn(\n 'inline-flex min-w-[20px] items-center justify-center rounded border px-ds-02b py-ds-01 text-ds-xs font-medium shadow-kbd transition-colors duration-fast-02 ease-productive-standard',\n isActive\n ? 'bg-accent-2 text-accent-11 border-accent-6'\n : 'bg-surface-raised text-surface-fg-subtle border-surface-border-strong',\n )}\n >\n {key === 'Ctrl' || key === 'ctrl' ? getModifierDisplay(isMac) : key}\n </kbd>\n ))}\n </span>\n )}\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 icon={IconCornerDownLeft} size=\"sm\" stroke=\"light\" className=\"text-surface-fg-subtle\" />\n </motion.span>\n )}\n </AnimatePresence>\n </motion.button>\n )\n })}\n </motion.div>\n ))}\n </div>\n\n {/* Footer with keyboard hints */}\n {resolvedFooterHints !== false && (\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 {resolvedFooterHints.map((hint, i) => (\n <div key={i} className=\"flex items-center gap-ds-02b\">\n {hint.keys === '↑↓' ? (\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-surface-border-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-surface-border-strong bg-surface-raised shadow-kbd\">\n <Icon icon={IconArrowDown} size=\"xs\" className=\"text-surface-fg-subtle\" />\n </kbd>\n </div>\n ) : hint.keys === '↵' ? (\n <kbd className=\"inline-flex h-[20px] items-center justify-center rounded-control border border-surface-border-strong bg-surface-raised px-ds-02b shadow-kbd\">\n <Icon icon={IconCornerDownLeft} size=\"xs\" className=\"text-surface-fg-subtle\" />\n </kbd>\n ) : (\n <kbd className=\"inline-flex h-[20px] items-center justify-center rounded-control border border-surface-border-strong bg-surface-raised px-ds-02b text-ds-xs font-medium text-surface-fg-subtle shadow-kbd\">\n {hint.keys}\n </kbd>\n )}\n <span className=\"text-ds-xs text-surface-fg-subtle\">\n {hint.label}\n </span>\n </div>\n ))}\n </motion.div>\n )}\n </DialogContentRaw>\n </DialogPortal>\n </Dialog>\n )\n },\n)\n\nCommandPalette.displayName = 'CommandPalette'\n\nexport { CommandPalette }\n"],"mappings":"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"}