@alfakit/react 0.0.0-stage → 0.1.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 (1014) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +19 -2
  3. package/dist/app/components.d.ts +49 -0
  4. package/dist/app/components.d.ts.map +1 -0
  5. package/dist/app/components.js +99 -0
  6. package/dist/app/components.js.map +1 -0
  7. package/dist/app/hooks.d.ts +79 -0
  8. package/dist/app/hooks.d.ts.map +1 -0
  9. package/dist/app/hooks.js +239 -0
  10. package/dist/app/hooks.js.map +1 -0
  11. package/dist/components/actions/action-button.d.ts +17 -0
  12. package/dist/components/actions/action-button.d.ts.map +1 -0
  13. package/dist/components/actions/action-button.js +150 -0
  14. package/dist/components/actions/action-button.js.map +1 -0
  15. package/dist/components/actions/action-context.d.ts +11 -0
  16. package/dist/components/actions/action-context.d.ts.map +1 -0
  17. package/dist/components/actions/action-context.js +24 -0
  18. package/dist/components/actions/action-context.js.map +1 -0
  19. package/dist/components/actions/action-list.d.ts +20 -0
  20. package/dist/components/actions/action-list.d.ts.map +1 -0
  21. package/dist/components/actions/action-list.js +181 -0
  22. package/dist/components/actions/action-list.js.map +1 -0
  23. package/dist/components/actions/action-modals.d.ts +5 -0
  24. package/dist/components/actions/action-modals.d.ts.map +1 -0
  25. package/dist/components/actions/action-modals.js +362 -0
  26. package/dist/components/actions/action-modals.js.map +1 -0
  27. package/dist/components/actions/index.d.ts +5 -0
  28. package/dist/components/ai/copilot-panel.js +439 -0
  29. package/dist/components/ai/copilot-panel.js.map +1 -0
  30. package/dist/components/ai/copilot.d.ts +12 -0
  31. package/dist/components/ai/copilot.d.ts.map +1 -0
  32. package/dist/components/ai/copilot.js +66 -0
  33. package/dist/components/ai/copilot.js.map +1 -0
  34. package/dist/components/ai/index.d.ts +3 -0
  35. package/dist/components/ai/index.js +3 -0
  36. package/dist/components/ai/stream-lazy.d.ts +9 -0
  37. package/dist/components/ai/stream-lazy.d.ts.map +1 -0
  38. package/dist/components/ai/stream-lazy.js +13 -0
  39. package/dist/components/ai/stream-lazy.js.map +1 -0
  40. package/dist/components/ai/stream.js +85 -0
  41. package/dist/components/ai/stream.js.map +1 -0
  42. package/dist/components/analytics/alfakit-analytics.d.ts +14 -0
  43. package/dist/components/analytics/alfakit-analytics.d.ts.map +1 -0
  44. package/dist/components/analytics/alfakit-analytics.js +36 -0
  45. package/dist/components/analytics/alfakit-analytics.js.map +1 -0
  46. package/dist/components/analytics/breakdown.d.ts +11 -0
  47. package/dist/components/analytics/breakdown.d.ts.map +1 -0
  48. package/dist/components/analytics/breakdown.js +155 -0
  49. package/dist/components/analytics/breakdown.js.map +1 -0
  50. package/dist/components/analytics/index.d.ts +3 -0
  51. package/dist/components/cms/admin-bar.js +68 -0
  52. package/dist/components/cms/admin-bar.js.map +1 -0
  53. package/dist/components/cms/blocks.d.ts +41 -0
  54. package/dist/components/cms/blocks.d.ts.map +1 -0
  55. package/dist/components/cms/blocks.js +400 -0
  56. package/dist/components/cms/blocks.js.map +1 -0
  57. package/dist/components/cms/context.d.ts +24 -0
  58. package/dist/components/cms/context.d.ts.map +1 -0
  59. package/dist/components/cms/context.js +43 -0
  60. package/dist/components/cms/context.js.map +1 -0
  61. package/dist/components/cms/head.d.ts +11 -0
  62. package/dist/components/cms/head.d.ts.map +1 -0
  63. package/dist/components/cms/head.js +40 -0
  64. package/dist/components/cms/head.js.map +1 -0
  65. package/dist/components/cms/index.d.ts +8 -0
  66. package/dist/components/cms/index.js +8 -0
  67. package/dist/components/cms/live-preview-lazy.d.ts +9 -0
  68. package/dist/components/cms/live-preview-lazy.d.ts.map +1 -0
  69. package/dist/components/cms/live-preview-lazy.js +13 -0
  70. package/dist/components/cms/live-preview-lazy.js.map +1 -0
  71. package/dist/components/cms/live-preview.js +288 -0
  72. package/dist/components/cms/live-preview.js.map +1 -0
  73. package/dist/components/cms/menu.d.ts +24 -0
  74. package/dist/components/cms/menu.d.ts.map +1 -0
  75. package/dist/components/cms/menu.js +181 -0
  76. package/dist/components/cms/menu.js.map +1 -0
  77. package/dist/components/cms/page.d.ts +27 -0
  78. package/dist/components/cms/page.d.ts.map +1 -0
  79. package/dist/components/cms/page.js +180 -0
  80. package/dist/components/cms/page.js.map +1 -0
  81. package/dist/components/cms/parts.d.ts +24 -0
  82. package/dist/components/cms/parts.d.ts.map +1 -0
  83. package/dist/components/cms/parts.js +58 -0
  84. package/dist/components/cms/parts.js.map +1 -0
  85. package/dist/components/dev-tools/dev-tools-inspector.js +302 -0
  86. package/dist/components/dev-tools/dev-tools-inspector.js.map +1 -0
  87. package/dist/components/dev-tools/dev-tools-slot.js +24 -0
  88. package/dist/components/dev-tools/dev-tools-slot.js.map +1 -0
  89. package/dist/components/entries/code-entry.d.ts +8 -0
  90. package/dist/components/entries/code-entry.d.ts.map +1 -0
  91. package/dist/components/entries/code-entry.js +59 -0
  92. package/dist/components/entries/code-entry.js.map +1 -0
  93. package/dist/components/entries/entry-wrapper.d.ts +13 -0
  94. package/dist/components/entries/entry-wrapper.d.ts.map +1 -0
  95. package/dist/components/entries/entry-wrapper.js +175 -0
  96. package/dist/components/entries/entry-wrapper.js.map +1 -0
  97. package/dist/components/entries/index.d.ts +6 -0
  98. package/dist/components/entries/other-entries.d.ts +20 -0
  99. package/dist/components/entries/other-entries.d.ts.map +1 -0
  100. package/dist/components/entries/other-entries.js +169 -0
  101. package/dist/components/entries/other-entries.js.map +1 -0
  102. package/dist/components/entries/repeatable-entry-table.js +33 -0
  103. package/dist/components/entries/repeatable-entry-table.js.map +1 -0
  104. package/dist/components/entries/text-entry.d.ts +12 -0
  105. package/dist/components/entries/text-entry.d.ts.map +1 -0
  106. package/dist/components/entries/text-entry.js +181 -0
  107. package/dist/components/entries/text-entry.js.map +1 -0
  108. package/dist/components/entries/view-entry.d.ts +8 -0
  109. package/dist/components/entries/view-entry.d.ts.map +1 -0
  110. package/dist/components/entries/view-entry.js +26 -0
  111. package/dist/components/entries/view-entry.js.map +1 -0
  112. package/dist/components/fields/builder-picker.js +215 -0
  113. package/dist/components/fields/builder-picker.js.map +1 -0
  114. package/dist/components/fields/builder.d.ts +16 -0
  115. package/dist/components/fields/builder.d.ts.map +1 -0
  116. package/dist/components/fields/builder.js +351 -0
  117. package/dist/components/fields/builder.js.map +1 -0
  118. package/dist/components/fields/checkbox-list.d.ts +7 -0
  119. package/dist/components/fields/checkbox-list.d.ts.map +1 -0
  120. package/dist/components/fields/checkbox-list.js +133 -0
  121. package/dist/components/fields/checkbox-list.js.map +1 -0
  122. package/dist/components/fields/checkbox.d.ts +7 -0
  123. package/dist/components/fields/checkbox.d.ts.map +1 -0
  124. package/dist/components/fields/checkbox.js +29 -0
  125. package/dist/components/fields/checkbox.js.map +1 -0
  126. package/dist/components/fields/code-editor-lazy.d.ts +9 -0
  127. package/dist/components/fields/code-editor-lazy.d.ts.map +1 -0
  128. package/dist/components/fields/code-editor-lazy.js +31 -0
  129. package/dist/components/fields/code-editor-lazy.js.map +1 -0
  130. package/dist/components/fields/code-editor.js +107 -0
  131. package/dist/components/fields/code-editor.js.map +1 -0
  132. package/dist/components/fields/code-tokens.js +18 -0
  133. package/dist/components/fields/code-tokens.js.map +1 -0
  134. package/dist/components/fields/color-picker.d.ts +7 -0
  135. package/dist/components/fields/color-picker.d.ts.map +1 -0
  136. package/dist/components/fields/color-picker.js +70 -0
  137. package/dist/components/fields/color-picker.js.map +1 -0
  138. package/dist/components/fields/date-picker.d.ts +7 -0
  139. package/dist/components/fields/date-picker.d.ts.map +1 -0
  140. package/dist/components/fields/date-picker.js +230 -0
  141. package/dist/components/fields/date-picker.js.map +1 -0
  142. package/dist/components/fields/file-upload.d.ts +7 -0
  143. package/dist/components/fields/file-upload.d.ts.map +1 -0
  144. package/dist/components/fields/file-upload.js +397 -0
  145. package/dist/components/fields/file-upload.js.map +1 -0
  146. package/dist/components/fields/hidden.d.ts +7 -0
  147. package/dist/components/fields/hidden.d.ts.map +1 -0
  148. package/dist/components/fields/hidden.js +17 -0
  149. package/dist/components/fields/hidden.js.map +1 -0
  150. package/dist/components/fields/icon-picker-lazy.d.ts +9 -0
  151. package/dist/components/fields/icon-picker-lazy.d.ts.map +1 -0
  152. package/dist/components/fields/icon-picker-lazy.js +45 -0
  153. package/dist/components/fields/icon-picker-lazy.js.map +1 -0
  154. package/dist/components/fields/icon-picker-value.js +48 -0
  155. package/dist/components/fields/icon-picker-value.js.map +1 -0
  156. package/dist/components/fields/icon-picker.js +228 -0
  157. package/dist/components/fields/icon-picker.js.map +1 -0
  158. package/dist/components/fields/image-editor-dialog-lazy.js +18 -0
  159. package/dist/components/fields/image-editor-dialog-lazy.js.map +1 -0
  160. package/dist/components/fields/image-editor-dialog.js +328 -0
  161. package/dist/components/fields/image-editor-dialog.js.map +1 -0
  162. package/dist/components/fields/index.d.ts +24 -0
  163. package/dist/components/fields/item-actions.js +270 -0
  164. package/dist/components/fields/item-actions.js.map +1 -0
  165. package/dist/components/fields/key-value.d.ts +11 -0
  166. package/dist/components/fields/key-value.d.ts.map +1 -0
  167. package/dist/components/fields/key-value.js +194 -0
  168. package/dist/components/fields/key-value.js.map +1 -0
  169. package/dist/components/fields/lazy-fields.d.ts +13 -0
  170. package/dist/components/fields/lazy-fields.d.ts.map +1 -0
  171. package/dist/components/fields/lazy-fields.js +34 -0
  172. package/dist/components/fields/lazy-fields.js.map +1 -0
  173. package/dist/components/fields/modal-table-select.js +386 -0
  174. package/dist/components/fields/modal-table-select.js.map +1 -0
  175. package/dist/components/fields/money-input.d.ts +7 -0
  176. package/dist/components/fields/money-input.d.ts.map +1 -0
  177. package/dist/components/fields/money-input.js +91 -0
  178. package/dist/components/fields/money-input.js.map +1 -0
  179. package/dist/components/fields/one-time-code-input.d.ts +12 -0
  180. package/dist/components/fields/one-time-code-input.d.ts.map +1 -0
  181. package/dist/components/fields/one-time-code-input.js +53 -0
  182. package/dist/components/fields/one-time-code-input.js.map +1 -0
  183. package/dist/components/fields/query-builder-rules.js +295 -0
  184. package/dist/components/fields/query-builder-rules.js.map +1 -0
  185. package/dist/components/fields/radio.d.ts +7 -0
  186. package/dist/components/fields/radio.d.ts.map +1 -0
  187. package/dist/components/fields/radio.js +57 -0
  188. package/dist/components/fields/radio.js.map +1 -0
  189. package/dist/components/fields/repeater-table.js +111 -0
  190. package/dist/components/fields/repeater-table.js.map +1 -0
  191. package/dist/components/fields/repeater.d.ts +15 -0
  192. package/dist/components/fields/repeater.d.ts.map +1 -0
  193. package/dist/components/fields/repeater.js +209 -0
  194. package/dist/components/fields/repeater.js.map +1 -0
  195. package/dist/components/fields/rich-editor-extensions.js +295 -0
  196. package/dist/components/fields/rich-editor-extensions.js.map +1 -0
  197. package/dist/components/fields/rich-editor-lazy.d.ts +9 -0
  198. package/dist/components/fields/rich-editor-lazy.d.ts.map +1 -0
  199. package/dist/components/fields/rich-editor-lazy.js +24 -0
  200. package/dist/components/fields/rich-editor-lazy.js.map +1 -0
  201. package/dist/components/fields/rich-editor.js +517 -0
  202. package/dist/components/fields/rich-editor.js.map +1 -0
  203. package/dist/components/fields/select.d.ts +7 -0
  204. package/dist/components/fields/select.d.ts.map +1 -0
  205. package/dist/components/fields/select.js +455 -0
  206. package/dist/components/fields/select.js.map +1 -0
  207. package/dist/components/fields/slider.d.ts +12 -0
  208. package/dist/components/fields/slider.d.ts.map +1 -0
  209. package/dist/components/fields/slider.js +146 -0
  210. package/dist/components/fields/slider.js.map +1 -0
  211. package/dist/components/fields/tags-input.d.ts +7 -0
  212. package/dist/components/fields/tags-input.d.ts.map +1 -0
  213. package/dist/components/fields/tags-input.js +129 -0
  214. package/dist/components/fields/tags-input.js.map +1 -0
  215. package/dist/components/fields/text-input.d.ts +7 -0
  216. package/dist/components/fields/text-input.d.ts.map +1 -0
  217. package/dist/components/fields/text-input.js +135 -0
  218. package/dist/components/fields/text-input.js.map +1 -0
  219. package/dist/components/fields/textarea.d.ts +7 -0
  220. package/dist/components/fields/textarea.d.ts.map +1 -0
  221. package/dist/components/fields/textarea.js +37 -0
  222. package/dist/components/fields/textarea.js.map +1 -0
  223. package/dist/components/fields/toggle-buttons.d.ts +7 -0
  224. package/dist/components/fields/toggle-buttons.d.ts.map +1 -0
  225. package/dist/components/fields/toggle-buttons.js +84 -0
  226. package/dist/components/fields/toggle-buttons.js.map +1 -0
  227. package/dist/components/fields/toggle.d.ts +7 -0
  228. package/dist/components/fields/toggle.d.ts.map +1 -0
  229. package/dist/components/fields/toggle.js +39 -0
  230. package/dist/components/fields/toggle.js.map +1 -0
  231. package/dist/components/file-manager/file-manager-lazy.d.ts +10 -0
  232. package/dist/components/file-manager/file-manager-lazy.d.ts.map +1 -0
  233. package/dist/components/file-manager/file-manager-lazy.js +49 -0
  234. package/dist/components/file-manager/file-manager-lazy.js.map +1 -0
  235. package/dist/components/file-manager/file-manager.d.ts +1 -0
  236. package/dist/components/file-manager/file-manager.js +1387 -0
  237. package/dist/components/file-manager/file-manager.js.map +1 -0
  238. package/dist/components/file-manager/index.d.ts +3 -0
  239. package/dist/components/file-manager/picker-field.d.ts +12 -0
  240. package/dist/components/file-manager/picker-field.d.ts.map +1 -0
  241. package/dist/components/file-manager/picker-field.js +124 -0
  242. package/dist/components/file-manager/picker-field.js.map +1 -0
  243. package/dist/components/form/form-block.d.ts +12 -0
  244. package/dist/components/form/form-block.d.ts.map +1 -0
  245. package/dist/components/form/form-block.js +155 -0
  246. package/dist/components/form/form-block.js.map +1 -0
  247. package/dist/components/form/form-context.d.ts +27 -0
  248. package/dist/components/form/form-context.d.ts.map +1 -0
  249. package/dist/components/form/form-context.js +65 -0
  250. package/dist/components/form/form-context.js.map +1 -0
  251. package/dist/components/helpdesk/chat-panel.js +209 -0
  252. package/dist/components/helpdesk/chat-panel.js.map +1 -0
  253. package/dist/components/helpdesk/chat-widget.d.ts +23 -0
  254. package/dist/components/helpdesk/chat-widget.d.ts.map +1 -0
  255. package/dist/components/helpdesk/chat-widget.js +76 -0
  256. package/dist/components/helpdesk/chat-widget.js.map +1 -0
  257. package/dist/components/helpdesk/inbox-lazy.d.ts +9 -0
  258. package/dist/components/helpdesk/inbox-lazy.d.ts.map +1 -0
  259. package/dist/components/helpdesk/inbox-lazy.js +46 -0
  260. package/dist/components/helpdesk/inbox-lazy.js.map +1 -0
  261. package/dist/components/helpdesk/inbox.js +609 -0
  262. package/dist/components/helpdesk/inbox.js.map +1 -0
  263. package/dist/components/helpdesk/index.d.ts +3 -0
  264. package/dist/components/kanban/index.d.ts +2 -0
  265. package/dist/components/kanban/index.js +2 -0
  266. package/dist/components/kanban/kanban-lazy.d.ts +10 -0
  267. package/dist/components/kanban/kanban-lazy.d.ts.map +1 -0
  268. package/dist/components/kanban/kanban-lazy.js +31 -0
  269. package/dist/components/kanban/kanban-lazy.js.map +1 -0
  270. package/dist/components/kanban/kanban.js +591 -0
  271. package/dist/components/kanban/kanban.js.map +1 -0
  272. package/dist/components/layout/actions.js +40 -0
  273. package/dist/components/layout/actions.js.map +1 -0
  274. package/dist/components/layout/component-context.d.ts +13 -0
  275. package/dist/components/layout/component-context.d.ts.map +1 -0
  276. package/dist/components/layout/component-context.js +28 -0
  277. package/dist/components/layout/component-context.js.map +1 -0
  278. package/dist/components/layout/deferred.js +135 -0
  279. package/dist/components/layout/deferred.js.map +1 -0
  280. package/dist/components/layout/elements.d.ts +23 -0
  281. package/dist/components/layout/elements.d.ts.map +1 -0
  282. package/dist/components/layout/elements.js +183 -0
  283. package/dist/components/layout/elements.js.map +1 -0
  284. package/dist/components/layout/fieldset.d.ts +7 -0
  285. package/dist/components/layout/fieldset.d.ts.map +1 -0
  286. package/dist/components/layout/fieldset.js +29 -0
  287. package/dist/components/layout/fieldset.js.map +1 -0
  288. package/dist/components/layout/fused-group.d.ts +11 -0
  289. package/dist/components/layout/fused-group.d.ts.map +1 -0
  290. package/dist/components/layout/fused-group.js +64 -0
  291. package/dist/components/layout/fused-group.js.map +1 -0
  292. package/dist/components/layout/grid.d.ts +7 -0
  293. package/dist/components/layout/grid.d.ts.map +1 -0
  294. package/dist/components/layout/grid.js +20 -0
  295. package/dist/components/layout/grid.js.map +1 -0
  296. package/dist/components/layout/index.d.ts +12 -0
  297. package/dist/components/layout/mixed-content.d.ts +14 -0
  298. package/dist/components/layout/mixed-content.d.ts.map +1 -0
  299. package/dist/components/layout/mixed-content.js +44 -0
  300. package/dist/components/layout/mixed-content.js.map +1 -0
  301. package/dist/components/layout/prime.d.ts +17 -0
  302. package/dist/components/layout/prime.d.ts.map +1 -0
  303. package/dist/components/layout/prime.js +126 -0
  304. package/dist/components/layout/prime.js.map +1 -0
  305. package/dist/components/layout/schema-children.d.ts +18 -0
  306. package/dist/components/layout/schema-children.d.ts.map +1 -0
  307. package/dist/components/layout/schema-children.js +26 -0
  308. package/dist/components/layout/schema-children.js.map +1 -0
  309. package/dist/components/layout/section.d.ts +7 -0
  310. package/dist/components/layout/section.d.ts.map +1 -0
  311. package/dist/components/layout/section.js +133 -0
  312. package/dist/components/layout/section.js.map +1 -0
  313. package/dist/components/layout/tabs.d.ts +7 -0
  314. package/dist/components/layout/tabs.d.ts.map +1 -0
  315. package/dist/components/layout/tabs.js +83 -0
  316. package/dist/components/layout/tabs.js.map +1 -0
  317. package/dist/components/layout/wizard.d.ts +7 -0
  318. package/dist/components/layout/wizard.d.ts.map +1 -0
  319. package/dist/components/layout/wizard.js +172 -0
  320. package/dist/components/layout/wizard.js.map +1 -0
  321. package/dist/components/notifications/flash-toasts.d.ts +20 -0
  322. package/dist/components/notifications/flash-toasts.d.ts.map +1 -0
  323. package/dist/components/notifications/flash-toasts.js +121 -0
  324. package/dist/components/notifications/flash-toasts.js.map +1 -0
  325. package/dist/components/notifications/index.d.ts +5 -0
  326. package/dist/components/notifications/notifications-menu.d.ts +15 -0
  327. package/dist/components/notifications/notifications-menu.d.ts.map +1 -0
  328. package/dist/components/notifications/notifications-menu.js +201 -0
  329. package/dist/components/notifications/notifications-menu.js.map +1 -0
  330. package/dist/components/notifications/realtime-notifications.d.ts +9 -0
  331. package/dist/components/notifications/realtime-notifications.d.ts.map +1 -0
  332. package/dist/components/notifications/realtime-notifications.js +30 -0
  333. package/dist/components/notifications/realtime-notifications.js.map +1 -0
  334. package/dist/components/notifications/undo-button.d.ts +13 -0
  335. package/dist/components/notifications/undo-button.d.ts.map +1 -0
  336. package/dist/components/notifications/undo-button.js +54 -0
  337. package/dist/components/notifications/undo-button.js.map +1 -0
  338. package/dist/components/panel/app-sidebar.d.ts +7 -0
  339. package/dist/components/panel/app-sidebar.d.ts.map +1 -0
  340. package/dist/components/panel/app-sidebar.js +302 -0
  341. package/dist/components/panel/app-sidebar.js.map +1 -0
  342. package/dist/components/panel/bottom-nav.d.ts +9 -0
  343. package/dist/components/panel/bottom-nav.d.ts.map +1 -0
  344. package/dist/components/panel/bottom-nav.js +100 -0
  345. package/dist/components/panel/bottom-nav.js.map +1 -0
  346. package/dist/components/panel/debug-panel-slot.d.ts +8 -0
  347. package/dist/components/panel/debug-panel-slot.d.ts.map +1 -0
  348. package/dist/components/panel/debug-panel-slot.js +19 -0
  349. package/dist/components/panel/debug-panel-slot.js.map +1 -0
  350. package/dist/components/panel/debug-panel.d.ts +18 -0
  351. package/dist/components/panel/debug-panel.d.ts.map +1 -0
  352. package/dist/components/panel/debug-panel.js +423 -0
  353. package/dist/components/panel/debug-panel.js.map +1 -0
  354. package/dist/components/panel/global-search-palette.js +278 -0
  355. package/dist/components/panel/global-search-palette.js.map +1 -0
  356. package/dist/components/panel/global-search.d.ts +13 -0
  357. package/dist/components/panel/global-search.d.ts.map +1 -0
  358. package/dist/components/panel/global-search.js +114 -0
  359. package/dist/components/panel/global-search.js.map +1 -0
  360. package/dist/components/panel/impersonation-banner.d.ts +11 -0
  361. package/dist/components/panel/impersonation-banner.d.ts.map +1 -0
  362. package/dist/components/panel/impersonation-banner.js +52 -0
  363. package/dist/components/panel/impersonation-banner.js.map +1 -0
  364. package/dist/components/panel/index.d.ts +18 -0
  365. package/dist/components/panel/keyboard-shortcuts.d.ts +10 -0
  366. package/dist/components/panel/keyboard-shortcuts.d.ts.map +1 -0
  367. package/dist/components/panel/keyboard-shortcuts.js +107 -0
  368. package/dist/components/panel/keyboard-shortcuts.js.map +1 -0
  369. package/dist/components/panel/lazy-placeholder.d.ts +11 -0
  370. package/dist/components/panel/lazy-placeholder.d.ts.map +1 -0
  371. package/dist/components/panel/lazy-placeholder.js +35 -0
  372. package/dist/components/panel/lazy-placeholder.js.map +1 -0
  373. package/dist/components/panel/locale-switcher.d.ts +19 -0
  374. package/dist/components/panel/locale-switcher.d.ts.map +1 -0
  375. package/dist/components/panel/locale-switcher.js +113 -0
  376. package/dist/components/panel/locale-switcher.js.map +1 -0
  377. package/dist/components/panel/menu-item.js +65 -0
  378. package/dist/components/panel/menu-item.js.map +1 -0
  379. package/dist/components/panel/nav-label.js +29 -0
  380. package/dist/components/panel/nav-label.js.map +1 -0
  381. package/dist/components/panel/pwa.d.ts +13 -0
  382. package/dist/components/panel/pwa.d.ts.map +1 -0
  383. package/dist/components/panel/pwa.js +90 -0
  384. package/dist/components/panel/pwa.js.map +1 -0
  385. package/dist/components/panel/schema-contract-guard.js +44 -0
  386. package/dist/components/panel/schema-contract-guard.js.map +1 -0
  387. package/dist/components/panel/shortcut-keys.d.ts +11 -0
  388. package/dist/components/panel/shortcut-keys.d.ts.map +1 -0
  389. package/dist/components/panel/shortcut-keys.js +33 -0
  390. package/dist/components/panel/shortcut-keys.js.map +1 -0
  391. package/dist/components/panel/shortcuts-overlay.d.ts +13 -0
  392. package/dist/components/panel/shortcuts-overlay.d.ts.map +1 -0
  393. package/dist/components/panel/shortcuts-overlay.js +159 -0
  394. package/dist/components/panel/shortcuts-overlay.js.map +1 -0
  395. package/dist/components/panel/slot.d.ts +18 -0
  396. package/dist/components/panel/slot.d.ts.map +1 -0
  397. package/dist/components/panel/slot.js +31 -0
  398. package/dist/components/panel/slot.js.map +1 -0
  399. package/dist/components/panel/sub-navigation.js +125 -0
  400. package/dist/components/panel/sub-navigation.js.map +1 -0
  401. package/dist/components/panel/tenant-menu.d.ts +10 -0
  402. package/dist/components/panel/tenant-menu.d.ts.map +1 -0
  403. package/dist/components/panel/tenant-menu.js +196 -0
  404. package/dist/components/panel/tenant-menu.js.map +1 -0
  405. package/dist/components/panel/theme-toggle.d.ts +4 -0
  406. package/dist/components/panel/theme-toggle.d.ts.map +1 -0
  407. package/dist/components/panel/theme-toggle.js +84 -0
  408. package/dist/components/panel/theme-toggle.js.map +1 -0
  409. package/dist/components/panel/topbar.d.ts +10 -0
  410. package/dist/components/panel/topbar.d.ts.map +1 -0
  411. package/dist/components/panel/topbar.js +74 -0
  412. package/dist/components/panel/topbar.js.map +1 -0
  413. package/dist/components/panel/user-menu.d.ts +16 -0
  414. package/dist/components/panel/user-menu.d.ts.map +1 -0
  415. package/dist/components/panel/user-menu.js +151 -0
  416. package/dist/components/panel/user-menu.js.map +1 -0
  417. package/dist/components/realtime/conflict-banner.d.ts +13 -0
  418. package/dist/components/realtime/conflict-banner.d.ts.map +1 -0
  419. package/dist/components/realtime/conflict-banner.js +193 -0
  420. package/dist/components/realtime/conflict-banner.js.map +1 -0
  421. package/dist/components/realtime/index.d.ts +4 -0
  422. package/dist/components/realtime/index.js +4 -0
  423. package/dist/components/realtime/live-records-pill.d.ts +19 -0
  424. package/dist/components/realtime/live-records-pill.d.ts.map +1 -0
  425. package/dist/components/realtime/live-records-pill.js +57 -0
  426. package/dist/components/realtime/live-records-pill.js.map +1 -0
  427. package/dist/components/realtime/record-presence.d.ts +12 -0
  428. package/dist/components/realtime/record-presence.d.ts.map +1 -0
  429. package/dist/components/realtime/record-presence.js +79 -0
  430. package/dist/components/realtime/record-presence.js.map +1 -0
  431. package/dist/components/schema/content-slot.js +49 -0
  432. package/dist/components/schema/content-slot.js.map +1 -0
  433. package/dist/components/schema/field-actions.js +72 -0
  434. package/dist/components/schema/field-actions.js.map +1 -0
  435. package/dist/components/schema/field-wrapper.d.ts +20 -0
  436. package/dist/components/schema/field-wrapper.d.ts.map +1 -0
  437. package/dist/components/schema/field-wrapper.js +210 -0
  438. package/dist/components/schema/field-wrapper.js.map +1 -0
  439. package/dist/components/schema/fused-group-context.js +14 -0
  440. package/dist/components/schema/fused-group-context.js.map +1 -0
  441. package/dist/components/schema/schema-renderer.d.ts +23 -0
  442. package/dist/components/schema/schema-renderer.d.ts.map +1 -0
  443. package/dist/components/schema/schema-renderer.js +77 -0
  444. package/dist/components/schema/schema-renderer.js.map +1 -0
  445. package/dist/components/schema/translatable-field.js +76 -0
  446. package/dist/components/schema/translatable-field.js.map +1 -0
  447. package/dist/components/schema/virtual-spacer.js +23 -0
  448. package/dist/components/schema/virtual-spacer.js.map +1 -0
  449. package/dist/components/table/cells.d.ts +12 -0
  450. package/dist/components/table/cells.d.ts.map +1 -0
  451. package/dist/components/table/cells.js +472 -0
  452. package/dist/components/table/cells.js.map +1 -0
  453. package/dist/components/table/column-manager.d.ts +17 -0
  454. package/dist/components/table/column-manager.d.ts.map +1 -0
  455. package/dist/components/table/column-manager.js +338 -0
  456. package/dist/components/table/column-manager.js.map +1 -0
  457. package/dist/components/table/content-layout.d.ts +30 -0
  458. package/dist/components/table/content-layout.d.ts.map +1 -0
  459. package/dist/components/table/content-layout.js +224 -0
  460. package/dist/components/table/content-layout.js.map +1 -0
  461. package/dist/components/table/filters.d.ts +15 -0
  462. package/dist/components/table/filters.d.ts.map +1 -0
  463. package/dist/components/table/filters.js +300 -0
  464. package/dist/components/table/filters.js.map +1 -0
  465. package/dist/components/table/grouping.js +194 -0
  466. package/dist/components/table/grouping.js.map +1 -0
  467. package/dist/components/table/index.d.ts +10 -0
  468. package/dist/components/table/list-tabs.js +105 -0
  469. package/dist/components/table/list-tabs.js.map +1 -0
  470. package/dist/components/table/pagination.d.ts +7 -0
  471. package/dist/components/table/pagination.d.ts.map +1 -0
  472. package/dist/components/table/pagination.js +255 -0
  473. package/dist/components/table/pagination.js.map +1 -0
  474. package/dist/components/table/record-cell.d.ts +25 -0
  475. package/dist/components/table/record-cell.d.ts.map +1 -0
  476. package/dist/components/table/record-cell.js +86 -0
  477. package/dist/components/table/record-cell.js.map +1 -0
  478. package/dist/components/table/spreadsheet-lazy.d.ts +7 -0
  479. package/dist/components/table/spreadsheet-lazy.d.ts.map +1 -0
  480. package/dist/components/table/spreadsheet-lazy.js +15 -0
  481. package/dist/components/table/spreadsheet-lazy.js.map +1 -0
  482. package/dist/components/table/spreadsheet.d.ts +11 -0
  483. package/dist/components/table/spreadsheet.d.ts.map +1 -0
  484. package/dist/components/table/spreadsheet.js +420 -0
  485. package/dist/components/table/spreadsheet.js.map +1 -0
  486. package/dist/components/table/summary-rows.js +104 -0
  487. package/dist/components/table/summary-rows.js.map +1 -0
  488. package/dist/components/table/table-block.d.ts +12 -0
  489. package/dist/components/table/table-block.d.ts.map +1 -0
  490. package/dist/components/table/table-block.js +875 -0
  491. package/dist/components/table/table-block.js.map +1 -0
  492. package/dist/components/table/table-views.d.ts +15 -0
  493. package/dist/components/table/table-views.d.ts.map +1 -0
  494. package/dist/components/table/table-views.js +376 -0
  495. package/dist/components/table/table-views.js.map +1 -0
  496. package/dist/components/table/trigger-button.js +44 -0
  497. package/dist/components/table/trigger-button.js.map +1 -0
  498. package/dist/components/translation-manager/index.d.ts +2 -0
  499. package/dist/components/translation-manager/index.js +2 -0
  500. package/dist/components/translation-manager/translation-manager-lazy.d.ts +9 -0
  501. package/dist/components/translation-manager/translation-manager-lazy.d.ts.map +1 -0
  502. package/dist/components/translation-manager/translation-manager-lazy.js +24 -0
  503. package/dist/components/translation-manager/translation-manager-lazy.js.map +1 -0
  504. package/dist/components/translation-manager/translation-manager.js +719 -0
  505. package/dist/components/translation-manager/translation-manager.js.map +1 -0
  506. package/dist/components/tree/index.d.ts +2 -0
  507. package/dist/components/tree/index.js +2 -0
  508. package/dist/components/tree/tree-lazy.d.ts +10 -0
  509. package/dist/components/tree/tree-lazy.d.ts.map +1 -0
  510. package/dist/components/tree/tree-lazy.js +24 -0
  511. package/dist/components/tree/tree-lazy.js.map +1 -0
  512. package/dist/components/tree/tree.js +291 -0
  513. package/dist/components/tree/tree.js.map +1 -0
  514. package/dist/components/ui/alert.d.ts +12 -0
  515. package/dist/components/ui/alert.d.ts.map +1 -0
  516. package/dist/components/ui/alert.js +37 -0
  517. package/dist/components/ui/alert.js.map +1 -0
  518. package/dist/components/ui/avatar.d.ts +9 -0
  519. package/dist/components/ui/avatar.d.ts.map +1 -0
  520. package/dist/components/ui/avatar.js +29 -0
  521. package/dist/components/ui/avatar.js.map +1 -0
  522. package/dist/components/ui/badge.d.ts +12 -0
  523. package/dist/components/ui/badge.d.ts.map +1 -0
  524. package/dist/components/ui/badge.js +26 -0
  525. package/dist/components/ui/badge.js.map +1 -0
  526. package/dist/components/ui/breadcrumb.d.ts +14 -0
  527. package/dist/components/ui/breadcrumb.d.ts.map +1 -0
  528. package/dist/components/ui/breadcrumb.js +79 -0
  529. package/dist/components/ui/breadcrumb.js.map +1 -0
  530. package/dist/components/ui/button.d.ts +13 -0
  531. package/dist/components/ui/button.d.ts.map +1 -0
  532. package/dist/components/ui/button.js +47 -0
  533. package/dist/components/ui/button.js.map +1 -0
  534. package/dist/components/ui/calendar.d.ts +7 -0
  535. package/dist/components/ui/calendar.d.ts.map +1 -0
  536. package/dist/components/ui/calendar.js +48 -0
  537. package/dist/components/ui/calendar.js.map +1 -0
  538. package/dist/components/ui/card.d.ts +12 -0
  539. package/dist/components/ui/card.d.ts.map +1 -0
  540. package/dist/components/ui/card.js +56 -0
  541. package/dist/components/ui/card.js.map +1 -0
  542. package/dist/components/ui/checkbox.d.ts +7 -0
  543. package/dist/components/ui/checkbox.d.ts.map +1 -0
  544. package/dist/components/ui/checkbox.js +21 -0
  545. package/dist/components/ui/checkbox.js.map +1 -0
  546. package/dist/components/ui/collapsible.d.ts +9 -0
  547. package/dist/components/ui/collapsible.d.ts.map +1 -0
  548. package/dist/components/ui/collapsible.js +25 -0
  549. package/dist/components/ui/collapsible.js.map +1 -0
  550. package/dist/components/ui/command.d.ts +23 -0
  551. package/dist/components/ui/command.d.ts.map +1 -0
  552. package/dist/components/ui/command.js +89 -0
  553. package/dist/components/ui/command.js.map +1 -0
  554. package/dist/components/ui/dialog.d.ts +18 -0
  555. package/dist/components/ui/dialog.d.ts.map +1 -0
  556. package/dist/components/ui/dialog.js +92 -0
  557. package/dist/components/ui/dialog.js.map +1 -0
  558. package/dist/components/ui/dropdown-menu.d.ts +28 -0
  559. package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
  560. package/dist/components/ui/dropdown-menu.js +123 -0
  561. package/dist/components/ui/dropdown-menu.js.map +1 -0
  562. package/dist/components/ui/input.d.ts +6 -0
  563. package/dist/components/ui/input.d.ts.map +1 -0
  564. package/dist/components/ui/input.js +15 -0
  565. package/dist/components/ui/input.js.map +1 -0
  566. package/dist/components/ui/label.d.ts +7 -0
  567. package/dist/components/ui/label.d.ts.map +1 -0
  568. package/dist/components/ui/label.js +15 -0
  569. package/dist/components/ui/label.js.map +1 -0
  570. package/dist/components/ui/popover.d.ts +10 -0
  571. package/dist/components/ui/popover.d.ts.map +1 -0
  572. package/dist/components/ui/popover.js +35 -0
  573. package/dist/components/ui/popover.js.map +1 -0
  574. package/dist/components/ui/radio-group.d.ts +8 -0
  575. package/dist/components/ui/radio-group.d.ts.map +1 -0
  576. package/dist/components/ui/radio-group.js +28 -0
  577. package/dist/components/ui/radio-group.js.map +1 -0
  578. package/dist/components/ui/scroll-area.d.ts +8 -0
  579. package/dist/components/ui/scroll-area.d.ts.map +1 -0
  580. package/dist/components/ui/scroll-area.js +36 -0
  581. package/dist/components/ui/scroll-area.js.map +1 -0
  582. package/dist/components/ui/select.d.ts +18 -0
  583. package/dist/components/ui/select.d.ts.map +1 -0
  584. package/dist/components/ui/select.js +96 -0
  585. package/dist/components/ui/select.js.map +1 -0
  586. package/dist/components/ui/separator.d.ts +7 -0
  587. package/dist/components/ui/separator.d.ts.map +1 -0
  588. package/dist/components/ui/separator.js +17 -0
  589. package/dist/components/ui/separator.js.map +1 -0
  590. package/dist/components/ui/sheet.d.ts +17 -0
  591. package/dist/components/ui/sheet.d.ts.map +1 -0
  592. package/dist/components/ui/sheet.js +89 -0
  593. package/dist/components/ui/sheet.js.map +1 -0
  594. package/dist/components/ui/sidebar.d.ts +72 -0
  595. package/dist/components/ui/sidebar.d.ts.map +1 -0
  596. package/dist/components/ui/sidebar.js +376 -0
  597. package/dist/components/ui/sidebar.js.map +1 -0
  598. package/dist/components/ui/skeleton.d.ts +6 -0
  599. package/dist/components/ui/skeleton.d.ts.map +1 -0
  600. package/dist/components/ui/skeleton.js +14 -0
  601. package/dist/components/ui/skeleton.js.map +1 -0
  602. package/dist/components/ui/sonner.d.ts +6 -0
  603. package/dist/components/ui/sonner.d.ts.map +1 -0
  604. package/dist/components/ui/sonner.js +34 -0
  605. package/dist/components/ui/sonner.js.map +1 -0
  606. package/dist/components/ui/spinner.d.ts +10 -0
  607. package/dist/components/ui/spinner.d.ts.map +1 -0
  608. package/dist/components/ui/spinner.js +30 -0
  609. package/dist/components/ui/spinner.js.map +1 -0
  610. package/dist/components/ui/switch.d.ts +7 -0
  611. package/dist/components/ui/switch.d.ts.map +1 -0
  612. package/dist/components/ui/switch.js +19 -0
  613. package/dist/components/ui/switch.js.map +1 -0
  614. package/dist/components/ui/table.d.ts +13 -0
  615. package/dist/components/ui/table.d.ts.map +1 -0
  616. package/dist/components/ui/table.js +67 -0
  617. package/dist/components/ui/table.js.map +1 -0
  618. package/dist/components/ui/tabs.d.ts +10 -0
  619. package/dist/components/ui/tabs.d.ts.map +1 -0
  620. package/dist/components/ui/tabs.js +36 -0
  621. package/dist/components/ui/tabs.js.map +1 -0
  622. package/dist/components/ui/textarea.d.ts +6 -0
  623. package/dist/components/ui/textarea.d.ts.map +1 -0
  624. package/dist/components/ui/textarea.js +14 -0
  625. package/dist/components/ui/textarea.js.map +1 -0
  626. package/dist/components/ui/tooltip.d.ts +10 -0
  627. package/dist/components/ui/tooltip.d.ts.map +1 -0
  628. package/dist/components/ui/tooltip.js +36 -0
  629. package/dist/components/ui/tooltip.js.map +1 -0
  630. package/dist/components/versions/record-versions.d.ts +12 -0
  631. package/dist/components/versions/record-versions.d.ts.map +1 -0
  632. package/dist/components/versions/record-versions.js +138 -0
  633. package/dist/components/versions/record-versions.js.map +1 -0
  634. package/dist/components/widgets/calendar-lazy.d.ts +9 -0
  635. package/dist/components/widgets/calendar-lazy.d.ts.map +1 -0
  636. package/dist/components/widgets/calendar-lazy.js +47 -0
  637. package/dist/components/widgets/calendar-lazy.js.map +1 -0
  638. package/dist/components/widgets/calendar.js +122 -0
  639. package/dist/components/widgets/calendar.js.map +1 -0
  640. package/dist/components/widgets/chart-filters.js +101 -0
  641. package/dist/components/widgets/chart-filters.js.map +1 -0
  642. package/dist/components/widgets/chart-lazy.d.ts +9 -0
  643. package/dist/components/widgets/chart-lazy.d.ts.map +1 -0
  644. package/dist/components/widgets/chart-lazy.js +27 -0
  645. package/dist/components/widgets/chart-lazy.js.map +1 -0
  646. package/dist/components/widgets/chart.js +216 -0
  647. package/dist/components/widgets/chart.js.map +1 -0
  648. package/dist/components/widgets/dashboard-customizer.d.ts +15 -0
  649. package/dist/components/widgets/dashboard-customizer.d.ts.map +1 -0
  650. package/dist/components/widgets/dashboard-customizer.js +219 -0
  651. package/dist/components/widgets/dashboard-customizer.js.map +1 -0
  652. package/dist/components/widgets/index.d.ts +6 -0
  653. package/dist/components/widgets/stats-overview.d.ts +7 -0
  654. package/dist/components/widgets/stats-overview.d.ts.map +1 -0
  655. package/dist/components/widgets/stats-overview.js +133 -0
  656. package/dist/components/widgets/stats-overview.js.map +1 -0
  657. package/dist/components/widgets/widgets-block.d.ts +10 -0
  658. package/dist/components/widgets/widgets-block.d.ts.map +1 -0
  659. package/dist/components/widgets/widgets-block.js +72 -0
  660. package/dist/components/widgets/widgets-block.js.map +1 -0
  661. package/dist/context.d.ts +55 -0
  662. package/dist/context.d.ts.map +1 -0
  663. package/dist/context.js +115 -0
  664. package/dist/context.js.map +1 -0
  665. package/dist/create-alfakit.d.ts +25 -0
  666. package/dist/create-alfakit.d.ts.map +1 -0
  667. package/dist/create-alfakit.js +182 -0
  668. package/dist/create-alfakit.js.map +1 -0
  669. package/dist/eject-manifest.json +1128 -0
  670. package/dist/hooks/use-hydrated.d.ts +9 -0
  671. package/dist/hooks/use-hydrated.d.ts.map +1 -0
  672. package/dist/hooks/use-hydrated.js +15 -0
  673. package/dist/hooks/use-hydrated.js.map +1 -0
  674. package/dist/hooks/use-mobile.d.ts +8 -0
  675. package/dist/hooks/use-mobile.d.ts.map +1 -0
  676. package/dist/hooks/use-mobile.js +21 -0
  677. package/dist/hooks/use-mobile.js.map +1 -0
  678. package/dist/hooks/use-store.d.ts +7 -0
  679. package/dist/hooks/use-store.d.ts.map +1 -0
  680. package/dist/hooks/use-store.js +13 -0
  681. package/dist/hooks/use-store.js.map +1 -0
  682. package/dist/hooks/use-virtual-list.d.ts +29 -0
  683. package/dist/hooks/use-virtual-list.d.ts.map +1 -0
  684. package/dist/hooks/use-virtual-list.js +73 -0
  685. package/dist/hooks/use-virtual-list.js.map +1 -0
  686. package/dist/index.d.ts +180 -0
  687. package/dist/index.js +161 -0
  688. package/dist/layouts/app-layout.d.ts +11 -0
  689. package/dist/layouts/app-layout.d.ts.map +1 -0
  690. package/dist/layouts/app-layout.js +104 -0
  691. package/dist/layouts/app-layout.js.map +1 -0
  692. package/dist/layouts/auth-layout.d.ts +11 -0
  693. package/dist/layouts/auth-layout.d.ts.map +1 -0
  694. package/dist/layouts/auth-layout.js +65 -0
  695. package/dist/layouts/auth-layout.js.map +1 -0
  696. package/dist/layouts/blank-layout.d.ts +7 -0
  697. package/dist/layouts/blank-layout.d.ts.map +1 -0
  698. package/dist/layouts/blank-layout.js +12 -0
  699. package/dist/layouts/blank-layout.js.map +1 -0
  700. package/dist/layouts/fullscreen-layout.d.ts +16 -0
  701. package/dist/layouts/fullscreen-layout.d.ts.map +1 -0
  702. package/dist/layouts/fullscreen-layout.js +107 -0
  703. package/dist/layouts/fullscreen-layout.js.map +1 -0
  704. package/dist/layouts/top-nav-layout.d.ts +15 -0
  705. package/dist/layouts/top-nav-layout.d.ts.map +1 -0
  706. package/dist/layouts/top-nav-layout.js +223 -0
  707. package/dist/layouts/top-nav-layout.js.map +1 -0
  708. package/dist/lib/attributes.d.ts +12 -0
  709. package/dist/lib/attributes.d.ts.map +1 -0
  710. package/dist/lib/attributes.js +55 -0
  711. package/dist/lib/attributes.js.map +1 -0
  712. package/dist/lib/colors.d.ts +23 -0
  713. package/dist/lib/colors.d.ts.map +1 -0
  714. package/dist/lib/colors.js +126 -0
  715. package/dist/lib/colors.js.map +1 -0
  716. package/dist/lib/discovery.d.ts +22 -0
  717. package/dist/lib/discovery.d.ts.map +1 -0
  718. package/dist/lib/discovery.js +45 -0
  719. package/dist/lib/discovery.js.map +1 -0
  720. package/dist/lib/icons.d.ts +24 -0
  721. package/dist/lib/icons.d.ts.map +1 -0
  722. package/dist/lib/icons.js +93 -0
  723. package/dist/lib/icons.js.map +1 -0
  724. package/dist/lib/lazy.d.ts +8 -0
  725. package/dist/lib/lazy.d.ts.map +1 -0
  726. package/dist/lib/lazy.js +47 -0
  727. package/dist/lib/lazy.js.map +1 -0
  728. package/dist/lib/new-tab-hint.js +15 -0
  729. package/dist/lib/new-tab-hint.js.map +1 -0
  730. package/dist/lib/utils.d.ts +5 -0
  731. package/dist/lib/utils.d.ts.map +1 -0
  732. package/dist/lib/utils.js +10 -0
  733. package/dist/lib/utils.js.map +1 -0
  734. package/dist/pages/page.d.ts +23 -0
  735. package/dist/pages/page.d.ts.map +1 -0
  736. package/dist/pages/page.js +328 -0
  737. package/dist/pages/page.js.map +1 -0
  738. package/dist/registry.d.ts +7 -0
  739. package/dist/registry.d.ts.map +1 -0
  740. package/dist/registry.js +113 -0
  741. package/dist/registry.js.map +1 -0
  742. package/dist/server.d.ts +28 -0
  743. package/dist/server.d.ts.map +1 -0
  744. package/dist/server.js +38 -0
  745. package/dist/server.js.map +1 -0
  746. package/dist/standalone/alfakit-component.d.ts +30 -0
  747. package/dist/standalone/alfakit-component.d.ts.map +1 -0
  748. package/dist/standalone/alfakit-component.js +156 -0
  749. package/dist/standalone/alfakit-component.js.map +1 -0
  750. package/dist/standalone/alfakit-toaster.d.ts +26 -0
  751. package/dist/standalone/alfakit-toaster.d.ts.map +1 -0
  752. package/dist/standalone/alfakit-toaster.js +38 -0
  753. package/dist/standalone/alfakit-toaster.js.map +1 -0
  754. package/dist/standalone/configure.d.ts +14 -0
  755. package/dist/standalone/configure.d.ts.map +1 -0
  756. package/dist/standalone/configure.js +27 -0
  757. package/dist/standalone/configure.js.map +1 -0
  758. package/dist/standalone/index.d.ts +4 -0
  759. package/dist/standalone/runtime.js +94 -0
  760. package/dist/standalone/runtime.js.map +1 -0
  761. package/dist/standalone.css +150 -0
  762. package/dist/starter-kit/index.d.ts +4 -0
  763. package/dist/starter-kit/index.js +4 -0
  764. package/dist/starter-kit/navigation.d.ts +20 -0
  765. package/dist/starter-kit/navigation.d.ts.map +1 -0
  766. package/dist/starter-kit/navigation.js +25 -0
  767. package/dist/starter-kit/navigation.js.map +1 -0
  768. package/dist/starter-kit/starter-kit.d.ts +56 -0
  769. package/dist/starter-kit/starter-kit.d.ts.map +1 -0
  770. package/dist/starter-kit/starter-kit.js +120 -0
  771. package/dist/starter-kit/starter-kit.js.map +1 -0
  772. package/dist/starter-kit/widgets-loaded.js +172 -0
  773. package/dist/starter-kit/widgets-loaded.js.map +1 -0
  774. package/dist/starter-kit/widgets-triggers.js +90 -0
  775. package/dist/starter-kit/widgets-triggers.js.map +1 -0
  776. package/dist/starter-kit/widgets.d.ts +38 -0
  777. package/dist/starter-kit/widgets.d.ts.map +1 -0
  778. package/dist/starter-kit/widgets.js +64 -0
  779. package/dist/starter-kit/widgets.js.map +1 -0
  780. package/dist/styles.css +305 -0
  781. package/dist/themes.css +1340 -0
  782. package/package.json +128 -3
  783. package/src/app/components.tsx +104 -0
  784. package/src/app/hooks.ts +365 -0
  785. package/src/components/actions/action-button.tsx +254 -0
  786. package/src/components/actions/action-context.tsx +23 -0
  787. package/src/components/actions/action-list.tsx +356 -0
  788. package/src/components/actions/action-modals.tsx +479 -0
  789. package/src/components/actions/index.ts +4 -0
  790. package/src/components/ai/copilot-panel.tsx +517 -0
  791. package/src/components/ai/copilot.tsx +82 -0
  792. package/src/components/ai/index.ts +2 -0
  793. package/src/components/ai/stream-lazy.tsx +8 -0
  794. package/src/components/ai/stream.tsx +100 -0
  795. package/src/components/analytics/alfakit-analytics.tsx +27 -0
  796. package/src/components/analytics/breakdown.tsx +150 -0
  797. package/src/components/analytics/index.ts +2 -0
  798. package/src/components/cms/admin-bar.tsx +66 -0
  799. package/src/components/cms/blocks.tsx +474 -0
  800. package/src/components/cms/context.tsx +57 -0
  801. package/src/components/cms/head.tsx +47 -0
  802. package/src/components/cms/index.ts +23 -0
  803. package/src/components/cms/live-preview-lazy.tsx +8 -0
  804. package/src/components/cms/live-preview.tsx +339 -0
  805. package/src/components/cms/menu.tsx +246 -0
  806. package/src/components/cms/page.tsx +219 -0
  807. package/src/components/cms/parts.tsx +84 -0
  808. package/src/components/dev-tools/dev-tools-inspector.tsx +374 -0
  809. package/src/components/dev-tools/dev-tools-slot.tsx +28 -0
  810. package/src/components/entries/code-entry.tsx +73 -0
  811. package/src/components/entries/entry-wrapper.tsx +171 -0
  812. package/src/components/entries/index.ts +11 -0
  813. package/src/components/entries/other-entries.tsx +235 -0
  814. package/src/components/entries/repeatable-entry-table.tsx +50 -0
  815. package/src/components/entries/text-entry.tsx +264 -0
  816. package/src/components/entries/view-entry.tsx +27 -0
  817. package/src/components/fields/builder-picker.tsx +323 -0
  818. package/src/components/fields/builder.tsx +537 -0
  819. package/src/components/fields/checkbox-list.tsx +181 -0
  820. package/src/components/fields/checkbox.tsx +26 -0
  821. package/src/components/fields/code-editor-lazy.tsx +33 -0
  822. package/src/components/fields/code-editor.tsx +157 -0
  823. package/src/components/fields/code-tokens.ts +15 -0
  824. package/src/components/fields/color-picker.tsx +72 -0
  825. package/src/components/fields/date-picker.tsx +336 -0
  826. package/src/components/fields/file-upload.tsx +643 -0
  827. package/src/components/fields/hidden.tsx +8 -0
  828. package/src/components/fields/icon-picker-lazy.tsx +44 -0
  829. package/src/components/fields/icon-picker-value.tsx +46 -0
  830. package/src/components/fields/icon-picker.tsx +293 -0
  831. package/src/components/fields/image-editor-dialog-lazy.tsx +22 -0
  832. package/src/components/fields/image-editor-dialog.tsx +431 -0
  833. package/src/components/fields/index.ts +23 -0
  834. package/src/components/fields/item-actions.tsx +381 -0
  835. package/src/components/fields/key-value.tsx +218 -0
  836. package/src/components/fields/lazy-fields.tsx +30 -0
  837. package/src/components/fields/modal-table-select.tsx +519 -0
  838. package/src/components/fields/money-input.tsx +132 -0
  839. package/src/components/fields/one-time-code-input.tsx +56 -0
  840. package/src/components/fields/query-builder-rules.tsx +444 -0
  841. package/src/components/fields/radio.tsx +67 -0
  842. package/src/components/fields/repeater-table.tsx +185 -0
  843. package/src/components/fields/repeater.tsx +317 -0
  844. package/src/components/fields/rich-editor-extensions.tsx +470 -0
  845. package/src/components/fields/rich-editor-lazy.tsx +21 -0
  846. package/src/components/fields/rich-editor.tsx +815 -0
  847. package/src/components/fields/select.tsx +667 -0
  848. package/src/components/fields/slider.tsx +262 -0
  849. package/src/components/fields/tags-input.tsx +176 -0
  850. package/src/components/fields/text-input.tsx +179 -0
  851. package/src/components/fields/textarea.tsx +37 -0
  852. package/src/components/fields/toggle-buttons.tsx +125 -0
  853. package/src/components/fields/toggle.tsx +50 -0
  854. package/src/components/file-manager/file-manager-lazy.tsx +52 -0
  855. package/src/components/file-manager/file-manager.tsx +1857 -0
  856. package/src/components/file-manager/index.ts +2 -0
  857. package/src/components/file-manager/picker-field.tsx +169 -0
  858. package/src/components/form/form-block.tsx +237 -0
  859. package/src/components/form/form-context.tsx +95 -0
  860. package/src/components/helpdesk/chat-panel.tsx +247 -0
  861. package/src/components/helpdesk/chat-widget.tsx +107 -0
  862. package/src/components/helpdesk/inbox-lazy.tsx +42 -0
  863. package/src/components/helpdesk/inbox.tsx +745 -0
  864. package/src/components/helpdesk/index.ts +2 -0
  865. package/src/components/kanban/index.ts +1 -0
  866. package/src/components/kanban/kanban-lazy.tsx +34 -0
  867. package/src/components/kanban/kanban.tsx +788 -0
  868. package/src/components/layout/actions.tsx +48 -0
  869. package/src/components/layout/component-context.ts +31 -0
  870. package/src/components/layout/deferred.tsx +156 -0
  871. package/src/components/layout/elements.tsx +259 -0
  872. package/src/components/layout/fieldset.tsx +35 -0
  873. package/src/components/layout/fused-group.tsx +96 -0
  874. package/src/components/layout/grid.tsx +19 -0
  875. package/src/components/layout/index.ts +18 -0
  876. package/src/components/layout/mixed-content.tsx +59 -0
  877. package/src/components/layout/prime.tsx +168 -0
  878. package/src/components/layout/schema-children.tsx +30 -0
  879. package/src/components/layout/section.tsx +173 -0
  880. package/src/components/layout/tabs.tsx +129 -0
  881. package/src/components/layout/wizard.tsx +244 -0
  882. package/src/components/notifications/flash-toasts.tsx +200 -0
  883. package/src/components/notifications/index.ts +4 -0
  884. package/src/components/notifications/notifications-menu.tsx +262 -0
  885. package/src/components/notifications/realtime-notifications.tsx +40 -0
  886. package/src/components/notifications/undo-button.tsx +50 -0
  887. package/src/components/panel/app-sidebar.tsx +439 -0
  888. package/src/components/panel/bottom-nav.tsx +130 -0
  889. package/src/components/panel/debug-panel-slot.tsx +25 -0
  890. package/src/components/panel/debug-panel.tsx +412 -0
  891. package/src/components/panel/global-search-palette.tsx +387 -0
  892. package/src/components/panel/global-search.tsx +147 -0
  893. package/src/components/panel/impersonation-banner.tsx +47 -0
  894. package/src/components/panel/index.ts +16 -0
  895. package/src/components/panel/keyboard-shortcuts.tsx +134 -0
  896. package/src/components/panel/lazy-placeholder.tsx +39 -0
  897. package/src/components/panel/locale-switcher.tsx +144 -0
  898. package/src/components/panel/menu-item.tsx +89 -0
  899. package/src/components/panel/nav-label.tsx +34 -0
  900. package/src/components/panel/pwa.tsx +118 -0
  901. package/src/components/panel/schema-contract-guard.tsx +50 -0
  902. package/src/components/panel/shortcut-keys.tsx +34 -0
  903. package/src/components/panel/shortcuts-overlay.tsx +238 -0
  904. package/src/components/panel/slot.tsx +43 -0
  905. package/src/components/panel/sub-navigation.tsx +161 -0
  906. package/src/components/panel/tenant-menu.tsx +175 -0
  907. package/src/components/panel/theme-toggle.tsx +69 -0
  908. package/src/components/panel/topbar.tsx +81 -0
  909. package/src/components/panel/user-menu.tsx +166 -0
  910. package/src/components/realtime/conflict-banner.tsx +208 -0
  911. package/src/components/realtime/index.ts +3 -0
  912. package/src/components/realtime/live-records-pill.tsx +66 -0
  913. package/src/components/realtime/record-presence.tsx +98 -0
  914. package/src/components/schema/content-slot.tsx +81 -0
  915. package/src/components/schema/field-actions.tsx +67 -0
  916. package/src/components/schema/field-wrapper.tsx +246 -0
  917. package/src/components/schema/fused-group-context.ts +11 -0
  918. package/src/components/schema/schema-renderer.tsx +127 -0
  919. package/src/components/schema/translatable-field.tsx +113 -0
  920. package/src/components/schema/virtual-spacer.tsx +14 -0
  921. package/src/components/table/cells.tsx +712 -0
  922. package/src/components/table/column-manager.tsx +480 -0
  923. package/src/components/table/content-layout.tsx +368 -0
  924. package/src/components/table/filters.tsx +378 -0
  925. package/src/components/table/grouping.tsx +275 -0
  926. package/src/components/table/index.ts +9 -0
  927. package/src/components/table/list-tabs.tsx +144 -0
  928. package/src/components/table/pagination.tsx +335 -0
  929. package/src/components/table/record-cell.tsx +147 -0
  930. package/src/components/table/spreadsheet-lazy.tsx +14 -0
  931. package/src/components/table/spreadsheet.tsx +602 -0
  932. package/src/components/table/summary-rows.tsx +160 -0
  933. package/src/components/table/table-block.tsx +1112 -0
  934. package/src/components/table/table-views.tsx +460 -0
  935. package/src/components/table/trigger-button.tsx +92 -0
  936. package/src/components/translation-manager/index.ts +1 -0
  937. package/src/components/translation-manager/translation-manager-lazy.tsx +25 -0
  938. package/src/components/translation-manager/translation-manager.tsx +827 -0
  939. package/src/components/tree/index.ts +1 -0
  940. package/src/components/tree/tree-lazy.tsx +30 -0
  941. package/src/components/tree/tree.tsx +385 -0
  942. package/src/components/ui/alert.tsx +57 -0
  943. package/src/components/ui/avatar.tsx +38 -0
  944. package/src/components/ui/badge.tsx +33 -0
  945. package/src/components/ui/breadcrumb.tsx +110 -0
  946. package/src/components/ui/button.tsx +54 -0
  947. package/src/components/ui/calendar.tsx +65 -0
  948. package/src/components/ui/card.tsx +74 -0
  949. package/src/components/ui/checkbox.tsx +30 -0
  950. package/src/components/ui/collapsible.tsx +20 -0
  951. package/src/components/ui/command.tsx +158 -0
  952. package/src/components/ui/dialog.tsx +124 -0
  953. package/src/components/ui/dropdown-menu.tsx +217 -0
  954. package/src/components/ui/input.tsx +20 -0
  955. package/src/components/ui/label.tsx +18 -0
  956. package/src/components/ui/popover.tsx +39 -0
  957. package/src/components/ui/radio-group.tsx +36 -0
  958. package/src/components/ui/scroll-area.tsx +53 -0
  959. package/src/components/ui/select.tsx +165 -0
  960. package/src/components/ui/separator.tsx +25 -0
  961. package/src/components/ui/sheet.tsx +131 -0
  962. package/src/components/ui/sidebar.tsx +660 -0
  963. package/src/components/ui/skeleton.tsx +14 -0
  964. package/src/components/ui/sonner.tsx +43 -0
  965. package/src/components/ui/spinner.tsx +31 -0
  966. package/src/components/ui/switch.tsx +25 -0
  967. package/src/components/ui/table.tsx +89 -0
  968. package/src/components/ui/tabs.tsx +51 -0
  969. package/src/components/ui/textarea.tsx +17 -0
  970. package/src/components/ui/tooltip.tsx +50 -0
  971. package/src/components/versions/record-versions.tsx +162 -0
  972. package/src/components/widgets/calendar-lazy.tsx +31 -0
  973. package/src/components/widgets/calendar.tsx +124 -0
  974. package/src/components/widgets/chart-filters.tsx +114 -0
  975. package/src/components/widgets/chart-lazy.tsx +21 -0
  976. package/src/components/widgets/chart.tsx +252 -0
  977. package/src/components/widgets/dashboard-customizer.tsx +309 -0
  978. package/src/components/widgets/index.ts +5 -0
  979. package/src/components/widgets/stats-overview.tsx +185 -0
  980. package/src/components/widgets/widgets-block.tsx +102 -0
  981. package/src/context.tsx +187 -0
  982. package/src/create-alfakit.tsx +300 -0
  983. package/src/hooks/use-hydrated.ts +16 -0
  984. package/src/hooks/use-mobile.ts +29 -0
  985. package/src/hooks/use-store.ts +18 -0
  986. package/src/hooks/use-virtual-list.ts +88 -0
  987. package/src/index.ts +135 -0
  988. package/src/layouts/app-layout.tsx +128 -0
  989. package/src/layouts/auth-layout.tsx +66 -0
  990. package/src/layouts/blank-layout.tsx +5 -0
  991. package/src/layouts/fullscreen-layout.tsx +86 -0
  992. package/src/layouts/top-nav-layout.tsx +293 -0
  993. package/src/lib/attributes.ts +79 -0
  994. package/src/lib/colors.ts +151 -0
  995. package/src/lib/discovery.tsx +68 -0
  996. package/src/lib/icons.tsx +145 -0
  997. package/src/lib/lazy.tsx +89 -0
  998. package/src/lib/new-tab-hint.tsx +8 -0
  999. package/src/lib/utils.ts +6 -0
  1000. package/src/pages/page.tsx +463 -0
  1001. package/src/registry.ts +132 -0
  1002. package/src/server.tsx +68 -0
  1003. package/src/standalone/alfakit-component.tsx +222 -0
  1004. package/src/standalone/alfakit-toaster.tsx +52 -0
  1005. package/src/standalone/configure.ts +24 -0
  1006. package/src/standalone/index.ts +3 -0
  1007. package/src/standalone/runtime.ts +153 -0
  1008. package/src/starter-kit/index.ts +14 -0
  1009. package/src/starter-kit/navigation.ts +34 -0
  1010. package/src/starter-kit/starter-kit.tsx +196 -0
  1011. package/src/starter-kit/widgets-loaded.tsx +217 -0
  1012. package/src/starter-kit/widgets-triggers.tsx +103 -0
  1013. package/src/starter-kit/widgets.tsx +103 -0
  1014. package/src/styles.css +305 -0
