@devalok/shilp-sutra 0.49.4 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/AGENTS.md +12 -4
  2. package/MIGRATION.md +6 -0
  3. package/dist/_chunks/badge-group.js +12 -11
  4. package/dist/_chunks/badge-group.js.map +1 -1
  5. package/dist/_chunks/block-shell.js +1 -1
  6. package/dist/_chunks/block-shell.js.map +1 -1
  7. package/dist/_chunks/chat.js +18 -18
  8. package/dist/_chunks/chat.js.map +1 -1
  9. package/dist/_chunks/document-preview.js +1 -1
  10. package/dist/_chunks/document-preview.js.map +1 -1
  11. package/dist/_chunks/emoji-suggestion.js +1 -1
  12. package/dist/_chunks/emoji-suggestion.js.map +1 -1
  13. package/dist/_chunks/grid-lines.js +2 -2
  14. package/dist/_chunks/grid-lines.js.map +1 -1
  15. package/dist/_chunks/image-preview.js +1 -1
  16. package/dist/_chunks/image-preview.js.map +1 -1
  17. package/dist/_chunks/mention-suggestion.js +2 -2
  18. package/dist/_chunks/mention-suggestion.js.map +1 -1
  19. package/dist/_chunks/oauth-button.js +8 -8
  20. package/dist/_chunks/oauth-button.js.map +1 -1
  21. package/dist/_chunks/shared.js +2 -2
  22. package/dist/_chunks/shared.js.map +1 -1
  23. package/dist/_chunks/success.js +5 -5
  24. package/dist/_chunks/success.js.map +1 -1
  25. package/dist/_chunks/tiptap.js +3956 -1796
  26. package/dist/_chunks/tiptap.js.map +1 -1
  27. package/dist/_chunks/tooltip.js +2 -2
  28. package/dist/_chunks/tooltip.js.map +1 -1
  29. package/dist/_chunks/tree-view.js +2 -2
  30. package/dist/_chunks/tree-view.js.map +1 -1
  31. package/dist/_chunks/use-calendar.js +3 -3
  32. package/dist/_chunks/use-calendar.js.map +1 -1
  33. package/dist/ai/block-renderer.js +1 -1
  34. package/dist/ai/block-renderer.js.map +1 -1
  35. package/dist/ai/blocks/error.js +1 -1
  36. package/dist/ai/blocks/error.js.map +1 -1
  37. package/dist/ai/command-bar.js +15 -15
  38. package/dist/ai/command-bar.js.map +1 -1
  39. package/dist/ai/conversation.js +6 -6
  40. package/dist/ai/conversation.js.map +1 -1
  41. package/dist/composed/activity-feed.js +4 -4
  42. package/dist/composed/activity-feed.js.map +1 -1
  43. package/dist/composed/avatar-group.js +6 -6
  44. package/dist/composed/avatar-group.js.map +1 -1
  45. package/dist/composed/bulk-action-bar.js +1 -1
  46. package/dist/composed/bulk-action-bar.js.map +1 -1
  47. package/dist/composed/command-palette.js +12 -12
  48. package/dist/composed/command-palette.js.map +1 -1
  49. package/dist/composed/content-card.js +1 -1
  50. package/dist/composed/content-card.js.map +1 -1
  51. package/dist/composed/date-picker/index.js +17 -17
  52. package/dist/composed/date-picker/index.js.map +1 -1
  53. package/dist/composed/deadline-indicator.js +2 -2
  54. package/dist/composed/deadline-indicator.js.map +1 -1
  55. package/dist/composed/diff.d.ts +108 -0
  56. package/dist/composed/diff.d.ts.map +1 -0
  57. package/dist/composed/diff.js +1004 -0
  58. package/dist/composed/diff.js.map +1 -0
  59. package/dist/composed/emoji-picker.js +6 -6
  60. package/dist/composed/emoji-picker.js.map +1 -1
  61. package/dist/composed/empty-state.js +2 -2
  62. package/dist/composed/empty-state.js.map +1 -1
  63. package/dist/composed/error-boundary.js +6 -6
  64. package/dist/composed/error-boundary.js.map +1 -1
  65. package/dist/composed/file-preview.js +5 -5
  66. package/dist/composed/file-preview.js.map +1 -1
  67. package/dist/composed/filter-bar.js +3 -3
  68. package/dist/composed/filter-bar.js.map +1 -1
  69. package/dist/composed/form-section.js +2 -2
  70. package/dist/composed/form-section.js.map +1 -1
  71. package/dist/composed/inline-edit.d.ts +1 -1
  72. package/dist/composed/inline-edit.d.ts.map +1 -1
  73. package/dist/composed/inline-edit.js.map +1 -1
  74. package/dist/composed/loading-skeleton.js +3 -3
  75. package/dist/composed/loading-skeleton.js.map +1 -1
  76. package/dist/composed/markdown-viewer.js +11 -11
  77. package/dist/composed/markdown-viewer.js.map +1 -1
  78. package/dist/composed/master-detail.js +1 -1
  79. package/dist/composed/master-detail.js.map +1 -1
  80. package/dist/composed/member-picker.js +2 -2
  81. package/dist/composed/member-picker.js.map +1 -1
  82. package/dist/composed/multi-select-popover.js +5 -5
  83. package/dist/composed/multi-select-popover.js.map +1 -1
  84. package/dist/composed/page-header.js +3 -3
  85. package/dist/composed/page-header.js.map +1 -1
  86. package/dist/composed/page-skeletons.js +4 -4
  87. package/dist/composed/page-skeletons.js.map +1 -1
  88. package/dist/composed/priority-indicator.js +1 -1
  89. package/dist/composed/priority-indicator.js.map +1 -1
  90. package/dist/composed/responsive-modal.js +2 -2
  91. package/dist/composed/responsive-modal.js.map +1 -1
  92. package/dist/composed/rich-chat-input.js +27 -27
  93. package/dist/composed/rich-chat-input.js.map +1 -1
  94. package/dist/composed/rich-text-editor.d.ts +81 -6
  95. package/dist/composed/rich-text-editor.d.ts.map +1 -1
  96. package/dist/composed/rich-text-editor.js +365 -210
  97. package/dist/composed/rich-text-editor.js.map +1 -1
  98. package/dist/composed/schedule-view.js +4 -4
  99. package/dist/composed/schedule-view.js.map +1 -1
  100. package/dist/shell/bottom-navbar.js +4 -4
  101. package/dist/shell/bottom-navbar.js.map +1 -1
  102. package/dist/shell/notification-center.js +13 -13
  103. package/dist/shell/notification-center.js.map +1 -1
  104. package/dist/shell/notification-preferences.js +10 -10
  105. package/dist/shell/notification-preferences.js.map +1 -1
  106. package/dist/shell/sidebar.js +9 -9
  107. package/dist/shell/sidebar.js.map +1 -1
  108. package/dist/shell/top-bar.js +8 -8
  109. package/dist/shell/top-bar.js.map +1 -1
  110. package/dist/tokens/semantic.css +6 -1
  111. package/dist/tokens/typography-semantic.css +6 -6
  112. package/dist/tokens/utilities.css +8 -0
  113. package/dist/ui/accordion.js +2 -2
  114. package/dist/ui/accordion.js.map +1 -1
  115. package/dist/ui/alert-dialog.js +4 -4
  116. package/dist/ui/alert-dialog.js.map +1 -1
  117. package/dist/ui/alert.js +1 -1
  118. package/dist/ui/alert.js.map +1 -1
  119. package/dist/ui/autocomplete.d.ts.map +1 -1
  120. package/dist/ui/autocomplete.js +11 -3
  121. package/dist/ui/autocomplete.js.map +1 -1
  122. package/dist/ui/avatar.js +5 -5
  123. package/dist/ui/avatar.js.map +1 -1
  124. package/dist/ui/badge-group.d.ts +2 -6
  125. package/dist/ui/badge-group.d.ts.map +1 -1
  126. package/dist/ui/badge-indicator.d.ts +2 -6
  127. package/dist/ui/badge-indicator.d.ts.map +1 -1
  128. package/dist/ui/badge-indicator.js +19 -18
  129. package/dist/ui/badge-indicator.js.map +1 -1
  130. package/dist/ui/badge.d.ts +2 -4
  131. package/dist/ui/badge.d.ts.map +1 -1
  132. package/dist/ui/banner.js +1 -1
  133. package/dist/ui/banner.js.map +1 -1
  134. package/dist/ui/breadcrumb.js +1 -1
  135. package/dist/ui/breadcrumb.js.map +1 -1
  136. package/dist/ui/button.js +7 -7
  137. package/dist/ui/button.js.map +1 -1
  138. package/dist/ui/card.d.ts +1 -1
  139. package/dist/ui/card.js +1 -1
  140. package/dist/ui/card.js.map +1 -1
  141. package/dist/ui/charts/gauge-chart.js +2 -2
  142. package/dist/ui/charts/gauge-chart.js.map +1 -1
  143. package/dist/ui/charts/pie-chart.js +1 -1
  144. package/dist/ui/charts/pie-chart.js.map +1 -1
  145. package/dist/ui/charts/radar-chart.d.ts +19 -1
  146. package/dist/ui/charts/radar-chart.d.ts.map +1 -1
  147. package/dist/ui/charts/radar-chart.js +80 -42
  148. package/dist/ui/charts/radar-chart.js.map +1 -1
  149. package/dist/ui/code.js +2 -2
  150. package/dist/ui/code.js.map +1 -1
  151. package/dist/ui/color-input.d.ts.map +1 -1
  152. package/dist/ui/color-input.js +164 -157
  153. package/dist/ui/color-input.js.map +1 -1
  154. package/dist/ui/combobox.d.ts.map +1 -1
  155. package/dist/ui/combobox.js +143 -142
  156. package/dist/ui/combobox.js.map +1 -1
  157. package/dist/ui/context-menu.js +6 -6
  158. package/dist/ui/context-menu.js.map +1 -1
  159. package/dist/ui/data-table-body.js +1 -1
  160. package/dist/ui/data-table-body.js.map +1 -1
  161. package/dist/ui/data-table-bulk-actions.js +1 -1
  162. package/dist/ui/data-table-bulk-actions.js.map +1 -1
  163. package/dist/ui/data-table-card.js +1 -1
  164. package/dist/ui/data-table-card.js.map +1 -1
  165. package/dist/ui/data-table-header.js +1 -1
  166. package/dist/ui/data-table-header.js.map +1 -1
  167. package/dist/ui/data-table-pagination.js +3 -3
  168. package/dist/ui/data-table-pagination.js.map +1 -1
  169. package/dist/ui/data-table-toolbar.js +1 -1
  170. package/dist/ui/data-table-toolbar.js.map +1 -1
  171. package/dist/ui/data-table.js +1 -1
  172. package/dist/ui/data-table.js.map +1 -1
  173. package/dist/ui/dialog.js +2 -2
  174. package/dist/ui/dialog.js.map +1 -1
  175. package/dist/ui/dot.js +4 -4
  176. package/dist/ui/dot.js.map +1 -1
  177. package/dist/ui/dropdown-menu.js +6 -6
  178. package/dist/ui/dropdown-menu.js.map +1 -1
  179. package/dist/ui/file-upload.js +5 -5
  180. package/dist/ui/file-upload.js.map +1 -1
  181. package/dist/ui/form.js +1 -1
  182. package/dist/ui/form.js.map +1 -1
  183. package/dist/ui/input-otp.d.ts.map +1 -1
  184. package/dist/ui/input-otp.js +3 -3
  185. package/dist/ui/input-otp.js.map +1 -1
  186. package/dist/ui/input.js +8 -8
  187. package/dist/ui/input.js.map +1 -1
  188. package/dist/ui/label.js +1 -1
  189. package/dist/ui/label.js.map +1 -1
  190. package/dist/ui/lib/utils.d.ts.map +1 -1
  191. package/dist/ui/lib/utils.js +42 -13
  192. package/dist/ui/lib/utils.js.map +1 -1
  193. package/dist/ui/menubar.js +7 -7
  194. package/dist/ui/menubar.js.map +1 -1
  195. package/dist/ui/navigation-menu.js +1 -1
  196. package/dist/ui/navigation-menu.js.map +1 -1
  197. package/dist/ui/number-input.d.ts.map +1 -1
  198. package/dist/ui/number-input.js +6 -5
  199. package/dist/ui/number-input.js.map +1 -1
  200. package/dist/ui/pagination.js +1 -1
  201. package/dist/ui/pagination.js.map +1 -1
  202. package/dist/ui/progress-ring.js +3 -3
  203. package/dist/ui/progress-ring.js.map +1 -1
  204. package/dist/ui/progress.js +2 -2
  205. package/dist/ui/progress.js.map +1 -1
  206. package/dist/ui/segmented-control.js +3 -3
  207. package/dist/ui/segmented-control.js.map +1 -1
  208. package/dist/ui/select.d.ts.map +1 -1
  209. package/dist/ui/select.js +7 -6
  210. package/dist/ui/select.js.map +1 -1
  211. package/dist/ui/sheet.js +2 -2
  212. package/dist/ui/sheet.js.map +1 -1
  213. package/dist/ui/sidebar.js +7 -7
  214. package/dist/ui/sidebar.js.map +1 -1
  215. package/dist/ui/split-button.js +6 -6
  216. package/dist/ui/split-button.js.map +1 -1
  217. package/dist/ui/stat-card.d.ts +1 -1
  218. package/dist/ui/stat-card.js +7 -7
  219. package/dist/ui/stat-card.js.map +1 -1
  220. package/dist/ui/stepper.js +3 -3
  221. package/dist/ui/stepper.js.map +1 -1
  222. package/dist/ui/table.js +3 -3
  223. package/dist/ui/table.js.map +1 -1
  224. package/dist/ui/tabs.js +3 -3
  225. package/dist/ui/tabs.js.map +1 -1
  226. package/dist/ui/text.d.ts.map +1 -1
  227. package/dist/ui/text.js.map +1 -1
  228. package/dist/ui/textarea.d.ts.map +1 -1
  229. package/dist/ui/textarea.js +7 -6
  230. package/dist/ui/textarea.js.map +1 -1
  231. package/dist/ui/toast.js +11 -11
  232. package/dist/ui/toast.js.map +1 -1
  233. package/dist/ui/toggle.js +3 -3
  234. package/dist/ui/toggle.js.map +1 -1
  235. package/dist/ui/tooltip.js +1 -1
  236. package/dist/ui/tooltip.js.map +1 -1
  237. package/docs/components/composed/diff.md +50 -0
  238. package/docs/components/ui/data-table.md +2 -2
  239. package/llms.txt +2 -1
  240. package/mcp-manifest.json +147 -3
  241. package/package.json +8 -1
  242. package/skill/SKILL.md +1 -1
  243. package/skill/references/components.md +2 -1
