@questpie/admin 3.5.2 → 3.5.4

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 (195) hide show
  1. package/README.md +8 -0
  2. package/dist/client/blocks/block-renderer.d.mts +2 -2
  3. package/dist/client/builder/index.d.mts +1 -1
  4. package/dist/client/builder/types/collection-types.d.mts +89 -5
  5. package/dist/client/builder/types/common.d.mts +5 -0
  6. package/dist/client/builder/types/field-types.d.mts +41 -1
  7. package/dist/client/builder/view/view.d.mts +3 -2
  8. package/dist/client/components/actions/action-dialog.mjs +5 -0
  9. package/dist/client/components/admin-link.d.mts +2 -2
  10. package/dist/client/components/fields/boolean-field.mjs +2 -1
  11. package/dist/client/components/fields/date-field.mjs +2 -1
  12. package/dist/client/components/fields/datetime-field.mjs +2 -1
  13. package/dist/client/components/fields/email-field.mjs +2 -1
  14. package/dist/client/components/fields/field-utils.d.mts +11 -0
  15. package/dist/client/components/fields/field-utils.mjs +3 -1
  16. package/dist/client/components/fields/field-wrapper.mjs +3 -3
  17. package/dist/client/components/fields/number-field.mjs +2 -1
  18. package/dist/client/components/fields/object-field.mjs +2 -1
  19. package/dist/client/components/fields/relation/displays/types.mjs +3 -3
  20. package/dist/client/components/fields/rich-text-editor/bubble-menu.mjs +7 -0
  21. package/dist/client/components/fields/rich-text-editor/extensions.mjs +19 -2
  22. package/dist/client/components/fields/rich-text-editor/image-popover.mjs +6 -2
  23. package/dist/client/components/fields/rich-text-editor/image-upload.mjs +2 -1
  24. package/dist/client/components/fields/rich-text-editor/index.d.mts +5 -3
  25. package/dist/client/components/fields/rich-text-editor/index.mjs +38 -76
  26. package/dist/client/components/fields/rich-text-editor/slash-commands.mjs +30 -7
  27. package/dist/client/components/fields/rich-text-editor/toolbar.mjs +1 -312
  28. package/dist/client/components/fields/rich-text-editor/types.d.mts +4 -0
  29. package/dist/client/components/fields/rich-text-editor/types.mjs +1 -1
  30. package/dist/client/components/fields/rich-text-editor/utils.mjs +6 -12
  31. package/dist/client/components/fields/select-field.mjs +2 -1
  32. package/dist/client/components/fields/text-field.mjs +2 -1
  33. package/dist/client/components/fields/textarea-field.mjs +2 -1
  34. package/dist/client/components/fields/time-field.mjs +2 -1
  35. package/dist/client/components/filter-builder/filter-builder-sheet.mjs +75 -22
  36. package/dist/client/components/layout/field-layout-renderer.mjs +4 -4
  37. package/dist/client/components/media/media-grid.mjs +2 -1
  38. package/dist/client/components/primitives/asset-preview.mjs +4 -2
  39. package/dist/client/components/primitives/dropzone.d.mts +100 -0
  40. package/dist/client/components/primitives/field-select-control.mjs +2 -1
  41. package/dist/client/components/ui/button.d.mts +23 -0
  42. package/dist/client/components/ui/button.mjs +2 -2
  43. package/dist/client/components/ui/dropdown-menu.d.mts +49 -0
  44. package/dist/client/components/ui/dropdown-menu.mjs +7 -19
  45. package/dist/client/components/ui/popover.mjs +1 -1
  46. package/dist/client/components/ui/search-input.d.mts +56 -0
  47. package/dist/client/components/ui/select.mjs +2 -2
  48. package/dist/client/components/ui/sheet.d.mts +40 -0
  49. package/dist/client/components/ui/table.d.mts +49 -0
  50. package/dist/client/components/ui/table.mjs +15 -1
  51. package/dist/client/components/ui/tooltip.d.mts +21 -0
  52. package/dist/client/contexts/focus-context.d.mts +2 -2
  53. package/dist/client/hooks/query-access.d.mts +9 -0
  54. package/dist/client/hooks/query-access.mjs +20 -0
  55. package/dist/client/hooks/typed-hooks.d.mts +4 -2
  56. package/dist/client/hooks/typed-hooks.mjs +30 -29
  57. package/dist/client/hooks/use-admin-config.mjs +20 -1
  58. package/dist/client/hooks/use-autosave.mjs +91 -0
  59. package/dist/client/hooks/use-collection.mjs +65 -23
  60. package/dist/client/hooks/use-reactive-fields.d.mts +1 -0
  61. package/dist/client/hooks/use-reactive-fields.mjs +16 -1
  62. package/dist/client/hooks/use-server-actions.mjs +12 -1
  63. package/dist/client/hooks/use-upload.d.mts +40 -0
  64. package/dist/client/hooks/use-upload.mjs +4 -2
  65. package/dist/client/hooks/use-view-state.mjs +15 -7
  66. package/dist/client/i18n/hooks.d.mts +20 -0
  67. package/dist/client/lib/utils.d.mts +6 -0
  68. package/dist/client/lib/view-filter-utils.mjs +30 -0
  69. package/dist/client/preview/block-scope-context.d.mts +2 -2
  70. package/dist/client/preview/preview-banner.d.mts +2 -2
  71. package/dist/client/preview/preview-field.d.mts +4 -4
  72. package/dist/client/runtime/provider.mjs +22 -3
  73. package/dist/client/scope/picker.d.mts +2 -2
  74. package/dist/client/scope/provider.d.mts +2 -2
  75. package/dist/client/styles/base.css +75 -79
  76. package/dist/client/utils/asset-url.mjs +27 -0
  77. package/dist/client/utils/build-field-definitions-from-schema.mjs +1 -0
  78. package/dist/client/views/auth/accept-invite-form.d.mts +2 -2
  79. package/dist/client/views/auth/auth-layout.d.mts +3 -3
  80. package/dist/client/views/auth/forgot-password-form.d.mts +2 -2
  81. package/dist/client/views/auth/login-form.d.mts +2 -2
  82. package/dist/client/views/auth/reset-password-form.d.mts +2 -2
  83. package/dist/client/views/auth/setup-form.d.mts +2 -2
  84. package/dist/client/views/collection/auto-form-fields.mjs +7 -6
  85. package/dist/client/views/collection/cells/primitive-cells.mjs +9 -6
  86. package/dist/client/views/collection/cells/shared/asset-thumbnail.d.mts +7 -0
  87. package/dist/client/views/collection/cells/shared/asset-thumbnail.mjs +3 -2
  88. package/dist/client/views/collection/cells/shared/cell-helpers.mjs +3 -2
  89. package/dist/client/views/collection/cells/upload-cells.mjs +2 -1
  90. package/dist/client/views/collection/columns/build-columns.mjs +3 -1
  91. package/dist/client/views/collection/document-view.d.mts +30 -0
  92. package/dist/client/views/collection/document-view.mjs +377 -0
  93. package/dist/client/views/collection/field-context.mjs +3 -2
  94. package/dist/client/views/collection/field-renderer.mjs +13 -5
  95. package/dist/client/views/collection/form-view.mjs +221 -282
  96. package/dist/client/views/collection/list-view.mjs +592 -190
  97. package/dist/client/views/collection/outline.mjs +44 -19
  98. package/dist/client/views/collection/quick-filter-bar.mjs +45 -0
  99. package/dist/client/views/collection/table-view.mjs +61 -17
  100. package/dist/client/views/globals/global-form-view.mjs +12 -9
  101. package/dist/client/views/layout/admin-layout-provider.mjs +4 -3
  102. package/dist/client/views/layout/admin-layout.mjs +108 -21
  103. package/dist/client/views/layout/admin-router.mjs +19 -3
  104. package/dist/client/views/layout/admin-sidebar.mjs +70 -20
  105. package/dist/client/views/layout/admin-theme.mjs +5 -4
  106. package/dist/client/views/layout/admin-view-layout.d.mts +36 -0
  107. package/dist/client/views/pages/accept-invite-page.d.mts +2 -2
  108. package/dist/client/views/pages/dashboard-page.d.mts +2 -2
  109. package/dist/client/views/pages/forgot-password-page.d.mts +2 -2
  110. package/dist/client/views/pages/invite-page.d.mts +2 -2
  111. package/dist/client/views/pages/login-page.d.mts +2 -2
  112. package/dist/client/views/pages/reset-password-page.d.mts +2 -2
  113. package/dist/client/views/pages/setup-page.d.mts +2 -2
  114. package/dist/client.d.mts +17 -2
  115. package/dist/client.mjs +17 -2
  116. package/dist/components/rich-text/rich-text-renderer.d.mts +5 -5
  117. package/dist/components/rich-text/rich-text-renderer.mjs +5 -2
  118. package/dist/factories.d.mts +4 -2
  119. package/dist/factories.mjs +2 -2
  120. package/dist/index.d.mts +17 -3
  121. package/dist/index.mjs +17 -2
  122. package/dist/modules/admin.d.mts +1 -1
  123. package/dist/server/adapters/index.d.mts +2 -0
  124. package/dist/server/adapters/nextjs.d.mts +1 -0
  125. package/dist/server/augmentation/actions.d.mts +9 -3
  126. package/dist/server/augmentation/dashboard.d.mts +11 -11
  127. package/dist/server/augmentation/form-layout.d.mts +16 -6
  128. package/dist/server/augmentation/index.d.mts +7 -0
  129. package/dist/server/augmentation/sidebar.d.mts +8 -8
  130. package/dist/server/augmentation/views.d.mts +4 -1
  131. package/dist/server/auth-helpers.d.mts +1 -0
  132. package/dist/server/codegen/admin-client-template.mjs +7 -6
  133. package/dist/server/fields/blocks.mjs +4 -1
  134. package/dist/server/fields/index.d.mts +1 -1
  135. package/dist/server/fields/reactive-runtime.mjs +3 -0
  136. package/dist/server/fields/rich-text.d.mts +16 -17
  137. package/dist/server/fields/rich-text.mjs +18 -7
  138. package/dist/server/i18n/messages/cs.mjs +2 -0
  139. package/dist/server/i18n/messages/de.mjs +2 -0
  140. package/dist/server/i18n/messages/en.mjs +4 -0
  141. package/dist/server/i18n/messages/es.mjs +2 -0
  142. package/dist/server/i18n/messages/fr.mjs +2 -0
  143. package/dist/server/i18n/messages/pl.mjs +2 -0
  144. package/dist/server/i18n/messages/pt.mjs +2 -0
  145. package/dist/server/i18n/messages/sk.mjs +2 -0
  146. package/dist/server/modules/admin/.generated/module.d.mts +1 -1
  147. package/dist/server/modules/admin/auth-helpers.mjs +7 -1
  148. package/dist/server/modules/admin/block/block-builder.d.mts +0 -8
  149. package/dist/server/modules/admin/block/introspection.d.mts +2 -2
  150. package/dist/server/modules/admin/block/introspection.mjs +28 -4
  151. package/dist/server/modules/admin/block/prefetch.d.mts +11 -0
  152. package/dist/server/modules/admin/block/prefetch.mjs +108 -27
  153. package/dist/server/modules/admin/client/.generated/module.d.mts +68 -67
  154. package/dist/server/modules/admin/client/.generated/module.mjs +2 -0
  155. package/dist/server/modules/admin/client/views/collection-document.d.mts +6 -0
  156. package/dist/server/modules/admin/client/views/collection-document.mjs +10 -0
  157. package/dist/server/modules/admin/collections/account.d.mts +53 -52
  158. package/dist/server/modules/admin/collections/admin-locks.d.mts +57 -56
  159. package/dist/server/modules/admin/collections/admin-preferences.d.mts +38 -37
  160. package/dist/server/modules/admin/collections/admin-saved-views.d.mts +50 -49
  161. package/dist/server/modules/admin/collections/apikey.d.mts +76 -67
  162. package/dist/server/modules/admin/collections/assets.d.mts +37 -36
  163. package/dist/server/modules/admin/collections/session.d.mts +42 -41
  164. package/dist/server/modules/admin/collections/user.d.mts +57 -56
  165. package/dist/server/modules/admin/collections/verification.d.mts +34 -33
  166. package/dist/server/modules/admin/dto/admin-config.dto.mjs +34 -4
  167. package/dist/server/modules/admin/factories.mjs +4 -34
  168. package/dist/server/modules/admin/index.d.mts +3 -3
  169. package/dist/server/modules/admin/routes/admin-config.d.mts +4 -2
  170. package/dist/server/modules/admin/routes/admin-config.mjs +56 -24
  171. package/dist/server/modules/admin/routes/execute-action.d.mts +9 -9
  172. package/dist/server/modules/admin/routes/execute-action.mjs +35 -9
  173. package/dist/server/modules/admin/routes/locales.mjs +1 -1
  174. package/dist/server/modules/admin/routes/preview.d.mts +11 -11
  175. package/dist/server/modules/admin/routes/preview.mjs +6 -5
  176. package/dist/server/modules/admin/routes/reactive.d.mts +9 -9
  177. package/dist/server/modules/admin/routes/reactive.mjs +2 -2
  178. package/dist/server/modules/admin/routes/route-helpers.d.mts +11 -7
  179. package/dist/server/modules/admin/routes/route-helpers.mjs +1 -1
  180. package/dist/server/modules/admin/routes/setup.d.mts +7 -7
  181. package/dist/server/modules/admin/routes/translations.d.mts +4 -4
  182. package/dist/server/modules/admin/routes/widget-data.d.mts +5 -5
  183. package/dist/server/modules/admin/routes/widget-data.mjs +12 -4
  184. package/dist/server/modules/admin-preferences/collections/saved-views.d.mts +45 -45
  185. package/dist/server/modules/audit/.generated/module.d.mts +6 -6
  186. package/dist/server/modules/audit/collections/audit-log.d.mts +81 -80
  187. package/dist/server/modules/audit/jobs/audit-cleanup.d.mts +2 -2
  188. package/dist/server/plugin.mjs +10 -5
  189. package/dist/server/proxy-factories.d.mts +8 -1
  190. package/dist/server/proxy-factories.mjs +33 -1
  191. package/dist/server.d.mts +3 -1
  192. package/dist/shared/types/index.d.mts +1 -0
  193. package/dist/shared/types/saved-views.types.d.mts +14 -7
  194. package/dist/shared.d.mts +3 -2
  195. package/package.json +5 -4
