@revenexx/studio-shared 0.2.0 → 0.3.1

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 (86) hide show
  1. package/dist/module.d.mts +7 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +37 -1
  4. package/dist/runtime/components/DataTable/ColumnsMenu.d.vue.ts +2 -0
  5. package/dist/runtime/components/DataTable/ColumnsMenu.vue +93 -36
  6. package/dist/runtime/components/DataTable/ColumnsMenu.vue.d.ts +2 -0
  7. package/dist/runtime/components/DataTable/DataTable.d.vue.ts +53 -4
  8. package/dist/runtime/components/DataTable/DataTable.vue +274 -118
  9. package/dist/runtime/components/DataTable/DataTable.vue.d.ts +53 -4
  10. package/dist/runtime/components/DataTable/FilterMenu.d.vue.ts +14 -3
  11. package/dist/runtime/components/DataTable/FilterMenu.vue +35 -29
  12. package/dist/runtime/components/DataTable/FilterMenu.vue.d.ts +14 -3
  13. package/dist/runtime/components/DataTable/SavedViewsMenu.d.vue.ts +6 -0
  14. package/dist/runtime/components/DataTable/SavedViewsMenu.vue +189 -0
  15. package/dist/runtime/components/DataTable/SavedViewsMenu.vue.d.ts +6 -0
  16. package/dist/runtime/components/DataTable/SortMenu.d.vue.ts +13 -0
  17. package/dist/runtime/components/DataTable/SortMenu.vue +47 -0
  18. package/dist/runtime/components/DataTable/SortMenu.vue.d.ts +13 -0
  19. package/dist/runtime/components/DataTable/ViewModeSwitch.d.vue.ts +20 -0
  20. package/dist/runtime/components/DataTable/ViewModeSwitch.vue +26 -0
  21. package/dist/runtime/components/DataTable/ViewModeSwitch.vue.d.ts +20 -0
  22. package/dist/runtime/components/DataTable/types.d.ts +49 -2
  23. package/dist/runtime/components/Media/MediaFolderTree.d.vue.ts +17 -0
  24. package/dist/runtime/components/Media/MediaFolderTree.vue +65 -0
  25. package/dist/runtime/components/Media/MediaFolderTree.vue.d.ts +17 -0
  26. package/dist/runtime/components/Media/MediaPickerDialog.d.vue.ts +18 -0
  27. package/dist/runtime/components/Media/MediaPickerDialog.vue +163 -0
  28. package/dist/runtime/components/Media/MediaPickerDialog.vue.d.ts +18 -0
  29. package/dist/runtime/components/Media/MediaUploader.d.vue.ts +29 -0
  30. package/dist/runtime/components/Media/MediaUploader.vue +161 -0
  31. package/dist/runtime/components/Media/MediaUploader.vue.d.ts +29 -0
  32. package/dist/runtime/components/Shared/BuildLogsFlyout.vue +14 -13
  33. package/dist/runtime/components/Shared/FloatingPanel.d.vue.ts +55 -0
  34. package/dist/runtime/components/Shared/FloatingPanel.vue +118 -0
  35. package/dist/runtime/components/Shared/FloatingPanel.vue.d.ts +55 -0
  36. package/dist/runtime/components/Shared/GitDeploymentSettings.vue +11 -10
  37. package/dist/runtime/components/Shared/ListSurface.d.vue.ts +38 -0
  38. package/dist/runtime/components/Shared/ListSurface.vue +27 -0
  39. package/dist/runtime/components/Shared/ListSurface.vue.d.ts +38 -0
  40. package/dist/runtime/components/Shared/RecordLayout.d.vue.ts +52 -0
  41. package/dist/runtime/components/Shared/RecordLayout.vue +63 -0
  42. package/dist/runtime/components/Shared/RecordLayout.vue.d.ts +52 -0
  43. package/dist/runtime/components/Shared/RecordRailCard.d.vue.ts +26 -0
  44. package/dist/runtime/components/Shared/RecordRailCard.vue +20 -0
  45. package/dist/runtime/components/Shared/RecordRailCard.vue.d.ts +26 -0
  46. package/dist/runtime/components/Shared/SidebarNav.d.vue.ts +5 -0
  47. package/dist/runtime/components/Shared/SidebarNav.vue +6 -26
  48. package/dist/runtime/components/Shared/SidebarNav.vue.d.ts +5 -0
  49. package/dist/runtime/components/Shared/SidebarNavLink.d.vue.ts +15 -0
  50. package/dist/runtime/components/Shared/SidebarNavLink.vue +49 -0
  51. package/dist/runtime/components/Shared/SidebarNavLink.vue.d.ts +15 -0
  52. package/dist/runtime/components/Shared/Wizard.vue +6 -3
  53. package/dist/runtime/composables/useFloatingPanel.d.ts +69 -0
  54. package/dist/runtime/composables/useFloatingPanel.js +112 -0
  55. package/dist/runtime/composables/useListRail.d.ts +9 -0
  56. package/dist/runtime/composables/useListRail.js +19 -0
  57. package/dist/runtime/composables/useMediaLibrary.d.ts +41 -0
  58. package/dist/runtime/composables/useMediaLibrary.js +36 -0
  59. package/dist/runtime/composables/usePlatformAuth.d.ts +18 -0
  60. package/dist/runtime/composables/usePlatformTalkback.d.ts +29 -0
  61. package/dist/runtime/composables/usePlatformTalkback.js +54 -0
  62. package/dist/runtime/composables/useT.d.ts +11 -0
  63. package/dist/runtime/composables/useT.js +8 -0
  64. package/dist/runtime/composables/useTalkbackChannels.d.ts +93 -0
  65. package/dist/runtime/composables/useTalkbackChannels.js +56 -0
  66. package/dist/runtime/lib/columnOrder.d.ts +45 -0
  67. package/dist/runtime/lib/columnOrder.js +28 -0
  68. package/dist/runtime/lib/columnVisibility.d.ts +41 -0
  69. package/dist/runtime/lib/columnVisibility.js +12 -0
  70. package/dist/runtime/lib/dataTableFilters.d.ts +83 -0
  71. package/dist/runtime/lib/dataTableFilters.js +23 -0
  72. package/dist/runtime/lib/formatBridge.d.ts +50 -0
  73. package/dist/runtime/lib/formatBridge.js +54 -0
  74. package/dist/runtime/lib/i18nBridge.d.ts +36 -0
  75. package/dist/runtime/lib/i18nBridge.js +17 -0
  76. package/dist/runtime/lib/isoLists.d.ts +61 -0
  77. package/dist/runtime/lib/isoLists.js +62 -0
  78. package/dist/runtime/lib/sidebarNav.d.ts +34 -0
  79. package/dist/runtime/lib/sidebarNav.js +22 -0
  80. package/dist/runtime/lib/talkback.d.ts +59 -0
  81. package/dist/runtime/lib/talkback.js +49 -0
  82. package/dist/runtime/types/nav.d.ts +15 -1
  83. package/dist/runtime/types/storage.d.ts +67 -0
  84. package/dist/runtime/types/storage.js +0 -0
  85. package/dist/types.d.mts +14 -0
  86. package/package.json +19 -3
