@revenexx/studio-shared 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/module.d.mts +6 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +32 -1
  4. package/dist/runtime/components/DataTable/ColumnsMenu.vue +3 -2
  5. package/dist/runtime/components/DataTable/DataTable.d.vue.ts +32 -4
  6. package/dist/runtime/components/DataTable/DataTable.vue +91 -34
  7. package/dist/runtime/components/DataTable/DataTable.vue.d.ts +32 -4
  8. package/dist/runtime/components/DataTable/FilterMenu.d.vue.ts +12 -3
  9. package/dist/runtime/components/DataTable/FilterMenu.vue +20 -10
  10. package/dist/runtime/components/DataTable/FilterMenu.vue.d.ts +12 -3
  11. package/dist/runtime/components/DataTable/SavedViewsMenu.d.vue.ts +6 -0
  12. package/dist/runtime/components/DataTable/SavedViewsMenu.vue +189 -0
  13. package/dist/runtime/components/DataTable/SavedViewsMenu.vue.d.ts +6 -0
  14. package/dist/runtime/components/DataTable/SortMenu.d.vue.ts +13 -0
  15. package/dist/runtime/components/DataTable/SortMenu.vue +47 -0
  16. package/dist/runtime/components/DataTable/SortMenu.vue.d.ts +13 -0
  17. package/dist/runtime/components/DataTable/ViewModeSwitch.d.vue.ts +20 -0
  18. package/dist/runtime/components/DataTable/ViewModeSwitch.vue +26 -0
  19. package/dist/runtime/components/DataTable/ViewModeSwitch.vue.d.ts +20 -0
  20. package/dist/runtime/components/DataTable/types.d.ts +34 -0
  21. package/dist/runtime/components/Media/MediaFolderTree.d.vue.ts +17 -0
  22. package/dist/runtime/components/Media/MediaFolderTree.vue +65 -0
  23. package/dist/runtime/components/Media/MediaFolderTree.vue.d.ts +17 -0
  24. package/dist/runtime/components/Media/MediaPickerDialog.d.vue.ts +18 -0
  25. package/dist/runtime/components/Media/MediaPickerDialog.vue +163 -0
  26. package/dist/runtime/components/Media/MediaPickerDialog.vue.d.ts +18 -0
  27. package/dist/runtime/components/Media/MediaUploader.d.vue.ts +29 -0
  28. package/dist/runtime/components/Media/MediaUploader.vue +161 -0
  29. package/dist/runtime/components/Media/MediaUploader.vue.d.ts +29 -0
  30. package/dist/runtime/components/Shared/BuildLogsFlyout.vue +14 -13
  31. package/dist/runtime/components/Shared/GitDeploymentSettings.vue +11 -10
  32. package/dist/runtime/components/Shared/ListSurface.d.vue.ts +38 -0
  33. package/dist/runtime/components/Shared/ListSurface.vue +27 -0
  34. package/dist/runtime/components/Shared/ListSurface.vue.d.ts +38 -0
  35. package/dist/runtime/components/Shared/RecordLayout.d.vue.ts +52 -0
  36. package/dist/runtime/components/Shared/RecordLayout.vue +63 -0
  37. package/dist/runtime/components/Shared/RecordLayout.vue.d.ts +52 -0
  38. package/dist/runtime/components/Shared/RecordRailCard.d.vue.ts +26 -0
  39. package/dist/runtime/components/Shared/RecordRailCard.vue +20 -0
  40. package/dist/runtime/components/Shared/RecordRailCard.vue.d.ts +26 -0
  41. package/dist/runtime/components/Shared/SidebarNav.d.vue.ts +5 -0
  42. package/dist/runtime/components/Shared/SidebarNav.vue +6 -26
  43. package/dist/runtime/components/Shared/SidebarNav.vue.d.ts +5 -0
  44. package/dist/runtime/components/Shared/SidebarNavLink.d.vue.ts +15 -0
  45. package/dist/runtime/components/Shared/SidebarNavLink.vue +49 -0
  46. package/dist/runtime/components/Shared/SidebarNavLink.vue.d.ts +15 -0
  47. package/dist/runtime/components/Shared/Wizard.vue +6 -3
  48. package/dist/runtime/composables/useListRail.d.ts +9 -0
  49. package/dist/runtime/composables/useListRail.js +19 -0
  50. package/dist/runtime/composables/useMediaLibrary.d.ts +41 -0
  51. package/dist/runtime/composables/useMediaLibrary.js +36 -0
  52. package/dist/runtime/composables/usePlatformAuth.d.ts +18 -0
  53. package/dist/runtime/composables/usePlatformTalkback.d.ts +29 -0
  54. package/dist/runtime/composables/usePlatformTalkback.js +54 -0
  55. package/dist/runtime/composables/useT.d.ts +11 -0
  56. package/dist/runtime/composables/useT.js +8 -0
  57. package/dist/runtime/composables/useTalkbackChannels.d.ts +78 -0
  58. package/dist/runtime/composables/useTalkbackChannels.js +52 -0
  59. package/dist/runtime/lib/formatBridge.d.ts +50 -0
  60. package/dist/runtime/lib/formatBridge.js +54 -0
  61. package/dist/runtime/lib/i18nBridge.d.ts +31 -0
  62. package/dist/runtime/lib/i18nBridge.js +17 -0
  63. package/dist/runtime/lib/isoLists.d.ts +61 -0
  64. package/dist/runtime/lib/isoLists.js +62 -0
  65. package/dist/runtime/lib/sidebarNav.d.ts +34 -0
  66. package/dist/runtime/lib/sidebarNav.js +22 -0
  67. package/dist/runtime/lib/talkback.d.ts +59 -0
  68. package/dist/runtime/lib/talkback.js +49 -0
  69. package/dist/runtime/types/nav.d.ts +15 -1
  70. package/dist/runtime/types/storage.d.ts +67 -0
  71. package/dist/runtime/types/storage.js +0 -0
  72. package/dist/types.d.mts +12 -0
  73. package/package.json +18 -2
