@braedonsaunders/appkit-ui 0.2.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 (300) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +11 -0
  3. package/account-menu.d.ts +49 -0
  4. package/account-menu.d.ts.map +1 -0
  5. package/account-menu.js +66 -0
  6. package/account-menu.js.map +1 -0
  7. package/alert.d.ts +13 -0
  8. package/alert.d.ts.map +1 -0
  9. package/alert.js +22 -0
  10. package/alert.js.map +1 -0
  11. package/animated-number.d.ts +13 -0
  12. package/animated-number.d.ts.map +1 -0
  13. package/animated-number.js +53 -0
  14. package/animated-number.js.map +1 -0
  15. package/api-reference.d.ts +44 -0
  16. package/api-reference.d.ts.map +1 -0
  17. package/api-reference.js +82 -0
  18. package/api-reference.js.map +1 -0
  19. package/app-shell.d.ts +39 -0
  20. package/app-shell.d.ts.map +1 -0
  21. package/app-shell.js +21 -0
  22. package/app-shell.js.map +1 -0
  23. package/app-sidebar.d.ts +22 -0
  24. package/app-sidebar.d.ts.map +1 -0
  25. package/app-sidebar.js +30 -0
  26. package/app-sidebar.js.map +1 -0
  27. package/avatar.d.ts +9 -0
  28. package/avatar.d.ts.map +1 -0
  29. package/avatar.js +18 -0
  30. package/avatar.js.map +1 -0
  31. package/badge.d.ts +9 -0
  32. package/badge.d.ts.map +1 -0
  33. package/badge.js +23 -0
  34. package/badge.js.map +1 -0
  35. package/button.d.ts +24 -0
  36. package/button.d.ts.map +1 -0
  37. package/button.js +50 -0
  38. package/button.js.map +1 -0
  39. package/card.d.ts +21 -0
  40. package/card.d.ts.map +1 -0
  41. package/card.js +36 -0
  42. package/card.js.map +1 -0
  43. package/checkbox.d.ts +4 -0
  44. package/checkbox.d.ts.map +1 -0
  45. package/checkbox.js +6 -0
  46. package/checkbox.js.map +1 -0
  47. package/confirm-dialog.d.ts +23 -0
  48. package/confirm-dialog.d.ts.map +1 -0
  49. package/confirm-dialog.js +67 -0
  50. package/confirm-dialog.js.map +1 -0
  51. package/context-menu.d.ts +47 -0
  52. package/context-menu.d.ts.map +1 -0
  53. package/context-menu.js +90 -0
  54. package/context-menu.js.map +1 -0
  55. package/dialog.d.ts +25 -0
  56. package/dialog.d.ts.map +1 -0
  57. package/dialog.js +89 -0
  58. package/dialog.js.map +1 -0
  59. package/document-title.d.ts +11 -0
  60. package/document-title.d.ts.map +1 -0
  61. package/document-title.js +17 -0
  62. package/document-title.js.map +1 -0
  63. package/drawer.d.ts +63 -0
  64. package/drawer.d.ts.map +1 -0
  65. package/drawer.js +188 -0
  66. package/drawer.js.map +1 -0
  67. package/empty-state.d.ts +10 -0
  68. package/empty-state.d.ts.map +1 -0
  69. package/empty-state.js +7 -0
  70. package/empty-state.js.map +1 -0
  71. package/file-uploader.d.ts +67 -0
  72. package/file-uploader.d.ts.map +1 -0
  73. package/file-uploader.js +156 -0
  74. package/file-uploader.js.map +1 -0
  75. package/filter-chips.d.ts +47 -0
  76. package/filter-chips.d.ts.map +1 -0
  77. package/filter-chips.js +62 -0
  78. package/filter-chips.js.map +1 -0
  79. package/global-search.d.ts +41 -0
  80. package/global-search.d.ts.map +1 -0
  81. package/global-search.js +116 -0
  82. package/global-search.js.map +1 -0
  83. package/index.d.ts +70 -0
  84. package/index.d.ts.map +1 -0
  85. package/index.js +70 -0
  86. package/index.js.map +1 -0
  87. package/input.d.ts +9 -0
  88. package/input.d.ts.map +1 -0
  89. package/input.js +11 -0
  90. package/input.js.map +1 -0
  91. package/label.d.ts +3 -0
  92. package/label.d.ts.map +1 -0
  93. package/label.js +6 -0
  94. package/label.js.map +1 -0
  95. package/line-grid.d.ts +48 -0
  96. package/line-grid.d.ts.map +1 -0
  97. package/line-grid.js +155 -0
  98. package/line-grid.js.map +1 -0
  99. package/link-context.d.ts +44 -0
  100. package/link-context.d.ts.map +1 -0
  101. package/link-context.js +32 -0
  102. package/link-context.js.map +1 -0
  103. package/list-nav.d.ts +28 -0
  104. package/list-nav.d.ts.map +1 -0
  105. package/list-nav.js +41 -0
  106. package/list-nav.js.map +1 -0
  107. package/list-pagination.d.ts +27 -0
  108. package/list-pagination.d.ts.map +1 -0
  109. package/list-pagination.js +43 -0
  110. package/list-pagination.js.map +1 -0
  111. package/list-params.d.ts +41 -0
  112. package/list-params.d.ts.map +1 -0
  113. package/list-params.js +84 -0
  114. package/list-params.js.map +1 -0
  115. package/list-search-input-state.d.ts +13 -0
  116. package/list-search-input-state.d.ts.map +1 -0
  117. package/list-search-input-state.js +27 -0
  118. package/list-search-input-state.js.map +1 -0
  119. package/list-search-input.d.ts +16 -0
  120. package/list-search-input.d.ts.map +1 -0
  121. package/list-search-input.js +51 -0
  122. package/list-search-input.js.map +1 -0
  123. package/mobile-tab-bar.d.ts +14 -0
  124. package/mobile-tab-bar.d.ts.map +1 -0
  125. package/mobile-tab-bar.js +25 -0
  126. package/mobile-tab-bar.js.map +1 -0
  127. package/multi-select.d.ts +32 -0
  128. package/multi-select.d.ts.map +1 -0
  129. package/multi-select.js +52 -0
  130. package/multi-select.js.map +1 -0
  131. package/nav-overflow.d.ts +12 -0
  132. package/nav-overflow.d.ts.map +1 -0
  133. package/nav-overflow.js +30 -0
  134. package/nav-overflow.js.map +1 -0
  135. package/navigation-config-editor.d.ts +26 -0
  136. package/navigation-config-editor.d.ts.map +1 -0
  137. package/navigation-config-editor.js +57 -0
  138. package/navigation-config-editor.js.map +1 -0
  139. package/navigation-config.d.ts +48 -0
  140. package/navigation-config.d.ts.map +1 -0
  141. package/navigation-config.js +75 -0
  142. package/navigation-config.js.map +1 -0
  143. package/navigation-mode.d.ts +16 -0
  144. package/navigation-mode.d.ts.map +1 -0
  145. package/navigation-mode.js +30 -0
  146. package/navigation-mode.js.map +1 -0
  147. package/notifications-bell.d.ts +22 -0
  148. package/notifications-bell.d.ts.map +1 -0
  149. package/notifications-bell.js +30 -0
  150. package/notifications-bell.js.map +1 -0
  151. package/org-chart.d.ts +85 -0
  152. package/org-chart.d.ts.map +1 -0
  153. package/org-chart.js +193 -0
  154. package/org-chart.js.map +1 -0
  155. package/package.json +61 -0
  156. package/page-header.d.ts +32 -0
  157. package/page-header.d.ts.map +1 -0
  158. package/page-header.js +14 -0
  159. package/page-header.js.map +1 -0
  160. package/page-layout.d.ts +29 -0
  161. package/page-layout.d.ts.map +1 -0
  162. package/page-layout.js +23 -0
  163. package/page-layout.js.map +1 -0
  164. package/page-transition.d.ts +13 -0
  165. package/page-transition.d.ts.map +1 -0
  166. package/page-transition.js +14 -0
  167. package/page-transition.js.map +1 -0
  168. package/paged-table.d.ts +54 -0
  169. package/paged-table.d.ts.map +1 -0
  170. package/paged-table.js +74 -0
  171. package/paged-table.js.map +1 -0
  172. package/popover.d.ts +16 -0
  173. package/popover.d.ts.map +1 -0
  174. package/popover.js +99 -0
  175. package/popover.js.map +1 -0
  176. package/progress.d.ts +7 -0
  177. package/progress.d.ts.map +1 -0
  178. package/progress.js +8 -0
  179. package/progress.js.map +1 -0
  180. package/prompt-dialog.d.ts +21 -0
  181. package/prompt-dialog.d.ts.map +1 -0
  182. package/prompt-dialog.js +109 -0
  183. package/prompt-dialog.js.map +1 -0
  184. package/record-list.d.ts +76 -0
  185. package/record-list.d.ts.map +1 -0
  186. package/record-list.js +51 -0
  187. package/record-list.js.map +1 -0
  188. package/search-select.d.ts +42 -0
  189. package/search-select.d.ts.map +1 -0
  190. package/search-select.js +194 -0
  191. package/search-select.js.map +1 -0
  192. package/select-options.d.ts +16 -0
  193. package/select-options.d.ts.map +1 -0
  194. package/select-options.js +119 -0
  195. package/select-options.js.map +1 -0
  196. package/select.d.ts +24 -0
  197. package/select.d.ts.map +1 -0
  198. package/select.js +65 -0
  199. package/select.js.map +1 -0
  200. package/separator.d.ts +6 -0
  201. package/separator.d.ts.map +1 -0
  202. package/separator.js +7 -0
  203. package/separator.js.map +1 -0
  204. package/settings-layout.d.ts +79 -0
  205. package/settings-layout.d.ts.map +1 -0
  206. package/settings-layout.js +74 -0
  207. package/settings-layout.js.map +1 -0
  208. package/sidebar-nav.d.ts +67 -0
  209. package/sidebar-nav.d.ts.map +1 -0
  210. package/sidebar-nav.js +291 -0
  211. package/sidebar-nav.js.map +1 -0
  212. package/signature-pad.d.ts +16 -0
  213. package/signature-pad.d.ts.map +1 -0
  214. package/signature-pad.js +355 -0
  215. package/signature-pad.js.map +1 -0
  216. package/skeleton.d.ts +9 -0
  217. package/skeleton.d.ts.map +1 -0
  218. package/skeleton.js +12 -0
  219. package/skeleton.js.map +1 -0
  220. package/slider.d.ts +13 -0
  221. package/slider.d.ts.map +1 -0
  222. package/slider.js +15 -0
  223. package/slider.js.map +1 -0
  224. package/sortable-th.d.ts +28 -0
  225. package/sortable-th.d.ts.map +1 -0
  226. package/sortable-th.js +35 -0
  227. package/sortable-th.js.map +1 -0
  228. package/sparkline.d.ts +16 -0
  229. package/sparkline.d.ts.map +1 -0
  230. package/sparkline.js +46 -0
  231. package/sparkline.js.map +1 -0
  232. package/spinner.d.ts +11 -0
  233. package/spinner.d.ts.map +1 -0
  234. package/spinner.js +11 -0
  235. package/spinner.js.map +1 -0
  236. package/styles.css +166 -0
  237. package/subtab-nav.d.ts +17 -0
  238. package/subtab-nav.d.ts.map +1 -0
  239. package/subtab-nav.js +11 -0
  240. package/subtab-nav.js.map +1 -0
  241. package/switch.d.ts +9 -0
  242. package/switch.d.ts.map +1 -0
  243. package/switch.js +11 -0
  244. package/switch.js.map +1 -0
  245. package/tab-content.d.ts +24 -0
  246. package/tab-content.d.ts.map +1 -0
  247. package/tab-content.js +26 -0
  248. package/tab-content.js.map +1 -0
  249. package/table.d.ts +28 -0
  250. package/table.d.ts.map +1 -0
  251. package/table.js +21 -0
  252. package/table.js.map +1 -0
  253. package/tabs.d.ts +18 -0
  254. package/tabs.d.ts.map +1 -0
  255. package/tabs.js +18 -0
  256. package/tabs.js.map +1 -0
  257. package/text-context.d.ts +9 -0
  258. package/text-context.d.ts.map +1 -0
  259. package/text-context.js +12 -0
  260. package/text-context.js.map +1 -0
  261. package/textarea.d.ts +4 -0
  262. package/textarea.d.ts.map +1 -0
  263. package/textarea.js +6 -0
  264. package/textarea.js.map +1 -0
  265. package/theme-script.d.ts +3 -0
  266. package/theme-script.d.ts.map +1 -0
  267. package/theme-script.js +6 -0
  268. package/theme-script.js.map +1 -0
  269. package/theme.d.ts +28 -0
  270. package/theme.d.ts.map +1 -0
  271. package/theme.js +92 -0
  272. package/theme.js.map +1 -0
  273. package/toast.d.ts +52 -0
  274. package/toast.d.ts.map +1 -0
  275. package/toast.js +178 -0
  276. package/toast.js.map +1 -0
  277. package/tooltip.d.ts +12 -0
  278. package/tooltip.d.ts.map +1 -0
  279. package/tooltip.js +14 -0
  280. package/tooltip.js.map +1 -0
  281. package/top-nav.d.ts +18 -0
  282. package/top-nav.d.ts.map +1 -0
  283. package/top-nav.js +160 -0
  284. package/top-nav.js.map +1 -0
  285. package/upload-limits.d.ts +4 -0
  286. package/upload-limits.d.ts.map +1 -0
  287. package/upload-limits.js +21 -0
  288. package/upload-limits.js.map +1 -0
  289. package/use-element-size.d.ts +7 -0
  290. package/use-element-size.d.ts.map +1 -0
  291. package/use-element-size.js +21 -0
  292. package/use-element-size.js.map +1 -0
  293. package/utils.d.ts +4 -0
  294. package/utils.d.ts.map +1 -0
  295. package/utils.js +7 -0
  296. package/utils.js.map +1 -0
  297. package/workspace-launcher.d.ts +50 -0
  298. package/workspace-launcher.d.ts.map +1 -0
  299. package/workspace-launcher.js +127 -0
  300. package/workspace-launcher.js.map +1 -0