@@ -34,12 +34,11 @@ function stableGroupKey(value) {
34
34
  if (typeof value === "object") return getId(value) ?? JSON.stringify(value);
35
35
  return String(value);
36
36
  }
37
- function shouldExpand(key, depth, outline, collapsedKeys) {
38
- if (collapsedKeys.has(key)) return false;
37
+ function shouldExpand(key, depth, outline, toggledKeys) {
39
38
  const defaultExpanded = outline?.defaultExpanded ?? true;
40
- if (defaultExpanded === true) return true;
41
- if (defaultExpanded === false) return false;
42
- return depth === 0;
39
+ const defaultValue = defaultExpanded === true ? true : defaultExpanded === false ? false : depth === 0;
40
+ if (toggledKeys.has(key)) return !defaultValue;
41
+ return defaultValue;
43
42
  }
44
43
  function compareByOrder(a, b, order) {
45
44
  if (Array.isArray(order)) {
@@ -86,7 +85,8 @@ function buildFieldRows(level, docs, depth, levelIndex, ctx, scopeKey) {
86
85
  }
87
86
  return Array.from(groups.values()).sort((a, b) => compareByOrder(a, b, level.order)).flatMap((group) => {
88
87
  const rowKey = `${scopeKey}/field:${level.field}:${group.key}`;
89
- const expanded = shouldExpand(rowKey, depth, ctx.outline, ctx.collapsedKeys);
88
+ const expanded = shouldExpand(rowKey, depth, ctx.outline, ctx.toggledKeys);
89
+ const meta = ctx.metaForValue(group.value, level.field);
90
90
  return [{
91
91
  kind: "group",
92
92
  key: rowKey,
@@ -94,7 +94,9 @@ function buildFieldRows(level, docs, depth, levelIndex, ctx, scopeKey) {
94
94
  depth,
95
95
  count: group.docs.length,
96
96
  expandable: true,
97
- collapsed: !expanded
97
+ collapsed: !expanded,
98
+ ...meta?.icon != null && { icon: meta.icon },
99
+ ...meta?.className && { className: meta.className }
98
100
  }, ...expanded ? buildLevelRows(group.docs, depth + 1, levelIndex + 1, ctx, rowKey) : []];
99
101
  });
100
102
  }
@@ -118,7 +120,7 @@ function buildRelationFieldRows(level, docs, depth, levelIndex, ctx, scopeKey) {
118
120
  }
119
121
  return Array.from(groups.values()).sort((a, b) => compareByOrder(a, b, level.order)).flatMap((group) => {
120
122
  const rowKey = `${scopeKey}/relation-field:${fieldPath}:${group.key}`;
121
- const expanded = shouldExpand(rowKey, depth, ctx.outline, ctx.collapsedKeys);
123
+ const expanded = shouldExpand(rowKey, depth, ctx.outline, ctx.toggledKeys);
122
124
  return [{
123
125
  kind: "group",
124
126
  key: rowKey,
@@ -151,7 +153,22 @@ function buildEdgeRows(level, docs, depth, levelIndex, ctx, scopeKey) {
151
153
  const childId = getId(childValue);
152
154
  if (!parentId || !childId || !docsById.has(childId)) continue;
153
155
  if (docsById.has(parentId) || !isRecord(parentValue)) continue;
154
- docsById.set(parentId, parentValue);
156
+ docsById.set(parentId, ctx.allDocsById?.get(parentId) ?? parentValue);
157
+ changed = true;
158
+ }
159
+ if (!changed) break;
160
+ }
161
+ if (ctx.allDocsById) for (let pass = 0; pass < ctx.maxDepth; pass++) {
162
+ let changed = false;
163
+ for (const edge of edgeDocs) {
164
+ const parentId = getId(getPathValue(edge, level.parentField));
165
+ const childId = getId(getPathValue(edge, level.childField));
166
+ if (!parentId || !childId) continue;
167
+ if (!docsById.has(parentId)) continue;
168
+ if (docsById.has(childId)) continue;
169
+ const childDoc = ctx.allDocsById.get(childId);
170
+ if (!childDoc) continue;
171
+ docsById.set(childId, childDoc);
155
172
  changed = true;
156
173
  }
157
174
  if (!changed) break;
@@ -201,7 +218,7 @@ function buildEdgeRows(level, docs, depth, levelIndex, ctx, scopeKey) {
201
218
  }
202
219
  for (const [key, group] of groups) {
203
220
  const rowKey = `${scopeKey}/edge-group:${level.collection}:${parentId}:${key}`;
204
- const expanded = shouldExpand(rowKey, nextDepth, ctx.outline, ctx.collapsedKeys);
221
+ const expanded = shouldExpand(rowKey, nextDepth, ctx.outline, ctx.toggledKeys);
205
222
  rows.push({
206
223
  kind: "group",
207
224
  key: rowKey,
@@ -223,18 +240,15 @@ function buildEdgeRows(level, docs, depth, levelIndex, ctx, scopeKey) {
223
240
  if (!doc || ancestorIds.has(id)) return;
224
241
  const rowKey = `${scopeKey}/record:${id}`;
225
242
  const hasChildren = (childrenByParent.get(id) ?? []).some((child) => docsById.has(child.childId));
226
- const expanded = hasChildren && shouldExpand(rowKey, rowDepth, ctx.outline, ctx.collapsedKeys);
227
243
  rows.push({
228
244
  kind: "record",
229
245
  key: rowKey,
230
246
  id,
231
247
  doc,
232
- depth: rowDepth,
233
- expandable: hasChildren,
234
- collapsed: hasChildren ? !expanded : void 0
248
+ depth: rowDepth
235
249
  });
236
250
  visited.add(id);
237
- if (hasChildren && expanded) {
251
+ if (hasChildren) {
238
252
  const nextAncestors = new Set(ancestorIds);
239
253
  nextAncestors.add(id);
240
254
  pushChildRows(id, rowDepth + 1, branchDepth, nextAncestors);
@@ -297,7 +311,7 @@ function buildPathRows(level, docs, depth, levelIndex, ctx, scopeKey) {
297
311
  function walk(node, rowDepth) {
298
312
  const rows = [];
299
313
  for (const child of Array.from(node.children.values()).sort((a, b) => a.label.localeCompare(b.label))) {
300
- const expanded = shouldExpand(child.key, rowDepth, ctx.outline, ctx.collapsedKeys);
314
+ const expanded = shouldExpand(child.key, rowDepth, ctx.outline, ctx.toggledKeys);
301
315
  rows.push({
302
316
  kind: "synthetic",
303
317
  key: child.key,
@@ -342,15 +356,26 @@ function buildLevelRows(docs, depth, levelIndex, ctx, scopeKey = "root") {
342
356
  if (level.kind === "edge") return buildEdgeRows(level, docs, depth, levelIndex, ctx, scopeKey);
343
357
  return buildPathRows(level, docs, depth, levelIndex, ctx, scopeKey);
344
358
  }
345
- function buildOutlineRows({ docs, outline, edgesByCollection = {}, collapsedKeys, labelForValue = defaultLabelForValue, maxDepth }) {
359
+ const noMeta = () => void 0;
360
+ function buildOutlineRows({ docs, outline, edgesByCollection = {}, toggledKeys, collapsedKeys, labelForValue = defaultLabelForValue, metaForValue = noMeta, maxDepth, allDocs }) {
346
361
  const levels = outline?.levels?.filter(Boolean) ?? [];
362
+ let allDocsById;
363
+ if (allDocs && allDocs.length > 0) {
364
+ allDocsById = /* @__PURE__ */ new Map();
365
+ for (const doc of allDocs) {
366
+ const id = getId(doc);
367
+ if (id) allDocsById.set(id, doc);
368
+ }
369
+ }
347
370
  const ctx = {
348
371
  levels,
349
372
  outline,
350
373
  edgesByCollection,
351
- collapsedKeys: new Set(collapsedKeys ?? []),
374
+ toggledKeys: new Set(toggledKeys ?? collapsedKeys ?? []),
352
375
  labelForValue,
353
- maxDepth: maxDepth ?? outline?.maxDepth ?? DEFAULT_MAX_DEPTH
376
+ metaForValue,
377
+ maxDepth: maxDepth ?? outline?.maxDepth ?? DEFAULT_MAX_DEPTH,
378
+ allDocsById
354
379
  };
355
380
  if (levels.length === 0) return buildLevelRows(docs, 0, 0, {
356
381
  ...ctx,
@@ -0,0 +1,45 @@
1
+ "use client";
2
+
3
+ import { useResolveText } from "../../i18n/hooks.mjs";
4
+ import { cn } from "../../lib/utils.mjs";
5
+ import { ComponentRenderer } from "../../components/component-renderer.mjs";
6
+ import { Button } from "../../components/ui/button.mjs";
7
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../../components/ui/tooltip.mjs";
8
+ import { cloneFilters, filtersEqual } from "../../lib/view-filter-utils.mjs";
9
+ import "react";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+
12
+ //#region src/client/views/collection/quick-filter-bar.tsx
13
+ function QuickFilterBar({ quickFilters, currentFilters, onApply }) {
14
+ const resolveText = useResolveText();
15
+ const visibleFilters = quickFilters?.filter((filter) => filter.filters) ?? [];
16
+ if (visibleFilters.length === 0) return null;
17
+ return /* @__PURE__ */ jsx("div", {
18
+ className: "flex min-w-0 flex-wrap items-center gap-2",
19
+ children: visibleFilters.map((filter) => {
20
+ const active = filtersEqual(currentFilters, filter.filters);
21
+ const label = resolveText(filter.label, filter.id);
22
+ const description = resolveText(filter.description);
23
+ const button = /* @__PURE__ */ jsxs(Button, {
24
+ variant: active ? "secondary" : "outline",
25
+ size: "sm",
26
+ className: cn("gap-1.5 text-xs", active && "border-border-strong bg-muted text-foreground"),
27
+ "aria-pressed": active,
28
+ onClick: () => onApply(cloneFilters(filter.filters)),
29
+ children: [/* @__PURE__ */ jsx(ComponentRenderer, {
30
+ reference: filter.icon,
31
+ additionalProps: { className: "size-4 shrink-0" }
32
+ }), /* @__PURE__ */ jsx("span", { children: label })]
33
+ }, filter.id);
34
+ if (!description) return button;
35
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, { render: button }), /* @__PURE__ */ jsx(TooltipContent, {
36
+ side: "bottom",
37
+ align: "start",
38
+ children: description
39
+ })] }, filter.id);
40
+ })
41
+ });
42
+ }
43
+
44
+ //#endregion
45
+ export { QuickFilterBar };
@@ -3,6 +3,7 @@ import { cn } from "../../lib/utils.mjs";
3
3
  import { selectRealtime, useAdminStore } from "../../runtime/provider.mjs";
4
4
  import { useSafeContentLocales } from "../../runtime/content-locales-provider.mjs";
5
5
  import { useScopedLocale } from "../../runtime/locale-scope.mjs";
6
+ import { resolveIconElement } from "../../components/component-renderer.mjs";
6
7
  import { flattenOptions } from "../../components/primitives/types.mjs";
7
8
  import { resolveOptionLabelForValue } from "../../components/primitives/option-label.mjs";
8
9
  import { Button } from "../../components/ui/button.mjs";
@@ -14,6 +15,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "../../components/ui/too
14
15
  import { Checkbox } from "../../components/ui/checkbox.mjs";
15
16
  import { createActionRegistryProxy } from "../../builder/types/action-registry.mjs";
16
17
  import { ActionButton } from "../../components/actions/action-button.mjs";
18
+ import { adminCollectionKey } from "../../hooks/query-access.mjs";
17
19
  import { useCollectionFields } from "../../hooks/use-collection-fields.mjs";
18
20
  import { useSuspenseCollectionMeta } from "../../hooks/use-collection-meta.mjs";
19
21
  import { ActionDialog } from "../../components/actions/action-dialog.mjs";
@@ -33,17 +35,18 @@ import { useUpload } from "../../hooks/use-upload.mjs";
33
35
  import { AssetPreview } from "../../components/primitives/asset-preview.mjs";
34
36
  import { Dropzone } from "../../components/primitives/dropzone.mjs";
35
37
  import { useDebouncedValue, useSearch } from "../../hooks/use-search.mjs";
38
+ import { SearchInput } from "../../components/ui/search-input.mjs";
36
39
  import { autoExpandFields, hasFieldsToExpand } from "../../utils/auto-expand-fields.mjs";
37
40
  import { computeDefaultColumns, getAllAvailableFields } from "./columns/column-defaults.mjs";
38
41
  import { buildColumns } from "./columns/build-columns.mjs";
39
42
  import { HeaderActions } from "../../components/actions/header-actions.mjs";
40
43
  import { FilterBuilderSheet } from "../../components/filter-builder/filter-builder-sheet.mjs";
41
- import { SearchInput } from "../../components/ui/search-input.mjs";
42
44
  import { useActions } from "../../hooks/use-action.mjs";
43
45
  import { useRealtimeHighlight } from "../../hooks/use-realtime-highlight.mjs";
44
46
  import { useDeleteSavedView, useSaveView, useSavedViews } from "../../hooks/use-saved-views.mjs";
45
47
  import { useViewState } from "../../hooks/use-view-state.mjs";
46
48
  import { BulkActionToolbar } from "./bulk-action-toolbar.mjs";
49
+ import { QuickFilterBar } from "./quick-filter-bar.mjs";
47
50
  import { Icon } from "@iconify/react";
48
51
  import * as React from "react";
49
52
  import { Suspense, useMemo, useState } from "react";
@@ -258,6 +261,8 @@ function mapListSchemaToConfig(list) {
258
261
  const config = {};
259
262
  if (list.columns?.length) config.columns = list.columns;
260
263
  if (list.defaultSort) config.defaultSort = list.defaultSort;
264
+ if (list.defaultFilters?.length) config.defaultFilters = list.defaultFilters;
265
+ if (list.quickFilters?.length) config.quickFilters = list.quickFilters;
261
266
  if (list.orderable) config.orderable = list.orderable;
262
267
  if (Array.isArray(list.searchable) && list.searchable.length) {
263
268
  config.searchFields = list.searchable;
@@ -398,6 +403,7 @@ function TableView(props) {
398
403
  */
399
404
  function TableViewInner({ collection, config, viewConfig, navigate, basePath = "/admin", showSearch = true, showFilters = true, showToolbar = true, realtime, headerActions, emptyState, actionsConfig }) {
400
405
  "use no memo";
406
+ const collectionKey = adminCollectionKey(collection);
401
407
  const globalRealtimeConfig = useAdminStore(selectRealtime);
402
408
  const { fields: resolvedFields, schema } = useCollectionFields(collection, { fallbackFields: config?.fields });
403
409
  const { collections: uploadCollections } = useUploadCollection();
@@ -475,10 +481,16 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
475
481
  const orderField = "order";
476
482
  const orderDirection = typeof orderableConfig === "object" ? orderableConfig.direction ?? "asc" : "asc";
477
483
  const orderStep = typeof orderableConfig === "object" ? orderableConfig.step ?? 10 : 10;
478
- const viewState = useViewState(defaultColumns, {
484
+ const defaultFilters = useMemo(() => resolvedListConfig?.defaultFilters ?? [], [resolvedListConfig?.defaultFilters]);
485
+ const viewState = useViewState(defaultColumns, useMemo(() => ({
479
486
  realtime: resolvedRealtime,
480
- groupBy: defaultGroupBy
481
- }, collection, user?.id);
487
+ groupBy: defaultGroupBy,
488
+ filters: defaultFilters
489
+ }), [
490
+ resolvedRealtime,
491
+ defaultGroupBy,
492
+ defaultFilters
493
+ ]), collection, user?.id);
482
494
  const effectiveRealtime = viewState.config.realtime ?? resolvedRealtime;
483
495
  const visibleColumnsForExpansion = viewState.config.visibleColumns.length > 0 ? viewState.config.visibleColumns : defaultColumns;
484
496
  const expandedFields = useMemo(() => autoExpandFields({
@@ -492,7 +504,7 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
492
504
  visibleColumnsForExpansion,
493
505
  collectionMeta?.relations
494
506
  ]);
495
- const isKnownSortField = React.useCallback((field) => !!field && (field === "_title" || !!resolvedFields?.[field]), [resolvedFields]);
507
+ const isKnownSortField = React.useCallback((field) => !!field && (field === "_title" || field === "createdAt" || field === "updatedAt" || !!resolvedFields?.[field]), [resolvedFields]);
496
508
  const hasOrderField = isKnownSortField(orderField);
497
509
  const canUseOrderableSort = isOrderableEnabled && hasOrderField;
498
510
  const effectiveSort = useMemo(() => {
@@ -666,15 +678,15 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
666
678
  limit: 100,
667
679
  highlights: true
668
680
  }, { enabled: isSearching });
669
- const { data: listData, isLoading: listLoading, error: listError } = useCollectionList(collection, queryOptions, { enabled: !isSearching }, { realtime: effectiveRealtime });
681
+ const { data: listData, isLoading: listLoading, error: listError } = useCollectionList(collectionKey, queryOptions, { enabled: !isSearching }, { realtime: effectiveRealtime });
670
682
  const isLoading = isSearching ? searchLoading : listLoading;
671
683
  const isSearchActive = isSearching && searchFetching;
672
684
  const { data: savedViewsData, isLoading: savedViewsLoading } = useSavedViews(collection, user?.id);
673
685
  const saveViewMutation = useSaveView(collection, user?.id);
674
686
  const deleteViewMutation = useDeleteSavedView(collection, user?.id);
675
- const deleteMutation = useCollectionDelete(collection);
676
- const restoreMutation = useCollectionRestore(collection);
677
- const updateBatchMutation = useCollectionUpdateBatch(collection);
687
+ const deleteMutation = useCollectionDelete(collectionKey);
688
+ const restoreMutation = useCollectionRestore(collectionKey);
689
+ const updateBatchMutation = useCollectionUpdateBatch(collectionKey);
678
690
  const availableFields = useMemo(() => {
679
691
  return getAllAvailableFields(resolvedFields, { meta: collectionMeta });
680
692
  }, [resolvedFields, collectionMeta]);
@@ -875,13 +887,23 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
875
887
  orderDirection,
876
888
  viewState
877
889
  ]);
878
- const hasViewOptionsState = hasActiveFilters || !!viewState.config.groupBy || viewState.config.visibleColumns.length !== defaultColumns.length || !!viewState.config.includeDeleted;
890
+ const hasViewOptionsState = hasActiveFilters || !!viewState.config.sortConfig || !!viewState.config.groupBy || viewState.config.visibleColumns.length !== defaultColumns.length || !!viewState.config.includeDeleted;
879
891
  const clearFilters = () => {
880
892
  viewState.setConfig({
881
893
  ...viewState.config,
882
894
  filters: []
883
895
  });
884
896
  };
897
+ const applyQuickFilters = React.useCallback((filters) => {
898
+ viewState.setConfig((current) => ({
899
+ ...current,
900
+ filters,
901
+ pagination: {
902
+ ...current.pagination ?? { pageSize: 25 },
903
+ page: 1
904
+ }
905
+ }));
906
+ }, [viewState]);
885
907
  const exitReorderMode = React.useCallback(() => {
886
908
  setOptimisticOrderIds(null);
887
909
  setIsReorderMode(false);
@@ -992,6 +1014,14 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
992
1014
  const groupField = groupableFields.find((field) => field.name === groupBy);
993
1015
  const collapsedGroups = new Set(viewState.config.collapsedGroups ?? []);
994
1016
  const serverGroups = !isSearching ? listData?.groups : void 0;
1017
+ const iconForValue = (value) => {
1018
+ if (groupField?.type !== "select") return null;
1019
+ const options = groupField.options?.options;
1020
+ if (!Array.isArray(options)) return null;
1021
+ const option = flattenOptions(options).find((opt) => String(opt.value) === String(value));
1022
+ if (!option?.icon) return null;
1023
+ return resolveIconElement(option.icon);
1024
+ };
995
1025
  if (serverGroups?.length) {
996
1026
  const rowsById = new Map(rows.map((row) => [row.id, row]));
997
1027
  return serverGroups.flatMap((group) => {
@@ -1003,6 +1033,7 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
1003
1033
  type: "group",
1004
1034
  key: groupKey,
1005
1035
  label,
1036
+ icon: iconForValue(group.value),
1006
1037
  count: group.count,
1007
1038
  collapsed
1008
1039
  }, ...collapsed ? [] : groupRows.map((row) => ({
@@ -1013,7 +1044,8 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
1013
1044
  }
1014
1045
  const groups = /* @__PURE__ */ new Map();
1015
1046
  for (const row of rows) {
1016
- const valueLabel = stringifyGroupValue(row.original?.[groupBy], groupField, resolveText, t, uiLocale, t("common.noValue"));
1047
+ const rawValue = row.original?.[groupBy];
1048
+ const valueLabel = stringifyGroupValue(rawValue, groupField, resolveText, t, uiLocale, t("common.noValue"));
1017
1049
  const groupKey = `${groupBy}:${valueLabel}`;
1018
1050
  const group = groups.get(groupKey);
1019
1051
  if (group) {
@@ -1022,8 +1054,9 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
1022
1054
  }
1023
1055
  groups.set(groupKey, {
1024
1056
  label: valueLabel,
1057
+ value: rawValue,
1025
1058
  rows: [row],
1026
- sortIndex: getGroupSortIndex(row.original?.[groupBy], groupField)
1059
+ sortIndex: getGroupSortIndex(rawValue, groupField)
1027
1060
  });
1028
1061
  }
1029
1062
  return Array.from(groups.entries()).sort(([, a], [, b]) => a.sortIndex - b.sortIndex).flatMap(([key, group]) => {
@@ -1032,6 +1065,7 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
1032
1065
  type: "group",
1033
1066
  key,
1034
1067
  label: group.label,
1068
+ icon: iconForValue(group.value),
1035
1069
  count: group.rows.length,
1036
1070
  collapsed
1037
1071
  }, ...collapsed ? [] : group.rows.map((row) => ({
@@ -1212,6 +1246,11 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
1212
1246
  containerClassName: "h-10"
1213
1247
  })
1214
1248
  }),
1249
+ /* @__PURE__ */ jsx(QuickFilterBar, {
1250
+ quickFilters: resolvedListConfig?.quickFilters,
1251
+ currentFilters: viewState.config.filters,
1252
+ onApply: applyQuickFilters
1253
+ }),
1215
1254
  isReorderMode && canUseOrderableSort && /* @__PURE__ */ jsxs("div", {
1216
1255
  className: "border-border/70 bg-muted/30 text-muted-foreground flex min-h-10 items-center justify-between gap-3 border-y px-3 py-2 font-mono text-xs",
1217
1256
  children: [/* @__PURE__ */ jsxs("div", {
@@ -1320,16 +1359,20 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
1320
1359
  children: /* @__PURE__ */ jsxs("button", {
1321
1360
  type: "button",
1322
1361
  "aria-expanded": !entry.collapsed,
1323
- className: "text-muted-foreground hover:text-foreground focus-visible:ring-ring/40 -ml-1 inline-flex min-h-8 items-center gap-2 rounded-md px-1 font-mono text-[11px] font-semibold tracking-[0.12em] uppercase transition-colors focus-visible:ring-2 focus-visible:outline-none",
1362
+ className: "text-muted-foreground hover:text-foreground focus-visible:ring-ring/40 -ml-1 inline-flex min-h-8 items-center gap-2 rounded-md px-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none",
1324
1363
  onClick: () => viewState.toggleCollapsedGroup(entry.key),
1325
1364
  children: [
1326
1365
  /* @__PURE__ */ jsx(Icon, {
1327
- icon: entry.collapsed ? "ph:caret-right" : "ph:caret-down",
1328
- className: "size-3.5 shrink-0"
1366
+ icon: "ph:caret-right-bold",
1367
+ className: cn("size-3 shrink-0 transition-transform", !entry.collapsed && "rotate-90")
1368
+ }),
1369
+ entry.icon && /* @__PURE__ */ jsx("span", {
1370
+ className: "size-4 shrink-0",
1371
+ children: entry.icon
1329
1372
  }),
1330
1373
  /* @__PURE__ */ jsx("span", { children: entry.label }),
1331
1374
  groupingConfig?.showCounts !== false && /* @__PURE__ */ jsx("span", {
1332
- className: "bg-muted text-muted-foreground inline-flex h-5 min-w-5 items-center justify-center rounded-full px-1.5 text-[10px] tracking-normal tabular-nums",
1375
+ className: "text-muted-foreground/60 tabular-nums",
1333
1376
  children: entry.count
1334
1377
  })
1335
1378
  ]
@@ -1543,7 +1586,8 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
1543
1586
  savedViewsLoading,
1544
1587
  onSaveView: handleSaveView,
1545
1588
  onDeleteView: handleDeleteView,
1546
- supportsSoftDelete: collectionMeta?.softDelete ?? false
1589
+ supportsSoftDelete: collectionMeta?.softDelete ?? false,
1590
+ defaultFilters
1547
1591
  }),
1548
1592
  dialogAction && /* @__PURE__ */ jsx(ActionDialog, {
1549
1593
  open: !!dialogAction,
@@ -12,13 +12,13 @@ import { Checkbox } from "../../components/ui/checkbox.mjs";
12
12
  import { DateTimeInput } from "../../components/primitives/date-input.mjs";
13
13
  import { ConfirmationDialog } from "../../components/actions/confirmation-dialog.mjs";
14
14
  import { useGlobalFields } from "../../hooks/use-global-fields.mjs";
15
+ import { ReactiveFieldStatesProvider, useReactiveFields } from "../../hooks/use-reactive-fields.mjs";
15
16
  import { AutoFormFields } from "../collection/auto-form-fields.mjs";
16
17
  import { EmptyState } from "../../components/ui/empty-state.mjs";
17
18
  import { HistorySidebar } from "../../components/history-sidebar.mjs";
18
19
  import { useGlobal, useGlobalRevertVersion, useGlobalUpdate, useGlobalVersions } from "../../hooks/use-global.mjs";
19
20
  import { useGlobalServerValidation } from "../../hooks/use-server-validation.mjs";
20
21
  import { useSidebarSearchParam } from "../../hooks/use-sidebar-search-param.mjs";
21
- import { useReactiveFields } from "../../hooks/use-reactive-fields.mjs";
22
22
  import { useTransitionStage } from "../../hooks/use-transition-stage.mjs";
23
23
  import { detectManyToManyRelations, hasManyToManyRelations } from "../../utils/detect-relations.mjs";
24
24
  import { shouldHandleAdminShortcut } from "../../utils/keyboard-shortcuts.mjs";
@@ -293,7 +293,7 @@ function GlobalFormView({ global: globalName, config, viewConfig, registry, show
293
293
  if (globalData) form.reset(globalData);
294
294
  }, [form, globalData]);
295
295
  const reactiveConfigs = React.useMemo(() => extractReactiveConfigs(globalSchema), [globalSchema]);
296
- useReactiveFields({
296
+ const { fieldStates: reactiveFieldStates } = useReactiveFields({
297
297
  collection: globalName,
298
298
  mode: "global",
299
299
  reactiveConfigs,
@@ -463,13 +463,16 @@ function GlobalFormView({ global: globalName, config, viewConfig, registry, show
463
463
  t
464
464
  })
465
465
  ] })
466
- }), /* @__PURE__ */ jsx(AutoFormFields, {
467
- collection: globalName,
468
- mode: "global",
469
- config: resolvedConfig,
470
- registry,
471
- resolvedFields: schemaFields,
472
- schema: globalSchema
466
+ }), /* @__PURE__ */ jsx(ReactiveFieldStatesProvider, {
467
+ fieldStates: reactiveFieldStates,
468
+ children: /* @__PURE__ */ jsx(AutoFormFields, {
469
+ collection: globalName,
470
+ mode: "global",
471
+ config: resolvedConfig,
472
+ registry,
473
+ resolvedFields: schemaFields,
474
+ schema: globalSchema
475
+ })
473
476
  })]
474
477
  }),
475
478
  isHistoryOpen && /* @__PURE__ */ jsx(HistorySidebar, {
@@ -1,7 +1,7 @@
1
1
  import { Admin } from "../../builder/admin.mjs";
2
2
  import { getAdminLocalesQueryOptions, getAdminTranslationsQueryOptions, getUiLocaleFromCookie } from "../../runtime/translations-provider.mjs";
3
3
  import { AdminProvider } from "../../runtime/provider.mjs";
4
- import { getAdminConfigQueryOptions } from "../../hooks/use-admin-config.mjs";
4
+ import { getAdminConfigQueryOptions, getPublicAdminConfigQueryOptions } from "../../hooks/use-admin-config.mjs";
5
5
  import { AdminThemeAppliedContext, useManagedAdminTheme } from "./admin-theme.mjs";
6
6
  import { AdminLayout } from "./admin-layout.mjs";
7
7
  import { AuthGuard } from "../../components/auth/auth-guard.mjs";
@@ -164,8 +164,10 @@ function isPublicPath(currentPath, basePath, publicPaths) {
164
164
  function AdminLayoutProvider({ admin: adminInput, client, authClient, queryClient, LinkComponent, activeRoute, basePath = "/admin", header, footer, sidebarProps, theme, setTheme, showThemeToggle, toasterProps, className, enableAuthGuard, publicPaths = DEFAULT_PUBLIC_PATHS, requiredRole = "admin", authLoadingFallback, authUnauthorizedFallback, useServerTranslations, translationsFallback, initialUiLocale, children }) {
165
165
  const qc = queryClient ? configureAdminQueryClient(queryClient) : getDefaultQueryClient();
166
166
  const { theme: managedTheme, setTheme: setManagedTheme } = useManagedAdminTheme(theme, setTheme);
167
+ const isCurrentPathPublic = isPublicPath(activeRoute ?? (typeof window !== "undefined" ? window.location.pathname : void 0), basePath, publicPaths);
167
168
  if (client?.routes) {
168
- qc.prefetchQuery(getAdminConfigQueryOptions(client));
169
+ if (isCurrentPathPublic) qc.prefetchQuery(getPublicAdminConfigQueryOptions(client));
170
+ else qc.prefetchQuery(getAdminConfigQueryOptions(client));
169
171
  if (useServerTranslations) {
170
172
  const locale = initialUiLocale ?? getUiLocaleFromCookie() ?? "en";
171
173
  qc.prefetchQuery(getAdminLocalesQueryOptions(client));
@@ -174,7 +176,6 @@ function AdminLayoutProvider({ admin: adminInput, client, authClient, queryClien
174
176
  }
175
177
  const admin = Admin.normalize(adminInput);
176
178
  const shouldUseAuthGuard = enableAuthGuard ?? authClient != null;
177
- const isCurrentPathPublic = isPublicPath(activeRoute ?? (typeof window !== "undefined" ? window.location.pathname : void 0), basePath, publicPaths);
178
179
  let innerContent;
179
180
  if (isCurrentPathPublic) innerContent = children;
180
181
  else {
@@ -9,6 +9,7 @@ import { Toaster } from "../../components/ui/sonner.mjs";
9
9
  import { GlobalSearch } from "../common/global-search.mjs";
10
10
  import { AdminSidebar } from "./admin-sidebar.mjs";
11
11
  import { AdminThemeAppliedContext, useManagedAdminTheme } from "./admin-theme.mjs";
12
+ import { Icon } from "@iconify/react";
12
13
  import * as React from "react";
13
14
  import { jsx, jsxs } from "react/jsx-runtime";
14
15
 
@@ -52,32 +53,118 @@ function shouldRenderShellRail(config, activeRoute, basePath) {
52
53
  if (!(!routes.include?.length || routes.include.some((rule) => routeMatchesRule(activeRoute, rule, basePath, match)))) return false;
53
54
  return !routes.exclude?.some((rule) => routeMatchesRule(activeRoute, rule, basePath, match));
54
55
  }
55
- function toCssLength(value) {
56
- if (value === void 0) return void 0;
57
- return typeof value === "number" ? `${value}px` : value;
56
+ const RAIL_WIDTH_KEY = "qa-admin-secondary-rail-width";
57
+ const RAIL_COLLAPSED_KEY = "qa-admin-secondary-rail-collapsed";
58
+ /**
59
+ * Client-side width + collapsed state for the secondary rail, persisted to
60
+ * localStorage. The server config provides the DEFAULT width and the min/max
61
+ * bounds; the user can resize within those bounds and collapse the rail, and the
62
+ * choice survives reloads.
63
+ */
64
+ function useRailState(defaultWidth, minWidth, maxWidth) {
65
+ const clamp = React.useCallback((w) => Math.min(maxWidth, Math.max(minWidth, Math.round(w))), [minWidth, maxWidth]);
66
+ const [width, setWidthState] = React.useState(() => {
67
+ if (typeof window === "undefined") return defaultWidth;
68
+ const saved = Number(window.localStorage.getItem(RAIL_WIDTH_KEY));
69
+ return Number.isFinite(saved) && saved > 0 ? clamp(saved) : defaultWidth;
70
+ });
71
+ const [collapsed, setCollapsedState] = React.useState(() => typeof window !== "undefined" && window.localStorage.getItem(RAIL_COLLAPSED_KEY) === "1");
72
+ return {
73
+ width,
74
+ setWidth: React.useCallback((w) => {
75
+ const next = clamp(w);
76
+ setWidthState(next);
77
+ if (typeof window !== "undefined") window.localStorage.setItem(RAIL_WIDTH_KEY, String(next));
78
+ }, [clamp]),
79
+ collapsed,
80
+ setCollapsed: React.useCallback((c) => {
81
+ setCollapsedState(c);
82
+ if (typeof window !== "undefined") window.localStorage.setItem(RAIL_COLLAPSED_KEY, c ? "1" : "0");
83
+ }, [])
84
+ };
58
85
  }
59
86
  function AdminShellRail({ config, activeRoute, basePath, navigate }) {
60
87
  const placement = config.placement ?? "left";
61
- const style = {
62
- width: toCssLength(config.width ?? 320),
63
- minWidth: toCssLength(config.minWidth ?? config.width ?? 280),
64
- maxWidth: toCssLength(config.maxWidth)
65
- };
66
- return /* @__PURE__ */ jsx("aside", {
67
- className: cn("qa-admin-layout__secondary-rail bg-background h-svh min-h-0 shrink-0 flex-col overflow-hidden", config.hiddenOnMobile === false ? "flex" : "hidden md:flex", placement === "left" ? "border-border-subtle border-r" : "border-border-subtle border-l", config.className),
88
+ const isRight = placement === "right";
89
+ const { width, setWidth, collapsed, setCollapsed } = useRailState(typeof config.width === "number" ? config.width : 320, typeof config.minWidth === "number" ? config.minWidth : 280, typeof config.maxWidth === "number" ? config.maxWidth : 560);
90
+ const onResizeStart = React.useCallback((e) => {
91
+ e.preventDefault();
92
+ const startX = e.clientX;
93
+ const startWidth = width;
94
+ document.body.style.cursor = "col-resize";
95
+ document.body.style.userSelect = "none";
96
+ const onMove = (ev) => {
97
+ const dx = ev.clientX - startX;
98
+ setWidth(isRight ? startWidth - dx : startWidth + dx);
99
+ };
100
+ const onUp = () => {
101
+ document.body.style.cursor = "";
102
+ document.body.style.userSelect = "";
103
+ window.removeEventListener("pointermove", onMove);
104
+ window.removeEventListener("pointerup", onUp);
105
+ };
106
+ window.addEventListener("pointermove", onMove);
107
+ window.addEventListener("pointerup", onUp);
108
+ }, [
109
+ width,
110
+ isRight,
111
+ setWidth
112
+ ]);
113
+ const baseClass = cn("qa-admin-layout__secondary-rail bg-background h-svh min-h-0 shrink-0 overflow-hidden border-border-subtle", isRight ? "border-l" : "border-r", config.hiddenOnMobile === false ? "flex" : "hidden md:flex");
114
+ if (collapsed) return /* @__PURE__ */ jsx("aside", {
115
+ className: cn(baseClass, "w-11 flex-col items-center"),
68
116
  "data-placement": placement,
69
- style,
70
- children: /* @__PURE__ */ jsx(ComponentRenderer, {
71
- reference: config.component,
72
- additionalProps: {
73
- activeRoute,
74
- basePath,
75
- placement,
76
- config,
77
- navigate
78
- }
117
+ "data-collapsed": "true",
118
+ children: /* @__PURE__ */ jsx("button", {
119
+ type: "button",
120
+ onClick: () => setCollapsed(false),
121
+ className: "text-muted-foreground hover:bg-surface-high hover:text-foreground focus-visible:ring-ring/40 mt-2.5 flex size-8 shrink-0 items-center justify-center rounded-[var(--control-radius-inner)] transition-[background-color,color,box-shadow] duration-[var(--motion-duration-base)] ease-[var(--motion-ease-standard)] focus-visible:ring-2 focus-visible:outline-none motion-reduce:transition-none",
122
+ title: "Expand panel",
123
+ "aria-label": "Expand panel",
124
+ children: /* @__PURE__ */ jsx(Icon, {
125
+ icon: isRight ? "ph:caret-left" : "ph:caret-right",
126
+ width: 16,
127
+ height: 16
128
+ })
79
129
  })
80
130
  });
131
+ return /* @__PURE__ */ jsxs("aside", {
132
+ className: cn(baseClass, "relative flex-col", config.className),
133
+ "data-placement": placement,
134
+ style: { width: `${width}px` },
135
+ children: [
136
+ /* @__PURE__ */ jsx("div", {
137
+ onPointerDown: onResizeStart,
138
+ className: cn("group/resize absolute inset-y-0 z-20 flex w-2 cursor-col-resize items-center justify-center", isRight ? "left-0 -translate-x-1/2" : "right-0 translate-x-1/2"),
139
+ role: "separator",
140
+ "aria-orientation": "vertical",
141
+ "aria-label": "Resize panel",
142
+ children: /* @__PURE__ */ jsx("div", { className: "bg-border-subtle group-hover/resize:bg-border-strong h-full w-px transition-[background-color] duration-[var(--motion-duration-base)] ease-[var(--motion-ease-standard)] motion-reduce:transition-none" })
143
+ }),
144
+ /* @__PURE__ */ jsx("button", {
145
+ type: "button",
146
+ onClick: () => setCollapsed(true),
147
+ className: cn("text-muted-foreground hover:bg-surface-high hover:text-foreground focus-visible:ring-ring/40 absolute top-2.5 z-20 flex size-7 shrink-0 items-center justify-center rounded-[var(--control-radius-inner)] transition-[background-color,color,box-shadow] duration-[var(--motion-duration-base)] ease-[var(--motion-ease-standard)] focus-visible:ring-2 focus-visible:outline-none motion-reduce:transition-none", isRight ? "left-2.5" : "right-2.5"),
148
+ title: "Collapse panel",
149
+ "aria-label": "Collapse panel",
150
+ children: /* @__PURE__ */ jsx(Icon, {
151
+ icon: isRight ? "ph:caret-right" : "ph:caret-left",
152
+ width: 15,
153
+ height: 15
154
+ })
155
+ }),
156
+ /* @__PURE__ */ jsx(ComponentRenderer, {
157
+ reference: config.component,
158
+ additionalProps: {
159
+ activeRoute,
160
+ basePath,
161
+ placement,
162
+ config,
163
+ navigate
164
+ }
165
+ })
166
+ ]
167
+ });
81
168
  }
82
169
  /**
83
170
  * AdminLayout Component
@@ -163,7 +250,7 @@ function AdminLayout({ LinkComponent, activeRoute, basePath = "/admin", brandNam
163
250
  }),
164
251
  secondaryRailConfig?.placement !== "right" && secondaryRail,
165
252
  /* @__PURE__ */ jsxs(SidebarInset, {
166
- className: "qa-admin-layout__content bg-background flex h-svh flex-col overflow-hidden md:rounded-tl-2xl",
253
+ className: "qa-admin-layout__content bg-background flex h-svh scrollbar-none flex-col overflow-hidden md:rounded-t-2xl",
167
254
  children: [
168
255
  shouldShowHeader && header && /* @__PURE__ */ jsx("header", {
169
256
  className: "qa-admin-layout__header border-border-subtle border-b",