@riducms/admin 0.2.3 → 0.4.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 (352) hide show
  1. package/THIRD_PARTY_NOTICES.md +60 -37
  2. package/package.json +29 -15
  3. package/src/app/admin-core-view-route.svelte +38 -53
  4. package/src/app/admin-extension-route.svelte +19 -0
  5. package/src/app/admin-layout.scss +175 -0
  6. package/src/app/admin-layout.svelte +144 -410
  7. package/src/app/admin-navigation-progress.svelte +11 -77
  8. package/src/app/admin-page-title.svelte +17 -85
  9. package/src/app/admin-page-title.ts +136 -0
  10. package/src/app/admin-route-error.svelte +24 -0
  11. package/src/app/admin-router-provider.svelte +34 -0
  12. package/src/app/admin-router-root.svelte +41 -0
  13. package/src/app/admin-routes.svelte +56 -19
  14. package/src/app/loading-boundary.svelte +3 -1
  15. package/src/app-meta.ts +13 -0
  16. package/src/app.css +138 -496
  17. package/src/app.svelte +60 -26
  18. package/src/components/brand/ridu-logo.svelte +19 -5
  19. package/src/components/data/list-table.scss +63 -0
  20. package/src/components/icons/LICENSE.md +24 -0
  21. package/src/components/icons/chevron.svelte +8 -0
  22. package/src/components/icons/plus.svelte +12 -0
  23. package/src/components/icons/search.svelte +12 -0
  24. package/src/components/icons/x.svelte +8 -0
  25. package/src/components/json-tree/json-tree-node.svelte +61 -67
  26. package/src/components/json-tree/json-viewer.scss +153 -0
  27. package/src/components/json-tree/json-viewer.svelte +57 -96
  28. package/src/components/ui/banner/banner.scss +28 -0
  29. package/src/components/ui/banner/banner.svelte +9 -8
  30. package/src/components/ui/banner/index.ts +1 -5
  31. package/src/components/ui/code-editor/code-editor.scss +20 -0
  32. package/src/components/ui/code-editor/code-editor.svelte +71 -0
  33. package/src/components/ui/code-editor/code-editor.ts +151 -0
  34. package/src/components/ui/code-editor/code-languages.ts +29 -0
  35. package/src/components/ui/collapsible-panel/collapsible-panel.scss +38 -0
  36. package/src/components/ui/collapsible-panel/collapsible-panel.svelte +13 -0
  37. package/src/components/ui/collapsible-panel/index.ts +1 -0
  38. package/src/components/ui/combobox/combobox.scss +163 -0
  39. package/src/components/ui/combobox/index.ts +22 -0
  40. package/src/components/ui/date-value-control/date-value-calendar.svelte +128 -16
  41. package/src/components/ui/date-value-control/date-value-control-loader.scss +26 -0
  42. package/src/components/ui/date-value-control/date-value-control-loader.svelte +35 -13
  43. package/src/components/ui/date-value-control/date-value-control.scss +223 -0
  44. package/src/components/ui/date-value-control/date-value-control.svelte +62 -44
  45. package/src/components/ui/drawer/drawer-depth.ts +8 -0
  46. package/src/components/ui/dropdown-menu/dropdown-menu-content.svelte +2 -5
  47. package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +2 -5
  48. package/src/components/ui/dropdown-menu/dropdown-menu-separator.svelte +2 -2
  49. package/src/components/ui/dropdown-menu/dropdown-menu.scss +62 -0
  50. package/src/components/ui/kbd/kbd.scss +17 -0
  51. package/src/components/ui/kbd/kbd.svelte +3 -10
  52. package/src/components/ui/navigation-drawer/navigation-drawer.scss +40 -0
  53. package/src/components/ui/navigation-drawer/navigation-drawer.svelte +38 -0
  54. package/src/components/ui/popover/popover-content.svelte +3 -5
  55. package/src/components/ui/popover/popover.scss +18 -0
  56. package/src/components/ui/radio-group/index.ts +0 -1
  57. package/src/components/ui/radio-group/radio-card-item.svelte +7 -12
  58. package/src/components/ui/radio-group/radio-group.svelte +2 -2
  59. package/src/components/ui/radio-group/radio.scss +45 -0
  60. package/src/components/ui/select/select-content.svelte +5 -6
  61. package/src/components/ui/select/select-item.svelte +8 -8
  62. package/src/components/ui/select/select-scroll-down-button.svelte +3 -5
  63. package/src/components/ui/select/select-scroll-up-button.svelte +3 -5
  64. package/src/components/ui/select/select-trigger.svelte +6 -7
  65. package/src/components/ui/select/select.scss +126 -0
  66. package/src/components/ui/sheet/sheet-content.svelte +6 -34
  67. package/src/components/ui/sheet/sheet-description.svelte +2 -2
  68. package/src/components/ui/sheet/sheet-overlay.svelte +2 -21
  69. package/src/components/ui/sheet/sheet-title.svelte +2 -2
  70. package/src/components/ui/sheet/sheet.scss +221 -0
  71. package/src/components/ui/skeleton/skeleton.scss +20 -0
  72. package/src/components/ui/skeleton/skeleton.svelte +4 -16
  73. package/src/components/ui/sonner/sonner.scss +121 -0
  74. package/src/components/ui/sonner/sonner.svelte +11 -10
  75. package/src/components/ui/status-indicator/status-indicator.scss +48 -0
  76. package/src/components/ui/status-indicator/status-indicator.svelte +14 -14
  77. package/src/components/ui/tabs/tabs-content.svelte +2 -2
  78. package/src/components/ui/tabs/tabs-list.svelte +7 -7
  79. package/src/components/ui/tabs/tabs-trigger.svelte +2 -8
  80. package/src/components/ui/tabs/tabs.scss +86 -0
  81. package/src/components/ui/tabs/tabs.svelte +2 -2
  82. package/src/components/ui/timezone-picker/timezone-picker.svelte +152 -0
  83. package/src/core/api/admin-client.ts +2 -2
  84. package/src/core/bootstrap/admin-bootstrap.ts +396 -0
  85. package/src/core/bootstrap/admin-prepared-state.ts +213 -0
  86. package/src/core/bootstrap/admin-route-modules.ts +66 -0
  87. package/src/core/clipboard/copy-feedback.svelte.ts +43 -0
  88. package/src/core/forms/embedded-fields.ts +4 -8
  89. package/src/core/forms/field-editor-authoring.ts +8 -0
  90. package/src/core/forms/field-issue-focus.ts +4 -4
  91. package/src/core/forms/field-occurrence.ts +4 -7
  92. package/src/core/forms/form-controller.svelte.ts +14 -21
  93. package/src/core/forms/form-draft-recovery.ts +17 -4
  94. package/src/core/forms/form-issue-correlation.ts +13 -13
  95. package/src/core/forms/form-path.ts +17 -0
  96. package/src/core/forms/form-schema.ts +12 -43
  97. package/src/core/forms/form-validation.ts +7 -46
  98. package/src/core/forms/plugin-field-authoring.ts +6 -2
  99. package/src/core/forms/plugin-field-write.ts +2 -6
  100. package/src/core/i18n/admin-i18n.svelte.ts +18 -0
  101. package/src/core/i18n/format-admin-date-time.ts +19 -0
  102. package/src/core/i18n/humanize-identifier.ts +5 -0
  103. package/src/core/i18n/time-zone-label.ts +20 -0
  104. package/src/core/loaders/admin-view-host.svelte +72 -0
  105. package/src/core/loaders/admin-view-loader.svelte.ts +90 -0
  106. package/src/core/notifications/validation-toast.svelte +2 -2
  107. package/src/core/plugins/field-registry.ts +7 -8
  108. package/src/core/routing/admin-paths.ts +53 -2
  109. package/src/core/routing/admin-scroll.svelte.ts +74 -0
  110. package/src/core/routing/admin-view-selection.ts +78 -0
  111. package/src/core/runtime/admin-runtime.svelte.ts +131 -135
  112. package/src/core/schema/field-default.ts +33 -0
  113. package/src/core/schema/field-localization.ts +15 -0
  114. package/src/core/schema/read-document-path.ts +11 -0
  115. package/src/features/account/account-menu.scss +37 -0
  116. package/src/features/account/account-menu.svelte +115 -0
  117. package/src/features/{auth → account}/account-navigation.svelte +16 -19
  118. package/src/features/{auth → account}/account-route.svelte +206 -49
  119. package/src/features/account/account-security-controller.svelte.ts +159 -0
  120. package/src/features/{auth → account}/account-security-route.svelte +140 -42
  121. package/src/features/api-reference/api-reference-code.svelte +114 -0
  122. package/src/features/api-reference/api-reference-content.svelte +258 -0
  123. package/src/features/api-reference/api-reference-drawer.scss +74 -0
  124. package/src/features/api-reference/api-reference-examples.ts +381 -0
  125. package/src/features/api-reference/api-reference-schema.ts +332 -0
  126. package/src/features/api-reference/api-reference.scss +190 -0
  127. package/src/features/api-reference/api-reference.svelte +74 -0
  128. package/src/features/auth/auth-brand.svelte +5 -3
  129. package/src/features/auth/auth-field.svelte +92 -0
  130. package/src/features/auth/auth-frame.svelte +5 -8
  131. package/src/features/auth/auth.scss +122 -0
  132. package/src/features/auth/create-first-user-controller.svelte.ts +30 -60
  133. package/src/features/auth/create-first-user-route.svelte +83 -122
  134. package/src/features/auth/forgot-password-route.svelte +32 -24
  135. package/src/features/auth/login-route.svelte +69 -117
  136. package/src/features/auth/logout-button.scss +34 -0
  137. package/src/features/auth/logout-button.svelte +52 -0
  138. package/src/features/auth/request-verification-route.svelte +28 -23
  139. package/src/features/auth/reset-password-route.svelte +111 -41
  140. package/src/features/auth/verify-email-route.svelte +52 -15
  141. package/src/features/bulk-edit/bulk-edit-fields.ts +23 -0
  142. package/src/features/bulk-edit/bulk-edit.scss +152 -0
  143. package/src/features/bulk-edit/field-picker.svelte +176 -0
  144. package/src/features/collections/bulk/bulk-actions.svelte +148 -0
  145. package/src/features/collections/bulk/bulk-confirmation.svelte +153 -0
  146. package/src/features/collections/bulk/bulk-editor-loader.svelte +95 -0
  147. package/src/features/collections/bulk/bulk-editor.svelte +186 -0
  148. package/src/features/collections/collection-list-cell-values.ts +144 -0
  149. package/src/features/collections/collection-list-controller.svelte.ts +268 -379
  150. package/src/features/collections/collection-list-preferences-controller.svelte.ts +54 -40
  151. package/src/features/collections/collection-list-query.ts +223 -0
  152. package/src/features/collections/collection-list-results.svelte +205 -329
  153. package/src/features/collections/collection-list-route.svelte +143 -884
  154. package/src/features/collections/collection-list.scss +261 -0
  155. package/src/features/collections/collection-list.svelte.ts +362 -0
  156. package/src/features/collections/controls/column-picker.svelte +110 -0
  157. package/src/features/collections/controls/column-pill.svelte +62 -0
  158. package/src/features/collections/controls/filter-builder.svelte +193 -0
  159. package/src/features/collections/controls/filter-row.svelte +142 -0
  160. package/src/features/collections/controls/list-controls.scss +196 -0
  161. package/src/features/collections/controls/list-options.svelte +203 -0
  162. package/src/features/collections/controls/list-pagination.scss +78 -0
  163. package/src/features/collections/controls/list-pagination.svelte +115 -0
  164. package/src/features/collections/controls/list-toolbar.svelte +132 -0
  165. package/src/features/collections/controls/sort-heading.scss +52 -0
  166. package/src/features/collections/controls/sort-heading.svelte +78 -0
  167. package/src/features/collections/list-columns.ts +43 -0
  168. package/src/features/collections/list-workspace.ts +155 -54
  169. package/src/features/dashboard/dashboard-route.svelte +82 -86
  170. package/src/features/dashboard/dashboard.scss +131 -0
  171. package/src/features/development/development-tools.svelte +20 -36
  172. package/src/features/documents/document-action-bar.svelte +438 -0
  173. package/src/features/documents/document-api-view-loader.svelte +17 -6
  174. package/src/features/documents/document-api-view.scss +120 -0
  175. package/src/features/documents/document-api-view.svelte +169 -454
  176. package/src/features/documents/document-controller.svelte.ts +531 -617
  177. package/src/features/documents/document-credentials.scss +37 -0
  178. package/src/features/documents/document-credentials.svelte +72 -0
  179. package/src/features/documents/{document-route-dirty.ts → document-credentials.ts} +25 -0
  180. package/src/features/documents/document-field-sections.scss +39 -0
  181. package/src/features/documents/document-field-sections.svelte +4 -13
  182. package/src/features/documents/document-loading.scss +24 -0
  183. package/src/features/documents/document-lock-controller.svelte.ts +187 -0
  184. package/src/features/documents/document-operation-dialogs.svelte +110 -0
  185. package/src/features/documents/document-route-loader.svelte +17 -5
  186. package/src/features/documents/document-route.svelte +305 -811
  187. package/src/features/documents/document-schedule.scss +127 -0
  188. package/src/features/documents/document-schedule.svelte +323 -0
  189. package/src/features/documents/document-title.ts +25 -0
  190. package/src/features/documents/document.scss +274 -0
  191. package/src/features/documents/live-preview-panel.scss +117 -0
  192. package/src/features/documents/live-preview-panel.svelte +167 -168
  193. package/src/features/documents/live-preview.ts +2 -10
  194. package/src/features/documents/upload-document-preview-loader.svelte +26 -23
  195. package/src/features/localization/content-locale-switcher.scss +43 -0
  196. package/src/features/localization/content-locale-switcher.svelte +24 -17
  197. package/src/features/navigation/admin-breadcrumbs.scss +56 -0
  198. package/src/features/navigation/admin-breadcrumbs.svelte +102 -37
  199. package/src/features/navigation/admin-command-menu.svelte +38 -15
  200. package/src/features/navigation/admin-navigation.scss +133 -0
  201. package/src/features/navigation/admin-navigation.svelte +169 -0
  202. package/src/features/navigation/breadcrumb-context.svelte.ts +15 -0
  203. package/src/features/navigation/navigation-preference.svelte.ts +70 -0
  204. package/src/features/reference-browser/reference-browser-editor.svelte +180 -0
  205. package/src/features/reference-browser/reference-browser-list.scss +113 -0
  206. package/src/features/reference-browser/reference-browser-list.svelte +370 -0
  207. package/src/features/reference-browser/reference-browser-loader.svelte +26 -8
  208. package/src/features/reference-browser/reference-browser-workflow.svelte.ts +336 -239
  209. package/src/features/reference-browser/reference-browser.scss +217 -0
  210. package/src/features/reference-browser/reference-browser.svelte +154 -797
  211. package/src/features/reference-browser/reference-collection-picker.svelte +116 -0
  212. package/src/features/reference-browser/reference-list-query.ts +35 -0
  213. package/src/features/uploads/bulk-upload-controller.svelte.ts +604 -0
  214. package/src/features/uploads/bulk-upload-edit-all.svelte +92 -0
  215. package/src/features/uploads/bulk-upload-editor.svelte +191 -0
  216. package/src/features/uploads/bulk-upload-file-picker.svelte +70 -0
  217. package/src/features/uploads/bulk-upload-remote.svelte +100 -0
  218. package/src/features/uploads/bulk-upload-route-loader.svelte +54 -0
  219. package/src/features/uploads/bulk-upload-route.svelte +172 -592
  220. package/src/features/uploads/bulk-upload-sidebar.svelte +125 -0
  221. package/src/features/uploads/bulk-upload.scss +526 -0
  222. package/src/features/uploads/bulk-upload.ts +105 -14
  223. package/src/features/uploads/crop-surface.ts +150 -0
  224. package/src/features/uploads/image-editor.svelte +231 -0
  225. package/src/features/uploads/rendition-preview.svelte +64 -0
  226. package/src/features/uploads/upload-control.svelte +340 -0
  227. package/src/features/uploads/upload-document-contracts.ts +30 -0
  228. package/src/features/uploads/upload-draft.svelte.ts +294 -0
  229. package/src/features/uploads/upload-metadata-fields.scss +32 -0
  230. package/src/features/uploads/upload-metadata-fields.svelte +8 -7
  231. package/src/features/uploads/upload-thumbnail.svelte +47 -0
  232. package/src/features/uploads/upload.scss +361 -0
  233. package/src/features/versions/version-comparison.svelte +361 -0
  234. package/src/features/versions/version-diff-fields.svelte +136 -0
  235. package/src/features/versions/version-diff.ts +296 -41
  236. package/src/features/versions/version-history-controller.svelte.ts +311 -0
  237. package/src/features/versions/version-history-route-loader.svelte +32 -0
  238. package/src/features/versions/version-history-route.svelte +157 -522
  239. package/src/features/versions/version-history.scss +444 -0
  240. package/src/features/versions/version-history.ts +45 -0
  241. package/src/features/versions/version-table.svelte +91 -0
  242. package/src/fields/embedded-schema-draft-editor.scss +48 -0
  243. package/src/fields/embedded-schema-draft-editor.svelte +24 -16
  244. package/src/fields/field-collapsible.svelte +14 -8
  245. package/src/fields/field-layout.scss +171 -0
  246. package/src/fields/field-layout.svelte +3 -5
  247. package/src/fields/field-renderer.svelte +17 -29
  248. package/src/fields/field-shell.svelte +1 -1
  249. package/src/fields/field-tab-layout.svelte +25 -19
  250. package/src/fields/join/join-field-controller.svelte.ts +513 -0
  251. package/src/fields/join/join-field-values.ts +111 -0
  252. package/src/fields/join/join-field.scss +189 -0
  253. package/src/fields/join/join-field.svelte +164 -332
  254. package/src/fields/live-validation-feedback.svelte +4 -3
  255. package/src/fields/local-field-editor.svelte +2 -2
  256. package/src/fields/nested/block-name-input.svelte +11 -12
  257. package/src/fields/nested/block-picker.scss +128 -0
  258. package/src/fields/nested/block-picker.svelte +81 -0
  259. package/src/fields/nested/block-placeholder.svg +2 -0
  260. package/src/fields/nested/nested-field.scss +331 -0
  261. package/src/fields/nested/nested-field.svelte +265 -245
  262. package/src/fields/nested/sortable-row.svelte +9 -7
  263. package/src/fields/plugin-field.svelte +4 -6
  264. package/src/fields/point/point-field.svelte +51 -38
  265. package/src/fields/primitive-list/primitive-list-field.svelte +63 -19
  266. package/src/fields/primitive-list/primitive-list.scss +109 -0
  267. package/src/fields/radio/radio-field.svelte +2 -1
  268. package/src/fields/relationship/relationship-field-controller.svelte.ts +81 -41
  269. package/src/fields/relationship/relationship-field.svelte +144 -175
  270. package/src/fields/relationship/relationship-lookup-controller.svelte.ts +58 -30
  271. package/src/fields/relationship/relationship-query.ts +2 -10
  272. package/src/fields/relationship/relationship-quick-picker-controller.svelte.ts +36 -62
  273. package/src/fields/relationship/relationship-quick-picker.svelte +178 -227
  274. package/src/fields/relationship/relationship-selection.svelte +120 -0
  275. package/src/fields/relationship/relationship-value.ts +46 -7
  276. package/src/fields/relationship/relationship.scss +182 -0
  277. package/src/fields/scalar/code-field.svelte +101 -0
  278. package/src/fields/scalar/date-control-value.ts +13 -0
  279. package/src/fields/scalar/scalar-field.svelte +25 -67
  280. package/src/fields/select/select-field.svelte +148 -94
  281. package/src/fields/text/slug-field.scss +28 -0
  282. package/src/fields/text/slug-field.svelte +19 -15
  283. package/src/fields/text/text-field.svelte +1 -2
  284. package/src/fields/ui/ui-field.svelte +7 -4
  285. package/src/fields/virtual/virtual-field.svelte +2 -5
  286. package/src/index.ts +1 -2
  287. package/src/routing.ts +2 -0
  288. package/src/app/deferred-redirect.svelte +0 -23
  289. package/src/components/code-block/code-block.svelte +0 -107
  290. package/src/components/ui/bulk-action-bar/bulk-action-bar.svelte +0 -50
  291. package/src/components/ui/bulk-action-bar/index.ts +0 -1
  292. package/src/components/ui/button/index.ts +0 -8
  293. package/src/components/ui/calendar/calendar-caption.svelte +0 -72
  294. package/src/components/ui/calendar/calendar-cell.svelte +0 -20
  295. package/src/components/ui/calendar/calendar-day.svelte +0 -27
  296. package/src/components/ui/calendar/calendar-grid-row.svelte +0 -13
  297. package/src/components/ui/calendar/calendar-grid.svelte +0 -17
  298. package/src/components/ui/calendar/calendar-head-cell.svelte +0 -20
  299. package/src/components/ui/calendar/calendar-header.svelte +0 -20
  300. package/src/components/ui/calendar/calendar-heading.svelte +0 -17
  301. package/src/components/ui/calendar/calendar-month-select.svelte +0 -45
  302. package/src/components/ui/calendar/calendar-month.svelte +0 -16
  303. package/src/components/ui/calendar/calendar-months.svelte +0 -20
  304. package/src/components/ui/calendar/calendar-nav.svelte +0 -20
  305. package/src/components/ui/calendar/calendar-next-button.svelte +0 -30
  306. package/src/components/ui/calendar/calendar-prev-button.svelte +0 -30
  307. package/src/components/ui/calendar/calendar-year-select.svelte +0 -44
  308. package/src/components/ui/calendar/calendar.svelte +0 -118
  309. package/src/components/ui/calendar/index.ts +0 -1
  310. package/src/components/ui/checkbox/checkbox.svelte +0 -35
  311. package/src/components/ui/checkbox/index.ts +0 -1
  312. package/src/components/ui/dialog/dialog-close.svelte +0 -11
  313. package/src/components/ui/dialog/dialog-content.svelte +0 -52
  314. package/src/components/ui/dialog/dialog-description.svelte +0 -20
  315. package/src/components/ui/dialog/dialog-footer.svelte +0 -20
  316. package/src/components/ui/dialog/dialog-header.svelte +0 -20
  317. package/src/components/ui/dialog/dialog-overlay.svelte +0 -20
  318. package/src/components/ui/dialog/dialog-title.svelte +0 -17
  319. package/src/components/ui/dialog/dialog-trigger.svelte +0 -11
  320. package/src/components/ui/dialog/dialog.svelte +0 -7
  321. package/src/components/ui/dialog/index.ts +0 -8
  322. package/src/components/ui/empty-state/empty-state.svelte +0 -34
  323. package/src/components/ui/empty-state/index.ts +0 -1
  324. package/src/components/ui/input/index.ts +0 -1
  325. package/src/components/ui/pagination/index.ts +0 -1
  326. package/src/components/ui/pagination/pagination.svelte +0 -88
  327. package/src/components/ui/radio-group/radio-group-item.svelte +0 -17
  328. package/src/components/ui/slider/index.ts +0 -3
  329. package/src/components/ui/slider/slider-content.svelte +0 -59
  330. package/src/components/ui/slider/slider.svelte +0 -35
  331. package/src/components/ui/table/index.ts +0 -6
  332. package/src/components/ui/table/table-body.svelte +0 -20
  333. package/src/components/ui/table/table-cell.svelte +0 -23
  334. package/src/components/ui/table/table-head.svelte +0 -23
  335. package/src/components/ui/table/table-header.svelte +0 -20
  336. package/src/components/ui/table/table-row.svelte +0 -23
  337. package/src/components/ui/table/table.svelte +0 -22
  338. package/src/components/ui/textarea/index.ts +0 -1
  339. package/src/components/ui/tooltip/index.ts +0 -4
  340. package/src/components/ui/tooltip/tooltip-content.svelte +0 -50
  341. package/src/components/ui/tooltip/tooltip-provider.svelte +0 -7
  342. package/src/components/ui/tooltip/tooltip-trigger.svelte +0 -7
  343. package/src/components/ui/tooltip/tooltip.svelte +0 -7
  344. package/src/features/auth/account-menu.svelte +0 -159
  345. package/src/features/auth/account-security-controller.svelte.ts +0 -119
  346. package/src/features/collections/collection-list-bulk-actions.svelte +0 -143
  347. package/src/features/collections/collection-list-bulk-editor.svelte +0 -157
  348. package/src/features/collections/collection-list-filter-popover.svelte +0 -238
  349. package/src/features/collections/collection-list-workspace-toolbar.svelte +0 -409
  350. package/src/features/collections/collection-table-skeleton.svelte +0 -44
  351. package/src/features/documents/document-heading.ts +0 -20
  352. package/src/features/documents/upload-document-preview.svelte +0 -525
