@devalok/shilp-sutra 0.49.2 → 0.49.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. package/dist/_chunks/badge-group.js.map +1 -1
  2. package/dist/_chunks/block-shell.js.map +1 -1
  3. package/dist/_chunks/chart-container.js.map +1 -1
  4. package/dist/_chunks/chat.js.map +1 -1
  5. package/dist/_chunks/colors.js.map +1 -1
  6. package/dist/_chunks/document-preview.js.map +1 -1
  7. package/dist/_chunks/emoji-suggestion.js.map +1 -1
  8. package/dist/_chunks/emoji.js +2 -2
  9. package/dist/_chunks/emoji.js.map +1 -1
  10. package/dist/_chunks/field-state.js.map +1 -1
  11. package/dist/_chunks/grid-lines.js.map +1 -1
  12. package/dist/_chunks/image-preview.js.map +1 -1
  13. package/dist/_chunks/keybinding.js.map +1 -1
  14. package/dist/_chunks/link-context.js.map +1 -1
  15. package/dist/_chunks/mention-suggestion.js.map +1 -1
  16. package/dist/_chunks/motion-provider.js.map +1 -1
  17. package/dist/_chunks/normalize-icon.js.map +1 -1
  18. package/dist/_chunks/oauth-button.js.map +1 -1
  19. package/dist/_chunks/popover.js.map +1 -1
  20. package/dist/_chunks/primitives.js +1129 -1162
  21. package/dist/_chunks/primitives.js.map +1 -1
  22. package/dist/_chunks/shared.js.map +1 -1
  23. package/dist/_chunks/success.js.map +1 -1
  24. package/dist/_chunks/tiptap.js +4283 -3460
  25. package/dist/_chunks/tiptap.js.map +1 -1
  26. package/dist/_chunks/tooltip.js.map +1 -1
  27. package/dist/_chunks/tree-view.js +1 -1
  28. package/dist/_chunks/tree-view.js.map +1 -1
  29. package/dist/_chunks/use-calendar.js.map +1 -1
  30. package/dist/_chunks/use-controllable-open.js.map +1 -1
  31. package/dist/_chunks/vendor-client.js +86 -59
  32. package/dist/_chunks/vendor-client.js.map +1 -1
  33. package/dist/_chunks/vendor-utils.js +555 -513
  34. package/dist/_chunks/vendor-utils.js.map +1 -1
  35. package/dist/ai/ai-command-provider.d.ts +1 -1
  36. package/dist/ai/ai-command-provider.d.ts.map +1 -1
  37. package/dist/ai/ai-command-provider.js.map +1 -1
  38. package/dist/ai/block-renderer.js.map +1 -1
  39. package/dist/ai/blocks/block-shell.d.ts +1 -1
  40. package/dist/ai/blocks/block-shell.d.ts.map +1 -1
  41. package/dist/ai/blocks/block-table.d.ts +1 -1
  42. package/dist/ai/blocks/block-table.d.ts.map +1 -1
  43. package/dist/ai/blocks/confirm.d.ts +1 -1
  44. package/dist/ai/blocks/confirm.d.ts.map +1 -1
  45. package/dist/ai/blocks/divider.d.ts +1 -1
  46. package/dist/ai/blocks/divider.d.ts.map +1 -1
  47. package/dist/ai/blocks/error.d.ts +1 -1
  48. package/dist/ai/blocks/error.d.ts.map +1 -1
  49. package/dist/ai/blocks/error.js.map +1 -1
  50. package/dist/ai/blocks/info.d.ts +1 -1
  51. package/dist/ai/blocks/info.d.ts.map +1 -1
  52. package/dist/ai/blocks/loading.d.ts +1 -1
  53. package/dist/ai/blocks/loading.d.ts.map +1 -1
  54. package/dist/ai/blocks/stat-row.d.ts +1 -1
  55. package/dist/ai/blocks/stat-row.d.ts.map +1 -1
  56. package/dist/ai/blocks/success.d.ts +1 -1
  57. package/dist/ai/blocks/success.d.ts.map +1 -1
  58. package/dist/ai/blocks/text.d.ts +1 -1
  59. package/dist/ai/blocks/text.d.ts.map +1 -1
  60. package/dist/ai/blocks/text.js.map +1 -1
  61. package/dist/ai/command-bar.js.map +1 -1
  62. package/dist/ai/conversation.js.map +1 -1
  63. package/dist/ai/devadoot-icon.d.ts +1 -1
  64. package/dist/ai/devadoot-icon.d.ts.map +1 -1
  65. package/dist/ai/index.js.map +1 -1
  66. package/dist/composed/activity-feed.js.map +1 -1
  67. package/dist/composed/avatar-group.js.map +1 -1
  68. package/dist/composed/bulk-action-bar.js.map +1 -1
  69. package/dist/composed/command-palette.js.map +1 -1
  70. package/dist/composed/confirm-dialog.js.map +1 -1
  71. package/dist/composed/content-card.js.map +1 -1
  72. package/dist/composed/date-picker/index.js.map +1 -1
  73. package/dist/composed/deadline-indicator.js.map +1 -1
  74. package/dist/composed/emoji-picker.d.ts +2 -2
  75. package/dist/composed/emoji-picker.d.ts.map +1 -1
  76. package/dist/composed/emoji-picker.js.map +1 -1
  77. package/dist/composed/empty-state.js.map +1 -1
  78. package/dist/composed/error-boundary.d.ts +1 -1
  79. package/dist/composed/error-boundary.js.map +1 -1
  80. package/dist/composed/extensions/emoji-node.js.map +1 -1
  81. package/dist/composed/file-preview/audio-preview.d.ts +2 -1
  82. package/dist/composed/file-preview/audio-preview.d.ts.map +1 -1
  83. package/dist/composed/file-preview/document-preview.d.ts +2 -1
  84. package/dist/composed/file-preview/document-preview.d.ts.map +1 -1
  85. package/dist/composed/file-preview/embed-preview.d.ts +2 -1
  86. package/dist/composed/file-preview/embed-preview.d.ts.map +1 -1
  87. package/dist/composed/file-preview/image-preview.d.ts +2 -1
  88. package/dist/composed/file-preview/image-preview.d.ts.map +1 -1
  89. package/dist/composed/file-preview/shared.d.ts +5 -5
  90. package/dist/composed/file-preview/shared.d.ts.map +1 -1
  91. package/dist/composed/file-preview/video-preview.d.ts +2 -1
  92. package/dist/composed/file-preview/video-preview.d.ts.map +1 -1
  93. package/dist/composed/file-preview.d.ts +1 -1
  94. package/dist/composed/file-preview.d.ts.map +1 -1
  95. package/dist/composed/file-preview.js.map +1 -1
  96. package/dist/composed/filter-bar.d.ts +2 -2
  97. package/dist/composed/filter-bar.d.ts.map +1 -1
  98. package/dist/composed/filter-bar.js.map +1 -1
  99. package/dist/composed/form-section.js.map +1 -1
  100. package/dist/composed/global-loading.js.map +1 -1
  101. package/dist/composed/inline-edit.js.map +1 -1
  102. package/dist/composed/lib/string-utils.js.map +1 -1
  103. package/dist/composed/loading-skeleton.js.map +1 -1
  104. package/dist/composed/markdown-viewer.js.map +1 -1
  105. package/dist/composed/master-detail.d.ts +3 -3
  106. package/dist/composed/master-detail.d.ts.map +1 -1
  107. package/dist/composed/master-detail.js.map +1 -1
  108. package/dist/composed/member-picker.js.map +1 -1
  109. package/dist/composed/multi-select-popover.js.map +1 -1
  110. package/dist/composed/page-header.js.map +1 -1
  111. package/dist/composed/page-skeletons.js.map +1 -1
  112. package/dist/composed/priority-indicator.js.map +1 -1
  113. package/dist/composed/responsive-modal.js +8 -4
  114. package/dist/composed/responsive-modal.js.map +1 -1
  115. package/dist/composed/rich-chat-input/attachment-strip.d.ts +2 -1
  116. package/dist/composed/rich-chat-input/attachment-strip.d.ts.map +1 -1
  117. package/dist/composed/rich-chat-input/chat-toolbar.d.ts +17 -17
  118. package/dist/composed/rich-chat-input/chat-toolbar.d.ts.map +1 -1
  119. package/dist/composed/rich-chat-input/recording-overlay.d.ts +2 -1
  120. package/dist/composed/rich-chat-input/recording-overlay.d.ts.map +1 -1
  121. package/dist/composed/rich-chat-input/reply-banner.d.ts +2 -1
  122. package/dist/composed/rich-chat-input/reply-banner.d.ts.map +1 -1
  123. package/dist/composed/rich-chat-input/schedule-send.d.ts +4 -3
  124. package/dist/composed/rich-chat-input/schedule-send.d.ts.map +1 -1
  125. package/dist/composed/rich-chat-input.js +2 -1
  126. package/dist/composed/rich-chat-input.js.map +1 -1
  127. package/dist/composed/rich-text-editor.js +2 -1
  128. package/dist/composed/rich-text-editor.js.map +1 -1
  129. package/dist/composed/schedule-view.js.map +1 -1
  130. package/dist/composed/simple-tooltip.js.map +1 -1
  131. package/dist/composed/status-badge.js.map +1 -1
  132. package/dist/hooks/use-color-mode.js +2 -1
  133. package/dist/hooks/use-color-mode.js.map +1 -1
  134. package/dist/hooks/use-mobile.js.map +1 -1
  135. package/dist/hooks/use-touch-device.js.map +1 -1
  136. package/dist/hooks/use-viewport-height.js.map +1 -1
  137. package/dist/motion/index.js.map +1 -1
  138. package/dist/motion/motion-provider.d.ts +1 -1
  139. package/dist/motion/motion-provider.d.ts.map +1 -1
  140. package/dist/motion/primitives-index.js.map +1 -1
  141. package/dist/primitives/_internal/react-dismissable-layer.d.ts.map +1 -1
  142. package/dist/primitives/_internal/react-focus-scope.d.ts.map +1 -1
  143. package/dist/shell/app-command-palette.js.map +1 -1
  144. package/dist/shell/bottom-navbar.js.map +1 -1
  145. package/dist/shell/command-registry.d.ts +1 -1
  146. package/dist/shell/command-registry.d.ts.map +1 -1
  147. package/dist/shell/command-registry.js.map +1 -1
  148. package/dist/shell/notification-center.js.map +1 -1
  149. package/dist/shell/notification-preferences.js.map +1 -1
  150. package/dist/shell/sidebar.js.map +1 -1
  151. package/dist/shell/top-bar.js.map +1 -1
  152. package/dist/ui/accordion.js.map +1 -1
  153. package/dist/ui/alert-dialog.js.map +1 -1
  154. package/dist/ui/alert.js.map +1 -1
  155. package/dist/ui/aspect-ratio.js.map +1 -1
  156. package/dist/ui/autocomplete.js.map +1 -1
  157. package/dist/ui/avatar.d.ts +1 -1
  158. package/dist/ui/avatar.js.map +1 -1
  159. package/dist/ui/badge-group.d.ts +1 -1
  160. package/dist/ui/badge-group.d.ts.map +1 -1
  161. package/dist/ui/badge-indicator.d.ts +1 -1
  162. package/dist/ui/badge-indicator.d.ts.map +1 -1
  163. package/dist/ui/badge-indicator.js.map +1 -1
  164. package/dist/ui/banner.js.map +1 -1
  165. package/dist/ui/breadcrumb.d.ts +2 -2
  166. package/dist/ui/breadcrumb.js.map +1 -1
  167. package/dist/ui/button-group.js.map +1 -1
  168. package/dist/ui/button-processing.d.ts +2 -1
  169. package/dist/ui/button-processing.d.ts.map +1 -1
  170. package/dist/ui/button-processing.js.map +1 -1
  171. package/dist/ui/button.js.map +1 -1
  172. package/dist/ui/card.js +1 -1
  173. package/dist/ui/card.js.map +1 -1
  174. package/dist/ui/charts/_internal/axes.d.ts +2 -1
  175. package/dist/ui/charts/_internal/axes.d.ts.map +1 -1
  176. package/dist/ui/charts/_internal/grid-lines.d.ts +2 -1
  177. package/dist/ui/charts/_internal/grid-lines.d.ts.map +1 -1
  178. package/dist/ui/charts/_internal/legend.d.ts +2 -1
  179. package/dist/ui/charts/_internal/legend.d.ts.map +1 -1
  180. package/dist/ui/charts/_internal/tooltip.d.ts +1 -1
  181. package/dist/ui/charts/_internal/tooltip.d.ts.map +1 -1
  182. package/dist/ui/charts/area-chart.js.map +1 -1
  183. package/dist/ui/charts/bar-chart.js.map +1 -1
  184. package/dist/ui/charts/gauge-chart.js.map +1 -1
  185. package/dist/ui/charts/line-chart.js.map +1 -1
  186. package/dist/ui/charts/pie-chart.js.map +1 -1
  187. package/dist/ui/charts/radar-chart.js.map +1 -1
  188. package/dist/ui/charts/sparkline.js +2 -2
  189. package/dist/ui/charts/sparkline.js.map +1 -1
  190. package/dist/ui/chat/date-separator.d.ts +2 -1
  191. package/dist/ui/chat/date-separator.d.ts.map +1 -1
  192. package/dist/ui/chat/message.d.ts +8 -8
  193. package/dist/ui/chat/message.d.ts.map +1 -1
  194. package/dist/ui/chat/typing-indicator.d.ts +2 -1
  195. package/dist/ui/chat/typing-indicator.d.ts.map +1 -1
  196. package/dist/ui/chat/unread-separator.d.ts +2 -1
  197. package/dist/ui/chat/unread-separator.d.ts.map +1 -1
  198. package/dist/ui/checkbox.js.map +1 -1
  199. package/dist/ui/code.js.map +1 -1
  200. package/dist/ui/collapsible.js.map +1 -1
  201. package/dist/ui/color-input.js.map +1 -1
  202. package/dist/ui/color-swatch.js.map +1 -1
  203. package/dist/ui/combobox.js +15 -6
  204. package/dist/ui/combobox.js.map +1 -1
  205. package/dist/ui/container.js.map +1 -1
  206. package/dist/ui/context-menu.d.ts +2 -2
  207. package/dist/ui/context-menu.js.map +1 -1
  208. package/dist/ui/data-table-body.d.ts +1 -1
  209. package/dist/ui/data-table-body.d.ts.map +1 -1
  210. package/dist/ui/data-table-body.js +1 -1
  211. package/dist/ui/data-table-body.js.map +1 -1
  212. package/dist/ui/data-table-bulk-actions.d.ts +2 -1
  213. package/dist/ui/data-table-bulk-actions.d.ts.map +1 -1
  214. package/dist/ui/data-table-bulk-actions.js.map +1 -1
  215. package/dist/ui/data-table-card.d.ts +1 -1
  216. package/dist/ui/data-table-card.d.ts.map +1 -1
  217. package/dist/ui/data-table-card.js.map +1 -1
  218. package/dist/ui/data-table-context.d.ts +1 -1
  219. package/dist/ui/data-table-context.d.ts.map +1 -1
  220. package/dist/ui/data-table-context.js.map +1 -1
  221. package/dist/ui/data-table-header.d.ts +2 -1
  222. package/dist/ui/data-table-header.d.ts.map +1 -1
  223. package/dist/ui/data-table-header.js.map +1 -1
  224. package/dist/ui/data-table-pagination.d.ts +2 -1
  225. package/dist/ui/data-table-pagination.d.ts.map +1 -1
  226. package/dist/ui/data-table-pagination.js.map +1 -1
  227. package/dist/ui/data-table-toolbar.d.ts +1 -1
  228. package/dist/ui/data-table-toolbar.d.ts.map +1 -1
  229. package/dist/ui/data-table-toolbar.js.map +1 -1
  230. package/dist/ui/data-table.d.ts +1 -1
  231. package/dist/ui/data-table.d.ts.map +1 -1
  232. package/dist/ui/data-table.js.map +1 -1
  233. package/dist/ui/devalok-grain.d.ts +2 -1
  234. package/dist/ui/devalok-grain.d.ts.map +1 -1
  235. package/dist/ui/devalok-grain.js.map +1 -1
  236. package/dist/ui/dialog.js.map +1 -1
  237. package/dist/ui/dot.js +1 -1
  238. package/dist/ui/dot.js.map +1 -1
  239. package/dist/ui/dropdown-menu.d.ts +1 -1
  240. package/dist/ui/dropdown-menu.js.map +1 -1
  241. package/dist/ui/file-upload.js.map +1 -1
  242. package/dist/ui/form.js.map +1 -1
  243. package/dist/ui/hover-card.js.map +1 -1
  244. package/dist/ui/icon-button.js.map +1 -1
  245. package/dist/ui/icon-context.d.ts +1 -1
  246. package/dist/ui/icon-context.d.ts.map +1 -1
  247. package/dist/ui/icon-context.js.map +1 -1
  248. package/dist/ui/icon-group.js.map +1 -1
  249. package/dist/ui/icon.js.map +1 -1
  250. package/dist/ui/index.js.map +1 -1
  251. package/dist/ui/input-otp.js.map +1 -1
  252. package/dist/ui/input.js.map +1 -1
  253. package/dist/ui/label.js.map +1 -1
  254. package/dist/ui/lib/bottom-sheet.d.ts +1 -1
  255. package/dist/ui/lib/bottom-sheet.d.ts.map +1 -1
  256. package/dist/ui/lib/date-utils.js.map +1 -1
  257. package/dist/ui/lib/link-context.d.ts +1 -1
  258. package/dist/ui/lib/link-context.d.ts.map +1 -1
  259. package/dist/ui/lib/motion.js.map +1 -1
  260. package/dist/ui/lib/utils.js.map +1 -1
  261. package/dist/ui/link.js.map +1 -1
  262. package/dist/ui/menubar.d.ts +1 -1
  263. package/dist/ui/menubar.js.map +1 -1
  264. package/dist/ui/navigation-menu.js +14 -9
  265. package/dist/ui/navigation-menu.js.map +1 -1
  266. package/dist/ui/number-input.js.map +1 -1
  267. package/dist/ui/pagination.js.map +1 -1
  268. package/dist/ui/progress-ring.js.map +1 -1
  269. package/dist/ui/progress.js.map +1 -1
  270. package/dist/ui/radio.js.map +1 -1
  271. package/dist/ui/search-input.js.map +1 -1
  272. package/dist/ui/segmented-control.js.map +1 -1
  273. package/dist/ui/select.js.map +1 -1
  274. package/dist/ui/separator.js.map +1 -1
  275. package/dist/ui/sheet.js.map +1 -1
  276. package/dist/ui/sidebar.js +1 -1
  277. package/dist/ui/sidebar.js.map +1 -1
  278. package/dist/ui/skeleton.js.map +1 -1
  279. package/dist/ui/slider.js.map +1 -1
  280. package/dist/ui/spinner.js.map +1 -1
  281. package/dist/ui/split-button.js.map +1 -1
  282. package/dist/ui/stack.js.map +1 -1
  283. package/dist/ui/stat-card.js.map +1 -1
  284. package/dist/ui/stat-flash.d.ts +2 -1
  285. package/dist/ui/stat-flash.d.ts.map +1 -1
  286. package/dist/ui/stat-flash.js +1 -1
  287. package/dist/ui/stat-flash.js.map +1 -1
  288. package/dist/ui/stepper.d.ts +1 -1
  289. package/dist/ui/stepper.d.ts.map +1 -1
  290. package/dist/ui/stepper.js +1 -1
  291. package/dist/ui/stepper.js.map +1 -1
  292. package/dist/ui/surface.js.map +1 -1
  293. package/dist/ui/switch.d.ts +1 -1
  294. package/dist/ui/switch.js.map +1 -1
  295. package/dist/ui/table-row-link.js.map +1 -1
  296. package/dist/ui/table.js.map +1 -1
  297. package/dist/ui/tabs.js.map +1 -1
  298. package/dist/ui/text.js.map +1 -1
  299. package/dist/ui/textarea.js.map +1 -1
  300. package/dist/ui/toast-registry.js.map +1 -1
  301. package/dist/ui/toast.d.ts +4 -4
  302. package/dist/ui/toast.d.ts.map +1 -1
  303. package/dist/ui/toast.js +5 -3
  304. package/dist/ui/toast.js.map +1 -1
  305. package/dist/ui/toaster.js.map +1 -1
  306. package/dist/ui/toggle-group.js.map +1 -1
  307. package/dist/ui/toggle.js.map +1 -1
  308. package/dist/ui/tooltip.js.map +1 -1
  309. package/dist/ui/truncated-text.js.map +1 -1
  310. package/dist/ui/visually-hidden.js.map +1 -1
  311. package/llms.txt +1 -1
  312. package/mcp-manifest.json +1 -1
  313. package/package.json +21 -21
  314. package/skill/SKILL.md +1 -1
  315. package/skill/references/components.md +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"devalok-grain.js","names":[],"sources":["../../src/ui/devalok-grain.tsx"],"sourcesContent":["'use client'\n\nimport { motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\n\nconst NOISE_SVG = `url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.45' numOctaves='3' seed='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)'/%3E%3C/svg%3E\")`\n\nexport type GrainIntensity = 'subtle' | 'medium' | 'heavy'\n\nconst NOISE_OPACITY = {\n subtle: { solid: 0.20, soft: 0.15 },\n medium: { solid: 0.28, soft: 0.22 },\n heavy: { solid: 0.45, soft: 0.35 },\n} as const\n\nconst GRADIENT = {\n subtle: { ld: 0.06, ll: 0.08, dk: 0.08 },\n medium: { ld: 0.10, ll: 0.12, dk: 0.12 },\n heavy: { ld: 0.16, ll: 0.18, dk: 0.18 },\n} as const\n\nexport interface DevalokGrainProps {\n /** Grain intensity level. Default: 'subtle' */\n intensity?: GrainIntensity\n /**\n * Surface type hint — affects noise opacity.\n * 'solid' for filled backgrounds (higher opacity).\n * 'soft' for tinted/muted backgrounds (lower opacity).\n * Default: 'solid'\n */\n surface?: 'solid' | 'soft'\n /**\n * Add an inner highlight (top-lit emboss) for a premium 3D feel.\n * Adds an inset shadow: light highlight on top edge, dark shadow on bottom edge.\n * Works best on solid variant buttons. Default: false\n */\n sheen?: boolean\n /**\n * Animate the grain entrance (fade in on mount). Default: false\n * When true, the grain fades in over 600ms with a slight scale for a natural reveal.\n */\n animated?: boolean\n /**\n * Increase grain visibility on parent hover. Default: false\n * When the parent element is hovered, the noise opacity increases by ~40%\n * and the gradient deepens slightly, creating a tactile \"pressing into paper\" feel.\n * Requires the parent to have a `group` class for `group-hover:` to work.\n */\n hoverIntensify?: boolean\n /**\n * Tint color for the directional gradient. Accepts any CSS color value.\n * The gradient goes from a darker shade of this color to a lighter shade.\n * Default: neutral black-to-white wash.\n *\n * @example\n * // Warm pink tint (Devalok brand)\n * <DevalokGrain tint=\"oklch(0.55 0.19 360)\" />\n *\n * // Cool blue tint\n * <DevalokGrain tint=\"oklch(0.55 0.12 240)\" />\n *\n * // Use a CSS variable\n * <DevalokGrain tint=\"var(--color-accent-9)\" />\n */\n tint?: string\n}\n\n/**\n * DevalokGrain — Brand texture that adds a subtle noise grain + directional gradient\n * to any surface. The Devalok signature: warm, tactile, paper-like.\n *\n * Drop as a child of any element that has `relative overflow-hidden isolate`.\n * The grain layers are absolute-positioned at z-[1] and use `rounded-[inherit]`\n * to match the parent's border-radius.\n *\n * @example\n * // Inside a Button (Button already has relative/overflow-hidden/isolate):\n * <Button>\n * <DevalokGrain />\n * Save changes\n * </Button>\n *\n * @example\n * // Inside a Card:\n * <Card className=\"relative overflow-hidden isolate\">\n * <DevalokGrain surface=\"soft\" />\n * Card content\n * </Card>\n *\n * @example\n * // Heavier grain for a hero section:\n * <div className=\"relative overflow-hidden isolate rounded-surface bg-accent-9 p-8\">\n * <DevalokGrain intensity=\"heavy\" />\n * <h1 className=\"relative z-[2]\">Hero</h1>\n * </div>\n */\nexport function DevalokGrain({\n intensity = 'subtle',\n surface = 'solid',\n sheen = false,\n tint,\n animated = false,\n hoverIntensify = false,\n}: DevalokGrainProps) {\n const prefersReduced = useReducedMotion()\n const noise = NOISE_OPACITY[intensity][surface]\n const hoverNoise = Math.min(noise * 1.4, 0.6) // 40% more on hover, capped\n const g = GRADIENT[intensity]\n\n // Gradient adds directional dimensionality — a subtle diagonal wash that makes the\n // surface feel lit from the top-left. When tinted, the gradient carries the tint color.\n // When untinted, a neutral black→white wash still adds depth without coloring the surface.\n const lightGradient = tint\n ? `linear-gradient(135deg, color-mix(in oklch, ${tint} ${Math.round(g.ld * 100)}%, transparent), color-mix(in oklch, white ${Math.round(g.ll * 100)}%, transparent))`\n : `linear-gradient(135deg, oklch(0 0 0 / ${g.ld}), oklch(1 0 0 / ${g.ll}))`\n const darkGradient = tint\n ? `linear-gradient(135deg, transparent, color-mix(in oklch, ${tint} ${Math.round(g.dk * 100)}%, transparent))`\n : `linear-gradient(135deg, transparent, oklch(0 0 0 / ${g.dk}))`\n\n const shouldAnimate = animated && !prefersReduced\n\n const Wrapper = shouldAnimate ? motion.span : 'span'\n const wrapperProps = shouldAnimate\n ? {\n initial: { opacity: 0 },\n animate: { opacity: 1 },\n transition: { duration: 0.6, ease: [0.2, 0, 0.38, 0.9] },\n }\n : {}\n\n return (\n <Wrapper\n data-grain=\"\"\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-0 z-[1] rounded-[inherit] isolate overflow-hidden\"\n {...(wrapperProps as any)}\n >\n {/* Gradient — only when tinted (neutral gradient looks like a dark smudge on light surfaces) */}\n {lightGradient && (\n <span\n className={`absolute inset-0 dark:hidden transition-opacity duration-300 ${\n hoverIntensify ? 'group-hover:opacity-100 opacity-80' : ''\n }`}\n style={{ background: lightGradient }}\n />\n )}\n {darkGradient && (\n <span\n className={`absolute inset-0 hidden dark:block transition-opacity duration-300 ${\n hoverIntensify ? 'group-hover:opacity-100 opacity-80' : ''\n }`}\n style={{ background: darkGradient }}\n />\n )}\n {/* Noise texture */}\n <span\n className={`absolute inset-0 transition-opacity duration-300 ${\n hoverIntensify ? 'group-hover:opacity-[var(--grain-hover-opacity)]' : ''\n }`}\n style={{\n backgroundImage: NOISE_SVG,\n backgroundSize: '100px 100px',\n filter: 'contrast(250%) brightness(105%)',\n opacity: noise,\n '--grain-hover-opacity': hoverIntensify ? hoverNoise : undefined,\n } as React.CSSProperties}\n />\n {/* Sheen — inner highlight */}\n {sheen && (\n <span className=\"absolute inset-0 shadow-raised-inner\" />\n )}\n </Wrapper>\n )\n}\n\nDevalokGrain.displayName = 'DevalokGrain'\n"],"mappings":";;;;;AAKA,IAAM,IAAY,+SAIZ,IAAgB;CACpB,QAAQ;EAAE,OAAO;EAAM,MAAM;EAAM;CACnC,QAAQ;EAAE,OAAO;EAAM,MAAM;EAAM;CACnC,OAAQ;EAAE,OAAO;EAAM,MAAM;EAAM;CACpC,EAEK,IAAW;CACf,QAAQ;EAAE,IAAI;EAAM,IAAI;EAAM,IAAI;EAAM;CACxC,QAAQ;EAAE,IAAI;EAAM,IAAI;EAAM,IAAI;EAAM;CACxC,OAAQ;EAAE,IAAI;EAAM,IAAI;EAAM,IAAI;EAAM;CACzC;AA6ED,SAAgB,EAAa,EAC3B,eAAY,UACZ,aAAU,SACV,WAAQ,IACR,SACA,cAAW,IACX,oBAAiB,MACG;CACpB,IAAM,IAAiB,GAAkB,EACnC,IAAQ,EAAc,GAAW,IACjC,IAAa,KAAK,IAAI,IAAQ,KAAK,GAAI,EACvC,IAAI,EAAS,IAKb,IAAgB,IAClB,+CAA+C,EAAK,GAAG,KAAK,MAAM,EAAE,KAAK,IAAI,CAAC,6CAA6C,KAAK,MAAM,EAAE,KAAK,IAAI,CAAC,oBAClJ,yCAAyC,EAAE,GAAG,mBAAmB,EAAE,GAAG,KACpE,IAAe,IACjB,4DAA4D,EAAK,GAAG,KAAK,MAAM,EAAE,KAAK,IAAI,CAAC,oBAC3F,sDAAsD,EAAE,GAAG,KAEzD,IAAgB,KAAY,CAAC;AAWnC,QACE,kBAVc,IAAgB,EAAO,OAAO,QAU5C;EACE,cAAW;EACX,eAAY;EACZ,WAAU;EACV,GAbiB,IACjB;GACE,SAAS,EAAE,SAAS,GAAG;GACvB,SAAS,EAAE,SAAS,GAAG;GACvB,YAAY;IAAE,UAAU;IAAK,MAAM;KAAC;KAAK;KAAG;KAAM;KAAI;IAAE;GACzD,GACD,EAAE;YAGJ;GAOG,KACC,kBAAC,QAAD;IACE,WAAW,gEACT,IAAiB,uCAAuC;IAE1D,OAAO,EAAE,YAAY,GAAe;IACpC,CAAA;GAEH,KACC,kBAAC,QAAD;IACE,WAAW,sEACT,IAAiB,uCAAuC;IAE1D,OAAO,EAAE,YAAY,GAAc;IACnC,CAAA;GAGJ,kBAAC,QAAD;IACE,WAAW,oDACT,IAAiB,qDAAqD;IAExE,OAAO;KACL,iBAAiB;KACjB,gBAAgB;KAChB,QAAQ;KACR,SAAS;KACT,yBAAyB,IAAiB,IAAa,KAAA;KACxD;IACD,CAAA;GAED,KACC,kBAAC,QAAD,EAAM,WAAU,wCAAyC,CAAA;GAEnD;;;AAId,EAAa,cAAc"}
