includio-cms 0.25.0 → 0.26.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 (349) hide show
  1. package/API.md +57 -4
  2. package/CHANGELOG.md +53 -0
  3. package/DOCS.md +1 -1
  4. package/README.md +2 -0
  5. package/ROADMAP.md +6 -0
  6. package/dist/admin/client/account/lang.d.ts +1 -0
  7. package/dist/admin/client/account/lang.js +4 -2
  8. package/dist/admin/client/account/profile-section.svelte +2 -2
  9. package/dist/admin/client/account/security-section.svelte +27 -4
  10. package/dist/admin/client/account/sessions-section.svelte +1 -1
  11. package/dist/admin/client/admin/admin-after-login-layout-content.svelte +1 -1
  12. package/dist/admin/client/admin/dashboard-page.svelte +34 -10
  13. package/dist/admin/client/collection/bulk-actions-bar.svelte +86 -44
  14. package/dist/admin/client/collection/bulk-actions-bar.svelte.d.ts +3 -1
  15. package/dist/admin/client/collection/collection-entries.svelte +52 -36
  16. package/dist/admin/client/collection/collection-entries.svelte.d.ts +3 -0
  17. package/dist/admin/client/collection/collection.svelte +28 -14
  18. package/dist/admin/client/collection/collection.svelte.d.ts +3 -0
  19. package/dist/admin/client/collection/data-table.svelte +279 -130
  20. package/dist/admin/client/collection/data-table.svelte.d.ts +11 -0
  21. package/dist/admin/client/collection/date-cell.svelte +4 -4
  22. package/dist/admin/client/collection/row-actions.svelte +2 -1
  23. package/dist/admin/client/collection/sortable-header.svelte +33 -9
  24. package/dist/admin/client/collection/state-display.svelte +102 -0
  25. package/dist/admin/client/collection/state-display.svelte.d.ts +12 -0
  26. package/dist/admin/client/collection/status-badge.svelte +99 -11
  27. package/dist/admin/client/collection/status-badge.svelte.d.ts +15 -1
  28. package/dist/admin/client/collection/table-pagination.svelte +21 -6
  29. package/dist/admin/client/collection/table-toolbar.svelte +105 -80
  30. package/dist/admin/client/collection/table-toolbar.svelte.d.ts +11 -8
  31. package/dist/admin/client/entry/entry-form.svelte +36 -11
  32. package/dist/admin/client/entry/entry-form.svelte.d.ts +1 -0
  33. package/dist/admin/client/entry/entry-header.svelte +22 -15
  34. package/dist/admin/client/entry/entry-header.svelte.d.ts +1 -0
  35. package/dist/admin/client/entry/entry.svelte +269 -165
  36. package/dist/admin/client/entry/header/a11y-header-badge.svelte +47 -0
  37. package/dist/admin/client/entry/header/a11y-header-badge.svelte.d.ts +8 -0
  38. package/dist/admin/client/entry/header/publish-panel.svelte +69 -13
  39. package/dist/admin/client/entry/header/save-indicator.svelte +57 -28
  40. package/dist/admin/client/entry/header/save-indicator.svelte.d.ts +1 -0
  41. package/dist/admin/client/entry/header/status-badge.svelte +60 -15
  42. package/dist/admin/client/entry/header/status-badge.svelte.d.ts +1 -2
  43. package/dist/admin/client/entry/header/version-history-sheet.svelte +1 -1
  44. package/dist/admin/client/entry/hybrid/hybrid-layout.svelte +74 -23
  45. package/dist/admin/client/entry/hybrid/hybrid-preview.svelte +1 -1
  46. package/dist/admin/client/entry/utils.d.ts +14 -0
  47. package/dist/admin/client/entry/utils.js +28 -0
  48. package/dist/admin/client/form/form-submission/form-submission.svelte +2 -2
  49. package/dist/admin/client/form/form-submissions.svelte +143 -194
  50. package/dist/admin/client/form/form-submissions.svelte.d.ts +2 -0
  51. package/dist/admin/client/login/lang.d.ts +3 -0
  52. package/dist/admin/client/login/lang.js +10 -4
  53. package/dist/admin/client/login/login-form.svelte +8 -1
  54. package/dist/admin/client/login/reset-password-page.svelte +24 -3
  55. package/dist/admin/client/login/schema.d.ts +14 -2
  56. package/dist/admin/client/login/schema.js +19 -8
  57. package/dist/admin/client/maintenance/maintenance-page.svelte +16 -17
  58. package/dist/admin/client/media/media-page.svelte +1 -1
  59. package/dist/admin/client/shop/coupon-edit-page.svelte +117 -13
  60. package/dist/admin/client/shop/coupon-form.svelte +282 -138
  61. package/dist/admin/client/shop/coupon-form.svelte.d.ts +1 -9
  62. package/dist/admin/client/shop/coupon-new-page.svelte +40 -10
  63. package/dist/admin/client/shop/coupon-new-page.svelte.d.ts +2 -17
  64. package/dist/admin/client/shop/coupon-schema.d.ts +28 -0
  65. package/dist/admin/client/shop/coupon-schema.js +53 -0
  66. package/dist/admin/client/shop/coupons-list-page.svelte +262 -118
  67. package/dist/admin/client/shop/coupons-list-page.svelte.d.ts +16 -1
  68. package/dist/admin/client/shop/shipping-method-edit-page.svelte +108 -59
  69. package/dist/admin/client/shop/shipping-method-form.svelte +36 -9
  70. package/dist/admin/client/shop/shipping-method-new-page.svelte +44 -13
  71. package/dist/admin/client/shop/shipping-methods-list-page.svelte +101 -59
  72. package/dist/admin/client/shop/shop-order-detail-page.svelte +113 -84
  73. package/dist/admin/client/shop/shop-orders-list-page.svelte +302 -152
  74. package/dist/admin/client/shop/shop-orders-list-page.svelte.d.ts +18 -1
  75. package/dist/admin/client/shop/shop-products-list-page.svelte +355 -118
  76. package/dist/admin/client/shop/shop-products-list-page.svelte.d.ts +19 -1
  77. package/dist/admin/client/users/accept-invite-page.svelte +24 -3
  78. package/dist/admin/client/users/create-user-dialog.svelte +3 -8
  79. package/dist/admin/client/users/lang.d.ts +2 -0
  80. package/dist/admin/client/users/lang.js +4 -0
  81. package/dist/admin/client/users/pending-invitations.svelte +2 -9
  82. package/dist/admin/client/users/user-name-cell.svelte +20 -0
  83. package/dist/admin/client/users/user-name-cell.svelte.d.ts +9 -0
  84. package/dist/admin/client/users/user-role-badge.svelte +16 -0
  85. package/dist/admin/client/users/user-role-badge.svelte.d.ts +7 -0
  86. package/dist/admin/client/users/user-row-actions.svelte +72 -0
  87. package/dist/admin/client/users/user-row-actions.svelte.d.ts +20 -0
  88. package/dist/admin/client/users/user-sessions-sheet.svelte +2 -11
  89. package/dist/admin/client/users/users-page.svelte +283 -497
  90. package/dist/admin/client/users/users-page.svelte.d.ts +12 -1
  91. package/dist/admin/components/dashboard/form-submissions-widget.svelte +59 -74
  92. package/dist/admin/components/dashboard/recent-activity.svelte +17 -5
  93. package/dist/admin/components/dashboard/recent-entries.svelte +19 -7
  94. package/dist/admin/components/dialogs/confirmation-dialog.svelte +105 -0
  95. package/dist/admin/components/dialogs/confirmation-dialog.svelte.d.ts +13 -0
  96. package/dist/admin/components/fields/block-picker-modal.svelte +6 -0
  97. package/dist/admin/components/fields/blocks-field.svelte +46 -1
  98. package/dist/admin/components/fields/boolean-field.svelte +1 -1
  99. package/dist/admin/components/fields/field-renderer.svelte +23 -21
  100. package/dist/admin/components/fields/file-field.svelte +344 -30
  101. package/dist/admin/components/fields/media-field.svelte +16 -2
  102. package/dist/admin/components/fields/radio-field.svelte +22 -0
  103. package/dist/admin/components/fields/relation-field.svelte +123 -97
  104. package/dist/admin/components/fields/relation-picker-dialog.svelte +2 -2
  105. package/dist/admin/components/fields/seo-field.svelte +60 -30
  106. package/dist/admin/components/fields/shop-field.svelte +9 -4
  107. package/dist/admin/components/fields/simple-array-field.svelte +321 -151
  108. package/dist/admin/components/fields/simple-array-field.svelte.d.ts +3 -0
  109. package/dist/admin/components/fields/slug-field.svelte +146 -21
  110. package/dist/admin/components/fields/text-field-wrapper.svelte +37 -20
  111. package/dist/admin/components/fields/text-field.svelte +7 -2
  112. package/dist/admin/components/fields/url-field-wrapper.svelte +10 -0
  113. package/dist/admin/components/fields/url-field.svelte +36 -23
  114. package/dist/admin/components/forms/form-error-summary.svelte +143 -0
  115. package/dist/admin/components/forms/form-error-summary.svelte.d.ts +27 -0
  116. package/dist/admin/components/layout/app-sidebar.svelte +7 -2
  117. package/dist/admin/components/layout/detail-page-shell.svelte +71 -0
  118. package/dist/admin/components/layout/detail-page-shell.svelte.d.ts +24 -0
  119. package/dist/admin/components/layout/lang.d.ts +5 -0
  120. package/dist/admin/components/layout/lang.js +10 -0
  121. package/dist/admin/components/layout/layout-renderer.svelte +71 -2
  122. package/dist/admin/components/layout/layout-renderer.svelte.d.ts +1 -0
  123. package/dist/admin/components/layout/layout-tabs.svelte +172 -0
  124. package/dist/admin/components/layout/layout-tabs.svelte.d.ts +24 -0
  125. package/dist/admin/components/layout/nav-breadcrumbs.svelte +25 -7
  126. package/dist/admin/components/layout/nav-collections.svelte +23 -36
  127. package/dist/admin/components/layout/nav-forms.svelte +19 -35
  128. package/dist/admin/components/layout/nav-main.svelte +3 -28
  129. package/dist/admin/components/layout/nav-search.svelte +70 -2
  130. package/dist/admin/components/layout/nav-section.svelte +77 -0
  131. package/dist/admin/components/layout/nav-section.svelte.d.ts +22 -0
  132. package/dist/admin/components/layout/nav-shop.svelte +3 -27
  133. package/dist/admin/components/layout/nav-singletons.svelte +16 -28
  134. package/dist/admin/components/layout/page-header.stories.svelte +93 -0
  135. package/dist/admin/components/layout/page-header.stories.svelte.d.ts +27 -0
  136. package/dist/admin/components/layout/page-header.svelte +68 -0
  137. package/dist/admin/components/layout/page-header.svelte.d.ts +17 -0
  138. package/dist/admin/components/layout/site-header.svelte +9 -0
  139. package/dist/admin/components/layout/site-header.svelte.d.ts +2 -17
  140. package/dist/admin/components/media/file/file-name-input.svelte +6 -2
  141. package/dist/admin/components/media/file/file-preview.svelte +130 -17
  142. package/dist/admin/components/media/file-upload.svelte +16 -7
  143. package/dist/admin/components/media/file-upload.svelte.d.ts +1 -0
  144. package/dist/admin/components/media/files-list.svelte +153 -53
  145. package/dist/admin/components/media/files-list.svelte.d.ts +1 -0
  146. package/dist/admin/components/media/media-library.svelte +577 -198
  147. package/dist/admin/components/media/media-library.svelte.d.ts +4 -0
  148. package/dist/admin/components/media/media-selector.svelte +4 -2
  149. package/dist/admin/components/media/media-selector.svelte.d.ts +1 -0
  150. package/dist/admin/components/media/tag-sidebar.svelte +4 -4
  151. package/dist/admin/components/tiptap/FigureNodeView.svelte +10 -0
  152. package/dist/admin/components/tiptap/bubble-menu.svelte +104 -0
  153. package/dist/admin/components/tiptap/bubble-menu.svelte.d.ts +19 -0
  154. package/dist/admin/components/tiptap/content-editor.svelte +28 -24
  155. package/dist/admin/components/tiptap/editor-toolbar.svelte +7 -7
  156. package/dist/admin/components/tiptap/extensions.js +5 -1
  157. package/dist/admin/components/tiptap/image-dialog.svelte +5 -1
  158. package/dist/admin/components/tiptap/link-dialog.svelte +2 -0
  159. package/dist/admin/components/tiptap/tiptap-editor.svelte +18 -20
  160. package/dist/admin/components/tiptap/video-dialog.svelte +1 -1
  161. package/dist/admin/i18n/errors.d.ts +140 -0
  162. package/dist/admin/i18n/errors.js +151 -0
  163. package/dist/admin/remote/entry.remote.d.ts +59 -4
  164. package/dist/admin/remote/entry.remote.js +239 -62
  165. package/dist/admin/remote/shop.remote.d.ts +37 -32
  166. package/dist/admin/remote/shop.remote.js +9 -2
  167. package/dist/admin/shared/password-generate.d.ts +6 -0
  168. package/dist/admin/shared/password-generate.js +40 -0
  169. package/dist/admin/shared/password-schema.d.ts +6 -0
  170. package/dist/admin/shared/password-schema.js +10 -3
  171. package/dist/admin/styles/admin.css +23 -6
  172. package/dist/admin/styles/tokens.md +244 -0
  173. package/dist/admin/utils/accordionActivation.d.ts +13 -0
  174. package/dist/admin/utils/accordionActivation.js +35 -0
  175. package/dist/admin/utils/entryLabel.d.ts +23 -0
  176. package/dist/admin/utils/entryLabel.js +51 -12
  177. package/dist/admin/utils/field-a11y.d.ts +29 -0
  178. package/dist/admin/utils/field-a11y.js +23 -0
  179. package/dist/admin/utils/fieldPathElement.d.ts +9 -0
  180. package/dist/admin/utils/fieldPathElement.js +18 -0
  181. package/dist/admin/utils/fileDisplay.d.ts +10 -0
  182. package/dist/admin/utils/fileDisplay.js +26 -0
  183. package/dist/admin/utils/flattenFormErrors.d.ts +19 -0
  184. package/dist/admin/utils/flattenFormErrors.js +102 -0
  185. package/dist/admin/utils/formatters.d.ts +12 -0
  186. package/dist/admin/utils/{formatDate.js → formatters.js} +23 -2
  187. package/dist/admin/utils/scrollWithin.d.ts +9 -0
  188. package/dist/admin/utils/scrollWithin.js +32 -0
  189. package/dist/admin/utils/tabActivation.d.ts +12 -0
  190. package/dist/admin/utils/tabActivation.js +24 -0
  191. package/dist/cms/runtime/schema.d.ts +1 -0
  192. package/dist/cms/runtime/schema.js +1 -0
  193. package/dist/cms/runtime/types.d.ts +80 -7
  194. package/dist/components/ui/accordion/accordion-content.svelte +17 -3
  195. package/dist/components/ui/accordion/accordion.stories.svelte +21 -1
  196. package/dist/components/ui/alert/alert.stories.svelte +14 -0
  197. package/dist/components/ui/alert-dialog/alert-dialog.stories.svelte +45 -0
  198. package/dist/components/ui/alert-dialog/alert-dialog.stories.svelte.d.ts +27 -0
  199. package/dist/components/ui/avatar/avatar.stories.svelte +27 -0
  200. package/dist/components/ui/badge/badge.stories.svelte +15 -0
  201. package/dist/components/ui/breadcrumb/breadcrumb.stories.svelte +47 -0
  202. package/dist/components/ui/breadcrumb/breadcrumb.svelte +1 -1
  203. package/dist/components/ui/button/button.stories.svelte +53 -6
  204. package/dist/components/ui/button/button.svelte +39 -5
  205. package/dist/components/ui/button/button.svelte.d.ts +4 -0
  206. package/dist/components/ui/button-group/button-group.stories.svelte +44 -0
  207. package/dist/components/ui/button-group/button-group.stories.svelte.d.ts +27 -0
  208. package/dist/components/ui/calendar/calendar.stories.svelte +36 -0
  209. package/dist/components/ui/calendar/calendar.stories.svelte.d.ts +27 -0
  210. package/dist/components/ui/card/card.stories.svelte +7 -0
  211. package/dist/components/ui/carousel/carousel.stories.svelte +43 -0
  212. package/dist/components/ui/carousel/carousel.stories.svelte.d.ts +27 -0
  213. package/dist/components/ui/checkbox/checkbox.stories.svelte +67 -0
  214. package/dist/components/ui/checkbox/checkbox.stories.svelte.d.ts +27 -0
  215. package/dist/components/ui/checkbox/checkbox.svelte +3 -3
  216. package/dist/components/ui/command/command.stories.svelte +18 -0
  217. package/dist/components/ui/data-table/data-table.stories.svelte +61 -0
  218. package/dist/components/ui/data-table/data-table.stories.svelte.d.ts +18 -0
  219. package/dist/components/ui/dialog/dialog-content.svelte +5 -0
  220. package/dist/components/ui/dialog/dialog-content.svelte.d.ts +2 -0
  221. package/dist/components/ui/dialog/dialog.stories.svelte +35 -0
  222. package/dist/components/ui/dropdown-menu/dropdown-menu.stories.svelte +74 -0
  223. package/dist/components/ui/dropdown-menu/dropdown-menu.stories.svelte.d.ts +27 -0
  224. package/dist/components/ui/field/field-context.svelte.d.ts +22 -0
  225. package/dist/components/ui/field/field-context.svelte.js +9 -0
  226. package/dist/components/ui/field/field-control.svelte +18 -0
  227. package/dist/components/ui/field/field-control.svelte.d.ts +8 -0
  228. package/dist/components/ui/field/field-description.svelte +12 -0
  229. package/dist/components/ui/field/field-error.svelte +14 -6
  230. package/dist/components/ui/field/field-label.svelte +10 -0
  231. package/dist/components/ui/field/field.stories.svelte +95 -9
  232. package/dist/components/ui/field/field.svelte +57 -0
  233. package/dist/components/ui/field/field.svelte.d.ts +2 -0
  234. package/dist/components/ui/field/index.d.ts +3 -1
  235. package/dist/components/ui/field/index.js +4 -2
  236. package/dist/components/ui/form/form-field-errors.svelte +1 -1
  237. package/dist/components/ui/form/form.stories.svelte +25 -0
  238. package/dist/components/ui/form/form.stories.svelte.d.ts +26 -0
  239. package/dist/components/ui/input/input.stories.svelte +26 -0
  240. package/dist/components/ui/input-group/input-group-input.svelte.d.ts +1 -1
  241. package/dist/components/ui/input-group/input-group.stories.svelte +43 -0
  242. package/dist/components/ui/input-group/input-group.stories.svelte.d.ts +27 -0
  243. package/dist/components/ui/item/item.stories.svelte +61 -0
  244. package/dist/components/ui/item/item.stories.svelte.d.ts +27 -0
  245. package/dist/components/ui/label/label.stories.svelte +7 -0
  246. package/dist/components/ui/live-region/index.d.ts +1 -0
  247. package/dist/components/ui/live-region/index.js +1 -0
  248. package/dist/components/ui/live-region/live-region-demo.svelte +32 -0
  249. package/dist/components/ui/live-region/live-region-demo.svelte.d.ts +7 -0
  250. package/dist/components/ui/live-region/live-region.stories.svelte +23 -0
  251. package/dist/components/ui/live-region/live-region.stories.svelte.d.ts +26 -0
  252. package/dist/components/ui/live-region/live-region.svelte +12 -0
  253. package/dist/components/ui/live-region/live-region.svelte.d.ts +8 -0
  254. package/dist/components/ui/popover/popover.stories.svelte +34 -0
  255. package/dist/components/ui/radio-group/radio-group.stories.svelte +58 -0
  256. package/dist/components/ui/radio-group/radio-group.stories.svelte.d.ts +27 -0
  257. package/dist/components/ui/resizable/resizable.stories.svelte +56 -0
  258. package/dist/components/ui/resizable/resizable.stories.svelte.d.ts +27 -0
  259. package/dist/components/ui/select/select.stories.svelte +49 -0
  260. package/dist/components/ui/separator/separator.stories.svelte +18 -0
  261. package/dist/components/ui/sheet/sheet.stories.svelte +34 -0
  262. package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +1 -1
  263. package/dist/components/ui/sidebar/sidebar-menu-button.svelte +1 -0
  264. package/dist/components/ui/sidebar/sidebar-trigger.svelte +1 -1
  265. package/dist/components/ui/sidebar/sidebar.stories.svelte +72 -0
  266. package/dist/components/ui/sidebar/sidebar.stories.svelte.d.ts +27 -0
  267. package/dist/components/ui/skeleton/skeleton.stories.svelte +39 -0
  268. package/dist/components/ui/skeleton/skeleton.stories.svelte.d.ts +27 -0
  269. package/dist/components/ui/skeleton/skeleton.svelte +6 -0
  270. package/dist/components/ui/sonner/index.d.ts +1 -1
  271. package/dist/components/ui/sonner/index.js +1 -1
  272. package/dist/components/ui/sonner/sonner.stories.svelte +7 -0
  273. package/dist/components/ui/sonner/sonner.svelte +17 -1
  274. package/dist/components/ui/sonner/sonner.svelte.d.ts +6 -0
  275. package/dist/components/ui/spinner/spinner.stories.svelte +30 -0
  276. package/dist/components/ui/spinner/spinner.stories.svelte.d.ts +27 -0
  277. package/dist/components/ui/switch/switch.stories.svelte +56 -0
  278. package/dist/components/ui/switch/switch.stories.svelte.d.ts +27 -0
  279. package/dist/components/ui/table/table-cell.svelte +1 -1
  280. package/dist/components/ui/table/table-head.svelte +1 -1
  281. package/dist/components/ui/table/table.stories.svelte +68 -0
  282. package/dist/components/ui/table/table.stories.svelte.d.ts +27 -0
  283. package/dist/components/ui/table/table.svelte +1 -1
  284. package/dist/components/ui/tabs/tabs.stories.svelte +48 -0
  285. package/dist/components/ui/tabs/tabs.stories.svelte.d.ts +27 -0
  286. package/dist/components/ui/textarea/textarea.stories.svelte +21 -0
  287. package/dist/components/ui/toggle/toggle.stories.svelte +23 -0
  288. package/dist/components/ui/toggle-group/toggle-group.stories.svelte +43 -0
  289. package/dist/components/ui/tooltip/tooltip.stories.svelte +46 -6
  290. package/dist/core/fields/fieldSchemaToTs.d.ts +7 -0
  291. package/dist/core/fields/fieldSchemaToTs.js +234 -90
  292. package/dist/core/fields/layoutUtils.d.ts +4 -1
  293. package/dist/core/fields/layoutUtils.js +41 -4
  294. package/dist/core/fields/resolveSeo.d.ts +70 -0
  295. package/dist/core/fields/resolveSeo.js +88 -0
  296. package/dist/core/fields/seoFieldDescriptor.d.ts +43 -0
  297. package/dist/core/fields/seoFieldDescriptor.js +74 -0
  298. package/dist/core/fields/slugPath.d.ts +13 -0
  299. package/dist/core/fields/slugPath.js +32 -0
  300. package/dist/core/fields/urlUtils.d.ts +8 -0
  301. package/dist/core/fields/urlUtils.js +27 -0
  302. package/dist/core/index.d.ts +1 -0
  303. package/dist/core/index.js +1 -0
  304. package/dist/core/server/entries/operations/create.js +13 -0
  305. package/dist/core/server/entries/operations/get.d.ts +7 -0
  306. package/dist/core/server/entries/operations/get.js +10 -6
  307. package/dist/core/server/entries/operations/slugUniqueness.d.ts +37 -0
  308. package/dist/core/server/entries/operations/slugUniqueness.js +116 -0
  309. package/dist/core/server/entries/operations/update.d.ts +6 -1
  310. package/dist/core/server/entries/operations/update.js +24 -1
  311. package/dist/core/server/fields/slugResolver.d.ts +3 -13
  312. package/dist/core/server/fields/slugResolver.js +8 -37
  313. package/dist/core/server/generator/fields.js +10 -17
  314. package/dist/core/server/generator/formFields.js +2 -1
  315. package/dist/core/server/generator/generator.js +4 -4
  316. package/dist/core/server/generator/utils.d.ts +1 -0
  317. package/dist/core/server/generator/utils.js +4 -0
  318. package/dist/paraglide/messages/_index.d.ts +3 -36
  319. package/dist/paraglide/messages/_index.js +3 -71
  320. package/dist/paraglide/messages/hello_world.d.ts +5 -0
  321. package/dist/paraglide/messages/hello_world.js +33 -0
  322. package/dist/paraglide/messages/login_hello.d.ts +16 -0
  323. package/dist/paraglide/messages/login_hello.js +34 -0
  324. package/dist/paraglide/messages/login_please_login.d.ts +16 -0
  325. package/dist/paraglide/messages/login_please_login.js +34 -0
  326. package/dist/shop/server/orders.d.ts +1 -0
  327. package/dist/shop/server/orders.js +14 -0
  328. package/dist/shop/server/shop-data.d.ts +2 -0
  329. package/dist/shop/server/shop-data.js +20 -5
  330. package/dist/sveltekit/server/handle.js +17 -0
  331. package/dist/types/cms.schema.js +4 -2
  332. package/dist/types/fields.d.ts +35 -0
  333. package/dist/types/index.d.ts +1 -1
  334. package/dist/types/layout.d.ts +35 -2
  335. package/dist/updates/0.26.0/index.d.ts +2 -0
  336. package/dist/updates/0.26.0/index.js +51 -0
  337. package/dist/updates/index.js +3 -1
  338. package/package.json +29 -7
  339. package/dist/admin/client/collection/empty-state.svelte +0 -28
  340. package/dist/admin/client/collection/empty-state.svelte.d.ts +0 -9
  341. package/dist/admin/client/form/submission-status-badge.svelte +0 -41
  342. package/dist/admin/client/form/submission-status-badge.svelte.d.ts +0 -7
  343. package/dist/admin/components/media/file-preview.svelte +0 -51
  344. package/dist/admin/components/media/file-preview.svelte.d.ts +0 -6
  345. package/dist/admin/utils/formatDate.d.ts +0 -5
  346. package/dist/paraglide/messages/en.d.ts +0 -5
  347. package/dist/paraglide/messages/en.js +0 -14
  348. package/dist/paraglide/messages/pl.d.ts +0 -5
  349. package/dist/paraglide/messages/pl.js +0 -14
