@devalok/shilp-sutra 0.56.0 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (374) hide show
  1. package/MIGRATION.md +198 -0
  2. package/dist/_chunks/badge-group.js +75 -174
  3. package/dist/_chunks/badge-group.js.map +1 -1
  4. package/dist/_chunks/chat.js +270 -253
  5. package/dist/_chunks/chat.js.map +1 -1
  6. package/dist/_chunks/document-preview.js +1 -1
  7. package/dist/_chunks/document-preview.js.map +1 -1
  8. package/dist/_chunks/emoji-suggestion.js +1 -1
  9. package/dist/_chunks/emoji-suggestion.js.map +1 -1
  10. package/dist/_chunks/image-preview.js +71 -70
  11. package/dist/_chunks/image-preview.js.map +1 -1
  12. package/dist/_chunks/mention-suggestion.js +2 -2
  13. package/dist/_chunks/mention-suggestion.js.map +1 -1
  14. package/dist/_chunks/motion-preference.js +15 -0
  15. package/dist/_chunks/motion-preference.js.map +1 -0
  16. package/dist/_chunks/motion-provider.js +1 -1
  17. package/dist/_chunks/motion-provider.js.map +1 -1
  18. package/dist/_chunks/oauth-button.js +1 -1
  19. package/dist/_chunks/oauth-button.js.map +1 -1
  20. package/dist/_chunks/popover.js +50 -49
  21. package/dist/_chunks/popover.js.map +1 -1
  22. package/dist/_chunks/primitives-internal.js +29 -29
  23. package/dist/_chunks/primitives-internal.js.map +1 -1
  24. package/dist/_chunks/shared.js +48 -47
  25. package/dist/_chunks/shared.js.map +1 -1
  26. package/dist/_chunks/success.js +36 -36
  27. package/dist/_chunks/success.js.map +1 -1
  28. package/dist/_chunks/tree-view.js +1 -1
  29. package/dist/_chunks/tree-view.js.map +1 -1
  30. package/dist/_chunks/use-calendar.js +4 -4
  31. package/dist/_chunks/use-calendar.js.map +1 -1
  32. package/dist/ai/block-renderer.js +8 -8
  33. package/dist/ai/blocks/text.js +1 -1
  34. package/dist/ai/blocks/text.js.map +1 -1
  35. package/dist/ai/command-bar.js +67 -67
  36. package/dist/ai/command-bar.js.map +1 -1
  37. package/dist/ai/conversation.js +35 -35
  38. package/dist/ai/conversation.js.map +1 -1
  39. package/dist/ai/index.js +4 -4
  40. package/dist/composed/activity-feed.d.ts.map +1 -1
  41. package/dist/composed/activity-feed.js +109 -108
  42. package/dist/composed/activity-feed.js.map +1 -1
  43. package/dist/composed/avatar-group.d.ts +7 -2
  44. package/dist/composed/avatar-group.d.ts.map +1 -1
  45. package/dist/composed/avatar-group.js +3 -3
  46. package/dist/composed/avatar-group.js.map +1 -1
  47. package/dist/composed/command-palette.js +137 -137
  48. package/dist/composed/command-palette.js.map +1 -1
  49. package/dist/composed/content-card.js +2 -2
  50. package/dist/composed/content-card.js.map +1 -1
  51. package/dist/composed/date-picker/index.js +9 -9
  52. package/dist/composed/date-picker/index.js.map +1 -1
  53. package/dist/composed/diff.d.ts +23 -2
  54. package/dist/composed/diff.d.ts.map +1 -1
  55. package/dist/composed/diff.js +341 -260
  56. package/dist/composed/diff.js.map +1 -1
  57. package/dist/composed/emoji-picker.js +3 -3
  58. package/dist/composed/emoji-picker.js.map +1 -1
  59. package/dist/composed/empty-state.js +4 -4
  60. package/dist/composed/error-boundary.js +2 -2
  61. package/dist/composed/error-boundary.js.map +1 -1
  62. package/dist/composed/file-preview/image-preview.d.ts.map +1 -1
  63. package/dist/composed/file-preview/shared.d.ts.map +1 -1
  64. package/dist/composed/file-preview/video-preview.d.ts.map +1 -1
  65. package/dist/composed/file-preview.js +152 -151
  66. package/dist/composed/file-preview.js.map +1 -1
  67. package/dist/composed/filter-bar.d.ts.map +1 -1
  68. package/dist/composed/filter-bar.js +53 -52
  69. package/dist/composed/filter-bar.js.map +1 -1
  70. package/dist/composed/form-section.d.ts.map +1 -1
  71. package/dist/composed/form-section.js +38 -37
  72. package/dist/composed/form-section.js.map +1 -1
  73. package/dist/composed/inline-edit.js +1 -1
  74. package/dist/composed/inline-edit.js.map +1 -1
  75. package/dist/composed/loading-skeleton.js +3 -3
  76. package/dist/composed/loading-skeleton.js.map +1 -1
  77. package/dist/composed/master-detail.d.ts.map +1 -1
  78. package/dist/composed/master-detail.js +1 -1
  79. package/dist/composed/master-detail.js.map +1 -1
  80. package/dist/composed/member-picker.js +1 -1
  81. package/dist/composed/member-picker.js.map +1 -1
  82. package/dist/composed/multi-select-popover.d.ts.map +1 -1
  83. package/dist/composed/multi-select-popover.js +109 -108
  84. package/dist/composed/multi-select-popover.js.map +1 -1
  85. package/dist/composed/page-skeletons.js +4 -4
  86. package/dist/composed/page-skeletons.js.map +1 -1
  87. package/dist/composed/responsive-modal.js +1 -1
  88. package/dist/composed/responsive-modal.js.map +1 -1
  89. package/dist/composed/rich-chat-input/attachment-strip.d.ts.map +1 -1
  90. package/dist/composed/rich-chat-input/reply-banner.d.ts.map +1 -1
  91. package/dist/composed/rich-chat-input/schedule-send.d.ts.map +1 -1
  92. package/dist/composed/rich-chat-input.d.ts.map +1 -1
  93. package/dist/composed/rich-chat-input.js +473 -472
  94. package/dist/composed/rich-chat-input.js.map +1 -1
  95. package/dist/composed/rich-text-editor.js +6 -6
  96. package/dist/composed/rich-text-editor.js.map +1 -1
  97. package/dist/composed/schedule-view.js +5 -5
  98. package/dist/composed/schedule-view.js.map +1 -1
  99. package/dist/composed/status-badge.d.ts.map +1 -1
  100. package/dist/composed/status-badge.js +29 -28
  101. package/dist/composed/status-badge.js.map +1 -1
  102. package/dist/hooks/index.d.ts +1 -0
  103. package/dist/hooks/index.d.ts.map +1 -1
  104. package/dist/hooks/index.js +4 -3
  105. package/dist/hooks/use-container-size.d.ts +18 -0
  106. package/dist/hooks/use-container-size.d.ts.map +1 -0
  107. package/dist/hooks/use-container-size.js +28 -0
  108. package/dist/hooks/use-container-size.js.map +1 -0
  109. package/dist/motion/index.d.ts +1 -0
  110. package/dist/motion/index.d.ts.map +1 -1
  111. package/dist/motion/index.js +2 -15
  112. package/dist/motion/motion-preference.d.ts +36 -0
  113. package/dist/motion/motion-preference.d.ts.map +1 -0
  114. package/dist/motion/motion-provider.d.ts +2 -1
  115. package/dist/motion/motion-provider.d.ts.map +1 -1
  116. package/dist/motion/primitives-index.js +103 -102
  117. package/dist/motion/primitives-index.js.map +1 -1
  118. package/dist/motion/primitives.d.ts.map +1 -1
  119. package/dist/shell/app-shell.d.ts +101 -0
  120. package/dist/shell/app-shell.d.ts.map +1 -0
  121. package/dist/shell/app-shell.js +76 -0
  122. package/dist/shell/app-shell.js.map +1 -0
  123. package/dist/shell/bottom-navbar.js +21 -21
  124. package/dist/shell/bottom-navbar.js.map +1 -1
  125. package/dist/shell/index.d.ts +2 -1
  126. package/dist/shell/index.d.ts.map +1 -1
  127. package/dist/shell/index.js +6 -5
  128. package/dist/shell/notification-center.d.ts +17 -0
  129. package/dist/shell/notification-center.d.ts.map +1 -1
  130. package/dist/shell/notification-center.js +110 -105
  131. package/dist/shell/notification-center.js.map +1 -1
  132. package/dist/shell/notification-preferences.js +1 -1
  133. package/dist/shell/notification-preferences.js.map +1 -1
  134. package/dist/shell/top-bar.js +7 -7
  135. package/dist/shell/top-bar.js.map +1 -1
  136. package/dist/tokens/generate-scale.d.ts +49 -0
  137. package/dist/tokens/generate-scale.d.ts.map +1 -1
  138. package/dist/tokens/index.css +1 -0
  139. package/dist/tokens/palettes.css +292 -0
  140. package/dist/tokens/primitives.css +29 -26
  141. package/dist/tokens/semantic.css +170 -47
  142. package/dist/tokens/shilp-sutra.css +1 -0
  143. package/dist/tokens/utilities.css +10 -7
  144. package/dist/tokens/variants.css +1 -1
  145. package/dist/ui/accordion.d.ts.map +1 -1
  146. package/dist/ui/accordion.js +27 -26
  147. package/dist/ui/accordion.js.map +1 -1
  148. package/dist/ui/alert-dialog.d.ts.map +1 -1
  149. package/dist/ui/alert-dialog.js +60 -59
  150. package/dist/ui/alert-dialog.js.map +1 -1
  151. package/dist/ui/alert.d.ts.map +1 -1
  152. package/dist/ui/alert.js +55 -113
  153. package/dist/ui/alert.js.map +1 -1
  154. package/dist/ui/autocomplete.js +80 -80
  155. package/dist/ui/autocomplete.js.map +1 -1
  156. package/dist/ui/avatar.d.ts.map +1 -1
  157. package/dist/ui/avatar.js +71 -70
  158. package/dist/ui/avatar.js.map +1 -1
  159. package/dist/ui/badge-indicator.d.ts +4 -8
  160. package/dist/ui/badge-indicator.d.ts.map +1 -1
  161. package/dist/ui/badge-indicator.js +3 -8
  162. package/dist/ui/badge-indicator.js.map +1 -1
  163. package/dist/ui/badge.d.ts +55 -34
  164. package/dist/ui/badge.d.ts.map +1 -1
  165. package/dist/ui/banner.d.ts.map +1 -1
  166. package/dist/ui/banner.js +43 -41
  167. package/dist/ui/banner.js.map +1 -1
  168. package/dist/ui/button-group.d.ts.map +1 -1
  169. package/dist/ui/button-group.js +3 -23
  170. package/dist/ui/button-group.js.map +1 -1
  171. package/dist/ui/button.d.ts +10 -1
  172. package/dist/ui/button.d.ts.map +1 -1
  173. package/dist/ui/button.js +79 -212
  174. package/dist/ui/button.js.map +1 -1
  175. package/dist/ui/card.d.ts.map +1 -1
  176. package/dist/ui/card.js +78 -75
  177. package/dist/ui/card.js.map +1 -1
  178. package/dist/ui/charts/radar-chart.d.ts.map +1 -1
  179. package/dist/ui/charts/radar-chart.js +1 -1
  180. package/dist/ui/charts/radar-chart.js.map +1 -1
  181. package/dist/ui/chat/date-separator.d.ts +8 -1
  182. package/dist/ui/chat/date-separator.d.ts.map +1 -1
  183. package/dist/ui/chat/message-list.d.ts.map +1 -1
  184. package/dist/ui/chat/message.d.ts.map +1 -1
  185. package/dist/ui/chat/typing-indicator.d.ts.map +1 -1
  186. package/dist/ui/checkbox.js +1 -1
  187. package/dist/ui/checkbox.js.map +1 -1
  188. package/dist/ui/code.js +2 -2
  189. package/dist/ui/code.js.map +1 -1
  190. package/dist/ui/color-input.d.ts.map +1 -1
  191. package/dist/ui/color-input.js +151 -150
  192. package/dist/ui/color-input.js.map +1 -1
  193. package/dist/ui/combobox.d.ts +14 -0
  194. package/dist/ui/combobox.d.ts.map +1 -1
  195. package/dist/ui/combobox.js +156 -152
  196. package/dist/ui/combobox.js.map +1 -1
  197. package/dist/ui/context-menu.d.ts.map +1 -1
  198. package/dist/ui/context-menu.js +70 -69
  199. package/dist/ui/context-menu.js.map +1 -1
  200. package/dist/ui/data-table-body.d.ts +3 -1
  201. package/dist/ui/data-table-body.d.ts.map +1 -1
  202. package/dist/ui/data-table-body.js +93 -92
  203. package/dist/ui/data-table-body.js.map +1 -1
  204. package/dist/ui/data-table-bulk-actions.d.ts +7 -1
  205. package/dist/ui/data-table-bulk-actions.d.ts.map +1 -1
  206. package/dist/ui/data-table-bulk-actions.js +14 -8
  207. package/dist/ui/data-table-bulk-actions.js.map +1 -1
  208. package/dist/ui/data-table-card.d.ts.map +1 -1
  209. package/dist/ui/data-table-card.js +66 -47
  210. package/dist/ui/data-table-card.js.map +1 -1
  211. package/dist/ui/data-table-context.d.ts +4 -0
  212. package/dist/ui/data-table-context.d.ts.map +1 -1
  213. package/dist/ui/data-table-context.js +2 -2
  214. package/dist/ui/data-table-context.js.map +1 -1
  215. package/dist/ui/data-table-header.d.ts.map +1 -1
  216. package/dist/ui/data-table-header.js +50 -46
  217. package/dist/ui/data-table-header.js.map +1 -1
  218. package/dist/ui/data-table-pagination.js +3 -3
  219. package/dist/ui/data-table-pagination.js.map +1 -1
  220. package/dist/ui/data-table-toolbar.d.ts +11 -1
  221. package/dist/ui/data-table-toolbar.d.ts.map +1 -1
  222. package/dist/ui/data-table-toolbar.js +7 -7
  223. package/dist/ui/data-table-toolbar.js.map +1 -1
  224. package/dist/ui/data-table.d.ts +52 -5
  225. package/dist/ui/data-table.d.ts.map +1 -1
  226. package/dist/ui/data-table.js +181 -169
  227. package/dist/ui/data-table.js.map +1 -1
  228. package/dist/ui/dialog.d.ts.map +1 -1
  229. package/dist/ui/dialog.js +62 -61
  230. package/dist/ui/dialog.js.map +1 -1
  231. package/dist/ui/dot.d.ts.map +1 -1
  232. package/dist/ui/dot.js +10 -41
  233. package/dist/ui/dot.js.map +1 -1
  234. package/dist/ui/dropdown-menu.d.ts.map +1 -1
  235. package/dist/ui/dropdown-menu.js +79 -78
  236. package/dist/ui/dropdown-menu.js.map +1 -1
  237. package/dist/ui/file-upload.d.ts.map +1 -1
  238. package/dist/ui/file-upload.js +117 -116
  239. package/dist/ui/file-upload.js.map +1 -1
  240. package/dist/ui/form.d.ts.map +1 -1
  241. package/dist/ui/form.js +27 -26
  242. package/dist/ui/form.js.map +1 -1
  243. package/dist/ui/hover-card.d.ts.map +1 -1
  244. package/dist/ui/hover-card.js +26 -25
  245. package/dist/ui/hover-card.js.map +1 -1
  246. package/dist/ui/icon.js +7 -7
  247. package/dist/ui/index.d.ts +2 -2
  248. package/dist/ui/index.d.ts.map +1 -1
  249. package/dist/ui/index.js +3 -3
  250. package/dist/ui/index.js.map +1 -1
  251. package/dist/ui/input-otp.js +1 -1
  252. package/dist/ui/input-otp.js.map +1 -1
  253. package/dist/ui/input.js +8 -8
  254. package/dist/ui/input.js.map +1 -1
  255. package/dist/ui/menubar.d.ts.map +1 -1
  256. package/dist/ui/menubar.js +73 -72
  257. package/dist/ui/menubar.js.map +1 -1
  258. package/dist/ui/navigation-menu.d.ts.map +1 -1
  259. package/dist/ui/navigation-menu.js +80 -79
  260. package/dist/ui/navigation-menu.js.map +1 -1
  261. package/dist/ui/number-input.js +3 -3
  262. package/dist/ui/number-input.js.map +1 -1
  263. package/dist/ui/pagination.js +1 -1
  264. package/dist/ui/pagination.js.map +1 -1
  265. package/dist/ui/popover.d.ts.map +1 -1
  266. package/dist/ui/progress-ring.js +2 -2
  267. package/dist/ui/progress-ring.js.map +1 -1
  268. package/dist/ui/progress.d.ts.map +1 -1
  269. package/dist/ui/progress.js +82 -74
  270. package/dist/ui/progress.js.map +1 -1
  271. package/dist/ui/radio.d.ts.map +1 -1
  272. package/dist/ui/radio.js +32 -31
  273. package/dist/ui/radio.js.map +1 -1
  274. package/dist/ui/search-input.d.ts.map +1 -1
  275. package/dist/ui/search-input.js +28 -27
  276. package/dist/ui/search-input.js.map +1 -1
  277. package/dist/ui/select.d.ts.map +1 -1
  278. package/dist/ui/select.js +67 -66
  279. package/dist/ui/select.js.map +1 -1
  280. package/dist/ui/sheet.js +1 -1
  281. package/dist/ui/sheet.js.map +1 -1
  282. package/dist/ui/sidebar.d.ts.map +1 -1
  283. package/dist/ui/sidebar.js +14 -14
  284. package/dist/ui/sidebar.js.map +1 -1
  285. package/dist/ui/slider.d.ts +63 -9
  286. package/dist/ui/slider.d.ts.map +1 -1
  287. package/dist/ui/slider.js +75 -33
  288. package/dist/ui/slider.js.map +1 -1
  289. package/dist/ui/split-button.d.ts +8 -2
  290. package/dist/ui/split-button.d.ts.map +1 -1
  291. package/dist/ui/split-button.js +49 -95
  292. package/dist/ui/split-button.js.map +1 -1
  293. package/dist/ui/stat-card.d.ts.map +1 -1
  294. package/dist/ui/stat-card.js +89 -88
  295. package/dist/ui/stat-card.js.map +1 -1
  296. package/dist/ui/stat-flash.js +16 -16
  297. package/dist/ui/stepper.d.ts.map +1 -1
  298. package/dist/ui/stepper.js +63 -62
  299. package/dist/ui/stepper.js.map +1 -1
  300. package/dist/ui/surface.d.ts.map +1 -1
  301. package/dist/ui/surface.js +2 -2
  302. package/dist/ui/surface.js.map +1 -1
  303. package/dist/ui/switch.js +1 -1
  304. package/dist/ui/switch.js.map +1 -1
  305. package/dist/ui/table.js +3 -3
  306. package/dist/ui/table.js.map +1 -1
  307. package/dist/ui/tabs.d.ts.map +1 -1
  308. package/dist/ui/tabs.js +68 -67
  309. package/dist/ui/tabs.js.map +1 -1
  310. package/dist/ui/textarea.js +5 -5
  311. package/dist/ui/textarea.js.map +1 -1
  312. package/dist/ui/toast.d.ts.map +1 -1
  313. package/dist/ui/toast.js +196 -195
  314. package/dist/ui/toast.js.map +1 -1
  315. package/dist/ui/toggle.d.ts.map +1 -1
  316. package/dist/ui/toggle.js +26 -24
  317. package/dist/ui/toggle.js.map +1 -1
  318. package/dist/ui/tooltip.d.ts.map +1 -1
  319. package/dist/ui/tooltip.js +37 -36
  320. package/dist/ui/tooltip.js.map +1 -1
  321. package/dist/ui/tree-view/tree-item.d.ts.map +1 -1
  322. package/docs/components/composed/avatar-group.md +4 -4
  323. package/docs/components/composed/diff.md +10 -2
  324. package/docs/components/composed/schedule-view.md +1 -1
  325. package/docs/components/shell/app-shell.md +132 -0
  326. package/docs/components/ui/avatar.md +1 -1
  327. package/docs/components/ui/button.md +1 -1
  328. package/docs/components/ui/chat.md +7 -1
  329. package/docs/components/ui/color-swatch.md +1 -1
  330. package/docs/components/ui/data-table-toolbar.md +6 -2
  331. package/docs/components/ui/data-table.md +41 -7
  332. package/docs/components/ui/dot.md +1 -1
  333. package/docs/components/ui/progress.md +1 -0
  334. package/docs/components/ui/surface.md +3 -3
  335. package/docs/components/ui/table.md +2 -2
  336. package/docs/recipes/customize-brand.md +58 -0
  337. package/docs/recipes/install-astro.md +1 -0
  338. package/docs/recipes/install-next-app-router.md +1 -0
  339. package/docs/recipes/install-next-pages.md +22 -1
  340. package/docs/recipes/install-remix.md +1 -0
  341. package/docs/recipes/install-tanstack-start.md +1 -0
  342. package/docs/recipes/install-vite.md +2 -0
  343. package/docs/recipes/server-components.md +7 -4
  344. package/docs/recipes/troubleshoot.md +1 -0
  345. package/llms.txt +2 -1
  346. package/make-kit/Guidelines.md +2 -2
  347. package/make-kit/components/button.md +1 -1
  348. package/make-kit/components/card.md +6 -6
  349. package/make-kit/components/surface.md +4 -4
  350. package/make-kit/foundations/color.md +4 -4
  351. package/make-kit/foundations/dark-mode.md +1 -1
  352. package/make-kit/foundations/radius.md +1 -1
  353. package/make-kit/foundations/surfaces.md +9 -9
  354. package/mcp-manifest.json +358 -35
  355. package/package.json +8 -4
  356. package/skill/SKILL.md +12 -6
  357. package/skill/references/components.md +2 -1
  358. package/skill/references/customize-brand.md +58 -0
  359. package/skill/references/server-components.md +7 -4
  360. package/skill/references/setup-astro.md +1 -0
  361. package/skill/references/setup-next-app-router.md +1 -0
  362. package/skill/references/setup-next-pages.md +22 -1
  363. package/skill/references/setup-remix.md +1 -0
  364. package/skill/references/setup-tanstack-start.md +1 -0
  365. package/skill/references/setup-vite.md +2 -0
  366. package/skill/references/troubleshoot.md +1 -0
  367. package/dist/motion/check-motion-provider.d.ts +0 -2
  368. package/dist/motion/check-motion-provider.d.ts.map +0 -1
  369. package/dist/motion/index.js.map +0 -1
  370. package/dist/primitives/react-toast.d.ts +0 -120
  371. package/dist/ui/charts/_internal/scales.d.ts +0 -5
  372. package/dist/ui/charts/_internal/scales.d.ts.map +0 -1
  373. package/dist/ui/lib/slot.d.ts +0 -8
  374. package/dist/ui/lib/slot.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"file-preview.js","names":[],"sources":["../../src/composed/file-preview/audio-preview.tsx","../../src/composed/file-preview/embed-preview.tsx","../../src/composed/file-preview/video-preview.tsx","../../src/composed/file-preview.tsx"],"sourcesContent":["'use client'\n\nimport { IconPlayerPause,IconPlayerPlay } from '@tabler/icons-react'\nimport * as React from 'react'\n\nimport { Button } from '../../ui/button'\nimport { Icon } from '../../ui/icon'\nimport { TruncatedText } from '../../ui/truncated-text'\nimport { ErrorFallback, formatTime,MediaSlider,VolumeControl } from './shared'\n\n// ============================================================\n// Audio Preview — Branded mini-player (Spotify/SoundCloud style)\n// ============================================================\n\nexport default function AudioPreview({ url, fileName, onError }: { url: string; fileName?: string; onError?: (msg: string) => void }) {\n const audioRef = React.useRef<HTMLAudioElement>(null)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const [playing, setPlaying] = React.useState(false)\n const [progress, setProgress] = React.useState(0)\n const [duration, setDuration] = React.useState(0)\n const [currentTime, setCurrentTime] = React.useState(0)\n const [volume, setVolume] = React.useState(1)\n const [muted, setMuted] = React.useState(false)\n const [error, setError] = React.useState(false)\n\n // Keyboard shortcuts\n React.useEffect(() => {\n function handleKeyDown(e: KeyboardEvent) {\n const el = containerRef.current\n if (!el || !el.contains(document.activeElement) && document.activeElement !== el) return\n const a = audioRef.current\n if (!a) return\n\n switch (e.key) {\n case ' ':\n e.preventDefault()\n setPlaying(prev => {\n if (prev) a.pause(); else a.play()\n return !prev\n })\n break\n case 'ArrowRight':\n e.preventDefault()\n a.currentTime = Math.min(a.duration || 0, a.currentTime + 5)\n break\n case 'ArrowLeft':\n e.preventDefault()\n a.currentTime = Math.max(0, a.currentTime - 5)\n break\n case 'm':\n e.preventDefault()\n setMuted(prev => {\n a.muted = !prev\n return !prev\n })\n break\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n \n }, [])\n\n function togglePlay() {\n if (!audioRef.current) return\n if (playing) audioRef.current.pause()\n else audioRef.current.play()\n setPlaying(!playing)\n }\n\n function handleSeek(nextProgress: number) {\n if (!audioRef.current || !duration) return\n audioRef.current.currentTime = (nextProgress / 100) * duration\n }\n\n function toggleMute() {\n if (!audioRef.current) return\n const next = !muted\n setMuted(next)\n audioRef.current.muted = next\n }\n\n if (error) return <ErrorFallback message=\"Could not load audio\" url={url} />\n\n return (\n <div ref={containerRef} className=\"rounded-surface bg-surface-raised shadow-raised overflow-hidden\" tabIndex={-1}>\n {/* eslint-disable-next-line jsx-a11y/media-has-caption */}\n <audio\n ref={audioRef}\n src={url}\n onTimeUpdate={() => {\n if (!audioRef.current) return\n setCurrentTime(audioRef.current.currentTime)\n if (audioRef.current.duration) setProgress((audioRef.current.currentTime / audioRef.current.duration) * 100)\n }}\n onLoadedMetadata={() => setDuration(audioRef.current?.duration ?? 0)}\n onEnded={() => setPlaying(false)}\n onError={() => { setError(true); onError?.('Audio failed to load') }}\n />\n\n {/* Progress bar — full width at top (Spotify style) */}\n <div className=\"px-ds-03 pt-ds-02\">\n <MediaSlider\n value={progress}\n max={100}\n step={0.1}\n onValueChange={handleSeek}\n tone=\"light\"\n ariaLabel=\"Audio progress\"\n />\n </div>\n\n {/* Controls */}\n <div className=\"flex items-center gap-ds-04 px-ds-05 py-ds-04\">\n <Button\n variant=\"solid\"\n size=\"icon-sm\"\n onClick={togglePlay}\n aria-label={playing ? 'Pause' : 'Play'}\n title={playing ? 'Pause' : 'Play'}\n className=\"shrink-0\"\n >\n {playing ? (\n <Icon icon={IconPlayerPause} size=\"sm\" />\n ) : (\n <Icon icon={IconPlayerPlay} size=\"sm\" className=\"ml-0.5\" />\n )}\n </Button>\n\n {/* File name + time */}\n <div className=\"flex-1 min-w-0\">\n {fileName && (\n <TruncatedText as=\"p\" mode=\"middle\" className=\"text-body-sm font-semibold text-surface-fg\">\n {fileName}\n </TruncatedText>\n )}\n <p className=\"text-caption font-mono text-surface-fg-muted tabular-nums\">\n {formatTime(currentTime)} / {formatTime(duration)}\n </p>\n </div>\n\n {/* Volume */}\n <VolumeControl\n volume={volume}\n muted={muted}\n onVolumeChange={(v) => {\n setVolume(v)\n if (audioRef.current) audioRef.current.volume = v\n if (v > 0 && muted) {\n setMuted(false)\n if (audioRef.current) audioRef.current.muted = false\n }\n }}\n onMuteToggle={toggleMute}\n variant=\"light\"\n />\n </div>\n </div>\n )\n}\n","'use client'\n\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { tweens } from '../../ui/lib/motion'\nimport { Skeleton } from '../../ui/skeleton'\nimport { ErrorFallback } from './shared'\n\n// ============================================================\n// Embed Preview — 16:9 with error timeout\n// ============================================================\n\nexport default function EmbedPreview({ url, embedUrl, onError }: { url: string; embedUrl: string; onError?: (msg: string) => void }) {\n const [loaded, setLoaded] = React.useState(false)\n const [error, setError] = React.useState(false)\n\n // Timeout — if iframe doesn't load in 15s, show error\n React.useEffect(() => {\n const timer = setTimeout(() => {\n if (!loaded) { setError(true); onError?.('Embed timed out') }\n }, 15000)\n return () => clearTimeout(timer)\n }, [loaded, onError])\n\n if (error) return <ErrorFallback message=\"Could not load embed\" url={url} />\n\n return (\n <div className=\"relative w-full rounded-control overflow-hidden\" style={{ aspectRatio: '16 / 9' }}>\n {!loaded && <Skeleton className=\"absolute inset-0\" />}\n <motion.iframe\n src={embedUrl}\n title=\"Embedded content\"\n onLoad={() => setLoaded(true)}\n onError={() => { setError(true); onError?.('Embed failed to load') }}\n initial={{ opacity: 0 }}\n animate={{ opacity: loaded ? 1 : 0 }}\n transition={tweens.fade}\n className=\"absolute inset-0 h-full w-full border-none\"\n allowFullScreen\n />\n </div>\n )\n}\n","'use client'\n\nimport {\n IconMaximize,\n IconPlayerPause,\n IconPlayerPlay,\n} from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Icon } from '../../ui/icon'\nimport { springs, tweens } from '../../ui/lib/motion'\nimport { ErrorFallback, formatTime,MediaSlider,VolumeControl } from './shared'\n\n// ============================================================\n// Video Preview — Custom player with DS styling\n// ============================================================\n\nconst PLAYBACK_RATES = [0.5, 0.75, 1, 1.25, 1.5, 2] as const\n\nexport default function VideoPreview({ url, onError }: { url: string; onError?: (msg: string) => void }) {\n const videoRef = React.useRef<HTMLVideoElement>(null)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const [playing, setPlaying] = React.useState(false)\n const [progress, setProgress] = React.useState(0)\n const [duration, setDuration] = React.useState(0)\n const [currentTime, setCurrentTime] = React.useState(0)\n const [muted, setMuted] = React.useState(false)\n const [error, setError] = React.useState(false)\n const [showControls, setShowControls] = React.useState(true)\n const [playbackRate, setPlaybackRate] = React.useState(1)\n // Kept current every render so the mount-time keydown handler (empty deps)\n // reads the live rate instead of the stale initial 1x. See #91.\n const playbackRateRef = React.useRef(playbackRate)\n playbackRateRef.current = playbackRate\n const hideTimer = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n\n React.useEffect(() => {\n return () => clearTimeout(hideTimer.current)\n }, [])\n\n // Keyboard shortcuts — YouTube style\n React.useEffect(() => {\n function handleKeyDown(e: KeyboardEvent) {\n const el = containerRef.current\n if (!el || !el.contains(document.activeElement) && document.activeElement !== el) return\n const v = videoRef.current\n if (!v) return\n\n switch (e.key) {\n case ' ':\n case 'k':\n e.preventDefault()\n setPlaying(prev => {\n if (prev) v.pause(); else v.play()\n return !prev\n })\n break\n case 'ArrowRight':\n e.preventDefault()\n v.currentTime = Math.min(v.duration, v.currentTime + 5)\n break\n case 'ArrowLeft':\n e.preventDefault()\n v.currentTime = Math.max(0, v.currentTime - 5)\n break\n case 'j':\n e.preventDefault()\n v.currentTime = Math.max(0, v.currentTime - 10)\n break\n case 'l':\n e.preventDefault()\n v.currentTime = Math.min(v.duration, v.currentTime + 10)\n break\n case 'm':\n e.preventDefault()\n setMuted(prev => {\n v.muted = !prev\n return !prev\n })\n break\n case 'f':\n e.preventDefault()\n v.requestFullscreen?.()\n break\n case '>':\n e.preventDefault()\n cyclePlaybackRate(1)\n break\n case '<':\n e.preventDefault()\n cyclePlaybackRate(-1)\n break\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n }, [])\n\n function cyclePlaybackRate(direction: 1 | -1) {\n const idx = PLAYBACK_RATES.indexOf(playbackRateRef.current as typeof PLAYBACK_RATES[number])\n const nextIdx = Math.max(0, Math.min(PLAYBACK_RATES.length - 1, idx + direction))\n const next = PLAYBACK_RATES[nextIdx]\n setPlaybackRate(next)\n if (videoRef.current) videoRef.current.playbackRate = next\n }\n\n function togglePlay() {\n if (!videoRef.current) return\n if (playing) videoRef.current.pause()\n else videoRef.current.play()\n setPlaying(!playing)\n }\n\n function handleSeek(nextProgress: number) {\n if (!videoRef.current || !duration) return\n videoRef.current.currentTime = (nextProgress / 100) * duration\n }\n\n function handleMouseMove() {\n setShowControls(true)\n clearTimeout(hideTimer.current)\n if (playing) {\n hideTimer.current = setTimeout(() => setShowControls(false), 3000)\n }\n }\n\n if (error) return <ErrorFallback message=\"Could not load video\" url={url} />\n\n return (\n <div\n ref={containerRef}\n className=\"group relative rounded-control bg-black overflow-hidden\"\n onMouseMove={handleMouseMove}\n onMouseLeave={() => playing && setShowControls(false)}\n tabIndex={-1}\n >\n {/* eslint-disable-next-line jsx-a11y/media-has-caption */}\n <video\n ref={videoRef}\n src={url}\n className=\"max-h-[70vh] w-full\"\n onClick={togglePlay}\n muted={muted}\n onTimeUpdate={() => {\n if (!videoRef.current) return\n setCurrentTime(videoRef.current.currentTime)\n if (videoRef.current.duration) setProgress((videoRef.current.currentTime / videoRef.current.duration) * 100)\n }}\n onLoadedMetadata={() => setDuration(videoRef.current?.duration ?? 0)}\n onEnded={() => { setPlaying(false); setShowControls(true) }}\n onError={() => { setError(true); onError?.('Video failed to load') }}\n playsInline\n />\n\n {/* Play button overlay — shown when paused */}\n {!playing && (\n <motion.button\n initial={{ opacity: 0, scale: 0.8 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={springs.snappy}\n onClick={togglePlay}\n className=\"absolute inset-0 flex items-center justify-center bg-black/30\"\n aria-label=\"Play video\"\n >\n <div className=\"flex h-14 w-14 items-center justify-center rounded-pill bg-white/90 shadow-floating\">\n <Icon icon={IconPlayerPlay} size=\"xl\" className=\"text-surface-fg ml-0.5\" />\n </div>\n </motion.button>\n )}\n\n {/* Bottom controls — auto-hide after 3s during playback */}\n <AnimatePresence>\n {showControls && duration > 0 && (\n <motion.div\n initial={{ opacity: 0, y: 10 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 10 }}\n transition={tweens.fade}\n className=\"absolute bottom-0 left-0 right-0 bg-linear-to-t from-black/80 to-transparent px-ds-04 pb-ds-04 pt-ds-08\"\n >\n {/* Progress bar */}\n <MediaSlider\n className=\"mb-ds-03\"\n value={progress}\n max={100}\n step={0.1}\n onValueChange={handleSeek}\n tone=\"dark\"\n ariaLabel=\"Video progress\"\n />\n\n {/* Controls row */}\n <div className=\"flex items-center gap-ds-03\">\n <button onClick={togglePlay} className=\"text-white hover:text-white/80\" aria-label={playing ? 'Pause' : 'Play'} title={playing ? 'Pause' : 'Play'}>\n {playing ? <Icon icon={IconPlayerPause} size=\"lg\" /> : <Icon icon={IconPlayerPlay} size=\"lg\" />}\n </button>\n <VolumeControl\n volume={muted ? 0 : 1}\n muted={muted}\n onVolumeChange={(v) => {\n if (videoRef.current) videoRef.current.volume = v\n if (v > 0 && muted) { setMuted(false); if (videoRef.current) videoRef.current.muted = false }\n }}\n onMuteToggle={() => { setMuted(!muted); if (videoRef.current) videoRef.current.muted = !muted }}\n variant=\"dark\"\n />\n <span className=\"text-body-sm font-mono text-white/70 tabular-nums\">\n {formatTime(currentTime)} / {formatTime(duration)}\n </span>\n <div className=\"flex-1\" />\n <button\n onClick={() => cyclePlaybackRate(1)}\n className=\"text-body-sm font-mono text-white/70 hover:text-white px-1 rounded-control-inner hover:bg-white/10 transition-colors\"\n aria-label={`Playback speed: ${playbackRate}x`}\n >\n {playbackRate}x\n </button>\n <button\n onClick={() => videoRef.current?.requestFullscreen?.()}\n className=\"text-white hover:text-white/80\"\n aria-label=\"Fullscreen (F)\"\n title=\"Fullscreen\"\n >\n <Icon icon={IconMaximize} size=\"sm\" />\n </button>\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )\n}\n","'use client'\n\nimport { IconDownload } from '@tabler/icons-react'\nimport * as React from 'react'\n\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { cn } from '../ui/lib/utils'\nimport { Skeleton } from '../ui/skeleton'\nimport { TruncatedText } from '../ui/truncated-text'\n\n// Sub-components: direct imports for non-peer-dep renderers,\n// React.lazy for renderers that pull in optional peer deps\n// (react-zoom-pan-pinch, react-pdf) to preserve code-splitting.\nconst LazyImagePreview = React.lazy(() => import('./file-preview/image-preview'))\nconst LazyDocumentPreview = React.lazy(() => import('./file-preview/document-preview'))\nimport AudioPreview from './file-preview/audio-preview'\nimport EmbedPreview from './file-preview/embed-preview'\nimport VideoPreview from './file-preview/video-preview'\n\n// ============================================================\n// Types\n// ============================================================\n\ntype FileType = 'image' | 'pdf' | 'video' | 'audio' | 'embed'\n\nexport interface FilePreviewProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onError'> {\n url: string\n /** Auto-detected from URL/mimeType if omitted */\n type?: FileType\n mimeType?: string\n alt?: string\n /** PDF starting page @default 1 */\n initialPage?: number\n /** File name for display */\n fileName?: string\n /** File size for display (e.g. '2.4 MB') */\n fileSize?: string\n /** Called on errors (broken URL, CORS, timeout) */\n onError?: (error: string) => void\n}\n\n// ============================================================\n// Type Detection\n// ============================================================\n\nfunction detectType(url: string, mimeType?: string): FileType {\n if (mimeType) {\n if (mimeType.startsWith('image/')) return 'image'\n if (mimeType === 'application/pdf') return 'pdf'\n if (mimeType.startsWith('video/')) return 'video'\n if (mimeType.startsWith('audio/')) return 'audio'\n }\n\n const ext = url.split('?')[0].split('.').pop()?.toLowerCase()\n if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'avif', 'bmp'].includes(ext ?? '')) return 'image'\n if (ext === 'pdf') return 'pdf'\n if (['mp4', 'webm', 'ogg', 'mov'].includes(ext ?? '')) return 'video'\n if (['mp3', 'wav', 'ogg', 'aac', 'flac'].includes(ext ?? '')) return 'audio'\n\n if (url.includes('figma.com') || url.includes('loom.com') || url.includes('youtube.com') || url.includes('youtu.be') || url.includes('vimeo.com')) {\n return 'embed'\n }\n\n return 'image'\n}\n\nfunction getEmbedUrl(url: string): string {\n const ytMatch = url.match(/(?:youtube\\.com\\/watch\\?v=|youtu\\.be\\/)([^&?]+)/)\n if (ytMatch) return `https://www.youtube.com/embed/${ytMatch[1]}`\n\n const vimeoMatch = url.match(/vimeo\\.com\\/(\\d+)/)\n if (vimeoMatch) return `https://player.vimeo.com/video/${vimeoMatch[1]}`\n\n if (url.includes('figma.com')) return `https://www.figma.com/embed?embed_host=share&url=${encodeURIComponent(url)}`\n\n const loomMatch = url.match(/loom\\.com\\/share\\/([^?]+)/)\n if (loomMatch) return `https://www.loom.com/embed/${loomMatch[1]}`\n\n return url\n}\n\n// ============================================================\n// FilePreview — Main component (router)\n// ============================================================\n\nfunction FilePreview({\n url,\n type: typeProp,\n mimeType,\n alt,\n initialPage = 1,\n fileName,\n fileSize,\n onError,\n className,\n ...props\n}: FilePreviewProps) {\n const type = typeProp ?? detectType(url, mimeType)\n const fallback = <Skeleton className=\"h-64 w-full rounded-control\" />\n\n return (\n <div className={cn('flex flex-col gap-ds-03', className)} {...props}>\n {/* File info bar */}\n {(fileName || fileSize) && (\n <div className=\"flex items-center gap-ds-03\">\n {fileName && (\n <TruncatedText mode=\"middle\" className=\"min-w-0 text-body-sm font-semibold text-surface-fg\">\n {fileName}\n </TruncatedText>\n )}\n {fileSize && <Badge variant=\"subtle\" size=\"xs\">{fileSize}</Badge>}\n </div>\n )}\n\n {/* Preview */}\n {type === 'image' && (\n <React.Suspense fallback={fallback}>\n <LazyImagePreview url={url} alt={alt} onError={onError} />\n </React.Suspense>\n )}\n {type === 'pdf' && (\n <React.Suspense fallback={fallback}>\n <LazyDocumentPreview url={url} initialPage={initialPage} onError={onError} />\n </React.Suspense>\n )}\n {type === 'video' && <VideoPreview url={url} onError={onError} />}\n {type === 'audio' && <AudioPreview url={url} fileName={fileName} onError={onError} />}\n {type === 'embed' && <EmbedPreview url={url} embedUrl={getEmbedUrl(url)} onError={onError} />}\n\n {/* Download — not shown for audio (it has its own layout) */}\n {type !== 'audio' && (\n <div className=\"flex justify-end\">\n <Button variant=\"ghost\" size=\"xs\" startIcon={<Icon icon={IconDownload} />} asChild>\n <a href={url} download target=\"_blank\" rel=\"noopener noreferrer\">Download</a>\n </Button>\n </div>\n )}\n </div>\n )\n}\n\nexport { FilePreview }\nexport type { FileType }\n"],"mappings":"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"}
