@revenexx/studio-shared 0.3.2 → 0.4.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 (166) hide show
  1. package/dist/module.d.mts +6 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +12 -1
  4. package/dist/runtime/components/Dashboard/DashboardConfirmDialog.d.vue.ts +16 -0
  5. package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue +39 -0
  6. package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue.d.ts +16 -0
  7. package/dist/runtime/components/Dashboard/DashboardControls.d.vue.ts +58 -0
  8. package/dist/runtime/components/Dashboard/DashboardControls.vue +209 -0
  9. package/dist/runtime/components/Dashboard/DashboardControls.vue.d.ts +58 -0
  10. package/dist/runtime/components/Dashboard/DashboardGrid.d.vue.ts +17 -0
  11. package/dist/runtime/components/Dashboard/DashboardGrid.vue +87 -0
  12. package/dist/runtime/components/Dashboard/DashboardGrid.vue.d.ts +17 -0
  13. package/dist/runtime/components/Dashboard/DashboardNameDialog.d.vue.ts +19 -0
  14. package/dist/runtime/components/Dashboard/DashboardNameDialog.vue +61 -0
  15. package/dist/runtime/components/Dashboard/DashboardNameDialog.vue.d.ts +19 -0
  16. package/dist/runtime/components/Dashboard/DashboardPickerDialog.d.vue.ts +15 -0
  17. package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue +100 -0
  18. package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue.d.ts +15 -0
  19. package/dist/runtime/components/Dashboard/DashboardShareDialog.d.vue.ts +28 -0
  20. package/dist/runtime/components/Dashboard/DashboardShareDialog.vue +155 -0
  21. package/dist/runtime/components/Dashboard/DashboardShareDialog.vue.d.ts +28 -0
  22. package/dist/runtime/components/Dashboard/DashboardTopBar.d.vue.ts +64 -0
  23. package/dist/runtime/components/Dashboard/DashboardTopBar.vue +79 -0
  24. package/dist/runtime/components/Dashboard/DashboardTopBar.vue.d.ts +64 -0
  25. package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.d.vue.ts +44 -0
  26. package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue +153 -0
  27. package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue.d.ts +44 -0
  28. package/dist/runtime/components/Dashboard/DashboardWidgetFrame.d.vue.ts +28 -0
  29. package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue +80 -0
  30. package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue.d.ts +28 -0
  31. package/dist/runtime/components/Dashboard/DashboardWidgetHost.d.vue.ts +7 -0
  32. package/dist/runtime/components/Dashboard/DashboardWidgetHost.vue +30 -0
  33. package/dist/runtime/components/Dashboard/DashboardWidgetHost.vue.d.ts +7 -0
  34. package/dist/runtime/components/Dashboard/DashboardWidgetIcon.d.vue.ts +9 -0
  35. package/dist/runtime/components/Dashboard/DashboardWidgetIcon.vue +13 -0
  36. package/dist/runtime/components/Dashboard/DashboardWidgetIcon.vue.d.ts +9 -0
  37. package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.d.vue.ts +24 -0
  38. package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.vue +140 -0
  39. package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.vue.d.ts +24 -0
  40. package/dist/runtime/components/Dashboard/DashboardWidgetPreview.d.vue.ts +23 -0
  41. package/dist/runtime/components/Dashboard/DashboardWidgetPreview.vue +124 -0
  42. package/dist/runtime/components/Dashboard/DashboardWidgetPreview.vue.d.ts +23 -0
  43. package/dist/runtime/components/DataTable/ColumnsMenu.d.vue.ts +2 -1
  44. package/dist/runtime/components/DataTable/ColumnsMenu.vue.d.ts +2 -1
  45. package/dist/runtime/components/DataTable/DataTable.d.vue.ts +105 -28
  46. package/dist/runtime/components/DataTable/DataTable.vue +403 -38
  47. package/dist/runtime/components/DataTable/DataTable.vue.d.ts +105 -28
  48. package/dist/runtime/components/DataTable/FacetChips.d.vue.ts +17 -0
  49. package/dist/runtime/components/DataTable/FacetChips.vue +72 -0
  50. package/dist/runtime/components/DataTable/FacetChips.vue.d.ts +17 -0
  51. package/dist/runtime/components/DataTable/FacetPanel.d.vue.ts +21 -0
  52. package/dist/runtime/components/DataTable/FacetPanel.vue +359 -0
  53. package/dist/runtime/components/DataTable/FacetPanel.vue.d.ts +21 -0
  54. package/dist/runtime/components/DataTable/FilterMenu.d.vue.ts +6 -5
  55. package/dist/runtime/components/DataTable/FilterMenu.vue.d.ts +6 -5
  56. package/dist/runtime/components/DataTable/SavedViewsMenu.d.vue.ts +6 -1
  57. package/dist/runtime/components/DataTable/SavedViewsMenu.vue +83 -63
  58. package/dist/runtime/components/DataTable/SavedViewsMenu.vue.d.ts +6 -1
  59. package/dist/runtime/components/DataTable/SortMenu.d.vue.ts +9 -3
  60. package/dist/runtime/components/DataTable/SortMenu.vue +40 -12
  61. package/dist/runtime/components/DataTable/SortMenu.vue.d.ts +9 -3
  62. package/dist/runtime/components/DataTable/ViewModeSwitch.d.vue.ts +4 -2
  63. package/dist/runtime/components/DataTable/ViewModeSwitch.vue.d.ts +4 -2
  64. package/dist/runtime/components/Form/FieldCell.d.vue.ts +32 -0
  65. package/dist/runtime/components/Form/FieldCell.vue +46 -0
  66. package/dist/runtime/components/Form/FieldCell.vue.d.ts +32 -0
  67. package/dist/runtime/components/Form/FieldGrid.d.vue.ts +13 -0
  68. package/dist/runtime/components/Form/FieldGrid.vue +11 -0
  69. package/dist/runtime/components/Form/FieldGrid.vue.d.ts +13 -0
  70. package/dist/runtime/components/Form/FormContextCard.d.vue.ts +33 -0
  71. package/dist/runtime/components/Form/FormContextCard.vue +56 -0
  72. package/dist/runtime/components/Form/FormContextCard.vue.d.ts +33 -0
  73. package/dist/runtime/components/Form/FormLayout.d.vue.ts +47 -0
  74. package/dist/runtime/components/Form/FormLayout.vue +169 -0
  75. package/dist/runtime/components/Form/FormLayout.vue.d.ts +47 -0
  76. package/dist/runtime/components/Form/FormSection.d.vue.ts +25 -0
  77. package/dist/runtime/components/Form/FormSection.vue +48 -0
  78. package/dist/runtime/components/Form/FormSection.vue.d.ts +25 -0
  79. package/dist/runtime/components/Form/SaveBar.d.vue.ts +44 -0
  80. package/dist/runtime/components/Form/SaveBar.vue +71 -0
  81. package/dist/runtime/components/Form/SaveBar.vue.d.ts +44 -0
  82. package/dist/runtime/components/Media/MediaFolderTree.d.vue.ts +2 -1
  83. package/dist/runtime/components/Media/MediaFolderTree.vue.d.ts +2 -1
  84. package/dist/runtime/components/Media/MediaPickerDialog.d.vue.ts +4 -3
  85. package/dist/runtime/components/Media/MediaPickerDialog.vue.d.ts +4 -3
  86. package/dist/runtime/components/Media/MediaUploader.d.vue.ts +4 -3
  87. package/dist/runtime/components/Media/MediaUploader.vue.d.ts +4 -3
  88. package/dist/runtime/components/Shared/AppIcon.d.vue.ts +21 -0
  89. package/dist/runtime/components/Shared/AppIcon.vue +47 -0
  90. package/dist/runtime/components/Shared/AppIcon.vue.d.ts +21 -0
  91. package/dist/runtime/components/Shared/BuildLogsFlyout.d.vue.ts +2 -1
  92. package/dist/runtime/components/Shared/BuildLogsFlyout.vue.d.ts +2 -1
  93. package/dist/runtime/components/Shared/DetailSheet.d.vue.ts +60 -0
  94. package/dist/runtime/components/Shared/DetailSheet.vue +207 -0
  95. package/dist/runtime/components/Shared/DetailSheet.vue.d.ts +60 -0
  96. package/dist/runtime/components/Shared/FloatingPanel.d.vue.ts +7 -6
  97. package/dist/runtime/components/Shared/FloatingPanel.vue.d.ts +7 -6
  98. package/dist/runtime/components/Shared/GitDeploymentSettings.d.vue.ts +2 -1
  99. package/dist/runtime/components/Shared/GitDeploymentSettings.vue.d.ts +2 -1
  100. package/dist/runtime/components/Shared/KpiTile.d.vue.ts +2 -1
  101. package/dist/runtime/components/Shared/KpiTile.vue.d.ts +2 -1
  102. package/dist/runtime/components/Shared/ListSurface.d.vue.ts +3 -2
  103. package/dist/runtime/components/Shared/ListSurface.vue.d.ts +3 -2
  104. package/dist/runtime/components/Shared/PageHeader.d.vue.ts +7 -6
  105. package/dist/runtime/components/Shared/PageHeader.vue.d.ts +7 -6
  106. package/dist/runtime/components/Shared/RecordLayout.d.vue.ts +8 -7
  107. package/dist/runtime/components/Shared/RecordLayout.vue.d.ts +8 -7
  108. package/dist/runtime/components/Shared/RecordRailCard.d.vue.ts +3 -2
  109. package/dist/runtime/components/Shared/RecordRailCard.vue.d.ts +3 -2
  110. package/dist/runtime/components/Shared/SidebarNav.d.vue.ts +3 -2
  111. package/dist/runtime/components/Shared/SidebarNav.vue.d.ts +3 -2
  112. package/dist/runtime/components/Shared/SidebarNavLink.d.vue.ts +2 -1
  113. package/dist/runtime/components/Shared/SidebarNavLink.vue.d.ts +2 -1
  114. package/dist/runtime/components/Shared/SortHeader.d.vue.ts +3 -2
  115. package/dist/runtime/components/Shared/SortHeader.vue +6 -2
  116. package/dist/runtime/components/Shared/SortHeader.vue.d.ts +3 -2
  117. package/dist/runtime/components/Shared/StateCard.d.vue.ts +6 -5
  118. package/dist/runtime/components/Shared/StateCard.vue.d.ts +6 -5
  119. package/dist/runtime/components/Shared/StudioCard.d.vue.ts +5 -4
  120. package/dist/runtime/components/Shared/StudioCard.vue.d.ts +5 -4
  121. package/dist/runtime/components/Shared/TabBar.d.vue.ts +9 -10
  122. package/dist/runtime/components/Shared/TabBar.vue +26 -7
  123. package/dist/runtime/components/Shared/TabBar.vue.d.ts +9 -10
  124. package/dist/runtime/components/Shared/ViewTab.d.vue.ts +2 -1
  125. package/dist/runtime/components/Shared/ViewTab.vue.d.ts +2 -1
  126. package/dist/runtime/components/Shared/Wizard.d.vue.ts +10 -7
  127. package/dist/runtime/components/Shared/Wizard.vue.d.ts +10 -7
  128. package/dist/runtime/composables/useDashboards.d.ts +27 -0
  129. package/dist/runtime/composables/useDashboards.js +143 -0
  130. package/dist/runtime/composables/useDetailSheet.d.ts +20 -0
  131. package/dist/runtime/composables/useDetailSheet.js +50 -0
  132. package/dist/runtime/composables/usePlatformTenant.d.ts +6 -0
  133. package/dist/runtime/composables/useSavedViews.d.ts +44 -0
  134. package/dist/runtime/composables/useSavedViews.js +44 -0
  135. package/dist/runtime/composables/useStickyListView.d.ts +60 -0
  136. package/dist/runtime/composables/useStickyListView.js +32 -0
  137. package/dist/runtime/lib/activeTenant.d.ts +58 -0
  138. package/dist/runtime/lib/activeTenant.js +45 -0
  139. package/dist/runtime/lib/appIcon.d.ts +48 -0
  140. package/dist/runtime/lib/appIcon.js +27 -0
  141. package/dist/runtime/lib/canonicalJson.d.ts +43 -0
  142. package/dist/runtime/lib/canonicalJson.js +38 -0
  143. package/dist/runtime/lib/dashboardIcons.d.ts +4 -0
  144. package/dist/runtime/lib/dashboardIcons.js +50 -0
  145. package/dist/runtime/lib/detailSheet.d.ts +43 -0
  146. package/dist/runtime/lib/detailSheet.js +34 -0
  147. package/dist/runtime/lib/facets.d.ts +200 -0
  148. package/dist/runtime/lib/facets.js +171 -0
  149. package/dist/runtime/lib/formLayout.d.ts +94 -0
  150. package/dist/runtime/lib/formLayout.js +70 -0
  151. package/dist/runtime/lib/formLayoutContext.d.ts +26 -0
  152. package/dist/runtime/lib/formLayoutContext.js +1 -0
  153. package/dist/runtime/lib/gridTracks.d.ts +55 -0
  154. package/dist/runtime/lib/gridTracks.js +29 -0
  155. package/dist/runtime/lib/rowMove.d.ts +38 -0
  156. package/dist/runtime/lib/rowMove.js +20 -0
  157. package/dist/runtime/lib/solarIconMap.d.ts +15 -0
  158. package/dist/runtime/lib/solarIconMap.js +280 -0
  159. package/dist/runtime/lib/solarIcons.d.ts +37 -0
  160. package/dist/runtime/lib/solarIcons.js +25 -0
  161. package/dist/runtime/lib/widgetRegistry.d.ts +124 -0
  162. package/dist/runtime/lib/widgetRegistry.js +66 -0
  163. package/dist/runtime/types/dashboard.d.ts +95 -0
  164. package/dist/runtime/types/dashboard.js +2 -0
  165. package/dist/types.d.mts +12 -0
  166. package/package.json +3 -2