@@ -0,0 +1,71 @@
1
+ <script lang="ts" module>
2
+ /**
3
+ * @internal
4
+ * Shared layout for detail/edit pages — wraps PageHeader + body grid + optional
5
+ * sidebar. Used by coupon-edit-page, shipping-method-edit-page, shop-order-detail-page.
6
+ *
7
+ * EntryPage stays custom (hybrid preview + lang switcher + 30+ specific guards).
8
+ *
9
+ * S8.
10
+ */
11
+ import type { Snippet } from 'svelte';
12
+ </script>
13
+
14
+ <script lang="ts">
15
+ import PageHeader from './page-header.svelte';
16
+ import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
17
+ import type { InterfaceLanguage } from '../../../types/languages.js';
18
+ import { cn } from '../../../utils.js';
19
+
20
+ type Props = {
21
+ title: string;
22
+ description?: string;
23
+ count?: number;
24
+ breadcrumb?: Snippet;
25
+ primaryActions?: Snippet;
26
+ secondaryActions?: Snippet;
27
+ sidebar?: Snippet;
28
+ children: Snippet;
29
+ class?: string;
30
+ };
31
+
32
+ let {
33
+ title,
34
+ description,
35
+ count,
36
+ breadcrumb,
37
+ primaryActions,
38
+ secondaryActions,
39
+ sidebar,
40
+ children,
41
+ class: className
42
+ }: Props = $props();
43
+
44
+ const interfaceLanguage = useInterfaceLanguage();
45
+ const lang: Record<InterfaceLanguage, { sidebarLabel: string }> = {
46
+ pl: { sidebarLabel: 'Panel boczny' },
47
+ en: { sidebarLabel: 'Sidebar' }
48
+ };
49
+ </script>
50
+
51
+ <div class={cn('flex flex-col', className)}>
52
+ {#if breadcrumb}
53
+ <div class="border-border border-b px-6 py-2">
54
+ {@render breadcrumb()}
55
+ </div>
56
+ {/if}
57
+
58
+ <PageHeader {title} {description} {count} {primaryActions} {secondaryActions} />
59
+
60
+ <div class="flex flex-col gap-6 px-6 pb-8 lg:grid lg:grid-cols-[1fr_320px]">
61
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
62
+ <main id="detail-main" tabindex={-1} class="min-w-0 outline-none">
63
+ {@render children()}
64
+ </main>
65
+ {#if sidebar}
66
+ <aside aria-label={lang[interfaceLanguage.current].sidebarLabel} class="min-w-0">
67
+ {@render sidebar()}
68
+ </aside>
69
+ {/if}
70
+ </div>
71
+ </div>
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @internal
3
+ * Shared layout for detail/edit pages — wraps PageHeader + body grid + optional
4
+ * sidebar. Used by coupon-edit-page, shipping-method-edit-page, shop-order-detail-page.
5
+ *
6
+ * EntryPage stays custom (hybrid preview + lang switcher + 30+ specific guards).
7
+ *
8
+ * S8.
9
+ */
10
+ import type { Snippet } from 'svelte';
11
+ type Props = {
12
+ title: string;
13
+ description?: string;
14
+ count?: number;
15
+ breadcrumb?: Snippet;
16
+ primaryActions?: Snippet;
17
+ secondaryActions?: Snippet;
18
+ sidebar?: Snippet;
19
+ children: Snippet;
20
+ class?: string;
21
+ };
22
+ declare const DetailPageShell: import("svelte").Component<Props, {}, "">;
23
+ type DetailPageShell = ReturnType<typeof DetailPageShell>;
24
+ export default DetailPageShell;
@@ -37,4 +37,9 @@ export declare const sidebarLang: Record<string, {
37
37
  forms: string;
38
38
  navigation: string;
39
39
  };
40
+ nav: {
41
+ primary: string;
42
+ breadcrumb: string;
43
+ skipToMain: string;
44
+ };
40
45
  }>;
@@ -37,6 +37,11 @@ export const sidebarLang = {
37
37
  singletons: 'Singletony',
38
38
  forms: 'Formularze',
39
39
  navigation: 'Nawigacja'
40
+ },
41
+ nav: {
42
+ primary: 'Główna nawigacja',
43
+ breadcrumb: 'Ścieżka okruszków',
44
+ skipToMain: 'Przejdź do głównej treści'
40
45
  }
41
46
  },