1
+ {"version":3,"file":"file-preview.js","names":[],"sources":["../../src/composed/file-preview/audio-preview.tsx","../../src/composed/file-preview/embed-preview.tsx","../../src/composed/file-preview/video-preview.tsx","../../src/composed/file-preview.tsx"],"sourcesContent":["'use client'\n\nimport { IconPlayerPause,IconPlayerPlay } from '@tabler/icons-react'\nimport * as React from 'react'\n\nimport { Button } from '../../ui/button'\nimport { Icon } from '../../ui/icon'\nimport { TruncatedText } from '../../ui/truncated-text'\nimport { ErrorFallback, formatTime,MediaSlider,VolumeControl } from './shared'\n\n// ============================================================\n// Audio Preview — Branded mini-player (Spotify/SoundCloud style)\n// ============================================================\n\nexport default function AudioPreview({ url, fileName, onError }: { url: string; fileName?: string; onError?: (msg: string) => void }) {\n const audioRef = React.useRef<HTMLAudioElement>(null)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const [playing, setPlaying] = React.useState(false)\n const [progress, setProgress] = React.useState(0)\n const [duration, setDuration] = React.useState(0)\n const [currentTime, setCurrentTime] = React.useState(0)\n const [volume, setVolume] = React.useState(1)\n const [muted, setMuted] = React.useState(false)\n const [error, setError] = React.useState(false)\n\n // Keyboard shortcuts\n React.useEffect(() => {\n function handleKeyDown(e: KeyboardEvent) {\n const el = containerRef.current\n if (!el || !el.contains(document.activeElement) && document.activeElement !== el) return\n const a = audioRef.current\n if (!a) return\n\n switch (e.key) {\n case ' ':\n e.preventDefault()\n setPlaying(prev => {\n if (prev) a.pause(); else a.play()\n return !prev\n })\n break\n case 'ArrowRight':\n e.preventDefault()\n a.currentTime = Math.min(a.duration || 0, a.currentTime + 5)\n break\n case 'ArrowLeft':\n e.preventDefault()\n a.currentTime = Math.max(0, a.currentTime - 5)\n break\n case 'm':\n e.preventDefault()\n setMuted(prev => {\n a.muted = !prev\n return !prev\n })\n break\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n \n }, [])\n\n function togglePlay() {\n if (!audioRef.current) return\n if (playing) audioRef.current.pause()\n else audioRef.current.play()\n setPlaying(!playing)\n }\n\n function handleSeek(nextProgress: number) {\n if (!audioRef.current || !duration) return\n audioRef.current.currentTime = (nextProgress / 100) * duration\n }\n\n function toggleMute() {\n if (!audioRef.current) return\n const next = !muted\n setMuted(next)\n audioRef.current.muted = next\n }\n\n if (error) return <ErrorFallback message=\"Could not load audio\" url={url} />\n\n return (\n <div ref={containerRef} className=\"rounded-surface bg-surface-panel overflow-hidden\" tabIndex={-1}>\n {/* eslint-disable-next-line jsx-a11y/media-has-caption */}\n <audio\n ref={audioRef}\n src={url}\n onTimeUpdate={() => {\n if (!audioRef.current) return\n setCurrentTime(audioRef.current.currentTime)\n if (audioRef.current.duration) setProgress((audioRef.current.currentTime / audioRef.current.duration) * 100)\n }}\n onLoadedMetadata={() => setDuration(audioRef.current?.duration ?? 0)}\n onEnded={() => setPlaying(false)}\n onError={() => { setError(true); onError?.('Audio failed to load') }}\n />\n\n {/* Progress bar — full width at top (Spotify style) */}\n <div className=\"px-ds-03 pt-ds-02\">\n <MediaSlider\n value={progress}\n max={100}\n step={0.1}\n onValueChange={handleSeek}\n tone=\"light\"\n ariaLabel=\"Audio progress\"\n />\n </div>\n\n {/* Controls */}\n <div className=\"flex items-center gap-ds-04 px-ds-05 py-ds-04\">\n <Button\n variant=\"solid\"\n size=\"icon-sm\"\n onClick={togglePlay}\n aria-label={playing ? 'Pause' : 'Play'}\n title={playing ? 'Pause' : 'Play'}\n className=\"shrink-0\"\n >\n {playing ? (\n <Icon icon={IconPlayerPause} size=\"sm\" />\n ) : (\n <Icon icon={IconPlayerPlay} size=\"sm\" className=\"ml-0.5\" />\n )}\n </Button>\n\n {/* File name + time */}\n <div className=\"flex-1 min-w-0\">\n {fileName && (\n <TruncatedText as=\"p\" mode=\"middle\" className=\"text-body-sm font-semibold text-surface-fg\">\n {fileName}\n </TruncatedText>\n )}\n <p className=\"text-caption font-mono text-surface-fg-muted tabular-nums\">\n {formatTime(currentTime)} / {formatTime(duration)}\n </p>\n </div>\n\n {/* Volume */}\n <VolumeControl\n volume={volume}\n muted={muted}\n onVolumeChange={(v) => {\n setVolume(v)\n if (audioRef.current) audioRef.current.volume = v\n if (v > 0 && muted) {\n setMuted(false)\n if (audioRef.current) audioRef.current.muted = false\n }\n }}\n onMuteToggle={toggleMute}\n variant=\"light\"\n />\n </div>\n </div>\n )\n}\n","'use client'\n\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { tweens } from '../../ui/lib/motion'\nimport { Skeleton } from '../../ui/skeleton'\nimport { ErrorFallback } from './shared'\n\n// ============================================================\n// Embed Preview — 16:9 with error timeout\n// ============================================================\n\nexport default function EmbedPreview({ url, embedUrl, onError }: { url: string; embedUrl: string; onError?: (msg: string) => void }) {\n const [loaded, setLoaded] = React.useState(false)\n const [error, setError] = React.useState(false)\n\n // Timeout — if iframe doesn't load in 15s, show error\n React.useEffect(() => {\n const timer = setTimeout(() => {\n if (!loaded) { setError(true); onError?.('Embed timed out') }\n }, 15000)\n return () => clearTimeout(timer)\n }, [loaded, onError])\n\n if (error) return <ErrorFallback message=\"Could not load embed\" url={url} />\n\n return (\n <div className=\"relative w-full rounded-control overflow-hidden\" style={{ aspectRatio: '16 / 9' }}>\n {!loaded && <Skeleton className=\"absolute inset-0\" />}\n <motion.iframe\n src={embedUrl}\n title=\"Embedded content\"\n onLoad={() => setLoaded(true)}\n onError={() => { setError(true); onError?.('Embed failed to load') }}\n initial={{ opacity: 0 }}\n animate={{ opacity: loaded ? 1 : 0 }}\n transition={tweens.fade}\n className=\"absolute inset-0 h-full w-full border-none\"\n allowFullScreen\n />\n </div>\n )\n}\n","'use client'\n\nimport {\n IconMaximize,\n IconPlayerPause,\n IconPlayerPlay,\n} from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { MotionPreference } from '../../motion/motion-preference'\nimport { Icon } from '../../ui/icon'\nimport { springs, tweens } from '../../ui/lib/motion'\nimport { ErrorFallback, formatTime,MediaSlider,VolumeControl } from './shared'\n\n// ============================================================\n// Video Preview — Custom player with DS styling\n// ============================================================\n\nconst PLAYBACK_RATES = [0.5, 0.75, 1, 1.25, 1.5, 2] as const\n\nexport default function VideoPreview({ url, onError }: { url: string; onError?: (msg: string) => void }) {\n const videoRef = React.useRef<HTMLVideoElement>(null)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const [playing, setPlaying] = React.useState(false)\n const [progress, setProgress] = React.useState(0)\n const [duration, setDuration] = React.useState(0)\n const [currentTime, setCurrentTime] = React.useState(0)\n const [muted, setMuted] = React.useState(false)\n const [error, setError] = React.useState(false)\n const [showControls, setShowControls] = React.useState(true)\n const [playbackRate, setPlaybackRate] = React.useState(1)\n // Kept current every render so the mount-time keydown handler (empty deps)\n // reads the live rate instead of the stale initial 1x. See #91.\n const playbackRateRef = React.useRef(playbackRate)\n playbackRateRef.current = playbackRate\n const hideTimer = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n\n React.useEffect(() => {\n return () => clearTimeout(hideTimer.current)\n }, [])\n\n // Keyboard shortcuts — YouTube style\n React.useEffect(() => {\n function handleKeyDown(e: KeyboardEvent) {\n const el = containerRef.current\n if (!el || !el.contains(document.activeElement) && document.activeElement !== el) return\n const v = videoRef.current\n if (!v) return\n\n switch (e.key) {\n case ' ':\n case 'k':\n e.preventDefault()\n setPlaying(prev => {\n if (prev) v.pause(); else v.play()\n return !prev\n })\n break\n case 'ArrowRight':\n e.preventDefault()\n v.currentTime = Math.min(v.duration, v.currentTime + 5)\n break\n case 'ArrowLeft':\n e.preventDefault()\n v.currentTime = Math.max(0, v.currentTime - 5)\n break\n case 'j':\n e.preventDefault()\n v.currentTime = Math.max(0, v.currentTime - 10)\n break\n case 'l':\n e.preventDefault()\n v.currentTime = Math.min(v.duration, v.currentTime + 10)\n break\n case 'm':\n e.preventDefault()\n setMuted(prev => {\n v.muted = !prev\n return !prev\n })\n break\n case 'f':\n e.preventDefault()\n v.requestFullscreen?.()\n break\n case '>':\n e.preventDefault()\n cyclePlaybackRate(1)\n break\n case '<':\n e.preventDefault()\n cyclePlaybackRate(-1)\n break\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n }, [])\n\n function cyclePlaybackRate(direction: 1 | -1) {\n const idx = PLAYBACK_RATES.indexOf(playbackRateRef.current as typeof PLAYBACK_RATES[number])\n const nextIdx = Math.max(0, Math.min(PLAYBACK_RATES.length - 1, idx + direction))\n const next = PLAYBACK_RATES[nextIdx]\n setPlaybackRate(next)\n if (videoRef.current) videoRef.current.playbackRate = next\n }\n\n function togglePlay() {\n if (!videoRef.current) return\n if (playing) videoRef.current.pause()\n else videoRef.current.play()\n setPlaying(!playing)\n }\n\n function handleSeek(nextProgress: number) {\n if (!videoRef.current || !duration) return\n videoRef.current.currentTime = (nextProgress / 100) * duration\n }\n\n function handleMouseMove() {\n setShowControls(true)\n clearTimeout(hideTimer.current)\n if (playing) {\n hideTimer.current = setTimeout(() => setShowControls(false), 3000)\n }\n }\n\n if (error) return <ErrorFallback message=\"Could not load video\" url={url} />\n\n return (\n <MotionPreference>\n <div\n ref={containerRef}\n className=\"group relative rounded-control bg-black overflow-hidden\"\n onMouseMove={handleMouseMove}\n onMouseLeave={() => playing && setShowControls(false)}\n tabIndex={-1}\n >\n {/* eslint-disable-next-line jsx-a11y/media-has-caption */}\n <video\n ref={videoRef}\n src={url}\n className=\"max-h-[70vh] w-full\"\n onClick={togglePlay}\n muted={muted}\n onTimeUpdate={() => {\n if (!videoRef.current) return\n setCurrentTime(videoRef.current.currentTime)\n if (videoRef.current.duration) setProgress((videoRef.current.currentTime / videoRef.current.duration) * 100)\n }}\n onLoadedMetadata={() => setDuration(videoRef.current?.duration ?? 0)}\n onEnded={() => { setPlaying(false); setShowControls(true) }}\n onError={() => { setError(true); onError?.('Video failed to load') }}\n playsInline\n />\n\n {/* Play button overlay — shown when paused */}\n {!playing && (\n <motion.button\n initial={{ opacity: 0, scale: 0.8 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={springs.snappy}\n onClick={togglePlay}\n className=\"absolute inset-0 flex items-center justify-center bg-black/30\"\n aria-label=\"Play video\"\n >\n <div className=\"flex h-14 w-14 items-center justify-center rounded-pill bg-white/90 shadow-floating\">\n <Icon icon={IconPlayerPlay} size=\"xl\" className=\"text-surface-fg ml-0.5\" />\n </div>\n </motion.button>\n )}\n\n {/* Bottom controls — auto-hide after 3s during playback */}\n <AnimatePresence>\n {showControls && duration > 0 && (\n <motion.div\n initial={{ opacity: 0, y: 10 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 10 }}\n transition={tweens.fade}\n className=\"absolute bottom-0 left-0 right-0 bg-linear-to-t from-black/80 to-transparent px-ds-04 pb-ds-04 pt-ds-08\"\n >\n {/* Progress bar */}\n <MediaSlider\n className=\"mb-ds-03\"\n value={progress}\n max={100}\n step={0.1}\n onValueChange={handleSeek}\n tone=\"dark\"\n ariaLabel=\"Video progress\"\n />\n\n {/* Controls row */}\n <div className=\"flex items-center gap-ds-03\">\n <button onClick={togglePlay} className=\"text-white hover:text-white/80\" aria-label={playing ? 'Pause' : 'Play'} title={playing ? 'Pause' : 'Play'}>\n {playing ? <Icon icon={IconPlayerPause} size=\"lg\" /> : <Icon icon={IconPlayerPlay} size=\"lg\" />}\n </button>\n <VolumeControl\n volume={muted ? 0 : 1}\n muted={muted}\n onVolumeChange={(v) => {\n if (videoRef.current) videoRef.current.volume = v\n if (v > 0 && muted) { setMuted(false); if (videoRef.current) videoRef.current.muted = false }\n }}\n onMuteToggle={() => { setMuted(!muted); if (videoRef.current) videoRef.current.muted = !muted }}\n variant=\"dark\"\n />\n <span className=\"text-body-sm font-mono text-white/70 tabular-nums\">\n {formatTime(currentTime)} / {formatTime(duration)}\n </span>\n <div className=\"flex-1\" />\n <button\n onClick={() => cyclePlaybackRate(1)}\n className=\"text-body-sm font-mono text-white/70 hover:text-white px-1 rounded-control-inner hover:bg-white/10 transition-colors\"\n aria-label={`Playback speed: ${playbackRate}x`}\n >\n {playbackRate}x\n </button>\n <button\n onClick={() => videoRef.current?.requestFullscreen?.()}\n className=\"text-white hover:text-white/80\"\n aria-label=\"Fullscreen (F)\"\n title=\"Fullscreen\"\n >\n <Icon icon={IconMaximize} size=\"sm\" />\n </button>\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n </MotionPreference>\n )\n}\n","'use client'\n\nimport { IconDownload } from '@tabler/icons-react'\nimport * as React from 'react'\n\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { cn } from '../ui/lib/utils'\nimport { Skeleton } from '../ui/skeleton'\nimport { TruncatedText } from '../ui/truncated-text'\n\n// Sub-components: direct imports for non-peer-dep renderers,\n// React.lazy for renderers that pull in optional peer deps\n// (react-zoom-pan-pinch, react-pdf) to preserve code-splitting.\nconst LazyImagePreview = React.lazy(() => import('./file-preview/image-preview'))\nconst LazyDocumentPreview = React.lazy(() => import('./file-preview/document-preview'))\nimport AudioPreview from './file-preview/audio-preview'\nimport EmbedPreview from './file-preview/embed-preview'\nimport VideoPreview from './file-preview/video-preview'\n\n// ============================================================\n// Types\n// ============================================================\n\ntype FileType = 'image' | 'pdf' | 'video' | 'audio' | 'embed'\n\nexport interface FilePreviewProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onError'> {\n url: string\n /** Auto-detected from URL/mimeType if omitted */\n type?: FileType\n mimeType?: string\n alt?: string\n /** PDF starting page @default 1 */\n initialPage?: number\n /** File name for display */\n fileName?: string\n /** File size for display (e.g. '2.4 MB') */\n fileSize?: string\n /** Called on errors (broken URL, CORS, timeout) */\n onError?: (error: string) => void\n}\n\n// ============================================================\n// Type Detection\n// ============================================================\n\nfunction detectType(url: string, mimeType?: string): FileType {\n if (mimeType) {\n if (mimeType.startsWith('image/')) return 'image'\n if (mimeType === 'application/pdf') return 'pdf'\n if (mimeType.startsWith('video/')) return 'video'\n if (mimeType.startsWith('audio/')) return 'audio'\n }\n\n const ext = url.split('?')[0].split('.').pop()?.toLowerCase()\n if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'avif', 'bmp'].includes(ext ?? '')) return 'image'\n if (ext === 'pdf') return 'pdf'\n if (['mp4', 'webm', 'ogg', 'mov'].includes(ext ?? '')) return 'video'\n if (['mp3', 'wav', 'ogg', 'aac', 'flac'].includes(ext ?? '')) return 'audio'\n\n if (url.includes('figma.com') || url.includes('loom.com') || url.includes('youtube.com') || url.includes('youtu.be') || url.includes('vimeo.com')) {\n return 'embed'\n }\n\n return 'image'\n}\n\nfunction getEmbedUrl(url: string): string {\n const ytMatch = url.match(/(?:youtube\\.com\\/watch\\?v=|youtu\\.be\\/)([^&?]+)/)\n if (ytMatch) return `https://www.youtube.com/embed/${ytMatch[1]}`\n\n const vimeoMatch = url.match(/vimeo\\.com\\/(\\d+)/)\n if (vimeoMatch) return `https://player.vimeo.com/video/${vimeoMatch[1]}`\n\n if (url.includes('figma.com')) return `https://www.figma.com/embed?embed_host=share&url=${encodeURIComponent(url)}`\n\n const loomMatch = url.match(/loom\\.com\\/share\\/([^?]+)/)\n if (loomMatch) return `https://www.loom.com/embed/${loomMatch[1]}`\n\n return url\n}\n\n// ============================================================\n// FilePreview — Main component (router)\n// ============================================================\n\nfunction FilePreview({\n url,\n type: typeProp,\n mimeType,\n alt,\n initialPage = 1,\n fileName,\n fileSize,\n onError,\n className,\n ...props\n}: FilePreviewProps) {\n const type = typeProp ?? detectType(url, mimeType)\n const fallback = <Skeleton className=\"h-64 w-full rounded-control\" />\n\n return (\n <div className={cn('flex flex-col gap-ds-03', className)} {...props}>\n {/* File info bar */}\n {(fileName || fileSize) && (\n <div className=\"flex items-center gap-ds-03\">\n {fileName && (\n <TruncatedText mode=\"middle\" className=\"min-w-0 text-body-sm font-semibold text-surface-fg\">\n {fileName}\n </TruncatedText>\n )}\n {fileSize && <Badge variant=\"subtle\" size=\"xs\">{fileSize}</Badge>}\n </div>\n )}\n\n {/* Preview */}\n {type === 'image' && (\n <React.Suspense fallback={fallback}>\n <LazyImagePreview url={url} alt={alt} onError={onError} />\n </React.Suspense>\n )}\n {type === 'pdf' && (\n <React.Suspense fallback={fallback}>\n <LazyDocumentPreview url={url} initialPage={initialPage} onError={onError} />\n </React.Suspense>\n )}\n {type === 'video' && <VideoPreview url={url} onError={onError} />}\n {type === 'audio' && <AudioPreview url={url} fileName={fileName} onError={onError} />}\n {type === 'embed' && <EmbedPreview url={url} embedUrl={getEmbedUrl(url)} onError={onError} />}\n\n {/* Download — not shown for audio (it has its own layout) */}\n {type !== 'audio' && (\n <div className=\"flex justify-end\">\n <Button variant=\"ghost\" size=\"xs\" startIcon={<Icon icon={IconDownload} />} asChild>\n <a href={url} download target=\"_blank\" rel=\"noopener noreferrer\">Download</a>\n </Button>\n </div>\n )}\n </div>\n )\n}\n\nexport { FilePreview }\nexport type { FileType }\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"filter-bar.d.ts","sourceRoot":"","sources":["../../src/composed/filter-bar.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAqB9B,KAAK,aAAa,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAQvC;;;;GAIG;AACH,MAAM,WAAW,cAAe,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC1E,8FAA8F;IAC9F,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,yFAAyF;IACzF,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACxC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,aAAa,CAAA;CACrB;AAED,QAAA,MAAM,SAAS,uFAsCb,CAAA;AAOF,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC3C,gDAAgD;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,iBAAS,YAAY,CAAC,EACpB,KAAK,EACL,KAAK,EACL,aAAa,EACb,OAAO,EACP,QAAgB,EAChB,SAAS,GACV,EAAE,iBAAiB,qBAsBnB;AAMD,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;IACzC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,iBAAS,iBAAiB,CAAC,EACzB,KAAK,EACL,KAAK,EACL,aAAa,EACb,OAAO,EACP,SAAS,GACV,EAAE,sBAAsB,qBAsDxB;AAED,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAC,YAAY,EAAE,CAAA"}
1
+ {"version":3,"file":"filter-bar.d.ts","sourceRoot":"","sources":["../../src/composed/filter-bar.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAsB9B,KAAK,aAAa,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAQvC;;;;GAIG;AACH,MAAM,WAAW,cAAe,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC1E,8FAA8F;IAC9F,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,yFAAyF;IACzF,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACxC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,aAAa,CAAA;CACrB;AAED,QAAA,MAAM,SAAS,uFAsCb,CAAA;AAOF,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC3C,gDAAgD;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,iBAAS,YAAY,CAAC,EACpB,KAAK,EACL,KAAK,EACL,aAAa,EACb,OAAO,EACP,QAAgB,EAChB,SAAS,GACV,EAAE,iBAAiB,qBAsBnB;AAMD,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;IACzC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,iBAAS,iBAAiB,CAAC,EACzB,KAAK,EACL,KAAK,EACL,aAAa,EACb,OAAO,EACP,SAAS,GACV,EAAE,sBAAsB,qBAwDxB;AAED,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAC,YAAY,EAAE,CAAA"}
@@ -1,104 +1,105 @@
1
1
  "use client";
2
2
  import { springs as e } from "../ui/lib/motion.js";
3
- import { cn as t } from "../ui/lib/utils.js";
4
- import { Icon as n } from "../ui/icon.js";
5
- import { BadgeCompound as r } from "../_chunks/badge-group.js";
6
- import { Button as i } from "../ui/button.js";
7
- import { SearchInput as a } from "../ui/search-input.js";
8
- import { Select as o, SelectContent as s, SelectItem as c, SelectTrigger as l, SelectValue as u } from "../ui/select.js";
9
- import { MultiSelectPopover as d } from "./multi-select-popover.js";
10
- import * as f from "react";
11
- import { jsx as p, jsxs as m } from "react/jsx-runtime";
12
- import { IconChevronDown as h } from "@tabler/icons-react";
13
- import { motion as g } from "framer-motion";
3
+ import { MotionPreference as t } from "../_chunks/motion-preference.js";
4
+ import { cn as n } from "../ui/lib/utils.js";
5
+ import { Icon as r } from "../ui/icon.js";
6
+ import { BadgeCompound as i } from "../_chunks/badge-group.js";
7
+ import { Button as a } from "../ui/button.js";
8
+ import { SearchInput as o } from "../ui/search-input.js";
9
+ import { Select as s, SelectContent as c, SelectItem as l, SelectTrigger as u, SelectValue as d } from "../ui/select.js";
10
+ import { MultiSelectPopover as f } from "./multi-select-popover.js";
11
+ import * as p from "react";
12
+ import { jsx as m, jsxs as h } from "react/jsx-runtime";
13
+ import { IconChevronDown as g } from "@tabler/icons-react";
14
+ import { motion as _ } from "framer-motion";
14
15
  //#region src/composed/filter-bar.tsx
15
- var _ = f.createContext({ size: "sm" }), v = f.forwardRef(({ searchValue: e, onSearchChange: n, searchPlaceholder: r = "Search...", onClearAll: o, size: s = "sm", children: c, className: l, ...u }, d) => /* @__PURE__ */ p(_.Provider, {
16
+ var v = p.createContext({ size: "sm" }), y = p.forwardRef(({ searchValue: e, onSearchChange: t, searchPlaceholder: r = "Search...", onClearAll: i, size: s = "sm", children: c, className: l, ...u }, d) => /* @__PURE__ */ m(v.Provider, {
16
17
  value: { size: s },
17
- children: /* @__PURE__ */ m("div", {
18
+ children: /* @__PURE__ */ h("div", {
18
19
  ref: d,
19
- className: t("flex flex-wrap items-center gap-ds-03", l),
20
+ className: n("flex flex-wrap items-center gap-ds-03", l),
20
21
  role: "toolbar",
21
22
  "aria-label": "Filters",
22
23
  ...u,
23
24
  children: [
24
- n && /* @__PURE__ */ p(a, {
25
+ t && /* @__PURE__ */ m(o, {
25
26
  size: s,
26
27
  value: e,
27
- onChange: (e) => n(e.target.value),
28
- onClear: e ? () => n("") : void 0,
28
+ onChange: (e) => t(e.target.value),
29
+ onClear: e ? () => t("") : void 0,
29
30
  placeholder: r,
30
31
  className: "w-48"
31
32
  }),
32
33
  c,
33
- o && /* @__PURE__ */ p(i, {
34
+ i && /* @__PURE__ */ m(a, {
34
35
  variant: "ghost",
35
36
  size: s,
36
- onClick: o,
37
+ onClick: i,
37
38
  children: "Clear all"
38
39
  })
39
40
  ]
40
41
  })
41
42
  }));
42
- v.displayName = "FilterBar";
43
- function y({ label: e, value: n, onValueChange: r, options: i, allLabel: a = "All", className: d }) {
44
- let { size: h } = f.useContext(_);
45
- return /* @__PURE__ */ m(o, {
46
- value: n,
43
+ y.displayName = "FilterBar";
44
+ function b({ label: e, value: t, onValueChange: r, options: i, allLabel: a = "All", className: o }) {
45
+ let { size: f } = p.useContext(v);
46
+ return /* @__PURE__ */ h(s, {
47
+ value: t,
47
48
  onValueChange: r,
48
- children: [/* @__PURE__ */ p(l, {
49
- size: h,
50
- className: t("w-40", n !== "" && n !== "all" && "border-accent-7", d),
51
- children: /* @__PURE__ */ p(u, { placeholder: e })
52
- }), /* @__PURE__ */ m(s, { children: [/* @__PURE__ */ p(c, {
49
+ children: [/* @__PURE__ */ m(u, {
50
+ size: f,
51
+ className: n("w-40", t !== "" && t !== "all" && "border-accent-7", o),
52
+ children: /* @__PURE__ */ m(d, { placeholder: e })
53
+ }), /* @__PURE__ */ h(c, { children: [/* @__PURE__ */ m(l, {
53
54
  value: "all",
54
55
  children: a
55
- }), i.map((e) => /* @__PURE__ */ p(c, {
56
+ }), i.map((e) => /* @__PURE__ */ m(l, {
56
57
  value: e.value,
57
58
  children: e.label
58
59
  }, e.value))] })]
59
60
  });
60
61
  }
61
- function b({ label: i, value: a, onValueChange: o, options: s, className: c }) {
62
- let { size: l } = f.useContext(_), u = a.length;
63
- return /* @__PURE__ */ p(d, {
64
- items: f.useMemo(() => s.map((e) => ({
62
+ function x({ label: a, value: o, onValueChange: s, options: c, className: l }) {
63
+ let { size: u } = p.useContext(v), d = o.length;
64
+ return /* @__PURE__ */ m(t, { children: /* @__PURE__ */ m(f, {
65
+ items: p.useMemo(() => c.map((e) => ({
65
66
  id: e.value,
66
67
  label: e.label
67
- })), [s]),
68
- value: a,
69
- onValueChange: o,
70
- searchPlaceholder: `Search ${i.toLowerCase()}...`,
71
- children: /* @__PURE__ */ m("button", {
68
+ })), [c]),
69
+ value: o,
70
+ onValueChange: s,
71
+ searchPlaceholder: `Search ${a.toLowerCase()}...`,
72
+ children: /* @__PURE__ */ h("button", {
72
73
  type: "button",
73
- className: t("flex items-center justify-between gap-ds-02 whitespace-nowrap rounded-control border bg-surface-raised-hover text-surface-fg", "hover:bg-surface-raised-active transition-colors duration-fast-01 ease-productive-standard", {
74
+ className: n("flex items-center justify-between gap-ds-02 whitespace-nowrap rounded-control border bg-surface-panel-hover text-surface-fg", "hover:bg-surface-panel-active transition-colors duration-fast-01 ease-productive-standard", {
74
75
  xs: "h-ds-xs-plus text-body-sm px-ds-02",
75
76
  sm: "h-ds-sm text-body-sm px-ds-03",
76
77
  md: "h-ds-md text-body-md px-ds-04"
77
- }[l], u > 0 ? "border-accent-7" : "border-surface-border-strong", "w-40", c),
78
- children: [/* @__PURE__ */ m("span", {
78
+ }[u], d > 0 ? "border-accent-7" : "border-surface-border-strong", "w-40", l),
79
+ children: [/* @__PURE__ */ h("span", {
79
80
  className: "flex min-w-0 items-center gap-ds-02",
80
- children: [/* @__PURE__ */ p("span", {
81
+ children: [/* @__PURE__ */ m("span", {
81
82
  className: "truncate",
82
- children: i
83
- }), u > 0 && /* @__PURE__ */ p(g.span, {
83
+ children: a
84
+ }), d > 0 && /* @__PURE__ */ m(_.span, {
84
85
  initial: { scale: .8 },
85
86
  animate: { scale: 1 },
86
87
  transition: e.snappy,
87
88
  className: "inline-flex",
88
- children: /* @__PURE__ */ p(r, {
89
+ children: /* @__PURE__ */ m(i, {
89
90
  size: "xs",
90
91
  variant: "solid",
91
92
  className: "ml-ds-01",
92
- children: u
93
+ children: d
93
94
  })
94
- }, u)]
95
- }), /* @__PURE__ */ p(n, {
96
- icon: h,
95
+ }, d)]
96
+ }), /* @__PURE__ */ m(r, {
97
+ icon: g,
97
98
  size: "xs",
98
99
  className: "opacity-50 shrink-0"
99
100
  })]
100
101
  })
101
- });
102
+ }) });
102
103
  }
103
104
  //#endregion
104
- export { v as FilterBar, b as FilterMultiSelect, y as FilterSelect };
105
+ export { y as FilterBar, x as FilterMultiSelect, b as FilterSelect };
@@ -1 +1 @@
1
- {"version":3,"file":"filter-bar.js","names":[],"sources":["../../src/composed/filter-bar.tsx"],"sourcesContent":["'use client'\n\nimport { IconChevronDown } from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { springs } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\nimport { SearchInput } from '../ui/search-input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../ui/select'\nimport { type MultiSelectItem,MultiSelectPopover } from './multi-select-popover'\n\n// ============================================================\n// Context\n// ============================================================\n\ntype FilterBarSize = 'xs' | 'sm' | 'md'\n\nconst FilterBarContext = React.createContext<{ size: FilterBarSize }>({ size: 'sm' })\n\n// ============================================================\n// FilterBar\n// ============================================================\n\n/**\n * A toolbar for filtering lists/tables. Composes a search input, slot-based\n * filter controls (Select, MultiSelect), and an optional \"Clear all\" button.\n * Size is propagated to all child controls via context.\n */\nexport interface FilterBarProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Controlled search input value. Omit `onSearchChange` to hide the search input entirely. */\n searchValue?: string\n /** Called when the search input changes. If not provided, the search input is hidden. */\n onSearchChange?: (value: string) => void\n searchPlaceholder?: string\n /** When provided, a \"Clear all\" button appears that calls this handler. */\n onClearAll?: () => void\n /** Size propagated to all child controls. @default 'sm' */\n size?: FilterBarSize\n}\n\nconst FilterBar = React.forwardRef<HTMLDivElement, FilterBarProps>(({\n searchValue,\n onSearchChange,\n searchPlaceholder = 'Search...',\n onClearAll,\n size = 'sm',\n children,\n className,\n ...props\n}, ref) => {\n return (\n <FilterBarContext.Provider value={{ size }}>\n <div\n ref={ref}\n className={cn('flex flex-wrap items-center gap-ds-03', className)}\n role=\"toolbar\"\n aria-label=\"Filters\"\n {...props}\n >\n {onSearchChange && (\n <SearchInput\n size={size}\n value={searchValue}\n onChange={(e) => onSearchChange(e.target.value)}\n onClear={searchValue ? () => onSearchChange('') : undefined}\n placeholder={searchPlaceholder}\n className=\"w-48\"\n />\n )}\n {children}\n {onClearAll && (\n <Button variant=\"ghost\" size={size} onClick={onClearAll}>\n Clear all\n </Button>\n )}\n </div>\n </FilterBarContext.Provider>\n )\n})\nFilterBar.displayName = 'FilterBar'\n\n// ============================================================\n// FilterSelect\n// ============================================================\n\nexport interface FilterSelectProps {\n label: string\n value: string\n onValueChange: (value: string) => void\n options: { value: string; label: string }[]\n /** Label for the \"all\" option @default 'All' */\n allLabel?: string\n className?: string\n}\n\nfunction FilterSelect({\n label,\n value,\n onValueChange,\n options,\n allLabel = 'All',\n className,\n}: FilterSelectProps) {\n const { size } = React.useContext(FilterBarContext)\n const isFiltered = value !== '' && value !== 'all'\n\n return (\n <Select value={value} onValueChange={onValueChange}>\n <SelectTrigger\n size={size}\n className={cn('w-40', isFiltered && 'border-accent-7', className)}\n >\n <SelectValue placeholder={label} />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">{allLabel}</SelectItem>\n {options.map((o) => (\n <SelectItem key={o.value} value={o.value}>\n {o.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n}\n\n// ============================================================\n// FilterMultiSelect\n// ============================================================\n\nexport interface FilterMultiSelectProps {\n label: string\n value: string[]\n onValueChange: (values: string[]) => void\n options: { value: string; label: string }[]\n className?: string\n}\n\nfunction FilterMultiSelect({\n label,\n value,\n onValueChange,\n options,\n className,\n}: FilterMultiSelectProps) {\n const { size } = React.useContext(FilterBarContext)\n const count = value.length\n\n const items: MultiSelectItem[] = React.useMemo(\n () => options.map((o) => ({ id: o.value, label: o.label })),\n [options],\n )\n\n // Trigger button sized by FilterBar context\n const triggerSizeClasses: Record<string, string> = {\n xs: 'h-ds-xs-plus text-body-sm px-ds-02',\n sm: 'h-ds-sm text-body-sm px-ds-03',\n md: 'h-ds-md text-body-md px-ds-04',\n }\n\n return (\n <MultiSelectPopover\n items={items}\n value={value}\n onValueChange={onValueChange}\n searchPlaceholder={`Search ${label.toLowerCase()}...`}\n >\n <button\n type=\"button\"\n className={cn(\n 'flex items-center justify-between gap-ds-02 whitespace-nowrap rounded-control border bg-surface-raised-hover text-surface-fg',\n 'hover:bg-surface-raised-active transition-colors duration-fast-01 ease-productive-standard',\n triggerSizeClasses[size],\n count > 0 ? 'border-accent-7' : 'border-surface-border-strong',\n 'w-40',\n className,\n )}\n >\n <span className=\"flex min-w-0 items-center gap-ds-02\">\n <span className=\"truncate\">{label}</span>\n {count > 0 && (\n <motion.span\n key={count}\n initial={{ scale: 0.8 }}\n animate={{ scale: 1 }}\n transition={springs.snappy}\n className=\"inline-flex\"\n >\n <Badge size=\"xs\" variant=\"solid\" className=\"ml-ds-01\">\n {count}\n </Badge>\n </motion.span>\n )}\n </span>\n <Icon icon={IconChevronDown} size=\"xs\" className=\"opacity-50 shrink-0\" />\n </button>\n </MultiSelectPopover>\n )\n}\n\nexport { FilterBar, FilterMultiSelect,FilterSelect }\n"],"mappings":";;;;;;;;;;;;;;AA2BA,IAAM,IAAmB,EAAM,cAAuC,EAAE,MAAM,KAAK,CAAC,GAuB9E,IAAY,EAAM,YAA4C,EAClE,gBACA,mBACA,uBAAoB,aACpB,eACA,UAAO,MACP,aACA,cACA,GAAG,KACF,MAEC,kBAAC,EAAiB,UAAlB;CAA2B,OAAO,EAAE,QAAK;WACvC,kBAAC,OAAD;EACO;EACL,WAAW,EAAG,yCAAyC,CAAS;EAChE,MAAK;EACL,cAAW;EACX,GAAI;YALN;GAOG,KACC,kBAAC,GAAD;IACQ;IACN,OAAO;IACP,WAAW,MAAM,EAAe,EAAE,OAAO,KAAK;IAC9C,SAAS,UAAoB,EAAe,EAAE,IAAI,KAAA;IAClD,aAAa;IACb,WAAU;GACX,CAAA;GAEF;GACA,KACC,kBAAC,GAAD;IAAQ,SAAQ;IAAc;IAAM,SAAS;cAAY;GAEjD,CAAA;EAEP;;AACoB,CAAA,CAE9B;AACD,EAAU,cAAc;AAgBxB,SAAS,EAAa,EACpB,UACA,UACA,kBACA,YACA,cAAW,OACX,gBACoB;CACpB,IAAM,EAAE,YAAS,EAAM,WAAW,CAAgB;CAGlD,OACE,kBAAC,GAAD;EAAe;EAAsB;YAArC,CACE,kBAAC,GAAD;GACQ;GACN,WAAW,EAAG,QAND,MAAU,MAAM,MAAU,SAMH,mBAAmB,CAAS;aAEhE,kBAAC,GAAD,EAAa,aAAa,EAAQ,CAAA;EACrB,CAAA,GACf,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;GAAY,OAAM;aAAO;EAAqB,CAAA,GAC7C,EAAQ,KAAK,MACZ,kBAAC,GAAD;GAA0B,OAAO,EAAE;aAChC,EAAE;EACO,GAFK,EAAE,KAEP,CACb,CACY,EAAA,CAAA,CACT;;AAEZ;AAcA,SAAS,EAAkB,EACzB,UACA,UACA,kBACA,YACA,gBACyB;CACzB,IAAM,EAAE,YAAS,EAAM,WAAW,CAAgB,GAC5C,IAAQ,EAAM;CAcpB,OACE,kBAAC,GAAD;EACS,OAdsB,EAAM,cAC/B,EAAQ,KAAK,OAAO;GAAE,IAAI,EAAE;GAAO,OAAO,EAAE;EAAM,EAAE,GAC1D,CAAC,CAAO,CAYC;EACA;EACQ;EACf,mBAAmB,UAAU,EAAM,YAAY,EAAE;YAEjD,kBAAC,UAAD;GACE,MAAK;GACL,WAAW,EACT,gIACA,8FACA;IAjBN,IAAI;IACJ,IAAI;IACJ,IAAI;GAeE,EAAmB,IACnB,IAAQ,IAAI,oBAAoB,gCAChC,QACA,CACF;aATF,CAWE,kBAAC,QAAD;IAAM,WAAU;cAAhB,CACE,kBAAC,QAAD;KAAM,WAAU;eAAY;IAAY,CAAA,GACvC,IAAQ,KACP,kBAAC,EAAO,MAAR;KAEE,SAAS,EAAE,OAAO,GAAI;KACtB,SAAS,EAAE,OAAO,EAAE;KACpB,YAAY,EAAQ;KACpB,WAAU;eAEV,kBAAC,GAAD;MAAO,MAAK;MAAK,SAAQ;MAAQ,WAAU;gBACxC;KACI,CAAA;IACI,GATN,CASM,CAEX;OACN,kBAAC,GAAD;IAAM,MAAM;IAAiB,MAAK;IAAK,WAAU;GAAuB,CAAA,CAClE;;CACU,CAAA;AAExB"}
1
+ {"version":3,"file":"filter-bar.js","names":[],"sources":["../../src/composed/filter-bar.tsx"],"sourcesContent":["'use client'\n\nimport { IconChevronDown } from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { MotionPreference } from '../motion/motion-preference'\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { springs } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\nimport { SearchInput } from '../ui/search-input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../ui/select'\nimport { type MultiSelectItem,MultiSelectPopover } from './multi-select-popover'\n\n// ============================================================\n// Context\n// ============================================================\n\ntype FilterBarSize = 'xs' | 'sm' | 'md'\n\nconst FilterBarContext = React.createContext<{ size: FilterBarSize }>({ size: 'sm' })\n\n// ============================================================\n// FilterBar\n// ============================================================\n\n/**\n * A toolbar for filtering lists/tables. Composes a search input, slot-based\n * filter controls (Select, MultiSelect), and an optional \"Clear all\" button.\n * Size is propagated to all child controls via context.\n */\nexport interface FilterBarProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Controlled search input value. Omit `onSearchChange` to hide the search input entirely. */\n searchValue?: string\n /** Called when the search input changes. If not provided, the search input is hidden. */\n onSearchChange?: (value: string) => void\n searchPlaceholder?: string\n /** When provided, a \"Clear all\" button appears that calls this handler. */\n onClearAll?: () => void\n /** Size propagated to all child controls. @default 'sm' */\n size?: FilterBarSize\n}\n\nconst FilterBar = React.forwardRef<HTMLDivElement, FilterBarProps>(({\n searchValue,\n onSearchChange,\n searchPlaceholder = 'Search...',\n onClearAll,\n size = 'sm',\n children,\n className,\n ...props\n}, ref) => {\n return (\n <FilterBarContext.Provider value={{ size }}>\n <div\n ref={ref}\n className={cn('flex flex-wrap items-center gap-ds-03', className)}\n role=\"toolbar\"\n aria-label=\"Filters\"\n {...props}\n >\n {onSearchChange && (\n <SearchInput\n size={size}\n value={searchValue}\n onChange={(e) => onSearchChange(e.target.value)}\n onClear={searchValue ? () => onSearchChange('') : undefined}\n placeholder={searchPlaceholder}\n className=\"w-48\"\n />\n )}\n {children}\n {onClearAll && (\n <Button variant=\"ghost\" size={size} onClick={onClearAll}>\n Clear all\n </Button>\n )}\n </div>\n </FilterBarContext.Provider>\n )\n})\nFilterBar.displayName = 'FilterBar'\n\n// ============================================================\n// FilterSelect\n// ============================================================\n\nexport interface FilterSelectProps {\n label: string\n value: string\n onValueChange: (value: string) => void\n options: { value: string; label: string }[]\n /** Label for the \"all\" option @default 'All' */\n allLabel?: string\n className?: string\n}\n\nfunction FilterSelect({\n label,\n value,\n onValueChange,\n options,\n allLabel = 'All',\n className,\n}: FilterSelectProps) {\n const { size } = React.useContext(FilterBarContext)\n const isFiltered = value !== '' && value !== 'all'\n\n return (\n <Select value={value} onValueChange={onValueChange}>\n <SelectTrigger\n size={size}\n className={cn('w-40', isFiltered && 'border-accent-7', className)}\n >\n <SelectValue placeholder={label} />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">{allLabel}</SelectItem>\n {options.map((o) => (\n <SelectItem key={o.value} value={o.value}>\n {o.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n}\n\n// ============================================================\n// FilterMultiSelect\n// ============================================================\n\nexport interface FilterMultiSelectProps {\n label: string\n value: string[]\n onValueChange: (values: string[]) => void\n options: { value: string; label: string }[]\n className?: string\n}\n\nfunction FilterMultiSelect({\n label,\n value,\n onValueChange,\n options,\n className,\n}: FilterMultiSelectProps) {\n const { size } = React.useContext(FilterBarContext)\n const count = value.length\n\n const items: MultiSelectItem[] = React.useMemo(\n () => options.map((o) => ({ id: o.value, label: o.label })),\n [options],\n )\n\n // Trigger button sized by FilterBar context\n const triggerSizeClasses: Record<string, string> = {\n xs: 'h-ds-xs-plus text-body-sm px-ds-02',\n sm: 'h-ds-sm text-body-sm px-ds-03',\n md: 'h-ds-md text-body-md px-ds-04',\n }\n\n return (\n <MotionPreference>\n <MultiSelectPopover\n items={items}\n value={value}\n onValueChange={onValueChange}\n searchPlaceholder={`Search ${label.toLowerCase()}...`}\n >\n <button\n type=\"button\"\n className={cn(\n 'flex items-center justify-between gap-ds-02 whitespace-nowrap rounded-control border bg-surface-panel-hover text-surface-fg',\n 'hover:bg-surface-panel-active transition-colors duration-fast-01 ease-productive-standard',\n triggerSizeClasses[size],\n count > 0 ? 'border-accent-7' : 'border-surface-border-strong',\n 'w-40',\n className,\n )}\n >\n <span className=\"flex min-w-0 items-center gap-ds-02\">\n <span className=\"truncate\">{label}</span>\n {count > 0 && (\n <motion.span\n key={count}\n initial={{ scale: 0.8 }}\n animate={{ scale: 1 }}\n transition={springs.snappy}\n className=\"inline-flex\"\n >\n <Badge size=\"xs\" variant=\"solid\" className=\"ml-ds-01\">\n {count}\n </Badge>\n </motion.span>\n )}\n </span>\n <Icon icon={IconChevronDown} size=\"xs\" className=\"opacity-50 shrink-0\" />\n </button>\n </MultiSelectPopover>\n </MotionPreference>\n )\n}\n\nexport { FilterBar, FilterMultiSelect,FilterSelect }\n"],"mappings":";;;;;;;;;;;;;;;AA4BA,IAAM,IAAmB,EAAM,cAAuC,EAAE,MAAM,KAAK,CAAC,GAuB9E,IAAY,EAAM,YAA4C,EAClE,gBACA,mBACA,uBAAoB,aACpB,eACA,UAAO,MACP,aACA,cACA,GAAG,KACF,MAEC,kBAAC,EAAiB,UAAlB;CAA2B,OAAO,EAAE,QAAK;WACvC,kBAAC,OAAD;EACO;EACL,WAAW,EAAG,yCAAyC,CAAS;EAChE,MAAK;EACL,cAAW;EACX,GAAI;YALN;GAOG,KACC,kBAAC,GAAD;IACQ;IACN,OAAO;IACP,WAAW,MAAM,EAAe,EAAE,OAAO,KAAK;IAC9C,SAAS,UAAoB,EAAe,EAAE,IAAI,KAAA;IAClD,aAAa;IACb,WAAU;GACX,CAAA;GAEF;GACA,KACC,kBAAC,GAAD;IAAQ,SAAQ;IAAc;IAAM,SAAS;cAAY;GAEjD,CAAA;EAEP;;AACoB,CAAA,CAE9B;AACD,EAAU,cAAc;AAgBxB,SAAS,EAAa,EACpB,UACA,UACA,kBACA,YACA,cAAW,OACX,gBACoB;CACpB,IAAM,EAAE,YAAS,EAAM,WAAW,CAAgB;CAGlD,OACE,kBAAC,GAAD;EAAe;EAAsB;YAArC,CACE,kBAAC,GAAD;GACQ;GACN,WAAW,EAAG,QAND,MAAU,MAAM,MAAU,SAMH,mBAAmB,CAAS;aAEhE,kBAAC,GAAD,EAAa,aAAa,EAAQ,CAAA;EACrB,CAAA,GACf,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;GAAY,OAAM;aAAO;EAAqB,CAAA,GAC7C,EAAQ,KAAK,MACZ,kBAAC,GAAD;GAA0B,OAAO,EAAE;aAChC,EAAE;EACO,GAFK,EAAE,KAEP,CACb,CACY,EAAA,CAAA,CACT;;AAEZ;AAcA,SAAS,EAAkB,EACzB,UACA,UACA,kBACA,YACA,gBACyB;CACzB,IAAM,EAAE,YAAS,EAAM,WAAW,CAAgB,GAC5C,IAAQ,EAAM;CAcpB,OACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EACS,OAfoB,EAAM,cAC/B,EAAQ,KAAK,OAAO;GAAE,IAAI,EAAE;GAAO,OAAO,EAAE;EAAM,EAAE,GAC1D,CAAC,CAAO,CAaG;EACA;EACQ;EACf,mBAAmB,UAAU,EAAM,YAAY,EAAE;YAEjD,kBAAC,UAAD;GACE,MAAK;GACL,WAAW,EACT,+HACA,6FACA;IAlBR,IAAI;IACJ,IAAI;IACJ,IAAI;GAgBI,EAAmB,IACnB,IAAQ,IAAI,oBAAoB,gCAChC,QACA,CACF;aATF,CAWE,kBAAC,QAAD;IAAM,WAAU;cAAhB,CACE,kBAAC,QAAD;KAAM,WAAU;eAAY;IAAY,CAAA,GACvC,IAAQ,KACP,kBAAC,EAAO,MAAR;KAEE,SAAS,EAAE,OAAO,GAAI;KACtB,SAAS,EAAE,OAAO,EAAE;KACpB,YAAY,EAAQ;KACpB,WAAU;eAEV,kBAAC,GAAD;MAAO,MAAK;MAAK,SAAQ;MAAQ,WAAU;gBACxC;KACI,CAAA;IACI,GATN,CASM,CAEX;OACN,kBAAC,GAAD;IAAM,MAAM;IAAiB,MAAK;IAAK,WAAU;GAAuB,CAAA,CAClE;;CACU,CAAA,EACJ,CAAA;AAEtB"}
@@ -1 +1 @@
1
- {"version":3,"file":"form-section.d.ts","sourceRoot":"","sources":["../../src/composed/form-section.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAW9B,MAAM,WAAW,gBAAiB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC5E,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,oBAAoB;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAMD,QAAA,MAAM,WAAW,yFAwDf,CAAA;AAGF,OAAO,EAAE,WAAW,EAAE,CAAA"}
1
+ {"version":3,"file":"form-section.d.ts","sourceRoot":"","sources":["../../src/composed/form-section.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAY9B,MAAM,WAAW,gBAAiB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC5E,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,oBAAoB;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAMD,QAAA,MAAM,WAAW,yFA0Df,CAAA;AAGF,OAAO,EAAE,WAAW,EAAE,CAAA"}
@@ -1,63 +1,64 @@
1
1
  "use client";
2
2
  import { springs as e } from "../ui/lib/motion.js";
3
- import { cn as t } from "../ui/lib/utils.js";
4
- import { Icon as n } from "../ui/icon.js";
5
- import { Collapsible as r, CollapsibleContent as i, CollapsibleTrigger as a } from "../ui/collapsible.js";
6
- import * as o from "react";
7
- import { jsx as s, jsxs as c } from "react/jsx-runtime";
8
- import { IconChevronDown as l } from "@tabler/icons-react";
9
- import { motion as u } from "framer-motion";
3
+ import { MotionPreference as t } from "../_chunks/motion-preference.js";
4
+ import { cn as n } from "../ui/lib/utils.js";
5
+ import { Icon as r } from "../ui/icon.js";
6
+ import { Collapsible as i, CollapsibleContent as a, CollapsibleTrigger as o } from "../ui/collapsible.js";
7
+ import * as s from "react";
8
+ import { jsx as c, jsxs as l } from "react/jsx-runtime";
9
+ import { IconChevronDown as u } from "@tabler/icons-react";
10
+ import { motion as d } from "framer-motion";
10
11
  //#region src/composed/form-section.tsx
11
- var d = o.forwardRef(({ title: d, description: f, collapsible: p = !1, defaultOpen: m = !0, children: h, className: g, ..._ }, v) => {
12
- let y = /* @__PURE__ */ c("div", {
12
+ var f = s.forwardRef(({ title: f, description: p, collapsible: m = !1, defaultOpen: h = !0, children: g, className: _, ...v }, y) => {
13
+ let b = /* @__PURE__ */ l("div", {
13
14
  className: "flex flex-col gap-ds-01",
14
- children: [/* @__PURE__ */ s("span", {
15
+ children: [/* @__PURE__ */ c("span", {
15
16
  className: "text-body-md font-semibold text-surface-fg font-sans",
16
- children: d
17
- }), f && /* @__PURE__ */ s("span", {
18
- className: "text-body-sm text-surface-fg-muted font-sans",
19
17
  children: f
18
+ }), p && /* @__PURE__ */ c("span", {
19
+ className: "text-body-sm text-surface-fg-muted font-sans",
20
+ children: p
20
21
  })]
21
- }), [b, x] = o.useState(m);
22
- return p ? /* @__PURE__ */ c(r, {
23
- defaultOpen: m,
24
- onOpenChange: x,
25
- className: t("space-y-ds-04", g),
26
- ..._,
22
+ }), [x, S] = s.useState(h);
23
+ return m ? /* @__PURE__ */ c(t, { children: /* @__PURE__ */ l(i, {
24
+ defaultOpen: h,
25
+ onOpenChange: S,
26
+ className: n("space-y-ds-04", _),
27
+ ...v,
27
28
  children: [
28
- /* @__PURE__ */ c(a, {
29
+ /* @__PURE__ */ l(o, {
29
30
  className: "flex w-full items-center justify-between py-ds-02 group",
30
- children: [y, /* @__PURE__ */ s(u.span, {
31
- animate: { rotate: b ? 180 : 0 },
31
+ children: [b, /* @__PURE__ */ c(d.span, {
32
+ animate: { rotate: x ? 180 : 0 },
32
33
  transition: e.snappy,
33
34
  className: "inline-flex",
34
- children: /* @__PURE__ */ s(n, {
35
- icon: l,
35
+ children: /* @__PURE__ */ c(r, {
36
+ icon: u,
36
37
  size: "sm",
37
38
  className: "text-surface-fg-muted"
38
39
  })
39
40
  })]
40
41
  }),
41
- /* @__PURE__ */ s("div", { className: "border-b border-surface-border-subtle" }),
42
- /* @__PURE__ */ s(i, { children: /* @__PURE__ */ s("div", {
42
+ /* @__PURE__ */ c("div", { className: "border-b border-surface-border-subtle" }),
43
+ /* @__PURE__ */ c(a, { children: /* @__PURE__ */ c("div", {
43
44
  className: "space-y-ds-04 pt-ds-02",
44
- children: h
45
+ children: g
45
46
  }) })
46
47
  ]
47
- }) : /* @__PURE__ */ c("div", {
48
- ref: v,
49
- className: t("space-y-ds-04", g),
50
- ..._,
48
+ }) }) : /* @__PURE__ */ l("div", {
49
+ ref: y,
50
+ className: n("space-y-ds-04", _),
51
+ ...v,
51
52
  children: [
52
- y,
53
- /* @__PURE__ */ s("div", { className: "border-b border-surface-border-subtle" }),
54
- /* @__PURE__ */ s("div", {
53
+ b,
54
+ /* @__PURE__ */ c("div", { className: "border-b border-surface-border-subtle" }),
55
+ /* @__PURE__ */ c("div", {
55
56
  className: "space-y-ds-04",
56
- children: h
57
+ children: g
57
58
  })
58
59
  ]
59
60
  });
60
61
  });
61
- d.displayName = "FormSection";
62
+ f.displayName = "FormSection";
62
63
  //#endregion
63
- export { d as FormSection };
64
+ export { f as FormSection };
@@ -1 +1 @@
1
- {"version":3,"file":"form-section.js","names":[],"sources":["../../src/composed/form-section.tsx"],"sourcesContent":["'use client'\n\nimport { IconChevronDown } from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../ui/collapsible'\nimport { Icon } from '../ui/icon'\nimport { springs } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\n\n// ============================================================\n// Types\n// ============================================================\n\nexport interface FormSectionProps extends React.HTMLAttributes<HTMLDivElement> {\n title: string\n description?: string\n collapsible?: boolean\n /** @default true */\n defaultOpen?: boolean\n}\n\n// ============================================================\n// FormSection\n// ============================================================\n\nconst FormSection = React.forwardRef<HTMLDivElement, FormSectionProps>(({\n title,\n description,\n collapsible = false,\n defaultOpen = true,\n children,\n className,\n ...props\n}, ref) => {\n const header = (\n <div className=\"flex flex-col gap-ds-01\">\n <span className=\"text-body-md font-semibold text-surface-fg font-sans\">\n {title}\n </span>\n {description && (\n <span className=\"text-body-sm text-surface-fg-muted font-sans\">\n {description}\n </span>\n )}\n </div>\n )\n\n const [isOpen, setIsOpen] = React.useState(defaultOpen)\n\n if (collapsible) {\n return (\n <Collapsible defaultOpen={defaultOpen} onOpenChange={setIsOpen} className={cn('space-y-ds-04', className)} {...props}>\n <CollapsibleTrigger className=\"flex w-full items-center justify-between py-ds-02 group\">\n {header}\n <motion.span\n animate={{ rotate: isOpen ? 180 : 0 }}\n transition={springs.snappy}\n className=\"inline-flex\"\n >\n <Icon icon={IconChevronDown} size=\"sm\" className=\"text-surface-fg-muted\" />\n </motion.span>\n </CollapsibleTrigger>\n <div className=\"border-b border-surface-border-subtle\" />\n <CollapsibleContent>\n <div className=\"space-y-ds-04 pt-ds-02\">\n {children}\n </div>\n </CollapsibleContent>\n </Collapsible>\n )\n }\n\n return (\n <div ref={ref} className={cn('space-y-ds-04', className)} {...props}>\n {header}\n <div className=\"border-b border-surface-border-subtle\" />\n <div className=\"space-y-ds-04\">\n {children}\n </div>\n </div>\n )\n})\nFormSection.displayName = 'FormSection'\n\nexport { FormSection }\n"],"mappings":";;;;;;;;;;AA2BA,IAAM,IAAc,EAAM,YAA8C,EACtE,UACA,gBACA,iBAAc,IACd,iBAAc,IACd,aACA,cACA,GAAG,KACF,MAAQ;CACT,IAAM,IACJ,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,QAAD;GAAM,WAAU;aACb;EACG,CAAA,GACL,KACC,kBAAC,QAAD;GAAM,WAAU;aACb;EACG,CAAA,CAEL;KAGD,CAAC,GAAQ,KAAa,EAAM,SAAS,CAAW;CAyBtD,OAvBI,IAEA,kBAAC,GAAD;EAA0B;EAAa,cAAc;EAAW,WAAW,EAAG,iBAAiB,CAAS;EAAG,GAAI;YAA/G;GACE,kBAAC,GAAD;IAAoB,WAAU;cAA9B,CACG,GACD,kBAAC,EAAO,MAAR;KACE,SAAS,EAAE,QAAQ,IAAS,MAAM,EAAE;KACpC,YAAY,EAAQ;KACpB,WAAU;eAEV,kBAAC,GAAD;MAAM,MAAM;MAAiB,MAAK;MAAK,WAAU;KAAyB,CAAA;IAC/D,CAAA,CACK;;GACpB,kBAAC,OAAD,EAAK,WAAU,wCAAyC,CAAA;GACxD,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;IAAK,WAAU;IACZ;GACE,CAAA,EACa,CAAA;EACT;MAKf,kBAAC,OAAD;EAAU;EAAK,WAAW,EAAG,iBAAiB,CAAS;EAAG,GAAI;YAA9D;GACG;GACD,kBAAC,OAAD,EAAK,WAAU,wCAAyC,CAAA;GACxD,kBAAC,OAAD;IAAK,WAAU;IACZ;GACE,CAAA;EACF;;AAET,CAAC;AACD,EAAY,cAAc"}
1
+ {"version":3,"file":"form-section.js","names":[],"sources":["../../src/composed/form-section.tsx"],"sourcesContent":["'use client'\n\nimport { IconChevronDown } from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { MotionPreference } from '../motion/motion-preference'\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../ui/collapsible'\nimport { Icon } from '../ui/icon'\nimport { springs } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\n\n// ============================================================\n// Types\n// ============================================================\n\nexport interface FormSectionProps extends React.HTMLAttributes<HTMLDivElement> {\n title: string\n description?: string\n collapsible?: boolean\n /** @default true */\n defaultOpen?: boolean\n}\n\n// ============================================================\n// FormSection\n// ============================================================\n\nconst FormSection = React.forwardRef<HTMLDivElement, FormSectionProps>(({\n title,\n description,\n collapsible = false,\n defaultOpen = true,\n children,\n className,\n ...props\n}, ref) => {\n const header = (\n <div className=\"flex flex-col gap-ds-01\">\n <span className=\"text-body-md font-semibold text-surface-fg font-sans\">\n {title}\n </span>\n {description && (\n <span className=\"text-body-sm text-surface-fg-muted font-sans\">\n {description}\n </span>\n )}\n </div>\n )\n\n const [isOpen, setIsOpen] = React.useState(defaultOpen)\n\n if (collapsible) {\n return (\n <MotionPreference>\n <Collapsible defaultOpen={defaultOpen} onOpenChange={setIsOpen} className={cn('space-y-ds-04', className)} {...props}>\n <CollapsibleTrigger className=\"flex w-full items-center justify-between py-ds-02 group\">\n {header}\n <motion.span\n animate={{ rotate: isOpen ? 180 : 0 }}\n transition={springs.snappy}\n className=\"inline-flex\"\n >\n <Icon icon={IconChevronDown} size=\"sm\" className=\"text-surface-fg-muted\" />\n </motion.span>\n </CollapsibleTrigger>\n <div className=\"border-b border-surface-border-subtle\" />\n <CollapsibleContent>\n <div className=\"space-y-ds-04 pt-ds-02\">\n {children}\n </div>\n </CollapsibleContent>\n </Collapsible>\n </MotionPreference>\n )\n }\n\n return (\n <div ref={ref} className={cn('space-y-ds-04', className)} {...props}>\n {header}\n <div className=\"border-b border-surface-border-subtle\" />\n <div className=\"space-y-ds-04\">\n {children}\n </div>\n </div>\n )\n})\nFormSection.displayName = 'FormSection'\n\nexport { FormSection }\n"],"mappings":";;;;;;;;;;;AA4BA,IAAM,IAAc,EAAM,YAA8C,EACtE,UACA,gBACA,iBAAc,IACd,iBAAc,IACd,aACA,cACA,GAAG,KACF,MAAQ;CACT,IAAM,IACJ,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,QAAD;GAAM,WAAU;aACb;EACG,CAAA,GACL,KACC,kBAAC,QAAD;GAAM,WAAU;aACb;EACG,CAAA,CAEL;KAGD,CAAC,GAAQ,KAAa,EAAM,SAAS,CAAW;CA2BtD,OAzBI,IAEA,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EAA0B;EAAa,cAAc;EAAW,WAAW,EAAG,iBAAiB,CAAS;EAAG,GAAI;YAA/G;GACE,kBAAC,GAAD;IAAoB,WAAU;cAA9B,CACG,GACD,kBAAC,EAAO,MAAR;KACE,SAAS,EAAE,QAAQ,IAAS,MAAM,EAAE;KACpC,YAAY,EAAQ;KACpB,WAAU;eAEV,kBAAC,GAAD;MAAM,MAAM;MAAiB,MAAK;MAAK,WAAU;KAAyB,CAAA;IAC/D,CAAA,CACK;;GACpB,kBAAC,OAAD,EAAK,WAAU,wCAAyC,CAAA;GACxD,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;IAAK,WAAU;IACZ;GACE,CAAA,EACa,CAAA;EACT;IACG,CAAA,IAKpB,kBAAC,OAAD;EAAU;EAAK,WAAW,EAAG,iBAAiB,CAAS;EAAG,GAAI;YAA9D;GACG;GACD,kBAAC,OAAD,EAAK,WAAU,wCAAyC,CAAA;GACxD,kBAAC,OAAD;IAAK,WAAU;IACZ;GACE,CAAA;EACF;;AAET,CAAC;AACD,EAAY,cAAc"}
@@ -78,7 +78,7 @@ var s = r.forwardRef(({ value: s, onSave: c, placeholder: l = "Click to edit", t
78
78
  onInput: j,
79
79
  onPaste: M,
80
80
  spellCheck: x,
81
- className: e("font-sans text-surface-fg outline-hidden", u, !d && !x && "cursor-text rounded-control-inner -mx-ds-01 px-ds-01 hover:bg-surface-raised-hover transition-colors duration-fast-01", x && "rounded-control-inner -mx-ds-01 px-ds-01 bg-surface-raised-hover ring-1 ring-accent-7", E && !x && "text-surface-fg-subtle italic", d && "cursor-default"),
81
+ className: e("font-sans text-surface-fg outline-hidden", u, !d && !x && "cursor-text rounded-control-inner -mx-ds-01 px-ds-01 hover:bg-surface-panel-hover transition-colors duration-fast-01", x && "rounded-control-inner -mx-ds-01 px-ds-01 bg-surface-panel-hover ring-1 ring-accent-7", E && !x && "text-surface-fg-subtle italic", d && "cursor-default"),
82
82
  "data-placeholder": l,
83
83
  children: s || (x ? "" : l)
84
84
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"inline-edit.js","names":[],"sources":["../../src/composed/inline-edit.tsx"],"sourcesContent":["'use client'\n\nimport { IconPencil } from '@tabler/icons-react'\nimport * as React from 'react'\n\nimport { Icon } from '../ui/icon'\nimport { cn } from '../ui/lib/utils'\nimport { Spinner } from '../ui/spinner'\n\n// ============================================================\n// Types\n// ============================================================\n\nexport interface InlineEditProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSave'> {\n value: string\n onSave: (newValue: string) => void | Promise<void>\n placeholder?: string\n /** CSS class applied to the editable text (e.g. 'text-heading-xs font-semibold') */\n textClassName?: string\n readOnly?: boolean\n maxLength?: number\n /** External saving state — shows spinner */\n saving?: boolean\n}\n\n// ============================================================\n// InlineEdit — contentEditable approach\n//\n// No mode switch. No input field. The text IS the editor.\n// Click → cursor appears in the text. Type. Enter saves. Escape reverts.\n// Like Notion, Linear, Figma layer names.\n// ============================================================\n\nconst InlineEdit = React.forwardRef<HTMLDivElement, InlineEditProps>(({\n value,\n onSave,\n placeholder = 'Click to edit',\n textClassName,\n readOnly = false,\n maxLength,\n saving: savingProp = false,\n className,\n // aria-label / aria-labelledby apply to the editable textbox (the inner\n // span), not the outer wrapper div. Intercept them from ...props so they\n // land on the role=\"textbox\" element that actually needs the name.\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n ...props\n}, ref) => {\n const [saving, setSaving] = React.useState(false)\n const [focused, setFocused] = React.useState(false)\n const editRef = React.useRef<HTMLSpanElement>(null)\n const snapshotRef = React.useRef(value)\n\n const isSaving = savingProp || saving\n const isEmpty = !value\n\n // Sync DOM text when value changes externally (and not focused)\n React.useEffect(() => {\n if (!focused && editRef.current) {\n editRef.current.textContent = value\n }\n }, [value, focused])\n\n function handleFocus() {\n if (readOnly || isSaving) return\n setFocused(true)\n snapshotRef.current = value\n\n // Select all text on focus (like clicking a file name in Finder)\n requestAnimationFrame(() => {\n if (!editRef.current) return\n const range = document.createRange()\n range.selectNodeContents(editRef.current)\n const sel = window.getSelection()\n sel?.removeAllRanges()\n sel?.addRange(range)\n })\n }\n\n async function commit() {\n if (!editRef.current) return\n setFocused(false)\n const raw = editRef.current.textContent ?? ''\n const trimmed = raw.trim()\n\n // Enforce maxLength\n const final = maxLength ? trimmed.slice(0, maxLength) : trimmed\n\n // Reset DOM to clean value\n editRef.current.textContent = final || value\n\n if (final === value) return\n const result = onSave(final)\n if (result instanceof Promise) {\n setSaving(true)\n try {\n await result\n } catch {\n // Revert on error\n if (editRef.current) editRef.current.textContent = value\n } finally {\n setSaving(false)\n }\n }\n }\n\n function cancel() {\n if (!editRef.current) return\n editRef.current.textContent = snapshotRef.current\n setFocused(false)\n editRef.current.blur()\n }\n\n function handleKeyDown(e: React.KeyboardEvent) {\n if (e.key === 'Escape') {\n e.preventDefault()\n cancel()\n } else if (e.key === 'Enter') {\n e.preventDefault()\n commit()\n }\n }\n\n function handleInput() {\n if (!editRef.current || !maxLength) return\n const text = editRef.current.textContent ?? ''\n if (text.length > maxLength) {\n editRef.current.textContent = text.slice(0, maxLength)\n // Move cursor to end\n const range = document.createRange()\n range.selectNodeContents(editRef.current)\n range.collapse(false)\n const sel = window.getSelection()\n sel?.removeAllRanges()\n sel?.addRange(range)\n }\n }\n\n // Prevent paste from inserting rich content\n function handlePaste(e: React.ClipboardEvent) {\n e.preventDefault()\n const text = e.clipboardData.getData('text/plain')\n document.execCommand('insertText', false, text)\n }\n\n return (\n <div ref={ref} className={cn('group inline-flex items-center gap-ds-02', className)} {...props}>\n <span\n ref={editRef}\n role={readOnly ? undefined : 'textbox'}\n contentEditable={!readOnly && !isSaving}\n suppressContentEditableWarning\n tabIndex={readOnly ? undefined : 0}\n // Fall back to the placeholder when no explicit label is supplied —\n // screen readers need _something_ on role=\"textbox\".\n aria-label={readOnly ? undefined : (ariaLabel ?? (ariaLabelledBy ? undefined : placeholder))}\n aria-labelledby={readOnly ? undefined : ariaLabelledBy}\n onFocus={handleFocus}\n onBlur={commit}\n onKeyDown={handleKeyDown}\n onInput={handleInput}\n onPaste={handlePaste}\n spellCheck={focused}\n className={cn(\n 'font-sans text-surface-fg outline-hidden',\n textClassName,\n // Idle: subtle hover hint\n !readOnly && !focused && 'cursor-text rounded-control-inner -mx-ds-01 px-ds-01 hover:bg-surface-raised-hover transition-colors duration-fast-01',\n // Focused: subtle underline to indicate editing\n focused && 'rounded-control-inner -mx-ds-01 px-ds-01 bg-surface-raised-hover ring-1 ring-accent-7',\n // Empty: show placeholder styling\n isEmpty && !focused && 'text-surface-fg-subtle italic',\n // Read-only\n readOnly && 'cursor-default',\n )}\n data-placeholder={placeholder}\n >\n {value || (focused ? '' : placeholder)}\n </span>\n {!readOnly && !focused && !isSaving && (\n <Icon icon={IconPencil} size=\"xs\" className=\"text-surface-fg-subtle opacity-0 group-hover:opacity-100 transition-opacity duration-fast-01 shrink-0\" />\n )}\n {isSaving && <Spinner size=\"sm\" />}\n </div>\n )\n})\nInlineEdit.displayName = 'InlineEdit'\n\nexport { InlineEdit }\n"],"mappings":"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"}
1
+ {"version":3,"file":"inline-edit.js","names":[],"sources":["../../src/composed/inline-edit.tsx"],"sourcesContent":["'use client'\n\nimport { IconPencil } from '@tabler/icons-react'\nimport * as React from 'react'\n\nimport { Icon } from '../ui/icon'\nimport { cn } from '../ui/lib/utils'\nimport { Spinner } from '../ui/spinner'\n\n// ============================================================\n// Types\n// ============================================================\n\nexport interface InlineEditProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSave'> {\n value: string\n onSave: (newValue: string) => void | Promise<void>\n placeholder?: string\n /** CSS class applied to the editable text (e.g. 'text-heading-xs font-semibold') */\n textClassName?: string\n readOnly?: boolean\n maxLength?: number\n /** External saving state — shows spinner */\n saving?: boolean\n}\n\n// ============================================================\n// InlineEdit — contentEditable approach\n//\n// No mode switch. No input field. The text IS the editor.\n// Click → cursor appears in the text. Type. Enter saves. Escape reverts.\n// Like Notion, Linear, Figma layer names.\n// ============================================================\n\nconst InlineEdit = React.forwardRef<HTMLDivElement, InlineEditProps>(({\n value,\n onSave,\n placeholder = 'Click to edit',\n textClassName,\n readOnly = false,\n maxLength,\n saving: savingProp = false,\n className,\n // aria-label / aria-labelledby apply to the editable textbox (the inner\n // span), not the outer wrapper div. Intercept them from ...props so they\n // land on the role=\"textbox\" element that actually needs the name.\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n ...props\n}, ref) => {\n const [saving, setSaving] = React.useState(false)\n const [focused, setFocused] = React.useState(false)\n const editRef = React.useRef<HTMLSpanElement>(null)\n const snapshotRef = React.useRef(value)\n\n const isSaving = savingProp || saving\n const isEmpty = !value\n\n // Sync DOM text when value changes externally (and not focused)\n React.useEffect(() => {\n if (!focused && editRef.current) {\n editRef.current.textContent = value\n }\n }, [value, focused])\n\n function handleFocus() {\n if (readOnly || isSaving) return\n setFocused(true)\n snapshotRef.current = value\n\n // Select all text on focus (like clicking a file name in Finder)\n requestAnimationFrame(() => {\n if (!editRef.current) return\n const range = document.createRange()\n range.selectNodeContents(editRef.current)\n const sel = window.getSelection()\n sel?.removeAllRanges()\n sel?.addRange(range)\n })\n }\n\n async function commit() {\n if (!editRef.current) return\n setFocused(false)\n const raw = editRef.current.textContent ?? ''\n const trimmed = raw.trim()\n\n // Enforce maxLength\n const final = maxLength ? trimmed.slice(0, maxLength) : trimmed\n\n // Reset DOM to clean value\n editRef.current.textContent = final || value\n\n if (final === value) return\n const result = onSave(final)\n if (result instanceof Promise) {\n setSaving(true)\n try {\n await result\n } catch {\n // Revert on error\n if (editRef.current) editRef.current.textContent = value\n } finally {\n setSaving(false)\n }\n }\n }\n\n function cancel() {\n if (!editRef.current) return\n editRef.current.textContent = snapshotRef.current\n setFocused(false)\n editRef.current.blur()\n }\n\n function handleKeyDown(e: React.KeyboardEvent) {\n if (e.key === 'Escape') {\n e.preventDefault()\n cancel()\n } else if (e.key === 'Enter') {\n e.preventDefault()\n commit()\n }\n }\n\n function handleInput() {\n if (!editRef.current || !maxLength) return\n const text = editRef.current.textContent ?? ''\n if (text.length > maxLength) {\n editRef.current.textContent = text.slice(0, maxLength)\n // Move cursor to end\n const range = document.createRange()\n range.selectNodeContents(editRef.current)\n range.collapse(false)\n const sel = window.getSelection()\n sel?.removeAllRanges()\n sel?.addRange(range)\n }\n }\n\n // Prevent paste from inserting rich content\n function handlePaste(e: React.ClipboardEvent) {\n e.preventDefault()\n const text = e.clipboardData.getData('text/plain')\n document.execCommand('insertText', false, text)\n }\n\n return (\n <div ref={ref} className={cn('group inline-flex items-center gap-ds-02', className)} {...props}>\n <span\n ref={editRef}\n role={readOnly ? undefined : 'textbox'}\n contentEditable={!readOnly && !isSaving}\n suppressContentEditableWarning\n tabIndex={readOnly ? undefined : 0}\n // Fall back to the placeholder when no explicit label is supplied —\n // screen readers need _something_ on role=\"textbox\".\n aria-label={readOnly ? undefined : (ariaLabel ?? (ariaLabelledBy ? undefined : placeholder))}\n aria-labelledby={readOnly ? undefined : ariaLabelledBy}\n onFocus={handleFocus}\n onBlur={commit}\n onKeyDown={handleKeyDown}\n onInput={handleInput}\n onPaste={handlePaste}\n spellCheck={focused}\n className={cn(\n 'font-sans text-surface-fg outline-hidden',\n textClassName,\n // Idle: subtle hover hint\n !readOnly && !focused && 'cursor-text rounded-control-inner -mx-ds-01 px-ds-01 hover:bg-surface-panel-hover transition-colors duration-fast-01',\n // Focused: subtle underline to indicate editing\n focused && 'rounded-control-inner -mx-ds-01 px-ds-01 bg-surface-panel-hover ring-1 ring-accent-7',\n // Empty: show placeholder styling\n isEmpty && !focused && 'text-surface-fg-subtle italic',\n // Read-only\n readOnly && 'cursor-default',\n )}\n data-placeholder={placeholder}\n >\n {value || (focused ? '' : placeholder)}\n </span>\n {!readOnly && !focused && !isSaving && (\n <Icon icon={IconPencil} size=\"xs\" className=\"text-surface-fg-subtle opacity-0 group-hover:opacity-100 transition-opacity duration-fast-01 shrink-0\" />\n )}\n {isSaving && <Spinner size=\"sm\" />}\n </div>\n )\n})\nInlineEdit.displayName = 'InlineEdit'\n\nexport { InlineEdit }\n"],"mappings":"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"}
@@ -22,7 +22,7 @@ var c = n.forwardRef(function({ className: n, label: a, ...c }, l) {
22
22
  ref: l,
23
23
  ...o(a),
24
24
  ...c,
25
- className: e("rounded-surface border border-card bg-surface-raised p-ds-05b", n),
25
+ className: e("rounded-surface border border-card bg-surface-panel p-ds-05b", n),
26
26
  children: [
27
27
  /* @__PURE__ */ r(s, { label: a }),
28
28
  /* @__PURE__ */ i("div", {
@@ -55,7 +55,7 @@ var l = n.forwardRef(function({ rows: n = 5, columns: c = 4, className: l, label
55
55
  children: [
56
56
  /* @__PURE__ */ r(s, { label: u }),
57
57
  /* @__PURE__ */ r("div", {
58
- className: "flex items-center gap-ds-05 border-b border-surface-border-strong bg-surface-raised px-ds-05 py-ds-04",
58
+ className: "flex items-center gap-ds-05 border-b border-surface-border-strong bg-surface-panel px-ds-05 py-ds-04",
59
59
  children: Array.from({ length: m }).map((n, i) => /* @__PURE__ */ r(t, { className: e("h-ds-04", i === 0 ? "w-ds-13" : "flex-1") }, `head-${i}`))
60
60
  }),
61
61
  Array.from({ length: p }).map((n, i) => /* @__PURE__ */ r("div", {
@@ -86,7 +86,7 @@ var u = n.forwardRef(function({ columns: n = 4, cardsPerColumn: c = 3, className
86
86
  ]
87
87
  }), /* @__PURE__ */ r(t, { className: "h-ico-sm w-ico-sm rounded" })]
88
88
  }), Array.from({ length: m }).map((e, a) => /* @__PURE__ */ i("div", {
89
- className: "rounded-surface border border-card bg-surface-raised p-ds-04",
89
+ className: "rounded-surface border border-card bg-surface-panel p-ds-04",
90
90
  children: [/* @__PURE__ */ i("div", {
91
91
  className: "space-y-ds-03",
92
92
  children: [/* @__PURE__ */ r(t, { className: "h-ds-04 w-4/5" }), /* @__PURE__ */ r(t, { className: "h-ds-04 w-3/5" })]