@revenexx/studio-shared 0.1.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -12
- package/dist/module.d.mts +15 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +58 -0
- package/dist/runtime/components/DataTable/ColumnsMenu.d.vue.ts +12 -0
- package/{components → dist/runtime/components}/DataTable/ColumnsMenu.vue +13 -19
- package/dist/runtime/components/DataTable/ColumnsMenu.vue.d.ts +12 -0
- package/dist/runtime/components/DataTable/DataTable.d.vue.ts +87 -0
- package/dist/runtime/components/DataTable/DataTable.vue +510 -0
- package/dist/runtime/components/DataTable/DataTable.vue.d.ts +87 -0
- package/dist/runtime/components/DataTable/FilterMenu.d.vue.ts +21 -0
- package/dist/runtime/components/DataTable/FilterMenu.vue +46 -0
- package/dist/runtime/components/DataTable/FilterMenu.vue.d.ts +21 -0
- package/dist/runtime/components/DataTable/SavedViewsMenu.d.vue.ts +6 -0
- package/dist/runtime/components/DataTable/SavedViewsMenu.vue +189 -0
- package/dist/runtime/components/DataTable/SavedViewsMenu.vue.d.ts +6 -0
- package/dist/runtime/components/DataTable/SortMenu.d.vue.ts +13 -0
- package/dist/runtime/components/DataTable/SortMenu.vue +47 -0
- package/dist/runtime/components/DataTable/SortMenu.vue.d.ts +13 -0
- package/dist/runtime/components/DataTable/ViewModeSwitch.d.vue.ts +20 -0
- package/dist/runtime/components/DataTable/ViewModeSwitch.vue +26 -0
- package/dist/runtime/components/DataTable/ViewModeSwitch.vue.d.ts +20 -0
- package/dist/runtime/components/DataTable/types.d.ts +104 -0
- package/dist/runtime/components/DataTable/types.js +0 -0
- package/dist/runtime/components/Media/MediaFolderTree.d.vue.ts +17 -0
- package/dist/runtime/components/Media/MediaFolderTree.vue +65 -0
- package/dist/runtime/components/Media/MediaFolderTree.vue.d.ts +17 -0
- package/dist/runtime/components/Media/MediaPickerDialog.d.vue.ts +18 -0
- package/dist/runtime/components/Media/MediaPickerDialog.vue +163 -0
- package/dist/runtime/components/Media/MediaPickerDialog.vue.d.ts +18 -0
- package/dist/runtime/components/Media/MediaUploader.d.vue.ts +29 -0
- package/dist/runtime/components/Media/MediaUploader.vue +161 -0
- package/dist/runtime/components/Media/MediaUploader.vue.d.ts +29 -0
- package/dist/runtime/components/Shared/BuildLogsFlyout.d.vue.ts +30 -0
- package/{components → dist/runtime/components}/Shared/BuildLogsFlyout.vue +37 -54
- package/dist/runtime/components/Shared/BuildLogsFlyout.vue.d.ts +30 -0
- package/dist/runtime/components/Shared/GitDeploymentSettings.d.vue.ts +17 -0
- package/dist/runtime/components/Shared/GitDeploymentSettings.vue +75 -0
- package/dist/runtime/components/Shared/GitDeploymentSettings.vue.d.ts +17 -0
- package/dist/runtime/components/Shared/KpiTile.d.vue.ts +13 -0
- package/{components → dist/runtime/components}/Shared/KpiTile.vue +11 -19
- package/dist/runtime/components/Shared/KpiTile.vue.d.ts +13 -0
- package/dist/runtime/components/Shared/ListSurface.d.vue.ts +38 -0
- package/dist/runtime/components/Shared/ListSurface.vue +27 -0
- package/dist/runtime/components/Shared/ListSurface.vue.d.ts +38 -0
- package/dist/runtime/components/Shared/PageHeader.d.vue.ts +23 -0
- package/{components → dist/runtime/components}/Shared/PageHeader.vue +6 -8
- package/dist/runtime/components/Shared/PageHeader.vue.d.ts +23 -0
- package/dist/runtime/components/Shared/RecordLayout.d.vue.ts +52 -0
- package/dist/runtime/components/Shared/RecordLayout.vue +63 -0
- package/dist/runtime/components/Shared/RecordLayout.vue.d.ts +52 -0
- package/dist/runtime/components/Shared/RecordRailCard.d.vue.ts +26 -0
- package/dist/runtime/components/Shared/RecordRailCard.vue +20 -0
- package/dist/runtime/components/Shared/RecordRailCard.vue.d.ts +26 -0
- package/dist/runtime/components/Shared/SidebarNav.d.vue.ts +41 -0
- package/dist/runtime/components/Shared/SidebarNav.vue +48 -0
- package/dist/runtime/components/Shared/SidebarNav.vue.d.ts +41 -0
- package/dist/runtime/components/Shared/SidebarNavLink.d.vue.ts +15 -0
- package/dist/runtime/components/Shared/SidebarNavLink.vue +49 -0
- package/dist/runtime/components/Shared/SidebarNavLink.vue.d.ts +15 -0
- package/dist/runtime/components/Shared/SortHeader.d.vue.ts +23 -0
- package/dist/runtime/components/Shared/SortHeader.vue +35 -0
- package/dist/runtime/components/Shared/SortHeader.vue.d.ts +23 -0
- package/dist/runtime/components/Shared/StateCard.d.vue.ts +32 -0
- package/{components → dist/runtime/components}/Shared/StateCard.vue +21 -36
- package/dist/runtime/components/Shared/StateCard.vue.d.ts +32 -0
- package/dist/runtime/components/Shared/StudioCard.d.vue.ts +15 -0
- package/dist/runtime/components/Shared/StudioCard.vue +19 -0
- package/dist/runtime/components/Shared/StudioCard.vue.d.ts +15 -0
- package/dist/runtime/components/Shared/TabBar.d.vue.ts +23 -0
- package/{components → dist/runtime/components}/Shared/TabBar.vue +7 -16
- package/dist/runtime/components/Shared/TabBar.vue.d.ts +23 -0
- package/dist/runtime/components/Shared/ViewTab.d.vue.ts +7 -0
- package/{components → dist/runtime/components}/Shared/ViewTab.vue +7 -9
- package/dist/runtime/components/Shared/ViewTab.vue.d.ts +7 -0
- package/dist/runtime/components/Shared/Wizard.d.vue.ts +47 -0
- package/dist/runtime/components/Shared/Wizard.vue +66 -0
- package/dist/runtime/components/Shared/Wizard.vue.d.ts +47 -0
- package/dist/runtime/composables/useListRail.d.ts +9 -0
- package/dist/runtime/composables/useListRail.js +19 -0
- package/dist/runtime/composables/useMediaLibrary.d.ts +41 -0
- package/dist/runtime/composables/useMediaLibrary.js +36 -0
- package/dist/runtime/composables/usePlatformAuth.d.ts +35 -0
- package/dist/runtime/composables/usePlatformAuth.js +17 -0
- package/dist/runtime/composables/usePlatformTalkback.d.ts +29 -0
- package/dist/runtime/composables/usePlatformTalkback.js +54 -0
- package/dist/runtime/composables/usePlatformTenant.d.ts +14 -0
- package/dist/runtime/composables/usePlatformTenant.js +18 -0
- package/dist/runtime/composables/useT.d.ts +11 -0
- package/dist/runtime/composables/useT.js +8 -0
- package/dist/runtime/composables/useTalkbackChannels.d.ts +78 -0
- package/dist/runtime/composables/useTalkbackChannels.js +52 -0
- package/dist/runtime/lib/formatBridge.d.ts +50 -0
- package/dist/runtime/lib/formatBridge.js +54 -0
- package/dist/runtime/lib/i18nBridge.d.ts +31 -0
- package/dist/runtime/lib/i18nBridge.js +17 -0
- package/dist/runtime/lib/isoLists.d.ts +61 -0
- package/dist/runtime/lib/isoLists.js +62 -0
- package/dist/runtime/lib/sidebarNav.d.ts +34 -0
- package/dist/runtime/lib/sidebarNav.js +22 -0
- package/dist/runtime/lib/talkback.d.ts +59 -0
- package/dist/runtime/lib/talkback.js +49 -0
- package/dist/runtime/types/nav.d.ts +37 -0
- package/dist/runtime/types/nav.js +0 -0
- package/dist/runtime/types/storage.d.ts +67 -0
- package/dist/runtime/types/storage.js +0 -0
- package/dist/types.d.mts +27 -0
- package/package.json +41 -13
- package/components/DataTable/DataTable.vue +0 -518
- package/components/DataTable/FilterMenu.vue +0 -40
- package/components/DataTable/types.ts +0 -82
- package/components/Shared/GitDeploymentSettings.vue +0 -87
- package/components/Shared/SidebarNav.vue +0 -100
- package/components/Shared/SortHeader.vue +0 -50
- package/components/Shared/StudioCard.vue +0 -24
- package/components/Shared/Wizard.vue +0 -80
- package/composables/usePlatformAuth.ts +0 -35
- package/composables/usePlatformTenant.ts +0 -35
- package/index.ts +0 -16
- package/nuxt.config.ts +0 -23
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
/** Popover width class (a wide panel of inputs needs more than the default). */
|
|
3
|
+
width?: string;
|
|
4
|
+
/** Tint the trigger while at least one filter is set. */
|
|
5
|
+
active?: boolean;
|
|
6
|
+
};
|
|
7
|
+
declare var __VLS_34: {};
|
|
8
|
+
type __VLS_Slots = {} & {
|
|
9
|
+
default?: (props: typeof __VLS_34) => any;
|
|
10
|
+
};
|
|
11
|
+
declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
12
|
+
active: boolean;
|
|
13
|
+
width: string;
|
|
14
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
15
|
+
declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
|
|
16
|
+
export default _default;
|
|
17
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
18
|
+
new (): {
|
|
19
|
+
$slots: S;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
@@ -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;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { Component } from 'vue';
|
|
2
|
+
export type Comparable = string | number | boolean | Date | null | undefined;
|
|
3
|
+
export interface DataTableColumn<T = unknown> {
|
|
4
|
+
/** Stable key — matches the slot name `cell-<key>` and is used in the
|
|
5
|
+
* sort state. */
|
|
6
|
+
key: string;
|
|
7
|
+
/** Header label. */
|
|
8
|
+
label: string;
|
|
9
|
+
/** CSS grid track. Defaults to `minmax(0,1fr)` for text columns; pass
|
|
10
|
+
* a fixed value like `120px` or `minmax(0,140px)` for compact ones. */
|
|
11
|
+
width?: string;
|
|
12
|
+
/** Enable sort affordance on this column. */
|
|
13
|
+
sortable?: boolean;
|
|
14
|
+
/** Returns the sort key for the row. Falls back to `row[key]` if it
|
|
15
|
+
* looks like a primitive. ISO date strings sort correctly as-is. */
|
|
16
|
+
sortGetter?: (row: T) => Comparable;
|
|
17
|
+
/** Returns the string a text-search query is matched against. Falls
|
|
18
|
+
* back to the stringified sortGetter result. */
|
|
19
|
+
searchGetter?: (row: T) => string;
|
|
20
|
+
/** Initial visibility (default `true`). */
|
|
21
|
+
visible?: boolean;
|
|
22
|
+
/** Hide-able via the column menu (default `true`). */
|
|
23
|
+
toggleable?: boolean;
|
|
24
|
+
/** Right-align cell content (numeric columns). */
|
|
25
|
+
alignRight?: boolean;
|
|
26
|
+
/** Optional icon component for the header. */
|
|
27
|
+
icon?: Component;
|
|
28
|
+
/** Marks the identifying column. When set (and no `#cell-<key>` slot is
|
|
29
|
+
* given) the DataTable renders the value as the bold record anchor —
|
|
30
|
+
* `font-semibold text-[#0B0522]`, truncated — optionally led by a tinted
|
|
31
|
+
* icon tile (see `cellIcon`). One per table. (datatable-ux) */
|
|
32
|
+
primary?: boolean;
|
|
33
|
+
/** Leading tinted icon tile for the cell (typically the `primary` column).
|
|
34
|
+
* A static icon component, rendered as the `bg-primary/10` rounded tile the
|
|
35
|
+
* scanning rules call for. (For a per-row icon use a `#cell-<key>` slot —
|
|
36
|
+
* `cellIcon` must NOT be a getter: Solar icons are functional components, so
|
|
37
|
+
* the table can't tell a getter from an icon and would crash invoking it.) */
|
|
38
|
+
cellIcon?: Component;
|
|
39
|
+
}
|
|
40
|
+
/** A built-in filter dimension rendered under the generic "Filter" dropdown as
|
|
41
|
+
* a labelled sub-menu (datatable-ux). Static mode only: the DataTable filters
|
|
42
|
+
* `data` in-memory and shows a count per option. Pass one, or an array of
|
|
43
|
+
* several dimensions — each becomes its own sub-menu. */
|
|
44
|
+
export interface DataTableFilter<T = unknown> {
|
|
45
|
+
/** Stable id for this dimension (defaults to `label`). */
|
|
46
|
+
key?: string;
|
|
47
|
+
/** Sub-menu title, e.g. "Status" (default `Status`). */
|
|
48
|
+
label?: string;
|
|
49
|
+
/** The row's value for this dimension (e.g. `r => r.status`). */
|
|
50
|
+
getter: (row: T) => string | null | undefined;
|
|
51
|
+
/** The selectable values + their labels. */
|
|
52
|
+
options: {
|
|
53
|
+
value: string;
|
|
54
|
+
label: string;
|
|
55
|
+
}[];
|
|
56
|
+
/** Label for the reset/"all" entry (default `All`). */
|
|
57
|
+
allLabel?: string;
|
|
58
|
+
}
|
|
59
|
+
/** A named snapshot of a list surface's filter + sort state. */
|
|
60
|
+
export interface DataTableSavedView<S = unknown> {
|
|
61
|
+
name: string;
|
|
62
|
+
snapshot: S;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Saved-view wiring for the DataTable toolbar. The table owns the control,
|
|
66
|
+
* the storage and the save/delete dialogs; the surface only says how to read
|
|
67
|
+
* and write its own filter state.
|
|
68
|
+
*/
|
|
69
|
+
export interface DataTableSavedViews<S = unknown> {
|
|
70
|
+
/** `localStorage` key — one bucket per list surface. */
|
|
71
|
+
storageKey: string;
|
|
72
|
+
/** Label of the reset entry (default `All`). */
|
|
73
|
+
allLabel?: string;
|
|
74
|
+
/** The surface's current filter + sort state. */
|
|
75
|
+
snapshot: () => S;
|
|
76
|
+
/** The unfiltered baseline the reset entry restores. */
|
|
77
|
+
defaultSnapshot: () => S;
|
|
78
|
+
/** Put a snapshot back onto the surface. */
|
|
79
|
+
apply: (snapshot: S) => void;
|
|
80
|
+
}
|
|
81
|
+
/** One entry of the toolbar's view-mode switch (List | Gallery | …). */
|
|
82
|
+
export interface DataTableViewMode {
|
|
83
|
+
/** `list` renders the table; any other value renders the `#view` slot. */
|
|
84
|
+
value: string;
|
|
85
|
+
label: string;
|
|
86
|
+
icon?: Component;
|
|
87
|
+
}
|
|
88
|
+
/** Collapsible left-rail affordance in the toolbar. */
|
|
89
|
+
export interface DataTableRail {
|
|
90
|
+
/** Button label (default `Filters`). */
|
|
91
|
+
label?: string;
|
|
92
|
+
}
|
|
93
|
+
export type SortDirection = 'asc' | 'desc' | null;
|
|
94
|
+
export interface SortState {
|
|
95
|
+
column: string | null;
|
|
96
|
+
direction: SortDirection;
|
|
97
|
+
}
|
|
98
|
+
export interface DataTableState {
|
|
99
|
+
search: string;
|
|
100
|
+
sort: SortState;
|
|
101
|
+
page: number;
|
|
102
|
+
pageSize: number;
|
|
103
|
+
}
|
|
104
|
+
export type RowKey<T> = keyof T | ((row: T) => string | number);
|
|
File without changes
|
|
@@ -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;
|