@revenexx/studio-shared 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/dist/module.d.mts +5 -3
  2. package/dist/module.json +2 -2
  3. package/dist/module.mjs +7 -3
  4. package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue +1 -1
  5. package/dist/runtime/components/Dashboard/DashboardGrid.vue +45 -1
  6. package/dist/runtime/components/Dashboard/DashboardNameDialog.vue +1 -1
  7. package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue +1 -1
  8. package/dist/runtime/components/Dashboard/DashboardShareDialog.vue +1 -1
  9. package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue +1 -1
  10. package/dist/runtime/components/Dashboard/DashboardWidgetFrame.d.vue.ts +2 -2
  11. package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue +14 -3
  12. package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue.d.ts +2 -2
  13. package/dist/runtime/components/DataTable/DataTable.d.vue.ts +73 -34
  14. package/dist/runtime/components/DataTable/DataTable.vue +352 -52
  15. package/dist/runtime/components/DataTable/DataTable.vue.d.ts +73 -34
  16. package/dist/runtime/components/DataTable/FacetPanel.vue +57 -8
  17. package/dist/runtime/components/DataTable/FilterMenu.vue +5 -5
  18. package/dist/runtime/components/DataTable/SavedViewsMenu.vue +61 -12
  19. package/dist/runtime/components/DataTable/SortMenu.vue +4 -1
  20. package/dist/runtime/components/DataTable/ToolbarLabelTrigger.d.vue.ts +23 -0
  21. package/dist/runtime/components/DataTable/ToolbarLabelTrigger.vue +29 -0
  22. package/dist/runtime/components/DataTable/ToolbarLabelTrigger.vue.d.ts +23 -0
  23. package/dist/runtime/components/DataTable/types.d.ts +17 -0
  24. package/dist/runtime/components/Form/FieldGrid.vue +1 -1
  25. package/dist/runtime/components/Form/SaveBar.d.vue.ts +1 -1
  26. package/dist/runtime/components/Form/SaveBar.vue.d.ts +1 -1
  27. package/dist/runtime/components/Media/MediaFolderTree.d.vue.ts +4 -4
  28. package/dist/runtime/components/Media/MediaFolderTree.vue +1 -2
  29. package/dist/runtime/components/Media/MediaFolderTree.vue.d.ts +4 -4
  30. package/dist/runtime/components/Media/MediaPickerDialog.d.vue.ts +11 -0
  31. package/dist/runtime/components/Media/MediaPickerDialog.vue +206 -54
  32. package/dist/runtime/components/Media/MediaPickerDialog.vue.d.ts +11 -0
  33. package/dist/runtime/components/Media/MediaUploader.vue +1 -1
  34. package/dist/runtime/components/Shared/AppIcon.d.vue.ts +1 -1
  35. package/dist/runtime/components/Shared/AppIcon.vue.d.ts +1 -1
  36. package/dist/runtime/components/Shared/CardSizeToggle.d.vue.ts +11 -0
  37. package/dist/runtime/components/Shared/CardSizeToggle.vue +19 -0
  38. package/dist/runtime/components/Shared/CardSizeToggle.vue.d.ts +11 -0
  39. package/dist/runtime/components/Shared/DetailSheet.d.vue.ts +1 -1
  40. package/dist/runtime/components/Shared/DetailSheet.vue +9 -5
  41. package/dist/runtime/components/Shared/DetailSheet.vue.d.ts +1 -1
  42. package/dist/runtime/components/Shared/EmptyNote.d.vue.ts +19 -0
  43. package/dist/runtime/components/Shared/EmptyNote.vue +25 -0
  44. package/dist/runtime/components/Shared/EmptyNote.vue.d.ts +19 -0
  45. package/dist/runtime/components/Shared/KpiTile.d.vue.ts +6 -0
  46. package/dist/runtime/components/Shared/KpiTile.vue +10 -9
  47. package/dist/runtime/components/Shared/KpiTile.vue.d.ts +6 -0
  48. package/dist/runtime/components/Shared/PageHeader.vue +15 -4
  49. package/dist/runtime/components/Shared/RecordLayout.vue +1 -2
  50. package/dist/runtime/components/Shared/RecordRailCard.vue +3 -3
  51. package/dist/runtime/components/Shared/SelectionBar.d.vue.ts +22 -0
  52. package/dist/runtime/components/Shared/SelectionBar.vue +40 -0
  53. package/dist/runtime/components/Shared/SelectionBar.vue.d.ts +22 -0
  54. package/dist/runtime/components/Shared/SidebarNav.d.vue.ts +36 -2
  55. package/dist/runtime/components/Shared/SidebarNav.vue +71 -25
  56. package/dist/runtime/components/Shared/SidebarNav.vue.d.ts +36 -2
  57. package/dist/runtime/components/Shared/SidebarNavAreas.d.vue.ts +7 -0
  58. package/dist/runtime/components/Shared/SidebarNavAreas.vue +291 -0
  59. package/dist/runtime/components/Shared/SidebarNavAreas.vue.d.ts +7 -0
  60. package/dist/runtime/components/Shared/SidebarNavLink.d.vue.ts +1 -9
  61. package/dist/runtime/components/Shared/SidebarNavLink.vue +31 -5
  62. package/dist/runtime/components/Shared/SidebarNavLink.vue.d.ts +1 -9
  63. package/dist/runtime/components/Shared/SortHeader.vue +6 -2
  64. package/dist/runtime/components/Shared/StateCard.vue +1 -1
  65. package/dist/runtime/components/Shared/TabBar.vue +6 -7
  66. package/dist/runtime/components/Shared/Wizard.d.vue.ts +11 -1
  67. package/dist/runtime/components/Shared/Wizard.vue +9 -5
  68. package/dist/runtime/components/Shared/Wizard.vue.d.ts +11 -1
  69. package/dist/runtime/composables/useMediaLibrary.d.ts +12 -0
  70. package/dist/runtime/composables/useSavedViews.d.ts +11 -1
  71. package/dist/runtime/composables/useSavedViews.js +4 -4
  72. package/dist/runtime/composables/useWidgetFigures.d.ts +12 -0
  73. package/dist/runtime/composables/useWidgetFigures.js +24 -0
  74. package/dist/runtime/composables/useWidgetHeader.d.ts +15 -0
  75. package/dist/runtime/composables/useWidgetHeader.js +15 -0
  76. package/dist/runtime/lib/auditEnvelope.d.ts +43 -0
  77. package/dist/runtime/lib/auditEnvelope.js +55 -0
  78. package/dist/runtime/lib/cardSize.d.ts +13 -0
  79. package/dist/runtime/lib/cardSize.js +20 -0
  80. package/dist/runtime/lib/dashboardIcons.js +2 -0
  81. package/dist/runtime/lib/dataTableFilters.d.ts +22 -0
  82. package/dist/runtime/lib/dataTableFilters.js +5 -0
  83. package/dist/runtime/lib/facets.d.ts +13 -0
  84. package/dist/runtime/lib/formLayout.d.ts +74 -4
  85. package/dist/runtime/lib/formLayout.js +53 -6
  86. package/dist/runtime/lib/gridTracks.d.ts +14 -1
  87. package/dist/runtime/lib/gridTracks.js +13 -2
  88. package/dist/runtime/lib/isoLists.d.ts +37 -2
  89. package/dist/runtime/lib/isoLists.js +54 -1
  90. package/dist/runtime/lib/notice.d.ts +14 -0
  91. package/dist/runtime/lib/notice.js +19 -0
  92. package/dist/runtime/lib/pageSize.d.ts +15 -0
  93. package/dist/runtime/lib/pageSize.js +26 -0
  94. package/dist/runtime/lib/sidebarAreas.d.ts +46 -0
  95. package/dist/runtime/lib/sidebarAreas.js +25 -0
  96. package/dist/runtime/lib/sidebarNav.d.ts +21 -2
  97. package/dist/runtime/lib/sidebarNav.js +23 -4
  98. package/dist/runtime/lib/solarIconMap.d.ts +2 -1
  99. package/dist/runtime/lib/solarIconMap.js +50 -4
  100. package/dist/runtime/lib/swipeToClose.d.ts +25 -0
  101. package/dist/runtime/lib/swipeToClose.js +118 -0
  102. package/dist/runtime/lib/tableExport.d.ts +31 -0
  103. package/dist/runtime/lib/tableExport.js +143 -0
  104. package/dist/runtime/lib/talkback.d.ts +5 -0
  105. package/dist/runtime/lib/talkback.js +8 -0
  106. package/dist/runtime/lib/talkbackTokenBatch.d.ts +35 -0
  107. package/dist/runtime/lib/talkbackTokenBatch.js +101 -0
  108. package/dist/runtime/types/nav.d.ts +9 -0
  109. package/dist/types.d.mts +4 -0
  110. package/package.json +4 -3