42
47
  en: {
@@ -77,6 +82,11 @@ export const sidebarLang = {
77
82
  singletons: 'Singletons',
78
83
  forms: 'Forms',
79
84
  navigation: 'Navigation'
85
+ },
86
+ nav: {
87
+ primary: 'Main navigation',
88
+ breadcrumb: 'Breadcrumb',
89
+ skipToMain: 'Skip to main content'
80
90
  }
81
91
  }
82
92
  };
@@ -9,11 +9,14 @@
9
9
  import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
10
10
  import { getLocalizedLabel } from '../../utils/collectionLabel.js';
11
11
  import LayoutRenderer from './layout-renderer.svelte';
12
+ import LayoutTabs, { type TabInfo } from './layout-tabs.svelte';
12
13
  import { cn } from '../../../utils.js';
14
+ import slugify from '../../imports/slugify.js';
13
15
  import {
14
16
  resolveFieldByPath,
15
17
  buildFormPath,
16
- getDistributedObjectSlugs
18
+ getDistributedObjectSlugs,
19
+ collectLeafPathsForNodes
17
20
  } from '../../../core/fields/layoutUtils.js';
18
21
 
19
22
  type Props = {
@@ -22,6 +25,7 @@
22
25
  form: SuperForm<Record<string, unknown>>;
23
26
  focusedPath?: string | null;
24
27
  flashingPath?: string | null;
28
+ errorPaths?: Set<string>;
25
29
  depth?: number;
26
30
  distributedSlugs?: Set<string>;
27
31
  };