1
+ {"version":3,"file":"devalok-grain.js","names":[],"sources":["../../src/ui/devalok-grain.tsx"],"sourcesContent":["'use client'\n\nimport { motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\n\nconst NOISE_SVG = `url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.45' numOctaves='3' seed='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)'/%3E%3C/svg%3E\")`\n\nexport type GrainIntensity = 'subtle' | 'medium' | 'heavy'\n\nconst NOISE_OPACITY = {\n subtle: { solid: 0.20, soft: 0.15 },\n medium: { solid: 0.28, soft: 0.22 },\n heavy: { solid: 0.45, soft: 0.35 },\n} as const\n\nconst GRADIENT = {\n subtle: { ld: 0.06, ll: 0.08, dk: 0.08 },\n medium: { ld: 0.10, ll: 0.12, dk: 0.12 },\n heavy: { ld: 0.16, ll: 0.18, dk: 0.18 },\n} as const\n\nexport interface DevalokGrainProps {\n /** Grain intensity level. Default: 'subtle' */\n intensity?: GrainIntensity\n /**\n * Surface type hint — affects noise opacity.\n * 'solid' for filled backgrounds (higher opacity).\n * 'soft' for tinted/muted backgrounds (lower opacity).\n * Default: 'solid'\n */\n surface?: 'solid' | 'soft'\n /**\n * Add an inner highlight (top-lit emboss) for a premium 3D feel.\n * Adds an inset shadow: light highlight on top edge, dark shadow on bottom edge.\n * Works best on solid variant buttons. Default: false\n */\n sheen?: boolean\n /**\n * Animate the grain entrance (fade in on mount). Default: false\n * When true, the grain fades in over 600ms with a slight scale for a natural reveal.\n */\n animated?: boolean\n /**\n * Increase grain visibility on parent hover. Default: false\n * When the parent element is hovered, the noise opacity increases by ~40%\n * and the gradient deepens slightly, creating a tactile \"pressing into paper\" feel.\n * Requires the parent to have a `group` class for `group-hover:` to work.\n */\n hoverIntensify?: boolean\n /**\n * Tint color for the directional gradient. Accepts any CSS color value.\n * The gradient goes from a darker shade of this color to a lighter shade.\n * Default: neutral black-to-white wash.\n *\n * @example\n * // Warm pink tint (Devalok brand)\n * <DevalokGrain tint=\"oklch(0.55 0.19 360)\" />\n *\n * // Cool blue tint\n * <DevalokGrain tint=\"oklch(0.55 0.12 240)\" />\n *\n * // Use a CSS variable\n * <DevalokGrain tint=\"var(--color-accent-9)\" />\n */\n tint?: string\n}\n\n/**\n * DevalokGrain — Brand texture that adds a subtle noise grain + directional gradient\n * to any surface. The Devalok signature: warm, tactile, paper-like.\n *\n * Drop as a child of any element that has `relative overflow-hidden isolate`.\n * The grain layers are absolute-positioned at z-[1] and use `rounded-[inherit]`\n * to match the parent's border-radius.\n *\n * @example\n * // Inside a Button (Button already has relative/overflow-hidden/isolate):\n * <Button>\n * <DevalokGrain />\n * Save changes\n * </Button>\n *\n * @example\n * // Inside a Card:\n * <Card className=\"relative overflow-hidden isolate\">\n * <DevalokGrain surface=\"soft\" />\n * Card content\n * </Card>\n *\n * @example\n * // Heavier grain for a hero section:\n * <div className=\"relative overflow-hidden isolate rounded-surface bg-accent-9 p-8\">\n * <DevalokGrain intensity=\"heavy\" />\n * <h1 className=\"relative z-[2]\">Hero</h1>\n * </div>\n */\nexport function DevalokGrain({\n intensity = 'subtle',\n surface = 'solid',\n sheen = false,\n tint,\n animated = false,\n hoverIntensify = false,\n}: DevalokGrainProps) {\n const prefersReduced = useReducedMotion()\n const noise = NOISE_OPACITY[intensity][surface]\n const hoverNoise = Math.min(noise * 1.4, 0.6) // 40% more on hover, capped\n const g = GRADIENT[intensity]\n\n // Gradient adds directional dimensionality — a subtle diagonal wash that makes the\n // surface feel lit from the top-left. When tinted, the gradient carries the tint color.\n // When untinted, a neutral black→white wash still adds depth without coloring the surface.\n const lightGradient = tint\n ? `linear-gradient(135deg, color-mix(in oklch, ${tint} ${Math.round(g.ld * 100)}%, transparent), color-mix(in oklch, white ${Math.round(g.ll * 100)}%, transparent))`\n : `linear-gradient(135deg, oklch(0 0 0 / ${g.ld}), oklch(1 0 0 / ${g.ll}))`\n const darkGradient = tint\n ? `linear-gradient(135deg, transparent, color-mix(in oklch, ${tint} ${Math.round(g.dk * 100)}%, transparent))`\n : `linear-gradient(135deg, transparent, oklch(0 0 0 / ${g.dk}))`\n\n const shouldAnimate = animated && !prefersReduced\n\n const Wrapper = shouldAnimate ? motion.span : 'span'\n const wrapperProps = shouldAnimate\n ? {\n initial: { opacity: 0 },\n animate: { opacity: 1 },\n transition: { duration: 0.6, ease: [0.2, 0, 0.38, 0.9] },\n }\n : {}\n\n return (\n <Wrapper\n data-grain=\"\"\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-0 z-[1] rounded-[inherit] isolate overflow-hidden\"\n {...(wrapperProps as any)}\n >\n {/* Gradient — only when tinted (neutral gradient looks like a dark smudge on light surfaces) */}\n {lightGradient && (\n <span\n className={`absolute inset-0 dark:hidden transition-opacity duration-300 ${\n hoverIntensify ? 'group-hover:opacity-100 opacity-80' : ''\n }`}\n style={{ background: lightGradient }}\n />\n )}\n {darkGradient && (\n <span\n className={`absolute inset-0 hidden dark:block transition-opacity duration-300 ${\n hoverIntensify ? 'group-hover:opacity-100 opacity-80' : ''\n }`}\n style={{ background: darkGradient }}\n />\n )}\n {/* Noise texture */}\n <span\n className={`absolute inset-0 transition-opacity duration-300 ${\n hoverIntensify ? 'group-hover:opacity-[var(--grain-hover-opacity)]' : ''\n }`}\n style={{\n backgroundImage: NOISE_SVG,\n backgroundSize: '100px 100px',\n filter: 'contrast(250%) brightness(105%)',\n opacity: noise,\n '--grain-hover-opacity': hoverIntensify ? hoverNoise : undefined,\n } as React.CSSProperties}\n />\n {/* Sheen — inner highlight */}\n {sheen && (\n <span className=\"absolute inset-0 shadow-raised-inner\" />\n )}\n </Wrapper>\n )\n}\n\nDevalokGrain.displayName = 'DevalokGrain'\n"],"mappings":";;;;;AAKA,IAAM,IAAY,+SAIZ,IAAgB;CACpB,QAAQ;EAAE,OAAO;EAAM,MAAM;CAAK;CAClC,QAAQ;EAAE,OAAO;EAAM,MAAM;CAAK;CAClC,OAAQ;EAAE,OAAO;EAAM,MAAM;CAAK;AACpC,GAEM,IAAW;CACf,QAAQ;EAAE,IAAI;EAAM,IAAI;EAAM,IAAI;CAAK;CACvC,QAAQ;EAAE,IAAI;EAAM,IAAI;EAAM,IAAI;CAAK;CACvC,OAAQ;EAAE,IAAI;EAAM,IAAI;EAAM,IAAI;CAAK;AACzC;AA6EA,SAAgB,EAAa,EAC3B,eAAY,UACZ,aAAU,SACV,WAAQ,IACR,SACA,cAAW,IACX,oBAAiB,MACG;CACpB,IAAM,IAAiB,EAAiB,GAClC,IAAQ,EAAc,EAAU,CAAC,IACjC,IAAa,KAAK,IAAI,IAAQ,KAAK,EAAG,GACtC,IAAI,EAAS,IAKb,IAAgB,IAClB,+CAA+C,EAAK,GAAG,KAAK,MAAM,EAAE,KAAK,GAAG,EAAE,6CAA6C,KAAK,MAAM,EAAE,KAAK,GAAG,EAAE,oBAClJ,yCAAyC,EAAE,GAAG,mBAAmB,EAAE,GAAG,KACpE,IAAe,IACjB,4DAA4D,EAAK,GAAG,KAAK,MAAM,EAAE,KAAK,GAAG,EAAE,oBAC3F,sDAAsD,EAAE,GAAG,KAEzD,IAAgB,KAAY,CAAC;CAWnC,OACE,kBAVc,IAAgB,EAAO,OAAO,QAU5C;EACE,cAAW;EACX,eAAY;EACZ,WAAU;EACV,GAbiB,IACjB;GACE,SAAS,EAAE,SAAS,EAAE;GACtB,SAAS,EAAE,SAAS,EAAE;GACtB,YAAY;IAAE,UAAU;IAAK,MAAM;KAAC;KAAK;KAAG;KAAM;IAAG;GAAE;EACzD,IACA,CAAC;YAGH;GAOG,KACC,kBAAC,QAAD;IACE,WAAW,gEACT,IAAiB,uCAAuC;IAE1D,OAAO,EAAE,YAAY,EAAc;GACpC,CAAA;GAEF,KACC,kBAAC,QAAD;IACE,WAAW,sEACT,IAAiB,uCAAuC;IAE1D,OAAO,EAAE,YAAY,EAAa;GACnC,CAAA;GAGH,kBAAC,QAAD;IACE,WAAW,oDACT,IAAiB,qDAAqD;IAExE,OAAO;KACL,iBAAiB;KACjB,gBAAgB;KAChB,QAAQ;KACR,SAAS;KACT,yBAAyB,IAAiB,IAAa,KAAA;IACzD;GACD,CAAA;GAEA,KACC,kBAAC,QAAD,EAAM,WAAU,uCAAwC,CAAA;EAEnD;;AAEb;AAEA,EAAa,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"dialog.js","names":[],"sources":["../../src/ui/dialog.tsx"],"sourcesContent":["'use client'\n\nimport * as DialogPrimitive from '@primitives/react-dialog'\nimport { IconX as CloseIcon } from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { useIsMobile } from '../hooks/use-mobile'\nimport { Icon } from './icon'\nimport { springs, tweens } from './lib/motion'\nimport { useControllableOpen } from './lib/use-controllable-open'\nimport { cn } from './lib/utils'\n\n// ── Internal context to thread `open` state to animated children ──\n\ntype DialogContextValue = { open: boolean }\nconst DialogContext = React.createContext<DialogContextValue>({ open: false })\nconst useDialogOpen = () => React.useContext(DialogContext)\n\n/**\n * Dialog compound component — accessible modal overlay with focus trap and Escape dismissal.\n *\n * **Parts (in composition order):**\n * - `Dialog` — manages open/closed state (this root)\n * - `DialogTrigger` — element that opens the dialog (use `asChild` to render your own button)\n * - `DialogContent` — the modal panel (auto-includes portal, overlay, and close button)\n * - `DialogHeader` — optional layout wrapper for title + description\n * - `DialogTitle` — required for accessibility (sets the dialog's ARIA label)\n * - `DialogDescription` — optional subtitle text\n * - `DialogFooter` — optional layout wrapper for action buttons\n * - `DialogClose` — manual close trigger (a close button is already built into DialogContent)\n * - `DialogContentRaw` — use instead of DialogContent when you need full portal/overlay control\n * - `DialogPortal` — low-level portal wrapper (exported for custom layout; used internally by DialogContent)\n * - `DialogOverlay` — the backdrop element (exported for custom overlay styling or positioning)\n *\n * @compound\n * @example\n * // Confirmation dialog:\n * <Dialog>\n * <DialogTrigger asChild>\n * <Button variant=\"solid\" color=\"error\">Delete project</Button>\n * </DialogTrigger>\n * <DialogContent>\n * <DialogHeader>\n * <DialogTitle>Are you absolutely sure?</DialogTitle>\n * <DialogDescription>\n * This action cannot be undone. This will permanently delete your project.\n * </DialogDescription>\n * </DialogHeader>\n * <DialogFooter>\n * <DialogClose asChild><Button variant=\"outline\">Cancel</Button></DialogClose>\n * <Button variant=\"solid\" color=\"error\" onClick={handleDelete}>Delete</Button>\n * </DialogFooter>\n * </DialogContent>\n * </Dialog>\n *\n * @example\n * // Controlled open state (no trigger in markup):\n * const [open, setOpen] = useState(false)\n * <Dialog open={open} onOpenChange={setOpen}>\n * <DialogContent>\n * <DialogTitle>Edit profile</DialogTitle>\n * // form fields\n * </DialogContent>\n * </Dialog>\n */\nconst Dialog: React.FC<React.ComponentPropsWithoutRef<typeof DialogPrimitive.Root>> = ({\n open: openProp,\n defaultOpen,\n onOpenChange,\n ...props\n}) => {\n const { open, setOpen } = useControllableOpen({ open: openProp, defaultOpen, onOpenChange })\n\n const contextValue = React.useMemo(() => ({ open }), [open])\n\n return (\n <DialogContext.Provider value={contextValue}>\n <DialogPrimitive.Root open={open} onOpenChange={setOpen} {...props} />\n </DialogContext.Provider>\n )\n}\nDialog.displayName = 'Dialog'\n\nconst DialogTrigger = DialogPrimitive.Trigger\n\nconst DialogPortal = DialogPrimitive.Portal\n\nconst DialogClose = DialogPrimitive.Close\n\nconst DialogOverlay = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n forceMount\n className={cn(\n 'fixed inset-0 z-overlay bg-overlay',\n className,\n )}\n {...props}\n />\n))\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName\n\ninterface DialogContentProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {\n /** When true (default), Dialog fills the screen on mobile (<768px). Set false to always use centered modal. */\n responsive?: boolean\n}\n\nconst DialogContent = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Content>,\n DialogContentProps\n>(({ className, children, responsive, ...props }, ref) => {\n const { open } = useDialogOpen()\n const isMobileRaw = useIsMobile()\n const isMobile = responsive !== false && isMobileRaw\n\n return (\n <AnimatePresence>\n {open && (\n <DialogPortal forceMount>\n <DialogOverlay asChild>\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n />\n </DialogOverlay>\n <DialogPrimitive.Content\n ref={ref}\n forceMount\n asChild\n {...props}\n >\n <motion.div\n initial={isMobile ? { y: '100%' } : { opacity: 0, scale: 0.95, x: '-50%', y: '-50%' }}\n animate={isMobile ? { y: 0 } : { opacity: 1, scale: 1, x: '-50%', y: '-50%' }}\n exit={isMobile ? { y: '100%' } : { opacity: 0, scale: 0.95, x: '-50%', y: '-50%' }}\n transition={isMobile\n ? springs.smooth\n : { ...springs.smooth, opacity: tweens.fade }\n }\n className={cn(\n 'fixed z-modal grid w-full gap-ds-05 bg-surface-overlay p-ds-06',\n responsive !== false\n ? 'inset-0 md:inset-auto md:left-[50%] md:top-[50%] md:max-w-lg md:rounded-overlay-lg md:shadow-overlay'\n : 'left-[50%] top-[50%] max-w-lg rounded-overlay-lg shadow-overlay',\n className,\n )}\n >\n {children}\n <DialogPrimitive.Close title=\"Close\" className=\"absolute right-ds-05 top-ds-05 min-h-ds-xs min-w-ds-xs flex items-center justify-center rounded-control-inner text-surface-fg-subtle transition-colors duration-fast-01 ease-productive-standard hover:text-surface-fg-muted hover:bg-surface-raised-hover active:scale-90 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 disabled:pointer-events-none\">\n <Icon icon={CloseIcon} size=\"lg\" />\n <span className=\"sr-only\">Close</span>\n </DialogPrimitive.Close>\n </motion.div>\n </DialogPrimitive.Content>\n </DialogPortal>\n )}\n </AnimatePresence>\n )\n})\nDialogContent.displayName = DialogPrimitive.Content.displayName\n\n/**\n * DialogContentRaw -- a minimal forwardRef wrapper around the primitive Content.\n * Unlike DialogContent, it does NOT include Portal, Overlay, or CloseButton.\n * Use this when you need full control over portal/overlay/close behaviour\n * (e.g. CommandPalette in shared/).\n */\nconst DialogContentRaw = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Content\n ref={ref}\n className={cn(className)}\n {...props}\n />\n))\nDialogContentRaw.displayName = 'DialogContentRaw'\n\nconst DialogHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n 'flex flex-col space-y-ds-02b text-center sm:text-left',\n className,\n )}\n {...props}\n />\n ),\n)\nDialogHeader.displayName = 'DialogHeader'\n\nconst DialogFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n 'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-ds-03',\n className,\n )}\n {...props}\n />\n ),\n)\nDialogFooter.displayName = 'DialogFooter'\n\nconst DialogTitle = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Title>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn(\n 'text-ds-lg font-semibold',\n className,\n )}\n {...props}\n />\n))\nDialogTitle.displayName = DialogPrimitive.Title.displayName\n\nconst DialogDescription = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n className={cn('text-ds-md text-surface-fg-muted', className)}\n {...props}\n />\n))\nDialogDescription.displayName = DialogPrimitive.Description.displayName\n\nexport type { DialogContentProps }\nexport type DialogTitleProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n\nexport {\n Dialog,\n DialogClose,\n DialogContent,\n DialogContentRaw,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogOverlay,\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"dialog.js","names":[],"sources":["../../src/ui/dialog.tsx"],"sourcesContent":["'use client'\n\nimport * as DialogPrimitive from '@primitives/react-dialog'\nimport { IconX as CloseIcon } from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { useIsMobile } from '../hooks/use-mobile'\nimport { Icon } from './icon'\nimport { springs, tweens } from './lib/motion'\nimport { useControllableOpen } from './lib/use-controllable-open'\nimport { cn } from './lib/utils'\n\n// ── Internal context to thread `open` state to animated children ──\n\ntype DialogContextValue = { open: boolean }\nconst DialogContext = React.createContext<DialogContextValue>({ open: false })\nconst useDialogOpen = () => React.useContext(DialogContext)\n\n/**\n * Dialog compound component — accessible modal overlay with focus trap and Escape dismissal.\n *\n * **Parts (in composition order):**\n * - `Dialog` — manages open/closed state (this root)\n * - `DialogTrigger` — element that opens the dialog (use `asChild` to render your own button)\n * - `DialogContent` — the modal panel (auto-includes portal, overlay, and close button)\n * - `DialogHeader` — optional layout wrapper for title + description\n * - `DialogTitle` — required for accessibility (sets the dialog's ARIA label)\n * - `DialogDescription` — optional subtitle text\n * - `DialogFooter` — optional layout wrapper for action buttons\n * - `DialogClose` — manual close trigger (a close button is already built into DialogContent)\n * - `DialogContentRaw` — use instead of DialogContent when you need full portal/overlay control\n * - `DialogPortal` — low-level portal wrapper (exported for custom layout; used internally by DialogContent)\n * - `DialogOverlay` — the backdrop element (exported for custom overlay styling or positioning)\n *\n * @compound\n * @example\n * // Confirmation dialog:\n * <Dialog>\n * <DialogTrigger asChild>\n * <Button variant=\"solid\" color=\"error\">Delete project</Button>\n * </DialogTrigger>\n * <DialogContent>\n * <DialogHeader>\n * <DialogTitle>Are you absolutely sure?</DialogTitle>\n * <DialogDescription>\n * This action cannot be undone. This will permanently delete your project.\n * </DialogDescription>\n * </DialogHeader>\n * <DialogFooter>\n * <DialogClose asChild><Button variant=\"outline\">Cancel</Button></DialogClose>\n * <Button variant=\"solid\" color=\"error\" onClick={handleDelete}>Delete</Button>\n * </DialogFooter>\n * </DialogContent>\n * </Dialog>\n *\n * @example\n * // Controlled open state (no trigger in markup):\n * const [open, setOpen] = useState(false)\n * <Dialog open={open} onOpenChange={setOpen}>\n * <DialogContent>\n * <DialogTitle>Edit profile</DialogTitle>\n * // form fields\n * </DialogContent>\n * </Dialog>\n */\nconst Dialog: React.FC<React.ComponentPropsWithoutRef<typeof DialogPrimitive.Root>> = ({\n open: openProp,\n defaultOpen,\n onOpenChange,\n ...props\n}) => {\n const { open, setOpen } = useControllableOpen({ open: openProp, defaultOpen, onOpenChange })\n\n const contextValue = React.useMemo(() => ({ open }), [open])\n\n return (\n <DialogContext.Provider value={contextValue}>\n <DialogPrimitive.Root open={open} onOpenChange={setOpen} {...props} />\n </DialogContext.Provider>\n )\n}\nDialog.displayName = 'Dialog'\n\nconst DialogTrigger = DialogPrimitive.Trigger\n\nconst DialogPortal = DialogPrimitive.Portal\n\nconst DialogClose = DialogPrimitive.Close\n\nconst DialogOverlay = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n forceMount\n className={cn(\n 'fixed inset-0 z-overlay bg-overlay',\n className,\n )}\n {...props}\n />\n))\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName\n\ninterface DialogContentProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {\n /** When true (default), Dialog fills the screen on mobile (<768px). Set false to always use centered modal. */\n responsive?: boolean\n}\n\nconst DialogContent = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Content>,\n DialogContentProps\n>(({ className, children, responsive, ...props }, ref) => {\n const { open } = useDialogOpen()\n const isMobileRaw = useIsMobile()\n const isMobile = responsive !== false && isMobileRaw\n\n return (\n <AnimatePresence>\n {open && (\n <DialogPortal forceMount>\n <DialogOverlay asChild>\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n />\n </DialogOverlay>\n <DialogPrimitive.Content\n ref={ref}\n forceMount\n asChild\n {...props}\n >\n <motion.div\n initial={isMobile ? { y: '100%' } : { opacity: 0, scale: 0.95, x: '-50%', y: '-50%' }}\n animate={isMobile ? { y: 0 } : { opacity: 1, scale: 1, x: '-50%', y: '-50%' }}\n exit={isMobile ? { y: '100%' } : { opacity: 0, scale: 0.95, x: '-50%', y: '-50%' }}\n transition={isMobile\n ? springs.smooth\n : { ...springs.smooth, opacity: tweens.fade }\n }\n className={cn(\n 'fixed z-modal grid w-full gap-ds-05 bg-surface-overlay p-ds-06',\n responsive !== false\n ? 'inset-0 md:inset-auto md:left-[50%] md:top-[50%] md:max-w-lg md:rounded-overlay-lg md:shadow-overlay'\n : 'left-[50%] top-[50%] max-w-lg rounded-overlay-lg shadow-overlay',\n className,\n )}\n >\n {children}\n <DialogPrimitive.Close title=\"Close\" className=\"absolute right-ds-05 top-ds-05 min-h-ds-xs min-w-ds-xs flex items-center justify-center rounded-control-inner text-surface-fg-subtle transition-colors duration-fast-01 ease-productive-standard hover:text-surface-fg-muted hover:bg-surface-raised-hover active:scale-90 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 disabled:pointer-events-none\">\n <Icon icon={CloseIcon} size=\"lg\" />\n <span className=\"sr-only\">Close</span>\n </DialogPrimitive.Close>\n </motion.div>\n </DialogPrimitive.Content>\n </DialogPortal>\n )}\n </AnimatePresence>\n )\n})\nDialogContent.displayName = DialogPrimitive.Content.displayName\n\n/**\n * DialogContentRaw -- a minimal forwardRef wrapper around the primitive Content.\n * Unlike DialogContent, it does NOT include Portal, Overlay, or CloseButton.\n * Use this when you need full control over portal/overlay/close behaviour\n * (e.g. CommandPalette in shared/).\n */\nconst DialogContentRaw = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Content\n ref={ref}\n className={cn(className)}\n {...props}\n />\n))\nDialogContentRaw.displayName = 'DialogContentRaw'\n\nconst DialogHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n 'flex flex-col space-y-ds-02b text-center sm:text-left',\n className,\n )}\n {...props}\n />\n ),\n)\nDialogHeader.displayName = 'DialogHeader'\n\nconst DialogFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n 'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-ds-03',\n className,\n )}\n {...props}\n />\n ),\n)\nDialogFooter.displayName = 'DialogFooter'\n\nconst DialogTitle = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Title>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn(\n 'text-ds-lg font-semibold',\n className,\n )}\n {...props}\n />\n))\nDialogTitle.displayName = DialogPrimitive.Title.displayName\n\nconst DialogDescription = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n className={cn('text-ds-md text-surface-fg-muted', className)}\n {...props}\n />\n))\nDialogDescription.displayName = DialogPrimitive.Description.displayName\n\nexport type { DialogContentProps }\nexport type DialogTitleProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n\nexport {\n Dialog,\n DialogClose,\n DialogContent,\n DialogContentRaw,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogOverlay,\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n}\n"],"mappings":"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"}
package/dist/ui/dot.js CHANGED
@@ -61,7 +61,7 @@ var a = {
61
61
  return /* @__PURE__ */ i("span", {
62
62
  ref: S,
63
63
  role: w ? "status" : void 0,
64
- "aria-hidden": w ? void 0 : !0,
64
+ "aria-hidden": !w || void 0,
65
65
  "aria-label": b,
66
66
  className: t("inline-flex items-center gap-ds-02", y),
67
67
  ...x,
@@ -1 +1 @@
1
- {"version":3,"file":"dot.js","names":[],"sources":["../../src/ui/dot.tsx"],"sourcesContent":["'use client'\n\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport * as React from 'react'\n\nimport { cn } from './lib/utils'\n\n/* ---------------------------------------------------------------------------\n * Dot — a small semantic status/indicator dot. The low-level primitive behind\n * StatusBadge, status/presence/health indicators, legend swatches, Avatar status.\n *\n * Intent-coloured, sized (xs–lg), filled / ring / off, optionally pulsing (with\n * a speed), optionally with a contrast border, and an optional label on either\n * side. Decorative by default (aria-hidden); a `label` (or `aria-label`) makes\n * it an announced status.\n *\n * For a dot/count that overlays a child element (notification badge), use\n * `BadgeIndicator` instead — that's the positioned-overlay pattern.\n *\n * API informed by Mantine `Indicator`, Ant `Badge status`, Chakra `Status`.\n * ------------------------------------------------------------------------ */\n\nexport type DotColor = 'accent' | 'success' | 'warning' | 'error' | 'info' | 'neutral' | 'current'\nexport type DotSize = 'xs' | 'sm' | 'md' | 'lg'\nexport type DotVariant = 'filled' | 'ring' | 'off'\n\nconst FILL: Record<DotColor, string> = {\n accent: 'bg-accent-9',\n success: 'bg-success-9',\n warning: 'bg-warning-9',\n error: 'bg-error-9',\n info: 'bg-info-9',\n neutral: 'bg-neutral-8',\n current: 'bg-current',\n}\n\nconst RING: Record<DotColor, string> = {\n accent: 'border-accent-9',\n success: 'border-success-9',\n warning: 'border-warning-9',\n error: 'border-error-9',\n info: 'border-info-9',\n neutral: 'border-neutral-8',\n current: 'border-current',\n}\n\n// Off — \"exists but inactive\": faint same-tone fill + a light border.\nconst OFF: Record<DotColor, string> = {\n accent: 'border-accent-9/40 bg-accent-9/10',\n success: 'border-success-9/40 bg-success-9/10',\n warning: 'border-warning-9/40 bg-warning-9/10',\n error: 'border-error-9/40 bg-error-9/10',\n info: 'border-info-9/40 bg-info-9/10',\n neutral: 'border-neutral-8/40 bg-neutral-8/10',\n current: 'border-current/40 bg-current/10',\n}\n\nconst TEXT: Record<DotColor, string> = {\n accent: 'text-accent-11',\n success: 'text-success-11',\n warning: 'text-warning-11',\n error: 'text-error-11',\n info: 'text-info-11',\n neutral: 'text-surface-fg-muted',\n current: '',\n}\n\nconst PULSE_SPEED: Record<'slow' | 'normal' | 'fast', string> = {\n slow: '2s',\n normal: '1s',\n fast: '0.6s',\n}\n\nconst dotVariants = cva('relative inline-flex shrink-0 rounded-pill', {\n variants: {\n size: {\n xs: 'h-1 w-1',\n sm: 'h-1.5 w-1.5',\n md: 'h-2 w-2',\n lg: 'h-2.5 w-2.5',\n },\n },\n defaultVariants: { size: 'md' },\n})\n\nconst labelTextSize: Record<DotSize, string> = {\n xs: 'text-ds-xs',\n sm: 'text-ds-xs',\n md: 'text-ds-sm',\n lg: 'text-ds-sm',\n}\n\n/**\n * Props for Dot — a small semantic indicator dot.\n *\n * @example\n * <Dot color=\"success\" /> // bare dot\n * <Dot color=\"error\" pulse pulseSpeed=\"fast\" label=\"Recording\" />\n * <Dot color=\"neutral\" variant=\"off\" label=\"Offline\" /> // inactive\n * <Dot color=\"success\" withBorder /> // contrast ring on busy bg\n */\nexport interface DotProps\n extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'color'>,\n VariantProps<typeof dotVariants> {\n /** Intent colour, or `current` to inherit text colour. @default 'neutral' */\n color?: DotColor\n /** `filled` disc, hollow `ring`, or `off` (faint fill + light border = inactive). @default 'filled' */\n variant?: DotVariant\n /** A contrast ring around the dot so it stays visible on busy/colored backgrounds. @default false */\n withBorder?: boolean\n /** Animate a pulsing ripple (ignored for `off`). @default false */\n pulse?: boolean\n /** Pulse tempo. @default 'normal' */\n pulseSpeed?: 'slow' | 'normal' | 'fast'\n /** Inline label. Makes the dot an announced status. */\n label?: React.ReactNode\n /** Which side the label sits on. @default 'end' */\n labelPosition?: 'start' | 'end'\n labelClassName?: string\n}\n\nconst Dot = React.forwardRef<HTMLSpanElement, DotProps>(\n (\n {\n color = 'neutral',\n size = 'md',\n variant = 'filled',\n withBorder = false,\n pulse = false,\n pulseSpeed = 'normal',\n label,\n labelPosition = 'end',\n labelClassName,\n className,\n 'aria-label': ariaLabel,\n ...props\n },\n ref,\n ) => {\n const resolvedSize = size ?? 'md'\n // A dot with a label (or explicit aria-label) is an announced status;\n // a bare dot is decorative (the surrounding element carries the meaning).\n const isAnnounced = label != null || ariaLabel != null\n // Off dots are inactive — a pulse would contradict that.\n const showPulse = pulse && variant !== 'off'\n\n const disc = cn(\n dotVariants({ size: resolvedSize }),\n variant === 'filled' && FILL[color],\n variant === 'ring' && cn('border-[1.5px] bg-transparent', RING[color]),\n variant === 'off' && cn('border', OFF[color]),\n withBorder && 'ring-2 ring-surface-raised',\n )\n\n const labelEl =\n label != null ? (\n <span className={cn(labelTextSize[resolvedSize], TEXT[color], 'font-sans', labelClassName)}>{label}</span>\n ) : null\n\n return (\n <span\n ref={ref}\n role={isAnnounced ? 'status' : undefined}\n aria-hidden={isAnnounced ? undefined : true}\n aria-label={ariaLabel}\n className={cn('inline-flex items-center gap-ds-02', className)}\n {...props}\n >\n {labelPosition === 'start' && labelEl}\n <span className=\"relative inline-flex shrink-0\">\n {/* Contained opacity breathe on the disc itself — never an expanding halo/ring. */}\n <span\n data-pulse={showPulse ? '' : undefined}\n style={showPulse ? { animationDuration: PULSE_SPEED[pulseSpeed] } : undefined}\n className={cn(disc, showPulse && 'animate-pulse motion-reduce:animate-none')}\n />\n </span>\n {labelPosition === 'end' && labelEl}\n </span>\n )\n },\n)\nDot.displayName = 'Dot'\n\nexport { Dot, dotVariants }\n"],"mappings":";;;;;;AA0BA,IAAM,IAAiC;CACrC,QAAQ;CACR,SAAS;CACT,SAAS;CACT,OAAO;CACP,MAAM;CACN,SAAS;CACT,SAAS;CACV,EAEK,IAAiC;CACrC,QAAQ;CACR,SAAS;CACT,SAAS;CACT,OAAO;CACP,MAAM;CACN,SAAS;CACT,SAAS;CACV,EAGK,IAAgC;CACpC,QAAQ;CACR,SAAS;CACT,SAAS;CACT,OAAO;CACP,MAAM;CACN,SAAS;CACT,SAAS;CACV,EAEK,IAAiC;CACrC,QAAQ;CACR,SAAS;CACT,SAAS;CACT,OAAO;CACP,MAAM;CACN,SAAS;CACT,SAAS;CACV,EAEK,IAA0D;CAC9D,MAAM;CACN,QAAQ;CACR,MAAM;CACP,EAEK,IAAc,EAAI,8CAA8C;CACpE,UAAU,EACR,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACL,EACF;CACD,iBAAiB,EAAE,MAAM,MAAM;CAChC,CAAC,EAEI,IAAyC;CAC7C,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACL,EA+BK,IAAM,EAAM,YAEd,EACE,WAAQ,WACR,UAAO,MACP,aAAU,UACV,gBAAa,IACb,WAAQ,IACR,gBAAa,UACb,UACA,mBAAgB,OAChB,mBACA,cACA,cAAc,GACd,GAAG,KAEL,MACG;CACH,IAAM,IAAe,KAAQ,MAGvB,IAAc,KAAS,QAAQ,KAAa,MAE5C,IAAY,KAAS,MAAY,OAEjC,IAAO,EACX,EAAY,EAAE,MAAM,GAAc,CAAC,EACnC,MAAY,YAAY,EAAK,IAC7B,MAAY,UAAU,EAAG,iCAAiC,EAAK,GAAO,EACtE,MAAY,SAAS,EAAG,UAAU,EAAI,GAAO,EAC7C,KAAc,6BACf,EAEK,IACJ,KAAS,OAEL,OADF,kBAAC,QAAD;EAAM,WAAW,EAAG,EAAc,IAAe,EAAK,IAAQ,aAAa,EAAe;YAAG;EAAa,CAAA;AAG9G,QACE,kBAAC,QAAD;EACO;EACL,MAAM,IAAc,WAAW,KAAA;EAC/B,eAAa,IAAc,KAAA,IAAY;EACvC,cAAY;EACZ,WAAW,EAAG,sCAAsC,EAAU;EAC9D,GAAI;YANN;GAQG,MAAkB,WAAW;GAC9B,kBAAC,QAAD;IAAM,WAAU;cAEd,kBAAC,QAAD;KACE,cAAY,IAAY,KAAK,KAAA;KAC7B,OAAO,IAAY,EAAE,mBAAmB,EAAY,IAAa,GAAG,KAAA;KACpE,WAAW,EAAG,GAAM,KAAa,2CAA2C;KAC5E,CAAA;IACG,CAAA;GACN,MAAkB,SAAS;GACvB;;EAGZ;AACD,EAAI,cAAc"}
1
+ {"version":3,"file":"dot.js","names":[],"sources":["../../src/ui/dot.tsx"],"sourcesContent":["'use client'\n\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport * as React from 'react'\n\nimport { cn } from './lib/utils'\n\n/* ---------------------------------------------------------------------------\n * Dot — a small semantic status/indicator dot. The low-level primitive behind\n * StatusBadge, status/presence/health indicators, legend swatches, Avatar status.\n *\n * Intent-coloured, sized (xs–lg), filled / ring / off, optionally pulsing (with\n * a speed), optionally with a contrast border, and an optional label on either\n * side. Decorative by default (aria-hidden); a `label` (or `aria-label`) makes\n * it an announced status.\n *\n * For a dot/count that overlays a child element (notification badge), use\n * `BadgeIndicator` instead — that's the positioned-overlay pattern.\n *\n * API informed by Mantine `Indicator`, Ant `Badge status`, Chakra `Status`.\n * ------------------------------------------------------------------------ */\n\nexport type DotColor = 'accent' | 'success' | 'warning' | 'error' | 'info' | 'neutral' | 'current'\nexport type DotSize = 'xs' | 'sm' | 'md' | 'lg'\nexport type DotVariant = 'filled' | 'ring' | 'off'\n\nconst FILL: Record<DotColor, string> = {\n accent: 'bg-accent-9',\n success: 'bg-success-9',\n warning: 'bg-warning-9',\n error: 'bg-error-9',\n info: 'bg-info-9',\n neutral: 'bg-neutral-8',\n current: 'bg-current',\n}\n\nconst RING: Record<DotColor, string> = {\n accent: 'border-accent-9',\n success: 'border-success-9',\n warning: 'border-warning-9',\n error: 'border-error-9',\n info: 'border-info-9',\n neutral: 'border-neutral-8',\n current: 'border-current',\n}\n\n// Off — \"exists but inactive\": faint same-tone fill + a light border.\nconst OFF: Record<DotColor, string> = {\n accent: 'border-accent-9/40 bg-accent-9/10',\n success: 'border-success-9/40 bg-success-9/10',\n warning: 'border-warning-9/40 bg-warning-9/10',\n error: 'border-error-9/40 bg-error-9/10',\n info: 'border-info-9/40 bg-info-9/10',\n neutral: 'border-neutral-8/40 bg-neutral-8/10',\n current: 'border-current/40 bg-current/10',\n}\n\nconst TEXT: Record<DotColor, string> = {\n accent: 'text-accent-11',\n success: 'text-success-11',\n warning: 'text-warning-11',\n error: 'text-error-11',\n info: 'text-info-11',\n neutral: 'text-surface-fg-muted',\n current: '',\n}\n\nconst PULSE_SPEED: Record<'slow' | 'normal' | 'fast', string> = {\n slow: '2s',\n normal: '1s',\n fast: '0.6s',\n}\n\nconst dotVariants = cva('relative inline-flex shrink-0 rounded-pill', {\n variants: {\n size: {\n xs: 'h-1 w-1',\n sm: 'h-1.5 w-1.5',\n md: 'h-2 w-2',\n lg: 'h-2.5 w-2.5',\n },\n },\n defaultVariants: { size: 'md' },\n})\n\nconst labelTextSize: Record<DotSize, string> = {\n xs: 'text-ds-xs',\n sm: 'text-ds-xs',\n md: 'text-ds-sm',\n lg: 'text-ds-sm',\n}\n\n/**\n * Props for Dot — a small semantic indicator dot.\n *\n * @example\n * <Dot color=\"success\" /> // bare dot\n * <Dot color=\"error\" pulse pulseSpeed=\"fast\" label=\"Recording\" />\n * <Dot color=\"neutral\" variant=\"off\" label=\"Offline\" /> // inactive\n * <Dot color=\"success\" withBorder /> // contrast ring on busy bg\n */\nexport interface DotProps\n extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'color'>,\n VariantProps<typeof dotVariants> {\n /** Intent colour, or `current` to inherit text colour. @default 'neutral' */\n color?: DotColor\n /** `filled` disc, hollow `ring`, or `off` (faint fill + light border = inactive). @default 'filled' */\n variant?: DotVariant\n /** A contrast ring around the dot so it stays visible on busy/colored backgrounds. @default false */\n withBorder?: boolean\n /** Animate a pulsing ripple (ignored for `off`). @default false */\n pulse?: boolean\n /** Pulse tempo. @default 'normal' */\n pulseSpeed?: 'slow' | 'normal' | 'fast'\n /** Inline label. Makes the dot an announced status. */\n label?: React.ReactNode\n /** Which side the label sits on. @default 'end' */\n labelPosition?: 'start' | 'end'\n labelClassName?: string\n}\n\nconst Dot = React.forwardRef<HTMLSpanElement, DotProps>(\n (\n {\n color = 'neutral',\n size = 'md',\n variant = 'filled',\n withBorder = false,\n pulse = false,\n pulseSpeed = 'normal',\n label,\n labelPosition = 'end',\n labelClassName,\n className,\n 'aria-label': ariaLabel,\n ...props\n },\n ref,\n ) => {\n const resolvedSize = size ?? 'md'\n // A dot with a label (or explicit aria-label) is an announced status;\n // a bare dot is decorative (the surrounding element carries the meaning).\n const isAnnounced = label != null || ariaLabel != null\n // Off dots are inactive — a pulse would contradict that.\n const showPulse = pulse && variant !== 'off'\n\n const disc = cn(\n dotVariants({ size: resolvedSize }),\n variant === 'filled' && FILL[color],\n variant === 'ring' && cn('border-[1.5px] bg-transparent', RING[color]),\n variant === 'off' && cn('border', OFF[color]),\n withBorder && 'ring-2 ring-surface-raised',\n )\n\n const labelEl =\n label != null ? (\n <span className={cn(labelTextSize[resolvedSize], TEXT[color], 'font-sans', labelClassName)}>{label}</span>\n ) : null\n\n return (\n <span\n ref={ref}\n role={isAnnounced ? 'status' : undefined}\n aria-hidden={isAnnounced ? undefined : true}\n aria-label={ariaLabel}\n className={cn('inline-flex items-center gap-ds-02', className)}\n {...props}\n >\n {labelPosition === 'start' && labelEl}\n <span className=\"relative inline-flex shrink-0\">\n {/* Contained opacity breathe on the disc itself — never an expanding halo/ring. */}\n <span\n data-pulse={showPulse ? '' : undefined}\n style={showPulse ? { animationDuration: PULSE_SPEED[pulseSpeed] } : undefined}\n className={cn(disc, showPulse && 'animate-pulse motion-reduce:animate-none')}\n />\n </span>\n {labelPosition === 'end' && labelEl}\n </span>\n )\n },\n)\nDot.displayName = 'Dot'\n\nexport { Dot, dotVariants }\n"],"mappings":";;;;;;AA0BA,IAAM,IAAiC;CACrC,QAAQ;CACR,SAAS;CACT,SAAS;CACT,OAAO;CACP,MAAM;CACN,SAAS;CACT,SAAS;AACX,GAEM,IAAiC;CACrC,QAAQ;CACR,SAAS;CACT,SAAS;CACT,OAAO;CACP,MAAM;CACN,SAAS;CACT,SAAS;AACX,GAGM,IAAgC;CACpC,QAAQ;CACR,SAAS;CACT,SAAS;CACT,OAAO;CACP,MAAM;CACN,SAAS;CACT,SAAS;AACX,GAEM,IAAiC;CACrC,QAAQ;CACR,SAAS;CACT,SAAS;CACT,OAAO;CACP,MAAM;CACN,SAAS;CACT,SAAS;AACX,GAEM,IAA0D;CAC9D,MAAM;CACN,QAAQ;CACR,MAAM;AACR,GAEM,IAAc,EAAI,8CAA8C;CACpE,UAAU,EACR,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;CACN,EACF;CACA,iBAAiB,EAAE,MAAM,KAAK;AAChC,CAAC,GAEK,IAAyC;CAC7C,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;AACN,GA+BM,IAAM,EAAM,YAEd,EACE,WAAQ,WACR,UAAO,MACP,aAAU,UACV,gBAAa,IACb,WAAQ,IACR,gBAAa,UACb,UACA,mBAAgB,OAChB,mBACA,cACA,cAAc,GACd,GAAG,KAEL,MACG;CACH,IAAM,IAAe,KAAQ,MAGvB,IAAc,KAAS,QAAQ,KAAa,MAE5C,IAAY,KAAS,MAAY,OAEjC,IAAO,EACX,EAAY,EAAE,MAAM,EAAa,CAAC,GAClC,MAAY,YAAY,EAAK,IAC7B,MAAY,UAAU,EAAG,iCAAiC,EAAK,EAAM,GACrE,MAAY,SAAS,EAAG,UAAU,EAAI,EAAM,GAC5C,KAAc,4BAChB,GAEM,IACJ,KAAS,OAEL,OADF,kBAAC,QAAD;EAAM,WAAW,EAAG,EAAc,IAAe,EAAK,IAAQ,aAAa,CAAc;YAAI;CAAY,CAAA;CAG7G,OACE,kBAAC,QAAD;EACO;EACL,MAAM,IAAc,WAAW,KAAA;EAC/B,eAAa,MAAc,KAAA;EAC3B,cAAY;EACZ,WAAW,EAAG,sCAAsC,CAAS;EAC7D,GAAI;YANN;GAQG,MAAkB,WAAW;GAC9B,kBAAC,QAAD;IAAM,WAAU;cAEd,kBAAC,QAAD;KACE,cAAY,IAAY,KAAK,KAAA;KAC7B,OAAO,IAAY,EAAE,mBAAmB,EAAY,GAAY,IAAI,KAAA;KACpE,WAAW,EAAG,GAAM,KAAa,0CAA0C;IAC5E,CAAA;GACG,CAAA;GACL,MAAkB,SAAS;EACxB;;AAEV,CACF;AACA,EAAI,cAAc"}
@@ -82,7 +82,7 @@ declare const DropdownMenuLabel: React.ForwardRefExoticComponent<Omit<DropdownMe
82
82
  } & React.RefAttributes<HTMLDivElement>>;
83
83
  declare const DropdownMenuSeparator: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuSeparatorProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
84
84
  declare const DropdownMenuShortcut: {
85
- ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>): import("react/jsx-runtime").JSX.Element;
85
+ ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>): React.JSX.Element;
86
86
  displayName: string;
87
87
  };
88
88
  export type DropdownMenuContentProps = React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>;
@@ -1 +1 @@
1
- {"version":3,"file":"dropdown-menu.js","names":[],"sources":["../../src/ui/dropdown-menu.tsx"],"sourcesContent":["'use client'\n\nimport * as DropdownMenuPrimitive from '@primitives/react-dropdown-menu'\nimport { IconCheck, IconChevronRight, IconCircle } from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Icon } from './icon'\nimport { springs, tweens } from './lib/motion'\nimport { useControllableOpen } from './lib/use-controllable-open'\nimport { cn } from './lib/utils'\n\n// ── Internal contexts to thread open state ──\n\nconst DropdownMenuOpenContext = React.createContext(false)\nconst DropdownMenuSubOpenContext = React.createContext(false)\n\n/**\n * DropdownMenu compound component — accessible floating menu triggered by a button, with full\n * keyboard navigation and focus management.\n *\n * **Parts (in composition order):**\n * - `DropdownMenu` — manages open/closed state (this root)\n * - `DropdownMenuTrigger` — element that opens the menu (use `asChild` to render your own button)\n * - `DropdownMenuContent` — the floating menu panel (renders in a portal automatically)\n * - `DropdownMenuItem` — clickable menu item (use `onSelect` for actions)\n * - `DropdownMenuCheckboxItem` — item with a checkbox indicator\n * - `DropdownMenuRadioGroup` — wraps radio items for exclusive selection\n * - `DropdownMenuRadioItem` — item with a radio indicator (must be inside `DropdownMenuRadioGroup`)\n * - `DropdownMenuSub` — root for a nested submenu\n * - `DropdownMenuSubTrigger` — item that reveals a submenu on hover/focus\n * - `DropdownMenuSubContent` — the nested submenu panel\n * - `DropdownMenuLabel` — non-interactive group label (not selectable)\n * - `DropdownMenuSeparator` — horizontal divider between sections\n * - `DropdownMenuShortcut` — right-aligned keyboard shortcut hint text (display only)\n * - `DropdownMenuGroup` — semantic grouping wrapper (no visual output)\n * - `DropdownMenuPortal` — low-level portal wrapper (exported for custom positioning; used internally by DropdownMenuContent)\n *\n * @compound\n * @example\n * // Standard action menu:\n * <DropdownMenu>\n * <DropdownMenuTrigger asChild>\n * <Button variant=\"ghost\" size=\"icon\"><IconDotsVertical /></Button>\n * </DropdownMenuTrigger>\n * <DropdownMenuContent align=\"end\">\n * <DropdownMenuLabel>My account</DropdownMenuLabel>\n * <DropdownMenuSeparator />\n * <DropdownMenuItem>\n * <IconUser className=\"mr-ds-03\" />Profile\n * <DropdownMenuShortcut>⌘P</DropdownMenuShortcut>\n * </DropdownMenuItem>\n * <DropdownMenuItem>Settings</DropdownMenuItem>\n * <DropdownMenuSeparator />\n * <DropdownMenuItem className=\"text-error-11\">Sign out</DropdownMenuItem>\n * </DropdownMenuContent>\n * </DropdownMenu>\n *\n * @example\n * // Menu with checkbox items and a nested submenu:\n * <DropdownMenu>\n * <DropdownMenuTrigger asChild><Button>View options</Button></DropdownMenuTrigger>\n * <DropdownMenuContent>\n * <DropdownMenuCheckboxItem checked={showPanel} onCheckedChange={setShowPanel}>\n * Show side panel\n * </DropdownMenuCheckboxItem>\n * <DropdownMenuSeparator />\n * <DropdownMenuSub>\n * <DropdownMenuSubTrigger>More tools</DropdownMenuSubTrigger>\n * <DropdownMenuSubContent>\n * <DropdownMenuItem>Export CSV</DropdownMenuItem>\n * <DropdownMenuItem>Export PDF</DropdownMenuItem>\n * </DropdownMenuSubContent>\n * </DropdownMenuSub>\n * </DropdownMenuContent>\n * </DropdownMenu>\n */\nconst DropdownMenu: React.FC<React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Root>> = ({\n open: controlledOpen,\n defaultOpen = false,\n onOpenChange,\n ...props\n}) => {\n const { open, setOpen } = useControllableOpen({ open: controlledOpen, defaultOpen, onOpenChange })\n\n return (\n <DropdownMenuOpenContext.Provider value={open}>\n <DropdownMenuPrimitive.Root open={open} onOpenChange={setOpen} {...props} />\n </DropdownMenuOpenContext.Provider>\n )\n}\nDropdownMenu.displayName = 'DropdownMenu'\n\nconst DropdownMenuTrigger = DropdownMenuPrimitive.Trigger\n\nconst DropdownMenuGroup = DropdownMenuPrimitive.Group\n\nconst DropdownMenuPortal = DropdownMenuPrimitive.Portal\n\nconst DropdownMenuSub: React.FC<React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Sub>> = ({\n open: controlledOpen,\n defaultOpen = false,\n onOpenChange,\n ...props\n}) => {\n const { open, setOpen } = useControllableOpen({ open: controlledOpen, defaultOpen, onOpenChange })\n\n return (\n <DropdownMenuSubOpenContext.Provider value={open}>\n <DropdownMenuPrimitive.Sub open={open} onOpenChange={setOpen} {...props} />\n </DropdownMenuSubOpenContext.Provider>\n )\n}\nDropdownMenuSub.displayName = 'DropdownMenuSub'\n\nconst DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup\n\nconst DropdownMenuSubTrigger = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {\n inset?: boolean\n }\n>(({ className, inset, children, ...props }, ref) => (\n <DropdownMenuPrimitive.SubTrigger\n ref={ref}\n className={cn(\n 'flex cursor-default select-none items-center gap-ds-03 rounded-control px-ds-03 py-ds-02b text-ds-md outline-hidden focus:bg-surface-raised data-[state=open]:bg-surface-raised [&_svg]:pointer-events-none [&_svg]:h-ico-sm [&_svg]:w-ico-sm [&_svg]:shrink-0',\n inset && 'pl-ds-07',\n className,\n )}\n {...props}\n >\n <span className=\"min-w-0 truncate\">{children}</span>\n <Icon icon={IconChevronRight} size=\"sm\" className=\"ml-auto shrink-0\" />\n </DropdownMenuPrimitive.SubTrigger>\n))\nDropdownMenuSubTrigger.displayName =\n DropdownMenuPrimitive.SubTrigger.displayName\n\nconst DropdownMenuSubContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>\n>(({ className, children, ...props }, ref) => {\n const open = React.useContext(DropdownMenuSubOpenContext)\n\n return (\n <AnimatePresence>\n {open && (\n <DropdownMenuPrimitive.SubContent\n ref={ref}\n forceMount\n asChild\n {...props}\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={{ ...springs.snappy, opacity: tweens.fade }}\n className={cn(\n 'z-popover min-w-[8rem] overflow-hidden rounded-overlay bg-surface-overlay p-ds-02 text-surface-fg shadow-floating',\n className,\n )}\n >\n {children}\n </motion.div>\n </DropdownMenuPrimitive.SubContent>\n )}\n </AnimatePresence>\n )\n})\nDropdownMenuSubContent.displayName =\n DropdownMenuPrimitive.SubContent.displayName\n\nconst DropdownMenuContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\n>(({ className, sideOffset = 4, children, ...props }, ref) => {\n const open = React.useContext(DropdownMenuOpenContext)\n\n return (\n <AnimatePresence>\n {open && (\n <DropdownMenuPrimitive.Portal forceMount>\n <DropdownMenuPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n forceMount\n asChild\n {...props}\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={{ ...springs.snappy, opacity: tweens.fade }}\n className={cn(\n 'z-popover min-w-[8rem] rounded-overlay bg-surface-overlay p-ds-02 text-surface-fg shadow-floating',\n className,\n )}\n >\n {children}\n </motion.div>\n </DropdownMenuPrimitive.Content>\n </DropdownMenuPrimitive.Portal>\n )}\n </AnimatePresence>\n )\n})\nDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName\n\nconst DropdownMenuItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {\n inset?: boolean\n }\n>(({ className, inset, ...props }, ref) => (\n <DropdownMenuPrimitive.Item\n ref={ref}\n className={cn(\n 'relative flex cursor-default select-none items-center gap-ds-03 rounded-control px-ds-03 py-ds-02b text-ds-md outline-hidden transition-colors duration-fast-01 ease-productive-standard hover:bg-surface-raised focus:bg-surface-raised focus:text-surface-fg active:bg-surface-raised-hover data-[disabled]:pointer-events-none data-[disabled]:opacity-action-disabled [&>svg]:h-ico-sm [&>svg]:w-ico-sm [&>svg]:shrink-0',\n inset && 'pl-ds-07',\n className,\n )}\n {...props}\n />\n))\nDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName\n\nconst DropdownMenuCheckboxItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n <DropdownMenuPrimitive.CheckboxItem\n ref={ref}\n className={cn(\n 'relative flex cursor-default select-none items-center rounded-control py-ds-02b pl-ds-07 pr-ds-03 text-ds-md outline-hidden transition-colors duration-fast-01 ease-productive-standard hover:bg-surface-raised focus:bg-surface-raised focus:text-surface-fg active:bg-surface-raised-hover data-[disabled]:pointer-events-none data-[disabled]:opacity-action-disabled',\n className,\n )}\n checked={checked}\n {...props}\n >\n <span className=\"absolute left-ds-03 flex h-ico-sm w-ico-sm items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <Icon icon={IconCheck} size=\"sm\" />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.CheckboxItem>\n))\nDropdownMenuCheckboxItem.displayName =\n DropdownMenuPrimitive.CheckboxItem.displayName\n\nconst DropdownMenuRadioItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n <DropdownMenuPrimitive.RadioItem\n ref={ref}\n className={cn(\n 'relative flex cursor-default select-none items-center rounded-control py-ds-02b pl-ds-07 pr-ds-03 text-ds-md outline-hidden transition-colors duration-fast-01 ease-productive-standard hover:bg-surface-raised focus:bg-surface-raised focus:text-surface-fg active:bg-surface-raised-hover data-[disabled]:pointer-events-none data-[disabled]:opacity-action-disabled',\n className,\n )}\n {...props}\n >\n <span className=\"absolute left-ds-03 flex h-ico-sm w-ico-sm items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <IconCircle className=\"h-2 w-2 fill-current\" />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n <span className=\"min-w-0 truncate\">{children}</span>\n </DropdownMenuPrimitive.RadioItem>\n))\nDropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName\n\nconst DropdownMenuLabel = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Label>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {\n inset?: boolean\n }\n>(({ className, inset, ...props }, ref) => (\n <DropdownMenuPrimitive.Label\n ref={ref}\n className={cn(\n 'px-ds-03 py-ds-02b text-ds-md font-semibold',\n inset && 'pl-ds-07',\n className,\n )}\n {...props}\n />\n))\nDropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName\n\nconst DropdownMenuSeparator = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Separator>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n className={cn('-mx-ds-01 my-ds-02 h-px bg-surface-border', className)}\n {...props}\n />\n))\nDropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName\n\nconst DropdownMenuShortcut = ({\n className,\n ...props\n}: React.HTMLAttributes<HTMLSpanElement>) => {\n return (\n <span\n className={cn('ml-auto text-ds-sm text-surface-fg-subtle', className)}\n {...props}\n />\n )\n}\nDropdownMenuShortcut.displayName = 'DropdownMenuShortcut'\n\nexport type DropdownMenuContentProps = React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\nexport type DropdownMenuItemProps = React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item>\n\nexport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuPortal,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuShortcut,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"dropdown-menu.js","names":[],"sources":["../../src/ui/dropdown-menu.tsx"],"sourcesContent":["'use client'\n\nimport * as DropdownMenuPrimitive from '@primitives/react-dropdown-menu'\nimport { IconCheck, IconChevronRight, IconCircle } from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Icon } from './icon'\nimport { springs, tweens } from './lib/motion'\nimport { useControllableOpen } from './lib/use-controllable-open'\nimport { cn } from './lib/utils'\n\n// ── Internal contexts to thread open state ──\n\nconst DropdownMenuOpenContext = React.createContext(false)\nconst DropdownMenuSubOpenContext = React.createContext(false)\n\n/**\n * DropdownMenu compound component — accessible floating menu triggered by a button, with full\n * keyboard navigation and focus management.\n *\n * **Parts (in composition order):**\n * - `DropdownMenu` — manages open/closed state (this root)\n * - `DropdownMenuTrigger` — element that opens the menu (use `asChild` to render your own button)\n * - `DropdownMenuContent` — the floating menu panel (renders in a portal automatically)\n * - `DropdownMenuItem` — clickable menu item (use `onSelect` for actions)\n * - `DropdownMenuCheckboxItem` — item with a checkbox indicator\n * - `DropdownMenuRadioGroup` — wraps radio items for exclusive selection\n * - `DropdownMenuRadioItem` — item with a radio indicator (must be inside `DropdownMenuRadioGroup`)\n * - `DropdownMenuSub` — root for a nested submenu\n * - `DropdownMenuSubTrigger` — item that reveals a submenu on hover/focus\n * - `DropdownMenuSubContent` — the nested submenu panel\n * - `DropdownMenuLabel` — non-interactive group label (not selectable)\n * - `DropdownMenuSeparator` — horizontal divider between sections\n * - `DropdownMenuShortcut` — right-aligned keyboard shortcut hint text (display only)\n * - `DropdownMenuGroup` — semantic grouping wrapper (no visual output)\n * - `DropdownMenuPortal` — low-level portal wrapper (exported for custom positioning; used internally by DropdownMenuContent)\n *\n * @compound\n * @example\n * // Standard action menu:\n * <DropdownMenu>\n * <DropdownMenuTrigger asChild>\n * <Button variant=\"ghost\" size=\"icon\"><IconDotsVertical /></Button>\n * </DropdownMenuTrigger>\n * <DropdownMenuContent align=\"end\">\n * <DropdownMenuLabel>My account</DropdownMenuLabel>\n * <DropdownMenuSeparator />\n * <DropdownMenuItem>\n * <IconUser className=\"mr-ds-03\" />Profile\n * <DropdownMenuShortcut>⌘P</DropdownMenuShortcut>\n * </DropdownMenuItem>\n * <DropdownMenuItem>Settings</DropdownMenuItem>\n * <DropdownMenuSeparator />\n * <DropdownMenuItem className=\"text-error-11\">Sign out</DropdownMenuItem>\n * </DropdownMenuContent>\n * </DropdownMenu>\n *\n * @example\n * // Menu with checkbox items and a nested submenu:\n * <DropdownMenu>\n * <DropdownMenuTrigger asChild><Button>View options</Button></DropdownMenuTrigger>\n * <DropdownMenuContent>\n * <DropdownMenuCheckboxItem checked={showPanel} onCheckedChange={setShowPanel}>\n * Show side panel\n * </DropdownMenuCheckboxItem>\n * <DropdownMenuSeparator />\n * <DropdownMenuSub>\n * <DropdownMenuSubTrigger>More tools</DropdownMenuSubTrigger>\n * <DropdownMenuSubContent>\n * <DropdownMenuItem>Export CSV</DropdownMenuItem>\n * <DropdownMenuItem>Export PDF</DropdownMenuItem>\n * </DropdownMenuSubContent>\n * </DropdownMenuSub>\n * </DropdownMenuContent>\n * </DropdownMenu>\n */\nconst DropdownMenu: React.FC<React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Root>> = ({\n open: controlledOpen,\n defaultOpen = false,\n onOpenChange,\n ...props\n}) => {\n const { open, setOpen } = useControllableOpen({ open: controlledOpen, defaultOpen, onOpenChange })\n\n return (\n <DropdownMenuOpenContext.Provider value={open}>\n <DropdownMenuPrimitive.Root open={open} onOpenChange={setOpen} {...props} />\n </DropdownMenuOpenContext.Provider>\n )\n}\nDropdownMenu.displayName = 'DropdownMenu'\n\nconst DropdownMenuTrigger = DropdownMenuPrimitive.Trigger\n\nconst DropdownMenuGroup = DropdownMenuPrimitive.Group\n\nconst DropdownMenuPortal = DropdownMenuPrimitive.Portal\n\nconst DropdownMenuSub: React.FC<React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Sub>> = ({\n open: controlledOpen,\n defaultOpen = false,\n onOpenChange,\n ...props\n}) => {\n const { open, setOpen } = useControllableOpen({ open: controlledOpen, defaultOpen, onOpenChange })\n\n return (\n <DropdownMenuSubOpenContext.Provider value={open}>\n <DropdownMenuPrimitive.Sub open={open} onOpenChange={setOpen} {...props} />\n </DropdownMenuSubOpenContext.Provider>\n )\n}\nDropdownMenuSub.displayName = 'DropdownMenuSub'\n\nconst DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup\n\nconst DropdownMenuSubTrigger = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {\n inset?: boolean\n }\n>(({ className, inset, children, ...props }, ref) => (\n <DropdownMenuPrimitive.SubTrigger\n ref={ref}\n className={cn(\n 'flex cursor-default select-none items-center gap-ds-03 rounded-control px-ds-03 py-ds-02b text-ds-md outline-hidden focus:bg-surface-raised data-[state=open]:bg-surface-raised [&_svg]:pointer-events-none [&_svg]:h-ico-sm [&_svg]:w-ico-sm [&_svg]:shrink-0',\n inset && 'pl-ds-07',\n className,\n )}\n {...props}\n >\n <span className=\"min-w-0 truncate\">{children}</span>\n <Icon icon={IconChevronRight} size=\"sm\" className=\"ml-auto shrink-0\" />\n </DropdownMenuPrimitive.SubTrigger>\n))\nDropdownMenuSubTrigger.displayName =\n DropdownMenuPrimitive.SubTrigger.displayName\n\nconst DropdownMenuSubContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>\n>(({ className, children, ...props }, ref) => {\n const open = React.useContext(DropdownMenuSubOpenContext)\n\n return (\n <AnimatePresence>\n {open && (\n <DropdownMenuPrimitive.SubContent\n ref={ref}\n forceMount\n asChild\n {...props}\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={{ ...springs.snappy, opacity: tweens.fade }}\n className={cn(\n 'z-popover min-w-[8rem] overflow-hidden rounded-overlay bg-surface-overlay p-ds-02 text-surface-fg shadow-floating',\n className,\n )}\n >\n {children}\n </motion.div>\n </DropdownMenuPrimitive.SubContent>\n )}\n </AnimatePresence>\n )\n})\nDropdownMenuSubContent.displayName =\n DropdownMenuPrimitive.SubContent.displayName\n\nconst DropdownMenuContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\n>(({ className, sideOffset = 4, children, ...props }, ref) => {\n const open = React.useContext(DropdownMenuOpenContext)\n\n return (\n <AnimatePresence>\n {open && (\n <DropdownMenuPrimitive.Portal forceMount>\n <DropdownMenuPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n forceMount\n asChild\n {...props}\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={{ ...springs.snappy, opacity: tweens.fade }}\n className={cn(\n 'z-popover min-w-[8rem] rounded-overlay bg-surface-overlay p-ds-02 text-surface-fg shadow-floating',\n className,\n )}\n >\n {children}\n </motion.div>\n </DropdownMenuPrimitive.Content>\n </DropdownMenuPrimitive.Portal>\n )}\n </AnimatePresence>\n )\n})\nDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName\n\nconst DropdownMenuItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {\n inset?: boolean\n }\n>(({ className, inset, ...props }, ref) => (\n <DropdownMenuPrimitive.Item\n ref={ref}\n className={cn(\n 'relative flex cursor-default select-none items-center gap-ds-03 rounded-control px-ds-03 py-ds-02b text-ds-md outline-hidden transition-colors duration-fast-01 ease-productive-standard hover:bg-surface-raised focus:bg-surface-raised focus:text-surface-fg active:bg-surface-raised-hover data-[disabled]:pointer-events-none data-[disabled]:opacity-action-disabled [&>svg]:h-ico-sm [&>svg]:w-ico-sm [&>svg]:shrink-0',\n inset && 'pl-ds-07',\n className,\n )}\n {...props}\n />\n))\nDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName\n\nconst DropdownMenuCheckboxItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n <DropdownMenuPrimitive.CheckboxItem\n ref={ref}\n className={cn(\n 'relative flex cursor-default select-none items-center rounded-control py-ds-02b pl-ds-07 pr-ds-03 text-ds-md outline-hidden transition-colors duration-fast-01 ease-productive-standard hover:bg-surface-raised focus:bg-surface-raised focus:text-surface-fg active:bg-surface-raised-hover data-[disabled]:pointer-events-none data-[disabled]:opacity-action-disabled',\n className,\n )}\n checked={checked}\n {...props}\n >\n <span className=\"absolute left-ds-03 flex h-ico-sm w-ico-sm items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <Icon icon={IconCheck} size=\"sm\" />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.CheckboxItem>\n))\nDropdownMenuCheckboxItem.displayName =\n DropdownMenuPrimitive.CheckboxItem.displayName\n\nconst DropdownMenuRadioItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n <DropdownMenuPrimitive.RadioItem\n ref={ref}\n className={cn(\n 'relative flex cursor-default select-none items-center rounded-control py-ds-02b pl-ds-07 pr-ds-03 text-ds-md outline-hidden transition-colors duration-fast-01 ease-productive-standard hover:bg-surface-raised focus:bg-surface-raised focus:text-surface-fg active:bg-surface-raised-hover data-[disabled]:pointer-events-none data-[disabled]:opacity-action-disabled',\n className,\n )}\n {...props}\n >\n <span className=\"absolute left-ds-03 flex h-ico-sm w-ico-sm items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <IconCircle className=\"h-2 w-2 fill-current\" />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n <span className=\"min-w-0 truncate\">{children}</span>\n </DropdownMenuPrimitive.RadioItem>\n))\nDropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName\n\nconst DropdownMenuLabel = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Label>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {\n inset?: boolean\n }\n>(({ className, inset, ...props }, ref) => (\n <DropdownMenuPrimitive.Label\n ref={ref}\n className={cn(\n 'px-ds-03 py-ds-02b text-ds-md font-semibold',\n inset && 'pl-ds-07',\n className,\n )}\n {...props}\n />\n))\nDropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName\n\nconst DropdownMenuSeparator = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Separator>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n className={cn('-mx-ds-01 my-ds-02 h-px bg-surface-border', className)}\n {...props}\n />\n))\nDropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName\n\nconst DropdownMenuShortcut = ({\n className,\n ...props\n}: React.HTMLAttributes<HTMLSpanElement>) => {\n return (\n <span\n className={cn('ml-auto text-ds-sm text-surface-fg-subtle', className)}\n {...props}\n />\n )\n}\nDropdownMenuShortcut.displayName = 'DropdownMenuShortcut'\n\nexport type DropdownMenuContentProps = React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\nexport type DropdownMenuItemProps = React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item>\n\nexport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuPortal,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuShortcut,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"file-upload.js","names":[],"sources":["../../src/ui/file-upload.tsx"],"sourcesContent":["'use client'\n\nimport { IconCheck,IconPaperclip, IconUpload } from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Icon } from './icon'\nimport { durations,springs, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\nimport { Spinner } from './spinner'\n\n/**\n * Props for FileUpload — a drag-and-drop file picker with client-side validation (type + size),\n * an upload progress bar, and a compact inline mode.\n *\n * **Two modes:**\n * - Default: large drop zone with an upload icon, label, and sublabel — good for primary upload areas.\n * - `compact={true}`: a small inline button with an attachment icon — good for secondary/inline uploads.\n *\n * **Validation:** Pass `accept` (MIME or extension) and `maxSize` (bytes). Errors are shown inline.\n * The `onFiles` callback only fires with valid files — invalid files are rejected with an error message.\n *\n * **`onFiles` required:** This is the only required prop. Integrating with a real uploader means\n * calling your upload API from this callback and driving `uploading` + `progress` externally.\n *\n * @example\n * // Avatar upload with image-only restriction:\n * <FileUpload\n * accept=\"image/*\"\n * maxSize={2 * 1024 * 1024}\n * onFiles={(files) => uploadAvatar(files[0])}\n * label=\"Upload profile photo\"\n * sublabel=\"PNG, JPG up to 2MB\"\n * />\n *\n * @example\n * // Document multi-upload with progress:\n * <FileUpload\n * multiple\n * accept=\".pdf,.doc,.docx\"\n * uploading={isUploading}\n * progress={uploadPercent}\n * onFiles={handleFiles}\n * />\n *\n * @example\n * // Compact inline attachment button in a comment form:\n * <FileUpload compact onFiles={(files) => attachFiles(files)} label=\"Attach files\" />\n * // These are just a few ways — feel free to combine props creatively!\n */\nexport interface FileUploadProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onError'> {\n /** Accepted file types (e.g., \"image/*\", \".pdf,.doc\") */\n accept?: string\n /** Max file size in bytes (default: 10MB) */\n maxSize?: number\n /** Allow multiple files */\n multiple?: boolean\n /** Currently uploading */\n uploading?: boolean\n /** Upload progress (0-100) */\n progress?: number\n /** Callback when files are selected/dropped */\n onFiles: (files: File[]) => void\n /** Error message to display */\n error?: string\n /** Compact mode (inline button instead of drop zone) */\n compact?: boolean\n /** Disabled */\n disabled?: boolean\n /** Custom label */\n label?: string\n /** Custom sublabel */\n sublabel?: string\n}\n\nconst DEFAULT_MAX_SIZE = 10 * 1024 * 1024 // 10MB\n\nfunction fileMatchesAccept(file: File, accept: string): boolean {\n return accept\n .split(',')\n .map((t) => t.trim())\n .some((type) => {\n if (type.startsWith('.'))\n return file.name.toLowerCase().endsWith(type.toLowerCase())\n if (type.endsWith('/*'))\n return file.type.startsWith(type.slice(0, type.indexOf('/') + 1))\n return file.type === type\n })\n}\n\nfunction formatBytes(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\nconst FileUpload = React.forwardRef<HTMLDivElement, FileUploadProps>(\n (\n {\n accept,\n maxSize = DEFAULT_MAX_SIZE,\n multiple = false,\n uploading = false,\n progress = 0,\n onFiles,\n error,\n compact = false,\n disabled = false,\n className,\n label,\n sublabel,\n ...props\n },\n ref,\n ) => {\n const inputRef = React.useRef<HTMLInputElement>(null)\n const [isDragActive, setIsDragActive] = React.useState(false)\n const [validationError, setValidationError] = React.useState<string | null>(\n null,\n )\n // Track whether a file dialog was opened, so we can detect when the\n // browser (or testing library) filters out all files via the accept attribute\n const dialogOpenRef = React.useRef(false)\n const filesAcceptedRef = React.useRef(false)\n\n const displayError = error || validationError\n\n const openPicker = React.useCallback(() => {\n if (disabled || uploading) return\n inputRef.current?.click()\n }, [disabled, uploading])\n\n const validateAndEmit = React.useCallback(\n (files: File[]) => {\n setValidationError(null)\n\n for (const file of files) {\n if (file.size > maxSize) {\n setValidationError(\n `File \"${file.name}\" exceeds the maximum size of ${formatBytes(maxSize)}`,\n )\n return\n }\n if (accept && !fileMatchesAccept(file, accept)) {\n setValidationError(\n `File \"${file.name}\" is not an accepted file type`,\n )\n return\n }\n }\n\n onFiles(files)\n },\n [maxSize, accept, onFiles],\n )\n\n const handleInputChange = React.useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n filesAcceptedRef.current = true\n const fileList = e.target.files\n if (!fileList || fileList.length === 0) return\n validateAndEmit(Array.from(fileList))\n // Reset input value so the same file can be selected again\n e.target.value = ''\n },\n [validateAndEmit],\n )\n\n // When the file input is clicked, the browser opens a dialog and blurs the input.\n // We track this to detect when the dialog closes without selecting valid files.\n const handleInputClick = React.useCallback(() => {\n dialogOpenRef.current = true\n filesAcceptedRef.current = false\n }, [])\n\n // After the file dialog closes, the browser re-focuses the input.\n // If no files were accepted (e.g., the browser/test library filtered by accept),\n // and the accept prop is set, show a validation error.\n const handleInputFocus = React.useCallback(() => {\n if (dialogOpenRef.current && !filesAcceptedRef.current && accept) {\n setValidationError('File type is not accepted')\n }\n dialogOpenRef.current = false\n }, [accept])\n\n const handleDragEnter = React.useCallback(\n (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault()\n e.stopPropagation()\n if (!disabled && !uploading) {\n setIsDragActive(true)\n }\n },\n [disabled, uploading],\n )\n\n const handleDragOver = React.useCallback(\n (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault()\n e.stopPropagation()\n if (!disabled && !uploading) {\n setIsDragActive(true)\n }\n },\n [disabled, uploading],\n )\n\n const handleDragLeave = React.useCallback(\n (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setIsDragActive(false)\n },\n [],\n )\n\n const handleDrop = React.useCallback(\n (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setIsDragActive(false)\n\n if (disabled || uploading) return\n\n const fileList = e.dataTransfer.files\n if (!fileList || fileList.length === 0) return\n validateAndEmit(Array.from(fileList))\n },\n [disabled, uploading, validateAndEmit],\n )\n\n const defaultLabel = label ?? 'Drop files here or click to browse'\n const defaultSublabel = sublabel ?? `Max file size: ${formatBytes(maxSize)}`\n const inputId = React.useId()\n\n if (compact) {\n return (\n <div ref={ref} className={cn('inline-flex flex-col', className)} {...props}>\n <button\n type=\"button\"\n onClick={openPicker}\n disabled={disabled}\n className={cn(\n 'inline-flex items-center gap-ds-02 rounded-control px-ds-03 py-ds-02',\n 'text-ds-sm font-medium',\n 'border border-surface-border-strong',\n 'bg-surface-raised-hover text-surface-fg-muted',\n 'hover:bg-accent-2',\n 'disabled:opacity-action-disabled disabled:cursor-not-allowed',\n 'transition-colors',\n )}\n >\n {uploading ? (\n <Spinner size=\"sm\" />\n ) : (\n <Icon icon={IconPaperclip} size=\"sm\" />\n )}\n {label ?? 'Attach files'}\n </button>\n <input\n ref={inputRef}\n id={inputId}\n type=\"file\"\n className=\"sr-only\"\n style={{ visibility: 'hidden' }}\n aria-hidden=\"true\"\n aria-label={label ?? 'Attach files'}\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n onChange={handleInputChange}\n onClick={handleInputClick}\n onFocus={handleInputFocus}\n tabIndex={-1}\n />\n {displayError && (\n <p\n role=\"alert\"\n aria-live=\"polite\"\n className=\"mt-ds-02 text-ds-xs text-error-11\"\n >\n {displayError}\n </p>\n )}\n </div>\n )\n }\n\n return (\n <div\n ref={ref}\n {...props}\n role=\"presentation\"\n className={cn('flex flex-col', className)}\n data-drag-active={isDragActive ? 'true' : undefined}\n onDragEnter={handleDragEnter}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onDrop={handleDrop}\n >\n <motion.div\n role=\"button\"\n tabIndex={0}\n aria-disabled={disabled || undefined}\n onClick={disabled ? undefined : openPicker}\n onKeyDown={(e: React.KeyboardEvent<HTMLDivElement>) => {\n if (!disabled && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault()\n openPicker()\n }\n }}\n className={cn(\n 'flex flex-col items-center justify-center gap-ds-03 rounded-surface',\n 'border-2 border-dashed p-ds-08',\n 'cursor-pointer',\n 'border-surface-border-strong bg-surface-raised-hover',\n isDragActive &&\n 'border-accent-7 bg-accent-2',\n disabled && 'opacity-action-disabled cursor-not-allowed',\n )}\n animate={{\n scale: isDragActive ? 1.02 : 1,\n }}\n transition={springs.snappy}\n >\n {/* Icon — animates between upload ↔ spinner ↔ checkmark */}\n <AnimatePresence mode=\"wait\">\n {uploading ? (\n <motion.div\n key=\"spinner\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={tweens.fade}\n >\n <Spinner size=\"md\" />\n </motion.div>\n ) : progress === 100 && !error ? (\n <motion.div\n key=\"complete\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={springs.bouncy}\n >\n <Icon icon={IconCheck} size=\"2xl\" className=\"text-success-11\" />\n </motion.div>\n ) : (\n <motion.div\n key=\"upload\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Icon icon={IconUpload} size=\"2xl\" className=\"text-surface-fg-subtle\" />\n </motion.div>\n )}\n </AnimatePresence>\n <span id={inputId + '-label'} className=\"text-ds-sm text-surface-fg-muted\">\n {defaultLabel}\n </span>\n {uploading ? (\n <div className=\"w-full max-w-xs\">\n <div\n role=\"progressbar\"\n aria-valuenow={progress}\n aria-valuemin={0}\n aria-valuemax={100}\n className=\"h-2 w-full overflow-hidden rounded-pill bg-surface-raised-hover\"\n >\n <motion.div\n className=\"h-full rounded-pill bg-accent-9\"\n animate={{ width: `${progress}%` }}\n transition={springs.smooth}\n />\n </div>\n </div>\n ) : (\n <span className=\"text-ds-xs text-surface-fg-subtle\">\n {defaultSublabel}\n </span>\n )}\n </motion.div>\n <input\n ref={inputRef}\n id={inputId}\n type=\"file\"\n className=\"sr-only\"\n style={{ visibility: 'hidden' }}\n aria-hidden=\"true\"\n aria-label={defaultLabel}\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n onChange={handleInputChange}\n onClick={handleInputClick}\n onFocus={handleInputFocus}\n tabIndex={-1}\n />\n {/* Error message with shake animation */}\n <AnimatePresence>\n {displayError && (\n <motion.p\n role=\"alert\"\n className=\"mt-ds-02 text-ds-xs text-error-11\"\n initial={{ opacity: 0, x: 0 }}\n animate={{ opacity: 1, x: [0, -4, 4, -4, 4, 0] }}\n exit={{ opacity: 0 }}\n transition={{ opacity: tweens.fade, x: { type: 'tween', duration: durations.slow01, ease: 'easeOut' } }}\n >\n {displayError}\n </motion.p>\n )}\n </AnimatePresence>\n </div>\n )\n },\n)\nFileUpload.displayName = 'FileUpload'\n\nexport { FileUpload }\n"],"mappings":"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"}
1
+ {"version":3,"file":"file-upload.js","names":[],"sources":["../../src/ui/file-upload.tsx"],"sourcesContent":["'use client'\n\nimport { IconCheck,IconPaperclip, IconUpload } from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Icon } from './icon'\nimport { durations,springs, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\nimport { Spinner } from './spinner'\n\n/**\n * Props for FileUpload — a drag-and-drop file picker with client-side validation (type + size),\n * an upload progress bar, and a compact inline mode.\n *\n * **Two modes:**\n * - Default: large drop zone with an upload icon, label, and sublabel — good for primary upload areas.\n * - `compact={true}`: a small inline button with an attachment icon — good for secondary/inline uploads.\n *\n * **Validation:** Pass `accept` (MIME or extension) and `maxSize` (bytes). Errors are shown inline.\n * The `onFiles` callback only fires with valid files — invalid files are rejected with an error message.\n *\n * **`onFiles` required:** This is the only required prop. Integrating with a real uploader means\n * calling your upload API from this callback and driving `uploading` + `progress` externally.\n *\n * @example\n * // Avatar upload with image-only restriction:\n * <FileUpload\n * accept=\"image/*\"\n * maxSize={2 * 1024 * 1024}\n * onFiles={(files) => uploadAvatar(files[0])}\n * label=\"Upload profile photo\"\n * sublabel=\"PNG, JPG up to 2MB\"\n * />\n *\n * @example\n * // Document multi-upload with progress:\n * <FileUpload\n * multiple\n * accept=\".pdf,.doc,.docx\"\n * uploading={isUploading}\n * progress={uploadPercent}\n * onFiles={handleFiles}\n * />\n *\n * @example\n * // Compact inline attachment button in a comment form:\n * <FileUpload compact onFiles={(files) => attachFiles(files)} label=\"Attach files\" />\n * // These are just a few ways — feel free to combine props creatively!\n */\nexport interface FileUploadProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onError'> {\n /** Accepted file types (e.g., \"image/*\", \".pdf,.doc\") */\n accept?: string\n /** Max file size in bytes (default: 10MB) */\n maxSize?: number\n /** Allow multiple files */\n multiple?: boolean\n /** Currently uploading */\n uploading?: boolean\n /** Upload progress (0-100) */\n progress?: number\n /** Callback when files are selected/dropped */\n onFiles: (files: File[]) => void\n /** Error message to display */\n error?: string\n /** Compact mode (inline button instead of drop zone) */\n compact?: boolean\n /** Disabled */\n disabled?: boolean\n /** Custom label */\n label?: string\n /** Custom sublabel */\n sublabel?: string\n}\n\nconst DEFAULT_MAX_SIZE = 10 * 1024 * 1024 // 10MB\n\nfunction fileMatchesAccept(file: File, accept: string): boolean {\n return accept\n .split(',')\n .map((t) => t.trim())\n .some((type) => {\n if (type.startsWith('.'))\n return file.name.toLowerCase().endsWith(type.toLowerCase())\n if (type.endsWith('/*'))\n return file.type.startsWith(type.slice(0, type.indexOf('/') + 1))\n return file.type === type\n })\n}\n\nfunction formatBytes(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\nconst FileUpload = React.forwardRef<HTMLDivElement, FileUploadProps>(\n (\n {\n accept,\n maxSize = DEFAULT_MAX_SIZE,\n multiple = false,\n uploading = false,\n progress = 0,\n onFiles,\n error,\n compact = false,\n disabled = false,\n className,\n label,\n sublabel,\n ...props\n },\n ref,\n ) => {\n const inputRef = React.useRef<HTMLInputElement>(null)\n const [isDragActive, setIsDragActive] = React.useState(false)\n const [validationError, setValidationError] = React.useState<string | null>(\n null,\n )\n // Track whether a file dialog was opened, so we can detect when the\n // browser (or testing library) filters out all files via the accept attribute\n const dialogOpenRef = React.useRef(false)\n const filesAcceptedRef = React.useRef(false)\n\n const displayError = error || validationError\n\n const openPicker = React.useCallback(() => {\n if (disabled || uploading) return\n inputRef.current?.click()\n }, [disabled, uploading])\n\n const validateAndEmit = React.useCallback(\n (files: File[]) => {\n setValidationError(null)\n\n for (const file of files) {\n if (file.size > maxSize) {\n setValidationError(\n `File \"${file.name}\" exceeds the maximum size of ${formatBytes(maxSize)}`,\n )\n return\n }\n if (accept && !fileMatchesAccept(file, accept)) {\n setValidationError(\n `File \"${file.name}\" is not an accepted file type`,\n )\n return\n }\n }\n\n onFiles(files)\n },\n [maxSize, accept, onFiles],\n )\n\n const handleInputChange = React.useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n filesAcceptedRef.current = true\n const fileList = e.target.files\n if (!fileList || fileList.length === 0) return\n validateAndEmit(Array.from(fileList))\n // Reset input value so the same file can be selected again\n e.target.value = ''\n },\n [validateAndEmit],\n )\n\n // When the file input is clicked, the browser opens a dialog and blurs the input.\n // We track this to detect when the dialog closes without selecting valid files.\n const handleInputClick = React.useCallback(() => {\n dialogOpenRef.current = true\n filesAcceptedRef.current = false\n }, [])\n\n // After the file dialog closes, the browser re-focuses the input.\n // If no files were accepted (e.g., the browser/test library filtered by accept),\n // and the accept prop is set, show a validation error.\n const handleInputFocus = React.useCallback(() => {\n if (dialogOpenRef.current && !filesAcceptedRef.current && accept) {\n setValidationError('File type is not accepted')\n }\n dialogOpenRef.current = false\n }, [accept])\n\n const handleDragEnter = React.useCallback(\n (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault()\n e.stopPropagation()\n if (!disabled && !uploading) {\n setIsDragActive(true)\n }\n },\n [disabled, uploading],\n )\n\n const handleDragOver = React.useCallback(\n (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault()\n e.stopPropagation()\n if (!disabled && !uploading) {\n setIsDragActive(true)\n }\n },\n [disabled, uploading],\n )\n\n const handleDragLeave = React.useCallback(\n (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setIsDragActive(false)\n },\n [],\n )\n\n const handleDrop = React.useCallback(\n (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault()\n e.stopPropagation()\n setIsDragActive(false)\n\n if (disabled || uploading) return\n\n const fileList = e.dataTransfer.files\n if (!fileList || fileList.length === 0) return\n validateAndEmit(Array.from(fileList))\n },\n [disabled, uploading, validateAndEmit],\n )\n\n const defaultLabel = label ?? 'Drop files here or click to browse'\n const defaultSublabel = sublabel ?? `Max file size: ${formatBytes(maxSize)}`\n const inputId = React.useId()\n\n if (compact) {\n return (\n <div ref={ref} className={cn('inline-flex flex-col', className)} {...props}>\n <button\n type=\"button\"\n onClick={openPicker}\n disabled={disabled}\n className={cn(\n 'inline-flex items-center gap-ds-02 rounded-control px-ds-03 py-ds-02',\n 'text-ds-sm font-medium',\n 'border border-surface-border-strong',\n 'bg-surface-raised-hover text-surface-fg-muted',\n 'hover:bg-accent-2',\n 'disabled:opacity-action-disabled disabled:cursor-not-allowed',\n 'transition-colors',\n )}\n >\n {uploading ? (\n <Spinner size=\"sm\" />\n ) : (\n <Icon icon={IconPaperclip} size=\"sm\" />\n )}\n {label ?? 'Attach files'}\n </button>\n <input\n ref={inputRef}\n id={inputId}\n type=\"file\"\n className=\"sr-only\"\n style={{ visibility: 'hidden' }}\n aria-hidden=\"true\"\n aria-label={label ?? 'Attach files'}\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n onChange={handleInputChange}\n onClick={handleInputClick}\n onFocus={handleInputFocus}\n tabIndex={-1}\n />\n {displayError && (\n <p\n role=\"alert\"\n aria-live=\"polite\"\n className=\"mt-ds-02 text-ds-xs text-error-11\"\n >\n {displayError}\n </p>\n )}\n </div>\n )\n }\n\n return (\n <div\n ref={ref}\n {...props}\n role=\"presentation\"\n className={cn('flex flex-col', className)}\n data-drag-active={isDragActive ? 'true' : undefined}\n onDragEnter={handleDragEnter}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onDrop={handleDrop}\n >\n <motion.div\n role=\"button\"\n tabIndex={0}\n aria-disabled={disabled || undefined}\n onClick={disabled ? undefined : openPicker}\n onKeyDown={(e: React.KeyboardEvent<HTMLDivElement>) => {\n if (!disabled && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault()\n openPicker()\n }\n }}\n className={cn(\n 'flex flex-col items-center justify-center gap-ds-03 rounded-surface',\n 'border-2 border-dashed p-ds-08',\n 'cursor-pointer',\n 'border-surface-border-strong bg-surface-raised-hover',\n isDragActive &&\n 'border-accent-7 bg-accent-2',\n disabled && 'opacity-action-disabled cursor-not-allowed',\n )}\n animate={{\n scale: isDragActive ? 1.02 : 1,\n }}\n transition={springs.snappy}\n >\n {/* Icon — animates between upload ↔ spinner ↔ checkmark */}\n <AnimatePresence mode=\"wait\">\n {uploading ? (\n <motion.div\n key=\"spinner\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={tweens.fade}\n >\n <Spinner size=\"md\" />\n </motion.div>\n ) : progress === 100 && !error ? (\n <motion.div\n key=\"complete\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={springs.bouncy}\n >\n <Icon icon={IconCheck} size=\"2xl\" className=\"text-success-11\" />\n </motion.div>\n ) : (\n <motion.div\n key=\"upload\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Icon icon={IconUpload} size=\"2xl\" className=\"text-surface-fg-subtle\" />\n </motion.div>\n )}\n </AnimatePresence>\n <span id={inputId + '-label'} className=\"text-ds-sm text-surface-fg-muted\">\n {defaultLabel}\n </span>\n {uploading ? (\n <div className=\"w-full max-w-xs\">\n <div\n role=\"progressbar\"\n aria-valuenow={progress}\n aria-valuemin={0}\n aria-valuemax={100}\n className=\"h-2 w-full overflow-hidden rounded-pill bg-surface-raised-hover\"\n >\n <motion.div\n className=\"h-full rounded-pill bg-accent-9\"\n animate={{ width: `${progress}%` }}\n transition={springs.smooth}\n />\n </div>\n </div>\n ) : (\n <span className=\"text-ds-xs text-surface-fg-subtle\">\n {defaultSublabel}\n </span>\n )}\n </motion.div>\n <input\n ref={inputRef}\n id={inputId}\n type=\"file\"\n className=\"sr-only\"\n style={{ visibility: 'hidden' }}\n aria-hidden=\"true\"\n aria-label={defaultLabel}\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n onChange={handleInputChange}\n onClick={handleInputClick}\n onFocus={handleInputFocus}\n tabIndex={-1}\n />\n {/* Error message with shake animation */}\n <AnimatePresence>\n {displayError && (\n <motion.p\n role=\"alert\"\n className=\"mt-ds-02 text-ds-xs text-error-11\"\n initial={{ opacity: 0, x: 0 }}\n animate={{ opacity: 1, x: [0, -4, 4, -4, 4, 0] }}\n exit={{ opacity: 0 }}\n transition={{ opacity: tweens.fade, x: { type: 'tween', duration: durations.slow01, ease: 'easeOut' } }}\n >\n {displayError}\n </motion.p>\n )}\n </AnimatePresence>\n </div>\n )\n },\n)\nFileUpload.displayName = 'FileUpload'\n\nexport { FileUpload }\n"],"mappings":";;;;;;;;;;AA4EA,IAAM,IAAmB,KAAK,OAAO;AAErC,SAAS,EAAkB,GAAY,GAAyB;CAC9D,OAAO,EACJ,MAAM,GAAG,CAAC,CACV,KAAK,MAAM,EAAE,KAAK,CAAC,CAAC,CACpB,MAAM,MACD,EAAK,WAAW,GAAG,IACd,EAAK,KAAK,YAAY,CAAC,CAAC,SAAS,EAAK,YAAY,CAAC,IACxD,EAAK,SAAS,IAAI,IACb,EAAK,KAAK,WAAW,EAAK,MAAM,GAAG,EAAK,QAAQ,GAAG,IAAI,CAAC,CAAC,IAC3D,EAAK,SAAS,CACtB;AACL;AAEA,SAAS,EAAY,GAAuB;CAG1C,OAFI,IAAQ,OAAa,GAAG,EAAM,MAC9B,IAAQ,OAAO,OAAa,IAAI,IAAQ,KAAA,CAAM,QAAQ,CAAC,EAAE,OACtD,IAAI,KAAS,OAAO,MAAA,CAAO,QAAQ,CAAC,EAAE;AAC/C;AAEA,IAAM,IAAa,EAAM,YAErB,EACE,WACA,aAAU,GACV,cAAW,IACX,eAAY,IACZ,cAAW,GACX,YACA,UACA,aAAU,IACV,cAAW,IACX,cACA,UACA,aACA,GAAG,KAEL,MACG;CACH,IAAM,IAAW,EAAM,OAAyB,IAAI,GAC9C,CAAC,GAAc,KAAmB,EAAM,SAAS,EAAK,GACtD,CAAC,GAAiB,KAAsB,EAAM,SAClD,IACF,GAGM,IAAgB,EAAM,OAAO,EAAK,GAClC,IAAmB,EAAM,OAAO,EAAK,GAErC,IAAe,KAAS,GAExB,IAAa,EAAM,kBAAkB;EACrC,KAAY,KAChB,EAAS,SAAS,MAAM;CAC1B,GAAG,CAAC,GAAU,CAAS,CAAC,GAElB,IAAkB,EAAM,aAC3B,MAAkB;EACjB,EAAmB,IAAI;EAEvB,KAAK,IAAM,KAAQ,GAAO;GACxB,IAAI,EAAK,OAAO,GAAS;IACvB,EACE,SAAS,EAAK,KAAK,gCAAgC,EAAY,CAAO,GACxE;IACA;GACF;GACA,IAAI,KAAU,CAAC,EAAkB,GAAM,CAAM,GAAG;IAC9C,EACE,SAAS,EAAK,KAAK,+BACrB;IACA;GACF;EACF;EAEA,EAAQ,CAAK;CACf,GACA;EAAC;EAAS;EAAQ;CAAO,CAC3B,GAEM,IAAoB,EAAM,aAC7B,MAA2C;EAC1C,EAAiB,UAAU;EAC3B,IAAM,IAAW,EAAE,OAAO;EACtB,CAAC,KAAY,EAAS,WAAW,MACrC,EAAgB,MAAM,KAAK,CAAQ,CAAC,GAEpC,EAAE,OAAO,QAAQ;CACnB,GACA,CAAC,CAAe,CAClB,GAIM,IAAmB,EAAM,kBAAkB;EAE/C,AADA,EAAc,UAAU,IACxB,EAAiB,UAAU;CAC7B,GAAG,CAAC,CAAC,GAKC,IAAmB,EAAM,kBAAkB;EAI/C,AAHI,EAAc,WAAW,CAAC,EAAiB,WAAW,KACxD,EAAmB,2BAA2B,GAEhD,EAAc,UAAU;CAC1B,GAAG,CAAC,CAAM,CAAC,GAEL,IAAkB,EAAM,aAC3B,MAAuC;EAGtC,AAFA,EAAE,eAAe,GACjB,EAAE,gBAAgB,GACd,CAAC,KAAY,CAAC,KAChB,EAAgB,EAAI;CAExB,GACA,CAAC,GAAU,CAAS,CACtB,GAEM,IAAiB,EAAM,aAC1B,MAAuC;EAGtC,AAFA,EAAE,eAAe,GACjB,EAAE,gBAAgB,GACd,CAAC,KAAY,CAAC,KAChB,EAAgB,EAAI;CAExB,GACA,CAAC,GAAU,CAAS,CACtB,GAEM,IAAkB,EAAM,aAC3B,MAAuC;EAGtC,AAFA,EAAE,eAAe,GACjB,EAAE,gBAAgB,GAClB,EAAgB,EAAK;CACvB,GACA,CAAC,CACH,GAEM,IAAa,EAAM,aACtB,MAAuC;EAKtC,IAJA,EAAE,eAAe,GACjB,EAAE,gBAAgB,GAClB,EAAgB,EAAK,GAEjB,KAAY,GAAW;EAE3B,IAAM,IAAW,EAAE,aAAa;EAC5B,CAAC,KAAY,EAAS,WAAW,KACrC,EAAgB,MAAM,KAAK,CAAQ,CAAC;CACtC,GACA;EAAC;EAAU;EAAW;CAAe,CACvC,GAEM,IAAe,KAAS,sCACxB,IAAkB,KAAY,kBAAkB,EAAY,CAAO,KACnE,IAAU,EAAM,MAAM;CAuD5B,OArDI,IAEA,kBAAC,OAAD;EAAU;EAAK,WAAW,EAAG,wBAAwB,CAAS;EAAG,GAAI;YAArE;GACE,kBAAC,UAAD;IACE,MAAK;IACL,SAAS;IACC;IACV,WAAW,EACT,wEACA,0BACA,uCACA,iDACA,qBACA,gEACA,mBACF;cAZF,CAcG,IACC,kBAAC,GAAD,EAAS,MAAK,KAAM,CAAA,IAEpB,kBAAC,GAAD;KAAM,MAAM;KAAe,MAAK;IAAM,CAAA,GAEvC,KAAS,cACJ;;GACR,kBAAC,SAAD;IACE,KAAK;IACL,IAAI;IACJ,MAAK;IACL,WAAU;IACV,OAAO,EAAE,YAAY,SAAS;IAC9B,eAAY;IACZ,cAAY,KAAS;IACb;IACE;IACA;IACV,UAAU;IACV,SAAS;IACT,SAAS;IACT,UAAU;GACX,CAAA;GACA,KACC,kBAAC,KAAD;IACE,MAAK;IACL,aAAU;IACV,WAAU;cAET;GACA,CAAA;EAEF;MAKP,kBAAC,OAAD;EACO;EACL,GAAI;EACJ,MAAK;EACL,WAAW,EAAG,iBAAiB,CAAS;EACxC,oBAAkB,IAAe,SAAS,KAAA;EAC1C,aAAa;EACb,YAAY;EACZ,aAAa;EACb,QAAQ;YATV;GAWE,kBAAC,EAAO,KAAR;IACE,MAAK;IACL,UAAU;IACV,iBAAe,KAAY,KAAA;IAC3B,SAAS,IAAW,KAAA,IAAY;IAChC,YAAY,MAA2C;KACrD,AAAI,CAAC,MAAa,EAAE,QAAQ,WAAW,EAAE,QAAQ,SAC/C,EAAE,eAAe,GACjB,EAAW;IAEf;IACA,WAAW,EACT,uEACA,kCACA,kBACA,wDACA,KACE,+BACF,KAAY,4CACd;IACA,SAAS,EACP,OAAO,IAAe,OAAO,EAC/B;IACA,YAAY,EAAQ;cAvBtB;KA0BE,kBAAC,GAAD;MAAiB,MAAK;gBACnB,IACC,kBAAC,EAAO,KAAR;OAEE,SAAS;QAAE,SAAS;QAAG,OAAO;OAAI;OAClC,SAAS;QAAE,SAAS;QAAG,OAAO;OAAE;OAChC,MAAM;QAAE,SAAS;QAAG,OAAO;OAAI;OAC/B,YAAY,EAAO;iBAEnB,kBAAC,GAAD,EAAS,MAAK,KAAM,CAAA;MACV,GAPN,SAOM,IACV,MAAa,OAAO,CAAC,IACvB,kBAAC,EAAO,KAAR;OAEE,SAAS;QAAE,SAAS;QAAG,OAAO;OAAI;OAClC,SAAS;QAAE,SAAS;QAAG,OAAO;OAAE;OAChC,MAAM;QAAE,SAAS;QAAG,OAAO;OAAI;OAC/B,YAAY,EAAQ;iBAEpB,kBAAC,GAAD;QAAM,MAAM;QAAW,MAAK;QAAM,WAAU;OAAmB,CAAA;MACrD,GAPN,UAOM,IAEZ,kBAAC,EAAO,KAAR;OAEE,SAAS,EAAE,SAAS,EAAE;OACtB,SAAS,EAAE,SAAS,EAAE;OACtB,MAAM,EAAE,SAAS,EAAE;OACnB,YAAY,EAAO;iBAEnB,kBAAC,GAAD;QAAM,MAAM;QAAY,MAAK;QAAM,WAAU;OAA0B,CAAA;MAC7D,GAPN,QAOM;KAEC,CAAA;KACjB,kBAAC,QAAD;MAAM,IAAI,IAAU;MAAU,WAAU;gBACrC;KACG,CAAA;KACL,IACC,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,OAAD;OACE,MAAK;OACL,iBAAe;OACf,iBAAe;OACf,iBAAe;OACf,WAAU;iBAEV,kBAAC,EAAO,KAAR;QACE,WAAU;QACV,SAAS,EAAE,OAAO,GAAG,EAAS,GAAG;QACjC,YAAY,EAAQ;OACrB,CAAA;MACE,CAAA;KACF,CAAA,IAEL,kBAAC,QAAD;MAAM,WAAU;gBACb;KACG,CAAA;IAEE;;GACZ,kBAAC,SAAD;IACE,KAAK;IACL,IAAI;IACJ,MAAK;IACL,WAAU;IACV,OAAO,EAAE,YAAY,SAAS;IAC9B,eAAY;IACZ,cAAY;IACJ;IACE;IACA;IACV,UAAU;IACV,SAAS;IACT,SAAS;IACT,UAAU;GACX,CAAA;GAED,kBAAC,GAAD,EAAA,UACG,KACC,kBAAC,EAAO,GAAR;IACE,MAAK;IACL,WAAU;IACV,SAAS;KAAE,SAAS;KAAG,GAAG;IAAE;IAC5B,SAAS;KAAE,SAAS;KAAG,GAAG;MAAC;MAAG;MAAI;MAAG;MAAI;MAAG;KAAC;IAAE;IAC/C,MAAM,EAAE,SAAS,EAAE;IACnB,YAAY;KAAE,SAAS,EAAO;KAAM,GAAG;MAAE,MAAM;MAAS,UAAU,EAAU;MAAQ,MAAM;KAAU;IAAE;cAErG;GACO,CAAA,EAEG,CAAA;EACd;;AAET,CACF;AACA,EAAW,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"form.js","names":[],"sources":["../../src/ui/form.tsx"],"sourcesContent":["'use client'\n\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { motionProps, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\n\nexport type FormHelperState = 'helper' | 'error' | 'warning' | 'success'\n\ninterface FormFieldContextValue {\n state?: FormHelperState\n helperTextId?: string\n /** Auto-generated id shared between Label (htmlFor) and the field input (id). */\n inputId?: string\n required?: boolean\n}\nconst FormFieldContext = React.createContext<FormFieldContextValue>({})\n\n/**\n * FormField — vertical flex container that provides validation state and\n * aria-describedby wiring to children via React context.\n *\n * **Automatic a11y wiring:** FormField generates a stable ID (or uses a provided `helperTextId`)\n * and passes it to `FormHelperText` via context. Use the `useFormField()` hook in custom inputs\n * to read `{ state, helperTextId, required }` and spread onto your input element.\n *\n * @example\n * <FormField state=\"error\">\n * <Label htmlFor=\"email\">Email</Label>\n * <Input id=\"email\" state=\"error\" />\n * <FormHelperText>Please enter a valid email address.</FormHelperText>\n * </FormField>\n */\nexport interface FormFieldProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Custom ID for the helper text element. If omitted, an auto-generated ID is used.\n * Pass this to `useFormField()` consumers that need to set `aria-describedby`.\n */\n helperTextId?: string\n /**\n * Custom ID for the field input element. If omitted, an auto-generated ID is used.\n * Label (when inside FormField) reads this via `htmlFor`; Input reads it via `id`.\n * Explicit `htmlFor` / `id` props on children always win.\n */\n inputId?: string\n /** Current validation state — propagated to child `FormHelperText` via context */\n state?: FormHelperState\n /** Whether the field is required — available to children via `useFormField()` */\n required?: boolean\n}\n\nconst FormField = React.forwardRef<HTMLDivElement, FormFieldProps>(\n ({ className, helperTextId, inputId, state = 'helper', required, children, ...props }, ref) => {\n const autoId = React.useId()\n const resolvedHelperId = helperTextId || `${autoId}-helper`\n const resolvedInputId = inputId || `${autoId}-input`\n const contextValue = React.useMemo(\n () => ({ state, helperTextId: resolvedHelperId, inputId: resolvedInputId, required }),\n [state, resolvedHelperId, resolvedInputId, required],\n )\n\n return (\n <FormFieldContext.Provider value={contextValue}>\n <div\n ref={ref}\n className={cn('flex flex-col gap-ds-02', className)}\n {...props}\n >\n {children}\n </div>\n </FormFieldContext.Provider>\n )\n },\n)\nFormField.displayName = 'FormField'\n\n// FormHelperText — small helper/error/warning/success message under a field\n//\n// When used for errors, set an `id` matching the input's `aria-describedby`.\nexport interface FormHelperTextProps extends React.HTMLAttributes<HTMLParagraphElement> {\n state?: FormHelperState\n}\n\nconst helperStateClasses: Record<FormHelperState, string> = {\n helper: 'text-surface-fg-subtle',\n error: 'text-error-11',\n warning: 'text-warning-11',\n success: 'text-success-11',\n}\n\nconst FormHelperText = React.forwardRef<HTMLParagraphElement, FormHelperTextProps>(\n ({ className, id: idProp, state: stateProp, ...props }, ref) => {\n const context = React.useContext(FormFieldContext)\n const state = stateProp ?? context.state ?? 'helper'\n const id = idProp ?? context.helperTextId\n\n return (\n <motion.p\n ref={ref}\n id={id}\n role={state === 'error' ? 'alert' : undefined}\n className={cn('text-ds-sm', helperStateClasses[state], className)}\n initial={{ opacity: 0, y: -4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -4 }}\n transition={tweens.fade}\n {...motionProps(props)}\n />\n )\n },\n)\nFormHelperText.displayName = 'FormHelperText'\n\n/**\n * Hook to read FormField context — returns `{ state, helperTextId, required }`.\n * Use in custom input components to auto-wire `aria-describedby` and `aria-invalid`.\n *\n * @example\n * function MyCustomInput(props) {\n * const { state, helperTextId, required } = useFormField()\n * return (\n * <input\n * aria-describedby={helperTextId}\n * aria-invalid={state === 'error' || undefined}\n * aria-required={required}\n * {...props}\n * />\n * )\n * }\n */\nexport function useFormField(): FormFieldContextValue {\n return React.useContext(FormFieldContext)\n}\n\nexport { FormField, FormFieldContext, FormHelperText }\n"],"mappings":";;;;;;;AAiBA,IAAM,IAAmB,EAAM,cAAqC,EAAE,CAAC,EAmCjE,IAAY,EAAM,YACrB,EAAE,cAAW,iBAAc,YAAS,WAAQ,UAAU,aAAU,aAAU,GAAG,KAAS,MAAQ;CAC7F,IAAM,IAAS,EAAM,OAAO,EACtB,IAAmB,KAAgB,GAAG,EAAO,UAC7C,IAAkB,KAAW,GAAG,EAAO,SACvC,IAAe,EAAM,eAClB;EAAE;EAAO,cAAc;EAAkB,SAAS;EAAiB;EAAU,GACpF;EAAC;EAAO;EAAkB;EAAiB;EAAS,CACrD;AAED,QACE,kBAAC,EAAiB,UAAlB;EAA2B,OAAO;YAChC,kBAAC,OAAD;GACO;GACL,WAAW,EAAG,2BAA2B,EAAU;GACnD,GAAI;GAEH;GACG,CAAA;EACoB,CAAA;EAGjC;AACD,EAAU,cAAc;AASxB,IAAM,IAAsD;CAC1D,QAAS;CACT,OAAS;CACT,SAAS;CACT,SAAS;CACV,EAEK,IAAiB,EAAM,YAC1B,EAAE,cAAW,IAAI,GAAQ,OAAO,GAAW,GAAG,KAAS,MAAQ;CAC9D,IAAM,IAAU,EAAM,WAAW,EAAiB,EAC5C,IAAQ,KAAa,EAAQ,SAAS,UACtC,IAAK,KAAU,EAAQ;AAE7B,QACE,kBAAC,EAAO,GAAR;EACO;EACD;EACJ,MAAM,MAAU,UAAU,UAAU,KAAA;EACpC,WAAW,EAAG,cAAc,EAAmB,IAAQ,EAAU;EACjE,SAAS;GAAE,SAAS;GAAG,GAAG;GAAI;EAC9B,SAAS;GAAE,SAAS;GAAG,GAAG;GAAG;EAC7B,MAAM;GAAE,SAAS;GAAG,GAAG;GAAI;EAC3B,YAAY,EAAO;EACnB,GAAI,EAAY,EAAM;EACtB,CAAA;EAGP;AACD,EAAe,cAAc;AAmB7B,SAAgB,IAAsC;AACpD,QAAO,EAAM,WAAW,EAAiB"}
1
+ {"version":3,"file":"form.js","names":[],"sources":["../../src/ui/form.tsx"],"sourcesContent":["'use client'\n\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { motionProps, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\n\nexport type FormHelperState = 'helper' | 'error' | 'warning' | 'success'\n\ninterface FormFieldContextValue {\n state?: FormHelperState\n helperTextId?: string\n /** Auto-generated id shared between Label (htmlFor) and the field input (id). */\n inputId?: string\n required?: boolean\n}\nconst FormFieldContext = React.createContext<FormFieldContextValue>({})\n\n/**\n * FormField — vertical flex container that provides validation state and\n * aria-describedby wiring to children via React context.\n *\n * **Automatic a11y wiring:** FormField generates a stable ID (or uses a provided `helperTextId`)\n * and passes it to `FormHelperText` via context. Use the `useFormField()` hook in custom inputs\n * to read `{ state, helperTextId, required }` and spread onto your input element.\n *\n * @example\n * <FormField state=\"error\">\n * <Label htmlFor=\"email\">Email</Label>\n * <Input id=\"email\" state=\"error\" />\n * <FormHelperText>Please enter a valid email address.</FormHelperText>\n * </FormField>\n */\nexport interface FormFieldProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Custom ID for the helper text element. If omitted, an auto-generated ID is used.\n * Pass this to `useFormField()` consumers that need to set `aria-describedby`.\n */\n helperTextId?: string\n /**\n * Custom ID for the field input element. If omitted, an auto-generated ID is used.\n * Label (when inside FormField) reads this via `htmlFor`; Input reads it via `id`.\n * Explicit `htmlFor` / `id` props on children always win.\n */\n inputId?: string\n /** Current validation state — propagated to child `FormHelperText` via context */\n state?: FormHelperState\n /** Whether the field is required — available to children via `useFormField()` */\n required?: boolean\n}\n\nconst FormField = React.forwardRef<HTMLDivElement, FormFieldProps>(\n ({ className, helperTextId, inputId, state = 'helper', required, children, ...props }, ref) => {\n const autoId = React.useId()\n const resolvedHelperId = helperTextId || `${autoId}-helper`\n const resolvedInputId = inputId || `${autoId}-input`\n const contextValue = React.useMemo(\n () => ({ state, helperTextId: resolvedHelperId, inputId: resolvedInputId, required }),\n [state, resolvedHelperId, resolvedInputId, required],\n )\n\n return (\n <FormFieldContext.Provider value={contextValue}>\n <div\n ref={ref}\n className={cn('flex flex-col gap-ds-02', className)}\n {...props}\n >\n {children}\n </div>\n </FormFieldContext.Provider>\n )\n },\n)\nFormField.displayName = 'FormField'\n\n// FormHelperText — small helper/error/warning/success message under a field\n//\n// When used for errors, set an `id` matching the input's `aria-describedby`.\nexport interface FormHelperTextProps extends React.HTMLAttributes<HTMLParagraphElement> {\n state?: FormHelperState\n}\n\nconst helperStateClasses: Record<FormHelperState, string> = {\n helper: 'text-surface-fg-subtle',\n error: 'text-error-11',\n warning: 'text-warning-11',\n success: 'text-success-11',\n}\n\nconst FormHelperText = React.forwardRef<HTMLParagraphElement, FormHelperTextProps>(\n ({ className, id: idProp, state: stateProp, ...props }, ref) => {\n const context = React.useContext(FormFieldContext)\n const state = stateProp ?? context.state ?? 'helper'\n const id = idProp ?? context.helperTextId\n\n return (\n <motion.p\n ref={ref}\n id={id}\n role={state === 'error' ? 'alert' : undefined}\n className={cn('text-ds-sm', helperStateClasses[state], className)}\n initial={{ opacity: 0, y: -4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -4 }}\n transition={tweens.fade}\n {...motionProps(props)}\n />\n )\n },\n)\nFormHelperText.displayName = 'FormHelperText'\n\n/**\n * Hook to read FormField context — returns `{ state, helperTextId, required }`.\n * Use in custom input components to auto-wire `aria-describedby` and `aria-invalid`.\n *\n * @example\n * function MyCustomInput(props) {\n * const { state, helperTextId, required } = useFormField()\n * return (\n * <input\n * aria-describedby={helperTextId}\n * aria-invalid={state === 'error' || undefined}\n * aria-required={required}\n * {...props}\n * />\n * )\n * }\n */\nexport function useFormField(): FormFieldContextValue {\n return React.useContext(FormFieldContext)\n}\n\nexport { FormField, FormFieldContext, FormHelperText }\n"],"mappings":";;;;;;;AAiBA,IAAM,IAAmB,EAAM,cAAqC,CAAC,CAAC,GAmChE,IAAY,EAAM,YACrB,EAAE,cAAW,iBAAc,YAAS,WAAQ,UAAU,aAAU,aAAU,GAAG,KAAS,MAAQ;CAC7F,IAAM,IAAS,EAAM,MAAM,GACrB,IAAmB,KAAgB,GAAG,EAAO,UAC7C,IAAkB,KAAW,GAAG,EAAO,SACvC,IAAe,EAAM,eAClB;EAAE;EAAO,cAAc;EAAkB,SAAS;EAAiB;CAAS,IACnF;EAAC;EAAO;EAAkB;EAAiB;CAAQ,CACrD;CAEA,OACE,kBAAC,EAAiB,UAAlB;EAA2B,OAAO;YAChC,kBAAC,OAAD;GACO;GACL,WAAW,EAAG,2BAA2B,CAAS;GAClD,GAAI;GAEH;EACE,CAAA;CACoB,CAAA;AAE/B,CACF;AACA,EAAU,cAAc;AASxB,IAAM,IAAsD;CAC1D,QAAS;CACT,OAAS;CACT,SAAS;CACT,SAAS;AACX,GAEM,IAAiB,EAAM,YAC1B,EAAE,cAAW,IAAI,GAAQ,OAAO,GAAW,GAAG,KAAS,MAAQ;CAC9D,IAAM,IAAU,EAAM,WAAW,CAAgB,GAC3C,IAAQ,KAAa,EAAQ,SAAS,UACtC,IAAK,KAAU,EAAQ;CAE7B,OACE,kBAAC,EAAO,GAAR;EACO;EACD;EACJ,MAAM,MAAU,UAAU,UAAU,KAAA;EACpC,WAAW,EAAG,cAAc,EAAmB,IAAQ,CAAS;EAChE,SAAS;GAAE,SAAS;GAAG,GAAG;EAAG;EAC7B,SAAS;GAAE,SAAS;GAAG,GAAG;EAAE;EAC5B,MAAM;GAAE,SAAS;GAAG,GAAG;EAAG;EAC1B,YAAY,EAAO;EACnB,GAAI,EAAY,CAAK;CACtB,CAAA;AAEL,CACF;AACA,EAAe,cAAc;AAmB7B,SAAgB,IAAsC;CACpD,OAAO,EAAM,WAAW,CAAgB;AAC1C"}
@@ -1 +1 @@
1
- {"version":3,"file":"hover-card.js","names":[],"sources":["../../src/ui/hover-card.tsx"],"sourcesContent":["'use client'\n\nimport * as HoverCardPrimitive from '@primitives/react-hover-card'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { springs, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\n\n// ── Internal context to thread `open` state to animated children ──\n\nconst HoverCardOpenContext = React.createContext(false)\n\n/**\n * HoverCard displays supplementary content on pointer hover.\n *\n * **Accessibility note:** HoverCard is pointer-only by design (WAI-ARIA HoverCard has no APG pattern).\n * Do NOT use for essential content that keyboard users must access.\n * For essential content, use {@link Popover} instead which supports keyboard activation.\n */\nconst HoverCard: React.FC<React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Root>> = ({\n open: controlledOpen,\n defaultOpen = false,\n onOpenChange,\n ...props\n}) => {\n const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)\n const isControlled = controlledOpen !== undefined\n const open = isControlled ? controlledOpen : uncontrolledOpen\n\n const handleOpenChange = React.useCallback(\n (value: boolean) => {\n if (!isControlled) setUncontrolledOpen(value)\n onOpenChange?.(value)\n },\n [isControlled, onOpenChange],\n )\n\n return (\n <HoverCardOpenContext.Provider value={open}>\n <HoverCardPrimitive.Root open={open} onOpenChange={handleOpenChange} {...props} />\n </HoverCardOpenContext.Provider>\n )\n}\nHoverCard.displayName = 'HoverCard'\n\nconst HoverCardTrigger = HoverCardPrimitive.Trigger\n\nconst HoverCardContent = React.forwardRef<\n React.ElementRef<typeof HoverCardPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>\n>(({ className, align = 'center', sideOffset = 4, children, ...props }, ref) => {\n const open = React.useContext(HoverCardOpenContext)\n\n return (\n <AnimatePresence>\n {open && (\n <HoverCardPrimitive.Portal forceMount>\n <HoverCardPrimitive.Content\n ref={ref}\n align={align}\n sideOffset={sideOffset}\n forceMount\n asChild\n {...props}\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={{ ...springs.snappy, opacity: tweens.fade }}\n className={cn(\n 'z-popover w-64 rounded-overlay bg-surface-overlay p-ds-05 shadow-floating outline-hidden',\n className,\n )}\n >\n {children}\n </motion.div>\n </HoverCardPrimitive.Content>\n </HoverCardPrimitive.Portal>\n )}\n </AnimatePresence>\n )\n})\nHoverCardContent.displayName = HoverCardPrimitive.Content.displayName\n\nexport type HoverCardContentProps = React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>\n\nexport { HoverCard, HoverCardContent,HoverCardTrigger }\n"],"mappings":";;;;;;;;AAWA,IAAM,IAAuB,EAAM,cAAc,GAAM,EASjD,KAAuF,EAC3F,MAAM,GACN,iBAAc,IACd,iBACA,GAAG,QACC;CACJ,IAAM,CAAC,GAAkB,KAAuB,EAAM,SAAS,EAAY,EACrE,IAAe,MAAmB,KAAA,GAClC,IAAO,IAAe,IAAiB,GAEvC,IAAmB,EAAM,aAC5B,MAAmB;AAElB,EADK,KAAc,EAAoB,EAAM,EAC7C,IAAe,EAAM;IAEvB,CAAC,GAAc,EAAa,CAC7B;AAED,QACE,kBAAC,EAAqB,UAAtB;EAA+B,OAAO;YACpC,kBAAC,GAAD;GAA+B;GAAM,cAAc;GAAkB,GAAI;GAAS,CAAA;EACpD,CAAA;;AAGpC,EAAU,cAAc;AAExB,IAAM,IAAmB,GAEnB,IAAmB,EAAM,YAG5B,EAAE,cAAW,WAAQ,UAAU,gBAAa,GAAG,aAAU,GAAG,KAAS,MAIpE,kBAAC,GAAD,EAAA,UAHW,EAAM,WAAW,EAAqB,IAK7C,kBAAC,GAAD;CAA2B,YAAA;WACzB,kBAAC,GAAD;EACO;EACE;EACK;EACZ,YAAA;EACA,SAAA;EACA,GAAI;YAEJ,kBAAC,EAAO,KAAR;GACE,SAAS;IAAE,SAAS;IAAG,OAAO;IAAM;GACpC,SAAS;IAAE,SAAS;IAAG,OAAO;IAAG;GACjC,MAAM;IAAE,SAAS;IAAG,OAAO;IAAM;GACjC,YAAY;IAAE,GAAG,EAAQ;IAAQ,SAAS,EAAO;IAAM;GACvD,WAAW,EACT,4FACA,EACD;GAEA;GACU,CAAA;EACc,CAAA;CACH,CAAA,EAEd,CAAA,CAEpB;AACF,EAAiB,cAAA,EAAyC"}
1
+ {"version":3,"file":"hover-card.js","names":[],"sources":["../../src/ui/hover-card.tsx"],"sourcesContent":["'use client'\n\nimport * as HoverCardPrimitive from '@primitives/react-hover-card'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { springs, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\n\n// ── Internal context to thread `open` state to animated children ──\n\nconst HoverCardOpenContext = React.createContext(false)\n\n/**\n * HoverCard displays supplementary content on pointer hover.\n *\n * **Accessibility note:** HoverCard is pointer-only by design (WAI-ARIA HoverCard has no APG pattern).\n * Do NOT use for essential content that keyboard users must access.\n * For essential content, use {@link Popover} instead which supports keyboard activation.\n */\nconst HoverCard: React.FC<React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Root>> = ({\n open: controlledOpen,\n defaultOpen = false,\n onOpenChange,\n ...props\n}) => {\n const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)\n const isControlled = controlledOpen !== undefined\n const open = isControlled ? controlledOpen : uncontrolledOpen\n\n const handleOpenChange = React.useCallback(\n (value: boolean) => {\n if (!isControlled) setUncontrolledOpen(value)\n onOpenChange?.(value)\n },\n [isControlled, onOpenChange],\n )\n\n return (\n <HoverCardOpenContext.Provider value={open}>\n <HoverCardPrimitive.Root open={open} onOpenChange={handleOpenChange} {...props} />\n </HoverCardOpenContext.Provider>\n )\n}\nHoverCard.displayName = 'HoverCard'\n\nconst HoverCardTrigger = HoverCardPrimitive.Trigger\n\nconst HoverCardContent = React.forwardRef<\n React.ElementRef<typeof HoverCardPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>\n>(({ className, align = 'center', sideOffset = 4, children, ...props }, ref) => {\n const open = React.useContext(HoverCardOpenContext)\n\n return (\n <AnimatePresence>\n {open && (\n <HoverCardPrimitive.Portal forceMount>\n <HoverCardPrimitive.Content\n ref={ref}\n align={align}\n sideOffset={sideOffset}\n forceMount\n asChild\n {...props}\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={{ ...springs.snappy, opacity: tweens.fade }}\n className={cn(\n 'z-popover w-64 rounded-overlay bg-surface-overlay p-ds-05 shadow-floating outline-hidden',\n className,\n )}\n >\n {children}\n </motion.div>\n </HoverCardPrimitive.Content>\n </HoverCardPrimitive.Portal>\n )}\n </AnimatePresence>\n )\n})\nHoverCardContent.displayName = HoverCardPrimitive.Content.displayName\n\nexport type HoverCardContentProps = React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>\n\nexport { HoverCard, HoverCardContent,HoverCardTrigger }\n"],"mappings":";;;;;;;;AAWA,IAAM,IAAuB,EAAM,cAAc,EAAK,GAShD,KAAuF,EAC3F,MAAM,GACN,iBAAc,IACd,iBACA,GAAG,QACC;CACJ,IAAM,CAAC,GAAkB,KAAuB,EAAM,SAAS,CAAW,GACpE,IAAe,MAAmB,KAAA,GAClC,IAAO,IAAe,IAAiB,GAEvC,IAAmB,EAAM,aAC5B,MAAmB;EAElB,AADK,KAAc,EAAoB,CAAK,GAC5C,IAAe,CAAK;CACtB,GACA,CAAC,GAAc,CAAY,CAC7B;CAEA,OACE,kBAAC,EAAqB,UAAtB;EAA+B,OAAO;YACpC,kBAAC,GAAD;GAA+B;GAAM,cAAc;GAAkB,GAAI;EAAQ,CAAA;CACpD,CAAA;AAEnC;AACA,EAAU,cAAc;AAExB,IAAM,IAAmB,GAEnB,IAAmB,EAAM,YAG5B,EAAE,cAAW,WAAQ,UAAU,gBAAa,GAAG,aAAU,GAAG,KAAS,MAIpE,kBAAC,GAAD,EAAA,UAHW,EAAM,WAAW,CAIzB,KACC,kBAAC,GAAD;CAA2B,YAAA;WACzB,kBAAC,GAAD;EACO;EACE;EACK;EACZ,YAAA;EACA,SAAA;EACA,GAAI;YAEJ,kBAAC,EAAO,KAAR;GACE,SAAS;IAAE,SAAS;IAAG,OAAO;GAAK;GACnC,SAAS;IAAE,SAAS;IAAG,OAAO;GAAE;GAChC,MAAM;IAAE,SAAS;IAAG,OAAO;GAAK;GAChC,YAAY;IAAE,GAAG,EAAQ;IAAQ,SAAS,EAAO;GAAK;GACtD,WAAW,EACT,4FACA,CACF;GAEC;EACS,CAAA;CACc,CAAA;AACH,CAAA,EAEd,CAAA,CAEpB;AACD,EAAiB,cAAA,EAAyC"}
@@ -1 +1 @@
1
- {"version":3,"file":"icon-button.js","names":[],"sources":["../../src/ui/icon-button.tsx"],"sourcesContent":["'use client'\n\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'\n\n/** Map friendly sizes to icon-* sizes for the underlying Button */\nconst sizeMap = {\n sm: 'icon-sm',\n md: 'icon-md',\n lg: 'icon-lg',\n} as const\n\ntype IconButtonSize = 'sm' | 'md' | 'lg'\n\n/**\n * Props for IconButton — a square or circular icon-only button built on top of `<Button>`,\n * with `aria-label` required for WCAG AA compliance.\n *\n * **`aria-label` is required** — icon-only buttons have no visible text, so a descriptive label\n * is mandatory for screen readers. TypeScript enforces this.\n *\n * **Shape:** `'square'` (default, rounded corners) | `'circle'` (fully round, great for avatar-adjacent actions)\n *\n * **Size:** `'sm'` | `'md'` (default) | `'lg'` — maps to `icon-sm/md/lg` sizes internally.\n *\n * **All `Button` variants** are supported: `solid`, `outline`, `ghost`, `link` x `default`, `error`.\n *\n * @example\n * // Ghost toolbar icon button:\n * <IconButton icon={<Icon icon={IconEdit} />} variant=\"ghost\" aria-label=\"Edit item\" />\n *\n * @example\n * // Circular close button (e.g. in a modal header):\n * <IconButton icon={<Icon icon={IconX} />} shape=\"circle\" variant=\"ghost\" size=\"sm\" aria-label=\"Close dialog\" />\n *\n * @example\n * // Solid floating action button:\n * <IconButton icon={<Icon icon={IconPlus} />} variant=\"solid\" size=\"lg\" aria-label=\"Create new project\" />\n *\n * @example\n * // Error icon button with loading state (e.g. confirm delete):\n * <IconButton icon={<Icon icon={IconTrash} />} variant=\"solid\" color=\"error\" loading={isDeleting} aria-label=\"Delete\" />\n * // These are just a few ways — feel free to combine props creatively!\n */\nexport interface IconButtonProps\n extends Omit<ButtonProps, 'startIcon' | 'endIcon' | 'fullWidth' | 'loadingPosition' | 'children' | 'size' | 'shape'> {\n /**\n * The icon to render. Accepts any `IconInput`:\n * - `<Icon icon={IconPlus} />` (canonical — size flows from context)\n * - `<IconPlus />` (raw Tabler element — passes through)\n * - `IconPlus` (Tabler component ref — auto-wrapped in `<Icon>`)\n * - Custom `<span>$</span>` or any ReactElement.\n */\n icon: IconInput\n /** Accessible label — required for icon-only buttons (WCAG AA) */\n 'aria-label': string\n /** Button shape. Default: 'square' */\n shape?: 'square' | 'circle'\n /** Button size. Default: 'md' */\n size?: IconButtonSize\n}\n\nconst IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>(\n ({ icon, shape = 'square', size = 'md', className, loading, ...props }, ref) => {\n return (\n <Button\n ref={ref}\n size={sizeMap[size]}\n className={cn(shape === 'circle' && 'rounded-pill', className)}\n loading={loading}\n loadingPosition=\"center\"\n {...props}\n >\n {normalizeIcon(icon)}\n </Button>\n )\n },\n)\nIconButton.displayName = 'IconButton'\n\nexport { IconButton }\n"],"mappings":";;;;;;;AAUA,IAAM,IAAU;CACd,IAAI;CACJ,IAAI;CACJ,IAAI;CACL,EAoDK,IAAa,EAAM,YACtB,EAAE,SAAM,WAAQ,UAAU,UAAO,MAAM,cAAW,YAAS,GAAG,KAAS,MAEpE,kBAAC,GAAD;CACO;CACL,MAAM,EAAQ;CACd,WAAW,EAAG,MAAU,YAAY,gBAAgB,EAAU;CACrD;CACT,iBAAgB;CAChB,GAAI;WAEH,EAAc,EAAK;CACb,CAAA,CAGd;AACD,EAAW,cAAc"}
1
+ {"version":3,"file":"icon-button.js","names":[],"sources":["../../src/ui/icon-button.tsx"],"sourcesContent":["'use client'\n\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'\n\n/** Map friendly sizes to icon-* sizes for the underlying Button */\nconst sizeMap = {\n sm: 'icon-sm',\n md: 'icon-md',\n lg: 'icon-lg',\n} as const\n\ntype IconButtonSize = 'sm' | 'md' | 'lg'\n\n/**\n * Props for IconButton — a square or circular icon-only button built on top of `<Button>`,\n * with `aria-label` required for WCAG AA compliance.\n *\n * **`aria-label` is required** — icon-only buttons have no visible text, so a descriptive label\n * is mandatory for screen readers. TypeScript enforces this.\n *\n * **Shape:** `'square'` (default, rounded corners) | `'circle'` (fully round, great for avatar-adjacent actions)\n *\n * **Size:** `'sm'` | `'md'` (default) | `'lg'` — maps to `icon-sm/md/lg` sizes internally.\n *\n * **All `Button` variants** are supported: `solid`, `outline`, `ghost`, `link` x `default`, `error`.\n *\n * @example\n * // Ghost toolbar icon button:\n * <IconButton icon={<Icon icon={IconEdit} />} variant=\"ghost\" aria-label=\"Edit item\" />\n *\n * @example\n * // Circular close button (e.g. in a modal header):\n * <IconButton icon={<Icon icon={IconX} />} shape=\"circle\" variant=\"ghost\" size=\"sm\" aria-label=\"Close dialog\" />\n *\n * @example\n * // Solid floating action button:\n * <IconButton icon={<Icon icon={IconPlus} />} variant=\"solid\" size=\"lg\" aria-label=\"Create new project\" />\n *\n * @example\n * // Error icon button with loading state (e.g. confirm delete):\n * <IconButton icon={<Icon icon={IconTrash} />} variant=\"solid\" color=\"error\" loading={isDeleting} aria-label=\"Delete\" />\n * // These are just a few ways — feel free to combine props creatively!\n */\nexport interface IconButtonProps\n extends Omit<ButtonProps, 'startIcon' | 'endIcon' | 'fullWidth' | 'loadingPosition' | 'children' | 'size' | 'shape'> {\n /**\n * The icon to render. Accepts any `IconInput`:\n * - `<Icon icon={IconPlus} />` (canonical — size flows from context)\n * - `<IconPlus />` (raw Tabler element — passes through)\n * - `IconPlus` (Tabler component ref — auto-wrapped in `<Icon>`)\n * - Custom `<span>$</span>` or any ReactElement.\n */\n icon: IconInput\n /** Accessible label — required for icon-only buttons (WCAG AA) */\n 'aria-label': string\n /** Button shape. Default: 'square' */\n shape?: 'square' | 'circle'\n /** Button size. Default: 'md' */\n size?: IconButtonSize\n}\n\nconst IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>(\n ({ icon, shape = 'square', size = 'md', className, loading, ...props }, ref) => {\n return (\n <Button\n ref={ref}\n size={sizeMap[size]}\n className={cn(shape === 'circle' && 'rounded-pill', className)}\n loading={loading}\n loadingPosition=\"center\"\n {...props}\n >\n {normalizeIcon(icon)}\n </Button>\n )\n },\n)\nIconButton.displayName = 'IconButton'\n\nexport { IconButton }\n"],"mappings":";;;;;;;AAUA,IAAM,IAAU;CACd,IAAI;CACJ,IAAI;CACJ,IAAI;AACN,GAoDM,IAAa,EAAM,YACtB,EAAE,SAAM,WAAQ,UAAU,UAAO,MAAM,cAAW,YAAS,GAAG,KAAS,MAEpE,kBAAC,GAAD;CACO;CACL,MAAM,EAAQ;CACd,WAAW,EAAG,MAAU,YAAY,gBAAgB,CAAS;CACpD;CACT,iBAAgB;CAChB,GAAI;WAEH,EAAc,CAAI;AACb,CAAA,CAGd;AACA,EAAW,cAAc"}
@@ -9,7 +9,7 @@ export interface IconContextValue {
9
9
  declare const IconContext: React.Context<IconContextValue>;
10
10
  export declare function IconProvider({ size, stroke, children, }: IconContextValue & {
11
11
  children: React.ReactNode;
12
- }): import("react/jsx-runtime").JSX.Element;
12
+ }): React.JSX.Element;
13
13
  export declare function useIconContext(): IconContextValue;
14
14
  export { IconContext };
15
15
  //# sourceMappingURL=icon-context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"icon-context.d.ts","sourceRoot":"","sources":["../../src/ui/icon-context.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,CAAA;AAC/D,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAA;AAErD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf,MAAM,CAAC,EAAE,UAAU,CAAA;CACpB;AAED,QAAA,MAAM,WAAW,iCAA4C,CAAA;AAE7D,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,MAAM,EACN,QAAQ,GACT,EAAE,gBAAgB,GAAG;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,2CAGlD;AAED,wBAAgB,cAAc,IAAI,gBAAgB,CAEjD;AAED,OAAO,EAAE,WAAW,EAAE,CAAA"}
1
+ {"version":3,"file":"icon-context.d.ts","sourceRoot":"","sources":["../../src/ui/icon-context.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,CAAA;AAC/D,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAA;AAErD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf,MAAM,CAAC,EAAE,UAAU,CAAA;CACpB;AAED,QAAA,MAAM,WAAW,iCAA4C,CAAA;AAE7D,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,MAAM,EACN,QAAQ,GACT,EAAE,gBAAgB,GAAG;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,qBAGlD;AAED,wBAAgB,cAAc,IAAI,gBAAgB,CAEjD;AAED,OAAO,EAAE,WAAW,EAAE,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"icon-context.js","names":[],"sources":["../../src/ui/icon-context.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\nexport type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'\nexport type IconStroke = 'light' | 'regular' | 'bold'\n\nexport interface IconContextValue {\n size?: IconSize\n stroke?: IconStroke\n}\n\nconst IconContext = React.createContext<IconContextValue>({})\n\nexport function IconProvider({\n size,\n stroke,\n children,\n}: IconContextValue & { children: React.ReactNode }) {\n const value = React.useMemo(() => ({ size, stroke }), [size, stroke])\n return <IconContext.Provider value={value}>{children}</IconContext.Provider>\n}\n\nexport function useIconContext(): IconContextValue {\n return React.useContext(IconContext)\n}\n\nexport { IconContext }\n"],"mappings":";;;;AAYA,IAAM,IAAc,EAAM,cAAgC,EAAE,CAAC;AAE7D,SAAgB,EAAa,EAC3B,SACA,WACA,eACmD;CACnD,IAAM,IAAQ,EAAM,eAAe;EAAE;EAAM;EAAQ,GAAG,CAAC,GAAM,EAAO,CAAC;AACrE,QAAO,kBAAC,EAAY,UAAb;EAA6B;EAAQ;EAAgC,CAAA;;AAG9E,SAAgB,IAAmC;AACjD,QAAO,EAAM,WAAW,EAAY"}
1
+ {"version":3,"file":"icon-context.js","names":[],"sources":["../../src/ui/icon-context.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\nexport type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'\nexport type IconStroke = 'light' | 'regular' | 'bold'\n\nexport interface IconContextValue {\n size?: IconSize\n stroke?: IconStroke\n}\n\nconst IconContext = React.createContext<IconContextValue>({})\n\nexport function IconProvider({\n size,\n stroke,\n children,\n}: IconContextValue & { children: React.ReactNode }) {\n const value = React.useMemo(() => ({ size, stroke }), [size, stroke])\n return <IconContext.Provider value={value}>{children}</IconContext.Provider>\n}\n\nexport function useIconContext(): IconContextValue {\n return React.useContext(IconContext)\n}\n\nexport { IconContext }\n"],"mappings":";;;;AAYA,IAAM,IAAc,EAAM,cAAgC,CAAC,CAAC;AAE5D,SAAgB,EAAa,EAC3B,SACA,WACA,eACmD;CACnD,IAAM,IAAQ,EAAM,eAAe;EAAE;EAAM;CAAO,IAAI,CAAC,GAAM,CAAM,CAAC;CACpE,OAAO,kBAAC,EAAY,UAAb;EAA6B;EAAQ;CAA+B,CAAA;AAC7E;AAEA,SAAgB,IAAmC;CACjD,OAAO,EAAM,WAAW,CAAW;AACrC"}
@@ -1 +1 @@
1
- {"version":3,"file":"icon-group.js","names":[],"sources":["../../src/ui/icon-group.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\nimport { IconProvider, type IconSize, type IconStroke } from './icon-context'\nimport { cn } from './lib/utils'\n\nconst gapClasses = {\n tight: 'gap-0.5',\n default: 'gap-1',\n loose: 'gap-2',\n} as const\n\nexport interface IconGroupProps {\n /** Icon size for all children */\n size?: IconSize\n /** Stroke weight for all children */\n stroke?: IconStroke\n /** Gap between icons. Default: 'default' (4px) */\n gap?: 'tight' | 'default' | 'loose'\n /** Accessible label — required when role is \"toolbar\" */\n label?: string\n /** Set to \"toolbar\" for formatting toolbars. Default: no role. */\n role?: 'toolbar'\n className?: string\n children: React.ReactNode\n}\n\nconst IconGroup = React.forwardRef<HTMLDivElement, IconGroupProps>(({\n size,\n stroke,\n gap = 'default',\n label,\n role: ariaRole,\n className,\n children,\n}, ref) => {\n return (\n <IconProvider size={size} stroke={stroke}>\n <div\n ref={ref}\n role={ariaRole}\n aria-label={ariaRole ? label : undefined}\n className={cn('inline-flex items-center', gapClasses[gap], className)}\n >\n {children}\n </div>\n </IconProvider>\n )\n})\nIconGroup.displayName = 'IconGroup'\n\nexport { IconGroup }\n"],"mappings":";;;;;;AAOA,IAAM,IAAa;CACjB,OAAO;CACP,SAAS;CACT,OAAO;CACR,EAiBK,IAAY,EAAM,YAA4C,EAClE,SACA,WACA,SAAM,WACN,UACA,MAAM,GACN,cACA,eACC,MAEC,kBAAC,GAAD;CAAoB;CAAc;WAChC,kBAAC,OAAD;EACO;EACL,MAAM;EACN,cAAY,IAAW,IAAQ,KAAA;EAC/B,WAAW,EAAG,4BAA4B,EAAW,IAAM,EAAU;EAEpE;EACG,CAAA;CACO,CAAA,CAEjB;AACF,EAAU,cAAc"}
1
+ {"version":3,"file":"icon-group.js","names":[],"sources":["../../src/ui/icon-group.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\nimport { IconProvider, type IconSize, type IconStroke } from './icon-context'\nimport { cn } from './lib/utils'\n\nconst gapClasses = {\n tight: 'gap-0.5',\n default: 'gap-1',\n loose: 'gap-2',\n} as const\n\nexport interface IconGroupProps {\n /** Icon size for all children */\n size?: IconSize\n /** Stroke weight for all children */\n stroke?: IconStroke\n /** Gap between icons. Default: 'default' (4px) */\n gap?: 'tight' | 'default' | 'loose'\n /** Accessible label — required when role is \"toolbar\" */\n label?: string\n /** Set to \"toolbar\" for formatting toolbars. Default: no role. */\n role?: 'toolbar'\n className?: string\n children: React.ReactNode\n}\n\nconst IconGroup = React.forwardRef<HTMLDivElement, IconGroupProps>(({\n size,\n stroke,\n gap = 'default',\n label,\n role: ariaRole,\n className,\n children,\n}, ref) => {\n return (\n <IconProvider size={size} stroke={stroke}>\n <div\n ref={ref}\n role={ariaRole}\n aria-label={ariaRole ? label : undefined}\n className={cn('inline-flex items-center', gapClasses[gap], className)}\n >\n {children}\n </div>\n </IconProvider>\n )\n})\nIconGroup.displayName = 'IconGroup'\n\nexport { IconGroup }\n"],"mappings":";;;;;;AAOA,IAAM,IAAa;CACjB,OAAO;CACP,SAAS;CACT,OAAO;AACT,GAiBM,IAAY,EAAM,YAA4C,EAClE,SACA,WACA,SAAM,WACN,UACA,MAAM,GACN,cACA,eACC,MAEC,kBAAC,GAAD;CAAoB;CAAc;WAChC,kBAAC,OAAD;EACO;EACL,MAAM;EACN,cAAY,IAAW,IAAQ,KAAA;EAC/B,WAAW,EAAG,4BAA4B,EAAW,IAAM,CAAS;EAEnE;CACE,CAAA;AACO,CAAA,CAEjB;AACD,EAAU,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"icon.js","names":[],"sources":["../../src/ui/icon.tsx"],"sourcesContent":["'use client'\n\nimport { AnimatePresence, motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { type IconSize, type IconStroke,useIconContext } from './icon-context'\nimport { springs, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\nimport { Spinner } from './spinner'\n\n/** Icon size tier → pixel dimensions */\nconst SIZE_PX: Record<IconSize, number> = {\n xs: 14, sm: 16, md: 18, lg: 20, xl: 24, '2xl': 32,\n}\n\n/** Stroke weight → strokeWidth per size tier (lighter strokes on smaller icons) */\nconst STROKE_MAP: Record<IconStroke, Record<IconSize, number>> = {\n light: { xs: 1.25, sm: 1.5, md: 1.5, lg: 1.75, xl: 2, '2xl': 2 },\n regular: { xs: 1.5, sm: 2, md: 2, lg: 2, xl: 2, '2xl': 2.25 },\n bold: { xs: 2, sm: 2.5, md: 2.5, lg: 2.5, xl: 2.5, '2xl': 2.5 },\n}\n\n/** Props accepted by Tabler icon components (and most SVG icon libraries) */\ntype IconComponentProps = Partial<Omit<React.ComponentPropsWithoutRef<'svg'>, 'stroke'>> & {\n size?: string | number\n stroke?: string | number\n title?: string\n}\n\nconst ANIMATION_PRESETS = {\n spin: {\n animate: { rotate: 360 },\n transition: { duration: 1, repeat: Infinity, ease: 'linear' as const },\n },\n pulse: {\n animate: { scale: [1, 1.15, 1] },\n transition: { duration: 2, repeat: Infinity, ease: 'easeInOut' as const },\n },\n bounce: {\n animate: { y: [0, -4, 0] },\n transition: { duration: 1.5, repeat: Infinity, ease: 'easeInOut' as const },\n },\n}\n\nconst ICON_TO_SPINNER_SIZE: Record<IconSize, 'sm' | 'md' | 'lg'> = {\n xs: 'sm', sm: 'sm', md: 'md', lg: 'md', xl: 'lg', '2xl': 'lg',\n}\n\nexport interface IconProps {\n /** The Tabler icon component (or any ForwardRef SVG icon component) */\n icon: React.ForwardRefExoticComponent<IconComponentProps & React.RefAttributes<SVGSVGElement>>\n /** Size tier — reads from IconContext if not set */\n size?: IconSize\n /** Stroke weight — reads from IconContext if not set */\n stroke?: IconStroke\n /** Accessible label — renders <title> + sets aria-label. Without this, icon is aria-hidden. */\n label?: string\n /**\n * Preset animation or controlled motion. 'none' disables inherited animation.\n * 'draw' renders the icon as an SVG path that draws in like a pen stroke\n * (works with IconCheck and IconX — others fall back to fade-in).\n */\n animate?: 'spin' | 'pulse' | 'bounce' | 'draw' | 'none' | { rotate?: number; scale?: number }\n /**\n * State machine for loading→success/error transitions. Delegates to Spinner (bare variant).\n *\n * **Priority rule:** If both `state` and `animate` are set, `state` wins —\n * the state machine check runs first and short-circuits rendering.\n */\n state?: 'idle' | 'loading' | 'success' | 'error'\n className?: string\n}\n\n/**\n * Icon — context-aware wrapper for Tabler icons with standardized sizing,\n * stroke weights, accessibility, animation presets, and loading state machine.\n *\n * Reads size and stroke from IconContext (provided by Button, IconGroup, etc.).\n * Explicit props always override context.\n *\n * **Priority rule:** If both `state` and `animate` are set, `state` wins.\n *\n * @example\n * <Icon icon={IconPlus} /> // md size, regular stroke\n * <Icon icon={IconPlus} size=\"xs\" stroke=\"light\" /> // 14px, stroke 1.25\n * <Icon icon={IconPlus} label=\"Add item\" /> // accessible, not decorative\n * <Icon icon={IconPlus} animate=\"spin\" /> // continuous rotation\n * <Icon icon={IconPlus} animate=\"pulse\" /> // scale pulse\n * <Icon icon={IconPlus} state=\"loading\" /> // bare spinner\n * <Icon icon={IconPlus} state=\"success\" /> // animated checkmark\n */\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(\n ({ icon: TablerIcon, size, stroke, label, animate, state, className }, ref) => {\n const ctx = useIconContext()\n const resolvedSize = size ?? ctx.size ?? 'md'\n const resolvedStroke = stroke ?? ctx.stroke ?? 'regular'\n const px = SIZE_PX[resolvedSize]\n const sw = STROKE_MAP[resolvedStroke][resolvedSize]\n const prefersReduced = useReducedMotion()\n\n // ── State machine (priority: state > animate) ──────────────────────\n if (state && state !== 'idle') {\n const spinnerSize = ICON_TO_SPINNER_SIZE[resolvedSize]\n const spinnerState = state === 'loading' ? 'spinning' : state\n return (\n <AnimatePresence mode=\"wait\">\n <motion.span\n key=\"spinner\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n className=\"inline-flex\"\n >\n <Spinner size={spinnerSize} state={spinnerState} variant=\"bare\" />\n </motion.span>\n </AnimatePresence>\n )\n }\n\n // ── Draw animation (SVG pathLength) ─────────────────────────────────\n if (animate === 'draw' && !prefersReduced) {\n const drawProps = {\n initial: { pathLength: 0, opacity: 0 },\n animate: { pathLength: 1, opacity: 1 },\n transition: { pathLength: { duration: 0.35, ease: 'easeOut' as const }, opacity: { duration: 0.1 } },\n }\n // Check which icon this is to render the appropriate SVG path.\n // Tabler displayNames: IconCheck → \"Check\", IconX → \"X\"\n const name = TablerIcon.displayName ?? ''\n const isCheck = name === 'Check' || name === 'IconCheck' || name === 'CircleCheck'\n const isX = name === 'X' || name === 'IconX'\n\n if (isCheck || isX) {\n return (\n <svg\n ref={ref}\n width={px}\n height={px}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={sw}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={className}\n aria-hidden={label ? undefined : 'true'}\n aria-label={label}\n role={label ? 'img' : undefined}\n >\n {isCheck ? (\n <motion.polyline points=\"4 12 10 18 20 6\" {...drawProps} />\n ) : (\n <>\n <motion.line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" {...drawProps} />\n <motion.line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"\n {...drawProps}\n transition={{\n pathLength: { duration: 0.25, ease: 'easeOut', delay: 0.1 },\n opacity: { duration: 0.1, delay: 0.1 },\n }}\n />\n </>\n )}\n </svg>\n )\n }\n // Unknown icon: fall through to fade-in via static render\n }\n\n // ── Determine animation ────────────────────────────────────────────\n const animatePreset =\n typeof animate === 'string' && animate !== 'none' && animate !== 'draw'\n ? ANIMATION_PRESETS[animate as keyof typeof ANIMATION_PRESETS]\n : null\n const animateObject =\n typeof animate === 'object' && animate !== null && (animate.rotate !== undefined || animate.scale !== undefined)\n ? animate\n : null\n\n if (prefersReduced || (!animatePreset && !animateObject)) {\n // Static render (no animation or reduced motion)\n return label ? (\n <TablerIcon ref={ref as any} size={px} stroke={sw} className={className} title={label} aria-label={label} role=\"img\" />\n ) : (\n <TablerIcon ref={ref as any} size={px} stroke={sw} className={className} aria-hidden=\"true\" />\n )\n }\n\n // ── Animated render ────────────────────────────────────────────────\n const motionProps = animatePreset ?? {\n animate: animateObject!,\n transition: springs.snappy,\n }\n\n const iconEl = label ? (\n <TablerIcon size={px} stroke={sw} title={label} aria-label={label} role=\"img\" />\n ) : (\n <TablerIcon size={px} stroke={sw} aria-hidden=\"true\" />\n )\n\n return (\n <motion.span\n ref={ref as any}\n className={cn('inline-flex', className)}\n {...motionProps}\n >\n {iconEl}\n </motion.span>\n )\n },\n)\nIcon.displayName = 'Icon'\n"],"mappings":"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"}
1
+ {"version":3,"file":"icon.js","names":[],"sources":["../../src/ui/icon.tsx"],"sourcesContent":["'use client'\n\nimport { AnimatePresence, motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { type IconSize, type IconStroke,useIconContext } from './icon-context'\nimport { springs, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\nimport { Spinner } from './spinner'\n\n/** Icon size tier → pixel dimensions */\nconst SIZE_PX: Record<IconSize, number> = {\n xs: 14, sm: 16, md: 18, lg: 20, xl: 24, '2xl': 32,\n}\n\n/** Stroke weight → strokeWidth per size tier (lighter strokes on smaller icons) */\nconst STROKE_MAP: Record<IconStroke, Record<IconSize, number>> = {\n light: { xs: 1.25, sm: 1.5, md: 1.5, lg: 1.75, xl: 2, '2xl': 2 },\n regular: { xs: 1.5, sm: 2, md: 2, lg: 2, xl: 2, '2xl': 2.25 },\n bold: { xs: 2, sm: 2.5, md: 2.5, lg: 2.5, xl: 2.5, '2xl': 2.5 },\n}\n\n/** Props accepted by Tabler icon components (and most SVG icon libraries) */\ntype IconComponentProps = Partial<Omit<React.ComponentPropsWithoutRef<'svg'>, 'stroke'>> & {\n size?: string | number\n stroke?: string | number\n title?: string\n}\n\nconst ANIMATION_PRESETS = {\n spin: {\n animate: { rotate: 360 },\n transition: { duration: 1, repeat: Infinity, ease: 'linear' as const },\n },\n pulse: {\n animate: { scale: [1, 1.15, 1] },\n transition: { duration: 2, repeat: Infinity, ease: 'easeInOut' as const },\n },\n bounce: {\n animate: { y: [0, -4, 0] },\n transition: { duration: 1.5, repeat: Infinity, ease: 'easeInOut' as const },\n },\n}\n\nconst ICON_TO_SPINNER_SIZE: Record<IconSize, 'sm' | 'md' | 'lg'> = {\n xs: 'sm', sm: 'sm', md: 'md', lg: 'md', xl: 'lg', '2xl': 'lg',\n}\n\nexport interface IconProps {\n /** The Tabler icon component (or any ForwardRef SVG icon component) */\n icon: React.ForwardRefExoticComponent<IconComponentProps & React.RefAttributes<SVGSVGElement>>\n /** Size tier — reads from IconContext if not set */\n size?: IconSize\n /** Stroke weight — reads from IconContext if not set */\n stroke?: IconStroke\n /** Accessible label — renders <title> + sets aria-label. Without this, icon is aria-hidden. */\n label?: string\n /**\n * Preset animation or controlled motion. 'none' disables inherited animation.\n * 'draw' renders the icon as an SVG path that draws in like a pen stroke\n * (works with IconCheck and IconX — others fall back to fade-in).\n */\n animate?: 'spin' | 'pulse' | 'bounce' | 'draw' | 'none' | { rotate?: number; scale?: number }\n /**\n * State machine for loading→success/error transitions. Delegates to Spinner (bare variant).\n *\n * **Priority rule:** If both `state` and `animate` are set, `state` wins —\n * the state machine check runs first and short-circuits rendering.\n */\n state?: 'idle' | 'loading' | 'success' | 'error'\n className?: string\n}\n\n/**\n * Icon — context-aware wrapper for Tabler icons with standardized sizing,\n * stroke weights, accessibility, animation presets, and loading state machine.\n *\n * Reads size and stroke from IconContext (provided by Button, IconGroup, etc.).\n * Explicit props always override context.\n *\n * **Priority rule:** If both `state` and `animate` are set, `state` wins.\n *\n * @example\n * <Icon icon={IconPlus} /> // md size, regular stroke\n * <Icon icon={IconPlus} size=\"xs\" stroke=\"light\" /> // 14px, stroke 1.25\n * <Icon icon={IconPlus} label=\"Add item\" /> // accessible, not decorative\n * <Icon icon={IconPlus} animate=\"spin\" /> // continuous rotation\n * <Icon icon={IconPlus} animate=\"pulse\" /> // scale pulse\n * <Icon icon={IconPlus} state=\"loading\" /> // bare spinner\n * <Icon icon={IconPlus} state=\"success\" /> // animated checkmark\n */\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(\n ({ icon: TablerIcon, size, stroke, label, animate, state, className }, ref) => {\n const ctx = useIconContext()\n const resolvedSize = size ?? ctx.size ?? 'md'\n const resolvedStroke = stroke ?? ctx.stroke ?? 'regular'\n const px = SIZE_PX[resolvedSize]\n const sw = STROKE_MAP[resolvedStroke][resolvedSize]\n const prefersReduced = useReducedMotion()\n\n // ── State machine (priority: state > animate) ──────────────────────\n if (state && state !== 'idle') {\n const spinnerSize = ICON_TO_SPINNER_SIZE[resolvedSize]\n const spinnerState = state === 'loading' ? 'spinning' : state\n return (\n <AnimatePresence mode=\"wait\">\n <motion.span\n key=\"spinner\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n className=\"inline-flex\"\n >\n <Spinner size={spinnerSize} state={spinnerState} variant=\"bare\" />\n </motion.span>\n </AnimatePresence>\n )\n }\n\n // ── Draw animation (SVG pathLength) ─────────────────────────────────\n if (animate === 'draw' && !prefersReduced) {\n const drawProps = {\n initial: { pathLength: 0, opacity: 0 },\n animate: { pathLength: 1, opacity: 1 },\n transition: { pathLength: { duration: 0.35, ease: 'easeOut' as const }, opacity: { duration: 0.1 } },\n }\n // Check which icon this is to render the appropriate SVG path.\n // Tabler displayNames: IconCheck → \"Check\", IconX → \"X\"\n const name = TablerIcon.displayName ?? ''\n const isCheck = name === 'Check' || name === 'IconCheck' || name === 'CircleCheck'\n const isX = name === 'X' || name === 'IconX'\n\n if (isCheck || isX) {\n return (\n <svg\n ref={ref}\n width={px}\n height={px}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={sw}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={className}\n aria-hidden={label ? undefined : 'true'}\n aria-label={label}\n role={label ? 'img' : undefined}\n >\n {isCheck ? (\n <motion.polyline points=\"4 12 10 18 20 6\" {...drawProps} />\n ) : (\n <>\n <motion.line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" {...drawProps} />\n <motion.line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"\n {...drawProps}\n transition={{\n pathLength: { duration: 0.25, ease: 'easeOut', delay: 0.1 },\n opacity: { duration: 0.1, delay: 0.1 },\n }}\n />\n </>\n )}\n </svg>\n )\n }\n // Unknown icon: fall through to fade-in via static render\n }\n\n // ── Determine animation ────────────────────────────────────────────\n const animatePreset =\n typeof animate === 'string' && animate !== 'none' && animate !== 'draw'\n ? ANIMATION_PRESETS[animate as keyof typeof ANIMATION_PRESETS]\n : null\n const animateObject =\n typeof animate === 'object' && animate !== null && (animate.rotate !== undefined || animate.scale !== undefined)\n ? animate\n : null\n\n if (prefersReduced || (!animatePreset && !animateObject)) {\n // Static render (no animation or reduced motion)\n return label ? (\n <TablerIcon ref={ref as any} size={px} stroke={sw} className={className} title={label} aria-label={label} role=\"img\" />\n ) : (\n <TablerIcon ref={ref as any} size={px} stroke={sw} className={className} aria-hidden=\"true\" />\n )\n }\n\n // ── Animated render ────────────────────────────────────────────────\n const motionProps = animatePreset ?? {\n animate: animateObject!,\n transition: springs.snappy,\n }\n\n const iconEl = label ? (\n <TablerIcon size={px} stroke={sw} title={label} aria-label={label} role=\"img\" />\n ) : (\n <TablerIcon size={px} stroke={sw} aria-hidden=\"true\" />\n )\n\n return (\n <motion.span\n ref={ref as any}\n className={cn('inline-flex', className)}\n {...motionProps}\n >\n {iconEl}\n </motion.span>\n )\n },\n)\nIcon.displayName = 'Icon'\n"],"mappings":"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"}