package/dist/module.d.mts CHANGED
@@ -1,8 +1,14 @@
1
1
  import * as nuxt_schema from 'nuxt/schema';
2
2
  export * from '../dist/runtime/components/DataTable/types.js';
3
+ export * from '../dist/runtime/types/storage.js';
3
4
  export { SidebarNavItem, SidebarNavSection } from '../dist/runtime/types/nav.js';
4
5
  export { PlatformAuth } from '../dist/runtime/composables/usePlatformAuth.js';
5
6
  export { PlatformTenant } from '../dist/runtime/composables/usePlatformTenant.js';
7
+ export { PlatformTalkback } from '../dist/runtime/composables/usePlatformTalkback.js';
8
+ export { TalkbackChannel, TalkbackChannelOptions, TalkbackMaybeRef } from '../dist/runtime/composables/useTalkbackChannels.js';
9
+ export { MediaLibraryClient } from '../dist/runtime/composables/useMediaLibrary.js';
10
+ export { IsoOption } from '../dist/runtime/lib/isoLists.js';
11
+ export { Translate, TranslateNamed, Translator } from '../dist/runtime/lib/i18nBridge.js';
6
12
 
7
13
  declare const _default: nuxt_schema.NuxtModule<nuxt_schema.ModuleOptions, nuxt_schema.ModuleOptions, false>;
8
14
 
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "studio-shared",
3
3
  "configKey": "studioShared",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -16,10 +16,41 @@ const module$1 = defineNuxtModule({
16
16
  new RegExp(runtime.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
17
17
  );
18
18
  nuxt.options.build.transpile.push(runtime);
19
+ nuxt.options.build.transpile.push("@revenexx/talkback-js");
20
+ nuxt.options.vite = nuxt.options.vite || {};
21
+ nuxt.options.vite.resolve = nuxt.options.vite.resolve || {};
22
+ const dedupe = nuxt.options.vite.resolve.dedupe ??= [];
23
+ for (const pkg of ["@revenexx/studio-shared", "@revenexx/talkback-js", "centrifuge"]) {
24
+ if (!dedupe.includes(pkg)) dedupe.push(pkg);
25
+ }
26
+ nuxt.options.runtimeConfig.public.talkbackHost ??= "";
27
+ nuxt.options.runtimeConfig.public.talkbackTokenEndpoint ??= "";
28
+ nuxt.options.runtimeConfig.public.talkbackSubscriptionTokenEndpoint ??= "";
19
29
  addComponentsDir({ path: join(runtime, "components"), pathPrefix: false });
20
30
  addImports([
21
31
  { name: "usePlatformAuth", from: join(runtime, "composables/usePlatformAuth"), priority: -1 },
22
- { name: "usePlatformTenant", from: join(runtime, "composables/usePlatformTenant"), priority: -1 }
32
+ { name: "usePlatformTenant", from: join(runtime, "composables/usePlatformTenant"), priority: -1 },
33
+ // Media storage provider seam behind the shared media components — the
34
+ // cockpit's `app/composables/useMediaLibrary.ts` overrides this default.
35
+ { name: "useMediaLibrary", from: join(runtime, "composables/useMediaLibrary"), priority: -1 },
36
+ // Collapsible list-rail state (persisted per surface) behind the
37
+ // DataTable toolbar's rail handle.
38
+ { name: "useListRail", from: join(runtime, "composables/useListRail"), priority: -1 },
39
+ // Realtime provider seam (Talkback). Composes usePlatformAuth +
40
+ // usePlatformTenant, so a host that overrides those gets a correct client
41
+ // without overriding this one too.
42
+ { name: "usePlatformTalkback", from: join(runtime, "composables/usePlatformTalkback"), priority: -1 }
43
+ ]);
44
+ addImports([
45
+ "useTalkbackTopic",
46
+ "useTalkbackResource",
47
+ "useTalkbackUser",
48
+ "useTalkbackStream",
49
+ "useTalkbackPresence"
50
+ ].map((name) => ({ name, from: join(runtime, "composables/useTalkbackChannels") })));
51
+ addImports([
52
+ { name: "useT", from: join(runtime, "composables/useT") },
53
+ { name: "useUiLocale", from: join(runtime, "composables/useT") }
23
54
  ]);
24
55
  }
25
56
  });
@@ -14,6 +14,7 @@ const props = defineProps({
14
14
  });
