@devalok/shilp-sutra 0.49.0 → 0.49.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/badge-group.js +41 -41
- package/dist/_chunks/badge-group.js.map +1 -1
- package/dist/_chunks/block-shell.js +1 -1
- package/dist/_chunks/chart-container.js +1 -1
- package/dist/_chunks/chat.js +6 -15
- package/dist/_chunks/chat.js.map +1 -1
- package/dist/_chunks/colors.js +2 -2
- package/dist/_chunks/document-preview.js +4 -4
- package/dist/_chunks/emoji-suggestion.js +5 -5
- package/dist/_chunks/emoji-suggestion.js.map +1 -1
- package/dist/_chunks/emoji.js +1 -1
- package/dist/_chunks/field-state.js +1 -1
- package/dist/_chunks/grid-lines.js +1 -1
- package/dist/_chunks/image-preview.js +7 -7
- package/dist/_chunks/keybinding.js +1 -1
- package/dist/_chunks/link-context.js +1 -1
- package/dist/_chunks/mention-suggestion.js +2 -2
- package/dist/_chunks/mention.js +1 -1
- package/dist/_chunks/motion-provider.js +1 -1
- package/dist/_chunks/normalize-icon.js +1 -1
- package/dist/_chunks/oauth-button.js +2 -2
- package/dist/_chunks/popover.js +17 -17
- package/dist/_chunks/primitives.js +1 -1
- package/dist/_chunks/shared.js +5 -5
- package/dist/_chunks/success.js +5 -8
- package/dist/_chunks/success.js.map +1 -1
- package/dist/_chunks/tiptap.js +11 -11
- package/dist/_chunks/tooltip.js +1 -1
- package/dist/_chunks/tree-view.js +2 -2
- package/dist/_chunks/use-calendar.js +1 -1
- package/dist/_chunks/use-controllable-open.js +1 -1
- package/dist/_chunks/vendor-client.js +1 -1
- package/dist/_chunks/vendor-utils.js +1 -1
- package/dist/ai/block-renderer.js +8 -11
- package/dist/ai/block-renderer.js.map +1 -1
- package/dist/ai/blocks/error.js +1 -1
- package/dist/ai/blocks/index.js +2 -2
- package/dist/ai/blocks/text.js +1 -1
- package/dist/ai/command-bar.d.ts.map +1 -1
- package/dist/ai/command-bar.js +8 -39
- package/dist/ai/command-bar.js.map +1 -1
- package/dist/ai/conversation.js +4 -10
- package/dist/ai/conversation.js.map +1 -1
- package/dist/ai/devadoot-icon.d.ts.map +1 -1
- package/dist/ai/index.js +10 -41
- package/dist/ai/index.js.map +1 -1
- package/dist/composed/activity-feed.js +3 -3
- package/dist/composed/activity-feed.js.map +1 -1
- package/dist/composed/avatar-group.js +3 -3
- package/dist/composed/avatar-group.js.map +1 -1
- package/dist/composed/bulk-action-bar.js +1 -1
- package/dist/composed/command-palette.js +6 -6
- package/dist/composed/content-card.js +1 -1
- package/dist/composed/date-picker/index.js +10 -10
- package/dist/composed/emoji-picker.js +4 -4
- package/dist/composed/empty-state.d.ts.map +1 -1
- package/dist/composed/empty-state.js +23 -33
- package/dist/composed/empty-state.js.map +1 -1
- package/dist/composed/extensions/emoji-node.js +1 -1
- package/dist/composed/extensions/emoji-suggestion.js +1 -1
- package/dist/composed/file-preview.js +14 -14
- package/dist/composed/filter-bar.js +1 -1
- package/dist/composed/multi-select-popover.js +3 -3
- package/dist/composed/priority-indicator.js +1 -1
- package/dist/composed/responsive-modal.js +21 -21
- package/dist/composed/rich-chat-input.js +445 -445
- package/dist/composed/rich-chat-input.js.map +1 -1
- package/dist/composed/rich-text-editor.js +28 -28
- package/dist/composed/schedule-view.js +1 -1
- package/dist/composed/schedule-view.js.map +1 -1
- package/dist/composed/status-badge.js +1 -1
- package/dist/motion/index.js +2 -2
- package/dist/shell/bottom-navbar.js +3 -3
- package/dist/shell/bottom-navbar.js.map +1 -1
- package/dist/shell/index.js +2 -2
- package/dist/shell/link-context.js +2 -2
- package/dist/shell/notification-center.js +3 -3
- package/dist/shell/sidebar.js +2 -2
- package/dist/shell/top-bar.js +1 -1
- package/dist/ui/accordion.js +14 -14
- package/dist/ui/alert-dialog.js +31 -31
- package/dist/ui/alert.js +1 -1
- package/dist/ui/aspect-ratio.js +1 -1
- package/dist/ui/autocomplete.js +7 -7
- package/dist/ui/avatar.d.ts.map +1 -1
- package/dist/ui/avatar.js +52 -67
- package/dist/ui/avatar.js.map +1 -1
- package/dist/ui/badge-group.js +1 -1
- package/dist/ui/badge.d.ts +2 -0
- package/dist/ui/badge.d.ts.map +1 -1
- package/dist/ui/badge.js +1 -1
- package/dist/ui/banner.js +1 -1
- package/dist/ui/breadcrumb.js +1 -1
- package/dist/ui/button.js +9 -9
- package/dist/ui/button.js.map +1 -1
- package/dist/ui/card.js +1 -1
- package/dist/ui/charts/area-chart.js +6 -6
- package/dist/ui/charts/bar-chart.js +6 -6
- package/dist/ui/charts/chart-container.js +1 -1
- package/dist/ui/charts/gauge-chart.js +2 -2
- package/dist/ui/charts/index.js +2 -2
- package/dist/ui/charts/line-chart.js +6 -6
- package/dist/ui/charts/pie-chart.js +5 -5
- package/dist/ui/charts/radar-chart.js +5 -5
- package/dist/ui/charts/sparkline.js +2 -2
- package/dist/ui/chat/index.js +2 -2
- package/dist/ui/checkbox.js +5 -5
- package/dist/ui/collapsible.js +4 -4
- package/dist/ui/color-input.js +4 -4
- package/dist/ui/combobox.js +8 -8
- package/dist/ui/combobox.js.map +1 -1
- package/dist/ui/context-menu.js +41 -41
- package/dist/ui/dialog.js +25 -25
- package/dist/ui/dot.d.ts.map +1 -1
- package/dist/ui/dot.js +24 -32
- package/dist/ui/dot.js.map +1 -1
- package/dist/ui/dropdown-menu.js +51 -51
- package/dist/ui/hover-card.js +9 -9
- package/dist/ui/icon-button.js +1 -1
- package/dist/ui/index.js +6 -6
- package/dist/ui/input.js +2 -2
- package/dist/ui/label.js +1 -1
- package/dist/ui/lib/utils.js +4 -4
- package/dist/ui/link.js +1 -1
- package/dist/ui/menubar.js +53 -53
- package/dist/ui/navigation-menu.js +27 -27
- package/dist/ui/number-input.js +2 -2
- package/dist/ui/oauth-button/index.js +2 -2
- package/dist/ui/pagination.js +1 -1
- package/dist/ui/popover.js +2 -2
- package/dist/ui/progress.js +2 -2
- package/dist/ui/radio.js +2 -2
- package/dist/ui/segmented-control.js +1 -1
- package/dist/ui/select.js +35 -35
- package/dist/ui/separator.js +1 -1
- package/dist/ui/sheet.js +22 -22
- package/dist/ui/sidebar.js +2 -2
- package/dist/ui/skeleton.js +1 -1
- package/dist/ui/slider.js +8 -8
- package/dist/ui/slider.js.map +1 -1
- package/dist/ui/split-button.js +3 -3
- package/dist/ui/stat-card.d.ts +2 -0
- package/dist/ui/stat-card.d.ts.map +1 -1
- package/dist/ui/stat-card.js +94 -126
- package/dist/ui/stat-card.js.map +1 -1
- package/dist/ui/stat-flash.js +1 -1
- package/dist/ui/stepper.js +1 -1
- package/dist/ui/surface.js +2 -2
- package/dist/ui/switch.js +2 -2
- package/dist/ui/table-row-link.js +1 -1
- package/dist/ui/tabs.js +13 -13
- package/dist/ui/text.js +1 -1
- package/dist/ui/textarea.js +2 -2
- package/dist/ui/toggle-group.js +1 -1
- package/dist/ui/toggle.js +2 -2
- package/dist/ui/tooltip.js +16 -16
- package/dist/ui/tree-view/index.js +2 -2
- package/llms.txt +1 -1
- package/mcp-manifest.json +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +1 -1
- package/skill/references/components.md +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation.js","names":[],"sources":["../../src/ai/conversation.tsx"],"sourcesContent":["'use client'\n\nimport {\n IconCircle,\n IconCircleCheck,\n IconCircleX,\n IconLoader2,\n} from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { useMotion } from '../motion/motion-provider'\nimport { Icon } from '../ui/icon'\nimport { IconProvider } from '../ui/icon-context'\nimport type { IconInput } from '../ui/lib/icon-input'\nimport { durations,springs } from '../ui/lib/motion'\nimport { normalizeIcon } from '../ui/lib/normalize-icon'\nimport { cn } from '../ui/lib/utils'\nimport { useAICommand } from './ai-command-provider'\nimport { BlockRenderer } from './block-renderer'\nimport type {\n BlockComponentProps,\n ConversationMessage,\n ProcessingStep,\n} from './types'\n\n// ── Props ────────────────────────────────────────────────────────────────────\n\n/**\n * Renders a scrollable AI conversation thread with support for streaming\n * processing steps, custom content blocks, and action confirmations.\n */\nexport interface AIConversationProps {\n /** Ordered conversation messages (user + assistant turns). */\n messages: ConversationMessage[]\n /** When true, shows a live processing indicator below the last message. */\n isProcessing?: boolean\n /** Granular processing steps shown during the processing state. */\n processingSteps?: ProcessingStep[]\n /** Agent identity displayed as a header on assistant messages. */\n agent?: { name: string; icon?: IconInput }\n /** Called when user interacts with an action block (confirm/cancel/undo). */\n onAction?: (actionId: string, type: 'confirm' | 'cancel' | 'undo') => void\n /** Map of custom block type names to React components for extensible rendering. */\n customBlocks?: Record<string, React.ComponentType<BlockComponentProps<any>>>\n /** Max height of the conversation container (CSS value). Enables scrolling. */\n maxHeight?: string | number\n /** Auto-scroll to bottom on new messages. @default true */\n autoScroll?: boolean\n className?: string\n}\n\n// ── Step status icon ─────────────────────────────────────────────────────────\n\nfunction StepStatusIcon({ status }: { status: ProcessingStep['status'] }) {\n switch (status) {\n case 'done':\n return (\n <Icon icon={IconCircleCheck} size=\"sm\" className=\"text-success-11\" />\n )\n case 'active':\n return (\n <motion.span\n className=\"inline-flex items-center justify-center text-accent-9\"\n animate={{ rotate: 360 }}\n transition={{ repeat: Infinity, duration: 1, ease: 'linear' }}\n aria-hidden=\"true\"\n >\n <Icon icon={IconLoader2} size=\"sm\" />\n </motion.span>\n )\n case 'error':\n return (\n <Icon icon={IconCircleX} size=\"sm\" className=\"text-error-11\" />\n )\n case 'pending':\n default:\n return (\n <Icon icon={IconCircle} size=\"sm\" className=\"text-surface-fg-subtle opacity-50\" />\n )\n }\n}\n\n// ── Agent header ─────────────────────────────────────────────────────────────\n\nfunction AgentHeader({\n name,\n icon,\n}: {\n name: string\n icon?: IconInput\n}) {\n return (\n <div className=\"flex items-center gap-ds-02b mb-ds-03\">\n {icon && <span className=\"h-4 w-4 flex items-center justify-center\"><IconProvider size=\"sm\">{normalizeIcon(icon)}</IconProvider></span>}\n <span className=\"text-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?: IconInput }\n onAction?: (actionId: string, type: 'confirm' | 'cancel' | 'undo') => void\n customBlocks?: Record<string, React.ComponentType<BlockComponentProps<any>>>\n}) {\n return (\n <div>\n <AgentHeader name={agent.name} icon={agent.icon} />\n {message.blocks && (\n <BlockRenderer\n blocks={message.blocks}\n onAction={onAction}\n customBlocks={customBlocks}\n />\n )}\n </div>\n )\n}\n\n// ── Processing indicator ─────────────────────────────────────────────────────\n\nfunction ProcessingIndicator({\n steps,\n agent,\n reducedMotion,\n}: {\n steps?: ProcessingStep[]\n agent: { name: string; icon?: IconInput }\n reducedMotion: boolean\n}) {\n // Step visualization\n if (steps && steps.length > 0) {\n return (\n <div role=\"status\" aria-busy=\"true\" aria-label=\"Processing\">\n <AgentHeader name={agent.name} icon={agent.icon} />\n <div className=\"flex flex-col gap-ds-02b\">\n {steps.map((step, i) => (\n <motion.div\n key={step.id}\n className=\"flex items-center gap-ds-02b\"\n initial={reducedMotion ? undefined : { opacity: 0, x: -8 }}\n animate={{ opacity: 1, x: 0 }}\n transition={\n reducedMotion\n ? { duration: 0 }\n : { duration: durations.moderate01b, delay: i * 0.05 }\n }\n >\n <StepStatusIcon status={step.status} />\n <span\n className={cn(\n 'text-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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"}
|
|
1
|
+
{"version":3,"file":"conversation.js","names":[],"sources":["../../src/ai/conversation.tsx"],"sourcesContent":["'use client'\n\nimport {\n IconCircle,\n IconCircleCheck,\n IconCircleX,\n IconLoader2,\n} from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { useMotion } from '../motion/motion-provider'\nimport { Icon } from '../ui/icon'\nimport { IconProvider } from '../ui/icon-context'\nimport type { IconInput } from '../ui/lib/icon-input'\nimport { durations,springs } from '../ui/lib/motion'\nimport { normalizeIcon } from '../ui/lib/normalize-icon'\nimport { cn } from '../ui/lib/utils'\nimport { useAICommand } from './ai-command-provider'\nimport { BlockRenderer } from './block-renderer'\nimport type {\n BlockComponentProps,\n ConversationMessage,\n ProcessingStep,\n} from './types'\n\n// ── Props ────────────────────────────────────────────────────────────────────\n\n/**\n * Renders a scrollable AI conversation thread with support for streaming\n * processing steps, custom content blocks, and action confirmations.\n */\nexport interface AIConversationProps {\n /** Ordered conversation messages (user + assistant turns). */\n messages: ConversationMessage[]\n /** When true, shows a live processing indicator below the last message. */\n isProcessing?: boolean\n /** Granular processing steps shown during the processing state. */\n processingSteps?: ProcessingStep[]\n /** Agent identity displayed as a header on assistant messages. */\n agent?: { name: string; icon?: IconInput }\n /** Called when user interacts with an action block (confirm/cancel/undo). */\n onAction?: (actionId: string, type: 'confirm' | 'cancel' | 'undo') => void\n /** Map of custom block type names to React components for extensible rendering. */\n customBlocks?: Record<string, React.ComponentType<BlockComponentProps<any>>>\n /** Max height of the conversation container (CSS value). Enables scrolling. */\n maxHeight?: string | number\n /** Auto-scroll to bottom on new messages. @default true */\n autoScroll?: boolean\n className?: string\n}\n\n// ── Step status icon ─────────────────────────────────────────────────────────\n\nfunction StepStatusIcon({ status }: { status: ProcessingStep['status'] }) {\n switch (status) {\n case 'done':\n return (\n <Icon icon={IconCircleCheck} size=\"sm\" className=\"text-success-11\" />\n )\n case 'active':\n return (\n <motion.span\n className=\"inline-flex items-center justify-center text-accent-9\"\n animate={{ rotate: 360 }}\n transition={{ repeat: Infinity, duration: 1, ease: 'linear' }}\n aria-hidden=\"true\"\n >\n <Icon icon={IconLoader2} size=\"sm\" />\n </motion.span>\n )\n case 'error':\n return (\n <Icon icon={IconCircleX} size=\"sm\" className=\"text-error-11\" />\n )\n case 'pending':\n default:\n return (\n <Icon icon={IconCircle} size=\"sm\" className=\"text-surface-fg-subtle opacity-50\" />\n )\n }\n}\n\n// ── Agent header ─────────────────────────────────────────────────────────────\n\nfunction AgentHeader({\n name,\n icon,\n}: {\n name: string\n icon?: IconInput\n}) {\n return (\n <div className=\"flex items-center gap-ds-02b mb-ds-03\">\n {icon && <span className=\"h-4 w-4 flex items-center justify-center\"><IconProvider size=\"sm\">{normalizeIcon(icon)}</IconProvider></span>}\n <span className=\"text-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={{ 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?: IconInput }\n onAction?: (actionId: string, type: 'confirm' | 'cancel' | 'undo') => void\n customBlocks?: Record<string, React.ComponentType<BlockComponentProps<any>>>\n}) {\n return (\n <div>\n <AgentHeader name={agent.name} icon={agent.icon} />\n {message.blocks && (\n <BlockRenderer\n blocks={message.blocks}\n onAction={onAction}\n customBlocks={customBlocks}\n />\n )}\n </div>\n )\n}\n\n// ── Processing indicator ─────────────────────────────────────────────────────\n\nfunction ProcessingIndicator({\n steps,\n agent,\n reducedMotion,\n}: {\n steps?: ProcessingStep[]\n agent: { name: string; icon?: IconInput }\n reducedMotion: boolean\n}) {\n // Step visualization\n if (steps && steps.length > 0) {\n return (\n <div role=\"status\" aria-busy=\"true\" aria-label=\"Processing\">\n <AgentHeader name={agent.name} icon={agent.icon} />\n <div className=\"flex flex-col gap-ds-02b\">\n {steps.map((step, i) => (\n <motion.div\n key={step.id}\n className=\"flex items-center gap-ds-02b\"\n initial={reducedMotion ? undefined : { opacity: 0, x: -8 }}\n animate={{ opacity: 1, x: 0 }}\n transition={\n reducedMotion\n ? { duration: 0 }\n : { duration: durations.moderate01b, delay: i * 0.05 }\n }\n >\n <StepStatusIcon status={step.status} />\n <span\n className={cn(\n 'text-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={{ y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 8 }}\n transition={springs.snappy}\n onClick={onClick}\n >\n {'\\u2193'} New response\n </motion.button>\n )\n}\n\n// ── Default agent ────────────────────────────────────────────────────────────\n\nconst DEFAULT_AGENT = { name: 'Assistant' }\n\n// ── AIConversation ───────────────────────────────────────────────────────────\n\nconst AIConversation = React.forwardRef<HTMLDivElement, AIConversationProps>(({\n messages,\n isProcessing = false,\n processingSteps,\n agent: agentProp,\n onAction,\n customBlocks,\n maxHeight,\n autoScroll = true,\n className,\n}, ref) => {\n const ctx = useAICommand()\n const { reducedMotion } = useMotion()\n\n // Resolve agent: prop > context > default\n const agent = agentProp ?? ctx?.agent ?? DEFAULT_AGENT\n\n // Resolve onAction: prop > context\n const resolvedOnAction = onAction ?? ctx?.onAction\n\n // Resolve customBlocks: prop wins over context\n const resolvedCustomBlocks = React.useMemo(() => {\n const contextBlocks = ctx?.customBlocks ?? {}\n return { ...contextBlocks, ...customBlocks }\n }, [ctx?.customBlocks, customBlocks])\n\n // ── Auto-scroll ──────────────────────────────────────────────────────────\n\n const scrollRef = React.useRef<HTMLDivElement>(null)\n const sentinelRef = React.useRef<HTMLDivElement>(null)\n const [isAtBottom, setIsAtBottom] = React.useState(true)\n const [hasNewContent, setHasNewContent] = React.useState(false)\n const prevMessageCount = React.useRef(messages.length)\n const prevProcessing = React.useRef(isProcessing)\n\n // Track if user is scrolled to bottom via IntersectionObserver\n React.useEffect(() => {\n if (!autoScroll || !sentinelRef.current) return\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n setIsAtBottom(entry.isIntersecting)\n if (entry.isIntersecting) {\n setHasNewContent(false)\n }\n },\n {\n root: scrollRef.current,\n threshold: 0.1,\n },\n )\n\n observer.observe(sentinelRef.current)\n return () => observer.disconnect()\n }, [autoScroll])\n\n // Auto-scroll or show pill on new content\n React.useEffect(() => {\n const contentChanged =\n messages.length !== prevMessageCount.current ||\n isProcessing !== prevProcessing.current\n\n prevMessageCount.current = messages.length\n prevProcessing.current = isProcessing\n\n if (!contentChanged || !autoScroll) return\n\n if (isAtBottom) {\n sentinelRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' })\n } else {\n setHasNewContent(true)\n }\n }, [messages, isProcessing, isAtBottom, autoScroll])\n\n const scrollToBottom = React.useCallback(() => {\n sentinelRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' })\n setIsAtBottom(true)\n setHasNewContent(false)\n }, [])\n\n // ── Render ───────────────────────────────────────────────────────────────\n\n return (\n <div ref={ref} className={cn('relative', className)}>\n <div\n ref={scrollRef}\n className=\"flex flex-col gap-ds-05 overflow-y-auto\"\n aria-live=\"polite\"\n style={\n maxHeight\n ? {\n maxHeight:\n typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight,\n }\n : undefined\n }\n >\n {messages.map((msg) =>\n msg.role === 'user' ? (\n <UserMessage\n key={msg.id}\n message={msg}\n reducedMotion={reducedMotion}\n />\n ) : (\n <AssistantMessage\n key={msg.id}\n message={msg}\n agent={agent}\n onAction={resolvedOnAction}\n customBlocks={resolvedCustomBlocks}\n />\n ),\n )}\n {isProcessing && (\n <ProcessingIndicator\n steps={processingSteps}\n agent={agent}\n reducedMotion={reducedMotion}\n />\n )}\n <div ref={sentinelRef} className=\"h-px\" />\n </div>\n <AnimatePresence>\n {!isAtBottom && hasNewContent && (\n <ScrollToBottomPill onClick={scrollToBottom} />\n )}\n </AnimatePresence>\n </div>\n )\n})\nAIConversation.displayName = 'AIConversation'\n\nexport { AIConversation }\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devadoot-icon.d.ts","sourceRoot":"","sources":["../../src/ai/devadoot-icon.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,YAAY,GAAG,WAAW,GAAG,OAAO,CAAA;AAEzE,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAiCD,QAAA,MAAM,YAAY,0DAIf,iBAAiB,
|
|
1
|
+
{"version":3,"file":"devadoot-icon.d.ts","sourceRoot":"","sources":["../../src/ai/devadoot-icon.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,YAAY,GAAG,WAAW,GAAG,OAAO,CAAA;AAEzE,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAiCD,QAAA,MAAM,YAAY,0DAIf,iBAAiB,6CAuHlB,CAAA;AAIF,OAAO,EAAE,YAAY,EAAE,CAAA"}
|
package/dist/ai/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn as e } from "../ui/lib/utils.js";
|
|
3
|
-
import {
|
|
3
|
+
import { useMotion as t } from "../_chunks/motion-provider.js";
|
|
4
4
|
import { AICommandProvider as n, useAICommand as r } from "./ai-command-provider.js";
|
|
5
5
|
import { CommandBar as i } from "./command-bar.js";
|
|
6
|
-
import { a,
|
|
6
|
+
import { BlockTable as a, ConfirmBlock as o, DividerBlock as s, InfoBlock as c, LoadingBlock as l, StatRowBlock as u, SuccessBlock as d } from "../_chunks/success.js";
|
|
7
7
|
import { AIConversation as f } from "./conversation.js";
|
|
8
8
|
import * as p from "react";
|
|
9
9
|
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
@@ -22,7 +22,7 @@ function O(e) {
|
|
|
22
22
|
}, [t, e]), b(t, (e) => `rotate(${e}, 16, 16)`);
|
|
23
23
|
}
|
|
24
24
|
var k = p.memo(function({ state: n = "idle", size: r = 20, className: i }) {
|
|
25
|
-
let { reducedMotion: a } = t(), o = O(n), s = p.useId()
|
|
25
|
+
let { reducedMotion: a } = t(), o = O(n), s = p.useId();
|
|
26
26
|
if (a) return /* @__PURE__ */ m("svg", {
|
|
27
27
|
viewBox: "0 0 32 32",
|
|
28
28
|
width: r,
|
|
@@ -34,7 +34,7 @@ var k = p.memo(function({ state: n = "idle", size: r = 20, className: i }) {
|
|
|
34
34
|
fill: n === "error" ? D : S
|
|
35
35
|
})
|
|
36
36
|
});
|
|
37
|
-
let
|
|
37
|
+
let c = n === "error" ? {
|
|
38
38
|
s1: D,
|
|
39
39
|
s2: "#FF6B6B",
|
|
40
40
|
s3: D
|
|
@@ -50,7 +50,7 @@ var k = p.memo(function({ state: n = "idle", size: r = 20, className: i }) {
|
|
|
50
50
|
s1: S,
|
|
51
51
|
s2: C,
|
|
52
52
|
s3: S
|
|
53
|
-
}
|
|
53
|
+
};
|
|
54
54
|
return /* @__PURE__ */ m("span", {
|
|
55
55
|
className: e("relative inline-flex items-center justify-center", i),
|
|
56
56
|
style: {
|
|
@@ -80,7 +80,7 @@ var k = p.memo(function({ state: n = "idle", size: r = 20, className: i }) {
|
|
|
80
80
|
} } : n === "error" ? { x: { duration: .4 } } : { duration: .3 },
|
|
81
81
|
style: { overflow: "visible" },
|
|
82
82
|
children: [
|
|
83
|
-
/* @__PURE__ */
|
|
83
|
+
/* @__PURE__ */ m("defs", { children: /* @__PURE__ */ h(v.linearGradient, {
|
|
84
84
|
id: s,
|
|
85
85
|
x1: "0",
|
|
86
86
|
y1: "0",
|
|
@@ -90,7 +90,7 @@ var k = p.memo(function({ state: n = "idle", size: r = 20, className: i }) {
|
|
|
90
90
|
children: [
|
|
91
91
|
/* @__PURE__ */ m(v.stop, {
|
|
92
92
|
offset: "0%",
|
|
93
|
-
animate: { stopColor:
|
|
93
|
+
animate: { stopColor: c.s1 },
|
|
94
94
|
transition: {
|
|
95
95
|
duration: n === "processing" ? 3 : 5,
|
|
96
96
|
repeat: Infinity,
|
|
@@ -100,7 +100,7 @@ var k = p.memo(function({ state: n = "idle", size: r = 20, className: i }) {
|
|
|
100
100
|
}),
|
|
101
101
|
/* @__PURE__ */ m(v.stop, {
|
|
102
102
|
offset: "50%",
|
|
103
|
-
animate: { stopColor:
|
|
103
|
+
animate: { stopColor: c.s2 },
|
|
104
104
|
transition: {
|
|
105
105
|
duration: n === "processing" ? 3 : 5,
|
|
106
106
|
repeat: Infinity,
|
|
@@ -111,7 +111,7 @@ var k = p.memo(function({ state: n = "idle", size: r = 20, className: i }) {
|
|
|
111
111
|
}),
|
|
112
112
|
/* @__PURE__ */ m(v.stop, {
|
|
113
113
|
offset: "100%",
|
|
114
|
-
animate: { stopColor:
|
|
114
|
+
animate: { stopColor: c.s3 },
|
|
115
115
|
transition: {
|
|
116
116
|
duration: n === "processing" ? 3 : 5,
|
|
117
117
|
repeat: Infinity,
|
|
@@ -121,37 +121,6 @@ var k = p.memo(function({ state: n = "idle", size: r = 20, className: i }) {
|
|
|
121
121
|
}
|
|
122
122
|
})
|
|
123
123
|
]
|
|
124
|
-
}), /* @__PURE__ */ m("filter", {
|
|
125
|
-
id: c,
|
|
126
|
-
x: "-50%",
|
|
127
|
-
y: "-50%",
|
|
128
|
-
width: "200%",
|
|
129
|
-
height: "200%",
|
|
130
|
-
children: /* @__PURE__ */ m(v.feGaussianBlur, {
|
|
131
|
-
in: "SourceGraphic",
|
|
132
|
-
animate: { stdDeviation: u },
|
|
133
|
-
transition: {
|
|
134
|
-
duration: .6,
|
|
135
|
-
ease: "easeOut"
|
|
136
|
-
}
|
|
137
|
-
})
|
|
138
|
-
})] }),
|
|
139
|
-
/* @__PURE__ */ m(g, { children: (n === "processing" || n === "error") && /* @__PURE__ */ m(v.path, {
|
|
140
|
-
d: x,
|
|
141
|
-
fill: `url(#${s})`,
|
|
142
|
-
filter: `url(#${c})`,
|
|
143
|
-
initial: { opacity: 0 },
|
|
144
|
-
animate: { opacity: [
|
|
145
|
-
.2,
|
|
146
|
-
.45,
|
|
147
|
-
.2
|
|
148
|
-
] },
|
|
149
|
-
exit: { opacity: 0 },
|
|
150
|
-
transition: { opacity: {
|
|
151
|
-
duration: 3,
|
|
152
|
-
repeat: Infinity,
|
|
153
|
-
ease: "easeInOut"
|
|
154
|
-
} }
|
|
155
124
|
}) }),
|
|
156
125
|
/* @__PURE__ */ m(g, { children: n === "processing" && /* @__PURE__ */ m(v.path, {
|
|
157
126
|
d: x,
|
|
@@ -180,4 +149,4 @@ var k = p.memo(function({ state: n = "idle", size: r = 20, className: i }) {
|
|
|
180
149
|
});
|
|
181
150
|
k.displayName = "DevadootIcon";
|
|
182
151
|
//#endregion
|
|
183
|
-
export { n as AICommandProvider, f as AIConversation,
|
|
152
|
+
export { n as AICommandProvider, f as AIConversation, a as BlockTable, i as CommandBar, o as ConfirmBlock, k as DevadootIcon, s as DividerBlock, c as InfoBlock, l as LoadingBlock, u as StatRowBlock, d as SuccessBlock, r as useAICommand };
|
package/dist/ai/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/ai/devadoot-icon.tsx"],"sourcesContent":["'use client'\n\n/**\n * DevadootIcon -- Animated Devalok chakra for the AI command system.\n *\n * Renders the Devalok chakra SVG with **fill-first** animations:\n * - idle: gradient color breathing (pink ↔ rose, slow cycle)\n * - processing: gradient sweep (pink → purple → blue → pink cycle) + glow pulse\n * - responded: bright flash → settle to solid brand + brief scale pop\n * - error: red fill + glow pulse\n *\n * Inspired by Google Gemini's gradient-as-identity approach: the color IS\n * the animation, not just rotation/scale applied to a static shape.\n */\nimport { animate, AnimatePresence,motion, useMotionValue, useTransform } from 'framer-motion'\nimport * as React from 'react'\n\nimport { useMotion } from '../motion/motion-provider'\nimport { cn } from '../ui/lib/utils'\n\nexport type DevadootState = 'idle' | 'processing' | 'responded' | 'error'\n\nexport interface DevadootIconProps {\n state?: DevadootState\n size?: number\n className?: string\n}\n\n// Devalok chakra SVG path\nconst CHAKRA_PATH =\n 'M25.97,21.39c-0.9-1.85,0.08-3.95-1.72-5.39c1.76-1.44,0.8-3.55,1.69-5.39c0.05-0.12,0.04-0.25-0.02-0.35c-0.06-0.1-0.16-0.18-0.29-0.19c-2.05-0.15-3.35-2.04-5.5-1.21c-0.39-2.21-2.7-2.44-3.84-4.13c-0.08-0.1-0.19-0.16-0.31-0.16c-0.12,0-0.23,0.05-0.31,0.16c-1.14,1.69-3.43,1.92-3.82,4.13c-2.14-0.83-3.47,1.07-5.52,1.21c-0.13,0.01-0.23,0.09-0.29,0.19c-0.06,0.1-0.07,0.23-0.02,0.35c0.9,1.85-0.08,3.95,1.72,5.39c-1.76,1.44-0.8,3.55-1.69,5.39C6,21.51,6.02,21.64,6.07,21.74c0.06,0.1,0.16,0.18,0.29,0.19c2.05,0.15,3.38,2.06,5.52,1.23c0.39,2.21,2.67,2.43,3.82,4.12c0.08,0.1,0.19,0.16,0.31,0.16c0.12,0,0.23-0.05,0.31-0.16c1.14-1.69,3.42-1.92,3.81-4.13c2.14,0.83,3.48-1.07,5.53-1.22c0.13-0.01,0.23-0.09,0.29-0.19C26.01,21.64,26.02,21.51,25.97,21.39z'\n\n// Brand colors — pink ↔ purple palette, no blue\nconst BRAND_PINK = '#D33163'\nconst BRAND_ROSE = '#E8457A'\nconst BRAND_PURPLE = '#9B5DE5'\nconst BRAND_MAGENTA = '#C850C0'\nconst BRAND_BRIGHT = '#FF6B9D'\nconst ERROR_RED = '#E5383B'\n\n/**\n * Gradient rotation driver — animates the angle of the SVG gradient.\n * This creates the \"sweep\" effect: colors appear to flow across the shape.\n */\nfunction useGradientRotation(state: DevadootState) {\n const angle = useMotionValue(0)\n\n React.useEffect(() => {\n const controls = animate(angle, 360, {\n duration: state === 'processing' ? 4 : 12,\n repeat: Infinity,\n ease: 'linear',\n })\n return () => controls.stop()\n }, [angle, state])\n\n return useTransform(angle, (v) => `rotate(${v}, 16, 16)`)\n}\n\nconst DevadootIcon = React.memo(function DevadootIcon({\n state = 'idle',\n size = 20,\n className,\n}: DevadootIconProps) {\n const { reducedMotion } = useMotion()\n const gradientRotation = useGradientRotation(state)\n const gradientId = React.useId()\n const filterId = React.useId()\n\n // Static fallback for reduced motion\n if (reducedMotion) {\n return (\n <svg\n viewBox=\"0 0 32 32\"\n width={size}\n height={size}\n className={className}\n aria-hidden=\"true\"\n >\n <path\n d={CHAKRA_PATH}\n fill={state === 'error' ? ERROR_RED : BRAND_PINK}\n />\n </svg>\n )\n }\n\n // Resolve gradient stop colors based on state\n const stops =\n state === 'error'\n ? { s1: ERROR_RED, s2: '#FF6B6B', s3: ERROR_RED }\n : state === 'processing'\n ? { s1: BRAND_PINK, s2: BRAND_PURPLE, s3: BRAND_MAGENTA }\n : state === 'responded'\n ? { s1: BRAND_BRIGHT, s2: BRAND_PINK, s3: BRAND_BRIGHT }\n : { s1: BRAND_PINK, s2: BRAND_ROSE, s3: BRAND_PINK }\n\n // Glow blur intensity\n const glowBlur = state === 'processing' ? 3 : state === 'error' ? 2.5 : 0\n\n return (\n <span\n className={cn('relative inline-flex items-center justify-center', className)}\n style={{ width: size, height: size }}\n aria-hidden=\"true\"\n >\n <motion.svg\n viewBox=\"0 0 32 32\"\n width={size}\n height={size}\n // Responded: brief pop. Error: shake. Others: no transform.\n animate={\n state === 'responded'\n ? { scale: [1.18, 1] }\n : state === 'error'\n ? { x: [0, -2, 2, -1.5, 1.5, 0] }\n : { scale: 1, x: 0 }\n }\n transition={\n state === 'responded'\n /* Near springs.bouncy (400/15/0.5) — stiffer pop for celebration feedback */\n ? { scale: { type: 'spring', stiffness: 500, damping: 15 } }\n : state === 'error'\n ? { x: { duration: 0.4 } }\n : { duration: 0.3 }\n }\n style={{ overflow: 'visible' }}\n >\n <defs>\n {/* Animated gradient — colors + rotation create the sweep */}\n <motion.linearGradient\n id={gradientId}\n x1=\"0\"\n y1=\"0\"\n x2=\"1\"\n y2=\"1\"\n gradientTransform={gradientRotation}\n >\n <motion.stop\n offset=\"0%\"\n animate={{ stopColor: stops.s1 }}\n transition={{ duration: state === 'processing' ? 3 : 5, repeat: Infinity, repeatType: 'reverse', ease: 'easeInOut' }}\n />\n <motion.stop\n offset=\"50%\"\n animate={{ stopColor: stops.s2 }}\n transition={{ duration: state === 'processing' ? 3 : 5, repeat: Infinity, repeatType: 'reverse', ease: 'easeInOut', delay: 0.5 }}\n />\n <motion.stop\n offset=\"100%\"\n animate={{ stopColor: stops.s3 }}\n transition={{ duration: state === 'processing' ? 3 : 5, repeat: Infinity, repeatType: 'reverse', ease: 'easeInOut', delay: 1 }}\n />\n </motion.linearGradient>\n\n {/* Glow filter — blur applied to shadow layer */}\n <filter id={filterId} x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\">\n <motion.feGaussianBlur\n in=\"SourceGraphic\"\n animate={{ stdDeviation: glowBlur }}\n transition={{ duration: 0.6, ease: 'easeOut' }}\n />\n </filter>\n </defs>\n\n {/* Layer 1: Glow shadow (blurred, semi-transparent copy behind) */}\n <AnimatePresence>\n {(state === 'processing' || state === 'error') && (\n <motion.path\n d={CHAKRA_PATH}\n fill={`url(#${gradientId})`}\n filter={`url(#${filterId})`}\n initial={{ opacity: 0 }}\n animate={{ opacity: [0.2, 0.45, 0.2] }}\n exit={{ opacity: 0 }}\n transition={{\n opacity: { duration: 3, repeat: Infinity, ease: 'easeInOut' },\n }}\n />\n )}\n </AnimatePresence>\n\n {/* Layer 2: Shimmer highlight (bright overlay that pulses) */}\n <AnimatePresence>\n {state === 'processing' && (\n <motion.path\n d={CHAKRA_PATH}\n fill=\"white\"\n initial={{ opacity: 0 }}\n animate={{ opacity: [0, 0.12, 0] }}\n exit={{ opacity: 0 }}\n transition={{\n opacity: { duration: 2.5, repeat: Infinity, ease: 'easeInOut' },\n }}\n />\n )}\n </AnimatePresence>\n\n {/* Layer 3: Main shape — gradient fill */}\n <motion.path\n d={CHAKRA_PATH}\n fill={`url(#${gradientId})`}\n // Responded: brief bright flash via fill override\n animate={\n state === 'responded'\n ? { fill: [`url(#${gradientId})`, `url(#${gradientId})`] }\n : {}\n }\n />\n </motion.svg>\n </span>\n )\n})\n\nDevadootIcon.displayName = 'DevadootIcon'\n\nexport { DevadootIcon }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/ai/devadoot-icon.tsx"],"sourcesContent":["'use client'\n\n/**\n * DevadootIcon -- Animated Devalok chakra for the AI command system.\n *\n * Renders the Devalok chakra SVG with **fill-first** animations:\n * - idle: gradient color breathing (pink ↔ rose, slow cycle)\n * - processing: gradient sweep (pink → purple → blue → pink cycle) + glow pulse\n * - responded: bright flash → settle to solid brand + brief scale pop\n * - error: red fill + glow pulse\n *\n * Inspired by Google Gemini's gradient-as-identity approach: the color IS\n * the animation, not just rotation/scale applied to a static shape.\n */\nimport { animate, AnimatePresence,motion, useMotionValue, useTransform } from 'framer-motion'\nimport * as React from 'react'\n\nimport { useMotion } from '../motion/motion-provider'\nimport { cn } from '../ui/lib/utils'\n\nexport type DevadootState = 'idle' | 'processing' | 'responded' | 'error'\n\nexport interface DevadootIconProps {\n state?: DevadootState\n size?: number\n className?: string\n}\n\n// Devalok chakra SVG path\nconst CHAKRA_PATH =\n 'M25.97,21.39c-0.9-1.85,0.08-3.95-1.72-5.39c1.76-1.44,0.8-3.55,1.69-5.39c0.05-0.12,0.04-0.25-0.02-0.35c-0.06-0.1-0.16-0.18-0.29-0.19c-2.05-0.15-3.35-2.04-5.5-1.21c-0.39-2.21-2.7-2.44-3.84-4.13c-0.08-0.1-0.19-0.16-0.31-0.16c-0.12,0-0.23,0.05-0.31,0.16c-1.14,1.69-3.43,1.92-3.82,4.13c-2.14-0.83-3.47,1.07-5.52,1.21c-0.13,0.01-0.23,0.09-0.29,0.19c-0.06,0.1-0.07,0.23-0.02,0.35c0.9,1.85-0.08,3.95,1.72,5.39c-1.76,1.44-0.8,3.55-1.69,5.39C6,21.51,6.02,21.64,6.07,21.74c0.06,0.1,0.16,0.18,0.29,0.19c2.05,0.15,3.38,2.06,5.52,1.23c0.39,2.21,2.67,2.43,3.82,4.12c0.08,0.1,0.19,0.16,0.31,0.16c0.12,0,0.23-0.05,0.31-0.16c1.14-1.69,3.42-1.92,3.81-4.13c2.14,0.83,3.48-1.07,5.53-1.22c0.13-0.01,0.23-0.09,0.29-0.19C26.01,21.64,26.02,21.51,25.97,21.39z'\n\n// Brand colors — pink ↔ purple palette, no blue\nconst BRAND_PINK = '#D33163'\nconst BRAND_ROSE = '#E8457A'\nconst BRAND_PURPLE = '#9B5DE5'\nconst BRAND_MAGENTA = '#C850C0'\nconst BRAND_BRIGHT = '#FF6B9D'\nconst ERROR_RED = '#E5383B'\n\n/**\n * Gradient rotation driver — animates the angle of the SVG gradient.\n * This creates the \"sweep\" effect: colors appear to flow across the shape.\n */\nfunction useGradientRotation(state: DevadootState) {\n const angle = useMotionValue(0)\n\n React.useEffect(() => {\n const controls = animate(angle, 360, {\n duration: state === 'processing' ? 4 : 12,\n repeat: Infinity,\n ease: 'linear',\n })\n return () => controls.stop()\n }, [angle, state])\n\n return useTransform(angle, (v) => `rotate(${v}, 16, 16)`)\n}\n\nconst DevadootIcon = React.memo(function DevadootIcon({\n state = 'idle',\n size = 20,\n className,\n}: DevadootIconProps) {\n const { reducedMotion } = useMotion()\n const gradientRotation = useGradientRotation(state)\n const gradientId = React.useId()\n\n // Static fallback for reduced motion\n if (reducedMotion) {\n return (\n <svg\n viewBox=\"0 0 32 32\"\n width={size}\n height={size}\n className={className}\n aria-hidden=\"true\"\n >\n <path\n d={CHAKRA_PATH}\n fill={state === 'error' ? ERROR_RED : BRAND_PINK}\n />\n </svg>\n )\n }\n\n // Resolve gradient stop colors based on state\n const stops =\n state === 'error'\n ? { s1: ERROR_RED, s2: '#FF6B6B', s3: ERROR_RED }\n : state === 'processing'\n ? { s1: BRAND_PINK, s2: BRAND_PURPLE, s3: BRAND_MAGENTA }\n : state === 'responded'\n ? { s1: BRAND_BRIGHT, s2: BRAND_PINK, s3: BRAND_BRIGHT }\n : { s1: BRAND_PINK, s2: BRAND_ROSE, s3: BRAND_PINK }\n\n return (\n <span\n className={cn('relative inline-flex items-center justify-center', className)}\n style={{ width: size, height: size }}\n aria-hidden=\"true\"\n >\n <motion.svg\n viewBox=\"0 0 32 32\"\n width={size}\n height={size}\n // Responded: brief pop. Error: shake. Others: no transform.\n animate={\n state === 'responded'\n ? { scale: [1.18, 1] }\n : state === 'error'\n ? { x: [0, -2, 2, -1.5, 1.5, 0] }\n : { scale: 1, x: 0 }\n }\n transition={\n state === 'responded'\n /* Near springs.bouncy (400/15/0.5) — stiffer pop for celebration feedback */\n ? { scale: { type: 'spring', stiffness: 500, damping: 15 } }\n : state === 'error'\n ? { x: { duration: 0.4 } }\n : { duration: 0.3 }\n }\n style={{ overflow: 'visible' }}\n >\n <defs>\n {/* Animated gradient — colors + rotation create the sweep */}\n <motion.linearGradient\n id={gradientId}\n x1=\"0\"\n y1=\"0\"\n x2=\"1\"\n y2=\"1\"\n gradientTransform={gradientRotation}\n >\n <motion.stop\n offset=\"0%\"\n animate={{ stopColor: stops.s1 }}\n transition={{ duration: state === 'processing' ? 3 : 5, repeat: Infinity, repeatType: 'reverse', ease: 'easeInOut' }}\n />\n <motion.stop\n offset=\"50%\"\n animate={{ stopColor: stops.s2 }}\n transition={{ duration: state === 'processing' ? 3 : 5, repeat: Infinity, repeatType: 'reverse', ease: 'easeInOut', delay: 0.5 }}\n />\n <motion.stop\n offset=\"100%\"\n animate={{ stopColor: stops.s3 }}\n transition={{ duration: state === 'processing' ? 3 : 5, repeat: Infinity, repeatType: 'reverse', ease: 'easeInOut', delay: 1 }}\n />\n </motion.linearGradient>\n </defs>\n\n {/* Layer 1: Shimmer highlight (bright overlay that pulses) */}\n <AnimatePresence>\n {state === 'processing' && (\n <motion.path\n d={CHAKRA_PATH}\n fill=\"white\"\n initial={{ opacity: 0 }}\n animate={{ opacity: [0, 0.12, 0] }}\n exit={{ opacity: 0 }}\n transition={{\n opacity: { duration: 2.5, repeat: Infinity, ease: 'easeInOut' },\n }}\n />\n )}\n </AnimatePresence>\n\n {/* Layer 3: Main shape — gradient fill */}\n <motion.path\n d={CHAKRA_PATH}\n fill={`url(#${gradientId})`}\n // Responded: brief bright flash via fill override\n animate={\n state === 'responded'\n ? { fill: [`url(#${gradientId})`, `url(#${gradientId})`] }\n : {}\n }\n />\n </motion.svg>\n </span>\n )\n})\n\nDevadootIcon.displayName = 'DevadootIcon'\n\nexport { DevadootIcon }\n"],"mappings":"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"}
|
|
@@ -138,7 +138,7 @@ function b({ item: e, compact: n, children: r }) {
|
|
|
138
138
|
}
|
|
139
139
|
function x({ label: n, isFirst: r }) {
|
|
140
140
|
return /* @__PURE__ */ f(m.div, {
|
|
141
|
-
initial:
|
|
141
|
+
initial: !1,
|
|
142
142
|
animate: { opacity: 1 },
|
|
143
143
|
transition: e.fade,
|
|
144
144
|
className: t("flex items-center gap-ds-03", r ? "" : "mt-ds-06", "mb-ds-03"),
|
|
@@ -191,7 +191,7 @@ var S = u.forwardRef(({ items: n, onLoadMore: r, loading: i = !1, hasMore: a = !
|
|
|
191
191
|
}), /* @__PURE__ */ f("div", {
|
|
192
192
|
className: t("relative flex flex-col", c ? "gap-1" : "gap-3"),
|
|
193
193
|
children: [/* @__PURE__ */ d("div", { className: "absolute bottom-0 left-[3px] top-0 w-px bg-surface-border" }), n.items.map((t, n) => /* @__PURE__ */ d(m.div, {
|
|
194
|
-
initial:
|
|
194
|
+
initial: !1,
|
|
195
195
|
animate: { opacity: 1 },
|
|
196
196
|
transition: {
|
|
197
197
|
...e.fade,
|
|
@@ -202,7 +202,7 @@ var S = u.forwardRef(({ items: n, onLoadMore: r, loading: i = !1, hasMore: a = !
|
|
|
202
202
|
})] }, n.label)) }) : /* @__PURE__ */ f("div", {
|
|
203
203
|
className: t("relative flex flex-col", c ? "gap-1" : "gap-3"),
|
|
204
204
|
children: [/* @__PURE__ */ d("div", { className: "absolute bottom-0 left-[3px] top-0 w-px bg-surface-border" }), O.map((t, n) => /* @__PURE__ */ d(m.div, {
|
|
205
|
-
initial:
|
|
205
|
+
initial: !1,
|
|
206
206
|
animate: { opacity: 1 },
|
|
207
207
|
transition: {
|
|
208
208
|
...e.fade,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"activity-feed.js","names":[],"sources":["../../src/composed/activity-feed.tsx"],"sourcesContent":["'use client'\n\nimport { IconChevronRight } from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Avatar, AvatarFallback, AvatarImage } from '../ui/avatar'\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { formatRelativeTime } from '../ui/lib/date-utils'\nimport type { IconInput } from '../ui/lib/icon-input'\nimport { tweens } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\nimport { Skeleton } from '../ui/skeleton'\nimport { getInitials } from './lib/string-utils'\n\nexport interface ActivityItem {\n id: string\n actor?: { name: string; image?: string }\n action: string | React.ReactNode\n timestamp: Date | string\n icon?: IconInput\n color?: 'default' | 'success' | 'warning' | 'error' | 'info'\n detail?: React.ReactNode\n}\n\nexport interface GroupLabels {\n today?: string\n yesterday?: string\n thisWeek?: string\n older?: string\n}\n\nexport interface ActivityFeedProps extends React.HTMLAttributes<HTMLDivElement> {\n items: ActivityItem[]\n onLoadMore?: () => void\n loading?: boolean\n hasMore?: boolean\n emptyState?: React.ReactNode\n compact?: boolean\n maxInitialItems?: number\n groupBy?: 'time' | 'none'\n groupLabels?: GroupLabels\n /** Custom renderer per item. Return ReactNode for custom rendering, undefined to use default ActivityEntry. */\n renderItem?: (item: ActivityItem, index: number) => React.ReactNode | undefined\n}\n\nconst dotColorMap = {\n default: 'bg-surface-fg-subtle',\n success: 'bg-success-9',\n warning: 'bg-warning-9',\n error: 'bg-error-9',\n info: 'bg-info-9',\n} as const\n\nconst DEFAULT_GROUP_LABELS: Required<GroupLabels> = {\n today: 'Today',\n yesterday: 'Yesterday',\n thisWeek: 'This Week',\n older: 'Older',\n}\n\n/**\n * Buckets items into time-based groups: today, yesterday, earlier this week (since Monday), older.\n * Skips empty groups.\n */\nexport function groupItemsByTime(\n items: ActivityItem[],\n labels?: GroupLabels,\n): { label: string; items: ActivityItem[] }[] {\n const l = { ...DEFAULT_GROUP_LABELS, ...labels }\n const now = new Date()\n\n // Start of today (midnight local)\n const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n // Start of yesterday\n const startOfYesterday = new Date(startOfToday.getTime() - 24 * 60 * 60 * 1000)\n // Start of this week (Monday)\n const dayOfWeek = now.getDay() // 0=Sun, 1=Mon, ...\n const daysSinceMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1\n const startOfWeek = new Date(\n startOfToday.getTime() - daysSinceMonday * 24 * 60 * 60 * 1000,\n )\n\n const today: ActivityItem[] = []\n const yesterday: ActivityItem[] = []\n const thisWeek: ActivityItem[] = []\n const older: ActivityItem[] = []\n\n for (const item of items) {\n const ts =\n typeof item.timestamp === 'string'\n ? new Date(item.timestamp)\n : item.timestamp\n const t = ts.getTime()\n\n if (t >= startOfToday.getTime()) {\n today.push(item)\n } else if (t >= startOfYesterday.getTime()) {\n yesterday.push(item)\n } else if (t >= startOfWeek.getTime()) {\n thisWeek.push(item)\n } else {\n older.push(item)\n }\n }\n\n const groups: { label: string; items: ActivityItem[] }[] = []\n if (today.length > 0) groups.push({ label: l.today, items: today })\n if (yesterday.length > 0) groups.push({ label: l.yesterday, items: yesterday })\n if (thisWeek.length > 0) groups.push({ label: l.thisWeek, items: thisWeek })\n if (older.length > 0) groups.push({ label: l.older, items: older })\n\n return groups\n}\n\nfunction LoadingSkeleton({ compact }: { compact: boolean }) {\n return (\n <div className={cn('flex flex-col', compact ? 'gap-1' : 'gap-3')}>\n {[0, 1, 2].map((i) => (\n <div key={i} className=\"flex items-center gap-ds-03\">\n <Skeleton className=\"h-2 w-2 shrink-0 rounded-pill\" />\n {!compact && <Skeleton className=\"h-4 w-4 shrink-0 rounded-pill\" />}\n <Skeleton className={cn('flex-1', compact ? 'h-3' : 'h-4')} />\n <Skeleton className=\"h-3 w-12\" />\n </div>\n ))}\n </div>\n )\n}\n\nfunction ActivityEntry({\n item,\n compact,\n}: {\n item: ActivityItem\n compact: boolean\n}) {\n const [expandedDetail, setExpandedDetail] = React.useState(false)\n const detailId = React.useId()\n const color = item.color ?? 'default'\n\n const handleActionClick = () => {\n if (item.detail) {\n setExpandedDetail((prev) => !prev)\n }\n }\n\n return (\n <div className={cn('relative flex items-start', compact ? 'gap-ds-02' : 'gap-ds-03')}>\n {/* Dot */}\n <div\n className={cn(\n 'relative z-10 mt-1.5 h-2 w-2 shrink-0 rounded-pill ring-2 ring-surface-base',\n dotColorMap[color],\n )}\n />\n\n {/* Avatar (non-compact only) */}\n {!compact && item.actor && (\n <Avatar className=\"h-5 w-5 shrink-0 text-[9px]\">\n {item.actor.image && (\n <AvatarImage src={item.actor.image} alt={item.actor.name} />\n )}\n <AvatarFallback className=\"text-[9px]\">\n {getInitials(item.actor.name)}\n </AvatarFallback>\n </Avatar>\n )}\n\n {/* Content */}\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <div className=\"flex items-center justify-between gap-ds-02\">\n <div className={cn('min-w-0 flex-1 flex items-center gap-ds-01 flex-wrap', compact ? 'text-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 {item.detail ? (\n <button\n type=\"button\"\n className=\"rounded-control text-surface-fg-muted hover:underline hover:bg-surface-raised focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9\"\n onClick={handleActionClick}\n aria-expanded={expandedDetail}\n aria-controls={detailId}\n >\n {item.action}\n </button>\n ) : (\n <span className=\"text-surface-fg-muted\">{item.action}</span>\n )}\n </div>\n\n <time\n className={cn(\n 'shrink-0 whitespace-nowrap text-surface-fg-subtle',\n compact ? 'text-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 id={detailId} 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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"}
|
|
1
|
+
{"version":3,"file":"activity-feed.js","names":[],"sources":["../../src/composed/activity-feed.tsx"],"sourcesContent":["'use client'\n\nimport { IconChevronRight } from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Avatar, AvatarFallback, AvatarImage } from '../ui/avatar'\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { formatRelativeTime } from '../ui/lib/date-utils'\nimport type { IconInput } from '../ui/lib/icon-input'\nimport { tweens } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\nimport { Skeleton } from '../ui/skeleton'\nimport { getInitials } from './lib/string-utils'\n\nexport interface ActivityItem {\n id: string\n actor?: { name: string; image?: string }\n action: string | React.ReactNode\n timestamp: Date | string\n icon?: IconInput\n color?: 'default' | 'success' | 'warning' | 'error' | 'info'\n detail?: React.ReactNode\n}\n\nexport interface GroupLabels {\n today?: string\n yesterday?: string\n thisWeek?: string\n older?: string\n}\n\nexport interface ActivityFeedProps extends React.HTMLAttributes<HTMLDivElement> {\n items: ActivityItem[]\n onLoadMore?: () => void\n loading?: boolean\n hasMore?: boolean\n emptyState?: React.ReactNode\n compact?: boolean\n maxInitialItems?: number\n groupBy?: 'time' | 'none'\n groupLabels?: GroupLabels\n /** Custom renderer per item. Return ReactNode for custom rendering, undefined to use default ActivityEntry. */\n renderItem?: (item: ActivityItem, index: number) => React.ReactNode | undefined\n}\n\nconst dotColorMap = {\n default: 'bg-surface-fg-subtle',\n success: 'bg-success-9',\n warning: 'bg-warning-9',\n error: 'bg-error-9',\n info: 'bg-info-9',\n} as const\n\nconst DEFAULT_GROUP_LABELS: Required<GroupLabels> = {\n today: 'Today',\n yesterday: 'Yesterday',\n thisWeek: 'This Week',\n older: 'Older',\n}\n\n/**\n * Buckets items into time-based groups: today, yesterday, earlier this week (since Monday), older.\n * Skips empty groups.\n */\nexport function groupItemsByTime(\n items: ActivityItem[],\n labels?: GroupLabels,\n): { label: string; items: ActivityItem[] }[] {\n const l = { ...DEFAULT_GROUP_LABELS, ...labels }\n const now = new Date()\n\n // Start of today (midnight local)\n const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n // Start of yesterday\n const startOfYesterday = new Date(startOfToday.getTime() - 24 * 60 * 60 * 1000)\n // Start of this week (Monday)\n const dayOfWeek = now.getDay() // 0=Sun, 1=Mon, ...\n const daysSinceMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1\n const startOfWeek = new Date(\n startOfToday.getTime() - daysSinceMonday * 24 * 60 * 60 * 1000,\n )\n\n const today: ActivityItem[] = []\n const yesterday: ActivityItem[] = []\n const thisWeek: ActivityItem[] = []\n const older: ActivityItem[] = []\n\n for (const item of items) {\n const ts =\n typeof item.timestamp === 'string'\n ? new Date(item.timestamp)\n : item.timestamp\n const t = ts.getTime()\n\n if (t >= startOfToday.getTime()) {\n today.push(item)\n } else if (t >= startOfYesterday.getTime()) {\n yesterday.push(item)\n } else if (t >= startOfWeek.getTime()) {\n thisWeek.push(item)\n } else {\n older.push(item)\n }\n }\n\n const groups: { label: string; items: ActivityItem[] }[] = []\n if (today.length > 0) groups.push({ label: l.today, items: today })\n if (yesterday.length > 0) groups.push({ label: l.yesterday, items: yesterday })\n if (thisWeek.length > 0) groups.push({ label: l.thisWeek, items: thisWeek })\n if (older.length > 0) groups.push({ label: l.older, items: older })\n\n return groups\n}\n\nfunction LoadingSkeleton({ compact }: { compact: boolean }) {\n return (\n <div className={cn('flex flex-col', compact ? 'gap-1' : 'gap-3')}>\n {[0, 1, 2].map((i) => (\n <div key={i} className=\"flex items-center gap-ds-03\">\n <Skeleton className=\"h-2 w-2 shrink-0 rounded-pill\" />\n {!compact && <Skeleton className=\"h-4 w-4 shrink-0 rounded-pill\" />}\n <Skeleton className={cn('flex-1', compact ? 'h-3' : 'h-4')} />\n <Skeleton className=\"h-3 w-12\" />\n </div>\n ))}\n </div>\n )\n}\n\nfunction ActivityEntry({\n item,\n compact,\n}: {\n item: ActivityItem\n compact: boolean\n}) {\n const [expandedDetail, setExpandedDetail] = React.useState(false)\n const detailId = React.useId()\n const color = item.color ?? 'default'\n\n const handleActionClick = () => {\n if (item.detail) {\n setExpandedDetail((prev) => !prev)\n }\n }\n\n return (\n <div className={cn('relative flex items-start', compact ? 'gap-ds-02' : 'gap-ds-03')}>\n {/* Dot */}\n <div\n className={cn(\n 'relative z-10 mt-1.5 h-2 w-2 shrink-0 rounded-pill ring-2 ring-surface-base',\n dotColorMap[color],\n )}\n />\n\n {/* Avatar (non-compact only) */}\n {!compact && item.actor && (\n <Avatar className=\"h-5 w-5 shrink-0 text-[9px]\">\n {item.actor.image && (\n <AvatarImage src={item.actor.image} alt={item.actor.name} />\n )}\n <AvatarFallback className=\"text-[9px]\">\n {getInitials(item.actor.name)}\n </AvatarFallback>\n </Avatar>\n )}\n\n {/* Content */}\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <div className=\"flex items-center justify-between gap-ds-02\">\n <div className={cn('min-w-0 flex-1 flex items-center gap-ds-01 flex-wrap', compact ? 'text-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 {item.detail ? (\n <button\n type=\"button\"\n className=\"rounded-control text-surface-fg-muted hover:underline hover:bg-surface-raised focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9\"\n onClick={handleActionClick}\n aria-expanded={expandedDetail}\n aria-controls={detailId}\n >\n {item.action}\n </button>\n ) : (\n <span className=\"text-surface-fg-muted\">{item.action}</span>\n )}\n </div>\n\n <time\n className={cn(\n 'shrink-0 whitespace-nowrap text-surface-fg-subtle',\n compact ? 'text-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 id={detailId} 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={false}\n animate={{ opacity: 1 }}\n transition={tweens.fade}\n className={cn(\n 'flex items-center gap-ds-03',\n !isFirst ? 'mt-ds-06' : '',\n 'mb-ds-03',\n )}\n >\n <hr className=\"flex-1 border-surface-border\" />\n <span className=\"bg-surface-base px-ds-03 text-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={false} animate={{ opacity: 1 }} transition={{ ...tweens.fade, delay: index * 0.03 }}>\n {resolveEntry(item, index)}\n </motion.div>\n ))}\n </div>\n </div>\n ))}\n </div>\n ) : (\n <div className={cn('relative flex flex-col', compact ? 'gap-1' : 'gap-3')}>\n {/* Timeline line */}\n <div className=\"absolute bottom-0 left-[3px] top-0 w-px bg-surface-border\" />\n {visibleItems.map((item, index) => (\n <motion.div key={item.id} initial={false} animate={{ opacity: 1 }} transition={{ ...tweens.fade, delay: index * 0.03 }}>\n {resolveEntry(item, index)}\n </motion.div>\n ))}\n </div>\n )}\n\n {/* Show all button */}\n {truncated && (\n <div className=\"relative mt-ds-03 flex justify-center\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setShowAll(true)}\n >\n Show all ({items.length})\n </Button>\n </div>\n )}\n\n {/* Load more button */}\n {hasMore && onLoadMore && !truncated && (\n <div className=\"relative mt-ds-03 flex justify-center\">\n <Button variant=\"ghost\" size=\"sm\" onClick={onLoadMore}>\n Load more\n </Button>\n </div>\n )}\n </div>\n )\n },\n)\nActivityFeed.displayName = 'ActivityFeed'\n\nexport { ActivityFeed }\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { springs as e } from "../ui/lib/motion.js";
|
|
3
|
-
import { t } from "../_chunks/vendor-utils.js";
|
|
3
|
+
import { cva as t } from "../_chunks/vendor-utils.js";
|
|
4
4
|
import { cn as n } from "../ui/lib/utils.js";
|
|
5
5
|
import { Avatar as r, AvatarFallback as i, AvatarImage as a } from "../ui/avatar.js";
|
|
6
6
|
import { Tooltip as o, TooltipContent as s, TooltipProvider as c, TooltipTrigger as l } from "../ui/tooltip.js";
|
|
@@ -57,7 +57,7 @@ var g = {
|
|
|
57
57
|
let t = L * R;
|
|
58
58
|
return w === "left" ? `translateX(-${(z - 1 - e) * t}px)` : `translateX(${e * t}px)`;
|
|
59
59
|
}
|
|
60
|
-
let V = "transition-
|
|
60
|
+
let V = "transition-opacity duration-300 ease-out hover:z-50 group-hover:[&:not(:hover)]:opacity-85";
|
|
61
61
|
return /* @__PURE__ */ f(c, { children: /* @__PURE__ */ p("div", {
|
|
62
62
|
ref: O,
|
|
63
63
|
role: "group",
|
|
@@ -132,7 +132,7 @@ var g = {
|
|
|
132
132
|
type: "button",
|
|
133
133
|
onClick: S,
|
|
134
134
|
"aria-label": `${A} more members`,
|
|
135
|
-
className: n(_({ size: y }), P, j, `flex cursor-pointer items-center justify-center bg-accent-2 font-semibold text-accent-11 ${M}`, "hover:
|
|
135
|
+
className: n(_({ size: y }), P, j, `flex cursor-pointer items-center justify-center bg-accent-2 font-semibold text-accent-11 ${M}`, "hover:bg-accent-3 transition-colors duration-300 ease-out"),
|
|
136
136
|
style: {
|
|
137
137
|
zIndex: 0,
|
|
138
138
|
transform: B(k.length)
|
|
@@ -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-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":"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"}
|
|
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-opacity duration-300 ease-out hover:z-50 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:bg-accent-3 transition-colors 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":"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"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { springs as e } from "../ui/lib/motion.js";
|
|
3
3
|
import { cn as t } from "../ui/lib/utils.js";
|
|
4
4
|
import { Icon as n } from "../ui/icon.js";
|
|
5
|
-
import {
|
|
5
|
+
import { BadgeCompound as r } from "../_chunks/badge-group.js";
|
|
6
6
|
import { Button as i } from "../ui/button.js";
|
|
7
7
|
import * as a from "react";
|
|
8
8
|
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
@@ -3,11 +3,11 @@ import { IconProvider as e } from "../ui/icon-context.js";
|
|
|
3
3
|
import { springs as t, tweens as n } from "../ui/lib/motion.js";
|
|
4
4
|
import { cn as r } from "../ui/lib/utils.js";
|
|
5
5
|
import { Icon as i } from "../ui/icon.js";
|
|
6
|
-
import {
|
|
6
|
+
import { normalizeIcon as a } from "../_chunks/normalize-icon.js";
|
|
7
7
|
import { Dialog as o, DialogContentRaw as s, DialogDescription as c, DialogOverlay as l, DialogPortal as ee, DialogTitle as u } from "../ui/dialog.js";
|
|
8
8
|
import { VisuallyHidden as te } from "../ui/visually-hidden.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { useMotion as ne } from "../_chunks/motion-provider.js";
|
|
10
|
+
import { getIsMac as d, getModifierDisplay as f, matchesKeybinding as p } from "../_chunks/keybinding.js";
|
|
11
11
|
import * as m from "react";
|
|
12
12
|
import { jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
13
13
|
import { IconArrowDown as re, IconArrowUp as ie, IconCornerDownLeft as _, IconSearch as ae } from "@tabler/icons-react";
|
|
@@ -28,7 +28,7 @@ var b = m.forwardRef(function({ groups: b = [], placeholder: x = "Search or jump
|
|
|
28
28
|
let R = m.useCallback((e) => {
|
|
29
29
|
let t = typeof e == "function" ? e(L.current) : e;
|
|
30
30
|
N || F(t), E?.(t);
|
|
31
|
-
}, [N, E]), [z, B] = m.useState(""), [V, H] = m.useState(0), U = m.useRef(null), W = m.useRef(null), G = m.useId(), K = `command-palette-listbox-${G}`, { reducedMotion: q } = ne(), J = { duration: 0 }, ue = m.useMemo(() =>
|
|
31
|
+
}, [N, E]), [z, B] = m.useState(""), [V, H] = m.useState(0), U = m.useRef(null), W = m.useRef(null), G = m.useId(), K = `command-palette-listbox-${G}`, { reducedMotion: q } = ne(), J = { duration: 0 }, ue = m.useMemo(() => d(), []), Y = m.useMemo(() => {
|
|
32
32
|
if (!z.trim()) return b;
|
|
33
33
|
let e = z.toLowerCase();
|
|
34
34
|
return b.map((t) => ({
|
|
@@ -40,7 +40,7 @@ var b = m.forwardRef(function({ groups: b = [], placeholder: x = "Search or jump
|
|
|
40
40
|
if (D === !1) return;
|
|
41
41
|
let e = Array.isArray(D) ? D : [D];
|
|
42
42
|
function t(t) {
|
|
43
|
-
for (let n of e) if (
|
|
43
|
+
for (let n of e) if (p(t, n)) {
|
|
44
44
|
t.preventDefault(), R((e) => !e);
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
@@ -229,7 +229,7 @@ var b = m.forwardRef(function({ groups: b = [], placeholder: x = "Search or jump
|
|
|
229
229
|
className: "flex shrink-0 items-center gap-ds-01",
|
|
230
230
|
children: y(n.shortcut).map((e, t) => /* @__PURE__ */ h("kbd", {
|
|
231
231
|
className: r("inline-flex min-w-ds-05b 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", s ? "bg-accent-2 text-accent-11 border-accent-6" : "bg-surface-raised text-surface-fg-subtle border-surface-border-strong"),
|
|
232
|
-
children: e === "Ctrl" || e === "ctrl" ?
|
|
232
|
+
children: e === "Ctrl" || e === "ctrl" ? f(ue) : e
|
|
233
233
|
}, t))
|
|
234
234
|
}),
|
|
235
235
|
/* @__PURE__ */ h(oe, { children: s && /* @__PURE__ */ h(v.span, {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn as e } from "../../ui/lib/utils.js";
|
|
3
3
|
import { Icon as t } from "../../ui/icon.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { Popover as n, PopoverContent as r, PopoverTrigger as i } from "../../_chunks/popover.js";
|
|
5
|
+
import { CalendarGrid as a, useCalendar as o } from "../../_chunks/use-calendar.js";
|
|
6
6
|
import * as s from "react";
|
|
7
7
|
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
8
8
|
import { IconCalendarClock as u, IconCalendarEvent as d, IconClock as f } from "@tabler/icons-react";
|
|
@@ -206,10 +206,10 @@ var M = s.forwardRef(function({ value: o, onChange: u, placeholder: f = "Pick a
|
|
|
206
206
|
});
|
|
207
207
|
}
|
|
208
208
|
};
|
|
209
|
-
return /* @__PURE__ */ l(
|
|
209
|
+
return /* @__PURE__ */ l(n, {
|
|
210
210
|
open: S,
|
|
211
211
|
onOpenChange: w,
|
|
212
|
-
children: [/* @__PURE__ */ c(
|
|
212
|
+
children: [/* @__PURE__ */ c(i, {
|
|
213
213
|
asChild: !0,
|
|
214
214
|
children: /* @__PURE__ */ l("button", {
|
|
215
215
|
ref: b,
|
|
@@ -408,10 +408,10 @@ var F = s.forwardRef(function({ startDate: o, endDate: u, onChange: f, placehold
|
|
|
408
408
|
default: return Z();
|
|
409
409
|
}
|
|
410
410
|
}, $ = T && T.length > 0;
|
|
411
|
-
return /* @__PURE__ */ l(
|
|
411
|
+
return /* @__PURE__ */ l(n, {
|
|
412
412
|
open: k,
|
|
413
413
|
onOpenChange: M,
|
|
414
|
-
children: [/* @__PURE__ */ c(
|
|
414
|
+
children: [/* @__PURE__ */ c(i, {
|
|
415
415
|
asChild: !0,
|
|
416
416
|
children: /* @__PURE__ */ l("button", {
|
|
417
417
|
ref: O,
|
|
@@ -510,10 +510,10 @@ var R = s.forwardRef(function({ value: o, onChange: d, minDate: f, maxDate: p, d
|
|
|
510
510
|
});
|
|
511
511
|
}
|
|
512
512
|
}, Q = "h-ds-sm rounded-control border border-surface-border-strong bg-surface-overlay px-ds-02 text-ds-md text-surface-fg transition-colors duration-fast-01 ease-productive-standard hover:border-surface-border-strong focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 cursor-pointer appearance-none";
|
|
513
|
-
return /* @__PURE__ */ l(
|
|
513
|
+
return /* @__PURE__ */ l(n, {
|
|
514
514
|
open: E,
|
|
515
515
|
onOpenChange: D,
|
|
516
|
-
children: [/* @__PURE__ */ c(
|
|
516
|
+
children: [/* @__PURE__ */ c(i, {
|
|
517
517
|
asChild: !0,
|
|
518
518
|
children: /* @__PURE__ */ l("button", {
|
|
519
519
|
ref: T,
|
|
@@ -639,10 +639,10 @@ var V = s.forwardRef(function({ value: a, onChange: o, format: u = "12h", minute
|
|
|
639
639
|
u,
|
|
640
640
|
m
|
|
641
641
|
]), H = u === "12h" ? A : E, U = "flex flex-col gap-ds-01 overflow-y-auto max-h-[200px] px-ds-01", W = "flex h-ds-sm w-full items-center justify-center rounded-control text-ds-md transition-colors duration-fast-01 ease-productive-standard cursor-pointer", G = "bg-accent-9 text-accent-fg", K = "text-surface-fg hover:bg-surface-raised-hover";
|
|
642
|
-
return /* @__PURE__ */ l(
|
|
642
|
+
return /* @__PURE__ */ l(n, {
|
|
643
643
|
open: w,
|
|
644
644
|
onOpenChange: T,
|
|
645
|
-
children: [/* @__PURE__ */ c(
|
|
645
|
+
children: [/* @__PURE__ */ c(i, {
|
|
646
646
|
asChild: !0,
|
|
647
647
|
children: /* @__PURE__ */ l("button", {
|
|
648
648
|
ref: C,
|