@revenexx/studio-shared 0.3.1 → 0.3.3
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/dist/module.d.mts +4 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +9 -0
- package/dist/runtime/components/Dashboard/DashboardConfirmDialog.d.vue.ts +15 -0
- package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue +39 -0
- package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue.d.ts +15 -0
- package/dist/runtime/components/Dashboard/DashboardControls.d.vue.ts +57 -0
- package/dist/runtime/components/Dashboard/DashboardControls.vue +209 -0
- package/dist/runtime/components/Dashboard/DashboardControls.vue.d.ts +57 -0
- package/dist/runtime/components/Dashboard/DashboardGrid.d.vue.ts +16 -0
- package/dist/runtime/components/Dashboard/DashboardGrid.vue +87 -0
- package/dist/runtime/components/Dashboard/DashboardGrid.vue.d.ts +16 -0
- package/dist/runtime/components/Dashboard/DashboardNameDialog.d.vue.ts +18 -0
- package/dist/runtime/components/Dashboard/DashboardNameDialog.vue +61 -0
- package/dist/runtime/components/Dashboard/DashboardNameDialog.vue.d.ts +18 -0
- package/dist/runtime/components/Dashboard/DashboardPickerDialog.d.vue.ts +14 -0
- package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue +100 -0
- package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue.d.ts +14 -0
- package/dist/runtime/components/Dashboard/DashboardShareDialog.d.vue.ts +27 -0
- package/dist/runtime/components/Dashboard/DashboardShareDialog.vue +155 -0
- package/dist/runtime/components/Dashboard/DashboardShareDialog.vue.d.ts +27 -0
- package/dist/runtime/components/Dashboard/DashboardTopBar.d.vue.ts +59 -0
- package/dist/runtime/components/Dashboard/DashboardTopBar.vue +79 -0
- package/dist/runtime/components/Dashboard/DashboardTopBar.vue.d.ts +59 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.d.vue.ts +42 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue +153 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue.d.ts +42 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetFrame.d.vue.ts +27 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue +80 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue.d.ts +27 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetHost.d.vue.ts +6 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetHost.vue +30 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetHost.vue.d.ts +6 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetIcon.d.vue.ts +8 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetIcon.vue +13 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetIcon.vue.d.ts +8 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.d.vue.ts +23 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.vue +140 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.vue.d.ts +23 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetPreview.d.vue.ts +22 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetPreview.vue +124 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetPreview.vue.d.ts +22 -0
- package/dist/runtime/components/DataTable/DataTable.d.vue.ts +39 -3
- package/dist/runtime/components/DataTable/DataTable.vue +306 -32
- package/dist/runtime/components/DataTable/DataTable.vue.d.ts +39 -3
- package/dist/runtime/components/DataTable/FacetChips.d.vue.ts +16 -0
- package/dist/runtime/components/DataTable/FacetChips.vue +72 -0
- package/dist/runtime/components/DataTable/FacetChips.vue.d.ts +16 -0
- package/dist/runtime/components/DataTable/FacetPanel.d.vue.ts +20 -0
- package/dist/runtime/components/DataTable/FacetPanel.vue +359 -0
- package/dist/runtime/components/DataTable/FacetPanel.vue.d.ts +20 -0
- package/dist/runtime/components/DataTable/SavedViewsMenu.d.vue.ts +5 -1
- package/dist/runtime/components/DataTable/SavedViewsMenu.vue +83 -63
- package/dist/runtime/components/DataTable/SavedViewsMenu.vue.d.ts +5 -1
- package/dist/runtime/components/DataTable/SortMenu.d.vue.ts +5 -1
- package/dist/runtime/components/DataTable/SortMenu.vue +40 -12
- package/dist/runtime/components/DataTable/SortMenu.vue.d.ts +5 -1
- package/dist/runtime/components/Media/MediaPickerDialog.d.vue.ts +2 -2
- package/dist/runtime/components/Media/MediaPickerDialog.vue.d.ts +2 -2
- package/dist/runtime/components/Media/MediaUploader.d.vue.ts +2 -2
- package/dist/runtime/components/Media/MediaUploader.vue.d.ts +2 -2
- package/dist/runtime/components/Shared/SortHeader.vue +6 -2
- package/dist/runtime/components/Shared/TabBar.d.vue.ts +23 -7
- package/dist/runtime/components/Shared/TabBar.vue +3 -2
- package/dist/runtime/components/Shared/TabBar.vue.d.ts +23 -7
- package/dist/runtime/composables/useDashboards.d.ts +27 -0
- package/dist/runtime/composables/useDashboards.js +143 -0
- package/dist/runtime/composables/useSavedViews.d.ts +44 -0
- package/dist/runtime/composables/useSavedViews.js +44 -0
- package/dist/runtime/composables/useStickyListView.d.ts +60 -0
- package/dist/runtime/composables/useStickyListView.js +32 -0
- package/dist/runtime/lib/canonicalJson.d.ts +43 -0
- package/dist/runtime/lib/canonicalJson.js +38 -0
- package/dist/runtime/lib/dashboardIcons.d.ts +4 -0
- package/dist/runtime/lib/dashboardIcons.js +50 -0
- package/dist/runtime/lib/facets.d.ts +200 -0
- package/dist/runtime/lib/facets.js +171 -0
- package/dist/runtime/lib/gridTracks.d.ts +55 -0
- package/dist/runtime/lib/gridTracks.js +29 -0
- package/dist/runtime/lib/widgetRegistry.d.ts +124 -0
- package/dist/runtime/lib/widgetRegistry.js +66 -0
- package/dist/runtime/types/dashboard.d.ts +95 -0
- package/dist/runtime/types/dashboard.js +2 -0
- package/dist/types.d.mts +8 -0
- package/package.json +4 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import {
|
|
2
|
+
import { Bookmark, CheckCircle, Diskette, TrashBinMinimalistic } from "#solar-icons";
|
|
3
3
|
import {
|
|
4
4
|
Button,
|
|
5
5
|
Dialog,
|
|
@@ -11,51 +11,33 @@ import {
|
|
|
11
11
|
DropdownMenu,
|
|
12
12
|
DropdownMenuContent,
|
|
13
13
|
DropdownMenuItem,
|
|
14
|
+
DropdownMenuLabel,
|
|
14
15
|
DropdownMenuSeparator,
|
|
15
16
|
DropdownMenuTrigger,
|
|
16
17
|
Input
|
|
17
18
|
} from "@revenexx/studio";
|
|
19
|
+
import { sameJson } from "../../lib/canonicalJson";
|
|
18
20
|
const props = defineProps({
|
|
19
|
-
config: { type: Object, required: true }
|
|
21
|
+
config: { type: Object, required: true },
|
|
22
|
+
size: { type: String, required: false, default: "sm" }
|
|
20
23
|
});
|
|
21
24
|
const t = useT();
|
|
25
|
+
const store = useSavedViews();
|
|
22
26
|
const views = ref([]);
|
|
23
|
-
function
|
|
24
|
-
|
|
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
|
-
}
|
|
27
|
+
async function load() {
|
|
28
|
+
views.value = await store.list(props.config.storageKey);
|
|
41
29
|
}
|
|
42
|
-
onMounted(
|
|
43
|
-
|
|
44
|
-
});
|
|
45
|
-
watch(() => props.config.storageKey, () => {
|
|
46
|
-
views.value = read();
|
|
47
|
-
});
|
|
30
|
+
onMounted(load);
|
|
31
|
+
watch(() => props.config.storageKey, load);
|
|
48
32
|
const allLabel = computed(() => props.config.allLabel ?? t("shared.datatable.savedviews.all.default"));
|
|
49
|
-
|
|
50
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
51
|
-
}
|
|
52
|
-
const isDefault = computed(() => same(props.config.snapshot(), props.config.defaultSnapshot()));
|
|
33
|
+
const isDefault = computed(() => sameJson(props.config.snapshot(), props.config.defaultSnapshot()));
|
|
53
34
|
function isActive(v) {
|
|
54
|
-
return
|
|
35
|
+
return sameJson(v.snapshot, props.config.snapshot());
|
|
55
36
|
}
|
|
37
|
+
const appliedView = computed(() => views.value.find(isActive) ?? null);
|
|
38
|
+
const hasAppliedView = computed(() => appliedView.value !== null);
|
|
56
39
|
const activeLabel = computed(() => {
|
|
57
|
-
|
|
58
|
-
if (hit) return hit.name;
|
|
40
|
+
if (appliedView.value) return appliedView.value.name;
|
|
59
41
|
return isDefault.value ? allLabel.value : t("shared.datatable.savedviews.custom");
|
|
60
42
|
});
|
|
61
43
|
function applyDefault() {
|
|
@@ -66,25 +48,31 @@ function applyView(v) {
|
|
|
66
48
|
}
|
|
67
49
|
const saveOpen = ref(false);
|
|
68
50
|
const saveName = ref("");
|
|
51
|
+
const saving = ref(false);
|
|
69
52
|
function openSave() {
|
|
70
|
-
saveName.value = "";
|
|
53
|
+
saveName.value = appliedView.value?.name ?? "";
|
|
71
54
|
saveOpen.value = true;
|
|
72
55
|
}
|
|
73
|
-
function save() {
|
|
56
|
+
async function save() {
|
|
74
57
|
const name = saveName.value.trim();
|
|
75
|
-
if (!name) return;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
58
|
+
if (!name || saving.value) return;
|
|
59
|
+
saving.value = true;
|
|
60
|
+
try {
|
|
61
|
+
const entry = await store.save(props.config.storageKey, name, props.config.snapshot());
|
|
62
|
+
const at = views.value.findIndex((v) => v.name === name);
|
|
63
|
+
if (at >= 0) views.value.splice(at, 1, entry);
|
|
64
|
+
else views.value = [...views.value, entry].sort((a, b) => a.name.localeCompare(b.name));
|
|
65
|
+
saveOpen.value = false;
|
|
66
|
+
} finally {
|
|
67
|
+
saving.value = false;
|
|
68
|
+
}
|
|
82
69
|
}
|
|
83
70
|
const deleteTarget = ref(null);
|
|
84
|
-
function remove() {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
71
|
+
async function remove() {
|
|
72
|
+
const target = deleteTarget.value;
|
|
73
|
+
if (!target) return;
|
|
74
|
+
await store.remove(props.config.storageKey, target.id);
|
|
75
|
+
views.value = views.value.filter((v) => v.id !== target.id);
|
|
88
76
|
deleteTarget.value = null;
|
|
89
77
|
}
|
|
90
78
|
</script>
|
|
@@ -93,34 +81,57 @@ function remove() {
|
|
|
93
81
|
<DropdownMenu>
|
|
94
82
|
<DropdownMenuTrigger as-child>
|
|
95
83
|
<Button
|
|
96
|
-
variant="outline"
|
|
97
|
-
size="sm"
|
|
98
|
-
|
|
99
|
-
:
|
|
100
|
-
:
|
|
84
|
+
:variant="hasAppliedView ? 'default' : 'outline'"
|
|
85
|
+
:size="size === 'sm' ? 'icon-sm' : 'icon'"
|
|
86
|
+
:data-active="hasAppliedView"
|
|
87
|
+
:aria-label="t('shared.datatable.savedviews.trigger.aria', { name: activeLabel })"
|
|
88
|
+
:title="t('shared.datatable.savedviews.trigger.aria', { name: activeLabel })"
|
|
101
89
|
>
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
90
|
+
<!--
|
|
91
|
+
Violet like the filter trigger and the column picker beside it, and
|
|
92
|
+
filled the same way once a stored view is in effect — the button
|
|
93
|
+
carries that state rather than a second mark inside it. On the filled
|
|
94
|
+
face the icon inherits the button's own foreground, or it would be
|
|
95
|
+
violet on violet.
|
|
96
|
+
-->
|
|
97
|
+
<span class="flex shrink-0 items-center" :class="hasAppliedView ? '' : 'text-primary'">
|
|
98
|
+
<Bookmark :class="size === 'sm' ? 'size-3.5' : 'size-4'" weight="BoldDuotone" />
|
|
99
|
+
</span>
|
|
105
100
|
</Button>
|
|
106
101
|
</DropdownMenuTrigger>
|
|
107
|
-
|
|
102
|
+
<!--
|
|
103
|
+
`align="start"`, like every other menu in this row. It was `end`, so this
|
|
104
|
+
was the one control whose menu grew to the LEFT of its trigger while the
|
|
105
|
+
market switcher's and the column picker's grew right — a menu that opens
|
|
106
|
+
the other way is one a reader has to re-find every time.
|
|
107
|
+
-->
|
|
108
|
+
<DropdownMenuContent align="start" class="w-60">
|
|
109
|
+
<!-- Named, like the market switcher's "Showing" and the column picker's
|
|
110
|
+
"Columns": a menu of bare rows does not say what it is a menu OF. -->
|
|
111
|
+
<DropdownMenuLabel>{{ t("shared.datatable.savedviews.heading") }}</DropdownMenuLabel>
|
|
112
|
+
<!--
|
|
113
|
+
A tick, not just violet text. The mark says WHICH row is in effect, and
|
|
114
|
+
a reader who cannot separate the two hues gets nothing from a colour —
|
|
115
|
+
the same reason the market switcher puts a check on its active option.
|
|
116
|
+
-->
|
|
108
117
|
<DropdownMenuItem class="gap-2" @select="applyDefault">
|
|
109
|
-
<span class="flex-1 truncate">{{ allLabel }}</span>
|
|
110
|
-
<
|
|
118
|
+
<span class="flex-1 truncate" :class="isDefault ? 'font-medium text-primary' : ''">{{ allLabel }}</span>
|
|
119
|
+
<CheckCircle v-if="isDefault" :size="14" weight="Bold" class="shrink-0 text-primary" />
|
|
111
120
|
</DropdownMenuItem>
|
|
112
121
|
<template v-if="views.length">
|
|
113
122
|
<DropdownMenuSeparator />
|
|
114
123
|
<DropdownMenuItem
|
|
115
124
|
v-for="v in views"
|
|
116
|
-
:key="v.
|
|
125
|
+
:key="v.id"
|
|
117
126
|
class="gap-2"
|
|
127
|
+
:aria-current="isActive(v) ? 'true' : void 0"
|
|
118
128
|
@select="applyView(v)"
|
|
119
129
|
>
|
|
120
|
-
<span class="flex-1 truncate">{{ v.name }}</span>
|
|
130
|
+
<span class="min-w-0 flex-1 truncate" :class="isActive(v) ? 'font-medium text-primary' : ''">{{ v.name }}</span>
|
|
131
|
+
<CheckCircle v-if="isActive(v)" :size="14" weight="Bold" class="shrink-0 text-primary" />
|
|
121
132
|
<span
|
|
122
133
|
role="button"
|
|
123
|
-
class="text-muted-foreground transition-colors hover:text-rose-600"
|
|
134
|
+
class="shrink-0 text-muted-foreground transition-colors hover:text-rose-600"
|
|
124
135
|
:aria-label="t('shared.datatable.savedviews.delete.aria', { name: v.name })"
|
|
125
136
|
@click.stop="deleteTarget = v"
|
|
126
137
|
@pointerdown.stop.prevent
|
|
@@ -130,9 +141,18 @@ function remove() {
|
|
|
130
141
|
</DropdownMenuItem>
|
|
131
142
|
</template>
|
|
132
143
|
<DropdownMenuSeparator />
|
|
133
|
-
<DropdownMenuItem @select="openSave">
|
|
134
|
-
|
|
144
|
+
<DropdownMenuItem class="gap-2" @select="openSave">
|
|
145
|
+
<Diskette :size="14" class="shrink-0 text-muted-foreground" />
|
|
146
|
+
<span class="flex-1">{{ t("shared.datatable.savedviews.action.savecurrent") }}</span>
|
|
135
147
|
</DropdownMenuItem>
|
|
148
|
+
<!--
|
|
149
|
+
Where the last read or write actually landed. One line, and only when
|
|
150
|
+
the answer is the surprising one: a control that claims nothing is
|
|
151
|
+
better than one that claims a sync it did not get.
|
|
152
|
+
-->
|
|
153
|
+
<p v-if="!store.durable" class="px-2 pt-1.5 pb-1 text-xs text-muted-foreground">
|
|
154
|
+
{{ t("shared.datatable.savedviews.local.note") }}
|
|
155
|
+
</p>
|
|
136
156
|
</DropdownMenuContent>
|
|
137
157
|
</DropdownMenu>
|
|
138
158
|
|
|
@@ -158,7 +178,7 @@ function remove() {
|
|
|
158
178
|
<Button variant="outline" @click="saveOpen = false">
|
|
159
179
|
{{ t("shared.datatable.savedviews.savedialog.action.cancel") }}
|
|
160
180
|
</Button>
|
|
161
|
-
<Button :disabled="!saveName.trim()" @click="save">
|
|
181
|
+
<Button :disabled="!saveName.trim() || saving" @click="save">
|
|
162
182
|
{{ t("shared.datatable.savedviews.savedialog.action.save") }}
|
|
163
183
|
</Button>
|
|
164
184
|
</DialogFooter>
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { DataTableSavedViews } from './types.js';
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
config: DataTableSavedViews;
|
|
4
|
+
/** Matches the row it sits in; `sm` is the 32px square its neighbours are. */
|
|
5
|
+
size?: 'sm' | 'default';
|
|
4
6
|
};
|
|
5
|
-
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
7
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
8
|
+
size: "sm" | "default";
|
|
9
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
6
10
|
export default _default;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { DataTableColumn, SortState } from './types.js';
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
columns: DataTableColumn[];
|
|
4
|
+
/** Matches the row it sits in; `sm` is the 32px square its neighbours are. */
|
|
5
|
+
size?: 'sm' | 'default';
|
|
4
6
|
};
|
|
5
7
|
type __VLS_PublicProps = __VLS_Props & {
|
|
6
8
|
modelValue: SortState;
|
|
@@ -9,5 +11,7 @@ declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {},
|
|
|
9
11
|
"update:modelValue": (value: SortState) => any;
|
|
10
12
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
11
13
|
"onUpdate:modelValue"?: ((value: SortState) => any) | undefined;
|
|
12
|
-
}>, {
|
|
14
|
+
}>, {
|
|
15
|
+
size: "sm" | "default";
|
|
16
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
17
|
export default _default;
|
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import {
|
|
2
|
+
import { CheckCircle, RoundSortVertical, SortFromBottomToTop, SortFromTopToBottom } from "#solar-icons";
|
|
3
3
|
import {
|
|
4
4
|
Button,
|
|
5
5
|
DropdownMenu,
|
|
6
6
|
DropdownMenuContent,
|
|
7
7
|
DropdownMenuItem,
|
|
8
|
+
DropdownMenuLabel,
|
|
8
9
|
DropdownMenuTrigger
|
|
9
10
|
} from "@revenexx/studio";
|
|
10
11
|
const props = defineProps({
|
|
11
|
-
columns: { type: Array, required: true }
|
|
12
|
+
columns: { type: Array, required: true },
|
|
13
|
+
size: { type: String, required: false, default: "sm" }
|
|
12
14
|
});
|
|
13
15
|
const sort = defineModel({ type: Object, ...{ required: true } });
|
|
14
16
|
const t = useT();
|
|
17
|
+
const sorted = computed(() => Boolean(sort.value.column && sort.value.direction));
|
|
18
|
+
const icon = computed(() => {
|
|
19
|
+
if (!sorted.value) return RoundSortVertical;
|
|
20
|
+
return sort.value.direction === "asc" ? SortFromBottomToTop : SortFromTopToBottom;
|
|
21
|
+
});
|
|
15
22
|
const label = computed(() => {
|
|
16
23
|
const col = props.columns.find((c) => c.key === sort.value.column);
|
|
17
|
-
if (!col || !
|
|
24
|
+
if (!col || !sorted.value) return t("shared.datatable.sortmenu.label.default");
|
|
18
25
|
return `${col.label} ${sort.value.direction === "asc" ? "\u2191" : "\u2193"}`;
|
|
19
26
|
});
|
|
27
|
+
function isActive(key, direction) {
|
|
28
|
+
return sort.value.column === key && sort.value.direction === direction;
|
|
29
|
+
}
|
|
20
30
|
function pick(key, direction) {
|
|
21
31
|
sort.value = { column: key, direction };
|
|
22
32
|
}
|
|
@@ -25,21 +35,39 @@ function pick(key, direction) {
|
|
|
25
35
|
<template>
|
|
26
36
|
<DropdownMenu>
|
|
27
37
|
<DropdownMenuTrigger as-child>
|
|
28
|
-
<Button
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
<Button
|
|
39
|
+
:variant="sorted ? 'default' : 'outline'"
|
|
40
|
+
:size="size === 'sm' ? 'icon-sm' : 'icon'"
|
|
41
|
+
:data-active="sorted"
|
|
42
|
+
:aria-label="t('shared.datatable.sortmenu.aria', { sort: label })"
|
|
43
|
+
:title="t('shared.datatable.sortmenu.aria', { sort: label })"
|
|
44
|
+
>
|
|
45
|
+
<!-- Violet while idle like the filter trigger and the column picker
|
|
46
|
+
beside it; on the filled face the icon takes the button's own
|
|
47
|
+
foreground, or it would be violet on violet. -->
|
|
48
|
+
<span class="flex shrink-0 items-center" :class="sorted ? '' : 'text-primary'">
|
|
49
|
+
<component :is="icon" weight="Linear" :class="size === 'sm' ? 'size-3.5' : 'size-4'" />
|
|
50
|
+
</span>
|
|
32
51
|
</Button>
|
|
33
52
|
</DropdownMenuTrigger>
|
|
34
|
-
<DropdownMenuContent align="start" class="w-
|
|
53
|
+
<DropdownMenuContent align="start" class="w-56">
|
|
54
|
+
<DropdownMenuLabel>{{ t("shared.datatable.sortmenu.heading") }}</DropdownMenuLabel>
|
|
35
55
|
<template v-for="col in columns" :key="col.key">
|
|
36
56
|
<DropdownMenuItem class="gap-2" @select="pick(col.key, 'asc')">
|
|
37
|
-
<
|
|
38
|
-
<span
|
|
57
|
+
<SortFromBottomToTop weight="Linear" :size="13" class="shrink-0 text-muted-foreground" />
|
|
58
|
+
<span
|
|
59
|
+
class="min-w-0 flex-1 truncate"
|
|
60
|
+
:class="isActive(col.key, 'asc') ? 'font-medium text-primary' : ''"
|
|
61
|
+
>{{ col.label }}</span>
|
|
62
|
+
<CheckCircle v-if="isActive(col.key, 'asc')" :size="14" weight="Bold" class="shrink-0 text-primary" />
|
|
39
63
|
</DropdownMenuItem>
|
|
40
64
|
<DropdownMenuItem class="gap-2" @select="pick(col.key, 'desc')">
|
|
41
|
-
<
|
|
42
|
-
<span
|
|
65
|
+
<SortFromTopToBottom weight="Linear" :size="13" class="shrink-0 text-muted-foreground" />
|
|
66
|
+
<span
|
|
67
|
+
class="min-w-0 flex-1 truncate"
|
|
68
|
+
:class="isActive(col.key, 'desc') ? 'font-medium text-primary' : ''"
|
|
69
|
+
>{{ col.label }}</span>
|
|
70
|
+
<CheckCircle v-if="isActive(col.key, 'desc')" :size="14" weight="Bold" class="shrink-0 text-primary" />
|
|
43
71
|
</DropdownMenuItem>
|
|
44
72
|
</template>
|
|
45
73
|
</DropdownMenuContent>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { DataTableColumn, SortState } from './types.js';
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
columns: DataTableColumn[];
|
|
4
|
+
/** Matches the row it sits in; `sm` is the 32px square its neighbours are. */
|
|
5
|
+
size?: 'sm' | 'default';
|
|
4
6
|
};
|
|
5
7
|
type __VLS_PublicProps = __VLS_Props & {
|
|
6
8
|
modelValue: SortState;
|
|
@@ -9,5 +11,7 @@ declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {},
|
|
|
9
11
|
"update:modelValue": (value: SortState) => any;
|
|
10
12
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
11
13
|
"onUpdate:modelValue"?: ((value: SortState) => any) | undefined;
|
|
12
|
-
}>, {
|
|
14
|
+
}>, {
|
|
15
|
+
size: "sm" | "default";
|
|
16
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
17
|
export default _default;
|
|
@@ -6,11 +6,11 @@ type __VLS_Props = {
|
|
|
6
6
|
kinds?: string[];
|
|
7
7
|
};
|
|
8
8
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
-
select: (assets: StorageAsset[]) => any;
|
|
10
9
|
"update:open": (value: boolean) => any;
|
|
10
|
+
select: (assets: StorageAsset[]) => any;
|
|
11
11
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
|
-
onSelect?: ((assets: StorageAsset[]) => any) | undefined;
|
|
13
12
|
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
13
|
+
onSelect?: ((assets: StorageAsset[]) => any) | undefined;
|
|
14
14
|
}>, {
|
|
15
15
|
multiple: boolean;
|
|
16
16
|
kinds: string[];
|
|
@@ -6,11 +6,11 @@ type __VLS_Props = {
|
|
|
6
6
|
kinds?: string[];
|
|
7
7
|
};
|
|
8
8
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
-
select: (assets: StorageAsset[]) => any;
|
|
10
9
|
"update:open": (value: boolean) => any;
|
|
10
|
+
select: (assets: StorageAsset[]) => any;
|
|
11
11
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
|
-
onSelect?: ((assets: StorageAsset[]) => any) | undefined;
|
|
13
12
|
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
13
|
+
onSelect?: ((assets: StorageAsset[]) => any) | undefined;
|
|
14
14
|
}>, {
|
|
15
15
|
multiple: boolean;
|
|
16
16
|
kinds: string[];
|
|
@@ -14,16 +14,16 @@ type __VLS_Props = {
|
|
|
14
14
|
folderName: string | null;
|
|
15
15
|
};
|
|
16
16
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
17
|
+
"update:open": (value: boolean) => any;
|
|
17
18
|
done: (summary: {
|
|
18
19
|
created: number;
|
|
19
20
|
failed: number;
|
|
20
21
|
}) => any;
|
|
21
|
-
"update:open": (value: boolean) => any;
|
|
22
22
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
23
|
+
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
23
24
|
onDone?: ((summary: {
|
|
24
25
|
created: number;
|
|
25
26
|
failed: number;
|
|
26
27
|
}) => any) | undefined;
|
|
27
|
-
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
28
28
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
29
29
|
export default _default;
|
|
@@ -14,16 +14,16 @@ type __VLS_Props = {
|
|
|
14
14
|
folderName: string | null;
|
|
15
15
|
};
|
|
16
16
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
17
|
+
"update:open": (value: boolean) => any;
|
|
17
18
|
done: (summary: {
|
|
18
19
|
created: number;
|
|
19
20
|
failed: number;
|
|
20
21
|
}) => any;
|
|
21
|
-
"update:open": (value: boolean) => any;
|
|
22
22
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
23
|
+
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
23
24
|
onDone?: ((summary: {
|
|
24
25
|
created: number;
|
|
25
26
|
failed: number;
|
|
26
27
|
}) => any) | undefined;
|
|
27
|
-
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
28
28
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
29
29
|
export default _default;
|
|
@@ -18,11 +18,15 @@ const sortIcon = computed(() => {
|
|
|
18
18
|
<template>
|
|
19
19
|
<button
|
|
20
20
|
type="button"
|
|
21
|
-
class="group inline-flex cursor-pointer items-center gap-1.5 -m-1 p-1 rounded-md text-left transition-colors hover:text-primary"
|
|
21
|
+
class="group inline-flex min-w-0 max-w-full cursor-pointer items-center gap-1.5 -m-1 p-1 rounded-md text-left transition-colors hover:text-primary"
|
|
22
22
|
:class="alignRight ? 'flex-row-reverse w-full justify-end' : ''"
|
|
23
23
|
@click="emit('toggle', column)"
|
|
24
24
|
>
|
|
25
|
-
|
|
25
|
+
<!-- The label truncates. Without this the button is as wide as its words
|
|
26
|
+
and a heading longer than its column simply prints over the next one —
|
|
27
|
+
"SortierungGeändert" on a category list, with no hint that two columns
|
|
28
|
+
are involved. The cell carries the full text as its `title`. -->
|
|
29
|
+
<span class="min-w-0 truncate"><slot /></span>
|
|
26
30
|
<component
|
|
27
31
|
:is="sortIcon"
|
|
28
32
|
weight="Linear"
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Underline tab bar — the standard cockpit
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
2
|
+
* Underline tab bar — the standard cockpit tabs. A full-width hairline sits
|
|
3
|
+
* behind the row; the active/hover indicator is a 2px bar spanning exactly the
|
|
4
|
+
* label width (inset-x-2.5 = the button's px-2.5), sitting on the baseline over
|
|
5
|
+
* the hairline. Active = primary; hover = soft primary. Tabs scroll
|
|
6
|
+
* horizontally on overflow. To bleed the line to the panel edges, pass e.g.
|
|
7
|
+
* `class="-mx-4 px-4"` from a padded page.
|
|
8
|
+
*
|
|
9
|
+
* ## `sticky`
|
|
10
|
+
*
|
|
11
|
+
* On by default, because the original job was a detail page: the row glues
|
|
12
|
+
* itself to the bottom of the sticky `PageHeader` (hence the magic `57px`) and
|
|
13
|
+
* frosts its background so content scrolls through.
|
|
14
|
+
*
|
|
15
|
+
* Off for a row INSIDE a panel — the queue tabs between a DataTable's toolbar
|
|
16
|
+
* and its header, say. There the offset is wrong (there is no page header above
|
|
17
|
+
* it) and the frosted layer is worse than nothing, because the panel already
|
|
18
|
+
* has a background of its own and the blur only muddies the rows scrolling past
|
|
19
|
+
* underneath. Same tabs, same indicator, same hairline; just not pinned.
|
|
8
20
|
*/
|
|
9
21
|
export interface TabItem {
|
|
10
22
|
key: string;
|
|
@@ -14,10 +26,14 @@ export interface TabItem {
|
|
|
14
26
|
type __VLS_Props = {
|
|
15
27
|
tabs: TabItem[];
|
|
16
28
|
modelValue: string;
|
|
29
|
+
/** Pin to the page header. Off for a bar inside a panel. */
|
|
30
|
+
sticky?: boolean;
|
|
17
31
|
};
|
|
18
32
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
19
33
|
"update:modelValue": (key: string) => any;
|
|
20
34
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
21
35
|
"onUpdate:modelValue"?: ((key: string) => any) | undefined;
|
|
22
|
-
}>, {
|
|
36
|
+
}>, {
|
|
37
|
+
sticky: boolean;
|
|
38
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
23
39
|
export default _default;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
defineProps({
|
|
3
3
|
tabs: { type: Array, required: true },
|
|
4
|
-
modelValue: { type: String, required: true }
|
|
4
|
+
modelValue: { type: String, required: true },
|
|
5
|
+
sticky: { type: Boolean, required: false, default: true }
|
|
5
6
|
});
|
|
6
7
|
defineEmits(["update:modelValue"]);
|
|
7
8
|
</script>
|
|
@@ -11,7 +12,7 @@ defineEmits(["update:modelValue"]);
|
|
|
11
12
|
pt-4 pb-3 + text-xl line + 1px border) so header and tabs stay glued as
|
|
12
13
|
one unit while the panel scrolls. Glassy frosted bg (blur + low opacity)
|
|
13
14
|
lets content scroll through. -->
|
|
14
|
-
<div class="sticky top-[57px] z-10 bg-white/40 backdrop-blur-xl">
|
|
15
|
+
<div :class="sticky ? 'sticky top-[57px] z-10 bg-white/40 backdrop-blur-xl' : 'relative'">
|
|
15
16
|
<!-- Pull the row out by the tab padding so the first/last label sits flush
|
|
16
17
|
with the surrounding content (headline) edges. -->
|
|
17
18
|
<div class="relative z-10 -mx-2.5 flex gap-1 overflow-x-auto">
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Underline tab bar — the standard cockpit
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
2
|
+
* Underline tab bar — the standard cockpit tabs. A full-width hairline sits
|
|
3
|
+
* behind the row; the active/hover indicator is a 2px bar spanning exactly the
|
|
4
|
+
* label width (inset-x-2.5 = the button's px-2.5), sitting on the baseline over
|
|
5
|
+
* the hairline. Active = primary; hover = soft primary. Tabs scroll
|
|
6
|
+
* horizontally on overflow. To bleed the line to the panel edges, pass e.g.
|
|
7
|
+
* `class="-mx-4 px-4"` from a padded page.
|
|
8
|
+
*
|
|
9
|
+
* ## `sticky`
|
|
10
|
+
*
|
|
11
|
+
* On by default, because the original job was a detail page: the row glues
|
|
12
|
+
* itself to the bottom of the sticky `PageHeader` (hence the magic `57px`) and
|
|
13
|
+
* frosts its background so content scrolls through.
|
|
14
|
+
*
|
|
15
|
+
* Off for a row INSIDE a panel — the queue tabs between a DataTable's toolbar
|
|
16
|
+
* and its header, say. There the offset is wrong (there is no page header above
|
|
17
|
+
* it) and the frosted layer is worse than nothing, because the panel already
|
|
18
|
+
* has a background of its own and the blur only muddies the rows scrolling past
|
|
19
|
+
* underneath. Same tabs, same indicator, same hairline; just not pinned.
|
|
8
20
|
*/
|
|
9
21
|
export interface TabItem {
|
|
10
22
|
key: string;
|
|
@@ -14,10 +26,14 @@ export interface TabItem {
|
|
|
14
26
|
type __VLS_Props = {
|
|
15
27
|
tabs: TabItem[];
|
|
16
28
|
modelValue: string;
|
|
29
|
+
/** Pin to the page header. Off for a bar inside a panel. */
|
|
30
|
+
sticky?: boolean;
|
|
17
31
|
};
|
|
18
32
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
19
33
|
"update:modelValue": (key: string) => any;
|
|
20
34
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
21
35
|
"onUpdate:modelValue"?: ((key: string) => any) | undefined;
|
|
22
|
-
}>, {
|
|
36
|
+
}>, {
|
|
37
|
+
sticky: boolean;
|
|
38
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
23
39
|
export default _default;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ComputedRef } from 'vue';
|
|
2
|
+
import type { Dashboard, DashboardScope, DashboardWritePayload } from '../types/dashboard.js';
|
|
3
|
+
export declare class DashboardApiError extends Error {
|
|
4
|
+
status: number;
|
|
5
|
+
constructor(status: number, message: string);
|
|
6
|
+
}
|
|
7
|
+
export interface UseDashboardsOptions {
|
|
8
|
+
/** Where the dashboard routes live. The cockpit serves them from Nitro. */
|
|
9
|
+
base?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function useDashboards(scope: DashboardScope, options?: UseDashboardsOptions): {
|
|
12
|
+
scope: DashboardScope;
|
|
13
|
+
dashboards: ComputedRef<Dashboard[]>;
|
|
14
|
+
defaultId: ComputedRef<string | null>;
|
|
15
|
+
loading: ComputedRef<boolean>;
|
|
16
|
+
saving: ComputedRef<boolean>;
|
|
17
|
+
error: ComputedRef<string | null>;
|
|
18
|
+
load: (force?: boolean) => Promise<Dashboard[]>;
|
|
19
|
+
resolve: (slug: string | null) => Dashboard | null;
|
|
20
|
+
create: (payload: DashboardWritePayload & {
|
|
21
|
+
name: string;
|
|
22
|
+
}) => Promise<Dashboard>;
|
|
23
|
+
save: (id: string, payload: DashboardWritePayload) => Promise<Dashboard>;
|
|
24
|
+
remove: (id: string) => Promise<void>;
|
|
25
|
+
duplicate: (id: string) => Promise<Dashboard>;
|
|
26
|
+
setDefault: (id: string | null) => Promise<void>;
|
|
27
|
+
};
|