@@ -32,6 +36,7 @@
32
36
  form,
33
37
  focusedPath = null,
34
38
  flashingPath = null,
39
+ errorPaths = undefined,
35
40
  depth = 0,
36
41
  distributedSlugs: parentDistributedSlugs
37
42
  }: Props = $props();
@@ -80,6 +85,41 @@
80
85
  }
81
86
  return '';
82
87
  }
88
+
89
+ const errorWordByLang: Record<string, string> = {
90
+ pl: 'zawiera błędy',
91
+ en: 'has errors'
92
+ };
93
+ const errorWord = $derived(
94
+ errorWordByLang[interfaceLanguage.current] ?? errorWordByLang.en
95
+ );
96
+
97
+ /**
98
+ * Build per-tab metadata for a tabs node: stable slugified value (unique
99
+ * within the group), localized label and whether any leaf field inside the
100
+ * tab is currently in `errorPaths`.
101
+ */
102
+ function buildTabs(children: LayoutNode[]): TabInfo[] {
103
+ const seen = new Set<string>();
104
+ return children.map((child, i) => {
105
+ const label = getLabel(child);
106
+ let value = label ? slugify(label, { lower: true, strict: true, trim: true }) : '';
107
+ if (!value || seen.has(value)) value = `tab-${i}`;
108
+ seen.add(value);
109
+ const hasError =
110
+ !!errorPaths &&
111
+ collectLeafPathsForNodes([child], fields).some((p) => errorPaths!.has(p));
112
+ return { value, label, child, hasError };
113
+ });
114
+ }
115
+
116
+ function groupId(tabsIndex: number): string {
117
+ return `tg-${depth}-${tabsIndex}`;
118
+ }
119
+
120
+ // URL sync: only the outermost tabs group (depth 0, first tabs node) reads
121
+ // from / writes to the `?tab` query param. Nested tabs keep local state only.
122
+ const firstTabsIndex = $derived(nodes.findIndex((n) => n.type === 'tabs'));
83
123
  </script>