package/drawer.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drawer.js","sourceRoot":"","sources":["../src/drawer.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACzE,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAA;AAmB5B,MAAM,UAAU,GAA+B;IAC7C,EAAE,EAAE,oBAAoB;IACxB,EAAE,EAAE,oBAAoB;IACxB,EAAE,EAAE,qBAAqB;IACzB,EAAE,EAAE,qBAAqB;IACzB,KAAK,EAAE,qBAAqB;IAC5B,IAAI,EAAE,QAAQ;CACf,CAAA;AAcD,MAAM,mBAAmB,GAAe;IACtC,KAAK,EAAE,OAAO;IACd,UAAU,EAAE,YAAY;IACxB,cAAc,EAAE,iBAAiB;CAClC,CAAA;AAED,MAAM,iBAAiB,GAAG,KAAK,CAAC,aAAa,CAAa,mBAAmB,CAAC,CAAA;AAE9E,MAAM,UAAU,kBAAkB,CAAC,EACjC,KAAK,EACL,QAAQ,GAIT;IACC,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,mBAAmB,EAAE,GAAG,KAAK,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;IACnF,OAAO,KAAC,iBAAiB,CAAC,QAAQ,IAAC,KAAK,EAAE,MAAM,YAAG,QAAQ,GAA8B,CAAA;AAC3F,CAAC;AAED,SAAS,aAAa;IACpB,OAAO,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAA;AAC5C,CAAC;AAED,8EAA8E;AAC9E,4CAA4C;AAC5C,IAAI,eAAe,GAAG,CAAC,CAAA;AACvB,IAAI,oBAAoB,GAAkB,IAAI,CAAA;AAE9C,MAAM,SAAS,GACb,2IAA2I,CAAA;AA6B7I;;;;;GAKG;AACH,MAAM,UAAU,MAAM,CAAC,EACrB,IAAI,EACJ,OAAO,EACP,KAAK,EACL,WAAW,EACX,IAAI,GAAG,IAAI,EACX,IAAI,GAAG,OAAO,EACd,QAAQ,EACR,MAAM,EACN,aAAa,EACb,OAAO,EACP,aAAa,EACb,cAAc,EACd,OAAO,GAAG,KAAK,EACf,iBAAiB,GAAG,KAAK,EACzB,iBAAiB,GAAG,KAAK,EACzB,cAAc,GACF;IACZ,MAAM,IAAI,GAAG,aAAa,EAAE,CAAA;IAC5B,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;IAC/C,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAA;IACjC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IACnD,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAA;IAE3C,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAA;IACrE,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,CAAC,IAAI;YAAE,aAAa,CAAC,iBAAiB,CAAC,CAAA;IAC7C,CAAC,EAAE,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAAC,CAAA;IAE7B,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAc,IAAI,CAAC,CAAA;IAChD,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACxC,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,UAAU,CAAC,OAAO,GAAG,OAAO,CAAA;IAC9B,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;IAEb,6EAA6E;IAC7E,0CAA0C;IAC1C,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,SAAS,KAAK,CAAC,CAAgB;YAC7B,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ;gBAAE,OAAM;YAC9B,IAAI,QAAQ,CAAC,aAAa,CAAC,mBAAmB,CAAC;gBAAE,OAAM;YACvD,IAAI,CAAC,OAAO,IAAI,QAAQ,CAAC,aAAa,CAAC,8BAA8B,CAAC;gBAAE,OAAM;YAC9E,UAAU,CAAC,OAAO,EAAE,CAAA;QACtB,CAAC;QACD,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;QAC3C,IAAI,eAAe,KAAK,CAAC;YAAE,oBAAoB,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAA;QAC9E,eAAe,IAAI,CAAC,CAAA;QACpB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAA;QACvC,OAAO,GAAG,EAAE;YACV,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;YAC9C,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,eAAe,GAAG,CAAC,CAAC,CAAA;YAClD,IAAI,eAAe,KAAK,CAAC,EAAE,CAAC;gBAC1B,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,oBAAoB,IAAI,EAAE,CAAA;gBACzD,oBAAoB,GAAG,IAAI,CAAA;YAC7B,CAAC;QACH,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAA;IAEnB,uEAAuE;IACvE,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,CAAC,IAAI;YAAE,OAAM;QACjB,MAAM,iBAAiB,GAAG,QAAQ,CAAC,aAAmC,CAAA;QACtE,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE;YACxC,IAAI,CAAC,OAAO,IAAI,QAAQ,CAAC,aAAa,CAAC,8BAA8B,CAAC;gBAAE,OAAM;YAC9E,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAA;YAC9B,IAAI,CAAC,KAAK;gBAAE,OAAM;YAClB,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC;gBAAE,OAAM;YAClD,MAAM,SAAS,GAAG,KAAK,CAAC,aAAa,CAAc,aAAa,CAAC,CAAA;YACjE,MAAM,KAAK,GAAG,KAAK,CAAC,aAAa,CAAc,SAAS,CAAC,CACxD;YAAA,CAAC,SAAS,IAAI,KAAK,IAAI,KAAK,CAAC,CAAC,KAAK,EAAE,CAAA;QACxC,CAAC,EAAE,CAAC,CAAC,CAAA;QAEL,SAAS,SAAS,CAAC,CAAgB;YACjC,IAAI,CAAC,CAAC,GAAG,KAAK,KAAK;gBAAE,OAAM;YAC3B,IAAI,CAAC,OAAO,IAAI,QAAQ,CAAC,aAAa,CAAC,8BAA8B,CAAC;gBAAE,OAAM;YAC9E,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAA;YAC9B,IAAI,CAAC,KAAK;gBAAE,OAAM;YAClB,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAc,SAAS,CAAC,CAAC,CAAC,MAAM,CAClF,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,YAAY,KAAK,IAAI,IAAI,EAAE,KAAK,QAAQ,CAAC,aAAa,CAClE,CAAA;YACD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC5B,CAAC,CAAC,cAAc,EAAE,CAAA;gBAClB,KAAK,CAAC,KAAK,EAAE,CAAA;gBACb,OAAM;YACR,CAAC;YACD,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,CAAE,CAAA;YAC9B,MAAM,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAE,CAAA;YACjD,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAA;YACvC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;gBACf,IAAI,QAAQ,KAAK,OAAO,IAAI,QAAQ,KAAK,KAAK,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAC5E,CAAC,CAAC,cAAc,EAAE,CAAA;oBAClB,MAAM,CAAC,KAAK,EAAE,CAAA;gBAChB,CAAC;YACH,CAAC;iBAAM,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;gBAC/B,CAAC,CAAC,cAAc,EAAE,CAAA;gBAClB,OAAO,CAAC,KAAK,EAAE,CAAA;YACjB,CAAC;QACH,CAAC;QAED,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;QAC/C,OAAO,GAAG,EAAE;YACV,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,CAAA;YAC/B,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;YAClD,IAAI,iBAAiB,IAAI,QAAQ,CAAC,QAAQ,CAAC,iBAAiB,CAAC;gBAAE,iBAAiB,CAAC,KAAK,EAAE,CAAA;QAC1F,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAA;IAEnB,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO,IAAI,CAAA;IAEhD,MAAM,SAAS,GAAG,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAA;IACpD,MAAM,WAAW,GAAG,MAAM;QACxB,CAAC,CAAC;YACE,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE;YACvB,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE;YACvB,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE;YACpB,UAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;SAC/B;QACH,CAAC,CAAC;YACE,OAAO,EAAE,EAAE,CAAC,EAAE,SAAS,EAAE;YACzB,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE;YACjB,IAAI,EAAE,EAAE,CAAC,EAAE,SAAS,EAAE;YACtB,UAAU,EAAE,EAAE,IAAI,EAAE,QAAiB,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE;SAChF,CAAA;IAEL,OAAO,YAAY,CACjB,KAAC,eAAe,IAAC,cAAc,EAAE,cAAc,YAC5C,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,CACjB,oCAEqB,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAC9C,SAAS,EAAE,EAAE,CACX,sDAAsD,EACtD,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAC5B;YACD,kEAAkE;YAClE,kEAAkE;YAClE,+DAA+D;YAC/D,kEAAkE;YAClE,iCAAiC;YACjC,KAAK,EAAE,EAAE,kBAAkB,EAAE,OAAO,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,eAAe,EAAE,aAEjF,KAAC,MAAM,CAAC,GAAG,IACT,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,EACvB,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,EACvB,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,EACpB,UAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAC9B,SAAS,EAAC,oDAAoD,EAC9D,OAAO,EAAE,OAAO,iBACJ,MAAM,GAClB,EACF,MAAC,MAAM,CAAC,KAAK,IACX,GAAG,EAAE,QAAQ,EACb,IAAI,EAAC,QAAQ,gBACF,MAAM,EACjB,QAAQ,EAAE,CAAC,CAAC,KACR,WAAW,EACf,SAAS,EAAE,EAAE,CACX,8IAA8I,EAC9I,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,kBAAkB;oBACxD,mEAAmE;oBACnE,gEAAgE;oBAChE,UAAU,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,EACzD,cAAc,CACf,aAEA,KAAK,IAAI,WAAW,IAAI,aAAa,CAAC,CAAC,CAAC,CACvC,kBAAQ,SAAS,EAAC,0EAA0E,aAC1F,eAAK,SAAS,EAAC,qBAAqB,aACjC,KAAK,CAAC,CAAC,CAAC,CACP,aAAI,SAAS,EAAC,0CAA0C,YACrD,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,GAC3C,CACN,CAAC,CAAC,CAAC,IAAI,EACP,WAAW,CAAC,CAAC,CAAC,CACb,YAAG,SAAS,EAAC,uBAAuB,YACjC,OAAO,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,GAC9D,CACL,CAAC,CAAC,CAAC,IAAI,IACJ,EACN,eAAK,SAAS,EAAC,kCAAkC,aAC9C,aAAa,CAAC,CAAC,CAAC,CACf,cAAK,SAAS,EAAC,+CAA+C,YAC3D,aAAa,GACV,CACP,CAAC,CAAC,CAAC,IAAI,EACP,CAAC,iBAAiB,CAAC,CAAC,CAAC,CACpB,iBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EACvC,SAAS,EAAC,uGAAuG,gBACrG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,EAC9D,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,YAExD,UAAU,CAAC,CAAC,CAAC,CACZ,eACE,KAAK,EAAC,IAAI,EACV,MAAM,EAAC,IAAI,EACX,OAAO,EAAC,WAAW,EACnB,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,aAEtB,mBAAU,MAAM,EAAC,kBAAkB,GAAG,EACtC,mBAAU,MAAM,EAAC,kBAAkB,GAAG,EACtC,eAAM,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,GAAG,GAAG,EACvC,eAAM,EAAE,EAAC,GAAG,EAAC,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,IAAI,GAAG,IACnC,CACP,CAAC,CAAC,CAAC,CACF,eACE,KAAK,EAAC,IAAI,EACV,MAAM,EAAC,IAAI,EACX,OAAO,EAAC,WAAW,EACnB,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,aAEtB,mBAAU,MAAM,EAAC,gBAAgB,GAAG,EACpC,mBAAU,MAAM,EAAC,gBAAgB,GAAG,EACpC,eAAM,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,GAAG,EAAC,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,IAAI,GAAG,EACvC,eAAM,EAAE,EAAC,GAAG,EAAC,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,IAAI,GAAG,IACnC,CACP,GACM,CACV,CAAC,CAAC,CAAC,IAAI,EACR,iBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,OAAO,EAChB,SAAS,EAAC,uFAAuF,gBACrF,IAAI,CAAC,KAAK,YAEtB,eACE,KAAK,EAAC,IAAI,EACV,MAAM,EAAC,IAAI,EACX,OAAO,EAAC,WAAW,EACnB,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,aAEtB,eAAM,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,GAAG,EAAC,EAAE,EAAC,GAAG,EAAC,EAAE,EAAC,IAAI,GAAG,EACtC,eAAM,EAAE,EAAC,GAAG,EAAC,EAAE,EAAC,GAAG,EAAC,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,IAAI,GAAG,IAClC,GACC,IACL,IACC,CACV,CAAC,CAAC,CAAC,IAAI,EACP,OAAO,CAAC,CAAC,CAAC,CACT,cAAK,SAAS,EAAC,iDAAiD,YAAE,OAAO,GAAO,CACjF,CAAC,CAAC,CAAC,IAAI,EACR,cACE,SAAS,EAAE,EAAE,CACX,mCAAmC,EACnC,aAAa,IAAI,2BAA2B,CAC7C,YAEA,QAAQ,GACL,EACL,MAAM,CAAC,CAAC,CAAC,CACR,iBAAQ,SAAS,EAAC,mFAAmF,YAClG,MAAM,GACA,CACV,CAAC,CAAC,CAAC,IAAI,IACK,KA3IX,QAAQ,CA4IR,CACP,CAAC,CAAC,CAAC,IAAI,GACQ,EAClB,QAAQ,CAAC,IAAI,CACd,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,CAAC,aAAa,CAAkC,IAAI,CAAC,CAAA;AAO/F;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAkB;IAC9E,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,CAAC,qBAAqB,CAAC,CAAA;IACxD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC5C,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAA;IAE5C,SAAS,SAAS;QAChB,IAAI,OAAO,MAAM,KAAK,WAAW;YAAE,OAAM;QACzC,IAAI,QAAQ;YAAE,QAAQ,CAAC,SAAS,CAAC,CAAA;;YAC5B,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IACxC,CAAC;IAED,OAAO,CACL,KAAC,MAAM,IAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,cAAc,EAAE,SAAS,KAAM,IAAI,YACnF,QAAQ,GACF,CACV,CAAA;AACH,CAAC","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport { AnimatePresence, motion, useReducedMotion } from 'framer-motion'\nimport { cn } from './utils'\n\n// Z-INDEX SCALE (single source of truth for the ecosystem)\n//\n// sidebar : z-10\n// header : z-20\n// sticky-bars : z-30\n// dropdowns : z-40\n// drawer : z-50 (stacked/nested: z-[55])\n// floating UI : z-[60] — Popover, Select dropdown, confirm dialog\n// toast : z-70\n//\n// The drawer's top edge is `--drawer-top` (default 0). An app shell sets it to\n// its header height so drawers sit beneath the persistent header:\n// :root { --drawer-top: 3.5rem; }\n\nexport type DrawerSize = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full'\nexport type DrawerSide = 'left' | 'right'\n\nconst SIZE_CLASS: Record<DrawerSize, string> = {\n sm: 'w-full sm:max-w-md',\n md: 'w-full sm:max-w-xl',\n lg: 'w-full sm:max-w-2xl',\n xl: 'w-full sm:max-w-4xl',\n '2xl': 'w-full sm:max-w-6xl',\n full: 'w-full',\n}\n\n// --- Injectable labels ------------------------------------------------------\n// appkit is i18n-agnostic. Apps wrap their tree in <DrawerTextProvider> to map\n// these to next-intl / their own dictionary; standalone use gets English.\n// `translate` supports message-key titles while remaining a pass-through for\n// raw ReactNode titles.\nexport type DrawerText = {\n close: string\n fullscreen: string\n exitFullscreen: string\n translate?: (key: string) => string\n}\n\nconst DEFAULT_DRAWER_TEXT: DrawerText = {\n close: 'Close',\n fullscreen: 'Fullscreen',\n exitFullscreen: 'Exit fullscreen',\n}\n\nconst DrawerTextContext = React.createContext<DrawerText>(DEFAULT_DRAWER_TEXT)\n\nexport function DrawerTextProvider({\n value,\n children,\n}: {\n value: Partial<DrawerText>\n children: React.ReactNode\n}) {\n const merged = React.useMemo(() => ({ ...DEFAULT_DRAWER_TEXT, ...value }), [value])\n return <DrawerTextContext.Provider value={merged}>{children}</DrawerTextContext.Provider>\n}\n\nfunction useDrawerText(): DrawerText {\n return React.useContext(DrawerTextContext)\n}\n\n// Ref-counted scroll lock — nested/stacked drawers share one lock so the body\n// only unlocks when the last drawer closes.\nlet openDrawerCount = 0\nlet originalBodyOverflow: string | null = null\n\nconst FOCUSABLE =\n 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex=\"-1\"])'\n\nexport type DrawerProps = {\n open: boolean\n onClose: () => void\n title?: React.ReactNode\n description?: React.ReactNode\n size?: DrawerSize\n side?: DrawerSide\n children: React.ReactNode\n footer?: React.ReactNode\n /** Primary action buttons pinned into the header, before the close button. */\n headerActions?: React.ReactNode\n /** Record-detail navigation rendered directly below the title header. */\n subtabs?: React.ReactNode\n /** Override the body wrapper classes (default: scroll + px-6 py-5). */\n bodyClassName?: string\n /** Extra classes for the panel (e.g. record-type tinting). */\n panelClassName?: string\n /** Raise above another open drawer in a deliberate nested flow. */\n stacked?: boolean\n /** Open at full viewport width; the user can still collapse it. */\n initialFullscreen?: boolean\n /** Hide the fullscreen expand/collapse affordance. */\n disableFullscreen?: boolean\n /** Fires after the exit animation completes (UrlDrawer defers navigation). */\n onExitComplete?: () => void\n}\n\n/**\n * Slide-in flyout for sub-entity create/edit and detail views. Portals to body;\n * spring slide-in; backdrop fade+blur; Esc + click-out + ref-counted scroll\n * lock; focus trap + restore; a NetSuite-style expand/collapse to full viewport\n * width (animated via max-width). Fully tokenized and reduced-motion aware.\n */\nexport function Drawer({\n open,\n onClose,\n title,\n description,\n size = 'md',\n side = 'right',\n children,\n footer,\n headerActions,\n subtabs,\n bodyClassName,\n panelClassName,\n stacked = false,\n initialFullscreen = false,\n disableFullscreen = false,\n onExitComplete,\n}: DrawerProps) {\n const text = useDrawerText()\n const tr = text.translate ?? ((s: string) => s)\n const reduce = useReducedMotion()\n const [mounted, setMounted] = React.useState(false)\n React.useEffect(() => setMounted(true), [])\n\n const [fullscreen, setFullscreen] = React.useState(initialFullscreen)\n React.useEffect(() => {\n if (!open) setFullscreen(initialFullscreen)\n }, [initialFullscreen, open])\n\n const panelRef = React.useRef<HTMLElement>(null)\n const onCloseRef = React.useRef(onClose)\n React.useEffect(() => {\n onCloseRef.current = onClose\n }, [onClose])\n\n // Esc + scroll lock. Stacked-aware: a base drawer ignores Esc while a nested\n // drawer or a floating overlay is on top.\n React.useEffect(() => {\n if (!open) return\n function onKey(e: KeyboardEvent) {\n if (e.key !== 'Escape') return\n if (document.querySelector('[data-ui-overlay]')) return\n if (!stacked && document.querySelector('[data-drawer-layer=\"nested\"]')) return\n onCloseRef.current()\n }\n document.addEventListener('keydown', onKey)\n if (openDrawerCount === 0) originalBodyOverflow = document.body.style.overflow\n openDrawerCount += 1\n document.body.style.overflow = 'hidden'\n return () => {\n document.removeEventListener('keydown', onKey)\n openDrawerCount = Math.max(0, openDrawerCount - 1)\n if (openDrawerCount === 0) {\n document.body.style.overflow = originalBodyOverflow ?? ''\n originalBodyOverflow = null\n }\n }\n }, [open, stacked])\n\n // Focus management: move focus in on open, trap Tab, restore on close.\n React.useEffect(() => {\n if (!open) return\n const previouslyFocused = document.activeElement as HTMLElement | null\n const focusTimer = window.setTimeout(() => {\n if (!stacked && document.querySelector('[data-drawer-layer=\"nested\"]')) return\n const panel = panelRef.current\n if (!panel) return\n if (panel.contains(document.activeElement)) return\n const preferred = panel.querySelector<HTMLElement>('[autofocus]')\n const first = panel.querySelector<HTMLElement>(FOCUSABLE)\n ;(preferred ?? first ?? panel).focus()\n }, 0)\n\n function onKeyDown(e: KeyboardEvent) {\n if (e.key !== 'Tab') return\n if (!stacked && document.querySelector('[data-drawer-layer=\"nested\"]')) return\n const panel = panelRef.current\n if (!panel) return\n const focusables = Array.from(panel.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(\n (el) => el.offsetParent !== null || el === document.activeElement,\n )\n if (focusables.length === 0) {\n e.preventDefault()\n panel.focus()\n return\n }\n const firstEl = focusables[0]!\n const lastEl = focusables[focusables.length - 1]!\n const activeEl = document.activeElement\n if (e.shiftKey) {\n if (activeEl === firstEl || activeEl === panel || !panel.contains(activeEl)) {\n e.preventDefault()\n lastEl.focus()\n }\n } else if (activeEl === lastEl) {\n e.preventDefault()\n firstEl.focus()\n }\n }\n\n document.addEventListener('keydown', onKeyDown)\n return () => {\n window.clearTimeout(focusTimer)\n document.removeEventListener('keydown', onKeyDown)\n if (previouslyFocused && document.contains(previouslyFocused)) previouslyFocused.focus()\n }\n }, [open, stacked])\n\n if (typeof document === 'undefined') return null\n\n const offscreen = side === 'left' ? '-100%' : '100%'\n const panelMotion = reduce\n ? {\n initial: { opacity: 0 },\n animate: { opacity: 1 },\n exit: { opacity: 0 },\n transition: { duration: 0.12 },\n }\n : {\n initial: { x: offscreen },\n animate: { x: 0 },\n exit: { x: offscreen },\n transition: { type: 'spring' as const, damping: 32, stiffness: 320, mass: 0.8 },\n }\n\n return createPortal(\n <AnimatePresence onExitComplete={onExitComplete}>\n {mounted && open ? (\n <div\n key=\"drawer\"\n data-drawer-layer={stacked ? 'nested' : 'base'}\n className={cn(\n 'fixed inset-x-0 bottom-0 top-[var(--drawer-top,0px)]',\n stacked ? 'z-[55]' : 'z-50',\n )}\n // Under the View Transitions API (PageTransition), a route change\n // paints the page snapshot in the browser's top layer — above any\n // z-index. Naming the drawer gives it its own top-layer group,\n // stacked over the page group, so the page can never flash across\n // an open drawer mid-navigation.\n style={{ viewTransitionName: stacked ? 'appkit-drawer-nested' : 'appkit-drawer' }}\n >\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.15 }}\n className=\"absolute inset-0 bg-overlay/40 backdrop-blur-[2px]\"\n onClick={onClose}\n aria-hidden=\"true\"\n />\n <motion.aside\n ref={panelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n tabIndex={-1}\n {...panelMotion}\n className={cn(\n 'absolute inset-y-0 flex flex-col overflow-hidden border-t border-border bg-surface shadow-lg transition-[max-width] duration-300 ease-in-out',\n side === 'left' ? 'left-0 border-r' : 'right-0 border-l',\n // Full replacement (not additive): two sm:max-w-* utilities on one\n // element resolve by stylesheet order, so stacking them no-ops.\n fullscreen ? 'w-full sm:max-w-[100vw]' : SIZE_CLASS[size],\n panelClassName,\n )}\n >\n {title || description || headerActions ? (\n <header className=\"flex items-center justify-between gap-4 border-b border-border px-6 py-4\">\n <div className=\"min-w-0 space-y-0.5\">\n {title ? (\n <h2 className=\"truncate text-base font-semibold text-fg\">\n {typeof title === 'string' ? tr(title) : title}\n </h2>\n ) : null}\n {description ? (\n <p className=\"text-sm text-fg-muted\">\n {typeof description === 'string' ? tr(description) : description}\n </p>\n ) : null}\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n {headerActions ? (\n <div className=\"flex flex-wrap items-center justify-end gap-2\">\n {headerActions}\n </div>\n ) : null}\n {!disableFullscreen ? (\n <button\n type=\"button\"\n onClick={() => setFullscreen((f) => !f)}\n className=\"hidden rounded-md p-1.5 text-fg-muted transition-colors hover:bg-surface-hover hover:text-fg sm:block\"\n aria-label={fullscreen ? text.exitFullscreen : text.fullscreen}\n title={fullscreen ? text.exitFullscreen : text.fullscreen}\n >\n {fullscreen ? (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <polyline points=\"4 14 10 14 10 20\" />\n <polyline points=\"20 10 14 10 14 4\" />\n <line x1=\"14\" y1=\"10\" x2=\"21\" y2=\"3\" />\n <line x1=\"3\" y1=\"21\" x2=\"10\" y2=\"14\" />\n </svg>\n ) : (\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <polyline points=\"15 3 21 3 21 9\" />\n <polyline points=\"9 21 3 21 3 15\" />\n <line x1=\"21\" y1=\"3\" x2=\"14\" y2=\"10\" />\n <line x1=\"3\" y1=\"21\" x2=\"10\" y2=\"14\" />\n </svg>\n )}\n </button>\n ) : null}\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-md p-1.5 text-fg-muted transition-colors hover:bg-surface-hover hover:text-fg\"\n aria-label={text.close}\n >\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n </button>\n </div>\n </header>\n ) : null}\n {subtabs ? (\n <div className=\"shrink-0 border-b border-border bg-surface px-6\">{subtabs}</div>\n ) : null}\n <div\n className={cn(\n 'app-scroll min-h-0 flex-1 text-fg',\n bodyClassName ?? 'overflow-y-auto px-6 py-5',\n )}\n >\n {children}\n </div>\n {footer ? (\n <footer className=\"flex items-center justify-end gap-2 border-t border-border bg-bg-subtle px-6 py-3\">\n {footer}\n </footer>\n ) : null}\n </motion.aside>\n </div>\n ) : null}\n </AnimatePresence>,\n document.body,\n )\n}\n\n/**\n * Client-side navigate fn supplied by the host app (e.g. Next's `router.push`)\n * so `UrlDrawer` can close by changing the URL — which re-runs the server\n * component that owns the drawer's `open` state. Falls back to a hard nav.\n */\nexport const DrawerNavigateContext = React.createContext<((href: string) => void) | null>(null)\n\nexport type UrlDrawerProps = Omit<DrawerProps, 'open' | 'onClose' | 'onExitComplete'> & {\n open: boolean\n closeHref: string\n}\n\n/**\n * URL-state drawer for server-rendered pages: `open` derives from a search\n * param, so closing needs a real navigation (deferred until the slide-out\n * finishes, so the exit animation always plays).\n */\nexport function UrlDrawer({ open, closeHref, children, ...rest }: UrlDrawerProps) {\n const navigate = React.useContext(DrawerNavigateContext)\n const [show, setShow] = React.useState(open)\n React.useEffect(() => setShow(open), [open])\n\n function afterExit() {\n if (typeof window === 'undefined') return\n if (navigate) navigate(closeHref)\n else window.location.assign(closeHref)\n }\n\n return (\n <Drawer open={show} onClose={() => setShow(false)} onExitComplete={afterExit} {...rest}>\n {children}\n </Drawer>\n )\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import * as React from 'react';
2
+ export type EmptyStateProps = {
3
+ icon?: React.ReactNode;
4
+ title: string;
5
+ description?: string;
6
+ action?: React.ReactNode;
7
+ className?: string;
8
+ };
9
+ export declare function EmptyState({ icon, title, description, action, className }: EmptyStateProps): React.JSX.Element;
10
+ //# sourceMappingURL=empty-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty-state.d.ts","sourceRoot":"","sources":["../src/empty-state.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,eAAe,qBAoB1F"}
package/empty-state.js ADDED
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import { cn } from './utils.js';
4
+ export function EmptyState({ icon, title, description, action, className }) {
5
+ return (_jsxs("div", { className: cn('reveal flex flex-col items-center justify-center rounded-xl border border-dashed border-border bg-bg-subtle px-8 py-14 text-center', className), children: [icon ? (_jsx("div", { className: "mb-5 flex size-14 items-center justify-center rounded-2xl bg-surface text-fg-subtle shadow-sm ring-1 ring-border [&_svg]:size-7", children: icon })) : null, _jsx("h3", { className: "text-base font-semibold text-fg", children: title }), description ? (_jsx("p", { className: "mt-1.5 max-w-md text-sm leading-relaxed text-fg-muted", children: description })) : null, action ? _jsx("div", { className: "mt-5", children: action }) : null] }));
6
+ }
7
+ //# sourceMappingURL=empty-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty-state.js","sourceRoot":"","sources":["../src/empty-state.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAA;AAU5B,MAAM,UAAU,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAmB;IACzF,OAAO,CACL,eACE,SAAS,EAAE,EAAE,CACX,oIAAoI,EACpI,SAAS,CACV,aAEA,IAAI,CAAC,CAAC,CAAC,CACN,cAAK,SAAS,EAAC,iIAAiI,YAC7I,IAAI,GACD,CACP,CAAC,CAAC,CAAC,IAAI,EACR,aAAI,SAAS,EAAC,iCAAiC,YAAE,KAAK,GAAM,EAC3D,WAAW,CAAC,CAAC,CAAC,CACb,YAAG,SAAS,EAAC,uDAAuD,YAAE,WAAW,GAAK,CACvF,CAAC,CAAC,CAAC,IAAI,EACP,MAAM,CAAC,CAAC,CAAC,cAAK,SAAS,EAAC,MAAM,YAAE,MAAM,GAAO,CAAC,CAAC,CAAC,IAAI,IACjD,CACP,CAAA;AACH,CAAC","sourcesContent":["import * as React from 'react'\nimport { cn } from './utils'\n\nexport type EmptyStateProps = {\n icon?: React.ReactNode\n title: string\n description?: string\n action?: React.ReactNode\n className?: string\n}\n\nexport function EmptyState({ icon, title, description, action, className }: EmptyStateProps) {\n return (\n <div\n className={cn(\n 'reveal flex flex-col items-center justify-center rounded-xl border border-dashed border-border bg-bg-subtle px-8 py-14 text-center',\n className,\n )}\n >\n {icon ? (\n <div className=\"mb-5 flex size-14 items-center justify-center rounded-2xl bg-surface text-fg-subtle shadow-sm ring-1 ring-border [&_svg]:size-7\">\n {icon}\n </div>\n ) : null}\n <h3 className=\"text-base font-semibold text-fg\">{title}</h3>\n {description ? (\n <p className=\"mt-1.5 max-w-md text-sm leading-relaxed text-fg-muted\">{description}</p>\n ) : null}\n {action ? <div className=\"mt-5\">{action}</div> : null}\n </div>\n )\n}\n"]}
@@ -0,0 +1,67 @@
1
+ import { type AttachmentKind } from './upload-limits.js';
2
+ export type UploadRequestResult = {
3
+ ok: true;
4
+ uploadId: string;
5
+ mode: 'single';
6
+ putUrl: string;
7
+ } | {
8
+ ok: true;
9
+ uploadId: string;
10
+ mode: 'multipart';
11
+ multipartUploadId: string;
12
+ partSizeBytes: number;
13
+ partUrls: string[];
14
+ } | {
15
+ ok: false;
16
+ error: string;
17
+ };
18
+ export type FinalizeUploadInput = {
19
+ uploadId: string;
20
+ multipartUploadId?: string;
21
+ };
22
+ export type RequestUploadAction = (input: {
23
+ kind: AttachmentKind;
24
+ filename: string;
25
+ contentType: string;
26
+ sizeBytes: number;
27
+ }) => Promise<UploadRequestResult>;
28
+ export type FinalizeUploadAction = (input: FinalizeUploadInput) => Promise<{
29
+ ok: true;
30
+ attachmentId: string;
31
+ url: string;
32
+ } | {
33
+ ok: false;
34
+ error: string;
35
+ }>;
36
+ export type UploadedFile = {
37
+ attachmentId: string;
38
+ filename: string;
39
+ contentType: string;
40
+ sizeBytes: number;
41
+ kind: AttachmentKind;
42
+ url: string;
43
+ };
44
+ export type FileUploaderProps = {
45
+ requestUploadAction: RequestUploadAction;
46
+ finalizeUploadAction: FinalizeUploadAction;
47
+ /** Called once per successfully uploaded file. */
48
+ onUploaded: (file: UploadedFile) => void;
49
+ /** Restrict the attachment kind tag we send to the server. */
50
+ kind?: AttachmentKind;
51
+ /** HTML accept attribute (e.g. ".pdf,.docx,application/pdf"). */
52
+ accept?: string;
53
+ /** Allow more than one file at a time. */
54
+ multiple?: boolean;
55
+ /** Max size per file in bytes. Defaults per kind (mirrors the server caps). */
56
+ maxSize?: number;
57
+ /** Compact dropzone (no big help text). */
58
+ compact?: boolean;
59
+ className?: string;
60
+ label?: string;
61
+ hint?: string;
62
+ };
63
+ export declare function uploadReservedFile(request: Extract<UploadRequestResult, {
64
+ ok: true;
65
+ }>, file: File, onProgress?: (percent: number) => void): Promise<FinalizeUploadInput>;
66
+ export declare function FileUploader({ requestUploadAction, finalizeUploadAction, onUploaded, kind, accept, multiple, maxSize, compact, className, label, hint, }: FileUploaderProps): import("react").JSX.Element;
67
+ //# sourceMappingURL=file-uploader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-uploader.d.ts","sourceRoot":"","sources":["../src/file-uploader.tsx"],"names":[],"mappings":"AAYA,OAAO,EAAyB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAI5E,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC9D;IACE,EAAE,EAAE,IAAI,CAAA;IACR,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,WAAW,CAAA;IACjB,iBAAiB,EAAE,MAAM,CAAA;IACzB,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB,GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAEhC,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,MAAM,CAAA;IAChB,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE;IACxC,IAAI,EAAE,cAAc,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;CAClB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAA;AAElC,MAAM,MAAM,oBAAoB,GAAG,CACjC,KAAK,EAAE,mBAAmB,KACvB,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAE5F,MAAM,MAAM,YAAY,GAAG;IACzB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,cAAc,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,mBAAmB,EAAE,mBAAmB,CAAA;IACxC,oBAAoB,EAAE,oBAAoB,CAAA;IAC1C,kDAAkD;IAClD,UAAU,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAA;IACxC,8DAA8D;IAC9D,IAAI,CAAC,EAAE,cAAc,CAAA;IACrB,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AA8BD,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,OAAO,CAAC,mBAAmB,EAAE;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,CAAC,EACnD,IAAI,EAAE,IAAI,EACV,UAAU,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAsB,GACtD,OAAO,CAAC,mBAAmB,CAAC,CA6B9B;AAED,wBAAgB,YAAY,CAAC,EAC3B,mBAAmB,EACnB,oBAAoB,EACpB,UAAU,EACV,IAAiB,EACjB,MAAM,EACN,QAAgB,EAChB,OAAO,EACP,OAAe,EACf,SAAS,EACT,KAAK,EACL,IAAI,GACL,EAAE,iBAAiB,+BA0LnB"}
@@ -0,0 +1,156 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // FileUploader — generic drag-and-drop file picker that hands off the
4
+ // pre-signed-upload protocol to the consumer via two callbacks:
5
+ // • requestUploadAction → presigns a single or multipart upload (server action)
6
+ // • finalizeUploadAction → creates the attachment row (server action)
7
+ //
8
+ // On success the uploader calls `onUploaded({ attachmentId, ... })` so the
9
+ // caller can attach the new attachment id to its own entity. Renders progress
10
+ // and per-file error states inline.
11
+ import { useCallback, useRef, useState } from 'react';
12
+ import { defaultMaxUploadBytes } from './upload-limits.js';
13
+ import { useUiText } from './text-context.js';
14
+ import { cn } from './utils.js';
15
+ function uploadWithXhr(args) {
16
+ return new Promise((resolve, reject) => {
17
+ const xhr = new XMLHttpRequest();
18
+ xhr.open('PUT', args.url);
19
+ if (args.contentType)
20
+ xhr.setRequestHeader('Content-Type', args.contentType);
21
+ xhr.upload.onprogress = (event) => args.onProgress(event.loaded);
22
+ xhr.onload = () => {
23
+ if (xhr.status >= 200 && xhr.status < 300)
24
+ resolve();
25
+ else
26
+ reject(new Error('Upload failed.'));
27
+ };
28
+ xhr.onerror = () => reject(new Error('Network error during upload'));
29
+ xhr.send(args.body);
30
+ });
31
+ }
32
+ export async function uploadReservedFile(request, file, onProgress = () => undefined) {
33
+ if (request.mode === 'single') {
34
+ await uploadWithXhr({
35
+ url: request.putUrl,
36
+ body: file,
37
+ contentType: file.type || 'application/octet-stream',
38
+ onProgress: (loaded) => onProgress(Math.round((loaded / file.size) * 100)),
39
+ });
40
+ return { uploadId: request.uploadId };
41
+ }
42
+ const expectedParts = Math.ceil(file.size / request.partSizeBytes);
43
+ if (request.partUrls.length !== expectedParts) {
44
+ throw new Error('Storage returned an invalid multipart upload plan');
45
+ }
46
+ for (const [index, partUrl] of request.partUrls.entries()) {
47
+ const start = index * request.partSizeBytes;
48
+ const end = Math.min(start + request.partSizeBytes, file.size);
49
+ const part = file.slice(start, end, 'application/octet-stream');
50
+ await uploadWithXhr({
51
+ url: partUrl,
52
+ body: part,
53
+ onProgress: (partLoaded) => onProgress(Math.round(((start + partLoaded) / file.size) * 100)),
54
+ });
55
+ }
56
+ return {
57
+ uploadId: request.uploadId,
58
+ multipartUploadId: request.multipartUploadId,
59
+ };
60
+ }
61
+ export function FileUploader({ requestUploadAction, finalizeUploadAction, onUploaded, kind = 'document', accept, multiple = false, maxSize, compact = false, className, label, hint, }) {
62
+ const t = useUiText();
63
+ const effectiveMaxSize = maxSize ?? defaultMaxUploadBytes(kind);
64
+ const inputRef = useRef(null);
65
+ const [items, setItems] = useState([]);
66
+ const [dragOver, setDragOver] = useState(false);
67
+ const upload = useCallback(async (file, id) => {
68
+ const updateItem = (patch) => setItems((prev) => prev.map((i) => (i.id === id ? { ...i, ...patch } : i)));
69
+ if (file.size > effectiveMaxSize) {
70
+ updateItem({
71
+ status: 'error',
72
+ error: t('File exceeds {value0} MB limit', {
73
+ value0: Math.round(effectiveMaxSize / 1024 / 1024),
74
+ }),
75
+ });
76
+ return;
77
+ }
78
+ updateItem({ status: 'uploading', progress: 0 });
79
+ const req = await requestUploadAction({
80
+ kind,
81
+ filename: file.name,
82
+ contentType: file.type || 'application/octet-stream',
83
+ sizeBytes: file.size,
84
+ });
85
+ if (!req.ok) {
86
+ updateItem({ status: 'error', error: req.error });
87
+ return;
88
+ }
89
+ let finalizeInput;
90
+ try {
91
+ finalizeInput = await uploadReservedFile(req, file, (progress) => updateItem({ progress }));
92
+ }
93
+ catch (err) {
94
+ updateItem({
95
+ status: 'error',
96
+ error: err instanceof Error ? t(err.message) : t('Upload failed.'),
97
+ });
98
+ return;
99
+ }
100
+ updateItem({ status: 'finalising', progress: 100 });
101
+ const finalise = await finalizeUploadAction(finalizeInput);
102
+ if (!finalise.ok) {
103
+ updateItem({ status: 'error', error: finalise.error });
104
+ return;
105
+ }
106
+ updateItem({ status: 'done' });
107
+ onUploaded({
108
+ attachmentId: finalise.attachmentId,
109
+ filename: file.name,
110
+ contentType: file.type || 'application/octet-stream',
111
+ sizeBytes: file.size,
112
+ kind,
113
+ url: finalise.url,
114
+ });
115
+ }, [finalizeUploadAction, kind, effectiveMaxSize, onUploaded, requestUploadAction, t]);
116
+ const handleFiles = useCallback((files) => {
117
+ if (!files || files.length === 0)
118
+ return;
119
+ // Respect `multiple`: a single-file uploader queues only the first file
120
+ // even when several are dropped at once.
121
+ const list = multiple ? Array.from(files) : Array.from(files).slice(0, 1);
122
+ const queued = list.map((file) => ({
123
+ id: globalThis.crypto.randomUUID(),
124
+ file,
125
+ status: 'queued',
126
+ }));
127
+ setItems((prev) => [...prev, ...queued]);
128
+ for (const q of queued)
129
+ void upload(q.file, q.id);
130
+ }, [multiple, upload]);
131
+ return (_jsxs("div", { className: cn('space-y-2', className), children: [_jsxs("div", { onDragOver: (e) => {
132
+ e.preventDefault();
133
+ setDragOver(true);
134
+ }, onDragLeave: () => setDragOver(false), onDrop: (e) => {
135
+ e.preventDefault();
136
+ setDragOver(false);
137
+ handleFiles(e.dataTransfer.files);
138
+ }, onClick: () => inputRef.current?.click(), role: "button", tabIndex: 0, onKeyDown: (e) => {
139
+ if (e.key === 'Enter' || e.key === ' ') {
140
+ e.preventDefault();
141
+ inputRef.current?.click();
142
+ }
143
+ }, className: cn('flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed text-center transition-colors', compact ? 'px-3 py-3' : 'px-4 py-6', dragOver
144
+ ? 'border-primary bg-primary-subtle text-primary'
145
+ : 'border-border-strong bg-bg-subtle text-fg-muted hover:border-primary/60 hover:bg-primary-subtle/50'), children: [_jsx("span", { className: cn('font-medium', compact ? 'text-xs' : 'text-sm'), children: t(label ?? 'Drop files here or click to select') }), !compact && hint ? (_jsx("span", { className: "mt-1 text-[11px] text-fg-muted", children: t(hint) })) : null, _jsx("input", { ref: inputRef, type: "file", className: "hidden", accept: accept, multiple: multiple, onChange: (e) => {
146
+ handleFiles(e.currentTarget.files);
147
+ // Reset so re-picking the same file (e.g. retry after an error)
148
+ // still fires a change event.
149
+ e.currentTarget.value = '';
150
+ } })] }), items.length > 0 ? (_jsx("ul", { className: "space-y-1", children: items.map((item) => (_jsxs("li", { className: "flex items-center justify-between gap-3 rounded-md border border-border bg-surface px-3 py-1.5 text-xs ", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "truncate font-medium text-fg", children: item.file.name }), item.status === 'error' ? (_jsx("div", { className: "text-[11px] text-danger", children: item.error ? t(item.error) : null })) : item.status === 'done' ? (_jsx("div", { className: "text-[11px] text-success", children: t('Uploaded') })) : (_jsx("div", { className: "text-[11px] text-fg-muted", children: item.status === 'uploading'
151
+ ? t('Uploading… {value0}%', { value0: item.progress ?? 0 })
152
+ : item.status === 'finalising'
153
+ ? t('Finalising…')
154
+ : t('Queued') })), (item.status === 'uploading' || item.status === 'finalising') && (_jsx("div", { className: "mt-1 h-1 overflow-hidden rounded-full bg-border-subtle", children: _jsx("div", { className: "h-full rounded-full bg-primary transition-[width] duration-200", style: { width: `${item.progress ?? 0}%` } }) }))] }), item.status === 'error' ? (_jsx("button", { type: "button", onClick: () => setItems((prev) => prev.filter((i) => i.id !== item.id)), className: "rounded px-2 py-0.5 text-[11px] font-medium text-danger hover:bg-danger-subtle", children: t('Dismiss') })) : null] }, item.id))) })) : null] }));
155
+ }
156
+ //# sourceMappingURL=file-uploader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-uploader.js","sourceRoot":"","sources":["../src/file-uploader.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\n// FileUploader — generic drag-and-drop file picker that hands off the\n// pre-signed-upload protocol to the consumer via two callbacks:\n// • requestUploadAction → presigns a single or multipart upload (server action)\n// • finalizeUploadAction → creates the attachment row (server action)\n//\n// On success the uploader calls `onUploaded({ attachmentId, ... })` so the\n// caller can attach the new attachment id to its own entity. Renders progress\n// and per-file error states inline.\n\nimport { useCallback, useRef, useState } from 'react'\nimport { defaultMaxUploadBytes, type AttachmentKind } from './upload-limits'\nimport { useUiText } from './text-context'\nimport { cn } from './utils'\n\nexport type UploadRequestResult =\n | { ok: true; uploadId: string; mode: 'single'; putUrl: string }\n | {\n ok: true\n uploadId: string\n mode: 'multipart'\n multipartUploadId: string\n partSizeBytes: number\n partUrls: string[]\n }\n | { ok: false; error: string }\n\nexport type FinalizeUploadInput = {\n uploadId: string\n multipartUploadId?: string\n}\n\nexport type RequestUploadAction = (input: {\n kind: AttachmentKind\n filename: string\n contentType: string\n sizeBytes: number\n}) => Promise<UploadRequestResult>\n\nexport type FinalizeUploadAction = (\n input: FinalizeUploadInput,\n) => Promise<{ ok: true; attachmentId: string; url: string } | { ok: false; error: string }>\n\nexport type UploadedFile = {\n attachmentId: string\n filename: string\n contentType: string\n sizeBytes: number\n kind: AttachmentKind\n url: string\n}\n\nexport type FileUploaderProps = {\n requestUploadAction: RequestUploadAction\n finalizeUploadAction: FinalizeUploadAction\n /** Called once per successfully uploaded file. */\n onUploaded: (file: UploadedFile) => void\n /** Restrict the attachment kind tag we send to the server. */\n kind?: AttachmentKind\n /** HTML accept attribute (e.g. \".pdf,.docx,application/pdf\"). */\n accept?: string\n /** Allow more than one file at a time. */\n multiple?: boolean\n /** Max size per file in bytes. Defaults per kind (mirrors the server caps). */\n maxSize?: number\n /** Compact dropzone (no big help text). */\n compact?: boolean\n className?: string\n label?: string\n hint?: string\n}\n\ntype Item = {\n id: string\n file: File\n status: 'queued' | 'uploading' | 'finalising' | 'done' | 'error'\n error?: string\n progress?: number\n}\n\nfunction uploadWithXhr(args: {\n url: string\n body: Blob\n contentType?: string\n onProgress: (loaded: number) => void\n}): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest()\n xhr.open('PUT', args.url)\n if (args.contentType) xhr.setRequestHeader('Content-Type', args.contentType)\n xhr.upload.onprogress = (event) => args.onProgress(event.loaded)\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) resolve()\n else reject(new Error('Upload failed.'))\n }\n xhr.onerror = () => reject(new Error('Network error during upload'))\n xhr.send(args.body)\n })\n}\n\nexport async function uploadReservedFile(\n request: Extract<UploadRequestResult, { ok: true }>,\n file: File,\n onProgress: (percent: number) => void = () => undefined,\n): Promise<FinalizeUploadInput> {\n if (request.mode === 'single') {\n await uploadWithXhr({\n url: request.putUrl,\n body: file,\n contentType: file.type || 'application/octet-stream',\n onProgress: (loaded) => onProgress(Math.round((loaded / file.size) * 100)),\n })\n return { uploadId: request.uploadId }\n }\n\n const expectedParts = Math.ceil(file.size / request.partSizeBytes)\n if (request.partUrls.length !== expectedParts) {\n throw new Error('Storage returned an invalid multipart upload plan')\n }\n for (const [index, partUrl] of request.partUrls.entries()) {\n const start = index * request.partSizeBytes\n const end = Math.min(start + request.partSizeBytes, file.size)\n const part = file.slice(start, end, 'application/octet-stream')\n await uploadWithXhr({\n url: partUrl,\n body: part,\n onProgress: (partLoaded) => onProgress(Math.round(((start + partLoaded) / file.size) * 100)),\n })\n }\n return {\n uploadId: request.uploadId,\n multipartUploadId: request.multipartUploadId,\n }\n}\n\nexport function FileUploader({\n requestUploadAction,\n finalizeUploadAction,\n onUploaded,\n kind = 'document',\n accept,\n multiple = false,\n maxSize,\n compact = false,\n className,\n label,\n hint,\n}: FileUploaderProps) {\n const t = useUiText()\n const effectiveMaxSize = maxSize ?? defaultMaxUploadBytes(kind)\n const inputRef = useRef<HTMLInputElement>(null)\n const [items, setItems] = useState<Item[]>([])\n const [dragOver, setDragOver] = useState(false)\n\n const upload = useCallback(\n async (file: File, id: string) => {\n const updateItem = (patch: Partial<Item>) =>\n setItems((prev) => prev.map((i) => (i.id === id ? { ...i, ...patch } : i)))\n\n if (file.size > effectiveMaxSize) {\n updateItem({\n status: 'error',\n error: t('File exceeds {value0} MB limit', {\n value0: Math.round(effectiveMaxSize / 1024 / 1024),\n }),\n })\n return\n }\n updateItem({ status: 'uploading', progress: 0 })\n\n const req = await requestUploadAction({\n kind,\n filename: file.name,\n contentType: file.type || 'application/octet-stream',\n sizeBytes: file.size,\n })\n if (!req.ok) {\n updateItem({ status: 'error', error: req.error })\n return\n }\n\n let finalizeInput: FinalizeUploadInput\n try {\n finalizeInput = await uploadReservedFile(req, file, (progress) => updateItem({ progress }))\n } catch (err) {\n updateItem({\n status: 'error',\n error: err instanceof Error ? t(err.message) : t('Upload failed.'),\n })\n return\n }\n\n updateItem({ status: 'finalising', progress: 100 })\n\n const finalise = await finalizeUploadAction(finalizeInput)\n if (!finalise.ok) {\n updateItem({ status: 'error', error: finalise.error })\n return\n }\n\n updateItem({ status: 'done' })\n onUploaded({\n attachmentId: finalise.attachmentId,\n filename: file.name,\n contentType: file.type || 'application/octet-stream',\n sizeBytes: file.size,\n kind,\n url: finalise.url,\n })\n },\n [finalizeUploadAction, kind, effectiveMaxSize, onUploaded, requestUploadAction, t],\n )\n\n const handleFiles = useCallback(\n (files: FileList | null) => {\n if (!files || files.length === 0) return\n // Respect `multiple`: a single-file uploader queues only the first file\n // even when several are dropped at once.\n const list = multiple ? Array.from(files) : Array.from(files).slice(0, 1)\n const queued = list.map<Item>((file) => ({\n id: globalThis.crypto.randomUUID(),\n file,\n status: 'queued',\n }))\n setItems((prev) => [...prev, ...queued])\n for (const q of queued) void upload(q.file, q.id)\n },\n [multiple, upload],\n )\n\n return (\n <div className={cn('space-y-2', className)}>\n <div\n onDragOver={(e) => {\n e.preventDefault()\n setDragOver(true)\n }}\n onDragLeave={() => setDragOver(false)}\n onDrop={(e) => {\n e.preventDefault()\n setDragOver(false)\n handleFiles(e.dataTransfer.files)\n }}\n onClick={() => inputRef.current?.click()}\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n inputRef.current?.click()\n }\n }}\n className={cn(\n 'flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed text-center transition-colors',\n compact ? 'px-3 py-3' : 'px-4 py-6',\n dragOver\n ? 'border-primary bg-primary-subtle text-primary'\n : 'border-border-strong bg-bg-subtle text-fg-muted hover:border-primary/60 hover:bg-primary-subtle/50',\n )}\n >\n <span className={cn('font-medium', compact ? 'text-xs' : 'text-sm')}>\n {t(label ?? 'Drop files here or click to select')}\n </span>\n {!compact && hint ? (\n <span className=\"mt-1 text-[11px] text-fg-muted\">{t(hint)}</span>\n ) : null}\n <input\n ref={inputRef}\n type=\"file\"\n className=\"hidden\"\n accept={accept}\n multiple={multiple}\n onChange={(e) => {\n handleFiles(e.currentTarget.files)\n // Reset so re-picking the same file (e.g. retry after an error)\n // still fires a change event.\n e.currentTarget.value = ''\n }}\n />\n </div>\n\n {items.length > 0 ? (\n <ul className=\"space-y-1\">\n {items.map((item) => (\n <li\n key={item.id}\n className=\"flex items-center justify-between gap-3 rounded-md border border-border bg-surface px-3 py-1.5 text-xs \"\n >\n <div className=\"min-w-0 flex-1\">\n <div className=\"truncate font-medium text-fg\">\n {item.file.name}\n </div>\n {item.status === 'error' ? (\n <div className=\"text-[11px] text-danger\">\n {item.error ? t(item.error) : null}\n </div>\n ) : item.status === 'done' ? (\n <div className=\"text-[11px] text-success\">\n {t('Uploaded')}\n </div>\n ) : (\n <div className=\"text-[11px] text-fg-muted\">\n {item.status === 'uploading'\n ? t('Uploading… {value0}%', { value0: item.progress ?? 0 })\n : item.status === 'finalising'\n ? t('Finalising…')\n : t('Queued')}\n </div>\n )}\n {(item.status === 'uploading' || item.status === 'finalising') && (\n <div className=\"mt-1 h-1 overflow-hidden rounded-full bg-border-subtle\">\n <div\n className=\"h-full rounded-full bg-primary transition-[width] duration-200\"\n style={{ width: `${item.progress ?? 0}%` }}\n />\n </div>\n )}\n </div>\n {item.status === 'error' ? (\n <button\n type=\"button\"\n onClick={() => setItems((prev) => prev.filter((i) => i.id !== item.id))}\n className=\"rounded px-2 py-0.5 text-[11px] font-medium text-danger hover:bg-danger-subtle\"\n >\n {t('Dismiss')}\n </button>\n ) : null}\n </li>\n ))}\n </ul>\n ) : null}\n </div>\n )\n}\n\n"]}
@@ -0,0 +1,47 @@
1
+ import * as React from 'react';
2
+ import { type ListSearchParams } from './list-params.js';
3
+ export type FilterOption = {
4
+ value: string;
5
+ label: string;
6
+ count?: number;
7
+ };
8
+ /**
9
+ * Single-select filter rendered as a compact dropdown button. Collapses a whole
10
+ * row of chips into one pill showing the active selection inline, so several
11
+ * filters + the search box fit one toolbar row. Selecting an option navigates —
12
+ * the param lives in the URL. Tokenized,
13
+ * next Link → soft-nav anchors, i18n → props.)
14
+ */
15
+ export declare function FilterChips({ basePath, currentParams, paramKey, label, options, allLabel, defaultValue, pageParamKey, hideAll, }: {
16
+ basePath: string;
17
+ currentParams: ListSearchParams;
18
+ paramKey: string;
19
+ label: string;
20
+ options: FilterOption[];
21
+ allLabel?: string;
22
+ /** Treated as the active selection while the URL carries no param; picking
23
+ * "All" then navigates to an explicit `all` sentinel. */
24
+ defaultValue?: string;
25
+ /** Pagination parameter reset when this filter changes. */
26
+ pageParamKey?: string;
27
+ /** Hide the generic All option (e.g. sort selectors). */
28
+ hideAll?: boolean;
29
+ }): React.JSX.Element;
30
+ /**
31
+ * Searchable URL-backed filter for long option lists. This preserves the
32
+ * production reference call surface while replacing framework router hooks
33
+ * with AppKit's injectable list-navigation bridge.
34
+ */
35
+ export declare function SearchSelectFilter({ paramKey, label, options, allLabel, pageParamKey, className, }: {
36
+ paramKey: string;
37
+ label: string;
38
+ options: {
39
+ value: string;
40
+ label: string;
41
+ hint?: string;
42
+ }[];
43
+ allLabel?: string;
44
+ pageParamKey?: string;
45
+ className?: string;
46
+ }): React.JSX.Element;
47
+ //# sourceMappingURL=filter-chips.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-chips.d.ts","sourceRoot":"","sources":["../src/filter-chips.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,OAAO,EAAyB,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAI5E,MAAM,MAAM,YAAY,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAE3E;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,aAAa,EACb,QAAQ,EACR,KAAK,EACL,OAAO,EACP,QAAgB,EAChB,YAAY,EACZ,YAAqB,EACrB,OAAe,GAChB,EAAE;IACD,QAAQ,EAAE,MAAM,CAAA;IAChB,aAAa,EAAE,gBAAgB,CAAA;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,YAAY,EAAE,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;8DAC0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,yDAAyD;IACzD,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,qBAiEA;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,KAAK,EACL,OAAO,EACP,QAAgB,EAChB,YAAqB,EACrB,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,qBAmCA"}
@@ -0,0 +1,62 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ import { Check, ChevronDown } from 'lucide-react';
5
+ import { Popover } from './popover.js';
6
+ import { SearchSelect } from './search-select.js';
7
+ import { mergeHref, pickString } from './list-params.js';
8
+ import { useListNav, useListNavClick } from './list-nav.js';
9
+ import { cn } from './utils.js';
10
+ /**
11
+ * Single-select filter rendered as a compact dropdown button. Collapses a whole
12
+ * row of chips into one pill showing the active selection inline, so several
13
+ * filters + the search box fit one toolbar row. Selecting an option navigates —
14
+ * the param lives in the URL. Tokenized,
15
+ * next Link → soft-nav anchors, i18n → props.)
16
+ */
17
+ export function FilterChips({ basePath, currentParams, paramKey, label, options, allLabel = 'All', defaultValue, pageParamKey = 'page', hideAll = false, }) {
18
+ const [open, setOpen] = React.useState(false);
19
+ const raw = pickString(currentParams[paramKey]);
20
+ const current = raw ?? defaultValue;
21
+ const active = options.find((o) => o.value === current);
22
+ const allHref = mergeHref(basePath, currentParams, {
23
+ [paramKey]: defaultValue ? 'all' : undefined,
24
+ [pageParamKey]: 1,
25
+ });
26
+ const allActive = defaultValue ? current === 'all' : !current;
27
+ return (_jsx(Popover, { open: open, onOpenChange: setOpen, align: "start", className: "min-w-[13rem] p-1", trigger: _jsxs("button", { type: "button", "aria-haspopup": "listbox", "aria-expanded": open, onClick: () => setOpen((v) => !v), className: cn('inline-flex h-8 max-w-[16rem] items-center gap-1.5 rounded-md border px-3 text-sm transition-colors', active
28
+ ? 'border-primary/40 bg-primary-subtle text-primary'
29
+ : 'border-border bg-surface text-fg hover:border-border-strong hover:bg-surface-hover'), children: [_jsx("span", { className: cn('shrink-0', active ? 'text-primary/80' : 'text-fg-muted'), children: active ? `${label}:` : label }), active ? _jsx("span", { className: "truncate font-semibold", children: active.label }) : null, _jsx(ChevronDown, { size: 14, className: cn('ml-auto shrink-0 transition-transform', open && 'rotate-180', active ? 'text-primary/70' : 'text-fg-subtle') })] }), children: _jsxs("div", { className: "max-h-72 overflow-auto", role: "listbox", children: [!hideAll ? (_jsx(FilterItem, { href: allHref, active: allActive, onSelect: () => setOpen(false), children: allLabel })) : null, options.map((opt) => (_jsx(FilterItem, { href: mergeHref(basePath, currentParams, { [paramKey]: opt.value, [pageParamKey]: 1 }), active: current === opt.value, count: opt.count, onSelect: () => setOpen(false), children: opt.label }, opt.value)))] }) }));
30
+ }
31
+ /**
32
+ * Searchable URL-backed filter for long option lists. This preserves the
33
+ * production reference call surface while replacing framework router hooks
34
+ * with AppKit's injectable list-navigation bridge.
35
+ */
36
+ export function SearchSelectFilter({ paramKey, label, options, allLabel = 'All', pageParamKey = 'page', className, }) {
37
+ const nav = useListNav();
38
+ const value = React.useMemo(() => new URLSearchParams(nav?.search ?? '').get(paramKey) ?? '', [nav?.search, paramKey]);
39
+ function onChange(nextValue) {
40
+ if (!nav)
41
+ return;
42
+ const next = new URLSearchParams(nav.search);
43
+ if (nextValue)
44
+ next.set(paramKey, nextValue);
45
+ else
46
+ next.delete(paramKey);
47
+ next.delete(pageParamKey);
48
+ const query = next.toString();
49
+ nav.replace(query ? `${nav.pathname}?${query}` : nav.pathname);
50
+ }
51
+ return _jsx(SearchSelect, { value: value, onChange: onChange, options: options, clearable: true, emptyLabel: allLabel, placeholder: label, ariaLabel: label, sheetTitle: label, searchable: true, className: cn('w-52', className), triggerClassName: cn('h-8 rounded-md px-3 shadow-none', value
52
+ ? 'border-primary/40 bg-primary-subtle [&>span]:font-semibold [&>span]:text-primary'
53
+ : 'border-border hover:border-border-strong hover:bg-surface-hover [&>span]:text-fg') });
54
+ }
55
+ function FilterItem({ href, active, count, onSelect, children, }) {
56
+ const navClick = useListNavClick(href);
57
+ return (_jsxs("a", { href: href, onClick: (e) => {
58
+ onSelect();
59
+ navClick(e);
60
+ }, role: "option", "aria-selected": active, className: cn('flex items-center gap-2 rounded px-2 py-1.5 text-sm transition-colors', active ? 'bg-primary-subtle font-medium text-primary' : 'text-fg hover:bg-surface-hover'), children: [_jsx(Check, { size: 14, className: cn('shrink-0', active ? 'text-primary' : 'text-transparent') }), _jsx("span", { className: "flex-1 truncate", children: children }), typeof count === 'number' ? (_jsx("span", { className: cn('shrink-0 text-xs tabular-nums', active ? 'text-primary/80' : 'text-fg-subtle'), children: count })) : null] }));
61
+ }
62
+ //# sourceMappingURL=filter-chips.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-chips.js","sourceRoot":"","sources":["../src/filter-chips.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { Check, ChevronDown } from 'lucide-react'\nimport { Popover } from './popover'\nimport { SearchSelect } from './search-select'\nimport { mergeHref, pickString, type ListSearchParams } from './list-params'\nimport { useListNav, useListNavClick } from './list-nav'\nimport { cn } from './utils'\n\nexport type FilterOption = { value: string; label: string; count?: number }\n\n/**\n * Single-select filter rendered as a compact dropdown button. Collapses a whole\n * row of chips into one pill showing the active selection inline, so several\n * filters + the search box fit one toolbar row. Selecting an option navigates —\n * the param lives in the URL. Tokenized,\n * next Link → soft-nav anchors, i18n → props.)\n */\nexport function FilterChips({\n basePath,\n currentParams,\n paramKey,\n label,\n options,\n allLabel = 'All',\n defaultValue,\n pageParamKey = 'page',\n hideAll = false,\n}: {\n basePath: string\n currentParams: ListSearchParams\n paramKey: string\n label: string\n options: FilterOption[]\n allLabel?: string\n /** Treated as the active selection while the URL carries no param; picking\n * \"All\" then navigates to an explicit `all` sentinel. */\n defaultValue?: string\n /** Pagination parameter reset when this filter changes. */\n pageParamKey?: string\n /** Hide the generic All option (e.g. sort selectors). */\n hideAll?: boolean\n}) {\n const [open, setOpen] = React.useState(false)\n const raw = pickString(currentParams[paramKey])\n const current = raw ?? defaultValue\n const active = options.find((o) => o.value === current)\n const allHref = mergeHref(basePath, currentParams, {\n [paramKey]: defaultValue ? 'all' : undefined,\n [pageParamKey]: 1,\n })\n const allActive = defaultValue ? current === 'all' : !current\n\n return (\n <Popover\n open={open}\n onOpenChange={setOpen}\n align=\"start\"\n className=\"min-w-[13rem] p-1\"\n trigger={\n <button\n type=\"button\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n onClick={() => setOpen((v) => !v)}\n className={cn(\n 'inline-flex h-8 max-w-[16rem] items-center gap-1.5 rounded-md border px-3 text-sm transition-colors',\n active\n ? 'border-primary/40 bg-primary-subtle text-primary'\n : 'border-border bg-surface text-fg hover:border-border-strong hover:bg-surface-hover',\n )}\n >\n <span className={cn('shrink-0', active ? 'text-primary/80' : 'text-fg-muted')}>\n {active ? `${label}:` : label}\n </span>\n {active ? <span className=\"truncate font-semibold\">{active.label}</span> : null}\n <ChevronDown\n size={14}\n className={cn(\n 'ml-auto shrink-0 transition-transform',\n open && 'rotate-180',\n active ? 'text-primary/70' : 'text-fg-subtle',\n )}\n />\n </button>\n }\n >\n <div className=\"max-h-72 overflow-auto\" role=\"listbox\">\n {!hideAll ? (\n <FilterItem href={allHref} active={allActive} onSelect={() => setOpen(false)}>\n {allLabel}\n </FilterItem>\n ) : null}\n {options.map((opt) => (\n <FilterItem\n key={opt.value}\n href={mergeHref(basePath, currentParams, { [paramKey]: opt.value, [pageParamKey]: 1 })}\n active={current === opt.value}\n count={opt.count}\n onSelect={() => setOpen(false)}\n >\n {opt.label}\n </FilterItem>\n ))}\n </div>\n </Popover>\n )\n}\n\n/**\n * Searchable URL-backed filter for long option lists. This preserves the\n * production reference call surface while replacing framework router hooks\n * with AppKit's injectable list-navigation bridge.\n */\nexport function SearchSelectFilter({\n paramKey,\n label,\n options,\n allLabel = 'All',\n pageParamKey = 'page',\n className,\n}: {\n paramKey: string\n label: string\n options: { value: string; label: string; hint?: string }[]\n allLabel?: string\n pageParamKey?: string\n className?: string\n}) {\n const nav = useListNav()\n const value = React.useMemo(\n () => new URLSearchParams(nav?.search ?? '').get(paramKey) ?? '',\n [nav?.search, paramKey],\n )\n\n function onChange(nextValue: string) {\n if (!nav) return\n const next = new URLSearchParams(nav.search)\n if (nextValue) next.set(paramKey, nextValue)\n else next.delete(paramKey)\n next.delete(pageParamKey)\n const query = next.toString()\n nav.replace(query ? `${nav.pathname}?${query}` : nav.pathname)\n }\n\n return <SearchSelect\n value={value}\n onChange={onChange}\n options={options}\n clearable\n emptyLabel={allLabel}\n placeholder={label}\n ariaLabel={label}\n sheetTitle={label}\n searchable\n className={cn('w-52', className)}\n triggerClassName={cn(\n 'h-8 rounded-md px-3 shadow-none',\n value\n ? 'border-primary/40 bg-primary-subtle [&>span]:font-semibold [&>span]:text-primary'\n : 'border-border hover:border-border-strong hover:bg-surface-hover [&>span]:text-fg',\n )}\n />\n}\n\nfunction FilterItem({\n href,\n active,\n count,\n onSelect,\n children,\n}: {\n href: string\n active: boolean\n count?: number\n onSelect: () => void\n children: React.ReactNode\n}) {\n const navClick = useListNavClick(href)\n return (\n <a\n href={href}\n onClick={(e) => {\n onSelect()\n navClick(e)\n }}\n role=\"option\"\n aria-selected={active}\n className={cn(\n 'flex items-center gap-2 rounded px-2 py-1.5 text-sm transition-colors',\n active ? 'bg-primary-subtle font-medium text-primary' : 'text-fg hover:bg-surface-hover',\n )}\n >\n <Check size={14} className={cn('shrink-0', active ? 'text-primary' : 'text-transparent')} />\n <span className=\"flex-1 truncate\">{children}</span>\n {typeof count === 'number' ? (\n <span className={cn('shrink-0 text-xs tabular-nums', active ? 'text-primary/80' : 'text-fg-subtle')}>\n {count}\n </span>\n ) : null}\n </a>\n )\n}\n"]}
@@ -0,0 +1,41 @@
1
+ import * as React from 'react';
2
+ export type GlobalSearchHit = {
3
+ id: string;
4
+ type: string;
5
+ title: string;
6
+ subtitle?: string;
7
+ href: string;
8
+ iconKey?: string;
9
+ badge?: string;
10
+ meta?: string;
11
+ };
12
+ export type GlobalSearchGroup = {
13
+ id: string;
14
+ label: string;
15
+ hits: GlobalSearchHit[];
16
+ };
17
+ export type GlobalSearchResult = {
18
+ groups: GlobalSearchGroup[];
19
+ total: number;
20
+ };
21
+ export type GlobalSearchLabels = {
22
+ placeholder: string;
23
+ ariaLabel: string;
24
+ clear: string;
25
+ searching: string;
26
+ noMatches: (query: string) => string;
27
+ navigate: string;
28
+ open: string;
29
+ close: string;
30
+ resultCount: (count: number) => string;
31
+ };
32
+ /** Production sibling search interaction with app-owned querying and navigation. */
33
+ export declare function GlobalSearch({ search, onNavigate, className, labels, minimumQueryLength, debounceMs, }: {
34
+ search: (query: string, signal: AbortSignal) => Promise<GlobalSearchResult>;
35
+ onNavigate: (hit: GlobalSearchHit) => void;
36
+ className?: string;
37
+ labels?: GlobalSearchLabels;
38
+ minimumQueryLength?: number;
39
+ debounceMs?: number;
40
+ }): React.JSX.Element;
41
+ //# sourceMappingURL=global-search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"global-search.d.ts","sourceRoot":"","sources":["../src/global-search.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAM9B,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,eAAe,EAAE,CAAA;CAAE,CAAA;AACtF,MAAM,MAAM,kBAAkB,GAAG;IAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAC/E,MAAM,MAAM,kBAAkB,GAAG;IAC/B,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IACpC,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;CACvC,CAAA;AAoBD,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,UAAU,EACV,SAAS,EACT,MAAuB,EACvB,kBAAsB,EACtB,UAAgB,GACjB,EAAE;IACD,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAA;IAC3E,UAAU,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,CAAA;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,kBAAkB,CAAA;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,qBA6FA"}