15
15
  const emit = defineEmits(["toggle"]);
16
16
  const toggleable = computed(() => props.columns.filter((c) => c.toggleable !== false));
17
+ const t = useT();
17
18
  </script>
18
19
 
19
20
  <template>
@@ -22,14 +23,14 @@ const toggleable = computed(() => props.columns.filter((c) => c.toggleable !== f
22
23
  <button
23
24
  type="button"
24
25
  class="flex size-7 items-center justify-center rounded-md border border-black/[0.06] bg-white text-muted-foreground transition hover:text-foreground"
25
- title="Columns"
26
+ :title="t('shared.datatable.columnsmenu.tooltip')"
26
27
  >
27
28
  <Columns :size="15" />
28
29
  </button>
29
30
  </DropdownMenuTrigger>
30
31
  <DropdownMenuContent align="end" class="w-48">
31
32
  <DropdownMenuLabel class="text-sm text-muted-foreground">
32
- Columns
33
+ {{ t("shared.datatable.columnsmenu.heading") }}
33
34
  </DropdownMenuLabel>
34
35
  <DropdownMenuSeparator />
35
36
  <DropdownMenuItem
@@ -1,11 +1,13 @@
1
- import type { DataTableColumn, DataTableFilter, DataTableState, RowKey, SortState } from './types.js';
1
+ import type { DataTableColumn, DataTableFilter, DataTableRail, DataTableSavedViews, DataTableState, DataTableViewMode, RowKey, SortState } from './types.js';
2
2
  declare const _default: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
3
3
  props: __VLS_PrettifyLocal<Pick<Partial<{}> & Omit<{
4
4
  readonly "onUpdate:sort"?: ((value: SortState) => any) | undefined;
5
5
  readonly "onUpdate:page"?: ((value: number) => any) | undefined;
6
+ readonly "onUpdate:viewMode"?: ((value: string) => any) | undefined;
7
+ readonly "onUpdate:railOpen"?: ((value: boolean) => any) | undefined;
6
8
  readonly onRowClick?: ((row: T) => any) | undefined;
7
9
  readonly onStateChange?: ((state: DataTableState) => any) | undefined;
8
- } & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, never>, "onUpdate:sort" | "onUpdate:page" | "onRowClick" | "onStateChange"> & ({
10
+ } & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, never>, "onUpdate:sort" | "onUpdate:page" | "onUpdate:viewMode" | "onUpdate:railOpen" | "onRowClick" | "onStateChange"> & ({
9
11
  data: T[];
10
12
  columns: DataTableColumn<T>[];
11
13
  rowKey: RowKey<T>;
@@ -13,10 +15,23 @@ declare const _default: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>
13
15
  placeholder?: string;
14
16
  };
15
17
  columnsMenu?: boolean;
16
- filter?: boolean;
18
+ /** Mount the surface's own filter panel in the toolbar's Filter popover
19
+ * (`#filter-menu`). Pass an object to widen it / flag it as active. */
20
+ filter?: boolean | {
21
+ width?: string;
22
+ active?: boolean;
23
+ };
17
24
  /** Built-in status/dimension filter pills (static mode). One dimension, or
18
25
  * several (each renders as its own sub-menu). */
19
26
  filterTabs?: DataTableFilter<T> | DataTableFilter<T>[];
27
+ /** Saved views control — the surface supplies snapshot/apply, the table
28
+ * owns the control, the storage and the dialogs. */
29
+ savedViews?: DataTableSavedViews | null;
30
+ /** View-mode switch (e.g. List | Gallery). Any mode other than `list`
31
+ * renders the `#view` slot in place of the table body. */
32
+ viewModes?: DataTableViewMode[] | null;
33
+ /** Collapse handle for the surface's left rail (see `ListSurface`). */
34
+ rail?: boolean | DataTableRail;
20
35
  pagination?: boolean | {
21
36
  pageSize?: number;
22
37
  };
@@ -33,10 +48,14 @@ declare const _default: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>
33
48
  } & {
34
49
  sort?: SortState;
35
50
  page?: number;
51
+ viewMode?: string;
52
+ railOpen?: boolean;
36
53
  }) & Partial<{}>> & import("vue").PublicProps;
37
54
  expose(exposed: import("vue").ShallowUnwrapRef<{}>): void;
38
55
  attrs: any;
39
56
  slots: {
57
+ [x: string]: ((props: {}) => any) | undefined;
58
+ } & {
40
59
  [x: string]: ((props: {
41
60
  row: any;
42
61
  }) => any) | undefined;
@@ -44,12 +63,21 @@ declare const _default: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>
44
63
  'filter-menu'?: (props: {}) => any;
45
64
  } & {
46
65
  'toolbar-left'?: (props: {}) => any;
66
+ } & {
67
+ 'toolbar-right'?: (props: {}) => any;
68
+ } & {
69
+ scope?: (props: {}) => any;
47
70
  } & {
48
71
  actions?: (props: {}) => any;
72
+ } & {
73
+ view?: (props: {
74
+ rows: any;
75
+ loading: any;
76
+ }) => any;
49
77
  } & {
50
78
  empty?: (props: {}) => any;
51
79
  };
52
- emit: (((evt: "rowClick", row: T) => void) & ((evt: "stateChange", state: DataTableState) => void)) & (((evt: "update:sort", value: SortState) => void) & ((evt: "update:page", value: number) => void));
80
+ emit: (((evt: "rowClick", row: T) => void) & ((evt: "stateChange", state: DataTableState) => void)) & (((evt: "update:sort", value: SortState) => void) & ((evt: "update:page", value: number) => void) & ((evt: "update:viewMode", value: string) => void) & ((evt: "update:railOpen", value: boolean) => void));
53
81
  }>) => import("vue").VNode & {
54
82
  __ctx?: Awaited<typeof __VLS_setup>;
55
83
  };