@@ -23,18 +23,38 @@ export interface ListFilter {
23
23
  value: string;
24
24
  }
25
25
 
26
+ export interface ListColumn {
27
+ path: string;
28
+ label: string;
29
+ field?: SchemaField;
30
+ }
31
+
32
+ /** OR groups containing AND conditions. */
33
+ export type ListFilterGroup = ListFilter[];
34
+
35
+ export interface ListColumnSelection {
36
+ path: string;
37
+ active: boolean;
38
+ }
39
+
26
40
  export interface ListWorkspacePreference {
27
- columns: string[];
28
- showStatus: boolean;
29
- showID: boolean;
30
- showCreated: boolean;
31
- showUpdated: boolean;
41
+ columns: ListColumnSelection[];
32
42
  limit: ListPageSize;
33
43
  }
34
44
 
45
+ /** The resolved list view, also stored when an author saves a named view. */
46
+ export interface ListViewState extends ListWorkspacePreference {
47
+ q: string;
48
+ status: string;
49
+ folder: string;
50
+ view: "list" | "hierarchy";
51
+ filters: ListFilterGroup[];
52
+ sort: string;
53
+ }
54
+
35
55
  const textOperators: readonly ListFilterOperator[] = [
36
- "like",
37
56
  "equals",
57
+ "like",
38
58
  "notEquals",
39
59
  "contains",
40
60
  "exists",
@@ -50,14 +70,31 @@ const orderedOperators: readonly ListFilterOperator[] = [
50
70
  ];
51
71
  const identityOperators: readonly ListFilterOperator[] = ["equals", "notEquals", "exists"];
52
72
 
53
- export function listColumnFields(collection: SchemaCollection | undefined, titleName?: string) {
73
+ export function listColumnFields(collection: SchemaCollection | undefined) {
54
74
  return (collection?.fields ?? []).flatMap((field) => {
55
- if (field.name === titleName || field.type === "ui") return [];
75
+ if (field.type === "ui") return [];
56
76
  if (field.type === "group") return nestedFields(field, "columns");
57
77
  return [field];
58
78
  });
59
79
  }
60
80
 
81
+ export function bulkEditableListFields(fields: readonly SchemaField[]) {
82
+ return fields.filter(
83
+ (field) =>
84
+ field.category === "scalar" &&
85
+ !field.localized &&
86
+ !field.unique &&
87
+ field.virtual === undefined &&
88
+ !field.admin.hidden &&
89
+ !field.admin.readOnly &&
90
+ field.admin.condition === undefined &&
91
+ (field.type !== "select" || !field.select?.hasMany) &&
92
+ ["text", "textarea", "email", "date", "number", "checkbox", "select", "radio"].includes(
93
+ field.type
94
+ )
95
+ );
96
+ }
97
+
61
98
  export function filterableFields(fields: readonly SchemaField[]) {
62
99
  return fields.flatMap((field) => {
63
100
  if (field.queryRestricted) return [];
@@ -87,18 +124,23 @@ export function sortableField(field: SchemaField) {
87
124
 
88
125
  export function defaultListColumns(
89
126
  collection: SchemaCollection | undefined,
90
- fields: readonly SchemaField[]
127
+ fields: readonly ListColumn[]
91
128
  ) {
92
129
  const byName = new Map(fields.map((field) => [field.path, field.path]));
93
130
  for (const field of fields) {
94
- if (field.path === field.name && !byName.has(field.name)) byName.set(field.name, field.path);
131
+ if (
132
+ field.field !== undefined &&
133
+ field.path === field.field.name &&
134
+ !byName.has(field.field.name)
135
+ )
136
+ byName.set(field.field!.name, field.path);
95
137
  }
96
138
  const configured = (collection?.admin.defaultColumns ?? []).flatMap((name) => {
97
139
  const path = byName.get(name);
98
140
  return path === undefined ? [] : [path];
99
141
  });
100
142
  if (configured.length > 0) return configured;
101
- return fields.slice(0, 2).map((field) => field.path);
143
+ return [...new Set([fields[0]?.path ?? "id", "id", "updatedAt", "createdAt"])];
102
144
  }
103
145
 
104
146
  export function filterOperatorsFor(field: SchemaField): readonly ListFilterOperator[] {
@@ -126,7 +168,17 @@ export function filterOperatorLabel(operator: ListFilterOperator, i18n: AdminI18
126
168
  return i18n.t(labels[operator]);
127
169
  }
128
170
 
129
- export function normalizeListFilters(value: unknown, fields: readonly SchemaField[]): ListFilter[] {
171
+ export function normalizeListFilters(
172
+ value: unknown,
173
+ fields: readonly SchemaField[]
174
+ ): ListFilterGroup[] {
175
+ if (!Array.isArray(value)) return [];
176
+ return value
177
+ .map((group) => normalizeFilterGroup(group, fields))
178
+ .filter((group) => group.length > 0);
179
+ }
180
+
181
+ function normalizeFilterGroup(value: unknown, fields: readonly SchemaField[]): ListFilter[] {
130
182
  if (!Array.isArray(value)) return [];
131
183
  const byName = new Map(fields.map((field) => [field.path, field]));
132
184
  return value.flatMap((candidate) => {
@@ -166,56 +218,21 @@ export function parseListFilters(encoded: string | null, fields: readonly Schema
166
218
  }
167
219
  }
168
220
 
169
- export function buildListFilterWhere(
170
- filters: readonly ListFilter[],
171
- fields: readonly SchemaField[]
172
- ) {
173
- const byName = new Map(fields.map((field) => [field.path, field]));
174
- return filters.flatMap((filter): Record<string, unknown>[] => {
175
- const field = byName.get(filter.field);
176
- if (field === undefined || !filterOperatorsFor(field).includes(filter.operator)) return [];
177
- if (filter.operator === "exists") {
178
- return [{ [filter.field]: { exists: filter.value !== "false" } }];
179
- }
180
- if (filter.value === "" && field.type !== "text-list") return [];
181
- let value: string | number | boolean = filter.value;
182
- if (field.type === "number" || field.type === "number-list") {
183
- value = Number(filter.value);
184
- if (!Number.isFinite(value)) return [];
185
- } else if (field.type === "checkbox") {
186
- value = filter.value === "true";
187
- }
188
- return [{ [filter.field]: { [filter.operator]: filter.operator === "in" ? [value] : value } }];
189
- });
190
- }
191
-
192
- export function parseListPageSize(value: string | null, fallback: ListPageSize = 25): ListPageSize {
221
+ export function parseListPageSize(value: string | null, fallback: ListPageSize = 10): ListPageSize {
193
222
  const parsed = Number(value);
194
223
  return listPageSizes.includes(parsed as ListPageSize) ? (parsed as ListPageSize) : fallback;
195
224
  }
196
225
 
197
226
  export function normalizeWorkspacePreference(
198
227
  value: unknown,
199
- fields: readonly SchemaField[],
228
+ fields: readonly Pick<ListColumn, "path">[],
200
229
  fallback: ListWorkspacePreference
201
230
  ): ListWorkspacePreference {
202
231
  if (typeof value !== "object" || value === null) return fallback;
203
- const eligible = new Set(fields.map((field) => field.path));
204
- const rawColumns = Reflect.get(value, "columns");
205
- const columns = Array.isArray(rawColumns)
206
- ? rawColumns.filter((name): name is string => typeof name === "string" && eligible.has(name))
207
- : fallback.columns;
208
- const showStatus = Reflect.get(value, "showStatus");
209
- const showID = Reflect.get(value, "showID");
210
- const showCreated = Reflect.get(value, "showCreated");
211
- const showUpdated = Reflect.get(value, "showUpdated");
232
+ const columns = normalizeColumnSelection(Reflect.get(value, "columns"), fields, fallback.columns);
212
233
  const rawLimit = Reflect.get(value, "limit");
213
234
  return {
214
235
  columns,
215
- showStatus: typeof showStatus === "boolean" ? showStatus : fallback.showStatus,
216
- showID: typeof showID === "boolean" ? showID : fallback.showID,
217
- showCreated: typeof showCreated === "boolean" ? showCreated : fallback.showCreated,
218
- showUpdated: typeof showUpdated === "boolean" ? showUpdated : fallback.showUpdated,
219
236
  limit: parseListPageSize(
220
237
  typeof rawLimit === "number" ? String(rawLimit) : null,
221
238
  fallback.limit
@@ -223,10 +240,37 @@ export function normalizeWorkspacePreference(
223
240
  };
224
241
  }
225
242
 
226
- export function visibleColumnNames(encoded: string | null, fallback: readonly string[]) {
227
- if (encoded === null) return [...fallback];
228
- const names = encoded.split(",").filter(Boolean);
229
- return [...new Set(names)];
243
+ export function normalizeColumnSelection(
244
+ value: unknown,
245
+ fields: readonly { path: string }[],
246
+ fallback: readonly ListColumnSelection[]
247
+ ): ListColumnSelection[] {
248
+ const eligible = new Set(fields.map((field) => field.path));
249
+ const seen = new Set<string>();
250
+ const columns = (Array.isArray(value) ? value : fallback).flatMap((candidate: unknown) => {
251
+ if (typeof candidate !== "object" || candidate === null) return [];
252
+ const path = Reflect.get(candidate, "path"),
253
+ active = Reflect.get(candidate, "active");
254
+ if (
255
+ typeof path !== "string" ||
256
+ typeof active !== "boolean" ||
257
+ !eligible.has(path) ||
258
+ seen.has(path)
259
+ )
260
+ return [];
261
+ seen.add(path);
262
+ return [{ path, active }];
263
+ });
264
+ return [
265
+ ...columns,
266
+ ...fields
267
+ .filter((field) => !seen.has(field.path))
268
+ .map((field) => ({ path: field.path, active: false })),
269
+ ];
270
+ }
271
+
272
+ export function encodeColumnSelection(columns: readonly ListColumnSelection[]) {
273
+ return columns.map((column) => (column.active ? column.path : `-${column.path}`)).join(",");
230
274
  }
231
275
 
232
276
  function nestedFields(field: SchemaField, mode: "columns" | "filters"): SchemaField[] {
@@ -277,3 +321,60 @@ function filterableLeaf(field: SchemaField) {
277
321
  field.type !== "code"
278
322
  );
279
323
  }
324
+
325
+ /** Queryable document metadata is owned by the runtime, rather than application fields. */
326
+ export function listMetadataFields(
327
+ collection: SchemaCollection | undefined,
328
+ i18n: AdminI18n
329
+ ): SchemaField[] {
330
+ const fields: SchemaField[] = [
331
+ {
332
+ id: "id",
333
+ name: "id",
334
+ path: "id",
335
+ type: "text",
336
+ category: "scalar",
337
+ required: false,
338
+ unique: false,
339
+ admin: { label: "ID" },
340
+ },
341
+ ...(["createdAt", "updatedAt"] as const).map((path) => ({
342
+ id: path,
343
+ name: path,
344
+ path,
345
+ type: "date" as const,
346
+ date: { format: "date-time" as const },
347
+ category: "scalar" as const,
348
+ required: false,
349
+ unique: false,
350
+ admin: {
351
+ label: i18n.t(path === "createdAt" ? "collections:createdAt" : "collections:updatedAt"),
352
+ },
353
+ })),
354
+ ];
355
+ if (collection?.capabilities.versions)
356
+ fields.push({
357
+ id: "_status",
358
+ name: "_status",
359
+ path: "_status",
360
+ type: "select",
361
+ category: "scalar",
362
+ required: false,
363
+ unique: false,
364
+ admin: {
365
+ label: i18n.t(
366
+ collection.fields.some((field) => field.name === "status")
367
+ ? "collections:publicationStatus"
368
+ : "documents:status"
369
+ ),
370
+ },
371
+ select: {
372
+ hasMany: false,
373
+ options: [
374
+ { label: i18n.t("documents:draft"), value: "draft" },
375
+ { label: i18n.t("documents:published"), value: "published" },
376
+ ],
377
+ },
378
+ });
379
+ return fields;
380
+ }
@@ -1,14 +1,17 @@
1
1
  <script lang="ts">
2
2
  import { Link } from "@hvniel/svelte-router";
3
- import ArrowRightIcon from "~icons/lucide/arrow-right";
3
+ import "@admin/features/dashboard/dashboard.scss";
4
4
  import PlusIcon from "~icons/lucide/plus";
5
5
  import { TooltipContent, TooltipRoot, TooltipTrigger } from "@riducms/ui";
6
6
 
7
7
  import { collectionPath, createDocumentPath, globalPath } from "@admin/core/routing/admin-paths";
8
8
  import { getAdminRuntime } from "@admin/core/runtime/admin-runtime.svelte";
9
9
  import { groupAdminResources } from "@admin/features/navigation/admin-resource-groups";
10
+ import AdminViewHost from "@admin/core/loaders/admin-view-host.svelte";
10
11
 
11
12
  const runtime = getAdminRuntime();
13
+ const extensions = runtime.config.extensions;
14
+
12
15
  const manifest = $derived(runtime.manifest);
13
16
  const resourceGroups = $derived(
14
17
  groupAdminResources(runtime.visibleCollections, runtime.visibleGlobals, {
@@ -16,40 +19,78 @@
16
19
  globals: runtime.i18n.t("navigation:globals"),
17
20
  })
18
21
  );
19
- const replacement = $derived(
20
- runtime.dashboardPanels.find((panel) => panel.position === "replace")
21
- );
22
- const beforePanels = $derived(
23
- runtime.dashboardPanels.filter((panel) => panel.position === "before")
24
- );
25
- const afterPanels = $derived(
26
- runtime.dashboardPanels.filter(
27
- (panel) => panel.position === undefined || panel.position === "after"
28
- )
22
+
23
+ const replacement = extensions.dashboardPanels.find((panel) => panel.position === "replace");
24
+ const beforePanels = extensions.dashboardPanels.filter((panel) => panel.position === "before");
25
+ const afterPanels = extensions.dashboardPanels.filter(
26
+ (panel) => panel.position === undefined || panel.position === "after"
29
27
  );
30
28
  </script>
31
29
 
30
+ {#snippet resourceCard(
31
+ label: string,
32
+ path: string,
33
+ create: { label: string; path: string } | undefined
34
+ )}
35
+ <article class="ridu-dashboard__card">
36
+ <Link
37
+ class="ridu-dashboard__card-link"
38
+ to={path}
39
+ aria-label={runtime.i18n.t("dashboard:open", { label })}
40
+ />
41
+ <h3 class="ridu-dashboard__card-title">
42
+ {label}
43
+ </h3>
44
+ {#if create !== undefined}
45
+ <TooltipRoot>
46
+ <TooltipTrigger>
47
+ {#snippet child({ props })}
48
+ <Link
49
+ {...props}
50
+ class="ridu-dashboard__create"
51
+ to={create.path}
52
+ aria-label={runtime.i18n.t("dashboard:create", {
53
+ label: create.label.toLocaleLowerCase(runtime.i18n.language),
54
+ })}
55
+ >
56
+ <PlusIcon aria-hidden="true" />
57
+ </Link>
58
+ {/snippet}
59
+ </TooltipTrigger>
60
+ <TooltipContent>
61
+ {runtime.i18n.t("dashboard:create", { label: create.label })}
62
+ </TooltipContent>
63
+ </TooltipRoot>
64
+ {/if}
65
+ </article>
66
+ {/snippet}
67
+
32
68
  {#if manifest !== undefined && replacement !== undefined}
33
- <replacement.component {manifest} user={runtime.session?.user} i18n={runtime.i18n} />
34
- {:else}<section class="w-full px-5 pb-7 sm:px-8 sm:pb-9 lg:px-15">
69
+ <AdminViewHost
70
+ view={replacement}
71
+ props={{ manifest, user: runtime.session?.user, i18n: runtime.i18n }}
72
+ />
73
+ {:else}
74
+ <section class="ridu-dashboard">
35
75
  {#if manifest !== undefined && beforePanels.length > 0}
36
- <div class="grid gap-3" aria-label={runtime.i18n.t("dashboard:extensions")}>
76
+ <div class="ridu-dashboard__extensions" aria-label={runtime.i18n.t("dashboard:extensions")}>
37
77
  {#each beforePanels as panel (panel.key)}
38
- <panel.component {manifest} user={runtime.session?.user} i18n={runtime.i18n} />
78
+ <AdminViewHost
79
+ view={panel}
80
+ props={{ manifest, user: runtime.session?.user, i18n: runtime.i18n }}
81
+ />
39
82
  {/each}
40
83
  </div>
41
84
  {/if}
42
85
 
43
- <div class="mt-9 grid gap-9">
86
+ <div class="ridu-dashboard__groups">
44
87
  {#each resourceGroups as group (group.key)}
45
88
  {const groupLabel = $derived(runtime.i18n.text(group.label, group.translations))}
46
- <section aria-label={groupLabel}>
47
- <div class="mb-3 flex items-end justify-between gap-4">
48
- <h2 class="text-[18px] font-medium text-foreground-strong">
49
- {groupLabel}
50
- </h2>
51
- </div>
52
- <div class="grid grid-cols-[repeat(auto-fill,minmax(min(260px,100%),260px))] gap-2.5">
89
+ <section class="ridu-dashboard__group" aria-label={groupLabel}>
90
+ <h2 class="ridu-dashboard__heading">
91
+ {groupLabel}
92
+ </h2>
93
+ <div class="ridu-dashboard__cards">
53
94
  {#each group.collections as collection (collection.id)}
54
95
  {const pluralLabel = $derived(
55
96
  runtime.i18n.text(collection.labels.plural, collection.labels.pluralTranslations)
@@ -60,71 +101,22 @@
60
101
  collection.labels.singularTranslations
61
102
  )
62
103
  )}
63
- <article
64
- class="group relative flex min-h-20 items-center gap-3 rounded-[3px] bg-control px-4 py-3.5 transition-colors hover:bg-control-hover"
65
- >
66
- <Link
67
- class="absolute inset-0 rounded-[3px] focus-visible:outline-2 focus-visible:outline-ring focus-visible:outline-offset-2"
68
- to={collectionPath(collection.slug)}
69
- aria-label={runtime.i18n.t("dashboard:open", {
70
- label: pluralLabel,
71
- })}
72
- />
73
- <h3 class="min-w-0 flex-1 text-[14px] font-medium text-foreground-strong">
74
- {pluralLabel}
75
- </h3>
76
- {#if runtime.collectionOperations[collection.slug]?.create}
77
- <TooltipRoot>
78
- <TooltipTrigger>
79
- {#snippet child({ props })}
80
- <Link
81
- {...props}
82
- class="relative z-1 grid size-8 shrink-0 place-items-center rounded-[3px] text-foreground-muted hover:bg-background-layer hover:text-foreground-strong"
83
- to={createDocumentPath(collection.slug)}
84
- aria-label={runtime.i18n.t("dashboard:create", {
85
- label: singularLabel.toLocaleLowerCase(runtime.i18n.language),
86
- })}
87
- >
88
- <PlusIcon class="size-4" />
89
- </Link>
90
- {/snippet}
91
- </TooltipTrigger>
92
- <TooltipContent>
93
- {runtime.i18n.t("dashboard:create", {
94
- label: singularLabel,
95
- })}
96
- </TooltipContent>
97
- </TooltipRoot>
98
- {:else}
99
- <ArrowRightIcon
100
- class="size-4 text-foreground-faint rtl:rotate-180"
101
- aria-hidden="true"
102
- />
103
- {/if}
104
- </article>
104
+ {@render resourceCard(
105
+ pluralLabel,
106
+ collectionPath(collection.slug),
107
+ runtime.collectionOperations[collection.slug]?.create
108
+ ? {
109
+ label: singularLabel,
110
+ path: createDocumentPath(collection.slug),
111
+ }
112
+ : undefined
113
+ )}
105
114
  {/each}
106
115
  {#each group.globals as global (global.id)}
107
116
  {const globalLabel = $derived(
108
117
  runtime.i18n.text(global.labels.singular, global.labels.singularTranslations)
109
118
  )}
110
- <article
111
- class="group relative flex min-h-20 items-center gap-3 rounded-[3px] bg-control px-4 py-3.5 transition-colors hover:bg-control-hover"
112
- >
113
- <Link
114
- class="absolute inset-0 rounded-[3px] focus-visible:outline-2 focus-visible:outline-ring focus-visible:outline-offset-2"
115
- to={globalPath(global.slug)}
116
- aria-label={runtime.i18n.t("dashboard:open", {
117
- label: globalLabel,
118
- })}
119
- />
120
- <h3 class="min-w-0 flex-1 text-[14px] font-medium text-foreground-strong">
121
- {globalLabel}
122
- </h3>
123
- <ArrowRightIcon
124
- class="size-4 text-foreground-faint rtl:rotate-180"
125
- aria-hidden="true"
126
- />
127
- </article>
119
+ {@render resourceCard(globalLabel, globalPath(global.slug), undefined)}
128
120
  {/each}
129
121
  </div>
130
122
  </section>
@@ -132,10 +124,14 @@
132
124
  </div>
133
125
 
134
126
  {#if manifest !== undefined && afterPanels.length > 0}
135
- <div class="mt-8 grid gap-3" aria-label={runtime.i18n.t("dashboard:extensions")}>
127
+ <div class="ridu-dashboard__extensions" aria-label={runtime.i18n.t("dashboard:extensions")}>
136
128
  {#each afterPanels as panel (panel.key)}
137
- <panel.component {manifest} user={runtime.session?.user} i18n={runtime.i18n} />
129
+ <AdminViewHost
130
+ view={panel}
131
+ props={{ manifest, user: runtime.session?.user, i18n: runtime.i18n }}
132
+ />
138
133
  {/each}
139
134
  </div>
140
135
  {/if}
141
- </section>{/if}
136
+ </section>
137
+ {/if}
@@ -0,0 +1,131 @@
1
+ @use "@riducms/ui/styles.scss" as ui;
2
+
3
+ @layer ridu.components {
4
+ .ridu-dashboard {
5
+ width: 100%;
6
+ padding: 0 var(--admin-gutter) var(--admin-gutter);
7
+ }
8
+ .ridu-dashboard__groups {
9
+ padding: 6px;
10
+ display: grid;
11
+ gap: var(--admin-space);
12
+ }
13
+ .ridu-dashboard__group {
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: var(--admin-space);
17
+ }
18
+ .ridu-dashboard__heading {
19
+ margin: 0;
20
+ font-size: 26px;
21
+ line-height: 32px;
22
+ font-weight: 500;
23
+ }
24
+ .ridu-dashboard__cards {
25
+ display: grid;
26
+ grid-template-columns: repeat(5, minmax(0, 1fr));
27
+ gap: 12px;
28
+ }
29
+ .ridu-dashboard__card {
30
+ position: relative;
31
+ display: flex;
32
+ justify-content: space-between;
33
+ align-self: start;
34
+ gap: 16px;
35
+ height: 100%;
36
+ min-height: 80px;
37
+ padding: 16px;
38
+ background: var(--background-surface);
39
+ border: 1px solid var(--control-border);
40
+ border-radius: 4px;
41
+ transition:
42
+ border-color 100ms,
43
+ box-shadow 100ms;
44
+
45
+ &:hover {
46
+ border-color: var(--control-border-hover);
47
+ box-shadow: var(--dashboard-card-shadow);
48
+ }
49
+ }
50
+ .ridu-dashboard__card-link {
51
+ position: absolute;
52
+ inset: 0;
53
+ border-radius: inherit;
54
+ }
55
+ .ridu-dashboard__card-link:focus-visible,
56
+ .ridu-dashboard__create:focus-visible {
57
+ @include ui.focus-outline;
58
+ }
59
+ .ridu-dashboard__card-title {
60
+ margin: 2px 0;
61
+ min-width: 0;
62
+ flex: 1;
63
+ font-size: var(--font-size-body);
64
+ font-weight: 600;
65
+ line-height: 16px;
66
+ overflow-wrap: anywhere;
67
+ }
68
+ .ridu-dashboard__create {
69
+ position: relative;
70
+ z-index: 1;
71
+ flex-shrink: 0;
72
+ display: grid;
73
+ place-items: center;
74
+ width: 22px;
75
+ height: 22px;
76
+ border: 1px solid var(--control-border);
77
+ border-radius: 50%;
78
+ color: var(--foreground-muted);
79
+
80
+ svg {
81
+ width: 16px;
82
+ height: 16px;
83
+ stroke-width: 1;
84
+ }
85
+
86
+ &:hover {
87
+ background: var(--background);
88
+ border-color: var(--ring);
89
+ box-shadow: var(--control-shadow);
90
+ color: var(--foreground);
91
+ }
92
+ }
93
+ .ridu-dashboard__extensions {
94
+ display: grid;
95
+ gap: var(--admin-space);
96
+ margin-bottom: 30px;
97
+ }
98
+ .ridu-dashboard__groups + .ridu-dashboard__extensions {
99
+ margin-top: 30px;
100
+ margin-bottom: 0;
101
+ }
102
+ @media (max-width: ui.$large) {
103
+ .ridu-dashboard__cards {
104
+ grid-template-columns: repeat(4, minmax(0, 1fr));
105
+ }
106
+ }
107
+ @media (max-width: ui.$medium) {
108
+ .ridu-dashboard__cards {
109
+ grid-template-columns: repeat(2, minmax(0, 1fr));
110
+ }
111
+ }
112
+ @media (max-width: ui.$small) {
113
+ .ridu-dashboard__cards {
114
+ gap: 8px;
115
+ }
116
+ .ridu-dashboard__heading {
117
+ font-size: 17px;
118
+ line-height: 32px;
119
+ }
120
+ }
121
+ @media (max-width: 400px) {
122
+ .ridu-dashboard__cards {
123
+ grid-template-columns: minmax(0, 1fr);
124
+ }
125
+ }
126
+ @media (prefers-reduced-motion: reduce) {
127
+ .ridu-dashboard__card {
128
+ transition: none;
129
+ }
130
+ }
131
+ }