@@ -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":"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"}
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 IconEye,\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 { Markdown } from '@tiptap/markdown'\nimport { type Editor,EditorContent, useEditor, useEditorState } from '@tiptap/react'\nimport StarterKit from '@tiptap/starter-kit'\nimport { AnimatePresence, motion, useReducedMotion } from 'framer-motion'\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-body-md leading-relaxed text-surface-fg',\n '[&_h2]:text-heading-sm [&_h2]:mb-ds-03 [&_h2]:mt-ds-05 [&_h2]:text-surface-fg',\n '[&_h3]:text-body-lg [&_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-body-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 // slop-allow: side-stripe — blockquote left-rule is standard typography, not a card accent-rail\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-body-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, trailing, showControls = true }: {\n editor: Editor\n toolbar?: ToolbarItem[]\n onImageClick?: () => void\n onFileClick?: () => void\n onEmojiClick?: () => void\n /** Right-aligned content (e.g. a source-view toggle). */\n trailing?: React.ReactNode\n /** Hide the formatting controls, keeping only `trailing`. @default true */\n showControls?: boolean\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 min-h-[2.75rem] flex-wrap items-center gap-ds-01 border-b border-surface-border-strong px-ds-04 py-ds-02b\">\n {showControls && (<>\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 </>)}\n {trailing && <div className=\"ml-auto flex items-center pl-ds-02\">{trailing}</div>}\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-body-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 *\n * Emits HTML by default, or Markdown when `format=\"markdown\"`. NOTE: the HTML\n * output is the editor's raw HTML and is NOT sanitized — do not render it as\n * untrusted content without sanitizing it first. Storing Markdown (and\n * rendering via MarkdownViewer) sidesteps that risk entirely.\n */\nexport interface RichTextEditorProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'onChange' | 'content'> {\n /** Content format read and emitted. `markdown` for Markdown in/out; `html` uses HTML. @default 'html' */\n format?: 'html' | 'markdown'\n /** Initial content, interpreted per `format`. Updates are NOT reactive — use `onChange` for controlled state. */\n content?: string\n placeholder?: string\n /** Called with the editor content (HTML, or Markdown when `format=\"markdown\"`) whenever it changes. */\n onChange?: (value: 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 /** Show a source-view toggle (raw Markdown ⇄ visual) in the top-right of the default editor. @default false */\n sourceToggle?: boolean\n /** Controlled source-view state — drive it from an external button. */\n sourceMode?: boolean\n /** Uncontrolled initial source-view state. @default false */\n defaultSourceMode?: boolean\n /** Called when the source view toggles (raw Markdown ⇄ visual). */\n onSourceModeChange?: (source: boolean) => void\n}\n\n// --------------------------- compound slot context ------------------------\n\ninterface RTEContextValue {\n editor: Editor\n toolbar?: ToolbarItem[]\n editable: boolean\n onImageClick: () => void\n onFileClick?: () => void\n onEmojiClick: () => void\n /** Raw-Markdown source view active. */\n sourceMode: boolean\n setSourceMode: (source: boolean) => void\n toggleSource: () => void\n sourceText: string\n onSourceChange: (value: string) => void\n}\n\nconst RTEContext = React.createContext<RTEContextValue | null>(null)\n\n/** Access the editor + toolbar wiring inside a `<RichTextEditor.Provider>`. */\nfunction useRichTextEditor(): RTEContextValue {\n const ctx = React.useContext(RTEContext)\n if (!ctx) throw new Error('RichTextEditor.Toolbar / .Content must be used within <RichTextEditor.Provider>')\n return ctx\n}\n\nexport interface RichTextEditorProviderProps extends RichTextEditorProps {\n children?: React.ReactNode\n}\n\n/**\n * Owns the Tiptap editor + all upload/emoji/mention plumbing and provides it to\n * `RichTextEditor.Toolbar` / `RichTextEditor.Content`. Compose your own layout\n * (toolbar top/bottom/floating, or bring your own) around these slots, or use\n * `<RichTextEditor>` for the batteries-included default.\n */\nconst RichTextEditorProvider = React.forwardRef<HTMLDivElement, RichTextEditorProviderProps>(\n function RichTextEditorProvider({\n format = 'html',\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 sourceMode: sourceModeProp,\n defaultSourceMode = false,\n onSourceModeChange,\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 // Default-render-only flag; dropped here so it never leaks onto the DOM.\n sourceToggle: _sourceToggle,\n children,\n ...props\n}, ref) {\n const editorRef = React.useRef<ReturnType<typeof useEditor>>(null)\n const isInternalChangeRef = React.useRef(false)\n const reduceMotion = useReducedMotion()\n const [showEmojiPicker, setShowEmojiPicker] = React.useState(false)\n\n // Source view (raw Markdown), controllable.\n const [uncontrolledSource, setUncontrolledSource] = React.useState(defaultSourceMode)\n const sourceMode = sourceModeProp ?? uncontrolledSource\n const setSourceMode = React.useCallback(\n (v: boolean) => {\n if (sourceModeProp === undefined) setUncontrolledSource(v)\n onSourceModeChange?.(v)\n },\n [sourceModeProp, onSourceModeChange],\n )\n const [sourceText, setSourceText] = React.useState('')\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 Markdown,\n ],\n content,\n contentType: format === 'markdown' ? 'markdown' : undefined,\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?.(format === 'markdown' ? ed.getMarkdown() : 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) return\n const current = format === 'markdown' ? editor.getMarkdown() : editor.getHTML()\n if (content !== current) {\n editor.commands.setContent(content, {\n emitUpdate: false,\n contentType: format === 'markdown' ? 'markdown' : undefined,\n })\n }\n }, [editor, content, format])\n\n // Entering source view: seed the textarea with the editor's current Markdown.\n React.useEffect(() => {\n if (sourceMode && editor) setSourceText(editor.getMarkdown())\n // Only re-seed on the source-mode transition, not on every keystroke.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [sourceMode])\n\n if (!editor) return null\n\n const onSourceChange = (v: string) => {\n setSourceText(v)\n // Push edits back into the doc so `onChange` fires in the configured format.\n editor.commands.setContent(v, { contentType: 'markdown' })\n }\n\n const ctx: RTEContextValue = {\n editor,\n toolbar,\n editable,\n onImageClick: () => imageInputRef.current?.click(),\n onFileClick: onFileUpload ? () => fileInputRef.current?.click() : undefined,\n onEmojiClick: () => setShowEmojiPicker((prev) => !prev),\n sourceMode,\n setSourceMode,\n toggleSource: () => setSourceMode(!sourceMode),\n sourceText,\n onSourceChange,\n }\n\n return (\n <RTEContext.Provider value={ctx}>\n <div ref={ref} {...props} className={cn('relative', className)}>\n {/* Emoji picker rendered outside the overflow-hidden box so it isn't clipped */}\n <AnimatePresence>\n {showEmojiPicker && (\n <motion.div\n key=\"emoji-picker\"\n ref={emojiPickerRef}\n initial={reduceMotion ? false : { opacity: 0, scale: 0.96, y: 4 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={reduceMotion ? { opacity: 0 } : { opacity: 0, scale: 0.96, y: 4 }}\n transition={{ duration: 0.14, ease: 'easeOut' }}\n className=\"absolute bottom-full right-0 z-popover mb-ds-02 origin-bottom-right\"\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 </motion.div>\n )}\n </AnimatePresence>\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 {children}\n </div>\n </RTEContext.Provider>\n )\n},\n)\n\nRichTextEditorProvider.displayName = 'RichTextEditor.Provider'\n\n/**\n * The formatting toolbar. Reads the editor from `<RichTextEditor.Provider>`.\n * In source view the format controls hide but `trailing` (e.g. the source\n * toggle) stays, so the bar persists.\n */\nfunction RichTextEditorToolbarSlot({ trailing }: { trailing?: React.ReactNode }) {\n const { editor, toolbar, editable, sourceMode, onImageClick, onFileClick, onEmojiClick } = useRichTextEditor()\n if (!editable) return null\n const showControls = !sourceMode\n if (!showControls && !trailing) return null\n return (\n <Toolbar\n editor={editor}\n toolbar={toolbar}\n showControls={showControls}\n trailing={trailing}\n onImageClick={onImageClick}\n onFileClick={onFileClick}\n onEmojiClick={onEmojiClick}\n />\n )\n}\nRichTextEditorToolbarSlot.displayName = 'RichTextEditor.Toolbar'\n\n/** The content area — the visual editor, or a raw-Markdown textarea in source view. */\nfunction RichTextEditorContentSlot() {\n const { editor, sourceMode, sourceText, onSourceChange } = useRichTextEditor()\n const reduce = useReducedMotion()\n const enter = reduce ? { opacity: 0 } : { opacity: 0, y: 4 }\n return (\n <AnimatePresence mode=\"wait\" initial={false}>\n {sourceMode ? (\n <motion.div key=\"source\" initial={enter} animate={{ opacity: 1, y: 0 }} exit={enter} transition={{ duration: 0.14, ease: 'easeOut' }}>\n <textarea\n value={sourceText}\n onChange={(e) => onSourceChange(e.target.value)}\n spellCheck={false}\n aria-label=\"Markdown source\"\n className=\"block min-h-[120px] w-full resize-y bg-surface-raised px-ds-04 py-ds-04 font-mono text-body-sm leading-ds-relaxed text-surface-fg focus:outline-hidden\"\n />\n </motion.div>\n ) : (\n <motion.div key=\"visual\" initial={enter} animate={{ opacity: 1, y: 0 }} exit={enter} transition={{ duration: 0.14, ease: 'easeOut' }}>\n <EditorContent editor={editor} />\n </motion.div>\n )}\n </AnimatePresence>\n )\n}\nRichTextEditorContentSlot.displayName = 'RichTextEditor.Content'\n\n/**\n * A source-view toggle (raw Markdown ⇄ visual). Text + icon by default;\n * pass `iconOnly` for a compact button. Place it anywhere inside a Provider.\n */\nfunction RichTextEditorSourceToggleSlot({ className, iconOnly = false }: { className?: string; iconOnly?: boolean }) {\n const { sourceMode, toggleSource } = useRichTextEditor()\n const aria = sourceMode ? 'Switch to visual editor' : 'Switch to Markdown source'\n const icon = sourceMode ? IconEye : IconCode\n if (iconOnly) {\n return (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-pressed={sourceMode}\n aria-label={aria}\n title={aria}\n className={className}\n onClick={toggleSource}\n >\n <Icon icon={icon} size=\"sm\" />\n </Button>\n )\n }\n return (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-pressed={sourceMode}\n aria-label={aria}\n startIcon={icon}\n className={className}\n onClick={toggleSource}\n >\n {sourceMode ? 'Editor' : 'Markdown'}\n </Button>\n )\n}\nRichTextEditorSourceToggleSlot.displayName = 'RichTextEditor.SourceToggle'\n\n// Batteries-included default: Provider + bordered box + toolbar-over-content.\nconst RichTextEditorBase = React.forwardRef<HTMLDivElement, RichTextEditorProps>(\n function RichTextEditor(props, ref) {\n return (\n <RichTextEditorProvider ref={ref} {...props}>\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 <RichTextEditorToolbarSlot\n trailing={props.sourceToggle ? <RichTextEditorSourceToggleSlot /> : undefined}\n />\n <RichTextEditorContentSlot />\n </div>\n </RichTextEditorProvider>\n )\n },\n)\nRichTextEditorBase.displayName = 'RichTextEditor'\n\nconst RichTextEditor = Object.assign(RichTextEditorBase, {\n Provider: RichTextEditorProvider,\n Toolbar: RichTextEditorToolbarSlot,\n Content: RichTextEditorContentSlot,\n SourceToggle: RichTextEditorSourceToggleSlot,\n})\n\nexport interface RichTextViewerProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'content'> {\n content: string\n /** Content format. `markdown` parses Markdown; `html` (default) parses HTML. @default 'html' */\n format?: 'html' | 'markdown'\n className?: string\n}\n\nconst RichTextViewer = React.forwardRef<HTMLDivElement, RichTextViewerProps>(\n function RichTextViewer({ content, format = 'html', 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 Markdown,\n ],\n content,\n contentType: format === 'markdown' ? 'markdown' : undefined,\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 {\n RichTextEditor,\n RichTextEditorProvider,\n RichTextEditorToolbarSlot as RichTextEditorToolbar,\n RichTextEditorContentSlot as RichTextEditorContent,\n RichTextEditorSourceToggleSlot as RichTextEditorSourceToggle,\n useRichTextEditor,\n RichTextViewer,\n}\n"],"mappings":";;;;;;;;;;;;;;AAiDA,IAAM,IAAgB;CACpB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAUA,SAAS,EAAc,EACrB,YACA,cAAW,IACX,cAAW,IACX,UACA,eACqB;CACrB,OACE,kBAAC,UAAD;EACE,MAAK;EACI;EACC;EACH;EACP,cAAY;EACZ,gBAAc;EACd,WAAW,EACT,iJACA,iCACA,iEACA,IACI,2CACA,wBACN;EAEC;CACK,CAAA;AAEZ;AAEA,SAAS,EAAW,EAAE,aAA8B;CAClD,IAAM,CAAC,GAAW,KAAgB,EAAM,SAAS,EAAK,GAChD,CAAC,GAAK,KAAU,EAAM,SAAS,EAAE,GACjC,IAAW,EAAM,OAAyB,IAAI,GAC9C,EAAE,cAAW,EAAe;EAChC;EACA,WAAW,EAAE,QAAQ,SAAS,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE;CAC7D,CAAC;CA8BD,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD;GAAe,eA9BQ;IACzB,IAAI,GAAQ;KACV,EAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI;KACvC;IACF;IACA,IAAM,IAAc,EAAO,cAAc,MAAM,CAAC,CAAC,QAAQ;IAGzD,AAFA,EAAO,CAAW,GAClB,EAAa,EAAI,GACjB,iBAAiB,EAAS,SAAS,MAAM,GAAG,CAAC;GAC/C;GAqB0C,UAAU;GAAQ,OAAM;aAC5D,kBAAC,GAAD;IAAM,MAAM;IAAU,MAAK;GAAM,CAAA;EACpB,CAAA,GACd,KACC,kBAAC,QAAD;GACE,WAxBc,MAAuB;IAM3C,AALA,EAAE,eAAe,GACb,EAAI,KAAK,KACX,EAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAI,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,GAE3D,EAAa,EAAK,GAClB,EAAO,EAAE;GACX;GAkBQ,cAAW;GACX,WAAU;aAHZ,CAKE,kBAAC,SAAD;IACE,KAAK;IACL,MAAK;IACL,OAAO;IACP,WAAW,MAAM,EAAO,EAAE,OAAO,KAAK;IACtC,YAxBa,MAA2B;KAChD,AAAI,EAAE,QAAQ,aACZ,EAAa,EAAK,GAClB,EAAO,EAAE,GACT,EAAO,SAAS,MAAM;IAE1B;IAmBU,aAAY;IACZ,WAAU;GACX,CAAA,GACD,kBAAC,GAAD;IAAQ,MAAK;IAAS,SAAQ;IAAQ,MAAK;cAAK;GAExC,CAAA,CACJ;IAEL;;AAET;AAEA,SAAS,IAAiB;CACxB,OAAO,kBAAC,OAAD,EAAK,WAAU,0CAA2C,CAAA;AACnE;AAEA,SAAS,EAAQ,EAAE,WAAQ,YAAS,iBAAc,gBAAa,iBAAc,aAAU,kBAAe,MAUnG;CAGD,IAAM,IAAQ,EAAe;EAC3B;EACA,WAAW,EAAE,QAAQ,SAAS;GAC5B,QAAQ,EAAE,SAAS,MAAM;GACzB,UAAU,EAAE,SAAS,QAAQ;GAC7B,aAAa,EAAE,SAAS,WAAW;GACnC,UAAU,EAAE,SAAS,QAAQ;GAC7B,aAAa,EAAE,SAAS,WAAW;GACnC,MAAM,EAAE,SAAS,WAAW,EAAE,OAAO,EAAE,CAAC;GACxC,MAAM,EAAE,SAAS,WAAW,EAAE,OAAO,EAAE,CAAC;GACxC,cAAc,EAAE,SAAS,YAAY;GACrC,cAAc,EAAE,SAAS,YAAY;GACrC,eAAe,EAAE,SAAS,aAAa;GACvC,YAAY,EAAE,SAAS,UAAU;GACjC,aAAa,EAAE,SAAS,WAAW;GACnC,QAAQ,EAAE,SAAS,MAAM;GACzB,aAAa,EAAE,SAAS,EAAE,WAAW,OAAO,CAAC;GAC7C,eAAe,EAAE,SAAS,EAAE,WAAW,SAAS,CAAC;GACjD,cAAc,EAAE,SAAS,EAAE,WAAW,QAAQ,CAAC;GAC/C,SAAS,EAAE,IAAI,CAAC,CAAC,KAAK;GACtB,SAAS,EAAE,IAAI,CAAC,CAAC,KAAK;EACxB;CACF,CAAC,GAEK,KAAQ,MAAsB,CAAC,KAAW,EAAQ,SAAS,CAAI,GAI/D,IAAY,EAAK,MAAM,KAAK,EAAK,QAAQ,KAAK,EAAK,WAAW,KAAK,EAAK,QAAQ,KAAK,EAAK,WAAW,GACrG,IAAW,EAAK,IAAI,KAAK,EAAK,IAAI,KAAK,EAAK,YAAY,GACxD,IAAU,EAAK,YAAY,KAAK,EAAK,aAAa,KAAK,EAAK,UAAU,KAAK,EAAK,WAAW,GAC3F,IAAW,EAAK,MAAM,KAAM,CAAC,CAAC,KAAgB,EAAK,OAAO,KAAO,CAAC,CAAC,KAAe,EAAK,MAAM,KAAM,EAAK,IAAI,GAC5G,IAAW,EAAK,WAAW,KAAK,EAAK,aAAa,KAAK,EAAK,YAAY,GACxE,IAAgB,CAAC,CAAC,KAAgB,EAAK,OAAO,GAC9C,IAAa,EAAK,MAAM,KAAK,EAAK,MAAM;CAE9C,OACE,kBAAC,OAAD;EAAK,MAAK;EAAU,cAAW;EAAkB,WAAU;YAA3D,CACG,KAAiB,kBAAA,GAAA,EAAA,UAAA;GAEjB,EAAK,MAAM,KACV,kBAAC,GAAD;IAAe,eAAe,EAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI;IAAG,UAAU,EAAM;IAAQ,OAAM;cACrG,kBAAC,GAAD;KAAM,MAAM;KAAU,MAAK;KAAK,QAAO;IAAQ,CAAA;GAClC,CAAA;GAEhB,EAAK,QAAQ,KACZ,kBAAC,GAAD;IAAe,eAAe,EAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,aAAa,CAAC,CAAC,IAAI;IAAG,UAAU,EAAM;IAAU,OAAM;cACzG,kBAAC,GAAD;KAAM,MAAM;KAAY,MAAK;KAAK,QAAO;IAAQ,CAAA;GACpC,CAAA;GAEhB,EAAK,WAAW,KACf,kBAAC,GAAD;IAAe,eAAe,EAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,gBAAgB,CAAC,CAAC,IAAI;IAAG,UAAU,EAAM;IAAa,OAAM;cAC/G,kBAAC,GAAD;KAAM,MAAM;KAAe,MAAK;KAAK,QAAO;IAAQ,CAAA;GACvC,CAAA;GAEhB,EAAK,QAAQ,KACZ,kBAAC,GAAD;IAAe,eAAe,EAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,aAAa,CAAC,CAAC,IAAI;IAAG,UAAU,EAAM;IAAU,OAAM;cACzG,kBAAC,GAAD;KAAM,MAAM;KAAmB,MAAK;KAAK,QAAO;IAAQ,CAAA;GAC3C,CAAA;GAEhB,EAAK,WAAW,KACf,kBAAC,GAAD;IAAe,eAAe,EAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,gBAAgB,CAAC,CAAC,IAAI;IAAG,UAAU,EAAM;IAAa,OAAM;cAC/G,kBAAC,GAAD;KAAM,MAAM;KAAe,MAAK;KAAK,QAAO;IAAQ,CAAA;GACvC,CAAA;GAGhB,KAAa,KAAY,kBAAC,GAAD,CAAiB,CAAA;GAG1C,EAAK,IAAI,KACR,kBAAC,GAAD;IAAe,eAAe,EAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,cAAc,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI;IAAG,UAAU,EAAM;IAAM,OAAM;cAClH,kBAAC,GAAD;KAAM,MAAM;KAAQ,MAAK;KAAK,QAAO;IAAQ,CAAA;GAChC,CAAA;GAEhB,EAAK,IAAI,KACR,kBAAC,GAAD;IAAe,eAAe,EAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,cAAc,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI;IAAG,UAAU,EAAM;IAAM,OAAM;cAClH,kBAAC,GAAD;KAAM,MAAM;KAAQ,MAAK;KAAK,QAAO;IAAQ,CAAA;GAChC,CAAA;GAEhB,EAAK,YAAY,KAChB,kBAAC,GAAD;IAAe,eAAe,EAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC,IAAI;IAAG,UAAU,EAAM;IAAc,OAAM;cACjH,kBAAC,GAAD;KAAM,MAAM;KAAgB,MAAK;KAAK,QAAO;IAAQ,CAAA;GACxC,CAAA;GAGhB,KAAY,KAAW,kBAAC,GAAD,CAAiB,CAAA;GAGxC,EAAK,YAAY,KAChB,kBAAC,GAAD;IAAe,eAAe,EAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC,IAAI;IAAG,UAAU,EAAM;IAAc,OAAM;cACjH,kBAAC,GAAD;KAAM,MAAM;KAAU,MAAK;KAAK,QAAO;IAAQ,CAAA;GAClC,CAAA;GAEhB,EAAK,aAAa,KACjB,kBAAC,GAAD;IAAe,eAAe,EAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC,IAAI;IAAG,UAAU,EAAM;IAAe,OAAM;cACnH,kBAAC,GAAD;KAAM,MAAM;KAAiB,MAAK;KAAK,QAAO;IAAQ,CAAA;GACzC,CAAA;GAEhB,EAAK,UAAU,KACd,kBAAC,GAAD;IAAe,eAAe,EAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,IAAI;IAAG,UAAU,EAAM;IAAY,OAAM;cAC7G,kBAAC,GAAD;KAAM,MAAM;KAAe,MAAK;KAAK,QAAO;IAAQ,CAAA;GACvC,CAAA;GAEhB,EAAK,WAAW,KACf,kBAAC,GAAD;IAAe,eAAe,EAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,gBAAgB,CAAC,CAAC,IAAI;IAAG,UAAU,EAAM;IAAa,OAAM;cAC/G,kBAAC,GAAD;KAAM,MAAM;KAAU,MAAK;KAAK,QAAO;IAAQ,CAAA;GAClC,CAAA;GAGhB,KAAW,KAAY,kBAAC,GAAD,CAAiB,CAAA;GAGxC,EAAK,MAAM,KAAK,kBAAC,GAAD,EAAoB,UAAS,CAAA;GAC7C,KAAgB,EAAK,OAAO,KAC3B,kBAAC,GAAD;IAAe,SAAS;IAAc,OAAM;cAC1C,kBAAC,GAAD;KAAM,MAAM;KAAW,MAAK;IAAM,CAAA;GACrB,CAAA;GAEhB,KAAe,EAAK,MAAM,KACzB,kBAAC,GAAD;IAAe,SAAS;IAAa,OAAM;cACzC,kBAAC,GAAD;KAAM,MAAM;KAAe,MAAK;IAAM,CAAA;GACzB,CAAA;GAEhB,EAAK,IAAI,KACR,kBAAC,GAAD;IAAe,eAAe,EAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC,IAAI;IAAG,OAAM;cACpF,kBAAC,GAAD;KAAM,MAAM;KAAgB,MAAK;IAAM,CAAA;GAC1B,CAAA;GAGhB,KAAY,KAAY,kBAAC,GAAD,CAAiB,CAAA;GAGzC,EAAK,WAAW,KACf,kBAAC,GAAD;IAAe,eAAe,EAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,aAAa,MAAM,CAAC,CAAC,IAAI;IAAG,UAAU,EAAM;IAAa,OAAM;cAClH,kBAAC,GAAD;KAAM,MAAM;KAAe,MAAK;IAAM,CAAA;GACzB,CAAA;GAEhB,EAAK,aAAa,KACjB,kBAAC,GAAD;IAAe,eAAe,EAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,aAAa,QAAQ,CAAC,CAAC,IAAI;IAAG,UAAU,EAAM;IAAe,OAAM;cACtH,kBAAC,GAAD;KAAM,MAAM;KAAiB,MAAK;IAAM,CAAA;GAC3B,CAAA;GAEhB,EAAK,YAAY,KAChB,kBAAC,GAAD;IAAe,eAAe,EAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,aAAa,OAAO,CAAC,CAAC,IAAI;IAAG,UAAU,EAAM;IAAc,OAAM;cACpH,kBAAC,GAAD;KAAM,MAAM;KAAgB,MAAK;IAAM,CAAA;GAC1B,CAAA;GAGhB,MAAa,KAAiB,MAAe,kBAAC,GAAD,CAAiB,CAAA;GAG9D,KAAgB,EAAK,OAAO,KAC3B,kBAAC,GAAD;IAAe,SAAS;IAAc,OAAM;cAC1C,kBAAC,GAAD;KAAM,MAAM;KAAe,MAAK;IAAM,CAAA;GACzB,CAAA;GAIhB,EAAK,MAAM,KACV,kBAAC,GAAD;IAAe,eAAe,EAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI;IAAG,UAAU,CAAC,EAAM;IAAS,OAAM;cACjG,kBAAC,GAAD;KAAM,MAAM;KAAiB,MAAK;IAAM,CAAA;GAC3B,CAAA;GAEhB,EAAK,MAAM,KACV,kBAAC,GAAD;IAAe,eAAe,EAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI;IAAG,UAAU,CAAC,EAAM;IAAS,OAAM;cACjG,kBAAC,GAAD;KAAM,MAAM;KAAoB,MAAK;IAAM,CAAA;GAC9B,CAAA;EAEf,EAAA,CAAA,GACD,KAAY,kBAAC,OAAD;GAAK,WAAU;aAAsC;EAAc,CAAA,CAC7E;;AAET;AAEA,IAAM,IAAa,EAAM,WACvB,OAAO,oBAAiB,CAAC,MAAM,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,CACnE;AAEA,SAAS,GAAgB,EAAE,eAAsD;CAC/E,IAAM,IAAW,kBAAC,OAAD;EAAK,WAAU;YAA8G,kBAAC,QAAD;GAAM,WAAU;aAAsC;EAAc,CAAA;CAAM,CAAA;CAExN,OACE,kBAAC,EAAM,UAAP;EAA0B;YACxB,kBAAC,GAAD,EAAsB,YAAW,CAAA;CACnB,CAAA;AAEpB;AA6EA,IAAM,IAAa,EAAM,cAAsC,IAAI;AAGnE,SAAS,IAAqC;CAC5C,IAAM,IAAM,EAAM,WAAW,CAAU;CACvC,IAAI,CAAC,GAAK,MAAU,MAAM,iFAAiF;CAC3G,OAAO;AACT;AAYA,IAAM,IAAyB,EAAM,WACnC,SAAgC,EAChC,YAAS,QACT,aAAU,IACV,iBAAc,oBACd,aACA,cACA,cAAW,IACX,YACA,kBACA,iBACA,aACA,oBACA,oBACA,YAAY,GACZ,uBAAoB,IACpB,uBAGA,UAAU,KAAY,UAEtB,cAAc,IACd,cACA,GAAG,KACF,GAAK;CACN,IAAM,IAAY,EAAM,OAAqC,IAAI,GAC3D,IAAsB,EAAM,OAAO,EAAK,GACxC,IAAe,EAAiB,GAChC,CAAC,GAAiB,KAAsB,EAAM,SAAS,EAAK,GAG5D,CAAC,IAAoB,MAAyB,EAAM,SAAS,CAAiB,GAC9E,IAAa,KAAkB,IAC/B,IAAgB,EAAM,aACzB,MAAe;EAEd,AADI,MAAmB,KAAA,KAAW,GAAsB,CAAC,GACzD,IAAqB,CAAC;CACxB,GACA,CAAC,GAAgB,CAAkB,CACrC,GACM,CAAC,GAAY,KAAiB,EAAM,SAAS,EAAE,GAC/C,IAAiB,EAAM,OAAuB,IAAI,GAClD,IAAgB,EAAM,OAAyB,IAAI,GACnD,IAAe,EAAM,OAAyB,IAAI,GAElD,IAAoB,OAAO,MAAe;EAC9C,IAAM,IAAK,EAAU;EAChB,OACL,IAAI,GAAe;GACjB,IAAM,IAAM,MAAM,EAAc,CAAI;GACpC,AAAI,KAAO,gBAAgB,KAAK,CAAG,KACjC,EAAG,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,KAAK,EAAI,CAAC,CAAC,CAAC,IAAI;EAElD,OAAO;GACL,IAAM,IAAS,IAAI,WAAW;GAI9B,AAHA,EAAO,eAAe;IACpB,EAAG,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,KAAK,EAAO,OAAiB,CAAC,CAAC,CAAC,IAAI;GACpE,GACA,EAAO,cAAc,CAAI;EAC3B;CACF,GAEM,IAAmB,OAAO,MAAe;EAC7C,IAAM,IAAK,EAAU;EACrB,IAAI,CAAC,KAAM,CAAC,GAAc;EAC1B,IAAM,IAAS,MAAM,EAAa,CAAI;EACtC,EAAG,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,cAAc;GAC/B,MAAM;GACN,OAAO;IAAE,KAAK,EAAO;IAAK,MAAM,EAAO;IAAM,MAAM,EAAO;GAAK;EACjE,CAAC,CAAC,CAAC,IAAI;CACT,GAEM,IAAS,EAAU;EACvB,mBAAmB;EACnB,YAAY;GACV,EAAW,UAAU;IACnB,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE;IAE1B,MAAM;KACJ,aAAa;KACb,WAAW;MAAC;MAAQ;MAAS;KAAQ;KACrC,WAAW,MAAiB,0BAA0B,KAAK,CAAI;KAC/D,gBAAgB;MACd,KAAK;MACL,QAAQ;KACV;IACF;GACF,CAAC;GACD,EAAY,UAAU;IACpB;IACA,kBACE;GACJ,CAAC;GACD,EAAU,UAAU,EAAE,YAAY,GAAM,CAAC;GACzC,EAAQ,UAAU;IAChB,YAAY;IACZ,aAAa;IACb,UAAU;IACV,YAAY;IACZ,UAAU,CAAC;IACX,UAAU,EAAE,QAAQ,GAAK;GAC3B,CAAC;GACD,EAAU,UAAU,EAAE,OAAO,CAAC,WAAW,WAAW,EAAE,CAAC;GACvD,EAAM,UAAU;IACd,aAAa;IACb,gBAAgB,EACd,OAAO,6BACT;GACF,CAAC;GACD;GACA,GAAI,KAAY,IAAkB,CAChC,EAAQ,UAAU;IAChB,gBAAgB,EAAE,OAAO,UAAU;IACnC,YAAY;KACV,OAAO,OAAO,EAAE,eACV,IAAwB,MAAM,EAAgB,CAAK,IACnD,IAAiB,EAAS,QAAO,MAAK,EAAE,MAAM,YAAY,CAAC,CAAC,SAAS,EAAM,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,IAClG,CAAC;KAEV,QAAQ,GAAyB,CAAe;IAClD;GACF,CAAC,CACH,IAAI,CAAC;GACL;GACA,EAAsB;GACtB;EACF;EACA;EACA,aAAa,MAAW,aAAa,aAAa,KAAA;EAClD;EACA,aAAa;GACX,aAAa,GAAO,GAAO,GAAQ,MAAU;IAC3C,IAAI,KAAS,CAAC,EAAM,cAAc,MAAM,QAAQ,OAAO;IACvD,IAAM,IAAO,EAAM,aAAa,MAAM;IAUtC,OATK,IACD,EAAK,KAAK,WAAW,QAAQ,KAC/B,EAAkB,CAAI,GACf,MAEL,KACF,EAAiB,CAAI,GACd,MAEF,KATW;GAUpB;GACA,cAAc,GAAO,MAAU;IAC7B,IAAM,IAAO,EAAM,eAAe,MAAM;IAUxC,OATK,IACD,EAAK,KAAK,WAAW,QAAQ,KAC/B,EAAkB,CAAI,GACf,MAEL,KACF,EAAiB,CAAI,GACd,MAEF,KATW;GAUpB;GACA,YAAY,EACV,OAAO,EACL,GAAG,GACH,wBACA,iCACF,EACF;EACF;EACA,WAAW,EAAE,QAAQ,QAAS;GAG5B,AAFA,EAAoB,UAAU,IAC9B,IAAW,MAAW,aAAa,EAAG,YAAY,IAAI,EAAG,QAAQ,CAAC,GAClE,qBAAqB;IACnB,EAAoB,UAAU;GAChC,CAAC;EACH;CACF,CAAC;CAqCD,IAnCA,EAAM,gBAAgB;EACpB,EAAU,UAAU;CACtB,GAAG,CAAC,CAAM,CAAC,GAGX,EAAM,gBAAgB;EACpB,IAAI,CAAC,GAAiB;EACtB,IAAM,KAAsB,MAAkB;GAC5C,AAAI,EAAe,WAAW,CAAC,EAAe,QAAQ,SAAS,EAAE,MAAc,KAC7E,EAAmB,EAAK;EAE5B;EAEA,OADA,SAAS,iBAAiB,aAAa,CAAkB,SAC5C,SAAS,oBAAoB,aAAa,CAAkB;CAC3E,GAAG,CAAC,CAAe,CAAC,GAEpB,EAAM,gBAAgB;EAChB,EAAoB,WACnB,KAED,OADY,MAAW,aAAa,EAAO,YAAY,IAAI,EAAO,QAAQ,MAE5E,EAAO,SAAS,WAAW,GAAS;GAClC,YAAY;GACZ,aAAa,MAAW,aAAa,aAAa,KAAA;EACpD,CAAC;CAEL,GAAG;EAAC;EAAQ;EAAS;CAAM,CAAC,GAG5B,EAAM,gBAAgB;EACpB,AAAI,KAAc,KAAQ,EAAc,EAAO,YAAY,CAAC;CAG9D,GAAG,CAAC,CAAU,CAAC,GAEX,CAAC,GAAQ,OAAO;CAQpB,IAAM,IAAuB;EAC3B;EACA;EACA;EACA,oBAAoB,EAAc,SAAS,MAAM;EACjD,aAAa,UAAqB,EAAa,SAAS,MAAM,IAAI,KAAA;EAClE,oBAAoB,GAAoB,MAAS,CAAC,CAAI;EACtD;EACA;EACA,oBAAoB,EAAc,CAAC,CAAU;EAC7C;EACA,iBAjBsB,MAAc;GAGpC,AAFA,EAAc,CAAC,GAEf,EAAO,SAAS,WAAW,GAAG,EAAE,aAAa,WAAW,CAAC;EAC3D;CAcA;CAEA,OACE,kBAAC,EAAW,UAAZ;EAAqB,OAAO;YAC1B,kBAAC,OAAD;GAAU;GAAK,GAAI;GAAO,WAAW,EAAG,YAAY,CAAS;aAA7D;IAEE,kBAAC,IAAD,EAAA,UACG,KACC,kBAAC,EAAO,KAAR;KAEE,KAAK;KACL,SAAS,MAAuB;MAAE,SAAS;MAAG,OAAO;MAAM,GAAG;KAAE;KAChE,SAAS;MAAE,SAAS;MAAG,OAAO;MAAG,GAAG;KAAE;KACtC,MAAM,IAAe,EAAE,SAAS,EAAE,IAAI;MAAE,SAAS;MAAG,OAAO;MAAM,GAAG;KAAE;KACtE,YAAY;MAAE,UAAU;MAAM,MAAM;KAAU;KAC9C,WAAU;KACV,YAAY,MAAM;MAChB,AAAI,EAAE,QAAQ,aACZ,EAAE,gBAAgB,GAClB,EAAmB,EAAK;KAE5B;eAEA,kBAAC,IAAD,EACE,WAAW,EAAE,OAAI,gBAAa;MAK5B,AAJA,EAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,cAAc;OACnC,MAAM;OACN,OAAO;QAAE;QAAI;OAAO;MACtB,CAAC,CAAC,CAAC,IAAI,GACP,EAAmB,EAAK;KAC1B,EACD,CAAA;IACS,GAvBN,cAuBM,EAEC,CAAA;IACjB,kBAAC,SAAD;KACE,KAAK;KACL,MAAK;KACL,QAAO;KACP,cAAW;KACX,WAAU;KACV,WAAW,MAAM;MACf,IAAM,IAAO,EAAE,OAAO,QAAQ;MAE9B,AADI,KAAM,EAAkB,CAAI,GAChC,EAAE,OAAO,QAAQ;KACnB;IACD,CAAA;IACA,KACC,kBAAC,SAAD;KACE,KAAK;KACL,MAAK;KACL,cAAW;KACX,WAAU;KACV,WAAW,MAAM;MACf,IAAM,IAAO,EAAE,OAAO,QAAQ;MAE9B,AADI,KAAM,EAAiB,CAAI,GAC/B,EAAE,OAAO,QAAQ;KACnB;IACD,CAAA;IAEF;GACE;;CACc,CAAA;AAEzB,CACA;AAEA,EAAuB,cAAc;AAOrC,SAAS,EAA0B,EAAE,eAA4C;CAC/E,IAAM,EAAE,WAAQ,YAAS,aAAU,eAAY,iBAAc,gBAAa,oBAAiB,EAAkB;CAC7G,IAAI,CAAC,GAAU,OAAO;CACtB,IAAM,IAAe,CAAC;CAEtB,OADI,CAAC,KAAgB,CAAC,IAAiB,OAErC,kBAAC,GAAD;EACU;EACC;EACK;EACJ;EACI;EACD;EACC;CACf,CAAA;AAEL;AACA,EAA0B,cAAc;AAGxC,SAAS,IAA4B;CACnC,IAAM,EAAE,WAAQ,eAAY,eAAY,sBAAmB,EAAkB,GAEvE,IADS,EACD,IAAS,EAAE,SAAS,EAAE,IAAI;EAAE,SAAS;EAAG,GAAG;CAAE;CAC3D,OACE,kBAAC,IAAD;EAAiB,MAAK;EAAO,SAAS;YACnC,IACC,kBAAC,EAAO,KAAR;GAAyB,SAAS;GAAO,SAAS;IAAE,SAAS;IAAG,GAAG;GAAE;GAAG,MAAM;GAAO,YAAY;IAAE,UAAU;IAAM,MAAM;GAAU;aACjI,kBAAC,YAAD;IACE,OAAO;IACP,WAAW,MAAM,EAAe,EAAE,OAAO,KAAK;IAC9C,YAAY;IACZ,cAAW;IACX,WAAU;GACX,CAAA;EACS,GARI,QAQJ,IAEZ,kBAAC,EAAO,KAAR;GAAyB,SAAS;GAAO,SAAS;IAAE,SAAS;IAAG,GAAG;GAAE;GAAG,MAAM;GAAO,YAAY;IAAE,UAAU;IAAM,MAAM;GAAU;aACjI,kBAAC,GAAD,EAAuB,UAAS,CAAA;EACtB,GAFI,QAEJ;CAEC,CAAA;AAErB;AACA,EAA0B,cAAc;AAMxC,SAAS,EAA+B,EAAE,cAAW,cAAW,MAAqD;CACnH,IAAM,EAAE,eAAY,oBAAiB,EAAkB,GACjD,IAAO,IAAa,4BAA4B,6BAChD,IAAO,IAAa,IAAU;CAiBpC,OAhBI,IAEA,kBAAC,GAAD;EACE,MAAK;EACL,SAAQ;EACR,MAAK;EACL,gBAAc;EACd,cAAY;EACZ,OAAO;EACI;EACX,SAAS;YAET,kBAAC,GAAD;GAAY;GAAM,MAAK;EAAM,CAAA;CACvB,CAAA,IAIV,kBAAC,GAAD;EACE,MAAK;EACL,SAAQ;EACR,MAAK;EACL,gBAAc;EACd,cAAY;EACZ,WAAW;EACA;EACX,SAAS;YAER,IAAa,WAAW;CACnB,CAAA;AAEZ;AACA,EAA+B,cAAc;AAG7C,IAAM,KAAqB,EAAM,WAC/B,SAAwB,GAAO,GAAK;CAClC,OACE,kBAAC,GAAD;EAA6B;EAAK,GAAI;YACpC,kBAAC,OAAD;GACE,WAAW,EACT,yFACA,sFACF;aAJF,CAME,kBAAC,GAAD,EACE,UAAU,EAAM,eAAe,kBAAC,GAAD,CAAiC,CAAA,IAAI,KAAA,EACrE,CAAA,GACD,kBAAC,GAAD,CAA4B,CAAA,CACzB;;CACiB,CAAA;AAE5B,CACF;AACA,GAAmB,cAAc;AAEjC,IAAM,KAAiB,OAAO,OAAO,IAAoB;CACvD,UAAU;CACV,SAAS;CACT,SAAS;CACT,cAAc;AAChB,CAAC,GASK,KAAiB,EAAM,WAC3B,SAAwB,EAAE,YAAS,YAAS,QAAQ,cAAW,GAAG,KAAS,GAAK;CAChF,IAAM,IAAS,EAAU;EACvB,mBAAmB;EACnB,YAAY;GACV,EAAW,UAAU;IACnB,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE;IAC1B,MAAM;KACJ,aAAa;KACb,WAAW;MAAC;MAAQ;MAAS;KAAQ;KACrC,WAAW,MAAiB,0BAA0B,KAAK,CAAI;KAC/D,gBAAgB;MACd,KAAK;MACL,QAAQ;KACV;IACF;GACF,CAAC;GACD,EAAU,UAAU,EAAE,YAAY,GAAM,CAAC;GACzC,EAAQ,UAAU;IAChB,YAAY;IACZ,aAAa;IACb,UAAU;IACV,YAAY;IACZ,UAAU,CAAC;IACX,UAAU,EAAE,QAAQ,GAAK;GAC3B,CAAC;GACD,EAAU,UAAU,EAAE,OAAO,CAAC,WAAW,WAAW,EAAE,CAAC;GACvD,EAAM,UAAU;IACd,aAAa;IACb,gBAAgB,EACd,OAAO,6BACT;GACF,CAAC;GACD;GACA,EAAQ,UAAU,EAChB,gBAAgB,EAAE,OAAO,UAAU,EACrC,CAAC;GACD;EACF;EACA;EACA,aAAa,MAAW,aAAa,aAAa,KAAA;EAClD,UAAU;EACV,aAAa,EACX,YAAY,EACV,OAAO,EAAG,GAAG,CAAa,EAC5B,EACF;CACF,CAAC;CAID,OAFK,IAGH,kBAAC,OAAD;EAAU;EAAK,GAAI;EAAkB;YACnC,kBAAC,GAAD,EAAuB,UAAS,CAAA;CAC7B,CAAA,IALa;AAOtB,CACA;AAEA,GAAe,cAAc"}
@@ -38,7 +38,7 @@ function _({ startHour: e, endHour: t }) {
38
38
  className: "relative shrink-0 w-[60px] border-r border-surface-border-strong",
39
39
  "aria-hidden": "true",
40
40
  children: n.map((e) => /* @__PURE__ */ r("div", {
41
- className: "text-ds-xs text-surface-fg-muted pr-ds-02 text-right",
41
+ className: "text-caption text-surface-fg-muted pr-ds-02 text-right",
42
42
  style: { height: `${i}%` },
43
43
  children: h(e)
44
44
  }, e))
@@ -63,7 +63,7 @@ function v({ date: n, events: a, startHour: o, endHour: f, slotDuration: h, onEv
63
63
  return /* @__PURE__ */ i("div", {
64
64
  className: "flex flex-1 flex-col min-w-ds-11",
65
65
  children: [y && /* @__PURE__ */ r("div", {
66
- className: e("text-center text-ds-sm font-semibold py-ds-02 border-b border-surface-border-strong", w ? "text-accent-11 bg-accent-2" : "text-surface-fg bg-surface-raised"),
66
+ className: e("text-center text-body-sm font-semibold py-ds-02 border-b border-surface-border-strong", w ? "text-accent-11 bg-accent-2" : "text-surface-fg bg-surface-raised"),
67
67
  children: s(n, "EEE d")
68
68
  }), /* @__PURE__ */ i("div", {
69
69
  className: "relative flex-1",
@@ -79,7 +79,7 @@ function v({ date: n, events: a, startHour: o, endHour: f, slotDuration: h, onEv
79
79
  let n = g(t, o, f), a = t.color ?? "accent", c = p[a], l = m[a];
80
80
  return /* @__PURE__ */ r("button", {
81
81
  type: "button",
82
- className: e("absolute left-ds-01 right-ds-01 rounded-control-inner px-ds-02 py-ds-01", "text-left text-ds-xs font-medium overflow-hidden cursor-pointer", "hover:shadow-raised transition-[box-shadow] duration-fast-02 ease-productive-standard", "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9", c),
82
+ className: e("absolute left-ds-01 right-ds-01 rounded-control-inner px-ds-02 py-ds-01", "text-left text-body-xs font-medium overflow-hidden cursor-pointer", "hover:shadow-raised transition-[box-shadow] duration-fast-02 ease-productive-standard", "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9", c),
83
83
  style: n,
84
84
  onClick: (e) => {
85
85
  e.stopPropagation(), _?.(t);
@@ -122,7 +122,7 @@ var y = n.forwardRef(({ view: t, date: o, events: c, onEventClick: l, onSlotClic
122
122
  ref: y,
123
123
  role: "region",
124
124
  "aria-label": t === "day" ? `Schedule for ${s(o, "EEEE, MMMM d, yyyy")}` : `Week schedule starting ${s(b[0], "MMMM d, yyyy")}`,
125
- className: e("flex rounded-control border border-surface-border-strong bg-surface-raised overflow-hidden", "h-[480px]", h),
125
+ className: e("flex rounded-control border border-card-strong bg-surface-raised overflow-hidden", "h-[480px]", h),
126
126
  ...g,
127
127
  children: [/* @__PURE__ */ r(_, {
128
128
  startHour: d,
@@ -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-caption 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-body-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-body-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-card-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":";;;;;;;AAwDA,IAAM,IAGF;CACF,QAAQ;CACR,SAAS;CACT,SAAS;CACT,OAAO;CACP,MAAM;CACN,SAAS;AACX,GAIM,IAGF;CACF,QAAQ;CACR,SAAS;CACT,SAAS;CACT,OAAO;CACP,MAAM;CACN,SAAS;AACX;AAMA,SAAS,EAAgB,GAAsB;CAI7C,OAHI,MAAS,IAAU,UACnB,IAAO,KAAW,GAAG,EAAK,OAC1B,MAAS,KAAW,UACjB,GAAG,IAAO,GAAG;AACtB;AAEA,SAAS,EACP,GACA,GACA,GACA;CACA,IAAM,KAAgB,IAAU,KAAa,IACvC,KACH,EAAS,EAAM,KAAK,IAAI,KAAa,KAAK,EAAW,EAAM,KAAK,GAC7D,IAAkB,EAAoB,EAAM,KAAK,EAAM,KAAK,GAE5D,IAAO,IAAoB,IAAgB,KAC3C,IAAU,IAAkB,IAAgB;CAElD,OAAO;EACL,KAAK,GAAG,EAAI;EACZ,QAAQ,GAAG,EAAO;CACpB;AACF;AAWA,SAAS,EAAW,EAAE,cAAW,cAA4B;CAC3D,IAAM,IAAkB,CAAC;CACzB,KAAK,IAAI,IAAI,GAAW,IAAI,GAAS,KACnC,EAAM,KAAK,CAAC;CAEd,IAAM,IAAa,OAAO,IAAU;CAEpC,OACE,kBAAC,OAAD;EACE,WAAU;EACV,eAAY;YAEX,EAAM,KAAK,MACV,kBAAC,OAAD;GAEE,WAAU;GACV,OAAO,EAAE,QAAQ,GAAG,EAAW,GAAG;aAEjC,EAAgB,CAAI;EAClB,GALE,CAKF,CACN;CACE,CAAA;AAET;AAaA,SAAS,EAAU,EACjB,SACA,WACA,cACA,YACA,iBACA,iBACA,gBACA,iBACiB;CACjB,IAAM,IAAY,EAAO,QAAQ,MAAM,EAAU,EAAE,OAAO,CAAI,CAAC,GACzD,KAAgB,IAAU,KAAa,IACvC,IAAY,IAAe,GAE3B,IAAsC,CAAC;CAC7C,KAAK,IAAI,IAAI,GAAG,IAAI,GAAW,KAAK;EAClC,IAAM,IAAY,IAAI,KAAK,CAAI;EAC/B,EAAU,SAAS,GAAW,IAAI,GAAc,GAAG,CAAC;EACpD,IAAM,IAAU,IAAI,KAAK,CAAS;EAElC,AADA,EAAQ,WAAW,EAAQ,WAAW,IAAI,CAAY,GACtD,EAAM,KAAK;GAAE,OAAO;GAAW,KAAK;EAAQ,CAAC;CAC/C;CAEA,IAAM,IAAc,EAAQ,CAAI,GAG5B,IAAiC;CACrC,IAAI,GAAa;EACf,IAAM,oBAAM,IAAI,KAAK,GACf,IAAU,EAAS,CAAG,GACtB,IAAS,EAAW,CAAG;EAC7B,AAAI,KAAW,KAAa,IAAU,MAEpC,MADoB,IAAU,KAAa,KAAK,KAChB,IAAgB;CAEpD;CAEA,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACG,KACC,kBAAC,OAAD;GACE,WAAW,EACT,yFACA,IACI,+BACA,mCACN;aAEC,EAAO,GAAM,OAAO;EAClB,CAAA,GAEP,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEG,EAAM,KAAK,GAAM,MAChB,kBAAC,UAAD;KAEE,MAAK;KACL,WAAW,EACT,wHACA,0GACA,IAAI,KAAM,IAAI,iCAAiC,8BACjD;KACA,OAAO,EAAE,QAAQ,GAAG,MAAM,EAAU,GAAG;KACvC,eAAe,IAAc,EAAK,OAAO,EAAK,GAAG;KACjD,cAAY,GAAG,EAAO,EAAK,OAAO,QAAQ,EAAE,MAAM,EAAO,EAAK,KAAK,QAAQ;IAC5E,GAVM,CAUN,CACF;IAGA,EAAU,KAAK,MAAU;KACxB,IAAM,IAAQ,EAAc,GAAO,GAAW,CAAO,GAC/C,IAAa,EAAM,SAAS,UAC5B,IAAa,EAAc,IAC3B,IAAW,EAAY;KAC7B,OACE,kBAAC,UAAD;MAEE,MAAK;MACL,WAAW,EACT,2EACA,qEACA,yFACA,iFACA,CACF;MACO;MACP,UAAU,MAAM;OAEd,AADA,EAAE,gBAAgB,GAClB,IAAe,CAAK;MACtB;MACA,cAAY,GAAG,EAAM,MAAM,IAAI,EAAO,EAAM,OAAO,QAAQ,EAAE,MAAM,EAAO,EAAM,KAAK,QAAQ;gBAE7F,kBAAC,QAAD;OAAM,WAAU;iBAAhB,CACE,kBAAC,QAAD;QACE,WAAW,EAAG,kDAAkD,CAAQ;QACxE,eAAY;OACb,CAAA,GACD,kBAAC,QAAD;QAAM,WAAU;kBAAgB,EAAM;OAAY,CAAA,CAC9C;;KACA,GAvBD,EAAM,EAuBL;IAEZ,CAAC;IAGA,KAAmB,QAClB,kBAAC,OAAD;KACE,WAAU;KACV,OAAO,EAAE,KAAK,GAAG,EAAgB,GAAG;KACpC,eAAY;eAEZ,kBAAC,GAAD;MAAK,OAAM;MAAQ,MAAK;MAAK,OAAA;MAAM,WAAU;KAAmC,CAAA;IAC7E,CAAA;GAEJ;IACF;;AAET;AAMA,IAAM,IAAe,EAAM,YAEvB,EACE,SACA,SACA,WACA,iBACA,gBACA,eAAY,GACZ,aAAU,IACV,kBAAe,IACf,cACA,GAAG,KAEL,MACG;CACH,IAAM,IAAe,EAAM,cAAc;EACvC,IAAI,MAAS,OAAO,OAAO,CAAC,CAAI;EAChC,IAAM,IAAY,EAAY,GAAM,EAAE,cAAc,EAAE,CAAC;EACvD,OAAO,MAAM,KAAK,EAAE,QAAQ,EAAE,IAAI,GAAG,MAAM,EAAQ,GAAW,CAAC,CAAC;CAClE,GAAG,CAAC,GAAM,CAAI,CAAC;CAEf,OACE,kBAAC,OAAD;EACO;EACL,MAAK;EACL,cACE,MAAS,QACL,gBAAgB,EAAO,GAAM,oBAAoB,MACjD,0BAA0B,EAAO,EAAK,IAAI,cAAc;EAE9D,WAAW,EACT,oFACA,aACA,CACF;EACA,GAAI;YAbN,CAeE,kBAAC,GAAD;GAAuB;GAAoB;EAAU,CAAA,GACrD,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAK,KAAK,MACT,kBAAC,GAAD;IAEE,MAAM;IACE;IACG;IACF;IACK;IACA;IACD;IACb,YAAY,MAAS;GACtB,GATM,EAAI,YAAY,CAStB,CACF;EACE,CAAA,CACF;;AAET,CACF;AACA,EAAa,cAAc"}
@@ -32,7 +32,7 @@ function b({ item: r, isActive: o, onClick: s }) {
32
32
  onClick: s,
33
33
  "aria-label": r.title,
34
34
  "aria-current": o ? "page" : void 0,
35
- className: n("flex h-16 w-full cursor-pointer flex-col items-center gap-ds-02 p-ds-02 pt-0 text-ds-sm transition-colors duration-fast-02 ease-productive-standard", o ? "font-semibold text-accent-11" : "text-surface-fg-subtle"),
35
+ className: n("flex h-16 w-full cursor-pointer flex-col items-center gap-ds-02 p-ds-02 pt-0 text-body-sm transition-colors duration-fast-02 ease-productive-standard", o ? "font-semibold text-accent-11" : "text-surface-fg-subtle"),
36
36
  children: /* @__PURE__ */ p("div", {
37
37
  className: "relative flex w-full flex-col items-center gap-ds-02",
38
38
  children: [
@@ -87,7 +87,7 @@ var x = o.forwardRef(({ currentPath: o = "/", user: y, primaryItems: x = [], mor
87
87
  children: [/* @__PURE__ */ p("div", {
88
88
  className: "mb-ds-04 flex items-center justify-between",
89
89
  children: [/* @__PURE__ */ f("span", {
90
- className: "text-ds-md font-semibold text-surface-fg",
90
+ className: "text-body-md font-semibold text-surface-fg",
91
91
  children: "More"
92
92
  }), /* @__PURE__ */ f("button", {
93
93
  onClick: () => k(!1),
@@ -104,7 +104,7 @@ var x = o.forwardRef(({ currentPath: o = "/", user: y, primaryItems: x = [], mor
104
104
  children: S.map((t) => /* @__PURE__ */ p(E, {
105
105
  href: t.href,
106
106
  onClick: () => k(!1),
107
- className: n("flex flex-col items-center gap-ds-02b rounded-overlay-lg p-ds-04 text-ds-sm transition-colors ease-productive-standard", M(t.href, t.exact) ? "bg-surface-raised-hover text-accent-11" : "text-surface-fg-subtle hover:bg-surface-raised-hover"),
107
+ className: n("flex flex-col items-center gap-ds-02b rounded-overlay-lg p-ds-04 text-body-sm transition-colors ease-productive-standard", M(t.href, t.exact) ? "bg-surface-raised-hover text-accent-11" : "text-surface-fg-subtle hover:bg-surface-raised-hover"),
108
108
  children: [/* @__PURE__ */ f("span", {
109
109
  className: "[&>svg]:h-ico-md [&>svg]:w-ico-md",
110
110
  children: /* @__PURE__ */ f(e, {
@@ -133,7 +133,7 @@ var x = o.forwardRef(({ currentPath: o = "/", user: y, primaryItems: x = [], mor
133
133
  "aria-expanded": O,
134
134
  whileTap: D ? void 0 : { y: -2 },
135
135
  transition: D ? { duration: 0 } : t.snappy,
136
- className: n("flex h-16 max-w-[70px] flex-1 cursor-pointer flex-col items-center gap-ds-02 p-ds-02 pt-0 text-ds-sm", O || N ? "font-semibold text-accent-11" : "text-surface-fg-subtle"),
136
+ className: n("flex h-16 max-w-[70px] flex-1 cursor-pointer flex-col items-center gap-ds-02 p-ds-02 pt-0 text-body-sm", O || N ? "font-semibold text-accent-11" : "text-surface-fg-subtle"),
137
137
  children: /* @__PURE__ */ p("div", {
138
138
  className: "relative flex w-full flex-col items-center gap-ds-02",
139
139
  children: [
@@ -1 +1 @@
1
- {"version":3,"file":"bottom-navbar.js","names":[],"sources":["../../src/shell/bottom-navbar.tsx"],"sourcesContent":["'use client'\n\n/**\n * BottomNavbar -- Mobile bottom navigation bar.\n *\n * Props-driven: accepts currentPath, user, navItems instead of\n * reading from Remix hooks or Zustand stores.\n */\nimport { IconDots, IconX } from '@tabler/icons-react'\nimport { AnimatePresence, motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\nimport { useCallback,useEffect, useRef, useState } from 'react'\n\nimport { Icon } from '../ui/icon'\nimport { IconProvider } from '../ui/icon-context'\nimport type { IconInput } from '../ui/lib/icon-input'\nimport { springs } from '../ui/lib/motion'\nimport { normalizeIcon } from '../ui/lib/normalize-icon'\nimport { cn } from '../ui/lib/utils'\nimport { useLink } from './link-context'\n\n// -----------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------\n\nexport interface BottomNavItem {\n title: string\n href: string\n /** Icon for this nav item. Accepts any `IconInput`. */\n icon: IconInput\n /** When true, the route matches only when the path is exactly equal */\n exact?: boolean\n /** Notification badge count. 0 or undefined = hidden, 1–99 = shown, >99 = \"99+\" */\n badge?: number\n}\n\nexport interface BottomNavbarUser {\n name: string\n role?: string\n}\n\nexport interface BottomNavbarProps\n extends React.HTMLAttributes<HTMLElement> {\n /** Currently active pathname */\n currentPath?: string\n /** User information (used to determine admin status, presence) */\n user?: BottomNavbarUser | null\n /** Primary nav items shown directly in the bottom bar (max 4 recommended) */\n primaryItems?: BottomNavItem[]\n /** Additional items shown in the \"More\" overflow menu */\n moreItems?: BottomNavItem[]\n /** Additional className for the nav element */\n className?: string\n}\n\n// -----------------------------------------------------------------------\n// NavBadge (internal)\n// -----------------------------------------------------------------------\n\nfunction NavBadge({ count }: { count: number }) {\n if (!count || count <= 0) return null\n const display = count > 99 ? '99+' : String(count)\n const isMultiDigit = count >= 10\n return (\n <span\n aria-label={`${count} notifications`}\n className={cn(\n 'absolute -right-1 -top-0.5 flex h-4 min-w-4 items-center justify-center rounded-pill bg-error-9 text-[10px] font-semibold leading-none text-error-fg animate-in zoom-in-75',\n isMultiDigit ? 'px-0.5' : '',\n )}\n >\n {display}\n </span>\n )\n}\n\n// -----------------------------------------------------------------------\n// BottomNavLink (internal)\n// -----------------------------------------------------------------------\n\nfunction BottomNavLink({\n item,\n isActive,\n onClick,\n}: {\n item: BottomNavItem\n isActive: boolean\n onClick?: () => void\n}) {\n const Link = useLink()\n const prefersReducedMotion = useReducedMotion()\n return (\n <motion.div whileTap={prefersReducedMotion ? undefined : { scale: 0.96 }} transition={prefersReducedMotion ? { duration: 0 } : springs.snappy} className=\"flex max-w-[70px] flex-1\">\n <Link\n href={item.href}\n onClick={onClick}\n aria-label={item.title}\n aria-current={isActive ? 'page' : undefined}\n className={cn(\n 'flex h-16 w-full cursor-pointer flex-col items-center gap-ds-02 p-ds-02 pt-0 text-ds-sm transition-colors duration-fast-02 ease-productive-standard',\n isActive\n ? 'font-semibold text-accent-11'\n : 'text-surface-fg-subtle',\n )}\n >\n <div className=\"relative flex w-full flex-col items-center gap-ds-02\">\n {isActive && (\n <motion.div\n layoutId=\"bottom-nav-indicator\"\n className=\"absolute top-0 h-[3px] w-full rounded-b-control-inner bg-accent-9 p-0\"\n aria-hidden=\"true\"\n transition={prefersReducedMotion ? { duration: 0 } : springs.snappy}\n />\n )}\n <div className=\"relative p-ds-03\">\n <span className=\"[&>svg]:h-ico-md [&>svg]:w-ico-md\" aria-hidden=\"true\"><IconProvider size=\"md\">{normalizeIcon(item.icon)}</IconProvider></span>\n {item.badge != null && <NavBadge count={item.badge} />}\n </div>\n <span className=\"text-center\">{item.title}</span>\n </div>\n </Link>\n </motion.div>\n )\n}\n\n// -----------------------------------------------------------------------\n// BottomNavbar\n// -----------------------------------------------------------------------\n\nconst BottomNavbar = React.forwardRef<HTMLElement, BottomNavbarProps>(\n (\n {\n currentPath = '/',\n user: _user,\n primaryItems = [],\n moreItems = [],\n className,\n ...props\n },\n ref,\n ) => {\n const Link = useLink()\n const prefersReducedMotion = useReducedMotion()\n const [showMore, setShowMore] = useState(false)\n const overlayRef = useRef<HTMLDivElement>(null)\n\n const closeMore = useCallback(() => setShowMore(false), [])\n\n // Focus first focusable item when overlay opens\n useEffect(() => {\n if (showMore && overlayRef.current) {\n const firstFocusable = overlayRef.current.querySelector<HTMLElement>('a, button')\n firstFocusable?.focus()\n }\n }, [showMore])\n\n const isActive = (path: string, exact = false) => {\n if (exact || path === '/') {\n return currentPath === path\n }\n return currentPath.startsWith(path)\n }\n\n // Check if any \"more\" item is active\n const isMoreActive = moreItems.some((item) =>\n isActive(item.href, item.exact),\n )\n\n return (\n <>\n {/* More Menu Overlay */}\n <AnimatePresence>\n {showMore && (\n // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events -- backdrop overlay, dismiss via mouse only; keyboard users close via Escape\n <div\n className=\"fixed inset-0 z-overlay md:hidden\"\n onClick={() => setShowMore(false)}\n >\n <div className=\"absolute inset-0 bg-overlay\" />\n { }\n <motion.div\n ref={overlayRef}\n role=\"dialog\"\n aria-label=\"More navigation\"\n initial={{ y: '100%' }}\n animate={{ y: 0 }}\n exit={{ y: '100%' }}\n transition={prefersReducedMotion ? { duration: 0 } : springs.smooth}\n className=\"absolute bottom-[72px] left-0 right-0 rounded-t-bubble border-t border-surface-border-strong bg-surface-overlay p-ds-05 pb-ds-03\"\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => {\n e.stopPropagation()\n if (e.key === 'Escape') closeMore()\n }}\n >\n <div className=\"mb-ds-04 flex items-center justify-between\">\n <span className=\"text-ds-md font-semibold text-surface-fg\">\n More\n </span>\n <button\n onClick={() => setShowMore(false)}\n aria-label=\"Close more menu\"\n className=\"flex h-ds-sm w-ds-sm items-center justify-center rounded-pill hover:bg-surface-raised-hover\"\n >\n <Icon icon={IconX} size=\"sm\" className=\"text-surface-fg-muted\" />\n </button>\n </div>\n <div className=\"grid grid-cols-4 gap-ds-03\">\n {moreItems.map((item) => (\n <Link\n key={item.href}\n href={item.href}\n onClick={() => setShowMore(false)}\n className={cn(\n 'flex flex-col items-center gap-ds-02b rounded-overlay-lg p-ds-04 text-ds-sm transition-colors ease-productive-standard',\n isActive(item.href, item.exact)\n ? 'bg-surface-raised-hover text-accent-11'\n : 'text-surface-fg-subtle hover:bg-surface-raised-hover',\n )}\n >\n <span className=\"[&>svg]:h-ico-md [&>svg]:w-ico-md\"><IconProvider size=\"md\">{normalizeIcon(item.icon)}</IconProvider></span>\n <span className=\"text-center\">\n {item.title}\n </span>\n </Link>\n ))}\n </div>\n </motion.div>\n </div>\n )}\n </AnimatePresence>\n\n {/* Bottom Navigation Bar */}\n <nav\n {...props}\n ref={ref}\n aria-label=\"Mobile navigation\"\n className={cn(\n 'fixed bottom-0 left-0 right-0 z-sticky flex w-full flex-row items-start justify-between border-t border-surface-border-strong bg-surface-chrome px-ds-05 pb-safe pt-0 md:hidden',\n className,\n )}\n >\n {primaryItems.map((item) => (\n <BottomNavLink\n key={item.href}\n item={item}\n isActive={isActive(item.href, item.exact)}\n />\n ))}\n\n {/* More Button */}\n {moreItems.length > 0 && (\n <motion.button\n type=\"button\"\n onClick={() => setShowMore(!showMore)}\n aria-label=\"More navigation options\"\n aria-expanded={showMore}\n whileTap={prefersReducedMotion ? undefined : { y: -2 }}\n transition={prefersReducedMotion ? { duration: 0 } : springs.snappy}\n className={cn(\n 'flex h-16 max-w-[70px] flex-1 cursor-pointer flex-col items-center gap-ds-02 p-ds-02 pt-0 text-ds-sm',\n showMore || isMoreActive\n ? 'font-semibold text-accent-11'\n : 'text-surface-fg-subtle',\n )}\n >\n <div className=\"relative flex w-full flex-col items-center gap-ds-02\">\n {(showMore || isMoreActive) && (\n <motion.div\n layoutId=\"bottom-nav-indicator\"\n className=\"absolute top-0 h-[3px] w-full rounded-b-control-inner bg-accent-9 p-0\"\n aria-hidden=\"true\"\n transition={prefersReducedMotion ? { duration: 0 } : springs.snappy}\n />\n )}\n <div className=\"p-ds-03\">\n <Icon icon={IconDots} />\n </div>\n <span className=\"text-center\">More</span>\n </div>\n </motion.button>\n )}\n </nav>\n </>\n )\n },\n)\nBottomNavbar.displayName = 'BottomNavbar'\n\nexport { BottomNavbar }\n"],"mappings":"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"}
1
+ {"version":3,"file":"bottom-navbar.js","names":[],"sources":["../../src/shell/bottom-navbar.tsx"],"sourcesContent":["'use client'\n\n/**\n * BottomNavbar -- Mobile bottom navigation bar.\n *\n * Props-driven: accepts currentPath, user, navItems instead of\n * reading from Remix hooks or Zustand stores.\n */\nimport { IconDots, IconX } from '@tabler/icons-react'\nimport { AnimatePresence, motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\nimport { useCallback,useEffect, useRef, useState } from 'react'\n\nimport { Icon } from '../ui/icon'\nimport { IconProvider } from '../ui/icon-context'\nimport type { IconInput } from '../ui/lib/icon-input'\nimport { springs } from '../ui/lib/motion'\nimport { normalizeIcon } from '../ui/lib/normalize-icon'\nimport { cn } from '../ui/lib/utils'\nimport { useLink } from './link-context'\n\n// -----------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------\n\nexport interface BottomNavItem {\n title: string\n href: string\n /** Icon for this nav item. Accepts any `IconInput`. */\n icon: IconInput\n /** When true, the route matches only when the path is exactly equal */\n exact?: boolean\n /** Notification badge count. 0 or undefined = hidden, 1–99 = shown, >99 = \"99+\" */\n badge?: number\n}\n\nexport interface BottomNavbarUser {\n name: string\n role?: string\n}\n\nexport interface BottomNavbarProps\n extends React.HTMLAttributes<HTMLElement> {\n /** Currently active pathname */\n currentPath?: string\n /** User information (used to determine admin status, presence) */\n user?: BottomNavbarUser | null\n /** Primary nav items shown directly in the bottom bar (max 4 recommended) */\n primaryItems?: BottomNavItem[]\n /** Additional items shown in the \"More\" overflow menu */\n moreItems?: BottomNavItem[]\n /** Additional className for the nav element */\n className?: string\n}\n\n// -----------------------------------------------------------------------\n// NavBadge (internal)\n// -----------------------------------------------------------------------\n\nfunction NavBadge({ count }: { count: number }) {\n if (!count || count <= 0) return null\n const display = count > 99 ? '99+' : String(count)\n const isMultiDigit = count >= 10\n return (\n <span\n aria-label={`${count} notifications`}\n className={cn(\n 'absolute -right-1 -top-0.5 flex h-4 min-w-4 items-center justify-center rounded-pill bg-error-9 text-[10px] font-semibold leading-none text-error-fg animate-in zoom-in-75',\n isMultiDigit ? 'px-0.5' : '',\n )}\n >\n {display}\n </span>\n )\n}\n\n// -----------------------------------------------------------------------\n// BottomNavLink (internal)\n// -----------------------------------------------------------------------\n\nfunction BottomNavLink({\n item,\n isActive,\n onClick,\n}: {\n item: BottomNavItem\n isActive: boolean\n onClick?: () => void\n}) {\n const Link = useLink()\n const prefersReducedMotion = useReducedMotion()\n return (\n <motion.div whileTap={prefersReducedMotion ? undefined : { scale: 0.96 }} transition={prefersReducedMotion ? { duration: 0 } : springs.snappy} className=\"flex max-w-[70px] flex-1\">\n <Link\n href={item.href}\n onClick={onClick}\n aria-label={item.title}\n aria-current={isActive ? 'page' : undefined}\n className={cn(\n 'flex h-16 w-full cursor-pointer flex-col items-center gap-ds-02 p-ds-02 pt-0 text-body-sm transition-colors duration-fast-02 ease-productive-standard',\n isActive\n ? 'font-semibold text-accent-11'\n : 'text-surface-fg-subtle',\n )}\n >\n <div className=\"relative flex w-full flex-col items-center gap-ds-02\">\n {isActive && (\n <motion.div\n layoutId=\"bottom-nav-indicator\"\n className=\"absolute top-0 h-[3px] w-full rounded-b-control-inner bg-accent-9 p-0\"\n aria-hidden=\"true\"\n transition={prefersReducedMotion ? { duration: 0 } : springs.snappy}\n />\n )}\n <div className=\"relative p-ds-03\">\n <span className=\"[&>svg]:h-ico-md [&>svg]:w-ico-md\" aria-hidden=\"true\"><IconProvider size=\"md\">{normalizeIcon(item.icon)}</IconProvider></span>\n {item.badge != null && <NavBadge count={item.badge} />}\n </div>\n <span className=\"text-center\">{item.title}</span>\n </div>\n </Link>\n </motion.div>\n )\n}\n\n// -----------------------------------------------------------------------\n// BottomNavbar\n// -----------------------------------------------------------------------\n\nconst BottomNavbar = React.forwardRef<HTMLElement, BottomNavbarProps>(\n (\n {\n currentPath = '/',\n user: _user,\n primaryItems = [],\n moreItems = [],\n className,\n ...props\n },\n ref,\n ) => {\n const Link = useLink()\n const prefersReducedMotion = useReducedMotion()\n const [showMore, setShowMore] = useState(false)\n const overlayRef = useRef<HTMLDivElement>(null)\n\n const closeMore = useCallback(() => setShowMore(false), [])\n\n // Focus first focusable item when overlay opens\n useEffect(() => {\n if (showMore && overlayRef.current) {\n const firstFocusable = overlayRef.current.querySelector<HTMLElement>('a, button')\n firstFocusable?.focus()\n }\n }, [showMore])\n\n const isActive = (path: string, exact = false) => {\n if (exact || path === '/') {\n return currentPath === path\n }\n return currentPath.startsWith(path)\n }\n\n // Check if any \"more\" item is active\n const isMoreActive = moreItems.some((item) =>\n isActive(item.href, item.exact),\n )\n\n return (\n <>\n {/* More Menu Overlay */}\n <AnimatePresence>\n {showMore && (\n // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events -- backdrop overlay, dismiss via mouse only; keyboard users close via Escape\n <div\n className=\"fixed inset-0 z-overlay md:hidden\"\n onClick={() => setShowMore(false)}\n >\n <div className=\"absolute inset-0 bg-overlay\" />\n { }\n <motion.div\n ref={overlayRef}\n role=\"dialog\"\n aria-label=\"More navigation\"\n initial={{ y: '100%' }}\n animate={{ y: 0 }}\n exit={{ y: '100%' }}\n transition={prefersReducedMotion ? { duration: 0 } : springs.smooth}\n className=\"absolute bottom-[72px] left-0 right-0 rounded-t-bubble border-t border-surface-border-strong bg-surface-overlay p-ds-05 pb-ds-03\"\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => {\n e.stopPropagation()\n if (e.key === 'Escape') closeMore()\n }}\n >\n <div className=\"mb-ds-04 flex items-center justify-between\">\n <span className=\"text-body-md font-semibold text-surface-fg\">\n More\n </span>\n <button\n onClick={() => setShowMore(false)}\n aria-label=\"Close more menu\"\n className=\"flex h-ds-sm w-ds-sm items-center justify-center rounded-pill hover:bg-surface-raised-hover\"\n >\n <Icon icon={IconX} size=\"sm\" className=\"text-surface-fg-muted\" />\n </button>\n </div>\n <div className=\"grid grid-cols-4 gap-ds-03\">\n {moreItems.map((item) => (\n <Link\n key={item.href}\n href={item.href}\n onClick={() => setShowMore(false)}\n className={cn(\n 'flex flex-col items-center gap-ds-02b rounded-overlay-lg p-ds-04 text-body-sm transition-colors ease-productive-standard',\n isActive(item.href, item.exact)\n ? 'bg-surface-raised-hover text-accent-11'\n : 'text-surface-fg-subtle hover:bg-surface-raised-hover',\n )}\n >\n <span className=\"[&>svg]:h-ico-md [&>svg]:w-ico-md\"><IconProvider size=\"md\">{normalizeIcon(item.icon)}</IconProvider></span>\n <span className=\"text-center\">\n {item.title}\n </span>\n </Link>\n ))}\n </div>\n </motion.div>\n </div>\n )}\n </AnimatePresence>\n\n {/* Bottom Navigation Bar */}\n <nav\n {...props}\n ref={ref}\n aria-label=\"Mobile navigation\"\n className={cn(\n 'fixed bottom-0 left-0 right-0 z-sticky flex w-full flex-row items-start justify-between border-t border-surface-border-strong bg-surface-chrome px-ds-05 pb-safe pt-0 md:hidden',\n className,\n )}\n >\n {primaryItems.map((item) => (\n <BottomNavLink\n key={item.href}\n item={item}\n isActive={isActive(item.href, item.exact)}\n />\n ))}\n\n {/* More Button */}\n {moreItems.length > 0 && (\n <motion.button\n type=\"button\"\n onClick={() => setShowMore(!showMore)}\n aria-label=\"More navigation options\"\n aria-expanded={showMore}\n whileTap={prefersReducedMotion ? undefined : { y: -2 }}\n transition={prefersReducedMotion ? { duration: 0 } : springs.snappy}\n className={cn(\n 'flex h-16 max-w-[70px] flex-1 cursor-pointer flex-col items-center gap-ds-02 p-ds-02 pt-0 text-body-sm',\n showMore || isMoreActive\n ? 'font-semibold text-accent-11'\n : 'text-surface-fg-subtle',\n )}\n >\n <div className=\"relative flex w-full flex-col items-center gap-ds-02\">\n {(showMore || isMoreActive) && (\n <motion.div\n layoutId=\"bottom-nav-indicator\"\n className=\"absolute top-0 h-[3px] w-full rounded-b-control-inner bg-accent-9 p-0\"\n aria-hidden=\"true\"\n transition={prefersReducedMotion ? { duration: 0 } : springs.snappy}\n />\n )}\n <div className=\"p-ds-03\">\n <Icon icon={IconDots} />\n </div>\n <span className=\"text-center\">More</span>\n </div>\n </motion.button>\n )}\n </nav>\n </>\n )\n },\n)\nBottomNavbar.displayName = 'BottomNavbar'\n\nexport { BottomNavbar }\n"],"mappings":"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"}
@@ -50,23 +50,23 @@ function te({ notification: e, onRead: n, onNavigate: i, getRoute: o, onDismiss:
50
50
  children: [
51
51
  /* @__PURE__ */ x(f, {
52
52
  as: "p",
53
- className: t("text-ds-md text-surface-fg", !e.isRead && "font-semibold"),
53
+ className: t("text-body-md text-surface-fg", !e.isRead && "font-semibold"),
54
54
  children: e.title
55
55
  }),
56
56
  e.body && /* @__PURE__ */ x("p", {
57
- className: "mt-ds-01 line-clamp-2 text-ds-sm text-surface-fg-subtle",
57
+ className: "mt-ds-01 line-clamp-2 text-body-sm text-surface-fg-subtle",
58
58
  children: e.body
59
59
  }),
60
60
  /* @__PURE__ */ S("div", {
61
61
  className: "mt-ds-02 flex items-center gap-ds-03",
62
62
  children: [/* @__PURE__ */ x("span", {
63
- className: "text-ds-sm text-surface-fg-subtle",
63
+ className: "text-body-sm text-surface-fg-subtle",
64
64
  children: g(e.createdAt)
65
65
  }), e.project && /* @__PURE__ */ S(b, { children: [/* @__PURE__ */ x("span", {
66
66
  className: "text-surface-fg-subtle",
67
67
  children: "·"
68
68
  }), /* @__PURE__ */ x(f, {
69
- className: "text-ds-sm text-surface-fg-subtle",
69
+ className: "text-body-sm text-surface-fg-subtle",
70
70
  children: e.project.title
71
71
  })] })]
72
72
  }),
@@ -122,10 +122,10 @@ var M = _.forwardRef(({ notifications: a = [], unreadCount: f, open: g, onOpenCh
122
122
  children: [/* @__PURE__ */ S("div", {
123
123
  className: "flex items-center gap-ds-03",
124
124
  children: [!K && /* @__PURE__ */ x("h3", {
125
- className: "text-ds-md font-semibold text-surface-fg",
125
+ className: "text-body-md font-semibold text-surface-fg",
126
126
  children: "Notifications"
127
127
  }), q > 0 && /* @__PURE__ */ x("span", {
128
- className: "flex h-5 min-w-5 items-center justify-center rounded-pill bg-accent-2 px-ds-02b text-ds-sm font-semibold text-accent-11",
128
+ className: "flex h-5 min-w-5 items-center justify-center rounded-pill bg-accent-2 px-ds-02b text-body-sm font-semibold text-accent-11",
129
129
  children: q
130
130
  })]
131
131
  }), /* @__PURE__ */ S("div", {
@@ -133,7 +133,7 @@ var M = _.forwardRef(({ notifications: a = [], unreadCount: f, open: g, onOpenCh
133
133
  children: [q > 0 && N && /* @__PURE__ */ S("button", {
134
134
  type: "button",
135
135
  onClick: N,
136
- className: "flex items-center gap-ds-02 text-ds-sm text-surface-fg-subtle transition-colors hover:text-accent-11",
136
+ className: "flex items-center gap-ds-02 text-body-sm text-surface-fg-subtle transition-colors hover:text-accent-11",
137
137
  children: [/* @__PURE__ */ x(r, {
138
138
  icon: w,
139
139
  size: "sm"
@@ -157,11 +157,11 @@ var M = _.forwardRef(({ notifications: a = [], unreadCount: f, open: g, onOpenCh
157
157
  })
158
158
  }),
159
159
  /* @__PURE__ */ x("p", {
160
- className: "mt-ds-04 text-ds-md text-surface-fg-subtle",
160
+ className: "mt-ds-04 text-body-md text-surface-fg-subtle",
161
161
  children: "No notifications yet"
162
162
  }),
163
163
  /* @__PURE__ */ x("p", {
164
- className: "mt-ds-02 text-ds-sm text-surface-fg-subtle",
164
+ className: "mt-ds-02 text-body-sm text-surface-fg-subtle",
165
165
  children: "You're all caught up!"
166
166
  })
167
167
  ]
@@ -174,7 +174,7 @@ var M = _.forwardRef(({ notifications: a = [], unreadCount: f, open: g, onOpenCh
174
174
  return !t || t.length === 0 ? null : /* @__PURE__ */ S("div", { children: [/* @__PURE__ */ x("div", {
175
175
  className: "sticky top-0 z-raised bg-surface-overlay px-ds-05 py-ds-02b",
176
176
  children: /* @__PURE__ */ x("span", {
177
- className: "text-ds-sm font-medium text-surface-fg-subtle",
177
+ className: "text-body-sm font-medium text-surface-fg-subtle",
178
178
  children: e
179
179
  })
180
180
  }), t.map((e, t) => /* @__PURE__ */ x(D.div, {
@@ -202,7 +202,7 @@ var M = _.forwardRef(({ notifications: a = [], unreadCount: f, open: g, onOpenCh
202
202
  ...H,
203
203
  ref: U,
204
204
  "aria-label": q > 0 ? `Notifications, ${q} unread` : "Notifications",
205
- className: t("relative flex h-ds-sm-plus w-ds-sm-plus items-center justify-center rounded-pill border border-surface-border-strong bg-surface-raised text-surface-fg-muted transition-colors ease-productive-standard hover:bg-surface-raised-hover", V),
205
+ className: t("relative flex h-ds-sm-plus w-ds-sm-plus items-center justify-center rounded-pill border border-card-strong bg-surface-raised text-surface-fg-muted transition-colors ease-productive-standard hover:bg-surface-raised-hover", V),
206
206
  children: [/* @__PURE__ */ x(r, {
207
207
  icon: C,
208
208
  size: "sm"
@@ -220,7 +220,7 @@ var M = _.forwardRef(({ notifications: a = [], unreadCount: f, open: g, onOpenCh
220
220
  duration: e.slow01,
221
221
  ease: "easeInOut"
222
222
  },
223
- className: "absolute -right-ds-01 -top-ds-01 flex h-4 min-w-4 items-center justify-center rounded-pill bg-accent-9 px-ds-02 text-ds-xs font-semibold text-accent-fg",
223
+ className: "absolute -right-ds-01 -top-ds-01 flex h-4 min-w-4 items-center justify-center rounded-pill bg-accent-9 px-ds-02 text-body-xs font-semibold text-accent-fg",
224
224
  children: q > 99 ? "99+" : q
225
225
  })]
226
226
  });
@@ -242,7 +242,7 @@ var M = _.forwardRef(({ notifications: a = [], unreadCount: f, open: g, onOpenCh
242
242
  className: t("p-0", B),
243
243
  responsive: !1,
244
244
  children: [/* @__PURE__ */ x(m, {
245
- className: "px-ds-05 pt-ds-04 text-ds-md font-semibold text-surface-fg",
245
+ className: "px-ds-05 pt-ds-04 text-body-md font-semibold text-surface-fg",
246
246
  children: "Notifications"
247
247
  }), Q]
248
248
  })]