@@ -1,5 +1,5 @@
1
1
  <script setup>
2
- import { AltArrowDown, Filter, Magnifier as Search } from "#solar-icons";
2
+ import { AltArrowDown, Filter, Magnifier as Search, SidebarMinimalistic } from "#solar-icons";
3
3
  import {
4
4
  Button,
5
5
  DropdownMenu,
@@ -15,14 +15,20 @@ import {
15
15
  import SortHeader from "../Shared/SortHeader.vue";
16
16
  import ColumnsMenu from "./ColumnsMenu.vue";
17
17
  import FilterMenu from "./FilterMenu.vue";
18
+ import SavedViewsMenu from "./SavedViewsMenu.vue";
19
+ import SortMenu from "./SortMenu.vue";
20
+ import ViewModeSwitch from "./ViewModeSwitch.vue";
18
21
  const props = defineProps({
19
22
  data: { type: Array, required: true },
20
23
  columns: { type: Array, required: true },
21
24
  rowKey: { type: Function, required: true, skipCheck: true },
22
25
  search: { type: [Boolean, Object], required: false, default: true },
23
26
  columnsMenu: { type: Boolean, required: false, default: true },
24
- filter: { type: Boolean, required: false, default: false },
27
+ filter: { type: [Boolean, Object], required: false, default: false },
25
28
  filterTabs: { type: [Object, Array], required: false },
29
+ savedViews: { type: [Object, null], required: false, default: null },
30
+ viewModes: { type: [Array, null], required: false, default: null },
31
+ rail: { type: [Boolean, Object], required: false, default: false },
26
32
  pagination: { type: [Boolean, Object], required: false, default: true },
27
33
  defaultSort: { type: Object, required: false },
28
34
  serverMode: { type: Boolean, required: false, default: false },
@@ -33,6 +39,7 @@ const props = defineProps({
33
39
  groupEmptyLabel: { type: String, required: false }
34
40
  });
35
41
  const emit = defineEmits(["rowClick", "stateChange"]);
42
+ const t = useT();
36
43
  const query = ref("");
37
44
  const sort = defineModel("sort", { type: Object, ...{
38
45
  default: () => ({
@@ -41,6 +48,8 @@ const sort = defineModel("sort", { type: Object, ...{
41
48
  })
42
49
  } });
43
50
  const page = defineModel("page", { type: Number, ...{ default: 0 } });
51
+ const viewMode = defineModel("viewMode", { type: String, ...{ default: "list" } });
52
+ const railOpen = defineModel("railOpen", { type: Boolean, ...{ default: true } });
44
53
  if (props.defaultSort && sort.value.column == null) {
45
54
  sort.value = { column: props.defaultSort.column, direction: props.defaultSort.direction };
46
55
  }
@@ -74,7 +83,7 @@ const filterDims = computed(() => {
74
83
  const raw = props.filterTabs;
75
84
  if (!raw) return [];
76
85
  const arr = Array.isArray(raw) ? raw : [raw];
77
- return arr.map((f, i) => ({ key: f.key ?? f.label ?? `dim${i}`, label: f.label ?? "Status", getter: f.getter, options: f.options, allLabel: f.allLabel }));
86
+ return arr.map((f, i) => ({ key: f.key ?? f.label ?? `dim${i}`, label: f.label ?? t("shared.datatable.filter.dimension.label.default"), getter: f.getter, options: f.options, allLabel: f.allLabel }));
78
87
  });
79
88
  const activeFilters = ref({});
80
89
  const anyFilterActive = computed(() => Object.values(activeFilters.value).some(Boolean));
@@ -151,7 +160,7 @@ const keyedRows = computed(() => {
151
160
  });
152
161
  const renderRows = computed(() => {
153
162
  if (!props.groupBy) return keyedRows.value.map(({ row, key }) => ({ kind: "row", row, key }));
154
- const emptyLabel = props.groupEmptyLabel ?? "Ungrouped";
163
+ const emptyLabel = props.groupEmptyLabel ?? t("shared.datatable.group.ungrouped.default");
155
164
  const groups = /* @__PURE__ */ new Map();
156
165
  for (const entry of keyedRows.value) {
157
166
  const labels2 = props.groupBy(entry.row);
@@ -207,12 +216,18 @@ const gridTemplate = computed(() => {
207
216
  return `grid-template-columns: ${tracks.join(" ")};`;
208
217
  });
209
218
  const placeholder = computed(
210
- () => typeof props.search === "object" ? props.search.placeholder ?? "Search\u2026" : "Search\u2026"
219
+ () => typeof props.search === "object" ? props.search.placeholder ?? t("shared.datatable.toolbar.search.placeholder.default") : t("shared.datatable.toolbar.search.placeholder.default")
211
220
  );
212
221
  const slots = useSlots();
213
222
  const showToolbar = computed(
214
- () => props.search || props.columnsMenu || props.filter || !!props.filterTabs || !!slots.actions || !!slots["toolbar-left"]
223
+ () => props.search || props.columnsMenu || props.filter || !!props.filterTabs || !!props.savedViews || !!props.viewModes?.length || !!props.rail || !!slots.actions || !!slots.scope || !!slots["toolbar-left"] || !!slots["toolbar-right"]
215
224
  );
225
+ const railLabel = computed(
226
+ () => (typeof props.rail === "object" ? props.rail.label : void 0) ?? t("shared.datatable.toolbar.rail.label.default")
227
+ );
228
+ const showTable = computed(() => !props.viewModes?.length || viewMode.value === "list");
229
+ const sortableColumns = computed(() => props.columns.filter((c) => c.sortable));
230
+ const showSortMenu = computed(() => !showTable.value && sortableColumns.value.length > 0);
216
231
  function cellIconFor(col) {
217
232
  return col.cellIcon ?? null;
218
233
  }
@@ -240,14 +255,31 @@ function defaultCell(col, row) {
240
255
  <template>
241
256
  <div class="rounded-lg border border-black/[0.06] overflow-hidden flex flex-col">
242
257
  <!-- Toolbar ------------------------------------------------------ -->
243
- <!-- Toolbar: LEFT = search · filter · columns · RIGHT = actions.
258
+ <!-- The single control row of a list surface:
259
+ LEFT = rail · search · filter · columns · #toolbar-left
260
+ RIGHT = #toolbar-right · saved views · #scope · view mode · #actions
244
261
  (Pagination always lives in the footer.) -->
245
262
  <div
246
263
  v-if="showToolbar"
247
- class="flex items-center justify-between gap-2 px-4 py-3 bg-white/55 backdrop-blur-md"
264
+ class="flex flex-wrap items-center justify-between gap-2 px-4 py-3 bg-white/55 backdrop-blur-md"
248
265
  >
249
266
  <div class="flex items-center gap-2 min-w-0">
250
- <div v-if="search" class="relative w-[21rem] max-w-full">
267
+ <Button
268
+ v-if="rail"
269
+ variant="outline"
270
+ size="sm"
271
+ class="h-7 shrink-0 gap-1.5 text-sm"
272
+ :class="railOpen ? 'border-primary/30 text-primary' : ''"
273
+ :aria-pressed="railOpen"
274
+ :aria-label="railOpen ? t('shared.datatable.toolbar.rail.aria.hide', { label: railLabel }) : t('shared.datatable.toolbar.rail.aria.show', { label: railLabel })"
275
+ @click="railOpen = !railOpen"
276
+ >
277
+ <SidebarMinimalistic :size="14" />
278
+ {{ railLabel }}
279
+ </Button>
280
+ <!-- Shrinks (never wraps the row) once the toolbar carries scope /
281
+ view-mode / saved-view controls next to it. -->
282
+ <div v-if="search" class="relative w-[21rem] min-w-40 max-w-full shrink">
251
283
  <Search :size="13" class="absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground" />
252
284
  <Input
253
285
  v-model="query"
@@ -265,7 +297,7 @@ function defaultCell(col, row) {
265
297
  :class="anyFilterActive ? 'border-primary/30 text-primary' : ''"
266
298
  >
267
299
  <Filter :size="14" />
268
- Filter
300
+ {{ t("shared.datatable.toolbar.filter.action") }}
269
301
  <AltArrowDown :size="12" class="opacity-60" />
270
302
  </Button>
271
303
  </DropdownMenuTrigger>
@@ -277,7 +309,7 @@ function defaultCell(col, row) {
277
309
  </DropdownMenuSubTrigger>
278
310
  <DropdownMenuSubContent class="w-48">
279
311
  <DropdownMenuItem class="gap-2" @select="setFilter(d.key, null)">
280
- <span class="flex-1">{{ d.allLabel ?? "All" }}</span>
312
+ <span class="flex-1">{{ d.allLabel ?? t("shared.datatable.toolbar.filter.all.default") }}</span>
281
313
  <span class="tabular-nums text-muted-foreground">{{ data.length }}</span>
282
314
  </DropdownMenuItem>
283
315
  <DropdownMenuSeparator />
@@ -294,11 +326,16 @@ function defaultCell(col, row) {
294
326
  </DropdownMenuSub>
295
327
  </DropdownMenuContent>
296
328
  </DropdownMenu>
297
- <FilterMenu v-if="filter">
329
+ <FilterMenu
330
+ v-if="filter"
331
+ :width="typeof filter === 'object' ? filter.width : void 0"
332
+ :active="typeof filter === 'object' ? filter.active : void 0"
333
+ >
298
334
  <slot name="filter-menu" />
299
335
  </FilterMenu>
336
+ <SortMenu v-if="showSortMenu" v-model="sort" :columns="sortableColumns" />
300
337
  <ColumnsMenu
301
- v-if="columnsMenu"
338
+ v-if="columnsMenu && showTable"
302
339
  :columns="menuColumns"
303
340
  :visible="visibleKeys"
304
341
  @toggle="toggleColumn"
@@ -306,12 +343,24 @@ function defaultCell(col, row) {
306
343
  <slot name="toolbar-left" />
307
344
  </div>
308
345
  <div class="flex items-center gap-1.5 ml-auto shrink-0">
346
+ <slot name="toolbar-right" />
347
+ <SavedViewsMenu v-if="savedViews" :config="savedViews" />
348
+ <!-- Market scope ("Showing: … / All markets") — the surface passes the
349
+ shared MarketScopeToggle in; the row position is fixed here. -->
350
+ <slot name="scope" />
351
+ <ViewModeSwitch v-if="viewModes?.length" v-model="viewMode" :modes="viewModes" />
309
352
  <slot name="actions" />
310
353
  </div>
311
354
  </div>
312
355
 
356
+ <!-- Alternate view mode (gallery, board, …) — same toolbar, same footer. -->
357
+ <div v-if="!showTable" class="border-t border-black/[0.04] bg-white/55">
358
+ <slot name="view" :rows="displayedRows" :loading="loading" />
359
+ </div>
360
+
313
361
  <!-- Header row -------------------------------------------------- -->
314
362
  <div
363
+ v-if="showTable"
315
364
  class="grid items-center gap-2 border-t rounded-t-lg border-gray-200 bg-gray-100 px-4 py-2.5 text-sm font-medium uppercase tracking-normal text-muted-foreground"
316
365
  :style="gridTemplate"
317
366
  >
@@ -320,22 +369,27 @@ function defaultCell(col, row) {
320
369
  :key="col.key"
321
370
  :class="col.alignRight ? 'text-right' : ''"
322
371
  >
323
- <SortHeader
324
- v-if="col.sortable"
325
- :column="col.key"
326
- :active="sort.column"
327
- :direction="sort.direction"
328
- :align-right="col.alignRight"
329
- @toggle="toggleSort"
330
- >
331
- {{ col.label }}
332
- </SortHeader>
333
- <span v-else>{{ col.label }}</span>
372
+ <!-- `#header-<key>` lets a page take over a header cell (e.g. a
373
+ select-all checkbox in a selection column). Default: sortable
374
+ SortHeader or the plain label. -->
375
+ <slot :name="`header-${col.key}`">
376
+ <SortHeader
377
+ v-if="col.sortable"
378
+ :column="col.key"
379
+ :active="sort.column"
380
+ :direction="sort.direction"
381
+ :align-right="col.alignRight"
382
+ @toggle="toggleSort"
383
+ >
384
+ {{ col.label }}
385
+ </SortHeader>
386
+ <span v-else>{{ col.label }}</span>
387
+ </slot>
334
388
  </div>
335
389
  </div>
336
390
 
337
391
  <!-- Body --------------------------------------------------------- -->
338
- <template v-for="entry in renderRows" :key="entry.key">
392
+ <template v-for="entry in showTable ? renderRows : []" :key="entry.key">
339
393
  <!-- Group header (groupBy mode) -->
340
394
  <div
341
395
  v-if="entry.kind === 'header'"
@@ -367,14 +421,17 @@ function defaultCell(col, row) {
367
421
  </span>
368
422
  <span class="text-sm font-semibold text-[#0B0522] truncate transition-colors group-hover:text-primary">{{ defaultCell(col, entry.row) }}</span>
369
423
  </div>
370
- <span v-else class="text-sm truncate">{{ defaultCell(col, entry.row) }}</span>
424
+ <!-- `block`, or `truncate` clips nothing: an inline span establishes
425
+ no box, so a long unbroken value overruns the next column
426
+ instead of ending in an ellipsis. -->
427
+ <span v-else class="block truncate text-sm" :title="String(defaultCell(col, entry.row) ?? '')">{{ defaultCell(col, entry.row) }}</span>
371
428
  </slot>
372
429
  </div>
373
430
  </div>
374
431
  </template>
375
432
 
376
433
  <!-- Loading skeleton (only when there are no rows yet) ----------- -->
377
- <template v-if="loading && !displayedRows.length">
434
+ <template v-if="showTable && loading && !displayedRows.length">
378
435
  <div
379
436
  v-for="n in 6"
380
437
  :key="`sk-${n}`"
@@ -396,11 +453,11 @@ function defaultCell(col, row) {
396
453
 
397
454
  <!-- Empty -------------------------------------------------------- -->
398
455
  <div
399
- v-if="!displayedRows.length && !loading"
456
+ v-if="showTable && !displayedRows.length && !loading"
400
457
  class="px-3 py-8 text-center text-sm text-muted-foreground"
401
458
  >
402
459
  <slot name="empty">
403
- {{ emptyText ?? "No entries." }}
460
+ {{ emptyText ?? t("shared.datatable.empty.default") }}
404
461
  </slot>
405
462
  </div>
406
463
 
@@ -411,12 +468,12 @@ function defaultCell(col, row) {
411
468
  >
412
469
  <div class="flex items-center gap-2.5">
413
470
  <span class="tabular-nums">
414
- <span v-if="totalCount === 0">No results</span>
415
- <span v-else>Showing {{ rangeStart }}–{{ rangeEnd }} of {{ totalCount }}</span>
471
+ <span v-if="totalCount === 0">{{ t("shared.datatable.footer.noresults") }}</span>
472
+ <span v-else>{{ t("shared.datatable.footer.showing", { start: rangeStart, end: rangeEnd, total: totalCount }) }}</span>
416
473
  </span>
417
474
  <span class="text-muted-foreground/40">-</span>
418
475
  <label class="flex items-center gap-1.5">
419
- <span class="hidden sm:inline">Rows per page</span>
476
+ <span class="hidden sm:inline">{{ t("shared.datatable.footer.pagesize.label") }}</span>
420
477
  <select
421
478
  v-model.number="pageSize"
422
479
  class="h-7 rounded-md border border-input bg-white px-1.5 text-sm tabular-nums focus:outline-none focus:ring-1 focus:ring-ring"
@@ -433,7 +490,7 @@ function defaultCell(col, row) {
433
490
  :disabled="page === 0 || loading"
434
491
  @click="prevPage"
435
492
  >
436
- Previous
493
+ {{ t("shared.datatable.footer.action.previous") }}
437
494
  </Button>
438
495
  <span class="tabular-nums">
439
496
  {{ page + 1 }} / {{ lastPage + 1 }}
@@ -445,7 +502,7 @@ function defaultCell(col, row) {
445
502
  :disabled="page >= lastPage || loading"
446
503
  @click="nextPage"
447
504
  >
448
- Next
505
+ {{ t("shared.datatable.footer.action.next") }}
449
506
  </Button>
450
507
  </div>
451
508
  </div>
@@ -1,11 +1,13 @@
1
- import type { DataTableColumn, DataTableFilter, DataTableState, RowKey, SortState } from './types.js';
1
+ import type { DataTableColumn, DataTableFilter, DataTableRail, DataTableSavedViews, DataTableState, DataTableViewMode, RowKey, SortState } from './types.js';
2
2
  declare const _default: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
3
3
  props: __VLS_PrettifyLocal<Pick<Partial<{}> & Omit<{
4
4
  readonly "onUpdate:sort"?: ((value: SortState) => any) | undefined;
5
5
  readonly "onUpdate:page"?: ((value: number) => any) | undefined;
6
+ readonly "onUpdate:viewMode"?: ((value: string) => any) | undefined;
7
+ readonly "onUpdate:railOpen"?: ((value: boolean) => any) | undefined;
6
8
  readonly onRowClick?: ((row: T) => any) | undefined;
7
9
  readonly onStateChange?: ((state: DataTableState) => any) | undefined;
8
- } & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, never>, "onUpdate:sort" | "onUpdate:page" | "onRowClick" | "onStateChange"> & ({
10
+ } & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, never>, "onUpdate:sort" | "onUpdate:page" | "onUpdate:viewMode" | "onUpdate:railOpen" | "onRowClick" | "onStateChange"> & ({
9
11
  data: T[];
10
12
  columns: DataTableColumn<T>[];
11
13
  rowKey: RowKey<T>;
@@ -13,10 +15,23 @@ declare const _default: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>
13
15
  placeholder?: string;
14
16
  };
15
17
  columnsMenu?: boolean;
16
- filter?: boolean;
18
+ /** Mount the surface's own filter panel in the toolbar's Filter popover
19
+ * (`#filter-menu`). Pass an object to widen it / flag it as active. */
20
+ filter?: boolean | {
21
+ width?: string;
22
+ active?: boolean;
23
+ };
17
24
  /** Built-in status/dimension filter pills (static mode). One dimension, or
18
25
  * several (each renders as its own sub-menu). */
19
26
  filterTabs?: DataTableFilter<T> | DataTableFilter<T>[];
27
+ /** Saved views control — the surface supplies snapshot/apply, the table
28
+ * owns the control, the storage and the dialogs. */
29
+ savedViews?: DataTableSavedViews | null;
30
+ /** View-mode switch (e.g. List | Gallery). Any mode other than `list`
31
+ * renders the `#view` slot in place of the table body. */
32
+ viewModes?: DataTableViewMode[] | null;
33
+ /** Collapse handle for the surface's left rail (see `ListSurface`). */
34
+ rail?: boolean | DataTableRail;
20
35
  pagination?: boolean | {
21
36
  pageSize?: number;
22
37
  };
@@ -33,10 +48,14 @@ declare const _default: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>
33
48
  } & {
34
49
  sort?: SortState;
35
50
  page?: number;
51
+ viewMode?: string;
52
+ railOpen?: boolean;
36
53
  }) & Partial<{}>> & import("vue").PublicProps;
37
54
  expose(exposed: import("vue").ShallowUnwrapRef<{}>): void;
38
55
  attrs: any;
39
56
  slots: {
57
+ [x: string]: ((props: {}) => any) | undefined;
58
+ } & {
40
59
  [x: string]: ((props: {
41
60
  row: any;
42
61
  }) => any) | undefined;
@@ -44,12 +63,21 @@ declare const _default: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>
44
63
  'filter-menu'?: (props: {}) => any;
45
64
  } & {
46
65
  'toolbar-left'?: (props: {}) => any;
66
+ } & {
67
+ 'toolbar-right'?: (props: {}) => any;
68
+ } & {
69
+ scope?: (props: {}) => any;
47
70
  } & {
48
71
  actions?: (props: {}) => any;
72
+ } & {
73
+ view?: (props: {
74
+ rows: any;
75
+ loading: any;
76
+ }) => any;
49
77
  } & {
50
78
  empty?: (props: {}) => any;
51
79
  };
52
- emit: (((evt: "rowClick", row: T) => void) & ((evt: "stateChange", state: DataTableState) => void)) & (((evt: "update:sort", value: SortState) => void) & ((evt: "update:page", value: number) => void));
80
+ emit: (((evt: "rowClick", row: T) => void) & ((evt: "stateChange", state: DataTableState) => void)) & (((evt: "update:sort", value: SortState) => void) & ((evt: "update:page", value: number) => void) & ((evt: "update:viewMode", value: string) => void) & ((evt: "update:railOpen", value: boolean) => void));
53
81
  }>) => import("vue").VNode & {
54
82
  __ctx?: Awaited<typeof __VLS_setup>;
55
83
  };
@@ -1,8 +1,17 @@
1
- declare var __VLS_26: {};
1
+ type __VLS_Props = {
2
+ /** Popover width class (a wide panel of inputs needs more than the default). */
3
+ width?: string;
4
+ /** Tint the trigger while at least one filter is set. */
5
+ active?: boolean;
6
+ };
7
+ declare var __VLS_34: {};
2
8
  type __VLS_Slots = {} & {
3
- default?: (props: typeof __VLS_26) => any;
9
+ default?: (props: typeof __VLS_34) => any;
4
10
  };
5
- declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
11
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
12
+ active: boolean;
13
+ width: string;
14
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
15
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
7
16
  export default _default;
8
17
  type __VLS_WithSlots<T, S> = T & {
@@ -1,6 +1,7 @@
1
1
  <script setup>
2
- import { Filter } from "#solar-icons";
2
+ import { AltArrowDown, Filter } from "#solar-icons";
3
3
  import {
4
+ Button,
4
5
  DropdownMenu,
5
6
  DropdownMenuContent,
6
7
  DropdownMenuItem,
@@ -8,27 +9,36 @@ import {
8
9
  DropdownMenuSeparator,
9
10
  DropdownMenuTrigger
10
11
  } from "@revenexx/studio";
12
+ defineProps({
13
+ width: { type: String, required: false, default: "w-56" },
14
+ active: { type: Boolean, required: false, default: false }
15
+ });
16
+ const t = useT();
11
17
  </script>
12
18
 
13
19
  <template>
14
20
  <DropdownMenu>
15
21
  <DropdownMenuTrigger as-child>
16
- <button
17
- type="button"
18
- class="flex size-7 items-center justify-center rounded-md border border-black/[0.06] bg-white text-muted-foreground transition hover:text-foreground"
19
- title="Filter"
22
+ <Button
23
+ variant="outline"
24
+ size="sm"
25
+ class="h-7 gap-1.5 text-sm"
26
+ :class="active ? 'border-primary/30 text-primary' : ''"
27
+ :aria-label="t('shared.datatable.filtermenu.aria')"
20
28
  >
21
- <Filter :size="12" />
22
- </button>
29
+ <Filter :size="14" />
30
+ {{ t("shared.datatable.filtermenu.action") }}
31
+ <AltArrowDown :size="12" class="opacity-60" />
32
+ </Button>
23
33
  </DropdownMenuTrigger>
24
- <DropdownMenuContent align="end" class="w-56">
34
+ <DropdownMenuContent align="start" :class="width">
25
35
  <DropdownMenuLabel class="text-sm text-muted-foreground">
26
- Filter
36
+ {{ t("shared.datatable.filtermenu.heading") }}
27
37
  </DropdownMenuLabel>
28
38
  <DropdownMenuSeparator />
29
39
  <slot>
30
40
  <DropdownMenuItem disabled>
31
- No filters defined.
41
+ {{ t("shared.datatable.filtermenu.empty") }}
32
42
  </DropdownMenuItem>
33
43
  </slot>
34
44
  </DropdownMenuContent>
@@ -1,8 +1,17 @@
1
- declare var __VLS_26: {};
1
+ type __VLS_Props = {
2
+ /** Popover width class (a wide panel of inputs needs more than the default). */
3
+ width?: string;
4
+ /** Tint the trigger while at least one filter is set. */
5
+ active?: boolean;
6
+ };
7
+ declare var __VLS_34: {};
2
8
  type __VLS_Slots = {} & {
3
- default?: (props: typeof __VLS_26) => any;
9
+ default?: (props: typeof __VLS_34) => any;
4
10
  };
5
- declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
11
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
12
+ active: boolean;
13
+ width: string;
14
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
15
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
7
16
  export default _default;
8
17
  type __VLS_WithSlots<T, S> = T & {
@@ -0,0 +1,6 @@
1
+ import type { DataTableSavedViews } from './types.js';
2
+ type __VLS_Props = {
3
+ config: DataTableSavedViews;
4
+ };
5
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ export default _default;