@refraction-ui/react 0.19.1 → 0.20.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 (342) hide show
  1. package/dist/chunk-533MCMSU.js +2355 -0
  2. package/dist/chunk-533MCMSU.js.map +7 -0
  3. package/dist/chunk-AMK4VCBG.js +250 -0
  4. package/dist/chunk-AMK4VCBG.js.map +7 -0
  5. package/dist/chunk-BOLFYVI6.js +90 -0
  6. package/dist/chunk-BOLFYVI6.js.map +7 -0
  7. package/dist/chunk-F7B64KT3.js +187 -0
  8. package/dist/chunk-F7B64KT3.js.map +7 -0
  9. package/dist/chunk-FD37DFCC.js +77 -0
  10. package/dist/chunk-FD37DFCC.js.map +7 -0
  11. package/dist/chunk-H54FLNAF.js +132 -0
  12. package/dist/chunk-H54FLNAF.js.map +7 -0
  13. package/dist/chunk-INP46354.js +664 -0
  14. package/dist/chunk-INP46354.js.map +7 -0
  15. package/dist/chunk-ONACRMG4.js +116 -0
  16. package/dist/chunk-ONACRMG4.js.map +7 -0
  17. package/dist/chunk-SDZ3LROZ.js +7 -0
  18. package/dist/chunk-SDZ3LROZ.js.map +7 -0
  19. package/dist/chunk-TOZBR4GL.js +154 -0
  20. package/dist/chunk-TOZBR4GL.js.map +7 -0
  21. package/dist/chunk-UNEM2TRI.js +225 -0
  22. package/dist/chunk-UNEM2TRI.js.map +7 -0
  23. package/dist/chunk-VK3EQ34F.js +92 -0
  24. package/dist/chunk-VK3EQ34F.js.map +7 -0
  25. package/dist/chunk-WK2IIUFO.js +316 -0
  26. package/dist/chunk-WK2IIUFO.js.map +7 -0
  27. package/dist/chunk-WSPJTQAV.js +59 -0
  28. package/dist/chunk-WSPJTQAV.js.map +7 -0
  29. package/dist/chunk-XWP763SH.js +3 -2
  30. package/dist/chunk-XWP763SH.js.map +7 -1
  31. package/dist/chunk-Y2FNNAIL.js +80 -0
  32. package/dist/chunk-Y2FNNAIL.js.map +1 -0
  33. package/dist/{chunk-4A4BW3GR.js → chunk-ZF3OHCGV.js} +28 -5
  34. package/dist/chunk-ZF3OHCGV.js.map +7 -0
  35. package/dist/form.cjs +5 -11
  36. package/dist/form.cjs.map +1 -1
  37. package/dist/form.js +6 -12
  38. package/dist/form.js.map +1 -1
  39. package/dist/index.cjs +2879 -33936
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +1 -0
  42. package/dist/index.d.ts +1 -0
  43. package/dist/index.js +126 -28780
  44. package/dist/internal/react-app-shell/index.d.cts +2 -22
  45. package/dist/internal/react-app-shell/index.d.ts +2 -22
  46. package/dist/internal/react-badge/index.d.cts +5 -0
  47. package/dist/internal/react-badge/index.d.ts +5 -0
  48. package/dist/internal/react-callout/index.d.cts +4 -0
  49. package/dist/internal/react-callout/index.d.ts +4 -0
  50. package/dist/internal/react-card/index.d.cts +8 -1
  51. package/dist/internal/react-card/index.d.ts +8 -1
  52. package/dist/internal/react-collapsible/index.d.cts +0 -3
  53. package/dist/internal/react-collapsible/index.d.ts +0 -3
  54. package/dist/internal/react-combobox/index.d.cts +0 -3
  55. package/dist/internal/react-combobox/index.d.ts +0 -3
  56. package/dist/internal/react-command/index.d.cts +0 -3
  57. package/dist/internal/react-command/index.d.ts +0 -3
  58. package/dist/internal/react-data-table/index.d.cts +0 -3
  59. package/dist/internal/react-data-table/index.d.ts +0 -3
  60. package/dist/internal/react-dialog/index.d.cts +0 -3
  61. package/dist/internal/react-dialog/index.d.ts +0 -3
  62. package/dist/internal/react-dropdown-menu/index.d.cts +0 -3
  63. package/dist/internal/react-dropdown-menu/index.d.ts +0 -3
  64. package/dist/internal/react-feedback-dialog/index.d.cts +0 -3
  65. package/dist/internal/react-feedback-dialog/index.d.ts +0 -3
  66. package/dist/internal/react-form/index.d.cts +2 -21
  67. package/dist/internal/react-form/index.d.ts +2 -21
  68. package/dist/internal/react-keyboard-shortcut/index.d.cts +0 -6
  69. package/dist/internal/react-keyboard-shortcut/index.d.ts +0 -6
  70. package/dist/internal/react-link-card/index.d.cts +8 -2
  71. package/dist/internal/react-link-card/index.d.ts +8 -2
  72. package/dist/internal/react-popover/index.d.cts +0 -3
  73. package/dist/internal/react-popover/index.d.ts +0 -3
  74. package/dist/internal/react-sheet/index.d.cts +0 -3
  75. package/dist/internal/react-sheet/index.d.ts +0 -3
  76. package/dist/internal/react-slot/index.d.cts +23 -0
  77. package/dist/internal/react-slot/index.d.ts +23 -0
  78. package/dist/internal/react-toast/index.d.cts +0 -3
  79. package/dist/internal/react-toast/index.d.ts +0 -3
  80. package/dist/internal/react-tooltip/index.d.cts +0 -3
  81. package/dist/internal/react-tooltip/index.d.ts +0 -3
  82. package/dist/react-accordion.js +125 -0
  83. package/dist/react-accordion.js.map +7 -0
  84. package/dist/react-ai.js +257 -0
  85. package/dist/react-ai.js.map +7 -0
  86. package/dist/react-analytics.js +1698 -0
  87. package/dist/react-analytics.js.map +7 -0
  88. package/dist/react-animated-text.js +164 -0
  89. package/dist/react-animated-text.js.map +7 -0
  90. package/dist/react-app-shell.js +631 -0
  91. package/dist/react-app-shell.js.map +7 -0
  92. package/dist/react-audience-feature-card.js +53 -0
  93. package/dist/react-audience-feature-card.js.map +7 -0
  94. package/dist/react-audio-room.js +166 -0
  95. package/dist/react-audio-room.js.map +7 -0
  96. package/dist/react-auth.js +211 -0
  97. package/dist/react-auth.js.map +7 -0
  98. package/dist/react-avatar-group.js +154 -0
  99. package/dist/react-avatar-group.js.map +7 -0
  100. package/dist/react-avatar.js +168 -0
  101. package/dist/react-avatar.js.map +7 -0
  102. package/dist/react-badge.js +86 -0
  103. package/dist/react-badge.js.map +7 -0
  104. package/dist/react-bottom-nav.js +67 -0
  105. package/dist/react-bottom-nav.js.map +7 -0
  106. package/dist/react-brand-network-cell.js +92 -0
  107. package/dist/react-brand-network-cell.js.map +7 -0
  108. package/dist/react-breadcrumbs.js +104 -0
  109. package/dist/react-breadcrumbs.js.map +7 -0
  110. package/dist/react-browser-chrome-mock.js +89 -0
  111. package/dist/react-browser-chrome-mock.js.map +7 -0
  112. package/dist/react-button.js +13 -0
  113. package/dist/react-button.js.map +7 -0
  114. package/dist/react-calendar.js +301 -0
  115. package/dist/react-calendar.js.map +7 -0
  116. package/dist/react-call-controls.js +113 -0
  117. package/dist/react-call-controls.js.map +7 -0
  118. package/dist/react-callout.js +152 -0
  119. package/dist/react-callout.js.map +7 -0
  120. package/dist/react-card-grid.js +34 -0
  121. package/dist/react-card-grid.js.map +7 -0
  122. package/dist/react-card.js +200 -0
  123. package/dist/react-card.js.map +7 -0
  124. package/dist/react-carousel.js +132 -0
  125. package/dist/react-carousel.js.map +7 -0
  126. package/dist/react-charts.js +509 -0
  127. package/dist/react-charts.js.map +7 -0
  128. package/dist/react-checkbox.js +156 -0
  129. package/dist/react-checkbox.js.map +7 -0
  130. package/dist/react-checklist.js +168 -0
  131. package/dist/react-checklist.js.map +7 -0
  132. package/dist/react-code-block.js +78 -0
  133. package/dist/react-code-block.js.map +7 -0
  134. package/dist/react-code-editor.js +184 -0
  135. package/dist/react-code-editor.js.map +7 -0
  136. package/dist/react-collapsible.js +192 -0
  137. package/dist/react-collapsible.js.map +7 -0
  138. package/dist/react-combobox.js +609 -0
  139. package/dist/react-combobox.js.map +7 -0
  140. package/dist/react-command-input.js +202 -0
  141. package/dist/react-command-input.js.map +7 -0
  142. package/dist/react-command.js +364 -0
  143. package/dist/react-command.js.map +7 -0
  144. package/dist/react-composer.js +2036 -0
  145. package/dist/react-composer.js.map +7 -0
  146. package/dist/react-content-protection.js +133 -0
  147. package/dist/react-content-protection.js.map +7 -0
  148. package/dist/react-conversation.js +1226 -0
  149. package/dist/react-conversation.js.map +7 -0
  150. package/dist/react-cookie-consent.js +291 -0
  151. package/dist/react-cookie-consent.js.map +7 -0
  152. package/dist/react-data-table.js +310 -0
  153. package/dist/react-data-table.js.map +7 -0
  154. package/dist/react-date-picker.js +95 -0
  155. package/dist/react-date-picker.js.map +7 -0
  156. package/dist/react-device-frame.js +137 -0
  157. package/dist/react-device-frame.js.map +7 -0
  158. package/dist/react-dialog.js +218 -0
  159. package/dist/react-dialog.js.map +7 -0
  160. package/dist/react-diff-viewer.js +460 -0
  161. package/dist/react-diff-viewer.js.map +7 -0
  162. package/dist/react-dropdown-menu.js +292 -0
  163. package/dist/react-dropdown-menu.js.map +7 -0
  164. package/dist/react-editor-status-bar.js +148 -0
  165. package/dist/react-editor-status-bar.js.map +7 -0
  166. package/dist/react-editor-tabs.js +159 -0
  167. package/dist/react-editor-tabs.js.map +7 -0
  168. package/dist/react-emoji-picker.js +238 -0
  169. package/dist/react-emoji-picker.js.map +7 -0
  170. package/dist/react-empty-state.js +90 -0
  171. package/dist/react-empty-state.js.map +7 -0
  172. package/dist/react-feedback-dialog.js +275 -0
  173. package/dist/react-feedback-dialog.js.map +7 -0
  174. package/dist/react-file-tree.js +376 -0
  175. package/dist/react-file-tree.js.map +7 -0
  176. package/dist/react-file-upload.js +360 -0
  177. package/dist/react-file-upload.js.map +7 -0
  178. package/dist/react-floating-reactions.js +124 -0
  179. package/dist/react-floating-reactions.js.map +7 -0
  180. package/dist/react-flow-editor.js +198 -0
  181. package/dist/react-flow-editor.js.map +7 -0
  182. package/dist/react-footer.js +67 -0
  183. package/dist/react-footer.js.map +7 -0
  184. package/dist/react-graph-view.js +197 -0
  185. package/dist/react-graph-view.js.map +7 -0
  186. package/dist/react-icon-system.js +9 -0
  187. package/dist/react-icon-system.js.map +7 -0
  188. package/dist/react-infinite-canvas.js +8 -0
  189. package/dist/react-infinite-canvas.js.map +7 -0
  190. package/dist/react-inline-editor.js +216 -0
  191. package/dist/react-inline-editor.js.map +7 -0
  192. package/dist/react-input-group.js +150 -0
  193. package/dist/react-input-group.js.map +7 -0
  194. package/dist/react-input.js +10 -0
  195. package/dist/react-input.js.map +7 -0
  196. package/dist/react-install-prompt.js +132 -0
  197. package/dist/react-install-prompt.js.map +7 -0
  198. package/dist/react-kanban-board.js +198 -0
  199. package/dist/react-kanban-board.js.map +7 -0
  200. package/dist/react-keyboard-shortcut.js +36 -0
  201. package/dist/react-keyboard-shortcut.js.map +7 -0
  202. package/dist/react-language-selector.js +311 -0
  203. package/dist/react-language-selector.js.map +7 -0
  204. package/dist/react-link-card.js +48 -0
  205. package/dist/react-link-card.js.map +7 -0
  206. package/dist/react-live-captions.js +92 -0
  207. package/dist/react-live-captions.js.map +7 -0
  208. package/dist/react-live-cursors.js +145 -0
  209. package/dist/react-live-cursors.js.map +7 -0
  210. package/dist/react-live-transcript.js +117 -0
  211. package/dist/react-live-transcript.js.map +7 -0
  212. package/dist/react-location-selector.js +252 -0
  213. package/dist/react-location-selector.js.map +7 -0
  214. package/dist/react-logger.js +415 -0
  215. package/dist/react-logger.js.map +7 -0
  216. package/dist/react-markdown-renderer.js +12 -0
  217. package/dist/react-markdown-renderer.js.map +7 -0
  218. package/dist/react-marquee-strip.js +62 -0
  219. package/dist/react-marquee-strip.js.map +7 -0
  220. package/dist/react-mascot.js +367 -0
  221. package/dist/react-mascot.js.map +7 -0
  222. package/dist/react-mastery-bar.js +97 -0
  223. package/dist/react-mastery-bar.js.map +7 -0
  224. package/dist/react-mini-map.js +207 -0
  225. package/dist/react-mini-map.js.map +7 -0
  226. package/dist/react-mobile-nav.js +216 -0
  227. package/dist/react-mobile-nav.js.map +7 -0
  228. package/dist/react-navbar.js +96 -0
  229. package/dist/react-navbar.js.map +7 -0
  230. package/dist/react-numbered-steps.js +67 -0
  231. package/dist/react-numbered-steps.js.map +7 -0
  232. package/dist/react-otp-input.js +318 -0
  233. package/dist/react-otp-input.js.map +7 -0
  234. package/dist/react-pagination.js +199 -0
  235. package/dist/react-pagination.js.map +7 -0
  236. package/dist/react-password-input.js +104 -0
  237. package/dist/react-password-input.js.map +7 -0
  238. package/dist/react-payment.js +81 -0
  239. package/dist/react-payment.js.map +7 -0
  240. package/dist/react-popover.js +176 -0
  241. package/dist/react-popover.js.map +7 -0
  242. package/dist/react-pre-call-lobby.js +309 -0
  243. package/dist/react-pre-call-lobby.js.map +7 -0
  244. package/dist/react-presence-indicator.js +86 -0
  245. package/dist/react-presence-indicator.js.map +7 -0
  246. package/dist/react-pricing-card.js +142 -0
  247. package/dist/react-pricing-card.js.map +7 -0
  248. package/dist/react-progress-display.js +188 -0
  249. package/dist/react-progress-display.js.map +7 -0
  250. package/dist/react-radial-gauge.js +193 -0
  251. package/dist/react-radial-gauge.js.map +7 -0
  252. package/dist/react-radio.js +169 -0
  253. package/dist/react-radio.js.map +7 -0
  254. package/dist/react-rating-scale.js +171 -0
  255. package/dist/react-rating-scale.js.map +7 -0
  256. package/dist/react-reaction-bar.js +111 -0
  257. package/dist/react-reaction-bar.js.map +7 -0
  258. package/dist/react-resizable-layout.js +298 -0
  259. package/dist/react-resizable-layout.js.map +7 -0
  260. package/dist/react-rich-editor.js +1 -0
  261. package/dist/react-rich-editor.js.map +7 -0
  262. package/dist/react-search-bar.js +285 -0
  263. package/dist/react-search-bar.js.map +7 -0
  264. package/dist/react-section-head.js +65 -0
  265. package/dist/react-section-head.js.map +7 -0
  266. package/dist/react-segmented-control.js +215 -0
  267. package/dist/react-segmented-control.js.map +7 -0
  268. package/dist/react-select.js +389 -0
  269. package/dist/react-select.js.map +7 -0
  270. package/dist/react-separator.js +86 -0
  271. package/dist/react-separator.js.map +7 -0
  272. package/dist/react-sheet.js +310 -0
  273. package/dist/react-sheet.js.map +7 -0
  274. package/dist/react-sidebar.js +100 -0
  275. package/dist/react-sidebar.js.map +7 -0
  276. package/dist/react-skeleton.js +91 -0
  277. package/dist/react-skeleton.js.map +7 -0
  278. package/dist/react-skip-to-content.js +38 -0
  279. package/dist/react-skip-to-content.js.map +7 -0
  280. package/dist/react-slide-viewer.js +264 -0
  281. package/dist/react-slide-viewer.js.map +7 -0
  282. package/dist/react-slider.js +105 -0
  283. package/dist/react-slider.js.map +7 -0
  284. package/dist/react-slot-picker.js +184 -0
  285. package/dist/react-slot-picker.js.map +7 -0
  286. package/dist/react-slot.js +7 -0
  287. package/dist/react-slot.js.map +7 -0
  288. package/dist/react-social-auth-button.js +169 -0
  289. package/dist/react-social-auth-button.js.map +7 -0
  290. package/dist/react-sortable-list.js +204 -0
  291. package/dist/react-sortable-list.js.map +7 -0
  292. package/dist/react-stat-grid.js +69 -0
  293. package/dist/react-stat-grid.js.map +7 -0
  294. package/dist/react-status-indicator.js +117 -0
  295. package/dist/react-status-indicator.js.map +7 -0
  296. package/dist/react-steps.js +167 -0
  297. package/dist/react-steps.js.map +7 -0
  298. package/dist/react-sticky-note.js +131 -0
  299. package/dist/react-sticky-note.js.map +7 -0
  300. package/dist/react-switch.js +139 -0
  301. package/dist/react-switch.js.map +7 -0
  302. package/dist/react-table-of-contents.js +80 -0
  303. package/dist/react-table-of-contents.js.map +7 -0
  304. package/dist/react-tabs.js +230 -0
  305. package/dist/react-tabs.js.map +7 -0
  306. package/dist/react-terminal.js +101 -0
  307. package/dist/react-terminal.js.map +7 -0
  308. package/dist/react-test-results.js +158 -0
  309. package/dist/react-test-results.js.map +7 -0
  310. package/dist/react-textarea.js +89 -0
  311. package/dist/react-textarea.js.map +7 -0
  312. package/dist/react-thread-view.js +139 -0
  313. package/dist/react-thread-view.js.map +7 -0
  314. package/dist/react-timeline.js +153 -0
  315. package/dist/react-timeline.js.map +7 -0
  316. package/dist/react-toast.js +253 -0
  317. package/dist/react-toast.js.map +7 -0
  318. package/dist/react-tooltip.js +221 -0
  319. package/dist/react-tooltip.js.map +7 -0
  320. package/dist/react-version-selector.js +270 -0
  321. package/dist/react-version-selector.js.map +7 -0
  322. package/dist/react-video-grid.js +129 -0
  323. package/dist/react-video-grid.js.map +7 -0
  324. package/dist/react-video-player.js +206 -0
  325. package/dist/react-video-player.js.map +7 -0
  326. package/dist/react-video-tile.js +8 -0
  327. package/dist/react-video-tile.js.map +7 -0
  328. package/dist/react-voice-pill.js +314 -0
  329. package/dist/react-voice-pill.js.map +7 -0
  330. package/dist/react-waveform.js +558 -0
  331. package/dist/react-waveform.js.map +7 -0
  332. package/dist/react-wizard.js +337 -0
  333. package/dist/react-wizard.js.map +7 -0
  334. package/dist/styles.css +7 -0
  335. package/dist/theme.js +1 -1
  336. package/package.json +3 -2
  337. package/dist/applicationinsights-web-H7OBLIVJ.js +0 -23393
  338. package/dist/applicationinsights-web-H7OBLIVJ.js.map +0 -1
  339. package/dist/chunk-4A4BW3GR.js.map +0 -1
  340. package/dist/index.js.map +0 -1
  341. package/dist/module-X3HTTRQK.js +0 -6438
  342. package/dist/module-X3HTTRQK.js.map +0 -1
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../file-tree/src/file-tree.ts", "../../file-tree/src/file-tree.styles.ts", "../../react-file-tree/src/react-file-tree.tsx"],
4
+ "sourcesContent": ["/**\n * @refraction-ui/file-tree — headless tree view.\n *\n * Owns the *behavior* of a hierarchical tree: the node model, expand/collapse\n * state, single selection, roving focus, and WAI-ARIA treeview keyboard\n * semantics. It has NO UI opinion — adapters render the nested markup and\n * spread the ARIA objects.\n */\n\n/** A node in the tree. Parents carry `children`; leaves do not. */\nexport interface FileTreeNode {\n /** Unique, stable id (used for expansion/selection/focus). */\n id: string\n /** Display label. */\n label: string\n /** Child nodes — presence (even empty is not allowed: omit for leaves) marks a parent. */\n children?: FileTreeNode[]\n /** Disabled nodes cannot be selected, toggled, or focused via click. */\n disabled?: boolean\n}\n\n/** Options for {@link createFileTree}. */\nexport interface FileTreeConfig {\n /** Root-level nodes. */\n nodes?: FileTreeNode[]\n /** Controlled expanded node ids. Pair with `onExpandedChange`. */\n expandedIds?: string[]\n /** Uncontrolled initial expanded node ids. */\n defaultExpandedIds?: string[]\n /** Controlled selected node id (`null` = none). Pair with `onSelectionChange`. */\n selectedId?: string | null\n /** Uncontrolled initial selected node id. */\n defaultSelectedId?: string | null\n /** Called whenever the expanded set changes. */\n onExpandedChange?: (ids: string[]) => void\n /** Called whenever the selection changes. */\n onSelectionChange?: (id: string | null) => void\n /** Accessible label for the tree (default `'File tree'`). */\n 'aria-label'?: string\n}\n\n/** Snapshot of the store. Returned by `getState()`; treat as immutable. */\nexport interface FileTreeState {\n nodes: FileTreeNode[]\n expandedIds: string[]\n selectedId: string | null\n /** Node holding the roving-tabindex focus (null = the first visible item). */\n focusedId: string | null\n}\n\n/** A row of the flattened, currently-visible tree (DFS order). */\nexport interface VisibleTreeItem {\n node: FileTreeNode\n /** Id of the parent node, or null for root-level items. */\n parentId: string | null\n /** 1-based nesting depth (ARIA `aria-level`). */\n depth: number\n hasChildren: boolean\n isExpanded: boolean\n isSelected: boolean\n isFocused: boolean\n}\n\n/** The framework-agnostic store. React/Astro adapters wrap this. */\nexport interface FileTreeAPI {\n /** Current immutable snapshot */\n getState(): FileTreeState\n /** Subscribe to changes; returns an unsubscribe fn (suits useSyncExternalStore) */\n subscribe(listener: () => void): () => void\n\n /** Flattened visible rows in DFS order (children of collapsed parents omitted) */\n getVisibleItems(): VisibleTreeItem[]\n isExpanded(id: string): boolean\n /** Toggle a parent's expanded state (no-op for leaves/disabled nodes) */\n toggle(id: string): void\n expand(id: string): void\n collapse(id: string): void\n /** Select a node (`null` clears). No-op for unknown/disabled nodes. */\n select(id: string | null): void\n /** Move the roving focus to a node */\n focus(id: string | null): void\n /**\n * WAI-ARIA treeview keyboard semantics, applied to the focused item:\n * ArrowDown/Up move, ArrowRight expands or descends, ArrowLeft collapses or\n * ascends, Home/End jump, Enter/Space selects.\n */\n handleKey(key: string): void\n\n /** Replace the node model (adapters sync the prop) */\n setNodes(nodes: FileTreeNode[]): void\n /** Sync a controlled expanded set */\n setExpandedIds(ids: string[]): void\n /** Sync a controlled selection */\n setSelectedId(id: string | null): void\n /** Update callbacks/options after creation */\n setOptions(options: {\n onExpandedChange?: (ids: string[]) => void\n onSelectionChange?: (id: string | null) => void\n 'aria-label'?: string\n }): void\n\n /** ARIA attributes for the tree container (`role=\"tree\"`). */\n getTreeAria(): Record<string, string | number | boolean>\n /** ARIA attributes for one visible row (`role=\"treeitem\"`). */\n getItemAria(item: VisibleTreeItem): Record<string, string | number | boolean>\n}\n\nfunction arrayEquals(a: string[], b: string[]): boolean {\n return a.length === b.length && a.every((v, i) => v === b[i])\n}\n\n/**\n * createFileTree — headless tree store with expand/collapse, single\n * selection, roving focus, and treeview keyboard semantics. Supports\n * controlled (`expandedIds` / `selectedId`) and uncontrolled\n * (`defaultExpandedIds` / `defaultSelectedId`) usage.\n */\nexport function createFileTree(config: FileTreeConfig = {}): FileTreeAPI {\n let nodes: FileTreeNode[] = config.nodes ?? []\n let ariaLabel = config['aria-label'] ?? 'File tree'\n let onExpandedChange = config.onExpandedChange\n let onSelectionChange = config.onSelectionChange\n\n let uncontrolledExpanded: string[] = config.defaultExpandedIds ?? []\n let controlledExpanded: string[] | undefined = config.expandedIds\n let uncontrolledSelected: string | null = config.defaultSelectedId ?? null\n let controlledSelected: string | null | undefined = config.selectedId\n let focusedId: string | null = null\n\n // Indexes rebuilt whenever the node model changes.\n let parentById = new Map<string, string | null>()\n let nodeById = new Map<string, FileTreeNode>()\n\n function reindex(): void {\n parentById = new Map()\n nodeById = new Map()\n const walk = (list: FileTreeNode[], parentId: string | null) => {\n for (const node of list) {\n nodeById.set(node.id, node)\n parentById.set(node.id, parentId)\n if (node.children) walk(node.children, node.id)\n }\n }\n walk(nodes, null)\n }\n reindex()\n\n const listeners = new Set<() => void>()\n let snapshot: FileTreeState = buildSnapshot()\n let visibleItems: VisibleTreeItem[] = buildVisibleItems()\n\n function expandedIds(): string[] {\n return controlledExpanded !== undefined ? controlledExpanded : uncontrolledExpanded\n }\n\n function selectedId(): string | null {\n return controlledSelected !== undefined ? controlledSelected : uncontrolledSelected\n }\n\n function buildSnapshot(): FileTreeState {\n return {\n nodes,\n expandedIds: expandedIds(),\n selectedId: selectedId(),\n focusedId,\n }\n }\n\n function buildVisibleItems(): VisibleTreeItem[] {\n const expanded = new Set(expandedIds())\n const selected = selectedId()\n const items: VisibleTreeItem[] = []\n const walk = (list: FileTreeNode[], parentId: string | null, depth: number) => {\n for (const node of list) {\n const hasChildren = Boolean(node.children && node.children.length > 0)\n const isExpanded = hasChildren && expanded.has(node.id)\n items.push({\n node,\n parentId,\n depth,\n hasChildren,\n isExpanded,\n isSelected: node.id === selected,\n isFocused: node.id === focusedId,\n })\n if (hasChildren && isExpanded) walk(node.children!, node.id, depth + 1)\n }\n }\n walk(nodes, null, 1)\n return items\n }\n\n function emit(): void {\n snapshot = buildSnapshot()\n visibleItems = buildVisibleItems()\n for (const l of listeners) l()\n }\n\n function setExpanded(ids: string[]): void {\n if (controlledExpanded === undefined) {\n if (arrayEquals(uncontrolledExpanded, ids)) return\n uncontrolledExpanded = ids\n }\n onExpandedChange?.(ids)\n emit()\n }\n\n function isExpandable(id: string): boolean {\n const node = nodeById.get(id)\n return Boolean(node && !node.disabled && node.children && node.children.length > 0)\n }\n\n const api: FileTreeAPI = {\n getState() {\n return snapshot\n },\n\n subscribe(listener) {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n\n getVisibleItems() {\n return visibleItems\n },\n\n isExpanded(id) {\n return expandedIds().includes(id)\n },\n\n toggle(id) {\n if (!isExpandable(id)) return\n const current = expandedIds()\n setExpanded(current.includes(id) ? current.filter((v) => v !== id) : [...current, id])\n },\n\n expand(id) {\n if (!isExpandable(id)) return\n const current = expandedIds()\n if (current.includes(id)) return\n setExpanded([...current, id])\n },\n\n collapse(id) {\n const current = expandedIds()\n if (!current.includes(id)) return\n setExpanded(current.filter((v) => v !== id))\n },\n\n select(id) {\n if (id !== null) {\n const node = nodeById.get(id)\n if (!node || node.disabled) return\n }\n if (controlledSelected === undefined) {\n if (uncontrolledSelected === id) return\n uncontrolledSelected = id\n }\n onSelectionChange?.(id)\n emit()\n },\n\n focus(id) {\n // Focus is only meaningful on rows that are actually rendered.\n if (id !== null && !visibleItems.some((i) => i.node.id === id)) return\n if (focusedId === id) return\n focusedId = id\n emit()\n },\n\n handleKey(key) {\n const items = visibleItems\n if (items.length === 0) return\n const currentIndex = focusedId ? items.findIndex((i) => i.node.id === focusedId) : -1\n\n switch (key) {\n case 'ArrowDown':\n api.focus(items[Math.min(currentIndex + 1, items.length - 1)]!.node.id)\n return\n case 'ArrowUp':\n api.focus(items[Math.max(currentIndex - 1, 0)]!.node.id)\n return\n case 'Home':\n api.focus(items[0]!.node.id)\n return\n case 'End':\n api.focus(items[items.length - 1]!.node.id)\n return\n case 'ArrowRight': {\n const item = currentIndex === -1 ? undefined : items[currentIndex]\n if (!item) {\n api.focus(items[0]!.node.id)\n return\n }\n if (!item.hasChildren) return\n if (!item.isExpanded) api.expand(item.node.id)\n // Expanded: descend to the first child (DFS order puts it next).\n else api.focus(items[currentIndex + 1]?.node.id ?? item.node.id)\n return\n }\n case 'ArrowLeft': {\n const item = currentIndex === -1 ? undefined : items[currentIndex]\n if (!item) return\n if (item.hasChildren && item.isExpanded) api.collapse(item.node.id)\n else if (item.parentId) api.focus(item.parentId)\n return\n }\n case 'Enter':\n case ' ': {\n const item = currentIndex === -1 ? undefined : items[currentIndex]\n if (item) api.select(item.node.id)\n return\n }\n default:\n return\n }\n },\n\n setNodes(nextNodes) {\n nodes = nextNodes\n reindex()\n emit()\n },\n\n setExpandedIds(ids) {\n if (controlledExpanded !== undefined && arrayEquals(controlledExpanded, ids)) return\n controlledExpanded = ids\n emit()\n },\n\n setSelectedId(id) {\n if (controlledSelected !== undefined && controlledSelected === id) return\n controlledSelected = id\n emit()\n },\n\n setOptions(options) {\n if (options.onExpandedChange !== undefined) onExpandedChange = options.onExpandedChange\n if (options.onSelectionChange !== undefined) onSelectionChange = options.onSelectionChange\n if (options['aria-label'] !== undefined) ariaLabel = options['aria-label']\n },\n\n getTreeAria(): Record<string, string | number | boolean> {\n return { role: 'tree', 'aria-label': ariaLabel }\n },\n\n getItemAria(item): Record<string, string | number | boolean> {\n const aria: Record<string, string | number | boolean> = {\n role: 'treeitem',\n 'aria-level': item.depth,\n 'aria-selected': item.isSelected,\n }\n if (item.hasChildren) aria['aria-expanded'] = item.isExpanded\n if (item.node.disabled) aria['aria-disabled'] = true\n return aria\n },\n }\n\n return api\n}\n", "import { cva } from '@refraction-ui/shared'\n\n/** Tree container. */\nexport const fileTreeVariants = cva({\n base: 'w-full text-sm',\n})\n\n/**\n * A single tree row (treeitem). The `selected` variant carries the\n * selected-row treatment so adapters never inline these classes.\n */\nexport const fileTreeItemVariants = cva({\n base: [\n 'flex w-full cursor-pointer select-none items-center gap-1.5 rounded-md px-2 py-1',\n 'text-sm outline-none transition-colors',\n 'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background',\n 'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',\n ].join(' '),\n variants: {\n selected: {\n true: 'bg-accent text-accent-foreground',\n false: 'text-foreground hover:bg-muted',\n },\n },\n defaultVariants: {\n selected: 'false',\n },\n})\n\n/** Expand/collapse glyph or file marker preceding the label. */\nexport const fileTreeGlyphClass = 'w-4 shrink-0 text-center text-muted-foreground'\n", "import * as React from 'react'\nimport { cn } from '@refraction-ui/shared'\nimport {\n createFileTree,\n fileTreeVariants,\n fileTreeItemVariants,\n fileTreeGlyphClass,\n type FileTreeAPI,\n type FileTreeNode,\n type FileTreeState,\n type VisibleTreeItem,\n} from '@refraction-ui/file-tree'\n\nexport type { FileTreeNode }\n\nexport interface FileTreeProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'> {\n /** Root-level nodes of the tree. */\n nodes?: FileTreeNode[]\n /** Controlled expanded node ids. Pair with `onExpandedChange`. */\n expandedIds?: string[]\n /** Uncontrolled initial expanded node ids. */\n defaultExpandedIds?: string[]\n /** Controlled selected node id (`null` = none). Pair with `onSelectionChange`. */\n selectedId?: string | null\n /** Uncontrolled initial selected node id. */\n defaultSelectedId?: string | null\n /** Called whenever the expanded set changes. */\n onExpandedChange?: (ids: string[]) => void\n /** Called whenever the selection changes. */\n onSelectionChange?: (id: string | null) => void\n}\n\n/** Keys the tree handles itself (WAI-ARIA treeview pattern). */\nconst HANDLED_KEYS = new Set([\n 'ArrowDown',\n 'ArrowUp',\n 'ArrowLeft',\n 'ArrowRight',\n 'Home',\n 'End',\n 'Enter',\n ' ',\n])\n\ninterface TreeItemsProps {\n nodes: FileTreeNode[]\n depth: number\n api: FileTreeAPI\n state: FileTreeState\n firstVisibleId: string | undefined\n}\n\nfunction TreeItems({ nodes, depth, api, state, firstVisibleId }: TreeItemsProps) {\n return (\n <>\n {nodes.map((node) => {\n const hasChildren = Boolean(node.children && node.children.length > 0)\n const isExpanded = hasChildren && state.expandedIds.includes(node.id)\n const isSelected = state.selectedId === node.id\n const item: VisibleTreeItem = {\n node,\n parentId: null, // unused by getItemAria; the core tracks real parents\n depth,\n hasChildren,\n isExpanded,\n isSelected,\n isFocused: state.focusedId === node.id,\n }\n const tabbable =\n state.focusedId === node.id || (state.focusedId === null && node.id === firstVisibleId)\n\n return (\n <li key={node.id} role=\"none\" className=\"list-none\">\n <div\n {...api.getItemAria(item)}\n tabIndex={tabbable ? 0 : -1}\n data-disabled={node.disabled ? 'true' : undefined}\n style={{ paddingLeft: `${(depth - 1) * 12}px` }}\n className={fileTreeItemVariants({ selected: isSelected ? 'true' : 'false' })}\n onClick={() => {\n api.focus(node.id)\n api.select(node.id)\n if (hasChildren) api.toggle(node.id)\n }}\n onFocus={() => api.focus(node.id)}\n >\n <span aria-hidden=\"true\" className={fileTreeGlyphClass}>\n {hasChildren ? (isExpanded ? '▾' : '▸') : '·'}\n </span>\n <span className=\"truncate\">{node.label}</span>\n </div>\n {hasChildren && isExpanded && (\n <ul role=\"group\" className=\"m-0 p-0\">\n <TreeItems\n nodes={node.children!}\n depth={depth + 1}\n api={api}\n state={state}\n firstVisibleId={firstVisibleId}\n />\n </ul>\n )}\n </li>\n )\n })}\n </>\n )\n}\n\n/**\n * FileTree — a hierarchical file/folder tree view.\n *\n * Renders `role=\"tree\"` with nested `role=\"group\"`/`role=\"treeitem\"` rows,\n * expand/collapse, single selection, roving tabindex, and WAI-ARIA treeview\n * keyboard navigation. All behavior comes from the headless\n * `@refraction-ui/file-tree` core; supports controlled (`expandedIds` /\n * `selectedId`) and uncontrolled usage.\n */\nexport const FileTree = React.forwardRef<HTMLDivElement, FileTreeProps>(\n function FileTree(\n {\n className,\n nodes = [],\n expandedIds: controlledExpandedIds,\n defaultExpandedIds,\n selectedId: controlledSelectedId,\n defaultSelectedId,\n onExpandedChange,\n onSelectionChange,\n 'aria-label': ariaLabel,\n ...props\n },\n ref,\n ) {\n // The headless core owns expansion/selection/focus; it is created once and\n // props are synced into it below.\n const apiRef = React.useRef<FileTreeAPI | null>(null)\n if (apiRef.current === null) {\n apiRef.current = createFileTree({\n nodes,\n expandedIds: controlledExpandedIds,\n defaultExpandedIds,\n selectedId: controlledSelectedId,\n defaultSelectedId,\n onExpandedChange,\n onSelectionChange,\n 'aria-label': ariaLabel,\n })\n }\n const api = apiRef.current\n\n const state = React.useSyncExternalStore(api.subscribe, api.getState, api.getState)\n\n React.useEffect(() => {\n api.setOptions({ onExpandedChange, onSelectionChange, 'aria-label': ariaLabel })\n })\n React.useEffect(() => {\n api.setNodes(nodes)\n }, [api, nodes])\n React.useEffect(() => {\n if (controlledExpandedIds !== undefined) api.setExpandedIds(controlledExpandedIds)\n }, [api, controlledExpandedIds])\n React.useEffect(() => {\n if (controlledSelectedId !== undefined) api.setSelectedId(controlledSelectedId)\n }, [api, controlledSelectedId])\n\n const firstVisibleId = api.getVisibleItems()[0]?.node.id\n\n return (\n <div ref={ref} className={cn(fileTreeVariants(), className)} {...props}>\n <ul\n {...api.getTreeAria()}\n className=\"m-0 p-0\"\n onKeyDown={(event) => {\n if (!HANDLED_KEYS.has(event.key)) return\n event.preventDefault()\n api.handleKey(event.key)\n }}\n >\n <TreeItems\n nodes={state.nodes}\n depth={1}\n api={api}\n state={state}\n firstVisibleId={firstVisibleId}\n />\n </ul>\n </div>\n )\n },\n)\n"],
5
+ "mappings": "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",
6
+ "names": ["FileTree"]
7
+ }
@@ -0,0 +1,360 @@
1
+ import {
2
+ cn,
3
+ cva,
4
+ generateId
5
+ } from "./chunk-ZF3OHCGV.js";
6
+
7
+ // ../react-file-upload/dist/index.js
8
+ import * as React from "react";
9
+
10
+ // ../file-upload/dist/index.js
11
+ function matchesAccept(fileType, fileName, accept) {
12
+ if (!accept) return true;
13
+ const acceptedTypes = accept.split(",").map((t) => t.trim());
14
+ for (const accepted of acceptedTypes) {
15
+ if (accepted.startsWith(".")) {
16
+ if (fileName.toLowerCase().endsWith(accepted.toLowerCase())) return true;
17
+ } else if (accepted.endsWith("/*")) {
18
+ const prefix = accepted.slice(0, -2);
19
+ if (fileType.startsWith(prefix)) return true;
20
+ } else {
21
+ if (fileType === accepted) return true;
22
+ }
23
+ }
24
+ return false;
25
+ }
26
+ function formatFileSize(bytes) {
27
+ if (bytes === 0) return "0 B";
28
+ const units = ["B", "KB", "MB", "GB"];
29
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
30
+ const size = bytes / Math.pow(1024, i);
31
+ return `${size.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
32
+ }
33
+ function createFileUpload(props = {}) {
34
+ const {
35
+ accept,
36
+ maxSize,
37
+ maxFiles,
38
+ multiple = false,
39
+ onFilesSelected,
40
+ onError
41
+ } = props;
42
+ let files = [];
43
+ let isDragging = false;
44
+ const dropZoneId = generateId("rfr-file-upload-zone");
45
+ const inputId = generateId("rfr-file-upload-input");
46
+ const labelId = generateId("rfr-file-upload-label");
47
+ const fileListId = generateId("rfr-file-upload-list");
48
+ function addFiles(incoming) {
49
+ const errors = [];
50
+ const validFiles = [];
51
+ const availableSlots = maxFiles ? maxFiles - files.length : Infinity;
52
+ if (!multiple && incoming.length > 1) {
53
+ incoming = [incoming[0]];
54
+ }
55
+ for (let i = 0; i < incoming.length; i++) {
56
+ const file = incoming[i];
57
+ if (validFiles.length >= availableSlots) {
58
+ errors.push({
59
+ file: file.name,
60
+ code: "count",
61
+ message: `Maximum of ${maxFiles} files allowed`
62
+ });
63
+ continue;
64
+ }
65
+ if (accept && !matchesAccept(file.type, file.name, accept)) {
66
+ errors.push({
67
+ file: file.name,
68
+ code: "type",
69
+ message: `File type "${file.type || "unknown"}" is not accepted`
70
+ });
71
+ continue;
72
+ }
73
+ if (maxSize && file.size > maxSize) {
74
+ errors.push({
75
+ file: file.name,
76
+ code: "size",
77
+ message: `File size ${formatFileSize(file.size)} exceeds limit of ${formatFileSize(maxSize)}`
78
+ });
79
+ continue;
80
+ }
81
+ validFiles.push({
82
+ id: generateId("rfr-file"),
83
+ name: file.name,
84
+ size: file.size,
85
+ type: file.type,
86
+ progress: 0,
87
+ status: "pending"
88
+ });
89
+ }
90
+ if (errors.length > 0) {
91
+ onError?.(errors);
92
+ }
93
+ if (validFiles.length > 0) {
94
+ if (!multiple) {
95
+ files = validFiles.slice(0, 1);
96
+ } else {
97
+ files = [...files, ...validFiles];
98
+ }
99
+ onFilesSelected?.(files);
100
+ }
101
+ }
102
+ function removeFile(id) {
103
+ files = files.filter((f) => f.id !== id);
104
+ }
105
+ function updateProgress(id, progress) {
106
+ files = files.map(
107
+ (f) => f.id === id ? { ...f, progress: Math.min(100, Math.max(0, progress)) } : f
108
+ );
109
+ }
110
+ function setStatus(id, status, error) {
111
+ files = files.map(
112
+ (f) => f.id === id ? { ...f, status, error, progress: status === "complete" ? 100 : f.progress } : f
113
+ );
114
+ }
115
+ const dropZoneProps = {
116
+ role: "button",
117
+ "aria-label": "Drop files here or click to upload",
118
+ id: dropZoneId,
119
+ tabIndex: 0
120
+ };
121
+ const inputProps = {
122
+ type: "file",
123
+ id: inputId,
124
+ accept: accept ?? void 0,
125
+ multiple,
126
+ "aria-hidden": true,
127
+ tabIndex: -1,
128
+ style: { display: "none" }
129
+ };
130
+ const dragHandlers = {
131
+ onDragEnter() {
132
+ isDragging = true;
133
+ },
134
+ onDragLeave() {
135
+ isDragging = false;
136
+ },
137
+ onDragOver() {
138
+ isDragging = true;
139
+ },
140
+ onDrop() {
141
+ isDragging = false;
142
+ }
143
+ };
144
+ return {
145
+ state: {
146
+ get files() {
147
+ return files;
148
+ },
149
+ get isDragging() {
150
+ return isDragging;
151
+ }
152
+ },
153
+ addFiles,
154
+ removeFile,
155
+ updateProgress,
156
+ setStatus,
157
+ dropZoneProps,
158
+ inputProps,
159
+ dragHandlers,
160
+ ids: {
161
+ dropZone: dropZoneId,
162
+ input: inputId,
163
+ label: labelId,
164
+ fileList: fileListId
165
+ }
166
+ };
167
+ }
168
+ var fileUploadDropZoneVariants = cva({
169
+ base: "flex flex-col items-center justify-center rounded-lg border-2 border-dashed p-8 text-center transition-colors cursor-pointer",
170
+ variants: {
171
+ state: {
172
+ idle: "border-muted-foreground/25 hover:border-muted-foreground/50",
173
+ dragging: "border-primary bg-primary/5",
174
+ disabled: "opacity-50 cursor-not-allowed"
175
+ }
176
+ },
177
+ defaultVariants: {
178
+ state: "idle"
179
+ }
180
+ });
181
+ var fileUploadFileListStyles = "mt-4 space-y-2";
182
+ var fileUploadFileItemStyles = "flex items-center gap-3 rounded-md border p-3 text-sm";
183
+ var fileUploadProgressStyles = "h-1.5 w-full rounded-full bg-secondary overflow-hidden";
184
+ var fileUploadProgressBarStyles = "h-full rounded-full bg-primary transition-all duration-300";
185
+ var fileUploadRemoveButtonStyles = "ml-auto text-muted-foreground hover:text-foreground cursor-pointer";
186
+
187
+ // ../react-file-upload/dist/index.js
188
+ var FileUpload = React.forwardRef(
189
+ function FileUpload2({ accept, maxSize, maxFiles, multiple = false, onFilesSelected, onError, className, children }, ref) {
190
+ const [files, setFiles] = React.useState([]);
191
+ const [isDragging, setIsDragging] = React.useState(false);
192
+ const inputRef = React.useRef(null);
193
+ const api = React.useMemo(
194
+ () => createFileUpload({
195
+ accept,
196
+ maxSize,
197
+ maxFiles,
198
+ multiple,
199
+ onFilesSelected: (newFiles) => {
200
+ setFiles(newFiles);
201
+ onFilesSelected?.(newFiles);
202
+ },
203
+ onError
204
+ }),
205
+ [accept, maxSize, maxFiles, multiple, onFilesSelected, onError]
206
+ );
207
+ const handleDragEnter = (e) => {
208
+ e.preventDefault();
209
+ e.stopPropagation();
210
+ setIsDragging(true);
211
+ };
212
+ const handleDragLeave = (e) => {
213
+ e.preventDefault();
214
+ e.stopPropagation();
215
+ setIsDragging(false);
216
+ };
217
+ const handleDragOver = (e) => {
218
+ e.preventDefault();
219
+ e.stopPropagation();
220
+ };
221
+ const handleDrop = (e) => {
222
+ e.preventDefault();
223
+ e.stopPropagation();
224
+ setIsDragging(false);
225
+ const droppedFiles = Array.from(e.dataTransfer.files).map((f) => ({
226
+ name: f.name,
227
+ size: f.size,
228
+ type: f.type
229
+ }));
230
+ if (droppedFiles.length > 0) {
231
+ api.addFiles(droppedFiles);
232
+ }
233
+ };
234
+ const handleInputChange = (e) => {
235
+ const selectedFiles = Array.from(e.target.files || []).map((f) => ({
236
+ name: f.name,
237
+ size: f.size,
238
+ type: f.type
239
+ }));
240
+ if (selectedFiles.length > 0) {
241
+ api.addFiles(selectedFiles);
242
+ }
243
+ e.target.value = "";
244
+ };
245
+ const handleRemove = (id) => {
246
+ setFiles((prev) => prev.filter((f) => f.id !== id));
247
+ api.removeFile(id);
248
+ };
249
+ const handleClick = () => {
250
+ inputRef.current?.click();
251
+ };
252
+ const handleKeyDown = (e) => {
253
+ if (e.key === "Enter" || e.key === " ") {
254
+ e.preventDefault();
255
+ handleClick();
256
+ }
257
+ };
258
+ return React.createElement(
259
+ "div",
260
+ { ref, className },
261
+ // Hidden file input
262
+ React.createElement("input", {
263
+ ref: inputRef,
264
+ ...api.inputProps,
265
+ onChange: handleInputChange
266
+ }),
267
+ // Drop zone
268
+ React.createElement(
269
+ "div",
270
+ {
271
+ ...api.dropZoneProps,
272
+ className: fileUploadDropZoneVariants({
273
+ state: isDragging ? "dragging" : "idle"
274
+ }),
275
+ onClick: handleClick,
276
+ onKeyDown: handleKeyDown,
277
+ onDragEnter: handleDragEnter,
278
+ onDragLeave: handleDragLeave,
279
+ onDragOver: handleDragOver,
280
+ onDrop: handleDrop
281
+ },
282
+ children ?? React.createElement(
283
+ React.Fragment,
284
+ null,
285
+ React.createElement("div", { className: "text-2xl mb-2" }, "\u{1F4C1}"),
286
+ React.createElement(
287
+ "p",
288
+ { className: "text-sm text-muted-foreground" },
289
+ "Drag & drop files here, or click to select"
290
+ ),
291
+ accept && React.createElement(
292
+ "p",
293
+ { className: "text-xs text-muted-foreground mt-1" },
294
+ `Accepted: ${accept}`
295
+ ),
296
+ maxSize && React.createElement(
297
+ "p",
298
+ { className: "text-xs text-muted-foreground" },
299
+ `Max size: ${formatFileSize(maxSize)}`
300
+ )
301
+ )
302
+ ),
303
+ // File list
304
+ files.length > 0 && React.createElement(
305
+ "div",
306
+ { className: fileUploadFileListStyles },
307
+ files.map(
308
+ (file) => React.createElement(
309
+ "div",
310
+ { key: file.id, className: fileUploadFileItemStyles },
311
+ React.createElement(
312
+ "div",
313
+ { className: "flex-1 min-w-0" },
314
+ React.createElement("div", { className: "font-medium truncate" }, file.name),
315
+ React.createElement(
316
+ "div",
317
+ { className: "text-xs text-muted-foreground" },
318
+ formatFileSize(file.size)
319
+ ),
320
+ file.status === "uploading" && React.createElement(
321
+ "div",
322
+ { className: cn(fileUploadProgressStyles, "mt-1") },
323
+ React.createElement("div", {
324
+ className: fileUploadProgressBarStyles,
325
+ style: { width: `${file.progress}%` }
326
+ })
327
+ ),
328
+ file.error && React.createElement(
329
+ "div",
330
+ { className: "text-xs text-destructive mt-0.5" },
331
+ file.error
332
+ )
333
+ ),
334
+ React.createElement(
335
+ "button",
336
+ {
337
+ type: "button",
338
+ className: fileUploadRemoveButtonStyles,
339
+ onClick: (e) => {
340
+ e.stopPropagation();
341
+ handleRemove(file.id);
342
+ },
343
+ "aria-label": `Remove ${file.name}`
344
+ },
345
+ "\xD7"
346
+ )
347
+ )
348
+ )
349
+ )
350
+ );
351
+ }
352
+ );
353
+ export {
354
+ FileUpload,
355
+ fileUploadDropZoneVariants,
356
+ fileUploadFileItemStyles,
357
+ fileUploadFileListStyles,
358
+ formatFileSize
359
+ };
360
+ //# sourceMappingURL=react-file-upload.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../file-upload/src/file-upload.ts", "../../file-upload/src/file-upload.styles.ts", "../../react-file-upload/src/file-upload.tsx"],
4
+ "sourcesContent": ["import type { AccessibilityProps } from '@refraction-ui/shared'\nimport { generateId } from '@refraction-ui/shared'\n\nexport type FileStatus = 'pending' | 'uploading' | 'complete' | 'error'\n\nexport interface FileEntry {\n /** Unique ID for this file entry */\n id: string\n /** Original file name */\n name: string\n /** File size in bytes */\n size: number\n /** MIME type */\n type: string\n /** Upload progress 0-100 */\n progress: number\n /** Current status */\n status: FileStatus\n /** Error message if status is 'error' */\n error?: string\n}\n\nexport interface FileUploadProps {\n /** Accepted file types (e.g., 'image/*,.pdf') */\n accept?: string\n /** Max file size in bytes */\n maxSize?: number\n /** Max number of files */\n maxFiles?: number\n /** Allow multiple file selection */\n multiple?: boolean\n /** Callback when files are selected/added */\n onFilesSelected?: (files: FileEntry[]) => void\n /** Callback when a validation error occurs */\n onError?: (errors: FileUploadError[]) => void\n}\n\nexport interface FileUploadError {\n file: string\n code: 'type' | 'size' | 'count'\n message: string\n}\n\nexport interface FileUploadState {\n files: FileEntry[]\n isDragging: boolean\n}\n\nexport interface FileUploadAPI {\n /** Current state */\n state: FileUploadState\n /** Add files (validates type, size, count) */\n addFiles(files: Array<{ name: string; size: number; type: string }>): void\n /** Remove a file by ID */\n removeFile(id: string): void\n /** Update a file's progress */\n updateProgress(id: string, progress: number): void\n /** Set a file's status */\n setStatus(id: string, status: FileStatus, error?: string): void\n /** ARIA props for the drop zone */\n dropZoneProps: Partial<AccessibilityProps> & Record<string, unknown>\n /** ARIA props for the file input */\n inputProps: Record<string, unknown>\n /** Drag event handlers (for headless use) */\n dragHandlers: {\n onDragEnter(): void\n onDragLeave(): void\n onDragOver(): void\n onDrop(): void\n }\n /** Generated IDs */\n ids: {\n dropZone: string\n input: string\n label: string\n fileList: string\n }\n}\n\nfunction matchesAccept(fileType: string, fileName: string, accept: string): boolean {\n if (!accept) return true\n const acceptedTypes = accept.split(',').map((t) => t.trim())\n for (const accepted of acceptedTypes) {\n if (accepted.startsWith('.')) {\n // Extension match\n if (fileName.toLowerCase().endsWith(accepted.toLowerCase())) return true\n } else if (accepted.endsWith('/*')) {\n // Wildcard MIME match (e.g., 'image/*')\n const prefix = accepted.slice(0, -2)\n if (fileType.startsWith(prefix)) return true\n } else {\n // Exact MIME match\n if (fileType === accepted) return true\n }\n }\n return false\n}\n\nexport function formatFileSize(bytes: number): string {\n if (bytes === 0) return '0 B'\n const units = ['B', 'KB', 'MB', 'GB']\n const i = Math.floor(Math.log(bytes) / Math.log(1024))\n const size = bytes / Math.pow(1024, i)\n return `${size.toFixed(i === 0 ? 0 : 1)} ${units[i]}`\n}\n\nexport function createFileUpload(props: FileUploadProps = {}): FileUploadAPI {\n const {\n accept,\n maxSize,\n maxFiles,\n multiple = false,\n onFilesSelected,\n onError,\n } = props\n\n let files: FileEntry[] = []\n let isDragging = false\n\n const dropZoneId = generateId('rfr-file-upload-zone')\n const inputId = generateId('rfr-file-upload-input')\n const labelId = generateId('rfr-file-upload-label')\n const fileListId = generateId('rfr-file-upload-list')\n\n function addFiles(incoming: Array<{ name: string; size: number; type: string }>): void {\n const errors: FileUploadError[] = []\n const validFiles: FileEntry[] = []\n\n // Check count limit\n const availableSlots = maxFiles ? maxFiles - files.length : Infinity\n if (!multiple && incoming.length > 1) {\n // Only take the first file if not multiple\n incoming = [incoming[0]]\n }\n\n for (let i = 0; i < incoming.length; i++) {\n const file = incoming[i]\n\n if (validFiles.length >= availableSlots) {\n errors.push({\n file: file.name,\n code: 'count',\n message: `Maximum of ${maxFiles} files allowed`,\n })\n continue\n }\n\n // Validate type\n if (accept && !matchesAccept(file.type, file.name, accept)) {\n errors.push({\n file: file.name,\n code: 'type',\n message: `File type \"${file.type || 'unknown'}\" is not accepted`,\n })\n continue\n }\n\n // Validate size\n if (maxSize && file.size > maxSize) {\n errors.push({\n file: file.name,\n code: 'size',\n message: `File size ${formatFileSize(file.size)} exceeds limit of ${formatFileSize(maxSize)}`,\n })\n continue\n }\n\n validFiles.push({\n id: generateId('rfr-file'),\n name: file.name,\n size: file.size,\n type: file.type,\n progress: 0,\n status: 'pending',\n })\n }\n\n if (errors.length > 0) {\n onError?.(errors)\n }\n\n if (validFiles.length > 0) {\n if (!multiple) {\n files = validFiles.slice(0, 1)\n } else {\n files = [...files, ...validFiles]\n }\n onFilesSelected?.(files)\n }\n }\n\n function removeFile(id: string): void {\n files = files.filter((f) => f.id !== id)\n }\n\n function updateProgress(id: string, progress: number): void {\n files = files.map((f) =>\n f.id === id ? { ...f, progress: Math.min(100, Math.max(0, progress)) } : f,\n )\n }\n\n function setStatus(id: string, status: FileStatus, error?: string): void {\n files = files.map((f) =>\n f.id === id ? { ...f, status, error, progress: status === 'complete' ? 100 : f.progress } : f,\n )\n }\n\n const dropZoneProps: Partial<AccessibilityProps> & Record<string, unknown> = {\n role: 'button',\n 'aria-label': 'Drop files here or click to upload',\n id: dropZoneId,\n tabIndex: 0,\n }\n\n const inputProps: Record<string, unknown> = {\n type: 'file',\n id: inputId,\n accept: accept ?? undefined,\n multiple,\n 'aria-hidden': true,\n tabIndex: -1,\n style: { display: 'none' },\n }\n\n const dragHandlers = {\n onDragEnter() {\n isDragging = true\n },\n onDragLeave() {\n isDragging = false\n },\n onDragOver() {\n isDragging = true\n },\n onDrop() {\n isDragging = false\n },\n }\n\n return {\n state: {\n get files() { return files },\n get isDragging() { return isDragging },\n },\n addFiles,\n removeFile,\n updateProgress,\n setStatus,\n dropZoneProps,\n inputProps,\n dragHandlers,\n ids: {\n dropZone: dropZoneId,\n input: inputId,\n label: labelId,\n fileList: fileListId,\n },\n }\n}\n", "import { cva } from '@refraction-ui/shared'\n\nexport const fileUploadDropZoneVariants = cva({\n base: 'flex flex-col items-center justify-center rounded-lg border-2 border-dashed p-8 text-center transition-colors cursor-pointer',\n variants: {\n state: {\n idle: 'border-muted-foreground/25 hover:border-muted-foreground/50',\n dragging: 'border-primary bg-primary/5',\n disabled: 'opacity-50 cursor-not-allowed',\n },\n },\n defaultVariants: {\n state: 'idle',\n },\n})\n\nexport const fileUploadFileListStyles =\n 'mt-4 space-y-2'\n\nexport const fileUploadFileItemStyles =\n 'flex items-center gap-3 rounded-md border p-3 text-sm'\n\nexport const fileUploadProgressStyles =\n 'h-1.5 w-full rounded-full bg-secondary overflow-hidden'\n\nexport const fileUploadProgressBarStyles =\n 'h-full rounded-full bg-primary transition-all duration-300'\n\nexport const fileUploadRemoveButtonStyles =\n 'ml-auto text-muted-foreground hover:text-foreground cursor-pointer'\n", "import * as React from 'react'\nimport {\n createFileUpload,\n formatFileSize,\n fileUploadDropZoneVariants,\n fileUploadFileListStyles,\n fileUploadFileItemStyles,\n fileUploadProgressStyles,\n fileUploadProgressBarStyles,\n fileUploadRemoveButtonStyles,\n type FileEntry,\n type FileUploadError,\n} from '@refraction-ui/file-upload'\nimport { cn } from '@refraction-ui/shared'\n\nexport interface FileUploadProps {\n accept?: string\n maxSize?: number\n maxFiles?: number\n multiple?: boolean\n onFilesSelected?: (files: FileEntry[]) => void\n onError?: (errors: FileUploadError[]) => void\n className?: string\n children?: React.ReactNode\n}\n\nexport const FileUpload = React.forwardRef<HTMLDivElement, FileUploadProps>(\n function FileUpload({ accept, maxSize, maxFiles, multiple = false, onFilesSelected, onError, className, children }, ref) {\n const [files, setFiles] = React.useState<FileEntry[]>([])\n const [isDragging, setIsDragging] = React.useState(false)\n const inputRef = React.useRef<HTMLInputElement>(null)\n\n const api = React.useMemo(\n () =>\n createFileUpload({\n accept,\n maxSize,\n maxFiles,\n multiple,\n onFilesSelected: (newFiles) => {\n setFiles(newFiles)\n onFilesSelected?.(newFiles)\n },\n onError,\n }),\n [accept, maxSize, maxFiles, multiple, onFilesSelected, onError],\n )\n\n const handleDragEnter = (e: React.DragEvent) => {\n e.preventDefault()\n e.stopPropagation()\n setIsDragging(true)\n }\n\n const handleDragLeave = (e: React.DragEvent) => {\n e.preventDefault()\n e.stopPropagation()\n setIsDragging(false)\n }\n\n const handleDragOver = (e: React.DragEvent) => {\n e.preventDefault()\n e.stopPropagation()\n }\n\n const handleDrop = (e: React.DragEvent) => {\n e.preventDefault()\n e.stopPropagation()\n setIsDragging(false)\n const droppedFiles = Array.from(e.dataTransfer.files).map((f) => ({\n name: f.name,\n size: f.size,\n type: f.type,\n }))\n if (droppedFiles.length > 0) {\n api.addFiles(droppedFiles)\n }\n }\n\n const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const selectedFiles = Array.from(e.target.files || []).map((f) => ({\n name: f.name,\n size: f.size,\n type: f.type,\n }))\n if (selectedFiles.length > 0) {\n api.addFiles(selectedFiles)\n }\n // Reset input so same file can be re-selected\n e.target.value = ''\n }\n\n const handleRemove = (id: string) => {\n setFiles((prev) => prev.filter((f) => f.id !== id))\n api.removeFile(id)\n }\n\n const handleClick = () => {\n inputRef.current?.click()\n }\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n handleClick()\n }\n }\n\n return React.createElement(\n 'div',\n { ref, className },\n // Hidden file input\n React.createElement('input', {\n ref: inputRef,\n ...api.inputProps,\n onChange: handleInputChange,\n }),\n // Drop zone\n React.createElement(\n 'div',\n {\n ...api.dropZoneProps,\n className: fileUploadDropZoneVariants({\n state: isDragging ? 'dragging' : 'idle',\n }),\n onClick: handleClick,\n onKeyDown: handleKeyDown,\n onDragEnter: handleDragEnter,\n onDragLeave: handleDragLeave,\n onDragOver: handleDragOver,\n onDrop: handleDrop,\n },\n children ??\n React.createElement(\n React.Fragment,\n null,\n React.createElement('div', { className: 'text-2xl mb-2' }, '\\u{1F4C1}'),\n React.createElement(\n 'p',\n { className: 'text-sm text-muted-foreground' },\n 'Drag & drop files here, or click to select',\n ),\n accept &&\n React.createElement(\n 'p',\n { className: 'text-xs text-muted-foreground mt-1' },\n `Accepted: ${accept}`,\n ),\n maxSize &&\n React.createElement(\n 'p',\n { className: 'text-xs text-muted-foreground' },\n `Max size: ${formatFileSize(maxSize)}`,\n ),\n ),\n ),\n // File list\n files.length > 0 &&\n React.createElement(\n 'div',\n { className: fileUploadFileListStyles },\n files.map((file) =>\n React.createElement(\n 'div',\n { key: file.id, className: fileUploadFileItemStyles },\n React.createElement(\n 'div',\n { className: 'flex-1 min-w-0' },\n React.createElement('div', { className: 'font-medium truncate' }, file.name),\n React.createElement(\n 'div',\n { className: 'text-xs text-muted-foreground' },\n formatFileSize(file.size),\n ),\n file.status === 'uploading' &&\n React.createElement(\n 'div',\n { className: cn(fileUploadProgressStyles, 'mt-1') },\n React.createElement('div', {\n className: fileUploadProgressBarStyles,\n style: { width: `${file.progress}%` },\n }),\n ),\n file.error &&\n React.createElement(\n 'div',\n { className: 'text-xs text-destructive mt-0.5' },\n file.error,\n ),\n ),\n React.createElement(\n 'button',\n {\n type: 'button',\n className: fileUploadRemoveButtonStyles,\n onClick: (e: React.MouseEvent) => {\n e.stopPropagation()\n handleRemove(file.id)\n },\n 'aria-label': `Remove ${file.name}`,\n },\n '\\u00D7',\n ),\n ),\n ),\n ),\n )\n },\n)\n"],
5
+ "mappings": "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",
6
+ "names": ["FileUpload"]
7
+ }
@@ -0,0 +1,124 @@
1
+ import {
2
+ cn,
3
+ cva
4
+ } from "./chunk-ZF3OHCGV.js";
5
+
6
+ // ../react-floating-reactions/dist/index.js
7
+ import * as React from "react";
8
+
9
+ // ../floating-reactions/dist/index.js
10
+ var REACTION_LIFETIME_MS = 3e3;
11
+ function laneOffsetPercent(lane = 0, lanes = 5) {
12
+ if (lanes <= 0) return 50;
13
+ const clamped = Math.max(0, Math.min(lane, lanes - 1));
14
+ const step = 100 / lanes;
15
+ return step * clamped + step / 2;
16
+ }
17
+ function createFloatingReactions() {
18
+ const ariaProps = {
19
+ role: "status",
20
+ "aria-live": "polite",
21
+ "aria-label": "Reactions"
22
+ };
23
+ const dataAttributes = {
24
+ "data-component": "floating-reactions"
25
+ };
26
+ return { ariaProps, dataAttributes };
27
+ }
28
+ var floatingReactionsVariants = cva({
29
+ base: "absolute inset-0 pointer-events-none overflow-hidden",
30
+ variants: {
31
+ /**
32
+ * Whether the overlay is currently active (has reactions). The parent may
33
+ * toggle visibility, but we keep the node mounted to preserve live-region
34
+ * semantics.
35
+ */
36
+ active: {
37
+ true: "",
38
+ false: ""
39
+ }
40
+ },
41
+ defaultVariants: {
42
+ active: "false"
43
+ }
44
+ });
45
+ var floatingReactionItemClass = [
46
+ "absolute bottom-4",
47
+ "text-3xl leading-none select-none",
48
+ "animate-[float-up_3s_ease-out_forwards]"
49
+ ].join(" ");
50
+ var floatingReactionLabelClass = "sr-only";
51
+
52
+ // ../react-floating-reactions/dist/index.js
53
+ import { jsx, jsxs } from "react/jsx-runtime";
54
+ var FloatingReactions = React.forwardRef(function FloatingReactions2({ reactions, lanes = 5, className, ...props }, ref) {
55
+ const { ariaProps, dataAttributes } = createFloatingReactions();
56
+ return /* @__PURE__ */ jsx(
57
+ "div",
58
+ {
59
+ ref,
60
+ className: cn(floatingReactionsVariants({ active: reactions.length > 0 ? "true" : "false" }), className),
61
+ ...ariaProps,
62
+ ...dataAttributes,
63
+ ...props,
64
+ children: reactions.map((reaction) => {
65
+ const left = laneOffsetPercent(reaction.lane ?? 0, lanes);
66
+ return /* @__PURE__ */ jsxs(
67
+ "span",
68
+ {
69
+ className: floatingReactionItemClass,
70
+ style: { left: `${left}%` },
71
+ "aria-hidden": "true",
72
+ children: [
73
+ reaction.emoji,
74
+ /* @__PURE__ */ jsx("span", { className: floatingReactionLabelClass, children: reaction.emoji })
75
+ ]
76
+ },
77
+ reaction.id
78
+ );
79
+ })
80
+ }
81
+ );
82
+ });
83
+ function ReactionBurst({ emoji, lane = 0, lanes = 5 }) {
84
+ const left = laneOffsetPercent(lane, lanes);
85
+ return /* @__PURE__ */ jsxs(
86
+ "span",
87
+ {
88
+ className: floatingReactionItemClass,
89
+ style: { left: `${left}%` },
90
+ "aria-hidden": "true",
91
+ children: [
92
+ emoji,
93
+ /* @__PURE__ */ jsx("span", { className: floatingReactionLabelClass, children: emoji })
94
+ ]
95
+ }
96
+ );
97
+ }
98
+ function useFloatingReactions() {
99
+ const [reactions, setReactions] = React.useState([]);
100
+ const counter = React.useRef(0);
101
+ const emit = React.useCallback(
102
+ (emoji, options = {}) => {
103
+ counter.current += 1;
104
+ const id = options.id ?? `fr-${counter.current}`;
105
+ const lane = options.lane;
106
+ const reaction = { id, emoji, lane };
107
+ setReactions((prev) => [...prev, reaction]);
108
+ setTimeout(() => {
109
+ setReactions((prev) => prev.filter((r) => r.id !== id));
110
+ }, REACTION_LIFETIME_MS);
111
+ },
112
+ []
113
+ );
114
+ const clear = React.useCallback(() => {
115
+ setReactions([]);
116
+ }, []);
117
+ return { reactions, emit, clear };
118
+ }
119
+ export {
120
+ FloatingReactions,
121
+ ReactionBurst,
122
+ useFloatingReactions
123
+ };
124
+ //# sourceMappingURL=react-floating-reactions.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../floating-reactions/src/floating-reactions.ts", "../../floating-reactions/src/floating-reactions.styles.ts", "../../react-floating-reactions/src/floating-reactions.tsx"],
4
+ "sourcesContent": ["/**\n * Headless core for floating emoji reactions.\n *\n * An emoji burst overlay where reactions animate upward and fade — distinct\n * from the static reaction-bar (which shows counts). This module is JSX-free\n * and framework-agnostic; adapters consume it and manage timing with\n * REACTION_LIFETIME_MS.\n */\n\n/** A single transient reaction displayed in the overlay. */\nexport interface FloatingReaction {\n /** Stable unique id assigned by the adapter (used as React key / tracker). */\n id: string\n /** The emoji character(s) to display, e.g. '👋' or '❤️'. */\n emoji: string\n /**\n * Horizontal offset bucket (0-indexed). Adapters use laneOffsetPercent() to\n * convert this into a CSS left % so multiple reactions spread across the\n * overlay rather than stacking.\n */\n lane?: number\n}\n\n/**\n * How long (ms) a floating reaction should live before the adapter removes it.\n * Use this constant in adapters to drive setTimeout-based auto-expiry so the\n * value stays in sync with the CSS animation duration.\n */\nexport const REACTION_LIFETIME_MS = 3000\n\n/**\n * Compute the horizontal left-offset percentage for a given lane bucket.\n *\n * Pure and deterministic — safe to call in tests and SSR.\n *\n * @param lane Zero-indexed lane number (clamped to [0, lanes-1]).\n * @param lanes Total number of lanes (default 5).\n * @returns A percentage value in [0, 100].\n *\n * @example\n * laneOffsetPercent(0, 5) // 10\n * laneOffsetPercent(2, 5) // 50\n * laneOffsetPercent(4, 5) // 90\n */\nexport function laneOffsetPercent(lane = 0, lanes = 5): number {\n if (lanes <= 0) return 50\n const clamped = Math.max(0, Math.min(lane, lanes - 1))\n // Divide [0,100] into `lanes` equal buckets and return the centre of each.\n const step = 100 / lanes\n return step * clamped + step / 2\n}\n\nexport interface FloatingReactionsAriaProps {\n role: string\n 'aria-live': 'polite' | 'assertive' | 'off'\n 'aria-label': string\n}\n\nexport interface FloatingReactionsAPI {\n /** ARIA attributes to spread on the overlay container. */\n ariaProps: FloatingReactionsAriaProps\n /** Data attributes for styling hooks. */\n dataAttributes: Record<string, string>\n}\n\n/**\n * Build the framework-agnostic accessibility and data props for the overlay\n * container.\n *\n * The overlay is a `role=\"status\"` live region so screen readers announce\n * incoming reactions without interrupting the user (polite).\n */\nexport function createFloatingReactions(): FloatingReactionsAPI {\n const ariaProps: FloatingReactionsAriaProps = {\n role: 'status',\n 'aria-live': 'polite',\n 'aria-label': 'Reactions',\n }\n\n const dataAttributes: Record<string, string> = {\n 'data-component': 'floating-reactions',\n }\n\n return { ariaProps, dataAttributes }\n}\n", "import { cva } from '@refraction-ui/shared'\n\n/**\n * Overlay container — fills the parent with absolute positioning so reactions\n * float over content. pointer-events-none keeps the overlay non-interactive;\n * overflow-hidden clips reactions that drift outside the bounds.\n */\nexport const floatingReactionsVariants = cva({\n base: 'absolute inset-0 pointer-events-none overflow-hidden',\n variants: {\n /**\n * Whether the overlay is currently active (has reactions). The parent may\n * toggle visibility, but we keep the node mounted to preserve live-region\n * semantics.\n */\n active: {\n true: '',\n false: '',\n },\n },\n defaultVariants: {\n active: 'false',\n },\n})\n\n/**\n * A single floating reaction item.\n *\n * The float-up animation is driven by the class\n * `animate-[float-up_3s_ease-out_forwards]`. Consumers must supply the\n * `@keyframes float-up` rule in their global CSS or Tailwind config:\n *\n * ```css\n * @keyframes float-up {\n * 0% { transform: translateY(0); opacity: 1; }\n * 80% { opacity: 1; }\n * 100% { transform: translateY(-80%); opacity: 0; }\n * }\n * ```\n *\n * Without the keyframe the item will still render in place and fade on removal\n * (graceful degradation).\n */\nexport const floatingReactionItemClass = [\n 'absolute bottom-4',\n 'text-3xl leading-none select-none',\n 'animate-[float-up_3s_ease-out_forwards]',\n].join(' ')\n\n/**\n * Accessible label node for the reaction — visually hidden so screen readers\n * read the emoji description once and don't repeat the decorative glyph.\n */\nexport const floatingReactionLabelClass = 'sr-only'\n", "import * as React from 'react'\nimport {\n createFloatingReactions,\n laneOffsetPercent,\n REACTION_LIFETIME_MS,\n floatingReactionsVariants,\n floatingReactionItemClass,\n floatingReactionLabelClass,\n type FloatingReaction,\n} from '@refraction-ui/floating-reactions'\nimport { cn } from '@refraction-ui/shared'\n\nexport type { FloatingReaction }\n\nexport interface FloatingReactionsProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'color' | 'content'> {\n /**\n * Controlled list of reactions currently visible in the overlay. The parent\n * is responsible for adding items and removing them after REACTION_LIFETIME_MS\n * (or use the useFloatingReactions hook which handles this automatically).\n */\n reactions: FloatingReaction[]\n /**\n * Total number of horizontal lane buckets used to spread reactions.\n * @default 5\n */\n lanes?: number\n}\n\n/**\n * FloatingReactions — a controlled overlay that renders transient emoji\n * reactions floating upward. Intended to be positioned absolutely over a\n * meeting/video surface.\n *\n * - Reactions animate upward and fade via `animate-[float-up_3s_ease-out_forwards]`.\n * Consumers must supply the `@keyframes float-up` rule (see\n * `floatingReactionItemClass` in the core styles for the recommended keyframe).\n * - The overlay is `pointer-events-none` so it never blocks interaction.\n * - role=\"status\" aria-live=\"polite\" announces new reactions to screen readers.\n */\nexport const FloatingReactions = React.forwardRef<\n HTMLDivElement,\n FloatingReactionsProps\n>(function FloatingReactions({ reactions, lanes = 5, className, ...props }, ref) {\n const { ariaProps, dataAttributes } = createFloatingReactions()\n\n return (\n <div\n ref={ref}\n className={cn(floatingReactionsVariants({ active: reactions.length > 0 ? 'true' : 'false' }), className)}\n {...ariaProps}\n {...dataAttributes}\n {...props}\n >\n {reactions.map((reaction) => {\n const left = laneOffsetPercent(reaction.lane ?? 0, lanes)\n return (\n <span\n key={reaction.id}\n className={floatingReactionItemClass}\n style={{ left: `${left}%` }}\n aria-hidden=\"true\"\n >\n {reaction.emoji}\n <span className={floatingReactionLabelClass}>{reaction.emoji}</span>\n </span>\n )\n })}\n </div>\n )\n})\n\n// ---------------------------------------------------------------------------\n// ReactionBurst — convenience alias for a single burst item (for story use).\n// ---------------------------------------------------------------------------\n\nexport interface ReactionBurstProps {\n emoji: string\n lane?: number\n lanes?: number\n}\n\n/**\n * ReactionBurst renders a single emoji at the specified lane offset — useful\n * for static demos or Storybook stories showing a specific burst position.\n */\nexport function ReactionBurst({ emoji, lane = 0, lanes = 5 }: ReactionBurstProps) {\n const left = laneOffsetPercent(lane, lanes)\n return (\n <span\n className={floatingReactionItemClass}\n style={{ left: `${left}%` }}\n aria-hidden=\"true\"\n >\n {emoji}\n <span className={floatingReactionLabelClass}>{emoji}</span>\n </span>\n )\n}\n\n// ---------------------------------------------------------------------------\n// useFloatingReactions — manages the list with auto-expiry.\n// ---------------------------------------------------------------------------\n\nexport interface UseFloatingReactionsResult {\n reactions: FloatingReaction[]\n /**\n * Add an emoji reaction. An auto-incrementing id (not Math.random) is\n * generated internally unless you pass one explicitly.\n */\n emit: (emoji: string, options?: { id?: string; lane?: number }) => void\n /** Remove all current reactions immediately. */\n clear: () => void\n}\n\n/**\n * Manages a list of FloatingReaction items with automatic expiry after\n * REACTION_LIFETIME_MS. IDs use an incrementing ref counter — no Math.random.\n *\n * @example\n * const { reactions, emit } = useFloatingReactions()\n * // <FloatingReactions reactions={reactions} />\n * // <button onClick={() => emit('👋')}>Wave</button>\n */\nexport function useFloatingReactions(): UseFloatingReactionsResult {\n const [reactions, setReactions] = React.useState<FloatingReaction[]>([])\n const counter = React.useRef(0)\n\n const emit = React.useCallback(\n (emoji: string, options: { id?: string; lane?: number } = {}) => {\n counter.current += 1\n const id = options.id ?? `fr-${counter.current}`\n const lane = options.lane\n\n const reaction: FloatingReaction = { id, emoji, lane }\n setReactions((prev) => [...prev, reaction])\n\n setTimeout(() => {\n setReactions((prev) => prev.filter((r) => r.id !== id))\n }, REACTION_LIFETIME_MS)\n },\n [],\n )\n\n const clear = React.useCallback(() => {\n setReactions([])\n }, [])\n\n return { reactions, emit, clear }\n}\n"],
5
+ "mappings": ";;;;;;;;;AA4BO,IAAM,uBAAuB;AAgB7B,SAAS,kBAAkB,OAAO,GAAG,QAAQ,GAAW;AAC7D,MAAI,SAAS,EAAG,QAAO;AACvB,QAAM,UAAU,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,QAAQ,CAAC,CAAC;AAErD,QAAM,OAAO,MAAM;AACnB,SAAO,OAAO,UAAU,OAAO;AACjC;AAsBO,SAAS,0BAAgD;AAC9D,QAAM,YAAwC;IAC5C,MAAM;IACN,aAAa;IACb,cAAc;EAAA;AAGhB,QAAM,iBAAyC;IAC7C,kBAAkB;EAAA;AAGpB,SAAO,EAAE,WAAW,eAAA;AACtB;AC7EO,IAAM,4BAA4B,IAAI;EAC3C,MAAM;EACN,UAAU;;;;;;IAMR,QAAQ;MACN,MAAM;MACN,OAAO;IAAA;EACT;EAEF,iBAAiB;IACf,QAAQ;EAAA;AAEZ,CAAC;AAoBM,IAAM,4BAA4B;EACvC;EACA;EACA;AACF,EAAE,KAAK,GAAG;AAMH,IAAM,6BAA6B;;;;ACbnC,IAAM,oBAA0B,iBAGrC,SAASA,mBAAkB,EAAE,WAAW,QAAQ,GAAG,WAAW,GAAG,MAAA,GAAS,KAAK;AAC/E,QAAM,EAAE,WAAW,eAAA,IAAmB,wBAAA;AAEtC,SACE;IAAC;IAAA;MACC;MACA,WAAW,GAAG,0BAA0B,EAAE,QAAQ,UAAU,SAAS,IAAI,SAAS,QAAA,CAAS,GAAG,SAAS;MACtG,GAAG;MACH,GAAG;MACH,GAAG;MAEH,UAAA,UAAU,IAAI,CAAC,aAAa;AAC3B,cAAM,OAAO,kBAAkB,SAAS,QAAQ,GAAG,KAAK;AACxD,eACE;UAAC;UAAA;YAEC,WAAW;YACX,OAAO,EAAE,MAAM,GAAG,IAAI,IAAA;YACtB,eAAY;YAEX,UAAA;cAAA,SAAS;cACV,oBAAC,QAAA,EAAK,WAAW,4BAA6B,UAAA,SAAS,MAAA,CAAM;YAAA;UAAA;UANxD,SAAS;QAAA;MASpB,CAAC;IAAA;EAAA;AAGP,CAAC;AAgBM,SAAS,cAAc,EAAE,OAAO,OAAO,GAAG,QAAQ,EAAA,GAAyB;AAChF,QAAM,OAAO,kBAAkB,MAAM,KAAK;AAC1C,SACE;IAAC;IAAA;MACC,WAAW;MACX,OAAO,EAAE,MAAM,GAAG,IAAI,IAAA;MACtB,eAAY;MAEX,UAAA;QAAA;QACD,oBAAC,QAAA,EAAK,WAAW,4BAA6B,UAAA,MAAA,CAAM;MAAA;IAAA;EAAA;AAG1D;AA0BO,SAAS,uBAAmD;AACjE,QAAM,CAAC,WAAW,YAAY,IAAU,eAA6B,CAAA,CAAE;AACvE,QAAM,UAAgB,aAAO,CAAC;AAE9B,QAAM,OAAa;IACjB,CAAC,OAAe,UAA0C,CAAA,MAAO;AAC/D,cAAQ,WAAW;AACnB,YAAM,KAAK,QAAQ,MAAM,MAAM,QAAQ,OAAO;AAC9C,YAAM,OAAO,QAAQ;AAErB,YAAM,WAA6B,EAAE,IAAI,OAAO,KAAA;AAChD,mBAAa,CAAC,SAAS,CAAC,GAAG,MAAM,QAAQ,CAAC;AAE1C,iBAAW,MAAM;AACf,qBAAa,CAAC,SAAS,KAAK,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;MACxD,GAAG,oBAAoB;IACzB;IACA,CAAA;EAAC;AAGH,QAAM,QAAc,kBAAY,MAAM;AACpC,iBAAa,CAAA,CAAE;EACjB,GAAG,CAAA,CAAE;AAEL,SAAO,EAAE,WAAW,MAAM,MAAA;AAC5B;",
6
+ "names": ["FloatingReactions"]
7
+ }