@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
@@ -1,16 +1,9 @@
1
1
  //#region src/client/components/fields/rich-text-editor/utils.ts
2
2
  /**
3
- * Get the current heading level or "paragraph"
3
+ * Get editor output in the configured format.
4
4
  */
5
- function getHeadingLevel(editor) {
6
- if (!editor) return "paragraph";
7
- for (let level = 1; level <= 6; level += 1) if (editor.isActive("heading", { level })) return String(level);
8
- return "paragraph";
9
- }
10
- /**
11
- * Get output as TipTap JSON.
12
- */
13
- function getOutput(editor) {
5
+ function getOutput(editor, mode = "json") {
6
+ if (mode === "markdown") return editor.storage.markdown?.getMarkdown?.() ?? "";
14
7
  return editor.getJSON();
15
8
  }
16
9
  /**
@@ -18,7 +11,8 @@ function getOutput(editor) {
18
11
  */
19
12
  function isSameValue(a, b) {
20
13
  if (a === b) return true;
21
- if (!a || !b) return false;
14
+ if (a == null || b == null) return false;
15
+ if (typeof a === "string" || typeof b === "string") return a === b;
22
16
  try {
23
17
  return JSON.stringify(a) === JSON.stringify(b);
24
18
  } catch {
@@ -47,4 +41,4 @@ function getCharacterCount(editor) {
47
41
  }
48
42
 
49
43
  //#endregion
50
- export { getCharacterCount, getHeadingLevel, getOutput, isSameValue };
44
+ export { getCharacterCount, getOutput, isSameValue };
@@ -23,7 +23,7 @@ function resolveOptionIcons(options) {
23
23
  icon: resolveIconElement(option.icon)
24
24
  } : option);
25
25
  }
26
- function SelectField({ name, label, description, placeholder, required, disabled, localized, locale, control, className, options, loadOptions, multiple, clearable, maxSelections, emptyMessage }) {
26
+ function SelectField({ name, label, description, placeholder, required, disabled, localized, locale, hideLabel, control, className, options, loadOptions, multiple, clearable, maxSelections, emptyMessage }) {
27
27
  const resolvedControl = useResolvedControl(control);
28
28
  const resolvedOptions = useMemo(() => resolveOptionIcons(options), [options]);
29
29
  const resolvedLoadOptions = useMemo(() => {
@@ -43,6 +43,7 @@ function SelectField({ name, label, description, placeholder, required, disabled
43
43
  disabled,
44
44
  localized,
45
45
  locale,
46
+ hideLabel,
46
47
  error: fieldState.error?.message,
47
48
  children: multiple ? /* @__PURE__ */ jsx(SelectMulti, {
48
49
  id: name,
@@ -6,7 +6,7 @@ import { jsx } from "react/jsx-runtime";
6
6
  import { Controller } from "react-hook-form";
7
7
 
8
8
  //#region src/client/components/fields/text-field.tsx
9
- function TextField({ name, label, description, placeholder, required, disabled, localized, locale, control, className, type = "text", maxLength, autoComplete }) {
9
+ function TextField({ name, label, description, placeholder, required, disabled, localized, locale, hideLabel, control, className, type = "text", maxLength, autoComplete }) {
10
10
  return /* @__PURE__ */ jsx(Controller, {
11
11
  name,
12
12
  control: useResolvedControl(control),
@@ -18,6 +18,7 @@ function TextField({ name, label, description, placeholder, required, disabled,
18
18
  disabled,
19
19
  localized,
20
20
  locale,
21
+ hideLabel,
21
22
  error: fieldState.error?.message,
22
23
  children: /* @__PURE__ */ jsx(TextInput, {
23
24
  id: name,
@@ -6,7 +6,7 @@ import { jsx } from "react/jsx-runtime";
6
6
  import { Controller } from "react-hook-form";
7
7
 
8
8
  //#region src/client/components/fields/textarea-field.tsx
9
- function TextareaField({ name, label, description, placeholder, required, disabled, localized, locale, control, className, rows, maxLength, autoResize }) {
9
+ function TextareaField({ name, label, description, placeholder, required, disabled, localized, locale, hideLabel, control, className, rows, maxLength, autoResize }) {
10
10
  return /* @__PURE__ */ jsx(Controller, {
11
11
  name,
12
12
  control: useResolvedControl(control),
@@ -18,6 +18,7 @@ function TextareaField({ name, label, description, placeholder, required, disabl
18
18
  disabled,
19
19
  localized,
20
20
  locale,
21
+ hideLabel,
21
22
  error: fieldState.error?.message,
22
23
  children: /* @__PURE__ */ jsx(TextareaInput, {
23
24
  id: name,
@@ -6,7 +6,7 @@ import { jsx } from "react/jsx-runtime";
6
6
  import { Controller } from "react-hook-form";
7
7
 
8
8
  //#region src/client/components/fields/time-field.tsx
9
- function TimeField({ name, label, description, placeholder, required, disabled, localized, locale, control, className, precision }) {
9
+ function TimeField({ name, label, description, placeholder, required, disabled, localized, locale, hideLabel, control, className, precision }) {
10
10
  return /* @__PURE__ */ jsx(Controller, {
11
11
  name,
12
12
  control: useResolvedControl(control),
@@ -19,6 +19,7 @@ function TimeField({ name, label, description, placeholder, required, disabled,
19
19
  disabled,
20
20
  localized,
21
21
  locale,
22
+ hideLabel,
22
23
  error: fieldState.error?.message,
23
24
  children: /* @__PURE__ */ jsx(TimeInput, {
24
25
  id: name,
@@ -4,6 +4,7 @@ import { Sheet, SheetContent, SheetFooter, SheetHeader, SheetTitle } from "../ui
4
4
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs.mjs";
5
5
  import { Switch } from "../ui/switch.mjs";
6
6
  import { SelectSingle } from "../primitives/select-single.mjs";
7
+ import { cloneFilters, filtersEqual, sortConfigEqual } from "../../lib/view-filter-utils.mjs";
7
8
  import { ColumnsTab } from "./columns-tab.mjs";
8
9
  import { FiltersTab } from "./filters-tab.mjs";
9
10
  import { SavedViewsTab } from "./saved-views-tab.mjs";
@@ -13,22 +14,10 @@ import { jsx, jsxs } from "react/jsx-runtime";
13
14
 
14
15
  //#region src/client/components/filter-builder/filter-builder-sheet.tsx
15
16
  const NO_GROUPING_VALUE = "__none";
17
+ const NO_SORT_VALUE = "__none";
16
18
  function arraysEqual(a, b, eq) {
17
19
  return a.length === b.length && a.every((v, i) => eq(v, b[i]));
18
20
  }
19
- function filterValueEqual(a, b) {
20
- if (a === b) return true;
21
- if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((v, i) => v === b[i]);
22
- return false;
23
- }
24
- function filtersEqual(a, b) {
25
- return arraysEqual(a, b, (x, y) => x.id === y.id && x.field === y.field && x.operator === y.operator && filterValueEqual(x.value, y.value));
26
- }
27
- function sortConfigEqual(a, b) {
28
- if (a === b) return true;
29
- if (!a || !b) return false;
30
- return a.field === b.field && a.direction === b.direction;
31
- }
32
21
  function viewConfigEqual(a, b) {
33
22
  return filtersEqual(a.filters, b.filters) && sortConfigEqual(a.sortConfig, b.sortConfig) && arraysEqual(a.visibleColumns, b.visibleColumns, (x, y) => x === y) && a.groupBy === b.groupBy && arraysEqual(a.collapsedGroups ?? [], b.collapsedGroups ?? [], (x, y) => x === y) && a.realtime === b.realtime && a.includeDeleted === b.includeDeleted;
34
23
  }
@@ -67,9 +56,10 @@ function ViewOptionRow({ title, description, control }) {
67
56
  })]
68
57
  });
69
58
  }
70
- function FilterBuilderSheet({ collection, availableFields, currentConfig, onConfigChange, isOpen, onOpenChange, defaultColumns, savedViews, savedViewsLoading = false, onSaveView, onDeleteView, supportsSoftDelete = false, groupableFields = EMPTY_GROUPABLE_FIELDS, defaultGroupBy = null }) {
59
+ function FilterBuilderSheet({ collection, availableFields, currentConfig, onConfigChange, isOpen, onOpenChange, defaultColumns, savedViews, savedViewsLoading = false, onSaveView, onDeleteView, supportsSoftDelete = false, groupableFields = EMPTY_GROUPABLE_FIELDS, defaultGroupBy = null, defaultFilters = [], panels }) {
71
60
  const resolvedSavedViews = savedViews ?? EMPTY_SAVED_VIEWS;
72
61
  const { t } = useTranslation();
62
+ const { columns: showColumns = true, filters: showFilters = true, savedViews: showSavedViews = true } = panels ?? {};
73
63
  const [localConfig, setLocalConfig] = useState(currentConfig);
74
64
  const [prev, setPrev] = useState({
75
65
  isOpen,
@@ -94,7 +84,7 @@ function FilterBuilderSheet({ collection, availableFields, currentConfig, onConf
94
84
  };
95
85
  const handleReset = () => {
96
86
  setLocalConfig({
97
- filters: [],
87
+ filters: cloneFilters(defaultFilters),
98
88
  sortConfig: null,
99
89
  visibleColumns: defaultColumns ?? availableFields.map((f) => f.name),
100
90
  groupBy: defaultGroupBy,
@@ -119,6 +109,21 @@ function FilterBuilderSheet({ collection, availableFields, currentConfig, onConf
119
109
  className: "size-4 opacity-60"
120
110
  })
121
111
  }))], [groupableFields, t]);
112
+ const sortOptions = useMemo(() => [{
113
+ value: NO_SORT_VALUE,
114
+ label: t("viewOptions.noSort"),
115
+ icon: /* @__PURE__ */ jsx(Icon, {
116
+ icon: "ph:sort-ascending",
117
+ className: "size-4 opacity-60"
118
+ })
119
+ }, ...availableFields.map((field) => ({
120
+ value: field.name,
121
+ label: field.label,
122
+ icon: /* @__PURE__ */ jsx(Icon, {
123
+ icon: "ph:text-aa",
124
+ className: "size-4 opacity-60"
125
+ })
126
+ }))], [availableFields, t]);
122
127
  return /* @__PURE__ */ jsx(Sheet, {
123
128
  open: isOpen,
124
129
  onOpenChange,
@@ -182,21 +187,69 @@ function FilterBuilderSheet({ collection, availableFields, currentConfig, onConf
182
187
  drawerTitle: t("viewOptions.groupBy"),
183
188
  className: "h-9 w-48"
184
189
  })
190
+ }),
191
+ /* @__PURE__ */ jsx(ViewOptionRow, {
192
+ title: t("viewOptions.sort"),
193
+ description: t("viewOptions.sortDescription"),
194
+ control: /* @__PURE__ */ jsxs("div", {
195
+ className: "flex items-center gap-2",
196
+ children: [/* @__PURE__ */ jsx(SelectSingle, {
197
+ id: "view-options-sort",
198
+ value: localConfig.sortConfig?.field ?? NO_SORT_VALUE,
199
+ onChange: (value) => {
200
+ const nextSortField = !value || value === NO_SORT_VALUE ? null : value;
201
+ setLocalConfig((prevConfig) => ({
202
+ ...prevConfig,
203
+ sortConfig: nextSortField ? {
204
+ field: nextSortField,
205
+ direction: prevConfig.sortConfig?.direction ?? "asc"
206
+ } : null,
207
+ pagination: {
208
+ ...prevConfig.pagination ?? { pageSize: 25 },
209
+ page: 1
210
+ }
211
+ }));
212
+ },
213
+ options: sortOptions,
214
+ clearable: false,
215
+ emptyMessage: t("viewOptions.noFieldsAvailable"),
216
+ placeholder: t("viewOptions.noSort"),
217
+ drawerTitle: t("viewOptions.sort"),
218
+ className: "h-9 w-48"
219
+ }), /* @__PURE__ */ jsx(Button, {
220
+ variant: "outline",
221
+ size: "icon-sm",
222
+ disabled: !localConfig.sortConfig?.field,
223
+ "aria-label": localConfig.sortConfig?.direction === "asc" ? t("table.sortDesc") : t("table.sortAsc"),
224
+ onClick: () => setLocalConfig((prevConfig) => ({
225
+ ...prevConfig,
226
+ sortConfig: prevConfig.sortConfig ? {
227
+ ...prevConfig.sortConfig,
228
+ direction: prevConfig.sortConfig.direction === "asc" ? "desc" : "asc"
229
+ } : null,
230
+ pagination: {
231
+ ...prevConfig.pagination ?? { pageSize: 25 },
232
+ page: 1
233
+ }
234
+ })),
235
+ children: /* @__PURE__ */ jsx(Icon, { icon: localConfig.sortConfig?.direction === "asc" ? "ph:sort-ascending" : "ph:sort-descending" })
236
+ })]
237
+ })
185
238
  })
186
239
  ]
187
240
  }), /* @__PURE__ */ jsxs(Tabs, {
188
- defaultValue: "columns",
241
+ defaultValue: showColumns ? "columns" : "filters",
189
242
  className: "mt-4",
190
243
  children: [
191
244
  /* @__PURE__ */ jsxs(TabsList, {
192
245
  className: "w-full",
193
246
  children: [
194
- /* @__PURE__ */ jsx(TabsTrigger, {
247
+ showColumns && /* @__PURE__ */ jsx(TabsTrigger, {
195
248
  value: "columns",
196
249
  className: "flex-1",
197
250
  children: t("viewOptions.columns")
198
251
  }),
199
- /* @__PURE__ */ jsxs(TabsTrigger, {
252
+ showFilters && /* @__PURE__ */ jsxs(TabsTrigger, {
200
253
  value: "filters",
201
254
  className: "flex-1",
202
255
  children: [t("viewOptions.filters"), localConfig.filters.length > 0 && /* @__PURE__ */ jsx("span", {
@@ -204,14 +257,14 @@ function FilterBuilderSheet({ collection, availableFields, currentConfig, onConf
204
257
  children: localConfig.filters.length
205
258
  })]
206
259
  }),
207
- /* @__PURE__ */ jsx(TabsTrigger, {
260
+ showSavedViews && /* @__PURE__ */ jsx(TabsTrigger, {
208
261
  value: "views",
209
262
  className: "flex-1",
210
263
  children: t("viewOptions.savedViews")
211
264
  })
212
265
  ]
213
266
  }),
214
- /* @__PURE__ */ jsx(TabsContent, {
267
+ showColumns && /* @__PURE__ */ jsx(TabsContent, {
215
268
  value: "columns",
216
269
  children: /* @__PURE__ */ jsx(ColumnsTab, {
217
270
  fields: availableFields,
@@ -222,7 +275,7 @@ function FilterBuilderSheet({ collection, availableFields, currentConfig, onConf
222
275
  }))
223
276
  })
224
277
  }),
225
- /* @__PURE__ */ jsx(TabsContent, {
278
+ showFilters && /* @__PURE__ */ jsx(TabsContent, {
226
279
  value: "filters",
227
280
  children: /* @__PURE__ */ jsx(FiltersTab, {
228
281
  fields: availableFields,
@@ -233,7 +286,7 @@ function FilterBuilderSheet({ collection, availableFields, currentConfig, onConf
233
286
  }))
234
287
  })
235
288
  }),
236
- /* @__PURE__ */ jsx(TabsContent, {
289
+ showSavedViews && /* @__PURE__ */ jsx(TabsContent, {
237
290
  value: "views",
238
291
  children: /* @__PURE__ */ jsx(SavedViewsTab, {
239
292
  collection,
@@ -96,18 +96,18 @@ function SectionRenderer({ section, index, ctx }) {
96
96
  const value = `section-${index}`;
97
97
  return /* @__PURE__ */ jsx(Accordion, {
98
98
  defaultValue: section.defaultCollapsed !== true ? [value] : [],
99
- className: "w-full",
99
+ className: "qa-field-layout__section qa-field-layout__section--collapsible panel-surface bg-card overflow-hidden",
100
100
  children: /* @__PURE__ */ jsxs(AccordionItem, {
101
101
  value,
102
- className: "border-transparent px-4",
102
+ className: "border-none px-0 data-open:bg-transparent",
103
103
  children: [/* @__PURE__ */ jsx(AccordionTrigger, {
104
- className: "hover:no-underline",
104
+ className: "hover:bg-surface-low aria-expanded:bg-surface-low min-h-12 px-4 py-3 hover:no-underline",
105
105
  children: /* @__PURE__ */ jsx("span", {
106
106
  className: "font-semibold",
107
107
  children: ctx.resolveText(section.label, "Section")
108
108
  })
109
109
  }), /* @__PURE__ */ jsxs(AccordionContent, {
110
- className: "pt-2 pb-4",
110
+ className: "border-border-subtle border-t px-4 pt-3 pb-4",
111
111
  children: [section.description && /* @__PURE__ */ jsx("p", {
112
112
  className: "text-muted-foreground mb-4 text-sm text-pretty",
113
113
  children: ctx.resolveText(section.description, "")
@@ -1,5 +1,6 @@
1
1
  import { useSafeI18n } from "../../i18n/hooks.mjs";
2
2
  import { cn } from "../../lib/utils.mjs";
3
+ import { resolveAssetUrl } from "../../utils/asset-url.mjs";
3
4
  import { Skeleton } from "../ui/skeleton.mjs";
4
5
  import { Icon } from "@iconify/react";
5
6
  import * as React from "react";
@@ -62,7 +63,7 @@ function MediaGridSkeleton({ columns = 4 }) {
62
63
  }
63
64
  function AssetItem({ asset, selected, selectionMode, onToggle, onClick }) {
64
65
  const [imageError, setImageError] = React.useState(false);
65
- const thumbnailUrl = asset.url;
66
+ const thumbnailUrl = resolveAssetUrl(asset.url);
66
67
  const isImageType = isImage(asset.mimeType);
67
68
  const showCheckbox = selectionMode !== "none";
68
69
  const handleClick = () => {
@@ -1,5 +1,6 @@
1
1
  import { cn } from "../../lib/utils.mjs";
2
2
  import { Button } from "../ui/button.mjs";
3
+ import { resolveAssetUrl } from "../../utils/asset-url.mjs";
3
4
  import { Skeleton } from "../ui/skeleton.mjs";
4
5
  import { Icon } from "@iconify/react";
5
6
  import * as React from "react";
@@ -76,13 +77,14 @@ function AssetPreview({ asset, pendingFile, onRemove, onEdit, loading = false, p
76
77
  const isImageType = isImage(mimeType);
77
78
  const fileIconName = getFileIconName(mimeType);
78
79
  const extension = getExtension(filename, mimeType);
80
+ const assetUrl = resolveAssetUrl(asset.url);
79
81
  const thumbnailUrl = React.useMemo(() => {
80
82
  if (pendingFile && isImageType) return URL.createObjectURL(pendingFile);
81
- if (asset.url && isImageType) return asset.url;
83
+ if (assetUrl && isImageType) return assetUrl;
82
84
  return null;
83
85
  }, [
84
86
  pendingFile,
85
- asset.url,
87
+ assetUrl,
86
88
  isImageType
87
89
  ]);
88
90
  React.useEffect(() => {
@@ -0,0 +1,100 @@
1
+ import * as React from "react";
2
+ import * as react_jsx_runtime25 from "react/jsx-runtime";
3
+
4
+ //#region src/client/components/primitives/dropzone.d.ts
5
+
6
+ interface DropzoneProps {
7
+ /**
8
+ * Called when files are dropped or selected
9
+ */
10
+ onDrop: (files: File[]) => void;
11
+ /**
12
+ * Accepted file types (MIME types or extensions)
13
+ * @example ["image/*", "application/pdf", ".doc"]
14
+ */
15
+ accept?: string[];
16
+ /**
17
+ * Maximum file size in bytes
18
+ */
19
+ maxSize?: number;
20
+ /**
21
+ * Whether multiple files can be selected
22
+ * @default false
23
+ */
24
+ multiple?: boolean;
25
+ /**
26
+ * Disable the dropzone
27
+ */
28
+ disabled?: boolean;
29
+ /**
30
+ * Show loading state (e.g., during upload)
31
+ */
32
+ loading?: boolean;
33
+ /**
34
+ * Current upload progress (0-100)
35
+ */
36
+ progress?: number;
37
+ /**
38
+ * Visual density of the dropzone.
39
+ * @default "panel"
40
+ */
41
+ variant?: "panel" | "compact";
42
+ /**
43
+ * Custom label text
44
+ * @default "Drop files here or click to browse"
45
+ */
46
+ label?: string;
47
+ /**
48
+ * Helper text shown below the label
49
+ */
50
+ hint?: string;
51
+ /**
52
+ * Error message to display
53
+ */
54
+ error?: string;
55
+ /**
56
+ * Additional className
57
+ */
58
+ className?: string;
59
+ /**
60
+ * Children to render inside the dropzone (custom content)
61
+ */
62
+ children?: React.ReactNode;
63
+ /**
64
+ * Secondary action rendered inside the dropzone.
65
+ */
66
+ action?: {
67
+ label: string;
68
+ icon?: string;
69
+ onClick: () => void;
70
+ disabled?: boolean;
71
+ };
72
+ /**
73
+ * Callback when validation fails
74
+ */
75
+ onValidationError?: (errors: ValidationError[]) => void;
76
+ }
77
+ interface ValidationError {
78
+ file: File;
79
+ type: "type" | "size";
80
+ message: string;
81
+ }
82
+ declare function Dropzone({
83
+ onDrop,
84
+ accept,
85
+ maxSize,
86
+ multiple,
87
+ disabled,
88
+ loading,
89
+ progress,
90
+ variant,
91
+ label,
92
+ hint,
93
+ error,
94
+ className,
95
+ children,
96
+ action,
97
+ onValidationError
98
+ }: DropzoneProps): react_jsx_runtime25.JSX.Element;
99
+ //#endregion
100
+ export { Dropzone };
@@ -6,9 +6,10 @@ import { InputGroup } from "../ui/input-group.mjs";
6
6
  import { jsx } from "react/jsx-runtime";
7
7
 
8
8
  //#region src/client/components/primitives/field-select-control.tsx
9
- function FieldSelectTrigger({ className, hasValue, asInputGroupControl, ...props }) {
9
+ function FieldSelectTrigger({ className, hasValue, asInputGroupControl, static: staticPress = true, ...props }) {
10
10
  return /* @__PURE__ */ jsx(Button, {
11
11
  variant: "outline",
12
+ static: staticPress,
12
13
  "data-slot": asInputGroupControl ? "input-group-control" : void 0,
13
14
  className: cn("qa-field-select-control qa-select-single control-surface w-full justify-between px-3 py-2 font-normal", "hover:bg-surface-low focus-visible:border-border-strong focus-visible:ring-ring/20 aria-expanded:border-border-strong aria-expanded:ring-ring/20 focus-visible:ring-[3px] aria-expanded:ring-[3px]", !hasValue && "text-muted-foreground", asInputGroupControl && "h-full min-w-0 flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0", className),
14
15
  ...props
@@ -0,0 +1,23 @@
1
+ import * as react_jsx_runtime26 from "react/jsx-runtime";
2
+ import { Button } from "@base-ui/react/button";
3
+ import { VariantProps } from "class-variance-authority";
4
+ import * as class_variance_authority_types0 from "class-variance-authority/types";
5
+
6
+ //#region src/client/components/ui/button.d.ts
7
+ declare const buttonVariants: (props?: ({
8
+ variant?: "default" | "link" | "outline" | "secondary" | "ghost" | "destructive" | null | undefined;
9
+ size?: "default" | "icon" | "xs" | "sm" | "lg" | "icon-xs" | "icon-sm" | "icon-lg" | null | undefined;
10
+ } & class_variance_authority_types0.ClassProp) | undefined) => string;
11
+ type ButtonProps = Button.Props & VariantProps<typeof buttonVariants> & {
12
+ static?: boolean;
13
+ };
14
+ declare function Button$1({
15
+ className,
16
+ type,
17
+ variant,
18
+ size,
19
+ static: staticPress,
20
+ ...props
21
+ }: ButtonProps): react_jsx_runtime26.JSX.Element;
22
+ //#endregion
23
+ export { Button$1 as Button };
@@ -6,7 +6,7 @@ import { Button } from "@base-ui/react/button";
6
6
  import { cva } from "class-variance-authority";
7
7
 
8
8
  //#region src/client/components/ui/button.tsx
9
- const buttonVariants = cva("qa-button font-chrome group/button text-foreground focus-visible:border-border-strong focus-visible:ring-ring/20 aria-invalid:border-border-strong aria-invalid:ring-ring/20 inline-flex shrink-0 cursor-pointer items-center justify-center rounded-[var(--control-radius)] border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-[background-color,color,border-color,box-shadow,transform,opacity] duration-150 ease-out outline-none select-none focus-visible:ring-[3px] active:scale-[0.96] disabled:pointer-events-none disabled:opacity-50 disabled:active:scale-100 aria-invalid:ring-[3px] motion-reduce:active:scale-100 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
9
+ const buttonVariants = cva("qa-button font-chrome group/button text-foreground focus-visible:border-border-strong focus-visible:ring-ring/20 aria-invalid:border-border-strong aria-invalid:ring-ring/20 inline-flex shrink-0 cursor-pointer items-center justify-center rounded-[var(--control-radius)] border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-[background-color,color,border-color,box-shadow,transform,opacity] duration-150 ease-out outline-none select-none focus-visible:ring-[3px] active:translate-y-px disabled:pointer-events-none disabled:opacity-50 disabled:active:translate-y-0 aria-invalid:ring-[3px] motion-reduce:active:translate-y-0 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
10
10
  variants: {
11
11
  variant: {
12
12
  default: "bg-primary text-primary-foreground shadow-[var(--control-shadow)] hover:opacity-95",
@@ -39,7 +39,7 @@ function Button$1({ className, type = "button", variant = "default", size = "def
39
39
  className: cn(buttonVariants({
40
40
  variant,
41
41
  size
42
- }), staticPress && "active:scale-100", className),
42
+ }), staticPress && "active:translate-y-0", className),
43
43
  ...props
44
44
  });
45
45
  }
@@ -0,0 +1,49 @@
1
+ import * as React from "react";
2
+ import * as react_jsx_runtime6 from "react/jsx-runtime";
3
+ import { Menu } from "@base-ui/react/menu";
4
+
5
+ //#region src/client/components/ui/dropdown-menu.d.ts
6
+ declare function DropdownMenu({
7
+ ...props
8
+ }: Menu.Root.Props): react_jsx_runtime6.JSX.Element;
9
+ declare function DropdownMenuTrigger({
10
+ type,
11
+ ...props
12
+ }: Menu.Trigger.Props): react_jsx_runtime6.JSX.Element;
13
+ declare function DropdownMenuContent({
14
+ align,
15
+ alignOffset,
16
+ side,
17
+ sideOffset,
18
+ className,
19
+ ...props
20
+ }: Menu.Popup.Props & Pick<Menu.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset">): react_jsx_runtime6.JSX.Element;
21
+ declare function DropdownMenuGroup({
22
+ ...props
23
+ }: Menu.Group.Props): react_jsx_runtime6.JSX.Element;
24
+ declare function DropdownMenuLabel({
25
+ className,
26
+ inset,
27
+ ...props
28
+ }: React.ComponentProps<"div"> & {
29
+ inset?: boolean;
30
+ }): react_jsx_runtime6.JSX.Element;
31
+ declare function DropdownMenuItem({
32
+ className,
33
+ inset,
34
+ variant,
35
+ ...props
36
+ }: Menu.Item.Props & {
37
+ inset?: boolean;
38
+ variant?: "default" | "destructive";
39
+ }): react_jsx_runtime6.JSX.Element;
40
+ declare function DropdownMenuSeparator({
41
+ className,
42
+ ...props
43
+ }: Menu.Separator.Props): react_jsx_runtime6.JSX.Element;
44
+ declare function DropdownMenuShortcut({
45
+ className,
46
+ ...props
47
+ }: React.ComponentProps<"span">): react_jsx_runtime6.JSX.Element;
48
+ //#endregion
49
+ export { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger };
@@ -33,6 +33,12 @@ function DropdownMenuContent({ align = "start", alignOffset = 0, side = "bottom"
33
33
  })
34
34
  }) });
35
35
  }
36
+ function DropdownMenuGroup({ ...props }) {
37
+ return /* @__PURE__ */ jsx(Menu.Group, {
38
+ "data-slot": "dropdown-menu-group",
39
+ ...props
40
+ });
41
+ }
36
42
  function DropdownMenuLabel({ className, inset, ...props }) {
37
43
  return /* @__PURE__ */ jsx("div", {
38
44
  "data-slot": "dropdown-menu-label",
@@ -79,24 +85,6 @@ function DropdownMenuSubContent({ align = "start", alignOffset = -3, side = "rig
79
85
  ...props
80
86
  });
81
87
  }
82
- function DropdownMenuRadioGroup({ ...props }) {
83
- return /* @__PURE__ */ jsx(Menu.RadioGroup, {
84
- "data-slot": "dropdown-menu-radio-group",
85
- ...props
86
- });
87
- }
88
- function DropdownMenuRadioItem({ className, children, ...props }) {
89
- return /* @__PURE__ */ jsxs(Menu.RadioItem, {
90
- "data-slot": "dropdown-menu-radio-item",
91
- className: cn("qa-dropdown-menu__radio-item item-surface focus:border-border focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex min-h-9 cursor-default items-center gap-2.5 px-3 py-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5", className),
92
- ...props,
93
- children: [/* @__PURE__ */ jsx("span", {
94
- className: "pointer-events-none absolute right-3 flex items-center justify-center",
95
- "data-slot": "dropdown-menu-radio-item-indicator",
96
- children: /* @__PURE__ */ jsx(Menu.RadioItemIndicator, { children: /* @__PURE__ */ jsx(Icon, { icon: "ph:check" }) })
97
- }), children]
98
- });
99
- }
100
88
  function DropdownMenuSeparator({ className, ...props }) {
101
89
  return /* @__PURE__ */ jsx(Menu.Separator, {
102
90
  "data-slot": "dropdown-menu-separator",
@@ -113,4 +101,4 @@ function DropdownMenuShortcut({ className, ...props }) {
113
101
  }
114
102
 
115
103
  //#endregion
116
- export { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger };
104
+ export { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger };
@@ -25,7 +25,7 @@ function PopoverContent({ className, align = "center", alignOffset = 0, side = "
25
25
  className: "isolate z-50",
26
26
  children: /* @__PURE__ */ jsx(Popover.Popup, {
27
27
  "data-slot": "popover-content",
28
- className: cn("qa-popover__content floating-surface motion-floating text-popover-foreground z-50 flex min-w-48 origin-(--transform-origin) flex-col gap-4 p-2.5 text-xs outline-hidden data-ending-style:scale-[var(--motion-scale-enter)] data-ending-style:opacity-0 data-starting-style:scale-[var(--motion-scale-enter)] data-starting-style:opacity-0", className),
28
+ className: cn("qa-popover__content floating-surface motion-floating text-popover-foreground z-50 flex min-w-48 origin-(--transform-origin) flex-col gap-4 overflow-hidden p-2.5 text-xs outline-hidden data-ending-style:scale-[var(--motion-scale-enter)] data-ending-style:opacity-0 data-starting-style:scale-[var(--motion-scale-enter)] data-starting-style:opacity-0", className),
29
29
  ...props
30
30
  })
31
31
  }) });