84
124
 
85
125
  {#snippet fieldSlot(ref: string, autoGrid?: boolean)}
@@ -116,6 +156,7 @@
116
156
  {form}
117
157
  {focusedPath}
118
158
  {flashingPath}
159
+ {errorPaths}
119
160
  depth={depth + 1}
120
161
  {distributedSlugs}
121
162
  />
@@ -186,7 +227,16 @@
186
227
  <Accordion.Trigger class="layout-accordion-trigger">
187
228
  {getLabel(node)}
188
229
  </Accordion.Trigger>
189
- <Accordion.Content>
230
+ <!--
231
+ forceMount keeps the panel in the DOM when closed so
232
+ validation/scroll-to-issue can find fields and the
233
+ accordion can be auto-expanded by activateContainingAccordions.
234
+ accordion-content.svelte collapses forceMount content with
235
+ a grid-template-rows transition — animated both ways, and it
236
+ holds the collapsed state (the one-shot keyframe cannot once
237
+ the panel stays permanently mounted).
238
+ -->
239
+ <Accordion.Content forceMount>
190
240
  {#if isLayoutLeaf(node)}
191
241
  <div class="layout-fields-stack">
192
242
  {#each node.fields as ref (ref)}
@@ -212,6 +262,25 @@
212
262
  {@render recurse(node.children)}
213
263
  {/if}
214
264
  </div>
265
+ {:else if node.type === 'tabs'}
266
+ {@const tabsIndex = nodes.indexOf(node)}
267
+ {@const gid = groupId(tabsIndex)}
268
+ {@const tabs = buildTabs(node.children)}
269
+ {@const isTopLevelGroup = depth === 0 && tabsIndex === firstTabsIndex}
270
+ <LayoutTabs {tabs} {gid} syncUrl={isTopLevelGroup} {errorWord}>
271
+ {#snippet panel(child: LayoutNode)}
272
+ {#if isLayoutLeaf(child)}
273
+ <div class="layout-fields-stack">
274
+ {#each child.fields as ref (ref)}
275
+ {@render fieldSlot(ref)}
276
+ {/each}
277
+ </div>
278
+ {/if}
279
+ {#if isLayoutBranch(child)}
280
+ {@render recurse(child.children)}
281
+ {/if}
282
+ {/snippet}
283
+ </LayoutTabs>
215
284
  {/if}
216
285
  {/each}
217
286
 
@@ -8,6 +8,7 @@ type Props = {
8
8
  form: SuperForm<Record<string, unknown>>;
9
9
  focusedPath?: string | null;
10
10
  flashingPath?: string | null;
11
+ errorPaths?: Set<string>;
11
12
  depth?: number;
12
13
  distributedSlugs?: Set<string>;
13
14
  };
@@ -0,0 +1,172 @@
1
+ <script lang="ts" module>
2
+ import type { LayoutNode } from '../../../types/layout.js';
3
+
4
+ /** Per-tab metadata computed by the renderer for a `tabs` node. */
5
+ export type TabInfo = {
6
+ /** Stable, group-unique slug used as the bits-ui tab value + DOM id. */
7
+ value: string;
8
+ /** Localized trigger text (current interface language). */
9
+ label: string;
10
+ /** The underlying `tab` layout node, rendered via the `panel` snippet. */
11
+ child: LayoutNode;
12
+ /** True when a leaf field inside this tab is currently invalid. */
13
+ hasError: boolean;
14
+ };
15
+ </script>
16
+
17
+ <script lang="ts">
18
+ import type { Snippet } from 'svelte';
19
+ import * as Tabs from '../../../components/ui/tabs/index.js';
20
+ import { Tabs as TabsPrimitive } from 'bits-ui';
21
+ import { cn } from '../../../utils.js';
22
+ import { browser } from '$app/environment';
23
+ import { page } from '$app/state';
24
+ import { replaceState } from '$app/navigation';
25
+
26
+ type Props = {
27
+ tabs: TabInfo[];
28
+ gid: string;
29
+ /** Only the outermost tabs group syncs the active tab to `?tab`. */
30
+ syncUrl: boolean;
31
+ errorWord: string;
32
+ panel: Snippet<[LayoutNode]>;
33
+ };
34
+
35
+ let { tabs, gid, syncUrl, errorWord, panel }: Props = $props();
36
+
37
+ function initialValue(): string {
38
+ if (syncUrl && browser) {
39
+ const fromUrl = page.url.searchParams.get('tab');
40
+ if (fromUrl && tabs.some((t) => t.value === fromUrl)) return fromUrl;
41
+ }
42
+ return tabs[0]?.value ?? '';
43
+ }
44
+
45
+ let active = $state(initialValue());
46
+
47
+ // Write the active tab back to the URL (outermost group only). Guard against
48
+ // the no-op case so we never trigger an infinite replaceState loop.
49
+ $effect(() => {
50
+ if (!syncUrl || !browser) return;
51
+ const current = page.url.searchParams.get('tab');
52
+ if (active && active !== current) {
53
+ const url = new URL(page.url);
54
+ url.searchParams.set('tab', active);
55
+ replaceState(url, page.state);
56
+ }
57
+ });
58
+ </script>
59
+
60
+ <Tabs.Root class="gap-0" bind:value={active}>
61
+ <Tabs.List class={cn('layout-tabs-list', syncUrl && 'bleed')}>
62
+ {#each tabs as tb (tb.value)}
63
+ <Tabs.Trigger
64
+ value={tb.value}
65
+ data-tab-value={tb.value}
66
+ data-tabs-group={gid}
67
+ class="layout-tab-trigger"
68
+ >
69
+ {tb.label}
70
+ {#if tb.hasError}
71
+ <span class="layout-tab-error-dot" aria-hidden="true"></span>
72
+ <span class="sr-only"> — {errorWord}</span>
73
+ {/if}
74
+ </Tabs.Trigger>
75
+ {/each}
76
+ </Tabs.List>
77
+ {#each tabs as tb (tb.value)}
78
+ <TabsPrimitive.Content value={tb.value}>
79
+ {#snippet child({ props })}
80
+ {@const panelActive = (props as Record<string, unknown>).hidden !== true}
81
+ <div
82
+ {...props}
83
+ data-slot="tabs-content"
84
+ data-tab-value={tb.value}
85
+ data-tabs-group={gid}
86
+ class={cn(
87
+ 'layout-tab-panel',
88
+ panelActive && 'layout-tab-panel-active',
89
+ syncUrl && 'bleed'
90
+ )}
91
+ >
92
+ {@render panel(tb.child)}
93
+ </div>
94
+ {/snippet}
95
+ </TabsPrimitive.Content>
96
+ {/each}
97
+ </Tabs.Root>
98
+
99
+ <style>
100
+ :global(.layout-tabs-list) {
101
+ display: flex;
102
+ width: 100%;
103
+ gap: 4px;
104
+ border-bottom: 1px solid var(--border);
105
+ margin-bottom: 20px;
106
+ height: auto;
107
+ background: transparent;
108
+ padding: 0;
109
+ border-radius: 0;
110
+ }
111
+
112
+ :global(.layout-tabs-list.bleed) {
113
+ box-sizing: border-box;
114
+ width: 100%;
115
+ margin-bottom: 0;
116
+ padding-inline: max(24px, calc((100% - 1100px) / 2));
117
+ border-top: 0;
118
+ border-left: 0;
119
+ border-right: 0;
120
+ box-shadow: none;
121
+ }
122
+
123
+ @media (max-width: 900px) {
124
+ :global(.layout-tabs-list.bleed) {
125
+ padding-inline: 16px;
126
+ }
127
+ }
128
+
129
+ :global(.layout-tab-panel.bleed) {
130
+ box-sizing: border-box;
131
+ max-width: 1100px;
132
+ margin: 0 auto;
133
+ padding: 24px;
134
+ }
135
+
136
+ @media (max-width: 900px) {
137
+ :global(.layout-tab-panel.bleed) {
138
+ padding: 16px;
139
+ }
140
+ }
141
+
142
+ :global(.layout-tab-trigger) {
143
+ flex: 1;
144
+ height: auto;
145
+ padding: 10px 14px;
146
+ border: none;
147
+ border-bottom: 2px solid transparent;
148
+ border-radius: 8px 8px 0 0;
149
+ background: transparent;
150
+ color: var(--muted-foreground);
151
+ font-size: 13px;
152
+ font-weight: 600;
153
+ box-shadow: none;
154
+ }
155
+
156
+ :global(.layout-tab-trigger[data-state='active']) {
157
+ color: var(--primary);
158
+ border-bottom-color: var(--primary);
159
+ background: transparent;
160
+ box-shadow: none;
161
+ }
162
+
163
+ :global(.layout-tab-error-dot) {
164
+ display: inline-block;
165
+ width: 6px;
166
+ height: 6px;
167
+ margin-left: 6px;
168
+ border-radius: 9999px;
169
+ background: var(--error, #c44b4b);
170
+ vertical-align: middle;
171
+ }
172
+ </style>
@@ -0,0 +1,24 @@
1
+ import type { LayoutNode } from '../../../types/layout.js';
2
+ /** Per-tab metadata computed by the renderer for a `tabs` node. */
3
+ export type TabInfo = {
4
+ /** Stable, group-unique slug used as the bits-ui tab value + DOM id. */
5
+ value: string;
6
+ /** Localized trigger text (current interface language). */
7
+ label: string;
8
+ /** The underlying `tab` layout node, rendered via the `panel` snippet. */
9
+ child: LayoutNode;
10
+ /** True when a leaf field inside this tab is currently invalid. */
11
+ hasError: boolean;
12
+ };
13
+ import type { Snippet } from 'svelte';
14
+ type Props = {
15
+ tabs: TabInfo[];
16
+ gid: string;
17
+ /** Only the outermost tabs group syncs the active tab to `?tab`. */
18
+ syncUrl: boolean;
19
+ errorWord: string;
20
+ panel: Snippet<[LayoutNode]>;
21
+ };
22
+ declare const LayoutTabs: import("svelte").Component<Props, {}, "">;
23
+ type LayoutTabs = ReturnType<typeof LayoutTabs>;
24
+ export default LayoutTabs;
@@ -1,22 +1,40 @@
1
1
  <script lang="ts">
2
2
  import { getBreadcrumbs } from '../../state/breadcrumbs.svelte.js';
3
3
  import * as Breadcrumb from '../../../components/ui/breadcrumb/index.js';
4
+ import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
5
+ import { sidebarLang } from './lang.js';
4
6
 
5
7
  const breadcrumbs = getBreadcrumbs();
8
+ const interfaceLanguage = useInterfaceLanguage();
6
9
  </script>
7
10
 
8
- <Breadcrumb.Root>
9
- <Breadcrumb.List class="gap-2 text-[13px]">
11
+ <Breadcrumb.Root
12
+ aria-label={sidebarLang[interfaceLanguage.current].nav.breadcrumb}
13
+ class="min-w-0 flex-1 overflow-hidden"
14
+ >
15
+ <Breadcrumb.List class="flex-nowrap gap-1.5 text-[13px] sm:gap-2">
10
16
  {#each breadcrumbs.state as crumb, i}
11
- <Breadcrumb.Item>
12
- {#if crumb.href && i < breadcrumbs.state.length - 1}
17
+ {@const isLast = i === breadcrumbs.state.length - 1}
18
+ {@const isFirst = i === 0}
19
+ {@const hideOnMobile = breadcrumbs.state.length > 2 && !isLast && !isFirst}
20
+ <Breadcrumb.Item class={hideOnMobile ? 'hidden sm:inline-flex' : ''}>
21
+ {#if crumb.href && !isLast}
13
22
  <Breadcrumb.Link class="font-medium" href={crumb.href}>{crumb.label}</Breadcrumb.Link>
14
23
  {:else}
15
- <Breadcrumb.Page class="max-w-60 truncate font-semibold">{crumb.label}</Breadcrumb.Page>
24
+ <Breadcrumb.Page class="max-w-[10rem] truncate font-semibold sm:max-w-60"
25
+ >{crumb.label}</Breadcrumb.Page
26
+ >
16
27
  {/if}
17
28
  </Breadcrumb.Item>
18
- {#if i < breadcrumbs.state.length - 1}
19
- <Breadcrumb.Separator>/</Breadcrumb.Separator>
29
+ {#if !isLast}
30
+ <Breadcrumb.Separator class={hideOnMobile ? 'hidden sm:inline-flex' : ''}
31
+ >/</Breadcrumb.Separator
32
+ >
33
+ {/if}
34
+ {#if breadcrumbs.state.length > 2 && isFirst}
35
+ <Breadcrumb.Separator class="inline-flex sm:hidden" aria-hidden="true"
36
+ >/&hellip;/</Breadcrumb.Separator
37
+ >
20
38
  {/if}
21
39
  {/each}
22
40
  </Breadcrumb.List>
@@ -2,19 +2,25 @@
2
2
  import * as Sidebar from '../../../components/ui/sidebar/index.js';
3
3
  import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
4
4
  import { getRemotes } from '../../../sveltekit/index.js';
5
+ import { getBreadcrumbs } from '../../state/breadcrumbs.svelte.js';
5
6
  import { sidebarLang } from './lang.js';
6
7
  import { getLocalizedLabel } from '../../utils/collectionLabel.js';
7
8
  import Skeleton from '../../../components/ui/skeleton/skeleton.svelte';
8
9
  import { page } from '$app/state';
9
10
  import FolderIcon from '@tabler/icons-svelte/icons/folder';
10
11
  import { getIcon } from '../icons/icon-map.js';
12
+ import NavSection, { type NavSectionItem } from './nav-section.svelte';
11
13
 
12
14
  const interfaceLanguage = useInterfaceLanguage();
13
15
  const remotes = getRemotes();
16
+ const breadcrumbs = getBreadcrumbs();
14
17
 
15
18
  function isActive(url: string) {
16
19
  const pathname = page.url.pathname;
17
- return pathname === url || pathname.startsWith(url + '/');
20
+ if (pathname === url || pathname.startsWith(url + '/')) return true;
21
+ // Also highlight a collection while editing one of its entries — the entry
22
+ // page sets a breadcrumb linking back to the collection list.
23
+ return breadcrumbs.state.some((b) => b.href === url);
18
24
  }
19
25
 
20
26
  const collectionsQuery = $derived(remotes.getCollections());
@@ -33,6 +39,15 @@
33
39
  }
34
40
  countsMap = map;
35
41
  });
42
+
43
+ const items: NavSectionItem[] = $derived(
44
+ collectionsQuery.current?.map((item) => ({
45
+ title: getLocalizedLabel(item.labels?.plural, interfaceLanguage.current) ?? item.slug,
46
+ url: `/admin/collections/${item.slug}`,
47
+ icon: getIcon(item.sidebarIcon) ?? FolderIcon,
48
+ count: countsMap.get(item.slug)
49
+ })) ?? []
50
+ );
36
51
  </script>
37
52
 
38
53
  {#if !collectionsQuery.ready}
@@ -47,39 +62,11 @@
47
62
  </div>
48
63
  </Sidebar.Menu>
49
64
  </Sidebar.Group>
50
- {:else if collectionsQuery.current && collectionsQuery.current.length > 0}
51
- <Sidebar.Group class="border-sidebar-border mt-1 border-t px-0 pt-2 pb-1">
52
- <Sidebar.GroupLabel class="group-data-[collapsible=icon]:hidden"
53
- >{sidebarLang[interfaceLanguage.current].collections.title}</Sidebar.GroupLabel
54
- >
55
- <Sidebar.Menu>
56
- {#each collectionsQuery.current as item (item.slug)}
57
- {@const name =
58
- getLocalizedLabel(item.labels?.plural, interfaceLanguage.current) ?? item.slug}
59
- {@const url = `/admin/collections/${item.slug}`}
60
- {@const active = isActive(url)}
61
- <Sidebar.MenuItem>
62
- <Sidebar.MenuButton isActive={active} tooltipContent={name}>
63
- {#snippet child({ props })}
64
- {@const ResolvedIcon = getIcon(item.sidebarIcon) ?? FolderIcon}
65
- <a {...props} href={url}>
66
- <ResolvedIcon
67
- class="size-5! shrink-0 {active ? 'text-primary' : 'text-muted-foreground'}"
68
- />
69
- <span class="flex-1 truncate {active ? 'text-primary' : 'text-muted-foreground'}"
70
- >{name}</span
71
- >
72
- {#if countsMap.get(item.slug)}
73
- <span
74
- class="bg-lavender-lighter text-primary inline-flex h-[18px] min-w-[18px] shrink-0 items-center justify-center rounded-full px-[5px] text-[10px] font-bold group-data-[collapsible=icon]:hidden"
75
- >{countsMap.get(item.slug)}</span
76
- >
77
- {/if}
78
- </a>
79
- {/snippet}
80
- </Sidebar.MenuButton>
81
- </Sidebar.MenuItem>
82
- {/each}
83
- </Sidebar.Menu>
84
- </Sidebar.Group>
65
+ {:else if items.length > 0}
66
+ <NavSection
67
+ {items}
68
+ label={sidebarLang[interfaceLanguage.current].collections.title}
69
+ {isActive}
70
+ withTopBorder
71
+ />
85
72
  {/if}
@@ -8,6 +8,7 @@
8
8
  import ClipboardIcon from '@tabler/icons-svelte/icons/clipboard-list';
9
9
  import { getIcon } from '../icons/icon-map.js';
10
10
  import { page } from '$app/state';
11
+ import NavSection, { type NavSectionItem } from './nav-section.svelte';
11
12
 
12
13
  const interfaceLanguage = useInterfaceLanguage();
13
14
  const remotes = getRemotes();
@@ -21,13 +22,23 @@
21
22
  const overviewQuery = $derived(remotes.getSubmissionsOverview());
22
23
 
23
24
  const countsMap = $derived.by(() => {
25
+ // Badge = unread submissions (things needing attention), not total
24
26
  const map = new Map<string, number>();
25
27
  if (!overviewQuery.ready || !overviewQuery.current) return map;
26
28
  for (const item of overviewQuery.current) {
27
- if (item.total > 0) map.set(item.slug, item.total);
29
+ if (item.unread > 0) map.set(item.slug, item.unread);
28
30
  }
29
31
  return map;
30
32
  });
33
+
34
+ const items: NavSectionItem[] = $derived(
35
+ formsQuery.current?.map((item) => ({
36
+ title: getLocalizedLabel(item.label, interfaceLanguage.current) ?? item.slug,
37
+ url: `/admin/forms/${item.slug}`,
38
+ icon: getIcon(item.sidebarIcon) ?? ClipboardIcon,
39
+ count: countsMap.get(item.slug)
40
+ })) ?? []
41
+ );
31
42
  </script>
32
43
 
33
44
  {#if !formsQuery.ready}
@@ -40,38 +51,11 @@
40
51
  </div>
41
52
  </Sidebar.Menu>
42
53
  </Sidebar.Group>
43
- {:else if formsQuery.current && formsQuery.current.length > 0}
44
- <Sidebar.Group class="border-sidebar-border mt-1 border-t px-0 pt-2 pb-1">
45
- <Sidebar.GroupLabel class="group-data-[collapsible=icon]:hidden"
46
- >{sidebarLang[interfaceLanguage.current].forms.title}</Sidebar.GroupLabel
47
- >
48
- <Sidebar.Menu>
49
- {#each formsQuery.current as item (item.slug)}
50
- {@const name = getLocalizedLabel(item.label, interfaceLanguage.current) ?? item.slug}
51
- {@const url = `/admin/forms/${item.slug}`}
52
- {@const active = isActive(url)}
53
- <Sidebar.MenuItem>
54
- <Sidebar.MenuButton isActive={active} tooltipContent={name}>
55
- {#snippet child({ props })}
56
- {@const ResolvedIcon = getIcon(item.sidebarIcon) ?? ClipboardIcon}
57
- <a {...props} href={url}>
58
- <ResolvedIcon
59
- class="size-5! shrink-0 {active ? 'text-primary' : 'text-muted-foreground'}"
60
- />
61
- <span class="flex-1 {active ? 'text-primary' : 'text-muted-foreground'} truncate"
62
- >{name}</span
63
- >
64
- {#if countsMap.get(item.slug)}
65
- <span
66
- class="bg-lavender-lighter text-primary inline-flex h-[18px] min-w-[18px] shrink-0 items-center justify-center rounded-full px-[5px] text-[10px] font-bold group-data-[collapsible=icon]:hidden"
67
- >{countsMap.get(item.slug)}</span
68
- >
69
- {/if}
70
- </a>
71
- {/snippet}
72
- </Sidebar.MenuButton>
73
- </Sidebar.MenuItem>
74
- {/each}
75
- </Sidebar.Menu>
76
- </Sidebar.Group>
54
+ {:else if items.length > 0}
55
+ <NavSection
56
+ {items}
57
+ label={sidebarLang[interfaceLanguage.current].forms.title}
58
+ {isActive}
59
+ withTopBorder
60
+ />
77
61
  {/if}