@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.
Files changed (85) hide show
  1. package/dist/module.d.mts +4 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +9 -0
  4. package/dist/runtime/components/Dashboard/DashboardConfirmDialog.d.vue.ts +15 -0
  5. package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue +39 -0
  6. package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue.d.ts +15 -0
  7. package/dist/runtime/components/Dashboard/DashboardControls.d.vue.ts +57 -0
  8. package/dist/runtime/components/Dashboard/DashboardControls.vue +209 -0
  9. package/dist/runtime/components/Dashboard/DashboardControls.vue.d.ts +57 -0
  10. package/dist/runtime/components/Dashboard/DashboardGrid.d.vue.ts +16 -0
  11. package/dist/runtime/components/Dashboard/DashboardGrid.vue +87 -0
  12. package/dist/runtime/components/Dashboard/DashboardGrid.vue.d.ts +16 -0
  13. package/dist/runtime/components/Dashboard/DashboardNameDialog.d.vue.ts +18 -0
  14. package/dist/runtime/components/Dashboard/DashboardNameDialog.vue +61 -0
  15. package/dist/runtime/components/Dashboard/DashboardNameDialog.vue.d.ts +18 -0
  16. package/dist/runtime/components/Dashboard/DashboardPickerDialog.d.vue.ts +14 -0
  17. package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue +100 -0
  18. package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue.d.ts +14 -0
  19. package/dist/runtime/components/Dashboard/DashboardShareDialog.d.vue.ts +27 -0
  20. package/dist/runtime/components/Dashboard/DashboardShareDialog.vue +155 -0
  21. package/dist/runtime/components/Dashboard/DashboardShareDialog.vue.d.ts +27 -0
  22. package/dist/runtime/components/Dashboard/DashboardTopBar.d.vue.ts +59 -0
  23. package/dist/runtime/components/Dashboard/DashboardTopBar.vue +79 -0
  24. package/dist/runtime/components/Dashboard/DashboardTopBar.vue.d.ts +59 -0
  25. package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.d.vue.ts +42 -0
  26. package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue +153 -0
  27. package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue.d.ts +42 -0
  28. package/dist/runtime/components/Dashboard/DashboardWidgetFrame.d.vue.ts +27 -0
  29. package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue +80 -0
  30. package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue.d.ts +27 -0
  31. package/dist/runtime/components/Dashboard/DashboardWidgetHost.d.vue.ts +6 -0
  32. package/dist/runtime/components/Dashboard/DashboardWidgetHost.vue +30 -0
  33. package/dist/runtime/components/Dashboard/DashboardWidgetHost.vue.d.ts +6 -0
  34. package/dist/runtime/components/Dashboard/DashboardWidgetIcon.d.vue.ts +8 -0
  35. package/dist/runtime/components/Dashboard/DashboardWidgetIcon.vue +13 -0
  36. package/dist/runtime/components/Dashboard/DashboardWidgetIcon.vue.d.ts +8 -0
  37. package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.d.vue.ts +23 -0
  38. package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.vue +140 -0
  39. package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.vue.d.ts +23 -0
  40. package/dist/runtime/components/Dashboard/DashboardWidgetPreview.d.vue.ts +22 -0
  41. package/dist/runtime/components/Dashboard/DashboardWidgetPreview.vue +124 -0
  42. package/dist/runtime/components/Dashboard/DashboardWidgetPreview.vue.d.ts +22 -0
  43. package/dist/runtime/components/DataTable/DataTable.d.vue.ts +39 -3
  44. package/dist/runtime/components/DataTable/DataTable.vue +306 -32
  45. package/dist/runtime/components/DataTable/DataTable.vue.d.ts +39 -3
  46. package/dist/runtime/components/DataTable/FacetChips.d.vue.ts +16 -0
  47. package/dist/runtime/components/DataTable/FacetChips.vue +72 -0
  48. package/dist/runtime/components/DataTable/FacetChips.vue.d.ts +16 -0
  49. package/dist/runtime/components/DataTable/FacetPanel.d.vue.ts +20 -0
  50. package/dist/runtime/components/DataTable/FacetPanel.vue +359 -0
  51. package/dist/runtime/components/DataTable/FacetPanel.vue.d.ts +20 -0
  52. package/dist/runtime/components/DataTable/SavedViewsMenu.d.vue.ts +5 -1
  53. package/dist/runtime/components/DataTable/SavedViewsMenu.vue +83 -63
  54. package/dist/runtime/components/DataTable/SavedViewsMenu.vue.d.ts +5 -1
  55. package/dist/runtime/components/DataTable/SortMenu.d.vue.ts +5 -1
  56. package/dist/runtime/components/DataTable/SortMenu.vue +40 -12
  57. package/dist/runtime/components/DataTable/SortMenu.vue.d.ts +5 -1
  58. package/dist/runtime/components/Media/MediaPickerDialog.d.vue.ts +2 -2
  59. package/dist/runtime/components/Media/MediaPickerDialog.vue.d.ts +2 -2
  60. package/dist/runtime/components/Media/MediaUploader.d.vue.ts +2 -2
  61. package/dist/runtime/components/Media/MediaUploader.vue.d.ts +2 -2
  62. package/dist/runtime/components/Shared/SortHeader.vue +6 -2
  63. package/dist/runtime/components/Shared/TabBar.d.vue.ts +23 -7
  64. package/dist/runtime/components/Shared/TabBar.vue +3 -2
  65. package/dist/runtime/components/Shared/TabBar.vue.d.ts +23 -7
  66. package/dist/runtime/composables/useDashboards.d.ts +27 -0
  67. package/dist/runtime/composables/useDashboards.js +143 -0
  68. package/dist/runtime/composables/useSavedViews.d.ts +44 -0
  69. package/dist/runtime/composables/useSavedViews.js +44 -0
  70. package/dist/runtime/composables/useStickyListView.d.ts +60 -0
  71. package/dist/runtime/composables/useStickyListView.js +32 -0
  72. package/dist/runtime/lib/canonicalJson.d.ts +43 -0
  73. package/dist/runtime/lib/canonicalJson.js +38 -0
  74. package/dist/runtime/lib/dashboardIcons.d.ts +4 -0
  75. package/dist/runtime/lib/dashboardIcons.js +50 -0
  76. package/dist/runtime/lib/facets.d.ts +200 -0
  77. package/dist/runtime/lib/facets.js +171 -0
  78. package/dist/runtime/lib/gridTracks.d.ts +55 -0
  79. package/dist/runtime/lib/gridTracks.js +29 -0
  80. package/dist/runtime/lib/widgetRegistry.d.ts +124 -0
  81. package/dist/runtime/lib/widgetRegistry.js +66 -0
  82. package/dist/runtime/types/dashboard.d.ts +95 -0
  83. package/dist/runtime/types/dashboard.js +2 -0
  84. package/dist/types.d.mts +8 -0
  85. package/package.json +4 -3