@@ -0,0 +1,291 @@
1
+ <script setup>
2
+ import { AltArrowDown as ChevronDownIcon, AltArrowRight as ChevronIcon } from "#solar-icons";
3
+ import { SIDEBAR_BEST_MATCH, sidebarItemIsCurrent, sidebarItemKey, sidebarItemTarget } from "../../lib/sidebarNav";
4
+ import {
5
+ AREA_AIM_DELAY,
6
+ AREA_CLOSE_DELAY,
7
+ AREA_OPEN_DELAY,
8
+ inSafeTriangle,
9
+ sidebarAreaBlocks
10
+ } from "../../lib/sidebarAreas";
11
+ import SidebarNavLink from "./SidebarNavLink.vue";
12
+ const props = defineProps({
13
+ sections: { type: Array, required: true }
14
+ });
15
+ const t = useT();
16
+ const route = useRoute();
17
+ const best = inject(SIDEBAR_BEST_MATCH, null);
18
+ const blocks = computed(() => sidebarAreaBlocks(props.sections, route.fullPath, best?.value));
19
+ const root = ref(null);
20
+ const panel = ref(null);
21
+ const openKey = ref(null);
22
+ const pinned = ref(false);
23
+ const rect = ref({ left: 0, top: 0, height: 0, width: 0 });
24
+ const titleHeight = ref(0);
25
+ const openSection = computed(
26
+ () => openKey.value ? props.sections.find((s) => s.key === openKey.value) ?? null : null
27
+ );
28
+ let openTimer;
29
+ let aimTimer;
30
+ let closeTimer;
31
+ let apex = null;
32
+ let last = null;
33
+ function headRows(section) {
34
+ return section.label ? section.items.slice(0, 1) : section.items;
35
+ }
36
+ function isOneRow(block) {
37
+ return block.kind === "area" || !listsItems(block.section);
38
+ }
39
+ function blockIsCurrent(block) {
40
+ return block.kind === "section" && headRows(block.section).some((item) => headIsCurrent(block.section, item));
41
+ }
42
+ function listsItems(section) {
43
+ return !!section.label && (section.items.length > 1 || section.items[0]?.label !== section.label);
44
+ }
45
+ function headIsCurrent(section, item) {
46
+ if (listsItems(section)) return false;
47
+ return sidebarItemIsCurrent(item, route.fullPath, best?.value);
48
+ }
49
+ function clearTimers() {
50
+ clearTimeout(openTimer);
51
+ clearTimeout(aimTimer);
52
+ clearTimeout(closeTimer);
53
+ }
54
+ const shelf = ref(null);
55
+ function measure() {
56
+ const aside = root.value?.closest("aside");
57
+ if (!aside) return;
58
+ shelf.value = aside.closest("[data-studio-shelf]");
59
+ const head = aside.firstElementChild;
60
+ titleHeight.value = head?.classList.contains("border-b") ? head.getBoundingClientRect().height : 0;
61
+ const r = aside.getBoundingClientRect();
62
+ const frame = shelf.value?.getBoundingClientRect() ?? r;
63
+ rect.value = { left: r.right + 1, top: frame.top, height: frame.height, width: r.width + 1 };
64
+ }
65
+ function open(key) {
66
+ measure();
67
+ openKey.value = key;
68
+ }
69
+ function close() {
70
+ clearTimers();
71
+ openKey.value = null;
72
+ pinned.value = false;
73
+ apex = null;
74
+ }
75
+ function panelEdge() {
76
+ if (!panel.value) return null;
77
+ const r = panel.value.getBoundingClientRect();
78
+ return [{ x: r.left, y: r.top }, { x: r.left, y: r.bottom }];
79
+ }
80
+ function aimingAtPanel(p) {
81
+ const edge = panelEdge();
82
+ return !!(apex && edge && inSafeTriangle(p, apex, edge[0], edge[1]));
83
+ }
84
+ function onMove(e) {
85
+ if (e.pointerType !== "mouse") return;
86
+ last = { x: e.clientX, y: e.clientY };
87
+ const row = e.target?.closest("[data-area]");
88
+ if (row && row.dataset.area === openKey.value || !openKey.value || !aimingAtPanel(last)) apex = last;
89
+ }
90
+ function onAreaEnter(key, e) {
91
+ if (e.pointerType !== "mouse") return;
92
+ clearTimers();
93
+ if (openKey.value === key) return;
94
+ if (openKey.value) {
95
+ if (aimingAtPanel({ x: e.clientX, y: e.clientY })) {
96
+ aimTimer = setTimeout(() => {
97
+ const under = last && document.elementFromPoint(last.x, last.y)?.closest("[data-area]");
98
+ if (under?.dataset.area === key) open(key);
99
+ }, AREA_AIM_DELAY);
100
+ return;
101
+ }
102
+ open(key);
103
+ return;
104
+ }
105
+ openTimer = setTimeout(() => open(key), AREA_OPEN_DELAY);
106
+ }
107
+ function onAreaLeave() {
108
+ clearTimeout(openTimer);
109
+ }
110
+ function onRowEnter(e) {
111
+ if (e.pointerType !== "mouse" || !openKey.value || pinned.value) return;
112
+ if (aimingAtPanel({ x: e.clientX, y: e.clientY })) return;
113
+ clearTimers();
114
+ closeTimer = setTimeout(close, 60);
115
+ }
116
+ function onLeave(e) {
117
+ if (e.pointerType !== "mouse" || pinned.value) return;
118
+ clearTimeout(openTimer);
119
+ if (!openKey.value) return;
120
+ const to = e.relatedTarget;
121
+ if (to && (panel.value?.contains(to) || root.value?.contains(to))) return;
122
+ closeTimer = setTimeout(close, AREA_CLOSE_DELAY);
123
+ }
124
+ function onEnterKeep() {
125
+ clearTimeout(closeTimer);
126
+ clearTimeout(aimTimer);
127
+ }
128
+ function onAreaClick(section) {
129
+ const target = section.items[0] && sidebarItemTarget(section.items[0]);
130
+ close();
131
+ if (target) void navigateTo(target);
132
+ }
133
+ function focusPanel() {
134
+ nextTick(() => panel.value?.querySelector("a")?.focus());
135
+ }
136
+ function onAreaKey(key, e) {
137
+ if (e.key === "ArrowRight") {
138
+ e.preventDefault();
139
+ pinned.value = true;
140
+ open(key);
141
+ focusPanel();
142
+ }
143
+ }
144
+ function onPanelKey(e) {
145
+ const links = [...panel.value?.querySelectorAll("a") ?? []];
146
+ const i = links.indexOf(document.activeElement);
147
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
148
+ e.preventDefault();
149
+ links[(i + (e.key === "ArrowDown" ? 1 : -1) + links.length) % links.length]?.focus();
150
+ } else if (e.key === "Escape" || e.key === "ArrowLeft") {
151
+ e.preventDefault();
152
+ const key = openKey.value;
153
+ close();
154
+ root.value?.querySelector(`[data-area="${key}"]`)?.focus();
155
+ }
156
+ }
157
+ function onDocDown(e) {
158
+ if (!openKey.value) return;
159
+ const t2 = e.target;
160
+ if (panel.value?.contains(t2) || root.value?.contains(t2)) return;
161
+ close();
162
+ }
163
+ function onDocKey(e) {
164
+ if (e.key === "Escape" && openKey.value) close();
165
+ }
166
+ watch(() => route.fullPath, close);
167
+ onMounted(() => {
168
+ document.addEventListener("pointerdown", onDocDown);
169
+ document.addEventListener("keydown", onDocKey);
170
+ window.addEventListener("resize", measure);
171
+ });
172
+ onBeforeUnmount(() => {
173
+ clearTimers();
174
+ document.removeEventListener("pointerdown", onDocDown);
175
+ document.removeEventListener("keydown", onDocKey);
176
+ window.removeEventListener("resize", measure);
177
+ });
178
+ </script>
179
+
180
+ <template>
181
+ <div ref="root" @pointermove="onMove" @pointerleave="onLeave" @pointerenter="onEnterKeep">
182
+ <!--
183
+ Every labelled section is one row with its icon in a tile (the node
184
+ palette of the Integration Studio, kept neutral). Hairlines divide the
185
+ sections. The route's section lists its entries under its row, flush;
186
+ every other row opens the panel on hover and its first page on click.
187
+ The open section sits on a faint violet wash between its hairlines, so
188
+ it stands out in the list; a one-row block takes the same wash on hover
189
+ (Michael 17.10.).
190
+ -->
191
+ <div
192
+ v-for="(block, i) in blocks"
193
+ :key="block.section.key"
194
+ class="flex flex-col gap-0.5 px-2 py-1"
195
+ :class="[
196
+ i > 0 ? 'border-t border-black/[0.06]' : '',
197
+ block.kind === 'section' && listsItems(block.section) ? 'rx-area-open bg-[#faf8ff]' : '',
198
+ openKey === block.section.key || blockIsCurrent(block) ? 'bg-[#faf8ff]' : '',
199
+ isOneRow(block) ? 'transition-colors hover:bg-[#faf8ff]' : ''
200
+ ]"
201
+ @pointerenter.capture="block.kind === 'section' ? onRowEnter($event) : void 0"
202
+ >
203
+ <template v-if="block.kind === 'section'">
204
+ <NuxtLink
205
+ v-for="item in headRows(block.section)"
206
+ :key="`h-${sidebarItemKey(item)}`"
207
+ :to="sidebarItemTarget(item)"
208
+ :data-nav-current="headIsCurrent(block.section, item) && !listsItems(block.section) || void 0"
209
+ class="group flex h-9 items-center gap-2.5 rounded-md px-2 text-sm font-medium text-[#0B0522] transition-colors"
210
+ >
211
+ <span
212
+ class="flex size-6 shrink-0 items-center justify-center rounded-md transition-colors"
213
+ :class="headIsCurrent(block.section, item) || listsItems(block.section) ? 'bg-primary/10 text-primary' : 'bg-black/[0.04] text-foreground/60 group-hover:bg-primary/10 group-hover:text-primary'"
214
+ >
215
+ <component :is="block.section.icon ?? item.icon" :size="14" />
216
+ </span>
217
+ <span class="min-w-0 flex-1 truncate">{{ block.section.label ?? item.label }}</span>
218
+ <!-- The open section points down at its entries; the others point right at their panel. -->
219
+ <ChevronDownIcon v-if="listsItems(block.section)" :size="12" weight="Bold" class="shrink-0 text-muted-foreground" />
220
+ </NuxtLink>
221
+ <template v-if="listsItems(block.section)">
222
+ <template v-for="item in block.section.items" :key="sidebarItemKey(item)">
223
+ <div v-if="item.heading" class="rx-area-heading">{{ item.heading }}</div>
224
+ <SidebarNavLink class="rx-area-item" :item="item" />
225
+ </template>
226
+ </template>
227
+ </template>
228
+ <button
229
+ v-else
230
+ type="button"
231
+ :data-area="block.section.key"
232
+ class="group flex h-9 w-full cursor-pointer items-center gap-2.5 rounded-md px-2 text-left text-sm font-medium text-[#0B0522]"
233
+ :aria-expanded="openKey === block.section.key"
234
+ aria-haspopup="true"
235
+ :aria-label="t('shared.nav.area.open', { section: block.section.label ?? '', count: block.section.items.length })"
236
+ @pointerenter="onAreaEnter(block.section.key, $event)"
237
+ @pointerleave="onAreaLeave"
238
+ @click="onAreaClick(block.section)"
239
+ @keydown="onAreaKey(block.section.key, $event)"
240
+ >
241
+ <span
242
+ class="flex size-6 shrink-0 items-center justify-center rounded-md transition-colors group-hover:bg-primary/10 group-hover:text-primary"
243
+ :class="openKey === block.section.key ? 'bg-primary/10 text-primary' : 'bg-black/[0.04] text-foreground/60'"
244
+ >
245
+ <component :is="block.section.icon" v-if="block.section.icon" :size="14" />
246
+ </span>
247
+ <span class="min-w-0 flex-1 truncate">{{ block.section.label }}</span>
248
+ <ChevronIcon :size="12" weight="Bold" class="shrink-0 group-hover:text-primary" :class="openKey === block.section.key ? 'text-primary' : 'text-muted-foreground'" />
249
+ </button>
250
+ </div>
251
+
252
+ <Teleport to="body">
253
+ <!--
254
+ The panel continues the shelf: its frame is the shelf's, the sidebar
255
+ pane's hairline runs down its left edge, and its title rule sits on the
256
+ sidebar's own. A soft shadow to the right lifts it off the page.
257
+ The shelf's frame is gray-400/10 over the lavender page, so it reads
258
+ as #eae9f3; the panel lies over the shelf's edge and would double that
259
+ tint, hence the solid colour.
260
+ -->
261
+ <div
262
+ v-if="openSection"
263
+ ref="panel"
264
+ role="menu"
265
+ :aria-label="openSection.label"
266
+ class="rx-area-panel fixed z-40 flex flex-col overflow-hidden rounded-r-lg border border-l-0 border-[#eae9f3] bg-white outline-1 outline-white/30 shadow-[16px_0_32px_-18px_rgb(42_29_97/0.28)]"
267
+ :style="{ left: `${rect.left}px`, top: `${rect.top}px`, height: `${rect.height}px`, width: `${rect.width}px` }"
268
+ @pointerenter="onEnterKeep"
269
+ @pointerleave="onLeave"
270
+ @keydown="onPanelKey"
271
+ >
272
+ <div class="flex shrink-0 items-center gap-2 border-b border-black/[0.06] px-4 pb-3 pt-4" :style="titleHeight ? { height: `${titleHeight}px` } : void 0">
273
+ <span v-if="openSection.icon" class="flex size-6 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary">
274
+ <component :is="openSection.icon" :size="14" />
275
+ </span>
276
+ <h2 class="min-w-0 flex-1 truncate text-lg font-medium text-[#0B0522]">{{ openSection.label }}</h2>
277
+ </div>
278
+ <div class="flex flex-1 flex-col gap-0.5 overflow-y-auto px-2 py-3">
279
+ <template v-for="item in openSection.items" :key="sidebarItemKey(item)">
280
+ <div v-if="item.heading" class="rx-area-heading">{{ item.heading }}</div>
281
+ <SidebarNavLink class="rx-area-item" :item="item" />
282
+ </template>
283
+ </div>
284
+ </div>
285
+ </Teleport>
286
+ </div>
287
+ </template>
288
+
289
+ <style scoped>
290
+ .rx-area-item :deep(.rx-nav-icon){margin-inline:5px}.rx-area-heading{color:var(--muted-foreground,#6b6880);font-size:11px;font-weight:500;letter-spacing:.04em;padding:10px 8px 2px 42px;text-transform:uppercase}.rx-area-panel{animation:rx-area-in 90ms ease-out}@keyframes rx-area-in{0%{opacity:.4;transform:translateX(-4px)}to{opacity:1;transform:none}}@media (prefers-reduced-motion:reduce){.rx-area-panel{animation:none}}
291
+ </style>
@@ -0,0 +1,7 @@
1
+ import type { SidebarNavSection } from '../../types/nav.js';
2
+ type __VLS_Props = {
3
+ sections: SidebarNavSection[];
4
+ };
5
+ declare const __VLS_export: 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
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -1,15 +1,7 @@
1
- /**
2
- * One row of a `SidebarNav` section — always exactly one link, never a group.
3
- *
4
- * An item's `children` are the **tabs of the page this row opens**, not rows
5
- * of their own (see `lib/sidebarNav.ts`), so there is nothing to expand here:
6
- * the row links to the item's route, or — for a pure container that nominates
7
- * no landing page — to its first tab, and stays highlighted while the route is
8
- * on any of them.
9
- */
10
1
  import type { SidebarNavItem } from '../../types/nav.js';
11
2
  type __VLS_Props = {
12
3
  item: SidebarNavItem;
4
+ collapsed?: boolean;
13
5
  };
14
6
  declare const __VLS_export: 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>;
15
7
  declare const _default: typeof __VLS_export;
@@ -1,12 +1,20 @@
1
1
  <script setup>
2
- import { sidebarItemIsCurrent, sidebarItemTarget } from "../../lib/sidebarNav";
2
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@revenexx/studio";
3
+ import { SIDEBAR_BEST_MATCH, sidebarItemIsCurrent, sidebarItemTarget } from "../../lib/sidebarNav";
3
4
  const props = defineProps({
4
- item: { type: Object, required: true }
5
+ item: { type: Object, required: true },
6
+ collapsed: { type: Boolean, required: false }
5
7
  });
6
8
  const t = useT();
7
9
  const route = useRoute();
8
10
  const target = computed(() => sidebarItemTarget(props.item));
9
- const current = computed(() => sidebarItemIsCurrent(props.item, route.path));
11
+ const best = inject(SIDEBAR_BEST_MATCH, null);
12
+ const current = computed(() => sidebarItemIsCurrent(props.item, route.fullPath, best?.value));
13
+ const iconRowClass = computed(() => [
14
+ "group relative flex size-9 items-center justify-center rounded-md transition-colors hover:text-primary",
15
+ current.value ? "bg-primary/10 text-[#0B0522]" : "text-foreground/70 hover:bg-primary/5",
16
+ props.item.preview ? "opacity-70" : ""
17
+ ]);
10
18
  const rowClass = computed(() => [
11
19
  "group flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-sm transition-colors hover:text-primary",
12
20
  current.value ? "bg-primary/10 text-[#0B0522] font-medium" : "text-foreground/80 hover:bg-primary/5",
@@ -15,8 +23,26 @@ const rowClass = computed(() => [
15
23
  </script>
16
24
 
17
25
  <template>
18
- <NuxtLink v-if="target" :to="target" :class="rowClass">
19
- <component :is="item.icon" v-if="item.icon" :size="14" :stroke-width="2" class="shrink-0 text-foreground/60 group-hover:text-primary" />
26
+ <TooltipProvider v-if="target && collapsed" :delay-duration="200">
27
+ <Tooltip>
28
+ <TooltipTrigger as-child>
29
+ <NuxtLink :to="target" :class="iconRowClass" :data-nav-current="current || void 0" :aria-label="item.label">
30
+ <component :is="item.icon" v-if="item.icon" :size="18" :stroke-width="2" class="rx-nav-icon shrink-0" :class="current ? 'text-primary' : 'text-foreground/60 group-hover:text-primary'" />
31
+ <span v-else class="text-sm font-medium">{{ item.label.slice(0, 1) }}</span>
32
+ <span
33
+ v-if="item.badge !== null && item.badge !== void 0"
34
+ class="absolute right-1 top-1 size-1.5 rounded-full bg-primary"
35
+ aria-hidden="true"
36
+ />
37
+ </NuxtLink>
38
+ </TooltipTrigger>
39
+ <TooltipContent side="right" class="text-sm">
40
+ {{ item.label }}<template v-if="item.badge !== null && item.badge !== void 0 && item.badge !== 'dot'"> · {{ item.badge }}</template>
41
+ </TooltipContent>
42
+ </Tooltip>
43
+ </TooltipProvider>
44
+ <NuxtLink v-else-if="target" :to="target" :class="rowClass" :data-nav-current="current || void 0">
45
+ <component :is="item.icon" v-if="item.icon" :size="14" :stroke-width="2" class="rx-nav-icon shrink-0 text-foreground/60 group-hover:text-primary" />
20
46
  <span class="min-w-0 flex-1 truncate flex items-center gap-1.5">
21
47
  {{ item.label }}
22
48
  <span
@@ -1,15 +1,7 @@
1
- /**
2
- * One row of a `SidebarNav` section — always exactly one link, never a group.
3
- *
4
- * An item's `children` are the **tabs of the page this row opens**, not rows
5
- * of their own (see `lib/sidebarNav.ts`), so there is nothing to expand here:
6
- * the row links to the item's route, or — for a pure container that nominates
7
- * no landing page — to its first tab, and stays highlighted while the route is
8
- * on any of them.
9
- */
10
1
  import type { SidebarNavItem } from '../../types/nav.js';
11
2
  type __VLS_Props = {
12
3
  item: SidebarNavItem;
4
+ collapsed?: boolean;
13
5
  };
14
6
  declare const __VLS_export: 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>;
15
7
  declare const _default: typeof __VLS_export;
@@ -18,7 +18,7 @@ const sortIcon = computed(() => {
18
18
  <template>
19
19
  <button
20
20
  type="button"
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"
21
+ class="group inline-flex min-w-0 cursor-pointer items-center gap-1.5 -my-1 -ml-1 mr-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
  >
@@ -26,7 +26,11 @@ const sortIcon = computed(() => {
26
26
  and a heading longer than its column simply prints over the next one —
27
27
  "SortierungGeändert" on a category list, with no hint that two columns
28
28
  are involved. The cell carries the full text as its `title`. -->
29
- <span class="min-w-0 truncate"><slot /></span>
29
+ <!-- Breaks only between words — „Kundennum-mer“ read as a typo (Re-QA
30
+ 18.10.). The left margin pulls the hover ground out to the cell edge; the
31
+ right margin gives the 4px back, so a column sized to its content
32
+ counts the whole button and the sort icon stays inside it. -->
33
+ <span class="min-w-0 break-normal leading-tight"><slot /></span>
30
34
  <component
31
35
  :is="sortIcon"
32
36
  weight="Linear"
@@ -27,7 +27,7 @@ const tileClass = computed(
27
27
 
28
28
  <template>
29
29
  <Card class="bg-white/60 backdrop-blur-sm">
30
- <CardHeader class="items-center text-center">
30
+ <CardHeader class="items-center justify-items-center text-center">
31
31
  <div
32
32
  class="flex size-12 items-center justify-center rounded-full mb-2"
33
33
  :class="tileClass"
@@ -23,9 +23,10 @@ function onKeydown(event, index) {
23
23
  <template>
24
24
  <!-- Sticks flush against the bottom of the sticky PageHeader (57px = its
25
25
  pt-4 pb-3 + text-xl line + 1px border) so header and tabs stay glued as
26
- one unit while the panel scrolls. Glassy frosted bg (blur + low opacity)
27
- lets content scroll through. -->
28
- <div :class="sticky ? 'sticky top-[57px] z-10 bg-white/40 backdrop-blur-xl' : 'relative'">
26
+ one unit while the panel scrolls. Opaque in the shell's ground, like the
27
+ header — content scrolling through read as overlapping text
28
+ (customers-09). -->
29
+ <div :class="sticky ? 'sticky top-[57px] z-10 bg-[var(--color-shell,#f3f1fa)]' : 'relative'">
29
30
  <!-- Pull the row out by the tab padding so the first/last label sits flush
30
31
  with the surrounding content (headline) edges. -->
31
32
  <div class="relative z-10 -mx-2.5 flex gap-1 overflow-x-auto" role="tablist" :aria-label="label">
@@ -42,11 +43,9 @@ function onKeydown(event, index) {
42
43
  @click="emit('update:modelValue', t.key)"
43
44
  @keydown="onKeydown($event, index)"
44
45
  >
46
+ <!-- No counters in tabs (Michael 13.10.): only some tabs could carry
47
+ one, and a half-counted tab bar reads as inconsistent. -->
45
48
  {{ t.label }}
46
- <span
47
- v-if="t.badge"
48
- class="ml-1 rounded-full bg-muted px-1.5 text-mono font-medium tabular-nums text-muted-foreground"
49
- >{{ t.badge }}</span>
50
49
  <!-- 2px indicator, exactly under the label (inset-x-3 = px-3). -->
51
50
  <span
52
51
  class="pointer-events-none absolute inset-x-2.5 bottom-0 h-0.5 rounded-full transition-colors"
@@ -9,6 +9,15 @@ type __VLS_Props = {
9
9
  /** Gate the Next/Finish button for the current step. */
10
10
  canAdvance?: boolean;
11
11
  busy?: boolean;
12
+ /** Label of the last step's button — the surface's own verb ("Anlegen"). */
13
+ finishLabel?: string;
14
+ /** Extra class on the foot, e.g. `rx-dialog-foot` inside a modal (PE-1055). */
15
+ footerClass?: string;
16
+ /**
17
+ * Fill a fixed-height parent: the steps and the foot stay put, only the
18
+ * step's content scrolls (a stepped create dialog, Michael 18.10.).
19
+ */
20
+ fill?: boolean;
12
21
  };
13
22
  declare function back(): void;
14
23
  declare function next(): void;
@@ -36,8 +45,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
36
45
  "onUpdate:modelValue"?: ((args_0: number) => any) | undefined;
37
46
  onFinish?: (() => any) | undefined;
38
47
  }>, {
39
- modelValue: number;
48
+ fill: boolean;
40
49
  busy: boolean;
50
+ modelValue: number;
41
51
  canAdvance: boolean;
42
52
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
43
53
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -1,10 +1,14 @@
1
1
  <script setup>
2
+ import { DIALOG_BODY_CLASS } from "../../lib/formLayout";
2
3
  import { Button } from "@revenexx/studio";
3
4
  const props = defineProps({
4
5
  steps: { type: Array, required: true },
5
6
  modelValue: { type: Number, required: false, default: 0 },
6
7
  canAdvance: { type: Boolean, required: false, default: true },
7
- busy: { type: Boolean, required: false, default: false }
8
+ busy: { type: Boolean, required: false, default: false },
9
+ finishLabel: { type: String, required: false },
10
+ footerClass: { type: String, required: false },
11
+ fill: { type: Boolean, required: false, default: false }
8
12
  });
9
13
  const emit = defineEmits(["update:modelValue", "finish"]);
10
14
  const t = useT();
@@ -29,7 +33,7 @@ function goTo(i) {
29
33
  </script>
30
34
 
31
35
  <template>
32
- <div class="space-y-5">
36
+ <div :class="fill ? 'flex min-h-0 flex-1 flex-col gap-5' : 'space-y-5'">
33
37
  <!-- Go-Live-style stepper: a fill bar + label per step, completed steps
34
38
  are clickable to jump back. -->
35
39
  <div class="flex gap-3">
@@ -46,11 +50,11 @@ function goTo(i) {
46
50
  </div>
47
51
  </div>
48
52
 
49
- <div>
53
+ <div :class="fill ? DIALOG_BODY_CLASS : ''" data-testid="wizard-body">
50
54
  <slot :step="activeStep" :index="index" />
51
55
  </div>
52
56
 
53
- <div class="flex items-center justify-between border-t pt-3">
57
+ <div class="flex shrink-0 items-center justify-between border-t pt-3" :class="footerClass">
54
58
  <!-- Custom footer (e.g. multiple finish actions on a review step). Falls
55
59
  back to the standard Back / Next·Finish pair. -->
56
60
  <slot name="footer" :back="back" :next="next" :is-first="isFirst" :is-last="isLast" :can-advance="canAdvance" :busy="busy" :index="index">
@@ -58,7 +62,7 @@ function goTo(i) {
58
62
  {{ t("shared.wizard.action.back") }}
59
63
  </Button>
60
64
  <Button :disabled="!canAdvance || busy" class="gap-1" @click="next">
61
- {{ isLast ? t("shared.wizard.action.finish") : t("shared.wizard.action.next") }}
65
+ {{ isLast ? finishLabel || t("shared.wizard.action.finish") : t("shared.wizard.action.next") }}
62
66
  </Button>
63
67
  </slot>
64
68
  </div>
@@ -9,6 +9,15 @@ type __VLS_Props = {
9
9
  /** Gate the Next/Finish button for the current step. */
10
10
  canAdvance?: boolean;
11
11
  busy?: boolean;
12
+ /** Label of the last step's button — the surface's own verb ("Anlegen"). */
13
+ finishLabel?: string;
14
+ /** Extra class on the foot, e.g. `rx-dialog-foot` inside a modal (PE-1055). */
15
+ footerClass?: string;
16
+ /**
17
+ * Fill a fixed-height parent: the steps and the foot stay put, only the
18
+ * step's content scrolls (a stepped create dialog, Michael 18.10.).
19
+ */
20
+ fill?: boolean;
12
21
  };
13
22
  declare function back(): void;
14
23
  declare function next(): void;
@@ -36,8 +45,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
36
45
  "onUpdate:modelValue"?: ((args_0: number) => any) | undefined;
37
46
  onFinish?: (() => any) | undefined;
38
47
  }>, {
39
- modelValue: number;
48
+ fill: boolean;
40
49
  busy: boolean;
50
+ modelValue: number;
41
51
  canAdvance: boolean;
42
52
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
43
53
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -37,5 +37,17 @@ export interface MediaLibraryClient {
37
37
  thumbnailUrl: (asset: StorageAsset, size?: number) => string | null;
38
38
  /** True while the asset is still being scanned/processed. */
39
39
  isProcessing: (asset: StorageAsset) => boolean;
40
+ /**
41
+ * Optional server-side narrowing (the cockpit's client has it): free-text
42
+ * search over name, title, alt text and tags; one kind; the sort key
43
+ * (`-created_at`, `original_name`, …); a reload of page 1; a reset. A host
44
+ * without them gets a picker without search, chips and sort.
45
+ */
46
+ search?: Ref<string>;
47
+ kind?: Ref<string | null>;
48
+ sortKey?: Ref<string>;
49
+ total?: Ref<number | null>;
50
+ loadAssets?: () => Promise<void>;
51
+ resetFilters?: () => void;
40
52
  }
41
53
  export declare function useMediaLibrary(): MediaLibraryClient;
@@ -28,12 +28,22 @@ export interface StoredSavedView {
28
28
  id: string;
29
29
  name: string;
30
30
  snapshot: unknown;
31
+ /** PE-1052: shared with everyone in the tenant. Private when absent. */
32
+ shared?: boolean;
33
+ /** Who shared it, for a view that is not the reader's own. */
34
+ ownerName?: string | null;
35
+ /** The reader's own view (may change and delete it). Absent = own. */
36
+ mine?: boolean;
37
+ }
38
+ export interface SaveViewOptions {
39
+ /** „Für alle im Mandanten freigeben“ (PE-1052). Default private. */
40
+ shared?: boolean;
31
41
  }
32
42
  export interface SavedViewsStore {
33
43
  /** Read this surface's views. Never throws — a failure is an empty list. */
34
44
  list: (surface: string) => Promise<StoredSavedView[]>;
35
45
  /** Save under a name, replacing what that name held. */
36
- save: (surface: string, name: string, snapshot: unknown) => Promise<StoredSavedView>;
46
+ save: (surface: string, name: string, snapshot: unknown, options?: SaveViewOptions) => Promise<StoredSavedView>;
37
47
  remove: (surface: string, id: string) => Promise<void>;
38
48
  /**
39
49
  * Does this store survive a new browser? The menu says so in one line, so
@@ -8,7 +8,7 @@ function readAll(surface) {
8
8
  if (!raw) return [];
9
9
  const parsed = JSON.parse(raw);
10
10
  if (!Array.isArray(parsed)) return [];
11
- return parsed.filter((v) => !!v && typeof v === "object" && typeof v.name === "string").map((v) => ({ id: localId(surface, v.name), name: v.name, snapshot: v.snapshot }));
11
+ return parsed.filter((v) => !!v && typeof v === "object" && typeof v.name === "string").map((v) => ({ id: localId(surface, v.name), name: v.name, snapshot: v.snapshot, shared: v.shared === true }));
12
12
  } catch {
13
13
  return [];
14
14
  }
@@ -17,7 +17,7 @@ function writeAll(surface, views) {
17
17
  try {
18
18
  if (typeof window === "undefined") return;
19
19
  window.localStorage.setItem(surface, JSON.stringify(
20
- views.map((v) => ({ name: v.name, snapshot: v.snapshot }))
20
+ views.map((v) => ({ name: v.name, snapshot: v.snapshot, ...v.shared ? { shared: true } : {} }))
21
21
  ));
22
22
  } catch {
23
23
  }
@@ -28,9 +28,9 @@ export function useSavedViews() {
28
28
  async list(surface) {
29
29
  return readAll(surface);
30
30
  },
31
- async save(surface, name, snapshot) {
31
+ async save(surface, name, snapshot, options) {
32
32
  const views = readAll(surface);
33
- const entry = { id: localId(surface, name), name, snapshot };
33
+ const entry = { id: localId(surface, name), name, snapshot, shared: options?.shared === true };
34
34
  const at = views.findIndex((v) => v.name === name);
35
35
  if (at >= 0) views.splice(at, 1, entry);
36
36
  else views.push(entry);
@@ -0,0 +1,12 @@
1
+ import type { MaybeRefOrGetter, Ref } from 'vue';
2
+ /** One figure as the widget shows it, already formatted. */
3
+ export interface WidgetFigure {
4
+ label: string;
5
+ value: string;
6
+ }
7
+ /** Called by the frame: the figures its widget may provide. */
8
+ export declare function provideWidgetFigures(): Ref<WidgetFigure[] | null>;
9
+ /** Called by a widget: keep its frame's figures in step with `figures`. */
10
+ export declare function useWidgetFigures(figures: MaybeRefOrGetter<WidgetFigure[] | null>): void;
11
+ /** The figures as card text: one `label: value` line each. */
12
+ export declare function figuresText(figures: WidgetFigure[] | null): string;