@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,153 @@
1
+ <script setup>
2
+ import {
3
+ Button,
4
+ Dialog,
5
+ DialogContent,
6
+ DialogDescription,
7
+ DialogHeader,
8
+ DialogTitle,
9
+ Input,
10
+ Label,
11
+ Select,
12
+ SelectContent,
13
+ SelectItem,
14
+ SelectTrigger,
15
+ SelectValue,
16
+ Switch,
17
+ Textarea
18
+ } from "@revenexx/studio";
19
+ import { computed, ref, watch } from "vue";
20
+ import { useWidgetRegistry, widgetDescription, widgetTitle } from "../../lib/widgetRegistry";
21
+ const t = useT();
22
+ const registry = useWidgetRegistry();
23
+ const props = defineProps({
24
+ open: { type: Boolean, required: true },
25
+ widget: { type: [Object, null], required: true },
26
+ hydrate: { type: Function, required: false }
27
+ });
28
+ const emit = defineEmits(["update:open", "apply"]);
29
+ const definition = computed(() => props.widget ? registry.get(props.widget.type) : void 0);
30
+ const heading = computed(() => widgetTitle(definition.value, t));
31
+ const subheading = computed(() => definition.value ? widgetDescription(definition.value, t) : "");
32
+ function fieldLabel(field) {
33
+ return field.label ?? (field.labelKey ? t(field.labelKey) : field.key);
34
+ }
35
+ function fieldHelp(field) {
36
+ return field.help ?? (field.helpKey ? t(field.helpKey) : "");
37
+ }
38
+ function optionLabel(option) {
39
+ return option.label ?? (option.labelKey ? t(option.labelKey) : option.value);
40
+ }
41
+ const title = ref("");
42
+ const draft = ref({});
43
+ watch(() => props.widget, (widget) => {
44
+ title.value = widget?.title ?? "";
45
+ if (!widget) {
46
+ draft.value = {};
47
+ return;
48
+ }
49
+ const config = { ...registry.defaultConfigFor(widget.type), ...widget.config ?? {} };
50
+ draft.value = props.hydrate ? props.hydrate(widget, config) : config;
51
+ }, { immediate: true });
52
+ function apply() {
53
+ if (!props.widget) return;
54
+ emit("apply", {
55
+ id: props.widget.id,
56
+ title: title.value.trim() || void 0,
57
+ config: { ...draft.value }
58
+ });
59
+ emit("update:open", false);
60
+ }
61
+ </script>
62
+
63
+ <template>
64
+ <Dialog :open="open" @update:open="emit('update:open', $event)">
65
+ <DialogContent class="max-w-lg">
66
+ <DialogHeader>
67
+ <DialogTitle>{{ heading }}</DialogTitle>
68
+ <DialogDescription>{{ subheading }}</DialogDescription>
69
+ </DialogHeader>
70
+
71
+ <!-- `-mx-1 px-1 pb-1`: the scroll container clipped the focus ring off the
72
+ left edge and the bottom row. The padding gives the ring room; the
73
+ negative margin keeps the fields aligned with the dialog. -->
74
+ <div class="-mx-1 max-h-[60vh] space-y-4 overflow-y-auto px-1 pb-1">
75
+ <div class="space-y-1.5">
76
+ <Label for="widget-title">{{ t("dashboard.config.title.label") }}</Label>
77
+ <Input id="widget-title" v-model="title" class="bg-white" :placeholder="heading" />
78
+ <p class="text-xs text-muted-foreground">
79
+ {{ t("dashboard.config.title.help") }}
80
+ </p>
81
+ </div>
82
+
83
+ <div v-for="field in definition?.configFields ?? []" :key="field.key" class="space-y-1.5">
84
+ <Label :for="`cfg-${field.key}`">{{ fieldLabel(field) }}</Label>
85
+
86
+ <Textarea
87
+ v-if="field.type === 'textarea'"
88
+ :id="`cfg-${field.key}`"
89
+ v-model="draft[field.key]"
90
+ class="min-h-40 bg-white"
91
+ :placeholder="field.placeholderKey ? t(field.placeholderKey) : void 0"
92
+ rows="9"
93
+ />
94
+
95
+ <Select
96
+ v-else-if="field.type === 'select'"
97
+ v-model="draft[field.key]"
98
+ >
99
+ <SelectTrigger :id="`cfg-${field.key}`" class="bg-white">
100
+ <SelectValue :placeholder="field.placeholderKey ? t(field.placeholderKey) : t('dashboard.config.field.choose')" />
101
+ </SelectTrigger>
102
+ <SelectContent>
103
+ <SelectItem v-for="option in field.options ?? []" :key="option.value" :value="option.value">
104
+ {{ optionLabel(option) }}
105
+ </SelectItem>
106
+ </SelectContent>
107
+ </Select>
108
+
109
+ <Switch
110
+ v-else-if="field.type === 'boolean'"
111
+ v-model="draft[field.key]"
112
+ />
113
+
114
+ <!-- A control this dialog does not own: the surface renders it and
115
+ hands the value back through one `set`, so the draft stays the
116
+ single copy of what the operator is editing. -->
117
+ <slot
118
+ v-else-if="field.type === 'links' || field.type === 'filters' || field.type === 'sort'"
119
+ name="field"
120
+ :field="field"
121
+ :widget="widget"
122
+ :value="draft[field.key]"
123
+ :set="(value) => {
124
+ draft = { ...draft, [field.key]: value };
125
+ }"
126
+ />
127
+
128
+ <Input
129
+ v-else
130
+ :id="`cfg-${field.key}`"
131
+ v-model="draft[field.key]"
132
+ class="bg-white"
133
+ :type="field.type === 'number' ? 'number' : 'text'"
134
+ :placeholder="field.placeholderKey ? t(field.placeholderKey) : void 0"
135
+ />
136
+
137
+ <p v-if="fieldHelp(field)" class="text-xs text-muted-foreground">
138
+ {{ fieldHelp(field) }}
139
+ </p>
140
+ </div>
141
+ </div>
142
+
143
+ <div class="flex justify-end gap-2">
144
+ <Button variant="outline" size="sm" class="h-7 text-sm" @click="emit('update:open', false)">
145
+ {{ t("dashboard.config.action.cancel") }}
146
+ </Button>
147
+ <Button size="sm" class="h-7 text-sm" @click="apply">
148
+ {{ t("dashboard.config.action.apply") }}
149
+ </Button>
150
+ </div>
151
+ </DialogContent>
152
+ </Dialog>
153
+ </template>
@@ -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
+ };
@@ -0,0 +1,27 @@
1
+ import type { WidgetDefinition } from '../../lib/widgetRegistry.js';
2
+ type __VLS_Props = {
3
+ definition?: WidgetDefinition;
4
+ title?: string;
5
+ editing?: boolean;
6
+ configurable?: boolean;
7
+ };
8
+ declare var __VLS_1: {}, __VLS_6: {};
9
+ type __VLS_Slots = {} & {
10
+ default?: (props: typeof __VLS_1) => any;
11
+ } & {
12
+ default?: (props: typeof __VLS_6) => any;
13
+ };
14
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
15
+ remove: () => any;
16
+ configure: () => any;
17
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
18
+ onRemove?: (() => any) | undefined;
19
+ onConfigure?: (() => any) | undefined;
20
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
21
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
22
+ export default _default;
23
+ type __VLS_WithSlots<T, S> = T & {
24
+ new (): {
25
+ $slots: S;
26
+ };
27
+ };
@@ -0,0 +1,80 @@
1
+ <script setup>
2
+ import { CloseCircle, MenuDots, Settings } from "#solar-icons";
3
+ import { computed } from "vue";
4
+ import WidgetIcon from "./DashboardWidgetIcon.vue";
5
+ import { widgetTitle } from "../../lib/widgetRegistry";
6
+ const props = defineProps({
7
+ definition: { type: Object, required: false },
8
+ title: { type: String, required: false },
9
+ editing: { type: Boolean, required: false },
10
+ configurable: { type: Boolean, required: false }
11
+ });
12
+ defineEmits(["configure", "remove"]);
13
+ const t = useT();
14
+ const heading = computed(() => widgetTitle(props.definition, t, props.title));
15
+ const bare = computed(() => props.definition?.bare === true);
16
+ </script>
17
+
18
+ <template>
19
+ <div class="relative h-full min-h-0" :class="editing ? 'select-none' : ''">
20
+ <!-- Bare widgets keep the card surface they already had; the frame stays
21
+ out of the way so the migration is a move, not a redesign. -->
22
+ <template v-if="bare">
23
+ <div class="h-full min-h-0 overflow-hidden">
24
+ <slot />
25
+ </div>
26
+ </template>
27
+
28
+ <template v-else>
29
+ <div class="flex h-full min-h-0 flex-col rounded-lg border border-gray-400/10 outline-1 outline-white/30">
30
+ <div class="flex h-full min-h-0 flex-col overflow-hidden rounded-lg bg-white">
31
+ <div class="flex shrink-0 items-center justify-between gap-2 border-b border-black/[0.06] px-4 py-3">
32
+ <div class="flex min-w-0 items-center gap-2">
33
+ <WidgetIcon :name="definition?.icon" :size="14" class="shrink-0 text-muted-foreground" />
34
+ <h3 class="truncate text-sm font-semibold text-[#0B0522]">
35
+ {{ heading }}
36
+ </h3>
37
+ </div>
38
+ </div>
39
+ <div class="min-h-0 flex-1 overflow-auto">
40
+ <slot />
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </template>
45
+
46
+ <template v-if="editing">
47
+ <!-- The whole card is the drag target, so a widget is grabbed wherever it
48
+ feels natural; the handle is the visual cue that this is possible. -->
49
+ <div class="widget-drag-handle absolute inset-0 z-10 cursor-move rounded-lg ring-2 ring-primary/20 ring-offset-0" />
50
+ <!-- The chips sit in the grid gutter rather than on the card. On the card
51
+ they covered the first line of every widget's own header, which is
52
+ exactly the line that says which widget it is. -->
53
+ <div class="absolute -top-2.5 left-2 z-20 flex items-center gap-1 rounded-md bg-white px-1.5 py-0.5 shadow-sm ring-1 ring-black/[0.08]">
54
+ <MenuDots :size="14" class="text-muted-foreground" />
55
+ <span class="max-w-[12rem] truncate text-[11px] font-medium text-muted-foreground">{{ heading }}</span>
56
+ </div>
57
+ <div class="absolute -top-2.5 right-2 z-20 flex items-center gap-1">
58
+ <button
59
+ v-if="configurable"
60
+ type="button"
61
+ class="rounded-md bg-white p-1 text-muted-foreground shadow-sm ring-1 ring-black/[0.08] hover:text-foreground"
62
+ title="Configure"
63
+ @pointerdown.stop
64
+ @click.stop="$emit('configure')"
65
+ >
66
+ <Settings :size="14" />
67
+ </button>
68
+ <button
69
+ type="button"
70
+ class="rounded-md bg-white p-1 text-muted-foreground shadow-sm ring-1 ring-black/[0.08] hover:text-red-600"
71
+ title="Remove"
72
+ @pointerdown.stop
73
+ @click.stop="$emit('remove')"
74
+ >
75
+ <CloseCircle :size="14" />
76
+ </button>
77
+ </div>
78
+ </template>
79
+ </div>
80
+ </template>
@@ -0,0 +1,27 @@
1
+ import type { WidgetDefinition } from '../../lib/widgetRegistry.js';
2
+ type __VLS_Props = {
3
+ definition?: WidgetDefinition;
4
+ title?: string;
5
+ editing?: boolean;
6
+ configurable?: boolean;
7
+ };
8
+ declare var __VLS_1: {}, __VLS_6: {};
9
+ type __VLS_Slots = {} & {
10
+ default?: (props: typeof __VLS_1) => any;
11
+ } & {
12
+ default?: (props: typeof __VLS_6) => any;
13
+ };
14
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
15
+ remove: () => any;
16
+ configure: () => any;
17
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
18
+ onRemove?: (() => any) | undefined;
19
+ onConfigure?: (() => any) | undefined;
20
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
21
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
22
+ export default _default;
23
+ type __VLS_WithSlots<T, S> = T & {
24
+ new (): {
25
+ $slots: S;
26
+ };
27
+ };
@@ -0,0 +1,6 @@
1
+ import type { DashboardWidget } from '../../types/dashboard.js';
2
+ type __VLS_Props = {
3
+ widget: DashboardWidget;
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,30 @@
1
+ <script setup>
2
+ import { computed } from "vue";
3
+ import { useWidgetRegistry } from "../../lib/widgetRegistry";
4
+ const props = defineProps({
5
+ widget: { type: Object, required: true }
6
+ });
7
+ const t = useT();
8
+ const registry = useWidgetRegistry();
9
+ const definition = computed(() => registry.get(props.widget.type));
10
+ const resolved = computed(() => registry.componentFor(props.widget.type));
11
+ const config = computed(() => ({
12
+ ...registry.defaultConfigFor(props.widget.type),
13
+ ...props.widget.config ?? {}
14
+ }));
15
+ </script>
16
+
17
+ <template>
18
+ <component :is="resolved" v-if="resolved" :config="config" class="h-full" />
19
+ <div
20
+ v-else-if="!definition"
21
+ class="flex h-full flex-col items-center justify-center gap-1 rounded-lg border border-dashed border-black/10 px-4 text-center"
22
+ >
23
+ <p class="text-sm font-medium text-[#0B0522]">
24
+ {{ t("dashboard.widget.unknown.title") }}
25
+ </p>
26
+ <p class="text-xs text-muted-foreground">
27
+ {{ t("dashboard.widget.unknown.description", { type: widget.type }) }}
28
+ </p>
29
+ </div>
30
+ </template>
@@ -0,0 +1,6 @@
1
+ import type { DashboardWidget } from '../../types/dashboard.js';
2
+ type __VLS_Props = {
3
+ widget: DashboardWidget;
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,8 @@
1
+ type __VLS_Props = {
2
+ name?: string;
3
+ size?: number;
4
+ };
5
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
6
+ size: number;
7
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
8
+ export default _default;
@@ -0,0 +1,13 @@
1
+ <script setup>
2
+ import { computed } from "vue";
3
+ import { dashboardIcon } from "../../lib/dashboardIcons";
4
+ const props = defineProps({
5
+ name: { type: String, required: false },
6
+ size: { type: Number, required: false, default: 16 }
7
+ });
8
+ const resolved = computed(() => dashboardIcon(props.name));
9
+ </script>
10
+
11
+ <template>
12
+ <component :is="resolved" :size="size" />
13
+ </template>
@@ -0,0 +1,8 @@
1
+ type __VLS_Props = {
2
+ name?: string;
3
+ size?: number;
4
+ };
5
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
6
+ size: number;
7
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
8
+ export default _default;
@@ -0,0 +1,23 @@
1
+ import type { WidgetDefinition } from '../../lib/widgetRegistry.js';
2
+ type __VLS_Props = {
3
+ open: boolean;
4
+ /** Types already on the dashboard, so the card can say so. */
5
+ placedTypes?: string[];
6
+ /**
7
+ * The tenant's installed apps. A prop rather than a lookup: which apps a
8
+ * tenant has is the SURFACE's question — the home screen asks the commerce
9
+ * metrics store, the Commerce Studio already holds the manifests — and the
10
+ * library only needs the answer.
11
+ */
12
+ installedApps?: string[];
13
+ };
14
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
15
+ "update:open": (value: boolean) => any;
16
+ add: (definition: WidgetDefinition) => any;
17
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
18
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
19
+ onAdd?: ((definition: WidgetDefinition) => any) | undefined;
20
+ }>, {
21
+ installedApps: string[];
22
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
23
+ export default _default;
@@ -0,0 +1,140 @@
1
+ <script setup>
2
+ import { Button, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, Input } from "@revenexx/studio";
3
+ import { AddSquare, Magnifier } from "#solar-icons";
4
+ import { computed, ref, watch } from "vue";
5
+ import WidgetIcon from "./DashboardWidgetIcon.vue";
6
+ import WidgetPreview from "./DashboardWidgetPreview.vue";
7
+ import { missingAppsFor, useWidgetRegistry, widgetDescription, widgetTitle } from "../../lib/widgetRegistry";
8
+ const t = useT();
9
+ const registry = useWidgetRegistry();
10
+ const props = defineProps({
11
+ open: { type: Boolean, required: true },
12
+ placedTypes: { type: Array, required: false },
13
+ installedApps: { type: Array, required: false, default: () => [] }
14
+ });
15
+ const emit = defineEmits(["update:open", "add"]);
16
+ const query = ref("");
17
+ const installed = computed(() => new Set(props.installedApps));
18
+ watch(() => props.open, (open) => {
19
+ if (open) query.value = "";
20
+ });
21
+ const KNOWN_APPS = ["orders", "customers", "products", "carts", "channels", "payments", "markets"];
22
+ const appLabel = (name) => KNOWN_APPS.includes(name) ? t(`dashboard.app.${name}`) : name;
23
+ const groups = computed(() => {
24
+ const term = query.value.trim().toLowerCase();
25
+ return registry.byCategory().map((group) => ({
26
+ ...group,
27
+ // Search runs over the translated text, so a German operator finds
28
+ // "Umsatz" and an English one finds "revenue" — matching the key would
29
+ // find neither.
30
+ widgets: group.widgets.filter((widget) => !term || title(widget).toLowerCase().includes(term) || description(widget).toLowerCase().includes(term) || t(group.labelKey).toLowerCase().includes(term))
31
+ })).filter((group) => group.widgets.length > 0);
32
+ });
33
+ const total = computed(() => groups.value.reduce((sum, g) => sum + g.widgets.length, 0));
34
+ function title(widget) {
35
+ return widgetTitle(widget, t);
36
+ }
37
+ function description(widget) {
38
+ return widgetDescription(widget, t);
39
+ }
40
+ function missing(widget) {
41
+ return missingAppsFor(widget, installed.value);
42
+ }
43
+ function placed(widget) {
44
+ return !!props.placedTypes?.includes(widget.type);
45
+ }
46
+ function unavailable(widget) {
47
+ return !!widget.comingSoon || missing(widget).length > 0;
48
+ }
49
+ </script>
50
+
51
+ <template>
52
+ <Dialog :open="open" @update:open="emit('update:open', $event)">
53
+ <DialogContent class="!max-w-[min(96vw,72rem)] w-[min(96vw,72rem)]">
54
+ <DialogHeader>
55
+ <DialogTitle>{{ t("dashboard.library.title") }}</DialogTitle>
56
+ <DialogDescription>{{ t("dashboard.library.description") }}</DialogDescription>
57
+ </DialogHeader>
58
+
59
+ <div class="relative">
60
+ <Magnifier :size="14" class="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" />
61
+ <Input v-model="query" :placeholder="t('dashboard.library.search_placeholder')" class="bg-white pl-8" />
62
+ </div>
63
+
64
+ <div class="max-h-[58vh] -mx-1 space-y-6 overflow-y-auto px-1 pb-1">
65
+ <p v-if="!total" class="py-10 text-center text-sm text-muted-foreground">
66
+ {{ t("dashboard.library.no_matches", { query }) }}
67
+ </p>
68
+
69
+ <section v-for="group in groups" :key="group.category">
70
+ <h4 class="mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
71
+ {{ t(group.labelKey) }}
72
+ </h4>
73
+ <div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
74
+ <div
75
+ v-for="widget in group.widgets"
76
+ :key="widget.type"
77
+ class="group flex flex-col overflow-hidden rounded-lg border border-black/[0.06] bg-white transition"
78
+ :class="unavailable(widget) ? 'opacity-80' : 'hover:border-black/15 hover:shadow-sm'"
79
+ >
80
+ <div class="relative p-2">
81
+ <WidgetPreview :shape="widget.preview" :dimmed="unavailable(widget)" />
82
+ <span
83
+ v-if="widget.comingSoon"
84
+ class="absolute right-3 top-3 rounded bg-primary/10 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-primary"
85
+ >{{ t("dashboard.library.coming_soon") }}</span>
86
+ <span
87
+ v-else-if="placed(widget)"
88
+ class="absolute right-3 top-3 rounded bg-white/90 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground ring-1 ring-black/[0.06]"
89
+ >{{ t("dashboard.library.placed") }}</span>
90
+ </div>
91
+
92
+ <div class="flex flex-1 flex-col px-3 pb-3">
93
+ <div class="flex items-start gap-2">
94
+ <span class="mt-0.5 flex size-6 shrink-0 items-center justify-center rounded bg-black/[0.04] text-muted-foreground">
95
+ <WidgetIcon :name="widget.icon" :size="13" />
96
+ </span>
97
+ <div class="min-w-0 flex-1">
98
+ <p class="truncate text-sm font-medium text-[#0B0522]">
99
+ {{ title(widget) }}
100
+ </p>
101
+ <p class="mt-0.5 line-clamp-2 text-xs leading-snug text-muted-foreground">
102
+ {{ description(widget) }}
103
+ </p>
104
+ </div>
105
+ </div>
106
+
107
+ <div class="mt-3 flex items-center justify-between gap-2">
108
+ <span v-if="widget.comingSoon" class="truncate text-[11px] text-muted-foreground">
109
+ {{ t("dashboard.library.coming_soon_hint") }}
110
+ </span>
111
+ <span v-else-if="missing(widget).length" class="truncate text-[11px] text-amber-700">
112
+ {{ t("dashboard.library.needs_apps", { apps: missing(widget).map(appLabel).join(" + ") }) }}
113
+ </span>
114
+ <span v-else class="text-[11px] text-muted-foreground">
115
+ {{ widget.defaultSize.w }}&times;{{ widget.defaultSize.h }}
116
+ </span>
117
+ <Button
118
+ size="sm"
119
+ variant="outline"
120
+ class="h-7 shrink-0 text-sm"
121
+ :disabled="unavailable(widget)"
122
+ @click="emit('add', widget)"
123
+ >
124
+ <AddSquare :size="13" class="mr-1" /> {{ t("dashboard.library.action.add") }}
125
+ </Button>
126
+ </div>
127
+ </div>
128
+ </div>
129
+ </div>
130
+ </section>
131
+ </div>
132
+
133
+ <div class="flex justify-end">
134
+ <Button variant="outline" size="sm" class="h-7 text-sm" @click="emit('update:open', false)">
135
+ {{ t("dashboard.library.action.done") }}
136
+ </Button>
137
+ </div>
138
+ </DialogContent>
139
+ </Dialog>
140
+ </template>
@@ -0,0 +1,23 @@
1
+ import type { WidgetDefinition } from '../../lib/widgetRegistry.js';
2
+ type __VLS_Props = {
3
+ open: boolean;
4
+ /** Types already on the dashboard, so the card can say so. */
5
+ placedTypes?: string[];
6
+ /**
7
+ * The tenant's installed apps. A prop rather than a lookup: which apps a
8
+ * tenant has is the SURFACE's question — the home screen asks the commerce
9
+ * metrics store, the Commerce Studio already holds the manifests — and the
10
+ * library only needs the answer.
11
+ */
12
+ installedApps?: string[];
13
+ };
14
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
15
+ "update:open": (value: boolean) => any;
16
+ add: (definition: WidgetDefinition) => any;
17
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
18
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
19
+ onAdd?: ((definition: WidgetDefinition) => any) | undefined;
20
+ }>, {
21
+ installedApps: string[];
22
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
23
+ export default _default;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The picture on a widget's library card (PE-480).
3
+ *
4
+ * A schematic, not a screenshot and not the live widget. The Integration
5
+ * Studio's template gallery draws its preview from the workflow definition for
6
+ * the same reason: a maintained screenshot goes stale the first time the widget
7
+ * changes and nobody notices. Rendering the real widget instead would be
8
+ * accurate but would fire every widget's queries the moment the library opens.
9
+ *
10
+ * So each widget declares a `preview` shape, and this draws it — pure layout,
11
+ * no data, no requests. It says what the widget *looks* like, which is what a
12
+ * person picking from a list actually needs.
13
+ */
14
+ import type { WidgetPreviewShape } from '../../lib/widgetRegistry.js';
15
+ type __VLS_Props = {
16
+ shape: WidgetPreviewShape;
17
+ dimmed?: boolean;
18
+ };
19
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
20
+ dimmed: boolean;
21
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
22
+ export default _default;