@devalok/shilp-sutra 0.49.5 → 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 (228) hide show
  1. package/AGENTS.md +12 -4
  2. package/MIGRATION.md +6 -0
  3. package/dist/_chunks/badge-group.js +4 -4
  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.js +3 -3
  120. package/dist/ui/autocomplete.js.map +1 -1
  121. package/dist/ui/avatar.js +5 -5
  122. package/dist/ui/avatar.js.map +1 -1
  123. package/dist/ui/banner.js +1 -1
  124. package/dist/ui/banner.js.map +1 -1
  125. package/dist/ui/breadcrumb.js +1 -1
  126. package/dist/ui/breadcrumb.js.map +1 -1
  127. package/dist/ui/button.js +7 -7
  128. package/dist/ui/button.js.map +1 -1
  129. package/dist/ui/card.d.ts +1 -1
  130. package/dist/ui/card.js +1 -1
  131. package/dist/ui/card.js.map +1 -1
  132. package/dist/ui/charts/gauge-chart.js +2 -2
  133. package/dist/ui/charts/gauge-chart.js.map +1 -1
  134. package/dist/ui/charts/pie-chart.js +1 -1
  135. package/dist/ui/charts/pie-chart.js.map +1 -1
  136. package/dist/ui/charts/radar-chart.d.ts +19 -1
  137. package/dist/ui/charts/radar-chart.d.ts.map +1 -1
  138. package/dist/ui/charts/radar-chart.js +80 -42
  139. package/dist/ui/charts/radar-chart.js.map +1 -1
  140. package/dist/ui/code.js +2 -2
  141. package/dist/ui/code.js.map +1 -1
  142. package/dist/ui/color-input.js +9 -9
  143. package/dist/ui/color-input.js.map +1 -1
  144. package/dist/ui/combobox.js +16 -16
  145. package/dist/ui/combobox.js.map +1 -1
  146. package/dist/ui/context-menu.js +6 -6
  147. package/dist/ui/context-menu.js.map +1 -1
  148. package/dist/ui/data-table-body.js +1 -1
  149. package/dist/ui/data-table-body.js.map +1 -1
  150. package/dist/ui/data-table-bulk-actions.js +1 -1
  151. package/dist/ui/data-table-bulk-actions.js.map +1 -1
  152. package/dist/ui/data-table-card.js +1 -1
  153. package/dist/ui/data-table-card.js.map +1 -1
  154. package/dist/ui/data-table-header.js +1 -1
  155. package/dist/ui/data-table-header.js.map +1 -1
  156. package/dist/ui/data-table-pagination.js +3 -3
  157. package/dist/ui/data-table-pagination.js.map +1 -1
  158. package/dist/ui/data-table-toolbar.js +1 -1
  159. package/dist/ui/data-table-toolbar.js.map +1 -1
  160. package/dist/ui/data-table.js +1 -1
  161. package/dist/ui/data-table.js.map +1 -1
  162. package/dist/ui/dialog.js +2 -2
  163. package/dist/ui/dialog.js.map +1 -1
  164. package/dist/ui/dot.js +4 -4
  165. package/dist/ui/dot.js.map +1 -1
  166. package/dist/ui/dropdown-menu.js +6 -6
  167. package/dist/ui/dropdown-menu.js.map +1 -1
  168. package/dist/ui/file-upload.js +5 -5
  169. package/dist/ui/file-upload.js.map +1 -1
  170. package/dist/ui/form.js +1 -1
  171. package/dist/ui/form.js.map +1 -1
  172. package/dist/ui/input-otp.d.ts.map +1 -1
  173. package/dist/ui/input-otp.js +3 -3
  174. package/dist/ui/input-otp.js.map +1 -1
  175. package/dist/ui/input.js +8 -8
  176. package/dist/ui/input.js.map +1 -1
  177. package/dist/ui/label.js +1 -1
  178. package/dist/ui/label.js.map +1 -1
  179. package/dist/ui/lib/utils.d.ts.map +1 -1
  180. package/dist/ui/lib/utils.js +42 -13
  181. package/dist/ui/lib/utils.js.map +1 -1
  182. package/dist/ui/menubar.js +7 -7
  183. package/dist/ui/menubar.js.map +1 -1
  184. package/dist/ui/navigation-menu.js +1 -1
  185. package/dist/ui/navigation-menu.js.map +1 -1
  186. package/dist/ui/number-input.js +4 -4
  187. package/dist/ui/number-input.js.map +1 -1
  188. package/dist/ui/pagination.js +1 -1
  189. package/dist/ui/pagination.js.map +1 -1
  190. package/dist/ui/progress-ring.js +3 -3
  191. package/dist/ui/progress-ring.js.map +1 -1
  192. package/dist/ui/progress.js +2 -2
  193. package/dist/ui/progress.js.map +1 -1
  194. package/dist/ui/segmented-control.js +3 -3
  195. package/dist/ui/segmented-control.js.map +1 -1
  196. package/dist/ui/select.js +6 -6
  197. package/dist/ui/select.js.map +1 -1
  198. package/dist/ui/sheet.js +2 -2
  199. package/dist/ui/sheet.js.map +1 -1
  200. package/dist/ui/sidebar.js +7 -7
  201. package/dist/ui/sidebar.js.map +1 -1
  202. package/dist/ui/split-button.js +6 -6
  203. package/dist/ui/split-button.js.map +1 -1
  204. package/dist/ui/stat-card.d.ts +1 -1
  205. package/dist/ui/stat-card.js +7 -7
  206. package/dist/ui/stat-card.js.map +1 -1
  207. package/dist/ui/stepper.js +3 -3
  208. package/dist/ui/stepper.js.map +1 -1
  209. package/dist/ui/table.js +3 -3
  210. package/dist/ui/table.js.map +1 -1
  211. package/dist/ui/tabs.js +3 -3
  212. package/dist/ui/tabs.js.map +1 -1
  213. package/dist/ui/text.d.ts.map +1 -1
  214. package/dist/ui/text.js.map +1 -1
  215. package/dist/ui/textarea.js +4 -4
  216. package/dist/ui/textarea.js.map +1 -1
  217. package/dist/ui/toast.js +11 -11
  218. package/dist/ui/toast.js.map +1 -1
  219. package/dist/ui/toggle.js +3 -3
  220. package/dist/ui/toggle.js.map +1 -1
  221. package/dist/ui/tooltip.js +1 -1
  222. package/dist/ui/tooltip.js.map +1 -1
  223. package/docs/components/composed/diff.md +50 -0
  224. package/llms.txt +2 -1
  225. package/mcp-manifest.json +145 -1
  226. package/package.json +8 -1
  227. package/skill/SKILL.md +1 -1
  228. package/skill/references/components.md +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"document-preview.js","names":[],"sources":["../../src/composed/file-preview/document-preview.tsx"],"sourcesContent":["'use client'\n\nimport { IconChevronLeft, IconChevronRight } from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\nimport { Document, Page, pdfjs } from 'react-pdf'\n\nimport { Button } from '../../ui/button'\nimport { Icon } from '../../ui/icon'\nimport { tweens } from '../../ui/lib/motion'\nimport { Skeleton } from '../../ui/skeleton'\nimport { ErrorFallback, Toolbar } from './shared'\n\n// ============================================================\n// PDF Preview — Adobe/Google Drive style\n// ============================================================\n\nexport default function DocumentPreview({ url, initialPage, onError }: { url: string; initialPage: number; onError?: (msg: string) => void }) {\n React.useEffect(() => {\n pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`\n }, [])\n\n const [numPages, setNumPages] = React.useState(0)\n const [page, setPage] = React.useState(initialPage)\n const [pageInput, setPageInput] = React.useState(String(initialPage))\n const [error, setError] = React.useState(false)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const [hovered, setHovered] = React.useState(false)\n\n // Sync page input when page changes via buttons\n React.useEffect(() => { setPageInput(String(page)) }, [page])\n\n // Keyboard nav — scoped to container focus/hover\n React.useEffect(() => {\n function handleKey(e: KeyboardEvent) {\n const el = containerRef.current\n if (!el) return\n const hasFocus = el.contains(document.activeElement)\n if (!hasFocus && !hovered) return\n\n if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {\n e.preventDefault()\n setPage((p) => Math.min(numPages, p + 1))\n } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {\n e.preventDefault()\n setPage((p) => Math.max(1, p - 1))\n }\n }\n document.addEventListener('keydown', handleKey)\n return () => document.removeEventListener('keydown', handleKey)\n }, [numPages, hovered])\n\n function handlePageInputSubmit(e: React.KeyboardEvent) {\n if (e.key === 'Enter') {\n const n = parseInt(pageInput, 10)\n if (n >= 1 && n <= numPages) setPage(n)\n else setPageInput(String(page))\n }\n }\n\n if (error) return <ErrorFallback message=\"Could not load PDF\" url={url} />\n\n return (\n <div\n ref={containerRef}\n className=\"flex flex-col items-center gap-ds-03\"\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n tabIndex={-1}\n >\n <div className=\"overflow-auto max-h-[70vh] rounded-control bg-surface-sunken border border-surface-border\">\n <Document\n file={url}\n onLoadSuccess={({ numPages: n }) => setNumPages(n)}\n onLoadError={() => { setError(true); onError?.('PDF failed to load') }}\n loading={<Skeleton className=\"h-[500px] w-[400px]\" />}\n error={<ErrorFallback message=\"Failed to load PDF\" url={url} />}\n >\n <AnimatePresence mode=\"wait\">\n <motion.div\n key={page}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Page pageNumber={page} renderTextLayer={false} renderAnnotationLayer={false} />\n </motion.div>\n </AnimatePresence>\n </Document>\n </div>\n\n {numPages > 0 && (\n <Toolbar>\n <Button variant=\"ghost\" size=\"icon-xs\" onClick={() => setPage((p) => Math.max(1, p - 1))} disabled={page <= 1} aria-label=\"Previous page (←)\" title=\"Previous page\">\n <Icon icon={IconChevronLeft} size=\"sm\" />\n </Button>\n <div className=\"flex items-center gap-ds-01 text-ds-xs text-surface-fg-muted\">\n <input\n type=\"text\"\n value={pageInput}\n onChange={(e) => setPageInput(e.target.value)}\n onKeyDown={handlePageInputSubmit}\n onBlur={() => setPageInput(String(page))}\n className=\"w-8 bg-transparent text-center font-mono text-surface-fg outline-hidden focus:bg-surface-raised-hover rounded-control-inner\"\n aria-label=\"Page number\"\n />\n <span>/ {numPages}</span>\n </div>\n <Button variant=\"ghost\" size=\"icon-xs\" onClick={() => setPage((p) => Math.min(numPages, p + 1))} disabled={page >= numPages} aria-label=\"Next page (→)\" title=\"Next page\">\n <Icon icon={IconChevronRight} size=\"sm\" />\n </Button>\n </Toolbar>\n )}\n </div>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"document-preview.js","names":[],"sources":["../../src/composed/file-preview/document-preview.tsx"],"sourcesContent":["'use client'\n\nimport { IconChevronLeft, IconChevronRight } from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\nimport { Document, Page, pdfjs } from 'react-pdf'\n\nimport { Button } from '../../ui/button'\nimport { Icon } from '../../ui/icon'\nimport { tweens } from '../../ui/lib/motion'\nimport { Skeleton } from '../../ui/skeleton'\nimport { ErrorFallback, Toolbar } from './shared'\n\n// ============================================================\n// PDF Preview — Adobe/Google Drive style\n// ============================================================\n\nexport default function DocumentPreview({ url, initialPage, onError }: { url: string; initialPage: number; onError?: (msg: string) => void }) {\n React.useEffect(() => {\n pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`\n }, [])\n\n const [numPages, setNumPages] = React.useState(0)\n const [page, setPage] = React.useState(initialPage)\n const [pageInput, setPageInput] = React.useState(String(initialPage))\n const [error, setError] = React.useState(false)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const [hovered, setHovered] = React.useState(false)\n\n // Sync page input when page changes via buttons\n React.useEffect(() => { setPageInput(String(page)) }, [page])\n\n // Keyboard nav — scoped to container focus/hover\n React.useEffect(() => {\n function handleKey(e: KeyboardEvent) {\n const el = containerRef.current\n if (!el) return\n const hasFocus = el.contains(document.activeElement)\n if (!hasFocus && !hovered) return\n\n if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {\n e.preventDefault()\n setPage((p) => Math.min(numPages, p + 1))\n } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {\n e.preventDefault()\n setPage((p) => Math.max(1, p - 1))\n }\n }\n document.addEventListener('keydown', handleKey)\n return () => document.removeEventListener('keydown', handleKey)\n }, [numPages, hovered])\n\n function handlePageInputSubmit(e: React.KeyboardEvent) {\n if (e.key === 'Enter') {\n const n = parseInt(pageInput, 10)\n if (n >= 1 && n <= numPages) setPage(n)\n else setPageInput(String(page))\n }\n }\n\n if (error) return <ErrorFallback message=\"Could not load PDF\" url={url} />\n\n return (\n <div\n ref={containerRef}\n className=\"flex flex-col items-center gap-ds-03\"\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n tabIndex={-1}\n >\n <div className=\"overflow-auto max-h-[70vh] rounded-control bg-surface-sunken border border-surface-border\">\n <Document\n file={url}\n onLoadSuccess={({ numPages: n }) => setNumPages(n)}\n onLoadError={() => { setError(true); onError?.('PDF failed to load') }}\n loading={<Skeleton className=\"h-[500px] w-[400px]\" />}\n error={<ErrorFallback message=\"Failed to load PDF\" url={url} />}\n >\n <AnimatePresence mode=\"wait\">\n <motion.div\n key={page}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Page pageNumber={page} renderTextLayer={false} renderAnnotationLayer={false} />\n </motion.div>\n </AnimatePresence>\n </Document>\n </div>\n\n {numPages > 0 && (\n <Toolbar>\n <Button variant=\"ghost\" size=\"icon-xs\" onClick={() => setPage((p) => Math.max(1, p - 1))} disabled={page <= 1} aria-label=\"Previous page (←)\" title=\"Previous page\">\n <Icon icon={IconChevronLeft} size=\"sm\" />\n </Button>\n <div className=\"flex items-center gap-ds-01 text-caption text-surface-fg-muted\">\n <input\n type=\"text\"\n value={pageInput}\n onChange={(e) => setPageInput(e.target.value)}\n onKeyDown={handlePageInputSubmit}\n onBlur={() => setPageInput(String(page))}\n className=\"w-8 bg-transparent text-center font-mono text-surface-fg outline-hidden focus:bg-surface-raised-hover rounded-control-inner\"\n aria-label=\"Page number\"\n />\n <span>/ {numPages}</span>\n </div>\n <Button variant=\"ghost\" size=\"icon-xs\" onClick={() => setPage((p) => Math.min(numPages, p + 1))} disabled={page >= numPages} aria-label=\"Next page (→)\" title=\"Next page\">\n <Icon icon={IconChevronRight} size=\"sm\" />\n </Button>\n </Toolbar>\n )}\n </div>\n )\n}\n"],"mappings":"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"}
@@ -47,7 +47,7 @@ var p = i.forwardRef(({ items: t, command: n }, r) => {
47
47
  role: "option",
48
48
  "aria-selected": r === s,
49
49
  onClick: () => n(t),
50
- className: e("flex w-full items-center gap-ds-03 px-ds-04 py-ds-02b text-left text-ds-sm", r === s ? "bg-surface-raised text-surface-fg" : "text-surface-fg-muted hover:bg-surface-raised"),
50
+ className: e("flex w-full items-center gap-ds-03 px-ds-04 py-ds-02b text-left text-body-sm", r === s ? "bg-surface-raised text-surface-fg" : "text-surface-fg-muted hover:bg-surface-raised"),
51
51
  children: [/* @__PURE__ */ a(f, {
52
52
  emoji: t,
53
53
  size: "1.25em"
@@ -1 +1 @@
1
- {"version":3,"file":"emoji-suggestion.js","names":[],"sources":["../../src/composed/extensions/emoji-data.ts","../../src/composed/extensions/emoji-suggestion.tsx"],"sourcesContent":["// Emoji dataset for the editors' `:shortcode:` autocomplete. Sources\n// `@emoji-mart/data` — a pure-JSON package with NO react peer, so it is\n// unaffected by the React-19 peer issue that drove the picker off\n// `@emoji-mart/react` (→ frimousse). Native-only since that migration: no\n// spritesheet coordinates, no per-art-style loaders.\n\ninterface EmojiSkin {\n native: string\n}\n\ninterface EmojiEntry {\n id: string\n name: string\n keywords?: string[]\n skins: EmojiSkin[]\n}\n\nexport interface EmojiDataset {\n emojis: Record<string, EmojiEntry>\n}\n\nexport interface ResolvedEmoji {\n id: string\n name: string\n native: string\n}\n\nlet cachedData: EmojiDataset | null = null\n\nexport async function loadEmojiData(): Promise<EmojiDataset> {\n if (cachedData) return cachedData\n const mod = await import('@emoji-mart/data')\n cachedData = ((mod as { default?: EmojiDataset }).default ?? mod) as EmojiDataset\n return cachedData\n}\n\nfunction resolve(entry: EmojiEntry): ResolvedEmoji {\n return { id: entry.id, name: entry.name, native: entry.skins[0]?.native ?? '' }\n}\n\nexport function searchEmoji(data: EmojiDataset, query: string, limit = 8): ResolvedEmoji[] {\n const entries = Object.values(data.emojis)\n if (!query) return entries.slice(0, limit).map(resolve)\n const q = query.toLowerCase()\n const results: ResolvedEmoji[] = []\n for (const e of entries) {\n if (results.length >= limit) break\n if (\n e.id.includes(q) ||\n e.name.toLowerCase().includes(q) ||\n e.keywords?.some((k) => k.includes(q))\n ) {\n results.push(resolve(e))\n }\n }\n return results\n}\n","import { Extension } from '@tiptap/core'\nimport Suggestion, { type SuggestionKeyDownProps,type SuggestionProps } from '@tiptap/suggestion'\nimport { PluginKey } from 'prosemirror-state'\nimport * as React from 'react'\nimport { createRoot, type Root } from 'react-dom/client'\n\nimport { cn } from '../../ui/lib/utils'\nimport { loadEmojiData, type ResolvedEmoji, searchEmoji } from './emoji-data'\n\n// Re-export for external consumers\nexport type { ResolvedEmoji as EmojiSuggestionItem }\n\n// Native-only since the frimousse migration.\nfunction EmojiImage({ emoji, size = '1.2em' }: { emoji: ResolvedEmoji; size?: string }) {\n return <span style={{ fontSize: size }}>{emoji.native}</span>\n}\n\ninterface EmojiListRef {\n onKeyDown: (props: SuggestionKeyDownProps) => boolean\n}\n\ninterface EmojiListProps {\n items: ResolvedEmoji[]\n command: (item: ResolvedEmoji) => void\n}\n\nconst EmojiList = React.forwardRef<EmojiListRef, EmojiListProps>(\n ({ items, command }, ref) => {\n const [selectedIndex, setSelectedIndex] = React.useState(0)\n\n React.useEffect(() => setSelectedIndex(0), [items])\n\n React.useImperativeHandle(ref, () => ({\n onKeyDown: ({ event }: SuggestionKeyDownProps) => {\n if (event.key === 'ArrowUp') {\n setSelectedIndex((i) => (i + items.length - 1) % items.length)\n return true\n }\n if (event.key === 'ArrowDown') {\n setSelectedIndex((i) => (i + 1) % items.length)\n return true\n }\n if (event.key === 'Enter') {\n if (items[selectedIndex]) command(items[selectedIndex])\n return true\n }\n return false\n },\n }))\n\n if (!items.length) return null\n\n return (\n <div role=\"listbox\" aria-label=\"Emoji suggestions\" className=\"z-popover max-h-[200px] overflow-x-hidden overflow-y-auto rounded-control bg-surface-overlay shadow-raised-hover\">\n {items.map((item, index) => (\n <button\n key={item.id}\n type=\"button\"\n role=\"option\"\n aria-selected={index === selectedIndex}\n onClick={() => command(item)}\n className={cn(\n 'flex w-full items-center gap-ds-03 px-ds-04 py-ds-02b text-left text-ds-sm',\n index === selectedIndex ? 'bg-surface-raised text-surface-fg' : 'text-surface-fg-muted hover:bg-surface-raised',\n )}\n >\n <EmojiImage emoji={item} size=\"1.25em\" />\n <span className=\"truncate\">:{item.id}:</span>\n </button>\n ))}\n </div>\n )\n },\n)\nEmojiList.displayName = 'EmojiList'\n\nfunction createEmojiSuggestionRenderer() {\n return () => {\n let root: Root | null = null\n let container: HTMLDivElement | null = null\n let componentRef: EmojiListRef | null = null\n\n return {\n onStart: (props: SuggestionProps<ResolvedEmoji>) => {\n container = document.createElement('div')\n container.style.position = 'absolute'\n container.style.zIndex = '1400'\n const rect = props.clientRect?.()\n if (rect) {\n container.style.left = `${rect.left}px`\n container.style.top = `${rect.bottom + 4}px`\n }\n document.body.appendChild(container)\n root = createRoot(container)\n root.render(\n <EmojiList\n ref={(r) => { componentRef = r }}\n items={props.items as ResolvedEmoji[]}\n command={(item) => props.command(item)}\n />,\n )\n },\n\n onUpdate: (props: SuggestionProps<ResolvedEmoji>) => {\n if (!root || !container) return\n const rect = props.clientRect?.()\n if (rect) {\n container.style.left = `${rect.left}px`\n container.style.top = `${rect.bottom + 4}px`\n }\n root.render(\n <EmojiList\n ref={(r) => { componentRef = r }}\n items={props.items as ResolvedEmoji[]}\n command={(item) => props.command(item)}\n />,\n )\n },\n\n onKeyDown: (props: SuggestionKeyDownProps) => {\n if (props.event.key === 'Escape') {\n if (container) {\n root?.unmount()\n container.remove()\n container = null\n root = null\n }\n return true\n }\n return componentRef?.onKeyDown(props) ?? false\n },\n\n onExit: () => {\n if (container) {\n root?.unmount()\n container.remove()\n container = null\n root = null\n }\n },\n }\n }\n}\n\n/** Factory: returns the configured `:shortcode:` emoji suggestion extension. */\nexport function createEmojiSuggestion() {\n return Extension.create({\n name: 'emojiSuggestion',\n\n addProseMirrorPlugins() {\n return [\n Suggestion({\n pluginKey: new PluginKey('emojiSuggestion'),\n editor: this.editor,\n char: ':',\n items: async ({ query }) => {\n const data = await loadEmojiData()\n return searchEmoji(data, query, 8)\n },\n command: ({ editor, range, props: item }) => {\n const emoji = item as ResolvedEmoji\n editor\n .chain()\n .focus()\n .deleteRange(range)\n .insertContent({\n type: 'emojiNode',\n attrs: { id: emoji.id, native: emoji.native },\n })\n .run()\n },\n render: createEmojiSuggestionRenderer(),\n }),\n ]\n },\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"emoji-suggestion.js","names":[],"sources":["../../src/composed/extensions/emoji-data.ts","../../src/composed/extensions/emoji-suggestion.tsx"],"sourcesContent":["// Emoji dataset for the editors' `:shortcode:` autocomplete. Sources\n// `@emoji-mart/data` — a pure-JSON package with NO react peer, so it is\n// unaffected by the React-19 peer issue that drove the picker off\n// `@emoji-mart/react` (→ frimousse). Native-only since that migration: no\n// spritesheet coordinates, no per-art-style loaders.\n\ninterface EmojiSkin {\n native: string\n}\n\ninterface EmojiEntry {\n id: string\n name: string\n keywords?: string[]\n skins: EmojiSkin[]\n}\n\nexport interface EmojiDataset {\n emojis: Record<string, EmojiEntry>\n}\n\nexport interface ResolvedEmoji {\n id: string\n name: string\n native: string\n}\n\nlet cachedData: EmojiDataset | null = null\n\nexport async function loadEmojiData(): Promise<EmojiDataset> {\n if (cachedData) return cachedData\n const mod = await import('@emoji-mart/data')\n cachedData = ((mod as { default?: EmojiDataset }).default ?? mod) as EmojiDataset\n return cachedData\n}\n\nfunction resolve(entry: EmojiEntry): ResolvedEmoji {\n return { id: entry.id, name: entry.name, native: entry.skins[0]?.native ?? '' }\n}\n\nexport function searchEmoji(data: EmojiDataset, query: string, limit = 8): ResolvedEmoji[] {\n const entries = Object.values(data.emojis)\n if (!query) return entries.slice(0, limit).map(resolve)\n const q = query.toLowerCase()\n const results: ResolvedEmoji[] = []\n for (const e of entries) {\n if (results.length >= limit) break\n if (\n e.id.includes(q) ||\n e.name.toLowerCase().includes(q) ||\n e.keywords?.some((k) => k.includes(q))\n ) {\n results.push(resolve(e))\n }\n }\n return results\n}\n","import { Extension } from '@tiptap/core'\nimport Suggestion, { type SuggestionKeyDownProps,type SuggestionProps } from '@tiptap/suggestion'\nimport { PluginKey } from 'prosemirror-state'\nimport * as React from 'react'\nimport { createRoot, type Root } from 'react-dom/client'\n\nimport { cn } from '../../ui/lib/utils'\nimport { loadEmojiData, type ResolvedEmoji, searchEmoji } from './emoji-data'\n\n// Re-export for external consumers\nexport type { ResolvedEmoji as EmojiSuggestionItem }\n\n// Native-only since the frimousse migration.\nfunction EmojiImage({ emoji, size = '1.2em' }: { emoji: ResolvedEmoji; size?: string }) {\n return <span style={{ fontSize: size }}>{emoji.native}</span>\n}\n\ninterface EmojiListRef {\n onKeyDown: (props: SuggestionKeyDownProps) => boolean\n}\n\ninterface EmojiListProps {\n items: ResolvedEmoji[]\n command: (item: ResolvedEmoji) => void\n}\n\nconst EmojiList = React.forwardRef<EmojiListRef, EmojiListProps>(\n ({ items, command }, ref) => {\n const [selectedIndex, setSelectedIndex] = React.useState(0)\n\n React.useEffect(() => setSelectedIndex(0), [items])\n\n React.useImperativeHandle(ref, () => ({\n onKeyDown: ({ event }: SuggestionKeyDownProps) => {\n if (event.key === 'ArrowUp') {\n setSelectedIndex((i) => (i + items.length - 1) % items.length)\n return true\n }\n if (event.key === 'ArrowDown') {\n setSelectedIndex((i) => (i + 1) % items.length)\n return true\n }\n if (event.key === 'Enter') {\n if (items[selectedIndex]) command(items[selectedIndex])\n return true\n }\n return false\n },\n }))\n\n if (!items.length) return null\n\n return (\n <div role=\"listbox\" aria-label=\"Emoji suggestions\" className=\"z-popover max-h-[200px] overflow-x-hidden overflow-y-auto rounded-control bg-surface-overlay shadow-raised-hover\">\n {items.map((item, index) => (\n <button\n key={item.id}\n type=\"button\"\n role=\"option\"\n aria-selected={index === selectedIndex}\n onClick={() => command(item)}\n className={cn(\n 'flex w-full items-center gap-ds-03 px-ds-04 py-ds-02b text-left text-body-sm',\n index === selectedIndex ? 'bg-surface-raised text-surface-fg' : 'text-surface-fg-muted hover:bg-surface-raised',\n )}\n >\n <EmojiImage emoji={item} size=\"1.25em\" />\n <span className=\"truncate\">:{item.id}:</span>\n </button>\n ))}\n </div>\n )\n },\n)\nEmojiList.displayName = 'EmojiList'\n\nfunction createEmojiSuggestionRenderer() {\n return () => {\n let root: Root | null = null\n let container: HTMLDivElement | null = null\n let componentRef: EmojiListRef | null = null\n\n return {\n onStart: (props: SuggestionProps<ResolvedEmoji>) => {\n container = document.createElement('div')\n container.style.position = 'absolute'\n container.style.zIndex = '1400'\n const rect = props.clientRect?.()\n if (rect) {\n container.style.left = `${rect.left}px`\n container.style.top = `${rect.bottom + 4}px`\n }\n document.body.appendChild(container)\n root = createRoot(container)\n root.render(\n <EmojiList\n ref={(r) => { componentRef = r }}\n items={props.items as ResolvedEmoji[]}\n command={(item) => props.command(item)}\n />,\n )\n },\n\n onUpdate: (props: SuggestionProps<ResolvedEmoji>) => {\n if (!root || !container) return\n const rect = props.clientRect?.()\n if (rect) {\n container.style.left = `${rect.left}px`\n container.style.top = `${rect.bottom + 4}px`\n }\n root.render(\n <EmojiList\n ref={(r) => { componentRef = r }}\n items={props.items as ResolvedEmoji[]}\n command={(item) => props.command(item)}\n />,\n )\n },\n\n onKeyDown: (props: SuggestionKeyDownProps) => {\n if (props.event.key === 'Escape') {\n if (container) {\n root?.unmount()\n container.remove()\n container = null\n root = null\n }\n return true\n }\n return componentRef?.onKeyDown(props) ?? false\n },\n\n onExit: () => {\n if (container) {\n root?.unmount()\n container.remove()\n container = null\n root = null\n }\n },\n }\n }\n}\n\n/** Factory: returns the configured `:shortcode:` emoji suggestion extension. */\nexport function createEmojiSuggestion() {\n return Extension.create({\n name: 'emojiSuggestion',\n\n addProseMirrorPlugins() {\n return [\n Suggestion({\n pluginKey: new PluginKey('emojiSuggestion'),\n editor: this.editor,\n char: ':',\n items: async ({ query }) => {\n const data = await loadEmojiData()\n return searchEmoji(data, query, 8)\n },\n command: ({ editor, range, props: item }) => {\n const emoji = item as ResolvedEmoji\n editor\n .chain()\n .focus()\n .deleteRange(range)\n .insertContent({\n type: 'emojiNode',\n attrs: { id: emoji.id, native: emoji.native },\n })\n .run()\n },\n render: createEmojiSuggestionRenderer(),\n }),\n ]\n },\n })\n}\n"],"mappings":"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"}
@@ -16,7 +16,7 @@ function l({ scale: r, orientation: l, transform: u, tickCount: d, tickFormat: f
16
16
  }[l], t = e(r);
17
17
  d && (t = t.ticks(d)), f && (t = t.tickFormat(f));
18
18
  let n = c(h.current);
19
- n.call(t), n.selectAll(".tick line").attr("stroke", "var(--color-surface-border)"), n.selectAll(".tick text").attr("fill", "var(--color-surface-fg-muted)").attr("font-size", "var(--text-ds-xs)"), n.selectAll(".domain").attr("stroke", "var(--color-surface-border-strong)");
19
+ n.call(t), n.selectAll(".tick line").attr("stroke", "var(--color-surface-border)"), n.selectAll(".tick text").attr("fill", "var(--color-surface-fg-muted)").attr("font-size", "var(--text-body-xs)"), n.selectAll(".domain").attr("stroke", "var(--color-surface-border-strong)");
20
20
  }, [
21
21
  r,
22
22
  l,
@@ -29,7 +29,7 @@ function l({ scale: r, orientation: l, transform: u, tickCount: d, tickFormat: f
29
29
  children: p && /* @__PURE__ */ n("text", {
30
30
  textAnchor: "middle",
31
31
  fill: "var(--color-surface-fg-muted)",
32
- fontSize: "var(--text-ds-sm)",
32
+ fontSize: "var(--text-body-sm)",
33
33
  ...l === "bottom" ? {
34
34
  x: "50%",
35
35
  dy: 35
@@ -1 +1 @@
1
- {"version":3,"file":"grid-lines.js","names":[],"sources":["../../src/ui/charts/_internal/axes.tsx","../../src/ui/charts/_internal/grid-lines.tsx"],"sourcesContent":["'use client'\n\nimport { axisBottom, axisLeft, axisRight, axisTop } from 'd3-axis'\nimport type { ScaleBand, ScaleLinear, ScalePoint, ScaleTime } from 'd3-scale'\nimport { select } from 'd3-selection'\nimport * as React from 'react'\nimport { useEffect,useRef } from 'react'\n\nexport type AnyScale =\n | ScaleLinear<number, number>\n | ScaleBand<string>\n | ScalePoint<string>\n | ScaleTime<number, number>\n\ninterface AxisProps {\n scale: AnyScale\n orientation: 'top' | 'right' | 'bottom' | 'left'\n transform?: string\n tickCount?: number\n tickFormat?: (value: unknown) => string\n label?: string\n className?: string\n}\n\nexport function Axis({\n scale,\n orientation,\n transform,\n tickCount,\n tickFormat,\n label,\n className,\n}: AxisProps) {\n const ref = useRef<SVGGElement>(null)\n\n useEffect(() => {\n if (!ref.current) return\n\n const axisFn = {\n top: axisTop,\n right: axisRight,\n bottom: axisBottom,\n left: axisLeft,\n }[orientation]\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let axis = axisFn(scale as any)\n if (tickCount) axis = axis.ticks(tickCount)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if (tickFormat) axis = axis.tickFormat(tickFormat as any)\n\n const g = select(ref.current)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n g.call(axis as any)\n\n // Style using design tokens\n g.selectAll('.tick line').attr('stroke', 'var(--color-surface-border)')\n g.selectAll('.tick text')\n .attr('fill', 'var(--color-surface-fg-muted)')\n .attr('font-size', 'var(--text-ds-xs)')\n g.selectAll('.domain').attr('stroke', 'var(--color-surface-border-strong)')\n }, [scale, orientation, tickCount, tickFormat])\n\n const labelProps =\n orientation === 'bottom'\n ? { x: '50%', dy: 35 }\n : orientation === 'left'\n ? { transform: 'rotate(-90)', y: -40, x: 0 }\n : {}\n\n return (\n <g ref={ref} transform={transform} className={className}>\n {label && (\n <text\n textAnchor=\"middle\"\n fill=\"var(--color-surface-fg-muted)\"\n fontSize=\"var(--text-ds-sm)\"\n {...labelProps}\n >\n {label}\n </text>\n )}\n </g>\n )\n}\nAxis.displayName = 'Axis'\n","import type { ScaleLinear, ScaleTime } from 'd3-scale'\nimport * as React from 'react'\n\ntype TickableScale = ScaleLinear<number, number> | ScaleTime<number, number>\n\ninterface GridLinesProps {\n width: number\n height: number\n xScale?: TickableScale\n yScale?: TickableScale\n horizontal?: boolean\n vertical?: boolean\n}\n\nexport function GridLines({\n width,\n height,\n xScale,\n yScale,\n horizontal = true,\n vertical = false,\n}: GridLinesProps) {\n return (\n <g className=\"grid-lines\">\n {horizontal &&\n yScale?.ticks &&\n yScale.ticks().map((tick: number | Date, i: number) => (\n <line\n key={`h-${i}`}\n x1={0}\n x2={width}\n y1={yScale(tick as number)}\n y2={yScale(tick as number)}\n stroke=\"var(--color-surface-border)\"\n strokeDasharray=\"3,3\"\n opacity={0.6}\n />\n ))}\n {vertical &&\n xScale?.ticks &&\n xScale.ticks().map((tick: number | Date, i: number) => (\n <line\n key={`v-${i}`}\n x1={xScale(tick as number)}\n x2={xScale(tick as number)}\n y1={0}\n y2={height}\n stroke=\"var(--color-surface-border)\"\n strokeDasharray=\"3,3\"\n opacity={0.6}\n />\n ))}\n </g>\n )\n}\nGridLines.displayName = 'GridLines'\n"],"mappings":";;;;;AAwBA,SAAgB,EAAK,EACnB,UACA,gBACA,cACA,cACA,eACA,UACA,gBACY;CACZ,IAAM,IAAM,EAAoB,IAAI;CAqCpC,OAnCA,QAAgB;EACd,IAAI,CAAC,EAAI,SAAS;EAElB,IAAM,IAAS;GACb,KAAK;GACL,OAAO;GACP,QAAQ;GACR,MAAM;EACR,EAAE,IAGE,IAAO,EAAO,CAAY;EAG9B,AAFI,MAAW,IAAO,EAAK,MAAM,CAAS,IAEtC,MAAY,IAAO,EAAK,WAAW,CAAiB;EAExD,IAAM,IAAI,EAAO,EAAI,OAAO;EAS5B,AAPA,EAAE,KAAK,CAAW,GAGlB,EAAE,UAAU,YAAY,CAAC,CAAC,KAAK,UAAU,6BAA6B,GACtE,EAAE,UAAU,YAAY,CAAC,CACtB,KAAK,QAAQ,+BAA+B,CAAC,CAC7C,KAAK,aAAa,mBAAmB,GACxC,EAAE,UAAU,SAAS,CAAC,CAAC,KAAK,UAAU,oCAAoC;CAC5E,GAAG;EAAC;EAAO;EAAa;EAAW;CAAU,CAAC,GAU5C,kBAAC,KAAD;EAAQ;EAAgB;EAAsB;YAC3C,KACC,kBAAC,QAAD;GACE,YAAW;GACX,MAAK;GACL,UAAS;GACT,GAbN,MAAgB,WACZ;IAAE,GAAG;IAAO,IAAI;GAAG,IACnB,MAAgB,SACd;IAAE,WAAW;IAAe,GAAG;IAAK,GAAG;GAAE,IACzC,CAAC;aAWA;EACG,CAAA;CAEP,CAAA;AAEP;AACA,EAAK,cAAc;;;ACvEnB,SAAgB,EAAU,EACxB,UACA,WACA,WACA,WACA,gBAAa,IACb,cAAW,MACM;CACjB,OACE,kBAAC,KAAD;EAAG,WAAU;YAAb,CACG,KACC,GAAQ,SACR,EAAO,MAAM,CAAC,CAAC,KAAK,GAAqB,MACvC,kBAAC,QAAD;GAEE,IAAI;GACJ,IAAI;GACJ,IAAI,EAAO,CAAc;GACzB,IAAI,EAAO,CAAc;GACzB,QAAO;GACP,iBAAgB;GAChB,SAAS;EACV,GARM,KAAK,GAQX,CACF,GACF,KACC,GAAQ,SACR,EAAO,MAAM,CAAC,CAAC,KAAK,GAAqB,MACvC,kBAAC,QAAD;GAEE,IAAI,EAAO,CAAc;GACzB,IAAI,EAAO,CAAc;GACzB,IAAI;GACJ,IAAI;GACJ,QAAO;GACP,iBAAgB;GAChB,SAAS;EACV,GARM,KAAK,GAQX,CACF,CACF;;AAEP;AACA,EAAU,cAAc"}
1
+ {"version":3,"file":"grid-lines.js","names":[],"sources":["../../src/ui/charts/_internal/axes.tsx","../../src/ui/charts/_internal/grid-lines.tsx"],"sourcesContent":["'use client'\n\nimport { axisBottom, axisLeft, axisRight, axisTop } from 'd3-axis'\nimport type { ScaleBand, ScaleLinear, ScalePoint, ScaleTime } from 'd3-scale'\nimport { select } from 'd3-selection'\nimport * as React from 'react'\nimport { useEffect,useRef } from 'react'\n\nexport type AnyScale =\n | ScaleLinear<number, number>\n | ScaleBand<string>\n | ScalePoint<string>\n | ScaleTime<number, number>\n\ninterface AxisProps {\n scale: AnyScale\n orientation: 'top' | 'right' | 'bottom' | 'left'\n transform?: string\n tickCount?: number\n tickFormat?: (value: unknown) => string\n label?: string\n className?: string\n}\n\nexport function Axis({\n scale,\n orientation,\n transform,\n tickCount,\n tickFormat,\n label,\n className,\n}: AxisProps) {\n const ref = useRef<SVGGElement>(null)\n\n useEffect(() => {\n if (!ref.current) return\n\n const axisFn = {\n top: axisTop,\n right: axisRight,\n bottom: axisBottom,\n left: axisLeft,\n }[orientation]\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let axis = axisFn(scale as any)\n if (tickCount) axis = axis.ticks(tickCount)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if (tickFormat) axis = axis.tickFormat(tickFormat as any)\n\n const g = select(ref.current)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n g.call(axis as any)\n\n // Style using design tokens\n g.selectAll('.tick line').attr('stroke', 'var(--color-surface-border)')\n g.selectAll('.tick text')\n .attr('fill', 'var(--color-surface-fg-muted)')\n .attr('font-size', 'var(--text-body-xs)')\n g.selectAll('.domain').attr('stroke', 'var(--color-surface-border-strong)')\n }, [scale, orientation, tickCount, tickFormat])\n\n const labelProps =\n orientation === 'bottom'\n ? { x: '50%', dy: 35 }\n : orientation === 'left'\n ? { transform: 'rotate(-90)', y: -40, x: 0 }\n : {}\n\n return (\n <g ref={ref} transform={transform} className={className}>\n {label && (\n <text\n textAnchor=\"middle\"\n fill=\"var(--color-surface-fg-muted)\"\n fontSize=\"var(--text-body-sm)\"\n {...labelProps}\n >\n {label}\n </text>\n )}\n </g>\n )\n}\nAxis.displayName = 'Axis'\n","import type { ScaleLinear, ScaleTime } from 'd3-scale'\nimport * as React from 'react'\n\ntype TickableScale = ScaleLinear<number, number> | ScaleTime<number, number>\n\ninterface GridLinesProps {\n width: number\n height: number\n xScale?: TickableScale\n yScale?: TickableScale\n horizontal?: boolean\n vertical?: boolean\n}\n\nexport function GridLines({\n width,\n height,\n xScale,\n yScale,\n horizontal = true,\n vertical = false,\n}: GridLinesProps) {\n return (\n <g className=\"grid-lines\">\n {horizontal &&\n yScale?.ticks &&\n yScale.ticks().map((tick: number | Date, i: number) => (\n <line\n key={`h-${i}`}\n x1={0}\n x2={width}\n y1={yScale(tick as number)}\n y2={yScale(tick as number)}\n stroke=\"var(--color-surface-border)\"\n strokeDasharray=\"3,3\"\n opacity={0.6}\n />\n ))}\n {vertical &&\n xScale?.ticks &&\n xScale.ticks().map((tick: number | Date, i: number) => (\n <line\n key={`v-${i}`}\n x1={xScale(tick as number)}\n x2={xScale(tick as number)}\n y1={0}\n y2={height}\n stroke=\"var(--color-surface-border)\"\n strokeDasharray=\"3,3\"\n opacity={0.6}\n />\n ))}\n </g>\n )\n}\nGridLines.displayName = 'GridLines'\n"],"mappings":";;;;;AAwBA,SAAgB,EAAK,EACnB,UACA,gBACA,cACA,cACA,eACA,UACA,gBACY;CACZ,IAAM,IAAM,EAAoB,IAAI;CAqCpC,OAnCA,QAAgB;EACd,IAAI,CAAC,EAAI,SAAS;EAElB,IAAM,IAAS;GACb,KAAK;GACL,OAAO;GACP,QAAQ;GACR,MAAM;EACR,EAAE,IAGE,IAAO,EAAO,CAAY;EAG9B,AAFI,MAAW,IAAO,EAAK,MAAM,CAAS,IAEtC,MAAY,IAAO,EAAK,WAAW,CAAiB;EAExD,IAAM,IAAI,EAAO,EAAI,OAAO;EAS5B,AAPA,EAAE,KAAK,CAAW,GAGlB,EAAE,UAAU,YAAY,CAAC,CAAC,KAAK,UAAU,6BAA6B,GACtE,EAAE,UAAU,YAAY,CAAC,CACtB,KAAK,QAAQ,+BAA+B,CAAC,CAC7C,KAAK,aAAa,qBAAqB,GAC1C,EAAE,UAAU,SAAS,CAAC,CAAC,KAAK,UAAU,oCAAoC;CAC5E,GAAG;EAAC;EAAO;EAAa;EAAW;CAAU,CAAC,GAU5C,kBAAC,KAAD;EAAQ;EAAgB;EAAsB;YAC3C,KACC,kBAAC,QAAD;GACE,YAAW;GACX,MAAK;GACL,UAAS;GACT,GAbN,MAAgB,WACZ;IAAE,GAAG;IAAO,IAAI;GAAG,IACnB,MAAgB,SACd;IAAE,WAAW;IAAe,GAAG;IAAK,GAAG;GAAE,IACzC,CAAC;aAWA;EACG,CAAA;CAEP,CAAA;AAEP;AACA,EAAK,cAAc;;;ACvEnB,SAAgB,EAAU,EACxB,UACA,WACA,WACA,WACA,gBAAa,IACb,cAAW,MACM;CACjB,OACE,kBAAC,KAAD;EAAG,WAAU;YAAb,CACG,KACC,GAAQ,SACR,EAAO,MAAM,CAAC,CAAC,KAAK,GAAqB,MACvC,kBAAC,QAAD;GAEE,IAAI;GACJ,IAAI;GACJ,IAAI,EAAO,CAAc;GACzB,IAAI,EAAO,CAAc;GACzB,QAAO;GACP,iBAAgB;GAChB,SAAS;EACV,GARM,KAAK,GAQX,CACF,GACF,KACC,GAAQ,SACR,EAAO,MAAM,CAAC,CAAC,KAAK,GAAqB,MACvC,kBAAC,QAAD;GAEE,IAAI,EAAO,CAAc;GACzB,IAAI,EAAO,CAAc;GACzB,IAAI;GACJ,IAAI;GACJ,QAAO;GACP,iBAAgB;GAChB,SAAS;EACV,GARM,KAAK,GAQX,CACF,CACF;;AAEP;AACA,EAAU,cAAc"}
@@ -79,7 +79,7 @@ function b({ url: b, alt: x, onError: S }) {
79
79
  })
80
80
  }),
81
81
  /* @__PURE__ */ d("span", {
82
- className: "text-ds-xs font-mono text-surface-fg-muted w-12 text-center tabular-nums select-none",
82
+ className: "text-caption font-mono text-surface-fg-muted w-12 text-center tabular-nums select-none",
83
83
  children: [D, "%"]
84
84
  }),
85
85
  /* @__PURE__ */ u(i, {
@@ -1 +1 @@
1
- {"version":3,"file":"image-preview.js","names":[],"sources":["../../src/composed/file-preview/image-preview.tsx"],"sourcesContent":["'use client'\n\nimport {\n IconMaximize,\n IconMinimize,\n IconZoomIn,\n IconZoomOut,\n IconZoomReset,\n} from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\nimport { TransformComponent,TransformWrapper } from 'react-zoom-pan-pinch'\n\nimport { Button } from '../../ui/button'\nimport { Icon } from '../../ui/icon'\nimport { tweens } from '../../ui/lib/motion'\nimport { cn } from '../../ui/lib/utils'\nimport { Skeleton } from '../../ui/skeleton'\nimport { ErrorFallback, Toolbar, ToolbarDivider } from './shared'\n\n// ============================================================\n// Image Preview — Google Drive / Figma style\n// ============================================================\n\nexport default function ImagePreview({ url, alt, onError }: { url: string; alt?: string; onError?: (msg: string) => void }) {\n const [loaded, setLoaded] = React.useState(false)\n const [error, setError] = React.useState(false)\n const [zoom, setZoom] = React.useState(100)\n const [fullscreen, setFullscreen] = React.useState(false)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const [hovered, setHovered] = React.useState(false)\n const controlsRef = React.useRef<{\n zoomIn: () => void; zoomOut: () => void; resetTransform: () => void;\n centerView: (s: number) => void\n } | null>(null)\n\n // Keyboard shortcuts\n React.useEffect(() => {\n function handleKeyDown(e: KeyboardEvent) {\n const el = containerRef.current\n if (!el) return\n const hasFocus = el.contains(document.activeElement)\n if (!hasFocus && !hovered) return\n const ctrl = controlsRef.current\n if (!ctrl) return\n\n if (e.key === '+' || e.key === '=') { e.preventDefault(); ctrl.zoomIn() }\n else if (e.key === '-') { e.preventDefault(); ctrl.zoomOut() }\n else if (e.key === '0') { e.preventDefault(); ctrl.resetTransform() }\n else if (e.key === 'f' || e.key === 'F') { e.preventDefault(); setFullscreen((f) => !f) }\n else if (e.key === 'Escape' && fullscreen) { e.preventDefault(); setFullscreen(false) }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n }, [hovered, fullscreen])\n\n if (error) return <ErrorFallback message=\"Could not load image\" url={url} />\n\n const containerClasses = fullscreen\n ? 'fixed inset-0 z-modal flex flex-col items-center justify-center bg-black/90 backdrop-blur-xs'\n : 'relative flex flex-col items-center gap-ds-03'\n\n return (\n <div\n ref={containerRef}\n className={containerClasses}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n tabIndex={-1}\n >\n <TransformWrapper\n initialScale={1}\n minScale={0.1}\n maxScale={8}\n centerOnInit\n doubleClick={{ mode: 'toggle', step: 2 }}\n onTransform={(_: unknown, state: { scale: number }) => setZoom(Math.round(state.scale * 100))}\n >\n {({ zoomIn, zoomOut, resetTransform, centerView }) => {\n controlsRef.current = { zoomIn, zoomOut, resetTransform, centerView }\n return (\n <>\n <div className={cn(\n 'overflow-hidden rounded-control bg-surface-sunken',\n fullscreen ? 'flex-1 w-full' : 'max-h-[70vh] max-w-full',\n )}>\n {!loaded && <Skeleton className=\"h-64 w-full rounded-control\" />}\n <TransformComponent\n wrapperClass={cn('w-full!', fullscreen && 'h-full!')}\n contentClass=\"w-full! flex! items-center! justify-center!\"\n >\n <motion.img\n src={url}\n alt={alt ?? ''}\n onLoad={() => setLoaded(true)}\n onError={() => { setError(true); onError?.('Image failed to load') }}\n initial={{ opacity: 0 }}\n animate={{ opacity: loaded ? 1 : 0 }}\n transition={tweens.fade}\n className={cn('max-w-full select-none', !loaded && 'hidden', fullscreen && 'max-h-[90vh] object-contain')}\n draggable={false}\n />\n </TransformComponent>\n </div>\n\n {/* Toolbar — Google Drive style */}\n {loaded && (\n <Toolbar className={fullscreen ? 'absolute bottom-ds-06' : undefined}>\n <Button variant=\"ghost\" size=\"icon-xs\" onClick={() => zoomOut()} aria-label=\"Zoom out (-)\" title=\"Zoom out\">\n <Icon icon={IconZoomOut} size=\"sm\" />\n </Button>\n <span className=\"text-ds-xs font-mono text-surface-fg-muted w-12 text-center tabular-nums select-none\">\n {zoom}%\n </span>\n <Button variant=\"ghost\" size=\"icon-xs\" onClick={() => zoomIn()} aria-label=\"Zoom in (+)\" title=\"Zoom in\">\n <Icon icon={IconZoomIn} size=\"sm\" />\n </Button>\n <ToolbarDivider />\n <Button variant=\"ghost\" size=\"icon-xs\" onClick={() => resetTransform()} aria-label=\"Reset zoom (0)\" title=\"Reset zoom\">\n <Icon icon={IconZoomReset} size=\"sm\" />\n </Button>\n <ToolbarDivider />\n <Button variant=\"ghost\" size=\"icon-xs\" onClick={() => setFullscreen((f) => !f)} aria-label={fullscreen ? 'Exit fullscreen (Esc)' : 'Fullscreen (F)'} title={fullscreen ? 'Exit fullscreen' : 'Fullscreen'}>\n {fullscreen ? <Icon icon={IconMinimize} size=\"sm\" /> : <Icon icon={IconMaximize} size=\"sm\" />}\n </Button>\n </Toolbar>\n )}\n </>\n )\n }}\n </TransformWrapper>\n </div>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"image-preview.js","names":[],"sources":["../../src/composed/file-preview/image-preview.tsx"],"sourcesContent":["'use client'\n\nimport {\n IconMaximize,\n IconMinimize,\n IconZoomIn,\n IconZoomOut,\n IconZoomReset,\n} from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\nimport { TransformComponent,TransformWrapper } from 'react-zoom-pan-pinch'\n\nimport { Button } from '../../ui/button'\nimport { Icon } from '../../ui/icon'\nimport { tweens } from '../../ui/lib/motion'\nimport { cn } from '../../ui/lib/utils'\nimport { Skeleton } from '../../ui/skeleton'\nimport { ErrorFallback, Toolbar, ToolbarDivider } from './shared'\n\n// ============================================================\n// Image Preview — Google Drive / Figma style\n// ============================================================\n\nexport default function ImagePreview({ url, alt, onError }: { url: string; alt?: string; onError?: (msg: string) => void }) {\n const [loaded, setLoaded] = React.useState(false)\n const [error, setError] = React.useState(false)\n const [zoom, setZoom] = React.useState(100)\n const [fullscreen, setFullscreen] = React.useState(false)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const [hovered, setHovered] = React.useState(false)\n const controlsRef = React.useRef<{\n zoomIn: () => void; zoomOut: () => void; resetTransform: () => void;\n centerView: (s: number) => void\n } | null>(null)\n\n // Keyboard shortcuts\n React.useEffect(() => {\n function handleKeyDown(e: KeyboardEvent) {\n const el = containerRef.current\n if (!el) return\n const hasFocus = el.contains(document.activeElement)\n if (!hasFocus && !hovered) return\n const ctrl = controlsRef.current\n if (!ctrl) return\n\n if (e.key === '+' || e.key === '=') { e.preventDefault(); ctrl.zoomIn() }\n else if (e.key === '-') { e.preventDefault(); ctrl.zoomOut() }\n else if (e.key === '0') { e.preventDefault(); ctrl.resetTransform() }\n else if (e.key === 'f' || e.key === 'F') { e.preventDefault(); setFullscreen((f) => !f) }\n else if (e.key === 'Escape' && fullscreen) { e.preventDefault(); setFullscreen(false) }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n }, [hovered, fullscreen])\n\n if (error) return <ErrorFallback message=\"Could not load image\" url={url} />\n\n const containerClasses = fullscreen\n ? 'fixed inset-0 z-modal flex flex-col items-center justify-center bg-black/90 backdrop-blur-xs'\n : 'relative flex flex-col items-center gap-ds-03'\n\n return (\n <div\n ref={containerRef}\n className={containerClasses}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n tabIndex={-1}\n >\n <TransformWrapper\n initialScale={1}\n minScale={0.1}\n maxScale={8}\n centerOnInit\n doubleClick={{ mode: 'toggle', step: 2 }}\n onTransform={(_: unknown, state: { scale: number }) => setZoom(Math.round(state.scale * 100))}\n >\n {({ zoomIn, zoomOut, resetTransform, centerView }) => {\n controlsRef.current = { zoomIn, zoomOut, resetTransform, centerView }\n return (\n <>\n <div className={cn(\n 'overflow-hidden rounded-control bg-surface-sunken',\n fullscreen ? 'flex-1 w-full' : 'max-h-[70vh] max-w-full',\n )}>\n {!loaded && <Skeleton className=\"h-64 w-full rounded-control\" />}\n <TransformComponent\n wrapperClass={cn('w-full!', fullscreen && 'h-full!')}\n contentClass=\"w-full! flex! items-center! justify-center!\"\n >\n <motion.img\n src={url}\n alt={alt ?? ''}\n onLoad={() => setLoaded(true)}\n onError={() => { setError(true); onError?.('Image failed to load') }}\n initial={{ opacity: 0 }}\n animate={{ opacity: loaded ? 1 : 0 }}\n transition={tweens.fade}\n className={cn('max-w-full select-none', !loaded && 'hidden', fullscreen && 'max-h-[90vh] object-contain')}\n draggable={false}\n />\n </TransformComponent>\n </div>\n\n {/* Toolbar — Google Drive style */}\n {loaded && (\n <Toolbar className={fullscreen ? 'absolute bottom-ds-06' : undefined}>\n <Button variant=\"ghost\" size=\"icon-xs\" onClick={() => zoomOut()} aria-label=\"Zoom out (-)\" title=\"Zoom out\">\n <Icon icon={IconZoomOut} size=\"sm\" />\n </Button>\n <span className=\"text-caption font-mono text-surface-fg-muted w-12 text-center tabular-nums select-none\">\n {zoom}%\n </span>\n <Button variant=\"ghost\" size=\"icon-xs\" onClick={() => zoomIn()} aria-label=\"Zoom in (+)\" title=\"Zoom in\">\n <Icon icon={IconZoomIn} size=\"sm\" />\n </Button>\n <ToolbarDivider />\n <Button variant=\"ghost\" size=\"icon-xs\" onClick={() => resetTransform()} aria-label=\"Reset zoom (0)\" title=\"Reset zoom\">\n <Icon icon={IconZoomReset} size=\"sm\" />\n </Button>\n <ToolbarDivider />\n <Button variant=\"ghost\" size=\"icon-xs\" onClick={() => setFullscreen((f) => !f)} aria-label={fullscreen ? 'Exit fullscreen (Esc)' : 'Fullscreen (F)'} title={fullscreen ? 'Exit fullscreen' : 'Fullscreen'}>\n {fullscreen ? <Icon icon={IconMinimize} size=\"sm\" /> : <Icon icon={IconMaximize} size=\"sm\" />}\n </Button>\n </Toolbar>\n )}\n </>\n )\n }}\n </TransformWrapper>\n </div>\n )\n}\n"],"mappings":"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"}
@@ -18,7 +18,7 @@ function m({ node: e }) {
18
18
  href: /^https?:\/\//.test(e.attrs.url) ? e.attrs.url : "#",
19
19
  target: "_blank",
20
20
  rel: "noopener noreferrer",
21
- className: "inline-flex items-center gap-ds-03 rounded-control border border-surface-border-strong bg-surface-raised px-ds-04 py-ds-03 text-ds-sm text-surface-fg transition-colors hover:border-surface-border-strong hover:bg-surface-raised-hover",
21
+ className: "inline-flex items-center gap-ds-03 rounded-control border border-surface-border-strong bg-surface-raised px-ds-04 py-ds-03 text-body-sm text-surface-fg transition-colors hover:border-surface-border-strong hover:bg-surface-raised-hover",
22
22
  contentEditable: !1,
23
23
  children: [
24
24
  /* @__PURE__ */ c(t, {
@@ -79,7 +79,7 @@ var h = r.create({
79
79
  role: "option",
80
80
  "aria-selected": r === i,
81
81
  onClick: () => n(t),
82
- className: e("flex w-full items-center gap-ds-03 px-ds-04 py-ds-02b text-left text-ds-sm", r === i ? "bg-surface-raised text-surface-fg" : "text-surface-fg-muted hover:bg-surface-raised"),
82
+ className: e("flex w-full items-center gap-ds-03 px-ds-04 py-ds-02b text-left text-body-sm", r === i ? "bg-surface-raised text-surface-fg" : "text-surface-fg-muted hover:bg-surface-raised"),
83
83
  children: [t.avatar ? /* @__PURE__ */ c("img", {
84
84
  src: t.avatar,
85
85
  alt: "",
@@ -1 +1 @@
1
- {"version":3,"file":"mention-suggestion.js","names":[],"sources":["../../src/composed/extensions/file-attachment.tsx","../../src/composed/extensions/mention-suggestion.tsx"],"sourcesContent":["import { IconDownload,IconFile } from '@tabler/icons-react'\nimport { mergeAttributes,Node } from '@tiptap/core'\nimport { type NodeViewProps,NodeViewWrapper, ReactNodeViewRenderer } from '@tiptap/react'\n\nimport { Icon } from '../../ui/icon'\nimport { TruncatedText } from '../../ui/truncated-text'\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\nfunction FileAttachmentView({ node }: NodeViewProps) {\n const safeUrl = /^https?:\\/\\//.test(node.attrs.url) ? node.attrs.url : '#'\n return (\n <NodeViewWrapper className=\"my-ds-02\">\n <a\n href={safeUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-ds-03 rounded-control border border-surface-border-strong bg-surface-raised px-ds-04 py-ds-03 text-ds-sm text-surface-fg transition-colors hover:border-surface-border-strong hover:bg-surface-raised-hover\"\n contentEditable={false}\n >\n <Icon icon={IconFile} size=\"sm\" className=\"shrink-0 text-surface-fg-subtle\" />\n <TruncatedText mode=\"middle\" className=\"min-w-0\">{node.attrs.name}</TruncatedText>\n <span className=\"shrink-0 text-surface-fg-subtle\">({formatFileSize(node.attrs.size)})</span>\n <Icon icon={IconDownload} size=\"sm\" className=\"shrink-0 text-surface-fg-subtle\" />\n </a>\n </NodeViewWrapper>\n )\n}\n\nexport const FileAttachment = Node.create({\n name: 'fileAttachment',\n group: 'block',\n atom: true,\n\n addAttributes() {\n return {\n url: { default: null },\n name: { default: 'Untitled' },\n size: { default: 0 },\n }\n },\n\n parseHTML() {\n return [{ tag: 'div[data-file-attachment]' }]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['div', mergeAttributes({ 'data-file-attachment': '' }, HTMLAttributes)]\n },\n\n addNodeView() {\n return ReactNodeViewRenderer(FileAttachmentView)\n },\n})\n","import type { SuggestionKeyDownProps,SuggestionProps } from '@tiptap/suggestion'\nimport * as React from 'react'\nimport { createRoot, type Root } from 'react-dom/client'\n\nimport { cn } from '../../ui/lib/utils'\nimport type { MentionItem } from '../rich-text-editor'\n\ninterface MentionListRef {\n onKeyDown: (props: SuggestionKeyDownProps) => boolean\n}\n\ninterface MentionListProps {\n items: MentionItem[]\n command: (item: MentionItem) => void\n}\n\nconst MentionList = React.forwardRef<MentionListRef, MentionListProps>(\n ({ items, command }, ref) => {\n const [selectedIndex, setSelectedIndex] = React.useState(0)\n\n React.useEffect(() => setSelectedIndex(0), [items])\n\n React.useImperativeHandle(ref, () => ({\n onKeyDown: ({ event }: SuggestionKeyDownProps) => {\n if (event.key === 'ArrowUp') {\n setSelectedIndex((i) => (i + items.length - 1) % items.length)\n return true\n }\n if (event.key === 'ArrowDown') {\n setSelectedIndex((i) => (i + 1) % items.length)\n return true\n }\n if (event.key === 'Enter') {\n if (items[selectedIndex]) command(items[selectedIndex])\n return true\n }\n return false\n },\n }))\n\n if (!items.length) return null\n\n return (\n <div role=\"listbox\" aria-label=\"Mention suggestions\" className=\"z-popover overflow-hidden rounded-control bg-surface-overlay shadow-raised-hover\">\n {items.map((item, index) => (\n <button\n key={item.id}\n type=\"button\"\n role=\"option\"\n aria-selected={index === selectedIndex}\n onClick={() => command(item)}\n className={cn(\n 'flex w-full items-center gap-ds-03 px-ds-04 py-ds-02b text-left text-ds-sm',\n index === selectedIndex ? 'bg-surface-raised text-surface-fg' : 'text-surface-fg-muted hover:bg-surface-raised',\n )}\n >\n {item.avatar ? (\n <img src={item.avatar} alt=\"\" className=\"h-ico-md w-ico-md rounded-pill object-cover\" />\n ) : (\n <span className=\"flex h-ico-md w-ico-md items-center justify-center rounded-pill bg-accent-2 text-[10px] font-semibold text-accent-11\">\n {item.label.charAt(0).toUpperCase()}\n </span>\n )}\n <span>{item.label}</span>\n </button>\n ))}\n </div>\n )\n },\n)\nMentionList.displayName = 'MentionList'\n\nexport function createSuggestionRenderer(onMentionSelect?: (item: MentionItem) => void) {\n return () => {\n let root: Root | null = null\n let container: HTMLDivElement | null = null\n let componentRef: MentionListRef | null = null\n\n return {\n onStart: (props: SuggestionProps<MentionItem>) => {\n container = document.createElement('div')\n container.style.position = 'absolute'\n container.style.zIndex = '1400' // z-popover\n\n const rect = props.clientRect?.()\n if (rect) {\n container.style.left = `${rect.left}px`\n container.style.top = `${rect.bottom + 4}px`\n }\n document.body.appendChild(container)\n\n root = createRoot(container)\n root.render(\n <MentionList\n ref={(r) => { componentRef = r }}\n items={props.items as MentionItem[]}\n command={(item) => {\n props.command({ id: item.id, label: item.label })\n onMentionSelect?.(item)\n }}\n />,\n )\n },\n\n onUpdate: (props: SuggestionProps<MentionItem>) => {\n if (!root || !container) return\n\n const rect = props.clientRect?.()\n if (rect) {\n container.style.left = `${rect.left}px`\n container.style.top = `${rect.bottom + 4}px`\n }\n\n root.render(\n <MentionList\n ref={(r) => { componentRef = r }}\n items={props.items as MentionItem[]}\n command={(item) => {\n props.command({ id: item.id, label: item.label })\n onMentionSelect?.(item)\n }}\n />,\n )\n },\n\n onKeyDown: (props: SuggestionKeyDownProps) => {\n if (props.event.key === 'Escape') {\n if (container) {\n root?.unmount()\n container.remove()\n container = null\n root = null\n }\n return true\n }\n return componentRef?.onKeyDown(props) ?? false\n },\n\n onExit: () => {\n if (container) {\n root?.unmount()\n container.remove()\n container = null\n root = null\n }\n },\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mention-suggestion.js","names":[],"sources":["../../src/composed/extensions/file-attachment.tsx","../../src/composed/extensions/mention-suggestion.tsx"],"sourcesContent":["import { IconDownload,IconFile } from '@tabler/icons-react'\nimport { mergeAttributes,Node } from '@tiptap/core'\nimport { type NodeViewProps,NodeViewWrapper, ReactNodeViewRenderer } from '@tiptap/react'\n\nimport { Icon } from '../../ui/icon'\nimport { TruncatedText } from '../../ui/truncated-text'\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\nfunction FileAttachmentView({ node }: NodeViewProps) {\n const safeUrl = /^https?:\\/\\//.test(node.attrs.url) ? node.attrs.url : '#'\n return (\n <NodeViewWrapper className=\"my-ds-02\">\n <a\n href={safeUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-ds-03 rounded-control border border-surface-border-strong bg-surface-raised px-ds-04 py-ds-03 text-body-sm text-surface-fg transition-colors hover:border-surface-border-strong hover:bg-surface-raised-hover\"\n contentEditable={false}\n >\n <Icon icon={IconFile} size=\"sm\" className=\"shrink-0 text-surface-fg-subtle\" />\n <TruncatedText mode=\"middle\" className=\"min-w-0\">{node.attrs.name}</TruncatedText>\n <span className=\"shrink-0 text-surface-fg-subtle\">({formatFileSize(node.attrs.size)})</span>\n <Icon icon={IconDownload} size=\"sm\" className=\"shrink-0 text-surface-fg-subtle\" />\n </a>\n </NodeViewWrapper>\n )\n}\n\nexport const FileAttachment = Node.create({\n name: 'fileAttachment',\n group: 'block',\n atom: true,\n\n addAttributes() {\n return {\n url: { default: null },\n name: { default: 'Untitled' },\n size: { default: 0 },\n }\n },\n\n parseHTML() {\n return [{ tag: 'div[data-file-attachment]' }]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['div', mergeAttributes({ 'data-file-attachment': '' }, HTMLAttributes)]\n },\n\n addNodeView() {\n return ReactNodeViewRenderer(FileAttachmentView)\n },\n})\n","import type { SuggestionKeyDownProps,SuggestionProps } from '@tiptap/suggestion'\nimport * as React from 'react'\nimport { createRoot, type Root } from 'react-dom/client'\n\nimport { cn } from '../../ui/lib/utils'\nimport type { MentionItem } from '../rich-text-editor'\n\ninterface MentionListRef {\n onKeyDown: (props: SuggestionKeyDownProps) => boolean\n}\n\ninterface MentionListProps {\n items: MentionItem[]\n command: (item: MentionItem) => void\n}\n\nconst MentionList = React.forwardRef<MentionListRef, MentionListProps>(\n ({ items, command }, ref) => {\n const [selectedIndex, setSelectedIndex] = React.useState(0)\n\n React.useEffect(() => setSelectedIndex(0), [items])\n\n React.useImperativeHandle(ref, () => ({\n onKeyDown: ({ event }: SuggestionKeyDownProps) => {\n if (event.key === 'ArrowUp') {\n setSelectedIndex((i) => (i + items.length - 1) % items.length)\n return true\n }\n if (event.key === 'ArrowDown') {\n setSelectedIndex((i) => (i + 1) % items.length)\n return true\n }\n if (event.key === 'Enter') {\n if (items[selectedIndex]) command(items[selectedIndex])\n return true\n }\n return false\n },\n }))\n\n if (!items.length) return null\n\n return (\n <div role=\"listbox\" aria-label=\"Mention suggestions\" className=\"z-popover overflow-hidden rounded-control bg-surface-overlay shadow-raised-hover\">\n {items.map((item, index) => (\n <button\n key={item.id}\n type=\"button\"\n role=\"option\"\n aria-selected={index === selectedIndex}\n onClick={() => command(item)}\n className={cn(\n 'flex w-full items-center gap-ds-03 px-ds-04 py-ds-02b text-left text-body-sm',\n index === selectedIndex ? 'bg-surface-raised text-surface-fg' : 'text-surface-fg-muted hover:bg-surface-raised',\n )}\n >\n {item.avatar ? (\n <img src={item.avatar} alt=\"\" className=\"h-ico-md w-ico-md rounded-pill object-cover\" />\n ) : (\n <span className=\"flex h-ico-md w-ico-md items-center justify-center rounded-pill bg-accent-2 text-[10px] font-semibold text-accent-11\">\n {item.label.charAt(0).toUpperCase()}\n </span>\n )}\n <span>{item.label}</span>\n </button>\n ))}\n </div>\n )\n },\n)\nMentionList.displayName = 'MentionList'\n\nexport function createSuggestionRenderer(onMentionSelect?: (item: MentionItem) => void) {\n return () => {\n let root: Root | null = null\n let container: HTMLDivElement | null = null\n let componentRef: MentionListRef | null = null\n\n return {\n onStart: (props: SuggestionProps<MentionItem>) => {\n container = document.createElement('div')\n container.style.position = 'absolute'\n container.style.zIndex = '1400' // z-popover\n\n const rect = props.clientRect?.()\n if (rect) {\n container.style.left = `${rect.left}px`\n container.style.top = `${rect.bottom + 4}px`\n }\n document.body.appendChild(container)\n\n root = createRoot(container)\n root.render(\n <MentionList\n ref={(r) => { componentRef = r }}\n items={props.items as MentionItem[]}\n command={(item) => {\n props.command({ id: item.id, label: item.label })\n onMentionSelect?.(item)\n }}\n />,\n )\n },\n\n onUpdate: (props: SuggestionProps<MentionItem>) => {\n if (!root || !container) return\n\n const rect = props.clientRect?.()\n if (rect) {\n container.style.left = `${rect.left}px`\n container.style.top = `${rect.bottom + 4}px`\n }\n\n root.render(\n <MentionList\n ref={(r) => { componentRef = r }}\n items={props.items as MentionItem[]}\n command={(item) => {\n props.command({ id: item.id, label: item.label })\n onMentionSelect?.(item)\n }}\n />,\n )\n },\n\n onKeyDown: (props: SuggestionKeyDownProps) => {\n if (props.event.key === 'Escape') {\n if (container) {\n root?.unmount()\n container.remove()\n container = null\n root = null\n }\n return true\n }\n return componentRef?.onKeyDown(props) ?? false\n },\n\n onExit: () => {\n if (container) {\n root?.unmount()\n container.remove()\n container = null\n root = null\n }\n },\n }\n }\n}\n"],"mappings":"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"}
@@ -85,7 +85,7 @@ var D = {
85
85
  size: k,
86
86
  "aria-hidden": !0
87
87
  }), j = g ?? T(r, i, d), M = s ?? (c === "outline" ? "outline" : "solid"), N = E(r, i), P = u || d || f ? `${N}${f ? " (last used)" : ""}` : void 0, F = /* @__PURE__ */ a("span", {
88
- className: "pointer-events-none inline-flex items-center rounded-pill bg-accent-9 text-accent-fg px-2 py-1 text-ds-2xs leading-none font-semibold uppercase tracking-wide shadow-overlay",
88
+ className: "pointer-events-none inline-flex items-center rounded-pill bg-accent-9 text-accent-fg px-2 py-1 text-label-xs leading-none font-semibold uppercase tracking-wide shadow-overlay",
89
89
  children: m
90
90
  }), I = (typeof p == "function" ? p({ label: m }) : p) ?? F, L = /* @__PURE__ */ a(n, {
91
91
  ref: S,
@@ -113,7 +113,7 @@ var D = {
113
113
  children: I
114
114
  }) : null,
115
115
  h ? /* @__PURE__ */ a("span", {
116
- className: "mt-ds-02 text-ds-sm text-surface-fg-subtle",
116
+ className: "mt-ds-02 text-body-sm text-surface-fg-subtle",
117
117
  children: h
118
118
  }) : null
119
119
  ]
@@ -148,7 +148,7 @@ var j = i.forwardRef(({ label: t = "or", className: n, ...r }, i) => /* @__PURE_
148
148
  className: "flex-1 border-t border-surface-border-subtle"
149
149
  }),
150
150
  /* @__PURE__ */ a("span", {
151
- className: "text-ds-sm text-surface-fg-subtle uppercase tracking-wide",
151
+ className: "text-label-sm text-surface-fg-subtle uppercase tracking-wide",
152
152
  children: t
153
153
  }),
154
154
  /* @__PURE__ */ a("hr", {
@@ -174,20 +174,20 @@ var M = i.forwardRef(({ provider: i, accountLabel: s, connected: c, onAction: l,
174
174
  }), /* @__PURE__ */ o("div", {
175
175
  className: "flex flex-col min-w-0",
176
176
  children: [/* @__PURE__ */ a(r, {
177
- className: "text-ds-md font-semibold text-surface-fg",
177
+ className: "text-body-md font-semibold text-surface-fg",
178
178
  children: v
179
179
  }), s ? typeof s == "string" ? /* @__PURE__ */ a(r, {
180
180
  mode: "middle",
181
- className: "text-ds-sm text-surface-fg-muted",
181
+ className: "text-body-sm text-surface-fg-muted",
182
182
  children: s
183
183
  }) : /* @__PURE__ */ a("span", {
184
- className: "text-ds-sm text-surface-fg-muted truncate",
184
+ className: "text-body-sm text-surface-fg-muted truncate",
185
185
  children: s
186
186
  }) : c ? /* @__PURE__ */ a("span", {
187
- className: "text-ds-sm text-success-11",
187
+ className: "text-body-sm text-success-11",
188
188
  children: "Connected"
189
189
  }) : /* @__PURE__ */ a("span", {
190
- className: "text-ds-sm text-surface-fg-subtle",
190
+ className: "text-body-sm text-surface-fg-subtle",
191
191
  children: "Not connected"
192
192
  })]
193
193
  })]
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-button.js","names":[],"sources":["../../src/ui/oauth-button/oauth-button.tsx"],"sourcesContent":["'use client'\n\nimport {\n IconBrandApple,\n IconBrandDiscord,\n IconBrandFacebook,\n IconBrandGithub,\n IconBrandGitlab,\n IconBrandGoogleFilled,\n IconBrandLinkedin,\n IconBrandSlack,\n IconBrandWindows,\n IconBrandXFilled,\n IconFingerprint,\n IconKey,\n IconMail,\n} from '@tabler/icons-react'\nimport * as React from 'react'\n\nimport { Button, type ButtonProps } from '../button'\nimport type { IconInput } from '../lib/icon-input'\nimport { normalizeIcon } from '../lib/normalize-icon'\nimport { cn } from '../lib/utils'\nimport { TruncatedText } from '../truncated-text'\n\n// ── Types ───────────────────────────────────────────────────────\n\nexport type OAuthProvider =\n | 'google'\n | 'apple'\n | 'github'\n | 'microsoft'\n | 'x'\n | 'linkedin'\n | 'facebook'\n | 'discord'\n | 'slack'\n | 'gitlab'\n | 'sso'\n | 'email'\n | 'passkey'\n\nexport type OAuthIntent = 'continue' | 'signin' | 'signup'\n\n/**\n * Visual treatment — 1:1 with Button's variant axis. OAuthButton is a thin\n * wrapper over Button, so every variant renders pixel-for-pixel identical\n * to a regular Button at the same variant + size. Brand identity lives in\n * the glyph (override via `icon` for a brand-multicolour SVG), not the\n * button surface.\n *\n * - `solid` (default) | `soft` | `outline` | `ghost`\n */\nexport type OAuthVariant = 'solid' | 'soft' | 'outline' | 'ghost'\n\n/** @deprecated Use `OAuthVariant`. brand/dark map to solid; outline passes through. */\nexport type OAuthAppearance = 'brand' | 'outline' | 'dark'\n\n// ── Static metadata ─────────────────────────────────────────────\n\nconst providerDisplay: Record<OAuthProvider, string> = {\n google: 'Google',\n apple: 'Apple',\n github: 'GitHub',\n microsoft: 'Microsoft',\n x: 'X',\n linkedin: 'LinkedIn',\n facebook: 'Facebook',\n discord: 'Discord',\n slack: 'Slack',\n gitlab: 'GitLab',\n sso: 'SSO',\n email: 'email',\n passkey: 'a passkey',\n}\n\n// Default glyph per provider — sourced from Tabler (already a peer dep).\n// Consumer can override with the `icon` prop to drop in a brand-multicolour SVG.\nconst providerIcon: Record<OAuthProvider, React.ComponentType<{ size?: number | string; 'aria-hidden'?: boolean }>> = {\n google: IconBrandGoogleFilled,\n apple: IconBrandApple,\n github: IconBrandGithub,\n microsoft: IconBrandWindows,\n x: IconBrandXFilled,\n linkedin: IconBrandLinkedin,\n facebook: IconBrandFacebook,\n discord: IconBrandDiscord,\n slack: IconBrandSlack,\n gitlab: IconBrandGitlab,\n sso: IconKey,\n email: IconMail,\n passkey: IconFingerprint,\n}\n\nfunction providerName(provider: OAuthProvider): string {\n // Display name for `compact` mode — drops the \"a\" article on passkey.\n return provider === 'passkey' ? 'Passkey' : providerDisplay[provider]\n}\n\nfunction defaultLabel(provider: OAuthProvider, intent: OAuthIntent, compact: boolean): string {\n if (compact) return providerName(provider)\n const name = providerDisplay[provider]\n switch (intent) {\n case 'signin':\n return `Sign in with ${name}`\n case 'signup':\n return `Sign up with ${name}`\n case 'continue':\n default:\n return `Continue with ${name}`\n }\n}\n\nconst providerCopyOverride: Partial<Record<OAuthProvider, Partial<Record<OAuthIntent, string>>>> = {\n passkey: {\n continue: 'Continue with a passkey',\n signin: 'Sign in with a passkey',\n signup: 'Create a passkey',\n },\n}\n\nfunction resolveLabel(provider: OAuthProvider, intent: OAuthIntent, compact: boolean): string {\n if (compact) return providerName(provider)\n return providerCopyOverride[provider]?.[intent] ?? defaultLabel(provider, intent, compact)\n}\n\n/** Long-form name used for aria-label even when compact label is rendered. */\nfunction resolveAriaName(provider: OAuthProvider, intent: OAuthIntent): string {\n return providerCopyOverride[provider]?.[intent] ?? defaultLabel(provider, intent, false)\n}\n\n// **Visual treatment:** OAuthButton is a thin wrapper over `<Button>` — every\n// height, padding, radius, border, hover, focus, active, and disabled state is\n// the DS Button's. We do NOT inject per-provider hex backgrounds: brand\n// identity lives in the glyph (Tabler default; consumer can pass a real\n// multicolour SVG via `icon`). This guarantees pixel-for-pixel consistency\n// with every other button in the system at every variant.\n\nconst ICON_PX: Record<string, number> = {\n xs: 14,\n sm: 16,\n md: 18,\n lg: 20,\n 'compact-xs': 14,\n 'compact-sm': 14,\n 'compact-md': 16,\n icon: 18,\n 'icon-xs': 14,\n 'icon-sm': 16,\n 'icon-md': 18,\n 'icon-lg': 20,\n}\n\nconst ICON_ONLY_SIZE: Record<string, NonNullable<ButtonProps['size']>> = {\n xs: 'icon-xs',\n sm: 'icon-sm',\n md: 'icon-md',\n lg: 'icon-lg',\n 'compact-xs': 'icon-xs',\n 'compact-sm': 'icon-sm',\n 'compact-md': 'icon-md',\n}\n\n// ── Component ───────────────────────────────────────────────────\n\n/**\n * Props for `OAuthButton` — a brand-aware sign-in button for OAuth providers.\n *\n * Composes on top of `<Button>`, inheriting `size`, `loading`, `onClickAsync`,\n * `fullWidth`, and async state machinery. The provider's icon, label, and\n * brand-coloured styling are derived from the `provider` prop.\n *\n * **Provider** (required): includes 10 social providers + 3 generic\n * identifiers (`sso` enterprise, `email` magic-link, `passkey` WebAuthn).\n *\n * **Intent**: drives the verb in the label.\n * - `continue` (default) — \"Continue with Google\" — works on login and signup.\n * - `signin` — \"Sign in with Google\".\n * - `signup` — \"Sign up with Google\".\n *\n * **Appearance**: visual treatment.\n * - `brand` (default) — provider's brand background colour.\n * - `outline` — DS-neutral outline with monochrome glyph. Best for uniform rows.\n * - `dark` — unified Apple-style across all providers for visual consistency.\n *\n * **icon**: override the default Tabler glyph (e.g. drop in the provider's\n * official multicolour SVG downloaded from their brand page).\n *\n * **iconOnly**: render a square icon button. Provider name kept in `aria-label`.\n *\n * **lastUsed**: shows a small \"Last used\" pill above the button — high-converting\n * UX hint. Consumer tracks which provider the user picked previously.\n *\n * **helperText**: optional reassurance copy rendered below the button.\n *\n * @example\n * <OAuthButton provider=\"google\" intent=\"continue\" fullWidth onClickAsync={signInWithGoogle} />\n *\n * @example\n * // True brand-multicolour glyph:\n * <OAuthButton provider=\"google\" icon={<GoogleGSvg />} />\n *\n * @example\n * <OAuthGroup>\n * <OAuthButton provider=\"google\" appearance=\"dark\" />\n * <OAuthButton provider=\"apple\" appearance=\"dark\" />\n * <OAuthButton provider=\"github\" appearance=\"dark\" />\n * </OAuthGroup>\n */\nexport interface OAuthButtonProps\n extends Omit<\n ButtonProps,\n 'variant' | 'color' | 'startIcon' | 'endIcon' | 'shape' | 'children'\n > {\n /** OAuth provider this button represents */\n provider: OAuthProvider\n /** Verb in the default label. Default: `continue` */\n intent?: OAuthIntent\n /**\n * Visual treatment. Default: `solid`. Mirrors Button's variant axis with\n * an extra `dark` for row coherence across providers.\n */\n variant?: OAuthVariant\n /** @deprecated Use `variant`. `appearance=\"brand\"` maps to `variant=\"solid\"`. */\n appearance?: OAuthAppearance\n /** Override the default glyph (e.g. brand-multicolour SVG). Accepts any `IconInput`. */\n icon?: IconInput\n /** Render a compact icon-only button (provider name kept in aria-label) */\n iconOnly?: boolean\n /**\n * Short label — renders just the provider name (\"Google\") instead of the\n * full \"Continue with Google\". Use under an explicit \"Or sign in with\"\n * divider where the verb is already established. aria-label still gets the\n * full long form for screen readers.\n */\n compact?: boolean\n /**\n * Show \"Last used\" hint — a small pill anchored to the top-right corner of\n * the button (sits ON the corner, half outside the button bounds).\n */\n lastUsed?: boolean\n /**\n * Customise the \"Last used\" badge. Pass a ReactNode to replace the entire\n * pill (use this for custom copy, icons, or styling), or a render fn that\n * receives the default props for tweaks. Only rendered when `lastUsed` is\n * also set so the wrapper / aria augmentation logic stays consistent.\n *\n * @example\n * <OAuthButton provider=\"google\" lastUsed lastUsedSlot={<MyBadge />} />\n */\n lastUsedSlot?: React.ReactNode | ((defaults: { label: string }) => React.ReactNode)\n /** Override the visible badge label text. Default: \"Last used\". */\n lastUsedLabel?: string\n /** Reassurance copy rendered below the button */\n helperText?: React.ReactNode\n /** Override the default label entirely (i18n / custom copy) */\n children?: React.ReactNode\n}\n\nconst OAuthButton = React.forwardRef<HTMLButtonElement, OAuthButtonProps>(\n (\n {\n provider,\n intent = 'continue',\n variant: variantProp,\n appearance,\n icon,\n iconOnly = false,\n compact = false,\n lastUsed = false,\n lastUsedSlot,\n lastUsedLabel = 'Last used',\n helperText,\n children,\n className,\n size: sizeProp,\n fullWidth = false,\n ...buttonProps\n },\n ref,\n ) => {\n const size: NonNullable<ButtonProps['size']> = sizeProp ?? 'md'\n const DefaultGlyph = providerIcon[provider]\n const iconPx = ICON_PX[size] ?? 18\n const glyphNode = normalizeIcon(icon) ?? <DefaultGlyph size={iconPx} aria-hidden />\n const label = children ?? resolveLabel(provider, intent, compact)\n\n // Resolve final variant. `appearance` is a deprecated alias mapped to\n // the closest DS variant — brand/dark both collapse to solid since\n // OAuthButton no longer injects per-provider brand colours.\n const variant: OAuthVariant =\n variantProp ??\n (appearance === 'outline' ? 'outline' : 'solid')\n\n // aria-label always carries the long-form name so screen readers stay\n // informative even when the visual label is compact or icon-only.\n const longName = resolveAriaName(provider, intent)\n const ariaLabelForButton = iconOnly || compact || lastUsed\n ? `${longName}${lastUsed ? ' (last used)' : ''}`\n : undefined\n\n // \"Last used\" badge — sits ON the top-right corner of the button,\n // overlapping the button edge for the classic \"notification badge\"\n // look. Rendered as a SIBLING to Button (not a child) because Button's\n // `overflow-hidden` would clip a child poking outside the corner.\n //\n // Composable: pass `lastUsedSlot` (ReactNode or render fn) to replace\n // the default pill. `lastUsedLabel` swaps just the text inside it.\n // aria-label augments the accessible name with \"(last used)\".\n // intentional: a 9px corner pill, smaller than Badge's smallest size (xs).\n // Not worth a sub-xs Badge size for one call site — kept as a documented exception.\n const defaultBadge = (\n <span className=\"pointer-events-none inline-flex items-center rounded-pill bg-accent-9 text-accent-fg px-2 py-1 text-ds-2xs leading-none font-semibold uppercase tracking-wide shadow-overlay\">\n {lastUsedLabel}\n </span>\n )\n const customBadge =\n typeof lastUsedSlot === 'function' ? lastUsedSlot({ label: lastUsedLabel }) : lastUsedSlot\n const renderedBadge = customBadge ?? defaultBadge\n\n const buttonEl = (\n <Button\n ref={ref}\n variant={variant}\n color=\"neutral\"\n size={iconOnly ? ICON_ONLY_SIZE[size] ?? 'icon-md' : size}\n fullWidth={fullWidth && !iconOnly}\n startIcon={iconOnly ? undefined : (glyphNode as React.ReactElement)}\n aria-label={ariaLabelForButton}\n data-provider={provider}\n data-oauth-variant={variant}\n data-last-used={lastUsed || undefined}\n className={className}\n {...buttonProps}\n >\n {iconOnly ? glyphNode : label}\n </Button>\n )\n\n const showBadge = lastUsed && !iconOnly\n if (!showBadge && !helperText) return buttonEl\n\n return (\n <div className={cn('relative inline-flex flex-col', fullWidth && 'w-full')}>\n {buttonEl}\n {showBadge ? (\n <span\n aria-hidden=\"true\"\n data-last-used-badge=\"\"\n className=\"pointer-events-none absolute -top-[10px] -right-2 z-10\"\n >\n {renderedBadge}\n </span>\n ) : null}\n {helperText ? (\n <span className=\"mt-ds-02 text-ds-sm text-surface-fg-subtle\">{helperText}</span>\n ) : null}\n </div>\n )\n },\n)\nOAuthButton.displayName = 'OAuthButton'\n\nexport { OAuthButton }\n\n// ── OAuthGroup ─────────────────────────────────────────────────\n\nexport interface OAuthGroupProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Layout. `vertical` (default) stacks full-width buttons. */\n orientation?: 'vertical' | 'horizontal'\n /** Gap token between buttons. Default: `ds-03` */\n gap?: 'ds-02' | 'ds-03' | 'ds-04'\n /** Stretch children to fill width (vertical only). Default: true */\n fullWidth?: boolean\n /**\n * Move any child with `lastUsed` to position 0. Stripe-style ordering — the\n * provider the user picked last time becomes the top option, which gives a\n * stronger conversion lift than a \"Last used\" badge does on its own.\n * Default: false (preserve source order).\n */\n reorderLastUsedFirst?: boolean\n}\n\n/**\n * Stacked layout wrapper for OAuth buttons with consistent spacing. Defaults to\n * vertical, full-width children — the dominant sign-in pattern. For an icon-only\n * row, set `orientation=\"horizontal\"` and use `<OAuthButton iconOnly />`.\n */\nconst OAuthGroup = React.forwardRef<HTMLDivElement, OAuthGroupProps>(\n (\n {\n orientation = 'vertical',\n gap = 'ds-03',\n fullWidth = true,\n reorderLastUsedFirst = false,\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const gapClass =\n gap === 'ds-02' ? 'gap-ds-02' : gap === 'ds-04' ? 'gap-ds-04' : 'gap-ds-03'\n const stretch = orientation === 'vertical' && fullWidth\n\n const ordered = React.useMemo(() => {\n if (!reorderLastUsedFirst) return children\n const arr = React.Children.toArray(children)\n const lastUsedIdx = arr.findIndex(\n (c) => React.isValidElement(c) && (c.props as { lastUsed?: boolean })?.lastUsed,\n )\n if (lastUsedIdx <= 0) return children\n const next = [...arr]\n const [pulled] = next.splice(lastUsedIdx, 1)\n next.unshift(pulled)\n return next\n }, [children, reorderLastUsedFirst])\n\n return (\n <div\n ref={ref}\n className={cn(\n 'flex',\n orientation === 'vertical' ? 'flex-col' : 'flex-row flex-wrap items-center',\n gapClass,\n // Force both the direct wrapper AND the leaf <button> to stretch.\n // OAuthButton's outer wrapper picks up [&>*]:w-full; the button itself\n // sits two layers deep when a helperText wrapper exists, so\n // [&_button]:w-full covers both cases (wrapped + unwrapped) without\n // affecting unrelated descendants — only Buttons live inside.\n stretch && 'w-full [&>*]:w-full [&_button]:w-full',\n className,\n )}\n {...props}\n >\n {ordered}\n </div>\n )\n },\n)\nOAuthGroup.displayName = 'OAuthGroup'\n\nexport { OAuthGroup }\n\n// ── OAuthDivider ───────────────────────────────────────────────\n\nexport interface OAuthDividerProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Text between the rules. Default: \"or\" */\n label?: React.ReactNode\n}\n\nconst OAuthDivider = React.forwardRef<HTMLDivElement, OAuthDividerProps>(\n ({ label = 'or', className, ...props }, ref) => (\n <div\n ref={ref}\n role=\"separator\"\n aria-orientation=\"horizontal\"\n className={cn('flex items-center gap-ds-03', className)}\n {...props}\n >\n <hr aria-hidden=\"true\" className=\"flex-1 border-t border-surface-border-subtle\" />\n <span className=\"text-ds-sm text-surface-fg-subtle uppercase tracking-wide\">{label}</span>\n <hr aria-hidden=\"true\" className=\"flex-1 border-t border-surface-border-subtle\" />\n </div>\n ),\n)\nOAuthDivider.displayName = 'OAuthDivider'\n\nexport { OAuthDivider }\n\n// ── OAuthConnectionRow (settings page — linked state) ──────────\n\nexport interface OAuthConnectionRowProps extends React.HTMLAttributes<HTMLDivElement> {\n provider: OAuthProvider\n /** Optional account identifier shown next to the provider name (e.g. email) */\n accountLabel?: React.ReactNode\n /** Whether the provider is currently linked */\n connected: boolean\n /** Click handler for the connect/disconnect action */\n onAction?: (e: React.MouseEvent<HTMLButtonElement>) => void\n /** Promise-returning click handler — uses Button's async state machine */\n onActionAsync?: (e: React.MouseEvent<HTMLButtonElement>) => Promise<void>\n /** Override the action label */\n actionLabel?: React.ReactNode\n /** Disable the action button */\n disabled?: boolean\n /** Override the default glyph. Accepts any `IconInput`. */\n icon?: IconInput\n}\n\n/**\n * Settings-page row representing a linked OAuth provider. Different surface\n * from sign-in: this is an existing connection the user can disconnect or\n * (re-)connect. Renders glyph + provider name + optional account label + a\n * connect/disconnect button.\n */\nconst OAuthConnectionRow = React.forwardRef<HTMLDivElement, OAuthConnectionRowProps>(\n (\n {\n provider,\n accountLabel,\n connected,\n onAction,\n onActionAsync,\n actionLabel,\n disabled,\n icon,\n className,\n ...props\n },\n ref,\n ) => {\n const DefaultGlyph = providerIcon[provider]\n const name = providerDisplay[provider]\n const resolvedLabel = actionLabel ?? (connected ? 'Disconnect' : `Connect ${name}`)\n\n return (\n <div\n ref={ref}\n className={cn(\n 'flex items-center justify-between gap-ds-04 py-ds-04 px-ds-04 rounded-surface border border-surface-border bg-surface-raised',\n className,\n )}\n data-provider={provider}\n data-connected={connected ? 'true' : 'false'}\n {...props}\n >\n <div className=\"flex items-center gap-ds-03 min-w-0\">\n {normalizeIcon(icon) ?? <DefaultGlyph size={24} aria-hidden />}\n <div className=\"flex flex-col min-w-0\">\n <TruncatedText className=\"text-ds-md font-semibold text-surface-fg\">{name}</TruncatedText>\n {accountLabel ? (\n // Account labels are usually an email — middle-truncate to keep the domain.\n // Only a string can be measured/truncated; fall back to a plain clip otherwise.\n typeof accountLabel === 'string' ? (\n <TruncatedText mode=\"middle\" className=\"text-ds-sm text-surface-fg-muted\">\n {accountLabel}\n </TruncatedText>\n ) : (\n <span className=\"text-ds-sm text-surface-fg-muted truncate\">{accountLabel}</span>\n )\n ) : connected ? (\n <span className=\"text-ds-sm text-success-11\">Connected</span>\n ) : (\n <span className=\"text-ds-sm text-surface-fg-subtle\">Not connected</span>\n )}\n </div>\n </div>\n <Button\n size=\"sm\"\n variant={connected ? 'soft' : 'solid'}\n color={connected ? 'neutral' : 'accent'}\n disabled={disabled}\n onClick={onAction}\n onClickAsync={onActionAsync}\n >\n {resolvedLabel}\n </Button>\n </div>\n )\n },\n)\nOAuthConnectionRow.displayName = 'OAuthConnectionRow'\n\nexport { OAuthConnectionRow }\n\n// ── OAuthGlyph (atomic glyph render) ───────────────────────────\n\nexport interface OAuthGlyphProps extends Omit<React.SVGAttributes<SVGElement>, 'children'> {\n provider: OAuthProvider\n /** Glyph pixel size. Default 18. */\n size?: number\n}\n\n/**\n * Render just the provider glyph — useful when building a custom OAuth\n * trigger (e.g. an icon-button inside a Tooltip, or a provider chip on a\n * profile card). Defaults to the Tabler glyph; the icon prop on\n * `OAuthButton` is the right escape hatch for the brand multi-colour mark.\n */\nconst OAuthGlyph = React.forwardRef<SVGSVGElement, OAuthGlyphProps>(\n ({ provider, size = 18, ...props }, ref) => {\n const Cmp = providerIcon[provider] as React.ComponentType<{\n size?: number\n 'aria-hidden'?: boolean\n ref?: React.Ref<SVGSVGElement>\n }>\n return <Cmp ref={ref} size={size} aria-hidden {...(props as Record<string, unknown>)} />\n },\n)\nOAuthGlyph.displayName = 'OAuthGlyph'\n\nexport { OAuthGlyph }\n\n// ── Static helpers (composable building blocks) ────────────────\n\n/**\n * Resolve the visible label for a provider × intent combo. Useful when\n * rendering OAuth UIs outside of `OAuthButton` (e.g. a Confirm dialog\n * \"Disconnect from Google?\" title).\n */\nexport function getOAuthLabel(provider: OAuthProvider, intent: OAuthIntent = 'continue', compact = false): string {\n return resolveLabel(provider, intent, compact)\n}\n\n/** Display name for a provider, ready for sentence interpolation. */\nexport function getOAuthName(provider: OAuthProvider): string {\n return providerName(provider)\n}\n\n// ── Compound namespace ─────────────────────────────────────────\n\n/**\n * Namespaced API — drop-in alternative to importing each piece separately.\n *\n * @example\n * import { OAuth } from '@devalok/shilp-sutra/ui/oauth-button'\n *\n * <OAuth.Group reorderLastUsedFirst>\n * <OAuth.Button provider=\"google\" lastUsed />\n * <OAuth.Button provider=\"passkey\" />\n * <OAuth.Divider />\n * <OAuth.Button provider=\"sso\" variant=\"ghost\" />\n * </OAuth.Group>\n */\nexport const OAuth = {\n Button: OAuthButton,\n Group: OAuthGroup,\n Divider: OAuthDivider,\n ConnectionRow: OAuthConnectionRow,\n Glyph: OAuthGlyph,\n getLabel: getOAuthLabel,\n getName: getOAuthName,\n} as const\n"],"mappings":"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"}
1
+ {"version":3,"file":"oauth-button.js","names":[],"sources":["../../src/ui/oauth-button/oauth-button.tsx"],"sourcesContent":["'use client'\n\nimport {\n IconBrandApple,\n IconBrandDiscord,\n IconBrandFacebook,\n IconBrandGithub,\n IconBrandGitlab,\n IconBrandGoogleFilled,\n IconBrandLinkedin,\n IconBrandSlack,\n IconBrandWindows,\n IconBrandXFilled,\n IconFingerprint,\n IconKey,\n IconMail,\n} from '@tabler/icons-react'\nimport * as React from 'react'\n\nimport { Button, type ButtonProps } from '../button'\nimport type { IconInput } from '../lib/icon-input'\nimport { normalizeIcon } from '../lib/normalize-icon'\nimport { cn } from '../lib/utils'\nimport { TruncatedText } from '../truncated-text'\n\n// ── Types ───────────────────────────────────────────────────────\n\nexport type OAuthProvider =\n | 'google'\n | 'apple'\n | 'github'\n | 'microsoft'\n | 'x'\n | 'linkedin'\n | 'facebook'\n | 'discord'\n | 'slack'\n | 'gitlab'\n | 'sso'\n | 'email'\n | 'passkey'\n\nexport type OAuthIntent = 'continue' | 'signin' | 'signup'\n\n/**\n * Visual treatment — 1:1 with Button's variant axis. OAuthButton is a thin\n * wrapper over Button, so every variant renders pixel-for-pixel identical\n * to a regular Button at the same variant + size. Brand identity lives in\n * the glyph (override via `icon` for a brand-multicolour SVG), not the\n * button surface.\n *\n * - `solid` (default) | `soft` | `outline` | `ghost`\n */\nexport type OAuthVariant = 'solid' | 'soft' | 'outline' | 'ghost'\n\n/** @deprecated Use `OAuthVariant`. brand/dark map to solid; outline passes through. */\nexport type OAuthAppearance = 'brand' | 'outline' | 'dark'\n\n// ── Static metadata ─────────────────────────────────────────────\n\nconst providerDisplay: Record<OAuthProvider, string> = {\n google: 'Google',\n apple: 'Apple',\n github: 'GitHub',\n microsoft: 'Microsoft',\n x: 'X',\n linkedin: 'LinkedIn',\n facebook: 'Facebook',\n discord: 'Discord',\n slack: 'Slack',\n gitlab: 'GitLab',\n sso: 'SSO',\n email: 'email',\n passkey: 'a passkey',\n}\n\n// Default glyph per provider — sourced from Tabler (already a peer dep).\n// Consumer can override with the `icon` prop to drop in a brand-multicolour SVG.\nconst providerIcon: Record<OAuthProvider, React.ComponentType<{ size?: number | string; 'aria-hidden'?: boolean }>> = {\n google: IconBrandGoogleFilled,\n apple: IconBrandApple,\n github: IconBrandGithub,\n microsoft: IconBrandWindows,\n x: IconBrandXFilled,\n linkedin: IconBrandLinkedin,\n facebook: IconBrandFacebook,\n discord: IconBrandDiscord,\n slack: IconBrandSlack,\n gitlab: IconBrandGitlab,\n sso: IconKey,\n email: IconMail,\n passkey: IconFingerprint,\n}\n\nfunction providerName(provider: OAuthProvider): string {\n // Display name for `compact` mode — drops the \"a\" article on passkey.\n return provider === 'passkey' ? 'Passkey' : providerDisplay[provider]\n}\n\nfunction defaultLabel(provider: OAuthProvider, intent: OAuthIntent, compact: boolean): string {\n if (compact) return providerName(provider)\n const name = providerDisplay[provider]\n switch (intent) {\n case 'signin':\n return `Sign in with ${name}`\n case 'signup':\n return `Sign up with ${name}`\n case 'continue':\n default:\n return `Continue with ${name}`\n }\n}\n\nconst providerCopyOverride: Partial<Record<OAuthProvider, Partial<Record<OAuthIntent, string>>>> = {\n passkey: {\n continue: 'Continue with a passkey',\n signin: 'Sign in with a passkey',\n signup: 'Create a passkey',\n },\n}\n\nfunction resolveLabel(provider: OAuthProvider, intent: OAuthIntent, compact: boolean): string {\n if (compact) return providerName(provider)\n return providerCopyOverride[provider]?.[intent] ?? defaultLabel(provider, intent, compact)\n}\n\n/** Long-form name used for aria-label even when compact label is rendered. */\nfunction resolveAriaName(provider: OAuthProvider, intent: OAuthIntent): string {\n return providerCopyOverride[provider]?.[intent] ?? defaultLabel(provider, intent, false)\n}\n\n// **Visual treatment:** OAuthButton is a thin wrapper over `<Button>` — every\n// height, padding, radius, border, hover, focus, active, and disabled state is\n// the DS Button's. We do NOT inject per-provider hex backgrounds: brand\n// identity lives in the glyph (Tabler default; consumer can pass a real\n// multicolour SVG via `icon`). This guarantees pixel-for-pixel consistency\n// with every other button in the system at every variant.\n\nconst ICON_PX: Record<string, number> = {\n xs: 14,\n sm: 16,\n md: 18,\n lg: 20,\n 'compact-xs': 14,\n 'compact-sm': 14,\n 'compact-md': 16,\n icon: 18,\n 'icon-xs': 14,\n 'icon-sm': 16,\n 'icon-md': 18,\n 'icon-lg': 20,\n}\n\nconst ICON_ONLY_SIZE: Record<string, NonNullable<ButtonProps['size']>> = {\n xs: 'icon-xs',\n sm: 'icon-sm',\n md: 'icon-md',\n lg: 'icon-lg',\n 'compact-xs': 'icon-xs',\n 'compact-sm': 'icon-sm',\n 'compact-md': 'icon-md',\n}\n\n// ── Component ───────────────────────────────────────────────────\n\n/**\n * Props for `OAuthButton` — a brand-aware sign-in button for OAuth providers.\n *\n * Composes on top of `<Button>`, inheriting `size`, `loading`, `onClickAsync`,\n * `fullWidth`, and async state machinery. The provider's icon, label, and\n * brand-coloured styling are derived from the `provider` prop.\n *\n * **Provider** (required): includes 10 social providers + 3 generic\n * identifiers (`sso` enterprise, `email` magic-link, `passkey` WebAuthn).\n *\n * **Intent**: drives the verb in the label.\n * - `continue` (default) — \"Continue with Google\" — works on login and signup.\n * - `signin` — \"Sign in with Google\".\n * - `signup` — \"Sign up with Google\".\n *\n * **Appearance**: visual treatment.\n * - `brand` (default) — provider's brand background colour.\n * - `outline` — DS-neutral outline with monochrome glyph. Best for uniform rows.\n * - `dark` — unified Apple-style across all providers for visual consistency.\n *\n * **icon**: override the default Tabler glyph (e.g. drop in the provider's\n * official multicolour SVG downloaded from their brand page).\n *\n * **iconOnly**: render a square icon button. Provider name kept in `aria-label`.\n *\n * **lastUsed**: shows a small \"Last used\" pill above the button — high-converting\n * UX hint. Consumer tracks which provider the user picked previously.\n *\n * **helperText**: optional reassurance copy rendered below the button.\n *\n * @example\n * <OAuthButton provider=\"google\" intent=\"continue\" fullWidth onClickAsync={signInWithGoogle} />\n *\n * @example\n * // True brand-multicolour glyph:\n * <OAuthButton provider=\"google\" icon={<GoogleGSvg />} />\n *\n * @example\n * <OAuthGroup>\n * <OAuthButton provider=\"google\" appearance=\"dark\" />\n * <OAuthButton provider=\"apple\" appearance=\"dark\" />\n * <OAuthButton provider=\"github\" appearance=\"dark\" />\n * </OAuthGroup>\n */\nexport interface OAuthButtonProps\n extends Omit<\n ButtonProps,\n 'variant' | 'color' | 'startIcon' | 'endIcon' | 'shape' | 'children'\n > {\n /** OAuth provider this button represents */\n provider: OAuthProvider\n /** Verb in the default label. Default: `continue` */\n intent?: OAuthIntent\n /**\n * Visual treatment. Default: `solid`. Mirrors Button's variant axis with\n * an extra `dark` for row coherence across providers.\n */\n variant?: OAuthVariant\n /** @deprecated Use `variant`. `appearance=\"brand\"` maps to `variant=\"solid\"`. */\n appearance?: OAuthAppearance\n /** Override the default glyph (e.g. brand-multicolour SVG). Accepts any `IconInput`. */\n icon?: IconInput\n /** Render a compact icon-only button (provider name kept in aria-label) */\n iconOnly?: boolean\n /**\n * Short label — renders just the provider name (\"Google\") instead of the\n * full \"Continue with Google\". Use under an explicit \"Or sign in with\"\n * divider where the verb is already established. aria-label still gets the\n * full long form for screen readers.\n */\n compact?: boolean\n /**\n * Show \"Last used\" hint — a small pill anchored to the top-right corner of\n * the button (sits ON the corner, half outside the button bounds).\n */\n lastUsed?: boolean\n /**\n * Customise the \"Last used\" badge. Pass a ReactNode to replace the entire\n * pill (use this for custom copy, icons, or styling), or a render fn that\n * receives the default props for tweaks. Only rendered when `lastUsed` is\n * also set so the wrapper / aria augmentation logic stays consistent.\n *\n * @example\n * <OAuthButton provider=\"google\" lastUsed lastUsedSlot={<MyBadge />} />\n */\n lastUsedSlot?: React.ReactNode | ((defaults: { label: string }) => React.ReactNode)\n /** Override the visible badge label text. Default: \"Last used\". */\n lastUsedLabel?: string\n /** Reassurance copy rendered below the button */\n helperText?: React.ReactNode\n /** Override the default label entirely (i18n / custom copy) */\n children?: React.ReactNode\n}\n\nconst OAuthButton = React.forwardRef<HTMLButtonElement, OAuthButtonProps>(\n (\n {\n provider,\n intent = 'continue',\n variant: variantProp,\n appearance,\n icon,\n iconOnly = false,\n compact = false,\n lastUsed = false,\n lastUsedSlot,\n lastUsedLabel = 'Last used',\n helperText,\n children,\n className,\n size: sizeProp,\n fullWidth = false,\n ...buttonProps\n },\n ref,\n ) => {\n const size: NonNullable<ButtonProps['size']> = sizeProp ?? 'md'\n const DefaultGlyph = providerIcon[provider]\n const iconPx = ICON_PX[size] ?? 18\n const glyphNode = normalizeIcon(icon) ?? <DefaultGlyph size={iconPx} aria-hidden />\n const label = children ?? resolveLabel(provider, intent, compact)\n\n // Resolve final variant. `appearance` is a deprecated alias mapped to\n // the closest DS variant — brand/dark both collapse to solid since\n // OAuthButton no longer injects per-provider brand colours.\n const variant: OAuthVariant =\n variantProp ??\n (appearance === 'outline' ? 'outline' : 'solid')\n\n // aria-label always carries the long-form name so screen readers stay\n // informative even when the visual label is compact or icon-only.\n const longName = resolveAriaName(provider, intent)\n const ariaLabelForButton = iconOnly || compact || lastUsed\n ? `${longName}${lastUsed ? ' (last used)' : ''}`\n : undefined\n\n // \"Last used\" badge — sits ON the top-right corner of the button,\n // overlapping the button edge for the classic \"notification badge\"\n // look. Rendered as a SIBLING to Button (not a child) because Button's\n // `overflow-hidden` would clip a child poking outside the corner.\n //\n // Composable: pass `lastUsedSlot` (ReactNode or render fn) to replace\n // the default pill. `lastUsedLabel` swaps just the text inside it.\n // aria-label augments the accessible name with \"(last used)\".\n // intentional: a 9px corner pill, smaller than Badge's smallest size (xs).\n // Not worth a sub-xs Badge size for one call site — kept as a documented exception.\n const defaultBadge = (\n <span className=\"pointer-events-none inline-flex items-center rounded-pill bg-accent-9 text-accent-fg px-2 py-1 text-label-xs leading-none font-semibold uppercase tracking-wide shadow-overlay\">\n {lastUsedLabel}\n </span>\n )\n const customBadge =\n typeof lastUsedSlot === 'function' ? lastUsedSlot({ label: lastUsedLabel }) : lastUsedSlot\n const renderedBadge = customBadge ?? defaultBadge\n\n const buttonEl = (\n <Button\n ref={ref}\n variant={variant}\n color=\"neutral\"\n size={iconOnly ? ICON_ONLY_SIZE[size] ?? 'icon-md' : size}\n fullWidth={fullWidth && !iconOnly}\n startIcon={iconOnly ? undefined : (glyphNode as React.ReactElement)}\n aria-label={ariaLabelForButton}\n data-provider={provider}\n data-oauth-variant={variant}\n data-last-used={lastUsed || undefined}\n className={className}\n {...buttonProps}\n >\n {iconOnly ? glyphNode : label}\n </Button>\n )\n\n const showBadge = lastUsed && !iconOnly\n if (!showBadge && !helperText) return buttonEl\n\n return (\n <div className={cn('relative inline-flex flex-col', fullWidth && 'w-full')}>\n {buttonEl}\n {showBadge ? (\n <span\n aria-hidden=\"true\"\n data-last-used-badge=\"\"\n className=\"pointer-events-none absolute -top-[10px] -right-2 z-10\"\n >\n {renderedBadge}\n </span>\n ) : null}\n {helperText ? (\n <span className=\"mt-ds-02 text-body-sm text-surface-fg-subtle\">{helperText}</span>\n ) : null}\n </div>\n )\n },\n)\nOAuthButton.displayName = 'OAuthButton'\n\nexport { OAuthButton }\n\n// ── OAuthGroup ─────────────────────────────────────────────────\n\nexport interface OAuthGroupProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Layout. `vertical` (default) stacks full-width buttons. */\n orientation?: 'vertical' | 'horizontal'\n /** Gap token between buttons. Default: `ds-03` */\n gap?: 'ds-02' | 'ds-03' | 'ds-04'\n /** Stretch children to fill width (vertical only). Default: true */\n fullWidth?: boolean\n /**\n * Move any child with `lastUsed` to position 0. Stripe-style ordering — the\n * provider the user picked last time becomes the top option, which gives a\n * stronger conversion lift than a \"Last used\" badge does on its own.\n * Default: false (preserve source order).\n */\n reorderLastUsedFirst?: boolean\n}\n\n/**\n * Stacked layout wrapper for OAuth buttons with consistent spacing. Defaults to\n * vertical, full-width children — the dominant sign-in pattern. For an icon-only\n * row, set `orientation=\"horizontal\"` and use `<OAuthButton iconOnly />`.\n */\nconst OAuthGroup = React.forwardRef<HTMLDivElement, OAuthGroupProps>(\n (\n {\n orientation = 'vertical',\n gap = 'ds-03',\n fullWidth = true,\n reorderLastUsedFirst = false,\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const gapClass =\n gap === 'ds-02' ? 'gap-ds-02' : gap === 'ds-04' ? 'gap-ds-04' : 'gap-ds-03'\n const stretch = orientation === 'vertical' && fullWidth\n\n const ordered = React.useMemo(() => {\n if (!reorderLastUsedFirst) return children\n const arr = React.Children.toArray(children)\n const lastUsedIdx = arr.findIndex(\n (c) => React.isValidElement(c) && (c.props as { lastUsed?: boolean })?.lastUsed,\n )\n if (lastUsedIdx <= 0) return children\n const next = [...arr]\n const [pulled] = next.splice(lastUsedIdx, 1)\n next.unshift(pulled)\n return next\n }, [children, reorderLastUsedFirst])\n\n return (\n <div\n ref={ref}\n className={cn(\n 'flex',\n orientation === 'vertical' ? 'flex-col' : 'flex-row flex-wrap items-center',\n gapClass,\n // Force both the direct wrapper AND the leaf <button> to stretch.\n // OAuthButton's outer wrapper picks up [&>*]:w-full; the button itself\n // sits two layers deep when a helperText wrapper exists, so\n // [&_button]:w-full covers both cases (wrapped + unwrapped) without\n // affecting unrelated descendants — only Buttons live inside.\n stretch && 'w-full [&>*]:w-full [&_button]:w-full',\n className,\n )}\n {...props}\n >\n {ordered}\n </div>\n )\n },\n)\nOAuthGroup.displayName = 'OAuthGroup'\n\nexport { OAuthGroup }\n\n// ── OAuthDivider ───────────────────────────────────────────────\n\nexport interface OAuthDividerProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Text between the rules. Default: \"or\" */\n label?: React.ReactNode\n}\n\nconst OAuthDivider = React.forwardRef<HTMLDivElement, OAuthDividerProps>(\n ({ label = 'or', className, ...props }, ref) => (\n <div\n ref={ref}\n role=\"separator\"\n aria-orientation=\"horizontal\"\n className={cn('flex items-center gap-ds-03', className)}\n {...props}\n >\n <hr aria-hidden=\"true\" className=\"flex-1 border-t border-surface-border-subtle\" />\n <span className=\"text-label-sm text-surface-fg-subtle uppercase tracking-wide\">{label}</span>\n <hr aria-hidden=\"true\" className=\"flex-1 border-t border-surface-border-subtle\" />\n </div>\n ),\n)\nOAuthDivider.displayName = 'OAuthDivider'\n\nexport { OAuthDivider }\n\n// ── OAuthConnectionRow (settings page — linked state) ──────────\n\nexport interface OAuthConnectionRowProps extends React.HTMLAttributes<HTMLDivElement> {\n provider: OAuthProvider\n /** Optional account identifier shown next to the provider name (e.g. email) */\n accountLabel?: React.ReactNode\n /** Whether the provider is currently linked */\n connected: boolean\n /** Click handler for the connect/disconnect action */\n onAction?: (e: React.MouseEvent<HTMLButtonElement>) => void\n /** Promise-returning click handler — uses Button's async state machine */\n onActionAsync?: (e: React.MouseEvent<HTMLButtonElement>) => Promise<void>\n /** Override the action label */\n actionLabel?: React.ReactNode\n /** Disable the action button */\n disabled?: boolean\n /** Override the default glyph. Accepts any `IconInput`. */\n icon?: IconInput\n}\n\n/**\n * Settings-page row representing a linked OAuth provider. Different surface\n * from sign-in: this is an existing connection the user can disconnect or\n * (re-)connect. Renders glyph + provider name + optional account label + a\n * connect/disconnect button.\n */\nconst OAuthConnectionRow = React.forwardRef<HTMLDivElement, OAuthConnectionRowProps>(\n (\n {\n provider,\n accountLabel,\n connected,\n onAction,\n onActionAsync,\n actionLabel,\n disabled,\n icon,\n className,\n ...props\n },\n ref,\n ) => {\n const DefaultGlyph = providerIcon[provider]\n const name = providerDisplay[provider]\n const resolvedLabel = actionLabel ?? (connected ? 'Disconnect' : `Connect ${name}`)\n\n return (\n <div\n ref={ref}\n className={cn(\n 'flex items-center justify-between gap-ds-04 py-ds-04 px-ds-04 rounded-surface border border-surface-border bg-surface-raised',\n className,\n )}\n data-provider={provider}\n data-connected={connected ? 'true' : 'false'}\n {...props}\n >\n <div className=\"flex items-center gap-ds-03 min-w-0\">\n {normalizeIcon(icon) ?? <DefaultGlyph size={24} aria-hidden />}\n <div className=\"flex flex-col min-w-0\">\n <TruncatedText className=\"text-body-md font-semibold text-surface-fg\">{name}</TruncatedText>\n {accountLabel ? (\n // Account labels are usually an email — middle-truncate to keep the domain.\n // Only a string can be measured/truncated; fall back to a plain clip otherwise.\n typeof accountLabel === 'string' ? (\n <TruncatedText mode=\"middle\" className=\"text-body-sm text-surface-fg-muted\">\n {accountLabel}\n </TruncatedText>\n ) : (\n <span className=\"text-body-sm text-surface-fg-muted truncate\">{accountLabel}</span>\n )\n ) : connected ? (\n <span className=\"text-body-sm text-success-11\">Connected</span>\n ) : (\n <span className=\"text-body-sm text-surface-fg-subtle\">Not connected</span>\n )}\n </div>\n </div>\n <Button\n size=\"sm\"\n variant={connected ? 'soft' : 'solid'}\n color={connected ? 'neutral' : 'accent'}\n disabled={disabled}\n onClick={onAction}\n onClickAsync={onActionAsync}\n >\n {resolvedLabel}\n </Button>\n </div>\n )\n },\n)\nOAuthConnectionRow.displayName = 'OAuthConnectionRow'\n\nexport { OAuthConnectionRow }\n\n// ── OAuthGlyph (atomic glyph render) ───────────────────────────\n\nexport interface OAuthGlyphProps extends Omit<React.SVGAttributes<SVGElement>, 'children'> {\n provider: OAuthProvider\n /** Glyph pixel size. Default 18. */\n size?: number\n}\n\n/**\n * Render just the provider glyph — useful when building a custom OAuth\n * trigger (e.g. an icon-button inside a Tooltip, or a provider chip on a\n * profile card). Defaults to the Tabler glyph; the icon prop on\n * `OAuthButton` is the right escape hatch for the brand multi-colour mark.\n */\nconst OAuthGlyph = React.forwardRef<SVGSVGElement, OAuthGlyphProps>(\n ({ provider, size = 18, ...props }, ref) => {\n const Cmp = providerIcon[provider] as React.ComponentType<{\n size?: number\n 'aria-hidden'?: boolean\n ref?: React.Ref<SVGSVGElement>\n }>\n return <Cmp ref={ref} size={size} aria-hidden {...(props as Record<string, unknown>)} />\n },\n)\nOAuthGlyph.displayName = 'OAuthGlyph'\n\nexport { OAuthGlyph }\n\n// ── Static helpers (composable building blocks) ────────────────\n\n/**\n * Resolve the visible label for a provider × intent combo. Useful when\n * rendering OAuth UIs outside of `OAuthButton` (e.g. a Confirm dialog\n * \"Disconnect from Google?\" title).\n */\nexport function getOAuthLabel(provider: OAuthProvider, intent: OAuthIntent = 'continue', compact = false): string {\n return resolveLabel(provider, intent, compact)\n}\n\n/** Display name for a provider, ready for sentence interpolation. */\nexport function getOAuthName(provider: OAuthProvider): string {\n return providerName(provider)\n}\n\n// ── Compound namespace ─────────────────────────────────────────\n\n/**\n * Namespaced API — drop-in alternative to importing each piece separately.\n *\n * @example\n * import { OAuth } from '@devalok/shilp-sutra/ui/oauth-button'\n *\n * <OAuth.Group reorderLastUsedFirst>\n * <OAuth.Button provider=\"google\" lastUsed />\n * <OAuth.Button provider=\"passkey\" />\n * <OAuth.Divider />\n * <OAuth.Button provider=\"sso\" variant=\"ghost\" />\n * </OAuth.Group>\n */\nexport const OAuth = {\n Button: OAuthButton,\n Group: OAuthGroup,\n Divider: OAuthDivider,\n ConnectionRow: OAuthConnectionRow,\n Glyph: OAuthGlyph,\n getLabel: getOAuthLabel,\n getName: getOAuthName,\n} as const\n"],"mappings":"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"}
@@ -19,10 +19,10 @@ function h({ message: e, url: t }) {
19
19
  className: "text-warning-9"
20
20
  }),
21
21
  /* @__PURE__ */ l("div", { children: [/* @__PURE__ */ c("p", {
22
- className: "text-ds-md font-semibold text-surface-fg",
22
+ className: "text-body-md font-semibold text-surface-fg",
23
23
  children: "Preview unavailable"
24
24
  }), /* @__PURE__ */ c("p", {
25
- className: "text-ds-sm text-surface-fg-muted mt-ds-01",
25
+ className: "text-body-sm text-surface-fg-muted mt-ds-01",
26
26
  children: e
27
27
  })] }),
28
28
  /* @__PURE__ */ c(s, {
@@ -1 +1 @@
1
- {"version":3,"file":"shared.js","names":[],"sources":["../../src/composed/file-preview/shared.tsx"],"sourcesContent":["'use client'\n\nimport * as SliderPrimitive from '@primitives/react-slider'\nimport {\n IconAlertTriangle,\n IconDownload,\n IconVolume,\n IconVolumeOff,\n} from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Button } from '../../ui/button'\nimport { Icon } from '../../ui/icon'\nimport { tweens } from '../../ui/lib/motion'\nimport { cn } from '../../ui/lib/utils'\n\n// ============================================================\n// Shared: Error Fallback\n// ============================================================\n\nexport function ErrorFallback({ message, url }: { message: string; url: string }) {\n return (\n <div className=\"flex flex-col items-center justify-center gap-ds-04 rounded-control border border-surface-border bg-surface-sunken p-ds-08 text-center\">\n <Icon icon={IconAlertTriangle} size=\"2xl\" className=\"text-warning-9\" />\n <div>\n <p className=\"text-ds-md font-semibold text-surface-fg\">Preview unavailable</p>\n <p className=\"text-ds-sm text-surface-fg-muted mt-ds-01\">{message}</p>\n </div>\n <Button variant=\"outline\" size=\"xs\" startIcon={<Icon icon={IconDownload} />} asChild>\n <a href={url} download target=\"_blank\" rel=\"noopener noreferrer\">Download file</a>\n </Button>\n </div>\n )\n}\n\n// ============================================================\n// Shared: Floating Toolbar\n// ============================================================\n\nexport function Toolbar({ children, className }: { children: React.ReactNode; className?: string }) {\n return (\n <motion.div\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n transition={tweens.fade}\n className={cn(\n 'flex items-center gap-ds-01 rounded-control bg-surface-overlay/95 px-ds-02 py-ds-01 shadow-floating backdrop-blur-xs',\n className,\n )}\n >\n {children}\n </motion.div>\n )\n}\n\nexport function ToolbarDivider() {\n return <div className=\"mx-ds-01 h-4 w-px bg-surface-border-subtle\" />\n}\n\n// ============================================================\n// Shared: MediaSlider — slim seek/scrub/volume control\n// Composes the Radix Slider primitive (the same one ui/Slider uses) so it's\n// keyboard-operable (Arrow/Home/End), shows a visible focus ring, and forced-colors-safe —\n// unlike the old mouse-only <div role=\"slider\">. Styled slim for media chrome;\n// the thumb is hover/focus-reveal (hidden at rest, shown on hover, drag, or\n// keyboard focus). `tone=\"dark\"` = white-on-overlay; `tone=\"light\"` = accent.\n// ============================================================\n\nexport function MediaSlider({\n value,\n max = 100,\n step = 1,\n onValueChange,\n tone = 'light',\n ariaLabel,\n disabled,\n className,\n}: {\n value: number\n max?: number\n step?: number\n onValueChange: (v: number) => void\n tone?: 'light' | 'dark'\n ariaLabel: string\n disabled?: boolean\n className?: string\n}) {\n const isDark = tone === 'dark'\n return (\n <SliderPrimitive.Root\n className={cn(\n 'group/ms relative flex h-3 w-full cursor-pointer touch-none select-none items-center',\n className,\n )}\n value={[value]}\n max={max}\n step={step}\n disabled={disabled}\n onValueChange={(vals) => onValueChange(vals[0] ?? 0)}\n aria-label={ariaLabel}\n >\n <SliderPrimitive.Track\n className={cn(\n 'relative h-1 w-full grow overflow-hidden rounded-pill',\n isDark ? 'bg-white/30' : 'bg-surface-sunken',\n )}\n >\n <SliderPrimitive.Range\n className={cn('absolute h-full rounded-pill', isDark ? 'bg-white' : 'bg-accent-9')}\n />\n </SliderPrimitive.Track>\n <SliderPrimitive.Thumb\n className={cn(\n 'block h-3 w-3 rounded-pill shadow-raised outline-hidden transition-[opacity,transform] duration-fast-02',\n // hover/focus-reveal: hidden at rest, shown on hover, drag (focus), or keyboard\n 'opacity-0 group-hover/ms:opacity-100 focus:opacity-100 active:scale-110',\n 'focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-offset-1',\n isDark ? 'bg-white focus-visible:ring-white' : 'bg-accent-9 focus-visible:ring-accent-9',\n )}\n />\n </SliderPrimitive.Root>\n )\n}\n\n// ============================================================\n// Shared: Volume Slider (Spotify/YouTube style)\n// ============================================================\n\nexport function VolumeControl({\n volume,\n muted,\n onVolumeChange,\n onMuteToggle,\n variant = 'light',\n}: {\n volume: number\n muted: boolean\n onVolumeChange: (v: number) => void\n onMuteToggle: () => void\n variant?: 'light' | 'dark'\n}) {\n const displayVolume = muted ? 0 : volume\n const isDark = variant === 'dark'\n const iconClass = isDark ? 'text-white hover:text-white/80' : 'text-surface-fg-muted hover:text-surface-fg'\n\n return (\n <div className=\"flex items-center gap-ds-02 shrink-0 group/vol\">\n <button onClick={onMuteToggle} className={cn('transition-colors', iconClass)} aria-label={muted ? 'Unmute (M)' : 'Mute (M)'} title={muted ? 'Unmute' : 'Mute'}>\n {muted || volume === 0 ? <Icon icon={IconVolumeOff} size=\"sm\" /> : <Icon icon={IconVolume} size=\"sm\" />}\n </button>\n <div className=\"flex w-0 items-center overflow-hidden transition-[width] duration-200 ease-productive-standard group-hover/vol:w-20 focus-within:w-20\">\n <MediaSlider\n value={displayVolume}\n max={1}\n step={0.01}\n onValueChange={onVolumeChange}\n tone={variant}\n ariaLabel=\"Volume\"\n />\n </div>\n </div>\n )\n}\n\n// ============================================================\n// Shared: Time Formatting\n// ============================================================\n\nexport function formatTime(s: number): string {\n if (!isFinite(s) || s < 0) return '0:00'\n const h = Math.floor(s / 3600)\n const m = Math.floor((s % 3600) / 60)\n const sec = Math.floor(s % 60)\n if (h > 0) return `${h}:${m.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}`\n return `${m}:${sec.toString().padStart(2, '0')}`\n}\n"],"mappings":";;;;;;;;;;AAqBA,SAAgB,EAAc,EAAE,YAAS,UAAyC;CAChF,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IAAM,MAAM;IAAmB,MAAK;IAAM,WAAU;GAAkB,CAAA;GACtE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;IAAG,WAAU;cAA2C;GAAsB,CAAA,GAC9E,kBAAC,KAAD;IAAG,WAAU;cAA6C;GAAW,CAAA,CAClE,EAAA,CAAA;GACL,kBAAC,GAAD;IAAQ,SAAQ;IAAU,MAAK;IAAK,WAAW,kBAAC,GAAD,EAAM,MAAM,EAAe,CAAA;IAAG,SAAA;cAC3E,kBAAC,KAAD;KAAG,MAAM;KAAK,UAAA;KAAS,QAAO;KAAS,KAAI;eAAsB;IAAgB,CAAA;GAC3E,CAAA;EACL;;AAET;AAMA,SAAgB,EAAQ,EAAE,aAAU,gBAAgE;CAClG,OACE,kBAAC,EAAO,KAAR;EACE,SAAS;GAAE,SAAS;GAAG,GAAG;EAAE;EAC5B,SAAS;GAAE,SAAS;GAAG,GAAG;EAAE;EAC5B,YAAY,EAAO;EACnB,WAAW,EACT,wHACA,CACF;EAEC;CACS,CAAA;AAEhB;AAEA,SAAgB,IAAiB;CAC/B,OAAO,kBAAC,OAAD,EAAK,WAAU,6CAA8C,CAAA;AACtE;AAWA,SAAgB,EAAY,EAC1B,UACA,SAAM,KACN,UAAO,GACP,kBACA,UAAO,SACP,cACA,aACA,gBAUC;CACD,IAAM,IAAS,MAAS;CACxB,OACE,kBAAC,GAAD;EACE,WAAW,EACT,wFACA,CACF;EACA,OAAO,CAAC,CAAK;EACR;EACC;EACI;EACV,gBAAgB,MAAS,EAAc,EAAK,MAAM,CAAC;EACnD,cAAY;YAVd,CAYE,kBAAC,GAAD;GACE,WAAW,EACT,yDACA,IAAS,gBAAgB,mBAC3B;aAEA,kBAAC,GAAD,EACE,WAAW,EAAG,gCAAgC,IAAS,aAAa,aAAa,EAClF,CAAA;EACoB,CAAA,GACvB,kBAAC,GAAD,EACE,WAAW,EACT,2GAEA,2EACA,8EACA,IAAS,sCAAsC,yCACjD,EACD,CAAA,CACmB;;AAE1B;AAMA,SAAgB,EAAc,EAC5B,WACA,UACA,mBACA,iBACA,aAAU,WAOT;CACD,IAAM,IAAgB,IAAQ,IAAI;CAIlC,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,UAAD;GAAQ,SAAS;GAAc,WAAW,EAAG,qBALlC,MAAY,SACA,mCAAmC,6CAIiB;GAAG,cAAY,IAAQ,eAAe;GAAY,OAAO,IAAQ,WAAW;aACpJ,KAAS,MAAW,IAAI,kBAAC,GAAD;IAAM,MAAM;IAAe,MAAK;GAAM,CAAA,IAAI,kBAAC,GAAD;IAAM,MAAM;IAAY,MAAK;GAAM,CAAA;EAChG,CAAA,GACR,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD;IACE,OAAO;IACP,KAAK;IACL,MAAM;IACN,eAAe;IACf,MAAM;IACN,WAAU;GACX,CAAA;EACE,CAAA,CACF;;AAET;AAMA,SAAgB,EAAW,GAAmB;CAC5C,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,GAAG,OAAO;CAClC,IAAM,IAAI,KAAK,MAAM,IAAI,IAAI,GACvB,IAAI,KAAK,MAAO,IAAI,OAAQ,EAAE,GAC9B,IAAM,KAAK,MAAM,IAAI,EAAE;CAE7B,OADI,IAAI,IAAU,GAAG,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE,GAAG,EAAI,SAAS,CAAC,CAAC,SAAS,GAAG,GAAG,MAClF,GAAG,EAAE,GAAG,EAAI,SAAS,CAAC,CAAC,SAAS,GAAG,GAAG;AAC/C"}
1
+ {"version":3,"file":"shared.js","names":[],"sources":["../../src/composed/file-preview/shared.tsx"],"sourcesContent":["'use client'\n\nimport * as SliderPrimitive from '@primitives/react-slider'\nimport {\n IconAlertTriangle,\n IconDownload,\n IconVolume,\n IconVolumeOff,\n} from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Button } from '../../ui/button'\nimport { Icon } from '../../ui/icon'\nimport { tweens } from '../../ui/lib/motion'\nimport { cn } from '../../ui/lib/utils'\n\n// ============================================================\n// Shared: Error Fallback\n// ============================================================\n\nexport function ErrorFallback({ message, url }: { message: string; url: string }) {\n return (\n <div className=\"flex flex-col items-center justify-center gap-ds-04 rounded-control border border-surface-border bg-surface-sunken p-ds-08 text-center\">\n <Icon icon={IconAlertTriangle} size=\"2xl\" className=\"text-warning-9\" />\n <div>\n <p className=\"text-body-md font-semibold text-surface-fg\">Preview unavailable</p>\n <p className=\"text-body-sm text-surface-fg-muted mt-ds-01\">{message}</p>\n </div>\n <Button variant=\"outline\" size=\"xs\" startIcon={<Icon icon={IconDownload} />} asChild>\n <a href={url} download target=\"_blank\" rel=\"noopener noreferrer\">Download file</a>\n </Button>\n </div>\n )\n}\n\n// ============================================================\n// Shared: Floating Toolbar\n// ============================================================\n\nexport function Toolbar({ children, className }: { children: React.ReactNode; className?: string }) {\n return (\n <motion.div\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n transition={tweens.fade}\n className={cn(\n 'flex items-center gap-ds-01 rounded-control bg-surface-overlay/95 px-ds-02 py-ds-01 shadow-floating backdrop-blur-xs',\n className,\n )}\n >\n {children}\n </motion.div>\n )\n}\n\nexport function ToolbarDivider() {\n return <div className=\"mx-ds-01 h-4 w-px bg-surface-border-subtle\" />\n}\n\n// ============================================================\n// Shared: MediaSlider — slim seek/scrub/volume control\n// Composes the Radix Slider primitive (the same one ui/Slider uses) so it's\n// keyboard-operable (Arrow/Home/End), shows a visible focus ring, and forced-colors-safe —\n// unlike the old mouse-only <div role=\"slider\">. Styled slim for media chrome;\n// the thumb is hover/focus-reveal (hidden at rest, shown on hover, drag, or\n// keyboard focus). `tone=\"dark\"` = white-on-overlay; `tone=\"light\"` = accent.\n// ============================================================\n\nexport function MediaSlider({\n value,\n max = 100,\n step = 1,\n onValueChange,\n tone = 'light',\n ariaLabel,\n disabled,\n className,\n}: {\n value: number\n max?: number\n step?: number\n onValueChange: (v: number) => void\n tone?: 'light' | 'dark'\n ariaLabel: string\n disabled?: boolean\n className?: string\n}) {\n const isDark = tone === 'dark'\n return (\n <SliderPrimitive.Root\n className={cn(\n 'group/ms relative flex h-3 w-full cursor-pointer touch-none select-none items-center',\n className,\n )}\n value={[value]}\n max={max}\n step={step}\n disabled={disabled}\n onValueChange={(vals) => onValueChange(vals[0] ?? 0)}\n aria-label={ariaLabel}\n >\n <SliderPrimitive.Track\n className={cn(\n 'relative h-1 w-full grow overflow-hidden rounded-pill',\n isDark ? 'bg-white/30' : 'bg-surface-sunken',\n )}\n >\n <SliderPrimitive.Range\n className={cn('absolute h-full rounded-pill', isDark ? 'bg-white' : 'bg-accent-9')}\n />\n </SliderPrimitive.Track>\n <SliderPrimitive.Thumb\n className={cn(\n 'block h-3 w-3 rounded-pill shadow-raised outline-hidden transition-[opacity,transform] duration-fast-02',\n // hover/focus-reveal: hidden at rest, shown on hover, drag (focus), or keyboard\n 'opacity-0 group-hover/ms:opacity-100 focus:opacity-100 active:scale-110',\n 'focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-offset-1',\n isDark ? 'bg-white focus-visible:ring-white' : 'bg-accent-9 focus-visible:ring-accent-9',\n )}\n />\n </SliderPrimitive.Root>\n )\n}\n\n// ============================================================\n// Shared: Volume Slider (Spotify/YouTube style)\n// ============================================================\n\nexport function VolumeControl({\n volume,\n muted,\n onVolumeChange,\n onMuteToggle,\n variant = 'light',\n}: {\n volume: number\n muted: boolean\n onVolumeChange: (v: number) => void\n onMuteToggle: () => void\n variant?: 'light' | 'dark'\n}) {\n const displayVolume = muted ? 0 : volume\n const isDark = variant === 'dark'\n const iconClass = isDark ? 'text-white hover:text-white/80' : 'text-surface-fg-muted hover:text-surface-fg'\n\n return (\n <div className=\"flex items-center gap-ds-02 shrink-0 group/vol\">\n <button onClick={onMuteToggle} className={cn('transition-colors', iconClass)} aria-label={muted ? 'Unmute (M)' : 'Mute (M)'} title={muted ? 'Unmute' : 'Mute'}>\n {muted || volume === 0 ? <Icon icon={IconVolumeOff} size=\"sm\" /> : <Icon icon={IconVolume} size=\"sm\" />}\n </button>\n <div className=\"flex w-0 items-center overflow-hidden transition-[width] duration-200 ease-productive-standard group-hover/vol:w-20 focus-within:w-20\">\n <MediaSlider\n value={displayVolume}\n max={1}\n step={0.01}\n onValueChange={onVolumeChange}\n tone={variant}\n ariaLabel=\"Volume\"\n />\n </div>\n </div>\n )\n}\n\n// ============================================================\n// Shared: Time Formatting\n// ============================================================\n\nexport function formatTime(s: number): string {\n if (!isFinite(s) || s < 0) return '0:00'\n const h = Math.floor(s / 3600)\n const m = Math.floor((s % 3600) / 60)\n const sec = Math.floor(s % 60)\n if (h > 0) return `${h}:${m.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}`\n return `${m}:${sec.toString().padStart(2, '0')}`\n}\n"],"mappings":";;;;;;;;;;AAqBA,SAAgB,EAAc,EAAE,YAAS,UAAyC;CAChF,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IAAM,MAAM;IAAmB,MAAK;IAAM,WAAU;GAAkB,CAAA;GACtE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;IAAG,WAAU;cAA6C;GAAsB,CAAA,GAChF,kBAAC,KAAD;IAAG,WAAU;cAA+C;GAAW,CAAA,CACpE,EAAA,CAAA;GACL,kBAAC,GAAD;IAAQ,SAAQ;IAAU,MAAK;IAAK,WAAW,kBAAC,GAAD,EAAM,MAAM,EAAe,CAAA;IAAG,SAAA;cAC3E,kBAAC,KAAD;KAAG,MAAM;KAAK,UAAA;KAAS,QAAO;KAAS,KAAI;eAAsB;IAAgB,CAAA;GAC3E,CAAA;EACL;;AAET;AAMA,SAAgB,EAAQ,EAAE,aAAU,gBAAgE;CAClG,OACE,kBAAC,EAAO,KAAR;EACE,SAAS;GAAE,SAAS;GAAG,GAAG;EAAE;EAC5B,SAAS;GAAE,SAAS;GAAG,GAAG;EAAE;EAC5B,YAAY,EAAO;EACnB,WAAW,EACT,wHACA,CACF;EAEC;CACS,CAAA;AAEhB;AAEA,SAAgB,IAAiB;CAC/B,OAAO,kBAAC,OAAD,EAAK,WAAU,6CAA8C,CAAA;AACtE;AAWA,SAAgB,EAAY,EAC1B,UACA,SAAM,KACN,UAAO,GACP,kBACA,UAAO,SACP,cACA,aACA,gBAUC;CACD,IAAM,IAAS,MAAS;CACxB,OACE,kBAAC,GAAD;EACE,WAAW,EACT,wFACA,CACF;EACA,OAAO,CAAC,CAAK;EACR;EACC;EACI;EACV,gBAAgB,MAAS,EAAc,EAAK,MAAM,CAAC;EACnD,cAAY;YAVd,CAYE,kBAAC,GAAD;GACE,WAAW,EACT,yDACA,IAAS,gBAAgB,mBAC3B;aAEA,kBAAC,GAAD,EACE,WAAW,EAAG,gCAAgC,IAAS,aAAa,aAAa,EAClF,CAAA;EACoB,CAAA,GACvB,kBAAC,GAAD,EACE,WAAW,EACT,2GAEA,2EACA,8EACA,IAAS,sCAAsC,yCACjD,EACD,CAAA,CACmB;;AAE1B;AAMA,SAAgB,EAAc,EAC5B,WACA,UACA,mBACA,iBACA,aAAU,WAOT;CACD,IAAM,IAAgB,IAAQ,IAAI;CAIlC,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,UAAD;GAAQ,SAAS;GAAc,WAAW,EAAG,qBALlC,MAAY,SACA,mCAAmC,6CAIiB;GAAG,cAAY,IAAQ,eAAe;GAAY,OAAO,IAAQ,WAAW;aACpJ,KAAS,MAAW,IAAI,kBAAC,GAAD;IAAM,MAAM;IAAe,MAAK;GAAM,CAAA,IAAI,kBAAC,GAAD;IAAM,MAAM;IAAY,MAAK;GAAM,CAAA;EAChG,CAAA,GACR,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD;IACE,OAAO;IACP,KAAK;IACL,MAAM;IACN,eAAe;IACf,MAAM;IACN,WAAU;GACX,CAAA;EACE,CAAA,CACF;;AAET;AAMA,SAAgB,EAAW,GAAmB;CAC5C,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,GAAG,OAAO;CAClC,IAAM,IAAI,KAAK,MAAM,IAAI,IAAI,GACvB,IAAI,KAAK,MAAO,IAAI,OAAQ,EAAE,GAC9B,IAAM,KAAK,MAAM,IAAI,EAAE;CAE7B,OADI,IAAI,IAAU,GAAG,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE,GAAG,EAAI,SAAS,CAAC,CAAC,SAAS,GAAG,GAAG,MAClF,GAAG,EAAE,GAAG,EAAI,SAAS,CAAC,CAAC,SAAS,GAAG,GAAG;AAC/C"}
@@ -103,7 +103,7 @@ var M = C.memo(function({ data: e, confidence: n }) {
103
103
  }
104
104
  },
105
105
  children: u.map((t) => /* @__PURE__ */ w(h, {
106
- className: r("text-ds-sm py-ds-02b px-ds-04", t.variant === "number" && "text-right tabular-nums font-medium"),
106
+ className: r("text-body-sm py-ds-02b px-ds-04", t.variant === "number" && "text-right tabular-nums font-medium"),
107
107
  children: A(e[t.key], t)
108
108
  }, t.key))
109
109
  }, a);
@@ -124,7 +124,7 @@ var N = C.memo(function({ data: e, confidence: t, onAction: n }) {
124
124
  confidence: t,
125
125
  children: [
126
126
  e.description && /* @__PURE__ */ w("p", {
127
- className: "text-ds-sm text-surface-fg-muted mb-3",
127
+ className: "text-body-sm text-surface-fg-muted mb-3",
128
128
  children: e.description
129
129
  }),
130
130
  /* @__PURE__ */ T("div", {
@@ -146,11 +146,11 @@ var N = C.memo(function({ data: e, confidence: t, onAction: n }) {
146
146
  asChild: !0,
147
147
  children: /* @__PURE__ */ w("button", {
148
148
  type: "button",
149
- className: "text-ds-xs text-surface-fg-subtle underline cursor-pointer",
149
+ className: "text-caption text-surface-fg-subtle underline cursor-pointer",
150
150
  children: "Why this action?"
151
151
  })
152
152
  }), /* @__PURE__ */ w(u, { children: /* @__PURE__ */ w("p", {
153
- className: "text-ds-sm text-surface-fg-muted mt-2 p-3 bg-surface-raised rounded-surface",
153
+ className: "text-body-sm text-surface-fg-muted mt-2 p-3 bg-surface-raised rounded-surface",
154
154
  children: e.rationale
155
155
  }) })]
156
156
  })
@@ -264,7 +264,7 @@ var R = C.memo(function({ data: t }) {
264
264
  delay: n * .06
265
265
  },
266
266
  children: [/* @__PURE__ */ w(L, { status: t.status }), /* @__PURE__ */ w("span", {
267
- className: r("text-ds-sm", t.status === "done" || t.status === "active" ? "text-surface-fg" : "text-surface-fg-subtle"),
267
+ className: r("text-body-sm", t.status === "done" || t.status === "active" ? "text-surface-fg" : "text-surface-fg-subtle"),
268
268
  children: t.label
269
269
  })]
270
270
  }, t.id)), /* @__PURE__ */ w("span", {