@@ -0,0 +1,1857 @@
1
+ import type {
2
+ FileManagerEntrySchema,
3
+ FileManagerEnvironment,
4
+ FileManagerKind,
5
+ FileManagerSchema,
6
+ FileManagerSortField,
7
+ } from '@alfakit/core'
8
+ import {
9
+ FileManagerController,
10
+ clientErrorNotification,
11
+ fetchEditableImage,
12
+ fileManagerAcceptAttribute,
13
+ fileManagerExpiry,
14
+ fileManagerSize,
15
+ } from '@alfakit/core'
16
+ import {
17
+ ArrowDownIcon,
18
+ ArrowUpIcon,
19
+ CheckIcon,
20
+ ChevronLeftIcon,
21
+ ChevronRightIcon,
22
+ CopyIcon,
23
+ DownloadIcon,
24
+ FileArchiveIcon,
25
+ FileIcon,
26
+ FileImageIcon,
27
+ FileMusicIcon,
28
+ FilePlayIcon,
29
+ FileSpreadsheetIcon,
30
+ FileTextIcon,
31
+ FolderIcon,
32
+ FolderInputIcon,
33
+ FolderOpenIcon,
34
+ FolderPlusIcon,
35
+ FolderTreeIcon,
36
+ InfoIcon,
37
+ LayoutGridIcon,
38
+ LinkIcon,
39
+ ListIcon,
40
+ PencilIcon,
41
+ PresentationIcon,
42
+ RefreshCwIcon,
43
+ RotateCcwIcon,
44
+ SearchIcon,
45
+ Share2Icon,
46
+ SquarePenIcon,
47
+ Trash2Icon,
48
+ UploadIcon,
49
+ XIcon,
50
+ type LucideIcon,
51
+ } from 'lucide-react'
52
+ import {
53
+ useEffect,
54
+ useId,
55
+ useRef,
56
+ useState,
57
+ type DragEvent,
58
+ type KeyboardEvent,
59
+ type MouseEvent,
60
+ type PointerEvent,
61
+ type ReactNode,
62
+ } from 'react'
63
+ import {
64
+ useActionRunner,
65
+ useChoice,
66
+ useDirection,
67
+ useFormatter,
68
+ usePanel,
69
+ useT,
70
+ } from '../../context'
71
+ import { useIsMobile } from '../../hooks/use-mobile'
72
+ import { useStore } from '../../hooks/use-store'
73
+ import { cn } from '../../lib/utils'
74
+ import { usePageActionContext } from '../actions/action-context'
75
+ import { ImageEditorDialog, type ImageEditRequest } from '../fields/image-editor-dialog'
76
+ import { showToast } from '../notifications/flash-toasts'
77
+ import { Button } from '../ui/button'
78
+ import { Card } from '../ui/card'
79
+ import { Checkbox } from '../ui/checkbox'
80
+ import {
81
+ Dialog,
82
+ DialogContent,
83
+ DialogDescription,
84
+ DialogFooter,
85
+ DialogHeader,
86
+ DialogTitle,
87
+ } from '../ui/dialog'
88
+ import { Input } from '../ui/input'
89
+ import { Sheet, SheetContent, SheetHeader, SheetTitle } from '../ui/sheet'
90
+ import { Spinner } from '../ui/spinner'
91
+
92
+ const SELECT =
93
+ 'border-input focus-visible:border-ring focus-visible:ring-ring/50 h-9 min-w-0 rounded-md border bg-transparent px-2 text-sm shadow-xs outline-none focus-visible:ring-[3px]'
94
+
95
+ const KIND_ICONS: Record<FileManagerKind, LucideIcon> = {
96
+ folder: FolderIcon,
97
+ image: FileImageIcon,
98
+ video: FilePlayIcon,
99
+ audio: FileMusicIcon,
100
+ pdf: FileTextIcon,
101
+ text: FileTextIcon,
102
+ archive: FileArchiveIcon,
103
+ document: FileTextIcon,
104
+ spreadsheet: FileSpreadsheetIcon,
105
+ presentation: PresentationIcon,
106
+ other: FileIcon,
107
+ }
108
+
109
+ const SORT_FIELDS: FileManagerSortField[] = ['name', 'size', 'modified', 'type']
110
+
111
+ const IMAGE_EDITOR = { aspectRatios: [null], circle: false }
112
+
113
+ /**
114
+ * The file manager of alfasix/alfakit-file-manager (schema component `file-manager`): a
115
+ * folder tree, breadcrumbs, grid and list views, sorting, search, multi-selection with
116
+ * mouse and keyboard, drag and drop (uploads and moves), uploads with progress, previews,
117
+ * details, sharing and the trash. Everything is listed, checked and changed by the server.
118
+ */
119
+ export function FileManager({ component }: { component?: FileManagerSchema }) {
120
+ return component ? (
121
+ <FileManagerView key={`${component.disk}:${component.mode}`} schema={component} />
122
+ ) : null
123
+ }
124
+
125
+ export interface FileManagerViewProps {
126
+ schema: FileManagerSchema
127
+ /** Picker: called with the chosen files. */
128
+ onChoose?: (entries: FileManagerEntrySchema[]) => void
129
+ onCancel?: () => void
130
+ }
131
+
132
+ export function FileManagerView({ schema, onChoose, onCancel }: FileManagerViewProps) {
133
+ const runner = useActionRunner()
134
+ const page = usePageActionContext()
135
+ const panel = usePanel()
136
+ const t = useT()
137
+ const choice = useChoice()
138
+ const direction = useDirection()
139
+ const formatter = useFormatter()
140
+ const labelId = useId()
141
+ const hintId = useId()
142
+ const tt = (key: string, replacements?: Record<string, string | number>, fallback?: string) =>
143
+ t(`file-manager.${key}`, replacements, fallback)
144
+
145
+ const environment = (): FileManagerEnvironment => ({
146
+ runner,
147
+ token: page.token,
148
+ uploadEndpoint: panel.endpoints.upload,
149
+ uploadChunkEndpoint: panel.endpoints.uploadChunks,
150
+ uploadChunkSize: panel.uploadChunkSize,
151
+ t,
152
+ choice,
153
+ formatter,
154
+ notify: (notifications) => notifications.forEach((notification) => showToast(notification)),
155
+ error: (message) => showToast(clientErrorNotification('file-manager', message)),
156
+ })
157
+ const [controller] = useState(
158
+ () => new FileManagerController(schema, environment(), formatter.locale),
159
+ )
160
+
161
+ useEffect(() => {
162
+ controller.setEnvironment(environment())
163
+ })
164
+
165
+ const first = useRef(true)
166
+
167
+ useEffect(() => {
168
+ if (first.current) {
169
+ first.current = false
170
+
171
+ return
172
+ }
173
+
174
+ controller.setSchema(schema)
175
+ }, [controller, schema])
176
+
177
+ useEffect(() => {
178
+ if (schema.mode === 'picker') void controller.ensureLoaded()
179
+
180
+ return () => controller.dispose()
181
+ }, [controller, schema.mode])
182
+
183
+ const state = useStore(controller.store)
184
+ const entries = controller.visibleEntries()
185
+ const selected = controller.selectedEntries()
186
+ const picker = schema.mode === 'picker'
187
+ const listRef = useRef<HTMLDivElement | null>(null)
188
+ const items = useRef(new Map<string, HTMLElement>())
189
+ const uploadInput = useRef<HTMLInputElement | null>(null)
190
+ const [foldersOpen, setFoldersOpen] = useState(false)
191
+ const mobile = useIsMobile()
192
+ const focusPath = state.focus ?? entries[0]?.path ?? null
193
+
194
+ const focusItem = (path: string | undefined) => {
195
+ if (path) requestAnimationFrame(() => items.current.get(path)?.focus())
196
+ }
197
+
198
+ const columns = () => {
199
+ const element = listRef.current
200
+
201
+ if (!element || state.view !== 'grid' || typeof getComputedStyle === 'undefined') return 1
202
+
203
+ return Math.max(
204
+ 1,
205
+ getComputedStyle(element).gridTemplateColumns.split(' ').filter(Boolean).length,
206
+ )
207
+ }
208
+
209
+ const onItemKeyDown = (event: KeyboardEvent<HTMLElement>) => {
210
+ const result = controller.handleKey(event, columns(), direction)
211
+
212
+ if (result.handled) {
213
+ event.preventDefault()
214
+ focusItem(result.focus)
215
+ }
216
+ }
217
+
218
+ // Taps add to the selection once something is selected (there is no Ctrl on touch screens).
219
+ const pointer = useRef('mouse')
220
+
221
+ const onItemClick = (entry: FileManagerEntrySchema, event: MouseEvent<HTMLElement>) => {
222
+ controller.click(entry.path, {
223
+ shift: event.shiftKey,
224
+ toggle: event.metaKey || event.ctrlKey,
225
+ touch: pointer.current === 'touch',
226
+ })
227
+ }
228
+
229
+ const onItemDoubleClick = (entry: FileManagerEntrySchema) => {
230
+ if (picker && entry.type === 'file') {
231
+ if (entry.selectable !== false && entry.value) onChoose?.([entry])
232
+
233
+ return
234
+ }
235
+
236
+ controller.openEntry(entry)
237
+ }
238
+
239
+ const copying = (event: DragEvent) => event.ctrlKey || event.altKey || event.metaKey
240
+
241
+ const dropHandlers = (target: string) => ({
242
+ onDragOver: (event: DragEvent<HTMLElement>) => {
243
+ if (controller.dragOver(target, event.dataTransfer, copying(event))) {
244
+ event.preventDefault()
245
+ event.stopPropagation()
246
+ }
247
+ },
248
+ onDragLeave: (event: DragEvent<HTMLElement>) => {
249
+ if (!event.currentTarget.contains(event.relatedTarget as Node | null))
250
+ controller.dragLeave(target)
251
+ },
252
+ onDrop: (event: DragEvent<HTMLElement>) => {
253
+ event.preventDefault()
254
+ event.stopPropagation()
255
+ void controller.drop(target, event.dataTransfer, copying(event))
256
+ },
257
+ })
258
+
259
+ const canWrite = !picker && !state.trash
260
+ const single = selected.length === 1 ? selected[0] : undefined
261
+ const folderLabel = state.trash
262
+ ? tt('trash', {}, 'Trash')
263
+ : (state.breadcrumbs[state.breadcrumbs.length - 1]?.name ?? tt('root', {}, 'Files'))
264
+
265
+ const tree = (
266
+ <FolderTree
267
+ controller={controller}
268
+ onOpen={(path) => {
269
+ setFoldersOpen(false)
270
+ void controller.open(path)
271
+ }}
272
+ active={state.trash ? null : state.path}
273
+ dropHandlers={canWrite ? dropHandlers : undefined}
274
+ dropTarget={state.dropTarget}
275
+ />
276
+ )
277
+
278
+ return (
279
+ <Card
280
+ className="relative gap-0 overflow-hidden py-0"
281
+ data-slot="file-manager"
282
+ data-view={state.view}
283
+ data-mode={schema.mode}
284
+ aria-labelledby={labelId}
285
+ >
286
+ <span id={labelId} className="sr-only">
287
+ {tt('label', {}, 'File manager')}
288
+ </span>
289
+ <div className="sr-only" role="status" aria-live="polite">
290
+ {state.announcement}
291
+ </div>
292
+
293
+ <div
294
+ className="flex flex-wrap items-center gap-2 border-b p-3"
295
+ data-slot="file-manager-toolbar"
296
+ >
297
+ <Button
298
+ type="button"
299
+ variant="outline"
300
+ size="icon-sm"
301
+ className="md:hidden"
302
+ onClick={() => setFoldersOpen(true)}
303
+ aria-label={tt('toggle_folders', {}, 'Show folders')}
304
+ >
305
+ <FolderTreeIcon aria-hidden="true" />
306
+ </Button>
307
+ {schema.disks.length > 1 && (
308
+ <label className="flex items-center">
309
+ <span className="sr-only">{tt('disk', {}, 'Storage')}</span>
310
+ <select
311
+ className={SELECT}
312
+ value={state.disk}
313
+ onChange={(event) => controller.setDisk(event.target.value)}
314
+ >
315
+ {schema.disks.map((disk) => (
316
+ <option key={disk.name} value={disk.name}>
317
+ {disk.label}
318
+ </option>
319
+ ))}
320
+ </select>
321
+ </label>
322
+ )}
323
+ <nav
324
+ aria-label={tt('breadcrumb', {}, 'Folder path')}
325
+ className="min-w-0 flex-1"
326
+ data-slot="file-manager-breadcrumb"
327
+ >
328
+ <ol className="flex min-w-0 flex-wrap items-center gap-1 text-sm">
329
+ <li className="flex items-center">
330
+ <button
331
+ type="button"
332
+ className="hover:bg-accent rounded px-1.5 py-1 font-medium"
333
+ aria-current={!state.trash && state.path === '' ? 'page' : undefined}
334
+ onClick={() =>
335
+ state.trash ? void controller.showTrash(false) : void controller.open('')
336
+ }
337
+ >
338
+ {tt('root', {}, 'Files')}
339
+ </button>
340
+ </li>
341
+ {state.trash ? (
342
+ <Crumb current>{tt('trash', {}, 'Trash')}</Crumb>
343
+ ) : (
344
+ state.breadcrumbs.map((crumb, index) => (
345
+ <Crumb
346
+ key={crumb.path}
347
+ current={index === state.breadcrumbs.length - 1}
348
+ onClick={() => void controller.open(crumb.path)}
349
+ >
350
+ {crumb.name}
351
+ </Crumb>
352
+ ))
353
+ )}
354
+ </ol>
355
+ </nav>
356
+ <div className="flex w-full flex-wrap items-center gap-2 sm:w-auto">
357
+ <div className="relative min-w-40 flex-1 sm:flex-none">
358
+ <SearchIcon
359
+ className="text-muted-foreground pointer-events-none absolute start-2.5 top-1/2 size-4 -translate-y-1/2"
360
+ aria-hidden="true"
361
+ />
362
+ <Input
363
+ type="search"
364
+ value={state.query}
365
+ onChange={(event) => controller.setQuery(event.target.value)}
366
+ placeholder={tt('search_placeholder', {}, 'Search…')}
367
+ aria-label={tt('search', {}, 'Search in this folder')}
368
+ className="h-9 ps-8 sm:w-52"
369
+ disabled={state.trash}
370
+ />
371
+ </div>
372
+ {schema.search.recursive && !state.trash && (
373
+ <label className="flex items-center gap-2 text-sm">
374
+ <Checkbox
375
+ checked={state.recursive}
376
+ onCheckedChange={(checked) => controller.setRecursive(checked === true)}
377
+ />
378
+ {tt('search_everywhere', {}, 'Include subfolders')}
379
+ </label>
380
+ )}
381
+ <label className="flex items-center gap-1">
382
+ <span className="sr-only">{tt('sort', {}, 'Sort by')}</span>
383
+ <select
384
+ className={SELECT}
385
+ value={state.sort.field}
386
+ onChange={(event) =>
387
+ controller.setSort(event.target.value as FileManagerSortField, state.sort.direction)
388
+ }
389
+ >
390
+ {SORT_FIELDS.map((field) => (
391
+ <option key={field} value={field}>
392
+ {tt(`sort_${field}`, {}, field)}
393
+ </option>
394
+ ))}
395
+ </select>
396
+ </label>
397
+ <Button
398
+ type="button"
399
+ variant="outline"
400
+ size="icon-sm"
401
+ onClick={() =>
402
+ controller.setSort(state.sort.field, state.sort.direction === 'asc' ? 'desc' : 'asc')
403
+ }
404
+ aria-label={
405
+ state.sort.direction === 'asc'
406
+ ? tt('ascending', {}, 'Ascending')
407
+ : tt('descending', {}, 'Descending')
408
+ }
409
+ >
410
+ {state.sort.direction === 'asc' ? (
411
+ <ArrowUpIcon aria-hidden="true" />
412
+ ) : (
413
+ <ArrowDownIcon aria-hidden="true" />
414
+ )}
415
+ </Button>
416
+ <div className="flex rounded-md border" role="group" aria-label={tt('view', {}, 'View')}>
417
+ <Button
418
+ type="button"
419
+ variant={state.view === 'grid' ? 'secondary' : 'ghost'}
420
+ size="icon-sm"
421
+ aria-pressed={state.view === 'grid'}
422
+ aria-label={tt('view_grid', {}, 'Grid')}
423
+ onClick={() => controller.setView('grid')}
424
+ >
425
+ <LayoutGridIcon aria-hidden="true" />
426
+ </Button>
427
+ <Button
428
+ type="button"
429
+ variant={state.view === 'list' ? 'secondary' : 'ghost'}
430
+ size="icon-sm"
431
+ aria-pressed={state.view === 'list'}
432
+ aria-label={tt('view_list', {}, 'List')}
433
+ onClick={() => controller.setView('list')}
434
+ >
435
+ <ListIcon aria-hidden="true" />
436
+ </Button>
437
+ </div>
438
+ <Button
439
+ type="button"
440
+ variant="ghost"
441
+ size="icon-sm"
442
+ onClick={() => void controller.refresh()}
443
+ aria-label={tt('refresh', {}, 'Refresh')}
444
+ >
445
+ <RefreshCwIcon aria-hidden="true" className={cn(state.loading && 'animate-spin')} />
446
+ </Button>
447
+ {!state.trash && controller.can('createFolder') && (
448
+ <Button
449
+ type="button"
450
+ variant="outline"
451
+ size="sm"
452
+ onClick={() => controller.openDialog({ type: 'createFolder' })}
453
+ >
454
+ <FolderPlusIcon aria-hidden="true" />
455
+ <span className="max-sm:sr-only">{tt('new_folder', {}, 'New folder')}</span>
456
+ </Button>
457
+ )}
458
+ {!state.trash && controller.can('upload') && schema.upload && (
459
+ <>
460
+ <Button type="button" size="sm" onClick={() => uploadInput.current?.click()}>
461
+ <UploadIcon aria-hidden="true" />
462
+ <span className="max-sm:sr-only">{tt('upload', {}, 'Upload')}</span>
463
+ </Button>
464
+ <input
465
+ ref={uploadInput}
466
+ type="file"
467
+ multiple
468
+ hidden
469
+ accept={fileManagerAcceptAttribute(schema.upload.accept)}
470
+ data-slot="file-manager-upload-input"
471
+ onChange={(event) => {
472
+ if (event.target.files) void controller.upload(Array.from(event.target.files))
473
+ event.target.value = ''
474
+ }}
475
+ />
476
+ </>
477
+ )}
478
+ {schema.trash && !picker && (
479
+ <Button
480
+ type="button"
481
+ variant={state.trash ? 'secondary' : 'ghost'}
482
+ size="sm"
483
+ aria-pressed={state.trash}
484
+ onClick={() => void controller.showTrash(!state.trash)}
485
+ >
486
+ <Trash2Icon aria-hidden="true" />
487
+ <span className="max-sm:sr-only">
488
+ {state.trash ? tt('back_to_files', {}, 'Back to files') : tt('trash', {}, 'Trash')}
489
+ </span>
490
+ </Button>
491
+ )}
492
+ </div>
493
+ </div>
494
+
495
+ {selected.length > 0 && !picker && (
496
+ <div
497
+ className="bg-muted/50 flex flex-wrap items-center gap-1 border-b px-3 py-2 text-sm"
498
+ data-slot="file-manager-selection"
499
+ >
500
+ <span className="me-2 font-medium">
501
+ {tt('selected', { count: selected.length }, ':count selected')}
502
+ </span>
503
+ {state.trash ? (
504
+ <>
505
+ {controller.can('restore') && (
506
+ <ToolButton icon={RotateCcwIcon} onClick={() => void controller.restore()}>
507
+ {tt('restore', {}, 'Restore')}
508
+ </ToolButton>
509
+ )}
510
+ {controller.can('delete') && (
511
+ <ToolButton
512
+ icon={Trash2Icon}
513
+ onClick={() => controller.confirmDelete()}
514
+ destructive
515
+ >
516
+ {tt('delete_permanently', {}, 'Delete permanently')}
517
+ </ToolButton>
518
+ )}
519
+ </>
520
+ ) : (
521
+ <>
522
+ {single && (
523
+ <ToolButton
524
+ icon={single.type === 'folder' ? FolderOpenIcon : SearchIcon}
525
+ onClick={() => controller.openEntry(single)}
526
+ >
527
+ {single.type === 'folder' ? tt('open', {}, 'Open') : tt('preview', {}, 'Preview')}
528
+ </ToolButton>
529
+ )}
530
+ {controller.can('download') && (single?.type === 'file' || controller.can('zip')) && (
531
+ <ToolButton icon={DownloadIcon} onClick={() => void controller.download()}>
532
+ {single?.type === 'file'
533
+ ? tt('download', {}, 'Download')
534
+ : tt('download_zip', {}, 'Download as zip')}
535
+ </ToolButton>
536
+ )}
537
+ {single && controller.can('rename') && (
538
+ <ToolButton
539
+ icon={PencilIcon}
540
+ onClick={() => controller.openDialog({ type: 'rename', entry: single })}
541
+ >
542
+ {tt('rename', {}, 'Rename')}
543
+ </ToolButton>
544
+ )}
545
+ {controller.can('move') && (
546
+ <ToolButton
547
+ icon={FolderInputIcon}
548
+ onClick={() =>
549
+ controller.openDialog({
550
+ type: 'move',
551
+ paths: state.selection,
552
+ destination: state.path,
553
+ })
554
+ }
555
+ >
556
+ {tt('move', {}, 'Move')}
557
+ </ToolButton>
558
+ )}
559
+ {controller.can('copy') && (
560
+ <ToolButton
561
+ icon={CopyIcon}
562
+ onClick={() =>
563
+ controller.openDialog({
564
+ type: 'copy',
565
+ paths: state.selection,
566
+ destination: state.path,
567
+ })
568
+ }
569
+ >
570
+ {tt('copy', {}, 'Copy')}
571
+ </ToolButton>
572
+ )}
573
+ {single?.type === 'file' && controller.can('share') && schema.share && (
574
+ <ToolButton icon={Share2Icon} onClick={() => void controller.openShare(single)}>
575
+ {tt('share', {}, 'Share')}
576
+ </ToolButton>
577
+ )}
578
+ <ToolButton icon={InfoIcon} onClick={() => void controller.showDetails(single)}>
579
+ {tt('details', {}, 'Details')}
580
+ </ToolButton>
581
+ {controller.can('delete') && (
582
+ <ToolButton
583
+ icon={Trash2Icon}
584
+ onClick={() => controller.confirmDelete()}
585
+ destructive
586
+ >
587
+ {tt('delete', {}, 'Delete')}
588
+ </ToolButton>
589
+ )}
590
+ </>
591
+ )}
592
+ <Button
593
+ type="button"
594
+ variant="ghost"
595
+ size="icon-sm"
596
+ className="ms-auto"
597
+ onClick={() => controller.clearSelection()}
598
+ aria-label={tt('clear_selection', {}, 'Clear selection')}
599
+ >
600
+ <XIcon aria-hidden="true" />
601
+ </Button>
602
+ </div>
603
+ )}
604
+
605
+ <div className={cn('flex', picker ? 'h-[60vh]' : 'min-h-[28rem]')}>
606
+ <aside
607
+ className="hidden w-60 shrink-0 overflow-y-auto border-e p-2 md:block"
608
+ data-slot="file-manager-sidebar"
609
+ >
610
+ {tree}
611
+ </aside>
612
+
613
+ <div
614
+ className="relative min-w-0 flex-1 overflow-y-auto"
615
+ data-slot="file-manager-content"
616
+ data-drop-target={state.dropTarget === '' ? '' : undefined}
617
+ {...(canWrite ? dropHandlers('') : {})}
618
+ >
619
+ {state.trash && state.retentionDays && (
620
+ <p className="text-muted-foreground border-b px-4 py-2 text-xs">
621
+ {tt(
622
+ 'trash_hint',
623
+ { days: state.retentionDays },
624
+ 'Items in the trash are deleted for good after :days days.',
625
+ )}
626
+ </p>
627
+ )}
628
+ {(state.truncated || state.resultsTruncated) && (
629
+ <p className="text-muted-foreground border-b px-4 py-2 text-xs" role="note">
630
+ {state.resultsTruncated
631
+ ? tt(
632
+ 'search_truncated',
633
+ {},
634
+ 'Only the first results are shown. Refine your search.',
635
+ )
636
+ : tt('truncated', {}, 'This folder has more items than can be shown here.')}
637
+ </p>
638
+ )}
639
+ <p id={hintId} className="sr-only">
640
+ {tt(
641
+ 'keyboard',
642
+ {},
643
+ 'Arrow keys move between items, Shift selects a range, Enter opens.',
644
+ )}
645
+ </p>
646
+
647
+ {(state.loading || state.searching) && entries.length === 0 ? (
648
+ <div className="flex justify-center py-16">
649
+ <Spinner />
650
+ <span className="sr-only">{tt('loading', {}, 'Loading files')}</span>
651
+ </div>
652
+ ) : entries.length === 0 ? (
653
+ <div
654
+ className="text-muted-foreground flex flex-col items-center gap-2 px-6 py-16 text-center text-sm"
655
+ data-slot="file-manager-empty"
656
+ >
657
+ <FolderOpenIcon className="size-10 opacity-40" aria-hidden="true" />
658
+ <p className="text-foreground font-medium">
659
+ {state.query
660
+ ? tt('no_results', {}, 'Nothing matches your search')
661
+ : state.trash
662
+ ? tt('empty_trash_state', {}, 'The trash is empty')
663
+ : tt('empty', {}, 'This folder is empty')}
664
+ </p>
665
+ {!state.query && canWrite && controller.can('upload') && (
666
+ <p>{tt('empty_hint', {}, 'Drop files here or choose Upload.')}</p>
667
+ )}
668
+ </div>
669
+ ) : (
670
+ <div
671
+ ref={listRef}
672
+ role="listbox"
673
+ aria-multiselectable={!picker || schema.multiple === true}
674
+ aria-labelledby={labelId}
675
+ aria-describedby={hintId}
676
+ aria-busy={state.loading || undefined}
677
+ data-slot="file-manager-items"
678
+ className={cn(
679
+ state.view === 'grid'
680
+ ? 'grid grid-cols-2 gap-3 p-3 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6'
681
+ : 'flex flex-col p-1',
682
+ state.loading && 'opacity-60',
683
+ )}
684
+ >
685
+ {state.view === 'list' && (
686
+ <div
687
+ className="text-muted-foreground hidden grid-cols-[1fr_7rem_10rem] gap-3 px-3 py-1.5 text-xs font-medium sm:grid"
688
+ aria-hidden="true"
689
+ >
690
+ <span>{tt('name', {}, 'Name')}</span>
691
+ <span className="text-end">{tt('size', {}, 'Size')}</span>
692
+ <span>{tt('modified', {}, 'Modified')}</span>
693
+ </div>
694
+ )}
695
+ {entries.map((entry) => (
696
+ <EntryItem
697
+ key={entry.path}
698
+ entry={entry}
699
+ view={state.view}
700
+ selected={state.selection.includes(entry.path)}
701
+ focusable={entry.path === focusPath}
702
+ dropTarget={state.dropTarget === entry.path}
703
+ disabled={picker && entry.type === 'file' && entry.selectable === false}
704
+ draggable={canWrite && (controller.can('move') || controller.can('copy'))}
705
+ showLocation={state.results !== null}
706
+ formatter={formatter}
707
+ tt={tt}
708
+ ref={(element) => {
709
+ if (element) items.current.set(entry.path, element)
710
+ else items.current.delete(entry.path)
711
+ }}
712
+ onPointerDown={(event) => (pointer.current = event.pointerType)}
713
+ onClick={(event) => onItemClick(entry, event)}
714
+ onDoubleClick={() => onItemDoubleClick(entry)}
715
+ onKeyDown={onItemKeyDown}
716
+ onFocus={() => controller.focus(entry.path)}
717
+ onDragStart={(event) => controller.dragStart(entry.path, event.dataTransfer)}
718
+ onDragEnd={() => controller.dragEnd()}
719
+ dropHandlers={
720
+ entry.type === 'folder' && canWrite ? dropHandlers(entry.path) : undefined
721
+ }
722
+ />
723
+ ))}
724
+ </div>
725
+ )}
726
+
727
+ {state.dropTarget === '' && (
728
+ <div
729
+ className="border-primary bg-primary/5 text-primary pointer-events-none absolute inset-2 flex items-center justify-center rounded-lg border-2 border-dashed text-sm font-medium"
730
+ data-slot="file-manager-drop"
731
+ >
732
+ {tt('drop_here', { folder: folderLabel }, 'Drop files to upload them to :folder')}
733
+ </div>
734
+ )}
735
+ </div>
736
+
737
+ {state.detailsOpen && (
738
+ <aside
739
+ className="hidden w-72 shrink-0 overflow-y-auto border-s md:block"
740
+ data-slot="file-manager-details"
741
+ >
742
+ <Details controller={controller} tt={tt} formatter={formatter} />
743
+ </aside>
744
+ )}
745
+ </div>
746
+
747
+ {state.trash && entries.length > 0 && controller.can('delete') && (
748
+ <div className="flex justify-end border-t p-3">
749
+ <Button
750
+ type="button"
751
+ variant="outline"
752
+ size="sm"
753
+ onClick={() => controller.openDialog({ type: 'emptyTrash' })}
754
+ >
755
+ <Trash2Icon aria-hidden="true" />
756
+ {tt('empty_trash', {}, 'Empty trash')}
757
+ </Button>
758
+ </div>
759
+ )}
760
+
761
+ {picker && (
762
+ <div
763
+ className="flex flex-wrap items-center justify-end gap-2 border-t p-3"
764
+ data-slot="file-manager-picker-footer"
765
+ >
766
+ <span className="text-muted-foreground me-auto text-sm">
767
+ {selected.length > 0
768
+ ? tt('selected', { count: controller.chosen().length }, ':count selected')
769
+ : ''}
770
+ </span>
771
+ <Button type="button" variant="outline" onClick={onCancel}>
772
+ {tt('cancel', {}, 'Cancel')}
773
+ </Button>
774
+ <Button
775
+ type="button"
776
+ disabled={controller.chosen().length === 0}
777
+ onClick={() => onChoose?.(controller.chosen())}
778
+ >
779
+ <CheckIcon aria-hidden="true" />
780
+ {tt('picker_choose', {}, 'Choose')}
781
+ </Button>
782
+ </div>
783
+ )}
784
+
785
+ <Sheet open={foldersOpen} onOpenChange={setFoldersOpen}>
786
+ <SheetContent side={direction === 'rtl' ? 'right' : 'left'} className="p-4">
787
+ <SheetHeader className="p-0">
788
+ <SheetTitle>{tt('folders', {}, 'Folders')}</SheetTitle>
789
+ </SheetHeader>
790
+ {tree}
791
+ </SheetContent>
792
+ </Sheet>
793
+
794
+ <Sheet
795
+ open={state.detailsOpen && mobile}
796
+ onOpenChange={(open) => !open && controller.closeDetails()}
797
+ >
798
+ <SheetContent side={direction === 'rtl' ? 'left' : 'right'} className="p-0">
799
+ <SheetHeader>
800
+ <SheetTitle>{tt('details', {}, 'Details')}</SheetTitle>
801
+ </SheetHeader>
802
+ <Details controller={controller} tt={tt} formatter={formatter} hideHeading />
803
+ </SheetContent>
804
+ </Sheet>
805
+
806
+ <Uploads controller={controller} tt={tt} />
807
+ <Dialogs controller={controller} tt={tt} choice={choice} formatter={formatter} />
808
+ <Preview controller={controller} tt={tt} direction={direction} />
809
+ </Card>
810
+ )
811
+ }
812
+
813
+ type Translate = (
814
+ key: string,
815
+ replacements?: Record<string, string | number>,
816
+ fallback?: string,
817
+ ) => string
818
+
819
+ function Crumb({
820
+ children,
821
+ current,
822
+ onClick,
823
+ }: {
824
+ children: ReactNode
825
+ current?: boolean
826
+ onClick?: () => void
827
+ }) {
828
+ return (
829
+ <li className="flex min-w-0 items-center gap-1">
830
+ <ChevronRightIcon
831
+ className="text-muted-foreground size-3.5 shrink-0 rtl:rotate-180"
832
+ aria-hidden="true"
833
+ />
834
+ {current || !onClick ? (
835
+ <span className="truncate px-1.5 py-1 font-medium" aria-current="page">
836
+ {children}
837
+ </span>
838
+ ) : (
839
+ <button
840
+ type="button"
841
+ className="hover:bg-accent truncate rounded px-1.5 py-1"
842
+ onClick={onClick}
843
+ >
844
+ {children}
845
+ </button>
846
+ )}
847
+ </li>
848
+ )
849
+ }
850
+
851
+ function ToolButton({
852
+ icon: IconComponent,
853
+ children,
854
+ onClick,
855
+ destructive,
856
+ }: {
857
+ icon: LucideIcon
858
+ children: ReactNode
859
+ onClick: () => void
860
+ destructive?: boolean
861
+ }) {
862
+ return (
863
+ <Button
864
+ type="button"
865
+ variant="ghost"
866
+ size="sm"
867
+ onClick={onClick}
868
+ className={cn(destructive && 'text-destructive hover:text-destructive')}
869
+ >
870
+ <IconComponent aria-hidden="true" />
871
+ <span className="max-md:sr-only">{children}</span>
872
+ </Button>
873
+ )
874
+ }
875
+
876
+ interface DropHandlers {
877
+ onDragOver: (event: DragEvent<HTMLElement>) => void
878
+ onDragLeave: (event: DragEvent<HTMLElement>) => void
879
+ onDrop: (event: DragEvent<HTMLElement>) => void
880
+ }
881
+
882
+ function EntryIcon({ entry, className }: { entry: FileManagerEntrySchema; className?: string }) {
883
+ const IconComponent = KIND_ICONS[entry.kind] ?? FileIcon
884
+
885
+ return (
886
+ <IconComponent
887
+ className={cn(entry.type === 'folder' && 'text-primary', className)}
888
+ aria-hidden="true"
889
+ />
890
+ )
891
+ }
892
+
893
+ function EntryItem({
894
+ entry,
895
+ view,
896
+ selected,
897
+ focusable,
898
+ dropTarget,
899
+ disabled,
900
+ draggable,
901
+ showLocation,
902
+ formatter,
903
+ tt,
904
+ ref,
905
+ dropHandlers,
906
+ ...handlers
907
+ }: {
908
+ entry: FileManagerEntrySchema
909
+ view: 'grid' | 'list'
910
+ selected: boolean
911
+ focusable: boolean
912
+ dropTarget: boolean
913
+ disabled: boolean
914
+ draggable: boolean
915
+ showLocation: boolean
916
+ formatter: ReturnType<typeof useFormatter>
917
+ tt: Translate
918
+ ref: (element: HTMLDivElement | null) => void
919
+ dropHandlers?: DropHandlers
920
+ onPointerDown: (event: PointerEvent<HTMLElement>) => void
921
+ onClick: (event: MouseEvent<HTMLElement>) => void
922
+ onDoubleClick: () => void
923
+ onKeyDown: (event: KeyboardEvent<HTMLElement>) => void
924
+ onFocus: () => void
925
+ onDragStart: (event: DragEvent<HTMLElement>) => void
926
+ onDragEnd: () => void
927
+ }) {
928
+ const [broken, setBroken] = useState(false)
929
+ const size = entry.type === 'file' ? fileManagerSize(entry.size, formatter) : ''
930
+ const modified = entry.modified ? formatter.dateTime(entry.modified) : ''
931
+ const location =
932
+ showLocation && entry.path.includes('/')
933
+ ? entry.path.slice(0, entry.path.lastIndexOf('/'))
934
+ : null
935
+ const thumbnail = entry.thumbnail && !broken ? entry.thumbnail : null
936
+
937
+ return (
938
+ <div
939
+ ref={ref}
940
+ role="option"
941
+ aria-label={entry.name}
942
+ aria-selected={selected}
943
+ aria-disabled={disabled || undefined}
944
+ tabIndex={focusable ? 0 : -1}
945
+ draggable={draggable}
946
+ data-slot="file-manager-item"
947
+ data-type={entry.type}
948
+ data-kind={entry.kind}
949
+ data-selected={selected ? '' : undefined}
950
+ data-drop-target={dropTarget ? '' : undefined}
951
+ title={entry.name}
952
+ className={cn(
953
+ 'group focus-visible:ring-ring/50 relative cursor-default rounded-lg outline-none select-none focus-visible:ring-[3px]',
954
+ view === 'grid'
955
+ ? 'hover:bg-accent/50 flex flex-col gap-2 border p-2'
956
+ : 'hover:bg-accent/50 grid grid-cols-[auto_1fr] items-center gap-3 px-3 py-2 sm:grid-cols-[auto_1fr_7rem_10rem]',
957
+ selected && 'bg-primary/10 border-primary/40 hover:bg-primary/15',
958
+ dropTarget && 'ring-primary ring-2',
959
+ disabled && 'opacity-50',
960
+ )}
961
+ {...handlers}
962
+ {...dropHandlers}
963
+ >
964
+ {view === 'grid' ? (
965
+ <>
966
+ <div className="bg-muted/60 flex aspect-[4/3] items-center justify-center overflow-hidden rounded-md">
967
+ {thumbnail ? (
968
+ <img
969
+ src={thumbnail}
970
+ alt=""
971
+ loading="lazy"
972
+ draggable={false}
973
+ className="size-full object-cover"
974
+ onError={() => setBroken(true)}
975
+ />
976
+ ) : (
977
+ <EntryIcon entry={entry} className="size-10" />
978
+ )}
979
+ </div>
980
+ <div className="min-w-0 px-0.5">
981
+ <p className="truncate text-sm font-medium">{entry.name}</p>
982
+ <p className="text-muted-foreground truncate text-xs">
983
+ {location ?? (entry.type === 'folder' ? tt(`kinds.folder`, {}, 'Folder') : size)}
984
+ </p>
985
+ </div>
986
+ </>
987
+ ) : (
988
+ <>
989
+ {thumbnail ? (
990
+ <img
991
+ src={thumbnail}
992
+ alt=""
993
+ loading="lazy"
994
+ draggable={false}
995
+ className="size-8 rounded object-cover"
996
+ onError={() => setBroken(true)}
997
+ />
998
+ ) : (
999
+ <EntryIcon entry={entry} className="size-5" />
1000
+ )}
1001
+ <div className="min-w-0">
1002
+ <p className="truncate text-sm font-medium">{entry.name}</p>
1003
+ {location && <p className="text-muted-foreground truncate text-xs">{location}</p>}
1004
+ </div>
1005
+ <span className="text-muted-foreground hidden text-end text-sm tabular-nums sm:block">
1006
+ {size}
1007
+ </span>
1008
+ <span className="text-muted-foreground hidden truncate text-sm sm:block">{modified}</span>
1009
+ </>
1010
+ )}
1011
+ {selected && (
1012
+ <CheckIcon
1013
+ className="bg-primary text-primary-foreground absolute end-1.5 top-1.5 size-4 rounded-full p-0.5"
1014
+ aria-hidden="true"
1015
+ />
1016
+ )}
1017
+ </div>
1018
+ )
1019
+ }
1020
+
1021
+ function FolderTree({
1022
+ controller,
1023
+ onOpen,
1024
+ active,
1025
+ dropHandlers,
1026
+ dropTarget,
1027
+ }: {
1028
+ controller: FileManagerController
1029
+ onOpen: (path: string) => void
1030
+ active: string | null
1031
+ dropHandlers?: (target: string) => DropHandlers
1032
+ dropTarget?: string | null
1033
+ }) {
1034
+ const t = useT()
1035
+ useStore(controller.store)
1036
+
1037
+ const renderLevel = (parent: string, level: number): ReactNode => {
1038
+ const folders = controller.children(parent)
1039
+
1040
+ if (!folders || folders.length === 0) return null
1041
+
1042
+ return (
1043
+ <ul className="flex flex-col" role={level === 1 ? undefined : 'group'}>
1044
+ {folders.map((folder) => {
1045
+ const expanded = controller.isExpanded(folder.path)
1046
+
1047
+ return (
1048
+ <li key={folder.path} data-slot="file-manager-tree-item">
1049
+ <div
1050
+ className={cn(
1051
+ 'hover:bg-accent flex items-center gap-1 rounded-md pe-2 text-sm',
1052
+ active === folder.path && 'bg-accent font-medium',
1053
+ dropTarget === folder.path && 'ring-primary ring-2',
1054
+ )}
1055
+ style={{ paddingInlineStart: `${(level - 1) * 0.75}rem` }}
1056
+ {...(dropHandlers ? dropHandlers(folder.path) : {})}
1057
+ >
1058
+ {folder.hasChildren ? (
1059
+ <button
1060
+ type="button"
1061
+ className="text-muted-foreground hover:text-foreground rounded p-1"
1062
+ aria-expanded={expanded}
1063
+ aria-label={t(
1064
+ expanded ? 'file-manager.collapse' : 'file-manager.expand',
1065
+ { name: folder.name },
1066
+ expanded ? 'Collapse :name' : 'Expand :name',
1067
+ )}
1068
+ onClick={() => void controller.toggleFolder(folder.path)}
1069
+ >
1070
+ <ChevronRightIcon
1071
+ className={cn(
1072
+ 'size-3.5 transition-transform',
1073
+ expanded ? 'rotate-90' : 'rtl:rotate-180',
1074
+ )}
1075
+ aria-hidden="true"
1076
+ />
1077
+ </button>
1078
+ ) : (
1079
+ <span className="w-5.5 shrink-0" aria-hidden="true" />
1080
+ )}
1081
+ <button
1082
+ type="button"
1083
+ className="flex min-w-0 flex-1 items-center gap-2 py-1.5 text-start"
1084
+ aria-current={active === folder.path ? 'page' : undefined}
1085
+ onClick={() => onOpen(folder.path)}
1086
+ >
1087
+ <FolderIcon className="text-primary size-4 shrink-0" aria-hidden="true" />
1088
+ <span className="truncate">{folder.name}</span>
1089
+ </button>
1090
+ </div>
1091
+ {expanded && renderLevel(folder.path, level + 1)}
1092
+ </li>
1093
+ )
1094
+ })}
1095
+ </ul>
1096
+ )
1097
+ }
1098
+
1099
+ return (
1100
+ <nav aria-label={t('file-manager.folders', {}, 'Folders')} data-slot="file-manager-tree">
1101
+ <button
1102
+ type="button"
1103
+ className={cn(
1104
+ 'hover:bg-accent flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-start text-sm',
1105
+ active === '' && 'bg-accent font-medium',
1106
+ dropTarget === '' && active !== '' && 'ring-primary ring-2',
1107
+ )}
1108
+ aria-current={active === '' ? 'page' : undefined}
1109
+ onClick={() => onOpen('')}
1110
+ >
1111
+ <FolderOpenIcon className="text-primary size-4" aria-hidden="true" />
1112
+ {t('file-manager.root', {}, 'Files')}
1113
+ </button>
1114
+ {renderLevel('', 1)}
1115
+ </nav>
1116
+ )
1117
+ }
1118
+
1119
+ function Details({
1120
+ controller,
1121
+ tt,
1122
+ formatter,
1123
+ hideHeading,
1124
+ }: {
1125
+ controller: FileManagerController
1126
+ tt: Translate
1127
+ formatter: ReturnType<typeof useFormatter>
1128
+ hideHeading?: boolean
1129
+ }) {
1130
+ const state = useStore(controller.store)
1131
+ const details = state.details
1132
+
1133
+ return (
1134
+ <div className="flex flex-col gap-4 p-4">
1135
+ {!hideHeading && (
1136
+ <div className="flex items-center justify-between gap-2">
1137
+ <h3 className="text-sm font-semibold">{tt('details', {}, 'Details')}</h3>
1138
+ <Button
1139
+ type="button"
1140
+ variant="ghost"
1141
+ size="icon-xs"
1142
+ onClick={() => controller.closeDetails()}
1143
+ aria-label={tt('close', {}, 'Close')}
1144
+ >
1145
+ <XIcon aria-hidden="true" />
1146
+ </Button>
1147
+ </div>
1148
+ )}
1149
+ {!details ? (
1150
+ <p className="text-muted-foreground text-sm">{tt('picker_empty', {}, 'No file chosen')}</p>
1151
+ ) : (
1152
+ <>
1153
+ <div className="bg-muted/60 flex aspect-video items-center justify-center overflow-hidden rounded-md">
1154
+ {details.thumbnail ? (
1155
+ <img src={details.thumbnail} alt="" className="size-full object-contain" />
1156
+ ) : (
1157
+ <EntryIcon entry={details} className="size-12" />
1158
+ )}
1159
+ </div>
1160
+ <p className="font-medium break-all">{details.name}</p>
1161
+ {state.detailsLoading && <Spinner />}
1162
+ <dl className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-sm">
1163
+ <DetailRow
1164
+ label={tt('type', {}, 'Type')}
1165
+ value={details.mime ?? tt(`kinds.${details.kind}`, {}, details.kind)}
1166
+ />
1167
+ {details.type === 'file' && (
1168
+ <DetailRow
1169
+ label={tt('size', {}, 'Size')}
1170
+ value={fileManagerSize(details.size, formatter)}
1171
+ />
1172
+ )}
1173
+ {details.items !== undefined && (
1174
+ <DetailRow
1175
+ label={tt('items', {}, 'Items')}
1176
+ value={`${formatter.number(details.items)}${details.itemsTruncated ? '+' : ''}`}
1177
+ />
1178
+ )}
1179
+ {details.width && details.height && (
1180
+ <DetailRow
1181
+ label={tt('dimensions', {}, 'Dimensions')}
1182
+ value={tt(
1183
+ 'dimensions_value',
1184
+ {
1185
+ width: formatter.number(details.width),
1186
+ height: formatter.number(details.height),
1187
+ },
1188
+ ':width × :height px',
1189
+ )}
1190
+ />
1191
+ )}
1192
+ {details.trashed ? (
1193
+ <>
1194
+ <DetailRow
1195
+ label={tt('original_location', {}, 'Original location')}
1196
+ value={details.trashed.originalPath}
1197
+ />
1198
+ {details.trashed.deletedAt && (
1199
+ <DetailRow
1200
+ label={tt('deleted_at', {}, 'Deleted')}
1201
+ value={formatter.dateTime(details.trashed.deletedAt)}
1202
+ />
1203
+ )}
1204
+ </>
1205
+ ) : (
1206
+ <>
1207
+ {details.modified && (
1208
+ <DetailRow
1209
+ label={tt('modified', {}, 'Modified')}
1210
+ value={formatter.dateTime(details.modified)}
1211
+ />
1212
+ )}
1213
+ {details.location !== undefined && (
1214
+ <DetailRow
1215
+ label={tt('location', {}, 'Location')}
1216
+ value={details.location === '' ? tt('root', {}, 'Files') : details.location}
1217
+ />
1218
+ )}
1219
+ {details.links !== undefined && (
1220
+ <DetailRow
1221
+ label={tt('links', {}, 'Links')}
1222
+ value={formatter.number(details.links)}
1223
+ />
1224
+ )}
1225
+ </>
1226
+ )}
1227
+ </dl>
1228
+ </>
1229
+ )}
1230
+ </div>
1231
+ )
1232
+ }
1233
+
1234
+ function DetailRow({ label, value }: { label: string; value: string }) {
1235
+ return (
1236
+ <>
1237
+ <dt className="text-muted-foreground">{label}</dt>
1238
+ <dd className="min-w-0 break-all">{value}</dd>
1239
+ </>
1240
+ )
1241
+ }
1242
+
1243
+ function Uploads({ controller, tt }: { controller: FileManagerController; tt: Translate }) {
1244
+ const state = useStore(controller.store)
1245
+ const formatter = useFormatter()
1246
+
1247
+ if (state.uploads.length === 0) return null
1248
+
1249
+ const done = state.uploads.filter((upload) => upload.status === 'done').length
1250
+ const finished = state.uploads.every(
1251
+ (upload) => upload.status === 'done' || upload.status === 'error',
1252
+ )
1253
+
1254
+ return (
1255
+ <section
1256
+ className="bg-popover fixed end-4 bottom-4 z-40 flex max-h-80 w-[min(22rem,calc(100vw-2rem))] flex-col overflow-hidden rounded-lg border shadow-lg"
1257
+ aria-label={tt('uploads', {}, 'Uploads')}
1258
+ data-slot="file-manager-uploads"
1259
+ >
1260
+ <header className="flex items-center gap-2 border-b px-3 py-2 text-sm font-medium">
1261
+ <UploadIcon className="size-4" aria-hidden="true" />
1262
+ <span className="flex-1" role="status">
1263
+ {tt('uploads_summary', { done, total: state.uploads.length }, ':done of :total uploaded')}
1264
+ </span>
1265
+ {finished && (
1266
+ <Button
1267
+ type="button"
1268
+ variant="ghost"
1269
+ size="icon-xs"
1270
+ onClick={() => controller.clearUploads()}
1271
+ aria-label={tt('dismiss', {}, 'Dismiss')}
1272
+ >
1273
+ <XIcon aria-hidden="true" />
1274
+ </Button>
1275
+ )}
1276
+ </header>
1277
+ <ul className="flex flex-col gap-2 overflow-y-auto p-3">
1278
+ {state.uploads.map((upload) => (
1279
+ <li key={upload.id} className="flex flex-col gap-1 text-sm" data-status={upload.status}>
1280
+ <div className="flex items-center gap-2">
1281
+ <span className="min-w-0 flex-1 truncate">{upload.name}</span>
1282
+ {upload.status === 'done' ? (
1283
+ <CheckIcon
1284
+ className="size-4 text-emerald-600"
1285
+ aria-label={tt('upload_done', {}, 'Uploaded')}
1286
+ />
1287
+ ) : upload.status === 'error' ? (
1288
+ <Button
1289
+ type="button"
1290
+ variant="ghost"
1291
+ size="icon-xs"
1292
+ onClick={() => controller.dismissUpload(upload.id)}
1293
+ aria-label={tt('dismiss', {}, 'Dismiss')}
1294
+ >
1295
+ <XIcon aria-hidden="true" />
1296
+ </Button>
1297
+ ) : (
1298
+ <span className="text-muted-foreground text-xs tabular-nums">
1299
+ {formatter.percent(upload.progress)}
1300
+ </span>
1301
+ )}
1302
+ </div>
1303
+ {upload.status === 'error' ? (
1304
+ <p className="text-destructive text-xs" role="alert">
1305
+ {upload.error ?? tt('upload_failed', {}, 'Failed')}
1306
+ </p>
1307
+ ) : (
1308
+ upload.status !== 'done' && (
1309
+ <div
1310
+ role="progressbar"
1311
+ aria-valuemin={0}
1312
+ aria-valuemax={100}
1313
+ aria-valuenow={Math.round(upload.progress * 100)}
1314
+ aria-label={tt(
1315
+ 'upload_progress',
1316
+ { name: upload.name, percent: formatter.percent(upload.progress) },
1317
+ ':name, :percent uploaded',
1318
+ )}
1319
+ className="bg-muted h-1.5 overflow-hidden rounded-full"
1320
+ >
1321
+ <div
1322
+ className="bg-primary h-full rounded-full transition-[width]"
1323
+ style={{ width: `${upload.progress * 100}%` }}
1324
+ />
1325
+ </div>
1326
+ )
1327
+ )}
1328
+ </li>
1329
+ ))}
1330
+ </ul>
1331
+ </section>
1332
+ )
1333
+ }
1334
+
1335
+ function Dialogs({
1336
+ controller,
1337
+ tt,
1338
+ choice,
1339
+ formatter,
1340
+ }: {
1341
+ controller: FileManagerController
1342
+ tt: Translate
1343
+ choice: ReturnType<typeof useChoice>
1344
+ formatter: ReturnType<typeof useFormatter>
1345
+ }) {
1346
+ const state = useStore(controller.store)
1347
+ const dialog = state.dialog
1348
+ const [name, setName] = useState('')
1349
+ const [expiry, setExpiry] = useState<string>('')
1350
+ const [copied, setCopied] = useState(false)
1351
+ const nameId = useId()
1352
+ const errorId = useId()
1353
+ const close = () => controller.closeDialog()
1354
+ const share = controller.component.share
1355
+
1356
+ // Reset the inputs when another dialog opens (state derived during render, no effect).
1357
+ const dialogKey = dialog ? `${dialog.type}:${'entry' in dialog ? dialog.entry.path : ''}` : ''
1358
+ const [openedDialog, setOpenedDialog] = useState('')
1359
+
1360
+ if (dialogKey !== openedDialog) {
1361
+ setOpenedDialog(dialogKey)
1362
+
1363
+ if (dialog?.type === 'rename') setName(dialog.entry.name)
1364
+ if (dialog?.type === 'createFolder') setName('')
1365
+ if (dialog?.type === 'share') {
1366
+ setExpiry(String(share?.expiries[0] ?? ''))
1367
+ setCopied(false)
1368
+ }
1369
+ }
1370
+
1371
+ const error = state.error ? (
1372
+ <p id={errorId} className="text-destructive text-sm" role="alert">
1373
+ {state.error}
1374
+ </p>
1375
+ ) : null
1376
+
1377
+ const copy = async (url: string) => {
1378
+ try {
1379
+ await navigator.clipboard.writeText(url)
1380
+ setCopied(true)
1381
+ } catch {
1382
+ setCopied(false)
1383
+ }
1384
+ }
1385
+
1386
+ return (
1387
+ <>
1388
+ <Dialog
1389
+ open={dialog?.type === 'createFolder' || dialog?.type === 'rename'}
1390
+ onOpenChange={(open) => !open && close()}
1391
+ >
1392
+ <DialogContent data-slot="file-manager-dialog">
1393
+ <form
1394
+ className="flex flex-col gap-4"
1395
+ onSubmit={(event) => {
1396
+ event.preventDefault()
1397
+
1398
+ if (dialog?.type === 'rename') void controller.rename(dialog.entry.path, name)
1399
+ else void controller.createFolder(name)
1400
+ }}
1401
+ >
1402
+ <DialogHeader>
1403
+ <DialogTitle>
1404
+ {dialog?.type === 'rename'
1405
+ ? tt('rename_heading', { name: dialog.entry.name }, 'Rename “:name”')
1406
+ : tt('new_folder', {}, 'New folder')}
1407
+ </DialogTitle>
1408
+ </DialogHeader>
1409
+ <div className="flex flex-col gap-2">
1410
+ <label htmlFor={nameId} className="text-sm font-medium">
1411
+ {dialog?.type === 'rename'
1412
+ ? tt('name', {}, 'Name')
1413
+ : tt('folder_name', {}, 'Folder name')}
1414
+ </label>
1415
+ <Input
1416
+ id={nameId}
1417
+ value={name}
1418
+ autoFocus
1419
+ onChange={(event) => setName(event.target.value)}
1420
+ aria-invalid={state.error ? true : undefined}
1421
+ aria-describedby={state.error ? errorId : undefined}
1422
+ />
1423
+ {error}
1424
+ </div>
1425
+ <DialogFooter>
1426
+ <Button type="button" variant="outline" onClick={close}>
1427
+ {tt('cancel', {}, 'Cancel')}
1428
+ </Button>
1429
+ <Button type="submit" disabled={state.busy || name.trim() === ''}>
1430
+ {state.busy && <Spinner />}
1431
+ {dialog?.type === 'rename' ? tt('save', {}, 'Save') : tt('create', {}, 'Create')}
1432
+ </Button>
1433
+ </DialogFooter>
1434
+ </form>
1435
+ </DialogContent>
1436
+ </Dialog>
1437
+
1438
+ <Dialog
1439
+ open={dialog?.type === 'move' || dialog?.type === 'copy'}
1440
+ onOpenChange={(open) => !open && close()}
1441
+ >
1442
+ <DialogContent data-slot="file-manager-dialog">
1443
+ <DialogHeader>
1444
+ <DialogTitle>
1445
+ {dialog?.type === 'move' || dialog?.type === 'copy'
1446
+ ? choice(
1447
+ `file-manager.${dialog.type}_heading`,
1448
+ dialog.paths.length,
1449
+ {},
1450
+ dialog.type === 'move' ? 'Move :count items' : 'Copy :count items',
1451
+ )
1452
+ : ''}
1453
+ </DialogTitle>
1454
+ <DialogDescription>{tt('destination', {}, 'Destination folder')}</DialogDescription>
1455
+ </DialogHeader>
1456
+ <div className="max-h-72 overflow-y-auto rounded-md border p-2">
1457
+ {(dialog?.type === 'move' || dialog?.type === 'copy') && (
1458
+ <FolderTree
1459
+ controller={controller}
1460
+ active={dialog.destination}
1461
+ onOpen={(path) => controller.openDialog({ ...dialog, destination: path })}
1462
+ />
1463
+ )}
1464
+ </div>
1465
+ {error}
1466
+ <DialogFooter>
1467
+ <Button type="button" variant="outline" onClick={close}>
1468
+ {tt('cancel', {}, 'Cancel')}
1469
+ </Button>
1470
+ <Button
1471
+ type="button"
1472
+ disabled={state.busy}
1473
+ onClick={() => {
1474
+ if (dialog?.type === 'move' || dialog?.type === 'copy')
1475
+ void controller.transfer(dialog.type, dialog.paths, dialog.destination)
1476
+ }}
1477
+ >
1478
+ {state.busy && <Spinner />}
1479
+ {dialog?.type === 'copy'
1480
+ ? tt('copy_here', {}, 'Copy here')
1481
+ : tt('move_here', {}, 'Move here')}
1482
+ </Button>
1483
+ </DialogFooter>
1484
+ </DialogContent>
1485
+ </Dialog>
1486
+
1487
+ <Dialog
1488
+ open={dialog?.type === 'delete' || dialog?.type === 'emptyTrash'}
1489
+ onOpenChange={(open) => !open && close()}
1490
+ >
1491
+ <DialogContent data-slot="file-manager-dialog">
1492
+ <DialogHeader>
1493
+ <DialogTitle>
1494
+ {dialog?.type === 'delete'
1495
+ ? choice(
1496
+ 'file-manager.delete_heading',
1497
+ dialog.paths.length,
1498
+ {},
1499
+ 'Delete :count items?',
1500
+ )
1501
+ : tt('empty_trash_heading', {}, 'Empty the trash?')}
1502
+ </DialogTitle>
1503
+ <DialogDescription>
1504
+ {dialog?.type === 'delete' && !dialog.permanent
1505
+ ? tt(
1506
+ 'delete_description_trash',
1507
+ {},
1508
+ 'They go to the trash, where you can restore them.',
1509
+ )
1510
+ : dialog?.type === 'emptyTrash'
1511
+ ? tt(
1512
+ 'empty_trash_description',
1513
+ {},
1514
+ 'Everything in the trash is deleted for good.',
1515
+ )
1516
+ : tt(
1517
+ 'delete_description',
1518
+ {},
1519
+ 'They are deleted for good. This cannot be undone.',
1520
+ )}
1521
+ </DialogDescription>
1522
+ </DialogHeader>
1523
+ {error}
1524
+ <DialogFooter>
1525
+ <Button type="button" variant="outline" onClick={close}>
1526
+ {tt('cancel', {}, 'Cancel')}
1527
+ </Button>
1528
+ <Button
1529
+ type="button"
1530
+ variant="destructive"
1531
+ disabled={state.busy}
1532
+ onClick={() => {
1533
+ if (dialog?.type === 'delete') void controller.remove(dialog.paths)
1534
+ else void controller.emptyTrash()
1535
+ }}
1536
+ >
1537
+ {state.busy && <Spinner />}
1538
+ {dialog?.type === 'emptyTrash'
1539
+ ? tt('empty_trash', {}, 'Empty trash')
1540
+ : dialog?.type === 'delete' && dialog.permanent
1541
+ ? tt('delete_permanently', {}, 'Delete permanently')
1542
+ : tt('delete', {}, 'Delete')}
1543
+ </Button>
1544
+ </DialogFooter>
1545
+ </DialogContent>
1546
+ </Dialog>
1547
+
1548
+ <Dialog open={dialog?.type === 'share'} onOpenChange={(open) => !open && close()}>
1549
+ <DialogContent data-slot="file-manager-dialog">
1550
+ {dialog?.type === 'share' && (
1551
+ <>
1552
+ <DialogHeader>
1553
+ <DialogTitle>
1554
+ {tt('share_heading', { name: dialog.entry.name }, 'Share “:name”')}
1555
+ </DialogTitle>
1556
+ <DialogDescription>
1557
+ {tt(
1558
+ 'share_hint',
1559
+ {},
1560
+ 'Anyone with the link can open this file, without signing in.',
1561
+ )}
1562
+ </DialogDescription>
1563
+ </DialogHeader>
1564
+ <div className="flex flex-wrap items-end gap-2">
1565
+ <label className="flex min-w-40 flex-1 flex-col gap-1 text-sm">
1566
+ <span className="font-medium">
1567
+ {tt('share_expiry', {}, 'Link expires after')}
1568
+ </span>
1569
+ <select
1570
+ className={SELECT}
1571
+ value={expiry}
1572
+ onChange={(event) => setExpiry(event.target.value)}
1573
+ >
1574
+ {(share?.expiries ?? []).map((minutes) => {
1575
+ const label = fileManagerExpiry(minutes)
1576
+
1577
+ return (
1578
+ <option key={minutes} value={minutes}>
1579
+ {choice(`file-manager.${label.key}`, label.count, {}, `:count`)}
1580
+ </option>
1581
+ )
1582
+ })}
1583
+ {share?.publicLinks && (
1584
+ <option value="never">{tt('expiry_never', {}, 'Never')}</option>
1585
+ )}
1586
+ </select>
1587
+ </label>
1588
+ <Button
1589
+ type="button"
1590
+ disabled={state.busy}
1591
+ onClick={() =>
1592
+ void controller.createLink(expiry === 'never' ? null : Number(expiry))
1593
+ }
1594
+ >
1595
+ {state.busy ? <Spinner /> : <LinkIcon aria-hidden="true" />}
1596
+ {tt('create_link', {}, 'Create link')}
1597
+ </Button>
1598
+ </div>
1599
+ {error}
1600
+ {dialog.url && (
1601
+ <div className="flex flex-col gap-1">
1602
+ <span className="text-sm font-medium">{tt('link', {}, 'Shareable link')}</span>
1603
+ <div className="flex gap-2">
1604
+ <Input
1605
+ readOnly
1606
+ value={dialog.url}
1607
+ dir="ltr"
1608
+ onFocus={(event) => event.target.select()}
1609
+ aria-label={tt('link', {}, 'Shareable link')}
1610
+ />
1611
+ <Button
1612
+ type="button"
1613
+ variant="outline"
1614
+ onClick={() => void copy(dialog.url ?? '')}
1615
+ >
1616
+ {copied ? <CheckIcon aria-hidden="true" /> : <CopyIcon aria-hidden="true" />}
1617
+ {copied
1618
+ ? tt('link_copied', {}, 'Link copied')
1619
+ : tt('copy_link', {}, 'Copy link')}
1620
+ </Button>
1621
+ </div>
1622
+ <span className="text-muted-foreground text-xs">
1623
+ {dialog.expiresAt
1624
+ ? tt(
1625
+ 'link_expires',
1626
+ { date: formatter.dateTime(dialog.expiresAt) },
1627
+ 'Expires :date',
1628
+ )
1629
+ : tt('link_never_expires', {}, 'Never expires')}
1630
+ </span>
1631
+ </div>
1632
+ )}
1633
+ {share?.publicLinks && (
1634
+ <div className="flex flex-col gap-2" data-slot="file-manager-links">
1635
+ <h4 className="text-sm font-medium">{tt('links', {}, 'Links')}</h4>
1636
+ {dialog.links === null ? (
1637
+ <Spinner />
1638
+ ) : dialog.links.length === 0 ? (
1639
+ <p className="text-muted-foreground text-sm">
1640
+ {tt('no_links', {}, 'No links yet')}
1641
+ </p>
1642
+ ) : (
1643
+ <ul className="flex max-h-48 flex-col divide-y overflow-y-auto rounded-md border">
1644
+ {dialog.links.map((link) => (
1645
+ <li key={link.id} className="flex items-center gap-2 px-3 py-2 text-sm">
1646
+ <div className="min-w-0 flex-1">
1647
+ <p
1648
+ className={cn(
1649
+ 'truncate',
1650
+ link.expired && 'text-muted-foreground line-through',
1651
+ )}
1652
+ dir="ltr"
1653
+ >
1654
+ {link.url}
1655
+ </p>
1656
+ <p className="text-muted-foreground text-xs">
1657
+ {link.expired
1658
+ ? tt('link_expired', {}, 'Expired')
1659
+ : link.expiresAt
1660
+ ? tt(
1661
+ 'link_expires',
1662
+ { date: formatter.dateTime(link.expiresAt) },
1663
+ 'Expires :date',
1664
+ )
1665
+ : tt('link_never_expires', {}, 'Never expires')}
1666
+ {' · '}
1667
+ {choice(
1668
+ 'file-manager.link_downloads',
1669
+ link.downloads,
1670
+ {},
1671
+ ':count downloads',
1672
+ )}
1673
+ </p>
1674
+ </div>
1675
+ {link.url && !link.expired && (
1676
+ <Button
1677
+ type="button"
1678
+ variant="ghost"
1679
+ size="icon-xs"
1680
+ onClick={() => void copy(link.url ?? '')}
1681
+ aria-label={tt('copy_link', {}, 'Copy link')}
1682
+ >
1683
+ <CopyIcon aria-hidden="true" />
1684
+ </Button>
1685
+ )}
1686
+ <Button
1687
+ type="button"
1688
+ variant="ghost"
1689
+ size="sm"
1690
+ className="text-destructive"
1691
+ onClick={() => void controller.revokeLink(link.id)}
1692
+ >
1693
+ {tt('revoke', {}, 'Revoke')}
1694
+ </Button>
1695
+ </li>
1696
+ ))}
1697
+ </ul>
1698
+ )}
1699
+ </div>
1700
+ )}
1701
+ <DialogFooter>
1702
+ <Button type="button" variant="outline" onClick={close}>
1703
+ {tt('close', {}, 'Close')}
1704
+ </Button>
1705
+ </DialogFooter>
1706
+ </>
1707
+ )}
1708
+ </DialogContent>
1709
+ </Dialog>
1710
+ </>
1711
+ )
1712
+ }
1713
+
1714
+ function Preview({
1715
+ controller,
1716
+ tt,
1717
+ direction,
1718
+ }: {
1719
+ controller: FileManagerController
1720
+ tt: Translate
1721
+ direction: 'ltr' | 'rtl'
1722
+ }) {
1723
+ const state = useStore(controller.store)
1724
+ const preview = state.preview
1725
+ const data = preview?.data
1726
+ const [editing, setEditing] = useState<ImageEditRequest | null>(null)
1727
+ const previous = controller.previewSibling(-1)
1728
+ const next = controller.previewSibling(1)
1729
+
1730
+ const edit = async () => {
1731
+ if (!data?.url || !preview) return
1732
+
1733
+ const source = await fetchEditableImage(data.url, preview.entry.name)
1734
+
1735
+ if (!source) return
1736
+
1737
+ setEditing({
1738
+ file: source,
1739
+ resolve: (edited) => {
1740
+ setEditing(null)
1741
+ if (edited && edited !== source) void controller.saveEditedImage(edited)
1742
+ },
1743
+ })
1744
+ }
1745
+
1746
+ const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
1747
+ const back = direction === 'rtl' ? 'ArrowRight' : 'ArrowLeft'
1748
+ const forward = direction === 'rtl' ? 'ArrowLeft' : 'ArrowRight'
1749
+
1750
+ if (
1751
+ event.target instanceof HTMLElement &&
1752
+ ['VIDEO', 'AUDIO', 'INPUT'].includes(event.target.tagName)
1753
+ )
1754
+ return
1755
+
1756
+ if (event.key === back && previous) void controller.openPreview(previous)
1757
+ if (event.key === forward && next) void controller.openPreview(next)
1758
+ }
1759
+
1760
+ return (
1761
+ <>
1762
+ <Dialog open={preview !== null} onOpenChange={(open) => !open && controller.closePreview()}>
1763
+ <DialogContent
1764
+ className="flex max-h-[90vh] flex-col sm:max-w-4xl"
1765
+ data-slot="file-manager-preview"
1766
+ onKeyDown={onKeyDown}
1767
+ >
1768
+ <DialogHeader>
1769
+ <DialogTitle className="truncate pe-8">{preview?.entry.name}</DialogTitle>
1770
+ </DialogHeader>
1771
+ <div className="bg-muted/40 flex min-h-64 flex-1 items-center justify-center overflow-auto rounded-md">
1772
+ {!data ? (
1773
+ <Spinner />
1774
+ ) : !data.previewable ? (
1775
+ <div className="text-muted-foreground flex flex-col items-center gap-2 p-8 text-center text-sm">
1776
+ {preview && <EntryIcon entry={preview.entry} className="size-12" />}
1777
+ <p>{tt('preview_unavailable', {}, 'There is no preview for this type of file.')}</p>
1778
+ </div>
1779
+ ) : data.kind === 'image' ? (
1780
+ <img
1781
+ src={data.url}
1782
+ alt={preview?.entry.name ?? ''}
1783
+ className="max-h-[70vh] max-w-full object-contain"
1784
+ />
1785
+ ) : data.kind === 'pdf' ? (
1786
+ <iframe
1787
+ src={data.url}
1788
+ title={preview?.entry.name ?? ''}
1789
+ className="h-[70vh] w-full rounded-md bg-white"
1790
+ />
1791
+ ) : data.kind === 'video' ? (
1792
+ <video src={data.url} controls className="max-h-[70vh] max-w-full">
1793
+ <track kind="captions" />
1794
+ </video>
1795
+ ) : data.kind === 'audio' ? (
1796
+ <audio src={data.url} controls className="w-full max-w-md" />
1797
+ ) : (
1798
+ <pre
1799
+ className="h-full max-h-[70vh] w-full overflow-auto p-4 font-mono text-xs whitespace-pre-wrap"
1800
+ dir="auto"
1801
+ tabIndex={0}
1802
+ >
1803
+ {data.text}
1804
+ </pre>
1805
+ )}
1806
+ </div>
1807
+ {data?.truncated && (
1808
+ <p className="text-muted-foreground text-xs">
1809
+ {tt('preview_truncated', {}, 'Only the beginning of this file is shown.')}
1810
+ </p>
1811
+ )}
1812
+ <DialogFooter className="flex-row flex-wrap items-center sm:justify-between">
1813
+ <div className="flex gap-1">
1814
+ <Button
1815
+ type="button"
1816
+ variant="outline"
1817
+ size="icon-sm"
1818
+ disabled={!previous}
1819
+ onClick={() => previous && void controller.openPreview(previous)}
1820
+ aria-label={tt('previous', {}, 'Previous file')}
1821
+ >
1822
+ <ChevronLeftIcon className="rtl:rotate-180" aria-hidden="true" />
1823
+ </Button>
1824
+ <Button
1825
+ type="button"
1826
+ variant="outline"
1827
+ size="icon-sm"
1828
+ disabled={!next}
1829
+ onClick={() => next && void controller.openPreview(next)}
1830
+ aria-label={tt('next', {}, 'Next file')}
1831
+ >
1832
+ <ChevronRightIcon className="rtl:rotate-180" aria-hidden="true" />
1833
+ </Button>
1834
+ </div>
1835
+ <div className="flex gap-2">
1836
+ {data?.editable && data.url && (
1837
+ <Button type="button" variant="outline" size="sm" onClick={() => void edit()}>
1838
+ <SquarePenIcon aria-hidden="true" />
1839
+ {tt('edit_image', {}, 'Edit image')}
1840
+ </Button>
1841
+ )}
1842
+ {data?.downloadUrl && (
1843
+ <Button type="button" size="sm" asChild>
1844
+ <a href={data.downloadUrl}>
1845
+ <DownloadIcon aria-hidden="true" />
1846
+ {tt('download', {}, 'Download')}
1847
+ </a>
1848
+ </Button>
1849
+ )}
1850
+ </div>
1851
+ </DialogFooter>
1852
+ </DialogContent>
1853
+ </Dialog>
1854
+ <ImageEditorDialog request={editing} options={IMAGE_EDITOR} />
1855
+ </>
1856
+ )
1857
+ }