@eifi1/ui-kit 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/README.md +57 -32
  2. package/dist/chart.d.ts +3 -1
  3. package/dist/chart.js +2 -0
  4. package/dist/chart.js.map +1 -1
  5. package/dist/components/account-settings-labels.d.ts +105 -0
  6. package/dist/components/account-settings-labels.js +81 -0
  7. package/dist/components/account-settings-labels.js.map +1 -0
  8. package/dist/components/account-settings.d.ts +36 -52
  9. package/dist/components/account-settings.js +44 -8
  10. package/dist/components/account-settings.js.map +1 -1
  11. package/dist/components/amount-input.d.ts +24 -4
  12. package/dist/components/amount-input.js +5 -2
  13. package/dist/components/amount-input.js.map +1 -1
  14. package/dist/components/authed-image.d.ts +68 -0
  15. package/dist/components/authed-image.js +117 -0
  16. package/dist/components/authed-image.js.map +1 -0
  17. package/dist/components/breadcrumbs.d.ts +2 -1
  18. package/dist/components/breadcrumbs.js +5 -2
  19. package/dist/components/breadcrumbs.js.map +1 -1
  20. package/dist/components/button-group.d.ts +51 -2
  21. package/dist/components/button-group.js +5 -1
  22. package/dist/components/button-group.js.map +1 -1
  23. package/dist/components/calculator.d.ts +15 -4
  24. package/dist/components/calendar-heatmap.d.ts +41 -3
  25. package/dist/components/calendar-heatmap.js +48 -7
  26. package/dist/components/calendar-heatmap.js.map +1 -1
  27. package/dist/components/chip.d.ts +2 -1
  28. package/dist/components/chip.js +6 -3
  29. package/dist/components/chip.js.map +1 -1
  30. package/dist/components/choice-card.d.ts +35 -3
  31. package/dist/components/choice-card.js +83 -25
  32. package/dist/components/choice-card.js.map +1 -1
  33. package/dist/components/combobox-core.js +2 -0
  34. package/dist/components/combobox-core.js.map +1 -1
  35. package/dist/components/combobox.d.ts +2 -2
  36. package/dist/components/combobox.js +31 -9
  37. package/dist/components/combobox.js.map +1 -1
  38. package/dist/components/copy-button.d.ts +51 -70
  39. package/dist/components/copy-button.js +1 -1
  40. package/dist/components/copy-button.js.map +1 -1
  41. package/dist/components/data-table-cells.d.ts +63 -0
  42. package/dist/components/data-table-cells.js +64 -0
  43. package/dist/components/data-table-cells.js.map +1 -0
  44. package/dist/components/data-table-filter-popover.d.ts +1 -1
  45. package/dist/components/data-table-filters.d.ts +1 -1
  46. package/dist/components/data-table-filters.js +31 -1
  47. package/dist/components/data-table-filters.js.map +1 -1
  48. package/dist/components/data-table-labels.d.ts +13 -0
  49. package/dist/components/data-table-labels.js +8 -1
  50. package/dist/components/data-table-labels.js.map +1 -1
  51. package/dist/components/data-table.d.ts +1 -1
  52. package/dist/components/data-table.js +102 -12
  53. package/dist/components/data-table.js.map +1 -1
  54. package/dist/components/date-picker.d.ts +3 -1
  55. package/dist/components/date-picker.js +15 -2
  56. package/dist/components/date-picker.js.map +1 -1
  57. package/dist/components/description-list.d.ts +23 -3
  58. package/dist/components/description-list.js +23 -3
  59. package/dist/components/description-list.js.map +1 -1
  60. package/dist/components/dialog-frame.d.ts +1 -0
  61. package/dist/components/entity-combobox.d.ts +1 -1
  62. package/dist/components/entity-combobox.js +18 -3
  63. package/dist/components/entity-combobox.js.map +1 -1
  64. package/dist/components/error-boundary.d.ts +92 -0
  65. package/dist/components/error-boundary.js +111 -0
  66. package/dist/components/error-boundary.js.map +1 -0
  67. package/dist/components/field.d.ts +30 -4
  68. package/dist/components/field.js +40 -17
  69. package/dist/components/field.js.map +1 -1
  70. package/dist/components/file-button.d.ts +50 -208
  71. package/dist/components/file-dropzone.d.ts +49 -2
  72. package/dist/components/floating-panel.d.ts +13 -4
  73. package/dist/components/floating-panel.js +7 -3
  74. package/dist/components/floating-panel.js.map +1 -1
  75. package/dist/components/form-actions.d.ts +53 -0
  76. package/dist/components/form-actions.js +98 -0
  77. package/dist/components/form-actions.js.map +1 -0
  78. package/dist/components/image-grid.d.ts +67 -0
  79. package/dist/components/image-grid.js +112 -0
  80. package/dist/components/image-grid.js.map +1 -0
  81. package/dist/components/lightbox.d.ts +104 -0
  82. package/dist/components/lightbox.js +229 -0
  83. package/dist/components/lightbox.js.map +1 -0
  84. package/dist/components/line-items.d.ts +114 -0
  85. package/dist/components/line-items.js +238 -0
  86. package/dist/components/line-items.js.map +1 -0
  87. package/dist/components/list.d.ts +23 -2
  88. package/dist/components/list.js +32 -13
  89. package/dist/components/list.js.map +1 -1
  90. package/dist/components/loading-state.d.ts +24 -0
  91. package/dist/components/loading-state.js +32 -0
  92. package/dist/components/loading-state.js.map +1 -0
  93. package/dist/components/menu-item.d.ts +2 -1
  94. package/dist/components/menu-item.js +5 -1
  95. package/dist/components/menu-item.js.map +1 -1
  96. package/dist/components/mini-calendar.d.ts +13 -1
  97. package/dist/components/mini-calendar.js +46 -2
  98. package/dist/components/mini-calendar.js.map +1 -1
  99. package/dist/components/modal.d.ts +20 -1
  100. package/dist/components/modal.js +26 -2
  101. package/dist/components/modal.js.map +1 -1
  102. package/dist/components/month-picker.d.ts +21 -1
  103. package/dist/components/month-picker.js +139 -26
  104. package/dist/components/month-picker.js.map +1 -1
  105. package/dist/components/multi-entity-combobox.d.ts +1 -1
  106. package/dist/components/multi-entity-combobox.js +19 -3
  107. package/dist/components/multi-entity-combobox.js.map +1 -1
  108. package/dist/components/nav-pills.d.ts +2 -1
  109. package/dist/components/nav-pills.js +5 -1
  110. package/dist/components/nav-pills.js.map +1 -1
  111. package/dist/components/number-field.d.ts +24 -5
  112. package/dist/components/number-field.js +5 -7
  113. package/dist/components/number-field.js.map +1 -1
  114. package/dist/components/number-input.d.ts +25 -5
  115. package/dist/components/number-input.js +7 -1
  116. package/dist/components/number-input.js.map +1 -1
  117. package/dist/components/numpad-sheet.d.ts +15 -4
  118. package/dist/components/passkeys-setting.d.ts +67 -0
  119. package/dist/components/passkeys-setting.js +214 -0
  120. package/dist/components/passkeys-setting.js.map +1 -0
  121. package/dist/components/picker-sheet.js +2 -1
  122. package/dist/components/picker-sheet.js.map +1 -1
  123. package/dist/components/pie-chart-labels.d.ts +20 -0
  124. package/dist/components/pie-chart-labels.js +12 -0
  125. package/dist/components/pie-chart-labels.js.map +1 -0
  126. package/dist/components/pie-chart.d.ts +101 -0
  127. package/dist/components/pie-chart.js +283 -0
  128. package/dist/components/pie-chart.js.map +1 -0
  129. package/dist/components/progress-bar.d.ts +44 -3
  130. package/dist/components/progress-bar.js +58 -22
  131. package/dist/components/progress-bar.js.map +1 -1
  132. package/dist/components/qr-code.d.ts +34 -0
  133. package/dist/components/qr-code.js +57 -0
  134. package/dist/components/qr-code.js.map +1 -0
  135. package/dist/components/search-field.d.ts +15 -3
  136. package/dist/components/search-field.js +5 -1
  137. package/dist/components/search-field.js.map +1 -1
  138. package/dist/components/settings-fields.d.ts +49 -1
  139. package/dist/components/signed-amount.d.ts +121 -0
  140. package/dist/components/signed-amount.js +152 -0
  141. package/dist/components/signed-amount.js.map +1 -0
  142. package/dist/components/skeleton.d.ts +10 -3
  143. package/dist/components/skeleton.js +12 -2
  144. package/dist/components/skeleton.js.map +1 -1
  145. package/dist/components/stat-tile.d.ts +2 -1
  146. package/dist/components/stat-tile.js +5 -2
  147. package/dist/components/stat-tile.js.map +1 -1
  148. package/dist/components/table.d.ts +39 -5
  149. package/dist/components/table.js +45 -11
  150. package/dist/components/table.js.map +1 -1
  151. package/dist/components/text-link.d.ts +176 -0
  152. package/dist/components/text-link.js +109 -0
  153. package/dist/components/text-link.js.map +1 -0
  154. package/dist/components/time-input.d.ts +49 -1
  155. package/dist/components/toggle-group.d.ts +13 -0
  156. package/dist/components/toggle-group.js +35 -6
  157. package/dist/components/toggle-group.js.map +1 -1
  158. package/dist/components/toggle-legend.d.ts +36 -3
  159. package/dist/components/toggle-legend.js +49 -0
  160. package/dist/components/toggle-legend.js.map +1 -1
  161. package/dist/components/ui.d.ts +51 -779
  162. package/dist/components/ui.js +215 -23
  163. package/dist/components/ui.js.map +1 -1
  164. package/dist/components/use-table-state.d.ts +3 -52
  165. package/dist/components/use-table-state.js +128 -30
  166. package/dist/components/use-table-state.js.map +1 -1
  167. package/dist/{data-table-filters-Dh9uF_S-.d.ts → data-table-Drun8Fk9.d.ts} +305 -61
  168. package/dist/data-table.d.ts +2 -1
  169. package/dist/data-table.js +5 -1
  170. package/dist/data-table.js.map +1 -1
  171. package/dist/feedback/feedback-dialog.d.ts +26 -3
  172. package/dist/feedback/feedback-dialog.js +16 -2
  173. package/dist/feedback/feedback-dialog.js.map +1 -1
  174. package/dist/feedback/feedback-thread.d.ts +133 -0
  175. package/dist/feedback/feedback-thread.js +237 -0
  176. package/dist/feedback/feedback-thread.js.map +1 -0
  177. package/dist/feedback.d.ts +2 -1
  178. package/dist/feedback.js +1 -0
  179. package/dist/feedback.js.map +1 -1
  180. package/dist/hooks/use-authed-src.d.ts +71 -0
  181. package/dist/hooks/use-authed-src.js +63 -0
  182. package/dist/hooks/use-authed-src.js.map +1 -0
  183. package/dist/hooks/use-file-drop.d.ts +49 -2
  184. package/dist/hooks/use-hotkey.d.ts +59 -0
  185. package/dist/hooks/use-hotkey.js +105 -0
  186. package/dist/hooks/use-hotkey.js.map +1 -0
  187. package/dist/hooks/use-search-param-state.d.ts +83 -0
  188. package/dist/hooks/use-search-param-state.js +94 -0
  189. package/dist/hooks/use-search-param-state.js.map +1 -0
  190. package/dist/i18n/defaults.d.ts +15 -4
  191. package/dist/i18n/defaults.js +31 -1
  192. package/dist/i18n/defaults.js.map +1 -1
  193. package/dist/i18n/kit-labels.d.ts +46 -312
  194. package/dist/i18n/kit-labels.js +20 -5
  195. package/dist/i18n/kit-labels.js.map +1 -1
  196. package/dist/i18n/locales/de-CH-informal.d.ts +15 -4
  197. package/dist/i18n/locales/de-CH.d.ts +15 -4
  198. package/dist/i18n/locales/de-informal.d.ts +15 -4
  199. package/dist/i18n/locales/de.d.ts +15 -4
  200. package/dist/i18n/locales/de.js +156 -3
  201. package/dist/i18n/locales/de.js.map +1 -1
  202. package/dist/i18n/locales/es.d.ts +15 -4
  203. package/dist/i18n/locales/es.js +153 -3
  204. package/dist/i18n/locales/es.js.map +1 -1
  205. package/dist/i18n/locales/fr.d.ts +15 -4
  206. package/dist/i18n/locales/fr.js +153 -3
  207. package/dist/i18n/locales/fr.js.map +1 -1
  208. package/dist/i18n/locales/hu.d.ts +15 -4
  209. package/dist/i18n/locales/hu.js +153 -3
  210. package/dist/i18n/locales/hu.js.map +1 -1
  211. package/dist/i18n/locales/it.d.ts +15 -4
  212. package/dist/i18n/locales/it.js +153 -3
  213. package/dist/i18n/locales/it.js.map +1 -1
  214. package/dist/i18n/locales/zh.d.ts +15 -4
  215. package/dist/i18n/locales/zh.js +153 -3
  216. package/dist/i18n/locales/zh.js.map +1 -1
  217. package/dist/index.d.ts +37 -17
  218. package/dist/index.js +40 -1
  219. package/dist/index.js.map +1 -1
  220. package/dist/kit-labels-v3biUF1L.d.ts +1645 -0
  221. package/dist/lib/format.d.ts +144 -0
  222. package/dist/lib/format.js +149 -0
  223. package/dist/lib/format.js.map +1 -0
  224. package/dist/lib/qr-encode.d.ts +44 -0
  225. package/dist/lib/qr-encode.js +338 -0
  226. package/dist/lib/qr-encode.js.map +1 -0
  227. package/dist/rhf/fields.d.ts +241 -0
  228. package/dist/rhf/fields.js +601 -0
  229. package/dist/rhf/fields.js.map +1 -0
  230. package/dist/rhf/form.d.ts +49 -1
  231. package/dist/rhf/line-items.d.ts +31 -0
  232. package/dist/rhf/line-items.js +59 -0
  233. package/dist/rhf/line-items.js.map +1 -0
  234. package/dist/rhf.d.ts +61 -1
  235. package/dist/rhf.js +2 -0
  236. package/dist/rhf.js.map +1 -1
  237. package/dist/shell/app-shell.d.ts +99 -2
  238. package/dist/shell/app-shell.js +58 -3
  239. package/dist/shell/app-shell.js.map +1 -1
  240. package/dist/shell/auth-layout.d.ts +74 -0
  241. package/dist/shell/auth-layout.js +97 -0
  242. package/dist/shell/auth-layout.js.map +1 -0
  243. package/dist/shell/top-bar-brand.d.ts +81 -0
  244. package/dist/shell/top-bar-brand.js +47 -0
  245. package/dist/shell/top-bar-brand.js.map +1 -0
  246. package/dist/shell/topbar-action-menu.d.ts +12 -1
  247. package/dist/shell/topbar-action-menu.js +16 -1
  248. package/dist/shell/topbar-action-menu.js.map +1 -1
  249. package/dist/shell.d.ts +56 -1
  250. package/dist/shell.js +2 -0
  251. package/dist/shell.js.map +1 -1
  252. package/dist/wizard/stepper-nav.d.ts +48 -1
  253. package/dist/wizard/wizard-context.d.ts +24 -1
  254. package/dist/wizard/wizard-context.js +19 -2
  255. package/dist/wizard/wizard-context.js.map +1 -1
  256. package/dist/wizard/wizard-step.d.ts +20 -5
  257. package/dist/wizard/wizard-step.js +16 -3
  258. package/dist/wizard/wizard-step.js.map +1 -1
  259. package/dist/wizard.d.ts +50 -3
  260. package/package.json +1 -1
  261. package/src/chart.ts +4 -0
  262. package/src/components/account-settings-labels.ts +190 -0
  263. package/src/components/account-settings.tsx +110 -63
  264. package/src/components/amount-input.tsx +13 -1
  265. package/src/components/authed-image.tsx +190 -0
  266. package/src/components/breadcrumbs.tsx +8 -2
  267. package/src/components/button-group.tsx +7 -2
  268. package/src/components/calendar-heatmap.tsx +105 -10
  269. package/src/components/chip.tsx +8 -4
  270. package/src/components/choice-card.tsx +136 -25
  271. package/src/components/combobox-core.tsx +5 -0
  272. package/src/components/combobox.tsx +31 -9
  273. package/src/components/copy-button.tsx +3 -2
  274. package/src/components/data-table-cells.tsx +119 -0
  275. package/src/components/data-table-filters.ts +77 -0
  276. package/src/components/data-table-labels.ts +23 -0
  277. package/src/components/data-table.tsx +224 -13
  278. package/src/components/date-picker.tsx +19 -3
  279. package/src/components/description-list.tsx +52 -3
  280. package/src/components/entity-combobox.tsx +18 -3
  281. package/src/components/error-boundary.tsx +216 -0
  282. package/src/components/field.tsx +56 -5
  283. package/src/components/floating-panel.tsx +20 -7
  284. package/src/components/form-actions.tsx +191 -0
  285. package/src/components/image-grid.tsx +181 -0
  286. package/src/components/lightbox.tsx +374 -0
  287. package/src/components/line-items.tsx +385 -0
  288. package/src/components/list.tsx +65 -16
  289. package/src/components/loading-state.tsx +47 -0
  290. package/src/components/menu-item.tsx +7 -2
  291. package/src/components/mini-calendar.tsx +72 -3
  292. package/src/components/modal.tsx +61 -3
  293. package/src/components/month-picker.tsx +181 -26
  294. package/src/components/multi-entity-combobox.tsx +19 -3
  295. package/src/components/nav-pills.tsx +8 -1
  296. package/src/components/number-field.tsx +15 -10
  297. package/src/components/number-input.tsx +16 -1
  298. package/src/components/passkeys-setting.tsx +331 -0
  299. package/src/components/picker-sheet.tsx +7 -1
  300. package/src/components/pie-chart-labels.ts +32 -0
  301. package/src/components/pie-chart.tsx +479 -0
  302. package/src/components/progress-bar.tsx +129 -19
  303. package/src/components/qr-code.tsx +83 -0
  304. package/src/components/search-field.tsx +20 -4
  305. package/src/components/signed-amount.tsx +286 -0
  306. package/src/components/skeleton.tsx +21 -3
  307. package/src/components/stat-tile.tsx +8 -2
  308. package/src/components/table.tsx +102 -13
  309. package/src/components/text-link.tsx +248 -0
  310. package/src/components/toggle-group.tsx +58 -10
  311. package/src/components/toggle-legend.tsx +87 -2
  312. package/src/components/ui.tsx +426 -36
  313. package/src/components/use-table-state.ts +265 -33
  314. package/src/data-table.ts +10 -0
  315. package/src/feedback/feedback-dialog.tsx +42 -3
  316. package/src/feedback/feedback-thread.tsx +434 -0
  317. package/src/feedback.ts +1 -0
  318. package/src/hooks/use-authed-src.ts +161 -0
  319. package/src/hooks/use-hotkey.ts +172 -0
  320. package/src/hooks/use-search-param-state.ts +187 -0
  321. package/src/i18n/defaults.ts +30 -0
  322. package/src/i18n/kit-labels.tsx +102 -8
  323. package/src/i18n/locales/de.ts +153 -0
  324. package/src/i18n/locales/es.ts +150 -0
  325. package/src/i18n/locales/fr.ts +153 -0
  326. package/src/i18n/locales/hu.ts +151 -0
  327. package/src/i18n/locales/it.ts +151 -0
  328. package/src/i18n/locales/zh.ts +150 -0
  329. package/src/index.ts +59 -0
  330. package/src/lib/format.ts +325 -0
  331. package/src/lib/qr-encode.ts +420 -0
  332. package/src/rhf/fields.tsx +1054 -0
  333. package/src/rhf/line-items.tsx +130 -0
  334. package/src/rhf.ts +6 -0
  335. package/src/shell/app-shell.tsx +147 -2
  336. package/src/shell/auth-layout.tsx +190 -0
  337. package/src/shell/top-bar-brand.tsx +76 -0
  338. package/src/shell/topbar-action-menu.tsx +21 -0
  339. package/src/shell.ts +2 -0
  340. package/src/wizard/wizard-context.tsx +41 -1
  341. package/src/wizard/wizard-step.tsx +43 -6
  342. package/tokens.css +12 -0