@@ -1,8 +1,19 @@
1
- declare var __VLS_26: {};
1
+ type __VLS_Props = {
2
+ /** Popover width class (a wide panel of inputs needs more than the default). */
3
+ width?: string;
4
+ /** The trigger goes primary while at least one filter is set. */
5
+ active?: boolean;
6
+ /** How many are set, for the badge. */
7
+ count?: number;
8
+ };
9
+ declare var __VLS_22: {};
2
10
  type __VLS_Slots = {} & {
3
- default?: (props: typeof __VLS_26) => any;
11
+ default?: (props: typeof __VLS_22) => any;
4
12
  };
5
- declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
13
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
14
+ active: boolean;
15
+ width: string;
16
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
17
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
7
18
  export default _default;
8
19
  type __VLS_WithSlots<T, S> = T & {
@@ -1,36 +1,42 @@
1
1
  <script setup>
2
2
  import { Filter } from "#solar-icons";
3
- import {
4
- DropdownMenu,
5
- DropdownMenuContent,
6
- DropdownMenuItem,
7
- DropdownMenuLabel,
8
- DropdownMenuSeparator,
9
- DropdownMenuTrigger
10
- } from "@revenexx/studio";
3
+ import { Popover, PopoverContent, PopoverTrigger, ToolbarFilterTrigger } from "@revenexx/studio";
4
+ defineProps({
5
+ width: { type: String, required: false, default: "w-72" },
6
+ active: { type: Boolean, required: false, default: false },
7
+ count: { type: Number, required: false }
8
+ });
9
+ const t = useT();
11
10
  </script>
12
11
 
13
12
  <template>
14
- <DropdownMenu>
15
- <DropdownMenuTrigger as-child>
16
- <button
17
- type="button"
18
- class="flex size-7 items-center justify-center rounded-md border border-black/[0.06] bg-white text-muted-foreground transition hover:text-foreground"
19
- title="Filter"
13
+ <Popover>
14
+ <PopoverTrigger as-child>
15
+ <ToolbarFilterTrigger
16
+ icon-only
17
+ :active="active"
18
+ :count="count"
19
+ :label="t('shared.datatable.filtermenu.aria')"
20
20
  >
21
- <Filter :size="12" />
22
- </button>
23
- </DropdownMenuTrigger>
24
- <DropdownMenuContent align="end" class="w-56">
25
- <DropdownMenuLabel class="text-sm text-muted-foreground">
26
- Filter
27
- </DropdownMenuLabel>
28
- <DropdownMenuSeparator />
29
- <slot>
30
- <DropdownMenuItem disabled>
31
- No filters defined.
32
- </DropdownMenuItem>
33
- </slot>
34
- </DropdownMenuContent>
35
- </DropdownMenu>
21
+ <template #icon>
22
+ <Filter :size="14" weight="BoldDuotone" />
23
+ </template>
24
+ </ToolbarFilterTrigger>
25
+ </PopoverTrigger>
26
+
27
+ <PopoverContent align="start" :class="['p-0', width]">
28
+ <p class="px-4 pt-3 pb-1 text-sm text-muted-foreground">
29
+ {{ t("shared.datatable.filtermenu.heading") }}
30
+ </p>
31
+ <!-- The surface's controls, stacked. Full width, because a filter panel
32
+ holding a half-width select looks like something failed to load. -->
33
+ <div class="space-y-2.5 p-4 pt-2 [&_[data-slot=select-trigger]]:w-full">
34
+ <slot>
35
+ <p class="py-4 text-center text-sm text-muted-foreground">
36
+ {{ t("shared.datatable.filtermenu.empty") }}
37
+ </p>
38
+ </slot>
39
+ </div>
40
+ </PopoverContent>
41
+ </Popover>
36
42
  </template>
@@ -1,8 +1,19 @@
1
- declare var __VLS_26: {};
1
+ type __VLS_Props = {
2
+ /** Popover width class (a wide panel of inputs needs more than the default). */
3
+ width?: string;
4
+ /** The trigger goes primary while at least one filter is set. */
5
+ active?: boolean;
6
+ /** How many are set, for the badge. */
7
+ count?: number;
8
+ };
9
+ declare var __VLS_22: {};
2
10
  type __VLS_Slots = {} & {
3
- default?: (props: typeof __VLS_26) => any;
11
+ default?: (props: typeof __VLS_22) => any;
4
12
  };
5
- declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
13
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
14
+ active: boolean;
15
+ width: string;
16
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
17
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
7
18
  export default _default;
8
19
  type __VLS_WithSlots<T, S> = T & {
@@ -0,0 +1,6 @@
1
+ import type { DataTableSavedViews } from './types.js';
2
+ type __VLS_Props = {
3
+ config: DataTableSavedViews;
4
+ };
5
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ export default _default;
@@ -0,0 +1,189 @@
1
+ <script setup>
2
+ import { AltArrowDown, Bookmark, TrashBinMinimalistic } from "#solar-icons";
3
+ import {
4
+ Button,
5
+ Dialog,
6
+ DialogContent,
7
+ DialogDescription,
8
+ DialogFooter,
9
+ DialogHeader,
10
+ DialogTitle,
11
+ DropdownMenu,
12
+ DropdownMenuContent,
13
+ DropdownMenuItem,
14
+ DropdownMenuSeparator,
15
+ DropdownMenuTrigger,
16
+ Input
17
+ } from "@revenexx/studio";
18
+ const props = defineProps({
19
+ config: { type: Object, required: true }
20
+ });
21
+ const t = useT();
22
+ const views = ref([]);
23
+ function read() {
24
+ try {
25
+ if (typeof window === "undefined") return [];
26
+ const raw = window.localStorage.getItem(props.config.storageKey);
27
+ if (!raw) return [];
28
+ const parsed = JSON.parse(raw);
29
+ if (!Array.isArray(parsed)) return [];
30
+ return parsed.filter((v) => !!v && typeof v === "object" && typeof v.name === "string");
31
+ } catch {
32
+ return [];
33
+ }
34
+ }
35
+ function persist() {
36
+ try {
37
+ if (typeof window === "undefined") return;
38
+ window.localStorage.setItem(props.config.storageKey, JSON.stringify(views.value));
39
+ } catch {
40
+ }
41
+ }
42
+ onMounted(() => {
43
+ views.value = read();
44
+ });
45
+ watch(() => props.config.storageKey, () => {
46
+ views.value = read();
47
+ });
48
+ const allLabel = computed(() => props.config.allLabel ?? t("shared.datatable.savedviews.all.default"));
49
+ function same(a, b) {
50
+ return JSON.stringify(a) === JSON.stringify(b);
51
+ }
52
+ const isDefault = computed(() => same(props.config.snapshot(), props.config.defaultSnapshot()));
53
+ function isActive(v) {
54
+ return same(v.snapshot, props.config.snapshot());
55
+ }
56
+ const activeLabel = computed(() => {
57
+ const hit = views.value.find(isActive);
58
+ if (hit) return hit.name;
59
+ return isDefault.value ? allLabel.value : t("shared.datatable.savedviews.custom");
60
+ });
61
+ function applyDefault() {
62
+ props.config.apply(props.config.defaultSnapshot());
63
+ }
64
+ function applyView(v) {
65
+ props.config.apply(v.snapshot);
66
+ }
67
+ const saveOpen = ref(false);
68
+ const saveName = ref("");
69
+ function openSave() {
70
+ saveName.value = "";
71
+ saveOpen.value = true;
72
+ }
73
+ function save() {
74
+ const name = saveName.value.trim();
75
+ if (!name) return;
76
+ const entry = { name, snapshot: props.config.snapshot() };
77
+ const idx = views.value.findIndex((v) => v.name === name);
78
+ if (idx >= 0) views.value.splice(idx, 1, entry);
79
+ else views.value.push(entry);
80
+ persist();
81
+ saveOpen.value = false;
82
+ }
83
+ const deleteTarget = ref(null);
84
+ function remove() {
85
+ if (!deleteTarget.value) return;
86
+ views.value = views.value.filter((v) => v.name !== deleteTarget.value.name);
87
+ persist();
88
+ deleteTarget.value = null;
89
+ }
90
+ </script>
91
+
92
+ <template>
93
+ <DropdownMenu>
94
+ <DropdownMenuTrigger as-child>
95
+ <Button
96
+ variant="outline"
97
+ size="sm"
98
+ class="h-7 max-w-48 gap-1.5 text-sm"
99
+ :class="isDefault ? '' : 'border-primary/30 text-primary'"
100
+ :aria-label="t('shared.datatable.savedviews.aria')"
101
+ >
102
+ <Bookmark :size="13" />
103
+ <span class="truncate">{{ activeLabel }}</span>
104
+ <AltArrowDown :size="12" class="opacity-60" />
105
+ </Button>
106
+ </DropdownMenuTrigger>
107
+ <DropdownMenuContent align="end" class="w-56">
108
+ <DropdownMenuItem class="gap-2" @select="applyDefault">
109
+ <span class="flex-1 truncate">{{ allLabel }}</span>
110
+ <span v-if="isDefault" class="text-xs text-primary">{{ t("shared.datatable.savedviews.active.badge") }}</span>
111
+ </DropdownMenuItem>
112
+ <template v-if="views.length">
113
+ <DropdownMenuSeparator />
114
+ <DropdownMenuItem
115
+ v-for="v in views"
116
+ :key="v.name"
117
+ class="gap-2"
118
+ @select="applyView(v)"
119
+ >
120
+ <span class="flex-1 truncate">{{ v.name }}</span>
121
+ <span
122
+ role="button"
123
+ class="text-muted-foreground transition-colors hover:text-rose-600"
124
+ :aria-label="t('shared.datatable.savedviews.delete.aria', { name: v.name })"
125
+ @click.stop="deleteTarget = v"
126
+ @pointerdown.stop.prevent
127
+ >
128
+ <TrashBinMinimalistic :size="13" />
129
+ </span>
130
+ </DropdownMenuItem>
131
+ </template>
132
+ <DropdownMenuSeparator />
133
+ <DropdownMenuItem @select="openSave">
134
+ {{ t("shared.datatable.savedviews.action.savecurrent") }}
135
+ </DropdownMenuItem>
136
+ </DropdownMenuContent>
137
+ </DropdownMenu>
138
+
139
+ <!-- Save the current filters + sort under a name -->
140
+ <Dialog :open="saveOpen" @update:open="(v2) => {
141
+ if (!v2) saveOpen = false;
142
+ }">
143
+ <DialogContent>
144
+ <DialogHeader>
145
+ <DialogTitle>{{ t("shared.datatable.savedviews.savedialog.title") }}</DialogTitle>
146
+ <DialogDescription>{{ t("shared.datatable.savedviews.savedialog.description") }}</DialogDescription>
147
+ </DialogHeader>
148
+ <div class="space-y-1">
149
+ <label for="saved-view-name" class="text-sm font-medium">{{ t("shared.datatable.savedviews.savedialog.field.name.label") }}</label>
150
+ <Input
151
+ id="saved-view-name"
152
+ v-model="saveName"
153
+ :placeholder="t('shared.datatable.savedviews.savedialog.field.name.placeholder')"
154
+ @keydown.enter="save"
155
+ />
156
+ </div>
157
+ <DialogFooter>
158
+ <Button variant="outline" @click="saveOpen = false">
159
+ {{ t("shared.datatable.savedviews.savedialog.action.cancel") }}
160
+ </Button>
161
+ <Button :disabled="!saveName.trim()" @click="save">
162
+ {{ t("shared.datatable.savedviews.savedialog.action.save") }}
163
+ </Button>
164
+ </DialogFooter>
165
+ </DialogContent>
166
+ </Dialog>
167
+
168
+ <!-- Delete a saved view -->
169
+ <Dialog :open="deleteTarget !== null" @update:open="(v2) => {
170
+ if (!v2) deleteTarget = null;
171
+ }">
172
+ <DialogContent>
173
+ <DialogHeader>
174
+ <DialogTitle>{{ t("shared.datatable.savedviews.deletedialog.title") }}</DialogTitle>
175
+ <DialogDescription>
176
+ {{ t("shared.datatable.savedviews.deletedialog.description", { name: deleteTarget?.name ?? "" }) }}
177
+ </DialogDescription>
178
+ </DialogHeader>
179
+ <DialogFooter>
180
+ <Button variant="outline" @click="deleteTarget = null">
181
+ {{ t("shared.datatable.savedviews.deletedialog.action.cancel") }}
182
+ </Button>
183
+ <Button variant="outline" class="border-rose-500/30 text-rose-700 hover:bg-rose-500/10" @click="remove">
184
+ {{ t("shared.datatable.savedviews.deletedialog.action.delete") }}
185
+ </Button>
186
+ </DialogFooter>
187
+ </DialogContent>
188
+ </Dialog>
189
+ </template>
@@ -0,0 +1,6 @@
1
+ import type { DataTableSavedViews } from './types.js';
2
+ type __VLS_Props = {
3
+ config: DataTableSavedViews;
4
+ };
5
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ export default _default;
@@ -0,0 +1,13 @@
1
+ import type { DataTableColumn, SortState } from './types.js';
2
+ type __VLS_Props = {
3
+ columns: DataTableColumn[];
4
+ };
5
+ type __VLS_PublicProps = __VLS_Props & {
6
+ modelValue: SortState;
7
+ };
8
+ declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ "update:modelValue": (value: SortState) => any;
10
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
11
+ "onUpdate:modelValue"?: ((value: SortState) => any) | undefined;
12
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
+ export default _default;
@@ -0,0 +1,47 @@
1
+ <script setup>
2
+ import { AltArrowDown, SortVertical } from "#solar-icons";
3
+ import {
4
+ Button,
5
+ DropdownMenu,
6
+ DropdownMenuContent,
7
+ DropdownMenuItem,
8
+ DropdownMenuTrigger
9
+ } from "@revenexx/studio";
10
+ const props = defineProps({
11
+ columns: { type: Array, required: true }
12
+ });
13
+ const sort = defineModel({ type: Object, ...{ required: true } });
14
+ const t = useT();
15
+ const label = computed(() => {
16
+ const col = props.columns.find((c) => c.key === sort.value.column);
17
+ if (!col || !sort.value.direction) return t("shared.datatable.sortmenu.label.default");
18
+ return `${col.label} ${sort.value.direction === "asc" ? "\u2191" : "\u2193"}`;
19
+ });
20
+ function pick(key, direction) {
21
+ sort.value = { column: key, direction };
22
+ }
23
+ </script>
24
+
25
+ <template>
26
+ <DropdownMenu>
27
+ <DropdownMenuTrigger as-child>
28
+ <Button variant="outline" size="sm" class="h-7 gap-1.5 text-sm" :aria-label="t('shared.datatable.sortmenu.aria')">
29
+ <SortVertical :size="14" />
30
+ {{ label }}
31
+ <AltArrowDown :size="12" class="opacity-60" />
32
+ </Button>
33
+ </DropdownMenuTrigger>
34
+ <DropdownMenuContent align="start" class="w-52">
35
+ <template v-for="col in columns" :key="col.key">
36
+ <DropdownMenuItem class="gap-2" @select="pick(col.key, 'asc')">
37
+ <span class="flex-1 truncate">{{ col.label }} ↑</span>
38
+ <span v-if="sort.column === col.key && sort.direction === 'asc'" class="text-xs text-primary">{{ t("shared.datatable.sortmenu.active.badge") }}</span>
39
+ </DropdownMenuItem>
40
+ <DropdownMenuItem class="gap-2" @select="pick(col.key, 'desc')">
41
+ <span class="flex-1 truncate">{{ col.label }} ↓</span>
42
+ <span v-if="sort.column === col.key && sort.direction === 'desc'" class="text-xs text-primary">{{ t("shared.datatable.sortmenu.active.badge") }}</span>
43
+ </DropdownMenuItem>
44
+ </template>
45
+ </DropdownMenuContent>
46
+ </DropdownMenu>
47
+ </template>
@@ -0,0 +1,13 @@
1
+ import type { DataTableColumn, SortState } from './types.js';
2
+ type __VLS_Props = {
3
+ columns: DataTableColumn[];
4
+ };
5
+ type __VLS_PublicProps = __VLS_Props & {
6
+ modelValue: SortState;
7
+ };
8
+ declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ "update:modelValue": (value: SortState) => any;
10
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
11
+ "onUpdate:modelValue"?: ((value: SortState) => any) | undefined;
12
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
+ export default _default;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * List | Gallery (| …) switch for the DataTable toolbar.
3
+ *
4
+ * A gallery is a view mode of a table, not a different kind of page: the
5
+ * toolbar, the footer and the data pipeline stay put, only the body swaps
6
+ * for the `#view` slot.
7
+ */
8
+ import type { DataTableViewMode } from './types.js';
9
+ type __VLS_Props = {
10
+ modes: DataTableViewMode[];
11
+ };
12
+ type __VLS_PublicProps = __VLS_Props & {
13
+ modelValue: string;
14
+ };
15
+ declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
16
+ "update:modelValue": (value: string) => any;
17
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
18
+ "onUpdate:modelValue"?: ((value: string) => any) | undefined;
19
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
20
+ export default _default;
@@ -0,0 +1,26 @@
1
+ <script setup>
2
+ defineProps({
3
+ modes: { type: Array, required: true }
4
+ });
5
+ const mode = defineModel({ type: String, ...{ required: true } });
6
+ </script>
7
+
8
+ <template>
9
+ <div class="inline-flex shrink-0 overflow-hidden rounded-md border border-black/[0.06]">
10
+ <button
11
+ v-for="(m, i) in modes"
12
+ :key="m.value"
13
+ type="button"
14
+ class="inline-flex items-center gap-1.5 px-2.5 py-1 text-sm transition-colors"
15
+ :class="[
16
+ i > 0 ? 'border-l border-black/[0.06]' : '',
17
+ mode === m.value ? 'bg-primary/10 font-medium text-[#0B0522]' : 'text-muted-foreground hover:bg-muted/60'
18
+ ]"
19
+ :aria-pressed="mode === m.value"
20
+ @click="mode = m.value"
21
+ >
22
+ <component :is="m.icon" v-if="m.icon" :size="13" />
23
+ {{ m.label }}
24
+ </button>
25
+ </div>
26
+ </template>
@@ -0,0 +1,20 @@
1
+ /**
2
+ * List | Gallery (| …) switch for the DataTable toolbar.
3
+ *
4
+ * A gallery is a view mode of a table, not a different kind of page: the
5
+ * toolbar, the footer and the data pipeline stay put, only the body swaps
6
+ * for the `#view` slot.
7
+ */
8
+ import type { DataTableViewMode } from './types.js';
9
+ type __VLS_Props = {
10
+ modes: DataTableViewMode[];
11
+ };
12
+ type __VLS_PublicProps = __VLS_Props & {
13
+ modelValue: string;
14
+ };
15
+ declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
16
+ "update:modelValue": (value: string) => any;
17
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
18
+ "onUpdate:modelValue"?: ((value: string) => any) | undefined;
19
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
20
+ export default _default;
@@ -46,8 +46,21 @@ export interface DataTableFilter<T = unknown> {
46
46
  key?: string;
47
47
  /** Sub-menu title, e.g. "Status" (default `Status`). */
48
48
  label?: string;
49
- /** The row's value for this dimension (e.g. `r => r.status`). */
50
- getter: (row: T) => string | null | undefined;
49
+ /**
50
+ * The row's value(s) for this dimension.
51
+ *
52
+ * A string for anything a row has exactly one of — `r => r.status`. An ARRAY
53
+ * where a row can have several, and that is not a convenience: modelled as
54
+ * one value, `r => r.locales.join(', ')` makes the SET the value, so the
55
+ * options become combinations (four languages produce up to fifteen, six up
56
+ * to sixty-three) and "which of these have Polish?" cannot be asked at all —
57
+ * the answer is spread across three options and none of the counts is it.
58
+ *
59
+ * With an array the row lands in every bucket it belongs to. The counts then
60
+ * overlap rather than sum to the row total, which is correct and looks like a
61
+ * bug; see `runtime/lib/dataTableFilters.ts`.
62
+ */
63
+ getter: (row: T) => string | string[] | null | undefined;
51
64
  /** The selectable values + their labels. */
52
65
  options: {
53
66
  value: string;
@@ -56,6 +69,40 @@ export interface DataTableFilter<T = unknown> {
56
69
  /** Label for the reset/"all" entry (default `All`). */
57
70
  allLabel?: string;
58
71
  }
72
+ /** A named snapshot of a list surface's filter + sort state. */
73
+ export interface DataTableSavedView<S = unknown> {
74
+ name: string;
75
+ snapshot: S;
76
+ }
77
+ /**
78
+ * Saved-view wiring for the DataTable toolbar. The table owns the control,
79
+ * the storage and the save/delete dialogs; the surface only says how to read
80
+ * and write its own filter state.
81
+ */
82
+ export interface DataTableSavedViews<S = unknown> {
83
+ /** `localStorage` key — one bucket per list surface. */
84
+ storageKey: string;
85
+ /** Label of the reset entry (default `All`). */
86
+ allLabel?: string;
87
+ /** The surface's current filter + sort state. */
88
+ snapshot: () => S;
89
+ /** The unfiltered baseline the reset entry restores. */
90
+ defaultSnapshot: () => S;
91
+ /** Put a snapshot back onto the surface. */
92
+ apply: (snapshot: S) => void;
93
+ }
94
+ /** One entry of the toolbar's view-mode switch (List | Gallery | …). */
95
+ export interface DataTableViewMode {
96
+ /** `list` renders the table; any other value renders the `#view` slot. */
97
+ value: string;
98
+ label: string;
99
+ icon?: Component;
100
+ }
101
+ /** Collapsible left-rail affordance in the toolbar. */
102
+ export interface DataTableRail {
103
+ /** Button label (default `Filters`). */
104
+ label?: string;
105
+ }
59
106
  export type SortDirection = 'asc' | 'desc' | null;
60
107
  export interface SortState {
61
108
  column: string | null;
@@ -0,0 +1,17 @@
1
+ import type { StorageFolderNode } from '../../types/storage.js';
2
+ /**
3
+ * Recursive folder tree for the media browser sidebar. Emits `select` with a
4
+ * folder id (or null for the synthetic root). Self-renders for children.
5
+ */
6
+ type __VLS_Props = {
7
+ nodes: StorageFolderNode[];
8
+ currentId: string | null;
9
+ /** Internal: nesting depth for indentation. */
10
+ depth?: number;
11
+ };
12
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
13
+ select: (id: string | null) => any;
14
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
15
+ onSelect?: ((id: string | null) => any) | undefined;
16
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ export default _default;
@@ -0,0 +1,65 @@
1
+ <script setup>
2
+ import { Folder as FolderIcon, FolderOpen, AltArrowRight } from "#solar-icons";
3
+ const props = defineProps({
4
+ nodes: { type: Array, required: true },
5
+ currentId: { type: [String, null], required: true },
6
+ depth: { type: Number, required: false }
7
+ });
8
+ const emit = defineEmits(["select"]);
9
+ const expanded = ref(/* @__PURE__ */ new Set());
10
+ function toggle(id) {
11
+ const next = new Set(expanded.value);
12
+ if (next.has(id)) next.delete(id);
13
+ else next.add(id);
14
+ expanded.value = next;
15
+ }
16
+ const depth = computed(() => props.depth ?? 0);
17
+ const t = useT();
18
+ </script>
19
+
20
+ <template>
21
+ <div class="flex flex-col">
22
+ <div
23
+ v-for="node in nodes"
24
+ :key="node.id"
25
+ >
26
+ <div
27
+ class="group flex items-center gap-1 rounded-md pr-2 py-1 text-sm transition-colors cursor-pointer"
28
+ :class="currentId === node.id ? 'bg-primary/10 text-[#0B0522] font-medium' : 'text-foreground/80 hover:bg-primary/5'"
29
+ :style="{ paddingLeft: `${depth * 12 + 4}px` }"
30
+ @click="emit('select', node.id)"
31
+ >
32
+ <button
33
+ type="button"
34
+ class="shrink-0 rounded p-0.5 text-foreground/40 hover:text-foreground/80"
35
+ :class="{ invisible: node.children.length === 0 }"
36
+ @click.stop="toggle(node.id)"
37
+ >
38
+ <AltArrowRight
39
+ :size="12"
40
+ class="transition-transform"
41
+ :class="{ 'rotate-90': expanded.has(node.id) }"
42
+ />
43
+ </button>
44
+ <component
45
+ :is="currentId === node.id ? FolderOpen : FolderIcon"
46
+ :size="14"
47
+ class="shrink-0 text-foreground/50"
48
+ />
49
+ <span class="flex-1 truncate">{{ node.name }}</span>
50
+ <span
51
+ v-if="node.is_system"
52
+ class="rounded bg-muted px-1 text-[10px] font-medium text-muted-foreground"
53
+ >{{ t("shared.media.foldertree.badge.system") }}</span>
54
+ </div>
55
+
56
+ <MediaFolderTree
57
+ v-if="node.children.length && expanded.has(node.id)"
58
+ :nodes="node.children"
59
+ :current-id="currentId"
60
+ :depth="depth + 1"
61
+ @select="(id) => emit('select', id)"
62
+ />
63
+ </div>
64
+ </div>
65
+ </template>
@@ -0,0 +1,17 @@
1
+ import type { StorageFolderNode } from '../../types/storage.js';
2
+ /**
3
+ * Recursive folder tree for the media browser sidebar. Emits `select` with a
4
+ * folder id (or null for the synthetic root). Self-renders for children.
5
+ */
6
+ type __VLS_Props = {
7
+ nodes: StorageFolderNode[];
8
+ currentId: string | null;
9
+ /** Internal: nesting depth for indentation. */
10
+ depth?: number;
11
+ };
12
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
13
+ select: (id: string | null) => any;
14
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
15
+ onSelect?: ((id: string | null) => any) | undefined;
16
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ export default _default;
@@ -0,0 +1,18 @@
1
+ import type { StorageAsset } from '../../types/storage.js';
2
+ type __VLS_Props = {
3
+ open: boolean;
4
+ multiple?: boolean;
5
+ /** Restrict the grid to these asset kinds (e.g. ['image']). */
6
+ kinds?: string[];
7
+ };
8
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ select: (assets: StorageAsset[]) => any;
10
+ "update:open": (value: boolean) => any;
11
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
+ onSelect?: ((assets: StorageAsset[]) => any) | undefined;
13
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
14
+ }>, {
15
+ multiple: boolean;
16
+ kinds: string[];
17
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
+ export default _default;