@@ -3,6 +3,7 @@ import {
3
3
  Eye,
4
4
  EyeClosed,
5
5
  Filter,
6
+ HamburgerMenu as DragHandle,
6
7
  ListArrowDownMinimalistic,
7
8
  Magnifier as Search,
8
9
  SidebarMinimalistic,
@@ -19,7 +20,12 @@ import {
19
20
  import { orderColumnsBy } from "../../lib/columnOrder";
20
21
  import { initiallyHidden, visibleColumnsOf } from "../../lib/columnVisibility";
21
22
  import { countByValue, matchesFilter } from "../../lib/dataTableFilters";
23
+ import { gridTemplateColumns } from "../../lib/gridTracks";
24
+ const RIGHT_ALIGNED = "text-right pr-4";
25
+ import { dropEdge, dropTargetIndex, moveKey, stepTarget } from "../../lib/rowMove";
22
26
  import SortHeader from "../Shared/SortHeader.vue";
27
+ import FacetChips from "./FacetChips.vue";
28
+ import FacetPanel from "./FacetPanel.vue";
23
29
  import FilterMenu from "./FilterMenu.vue";
24
30
  import SavedViewsMenu from "./SavedViewsMenu.vue";
25
31
  import SortMenu from "./SortMenu.vue";
@@ -31,10 +37,12 @@ const props = defineProps({
31
37
  columnsMenu: { type: Boolean, required: false, default: true },
32
38
  filter: { type: [Boolean, Object], required: false, default: false },
33
39
  filterTabs: { type: [Object, Array], required: false },
40
+ facets: { type: Array, required: false, default: () => [] },
41
+ chipFacetLabels: { type: Boolean, required: false, default: false },
34
42
  savedViews: { type: [Object, null], required: false, default: null },
35
43
  viewModes: { type: [Array, null], required: false, default: null },
36
44
  rail: { type: [Boolean, Object], required: false, default: false },
37
- scrollBody: { type: [Boolean, Object], required: false },
45
+ scrollBody: { type: [Boolean, String, Object], required: false },
38
46
  pagination: { type: [Boolean, Object], required: false, default: true },
39
47
  defaultSort: { type: Object, required: false },
40
48
  serverMode: { type: Boolean, required: false, default: false },
@@ -42,9 +50,13 @@ const props = defineProps({
42
50
  loading: { type: Boolean, required: false, default: false },
43
51
  emptyText: { type: String, required: false },
44
52
  groupBy: { type: [Function, null], required: false, default: null },
45
- groupEmptyLabel: { type: String, required: false }
53
+ groupEmptyLabel: { type: String, required: false },
54
+ activeRowKey: { type: [String, Number, null], required: false },
55
+ rowClass: { type: [Function, null], required: false, default: null },
56
+ reorderable: { type: Boolean, required: false, default: false },
57
+ reorderDisabled: { type: [String, null], required: false, default: null }
46
58
  });
47
- const emit = defineEmits(["rowClick", "stateChange"]);
59
+ const emit = defineEmits(["rowClick", "stateChange", "orderChange", "rowMove"]);
48
60
  const t = useT();
49
61
  const query = ref("");
50
62
  const sort = defineModel("sort", { type: Object, ...{
@@ -56,6 +68,8 @@ const sort = defineModel("sort", { type: Object, ...{
56
68
  const page = defineModel("page", { type: Number, ...{ default: 0 } });
57
69
  const viewMode = defineModel("viewMode", { type: String, ...{ default: "list" } });
58
70
  const railOpen = defineModel("railOpen", { type: Boolean, ...{ default: true } });
71
+ const facetSelection = defineModel("facetSelection", { type: Object, ...{ default: () => ({}) } });
72
+ const panelFacets = computed(() => props.facets);
59
73
  if (props.defaultSort && sort.value.column == null) {
60
74
  sort.value = { column: props.defaultSort.column, direction: props.defaultSort.direction };
61
75
  }
@@ -157,9 +171,24 @@ const sorted = computed(() => {
157
171
  return 0;
158
172
  });
159
173
  });
174
+ watch(sorted, (rows) => emit("orderChange", rows.map(rowKeyFor)), { immediate: true });
160
175
  const totalCount = computed(
161
176
  () => props.serverMode ? props.total ?? props.data.length : sorted.value.length
162
177
  );
178
+ const activeNarrowing = computed(() => filterDims.value.map((d) => ({
179
+ label: d.label,
180
+ values: (activeFilters.value[d.key] ?? []).map((v) => d.options.find((o) => o.value === v)?.label ?? v)
181
+ })).filter((d) => d.values.length > 0));
182
+ function clearNarrowing() {
183
+ query.value = "";
184
+ activeFilters.value = {};
185
+ facetSelection.value = {};
186
+ }
187
+ const liveStatus = computed(() => {
188
+ if (!showTable.value) return "";
189
+ if (props.loading) return t("shared.datatable.state.loading");
190
+ return totalCount.value === 0 ? t("shared.datatable.footer.noresults") : t("shared.datatable.footer.showing", { start: rangeStart.value, end: rangeEnd.value, total: totalCount.value });
191
+ });
163
192
  const paginated = computed(() => {
164
193
  if (props.serverMode || !props.pagination) return sorted.value;
165
194
  const start = page.value * pageSize.value;
@@ -233,10 +262,8 @@ function rowKeyFor(row) {
233
262
  if (typeof props.rowKey === "function") return props.rowKey(row);
234
263
  return row[props.rowKey];
235
264
  }
236
- const gridTemplate = computed(() => {
237
- const tracks = visibleColumns.value.map((c) => c.width ?? "minmax(0,1fr)");
238
- return `grid-template-columns: ${tracks.join(" ")};`;
239
- });
265
+ const gridTemplate = computed(() => `grid-template-columns: ${gridTemplateColumns(renderColumns.value.map((c) => c.width))};`);
266
+ const SUBGRID_ROW = "grid-column: 1 / -1; grid-template-columns: subgrid;";
240
267
  const placeholder = computed(
241
268
  () => typeof props.search === "object" ? props.search.placeholder ?? t("shared.datatable.toolbar.search.placeholder.default") : t("shared.datatable.toolbar.search.placeholder.default")
242
269
  );
@@ -249,7 +276,7 @@ const showToolbar = computed(
249
276
  () => 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"]
250
277
  );
251
278
  const railLabel = computed(
252
- () => (typeof props.rail === "object" ? props.rail.label : void 0) ?? t("shared.datatable.toolbar.rail.label.default")
279
+ () => (typeof props.rail === "object" ? props.rail.label : void 0) ?? null
253
280
  );
254
281
  const showTable = computed(() => !props.viewModes?.length || viewMode.value === "list");
255
282
  const sortableColumns = computed(() => props.columns.filter((c) => c.sortable));
@@ -257,8 +284,121 @@ const showSortMenu = computed(() => !showTable.value && sortableColumns.value.le
257
284
  function cellIconFor(col) {
258
285
  return col.cellIcon ?? null;
259
286
  }
287
+ const scrollEl = ref(null);
288
+ const footerEl = ref(null);
289
+ const fitHeight = ref(null);
290
+ const MIN_FIT_PX = 220;
291
+ const BOTTOM_GAP_PX = 16;
292
+ function scrollParent(el) {
293
+ let node = el.parentElement;
294
+ while (node && node !== document.body) {
295
+ const overflow = getComputedStyle(node).overflowY;
296
+ if (overflow === "auto" || overflow === "scroll") return node;
297
+ node = node.parentElement;
298
+ }
299
+ return document.documentElement;
300
+ }
301
+ function measureFit() {
302
+ if (props.scrollBody !== "fit" || !import.meta.client) return;
303
+ const el = scrollEl.value;
304
+ if (!el) return;
305
+ const container = scrollParent(el);
306
+ const offset = el.getBoundingClientRect().top - container.getBoundingClientRect().top + container.scrollTop;
307
+ const reserve = (footerEl.value?.offsetHeight ?? 0) + BOTTOM_GAP_PX;
308
+ const overflow = Math.max(container.scrollHeight - container.clientHeight, 0);
309
+ const available = Math.max(
310
+ Math.floor(container.clientHeight - offset - reserve - overflow) - 1,
311
+ MIN_FIT_PX
312
+ );
313
+ const current = fitHeight.value ? Number.parseInt(fitHeight.value, 10) : null;
314
+ if (current !== null && Math.abs(current - available) < 8) return;
315
+ fitHeight.value = `${available}px`;
316
+ }
317
+ if (import.meta.client) {
318
+ let frame = 0;
319
+ let timers = [];
320
+ const schedule = () => {
321
+ if (frame) return;
322
+ frame = requestAnimationFrame(() => {
323
+ frame = 0;
324
+ measureFit();
325
+ });
326
+ };
327
+ onMounted(() => {
328
+ measureFit();
329
+ window.addEventListener("resize", schedule);
330
+ timers = [setTimeout(schedule, 250), setTimeout(schedule, 1200)];
331
+ });
332
+ onBeforeUnmount(() => {
333
+ window.removeEventListener("resize", schedule);
334
+ for (const timer of timers) clearTimeout(timer);
335
+ timers = [];
336
+ if (frame) cancelAnimationFrame(frame);
337
+ });
338
+ watch(() => [props.data.length, props.loading], () => nextTick(() => {
339
+ schedule();
340
+ syncThumb();
341
+ }));
342
+ onMounted(() => {
343
+ nextTick(syncThumb);
344
+ window.addEventListener("resize", syncThumb);
345
+ });
346
+ onBeforeUnmount(() => window.removeEventListener("resize", syncThumb));
347
+ watch(fitHeight, () => nextTick(syncThumb));
348
+ }
349
+ const headerRowEl = ref(null);
350
+ const headerHeight = ref(0);
351
+ const rowsOverflow = ref(false);
352
+ const thumbHeight = ref(0);
353
+ const thumbOffset = ref(0);
354
+ const MIN_THUMB_PX = 32;
355
+ const drawnBarSupported = import.meta.client && typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("selector(::-webkit-scrollbar)");
356
+ function syncThumb() {
357
+ const el = scrollEl.value;
358
+ if (!props.scrollBody || !el) return;
359
+ headerHeight.value = headerRowEl.value?.offsetHeight ?? 0;
360
+ const trackHeight = el.clientHeight - headerHeight.value;
361
+ const scrollable = el.scrollHeight - el.clientHeight;
362
+ rowsOverflow.value = drawnBarSupported && scrollable > 1 && trackHeight > MIN_THUMB_PX;
363
+ if (!rowsOverflow.value) return;
364
+ const ratio = el.clientHeight / el.scrollHeight;
365
+ const height = Math.max(Math.round(trackHeight * ratio), MIN_THUMB_PX);
366
+ thumbHeight.value = height;
367
+ thumbOffset.value = Math.round(el.scrollTop / scrollable * (trackHeight - height));
368
+ }
369
+ function onRowsScroll() {
370
+ syncThumb();
371
+ }
372
+ function startThumbDrag(event) {
373
+ const el = scrollEl.value;
374
+ if (!el) return;
375
+ event.preventDefault();
376
+ const startY = event.clientY;
377
+ const startScroll = el.scrollTop;
378
+ const trackHeight = el.clientHeight - headerHeight.value;
379
+ const scrollable = el.scrollHeight - el.clientHeight;
380
+ const travel = trackHeight - thumbHeight.value;
381
+ if (travel <= 0) return;
382
+ const target = event.target;
383
+ target.setPointerCapture(event.pointerId);
384
+ const move = (e) => {
385
+ el.scrollTop = startScroll + (e.clientY - startY) / travel * scrollable;
386
+ };
387
+ const up = (e) => {
388
+ target.releasePointerCapture(e.pointerId);
389
+ target.removeEventListener("pointermove", move);
390
+ target.removeEventListener("pointerup", up);
391
+ target.removeEventListener("pointercancel", up);
392
+ };
393
+ target.addEventListener("pointermove", move);
394
+ target.addEventListener("pointerup", up);
395
+ target.addEventListener("pointercancel", up);
396
+ }
260
397
  const scrollStyle = computed(() => {
261
398
  if (!props.scrollBody) return void 0;
399
+ if (props.scrollBody === "fit") {
400
+ return fitHeight.value ? { maxHeight: fitHeight.value } : void 0;
401
+ }
262
402
  const configured = typeof props.scrollBody === "object" ? props.scrollBody.maxHeight : void 0;
263
403
  return { maxHeight: configured ?? "calc(100vh - 22rem)" };
264
404
  });
@@ -276,6 +416,88 @@ function prevPage() {
276
416
  function nextPage() {
277
417
  if (page.value < lastPage.value) page.value += 1;
278
418
  }
419
+ const MOVE_COLUMN = "__move";
420
+ const renderColumns = computed(() => props.reorderable ? [{ key: MOVE_COLUMN, label: "", width: "max-content" }, ...visibleColumns.value] : visibleColumns.value);
421
+ const reorderBlocked = computed(() => {
422
+ if (!props.reorderable) return null;
423
+ if (props.reorderDisabled) return props.reorderDisabled;
424
+ if (!props.serverMode && query.value.trim()) return t("shared.datatable.reorder.blocked.search");
425
+ if (Object.values(activeFilters.value).some((v) => v.length)) return t("shared.datatable.reorder.blocked.filter");
426
+ if (props.groupBy) return t("shared.datatable.reorder.blocked.group");
427
+ return null;
428
+ });
429
+ const dragKey = ref(null);
430
+ const dropAt = ref(null);
431
+ const moveStatus = ref("");
432
+ function orderedKeys() {
433
+ return sorted.value.map(rowKeyFor);
434
+ }
435
+ function commitMove(keys, from, to) {
436
+ emit("rowMove", { key: keys[from], from, to, keys: moveKey(keys, from, to) });
437
+ moveStatus.value = t("shared.datatable.reorder.moved", { position: to + 1, total: keys.length });
438
+ }
439
+ function finishDrag() {
440
+ dragKey.value = null;
441
+ dropAt.value = null;
442
+ }
443
+ function onHandleDragStart(event, row) {
444
+ if (reorderBlocked.value) {
445
+ event.preventDefault();
446
+ return;
447
+ }
448
+ const key = rowKeyFor(row);
449
+ dragKey.value = key;
450
+ if (event.dataTransfer) {
451
+ event.dataTransfer.effectAllowed = "move";
452
+ event.dataTransfer.setData("text/plain", String(key));
453
+ const rowEl = event.target?.closest('[role="row"]');
454
+ if (rowEl) event.dataTransfer.setDragImage(rowEl, 16, 16);
455
+ }
456
+ }
457
+ function onRowDragOver(event, row) {
458
+ if (dragKey.value == null) return;
459
+ event.preventDefault();
460
+ if (event.dataTransfer) event.dataTransfer.dropEffect = "move";
461
+ const rect = event.currentTarget.getBoundingClientRect();
462
+ dropAt.value = { key: rowKeyFor(row), edge: dropEdge(event.clientY, rect.top, rect.height) };
463
+ }
464
+ function onRowDrop(event, row) {
465
+ if (dragKey.value == null) return;
466
+ event.preventDefault();
467
+ const keys = orderedKeys();
468
+ const from = keys.indexOf(dragKey.value);
469
+ const target = keys.indexOf(rowKeyFor(row));
470
+ const edge = dropAt.value?.edge ?? "before";
471
+ finishDrag();
472
+ const to = dropTargetIndex(from, target, edge);
473
+ if (to !== null) commitMove(keys, from, to);
474
+ }
475
+ function onHandleKeydown(event, row) {
476
+ if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return;
477
+ event.preventDefault();
478
+ if (reorderBlocked.value) return;
479
+ const keys = orderedKeys();
480
+ const key = rowKeyFor(row);
481
+ const from = keys.indexOf(key);
482
+ const to = stepTarget(from, event.key === "ArrowUp" ? -1 : 1, keys.length);
483
+ if (to === null) return;
484
+ commitMove(keys, from, to);
485
+ const handle = event.currentTarget;
486
+ const root = handle.closest('[role="table"]');
487
+ void nextTick(() => setTimeout(() => {
488
+ const again = root?.querySelector(`[data-move-key="${CSS.escape(String(key))}"]`);
489
+ again?.focus();
490
+ }, 0));
491
+ }
492
+ function dropClass(row) {
493
+ const at = dropAt.value;
494
+ if (!at || dragKey.value == null || at.key !== rowKeyFor(row)) return "";
495
+ return at.edge === "before" ? "shadow-[inset_0_2px_0_0_var(--color-primary)]" : "shadow-[inset_0_-2px_0_0_var(--color-primary)]";
496
+ }
497
+ function rowGround(row) {
498
+ if (props.activeRowKey != null && rowKeyFor(row) === props.activeRowKey) return "bg-primary/[0.06]";
499
+ return props.rowClass?.(row) || "bg-gray-50";
500
+ }
279
501
  function defaultCell(col, row) {
280
502
  const v = col.sortGetter ? col.sortGetter(row) : row[col.key];
281
503
  if (v == null) return "\u2014";
@@ -329,13 +551,16 @@ function defaultCell(col, row) {
329
551
  variant="outline"
330
552
  size="sm"
331
553
  class="h-7 shrink-0 gap-1.5 text-sm"
332
- :class="railOpen ? 'border-primary/30 text-primary' : ''"
554
+ :class="[
555
+ railOpen ? 'border-primary/30 text-primary' : '',
556
+ railLabel ? '' : 'w-7 px-0 justify-center'
557
+ ]"
333
558
  :aria-pressed="railOpen"
334
- :aria-label="railOpen ? t('shared.datatable.toolbar.rail.aria.hide', { label: railLabel }) : t('shared.datatable.toolbar.rail.aria.show', { label: railLabel })"
559
+ :aria-label="railLabel ? railOpen ? t('shared.datatable.toolbar.rail.aria.hide', { label: railLabel }) : t('shared.datatable.toolbar.rail.aria.show', { label: railLabel }) : railOpen ? t('shared.datatable.toolbar.rail.aria.hideUnnamed') : t('shared.datatable.toolbar.rail.aria.showUnnamed')"
335
560
  @click="railOpen = !railOpen"
336
561
  >
337
562
  <SidebarMinimalistic :size="14" />
338
- {{ railLabel }}
563
+ <template v-if="railLabel">{{ railLabel }}</template>
339
564
  </Button>
340
565
  <!-- Shrinks (never wraps the row) once the toolbar carries scope /
341
566
  view-mode / saved-view controls next to it.
@@ -384,8 +609,20 @@ function defaultCell(col, row) {
384
609
  <Filter :size="14" weight="BoldDuotone" />
385
610
  </template>
386
611
  </FilterPanel>
612
+ <!--
613
+ The one filter control, for a surface that owns its own query.
614
+
615
+ Drawn in the same slot as the other two so a surface never shows two
616
+ filter triggers: `facets` and `filter` are alternatives, and
617
+ `filterTabs` is the in-memory path this replaces.
618
+ -->
619
+ <FacetPanel
620
+ v-if="panelFacets.length"
621
+ v-model="facetSelection"
622
+ :facets="facets"
623
+ />
387
624
  <FilterMenu
388
- v-if="filter"
625
+ v-if="filter && !panelFacets.length"
389
626
  :width="typeof filter === 'object' ? filter.width : void 0"
390
627
  :active="typeof filter === 'object' ? filter.active : void 0"
391
628
  :count="typeof filter === 'object' ? filter.count : void 0"
@@ -422,23 +659,51 @@ function defaultCell(col, row) {
422
659
  <EyeClosed :size="16" weight="Linear" />
423
660
  </template>
424
661
  </ColumnPicker>
662
+ <!--
663
+ Saved views, beside the columns picker rather than out past the market
664
+ scope and the view-mode switch.
665
+
666
+ It belongs with what it saves. Filters, sort and columns are the three
667
+ things a view is made of, so the control that names one sits at the end
668
+ of that group — not after the market scope, which is not part of a
669
+ view, or the view-mode switch, which is about what you are looking at.
670
+ -->
671
+ <SavedViewsMenu v-if="savedViews" :config="savedViews" />
425
672
  <!-- Market scope — the surface passes the shared MarketScopeToggle in;
426
673
  the row position is fixed here. -->
427
674
  <slot name="scope" />
428
675
  <!-- The editor's device switch, which is the right shape wherever the
429
676
  choice changes what you are LOOKING AT rather than which rows. -->
430
677
  <SegmentedToggle v-if="viewModes?.length" v-model="viewMode" variant="inset">
678
+ <!--
679
+ Violet on hover, and violet while it is the one in effect — the
680
+ same language the filter trigger, the column picker and the saved
681
+ views speak two controls to the left. The track's own variant only
682
+ lifts the active segment onto a white pill, which says WHICH is
683
+ picked but not that the row is a set of live controls.
684
+
685
+ The hover is written against `data-[state=off]` and marked
686
+ important because the design system's own toggle already ships
687
+ `data-[state=off]:hover:text-foreground`. A plain `hover:` loses to
688
+ it on specificity, and matching the specificity alone would leave
689
+ the winner to whatever order Tailwind happened to emit the two
690
+ utilities in — a coin toss that reads as a bug the day it lands the
691
+ other way. Delete the `!` if the toggle's own hover ever goes violet.
692
+
693
+ `aria-label` because these are icon-only: the mode still has to
694
+ announce itself.
695
+ -->
431
696
  <SegmentedToggleItem
432
697
  v-for="m in viewModes"
433
698
  :key="m.value"
434
699
  :value="m.value"
435
700
  :aria-label="m.label"
701
+ class="transition-colors data-[state=on]:text-primary data-[state=off]:hover:text-primary!"
436
702
  >
437
703
  <component :is="m.icon" v-if="m.icon" :size="14" weight="Bold" />
438
704
  <template v-if="!m.icon">{{ m.label }}</template>
439
705
  </SegmentedToggleItem>
440
706
  </SegmentedToggle>
441
- <SavedViewsMenu v-if="savedViews" :config="savedViews" />
442
707
  <slot name="toolbar-left" />
443
708
  </div>
444
709
  <div class="flex items-center gap-2 ml-auto shrink-0">
@@ -447,15 +712,40 @@ function defaultCell(col, row) {
447
712
  </div>
448
713
  </div>
449
714
 
715
+ <!--
716
+ What is filtering right now. Not optional beside a paned panel: with it
717
+ closed, a count on an icon says that something narrows the list and never
718
+ what.
719
+ -->
720
+ <FacetChips
721
+ v-if="facets.length"
722
+ v-model="facetSelection"
723
+ :facets="facets"
724
+ :show-facet="chipFacetLabels"
725
+ />
726
+
450
727
  <!-- Alternate view mode (gallery, board, …) — same toolbar, same footer. -->
451
728
  <div v-if="!showTable" class="border-t border-black/[0.04] bg-white/55">
452
729
  <slot name="view" :rows="displayedRows" :loading="loading" />
453
730
  </div>
454
731
 
455
- <!-- The header and the rows share one box so their columns cannot drift.
456
- `display: contents` when scrolling is off makes this element vanish
457
- from layout entirely, so every table that has not asked for it renders
458
- exactly as it did.
732
+ <!-- Two boxes: one that scrolls, and the table itself.
733
+
734
+ The table is ONE GRID and the rows are subgrids of it (`SUBGRID_ROW`),
735
+ which is what keeps a heading over its own column. It used to be a grid
736
+ per row, and a content-sized track then measured itself separately in
737
+ each one — see `SUBGRID_ROW` for what that did to every list with row
738
+ actions.
739
+
740
+ The scrolling is the outer box's job, and it has to be, because the
741
+ grid carries `min-w-min`: a box that grows to its own content cannot
742
+ also be the thing that scrolls it. The panel around them clips
743
+ (`overflow-hidden`, for the rounded corners), so without this a table
744
+ whose declared widths outgrow the panel simply lost the excess.
745
+
746
+ The inner element used to be `display: contents` when scrolling was
747
+ off, which made it vanish from layout — and a box that is not in the
748
+ layout can be neither a grid nor a scroller.
459
749
 
460
750
  IT IS ALSO THE TABLE. The box that holds the header and the rows and
461
751
  nothing else is the only element in this component that can carry the
@@ -470,20 +760,33 @@ function defaultCell(col, row) {
470
760
  that. A static table read with a screen reader's own table commands is
471
761
  what these lists actually are, and both roles announce the column
472
762
  heading with the cell, which is the thing that was missing. -->
763
+ <div :class="scrollBody ? 'relative flex min-h-0 flex-col' : ''">
473
764
  <div
474
- :role="showTable ? 'table' : void 0"
475
- :aria-busy="showTable && loading ? 'true' : void 0"
476
- :class="scrollBody ? 'min-h-0 overflow-y-auto' : 'contents'"
765
+ ref="scrollEl"
766
+ :class="scrollBody ? 'rx-rows min-h-0 overflow-auto' : 'overflow-x-auto'"
477
767
  :style="scrollStyle"
768
+ @scroll="onRowsScroll"
478
769
  >
770
+ <div
771
+ :role="showTable ? 'table' : void 0"
772
+ :aria-busy="showTable && loading ? 'true' : void 0"
773
+ :class="showTable ? 'grid min-w-min gap-x-2' : ''"
774
+ :style="showTable ? gridTemplate : void 0"
775
+ >
479
776
  <!-- Header row -------------------------------------------------- -->
480
777
  <div
481
778
  v-if="showTable"
779
+ ref="headerRowEl"
482
780
  role="row"
483
- 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"
781
+ class="grid items-center gap-x-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"
484
782
  :class="scrollBody ? 'sticky top-0 z-10' : ''"
485
- :style="gridTemplate"
783
+ :style="SUBGRID_ROW"
486
784
  >
785
+ <!-- The handle column's heading: nothing to see, but a columnheader the
786
+ handles' cells can be named by. -->
787
+ <div v-if="reorderable" role="columnheader" class="w-6">
788
+ <span class="sr-only">{{ t("shared.datatable.reorder.column") }}</span>
789
+ </div>
487
790
  <!-- `columnheader` is what names every cell underneath it, so this is
488
791
  the half of the fix that does the work. `aria-sort` goes with it:
489
792
  a heading that carries a sort button and says nothing about the
@@ -494,7 +797,9 @@ function defaultCell(col, row) {
494
797
  :key="col.key"
495
798
  role="columnheader"
496
799
  :aria-sort="ariaSortFor(col)"
497
- :class="col.alignRight ? 'text-right' : ''"
800
+ :title="col.label || void 0"
801
+ class="min-w-0"
802
+ :class="col.alignRight ? RIGHT_ALIGNED : ''"
498
803
  >
499
804
  <!-- `#header-<key>` lets a page take over a header cell (e.g. a
500
805
  select-all checkbox in a selection column). Default: sortable
@@ -514,7 +819,7 @@ function defaultCell(col, row) {
514
819
  the row's uppercase. Without it a column reads "LAST CHECK"
515
820
  beside "Channel" and "Provider" for no reason a reader can see —
516
821
  the header shouts wherever a column happens not to be sortable. -->
517
- <span v-else class="normal-case">{{ col.label }}</span>
822
+ <span v-else class="block truncate normal-case">{{ col.label }}</span>
518
823
  </slot>
519
824
  </div>
520
825
  </div>
@@ -527,9 +832,12 @@ function defaultCell(col, row) {
527
832
  <div
528
833
  v-if="entry.kind === 'header'"
529
834
  role="row"
530
- class="border-t border-black/[0.04] bg-gray-100/70 px-4 py-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground"
835
+ class="col-[1/-1] border-t border-black/[0.04] bg-gray-100/70 px-4 py-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground"
531
836
  >
532
- <div role="cell" :aria-colspan="visibleColumns.length" class="flex items-center gap-2">
837
+ <!-- Spans every column like the rows do, so a table wide enough to
838
+ scroll does not show this band stopping short of the rows it
839
+ groups. No subgrid: it holds one cell, not a set of columns. -->
840
+ <div role="cell" :aria-colspan="renderColumns.length" class="flex items-center gap-2">
533
841
  {{ entry.label }}
534
842
  <span class="tabular-nums normal-case text-muted-foreground/70">{{ entry.count }}</span>
535
843
  </div>
@@ -537,16 +845,40 @@ function defaultCell(col, row) {
537
845
  <div
538
846
  v-else
539
847
  role="row"
540
- class="group grid items-center gap-2 border-t border-black/[0.04] px-4 py-3 cursor-pointer bg-gray-50 transition-colors"
541
- :style="gridTemplate"
848
+ class="group grid items-center gap-x-2 border-t border-black/[0.04] px-4 py-3 cursor-pointer transition-colors"
849
+ :class="[rowGround(entry.row), dropClass(entry.row), dragKey != null && dragKey === rowKeyFor(entry.row) ? 'opacity-50' : '']"
850
+ :aria-current="activeRowKey != null && rowKeyFor(entry.row) === activeRowKey ? 'true' : void 0"
851
+ :style="SUBGRID_ROW"
542
852
  @click="emit('rowClick', entry.row)"
853
+ @dragover="onRowDragOver($event, entry.row)"
854
+ @drop="onRowDrop($event, entry.row)"
543
855
  >
856
+ <!-- The grip. A button, so it is reachable and the arrow keys can move
857
+ the row; `draggable` on it rather than on the row, so selecting a
858
+ cell's text or clicking an inline input never starts a drag. -->
859
+ <div v-if="reorderable" role="cell" class="flex items-center" @click.stop>
860
+ <button
861
+ type="button"
862
+ :data-move-key="String(rowKeyFor(entry.row))"
863
+ :draggable="!reorderBlocked"
864
+ :aria-disabled="reorderBlocked ? 'true' : void 0"
865
+ :aria-label="t('shared.datatable.reorder.handle')"
866
+ :title="reorderBlocked ?? t('shared.datatable.reorder.hint')"
867
+ class="inline-flex size-6 items-center justify-center rounded-md text-muted-foreground transition-colors"
868
+ :class="reorderBlocked ? 'cursor-not-allowed opacity-30' : 'cursor-grab hover:bg-muted hover:text-primary active:cursor-grabbing'"
869
+ @dragstart="onHandleDragStart($event, entry.row)"
870
+ @dragend="finishDrag"
871
+ @keydown="onHandleKeydown($event, entry.row)"
872
+ >
873
+ <DragHandle :size="14" weight="Linear" />
874
+ </button>
875
+ </div>
544
876
  <div
545
877
  v-for="col in visibleColumns"
546
878
  :key="col.key"
547
879
  role="cell"
548
880
  class="min-w-0"
549
- :class="col.alignRight ? 'text-right' : ''"
881
+ :class="col.alignRight ? RIGHT_ALIGNED : ''"
550
882
  >
551
883
  <slot :name="`cell-${col.key}`" :row="entry.row">
552
884
  <!-- Primary column: bold record anchor + optional tinted icon tile. -->
@@ -578,11 +910,11 @@ function defaultCell(col, row) {
578
910
  v-for="n in 6"
579
911
  :key="`sk-${n}`"
580
912
  aria-hidden="true"
581
- class="grid items-center gap-2 border-t border-black/[0.04] bg-gray-50 px-4 py-3"
582
- :style="gridTemplate"
913
+ class="grid items-center gap-x-2 border-t border-black/[0.04] bg-gray-50 px-4 py-3"
914
+ :style="SUBGRID_ROW"
583
915
  >
584
916
  <div
585
- v-for="col in visibleColumns"
917
+ v-for="col in renderColumns"
586
918
  :key="col.key"
587
919
  :class="col.alignRight ? 'flex justify-end' : ''"
588
920
  >
@@ -598,21 +930,50 @@ function defaultCell(col, row) {
598
930
  <div
599
931
  v-if="showTable && !displayedRows.length && !loading"
600
932
  role="row"
601
- class="px-3 py-8 text-center text-sm text-muted-foreground"
933
+ class="col-[1/-1] px-3 py-8 text-center text-sm text-muted-foreground"
602
934
  >
603
- <div role="cell" :aria-colspan="visibleColumns.length">
604
- <slot name="empty">
935
+ <div role="cell" :aria-colspan="renderColumns.length">
936
+ <slot name="empty" :query="query" :filters="activeNarrowing" :clear="clearNarrowing">
605
937
  {{ emptyText ?? t("shared.datatable.empty.default") }}
606
938
  </slot>
607
939
  </div>
608
940
  </div>
609
941
 
942
+ </div>
610
943
  </div>
611
944
 
945
+ <!-- The rows' scrollbar ---------------------------------------- -->
946
+ <!-- Drawn, not borrowed. A browser's own bar belongs to the scrolling
947
+ box, and the column head is inside that box — it has to be, because
948
+ the head and the rows are one grid and a `max-content` track resolves
949
+ per grid (PE-610). So the native bar runs beside the head, which is
950
+ not where a bar over the ROWS belongs. This one starts under the
951
+ head, ends at the footer, and drives the same native scrolling. -->
952
+ <div
953
+ v-if="scrollBody && rowsOverflow"
954
+ class="pointer-events-none absolute right-0 z-20 w-3.5"
955
+ :style="{ top: `${headerHeight}px`, bottom: '0px' }"
956
+ >
957
+ <div
958
+ class="rx-thumb pointer-events-auto absolute right-[3px] w-2 cursor-default rounded-full bg-[rgb(11_5_34_/_0.18)] transition-colors hover:bg-[rgb(11_5_34_/_0.32)]"
959
+ :style="{ height: `${thumbHeight}px`, transform: `translateY(${thumbOffset}px)` }"
960
+ @pointerdown="startThumbDrag"
961
+ />
962
+ </div>
963
+ </div>
964
+
965
+ <!-- Result count, spoken. Outside the table role: a live region is not
966
+ a row, and the table may own rows only. -->
967
+ <span role="status" class="sr-only">{{ liveStatus }}</span>
968
+ <!-- A move, said once: a reader who stepped a row with the arrow keys
969
+ hears where it landed. -->
970
+ <span v-if="reorderable" role="status" class="sr-only">{{ moveStatus }}</span>
971
+
612
972
  <!-- Footer ------------------------------------------------------- -->
613
973
  <div
614
974
  v-if="showFooter"
615
- class="flex items-center justify-between gap-2 border-t border-gray-200 bg-white/55 backdrop-blur-md px-4 py-3 text-sm text-muted-foreground/60"
975
+ ref="footerEl"
976
+ class="flex items-center justify-between gap-2 border-t border-gray-200 bg-white/55 backdrop-blur-md px-4 py-3 text-sm text-muted-foreground"
616
977
  >
617
978
  <div class="flex items-center gap-2.5">
618
979
  <span class="tabular-nums">
@@ -634,7 +995,7 @@ function defaultCell(col, row) {
634
995
  <Button
635
996
  variant="outline"
636
997
  size="sm"
637
- class="h-7 text-sm"
998
+ class="h-7 text-sm disabled:opacity-100 disabled:text-muted-foreground"
638
999
  :disabled="page === 0 || loading"
639
1000
  @click="prevPage"
640
1001
  >
@@ -646,7 +1007,7 @@ function defaultCell(col, row) {
646
1007
  <Button
647
1008
  variant="outline"
648
1009
  size="sm"
649
- class="h-7 text-sm"
1010
+ class="h-7 text-sm disabled:opacity-100 disabled:text-muted-foreground"
650
1011
  :disabled="page >= lastPage || loading"
651
1012
  @click="nextPage"
652
1013
  >
@@ -656,3 +1017,7 @@ function defaultCell(col, row) {
656
1017
  </div>
657
1018
  </div>
658
1019
  </template>
1020
+
1021
+ <style scoped>
1022
+ .rx-rows::-webkit-scrollbar{height:10px;width:0}.rx-rows::-webkit-scrollbar-track{background:transparent}.rx-rows::-webkit-scrollbar-thumb{background:rgba(11,5,34,.18);background-clip:padding-box;border:3px solid transparent;border-radius:9999px}.rx-thumb{min-height:32px}
1023
+ </style>