@devalok/shilp-sutra 0.49.2 → 0.49.4
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.map +1 -1
- package/dist/_chunks/block-shell.js.map +1 -1
- package/dist/_chunks/chart-container.js.map +1 -1
- package/dist/_chunks/chat.js.map +1 -1
- package/dist/_chunks/colors.js.map +1 -1
- package/dist/_chunks/document-preview.js.map +1 -1
- package/dist/_chunks/emoji-suggestion.js.map +1 -1
- package/dist/_chunks/emoji.js +2 -2
- package/dist/_chunks/emoji.js.map +1 -1
- package/dist/_chunks/field-state.js.map +1 -1
- package/dist/_chunks/grid-lines.js.map +1 -1
- package/dist/_chunks/image-preview.js.map +1 -1
- package/dist/_chunks/keybinding.js.map +1 -1
- package/dist/_chunks/link-context.js.map +1 -1
- package/dist/_chunks/mention-suggestion.js.map +1 -1
- package/dist/_chunks/motion-provider.js.map +1 -1
- package/dist/_chunks/normalize-icon.js.map +1 -1
- package/dist/_chunks/oauth-button.js.map +1 -1
- package/dist/_chunks/popover.js.map +1 -1
- package/dist/_chunks/primitives.js +1129 -1162
- package/dist/_chunks/primitives.js.map +1 -1
- package/dist/_chunks/shared.js.map +1 -1
- package/dist/_chunks/success.js.map +1 -1
- package/dist/_chunks/tiptap.js +4283 -3460
- package/dist/_chunks/tiptap.js.map +1 -1
- package/dist/_chunks/tooltip.js.map +1 -1
- package/dist/_chunks/tree-view.js +1 -1
- package/dist/_chunks/tree-view.js.map +1 -1
- package/dist/_chunks/use-calendar.js.map +1 -1
- package/dist/_chunks/use-controllable-open.js.map +1 -1
- package/dist/_chunks/vendor-client.js +86 -59
- package/dist/_chunks/vendor-client.js.map +1 -1
- package/dist/_chunks/vendor-utils.js +555 -513
- package/dist/_chunks/vendor-utils.js.map +1 -1
- package/dist/ai/ai-command-provider.d.ts +1 -1
- package/dist/ai/ai-command-provider.d.ts.map +1 -1
- package/dist/ai/ai-command-provider.js.map +1 -1
- package/dist/ai/block-renderer.js.map +1 -1
- package/dist/ai/blocks/block-shell.d.ts +1 -1
- package/dist/ai/blocks/block-shell.d.ts.map +1 -1
- package/dist/ai/blocks/block-table.d.ts +1 -1
- package/dist/ai/blocks/block-table.d.ts.map +1 -1
- package/dist/ai/blocks/confirm.d.ts +1 -1
- package/dist/ai/blocks/confirm.d.ts.map +1 -1
- package/dist/ai/blocks/divider.d.ts +1 -1
- package/dist/ai/blocks/divider.d.ts.map +1 -1
- package/dist/ai/blocks/error.d.ts +1 -1
- package/dist/ai/blocks/error.d.ts.map +1 -1
- package/dist/ai/blocks/error.js.map +1 -1
- package/dist/ai/blocks/info.d.ts +1 -1
- package/dist/ai/blocks/info.d.ts.map +1 -1
- package/dist/ai/blocks/loading.d.ts +1 -1
- package/dist/ai/blocks/loading.d.ts.map +1 -1
- package/dist/ai/blocks/stat-row.d.ts +1 -1
- package/dist/ai/blocks/stat-row.d.ts.map +1 -1
- package/dist/ai/blocks/success.d.ts +1 -1
- package/dist/ai/blocks/success.d.ts.map +1 -1
- package/dist/ai/blocks/text.d.ts +1 -1
- package/dist/ai/blocks/text.d.ts.map +1 -1
- package/dist/ai/blocks/text.js.map +1 -1
- package/dist/ai/command-bar.js.map +1 -1
- package/dist/ai/conversation.js.map +1 -1
- package/dist/ai/devadoot-icon.d.ts +1 -1
- package/dist/ai/devadoot-icon.d.ts.map +1 -1
- package/dist/ai/index.js.map +1 -1
- package/dist/composed/activity-feed.js.map +1 -1
- package/dist/composed/avatar-group.js.map +1 -1
- package/dist/composed/bulk-action-bar.js.map +1 -1
- package/dist/composed/command-palette.js.map +1 -1
- package/dist/composed/confirm-dialog.js.map +1 -1
- package/dist/composed/content-card.js.map +1 -1
- package/dist/composed/date-picker/index.js.map +1 -1
- package/dist/composed/deadline-indicator.js.map +1 -1
- package/dist/composed/emoji-picker.d.ts +2 -2
- package/dist/composed/emoji-picker.d.ts.map +1 -1
- package/dist/composed/emoji-picker.js.map +1 -1
- package/dist/composed/empty-state.js.map +1 -1
- package/dist/composed/error-boundary.d.ts +1 -1
- package/dist/composed/error-boundary.js.map +1 -1
- package/dist/composed/extensions/emoji-node.js.map +1 -1
- package/dist/composed/file-preview/audio-preview.d.ts +2 -1
- package/dist/composed/file-preview/audio-preview.d.ts.map +1 -1
- package/dist/composed/file-preview/document-preview.d.ts +2 -1
- package/dist/composed/file-preview/document-preview.d.ts.map +1 -1
- package/dist/composed/file-preview/embed-preview.d.ts +2 -1
- package/dist/composed/file-preview/embed-preview.d.ts.map +1 -1
- package/dist/composed/file-preview/image-preview.d.ts +2 -1
- package/dist/composed/file-preview/image-preview.d.ts.map +1 -1
- package/dist/composed/file-preview/shared.d.ts +5 -5
- package/dist/composed/file-preview/shared.d.ts.map +1 -1
- package/dist/composed/file-preview/video-preview.d.ts +2 -1
- package/dist/composed/file-preview/video-preview.d.ts.map +1 -1
- package/dist/composed/file-preview.d.ts +1 -1
- package/dist/composed/file-preview.d.ts.map +1 -1
- package/dist/composed/file-preview.js.map +1 -1
- package/dist/composed/filter-bar.d.ts +2 -2
- package/dist/composed/filter-bar.d.ts.map +1 -1
- package/dist/composed/filter-bar.js.map +1 -1
- package/dist/composed/form-section.js.map +1 -1
- package/dist/composed/global-loading.js.map +1 -1
- package/dist/composed/inline-edit.js.map +1 -1
- package/dist/composed/lib/string-utils.js.map +1 -1
- package/dist/composed/loading-skeleton.js.map +1 -1
- package/dist/composed/markdown-viewer.js.map +1 -1
- package/dist/composed/master-detail.d.ts +3 -3
- package/dist/composed/master-detail.d.ts.map +1 -1
- package/dist/composed/master-detail.js.map +1 -1
- package/dist/composed/member-picker.js.map +1 -1
- package/dist/composed/multi-select-popover.js.map +1 -1
- package/dist/composed/page-header.js.map +1 -1
- package/dist/composed/page-skeletons.js.map +1 -1
- package/dist/composed/priority-indicator.js.map +1 -1
- package/dist/composed/responsive-modal.js +8 -4
- package/dist/composed/responsive-modal.js.map +1 -1
- package/dist/composed/rich-chat-input/attachment-strip.d.ts +2 -1
- package/dist/composed/rich-chat-input/attachment-strip.d.ts.map +1 -1
- package/dist/composed/rich-chat-input/chat-toolbar.d.ts +17 -17
- package/dist/composed/rich-chat-input/chat-toolbar.d.ts.map +1 -1
- package/dist/composed/rich-chat-input/recording-overlay.d.ts +2 -1
- package/dist/composed/rich-chat-input/recording-overlay.d.ts.map +1 -1
- package/dist/composed/rich-chat-input/reply-banner.d.ts +2 -1
- package/dist/composed/rich-chat-input/reply-banner.d.ts.map +1 -1
- package/dist/composed/rich-chat-input/schedule-send.d.ts +4 -3
- package/dist/composed/rich-chat-input/schedule-send.d.ts.map +1 -1
- package/dist/composed/rich-chat-input.js +2 -1
- package/dist/composed/rich-chat-input.js.map +1 -1
- package/dist/composed/rich-text-editor.js +2 -1
- package/dist/composed/rich-text-editor.js.map +1 -1
- package/dist/composed/schedule-view.js.map +1 -1
- package/dist/composed/simple-tooltip.js.map +1 -1
- package/dist/composed/status-badge.js.map +1 -1
- package/dist/hooks/use-color-mode.js +2 -1
- package/dist/hooks/use-color-mode.js.map +1 -1
- package/dist/hooks/use-mobile.js.map +1 -1
- package/dist/hooks/use-touch-device.js.map +1 -1
- package/dist/hooks/use-viewport-height.js.map +1 -1
- package/dist/motion/index.js.map +1 -1
- package/dist/motion/motion-provider.d.ts +1 -1
- package/dist/motion/motion-provider.d.ts.map +1 -1
- package/dist/motion/primitives-index.js.map +1 -1
- package/dist/primitives/_internal/react-dismissable-layer.d.ts.map +1 -1
- package/dist/primitives/_internal/react-focus-scope.d.ts.map +1 -1
- package/dist/shell/app-command-palette.js.map +1 -1
- package/dist/shell/bottom-navbar.js.map +1 -1
- package/dist/shell/command-registry.d.ts +1 -1
- package/dist/shell/command-registry.d.ts.map +1 -1
- package/dist/shell/command-registry.js.map +1 -1
- package/dist/shell/notification-center.js.map +1 -1
- package/dist/shell/notification-preferences.js.map +1 -1
- package/dist/shell/sidebar.js.map +1 -1
- package/dist/shell/top-bar.js.map +1 -1
- package/dist/ui/accordion.js.map +1 -1
- package/dist/ui/alert-dialog.js.map +1 -1
- package/dist/ui/alert.js.map +1 -1
- package/dist/ui/aspect-ratio.js.map +1 -1
- package/dist/ui/autocomplete.js.map +1 -1
- package/dist/ui/avatar.d.ts +1 -1
- package/dist/ui/avatar.js.map +1 -1
- package/dist/ui/badge-group.d.ts +1 -1
- package/dist/ui/badge-group.d.ts.map +1 -1
- package/dist/ui/badge-indicator.d.ts +1 -1
- package/dist/ui/badge-indicator.d.ts.map +1 -1
- package/dist/ui/badge-indicator.js.map +1 -1
- package/dist/ui/banner.js.map +1 -1
- package/dist/ui/breadcrumb.d.ts +2 -2
- package/dist/ui/breadcrumb.js.map +1 -1
- package/dist/ui/button-group.js.map +1 -1
- package/dist/ui/button-processing.d.ts +2 -1
- package/dist/ui/button-processing.d.ts.map +1 -1
- package/dist/ui/button-processing.js.map +1 -1
- package/dist/ui/button.js.map +1 -1
- package/dist/ui/card.js +1 -1
- package/dist/ui/card.js.map +1 -1
- package/dist/ui/charts/_internal/axes.d.ts +2 -1
- package/dist/ui/charts/_internal/axes.d.ts.map +1 -1
- package/dist/ui/charts/_internal/grid-lines.d.ts +2 -1
- package/dist/ui/charts/_internal/grid-lines.d.ts.map +1 -1
- package/dist/ui/charts/_internal/legend.d.ts +2 -1
- package/dist/ui/charts/_internal/legend.d.ts.map +1 -1
- package/dist/ui/charts/_internal/tooltip.d.ts +1 -1
- package/dist/ui/charts/_internal/tooltip.d.ts.map +1 -1
- package/dist/ui/charts/area-chart.js.map +1 -1
- package/dist/ui/charts/bar-chart.js.map +1 -1
- package/dist/ui/charts/gauge-chart.js.map +1 -1
- package/dist/ui/charts/line-chart.js.map +1 -1
- package/dist/ui/charts/pie-chart.js.map +1 -1
- package/dist/ui/charts/radar-chart.js.map +1 -1
- package/dist/ui/charts/sparkline.js +2 -2
- package/dist/ui/charts/sparkline.js.map +1 -1
- package/dist/ui/chat/date-separator.d.ts +2 -1
- package/dist/ui/chat/date-separator.d.ts.map +1 -1
- package/dist/ui/chat/message.d.ts +8 -8
- package/dist/ui/chat/message.d.ts.map +1 -1
- package/dist/ui/chat/typing-indicator.d.ts +2 -1
- package/dist/ui/chat/typing-indicator.d.ts.map +1 -1
- package/dist/ui/chat/unread-separator.d.ts +2 -1
- package/dist/ui/chat/unread-separator.d.ts.map +1 -1
- package/dist/ui/checkbox.js.map +1 -1
- package/dist/ui/code.js.map +1 -1
- package/dist/ui/collapsible.js.map +1 -1
- package/dist/ui/color-input.js.map +1 -1
- package/dist/ui/color-swatch.js.map +1 -1
- package/dist/ui/combobox.js +15 -6
- package/dist/ui/combobox.js.map +1 -1
- package/dist/ui/container.js.map +1 -1
- package/dist/ui/context-menu.d.ts +2 -2
- package/dist/ui/context-menu.js.map +1 -1
- package/dist/ui/data-table-body.d.ts +1 -1
- package/dist/ui/data-table-body.d.ts.map +1 -1
- package/dist/ui/data-table-body.js +1 -1
- package/dist/ui/data-table-body.js.map +1 -1
- package/dist/ui/data-table-bulk-actions.d.ts +2 -1
- package/dist/ui/data-table-bulk-actions.d.ts.map +1 -1
- package/dist/ui/data-table-bulk-actions.js.map +1 -1
- package/dist/ui/data-table-card.d.ts +1 -1
- package/dist/ui/data-table-card.d.ts.map +1 -1
- package/dist/ui/data-table-card.js.map +1 -1
- package/dist/ui/data-table-context.d.ts +1 -1
- package/dist/ui/data-table-context.d.ts.map +1 -1
- package/dist/ui/data-table-context.js.map +1 -1
- package/dist/ui/data-table-header.d.ts +2 -1
- package/dist/ui/data-table-header.d.ts.map +1 -1
- package/dist/ui/data-table-header.js.map +1 -1
- package/dist/ui/data-table-pagination.d.ts +2 -1
- package/dist/ui/data-table-pagination.d.ts.map +1 -1
- package/dist/ui/data-table-pagination.js.map +1 -1
- package/dist/ui/data-table-toolbar.d.ts +1 -1
- package/dist/ui/data-table-toolbar.d.ts.map +1 -1
- package/dist/ui/data-table-toolbar.js.map +1 -1
- package/dist/ui/data-table.d.ts +1 -1
- package/dist/ui/data-table.d.ts.map +1 -1
- package/dist/ui/data-table.js.map +1 -1
- package/dist/ui/devalok-grain.d.ts +2 -1
- package/dist/ui/devalok-grain.d.ts.map +1 -1
- package/dist/ui/devalok-grain.js.map +1 -1
- package/dist/ui/dialog.js.map +1 -1
- package/dist/ui/dot.js +1 -1
- package/dist/ui/dot.js.map +1 -1
- package/dist/ui/dropdown-menu.d.ts +1 -1
- package/dist/ui/dropdown-menu.js.map +1 -1
- package/dist/ui/file-upload.js.map +1 -1
- package/dist/ui/form.js.map +1 -1
- package/dist/ui/hover-card.js.map +1 -1
- package/dist/ui/icon-button.js.map +1 -1
- package/dist/ui/icon-context.d.ts +1 -1
- package/dist/ui/icon-context.d.ts.map +1 -1
- package/dist/ui/icon-context.js.map +1 -1
- package/dist/ui/icon-group.js.map +1 -1
- package/dist/ui/icon.js.map +1 -1
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/input-otp.js.map +1 -1
- package/dist/ui/input.js.map +1 -1
- package/dist/ui/label.js.map +1 -1
- package/dist/ui/lib/bottom-sheet.d.ts +1 -1
- package/dist/ui/lib/bottom-sheet.d.ts.map +1 -1
- package/dist/ui/lib/date-utils.js.map +1 -1
- package/dist/ui/lib/link-context.d.ts +1 -1
- package/dist/ui/lib/link-context.d.ts.map +1 -1
- package/dist/ui/lib/motion.js.map +1 -1
- package/dist/ui/lib/utils.js.map +1 -1
- package/dist/ui/link.js.map +1 -1
- package/dist/ui/menubar.d.ts +1 -1
- package/dist/ui/menubar.js.map +1 -1
- package/dist/ui/navigation-menu.js +14 -9
- package/dist/ui/navigation-menu.js.map +1 -1
- package/dist/ui/number-input.js.map +1 -1
- package/dist/ui/pagination.js.map +1 -1
- package/dist/ui/progress-ring.js.map +1 -1
- package/dist/ui/progress.js.map +1 -1
- package/dist/ui/radio.js.map +1 -1
- package/dist/ui/search-input.js.map +1 -1
- package/dist/ui/segmented-control.js.map +1 -1
- package/dist/ui/select.js.map +1 -1
- package/dist/ui/separator.js.map +1 -1
- package/dist/ui/sheet.js.map +1 -1
- package/dist/ui/sidebar.js +1 -1
- package/dist/ui/sidebar.js.map +1 -1
- package/dist/ui/skeleton.js.map +1 -1
- package/dist/ui/slider.js.map +1 -1
- package/dist/ui/spinner.js.map +1 -1
- package/dist/ui/split-button.js.map +1 -1
- package/dist/ui/stack.js.map +1 -1
- package/dist/ui/stat-card.js.map +1 -1
- package/dist/ui/stat-flash.d.ts +2 -1
- package/dist/ui/stat-flash.d.ts.map +1 -1
- package/dist/ui/stat-flash.js +1 -1
- package/dist/ui/stat-flash.js.map +1 -1
- package/dist/ui/stepper.d.ts +1 -1
- package/dist/ui/stepper.d.ts.map +1 -1
- package/dist/ui/stepper.js +1 -1
- package/dist/ui/stepper.js.map +1 -1
- package/dist/ui/surface.js.map +1 -1
- package/dist/ui/switch.d.ts +1 -1
- package/dist/ui/switch.js.map +1 -1
- package/dist/ui/table-row-link.js.map +1 -1
- package/dist/ui/table.js.map +1 -1
- package/dist/ui/tabs.js.map +1 -1
- package/dist/ui/text.js.map +1 -1
- package/dist/ui/textarea.js.map +1 -1
- package/dist/ui/toast-registry.js.map +1 -1
- package/dist/ui/toast.d.ts +4 -4
- package/dist/ui/toast.d.ts.map +1 -1
- package/dist/ui/toast.js +5 -3
- package/dist/ui/toast.js.map +1 -1
- package/dist/ui/toaster.js.map +1 -1
- package/dist/ui/toggle-group.js.map +1 -1
- package/dist/ui/toggle.js.map +1 -1
- package/dist/ui/tooltip.js.map +1 -1
- package/dist/ui/truncated-text.js.map +1 -1
- package/dist/ui/visually-hidden.js.map +1 -1
- package/llms.txt +1 -1
- package/mcp-manifest.json +1 -1
- package/package.json +21 -21
- package/skill/SKILL.md +1 -1
- package/skill/references/components.md +1 -1
|
@@ -55,7 +55,8 @@ function G({ editor: e }) {
|
|
|
55
55
|
e.chain().focus().unsetLink().run();
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
let t = e.getAttributes("link").href || "";
|
|
59
|
+
o(t), i(!0), setTimeout(() => s.current?.focus(), 0);
|
|
59
60
|
},
|
|
60
61
|
isActive: c,
|
|
61
62
|
title: "Link",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rich-text-editor.js","names":[],"sources":["../../src/composed/rich-text-editor.tsx"],"sourcesContent":["'use client'\n\nimport {\n IconAlignCenter,\n IconAlignLeft,\n IconAlignRight,\n IconArrowBackUp,\n IconArrowForwardUp,\n IconBlockquote,\n IconBold,\n IconCode,\n IconH2,\n IconH3,\n IconHighlight,\n IconItalic,\n IconLineDashed,\n IconLink,\n IconList,\n IconListCheck,\n IconListNumbers,\n IconMoodSmile,\n IconPaperclip,\n IconPhoto,\n IconStrikethrough,\n IconUnderline,\n} from '@tabler/icons-react'\nimport Highlight from '@tiptap/extension-highlight'\nimport Image from '@tiptap/extension-image'\nimport { ListKit } from '@tiptap/extension-list'\nimport Mention from '@tiptap/extension-mention'\nimport TextAlign from '@tiptap/extension-text-align'\nimport { Placeholder } from '@tiptap/extensions'\nimport { type Editor,EditorContent, useEditor, useEditorState } from '@tiptap/react'\nimport StarterKit from '@tiptap/starter-kit'\nimport * as React from 'react'\n\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { MENTION_TOKEN_CLASS } from '../ui/lib/mention'\nimport { cn } from '../ui/lib/utils'\nimport type { EmojiData, EmojiSet } from './emoji-picker'\nimport { EmojiNode } from './extensions/emoji-node'\nimport { createEmojiSuggestion } from './extensions/emoji-suggestion'\nimport { FileAttachment } from './extensions/file-attachment'\nimport { createSuggestionRenderer } from './extensions/mention-suggestion'\n\nconst PROSE_CLASSES = [\n 'prose prose-sm max-w-none',\n 'font-body text-ds-md leading-relaxed text-surface-fg',\n '[&_h2]:text-ds-xl [&_h2]:mb-ds-03 [&_h2]:mt-ds-05 [&_h2]:text-surface-fg',\n '[&_h3]:text-ds-base [&_h3]:font-semibold [&_h3]:mb-ds-02b [&_h3]:mt-ds-04 [&_h3]:text-surface-fg',\n '[&_p]:mb-ds-02b [&_p]:text-surface-fg-muted',\n '[&_ul]:ml-ds-05 [&_ul]:list-disc [&_ol]:ml-ds-05 [&_ol]:list-decimal',\n '[&_li]:text-surface-fg-muted',\n '[&_code]:rounded [&_code]:bg-surface-raised [&_code]:px-ds-02b [&_code]:py-ds-01 [&_code]:text-ds-md [&_code]:text-accent-11',\n '[&_pre]:rounded-surface [&_pre]:bg-surface-raised [&_pre]:p-ds-04',\n '[&_strong]:font-semibold [&_strong]:text-surface-fg',\n '[&_blockquote]:border-l-[3px] [&_blockquote]:border-accent-6 [&_blockquote]:pl-ds-04 [&_blockquote]:italic [&_blockquote]:text-surface-fg-subtle',\n '[&_mark]:rounded-xs [&_mark]:bg-warning-3 [&_mark]:px-[2px]',\n '[&_ul[data-type=\"taskList\"]]:ml-0 [&_ul[data-type=\"taskList\"]]:list-none [&_li[data-type=\"taskItem\"]]:flex [&_li[data-type=\"taskItem\"]]:items-start [&_li[data-type=\"taskItem\"]]:gap-ds-02',\n '[&_hr]:my-ds-04 [&_hr]:border-surface-border-strong',\n '[&_a]:text-accent-11 [&_a]:underline [&_a]:decoration-accent-6 hover:[&_a]:decoration-accent-11',\n '[&_img]:max-w-full [&_img]:rounded-control [&_img]:my-ds-03',\n MENTION_TOKEN_CLASS,\n] as const\n\ninterface ToolbarButtonProps {\n onClick: () => void\n isActive?: boolean\n disabled?: boolean\n title: string\n children: React.ReactNode\n}\n\nfunction ToolbarButton({\n onClick,\n isActive = false,\n disabled = false,\n title,\n children,\n}: ToolbarButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n disabled={disabled}\n title={title}\n aria-label={title}\n aria-pressed={isActive}\n className={cn(\n 'inline-flex h-ds-xs-plus w-ds-xs-plus items-center justify-center rounded-control transition-colors duration-fast-01 ease-productive-standard',\n 'hover:bg-surface-raised-hover',\n 'disabled:pointer-events-none disabled:opacity-action-disabled',\n isActive\n ? 'bg-surface-raised-hover text-accent-11'\n : 'text-surface-fg-subtle',\n )}\n >\n {children}\n </button>\n )\n}\n\nfunction LinkButton({ editor }: { editor: Editor }) {\n const [showInput, setShowInput] = React.useState(false)\n const [url, setUrl] = React.useState('')\n const inputRef = React.useRef<HTMLInputElement>(null)\n const { isLink } = useEditorState({\n editor,\n selector: ({ editor: e }) => ({ isLink: e.isActive('link') }),\n })\n\n const handleToggle = () => {\n if (isLink) {\n editor.chain().focus().unsetLink().run()\n return\n }\n const previousUrl = editor.getAttributes('link').href || ''\n setUrl(previousUrl)\n setShowInput(true)\n setTimeout(() => inputRef.current?.focus(), 0)\n }\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault()\n if (url.trim()) {\n editor.chain().focus().setLink({ href: url.trim() }).run()\n }\n setShowInput(false)\n setUrl('')\n }\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Escape') {\n setShowInput(false)\n setUrl('')\n editor.commands.focus()\n }\n }\n\n return (\n <div className=\"relative\">\n <ToolbarButton onClick={handleToggle} isActive={isLink} title=\"Link\">\n <Icon icon={IconLink} size=\"sm\" />\n </ToolbarButton>\n {showInput && (\n <form\n onSubmit={handleSubmit}\n aria-label=\"Edit link URL\"\n className=\"absolute left-0 top-full z-popover mt-ds-01 flex items-center gap-ds-02 rounded-control bg-surface-overlay p-ds-02 shadow-raised-hover\"\n >\n <input\n ref={inputRef}\n type=\"url\"\n value={url}\n onChange={(e) => setUrl(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder=\"https://...\"\n className=\"h-ds-sm w-[240px] rounded-control-inner border border-surface-border-strong bg-surface-overlay px-ds-03 text-ds-sm text-surface-fg focus:border-accent-7 focus:outline-hidden\"\n />\n <Button type=\"submit\" variant=\"solid\" size=\"sm\">\n Apply\n </Button>\n </form>\n )}\n </div>\n )\n}\n\nfunction ToolbarDivider() {\n return <div className=\"mx-ds-02 h-ds-05 w-px bg-surface-border\" />\n}\n\nfunction Toolbar({ editor, toolbar, onImageClick, onFileClick, onEmojiClick }: {\n editor: Editor\n toolbar?: ToolbarItem[]\n onImageClick?: () => void\n onFileClick?: () => void\n onEmojiClick?: () => void\n}) {\n // v3: useEditorState subscribes to specific state slices so the toolbar\n // re-renders only when active-state or can-undo/redo actually changes.\n const state = useEditorState({\n editor,\n selector: ({ editor: e }) => ({\n isBold: e.isActive('bold'),\n isItalic: e.isActive('italic'),\n isUnderline: e.isActive('underline'),\n isStrike: e.isActive('strike'),\n isHighlight: e.isActive('highlight'),\n isH2: e.isActive('heading', { level: 2 }),\n isH3: e.isActive('heading', { level: 3 }),\n isBlockquote: e.isActive('blockquote'),\n isBulletList: e.isActive('bulletList'),\n isOrderedList: e.isActive('orderedList'),\n isTaskList: e.isActive('taskList'),\n isCodeBlock: e.isActive('codeBlock'),\n isLink: e.isActive('link'),\n isAlignLeft: e.isActive({ textAlign: 'left' }),\n isAlignCenter: e.isActive({ textAlign: 'center' }),\n isAlignRight: e.isActive({ textAlign: 'right' }),\n canUndo: e.can().undo(),\n canRedo: e.can().redo(),\n }),\n })\n\n const show = (item: ToolbarItem) => !toolbar || toolbar.includes(item)\n\n // Each group is an array of ToolbarItem names that belong to it.\n // A divider renders between two adjacent groups only if both have at least one visible item.\n const hasInline = show('bold') || show('italic') || show('underline') || show('strike') || show('highlight')\n const hasBlock = show('h2') || show('h3') || show('blockquote')\n const hasList = show('bulletList') || show('orderedList') || show('taskList') || show('codeBlock')\n const hasMedia = show('link') || (!!onImageClick && show('image')) || (!!onFileClick && show('file')) || show('hr')\n const hasAlign = show('alignLeft') || show('alignCenter') || show('alignRight')\n const hasEmojiGroup = !!onEmojiClick && show('emoji')\n const hasHistory = show('undo') || show('redo')\n\n return (\n <div role=\"toolbar\" aria-label=\"Text formatting\" className=\"flex flex-wrap items-center gap-ds-01 border-b border-surface-border-strong px-ds-04 py-ds-02b\">\n {/* Inline formatting */}\n {show('bold') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleBold().run()} isActive={state.isBold} title=\"Bold\">\n <Icon icon={IconBold} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n {show('italic') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleItalic().run()} isActive={state.isItalic} title=\"Italic\">\n <Icon icon={IconItalic} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n {show('underline') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleUnderline().run()} isActive={state.isUnderline} title=\"Underline\">\n <Icon icon={IconUnderline} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n {show('strike') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleStrike().run()} isActive={state.isStrike} title=\"Strikethrough\">\n <Icon icon={IconStrikethrough} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n {show('highlight') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleHighlight().run()} isActive={state.isHighlight} title=\"Highlight\">\n <Icon icon={IconHighlight} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n\n {hasInline && hasBlock && <ToolbarDivider />}\n\n {/* Block formatting */}\n {show('h2') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} isActive={state.isH2} title=\"Heading 2\">\n <Icon icon={IconH2} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n {show('h3') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} isActive={state.isH3} title=\"Heading 3\">\n <Icon icon={IconH3} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n {show('blockquote') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleBlockquote().run()} isActive={state.isBlockquote} title=\"Blockquote\">\n <Icon icon={IconBlockquote} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n\n {hasBlock && hasList && <ToolbarDivider />}\n\n {/* Lists */}\n {show('bulletList') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleBulletList().run()} isActive={state.isBulletList} title=\"Bullet list\">\n <Icon icon={IconList} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n {show('orderedList') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleOrderedList().run()} isActive={state.isOrderedList} title=\"Ordered list\">\n <Icon icon={IconListNumbers} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n {show('taskList') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleTaskList().run()} isActive={state.isTaskList} title=\"Task list\">\n <Icon icon={IconListCheck} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n {show('codeBlock') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleCodeBlock().run()} isActive={state.isCodeBlock} title=\"Code block\">\n <Icon icon={IconCode} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n\n {hasList && hasMedia && <ToolbarDivider />}\n\n {/* Media & Links */}\n {show('link') && <LinkButton editor={editor} />}\n {onImageClick && show('image') && (\n <ToolbarButton onClick={onImageClick} title=\"Insert image\">\n <Icon icon={IconPhoto} size=\"sm\" />\n </ToolbarButton>\n )}\n {onFileClick && show('file') && (\n <ToolbarButton onClick={onFileClick} title=\"Attach file\">\n <Icon icon={IconPaperclip} size=\"sm\" />\n </ToolbarButton>\n )}\n {show('hr') && (\n <ToolbarButton onClick={() => editor.chain().focus().setHorizontalRule().run()} title=\"Horizontal rule\">\n <Icon icon={IconLineDashed} size=\"sm\" />\n </ToolbarButton>\n )}\n\n {hasMedia && hasAlign && <ToolbarDivider />}\n\n {/* Alignment */}\n {show('alignLeft') && (\n <ToolbarButton onClick={() => editor.chain().focus().setTextAlign('left').run()} isActive={state.isAlignLeft} title=\"Align left\">\n <Icon icon={IconAlignLeft} size=\"sm\" />\n </ToolbarButton>\n )}\n {show('alignCenter') && (\n <ToolbarButton onClick={() => editor.chain().focus().setTextAlign('center').run()} isActive={state.isAlignCenter} title=\"Align center\">\n <Icon icon={IconAlignCenter} size=\"sm\" />\n </ToolbarButton>\n )}\n {show('alignRight') && (\n <ToolbarButton onClick={() => editor.chain().focus().setTextAlign('right').run()} isActive={state.isAlignRight} title=\"Align right\">\n <Icon icon={IconAlignRight} size=\"sm\" />\n </ToolbarButton>\n )}\n\n {hasAlign && (hasEmojiGroup || hasHistory) && <ToolbarDivider />}\n\n {/* Emoji */}\n {onEmojiClick && show('emoji') && (\n <ToolbarButton onClick={onEmojiClick} title=\"Emoji\">\n <Icon icon={IconMoodSmile} size=\"sm\" />\n </ToolbarButton>\n )}\n\n {/* History */}\n {show('undo') && (\n <ToolbarButton onClick={() => editor.chain().focus().undo().run()} disabled={!state.canUndo} title=\"Undo\">\n <Icon icon={IconArrowBackUp} size=\"sm\" />\n </ToolbarButton>\n )}\n {show('redo') && (\n <ToolbarButton onClick={() => editor.chain().focus().redo().run()} disabled={!state.canRedo} title=\"Redo\">\n <Icon icon={IconArrowForwardUp} size=\"sm\" />\n </ToolbarButton>\n )}\n </div>\n )\n}\n\nconst LazyPicker = React.lazy(() =>\n import('./emoji-picker').then((m) => ({ default: m.EmojiPicker })),\n)\n\nfunction EmojiPickerLazy({ onSelect }: { onSelect: (emoji: EmojiData) => void }) {\n const fallback = <div className=\"flex h-[435px] w-[352px] items-center justify-center rounded-surface bg-surface-overlay shadow-raised-hover\"><span className=\"text-ds-sm text-surface-fg-subtle\">Loading…</span></div>\n\n return (\n <React.Suspense fallback={fallback}>\n <LazyPicker onSelect={onSelect} />\n </React.Suspense>\n )\n}\n\nexport type ToolbarItem =\n | 'bold' | 'italic' | 'underline' | 'strike' | 'highlight'\n | 'h2' | 'h3' | 'blockquote'\n | 'bulletList' | 'orderedList' | 'taskList' | 'codeBlock'\n | 'link' | 'image' | 'file' | 'hr'\n | 'alignLeft' | 'alignCenter' | 'alignRight'\n | 'emoji' | 'undo' | 'redo'\n\nexport interface MentionItem {\n id: string\n label: string\n avatar?: string\n}\n\n/**\n * A Tiptap-powered rich text editor with a configurable toolbar, @mentions,\n * emoji suggestions, file/image uploads, and task lists.\n * Outputs sanitized HTML via `onChange`.\n */\nexport interface RichTextEditorProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'onChange' | 'content'> {\n /** Initial HTML content. Updates are NOT reactive — use `onChange` for controlled state. */\n content?: string\n placeholder?: string\n /** Called with the editor's HTML string whenever content changes. */\n onChange?: (html: string) => void\n className?: string\n /** Set to false to render in read-only mode (e.g. for previewing). */\n editable?: boolean\n /** Called when an image is pasted/dropped. Return a URL. If not provided, images inline as base64. */\n onImageUpload?: (file: File) => Promise<string>\n /** Called when a non-image file is dropped/pasted. If not provided, non-image files are ignored. */\n onFileUpload?: (file: File) => Promise<{ url: string; name: string; size: number }>\n /** Whitelist of toolbar items to display. Omit to show all. */\n toolbar?: ToolbarItem[]\n /** Static list of mentionable items (shown when user types @). */\n mentions?: MentionItem[]\n /** Async mention search. Takes precedence over static `mentions` list. */\n onMentionSearch?: (query: string) => Promise<MentionItem[]>\n /** Called when a mention is selected from the suggestion dropdown. */\n onMentionSelect?: (item: MentionItem) => void\n /** Emoji art style. @default 'native' */\n emojiSet?: EmojiSet\n}\n\nconst RichTextEditor = React.forwardRef<HTMLDivElement, RichTextEditorProps>(\n function RichTextEditor({\n content = '',\n placeholder = 'Start writing...',\n onChange,\n className,\n editable = true,\n toolbar,\n onImageUpload,\n onFileUpload,\n mentions,\n onMentionSearch,\n onMentionSelect,\n // @deprecated no-op since the frimousse migration (native-only emoji). Kept\n // out of `...props` so it never leaks onto the DOM.\n emojiSet: _emojiSet = 'native',\n ...props\n}, ref) {\n const editorRef = React.useRef<ReturnType<typeof useEditor>>(null)\n const isInternalChangeRef = React.useRef(false)\n const [showEmojiPicker, setShowEmojiPicker] = React.useState(false)\n const emojiPickerRef = React.useRef<HTMLDivElement>(null)\n const imageInputRef = React.useRef<HTMLInputElement>(null)\n const fileInputRef = React.useRef<HTMLInputElement>(null)\n\n const handleImageInsert = async (file: File) => {\n const ed = editorRef.current\n if (!ed) return\n if (onImageUpload) {\n const url = await onImageUpload(file)\n if (url && /^https?:\\/\\//i.test(url)) {\n ed.chain().focus().setImage({ src: url }).run()\n }\n } else {\n const reader = new FileReader()\n reader.onload = () => {\n ed.chain().focus().setImage({ src: reader.result as string }).run()\n }\n reader.readAsDataURL(file)\n }\n }\n\n const handleFileInsert = async (file: File) => {\n const ed = editorRef.current\n if (!ed || !onFileUpload) return\n const result = await onFileUpload(file)\n ed.chain().focus().insertContent({\n type: 'fileAttachment',\n attrs: { url: result.url, name: result.name, size: result.size },\n }).run()\n }\n\n const editor = useEditor({\n immediatelyRender: false, // SSR-safe — prevents hydration mismatch in Next.js\n extensions: [\n StarterKit.configure({\n heading: { levels: [2, 3] },\n // v3: Underline + Link are now included in StarterKit\n link: {\n openOnClick: false,\n protocols: ['http', 'https', 'mailto'],\n validate: (href: string) => /^(https?:\\/\\/|mailto:)/i.test(href),\n HTMLAttributes: {\n rel: 'noopener noreferrer',\n target: '_blank',\n },\n },\n }),\n Placeholder.configure({\n placeholder,\n emptyEditorClass:\n 'before:content-[attr(data-placeholder)] before:text-surface-fg-subtle before:float-left before:h-0 before:pointer-events-none',\n }),\n Highlight.configure({ multicolor: false }),\n ListKit.configure({\n bulletList: false, // Already in StarterKit\n orderedList: false, // Already in StarterKit\n listItem: false, // Already in StarterKit\n listKeymap: false, // Already in StarterKit\n taskList: {},\n taskItem: { nested: true },\n }),\n TextAlign.configure({ types: ['heading', 'paragraph'] }),\n Image.configure({\n allowBase64: true,\n HTMLAttributes: {\n class: 'max-w-full rounded-control',\n },\n }),\n FileAttachment,\n ...(mentions || onMentionSearch ? [\n Mention.configure({\n HTMLAttributes: { class: 'mention' },\n suggestion: {\n items: async ({ query }: { query: string }) => {\n if (onMentionSearch) return await onMentionSearch(query)\n if (mentions) return mentions.filter(m => m.label.toLowerCase().includes(query.toLowerCase())).slice(0, 8)\n return []\n },\n render: createSuggestionRenderer(onMentionSelect),\n },\n }),\n ] : []),\n EmojiNode,\n createEmojiSuggestion(),\n ],\n content,\n editable,\n editorProps: {\n handleDrop: (_view, event, _slice, moved) => {\n if (moved || !event.dataTransfer?.files.length) return false\n const file = event.dataTransfer.files[0]\n if (!file) return false\n if (file.type.startsWith('image/')) {\n handleImageInsert(file)\n return true\n }\n if (onFileUpload) {\n handleFileInsert(file)\n return true\n }\n return false\n },\n handlePaste: (_view, event) => {\n const file = event.clipboardData?.files[0]\n if (!file) return false\n if (file.type.startsWith('image/')) {\n handleImageInsert(file)\n return true\n }\n if (onFileUpload) {\n handleFileInsert(file)\n return true\n }\n return false\n },\n attributes: {\n class: cn(\n ...PROSE_CLASSES,\n 'focus:outline-hidden',\n 'min-h-[120px] px-ds-04 py-ds-04',\n ),\n },\n },\n onUpdate: ({ editor: ed }) => {\n isInternalChangeRef.current = true\n onChange?.(ed.getHTML())\n queueMicrotask(() => {\n isInternalChangeRef.current = false\n })\n },\n })\n\n React.useEffect(() => {\n editorRef.current = editor\n }, [editor])\n\n // Close emoji picker on click outside\n React.useEffect(() => {\n if (!showEmojiPicker) return\n const handleClickOutside = (e: MouseEvent) => {\n if (emojiPickerRef.current && !emojiPickerRef.current.contains(e.target as Node)) {\n setShowEmojiPicker(false)\n }\n }\n document.addEventListener('mousedown', handleClickOutside)\n return () => document.removeEventListener('mousedown', handleClickOutside)\n }, [showEmojiPicker])\n\n React.useEffect(() => {\n if (isInternalChangeRef.current) return\n if (editor && content !== editor.getHTML()) {\n editor.commands.setContent(content, { emitUpdate: false })\n }\n }, [editor, content])\n\n if (!editor) return null\n\n return (\n <div ref={ref} {...props} className={cn('relative', className)}>\n {/* Emoji picker rendered outside the overflow-hidden box so it isn't clipped */}\n {showEmojiPicker && (\n <div\n ref={emojiPickerRef}\n className=\"absolute bottom-full right-0 z-popover mb-ds-02\"\n onKeyDown={(e) => {\n if (e.key === 'Escape') {\n e.stopPropagation()\n setShowEmojiPicker(false)\n }\n }}\n >\n <EmojiPickerLazy\n onSelect={({ id, native }) => {\n editor.chain().focus().insertContent({\n type: 'emojiNode',\n attrs: { id, native },\n }).run()\n setShowEmojiPicker(false)\n }}\n />\n </div>\n )}\n <div\n className={cn(\n 'overflow-hidden rounded-surface border border-surface-border-strong bg-surface-raised',\n 'transition-colors ease-productive-standard focus-within:border-surface-border-strong',\n )}\n >\n <input\n ref={imageInputRef}\n type=\"file\"\n accept=\"image/*\"\n aria-label=\"Upload image\"\n className=\"hidden\"\n onChange={(e) => {\n const file = e.target.files?.[0]\n if (file) handleImageInsert(file)\n e.target.value = ''\n }}\n />\n {onFileUpload && (\n <input\n ref={fileInputRef}\n type=\"file\"\n aria-label=\"Upload file\"\n className=\"hidden\"\n onChange={(e) => {\n const file = e.target.files?.[0]\n if (file) handleFileInsert(file)\n e.target.value = ''\n }}\n />\n )}\n {editable && (\n <Toolbar\n editor={editor}\n toolbar={toolbar}\n onImageClick={() => imageInputRef.current?.click()}\n onFileClick={onFileUpload ? () => fileInputRef.current?.click() : undefined}\n onEmojiClick={() => setShowEmojiPicker((prev) => !prev)}\n />\n )}\n <EditorContent editor={editor} />\n </div>\n </div>\n )\n},\n)\n\nRichTextEditor.displayName = 'RichTextEditor'\n\nexport interface RichTextViewerProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'content'> {\n content: string\n className?: string\n}\n\nconst RichTextViewer = React.forwardRef<HTMLDivElement, RichTextViewerProps>(\n function RichTextViewer({ content, className, ...props }, ref) {\n const editor = useEditor({\n immediatelyRender: false, // SSR-safe — prevents hydration mismatch in Next.js\n extensions: [\n StarterKit.configure({\n heading: { levels: [2, 3] },\n link: {\n openOnClick: true,\n protocols: ['http', 'https', 'mailto'],\n validate: (href: string) => /^(https?:\\/\\/|mailto:)/i.test(href),\n HTMLAttributes: {\n rel: 'noopener noreferrer',\n target: '_blank',\n },\n },\n }),\n Highlight.configure({ multicolor: false }),\n ListKit.configure({\n bulletList: false,\n orderedList: false,\n listItem: false,\n listKeymap: false,\n taskList: {},\n taskItem: { nested: true },\n }),\n TextAlign.configure({ types: ['heading', 'paragraph'] }),\n Image.configure({\n allowBase64: true,\n HTMLAttributes: {\n class: 'max-w-full rounded-control',\n },\n }),\n FileAttachment,\n Mention.configure({\n HTMLAttributes: { class: 'mention' },\n }),\n ],\n content,\n editable: false,\n editorProps: {\n attributes: {\n class: cn(...PROSE_CLASSES),\n },\n },\n })\n\n if (!editor) return null\n\n return (\n <div ref={ref} {...props} className={className}>\n <EditorContent editor={editor} />\n </div>\n )\n},\n)\n\nRichTextViewer.displayName = 'RichTextViewer'\n\nexport { RichTextEditor, RichTextViewer }\n"],"mappings":";;;;;;;;;;;;;AA8CA,IAAM,IAAgB;CACpB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAUD,SAAS,EAAc,EACrB,YACA,cAAW,IACX,cAAW,IACX,UACA,eACqB;AACrB,QACE,kBAAC,UAAD;EACE,MAAK;EACI;EACC;EACH;EACP,cAAY;EACZ,gBAAc;EACd,WAAW,EACT,iJACA,iCACA,iEACA,IACI,2CACA,yBACL;EAEA;EACM,CAAA;;AAIb,SAAS,EAAW,EAAE,aAA8B;CAClD,IAAM,CAAC,GAAW,KAAgB,EAAM,SAAS,GAAM,EACjD,CAAC,GAAK,KAAU,EAAM,SAAS,GAAG,EAClC,IAAW,EAAM,OAAyB,KAAK,EAC/C,EAAE,cAAW,EAAe;EAChC;EACA,WAAW,EAAE,QAAQ,SAAS,EAAE,QAAQ,EAAE,SAAS,OAAO,EAAE;EAC7D,CAAC;AA8BF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD;GAAe,eA9BQ;AACzB,QAAI,GAAQ;AACV,OAAO,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK;AACxC;;AAKF,IAFA,EADoB,EAAO,cAAc,OAAO,CAAC,QAAQ,GACtC,EACnB,EAAa,GAAK,EAClB,iBAAiB,EAAS,SAAS,OAAO,EAAE,EAAE;;GAsBN,UAAU;GAAQ,OAAM;aAC5D,kBAAC,GAAD;IAAM,MAAM;IAAU,MAAK;IAAO,CAAA;GACpB,CAAA,EACf,KACC,kBAAC,QAAD;GACE,WAxBc,MAAuB;AAM3C,IALA,EAAE,gBAAgB,EACd,EAAI,MAAM,IACZ,EAAO,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAI,MAAM,EAAE,CAAC,CAAC,KAAK,EAE5D,EAAa,GAAM,EACnB,EAAO,GAAG;;GAmBJ,cAAW;GACX,WAAU;aAHZ,CAKE,kBAAC,SAAD;IACE,KAAK;IACL,MAAK;IACL,OAAO;IACP,WAAW,MAAM,EAAO,EAAE,OAAO,MAAM;IACvC,YAxBa,MAA2B;AAChD,KAAI,EAAE,QAAQ,aACZ,EAAa,GAAM,EACnB,EAAO,GAAG,EACV,EAAO,SAAS,OAAO;;IAqBjB,aAAY;IACZ,WAAU;IACV,CAAA,EACF,kBAAC,GAAD;IAAQ,MAAK;IAAS,SAAQ;IAAQ,MAAK;cAAK;IAEvC,CAAA,CACJ;KAEL;;;AAIV,SAAS,IAAiB;AACxB,QAAO,kBAAC,OAAD,EAAK,WAAU,2CAA4C,CAAA;;AAGpE,SAAS,EAAQ,EAAE,WAAQ,YAAS,iBAAc,gBAAa,mBAM5D;CAGD,IAAM,IAAQ,EAAe;EAC3B;EACA,WAAW,EAAE,QAAQ,SAAS;GAC5B,QAAQ,EAAE,SAAS,OAAO;GAC1B,UAAU,EAAE,SAAS,SAAS;GAC9B,aAAa,EAAE,SAAS,YAAY;GACpC,UAAU,EAAE,SAAS,SAAS;GAC9B,aAAa,EAAE,SAAS,YAAY;GACpC,MAAM,EAAE,SAAS,WAAW,EAAE,OAAO,GAAG,CAAC;GACzC,MAAM,EAAE,SAAS,WAAW,EAAE,OAAO,GAAG,CAAC;GACzC,cAAc,EAAE,SAAS,aAAa;GACtC,cAAc,EAAE,SAAS,aAAa;GACtC,eAAe,EAAE,SAAS,cAAc;GACxC,YAAY,EAAE,SAAS,WAAW;GAClC,aAAa,EAAE,SAAS,YAAY;GACpC,QAAQ,EAAE,SAAS,OAAO;GAC1B,aAAa,EAAE,SAAS,EAAE,WAAW,QAAQ,CAAC;GAC9C,eAAe,EAAE,SAAS,EAAE,WAAW,UAAU,CAAC;GAClD,cAAc,EAAE,SAAS,EAAE,WAAW,SAAS,CAAC;GAChD,SAAS,EAAE,KAAK,CAAC,MAAM;GACvB,SAAS,EAAE,KAAK,CAAC,MAAM;GACxB;EACF,CAAC,EAEI,KAAQ,MAAsB,CAAC,KAAW,EAAQ,SAAS,EAAK,EAIhE,IAAY,EAAK,OAAO,IAAI,EAAK,SAAS,IAAI,EAAK,YAAY,IAAI,EAAK,SAAS,IAAI,EAAK,YAAY,EACtG,IAAW,EAAK,KAAK,IAAI,EAAK,KAAK,IAAI,EAAK,aAAa,EACzD,IAAU,EAAK,aAAa,IAAI,EAAK,cAAc,IAAI,EAAK,WAAW,IAAI,EAAK,YAAY,EAC5F,IAAW,EAAK,OAAO,IAAK,CAAC,CAAC,KAAgB,EAAK,QAAQ,IAAM,CAAC,CAAC,KAAe,EAAK,OAAO,IAAK,EAAK,KAAK,EAC7G,IAAW,EAAK,YAAY,IAAI,EAAK,cAAc,IAAI,EAAK,aAAa,EACzE,IAAgB,CAAC,CAAC,KAAgB,EAAK,QAAQ,EAC/C,IAAa,EAAK,OAAO,IAAI,EAAK,OAAO;AAE/C,QACE,kBAAC,OAAD;EAAK,MAAK;EAAU,cAAW;EAAkB,WAAU;YAA3D;GAEG,EAAK,OAAO,IACX,kBAAC,GAAD;IAAe,eAAe,EAAO,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK;IAAE,UAAU,EAAM;IAAQ,OAAM;cACrG,kBAAC,GAAD;KAAM,MAAM;KAAU,MAAK;KAAK,QAAO;KAAS,CAAA;IAClC,CAAA;GAEjB,EAAK,SAAS,IACb,kBAAC,GAAD;IAAe,eAAe,EAAO,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,KAAK;IAAE,UAAU,EAAM;IAAU,OAAM;cACzG,kBAAC,GAAD;KAAM,MAAM;KAAY,MAAK;KAAK,QAAO;KAAS,CAAA;IACpC,CAAA;GAEjB,EAAK,YAAY,IAChB,kBAAC,GAAD;IAAe,eAAe,EAAO,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,KAAK;IAAE,UAAU,EAAM;IAAa,OAAM;cAC/G,kBAAC,GAAD;KAAM,MAAM;KAAe,MAAK;KAAK,QAAO;KAAS,CAAA;IACvC,CAAA;GAEjB,EAAK,SAAS,IACb,kBAAC,GAAD;IAAe,eAAe,EAAO,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,KAAK;IAAE,UAAU,EAAM;IAAU,OAAM;cACzG,kBAAC,GAAD;KAAM,MAAM;KAAmB,MAAK;KAAK,QAAO;KAAS,CAAA;IAC3C,CAAA;GAEjB,EAAK,YAAY,IAChB,kBAAC,GAAD;IAAe,eAAe,EAAO,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,KAAK;IAAE,UAAU,EAAM;IAAa,OAAM;cAC/G,kBAAC,GAAD;KAAM,MAAM;KAAe,MAAK;KAAK,QAAO;KAAS,CAAA;IACvC,CAAA;GAGjB,KAAa,KAAY,kBAAC,GAAD,EAAkB,CAAA;GAG3C,EAAK,KAAK,IACT,kBAAC,GAAD;IAAe,eAAe,EAAO,OAAO,CAAC,OAAO,CAAC,cAAc,EAAE,OAAO,GAAG,CAAC,CAAC,KAAK;IAAE,UAAU,EAAM;IAAM,OAAM;cAClH,kBAAC,GAAD;KAAM,MAAM;KAAQ,MAAK;KAAK,QAAO;KAAS,CAAA;IAChC,CAAA;GAEjB,EAAK,KAAK,IACT,kBAAC,GAAD;IAAe,eAAe,EAAO,OAAO,CAAC,OAAO,CAAC,cAAc,EAAE,OAAO,GAAG,CAAC,CAAC,KAAK;IAAE,UAAU,EAAM;IAAM,OAAM;cAClH,kBAAC,GAAD;KAAM,MAAM;KAAQ,MAAK;KAAK,QAAO;KAAS,CAAA;IAChC,CAAA;GAEjB,EAAK,aAAa,IACjB,kBAAC,GAAD;IAAe,eAAe,EAAO,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,KAAK;IAAE,UAAU,EAAM;IAAc,OAAM;cACjH,kBAAC,GAAD;KAAM,MAAM;KAAgB,MAAK;KAAK,QAAO;KAAS,CAAA;IACxC,CAAA;GAGjB,KAAY,KAAW,kBAAC,GAAD,EAAkB,CAAA;GAGzC,EAAK,aAAa,IACjB,kBAAC,GAAD;IAAe,eAAe,EAAO,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,KAAK;IAAE,UAAU,EAAM;IAAc,OAAM;cACjH,kBAAC,GAAD;KAAM,MAAM;KAAU,MAAK;KAAK,QAAO;KAAS,CAAA;IAClC,CAAA;GAEjB,EAAK,cAAc,IAClB,kBAAC,GAAD;IAAe,eAAe,EAAO,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,KAAK;IAAE,UAAU,EAAM;IAAe,OAAM;cACnH,kBAAC,GAAD;KAAM,MAAM;KAAiB,MAAK;KAAK,QAAO;KAAS,CAAA;IACzC,CAAA;GAEjB,EAAK,WAAW,IACf,kBAAC,GAAD;IAAe,eAAe,EAAO,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,KAAK;IAAE,UAAU,EAAM;IAAY,OAAM;cAC7G,kBAAC,GAAD;KAAM,MAAM;KAAe,MAAK;KAAK,QAAO;KAAS,CAAA;IACvC,CAAA;GAEjB,EAAK,YAAY,IAChB,kBAAC,GAAD;IAAe,eAAe,EAAO,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,KAAK;IAAE,UAAU,EAAM;IAAa,OAAM;cAC/G,kBAAC,GAAD;KAAM,MAAM;KAAU,MAAK;KAAK,QAAO;KAAS,CAAA;IAClC,CAAA;GAGjB,KAAW,KAAY,kBAAC,GAAD,EAAkB,CAAA;GAGzC,EAAK,OAAO,IAAI,kBAAC,GAAD,EAAoB,WAAU,CAAA;GAC9C,KAAgB,EAAK,QAAQ,IAC5B,kBAAC,GAAD;IAAe,SAAS;IAAc,OAAM;cAC1C,kBAAC,GAAD;KAAM,MAAM;KAAW,MAAK;KAAO,CAAA;IACrB,CAAA;GAEjB,KAAe,EAAK,OAAO,IAC1B,kBAAC,GAAD;IAAe,SAAS;IAAa,OAAM;cACzC,kBAAC,GAAD;KAAM,MAAM;KAAe,MAAK;KAAO,CAAA;IACzB,CAAA;GAEjB,EAAK,KAAK,IACT,kBAAC,GAAD;IAAe,eAAe,EAAO,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,KAAK;IAAE,OAAM;cACpF,kBAAC,GAAD;KAAM,MAAM;KAAgB,MAAK;KAAO,CAAA;IAC1B,CAAA;GAGjB,KAAY,KAAY,kBAAC,GAAD,EAAkB,CAAA;GAG1C,EAAK,YAAY,IAChB,kBAAC,GAAD;IAAe,eAAe,EAAO,OAAO,CAAC,OAAO,CAAC,aAAa,OAAO,CAAC,KAAK;IAAE,UAAU,EAAM;IAAa,OAAM;cAClH,kBAAC,GAAD;KAAM,MAAM;KAAe,MAAK;KAAO,CAAA;IACzB,CAAA;GAEjB,EAAK,cAAc,IAClB,kBAAC,GAAD;IAAe,eAAe,EAAO,OAAO,CAAC,OAAO,CAAC,aAAa,SAAS,CAAC,KAAK;IAAE,UAAU,EAAM;IAAe,OAAM;cACtH,kBAAC,GAAD;KAAM,MAAM;KAAiB,MAAK;KAAO,CAAA;IAC3B,CAAA;GAEjB,EAAK,aAAa,IACjB,kBAAC,GAAD;IAAe,eAAe,EAAO,OAAO,CAAC,OAAO,CAAC,aAAa,QAAQ,CAAC,KAAK;IAAE,UAAU,EAAM;IAAc,OAAM;cACpH,kBAAC,GAAD;KAAM,MAAM;KAAgB,MAAK;KAAO,CAAA;IAC1B,CAAA;GAGjB,MAAa,KAAiB,MAAe,kBAAC,GAAD,EAAkB,CAAA;GAG/D,KAAgB,EAAK,QAAQ,IAC5B,kBAAC,GAAD;IAAe,SAAS;IAAc,OAAM;cAC1C,kBAAC,GAAD;KAAM,MAAM;KAAe,MAAK;KAAO,CAAA;IACzB,CAAA;GAIjB,EAAK,OAAO,IACX,kBAAC,GAAD;IAAe,eAAe,EAAO,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK;IAAE,UAAU,CAAC,EAAM;IAAS,OAAM;cACjG,kBAAC,GAAD;KAAM,MAAM;KAAiB,MAAK;KAAO,CAAA;IAC3B,CAAA;GAEjB,EAAK,OAAO,IACX,kBAAC,GAAD;IAAe,eAAe,EAAO,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK;IAAE,UAAU,CAAC,EAAM;IAAS,OAAM;cACjG,kBAAC,GAAD;KAAM,MAAM;KAAoB,MAAK;KAAO,CAAA;IAC9B,CAAA;GAEd;;;AAIV,IAAM,IAAa,EAAM,WACvB,OAAO,qBAAkB,MAAM,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,CACnE;AAED,SAAS,EAAgB,EAAE,eAAsD;CAC/E,IAAM,IAAW,kBAAC,OAAD;EAAK,WAAU;YAA8G,kBAAC,QAAD;GAAM,WAAU;aAAoC;GAAe,CAAA;EAAM,CAAA;AAEvN,QACE,kBAAC,EAAM,UAAP;EAA0B;YACxB,kBAAC,GAAD,EAAsB,aAAY,CAAA;EACnB,CAAA;;AAgDrB,IAAM,IAAiB,EAAM,WAC3B,SAAwB,EACxB,aAAU,IACV,iBAAc,oBACd,aACA,cACA,cAAW,IACX,YACA,kBACA,iBACA,aACA,oBACA,oBAGA,UAAU,IAAY,UACtB,GAAG,KACF,GAAK;CACN,IAAM,IAAY,EAAM,OAAqC,KAAK,EAC5D,IAAsB,EAAM,OAAO,GAAM,EACzC,CAAC,GAAiB,KAAsB,EAAM,SAAS,GAAM,EAC7D,IAAiB,EAAM,OAAuB,KAAK,EACnD,IAAgB,EAAM,OAAyB,KAAK,EACpD,IAAe,EAAM,OAAyB,KAAK,EAEnD,IAAoB,OAAO,MAAe;EAC9C,IAAM,IAAK,EAAU;AAChB,QACL,KAAI,GAAe;GACjB,IAAM,IAAM,MAAM,EAAc,EAAK;AACrC,GAAI,KAAO,gBAAgB,KAAK,EAAI,IAClC,EAAG,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,GAAK,CAAC,CAAC,KAAK;SAE5C;GACL,IAAM,IAAS,IAAI,YAAY;AAI/B,GAHA,EAAO,eAAe;AACpB,MAAG,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,EAAO,QAAkB,CAAC,CAAC,KAAK;MAErE,EAAO,cAAc,EAAK;;IAIxB,IAAmB,OAAO,MAAe;EAC7C,IAAM,IAAK,EAAU;AACrB,MAAI,CAAC,KAAM,CAAC,EAAc;EAC1B,IAAM,IAAS,MAAM,EAAa,EAAK;AACvC,IAAG,OAAO,CAAC,OAAO,CAAC,cAAc;GAC/B,MAAM;GACN,OAAO;IAAE,KAAK,EAAO;IAAK,MAAM,EAAO;IAAM,MAAM,EAAO;IAAM;GACjE,CAAC,CAAC,KAAK;IAGJ,IAAS,EAAU;EACvB,mBAAmB;EACnB,YAAY;GACV,EAAW,UAAU;IACnB,SAAS,EAAE,QAAQ,CAAC,GAAG,EAAE,EAAE;IAE3B,MAAM;KACJ,aAAa;KACb,WAAW;MAAC;MAAQ;MAAS;MAAS;KACtC,WAAW,MAAiB,0BAA0B,KAAK,EAAK;KAChE,gBAAgB;MACd,KAAK;MACL,QAAQ;MACT;KACF;IACF,CAAC;GACF,EAAY,UAAU;IACpB;IACA,kBACE;IACH,CAAC;GACF,EAAU,UAAU,EAAE,YAAY,IAAO,CAAC;GAC1C,EAAQ,UAAU;IAChB,YAAY;IACZ,aAAa;IACb,UAAU;IACV,YAAY;IACZ,UAAU,EAAE;IACZ,UAAU,EAAE,QAAQ,IAAM;IAC3B,CAAC;GACF,EAAU,UAAU,EAAE,OAAO,CAAC,WAAW,YAAY,EAAE,CAAC;GACxD,EAAM,UAAU;IACd,aAAa;IACb,gBAAgB,EACd,OAAO,8BACR;IACF,CAAC;GACF;GACA,GAAI,KAAY,IAAkB,CAChC,EAAQ,UAAU;IAChB,gBAAgB,EAAE,OAAO,WAAW;IACpC,YAAY;KACV,OAAO,OAAO,EAAE,eACV,IAAwB,MAAM,EAAgB,EAAM,GACpD,IAAiB,EAAS,QAAO,MAAK,EAAE,MAAM,aAAa,CAAC,SAAS,EAAM,aAAa,CAAC,CAAC,CAAC,MAAM,GAAG,EAAE,GACnG,EAAE;KAEX,QAAQ,EAAyB,EAAgB;KAClD;IACF,CAAC,CACH,GAAG,EAAE;GACN;GACA,GAAuB;GACxB;EACD;EACA;EACA,aAAa;GACX,aAAa,GAAO,GAAO,GAAQ,MAAU;AAC3C,QAAI,KAAS,CAAC,EAAM,cAAc,MAAM,OAAQ,QAAO;IACvD,IAAM,IAAO,EAAM,aAAa,MAAM;AAUtC,WATK,IACD,EAAK,KAAK,WAAW,SAAS,IAChC,EAAkB,EAAK,EAChB,MAEL,KACF,EAAiB,EAAK,EACf,MAEF,KATW;;GAWpB,cAAc,GAAO,MAAU;IAC7B,IAAM,IAAO,EAAM,eAAe,MAAM;AAUxC,WATK,IACD,EAAK,KAAK,WAAW,SAAS,IAChC,EAAkB,EAAK,EAChB,MAEL,KACF,EAAiB,EAAK,EACf,MAEF,KATW;;GAWpB,YAAY,EACV,OAAO,EACL,GAAG,GACH,wBACA,kCACD,EACF;GACF;EACD,WAAW,EAAE,QAAQ,QAAS;AAG5B,GAFA,EAAoB,UAAU,IAC9B,IAAW,EAAG,SAAS,CAAC,EACxB,qBAAqB;AACnB,MAAoB,UAAU;KAC9B;;EAEL,CAAC;AA2BF,QAzBA,EAAM,gBAAgB;AACpB,IAAU,UAAU;IACnB,CAAC,EAAO,CAAC,EAGZ,EAAM,gBAAgB;AACpB,MAAI,CAAC,EAAiB;EACtB,IAAM,KAAsB,MAAkB;AAC5C,GAAI,EAAe,WAAW,CAAC,EAAe,QAAQ,SAAS,EAAE,OAAe,IAC9E,EAAmB,GAAM;;AAI7B,SADA,SAAS,iBAAiB,aAAa,EAAmB,QAC7C,SAAS,oBAAoB,aAAa,EAAmB;IACzE,CAAC,EAAgB,CAAC,EAErB,EAAM,gBAAgB;AAChB,IAAoB,WACpB,KAAU,MAAY,EAAO,SAAS,IACxC,EAAO,SAAS,WAAW,GAAS,EAAE,YAAY,IAAO,CAAC;IAE3D,CAAC,GAAQ,EAAQ,CAAC,EAEhB,IAGH,kBAAC,OAAD;EAAU;EAAK,GAAI;EAAO,WAAW,EAAG,YAAY,EAAU;YAA9D,CAEG,KACC,kBAAC,OAAD;GACE,KAAK;GACL,WAAU;GACV,YAAY,MAAM;AAChB,IAAI,EAAE,QAAQ,aACZ,EAAE,iBAAiB,EACnB,EAAmB,GAAM;;aAI7B,kBAAC,GAAD,EACE,WAAW,EAAE,OAAI,gBAAa;AAK5B,IAJA,EAAO,OAAO,CAAC,OAAO,CAAC,cAAc;KACnC,MAAM;KACN,OAAO;MAAE;MAAI;MAAQ;KACtB,CAAC,CAAC,KAAK,EACR,EAAmB,GAAM;MAE3B,CAAA;GACE,CAAA,EAER,kBAAC,OAAD;GACE,WAAW,EACT,yFACA,uFACD;aAJH;IAME,kBAAC,SAAD;KACE,KAAK;KACL,MAAK;KACL,QAAO;KACP,cAAW;KACX,WAAU;KACV,WAAW,MAAM;MACf,IAAM,IAAO,EAAE,OAAO,QAAQ;AAE9B,MADI,KAAM,EAAkB,EAAK,EACjC,EAAE,OAAO,QAAQ;;KAEnB,CAAA;IACD,KACC,kBAAC,SAAD;KACE,KAAK;KACL,MAAK;KACL,cAAW;KACX,WAAU;KACV,WAAW,MAAM;MACf,IAAM,IAAO,EAAE,OAAO,QAAQ;AAE9B,MADI,KAAM,EAAiB,EAAK,EAChC,EAAE,OAAO,QAAQ;;KAEnB,CAAA;IAEH,KACC,kBAAC,GAAD;KACU;KACC;KACT,oBAAoB,EAAc,SAAS,OAAO;KAClD,aAAa,UAAqB,EAAa,SAAS,OAAO,GAAG,KAAA;KAClE,oBAAoB,GAAoB,MAAS,CAAC,EAAK;KACvD,CAAA;IAEJ,kBAAC,GAAD,EAAuB,WAAU,CAAA;IAC7B;KACF;MArEY;EAwErB;AAED,EAAe,cAAc;AAO7B,IAAM,IAAiB,EAAM,WAC3B,SAAwB,EAAE,YAAS,cAAW,GAAG,KAAS,GAAK;CAC/D,IAAM,IAAS,EAAU;EACvB,mBAAmB;EACnB,YAAY;GACV,EAAW,UAAU;IACnB,SAAS,EAAE,QAAQ,CAAC,GAAG,EAAE,EAAE;IAC3B,MAAM;KACJ,aAAa;KACb,WAAW;MAAC;MAAQ;MAAS;MAAS;KACtC,WAAW,MAAiB,0BAA0B,KAAK,EAAK;KAChE,gBAAgB;MACd,KAAK;MACL,QAAQ;MACT;KACF;IACF,CAAC;GACF,EAAU,UAAU,EAAE,YAAY,IAAO,CAAC;GAC1C,EAAQ,UAAU;IAChB,YAAY;IACZ,aAAa;IACb,UAAU;IACV,YAAY;IACZ,UAAU,EAAE;IACZ,UAAU,EAAE,QAAQ,IAAM;IAC3B,CAAC;GACF,EAAU,UAAU,EAAE,OAAO,CAAC,WAAW,YAAY,EAAE,CAAC;GACxD,EAAM,UAAU;IACd,aAAa;IACb,gBAAgB,EACd,OAAO,8BACR;IACF,CAAC;GACF;GACA,EAAQ,UAAU,EAChB,gBAAgB,EAAE,OAAO,WAAW,EACrC,CAAC;GACH;EACD;EACA,UAAU;EACV,aAAa,EACX,YAAY,EACV,OAAO,EAAG,GAAG,EAAc,EAC5B,EACF;EACF,CAAC;AAIF,QAFK,IAGH,kBAAC,OAAD;EAAU;EAAK,GAAI;EAAkB;YACnC,kBAAC,GAAD,EAAuB,WAAU,CAAA;EAC7B,CAAA,GALY;EAQrB;AAED,EAAe,cAAc"}
|
|
1
|
+
{"version":3,"file":"rich-text-editor.js","names":[],"sources":["../../src/composed/rich-text-editor.tsx"],"sourcesContent":["'use client'\n\nimport {\n IconAlignCenter,\n IconAlignLeft,\n IconAlignRight,\n IconArrowBackUp,\n IconArrowForwardUp,\n IconBlockquote,\n IconBold,\n IconCode,\n IconH2,\n IconH3,\n IconHighlight,\n IconItalic,\n IconLineDashed,\n IconLink,\n IconList,\n IconListCheck,\n IconListNumbers,\n IconMoodSmile,\n IconPaperclip,\n IconPhoto,\n IconStrikethrough,\n IconUnderline,\n} from '@tabler/icons-react'\nimport Highlight from '@tiptap/extension-highlight'\nimport Image from '@tiptap/extension-image'\nimport { ListKit } from '@tiptap/extension-list'\nimport Mention from '@tiptap/extension-mention'\nimport TextAlign from '@tiptap/extension-text-align'\nimport { Placeholder } from '@tiptap/extensions'\nimport { type Editor,EditorContent, useEditor, useEditorState } from '@tiptap/react'\nimport StarterKit from '@tiptap/starter-kit'\nimport * as React from 'react'\n\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { MENTION_TOKEN_CLASS } from '../ui/lib/mention'\nimport { cn } from '../ui/lib/utils'\nimport type { EmojiData, EmojiSet } from './emoji-picker'\nimport { EmojiNode } from './extensions/emoji-node'\nimport { createEmojiSuggestion } from './extensions/emoji-suggestion'\nimport { FileAttachment } from './extensions/file-attachment'\nimport { createSuggestionRenderer } from './extensions/mention-suggestion'\n\nconst PROSE_CLASSES = [\n 'prose prose-sm max-w-none',\n 'font-body text-ds-md leading-relaxed text-surface-fg',\n '[&_h2]:text-ds-xl [&_h2]:mb-ds-03 [&_h2]:mt-ds-05 [&_h2]:text-surface-fg',\n '[&_h3]:text-ds-base [&_h3]:font-semibold [&_h3]:mb-ds-02b [&_h3]:mt-ds-04 [&_h3]:text-surface-fg',\n '[&_p]:mb-ds-02b [&_p]:text-surface-fg-muted',\n '[&_ul]:ml-ds-05 [&_ul]:list-disc [&_ol]:ml-ds-05 [&_ol]:list-decimal',\n '[&_li]:text-surface-fg-muted',\n '[&_code]:rounded [&_code]:bg-surface-raised [&_code]:px-ds-02b [&_code]:py-ds-01 [&_code]:text-ds-md [&_code]:text-accent-11',\n '[&_pre]:rounded-surface [&_pre]:bg-surface-raised [&_pre]:p-ds-04',\n '[&_strong]:font-semibold [&_strong]:text-surface-fg',\n '[&_blockquote]:border-l-[3px] [&_blockquote]:border-accent-6 [&_blockquote]:pl-ds-04 [&_blockquote]:italic [&_blockquote]:text-surface-fg-subtle',\n '[&_mark]:rounded-xs [&_mark]:bg-warning-3 [&_mark]:px-[2px]',\n '[&_ul[data-type=\"taskList\"]]:ml-0 [&_ul[data-type=\"taskList\"]]:list-none [&_li[data-type=\"taskItem\"]]:flex [&_li[data-type=\"taskItem\"]]:items-start [&_li[data-type=\"taskItem\"]]:gap-ds-02',\n '[&_hr]:my-ds-04 [&_hr]:border-surface-border-strong',\n '[&_a]:text-accent-11 [&_a]:underline [&_a]:decoration-accent-6 hover:[&_a]:decoration-accent-11',\n '[&_img]:max-w-full [&_img]:rounded-control [&_img]:my-ds-03',\n MENTION_TOKEN_CLASS,\n] as const\n\ninterface ToolbarButtonProps {\n onClick: () => void\n isActive?: boolean\n disabled?: boolean\n title: string\n children: React.ReactNode\n}\n\nfunction ToolbarButton({\n onClick,\n isActive = false,\n disabled = false,\n title,\n children,\n}: ToolbarButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n disabled={disabled}\n title={title}\n aria-label={title}\n aria-pressed={isActive}\n className={cn(\n 'inline-flex h-ds-xs-plus w-ds-xs-plus items-center justify-center rounded-control transition-colors duration-fast-01 ease-productive-standard',\n 'hover:bg-surface-raised-hover',\n 'disabled:pointer-events-none disabled:opacity-action-disabled',\n isActive\n ? 'bg-surface-raised-hover text-accent-11'\n : 'text-surface-fg-subtle',\n )}\n >\n {children}\n </button>\n )\n}\n\nfunction LinkButton({ editor }: { editor: Editor }) {\n const [showInput, setShowInput] = React.useState(false)\n const [url, setUrl] = React.useState('')\n const inputRef = React.useRef<HTMLInputElement>(null)\n const { isLink } = useEditorState({\n editor,\n selector: ({ editor: e }) => ({ isLink: e.isActive('link') }),\n })\n\n const handleToggle = () => {\n if (isLink) {\n editor.chain().focus().unsetLink().run()\n return\n }\n const previousUrl = editor.getAttributes('link').href || ''\n setUrl(previousUrl)\n setShowInput(true)\n setTimeout(() => inputRef.current?.focus(), 0)\n }\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault()\n if (url.trim()) {\n editor.chain().focus().setLink({ href: url.trim() }).run()\n }\n setShowInput(false)\n setUrl('')\n }\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Escape') {\n setShowInput(false)\n setUrl('')\n editor.commands.focus()\n }\n }\n\n return (\n <div className=\"relative\">\n <ToolbarButton onClick={handleToggle} isActive={isLink} title=\"Link\">\n <Icon icon={IconLink} size=\"sm\" />\n </ToolbarButton>\n {showInput && (\n <form\n onSubmit={handleSubmit}\n aria-label=\"Edit link URL\"\n className=\"absolute left-0 top-full z-popover mt-ds-01 flex items-center gap-ds-02 rounded-control bg-surface-overlay p-ds-02 shadow-raised-hover\"\n >\n <input\n ref={inputRef}\n type=\"url\"\n value={url}\n onChange={(e) => setUrl(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder=\"https://...\"\n className=\"h-ds-sm w-[240px] rounded-control-inner border border-surface-border-strong bg-surface-overlay px-ds-03 text-ds-sm text-surface-fg focus:border-accent-7 focus:outline-hidden\"\n />\n <Button type=\"submit\" variant=\"solid\" size=\"sm\">\n Apply\n </Button>\n </form>\n )}\n </div>\n )\n}\n\nfunction ToolbarDivider() {\n return <div className=\"mx-ds-02 h-ds-05 w-px bg-surface-border\" />\n}\n\nfunction Toolbar({ editor, toolbar, onImageClick, onFileClick, onEmojiClick }: {\n editor: Editor\n toolbar?: ToolbarItem[]\n onImageClick?: () => void\n onFileClick?: () => void\n onEmojiClick?: () => void\n}) {\n // v3: useEditorState subscribes to specific state slices so the toolbar\n // re-renders only when active-state or can-undo/redo actually changes.\n const state = useEditorState({\n editor,\n selector: ({ editor: e }) => ({\n isBold: e.isActive('bold'),\n isItalic: e.isActive('italic'),\n isUnderline: e.isActive('underline'),\n isStrike: e.isActive('strike'),\n isHighlight: e.isActive('highlight'),\n isH2: e.isActive('heading', { level: 2 }),\n isH3: e.isActive('heading', { level: 3 }),\n isBlockquote: e.isActive('blockquote'),\n isBulletList: e.isActive('bulletList'),\n isOrderedList: e.isActive('orderedList'),\n isTaskList: e.isActive('taskList'),\n isCodeBlock: e.isActive('codeBlock'),\n isLink: e.isActive('link'),\n isAlignLeft: e.isActive({ textAlign: 'left' }),\n isAlignCenter: e.isActive({ textAlign: 'center' }),\n isAlignRight: e.isActive({ textAlign: 'right' }),\n canUndo: e.can().undo(),\n canRedo: e.can().redo(),\n }),\n })\n\n const show = (item: ToolbarItem) => !toolbar || toolbar.includes(item)\n\n // Each group is an array of ToolbarItem names that belong to it.\n // A divider renders between two adjacent groups only if both have at least one visible item.\n const hasInline = show('bold') || show('italic') || show('underline') || show('strike') || show('highlight')\n const hasBlock = show('h2') || show('h3') || show('blockquote')\n const hasList = show('bulletList') || show('orderedList') || show('taskList') || show('codeBlock')\n const hasMedia = show('link') || (!!onImageClick && show('image')) || (!!onFileClick && show('file')) || show('hr')\n const hasAlign = show('alignLeft') || show('alignCenter') || show('alignRight')\n const hasEmojiGroup = !!onEmojiClick && show('emoji')\n const hasHistory = show('undo') || show('redo')\n\n return (\n <div role=\"toolbar\" aria-label=\"Text formatting\" className=\"flex flex-wrap items-center gap-ds-01 border-b border-surface-border-strong px-ds-04 py-ds-02b\">\n {/* Inline formatting */}\n {show('bold') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleBold().run()} isActive={state.isBold} title=\"Bold\">\n <Icon icon={IconBold} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n {show('italic') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleItalic().run()} isActive={state.isItalic} title=\"Italic\">\n <Icon icon={IconItalic} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n {show('underline') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleUnderline().run()} isActive={state.isUnderline} title=\"Underline\">\n <Icon icon={IconUnderline} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n {show('strike') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleStrike().run()} isActive={state.isStrike} title=\"Strikethrough\">\n <Icon icon={IconStrikethrough} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n {show('highlight') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleHighlight().run()} isActive={state.isHighlight} title=\"Highlight\">\n <Icon icon={IconHighlight} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n\n {hasInline && hasBlock && <ToolbarDivider />}\n\n {/* Block formatting */}\n {show('h2') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} isActive={state.isH2} title=\"Heading 2\">\n <Icon icon={IconH2} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n {show('h3') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} isActive={state.isH3} title=\"Heading 3\">\n <Icon icon={IconH3} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n {show('blockquote') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleBlockquote().run()} isActive={state.isBlockquote} title=\"Blockquote\">\n <Icon icon={IconBlockquote} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n\n {hasBlock && hasList && <ToolbarDivider />}\n\n {/* Lists */}\n {show('bulletList') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleBulletList().run()} isActive={state.isBulletList} title=\"Bullet list\">\n <Icon icon={IconList} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n {show('orderedList') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleOrderedList().run()} isActive={state.isOrderedList} title=\"Ordered list\">\n <Icon icon={IconListNumbers} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n {show('taskList') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleTaskList().run()} isActive={state.isTaskList} title=\"Task list\">\n <Icon icon={IconListCheck} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n {show('codeBlock') && (\n <ToolbarButton onClick={() => editor.chain().focus().toggleCodeBlock().run()} isActive={state.isCodeBlock} title=\"Code block\">\n <Icon icon={IconCode} size=\"sm\" stroke=\"bold\" />\n </ToolbarButton>\n )}\n\n {hasList && hasMedia && <ToolbarDivider />}\n\n {/* Media & Links */}\n {show('link') && <LinkButton editor={editor} />}\n {onImageClick && show('image') && (\n <ToolbarButton onClick={onImageClick} title=\"Insert image\">\n <Icon icon={IconPhoto} size=\"sm\" />\n </ToolbarButton>\n )}\n {onFileClick && show('file') && (\n <ToolbarButton onClick={onFileClick} title=\"Attach file\">\n <Icon icon={IconPaperclip} size=\"sm\" />\n </ToolbarButton>\n )}\n {show('hr') && (\n <ToolbarButton onClick={() => editor.chain().focus().setHorizontalRule().run()} title=\"Horizontal rule\">\n <Icon icon={IconLineDashed} size=\"sm\" />\n </ToolbarButton>\n )}\n\n {hasMedia && hasAlign && <ToolbarDivider />}\n\n {/* Alignment */}\n {show('alignLeft') && (\n <ToolbarButton onClick={() => editor.chain().focus().setTextAlign('left').run()} isActive={state.isAlignLeft} title=\"Align left\">\n <Icon icon={IconAlignLeft} size=\"sm\" />\n </ToolbarButton>\n )}\n {show('alignCenter') && (\n <ToolbarButton onClick={() => editor.chain().focus().setTextAlign('center').run()} isActive={state.isAlignCenter} title=\"Align center\">\n <Icon icon={IconAlignCenter} size=\"sm\" />\n </ToolbarButton>\n )}\n {show('alignRight') && (\n <ToolbarButton onClick={() => editor.chain().focus().setTextAlign('right').run()} isActive={state.isAlignRight} title=\"Align right\">\n <Icon icon={IconAlignRight} size=\"sm\" />\n </ToolbarButton>\n )}\n\n {hasAlign && (hasEmojiGroup || hasHistory) && <ToolbarDivider />}\n\n {/* Emoji */}\n {onEmojiClick && show('emoji') && (\n <ToolbarButton onClick={onEmojiClick} title=\"Emoji\">\n <Icon icon={IconMoodSmile} size=\"sm\" />\n </ToolbarButton>\n )}\n\n {/* History */}\n {show('undo') && (\n <ToolbarButton onClick={() => editor.chain().focus().undo().run()} disabled={!state.canUndo} title=\"Undo\">\n <Icon icon={IconArrowBackUp} size=\"sm\" />\n </ToolbarButton>\n )}\n {show('redo') && (\n <ToolbarButton onClick={() => editor.chain().focus().redo().run()} disabled={!state.canRedo} title=\"Redo\">\n <Icon icon={IconArrowForwardUp} size=\"sm\" />\n </ToolbarButton>\n )}\n </div>\n )\n}\n\nconst LazyPicker = React.lazy(() =>\n import('./emoji-picker').then((m) => ({ default: m.EmojiPicker })),\n)\n\nfunction EmojiPickerLazy({ onSelect }: { onSelect: (emoji: EmojiData) => void }) {\n const fallback = <div className=\"flex h-[435px] w-[352px] items-center justify-center rounded-surface bg-surface-overlay shadow-raised-hover\"><span className=\"text-ds-sm text-surface-fg-subtle\">Loading…</span></div>\n\n return (\n <React.Suspense fallback={fallback}>\n <LazyPicker onSelect={onSelect} />\n </React.Suspense>\n )\n}\n\nexport type ToolbarItem =\n | 'bold' | 'italic' | 'underline' | 'strike' | 'highlight'\n | 'h2' | 'h3' | 'blockquote'\n | 'bulletList' | 'orderedList' | 'taskList' | 'codeBlock'\n | 'link' | 'image' | 'file' | 'hr'\n | 'alignLeft' | 'alignCenter' | 'alignRight'\n | 'emoji' | 'undo' | 'redo'\n\nexport interface MentionItem {\n id: string\n label: string\n avatar?: string\n}\n\n/**\n * A Tiptap-powered rich text editor with a configurable toolbar, @mentions,\n * emoji suggestions, file/image uploads, and task lists.\n * Outputs sanitized HTML via `onChange`.\n */\nexport interface RichTextEditorProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'onChange' | 'content'> {\n /** Initial HTML content. Updates are NOT reactive — use `onChange` for controlled state. */\n content?: string\n placeholder?: string\n /** Called with the editor's HTML string whenever content changes. */\n onChange?: (html: string) => void\n className?: string\n /** Set to false to render in read-only mode (e.g. for previewing). */\n editable?: boolean\n /** Called when an image is pasted/dropped. Return a URL. If not provided, images inline as base64. */\n onImageUpload?: (file: File) => Promise<string>\n /** Called when a non-image file is dropped/pasted. If not provided, non-image files are ignored. */\n onFileUpload?: (file: File) => Promise<{ url: string; name: string; size: number }>\n /** Whitelist of toolbar items to display. Omit to show all. */\n toolbar?: ToolbarItem[]\n /** Static list of mentionable items (shown when user types @). */\n mentions?: MentionItem[]\n /** Async mention search. Takes precedence over static `mentions` list. */\n onMentionSearch?: (query: string) => Promise<MentionItem[]>\n /** Called when a mention is selected from the suggestion dropdown. */\n onMentionSelect?: (item: MentionItem) => void\n /** Emoji art style. @default 'native' */\n emojiSet?: EmojiSet\n}\n\nconst RichTextEditor = React.forwardRef<HTMLDivElement, RichTextEditorProps>(\n function RichTextEditor({\n content = '',\n placeholder = 'Start writing...',\n onChange,\n className,\n editable = true,\n toolbar,\n onImageUpload,\n onFileUpload,\n mentions,\n onMentionSearch,\n onMentionSelect,\n // @deprecated no-op since the frimousse migration (native-only emoji). Kept\n // out of `...props` so it never leaks onto the DOM.\n emojiSet: _emojiSet = 'native',\n ...props\n}, ref) {\n const editorRef = React.useRef<ReturnType<typeof useEditor>>(null)\n const isInternalChangeRef = React.useRef(false)\n const [showEmojiPicker, setShowEmojiPicker] = React.useState(false)\n const emojiPickerRef = React.useRef<HTMLDivElement>(null)\n const imageInputRef = React.useRef<HTMLInputElement>(null)\n const fileInputRef = React.useRef<HTMLInputElement>(null)\n\n const handleImageInsert = async (file: File) => {\n const ed = editorRef.current\n if (!ed) return\n if (onImageUpload) {\n const url = await onImageUpload(file)\n if (url && /^https?:\\/\\//i.test(url)) {\n ed.chain().focus().setImage({ src: url }).run()\n }\n } else {\n const reader = new FileReader()\n reader.onload = () => {\n ed.chain().focus().setImage({ src: reader.result as string }).run()\n }\n reader.readAsDataURL(file)\n }\n }\n\n const handleFileInsert = async (file: File) => {\n const ed = editorRef.current\n if (!ed || !onFileUpload) return\n const result = await onFileUpload(file)\n ed.chain().focus().insertContent({\n type: 'fileAttachment',\n attrs: { url: result.url, name: result.name, size: result.size },\n }).run()\n }\n\n const editor = useEditor({\n immediatelyRender: false, // SSR-safe — prevents hydration mismatch in Next.js\n extensions: [\n StarterKit.configure({\n heading: { levels: [2, 3] },\n // v3: Underline + Link are now included in StarterKit\n link: {\n openOnClick: false,\n protocols: ['http', 'https', 'mailto'],\n validate: (href: string) => /^(https?:\\/\\/|mailto:)/i.test(href),\n HTMLAttributes: {\n rel: 'noopener noreferrer',\n target: '_blank',\n },\n },\n }),\n Placeholder.configure({\n placeholder,\n emptyEditorClass:\n 'before:content-[attr(data-placeholder)] before:text-surface-fg-subtle before:float-left before:h-0 before:pointer-events-none',\n }),\n Highlight.configure({ multicolor: false }),\n ListKit.configure({\n bulletList: false, // Already in StarterKit\n orderedList: false, // Already in StarterKit\n listItem: false, // Already in StarterKit\n listKeymap: false, // Already in StarterKit\n taskList: {},\n taskItem: { nested: true },\n }),\n TextAlign.configure({ types: ['heading', 'paragraph'] }),\n Image.configure({\n allowBase64: true,\n HTMLAttributes: {\n class: 'max-w-full rounded-control',\n },\n }),\n FileAttachment,\n ...(mentions || onMentionSearch ? [\n Mention.configure({\n HTMLAttributes: { class: 'mention' },\n suggestion: {\n items: async ({ query }: { query: string }) => {\n if (onMentionSearch) return await onMentionSearch(query)\n if (mentions) return mentions.filter(m => m.label.toLowerCase().includes(query.toLowerCase())).slice(0, 8)\n return []\n },\n render: createSuggestionRenderer(onMentionSelect),\n },\n }),\n ] : []),\n EmojiNode,\n createEmojiSuggestion(),\n ],\n content,\n editable,\n editorProps: {\n handleDrop: (_view, event, _slice, moved) => {\n if (moved || !event.dataTransfer?.files.length) return false\n const file = event.dataTransfer.files[0]\n if (!file) return false\n if (file.type.startsWith('image/')) {\n handleImageInsert(file)\n return true\n }\n if (onFileUpload) {\n handleFileInsert(file)\n return true\n }\n return false\n },\n handlePaste: (_view, event) => {\n const file = event.clipboardData?.files[0]\n if (!file) return false\n if (file.type.startsWith('image/')) {\n handleImageInsert(file)\n return true\n }\n if (onFileUpload) {\n handleFileInsert(file)\n return true\n }\n return false\n },\n attributes: {\n class: cn(\n ...PROSE_CLASSES,\n 'focus:outline-hidden',\n 'min-h-[120px] px-ds-04 py-ds-04',\n ),\n },\n },\n onUpdate: ({ editor: ed }) => {\n isInternalChangeRef.current = true\n onChange?.(ed.getHTML())\n queueMicrotask(() => {\n isInternalChangeRef.current = false\n })\n },\n })\n\n React.useEffect(() => {\n editorRef.current = editor\n }, [editor])\n\n // Close emoji picker on click outside\n React.useEffect(() => {\n if (!showEmojiPicker) return\n const handleClickOutside = (e: MouseEvent) => {\n if (emojiPickerRef.current && !emojiPickerRef.current.contains(e.target as Node)) {\n setShowEmojiPicker(false)\n }\n }\n document.addEventListener('mousedown', handleClickOutside)\n return () => document.removeEventListener('mousedown', handleClickOutside)\n }, [showEmojiPicker])\n\n React.useEffect(() => {\n if (isInternalChangeRef.current) return\n if (editor && content !== editor.getHTML()) {\n editor.commands.setContent(content, { emitUpdate: false })\n }\n }, [editor, content])\n\n if (!editor) return null\n\n return (\n <div ref={ref} {...props} className={cn('relative', className)}>\n {/* Emoji picker rendered outside the overflow-hidden box so it isn't clipped */}\n {showEmojiPicker && (\n <div\n ref={emojiPickerRef}\n className=\"absolute bottom-full right-0 z-popover mb-ds-02\"\n onKeyDown={(e) => {\n if (e.key === 'Escape') {\n e.stopPropagation()\n setShowEmojiPicker(false)\n }\n }}\n >\n <EmojiPickerLazy\n onSelect={({ id, native }) => {\n editor.chain().focus().insertContent({\n type: 'emojiNode',\n attrs: { id, native },\n }).run()\n setShowEmojiPicker(false)\n }}\n />\n </div>\n )}\n <div\n className={cn(\n 'overflow-hidden rounded-surface border border-surface-border-strong bg-surface-raised',\n 'transition-colors ease-productive-standard focus-within:border-surface-border-strong',\n )}\n >\n <input\n ref={imageInputRef}\n type=\"file\"\n accept=\"image/*\"\n aria-label=\"Upload image\"\n className=\"hidden\"\n onChange={(e) => {\n const file = e.target.files?.[0]\n if (file) handleImageInsert(file)\n e.target.value = ''\n }}\n />\n {onFileUpload && (\n <input\n ref={fileInputRef}\n type=\"file\"\n aria-label=\"Upload file\"\n className=\"hidden\"\n onChange={(e) => {\n const file = e.target.files?.[0]\n if (file) handleFileInsert(file)\n e.target.value = ''\n }}\n />\n )}\n {editable && (\n <Toolbar\n editor={editor}\n toolbar={toolbar}\n onImageClick={() => imageInputRef.current?.click()}\n onFileClick={onFileUpload ? () => fileInputRef.current?.click() : undefined}\n onEmojiClick={() => setShowEmojiPicker((prev) => !prev)}\n />\n )}\n <EditorContent editor={editor} />\n </div>\n </div>\n )\n},\n)\n\nRichTextEditor.displayName = 'RichTextEditor'\n\nexport interface RichTextViewerProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'content'> {\n content: string\n className?: string\n}\n\nconst RichTextViewer = React.forwardRef<HTMLDivElement, RichTextViewerProps>(\n function RichTextViewer({ content, className, ...props }, ref) {\n const editor = useEditor({\n immediatelyRender: false, // SSR-safe — prevents hydration mismatch in Next.js\n extensions: [\n StarterKit.configure({\n heading: { levels: [2, 3] },\n link: {\n openOnClick: true,\n protocols: ['http', 'https', 'mailto'],\n validate: (href: string) => /^(https?:\\/\\/|mailto:)/i.test(href),\n HTMLAttributes: {\n rel: 'noopener noreferrer',\n target: '_blank',\n },\n },\n }),\n Highlight.configure({ multicolor: false }),\n ListKit.configure({\n bulletList: false,\n orderedList: false,\n listItem: false,\n listKeymap: false,\n taskList: {},\n taskItem: { nested: true },\n }),\n TextAlign.configure({ types: ['heading', 'paragraph'] }),\n Image.configure({\n allowBase64: true,\n HTMLAttributes: {\n class: 'max-w-full rounded-control',\n },\n }),\n FileAttachment,\n Mention.configure({\n HTMLAttributes: { class: 'mention' },\n }),\n ],\n content,\n editable: false,\n editorProps: {\n attributes: {\n class: cn(...PROSE_CLASSES),\n },\n },\n })\n\n if (!editor) return null\n\n return (\n <div ref={ref} {...props} className={className}>\n <EditorContent editor={editor} />\n </div>\n )\n},\n)\n\nRichTextViewer.displayName = 'RichTextViewer'\n\nexport { RichTextEditor, RichTextViewer }\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schedule-view.js","names":[],"sources":["../../src/composed/schedule-view.tsx"],"sourcesContent":["'use client'\n\nimport {\n addDays,\n differenceInMinutes,\n format,\n getHours,\n getMinutes,\n isSameDay,\n isToday,\n startOfWeek,\n} from 'date-fns'\nimport * as React from 'react'\n\nimport { Dot } from '../ui/dot'\nimport { cn } from '../ui/lib/utils'\n\n/* ------------------------------------------------------------------ */\n/* Types */\n/* ------------------------------------------------------------------ */\n\nexport interface ScheduleEvent {\n id: string\n title: string\n start: Date\n end: Date\n color?: 'accent' | 'success' | 'warning' | 'error' | 'info' | 'neutral'\n}\n\n/**\n * A day/week calendar schedule grid. Renders time-slotted events with\n * proportional height, color coding, click handlers, and a current-time indicator.\n */\nexport interface ScheduleViewProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Display mode: single-day column or full-week (7 columns). */\n view: 'day' | 'week'\n /** Current day (day view) or any date within the target week (week view). */\n date: Date\n /** Events to display on the calendar grid. */\n events: ScheduleEvent[]\n /** Called when an event block is clicked. */\n onEventClick?: (event: ScheduleEvent) => void\n /** Called when an empty time slot is clicked, with the slot's start and end times. */\n onSlotClick?: (start: Date, end: Date) => void\n /** First visible hour (default 8). */\n startHour?: number\n /** Last visible hour -- exclusive (default 18). */\n endHour?: number\n /** Slot duration in minutes (default 30). */\n slotDuration?: number\n}\n\n/* ------------------------------------------------------------------ */\n/* Color map */\n/* ------------------------------------------------------------------ */\n\nconst eventColorMap: Record<\n NonNullable<ScheduleEvent['color']>,\n string\n> = {\n accent: 'bg-accent-2 text-accent-11',\n success: 'bg-success-3 text-success-11',\n warning: 'bg-warning-3 text-warning-11',\n error: 'bg-error-3 text-error-11',\n info: 'bg-info-3 text-info-11',\n neutral: 'bg-surface-raised text-surface-fg-muted',\n}\n\n// Solid category dot — the color-blind-safe, shape-based signal that replaces the\n// former left accent rail (the AI tell). Tint above carries the ambient color.\nconst eventDotMap: Record<\n NonNullable<ScheduleEvent['color']>,\n string\n> = {\n accent: 'bg-accent-9',\n success: 'bg-success-9',\n warning: 'bg-warning-9',\n error: 'bg-error-9',\n info: 'bg-info-9',\n neutral: 'bg-surface-fg-subtle',\n}\n\n/* ------------------------------------------------------------------ */\n/* Helpers */\n/* ------------------------------------------------------------------ */\n\nfunction formatHourLabel(hour: number): string {\n if (hour === 0) return '12 AM'\n if (hour < 12) return `${hour} AM`\n if (hour === 12) return '12 PM'\n return `${hour - 12} PM`\n}\n\nfunction getEventStyle(\n event: ScheduleEvent,\n startHour: number,\n endHour: number,\n) {\n const totalMinutes = (endHour - startHour) * 60\n const eventStartMinutes =\n (getHours(event.start) - startHour) * 60 + getMinutes(event.start)\n const durationMinutes = differenceInMinutes(event.end, event.start)\n\n const top = (eventStartMinutes / totalMinutes) * 100\n const height = (durationMinutes / totalMinutes) * 100\n\n return {\n top: `${top}%`,\n height: `${height}%`,\n }\n}\n\n/* ------------------------------------------------------------------ */\n/* Sub-components */\n/* ------------------------------------------------------------------ */\n\ninterface TimeColumnProps {\n startHour: number\n endHour: number\n}\n\nfunction TimeColumn({ startHour, endHour }: TimeColumnProps) {\n const hours: number[] = []\n for (let h = startHour; h < endHour; h++) {\n hours.push(h)\n }\n const slotHeight = 100 / (endHour - startHour)\n\n return (\n <div\n className=\"relative shrink-0 w-[60px] border-r border-surface-border-strong\"\n aria-hidden=\"true\"\n >\n {hours.map((hour) => (\n <div\n key={hour}\n className=\"text-ds-xs text-surface-fg-muted pr-ds-02 text-right\"\n style={{ height: `${slotHeight}%` }}\n >\n {formatHourLabel(hour)}\n </div>\n ))}\n </div>\n )\n}\n\ninterface DayColumnProps {\n date: Date\n events: ScheduleEvent[]\n startHour: number\n endHour: number\n slotDuration: number\n onEventClick?: (event: ScheduleEvent) => void\n onSlotClick?: (start: Date, end: Date) => void\n showHeader?: boolean\n}\n\nfunction DayColumn({\n date,\n events,\n startHour,\n endHour,\n slotDuration,\n onEventClick,\n onSlotClick,\n showHeader,\n}: DayColumnProps) {\n const dayEvents = events.filter((e) => isSameDay(e.start, date))\n const totalMinutes = (endHour - startHour) * 60\n const slotCount = totalMinutes / slotDuration\n\n const slots: { start: Date; end: Date }[] = []\n for (let i = 0; i < slotCount; i++) {\n const slotStart = new Date(date)\n slotStart.setHours(startHour, i * slotDuration, 0, 0)\n const slotEnd = new Date(slotStart)\n slotEnd.setMinutes(slotEnd.getMinutes() + slotDuration)\n slots.push({ start: slotStart, end: slotEnd })\n }\n\n const todayInView = isToday(date)\n\n // Current time indicator position\n let nowIndicatorTop: number | null = null\n if (todayInView) {\n const now = new Date()\n const nowHour = getHours(now)\n const nowMin = getMinutes(now)\n if (nowHour >= startHour && nowHour < endHour) {\n const nowMinutes = (nowHour - startHour) * 60 + nowMin\n nowIndicatorTop = (nowMinutes / totalMinutes) * 100\n }\n }\n\n return (\n <div className=\"flex flex-1 flex-col min-w-ds-11\">\n {showHeader && (\n <div\n className={cn(\n 'text-center text-ds-sm font-semibold py-ds-02 border-b border-surface-border-strong',\n todayInView\n ? 'text-accent-11 bg-accent-2'\n : 'text-surface-fg bg-surface-raised',\n )}\n >\n {format(date, 'EEE d')}\n </div>\n )}\n <div className=\"relative flex-1\">\n {/* Slot grid lines */}\n {slots.map((slot, i) => (\n <button\n key={i}\n type=\"button\"\n className={cn(\n 'block w-full border-b border-surface-border hover:bg-surface-raised-hover transition-colors ease-productive-standard',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-accent-9',\n i % 2 === 0 ? 'border-surface-border-strong' : 'border-surface-border-subtle',\n )}\n style={{ height: `${100 / slotCount}%` }}\n onClick={() => onSlotClick?.(slot.start, slot.end)}\n aria-label={`${format(slot.start, 'h:mm a')} to ${format(slot.end, 'h:mm a')}`}\n />\n ))}\n\n {/* Events */}\n {dayEvents.map((event) => {\n const style = getEventStyle(event, startHour, endHour)\n const eventColor = event.color ?? 'accent'\n const colorClass = eventColorMap[eventColor]\n const dotClass = eventDotMap[eventColor]\n return (\n <button\n key={event.id}\n type=\"button\"\n className={cn(\n 'absolute left-ds-01 right-ds-01 rounded-control-inner px-ds-02 py-ds-01',\n 'text-left text-ds-xs font-medium overflow-hidden cursor-pointer',\n 'hover:shadow-raised transition-[box-shadow] duration-fast-02 ease-productive-standard',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9',\n colorClass,\n )}\n style={style}\n onClick={(e) => {\n e.stopPropagation()\n onEventClick?.(event)\n }}\n aria-label={`${event.title}, ${format(event.start, 'h:mm a')} to ${format(event.end, 'h:mm a')}`}\n >\n <span className=\"flex items-start gap-ds-02\">\n <span\n className={cn('mt-[3px] h-ds-03 w-ds-03 shrink-0 rounded-pill', dotClass)}\n aria-hidden=\"true\"\n />\n <span className=\"line-clamp-2\">{event.title}</span>\n </span>\n </button>\n )\n })}\n\n {/* Current time indicator */}\n {nowIndicatorTop != null && (\n <div\n className=\"absolute left-0 right-0 h-ds-01 bg-error-9 z-10 pointer-events-none\"\n style={{ top: `${nowIndicatorTop}%` }}\n aria-hidden=\"true\"\n >\n <Dot color=\"error\" size=\"lg\" pulse className=\"absolute -left-[5px] -top-[4px]\" />\n </div>\n )}\n </div>\n </div>\n )\n}\n\n/* ------------------------------------------------------------------ */\n/* ScheduleView */\n/* ------------------------------------------------------------------ */\n\nconst ScheduleView = React.forwardRef<HTMLDivElement, ScheduleViewProps>(\n (\n {\n view,\n date,\n events,\n onEventClick,\n onSlotClick,\n startHour = 8,\n endHour = 18,\n slotDuration = 30,\n className,\n ...props\n },\n ref,\n ) => {\n const days: Date[] = React.useMemo(() => {\n if (view === 'day') return [date]\n const weekStart = startOfWeek(date, { weekStartsOn: 1 }) // Monday\n return Array.from({ length: 7 }, (_, i) => addDays(weekStart, i))\n }, [view, date])\n\n return (\n <div\n ref={ref}\n role=\"region\"\n aria-label={\n view === 'day'\n ? `Schedule for ${format(date, 'EEEE, MMMM d, yyyy')}`\n : `Week schedule starting ${format(days[0], 'MMMM d, yyyy')}`\n }\n className={cn(\n 'flex rounded-control border border-surface-border-strong bg-surface-raised overflow-hidden',\n 'h-[480px]',\n className,\n )}\n {...props}\n >\n <TimeColumn startHour={startHour} endHour={endHour} />\n <div className=\"flex flex-1 divide-x divide-surface-border overflow-x-auto\">\n {days.map((day) => (\n <DayColumn\n key={day.toISOString()}\n date={day}\n events={events}\n startHour={startHour}\n endHour={endHour}\n slotDuration={slotDuration}\n onEventClick={onEventClick}\n onSlotClick={onSlotClick}\n showHeader={view === 'week'}\n />\n ))}\n </div>\n </div>\n )\n },\n)\nScheduleView.displayName = 'ScheduleView'\n\nexport { ScheduleView }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"schedule-view.js","names":[],"sources":["../../src/composed/schedule-view.tsx"],"sourcesContent":["'use client'\n\nimport {\n addDays,\n differenceInMinutes,\n format,\n getHours,\n getMinutes,\n isSameDay,\n isToday,\n startOfWeek,\n} from 'date-fns'\nimport * as React from 'react'\n\nimport { Dot } from '../ui/dot'\nimport { cn } from '../ui/lib/utils'\n\n/* ------------------------------------------------------------------ */\n/* Types */\n/* ------------------------------------------------------------------ */\n\nexport interface ScheduleEvent {\n id: string\n title: string\n start: Date\n end: Date\n color?: 'accent' | 'success' | 'warning' | 'error' | 'info' | 'neutral'\n}\n\n/**\n * A day/week calendar schedule grid. Renders time-slotted events with\n * proportional height, color coding, click handlers, and a current-time indicator.\n */\nexport interface ScheduleViewProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Display mode: single-day column or full-week (7 columns). */\n view: 'day' | 'week'\n /** Current day (day view) or any date within the target week (week view). */\n date: Date\n /** Events to display on the calendar grid. */\n events: ScheduleEvent[]\n /** Called when an event block is clicked. */\n onEventClick?: (event: ScheduleEvent) => void\n /** Called when an empty time slot is clicked, with the slot's start and end times. */\n onSlotClick?: (start: Date, end: Date) => void\n /** First visible hour (default 8). */\n startHour?: number\n /** Last visible hour -- exclusive (default 18). */\n endHour?: number\n /** Slot duration in minutes (default 30). */\n slotDuration?: number\n}\n\n/* ------------------------------------------------------------------ */\n/* Color map */\n/* ------------------------------------------------------------------ */\n\nconst eventColorMap: Record<\n NonNullable<ScheduleEvent['color']>,\n string\n> = {\n accent: 'bg-accent-2 text-accent-11',\n success: 'bg-success-3 text-success-11',\n warning: 'bg-warning-3 text-warning-11',\n error: 'bg-error-3 text-error-11',\n info: 'bg-info-3 text-info-11',\n neutral: 'bg-surface-raised text-surface-fg-muted',\n}\n\n// Solid category dot — the color-blind-safe, shape-based signal that replaces the\n// former left accent rail (the AI tell). Tint above carries the ambient color.\nconst eventDotMap: Record<\n NonNullable<ScheduleEvent['color']>,\n string\n> = {\n accent: 'bg-accent-9',\n success: 'bg-success-9',\n warning: 'bg-warning-9',\n error: 'bg-error-9',\n info: 'bg-info-9',\n neutral: 'bg-surface-fg-subtle',\n}\n\n/* ------------------------------------------------------------------ */\n/* Helpers */\n/* ------------------------------------------------------------------ */\n\nfunction formatHourLabel(hour: number): string {\n if (hour === 0) return '12 AM'\n if (hour < 12) return `${hour} AM`\n if (hour === 12) return '12 PM'\n return `${hour - 12} PM`\n}\n\nfunction getEventStyle(\n event: ScheduleEvent,\n startHour: number,\n endHour: number,\n) {\n const totalMinutes = (endHour - startHour) * 60\n const eventStartMinutes =\n (getHours(event.start) - startHour) * 60 + getMinutes(event.start)\n const durationMinutes = differenceInMinutes(event.end, event.start)\n\n const top = (eventStartMinutes / totalMinutes) * 100\n const height = (durationMinutes / totalMinutes) * 100\n\n return {\n top: `${top}%`,\n height: `${height}%`,\n }\n}\n\n/* ------------------------------------------------------------------ */\n/* Sub-components */\n/* ------------------------------------------------------------------ */\n\ninterface TimeColumnProps {\n startHour: number\n endHour: number\n}\n\nfunction TimeColumn({ startHour, endHour }: TimeColumnProps) {\n const hours: number[] = []\n for (let h = startHour; h < endHour; h++) {\n hours.push(h)\n }\n const slotHeight = 100 / (endHour - startHour)\n\n return (\n <div\n className=\"relative shrink-0 w-[60px] border-r border-surface-border-strong\"\n aria-hidden=\"true\"\n >\n {hours.map((hour) => (\n <div\n key={hour}\n className=\"text-ds-xs text-surface-fg-muted pr-ds-02 text-right\"\n style={{ height: `${slotHeight}%` }}\n >\n {formatHourLabel(hour)}\n </div>\n ))}\n </div>\n )\n}\n\ninterface DayColumnProps {\n date: Date\n events: ScheduleEvent[]\n startHour: number\n endHour: number\n slotDuration: number\n onEventClick?: (event: ScheduleEvent) => void\n onSlotClick?: (start: Date, end: Date) => void\n showHeader?: boolean\n}\n\nfunction DayColumn({\n date,\n events,\n startHour,\n endHour,\n slotDuration,\n onEventClick,\n onSlotClick,\n showHeader,\n}: DayColumnProps) {\n const dayEvents = events.filter((e) => isSameDay(e.start, date))\n const totalMinutes = (endHour - startHour) * 60\n const slotCount = totalMinutes / slotDuration\n\n const slots: { start: Date; end: Date }[] = []\n for (let i = 0; i < slotCount; i++) {\n const slotStart = new Date(date)\n slotStart.setHours(startHour, i * slotDuration, 0, 0)\n const slotEnd = new Date(slotStart)\n slotEnd.setMinutes(slotEnd.getMinutes() + slotDuration)\n slots.push({ start: slotStart, end: slotEnd })\n }\n\n const todayInView = isToday(date)\n\n // Current time indicator position\n let nowIndicatorTop: number | null = null\n if (todayInView) {\n const now = new Date()\n const nowHour = getHours(now)\n const nowMin = getMinutes(now)\n if (nowHour >= startHour && nowHour < endHour) {\n const nowMinutes = (nowHour - startHour) * 60 + nowMin\n nowIndicatorTop = (nowMinutes / totalMinutes) * 100\n }\n }\n\n return (\n <div className=\"flex flex-1 flex-col min-w-ds-11\">\n {showHeader && (\n <div\n className={cn(\n 'text-center text-ds-sm font-semibold py-ds-02 border-b border-surface-border-strong',\n todayInView\n ? 'text-accent-11 bg-accent-2'\n : 'text-surface-fg bg-surface-raised',\n )}\n >\n {format(date, 'EEE d')}\n </div>\n )}\n <div className=\"relative flex-1\">\n {/* Slot grid lines */}\n {slots.map((slot, i) => (\n <button\n key={i}\n type=\"button\"\n className={cn(\n 'block w-full border-b border-surface-border hover:bg-surface-raised-hover transition-colors ease-productive-standard',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-accent-9',\n i % 2 === 0 ? 'border-surface-border-strong' : 'border-surface-border-subtle',\n )}\n style={{ height: `${100 / slotCount}%` }}\n onClick={() => onSlotClick?.(slot.start, slot.end)}\n aria-label={`${format(slot.start, 'h:mm a')} to ${format(slot.end, 'h:mm a')}`}\n />\n ))}\n\n {/* Events */}\n {dayEvents.map((event) => {\n const style = getEventStyle(event, startHour, endHour)\n const eventColor = event.color ?? 'accent'\n const colorClass = eventColorMap[eventColor]\n const dotClass = eventDotMap[eventColor]\n return (\n <button\n key={event.id}\n type=\"button\"\n className={cn(\n 'absolute left-ds-01 right-ds-01 rounded-control-inner px-ds-02 py-ds-01',\n 'text-left text-ds-xs font-medium overflow-hidden cursor-pointer',\n 'hover:shadow-raised transition-[box-shadow] duration-fast-02 ease-productive-standard',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9',\n colorClass,\n )}\n style={style}\n onClick={(e) => {\n e.stopPropagation()\n onEventClick?.(event)\n }}\n aria-label={`${event.title}, ${format(event.start, 'h:mm a')} to ${format(event.end, 'h:mm a')}`}\n >\n <span className=\"flex items-start gap-ds-02\">\n <span\n className={cn('mt-[3px] h-ds-03 w-ds-03 shrink-0 rounded-pill', dotClass)}\n aria-hidden=\"true\"\n />\n <span className=\"line-clamp-2\">{event.title}</span>\n </span>\n </button>\n )\n })}\n\n {/* Current time indicator */}\n {nowIndicatorTop != null && (\n <div\n className=\"absolute left-0 right-0 h-ds-01 bg-error-9 z-10 pointer-events-none\"\n style={{ top: `${nowIndicatorTop}%` }}\n aria-hidden=\"true\"\n >\n <Dot color=\"error\" size=\"lg\" pulse className=\"absolute -left-[5px] -top-[4px]\" />\n </div>\n )}\n </div>\n </div>\n )\n}\n\n/* ------------------------------------------------------------------ */\n/* ScheduleView */\n/* ------------------------------------------------------------------ */\n\nconst ScheduleView = React.forwardRef<HTMLDivElement, ScheduleViewProps>(\n (\n {\n view,\n date,\n events,\n onEventClick,\n onSlotClick,\n startHour = 8,\n endHour = 18,\n slotDuration = 30,\n className,\n ...props\n },\n ref,\n ) => {\n const days: Date[] = React.useMemo(() => {\n if (view === 'day') return [date]\n const weekStart = startOfWeek(date, { weekStartsOn: 1 }) // Monday\n return Array.from({ length: 7 }, (_, i) => addDays(weekStart, i))\n }, [view, date])\n\n return (\n <div\n ref={ref}\n role=\"region\"\n aria-label={\n view === 'day'\n ? `Schedule for ${format(date, 'EEEE, MMMM d, yyyy')}`\n : `Week schedule starting ${format(days[0], 'MMMM d, yyyy')}`\n }\n className={cn(\n 'flex rounded-control border border-surface-border-strong bg-surface-raised overflow-hidden',\n 'h-[480px]',\n className,\n )}\n {...props}\n >\n <TimeColumn startHour={startHour} endHour={endHour} />\n <div className=\"flex flex-1 divide-x divide-surface-border overflow-x-auto\">\n {days.map((day) => (\n <DayColumn\n key={day.toISOString()}\n date={day}\n events={events}\n startHour={startHour}\n endHour={endHour}\n slotDuration={slotDuration}\n onEventClick={onEventClick}\n onSlotClick={onSlotClick}\n showHeader={view === 'week'}\n />\n ))}\n </div>\n </div>\n )\n },\n)\nScheduleView.displayName = 'ScheduleView'\n\nexport { ScheduleView }\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"simple-tooltip.js","names":[],"sources":["../../src/composed/simple-tooltip.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '../ui/tooltip'\n\nexport interface SimpleTooltipProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'content'> {\n content: React.ReactNode\n side?: 'top' | 'right' | 'bottom' | 'left'\n align?: 'start' | 'center' | 'end'\n delayDuration?: number\n children: React.ReactNode\n}\n\nconst SimpleTooltip = React.forwardRef<HTMLButtonElement, SimpleTooltipProps>(\n (\n {\n content,\n side = 'top',\n align = 'center',\n delayDuration = 300,\n children,\n className,\n ...props\n },\n ref,\n ) => (\n <TooltipProvider delayDuration={delayDuration}>\n <Tooltip>\n <TooltipTrigger ref={ref} asChild>{children}</TooltipTrigger>\n <TooltipContent side={side} align={align} className={className} {...props}>\n {content}\n </TooltipContent>\n </Tooltip>\n </TooltipProvider>\n ),\n)\nSimpleTooltip.displayName = 'SimpleTooltip'\n\nexport { SimpleTooltip }\n"],"mappings":";;;;;AAmBA,IAAM,IAAgB,EAAM,YAExB,EACE,YACA,UAAO,OACP,WAAQ,UACR,mBAAgB,KAChB,aACA,cACA,GAAG,KAEL,MAEA,kBAAC,GAAD;CAAgC;WAC9B,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;EAAqB;EAAK,SAAA;EAAS;
|
|
1
|
+
{"version":3,"file":"simple-tooltip.js","names":[],"sources":["../../src/composed/simple-tooltip.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '../ui/tooltip'\n\nexport interface SimpleTooltipProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'content'> {\n content: React.ReactNode\n side?: 'top' | 'right' | 'bottom' | 'left'\n align?: 'start' | 'center' | 'end'\n delayDuration?: number\n children: React.ReactNode\n}\n\nconst SimpleTooltip = React.forwardRef<HTMLButtonElement, SimpleTooltipProps>(\n (\n {\n content,\n side = 'top',\n align = 'center',\n delayDuration = 300,\n children,\n className,\n ...props\n },\n ref,\n ) => (\n <TooltipProvider delayDuration={delayDuration}>\n <Tooltip>\n <TooltipTrigger ref={ref} asChild>{children}</TooltipTrigger>\n <TooltipContent side={side} align={align} className={className} {...props}>\n {content}\n </TooltipContent>\n </Tooltip>\n </TooltipProvider>\n ),\n)\nSimpleTooltip.displayName = 'SimpleTooltip'\n\nexport { SimpleTooltip }\n"],"mappings":";;;;;AAmBA,IAAM,IAAgB,EAAM,YAExB,EACE,YACA,UAAO,OACP,WAAQ,UACR,mBAAgB,KAChB,aACA,cACA,GAAG,KAEL,MAEA,kBAAC,GAAD;CAAgC;WAC9B,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;EAAqB;EAAK,SAAA;EAAS;CAAyB,CAAA,GAC5D,kBAAC,GAAD;EAAsB;EAAa;EAAkB;EAAW,GAAI;YACjE;CACa,CAAA,CACT,EAAA,CAAA;AACM,CAAA,CAErB;AACA,EAAc,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status-badge.js","names":[],"sources":["../../src/composed/status-badge.tsx"],"sourcesContent":["'use client'\n\nimport { IconChevronDown } from '@tabler/icons-react'\nimport { AnimatePresence, motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Badge } from '../ui/badge'\nimport { Dot, type DotColor } from '../ui/dot'\nimport type { IconInput } from '../ui/lib/icon-input'\nimport { motionProps } from '../ui/lib/motion'\n\n/**\n * Domain work-statuses → the shared intent colour used by both the Badge pill\n * and the leading Dot. StatusBadge is a thin semantic wrapper: it owns the\n * status vocabulary and composes `<Badge>` + `<Dot>` rather than re-styling a pill.\n */\ntype StatusKey =\n | 'active' | 'pending' | 'approved' | 'rejected' | 'completed'\n | 'blocked' | 'in-progress' | 'review' | 'cancelled' | 'draft'\n\n// Intent colours shared by Badge + Dot (never 'current' — that's a Badge-internal mode).\ntype IntentColor = Exclude<DotColor, 'current'>\n\nconst STATUS_TO_COLOR: Record<StatusKey, IntentColor> = {\n active: 'success',\n approved: 'success',\n completed: 'success',\n pending: 'warning',\n rejected: 'error',\n blocked: 'error',\n 'in-progress': 'accent',\n review: 'info',\n cancelled: 'neutral',\n draft: 'neutral',\n}\n\ntype StatusColor = 'success' | 'warning' | 'error' | 'info' | 'neutral'\n\ninterface StatusBadgeBaseProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'children' | 'color'> {\n label?: string\n hideDot?: boolean\n size?: 'sm' | 'md'\n onClick?: () => void\n /** Trailing icon. When omitted on a clickable badge, a chevron is shown. */\n icon?: IconInput\n}\n\ninterface StatusBadgeWithStatus extends StatusBadgeBaseProps {\n status?: StatusKey\n color?: never\n}\n\ninterface StatusBadgeWithColor extends StatusBadgeBaseProps {\n status?: never\n color: StatusColor\n}\n\nexport type StatusBadgeProps = StatusBadgeWithStatus | StatusBadgeWithColor\n\n/* Between durations.moderate02 (0.24) and slow01 (0.4) — smooth status morph. */\nconst statusMorphTransition = { duration: 0.3, ease: 'easeOut' as const }\n\nconst titleCase = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)\n\nconst StatusBadge = React.forwardRef<HTMLElement, StatusBadgeProps>(\n ({ status, color, size = 'md', label, hideDot = false, onClick, icon, className, ...props }, ref) => {\n // A `color` wins if given; otherwise derive from `status` (default 'pending').\n const key = color ?? status ?? 'pending'\n const dotColor: IntentColor = color ?? STATUS_TO_COLOR[(status ?? 'pending') as StatusKey]\n const displayLabel = label ?? titleCase(color ?? status ?? 'pending').replace('-', ' ')\n const isClickable = onClick != null\n\n // Trailing: an explicit icon, else a chevron for clickable badges.\n const endIcon: IconInput | undefined = icon ?? (isClickable ? IconChevronDown : undefined)\n\n return (\n <AnimatePresence mode=\"wait\">\n <motion.span\n key={key}\n initial={{ opacity: 0.6, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0.6, scale: 0.95 }}\n transition={statusMorphTransition}\n className=\"inline-flex\"\n {...motionProps(props)}\n >\n <Badge\n ref={ref}\n variant=\"soft\"\n color={dotColor}\n size={size}\n onClick={onClick}\n startIcon={!hideDot ? <Dot color={dotColor} size=\"sm\" /> : undefined}\n endIcon={endIcon}\n className={className}\n >\n {displayLabel}\n </Badge>\n </motion.span>\n </AnimatePresence>\n )\n },\n)\nStatusBadge.displayName = 'StatusBadge'\n\nexport { StatusBadge }\n"],"mappings":";;;;;;;;;AAuBA,IAAM,IAAkD;CACtD,QAAQ;CACR,UAAU;CACV,WAAW;CACX,SAAS;CACT,UAAU;CACV,SAAS;CACT,eAAe;CACf,QAAQ;CACR,WAAW;CACX,OAAO;
|
|
1
|
+
{"version":3,"file":"status-badge.js","names":[],"sources":["../../src/composed/status-badge.tsx"],"sourcesContent":["'use client'\n\nimport { IconChevronDown } from '@tabler/icons-react'\nimport { AnimatePresence, motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Badge } from '../ui/badge'\nimport { Dot, type DotColor } from '../ui/dot'\nimport type { IconInput } from '../ui/lib/icon-input'\nimport { motionProps } from '../ui/lib/motion'\n\n/**\n * Domain work-statuses → the shared intent colour used by both the Badge pill\n * and the leading Dot. StatusBadge is a thin semantic wrapper: it owns the\n * status vocabulary and composes `<Badge>` + `<Dot>` rather than re-styling a pill.\n */\ntype StatusKey =\n | 'active' | 'pending' | 'approved' | 'rejected' | 'completed'\n | 'blocked' | 'in-progress' | 'review' | 'cancelled' | 'draft'\n\n// Intent colours shared by Badge + Dot (never 'current' — that's a Badge-internal mode).\ntype IntentColor = Exclude<DotColor, 'current'>\n\nconst STATUS_TO_COLOR: Record<StatusKey, IntentColor> = {\n active: 'success',\n approved: 'success',\n completed: 'success',\n pending: 'warning',\n rejected: 'error',\n blocked: 'error',\n 'in-progress': 'accent',\n review: 'info',\n cancelled: 'neutral',\n draft: 'neutral',\n}\n\ntype StatusColor = 'success' | 'warning' | 'error' | 'info' | 'neutral'\n\ninterface StatusBadgeBaseProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'children' | 'color'> {\n label?: string\n hideDot?: boolean\n size?: 'sm' | 'md'\n onClick?: () => void\n /** Trailing icon. When omitted on a clickable badge, a chevron is shown. */\n icon?: IconInput\n}\n\ninterface StatusBadgeWithStatus extends StatusBadgeBaseProps {\n status?: StatusKey\n color?: never\n}\n\ninterface StatusBadgeWithColor extends StatusBadgeBaseProps {\n status?: never\n color: StatusColor\n}\n\nexport type StatusBadgeProps = StatusBadgeWithStatus | StatusBadgeWithColor\n\n/* Between durations.moderate02 (0.24) and slow01 (0.4) — smooth status morph. */\nconst statusMorphTransition = { duration: 0.3, ease: 'easeOut' as const }\n\nconst titleCase = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)\n\nconst StatusBadge = React.forwardRef<HTMLElement, StatusBadgeProps>(\n ({ status, color, size = 'md', label, hideDot = false, onClick, icon, className, ...props }, ref) => {\n // A `color` wins if given; otherwise derive from `status` (default 'pending').\n const key = color ?? status ?? 'pending'\n const dotColor: IntentColor = color ?? STATUS_TO_COLOR[(status ?? 'pending') as StatusKey]\n const displayLabel = label ?? titleCase(color ?? status ?? 'pending').replace('-', ' ')\n const isClickable = onClick != null\n\n // Trailing: an explicit icon, else a chevron for clickable badges.\n const endIcon: IconInput | undefined = icon ?? (isClickable ? IconChevronDown : undefined)\n\n return (\n <AnimatePresence mode=\"wait\">\n <motion.span\n key={key}\n initial={{ opacity: 0.6, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0.6, scale: 0.95 }}\n transition={statusMorphTransition}\n className=\"inline-flex\"\n {...motionProps(props)}\n >\n <Badge\n ref={ref}\n variant=\"soft\"\n color={dotColor}\n size={size}\n onClick={onClick}\n startIcon={!hideDot ? <Dot color={dotColor} size=\"sm\" /> : undefined}\n endIcon={endIcon}\n className={className}\n >\n {displayLabel}\n </Badge>\n </motion.span>\n </AnimatePresence>\n )\n },\n)\nStatusBadge.displayName = 'StatusBadge'\n\nexport { StatusBadge }\n"],"mappings":";;;;;;;;;AAuBA,IAAM,IAAkD;CACtD,QAAQ;CACR,UAAU;CACV,WAAW;CACX,SAAS;CACT,UAAU;CACV,SAAS;CACT,eAAe;CACf,QAAQ;CACR,WAAW;CACX,OAAO;AACT,GA0BM,IAAwB;CAAE,UAAU;CAAK,MAAM;AAAmB,GAElE,KAAa,MAAc,EAAE,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,EAAE,MAAM,CAAC,GAEhE,IAAc,EAAM,YACvB,EAAE,WAAQ,UAAO,UAAO,MAAM,UAAO,aAAU,IAAO,YAAS,SAAM,cAAW,GAAG,KAAS,MAAQ;CAEnG,IAAM,IAAM,KAAS,KAAU,WACzB,IAAwB,KAAS,EAAiB,KAAU,YAC5D,IAAe,KAAS,EAAU,KAAS,KAAU,SAAS,CAAC,CAAC,QAAQ,KAAK,GAAG,GAIhF,IAAiC,MAHnB,KAAW,OAGiD,KAAA,IAAlB;CAE9D,OACE,kBAAC,GAAD;EAAiB,MAAK;YACpB,kBAAC,EAAO,MAAR;GAEE,SAAS;IAAE,SAAS;IAAK,OAAO;GAAK;GACrC,SAAS;IAAE,SAAS;IAAG,OAAO;GAAE;GAChC,MAAM;IAAE,SAAS;IAAK,OAAO;GAAK;GAClC,YAAY;GACZ,WAAU;GACV,GAAI,EAAY,CAAK;aAErB,kBAAC,GAAD;IACO;IACL,SAAQ;IACR,OAAO;IACD;IACG;IACT,WAAY,IAA+C,KAAA,IAArC,kBAAC,GAAD;KAAK,OAAO;KAAU,MAAK;IAAM,CAAA;IAC9C;IACE;cAEV;GACI,CAAA;EACI,GApBN,CAoBM;CACE,CAAA;AAErB,CACF;AACA,EAAY,cAAc"}
|
|
@@ -13,7 +13,8 @@ function i() {
|
|
|
13
13
|
let t = r(e);
|
|
14
14
|
document.documentElement.classList.toggle("dark", t === "dark"), localStorage.setItem("theme", e), document.cookie = `theme=${e};path=/;max-age=31536000;SameSite=Lax`, a(e);
|
|
15
15
|
}, []), s = e(() => {
|
|
16
|
-
|
|
16
|
+
let e = document.documentElement.classList.contains("dark");
|
|
17
|
+
o(e ? "light" : "dark");
|
|
17
18
|
}, [o]);
|
|
18
19
|
return t(() => {
|
|
19
20
|
if (i !== "system") return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-color-mode.js","names":[],"sources":["../../src/hooks/use-color-mode.ts"],"sourcesContent":["'use client'\n\nimport { useCallback, useEffect,useState } from 'react'\n\nexport type ColorMode = 'light' | 'dark' | 'system'\n\nfunction resolveMode(mode: ColorMode): 'light' | 'dark' {\n if (mode === 'system') {\n if (typeof window === 'undefined') return 'light'\n return window.matchMedia('(prefers-color-scheme: dark)').matches\n ? 'dark'\n : 'light'\n }\n return mode\n}\n\nexport function useColorMode() {\n const [mode, setModeState] = useState<ColorMode>(() => {\n if (typeof window === 'undefined') return 'system'\n const stored = localStorage.getItem('theme')\n return stored === 'light' || stored === 'dark' || stored === 'system' ? stored : 'system'\n })\n\n const setColorMode = useCallback((newMode: ColorMode) => {\n const resolved = resolveMode(newMode)\n document.documentElement.classList.toggle('dark', resolved === 'dark')\n localStorage.setItem('theme', newMode)\n document.cookie = `theme=${newMode};path=/;max-age=31536000;SameSite=Lax`\n setModeState(newMode)\n }, [])\n\n const toggleColorMode = useCallback(() => {\n const isDark = document.documentElement.classList.contains('dark')\n setColorMode(isDark ? 'light' : 'dark')\n }, [setColorMode])\n\n useEffect(() => {\n if (mode !== 'system') return\n const mq = window.matchMedia('(prefers-color-scheme: dark)')\n const handler = (e: MediaQueryListEvent) => {\n document.documentElement.classList.toggle('dark', e.matches)\n }\n mq.addEventListener('change', handler)\n return () => mq.removeEventListener('change', handler)\n }, [mode])\n\n return { colorMode: mode, setColorMode, toggleColorMode } as const\n}\n"],"mappings":";;;AAMA,SAAS,EAAY,GAAmC;
|
|
1
|
+
{"version":3,"file":"use-color-mode.js","names":[],"sources":["../../src/hooks/use-color-mode.ts"],"sourcesContent":["'use client'\n\nimport { useCallback, useEffect,useState } from 'react'\n\nexport type ColorMode = 'light' | 'dark' | 'system'\n\nfunction resolveMode(mode: ColorMode): 'light' | 'dark' {\n if (mode === 'system') {\n if (typeof window === 'undefined') return 'light'\n return window.matchMedia('(prefers-color-scheme: dark)').matches\n ? 'dark'\n : 'light'\n }\n return mode\n}\n\nexport function useColorMode() {\n const [mode, setModeState] = useState<ColorMode>(() => {\n if (typeof window === 'undefined') return 'system'\n const stored = localStorage.getItem('theme')\n return stored === 'light' || stored === 'dark' || stored === 'system' ? stored : 'system'\n })\n\n const setColorMode = useCallback((newMode: ColorMode) => {\n const resolved = resolveMode(newMode)\n document.documentElement.classList.toggle('dark', resolved === 'dark')\n localStorage.setItem('theme', newMode)\n document.cookie = `theme=${newMode};path=/;max-age=31536000;SameSite=Lax`\n setModeState(newMode)\n }, [])\n\n const toggleColorMode = useCallback(() => {\n const isDark = document.documentElement.classList.contains('dark')\n setColorMode(isDark ? 'light' : 'dark')\n }, [setColorMode])\n\n useEffect(() => {\n if (mode !== 'system') return\n const mq = window.matchMedia('(prefers-color-scheme: dark)')\n const handler = (e: MediaQueryListEvent) => {\n document.documentElement.classList.toggle('dark', e.matches)\n }\n mq.addEventListener('change', handler)\n return () => mq.removeEventListener('change', handler)\n }, [mode])\n\n return { colorMode: mode, setColorMode, toggleColorMode } as const\n}\n"],"mappings":";;;AAMA,SAAS,EAAY,GAAmC;CAOtD,OANI,MAAS,WACP,OAAO,SAAW,MAAoB,UACnC,OAAO,WAAW,8BAA8B,CAAC,CAAC,UACrD,SACA,UAEC;AACT;AAEA,SAAgB,IAAe;CAC7B,IAAM,CAAC,GAAM,KAAgB,QAA0B;EACrD,IAAI,OAAO,SAAW,KAAa,OAAO;EAC1C,IAAM,IAAS,aAAa,QAAQ,OAAO;EAC3C,OAAO,MAAW,WAAW,MAAW,UAAU,MAAW,WAAW,IAAS;CACnF,CAAC,GAEK,IAAe,GAAa,MAAuB;EACvD,IAAM,IAAW,EAAY,CAAO;EAIpC,AAHA,SAAS,gBAAgB,UAAU,OAAO,QAAQ,MAAa,MAAM,GACrE,aAAa,QAAQ,SAAS,CAAO,GACrC,SAAS,SAAS,SAAS,EAAQ,wCACnC,EAAa,CAAO;CACtB,GAAG,CAAC,CAAC,GAEC,IAAkB,QAAkB;EACxC,IAAM,IAAS,SAAS,gBAAgB,UAAU,SAAS,MAAM;EACjE,EAAa,IAAS,UAAU,MAAM;CACxC,GAAG,CAAC,CAAY,CAAC;CAYjB,OAVA,QAAgB;EACd,IAAI,MAAS,UAAU;EACvB,IAAM,IAAK,OAAO,WAAW,8BAA8B,GACrD,KAAW,MAA2B;GAC1C,SAAS,gBAAgB,UAAU,OAAO,QAAQ,EAAE,OAAO;EAC7D;EAEA,OADA,EAAG,iBAAiB,UAAU,CAAO,SACxB,EAAG,oBAAoB,UAAU,CAAO;CACvD,GAAG,CAAC,CAAI,CAAC,GAEF;EAAE,WAAW;EAAM;EAAc;CAAgB;AAC1D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-mobile.js","names":[],"sources":["../../src/hooks/use-mobile.ts"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\nconst MOBILE_BREAKPOINT = 768\n\n/**\n * Returns `false` during SSR and before the first client-side effect.\n * If you need to distinguish \"not yet measured\" from \"not mobile\",\n * use the raw state instead.\n */\nexport function useIsMobile() {\n const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)\n\n React.useEffect(() => {\n const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)\n const onChange = () => {\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)\n }\n mql.addEventListener('change', onChange)\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)\n return () => mql.removeEventListener('change', onChange)\n }, [])\n\n return !!isMobile\n}\n"],"mappings":";;;AAIA,IAAM,IAAoB;AAO1B,SAAgB,IAAc;CAC5B,IAAM,CAAC,GAAU,KAAe,EAAM,SAA8B,KAAA,
|
|
1
|
+
{"version":3,"file":"use-mobile.js","names":[],"sources":["../../src/hooks/use-mobile.ts"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\nconst MOBILE_BREAKPOINT = 768\n\n/**\n * Returns `false` during SSR and before the first client-side effect.\n * If you need to distinguish \"not yet measured\" from \"not mobile\",\n * use the raw state instead.\n */\nexport function useIsMobile() {\n const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)\n\n React.useEffect(() => {\n const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)\n const onChange = () => {\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)\n }\n mql.addEventListener('change', onChange)\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)\n return () => mql.removeEventListener('change', onChange)\n }, [])\n\n return !!isMobile\n}\n"],"mappings":";;;AAIA,IAAM,IAAoB;AAO1B,SAAgB,IAAc;CAC5B,IAAM,CAAC,GAAU,KAAe,EAAM,SAA8B,KAAA,CAAS;CAY7E,OAVA,EAAM,gBAAgB;EACpB,IAAM,IAAM,OAAO,WAAW,eAAe,IAAoB,EAAE,IAAI,GACjE,UAAiB;GACrB,EAAY,OAAO,aAAa,CAAiB;EACnD;EAGA,OAFA,EAAI,iBAAiB,UAAU,CAAQ,GACvC,EAAY,OAAO,aAAa,CAAiB,SACpC,EAAI,oBAAoB,UAAU,CAAQ;CACzD,GAAG,CAAC,CAAC,GAEE,CAAC,CAAC;AACX"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-touch-device.js","names":[],"sources":["../../src/hooks/use-touch-device.ts"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\n/**\n * Detects whether the device supports touch input.\n *\n * Use `useIsMobile()` for layout decisions (viewport width).\n * Use `useTouchDevice()` for interaction decisions (hover vs touch).\n *\n * SSR-safe: returns false on server.\n * Stable: only checks on mount, doesn't change on resize.\n */\nexport function useTouchDevice(): boolean {\n const [isTouch, setIsTouch] = React.useState(false)\n\n React.useEffect(() => {\n setIsTouch(\n 'ontouchstart' in window || navigator.maxTouchPoints > 0\n )\n }, [])\n\n return isTouch\n}\n"],"mappings":";;;AAaA,SAAgB,IAA0B;CACxC,IAAM,CAAC,GAAS,KAAc,EAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"use-touch-device.js","names":[],"sources":["../../src/hooks/use-touch-device.ts"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\n/**\n * Detects whether the device supports touch input.\n *\n * Use `useIsMobile()` for layout decisions (viewport width).\n * Use `useTouchDevice()` for interaction decisions (hover vs touch).\n *\n * SSR-safe: returns false on server.\n * Stable: only checks on mount, doesn't change on resize.\n */\nexport function useTouchDevice(): boolean {\n const [isTouch, setIsTouch] = React.useState(false)\n\n React.useEffect(() => {\n setIsTouch(\n 'ontouchstart' in window || navigator.maxTouchPoints > 0\n )\n }, [])\n\n return isTouch\n}\n"],"mappings":";;;AAaA,SAAgB,IAA0B;CACxC,IAAM,CAAC,GAAS,KAAc,EAAM,SAAS,EAAK;CAQlD,OANA,EAAM,gBAAgB;EACpB,EACE,kBAAkB,UAAU,UAAU,iBAAiB,CACzD;CACF,GAAG,CAAC,CAAC,GAEE;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-viewport-height.js","names":[],"sources":["../../src/hooks/use-viewport-height.ts"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\n/**\n * Returns the current dynamic viewport height in pixels.\n *\n * Unlike CSS `100vh`, this accounts for mobile browser toolbar\n * show/hide and virtual keyboard appearance via the Visual Viewport API.\n *\n * SSR-safe: returns 0 on server (check for 0 to detect SSR).\n */\nexport function useViewportHeight(): number {\n const [height, setHeight] = React.useState(0)\n\n React.useEffect(() => {\n const update = () => {\n setHeight(window.visualViewport?.height ?? window.innerHeight)\n }\n\n update()\n\n const vv = window.visualViewport\n if (vv) {\n vv.addEventListener('resize', update)\n return () => vv.removeEventListener('resize', update)\n } else {\n window.addEventListener('resize', update)\n return () => window.removeEventListener('resize', update)\n }\n }, [])\n\n return height\n}\n"],"mappings":";;;AAYA,SAAgB,IAA4B;CAC1C,IAAM,CAAC,GAAQ,KAAa,EAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"use-viewport-height.js","names":[],"sources":["../../src/hooks/use-viewport-height.ts"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\n/**\n * Returns the current dynamic viewport height in pixels.\n *\n * Unlike CSS `100vh`, this accounts for mobile browser toolbar\n * show/hide and virtual keyboard appearance via the Visual Viewport API.\n *\n * SSR-safe: returns 0 on server (check for 0 to detect SSR).\n */\nexport function useViewportHeight(): number {\n const [height, setHeight] = React.useState(0)\n\n React.useEffect(() => {\n const update = () => {\n setHeight(window.visualViewport?.height ?? window.innerHeight)\n }\n\n update()\n\n const vv = window.visualViewport\n if (vv) {\n vv.addEventListener('resize', update)\n return () => vv.removeEventListener('resize', update)\n } else {\n window.addEventListener('resize', update)\n return () => window.removeEventListener('resize', update)\n }\n }, [])\n\n return height\n}\n"],"mappings":";;;AAYA,SAAgB,IAA4B;CAC1C,IAAM,CAAC,GAAQ,KAAa,EAAM,SAAS,CAAC;CAmB5C,OAjBA,EAAM,gBAAgB;EACpB,IAAM,UAAe;GACnB,EAAU,OAAO,gBAAgB,UAAU,OAAO,WAAW;EAC/D;EAEA,EAAO;EAEP,IAAM,IAAK,OAAO;EAMhB,OALE,KACF,EAAG,iBAAiB,UAAU,CAAM,SACvB,EAAG,oBAAoB,UAAU,CAAM,MAEpD,OAAO,iBAAiB,UAAU,CAAM,SAC3B,OAAO,oBAAoB,UAAU,CAAM;CAE5D,GAAG,CAAC,CAAC,GAEE;AACT"}
|
package/dist/motion/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/motion/check-motion-provider.ts","../../src/motion/index.ts"],"sourcesContent":["/**\n * Dev-mode hint for missing MotionProvider.\n *\n * If the consumer hasn't wrapped their app in <MotionProvider>, Framer Motion\n * animations still work but reduced-motion preferences won't propagate through\n * MotionConfig. This check emits a one-time console.info in dev mode.\n *\n * Only runs once, only in development, only in the browser.\n */\nlet checked = false\n\nexport function checkMotionProvider(): void {\n if (checked) return\n if (typeof window === 'undefined') return\n\n // Skip in production\n try {\n // @ts-expect-error -- import.meta.env is Vite-specific, not in base TS lib\n if (import.meta.env?.PROD) return\n } catch { /* not in Vite */ }\n\n checked = true\n\n // eslint-disable-next-line no-console\n console.info(\n '[shilp-sutra] Tip: Wrap your app in <MotionProvider> for reduced-motion support ' +\n 'across all shilp-sutra animations. See https://www.npmjs.com/package/@devalok/shilp-sutra',\n )\n}\n","export { motionProps, type SpringPreset, springs, stagger, type TweenPreset,tweens, withReducedMotion } from '../ui/lib/motion'\nexport { MotionProvider, type MotionProviderProps,useMotion } from './motion-provider'\n\nimport { checkMotionProvider } from './check-motion-provider'\ncheckMotionProvider()\n"],"mappings":";;;AASA,IAAI,IAAU;AAEd,SAAgB,IAA4B;
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/motion/check-motion-provider.ts","../../src/motion/index.ts"],"sourcesContent":["/**\n * Dev-mode hint for missing MotionProvider.\n *\n * If the consumer hasn't wrapped their app in <MotionProvider>, Framer Motion\n * animations still work but reduced-motion preferences won't propagate through\n * MotionConfig. This check emits a one-time console.info in dev mode.\n *\n * Only runs once, only in development, only in the browser.\n */\nlet checked = false\n\nexport function checkMotionProvider(): void {\n if (checked) return\n if (typeof window === 'undefined') return\n\n // Skip in production\n try {\n // @ts-expect-error -- import.meta.env is Vite-specific, not in base TS lib\n if (import.meta.env?.PROD) return\n } catch { /* not in Vite */ }\n\n checked = true\n\n // eslint-disable-next-line no-console\n console.info(\n '[shilp-sutra] Tip: Wrap your app in <MotionProvider> for reduced-motion support ' +\n 'across all shilp-sutra animations. See https://www.npmjs.com/package/@devalok/shilp-sutra',\n )\n}\n","export { motionProps, type SpringPreset, springs, stagger, type TweenPreset,tweens, withReducedMotion } from '../ui/lib/motion'\nexport { MotionProvider, type MotionProviderProps,useMotion } from './motion-provider'\n\nimport { checkMotionProvider } from './check-motion-provider'\ncheckMotionProvider()\n"],"mappings":";;;AASA,IAAI,IAAU;AAEd,SAAgB,IAA4B;CACtC,UACA,SAAO,SAAW,MAGtB;MAAI;GAEyB;EAC7B,QAAQ,CAAoB;EAK5B,AAHA,IAAU,IAGV,QAAQ,KACN,2KAEF;CAR4B;AAS9B;;;ACxBA,EAAoB"}
|
|
@@ -12,7 +12,7 @@ type MotionProviderProps = {
|
|
|
12
12
|
/** 'user' = detect OS preference, true = force off, false = force on */
|
|
13
13
|
reducedMotion?: ReducedMotionMode;
|
|
14
14
|
};
|
|
15
|
-
declare function MotionProvider({ children, reducedMotion }: MotionProviderProps):
|
|
15
|
+
declare function MotionProvider({ children, reducedMotion }: MotionProviderProps): React.JSX.Element;
|
|
16
16
|
declare function useMotion(): MotionContextValue;
|
|
17
17
|
export { MotionProvider, type MotionProviderProps, useMotion };
|
|
18
18
|
//# sourceMappingURL=motion-provider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"motion-provider.d.ts","sourceRoot":"","sources":["../../src/motion/motion-provider.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAElD,KAAK,iBAAiB,GAAG,MAAM,GAAG,OAAO,CAAA;AAEzC,KAAK,kBAAkB,GAAG;IACxB,OAAO,EAAE,OAAO,OAAO,CAAA;IACvB,MAAM,EAAE,OAAO,MAAM,CAAA;IACrB,aAAa,EAAE,OAAO,CAAA;CACvB,CAAA;AAQD,KAAK,mBAAmB,GAAG;IACzB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,wEAAwE;IACxE,aAAa,CAAC,EAAE,iBAAiB,CAAA;CAClC,CAAA;AAED,iBAAS,cAAc,CAAC,EAAE,QAAQ,EAAE,aAAsB,EAAE,EAAE,mBAAmB,
|
|
1
|
+
{"version":3,"file":"motion-provider.d.ts","sourceRoot":"","sources":["../../src/motion/motion-provider.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAElD,KAAK,iBAAiB,GAAG,MAAM,GAAG,OAAO,CAAA;AAEzC,KAAK,kBAAkB,GAAG;IACxB,OAAO,EAAE,OAAO,OAAO,CAAA;IACvB,MAAM,EAAE,OAAO,MAAM,CAAA;IACrB,aAAa,EAAE,OAAO,CAAA;CACvB,CAAA;AAQD,KAAK,mBAAmB,GAAG;IACzB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,wEAAwE;IACxE,aAAa,CAAC,EAAE,iBAAiB,CAAA;CAClC,CAAA;AAED,iBAAS,cAAc,CAAC,EAAE,QAAQ,EAAE,aAAsB,EAAE,EAAE,mBAAmB,qBAgBhF;AAED,iBAAS,SAAS,uBAEjB;AAED,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAC,SAAS,EAAE,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"primitives-index.js","names":[],"sources":["../../src/motion/primitives.tsx"],"sourcesContent":["'use client'\n\nimport { AnimatePresence, type HTMLMotionProps,motion, type Transition, type Variants } from 'framer-motion'\nimport * as React from 'react'\n\nimport type { SpringPreset } from '../ui/lib/motion'\nimport { springs, tweens } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\n\n// ── Shared types ──\n\ntype MotionDivProps = Omit<HTMLMotionProps<'div'>, 'ref'>\n\ntype MotionPrimitiveProps = {\n show: boolean\n children: React.ReactNode\n className?: string\n preset?: SpringPreset\n layout?: boolean | 'position' | 'size' | 'preserve-aspect'\n layoutId?: string\n whileInView?: boolean\n viewportOnce?: boolean\n viewportMargin?: string\n}\n\n// ── MotionFade ──\n// Default: fade tween only (no spring needed for pure opacity)\n\nconst MotionFade = React.forwardRef<HTMLDivElement, MotionPrimitiveProps & MotionDivProps>(\n ({ show, children, className, layout, layoutId, whileInView, viewportOnce = true, viewportMargin = '-50px', ...rest }, ref) => {\n const inViewProps = whileInView ? {\n initial: 'hidden',\n whileInView: 'visible',\n viewport: { once: viewportOnce, margin: viewportMargin },\n } : {}\n\n return (\n <AnimatePresence>\n {show && (\n <motion.div\n ref={ref}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n className={cn(className)}\n layout={layout}\n layoutId={layoutId}\n {...inViewProps}\n {...rest}\n >\n {children}\n </motion.div>\n )}\n </AnimatePresence>\n )\n },\n)\nMotionFade.displayName = 'MotionFade'\n\n// ── MotionScale ──\n// Default: snappy spring for scale + fade tween for opacity\n\nconst MotionScale = React.forwardRef<HTMLDivElement, MotionPrimitiveProps & MotionDivProps>(\n ({ show, children, className, preset = 'snappy', layout, layoutId, whileInView, viewportOnce = true, viewportMargin = '-50px', ...rest }, ref) => {\n const spring = springs[preset]\n const inViewProps = whileInView ? {\n initial: 'hidden',\n whileInView: 'visible',\n viewport: { once: viewportOnce, margin: viewportMargin },\n } : {}\n\n return (\n <AnimatePresence>\n {show && (\n <motion.div\n ref={ref}\n initial={{ opacity: 0, scale: 0.96 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.96 }}\n transition={{ ...spring, opacity: tweens.fade }}\n className={cn(className)}\n layout={layout}\n layoutId={layoutId}\n {...inViewProps}\n {...rest}\n >\n {children}\n </motion.div>\n )}\n </AnimatePresence>\n )\n },\n)\nMotionScale.displayName = 'MotionScale'\n\n// ── MotionPop ──\n// Default: bouncy spring with overshoot for scale + fade\n\nconst MotionPop = React.forwardRef<HTMLDivElement, MotionPrimitiveProps & MotionDivProps>(\n ({ show, children, className, preset = 'bouncy', layout, layoutId, whileInView, viewportOnce = true, viewportMargin = '-50px', ...rest }, ref) => {\n const spring = springs[preset]\n const inViewProps = whileInView ? {\n initial: 'hidden',\n whileInView: 'visible',\n viewport: { once: viewportOnce, margin: viewportMargin },\n } : {}\n\n return (\n <AnimatePresence>\n {show && (\n <motion.div\n ref={ref}\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={{ ...spring, opacity: tweens.fade }}\n className={cn(className)}\n layout={layout}\n layoutId={layoutId}\n {...inViewProps}\n {...rest}\n >\n {children}\n </motion.div>\n )}\n </AnimatePresence>\n )\n },\n)\nMotionPop.displayName = 'MotionPop'\n\n// ── MotionSlide ──\n// Default: smooth spring for translate + fade\n\nconst MotionSlide = React.forwardRef<\n HTMLDivElement,\n MotionPrimitiveProps & { direction?: 'up' | 'down' | 'left' | 'right' } & MotionDivProps\n>(\n ({ show, children, className, preset = 'smooth', direction = 'up', layout, layoutId, whileInView, viewportOnce = true, viewportMargin = '-50px', ...rest }, ref) => {\n const spring = springs[preset]\n const offsets = {\n up: { y: 16 },\n down: { y: -16 },\n left: { x: 16 },\n right: { x: -16 },\n }\n const offset = offsets[direction]\n const inViewProps = whileInView ? {\n initial: 'hidden',\n whileInView: 'visible',\n viewport: { once: viewportOnce, margin: viewportMargin },\n } : {}\n\n return (\n <AnimatePresence>\n {show && (\n <motion.div\n ref={ref}\n initial={{ opacity: 0, ...offset }}\n animate={{ opacity: 1, x: 0, y: 0 }}\n exit={{ opacity: 0, ...offset }}\n transition={{ ...spring, opacity: tweens.fade }}\n className={cn(className)}\n layout={layout}\n layoutId={layoutId}\n {...inViewProps}\n {...rest}\n >\n {children}\n </motion.div>\n )}\n </AnimatePresence>\n )\n },\n)\nMotionSlide.displayName = 'MotionSlide'\n\n// ── MotionCollapse ──\n// Default: gentle spring for height + fade\n// Uses height: \"auto\" — Framer Motion handles this natively\n\nconst MotionCollapse = React.forwardRef<HTMLDivElement, MotionPrimitiveProps & MotionDivProps>(\n ({ show, children, className, preset = 'gentle', layout, layoutId, ...rest }, ref) => {\n const spring = springs[preset]\n\n return (\n <AnimatePresence initial={false}>\n {show && (\n <motion.div\n ref={ref}\n initial={{ height: 0, opacity: 0, overflow: 'hidden' }}\n animate={{ height: 'auto', opacity: 1, overflow: 'hidden' }}\n exit={{ height: 0, opacity: 0, overflow: 'hidden' }}\n transition={{ ...spring, opacity: tweens.fade }}\n className={cn(className)}\n layout={layout}\n layoutId={layoutId}\n {...rest}\n >\n {children}\n </motion.div>\n )}\n </AnimatePresence>\n )\n },\n)\nMotionCollapse.displayName = 'MotionCollapse'\n\n// ── MotionStagger + MotionStaggerItem ──\n// Parent orchestrates children with staggerChildren\n\ntype MotionStaggerProps = {\n children: React.ReactNode\n className?: string\n delay?: number\n whileInView?: boolean\n viewportOnce?: boolean\n viewportMargin?: string\n}\n\nconst MotionStagger = React.forwardRef<HTMLDivElement, MotionStaggerProps & MotionDivProps>(\n ({ children, className, delay = 0.04, whileInView, viewportOnce = true, viewportMargin = '-50px', ...rest }, ref) => {\n const variants: Variants = {\n hidden: {},\n visible: {\n transition: { staggerChildren: delay },\n },\n }\n\n const viewProps = whileInView\n ? { initial: 'hidden' as const, whileInView: 'visible' as const, viewport: { once: viewportOnce, margin: viewportMargin } }\n : { initial: 'hidden' as const, animate: 'visible' as const }\n\n return (\n <motion.div\n ref={ref}\n variants={variants}\n className={cn(className)}\n {...viewProps}\n {...rest}\n >\n {children}\n </motion.div>\n )\n },\n)\nMotionStagger.displayName = 'MotionStagger'\n\nconst itemVariants: Variants = {\n hidden: { opacity: 0, y: 8 },\n visible: {\n opacity: 1,\n y: 0,\n transition: { ...springs.smooth, opacity: tweens.fade },\n },\n}\n\nconst MotionStaggerItem = React.forwardRef<HTMLDivElement, { children: React.ReactNode; className?: string } & MotionDivProps>(\n ({ children, className, ...rest }, ref) => {\n return (\n <motion.div\n ref={ref}\n variants={itemVariants}\n className={cn(className)}\n {...rest}\n >\n {children}\n </motion.div>\n )\n },\n)\nMotionStaggerItem.displayName = 'MotionStaggerItem'\n\nexport {\n MotionCollapse,\n MotionFade,\n MotionPop,\n type MotionPrimitiveProps,\n MotionScale,\n MotionSlide,\n MotionStagger,\n MotionStaggerItem,\n type MotionStaggerProps,\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"primitives-index.js","names":[],"sources":["../../src/motion/primitives.tsx"],"sourcesContent":["'use client'\n\nimport { AnimatePresence, type HTMLMotionProps,motion, type Transition, type Variants } from 'framer-motion'\nimport * as React from 'react'\n\nimport type { SpringPreset } from '../ui/lib/motion'\nimport { springs, tweens } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\n\n// ── Shared types ──\n\ntype MotionDivProps = Omit<HTMLMotionProps<'div'>, 'ref'>\n\ntype MotionPrimitiveProps = {\n show: boolean\n children: React.ReactNode\n className?: string\n preset?: SpringPreset\n layout?: boolean | 'position' | 'size' | 'preserve-aspect'\n layoutId?: string\n whileInView?: boolean\n viewportOnce?: boolean\n viewportMargin?: string\n}\n\n// ── MotionFade ──\n// Default: fade tween only (no spring needed for pure opacity)\n\nconst MotionFade = React.forwardRef<HTMLDivElement, MotionPrimitiveProps & MotionDivProps>(\n ({ show, children, className, layout, layoutId, whileInView, viewportOnce = true, viewportMargin = '-50px', ...rest }, ref) => {\n const inViewProps = whileInView ? {\n initial: 'hidden',\n whileInView: 'visible',\n viewport: { once: viewportOnce, margin: viewportMargin },\n } : {}\n\n return (\n <AnimatePresence>\n {show && (\n <motion.div\n ref={ref}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n className={cn(className)}\n layout={layout}\n layoutId={layoutId}\n {...inViewProps}\n {...rest}\n >\n {children}\n </motion.div>\n )}\n </AnimatePresence>\n )\n },\n)\nMotionFade.displayName = 'MotionFade'\n\n// ── MotionScale ──\n// Default: snappy spring for scale + fade tween for opacity\n\nconst MotionScale = React.forwardRef<HTMLDivElement, MotionPrimitiveProps & MotionDivProps>(\n ({ show, children, className, preset = 'snappy', layout, layoutId, whileInView, viewportOnce = true, viewportMargin = '-50px', ...rest }, ref) => {\n const spring = springs[preset]\n const inViewProps = whileInView ? {\n initial: 'hidden',\n whileInView: 'visible',\n viewport: { once: viewportOnce, margin: viewportMargin },\n } : {}\n\n return (\n <AnimatePresence>\n {show && (\n <motion.div\n ref={ref}\n initial={{ opacity: 0, scale: 0.96 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.96 }}\n transition={{ ...spring, opacity: tweens.fade }}\n className={cn(className)}\n layout={layout}\n layoutId={layoutId}\n {...inViewProps}\n {...rest}\n >\n {children}\n </motion.div>\n )}\n </AnimatePresence>\n )\n },\n)\nMotionScale.displayName = 'MotionScale'\n\n// ── MotionPop ──\n// Default: bouncy spring with overshoot for scale + fade\n\nconst MotionPop = React.forwardRef<HTMLDivElement, MotionPrimitiveProps & MotionDivProps>(\n ({ show, children, className, preset = 'bouncy', layout, layoutId, whileInView, viewportOnce = true, viewportMargin = '-50px', ...rest }, ref) => {\n const spring = springs[preset]\n const inViewProps = whileInView ? {\n initial: 'hidden',\n whileInView: 'visible',\n viewport: { once: viewportOnce, margin: viewportMargin },\n } : {}\n\n return (\n <AnimatePresence>\n {show && (\n <motion.div\n ref={ref}\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={{ ...spring, opacity: tweens.fade }}\n className={cn(className)}\n layout={layout}\n layoutId={layoutId}\n {...inViewProps}\n {...rest}\n >\n {children}\n </motion.div>\n )}\n </AnimatePresence>\n )\n },\n)\nMotionPop.displayName = 'MotionPop'\n\n// ── MotionSlide ──\n// Default: smooth spring for translate + fade\n\nconst MotionSlide = React.forwardRef<\n HTMLDivElement,\n MotionPrimitiveProps & { direction?: 'up' | 'down' | 'left' | 'right' } & MotionDivProps\n>(\n ({ show, children, className, preset = 'smooth', direction = 'up', layout, layoutId, whileInView, viewportOnce = true, viewportMargin = '-50px', ...rest }, ref) => {\n const spring = springs[preset]\n const offsets = {\n up: { y: 16 },\n down: { y: -16 },\n left: { x: 16 },\n right: { x: -16 },\n }\n const offset = offsets[direction]\n const inViewProps = whileInView ? {\n initial: 'hidden',\n whileInView: 'visible',\n viewport: { once: viewportOnce, margin: viewportMargin },\n } : {}\n\n return (\n <AnimatePresence>\n {show && (\n <motion.div\n ref={ref}\n initial={{ opacity: 0, ...offset }}\n animate={{ opacity: 1, x: 0, y: 0 }}\n exit={{ opacity: 0, ...offset }}\n transition={{ ...spring, opacity: tweens.fade }}\n className={cn(className)}\n layout={layout}\n layoutId={layoutId}\n {...inViewProps}\n {...rest}\n >\n {children}\n </motion.div>\n )}\n </AnimatePresence>\n )\n },\n)\nMotionSlide.displayName = 'MotionSlide'\n\n// ── MotionCollapse ──\n// Default: gentle spring for height + fade\n// Uses height: \"auto\" — Framer Motion handles this natively\n\nconst MotionCollapse = React.forwardRef<HTMLDivElement, MotionPrimitiveProps & MotionDivProps>(\n ({ show, children, className, preset = 'gentle', layout, layoutId, ...rest }, ref) => {\n const spring = springs[preset]\n\n return (\n <AnimatePresence initial={false}>\n {show && (\n <motion.div\n ref={ref}\n initial={{ height: 0, opacity: 0, overflow: 'hidden' }}\n animate={{ height: 'auto', opacity: 1, overflow: 'hidden' }}\n exit={{ height: 0, opacity: 0, overflow: 'hidden' }}\n transition={{ ...spring, opacity: tweens.fade }}\n className={cn(className)}\n layout={layout}\n layoutId={layoutId}\n {...rest}\n >\n {children}\n </motion.div>\n )}\n </AnimatePresence>\n )\n },\n)\nMotionCollapse.displayName = 'MotionCollapse'\n\n// ── MotionStagger + MotionStaggerItem ──\n// Parent orchestrates children with staggerChildren\n\ntype MotionStaggerProps = {\n children: React.ReactNode\n className?: string\n delay?: number\n whileInView?: boolean\n viewportOnce?: boolean\n viewportMargin?: string\n}\n\nconst MotionStagger = React.forwardRef<HTMLDivElement, MotionStaggerProps & MotionDivProps>(\n ({ children, className, delay = 0.04, whileInView, viewportOnce = true, viewportMargin = '-50px', ...rest }, ref) => {\n const variants: Variants = {\n hidden: {},\n visible: {\n transition: { staggerChildren: delay },\n },\n }\n\n const viewProps = whileInView\n ? { initial: 'hidden' as const, whileInView: 'visible' as const, viewport: { once: viewportOnce, margin: viewportMargin } }\n : { initial: 'hidden' as const, animate: 'visible' as const }\n\n return (\n <motion.div\n ref={ref}\n variants={variants}\n className={cn(className)}\n {...viewProps}\n {...rest}\n >\n {children}\n </motion.div>\n )\n },\n)\nMotionStagger.displayName = 'MotionStagger'\n\nconst itemVariants: Variants = {\n hidden: { opacity: 0, y: 8 },\n visible: {\n opacity: 1,\n y: 0,\n transition: { ...springs.smooth, opacity: tweens.fade },\n },\n}\n\nconst MotionStaggerItem = React.forwardRef<HTMLDivElement, { children: React.ReactNode; className?: string } & MotionDivProps>(\n ({ children, className, ...rest }, ref) => {\n return (\n <motion.div\n ref={ref}\n variants={itemVariants}\n className={cn(className)}\n {...rest}\n >\n {children}\n </motion.div>\n )\n },\n)\nMotionStaggerItem.displayName = 'MotionStaggerItem'\n\nexport {\n MotionCollapse,\n MotionFade,\n MotionPop,\n type MotionPrimitiveProps,\n MotionScale,\n MotionSlide,\n MotionStagger,\n MotionStaggerItem,\n type MotionStaggerProps,\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-dismissable-layer.d.ts","sourceRoot":"","sources":["../../../src/primitives/_internal/react-dismissable-layer.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,SAAS,EAA+B,MAAM,mBAAmB,CAAC;AAmB3E,KAAK,iBAAiB,GAAG,KAAK,CAAC,wBAAwB,CAAC,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;AAC9E,UAAU,qBAAsB,SAAQ,iBAAiB;IACvD,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACjD,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,CAAC;IAChE,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACpD,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,GAAG,iBAAiB,KAAK,IAAI,CAAC;IACjF,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,QAAA,MAAM,gBAAgB,8FAuGrB,CAAC;AAWF,UAAU,2BAA4B,SAAQ,iBAAiB;CAAG;AAElE,QAAA,MAAM,sBAAsB,oGAiB1B,CAAC;AAMH,KAAK,uBAAuB,GAAG,WAAW,CAAC;IAAE,aAAa,EAAE,YAAY,CAAA;CAAE,CAAC,CAAC;AAC5E,KAAK,iBAAiB,GAAG,WAAW,CAAC;IAAE,aAAa,EAAE,UAAU,CAAA;CAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"react-dismissable-layer.d.ts","sourceRoot":"","sources":["../../../src/primitives/_internal/react-dismissable-layer.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,SAAS,EAA+B,MAAM,mBAAmB,CAAC;AAmB3E,KAAK,iBAAiB,GAAG,KAAK,CAAC,wBAAwB,CAAC,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;AAC9E,UAAU,qBAAsB,SAAQ,iBAAiB;IACvD,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACjD,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,CAAC;IAChE,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACpD,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,GAAG,iBAAiB,KAAK,IAAI,CAAC;IACjF,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,QAAA,MAAM,gBAAgB,8FAuGrB,CAAC;AAWF,UAAU,2BAA4B,SAAQ,iBAAiB;CAAG;AAElE,QAAA,MAAM,sBAAsB,oGAiB1B,CAAC;AAMH,KAAK,uBAAuB,GAAG,WAAW,CAAC;IAAE,aAAa,EAAE,YAAY,CAAA;CAAE,CAAC,CAAC;AAC5E,KAAK,iBAAiB,GAAG,WAAW,CAAC;IAAE,aAAa,EAAE,UAAU,CAAA;CAAE,CAAC,CAAC;AAuGpE,QAAA,MAAM,IAAI,8FAAmB,CAAC;AAC9B,QAAA,MAAM,MAAM,oGAAyB,CAAC;AAEtC,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,IAAI,EACJ,MAAM,GACP,CAAC;AACF,YAAY,EAAE,qBAAqB,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-focus-scope.d.ts","sourceRoot":"","sources":["../../../src/primitives/_internal/react-focus-scope.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAY9C,KAAK,iBAAiB,GAAG,KAAK,CAAC,wBAAwB,CAAC,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;AAC9E,UAAU,eAAgB,SAAQ,iBAAiB;IACjD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAC1C,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAC7C;AAED,QAAA,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"react-focus-scope.d.ts","sourceRoot":"","sources":["../../../src/primitives/_internal/react-focus-scope.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAY9C,KAAK,iBAAiB,GAAG,KAAK,CAAC,wBAAwB,CAAC,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;AAC9E,UAAU,eAAgB,SAAQ,iBAAiB;IACjD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAC1C,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAC7C;AAED,QAAA,MAAM,UAAU,wFAoId,CAAC;AAmGH,QAAA,MAAM,IAAI,wFAAa,CAAC;AAExB,OAAO,EACL,UAAU,EACV,IAAI,GACL,CAAC;AACF,YAAY,EAAE,eAAe,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-command-palette.js","names":[],"sources":["../../src/shell/app-command-palette.tsx"],"sourcesContent":["'use client'\n\n/**\n * AppCommandPalette -- Application-level command palette wrapper.\n *\n * Props-driven: accepts navigation items, search callbacks, and user\n * info instead of reading from Zustand stores or Remix hooks.\n *\n * Depends on the CommandPalette shared component being available.\n */\nimport { IconFileText } from '@tabler/icons-react'\nimport * as React from 'react'\nimport { useCallback, useMemo } from 'react'\n\nimport { type CommandGroup, type CommandItem, CommandPalette, type CommandPaletteProps, type FooterHint } from '../composed/command-palette'\nimport { Icon } from '../ui/icon'\nimport type { IconInput } from '../ui/lib/icon-input'\nimport { cn } from '../ui/lib/utils'\nimport { useCommandRegistry } from './command-registry'\n\n// -----------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------\n\nexport interface SearchResult {\n id: string\n title: string\n snippet?: string\n entityType: string\n projectId?: string | null\n metadata?: Record<string, unknown>\n /** Custom icon for this result. Overrides the default entity-type icon. */\n icon?: IconInput\n /** Relevance score for sorting (higher = more relevant). */\n rank?: number\n /** Keyboard shortcut hint to display on this result. */\n shortcut?: string\n /** Navigation href. Used as fallback when onSearchResultSelect is not provided. */\n href?: string\n}\n\n/** A group of search results with a label. */\nexport interface SearchResultGroup {\n label: string\n results: SearchResult[]\n}\n\nexport interface AppCommandPaletteUser {\n name: string\n role?: string\n}\n\nexport interface AppCommandPaletteProps\n extends Omit<React.ComponentPropsWithRef<'div'>, 'onSearch'> {\n /** Current user (used to determine admin access) */\n user?: AppCommandPaletteUser | null\n /** When true, shows admin command groups regardless of user.role. Takes precedence over role-based detection. */\n isAdmin?: boolean\n /** Additional command groups to show */\n extraGroups?: CommandGroup[]\n /** Called when the user selects a navigation target */\n onNavigate?: (path: string) => void\n /** Called when the search input changes (for server-side search) */\n onSearch?: (query: string) => void\n /** Flat search results (displayed in a single \"Search Results\" group).\n * For grouped results, use `searchResultGroups` instead. */\n searchResults?: SearchResult[]\n /** Grouped search results — displayed as multiple labeled sections.\n * Takes precedence over `searchResults` when provided. */\n searchResultGroups?: SearchResultGroup[]\n /** Whether a search is currently in progress */\n isSearching?: boolean\n /** Called when the user selects a search result. When provided, the component\n * does NOT perform internal navigation — the consumer owns routing entirely. */\n onSearchResultSelect?: (result: SearchResult) => void\n /** Custom label for the search results group. Overrides \"Search Results\" / \"Searching...\".\n * Can be a string or a function receiving the result count. */\n searchResultsLabel?: string | ((count: number) => string)\n // -- Pass-through CommandPalette props --\n /** Controlled open state. */\n open?: boolean\n /** Default open state. */\n defaultOpen?: boolean\n /** Called when the open state changes. */\n onOpenChange?: (open: boolean) => void\n /** Keybinding(s) to toggle. Pass `false` to disable. Default: 'mod+k'. */\n keybinding?: CommandPaletteProps['keybinding']\n /** Max height of results. Default '320px'. */\n maxHeight?: CommandPaletteProps['maxHeight']\n /** Custom empty state. */\n emptyState?: React.ReactNode\n /** Custom footer hints. Pass `false` to hide. */\n footerHints?: FooterHint[] | false\n}\n\n// -----------------------------------------------------------------------\n// Constants\n// -----------------------------------------------------------------------\n\n// -----------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------\n\n/** Convert a SearchResult into a CommandItem. */\nfunction searchResultToCommandItem(\n r: SearchResult,\n onSearchResultSelect: ((result: SearchResult) => void) | undefined,\n nav: (to: string) => void,\n): CommandItem {\n return {\n id: `search-${r.entityType}-${r.id}`,\n label: r.title,\n filterValue: r.title,\n description: r.snippet,\n icon: r.icon ?? <Icon icon={IconFileText} />,\n shortcut: r.shortcut,\n onSelect: () => {\n if (onSearchResultSelect) {\n onSearchResultSelect(r)\n } else if (r.href) {\n nav(r.href)\n }\n },\n }\n}\n\n// -----------------------------------------------------------------------\n// AppCommandPalette\n// -----------------------------------------------------------------------\n\nconst AppCommandPalette = React.forwardRef<HTMLDivElement, AppCommandPaletteProps>(\n function AppCommandPalette(\n {\n user,\n isAdmin: isAdminProp,\n extraGroups = [],\n onNavigate,\n onSearch,\n searchResults = [],\n searchResultGroups,\n isSearching = false,\n onSearchResultSelect,\n searchResultsLabel,\n open,\n defaultOpen,\n onOpenChange,\n keybinding,\n maxHeight,\n emptyState,\n footerHints,\n className,\n ...props\n },\n ref,\n ) {\n const isAdmin =\n isAdminProp ?? (user?.role === 'Admin' || user?.role === 'SuperAdmin')\n\n const registry = useCommandRegistry()\n\n const nav = useCallback(\n (to: string) => {\n onNavigate?.(to)\n },\n [onNavigate],\n )\n\n // -- Static groups ---------------------------------------------------\n\n const pagesGroup: CommandGroup = useMemo(\n () => ({\n label: 'Pages',\n items: registry\n ? registry.pages.map((p) => ({\n id: p.id,\n label: p.label,\n icon: p.icon,\n keywords: p.keywords,\n onSelect: () => nav(p.path),\n }))\n : [], // Consumer provides pages via CommandRegistryProvider\n }),\n [nav, registry],\n )\n\n const adminGroup: CommandGroup = useMemo(\n () => ({\n label: 'Admin',\n items: registry\n ? registry.adminPages.map((p) => ({\n id: p.id,\n label: p.label,\n icon: p.icon,\n keywords: p.keywords,\n onSelect: () => nav(p.path),\n }))\n : [], // Consumer provides admin pages via CommandRegistryProvider\n }),\n [nav, registry],\n )\n\n // -- Dynamic search results groups ------------------------------------\n\n const searchGroups: CommandGroup[] = useMemo(() => {\n // Grouped results take precedence (#2)\n if (searchResultGroups && searchResultGroups.length > 0) {\n return searchResultGroups.map((group) => {\n // Sort by rank if provided\n const sorted = group.results.some(r => r.rank != null)\n ? [...group.results].sort((a, b) => (b.rank ?? 0) - (a.rank ?? 0))\n : group.results\n\n return {\n label: group.label,\n items: sorted.map(r => searchResultToCommandItem(r, onSearchResultSelect, nav)),\n }\n })\n }\n\n // Flat results (legacy behavior)\n if (searchResults.length === 0) return []\n\n // Sort by rank if any result has one\n const sorted = searchResults.some(r => r.rank != null)\n ? [...searchResults].sort((a, b) => (b.rank ?? 0) - (a.rank ?? 0))\n : searchResults\n\n const items = sorted.map(r => searchResultToCommandItem(r, onSearchResultSelect, nav))\n\n // Resolve the label (#10)\n let label: string\n if (searchResultsLabel) {\n label = typeof searchResultsLabel === 'function'\n ? searchResultsLabel(items.length)\n : searchResultsLabel\n } else {\n label = isSearching ? 'Searching...' : 'Search Results'\n }\n\n return [{ label, items }]\n }, [searchResults, searchResultGroups, isSearching, nav, onSearchResultSelect, searchResultsLabel])\n\n // -- Assemble groups -------------------------------------------------\n\n const groups: CommandGroup[] = useMemo(() => {\n const g: CommandGroup[] = []\n g.push(...searchGroups)\n g.push(pagesGroup)\n if (isAdmin) g.push(adminGroup)\n g.push(...extraGroups)\n return g\n }, [searchGroups, pagesGroup, isAdmin, adminGroup, extraGroups])\n\n // -- Handle search ---------------------------------------------------\n\n const handleSearch = useCallback(\n (query: string) => {\n onSearch?.(query)\n },\n [onSearch],\n )\n\n return (\n <CommandPalette\n ref={ref}\n groups={groups}\n placeholder=\"Search or jump to...\"\n onSearch={handleSearch}\n emptyMessage=\"No results found. Try a different search term.\"\n open={open}\n defaultOpen={defaultOpen}\n onOpenChange={onOpenChange}\n keybinding={keybinding}\n maxHeight={maxHeight}\n emptyState={emptyState}\n footerHints={footerHints}\n className={cn(className)}\n {...props}\n />\n )\n },\n)\n\nAppCommandPalette.displayName = 'AppCommandPalette'\n\nexport { AppCommandPalette }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"app-command-palette.js","names":[],"sources":["../../src/shell/app-command-palette.tsx"],"sourcesContent":["'use client'\n\n/**\n * AppCommandPalette -- Application-level command palette wrapper.\n *\n * Props-driven: accepts navigation items, search callbacks, and user\n * info instead of reading from Zustand stores or Remix hooks.\n *\n * Depends on the CommandPalette shared component being available.\n */\nimport { IconFileText } from '@tabler/icons-react'\nimport * as React from 'react'\nimport { useCallback, useMemo } from 'react'\n\nimport { type CommandGroup, type CommandItem, CommandPalette, type CommandPaletteProps, type FooterHint } from '../composed/command-palette'\nimport { Icon } from '../ui/icon'\nimport type { IconInput } from '../ui/lib/icon-input'\nimport { cn } from '../ui/lib/utils'\nimport { useCommandRegistry } from './command-registry'\n\n// -----------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------\n\nexport interface SearchResult {\n id: string\n title: string\n snippet?: string\n entityType: string\n projectId?: string | null\n metadata?: Record<string, unknown>\n /** Custom icon for this result. Overrides the default entity-type icon. */\n icon?: IconInput\n /** Relevance score for sorting (higher = more relevant). */\n rank?: number\n /** Keyboard shortcut hint to display on this result. */\n shortcut?: string\n /** Navigation href. Used as fallback when onSearchResultSelect is not provided. */\n href?: string\n}\n\n/** A group of search results with a label. */\nexport interface SearchResultGroup {\n label: string\n results: SearchResult[]\n}\n\nexport interface AppCommandPaletteUser {\n name: string\n role?: string\n}\n\nexport interface AppCommandPaletteProps\n extends Omit<React.ComponentPropsWithRef<'div'>, 'onSearch'> {\n /** Current user (used to determine admin access) */\n user?: AppCommandPaletteUser | null\n /** When true, shows admin command groups regardless of user.role. Takes precedence over role-based detection. */\n isAdmin?: boolean\n /** Additional command groups to show */\n extraGroups?: CommandGroup[]\n /** Called when the user selects a navigation target */\n onNavigate?: (path: string) => void\n /** Called when the search input changes (for server-side search) */\n onSearch?: (query: string) => void\n /** Flat search results (displayed in a single \"Search Results\" group).\n * For grouped results, use `searchResultGroups` instead. */\n searchResults?: SearchResult[]\n /** Grouped search results — displayed as multiple labeled sections.\n * Takes precedence over `searchResults` when provided. */\n searchResultGroups?: SearchResultGroup[]\n /** Whether a search is currently in progress */\n isSearching?: boolean\n /** Called when the user selects a search result. When provided, the component\n * does NOT perform internal navigation — the consumer owns routing entirely. */\n onSearchResultSelect?: (result: SearchResult) => void\n /** Custom label for the search results group. Overrides \"Search Results\" / \"Searching...\".\n * Can be a string or a function receiving the result count. */\n searchResultsLabel?: string | ((count: number) => string)\n // -- Pass-through CommandPalette props --\n /** Controlled open state. */\n open?: boolean\n /** Default open state. */\n defaultOpen?: boolean\n /** Called when the open state changes. */\n onOpenChange?: (open: boolean) => void\n /** Keybinding(s) to toggle. Pass `false` to disable. Default: 'mod+k'. */\n keybinding?: CommandPaletteProps['keybinding']\n /** Max height of results. Default '320px'. */\n maxHeight?: CommandPaletteProps['maxHeight']\n /** Custom empty state. */\n emptyState?: React.ReactNode\n /** Custom footer hints. Pass `false` to hide. */\n footerHints?: FooterHint[] | false\n}\n\n// -----------------------------------------------------------------------\n// Constants\n// -----------------------------------------------------------------------\n\n// -----------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------\n\n/** Convert a SearchResult into a CommandItem. */\nfunction searchResultToCommandItem(\n r: SearchResult,\n onSearchResultSelect: ((result: SearchResult) => void) | undefined,\n nav: (to: string) => void,\n): CommandItem {\n return {\n id: `search-${r.entityType}-${r.id}`,\n label: r.title,\n filterValue: r.title,\n description: r.snippet,\n icon: r.icon ?? <Icon icon={IconFileText} />,\n shortcut: r.shortcut,\n onSelect: () => {\n if (onSearchResultSelect) {\n onSearchResultSelect(r)\n } else if (r.href) {\n nav(r.href)\n }\n },\n }\n}\n\n// -----------------------------------------------------------------------\n// AppCommandPalette\n// -----------------------------------------------------------------------\n\nconst AppCommandPalette = React.forwardRef<HTMLDivElement, AppCommandPaletteProps>(\n function AppCommandPalette(\n {\n user,\n isAdmin: isAdminProp,\n extraGroups = [],\n onNavigate,\n onSearch,\n searchResults = [],\n searchResultGroups,\n isSearching = false,\n onSearchResultSelect,\n searchResultsLabel,\n open,\n defaultOpen,\n onOpenChange,\n keybinding,\n maxHeight,\n emptyState,\n footerHints,\n className,\n ...props\n },\n ref,\n ) {\n const isAdmin =\n isAdminProp ?? (user?.role === 'Admin' || user?.role === 'SuperAdmin')\n\n const registry = useCommandRegistry()\n\n const nav = useCallback(\n (to: string) => {\n onNavigate?.(to)\n },\n [onNavigate],\n )\n\n // -- Static groups ---------------------------------------------------\n\n const pagesGroup: CommandGroup = useMemo(\n () => ({\n label: 'Pages',\n items: registry\n ? registry.pages.map((p) => ({\n id: p.id,\n label: p.label,\n icon: p.icon,\n keywords: p.keywords,\n onSelect: () => nav(p.path),\n }))\n : [], // Consumer provides pages via CommandRegistryProvider\n }),\n [nav, registry],\n )\n\n const adminGroup: CommandGroup = useMemo(\n () => ({\n label: 'Admin',\n items: registry\n ? registry.adminPages.map((p) => ({\n id: p.id,\n label: p.label,\n icon: p.icon,\n keywords: p.keywords,\n onSelect: () => nav(p.path),\n }))\n : [], // Consumer provides admin pages via CommandRegistryProvider\n }),\n [nav, registry],\n )\n\n // -- Dynamic search results groups ------------------------------------\n\n const searchGroups: CommandGroup[] = useMemo(() => {\n // Grouped results take precedence (#2)\n if (searchResultGroups && searchResultGroups.length > 0) {\n return searchResultGroups.map((group) => {\n // Sort by rank if provided\n const sorted = group.results.some(r => r.rank != null)\n ? [...group.results].sort((a, b) => (b.rank ?? 0) - (a.rank ?? 0))\n : group.results\n\n return {\n label: group.label,\n items: sorted.map(r => searchResultToCommandItem(r, onSearchResultSelect, nav)),\n }\n })\n }\n\n // Flat results (legacy behavior)\n if (searchResults.length === 0) return []\n\n // Sort by rank if any result has one\n const sorted = searchResults.some(r => r.rank != null)\n ? [...searchResults].sort((a, b) => (b.rank ?? 0) - (a.rank ?? 0))\n : searchResults\n\n const items = sorted.map(r => searchResultToCommandItem(r, onSearchResultSelect, nav))\n\n // Resolve the label (#10)\n let label: string\n if (searchResultsLabel) {\n label = typeof searchResultsLabel === 'function'\n ? searchResultsLabel(items.length)\n : searchResultsLabel\n } else {\n label = isSearching ? 'Searching...' : 'Search Results'\n }\n\n return [{ label, items }]\n }, [searchResults, searchResultGroups, isSearching, nav, onSearchResultSelect, searchResultsLabel])\n\n // -- Assemble groups -------------------------------------------------\n\n const groups: CommandGroup[] = useMemo(() => {\n const g: CommandGroup[] = []\n g.push(...searchGroups)\n g.push(pagesGroup)\n if (isAdmin) g.push(adminGroup)\n g.push(...extraGroups)\n return g\n }, [searchGroups, pagesGroup, isAdmin, adminGroup, extraGroups])\n\n // -- Handle search ---------------------------------------------------\n\n const handleSearch = useCallback(\n (query: string) => {\n onSearch?.(query)\n },\n [onSearch],\n )\n\n return (\n <CommandPalette\n ref={ref}\n groups={groups}\n placeholder=\"Search or jump to...\"\n onSearch={handleSearch}\n emptyMessage=\"No results found. Try a different search term.\"\n open={open}\n defaultOpen={defaultOpen}\n onOpenChange={onOpenChange}\n keybinding={keybinding}\n maxHeight={maxHeight}\n emptyState={emptyState}\n footerHints={footerHints}\n className={cn(className)}\n {...props}\n />\n )\n },\n)\n\nAppCommandPalette.displayName = 'AppCommandPalette'\n\nexport { AppCommandPalette }\n"],"mappings":"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"}
|