@@ -0,0 +1,53 @@
1
+ import 'react';
2
+ export { N as DEFAULT_FORM_ACTIONS_LABELS, aj as FormActions, ak as FormActionsAlign, al as FormActionsDestructive, am as FormActionsLabels, an as FormActionsPlacement, ao as FormActionsProps } from '../kit-labels-v3biUF1L.js';
3
+ import './data-table-labels.js';
4
+ import './mini-calendar.js';
5
+ import './calendar-heatmap.js';
6
+ import './popover.js';
7
+ import './chip.js';
8
+ import 'lucide-react';
9
+ import './field-sync.js';
10
+ import './tooltip.js';
11
+ import '../hooks/use-anchored-rect.js';
12
+ import '../lib/clipping.js';
13
+ import '../wizard/types.js';
14
+ import '../tour/tour.js';
15
+ import '../search/command-palette.js';
16
+ import '../search/global-search.js';
17
+ import '../search/search-index.js';
18
+ import './month-picker.js';
19
+ import './page-contents.js';
20
+ import './series-chart-labels.js';
21
+ import './pie-chart-labels.js';
22
+ import './sparkline.js';
23
+ import './stat-tile.js';
24
+ import './signature-pad.js';
25
+ import './password-strength.js';
26
+ import './danger-confirm.js';
27
+ import './swatch-picker.js';
28
+ import './tile-radio.js';
29
+ import './icon-picker.js';
30
+ import './dialog-frame.js';
31
+ import './modal.js';
32
+ import '../hooks/use-search-param-state.js';
33
+ import './measured-grid.js';
34
+ import '../feedback/feedback-attachment.js';
35
+ import '../feedback/feedback-dialog.js';
36
+ import '../feedback/feedback-thread.js';
37
+ import '../feedback/feedback-inbox.js';
38
+ import './account-settings-labels.js';
39
+ import './confirm-dialog.js';
40
+ import './floating-panel.js';
41
+ import './bulk-action-bar.js';
42
+ import './list.js';
43
+ import './breadcrumbs.js';
44
+ import './toast.js';
45
+ import './description-list.js';
46
+ import './line-items.js';
47
+ import './progress-bar.js';
48
+ import './signed-amount.js';
49
+ import './error-boundary.js';
50
+ import './authed-image.js';
51
+ import '../hooks/use-authed-src.js';
52
+ import './image-grid.js';
53
+ import './lightbox.js';
@@ -0,0 +1,98 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { isValidElement } from "react";
4
+ import { cn } from "../lib/cn.js";
5
+ import { useKitLabels } from "../i18n/kit-labels.js";
6
+ import { Button, Spinner } from "./ui.js";
7
+ const DEFAULT_FORM_ACTIONS_LABELS = {
8
+ save: "Save",
9
+ cancel: "Cancel"
10
+ };
11
+ const ALIGN_CLASS = {
12
+ start: "justify-start",
13
+ center: "justify-center",
14
+ end: "justify-end",
15
+ between: "justify-between"
16
+ };
17
+ const PLACEMENT_CLASS = {
18
+ inline: "pt-4",
19
+ sticky: "sticky bottom-0 z-10 border-t border-[var(--border)] bg-[var(--bg-surface)] pt-3",
20
+ dialog: ""
21
+ };
22
+ function isDestructiveData(value) {
23
+ return typeof value === "object" && value !== null && !isValidElement(value) && "onClick" in value;
24
+ }
25
+ function FormActions({
26
+ onCancel,
27
+ onSubmit,
28
+ submitLabel,
29
+ cancelLabel,
30
+ pendingLabel,
31
+ pending = false,
32
+ submitDisabled = false,
33
+ submitDisabledReason,
34
+ submitVariant = "brand",
35
+ destructive,
36
+ align,
37
+ placement = "inline",
38
+ form,
39
+ children,
40
+ className,
41
+ style,
42
+ ...rest
43
+ }) {
44
+ const labels = useKitLabels("form", DEFAULT_FORM_ACTIONS_LABELS);
45
+ const hasDestructive = destructive !== void 0 && destructive !== null && destructive !== false;
46
+ const justify = align ?? (hasDestructive ? "between" : "end");
47
+ const start = !hasDestructive ? null : isDestructiveData(destructive) ? /* @__PURE__ */ jsx(
48
+ Button,
49
+ {
50
+ type: "button",
51
+ variant: "ghost",
52
+ tone: "danger",
53
+ onClick: destructive.onClick,
54
+ disabled: destructive.disabled,
55
+ disabledReason: destructive.disabledReason,
56
+ children: destructive.label
57
+ }
58
+ ) : destructive;
59
+ const saveLabel = submitLabel ?? labels.save;
60
+ return /* @__PURE__ */ jsxs(
61
+ "div",
62
+ {
63
+ "data-slot": "form-actions",
64
+ "data-placement": placement,
65
+ ...rest,
66
+ style: placement === "sticky" ? { paddingBottom: "max(0.75rem, env(safe-area-inset-bottom))", ...style } : style,
67
+ className: cn("flex flex-wrap items-center gap-2", ALIGN_CLASS[justify], PLACEMENT_CLASS[placement], className),
68
+ children: [
69
+ start && /* @__PURE__ */ jsx("div", { className: "me-auto flex items-center gap-2", children: start }),
70
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
71
+ children,
72
+ onCancel && /* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", onClick: onCancel, children: cancelLabel ?? labels.cancel }),
73
+ /* @__PURE__ */ jsxs(
74
+ Button,
75
+ {
76
+ type: onSubmit ? "button" : "submit",
77
+ form,
78
+ onClick: onSubmit,
79
+ variant: submitVariant,
80
+ disabled: pending || submitDisabled,
81
+ disabledReason: pending ? void 0 : submitDisabledReason,
82
+ "aria-busy": pending || void 0,
83
+ children: [
84
+ pending && /* @__PURE__ */ jsx(Spinner, { label: null, className: "size-4" }),
85
+ pending && pendingLabel !== void 0 ? pendingLabel : saveLabel
86
+ ]
87
+ }
88
+ )
89
+ ] })
90
+ ]
91
+ }
92
+ );
93
+ }
94
+ export {
95
+ DEFAULT_FORM_ACTIONS_LABELS,
96
+ FormActions
97
+ };
98
+ //# sourceMappingURL=form-actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/form-actions.tsx"],"sourcesContent":["import { isValidElement } from \"react\";\nimport type { ComponentPropsWithoutRef, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { Button, Spinner, type ButtonVariant } from \"./ui\";\n\n/** The words {@link FormActions} renders on its own behalf — the `form` namespace. */\nexport interface FormActionsLabels {\n save: string;\n cancel: string;\n}\n\nexport const DEFAULT_FORM_ACTIONS_LABELS: FormActionsLabels = {\n save: \"Save\",\n cancel: \"Cancel\",\n};\n\nexport type FormActionsAlign = \"start\" | \"center\" | \"end\" | \"between\";\n\n/**\n * Where the row sits:\n * - `inline` (default): under the form's last field, `pt-4` above it.\n * - `sticky`: stuck to the bottom of the scrolling page or pane, on the surface\n * colour with a rule above, clear of the phone's home indicator — for a long form\n * whose Save should not be a scroll away.\n * - `dialog`: no spacing of its own, for a dialog's footer slot (DialogFrame,\n * FullBleedDialog's `footer`), which already pads and rules it.\n */\nexport type FormActionsPlacement = \"inline\" | \"sticky\" | \"dialog\";\n\n/** The start-side action of {@link FormActionsProps.destructive}, as data. */\nexport interface FormActionsDestructive {\n label: ReactNode;\n onClick: () => void;\n disabled?: boolean;\n /** See Button's `disabledReason`. */\n disabledReason?: ReactNode;\n}\n\nconst ALIGN_CLASS: Record<FormActionsAlign, string> = {\n start: \"justify-start\",\n center: \"justify-center\",\n end: \"justify-end\",\n between: \"justify-between\",\n};\n\nconst PLACEMENT_CLASS: Record<FormActionsPlacement, string> = {\n inline: \"pt-4\",\n sticky: \"sticky bottom-0 z-10 border-t border-[var(--border)] bg-[var(--bg-surface)] pt-3\",\n dialog: \"\",\n};\n\nexport interface FormActionsProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"children\"> {\n /** Called by Cancel. Omitted: no Cancel button (a form that only saves). */\n onCancel?: () => void;\n /**\n * Given: the save button is `type=\"button\"` and calls this — for a dialog or a row\n * that saves from a handler. Omitted: it is `type=\"submit\"` and submits the\n * enclosing `<form>` (or the one named by {@link form}).\n */\n onSubmit?: () => void;\n /** The save button's text. Default `form.save` from the provider, else \"Save\". */\n submitLabel?: ReactNode;\n /** The cancel button's text. Default `form.cancel` from the provider, else \"Cancel\". */\n cancelLabel?: ReactNode;\n /** The save button's text while `pending` (\"Saving…\"). Default: `submitLabel`. */\n pendingLabel?: ReactNode;\n /**\n * The save is running: a spinner in the save button, `aria-busy` on it, and the\n * button disabled, so a second click cannot send the form twice. Cancel stays\n * usable.\n */\n pending?: boolean;\n /** Disable save for a reason of the form's own (an unbalanced entry). */\n submitDisabled?: boolean;\n /** Why save is disabled — see Button's `disabledReason`. Keeps it focusable. */\n submitDisabledReason?: ReactNode;\n /** The save button's variant. Default `brand`; `danger` for a save that destroys\n * (kastlan's `destructive` flag on its own FormActions). */\n submitVariant?: ButtonVariant;\n /**\n * A destructive action at the START of the row, apart from Save and Cancel — the\n * \"Delete\" of an edit dialog. Pass `{ label, onClick }` for the kit's quiet danger\n * button, or an element of your own (a {@link DangerConfirm}, say). With it the row\n * is `between`-aligned unless `align` says otherwise.\n */\n destructive?: ReactNode | FormActionsDestructive;\n /** Horizontal alignment. Default `end` (`between` with a `destructive` action). */\n align?: FormActionsAlign;\n /** See {@link FormActionsPlacement}. Default `inline`. */\n placement?: FormActionsPlacement;\n /** The `id` of the `<form>` the save button submits, when the row is rendered\n * outside it (a dialog's footer slot). */\n form?: string;\n /** Extra actions, placed before Cancel. */\n children?: ReactNode;\n}\n\nfunction isDestructiveData(value: unknown): value is FormActionsDestructive {\n return typeof value === \"object\" && value !== null && !isValidElement(value) && \"onClick\" in value;\n}\n\n/**\n * The Save / Cancel row every form, wizard-step form and dialog ends in — kastlan's\n * `FormActions`, with the labels from the provider, a pending state and a place for\n * the destructive action.\n *\n * ```tsx\n * <form onSubmit={form.handleSubmit(save)}>\n * …\n * <FormActions onCancel={close} pending={mutation.isPending} />\n * </form>\n * ```\n *\n * The spinner is the kit's {@link Spinner}, drawn inside the button with `label={null}`\n * so the button's name stays its text; `aria-busy` is what says it is working.\n */\nexport function FormActions({\n onCancel,\n onSubmit,\n submitLabel,\n cancelLabel,\n pendingLabel,\n pending = false,\n submitDisabled = false,\n submitDisabledReason,\n submitVariant = \"brand\",\n destructive,\n align,\n placement = \"inline\",\n form,\n children,\n className,\n style,\n ...rest\n}: FormActionsProps) {\n const labels = useKitLabels(\"form\", DEFAULT_FORM_ACTIONS_LABELS);\n const hasDestructive = destructive !== undefined && destructive !== null && destructive !== false;\n const justify = align ?? (hasDestructive ? \"between\" : \"end\");\n const start = !hasDestructive ? null : isDestructiveData(destructive) ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n tone=\"danger\"\n onClick={destructive.onClick}\n disabled={destructive.disabled}\n disabledReason={destructive.disabledReason}\n >\n {destructive.label}\n </Button>\n ) : (\n destructive\n );\n const saveLabel = submitLabel ?? labels.save;\n\n return (\n <div\n data-slot=\"form-actions\"\n data-placement={placement}\n {...rest}\n style={\n placement === \"sticky\"\n ? { paddingBottom: \"max(0.75rem, env(safe-area-inset-bottom))\", ...style }\n : style\n }\n className={cn(\"flex flex-wrap items-center gap-2\", ALIGN_CLASS[justify], PLACEMENT_CLASS[placement], className)}\n >\n {start && <div className=\"me-auto flex items-center gap-2\">{start}</div>}\n <div className=\"flex flex-wrap items-center gap-2\">\n {children}\n {onCancel && (\n <Button type=\"button\" variant=\"secondary\" onClick={onCancel}>\n {cancelLabel ?? labels.cancel}\n </Button>\n )}\n <Button\n type={onSubmit ? \"button\" : \"submit\"}\n form={form}\n onClick={onSubmit}\n variant={submitVariant}\n disabled={pending || submitDisabled}\n disabledReason={pending ? undefined : submitDisabledReason}\n aria-busy={pending || undefined}\n >\n {pending && <Spinner label={null} className=\"size-4\" />}\n {pending && pendingLabel !== undefined ? pendingLabel : saveLabel}\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":";AA4II,cAmCI,YAnCJ;AA5IJ,SAAS,sBAAsB;AAE/B,SAAS,UAAU;AACnB,SAAS,oBAAoB;AAC7B,SAAS,QAAQ,eAAmC;AAQ7C,MAAM,8BAAiD;AAAA,EAC5D,MAAM;AAAA,EACN,QAAQ;AACV;AAwBA,MAAM,cAAgD;AAAA,EACpD,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,SAAS;AACX;AAEA,MAAM,kBAAwD;AAAA,EAC5D,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AACV;AAgDA,SAAS,kBAAkB,OAAiD;AAC1E,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,eAAe,KAAK,KAAK,aAAa;AAC/F;AAiBO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,iBAAiB;AAAA,EACjB;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAqB;AACnB,QAAM,SAAS,aAAa,QAAQ,2BAA2B;AAC/D,QAAM,iBAAiB,gBAAgB,UAAa,gBAAgB,QAAQ,gBAAgB;AAC5F,QAAM,UAAU,UAAU,iBAAiB,YAAY;AACvD,QAAM,QAAQ,CAAC,iBAAiB,OAAO,kBAAkB,WAAW,IAClE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,SAAQ;AAAA,MACR,MAAK;AAAA,MACL,SAAS,YAAY;AAAA,MACrB,UAAU,YAAY;AAAA,MACtB,gBAAgB,YAAY;AAAA,MAE3B,sBAAY;AAAA;AAAA,EACf,IAEA;AAEF,QAAM,YAAY,eAAe,OAAO;AAExC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,kBAAgB;AAAA,MACf,GAAG;AAAA,MACJ,OACE,cAAc,WACV,EAAE,eAAe,6CAA6C,GAAG,MAAM,IACvE;AAAA,MAEN,WAAW,GAAG,qCAAqC,YAAY,OAAO,GAAG,gBAAgB,SAAS,GAAG,SAAS;AAAA,MAE7G;AAAA,iBAAS,oBAAC,SAAI,WAAU,mCAAmC,iBAAM;AAAA,QAClE,qBAAC,SAAI,WAAU,qCACZ;AAAA;AAAA,UACA,YACC,oBAAC,UAAO,MAAK,UAAS,SAAQ,aAAY,SAAS,UAChD,yBAAe,OAAO,QACzB;AAAA,UAEF;AAAA,YAAC;AAAA;AAAA,cACC,MAAM,WAAW,WAAW;AAAA,cAC5B;AAAA,cACA,SAAS;AAAA,cACT,SAAS;AAAA,cACT,UAAU,WAAW;AAAA,cACrB,gBAAgB,UAAU,SAAY;AAAA,cACtC,aAAW,WAAW;AAAA,cAErB;AAAA,2BAAW,oBAAC,WAAQ,OAAO,MAAM,WAAU,UAAS;AAAA,gBACpD,WAAW,iBAAiB,SAAY,eAAe;AAAA;AAAA;AAAA,UAC1D;AAAA,WACF;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -0,0 +1,67 @@
1
+ import * as react from 'react';
2
+ import { ComponentPropsWithoutRef, ReactNode } from 'react';
3
+ import { AuthedFetcher } from '../hooks/use-authed-src.js';
4
+ import { ImageItem, LightboxLabels } from './lightbox.js';
5
+
6
+ interface ImageGridLabels {
7
+ /** The list's accessible name. */
8
+ list: string;
9
+ /** An open button's name when an item's `alt` is empty: "Image 3 of 7". */
10
+ item: (index: number, count: number) => string;
11
+ /** The name of the group of per-image actions, given the image's name. */
12
+ actions: (name: string) => string;
13
+ }
14
+ declare const DEFAULT_IMAGE_GRID_LABELS: ImageGridLabels;
15
+ interface ImageGridProps extends Omit<ComponentPropsWithoutRef<"ul">, "children"> {
16
+ items: readonly ImageItem[];
17
+ /** The app's authenticated GET — thumbnails and the lightbox fetch through it. Only
18
+ * images are fetched for the grid; a file (PDF) tile shows its icon and name. */
19
+ fetcher?: AuthedFetcher | null;
20
+ /**
21
+ * Per-image actions — a delete, an edit. Rendered BESIDE the open button (on top of
22
+ * the thumbnail's corner), never inside it: a button inside a button is invalid HTML
23
+ * and a screen reader flattens it into the outer one's name, where it cannot be
24
+ * reached. Use `IconButton variant="overlay"` for controls that sit on a photo.
25
+ */
26
+ renderActions?: (item: ImageItem, index: number) => ReactNode;
27
+ /**
28
+ * What a tile's click does. Left out, the grid opens its own {@link Lightbox} on that
29
+ * image. Given, the grid opens nothing — for a caller that keeps the lightbox's
30
+ * index in the URL, or that opens something else.
31
+ */
32
+ onOpen?: (index: number) => void;
33
+ /**
34
+ * A fixed column count. Left out, the grid is responsive: as many columns of at
35
+ * least {@link minTileSize} px as fit, so the same grid is three across in a card on
36
+ * a phone and eight across on a desktop page — no breakpoints to choose.
37
+ */
38
+ columns?: number;
39
+ /** The smallest a tile may be in the responsive layout. Default 96 (px). */
40
+ minTileSize?: number;
41
+ /** `square` (default) crops to a square; `video` to 16:9; `auto` keeps each image's
42
+ * own proportions (a floor plan), within a square-ish box. */
43
+ aspect?: "square" | "video" | "auto";
44
+ /** Props for the built-in lightbox (ignored with {@link onOpen}). */
45
+ lightbox?: {
46
+ loop?: boolean;
47
+ zoom?: boolean;
48
+ download?: boolean;
49
+ onDownload?: (item: ImageItem, index: number) => void;
50
+ labels?: Partial<LightboxLabels>;
51
+ };
52
+ labels?: Partial<ImageGridLabels>;
53
+ }
54
+ /**
55
+ * A grid of thumbnails that open in a {@link Lightbox} — kastlan's room and defect
56
+ * photos (room-inspector, unit-rooms-tab) and its floor plans (unit-floor-plan), each
57
+ * of which drew its own `grid grid-cols-N` of `<img>` that nothing could open, with no
58
+ * loading state and nowhere to put a delete.
59
+ *
60
+ * Each tile is a real `<button>` named by the image's alt, with the actions beside it
61
+ * and the caption below it — outside the button, so the name stays the alt and the
62
+ * caption is read once. Placeholders come from {@link AuthedImage}: a skeleton while
63
+ * loading, a "couldn't load" tile on failure.
64
+ */
65
+ declare function ImageGrid({ items, fetcher, renderActions, onOpen, columns, minTileSize, aspect, lightbox, labels: labelsProp, className, style, ...rest }: ImageGridProps): react.JSX.Element;
66
+
67
+ export { DEFAULT_IMAGE_GRID_LABELS, ImageGrid, type ImageGridLabels, type ImageGridProps };
@@ -0,0 +1,112 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { FileText } from "lucide-react";
5
+ import { cn } from "../lib/cn.js";
6
+ import { useKitLabels } from "../i18n/kit-labels.js";
7
+ import { AuthedImage } from "./authed-image.js";
8
+ import { Lightbox, imageItemKind } from "./lightbox.js";
9
+ const DEFAULT_IMAGE_GRID_LABELS = {
10
+ list: "Images",
11
+ item: (index, count) => `Image ${index} of ${count}`,
12
+ actions: (name) => `Actions for ${name}`
13
+ };
14
+ const ASPECT_CLASS = {
15
+ square: "aspect-square",
16
+ video: "aspect-video",
17
+ auto: "aspect-[4/3]"
18
+ };
19
+ function ImageGrid({
20
+ items,
21
+ fetcher,
22
+ renderActions,
23
+ onOpen,
24
+ columns,
25
+ minTileSize = 96,
26
+ aspect = "square",
27
+ lightbox,
28
+ labels: labelsProp,
29
+ className,
30
+ style,
31
+ ...rest
32
+ }) {
33
+ const labels = useKitLabels("imageGrid", DEFAULT_IMAGE_GRID_LABELS, labelsProp);
34
+ const [openIndex, setOpenIndex] = useState(null);
35
+ const count = items.length;
36
+ const gridStyle = {
37
+ gridTemplateColumns: columns !== void 0 ? `repeat(${columns}, minmax(0, 1fr))` : `repeat(auto-fill, minmax(min(${minTileSize}px, 100%), 1fr))`,
38
+ ...style
39
+ };
40
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
41
+ /* @__PURE__ */ jsx(
42
+ "ul",
43
+ {
44
+ "aria-label": labels.list,
45
+ ...rest,
46
+ className: cn("grid gap-2", className),
47
+ style: gridStyle,
48
+ children: items.map((item, i) => {
49
+ const name = item.alt || labels.item(i + 1, count);
50
+ const kind = imageItemKind(item);
51
+ const actions = renderActions?.(item, i);
52
+ return /* @__PURE__ */ jsxs("li", { className: "relative min-w-0", children: [
53
+ /* @__PURE__ */ jsx(
54
+ "button",
55
+ {
56
+ type: "button",
57
+ "aria-label": name,
58
+ onClick: () => onOpen ? onOpen(i) : setOpenIndex(i),
59
+ className: cn(
60
+ "block w-full overflow-hidden rounded-md border border-[var(--border)] bg-[var(--bg-hover)]",
61
+ "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--brand)]",
62
+ ASPECT_CLASS[aspect]
63
+ ),
64
+ children: kind === "file" ? /* @__PURE__ */ jsxs("span", { className: "flex h-full w-full flex-col items-center justify-center gap-1 p-2 text-center text-xs text-[var(--text-secondary)]", children: [
65
+ /* @__PURE__ */ jsx(FileText, { "aria-hidden": true, className: "size-6 shrink-0 text-[var(--text-muted)]" }),
66
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "line-clamp-2 break-all", children: item.fileName ?? item.alt })
67
+ ] }) : /* @__PURE__ */ jsx(
68
+ AuthedImage,
69
+ {
70
+ src: item.thumbnailSrc ?? item.src,
71
+ alt: "",
72
+ fetcher,
73
+ loading: "lazy",
74
+ wrapperClassName: "h-full w-full",
75
+ className: cn("h-full w-full", aspect === "auto" ? "object-contain" : "object-cover")
76
+ }
77
+ )
78
+ }
79
+ ),
80
+ actions !== void 0 && actions !== null && actions !== false && /* @__PURE__ */ jsx(
81
+ "div",
82
+ {
83
+ role: "group",
84
+ "aria-label": labels.actions(name),
85
+ className: "absolute end-1 top-1 flex gap-1",
86
+ children: actions
87
+ }
88
+ ),
89
+ item.caption !== void 0 && item.caption !== null && item.caption !== false && /* @__PURE__ */ jsx("div", { className: "mt-1 line-clamp-2 text-xs text-[var(--text-secondary)]", children: item.caption })
90
+ ] }, item.key ?? item.src);
91
+ })
92
+ }
93
+ ),
94
+ !onOpen && /* @__PURE__ */ jsx(
95
+ Lightbox,
96
+ {
97
+ open: openIndex !== null,
98
+ onClose: () => setOpenIndex(null),
99
+ items,
100
+ index: openIndex ?? 0,
101
+ onIndexChange: setOpenIndex,
102
+ fetcher,
103
+ ...lightbox
104
+ }
105
+ )
106
+ ] });
107
+ }
108
+ export {
109
+ DEFAULT_IMAGE_GRID_LABELS,
110
+ ImageGrid
111
+ };
112
+ //# sourceMappingURL=image-grid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/image-grid.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ComponentPropsWithoutRef, CSSProperties, ReactNode } from \"react\";\nimport { FileText } from \"lucide-react\";\n\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport type { AuthedFetcher } from \"../hooks/use-authed-src\";\nimport { AuthedImage } from \"./authed-image\";\nimport { Lightbox, imageItemKind } from \"./lightbox\";\nimport type { ImageItem, LightboxLabels } from \"./lightbox\";\n\nexport interface ImageGridLabels {\n /** The list's accessible name. */\n list: string;\n /** An open button's name when an item's `alt` is empty: \"Image 3 of 7\". */\n item: (index: number, count: number) => string;\n /** The name of the group of per-image actions, given the image's name. */\n actions: (name: string) => string;\n}\n\nexport const DEFAULT_IMAGE_GRID_LABELS: ImageGridLabels = {\n list: \"Images\",\n item: (index, count) => `Image ${index} of ${count}`,\n actions: (name) => `Actions for ${name}`,\n};\n\nexport interface ImageGridProps extends Omit<ComponentPropsWithoutRef<\"ul\">, \"children\"> {\n items: readonly ImageItem[];\n /** The app's authenticated GET — thumbnails and the lightbox fetch through it. Only\n * images are fetched for the grid; a file (PDF) tile shows its icon and name. */\n fetcher?: AuthedFetcher | null;\n /**\n * Per-image actions — a delete, an edit. Rendered BESIDE the open button (on top of\n * the thumbnail's corner), never inside it: a button inside a button is invalid HTML\n * and a screen reader flattens it into the outer one's name, where it cannot be\n * reached. Use `IconButton variant=\"overlay\"` for controls that sit on a photo.\n */\n renderActions?: (item: ImageItem, index: number) => ReactNode;\n /**\n * What a tile's click does. Left out, the grid opens its own {@link Lightbox} on that\n * image. Given, the grid opens nothing — for a caller that keeps the lightbox's\n * index in the URL, or that opens something else.\n */\n onOpen?: (index: number) => void;\n /**\n * A fixed column count. Left out, the grid is responsive: as many columns of at\n * least {@link minTileSize} px as fit, so the same grid is three across in a card on\n * a phone and eight across on a desktop page — no breakpoints to choose.\n */\n columns?: number;\n /** The smallest a tile may be in the responsive layout. Default 96 (px). */\n minTileSize?: number;\n /** `square` (default) crops to a square; `video` to 16:9; `auto` keeps each image's\n * own proportions (a floor plan), within a square-ish box. */\n aspect?: \"square\" | \"video\" | \"auto\";\n /** Props for the built-in lightbox (ignored with {@link onOpen}). */\n lightbox?: {\n loop?: boolean;\n zoom?: boolean;\n download?: boolean;\n onDownload?: (item: ImageItem, index: number) => void;\n labels?: Partial<LightboxLabels>;\n };\n labels?: Partial<ImageGridLabels>;\n}\n\nconst ASPECT_CLASS = {\n square: \"aspect-square\",\n video: \"aspect-video\",\n auto: \"aspect-[4/3]\",\n} as const;\n\n/**\n * A grid of thumbnails that open in a {@link Lightbox} — kastlan's room and defect\n * photos (room-inspector, unit-rooms-tab) and its floor plans (unit-floor-plan), each\n * of which drew its own `grid grid-cols-N` of `<img>` that nothing could open, with no\n * loading state and nowhere to put a delete.\n *\n * Each tile is a real `<button>` named by the image's alt, with the actions beside it\n * and the caption below it — outside the button, so the name stays the alt and the\n * caption is read once. Placeholders come from {@link AuthedImage}: a skeleton while\n * loading, a \"couldn't load\" tile on failure.\n */\nexport function ImageGrid({\n items,\n fetcher,\n renderActions,\n onOpen,\n columns,\n minTileSize = 96,\n aspect = \"square\",\n lightbox,\n labels: labelsProp,\n className,\n style,\n ...rest\n}: ImageGridProps) {\n const labels = useKitLabels(\"imageGrid\", DEFAULT_IMAGE_GRID_LABELS, labelsProp);\n const [openIndex, setOpenIndex] = useState<number | null>(null);\n const count = items.length;\n\n const gridStyle: CSSProperties = {\n gridTemplateColumns:\n columns !== undefined\n ? `repeat(${columns}, minmax(0, 1fr))`\n : `repeat(auto-fill, minmax(min(${minTileSize}px, 100%), 1fr))`,\n ...style,\n };\n\n return (\n <>\n <ul\n aria-label={labels.list}\n {...rest}\n className={cn(\"grid gap-2\", className)}\n style={gridStyle}\n >\n {items.map((item, i) => {\n const name = item.alt || labels.item(i + 1, count);\n const kind = imageItemKind(item);\n const actions = renderActions?.(item, i);\n return (\n <li key={item.key ?? item.src} className=\"relative min-w-0\">\n <button\n type=\"button\"\n aria-label={name}\n onClick={() => (onOpen ? onOpen(i) : setOpenIndex(i))}\n className={cn(\n \"block w-full overflow-hidden rounded-md border border-[var(--border)] bg-[var(--bg-hover)]\",\n \"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--brand)]\",\n ASPECT_CLASS[aspect],\n )}\n >\n {kind === \"file\" ? (\n <span className=\"flex h-full w-full flex-col items-center justify-center gap-1 p-2 text-center text-xs text-[var(--text-secondary)]\">\n <FileText aria-hidden className=\"size-6 shrink-0 text-[var(--text-muted)]\" />\n <span aria-hidden className=\"line-clamp-2 break-all\">{item.fileName ?? item.alt}</span>\n </span>\n ) : (\n <AuthedImage\n src={item.thumbnailSrc ?? item.src}\n // The button is named already; the picture inside it is decoration\n // to a reader, or the name would be read twice.\n alt=\"\"\n fetcher={fetcher}\n loading=\"lazy\"\n wrapperClassName=\"h-full w-full\"\n className={cn(\"h-full w-full\", aspect === \"auto\" ? \"object-contain\" : \"object-cover\")}\n />\n )}\n </button>\n {actions !== undefined && actions !== null && actions !== false && (\n <div\n role=\"group\"\n aria-label={labels.actions(name)}\n className=\"absolute end-1 top-1 flex gap-1\"\n >\n {actions}\n </div>\n )}\n {item.caption !== undefined && item.caption !== null && item.caption !== false && (\n <div className=\"mt-1 line-clamp-2 text-xs text-[var(--text-secondary)]\">{item.caption}</div>\n )}\n </li>\n );\n })}\n </ul>\n {!onOpen && (\n <Lightbox\n open={openIndex !== null}\n onClose={() => setOpenIndex(null)}\n items={items}\n index={openIndex ?? 0}\n onIndexChange={setOpenIndex}\n fetcher={fetcher}\n {...lightbox}\n />\n )}\n </>\n );\n}\n"],"mappings":";AA8GI,mBAyBgB,KADF,YAxBd;AA9GJ,SAAS,gBAAgB;AAEzB,SAAS,gBAAgB;AAEzB,SAAS,UAAU;AACnB,SAAS,oBAAoB;AAE7B,SAAS,mBAAmB;AAC5B,SAAS,UAAU,qBAAqB;AAYjC,MAAM,4BAA6C;AAAA,EACxD,MAAM;AAAA,EACN,MAAM,CAAC,OAAO,UAAU,SAAS,KAAK,OAAO,KAAK;AAAA,EAClD,SAAS,CAAC,SAAS,eAAe,IAAI;AACxC;AA0CA,MAAM,eAAe;AAAA,EACnB,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AACR;AAaO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,SAAS;AAAA,EACT;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAmB;AACjB,QAAM,SAAS,aAAa,aAAa,2BAA2B,UAAU;AAC9E,QAAM,CAAC,WAAW,YAAY,IAAI,SAAwB,IAAI;AAC9D,QAAM,QAAQ,MAAM;AAEpB,QAAM,YAA2B;AAAA,IAC/B,qBACE,YAAY,SACR,UAAU,OAAO,sBACjB,gCAAgC,WAAW;AAAA,IACjD,GAAG;AAAA,EACL;AAEA,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,cAAY,OAAO;AAAA,QAClB,GAAG;AAAA,QACJ,WAAW,GAAG,cAAc,SAAS;AAAA,QACrC,OAAO;AAAA,QAEN,gBAAM,IAAI,CAAC,MAAM,MAAM;AACtB,gBAAM,OAAO,KAAK,OAAO,OAAO,KAAK,IAAI,GAAG,KAAK;AACjD,gBAAM,OAAO,cAAc,IAAI;AAC/B,gBAAM,UAAU,gBAAgB,MAAM,CAAC;AACvC,iBACE,qBAAC,QAA8B,WAAU,oBACvC;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,cAAY;AAAA,gBACZ,SAAS,MAAO,SAAS,OAAO,CAAC,IAAI,aAAa,CAAC;AAAA,gBACnD,WAAW;AAAA,kBACT;AAAA,kBACA;AAAA,kBACA,aAAa,MAAM;AAAA,gBACrB;AAAA,gBAEC,mBAAS,SACR,qBAAC,UAAK,WAAU,sHACd;AAAA,sCAAC,YAAS,eAAW,MAAC,WAAU,4CAA2C;AAAA,kBAC3E,oBAAC,UAAK,eAAW,MAAC,WAAU,0BAA0B,eAAK,YAAY,KAAK,KAAI;AAAA,mBAClF,IAEA;AAAA,kBAAC;AAAA;AAAA,oBACC,KAAK,KAAK,gBAAgB,KAAK;AAAA,oBAG/B,KAAI;AAAA,oBACJ;AAAA,oBACA,SAAQ;AAAA,oBACR,kBAAiB;AAAA,oBACjB,WAAW,GAAG,iBAAiB,WAAW,SAAS,mBAAmB,cAAc;AAAA;AAAA,gBACtF;AAAA;AAAA,YAEJ;AAAA,YACC,YAAY,UAAa,YAAY,QAAQ,YAAY,SACxD;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,cAAY,OAAO,QAAQ,IAAI;AAAA,gBAC/B,WAAU;AAAA,gBAET;AAAA;AAAA,YACH;AAAA,YAED,KAAK,YAAY,UAAa,KAAK,YAAY,QAAQ,KAAK,YAAY,SACvE,oBAAC,SAAI,WAAU,0DAA0D,eAAK,SAAQ;AAAA,eAvCjF,KAAK,OAAO,KAAK,GAyC1B;AAAA,QAEJ,CAAC;AAAA;AAAA,IACH;AAAA,IACC,CAAC,UACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,cAAc;AAAA,QACpB,SAAS,MAAM,aAAa,IAAI;AAAA,QAChC;AAAA,QACA,OAAO,aAAa;AAAA,QACpB,eAAe;AAAA,QACf;AAAA,QACC,GAAG;AAAA;AAAA,IACN;AAAA,KAEJ;AAEJ;","names":[]}
@@ -0,0 +1,104 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { AuthedFetcher } from '../hooks/use-authed-src.js';
4
+
5
+ /** `image`: shown as a picture. `file`: anything a browser cannot put in an `<img>` —
6
+ * a floor plan uploaded as a PDF — shown as a file card with open and download. */
7
+ type ImageItemKind = "image" | "file";
8
+ interface ImageItem {
9
+ /** React key. Defaults to `src`. */
10
+ key?: string | number;
11
+ /** The full-size file — what the lightbox shows and downloads. An API path when the
12
+ * component is given a `fetcher`. */
13
+ src: string;
14
+ /** A smaller rendition for the grid, when the server has one. Default: `src`. */
15
+ thumbnailSrc?: string;
16
+ /** Required: the picture's text alternative, and the open button's name. */
17
+ alt: string;
18
+ /** Shown under the thumbnail and at the foot of the lightbox. */
19
+ caption?: ReactNode;
20
+ /** The file's MIME type, when known (`"application/pdf"`). Decides {@link kind}. */
21
+ mimeType?: string;
22
+ /** Say it outright. Default: from {@link mimeType}, else from the file extension in
23
+ * `src`, else — for a fetched file — from the type the server sent back. */
24
+ kind?: ImageItemKind;
25
+ /** The name a download is saved under, and a file card's heading. */
26
+ fileName?: string;
27
+ }
28
+ /** What an item is, from what is known about it WITHOUT fetching it — see
29
+ * {@link ImageItem.kind}. `fetchedType` is the blob's type once it is in. */
30
+ declare function imageItemKind(item: ImageItem, fetchedType?: string): ImageItemKind;
31
+ interface LightboxLabels {
32
+ /** The dialog's accessible name. */
33
+ dialog: string;
34
+ close: string;
35
+ previous: string;
36
+ next: string;
37
+ /** The visible counter: "3 / 7". */
38
+ counter: (index: number, count: number) => string;
39
+ /** What a screen reader hears on each page turn: "Image 3 of 7". The alt follows. */
40
+ position: (index: number, count: number) => string;
41
+ download: string;
42
+ /** The zoom toggle (a pressed/not-pressed button — its name does not change). */
43
+ zoom: string;
44
+ /** A file card's note: the file has no picture to show. */
45
+ noPreview: string;
46
+ /** A file card's link to the browser's own viewer (a PDF opens in its viewer). */
47
+ openInNewTab: string;
48
+ }
49
+ declare const DEFAULT_LIGHTBOX_LABELS: LightboxLabels;
50
+ interface LightboxProps {
51
+ open: boolean;
52
+ /** The X, Escape and the Back gesture all call this. */
53
+ onClose: () => void;
54
+ items: readonly ImageItem[];
55
+ /** The item on screen (0-based). Controlled, so the grid — or a URL — can own it. */
56
+ index: number;
57
+ onIndexChange: (index: number) => void;
58
+ /** The app's authenticated GET (see {@link useAuthedSrc}); left out, `src` is
59
+ * loaded by the browser as usual. */
60
+ fetcher?: AuthedFetcher | null;
61
+ /** Previous from the first goes to the last, and next from the last to the first.
62
+ * Default `false`: the buttons at the ends are disabled, which says where the set
63
+ * ends. */
64
+ loop?: boolean;
65
+ /** Double-click (or double-tap) the picture, or the zoom button, to see it at full
66
+ * size and pan by scrolling. Default `true`. Pinch zoom is the browser's own. */
67
+ zoom?: boolean;
68
+ /** A download link in the header. Default `true`. */
69
+ download?: boolean;
70
+ /**
71
+ * Download through the app instead — kastlan's floor plans have `openDocument(id)`,
72
+ * which knows the stored file name. Replaces the link with a button that calls this.
73
+ */
74
+ onDownload?: (item: ImageItem, index: number) => void;
75
+ /** Push a history entry so Back closes the viewer. Default `true`; see
76
+ * `FullBleedDialog`'s `backCloses` for when to turn it off. */
77
+ backCloses?: boolean;
78
+ labels?: Partial<LightboxLabels>;
79
+ }
80
+ /**
81
+ * A full-screen viewer for a set of images (and files that are not images).
82
+ *
83
+ * Built on {@link FullBleedDialog} rather than beside it, so the viewer has the kit's
84
+ * one implementation of everything an overlay owes the page: the focus trap, Escape,
85
+ * the Back gesture closing it instead of leaving the page (`useOverlayHistory`), the
86
+ * body-scroll lock and the enter/exit motion.
87
+ *
88
+ * Paging: the previous/next buttons, ←/→ (which swap in a right-to-left page — the
89
+ * direction is read at the point the viewer is rendered, since the dialog itself is
90
+ * portalled out of it), Home/End, and a horizontal swipe on a touch screen.
91
+ *
92
+ * Zoom is a TOGGLE, not a gesture: double-click (or the zoom button) shows the picture
93
+ * at its natural size and the body scrolls to pan. Pinch-to-zoom is not implemented —
94
+ * the browser's own page pinch still works — because a pinch handler would have to own
95
+ * every touch on the picture, and with it the swipe.
96
+ *
97
+ * A non-image (a PDF floor plan) is shown as a file card: its name, "no preview", a
98
+ * link that opens it in the browser's own viewer, and the download. An `<iframe>`
99
+ * preview was the alternative; phone browsers render a PDF in one inconsistently or
100
+ * not at all, and a card works everywhere.
101
+ */
102
+ declare function Lightbox({ open, onClose, items, index: indexProp, onIndexChange, fetcher, loop, zoom: zoomEnabled, download, onDownload, backCloses, labels: labelsProp, }: LightboxProps): react.JSX.Element;
103
+
104
+ export { DEFAULT_LIGHTBOX_LABELS, type ImageItem, type ImageItemKind, Lightbox, type LightboxLabels, type LightboxProps, imageItemKind };