@@ -0,0 +1,18 @@
1
+ type __VLS_Props = {
2
+ open: boolean;
3
+ title: string;
4
+ description?: string;
5
+ label: string;
6
+ placeholder?: string;
7
+ confirmLabel: string;
8
+ /** Pre-filled value — a rename starts from the current name. */
9
+ initial?: string;
10
+ };
11
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
12
+ "update:open": (value: boolean) => any;
13
+ submit: (name: string) => any;
14
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
15
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
16
+ onSubmit?: ((name: string) => any) | undefined;
17
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
18
+ export default _default;
@@ -0,0 +1,14 @@
1
+ import type { Dashboard } from '../../types/dashboard.js';
2
+ type __VLS_Props = {
3
+ open: boolean;
4
+ dashboards: Dashboard[];
5
+ currentId?: string | null;
6
+ };
7
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
8
+ "update:open": (value: boolean) => any;
9
+ select: (id: string) => any;
10
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
11
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
12
+ onSelect?: ((id: string) => any) | undefined;
13
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
14
+ export default _default;
@@ -0,0 +1,100 @@
1
+ <script setup>
2
+ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, Input } from "@revenexx/studio";
3
+ import { Global, Lock, Magnifier, Pen, Star } from "#solar-icons";
4
+ import { computed, ref } from "vue";
5
+ import { dateTimeFormat } from "../../lib/formatBridge";
6
+ const props = defineProps({
7
+ open: { type: Boolean, required: true },
8
+ dashboards: { type: Array, required: true },
9
+ currentId: { type: [String, null], required: false }
10
+ });
11
+ const emit = defineEmits(["update:open", "select"]);
12
+ const t = useT();
13
+ function formatDateTime(value) {
14
+ const date = value instanceof Date ? value : new Date(value);
15
+ return Number.isNaN(date.getTime()) ? "" : dateTimeFormat({ dateStyle: "medium", timeStyle: "short" }).format(date);
16
+ }
17
+ const query = ref("");
18
+ watch(() => props.open, (open) => {
19
+ if (open) query.value = "";
20
+ });
21
+ const groups = computed(() => {
22
+ const term = query.value.trim().toLowerCase();
23
+ const match = (d) => !term || d.name.toLowerCase().includes(term) || (d.ownerName ?? "").toLowerCase().includes(term);
24
+ return [
25
+ { key: "mine", label: t("dashboard.header.group.mine"), items: props.dashboards.filter((d) => d.isOwner && match(d)) },
26
+ { key: "shared", label: t("dashboard.header.group.shared"), items: props.dashboards.filter((d) => !d.isOwner && match(d)) }
27
+ ].filter((group) => group.items.length > 0);
28
+ });
29
+ const total = computed(() => groups.value.reduce((sum, g) => sum + g.items.length, 0));
30
+ function pick(id) {
31
+ emit("select", id);
32
+ emit("update:open", false);
33
+ }
34
+ </script>
35
+
36
+ <template>
37
+ <Dialog :open="open" @update:open="emit('update:open', $event)">
38
+ <DialogContent class="max-w-2xl">
39
+ <DialogHeader>
40
+ <DialogTitle>{{ t("dashboard.picker.title") }}</DialogTitle>
41
+ <DialogDescription>{{ t("dashboard.picker.description", { count: dashboards.length }) }}</DialogDescription>
42
+ </DialogHeader>
43
+
44
+ <div class="relative">
45
+ <Magnifier :size="14" class="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" />
46
+ <Input v-model="query" :placeholder="t('dashboard.picker.search_placeholder')" class="bg-white pl-8" />
47
+ </div>
48
+
49
+ <div class="max-h-[55vh] -mx-1 space-y-4 overflow-y-auto px-1 pb-1">
50
+ <p v-if="!total" class="py-10 text-center text-sm text-muted-foreground">
51
+ {{ t("dashboard.picker.no_matches", { query }) }}
52
+ </p>
53
+
54
+ <section v-for="group in groups" :key="group.key">
55
+ <h4 class="mb-1.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
56
+ {{ group.label }}
57
+ </h4>
58
+ <ul class="flex flex-col gap-1">
59
+ <li v-for="dashboard in group.items" :key="dashboard.id">
60
+ <button
61
+ type="button"
62
+ class="flex w-full items-center gap-3 rounded-md border px-3 py-2 text-left transition"
63
+ :class="dashboard.id === currentId ? 'border-primary/40 bg-primary/[0.04]' : 'border-transparent hover:border-black/[0.08] hover:bg-black/[0.03]'"
64
+ @click="pick(dashboard.id)"
65
+ >
66
+ <span class="min-w-0 flex-1">
67
+ <span class="flex items-center gap-1.5">
68
+ <span class="truncate text-sm font-medium text-[#0B0522]">{{ dashboard.name }}</span>
69
+ <span
70
+ v-if="dashboard.isDefault"
71
+ class="shrink-0 rounded bg-amber-50 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-amber-700"
72
+ >{{ t("dashboard.header.menu.default_badge") }}</span>
73
+ </span>
74
+ <span class="mt-0.5 block truncate text-xs text-muted-foreground">
75
+ <template v-if="dashboard.ownerName">{{ t("dashboard.picker.owner", { name: dashboard.ownerName }) }} · </template>
76
+ {{ t("dashboard.picker.updated", { at: formatDateTime(dashboard.updatedAt) }) }}
77
+ </span>
78
+ </span>
79
+
80
+ <!-- How it is shared, at a glance: private, read-only or open. -->
81
+ <span class="flex shrink-0 items-center gap-1 text-xs text-muted-foreground">
82
+ <template v-if="dashboard.visibility === 'private'">
83
+ <Lock :size="12" /> {{ t("dashboard.share.private.title") }}
84
+ </template>
85
+ <template v-else-if="dashboard.tenantPermission === 'edit'">
86
+ <Pen :size="12" /> {{ t("dashboard.share.permission.edit.title") }}
87
+ </template>
88
+ <template v-else>
89
+ <Global :size="12" /> {{ t("dashboard.share.permission.view.title") }}
90
+ </template>
91
+ </span>
92
+ <Star v-if="dashboard.id === currentId" :size="12" class="shrink-0 text-primary opacity-0" />
93
+ </button>
94
+ </li>
95
+ </ul>
96
+ </section>
97
+ </div>
98
+ </DialogContent>
99
+ </Dialog>
100
+ </template>
@@ -0,0 +1,14 @@
1
+ import type { Dashboard } from '../../types/dashboard.js';
2
+ type __VLS_Props = {
3
+ open: boolean;
4
+ dashboards: Dashboard[];
5
+ currentId?: string | null;
6
+ };
7
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
8
+ "update:open": (value: boolean) => any;
9
+ select: (id: string) => any;
10
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
11
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
12
+ onSelect?: ((id: string) => any) | undefined;
13
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
14
+ export default _default;
@@ -0,0 +1,27 @@
1
+ import type { Dashboard, DashboardTenantPermission, DashboardVisibility } from '../../types/dashboard.js';
2
+ type __VLS_Props = {
3
+ open: boolean;
4
+ dashboard: Dashboard | null;
5
+ /**
6
+ * Where this surface's dashboards live. The link is `path?dashboard=slug`,
7
+ * and a Commerce dashboard opened at `/` would land on the home screen and
8
+ * silently resolve to something else.
9
+ */
10
+ path?: string;
11
+ };
12
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
13
+ "update:open": (value: boolean) => any;
14
+ change: (payload: {
15
+ visibility: DashboardVisibility;
16
+ tenantPermission: DashboardTenantPermission;
17
+ }) => any;
18
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
19
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
20
+ onChange?: ((payload: {
21
+ visibility: DashboardVisibility;
22
+ tenantPermission: DashboardTenantPermission;
23
+ }) => any) | undefined;
24
+ }>, {
25
+ path: string;
26
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
27
+ export default _default;
@@ -0,0 +1,155 @@
1
+ <script setup>
2
+ import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@revenexx/studio";
3
+ import { CheckCircle, Copy, Global, Lock, Pen } from "#solar-icons";
4
+ import { computed, ref, watch } from "vue";
5
+ import { dateTimeFormat } from "../../lib/formatBridge";
6
+ const props = defineProps({
7
+ open: { type: Boolean, required: true },
8
+ dashboard: { type: [Object, null], required: true },
9
+ path: { type: String, required: false, default: "/" }
10
+ });
11
+ const emit = defineEmits(["update:open", "change"]);
12
+ const t = useT();
13
+ const router = useRouter();
14
+ function modeOf(dashboard) {
15
+ if (!dashboard || dashboard.visibility !== "tenant") return "private";
16
+ return dashboard.tenantPermission === "edit" ? "tenant-edit" : "tenant-view";
17
+ }
18
+ const MODE_VALUES = {
19
+ "private": { visibility: "private", tenantPermission: "view" },
20
+ "tenant-view": { visibility: "tenant", tenantPermission: "view" },
21
+ "tenant-edit": { visibility: "tenant", tenantPermission: "edit" }
22
+ };
23
+ const choice = ref("private");
24
+ watch(() => props.open, (open) => {
25
+ if (open) choice.value = modeOf(props.dashboard);
26
+ });
27
+ function formatDateTime(value) {
28
+ const date = value instanceof Date ? value : new Date(value);
29
+ return Number.isNaN(date.getTime()) ? "" : dateTimeFormat({ dateStyle: "medium", timeStyle: "short" }).format(date);
30
+ }
31
+ const options = computed(() => [
32
+ {
33
+ value: "private",
34
+ icon: Lock,
35
+ title: t("dashboard.share.private.title"),
36
+ description: t("dashboard.share.private.description")
37
+ },
38
+ {
39
+ value: "tenant-view",
40
+ icon: Global,
41
+ title: t("dashboard.share.mode.view.title"),
42
+ description: t("dashboard.share.mode.view.description")
43
+ },
44
+ {
45
+ value: "tenant-edit",
46
+ icon: Pen,
47
+ title: t("dashboard.share.mode.edit.title"),
48
+ description: t("dashboard.share.mode.edit.description")
49
+ }
50
+ ]);
51
+ const link = computed(() => {
52
+ if (!props.dashboard || !import.meta.client) return "";
53
+ const href = router.resolve({ path: props.path, query: { dashboard: props.dashboard.slug } }).href;
54
+ return new URL(href, window.location.origin).toString();
55
+ });
56
+ const copied = ref(false);
57
+ let copiedTimer = null;
58
+ async function copyLink() {
59
+ try {
60
+ await navigator.clipboard.writeText(link.value);
61
+ copied.value = true;
62
+ if (copiedTimer) clearTimeout(copiedTimer);
63
+ copiedTimer = setTimeout(() => {
64
+ copied.value = false;
65
+ }, 2e3);
66
+ } catch {
67
+ }
68
+ }
69
+ onUnmounted(() => {
70
+ if (copiedTimer) clearTimeout(copiedTimer);
71
+ });
72
+ const meta = computed(() => {
73
+ const d = props.dashboard;
74
+ if (!d) return "";
75
+ const parts = [];
76
+ if (d.ownerName) parts.push(t("dashboard.share.meta.created", { name: d.ownerName }));
77
+ parts.push(d.updatedByName ? t("dashboard.share.meta.updated", { name: d.updatedByName, at: formatDateTime(d.updatedAt) }) : t("dashboard.share.meta.updated_unknown", { at: formatDateTime(d.updatedAt) }));
78
+ if (!parts[0].match(/^[A-ZÄÖÜ]/)) parts[0] = parts[0].charAt(0).toUpperCase() + parts[0].slice(1);
79
+ return parts.join(" \xB7 ");
80
+ });
81
+ const dirty = computed(() => choice.value !== modeOf(props.dashboard));
82
+ function apply() {
83
+ if (dirty.value) emit("change", MODE_VALUES[choice.value]);
84
+ emit("update:open", false);
85
+ }
86
+ </script>
87
+
88
+ <template>
89
+ <Dialog :open="open" @update:open="emit('update:open', $event)">
90
+ <DialogContent class="max-w-lg">
91
+ <DialogHeader>
92
+ <DialogTitle>{{ t("dashboard.share.title", { name: dashboard?.name ?? "" }) }}</DialogTitle>
93
+ <DialogDescription>{{ t("dashboard.share.description") }}</DialogDescription>
94
+ </DialogHeader>
95
+
96
+ <div class="space-y-2">
97
+ <button
98
+ v-for="option in options"
99
+ :key="option.value"
100
+ type="button"
101
+ class="flex w-full items-start gap-3 rounded-lg border p-3 text-left transition"
102
+ :class="choice === option.value ? 'border-primary/40 bg-primary/[0.04]' : 'border-black/[0.06] hover:border-black/15 hover:bg-black/[0.02]'"
103
+ @click="choice = option.value"
104
+ >
105
+ <span
106
+ class="mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-md"
107
+ :class="choice === option.value ? 'bg-primary/10 text-primary' : 'bg-black/[0.04] text-muted-foreground'"
108
+ >
109
+ <component :is="option.icon" :size="15" />
110
+ </span>
111
+ <span class="min-w-0 flex-1">
112
+ <span class="block text-sm font-medium text-[#0B0522]">{{ option.title }}</span>
113
+ <span class="mt-0.5 block text-xs leading-snug text-muted-foreground">{{ option.description }}</span>
114
+ </span>
115
+ <CheckCircle v-if="choice === option.value" :size="16" class="mt-0.5 shrink-0 text-primary" />
116
+ </button>
117
+ </div>
118
+
119
+ <!-- The link works for anyone who can already see the dashboard; it is a
120
+ convenience, not a grant. Sharing is what grants. -->
121
+ <div v-if="choice !== 'private'" class="flex items-center gap-2 rounded-md border border-black/[0.06] bg-black/[0.02] px-2 py-1.5">
122
+ <input
123
+ :value="link"
124
+ readonly
125
+ class="min-w-0 flex-1 truncate bg-transparent text-xs text-muted-foreground outline-none"
126
+ @focus="$event.target.select()"
127
+ >
128
+ <button
129
+ type="button"
130
+ class="flex shrink-0 items-center gap-1 rounded px-1.5 py-1 text-xs font-medium text-primary hover:bg-primary/10"
131
+ @click="copyLink"
132
+ >
133
+ <component :is="copied ? CheckCircle : Copy" :size="13" />
134
+ {{ copied ? t("dashboard.share.link.copied") : t("dashboard.share.link.copy") }}
135
+ </button>
136
+ </div>
137
+
138
+ <!-- Who made it and who touched it last. Names are captured at write
139
+ time; the cockpit has no user directory to resolve a subject id
140
+ against, and a lookup per row would not be worth it. -->
141
+ <p v-if="meta" class="border-t border-black/[0.06] pt-3 text-xs text-muted-foreground">
142
+ {{ meta }}
143
+ </p>
144
+
145
+ <DialogFooter>
146
+ <Button variant="outline" size="sm" class="h-7 text-sm" @click="emit('update:open', false)">
147
+ {{ t("dashboard.dialog.action.cancel") }}
148
+ </Button>
149
+ <Button size="sm" class="h-7 text-sm" :disabled="!dirty" @click="apply">
150
+ {{ t("dashboard.share.action") }}
151
+ </Button>
152
+ </DialogFooter>
153
+ </DialogContent>
154
+ </Dialog>
155
+ </template>
@@ -0,0 +1,27 @@
1
+ import type { Dashboard, DashboardTenantPermission, DashboardVisibility } from '../../types/dashboard.js';
2
+ type __VLS_Props = {
3
+ open: boolean;
4
+ dashboard: Dashboard | null;
5
+ /**
6
+ * Where this surface's dashboards live. The link is `path?dashboard=slug`,
7
+ * and a Commerce dashboard opened at `/` would land on the home screen and
8
+ * silently resolve to something else.
9
+ */
10
+ path?: string;
11
+ };
12
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
13
+ "update:open": (value: boolean) => any;
14
+ change: (payload: {
15
+ visibility: DashboardVisibility;
16
+ tenantPermission: DashboardTenantPermission;
17
+ }) => any;
18
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
19
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
20
+ onChange?: ((payload: {
21
+ visibility: DashboardVisibility;
22
+ tenantPermission: DashboardTenantPermission;
23
+ }) => any) | undefined;
24
+ }>, {
25
+ path: string;
26
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
27
+ export default _default;
@@ -0,0 +1,59 @@
1
+ import type { Dashboard } from '../../types/dashboard.js';
2
+ type __VLS_Props = {
3
+ dashboards: Dashboard[];
4
+ current: Dashboard | null;
5
+ editing: boolean;
6
+ dirty: boolean;
7
+ saving: boolean;
8
+ /**
9
+ * The person this row greets. A prop rather than a lookup: who is signed in
10
+ * is the HOST's question, and a published module has no user directory.
11
+ */
12
+ userName?: string;
13
+ /** The line above the greeting. Defaults to today's date. */
14
+ eyebrow?: string;
15
+ /** The big line. Defaults to the greeting. */
16
+ heading?: string;
17
+ };
18
+ declare var __VLS_19: any;
19
+ type __VLS_Slots = {} & {
20
+ scope?: (props: typeof __VLS_19) => any;
21
+ };
22
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
23
+ edit: () => any;
24
+ select: (id: string) => any;
25
+ save: () => any;
26
+ discard: () => any;
27
+ addWidget: () => any;
28
+ rename: () => any;
29
+ share: () => any;
30
+ browse: () => any;
31
+ create: () => any;
32
+ duplicate: () => any;
33
+ remove: () => any;
34
+ setDefault: () => any;
35
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
36
+ onEdit?: (() => any) | undefined;
37
+ onSelect?: ((id: string) => any) | undefined;
38
+ onSave?: (() => any) | undefined;
39
+ onDiscard?: (() => any) | undefined;
40
+ onAddWidget?: (() => any) | undefined;
41
+ onRename?: (() => any) | undefined;
42
+ onShare?: (() => any) | undefined;
43
+ onBrowse?: (() => any) | undefined;
44
+ onCreate?: (() => any) | undefined;
45
+ onDuplicate?: (() => any) | undefined;
46
+ onRemove?: (() => any) | undefined;
47
+ onSetDefault?: (() => any) | undefined;
48
+ }>, {
49
+ heading: string;
50
+ userName: string;
51
+ eyebrow: string;
52
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
53
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
54
+ export default _default;
55
+ type __VLS_WithSlots<T, S> = T & {
56
+ new (): {
57
+ $slots: S;
58
+ };
59
+ };
@@ -0,0 +1,79 @@
1
+ <script setup>
2
+ import { computed } from "vue";
3
+ import DashboardControls from "./DashboardControls.vue";
4
+ import { dateTimeFormat } from "../../lib/formatBridge";
5
+ const props = defineProps({
6
+ dashboards: { type: Array, required: true },
7
+ current: { type: [Object, null], required: true },
8
+ editing: { type: Boolean, required: true },
9
+ dirty: { type: Boolean, required: true },
10
+ saving: { type: Boolean, required: true },
11
+ userName: { type: String, required: false, default: "" },
12
+ eyebrow: { type: String, required: false, default: "" },
13
+ heading: { type: String, required: false, default: "" }
14
+ });
15
+ const emit = defineEmits(["select", "edit", "save", "discard", "addWidget", "rename", "share", "browse", "create", "duplicate", "remove", "setDefault"]);
16
+ const t = useT();
17
+ const greeting = computed(() => {
18
+ const hour = (/* @__PURE__ */ new Date()).getHours();
19
+ if (hour < 11) return t("dashboard.topbar.greeting.morning");
20
+ if (hour < 17) return t("dashboard.topbar.greeting.day");
21
+ return t("dashboard.topbar.greeting.evening");
22
+ });
23
+ const fullName = computed(() => props.userName.trim());
24
+ const dateLabel = computed(() => props.eyebrow || dateTimeFormat({ weekday: "long", day: "numeric", month: "long" }).format(/* @__PURE__ */ new Date()));
25
+ </script>
26
+
27
+ <template>
28
+ <div class="relative z-30 mb-3 rounded-lg border border-gray-400/10 outline-1 outline-white/30">
29
+ <div class="relative flex flex-wrap items-center justify-between gap-4 rounded-lg bg-gradient-to-br from-white via-violet-50/50 to-white px-5 py-5">
30
+ <!-- Decoration, in its own clipped layer. Clipping the row itself would
31
+ cut off the dashboard menu when it opens. Hidden from assistive tech,
32
+ and the drift stops for anyone who asked the system for less motion. -->
33
+ <div class="pointer-events-none absolute inset-0 overflow-hidden rounded-lg" aria-hidden="true">
34
+ <div class="absolute inset-0 -translate-x-full bg-gradient-to-r from-transparent via-white/60 to-transparent motion-safe:animate-[dashboard-shimmer_9s_ease-in-out_infinite]" />
35
+ <div class="absolute -right-16 -top-20 size-56 rounded-full bg-gradient-to-br from-violet-300/25 to-amber-200/20 blur-3xl" />
36
+ </div>
37
+
38
+ <div class="relative min-w-0">
39
+ <div class="text-mono uppercase text-muted-foreground">
40
+ {{ dateLabel }}
41
+ </div>
42
+ <h1 class="mt-1 truncate text-2xl font-semibold text-[#0B0522]">
43
+ <template v-if="heading">{{ heading }}</template>
44
+ <template v-else>
45
+ {{ greeting }}<template v-if="fullName">, <span class="text-primary">{{ fullName }}</span></template>.
46
+ </template>
47
+ </h1>
48
+ </div>
49
+
50
+ <DashboardControls
51
+ :dashboards="dashboards"
52
+ :current="current"
53
+ :editing="editing"
54
+ :dirty="dirty"
55
+ :saving="saving"
56
+ @select="emit('select', $event)"
57
+ @edit="emit('edit')"
58
+ @save="emit('save')"
59
+ @discard="emit('discard')"
60
+ @add-widget="emit('addWidget')"
61
+ @rename="emit('rename')"
62
+ @share="emit('share')"
63
+ @browse="emit('browse')"
64
+ @create="emit('create')"
65
+ @duplicate="emit('duplicate')"
66
+ @remove="emit('remove')"
67
+ @set-default="emit('setDefault')"
68
+ >
69
+ <template #scope="slotProps">
70
+ <slot name="scope" v-bind="slotProps" />
71
+ </template>
72
+ </DashboardControls>
73
+ </div>
74
+ </div>
75
+ </template>
76
+
77
+ <style>
78
+ @keyframes dashboard-shimmer{0%,60%{transform:translateX(-100%)}to{transform:translateX(200%)}}
79
+ </style>
@@ -0,0 +1,59 @@
1
+ import type { Dashboard } from '../../types/dashboard.js';
2
+ type __VLS_Props = {
3
+ dashboards: Dashboard[];
4
+ current: Dashboard | null;
5
+ editing: boolean;
6
+ dirty: boolean;
7
+ saving: boolean;
8
+ /**
9
+ * The person this row greets. A prop rather than a lookup: who is signed in
10
+ * is the HOST's question, and a published module has no user directory.
11
+ */
12
+ userName?: string;
13
+ /** The line above the greeting. Defaults to today's date. */
14
+ eyebrow?: string;
15
+ /** The big line. Defaults to the greeting. */
16
+ heading?: string;
17
+ };
18
+ declare var __VLS_19: any;
19
+ type __VLS_Slots = {} & {
20
+ scope?: (props: typeof __VLS_19) => any;
21
+ };
22
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
23
+ edit: () => any;
24
+ select: (id: string) => any;
25
+ save: () => any;
26
+ discard: () => any;
27
+ addWidget: () => any;
28
+ rename: () => any;
29
+ share: () => any;
30
+ browse: () => any;
31
+ create: () => any;
32
+ duplicate: () => any;
33
+ remove: () => any;
34
+ setDefault: () => any;
35
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
36
+ onEdit?: (() => any) | undefined;
37
+ onSelect?: ((id: string) => any) | undefined;
38
+ onSave?: (() => any) | undefined;
39
+ onDiscard?: (() => any) | undefined;
40
+ onAddWidget?: (() => any) | undefined;
41
+ onRename?: (() => any) | undefined;
42
+ onShare?: (() => any) | undefined;
43
+ onBrowse?: (() => any) | undefined;
44
+ onCreate?: (() => any) | undefined;
45
+ onDuplicate?: (() => any) | undefined;
46
+ onRemove?: (() => any) | undefined;
47
+ onSetDefault?: (() => any) | undefined;
48
+ }>, {
49
+ heading: string;
50
+ userName: string;
51
+ eyebrow: string;
52
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
53
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
54
+ export default _default;
55
+ type __VLS_WithSlots<T, S> = T & {
56
+ new (): {
57
+ $slots: S;
58
+ };
59
+ };
@@ -0,0 +1,42 @@
1
+ import type { DashboardWidget } from '../../types/dashboard.js';
2
+ type __VLS_Props = {
3
+ open: boolean;
4
+ widget: DashboardWidget | null;
5
+ /**
6
+ * Config the surface wants materialised before the form opens — the cockpit
7
+ * resolves its quick-link defaults into rows here, so the editor starts from
8
+ * what is on screen rather than from an empty list the widget is not showing.
9
+ */
10
+ hydrate?: (widget: DashboardWidget, config: Record<string, unknown>) => Record<string, unknown>;
11
+ };
12
+ declare var __VLS_66: {
13
+ field: any;
14
+ widget: any;
15
+ value: any;
16
+ set: (value: unknown) => void;
17
+ };
18
+ type __VLS_Slots = {} & {
19
+ field?: (props: typeof __VLS_66) => any;
20
+ };
21
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
22
+ "update:open": (value: boolean) => any;
23
+ apply: (payload: {
24
+ id: string;
25
+ title?: string;
26
+ config: Record<string, unknown>;
27
+ }) => any;
28
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
29
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
30
+ onApply?: ((payload: {
31
+ id: string;
32
+ title?: string;
33
+ config: Record<string, unknown>;
34
+ }) => any) | undefined;
35
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
36
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
37
+ export default _default;
38
+ type __VLS_WithSlots<T, S> = T & {
39
+ new (): {
40
+ $slots: S;
41
+ };
42
+ };