nuxt-ui-tools 1.6.5 → 1.8.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 (185) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +20 -0
  3. package/dist/runtime/dashboard/components/block/dashboard-skeleton.vue +12 -0
  4. package/dist/runtime/dashboard/components/dashboard-details.d.vue.ts +38 -0
  5. package/dist/runtime/dashboard/components/dashboard-details.vue +194 -0
  6. package/dist/runtime/dashboard/components/dashboard-details.vue.d.ts +38 -0
  7. package/dist/runtime/dashboard/components/dashboard-page.d.vue.ts +8 -0
  8. package/dist/runtime/dashboard/components/dashboard-page.vue +20 -3
  9. package/dist/runtime/dashboard/components/dashboard-page.vue.d.ts +8 -0
  10. package/dist/runtime/dashboard/components/dashboard-view-tabs.d.vue.ts +2 -5
  11. package/dist/runtime/dashboard/components/dashboard-view-tabs.vue +5 -1
  12. package/dist/runtime/dashboard/components/dashboard-view-tabs.vue.d.ts +2 -5
  13. package/dist/runtime/dashboard/composables/use-dashboard-api.js +4 -1
  14. package/dist/runtime/dashboard/composables/use-dashboard-views.d.ts +4 -1
  15. package/dist/runtime/dashboard/composables/use-dashboard-views.js +15 -1
  16. package/dist/runtime/dashboard/schema/index.d.ts +15 -1
  17. package/dist/runtime/dashboard/types/blocks.d.ts +27 -1
  18. package/dist/runtime/dashboard/types/runtime.d.ts +3 -0
  19. package/dist/runtime/dashboard/types/schema.d.ts +23 -4
  20. package/dist/runtime/dashboard/types/ui.d.ts +27 -0
  21. package/dist/runtime/dashboard/utils/schema.js +1 -0
  22. package/dist/runtime/file-preview/components/provider/file-preview-host.d.vue.ts +7 -0
  23. package/dist/runtime/file-preview/components/provider/file-preview-host.vue +147 -0
  24. package/dist/runtime/file-preview/components/provider/file-preview-host.vue.d.ts +7 -0
  25. package/dist/runtime/file-preview/components/provider/file-preview-provider.d.vue.ts +21 -0
  26. package/dist/runtime/file-preview/components/provider/file-preview-provider.vue +23 -0
  27. package/dist/runtime/file-preview/components/provider/file-preview-provider.vue.d.ts +21 -0
  28. package/dist/runtime/file-preview/components/renderers/audio-renderer.d.vue.ts +10 -0
  29. package/dist/runtime/file-preview/components/renderers/audio-renderer.vue +83 -0
  30. package/dist/runtime/file-preview/components/renderers/audio-renderer.vue.d.ts +10 -0
  31. package/dist/runtime/file-preview/components/renderers/csv-renderer.d.vue.ts +10 -0
  32. package/dist/runtime/file-preview/components/renderers/csv-renderer.vue +116 -0
  33. package/dist/runtime/file-preview/components/renderers/csv-renderer.vue.d.ts +10 -0
  34. package/dist/runtime/file-preview/components/renderers/fallback-renderer.d.vue.ts +10 -0
  35. package/dist/runtime/file-preview/components/renderers/fallback-renderer.vue +41 -0
  36. package/dist/runtime/file-preview/components/renderers/fallback-renderer.vue.d.ts +10 -0
  37. package/dist/runtime/file-preview/components/renderers/file-preview-code.d.vue.ts +9 -0
  38. package/dist/runtime/file-preview/components/renderers/file-preview-code.vue +83 -0
  39. package/dist/runtime/file-preview/components/renderers/file-preview-code.vue.d.ts +9 -0
  40. package/dist/runtime/file-preview/components/renderers/file-preview-text-loading.d.vue.ts +3 -0
  41. package/dist/runtime/file-preview/components/renderers/file-preview-text-loading.vue +14 -0
  42. package/dist/runtime/file-preview/components/renderers/file-preview-text-loading.vue.d.ts +3 -0
  43. package/dist/runtime/file-preview/components/renderers/image-renderer.d.vue.ts +10 -0
  44. package/dist/runtime/file-preview/components/renderers/image-renderer.vue +182 -0
  45. package/dist/runtime/file-preview/components/renderers/image-renderer.vue.d.ts +10 -0
  46. package/dist/runtime/file-preview/components/renderers/markdown-renderer.d.vue.ts +10 -0
  47. package/dist/runtime/file-preview/components/renderers/markdown-renderer.vue +78 -0
  48. package/dist/runtime/file-preview/components/renderers/markdown-renderer.vue.d.ts +10 -0
  49. package/dist/runtime/file-preview/components/renderers/media-controls.d.vue.ts +10 -0
  50. package/dist/runtime/file-preview/components/renderers/media-controls.vue +145 -0
  51. package/dist/runtime/file-preview/components/renderers/media-controls.vue.d.ts +10 -0
  52. package/dist/runtime/file-preview/components/renderers/pdf-renderer.d.vue.ts +10 -0
  53. package/dist/runtime/file-preview/components/renderers/pdf-renderer.vue +58 -0
  54. package/dist/runtime/file-preview/components/renderers/pdf-renderer.vue.d.ts +10 -0
  55. package/dist/runtime/file-preview/components/renderers/text-renderer.d.vue.ts +10 -0
  56. package/dist/runtime/file-preview/components/renderers/text-renderer.vue +41 -0
  57. package/dist/runtime/file-preview/components/renderers/text-renderer.vue.d.ts +10 -0
  58. package/dist/runtime/file-preview/components/renderers/video-renderer.d.vue.ts +10 -0
  59. package/dist/runtime/file-preview/components/renderers/video-renderer.vue +110 -0
  60. package/dist/runtime/file-preview/components/renderers/video-renderer.vue.d.ts +10 -0
  61. package/dist/runtime/file-preview/components/shell/file-preview-button.d.vue.ts +16 -0
  62. package/dist/runtime/file-preview/components/shell/file-preview-button.vue +40 -0
  63. package/dist/runtime/file-preview/components/shell/file-preview-button.vue.d.ts +16 -0
  64. package/dist/runtime/{form/components/renderer/form-field-error.d.vue.ts → file-preview/components/shell/file-preview-details.d.vue.ts} +4 -2
  65. package/dist/runtime/file-preview/components/shell/file-preview-details.vue +25 -0
  66. package/dist/runtime/{form/components/renderer/form-field-error.vue.d.ts → file-preview/components/shell/file-preview-details.vue.d.ts} +4 -2
  67. package/dist/runtime/file-preview/components/shell/file-preview-header.d.vue.ts +44 -0
  68. package/dist/runtime/file-preview/components/shell/file-preview-header.vue +163 -0
  69. package/dist/runtime/file-preview/components/shell/file-preview-header.vue.d.ts +44 -0
  70. package/dist/runtime/file-preview/components/shell/file-preview-message.d.vue.ts +21 -0
  71. package/dist/runtime/file-preview/components/shell/file-preview-message.vue +49 -0
  72. package/dist/runtime/file-preview/components/shell/file-preview-message.vue.d.ts +21 -0
  73. package/dist/runtime/file-preview/components/shell/file-preview-shell.d.vue.ts +17 -0
  74. package/dist/runtime/file-preview/components/shell/file-preview-shell.vue +280 -0
  75. package/dist/runtime/file-preview/components/shell/file-preview-shell.vue.d.ts +17 -0
  76. package/dist/runtime/file-preview/components/shell/file-preview-stage.d.vue.ts +37 -0
  77. package/dist/runtime/file-preview/components/shell/file-preview-stage.vue +173 -0
  78. package/dist/runtime/file-preview/components/shell/file-preview-stage.vue.d.ts +37 -0
  79. package/dist/runtime/file-preview/components/shell/file-preview-thumb.d.vue.ts +14 -0
  80. package/dist/runtime/file-preview/components/shell/file-preview-thumb.vue +69 -0
  81. package/dist/runtime/file-preview/components/shell/file-preview-thumb.vue.d.ts +14 -0
  82. package/dist/runtime/file-preview/components/shell/file-preview-tools.d.vue.ts +13 -0
  83. package/dist/runtime/file-preview/components/shell/file-preview-tools.vue +10 -0
  84. package/dist/runtime/file-preview/components/shell/file-preview-tools.vue.d.ts +13 -0
  85. package/dist/runtime/file-preview/composables/use-file-preview-api.d.ts +21 -0
  86. package/dist/runtime/file-preview/composables/use-file-preview-api.js +83 -0
  87. package/dist/runtime/file-preview/composables/use-file-preview-shell.d.ts +13 -0
  88. package/dist/runtime/file-preview/composables/use-file-preview-shell.js +30 -0
  89. package/dist/runtime/file-preview/composables/use-file-preview-source.d.ts +17 -0
  90. package/dist/runtime/file-preview/composables/use-file-preview-source.js +51 -0
  91. package/dist/runtime/file-preview/composables/use-file-preview-text.d.ts +14 -0
  92. package/dist/runtime/file-preview/composables/use-file-preview-text.js +79 -0
  93. package/dist/runtime/file-preview/index.d.ts +5 -0
  94. package/dist/runtime/file-preview/index.js +14 -0
  95. package/dist/runtime/file-preview/plugins/file-preview-api.d.ts +11 -0
  96. package/dist/runtime/file-preview/plugins/file-preview-api.js +10 -0
  97. package/dist/runtime/file-preview/types/api.d.ts +74 -0
  98. package/dist/runtime/file-preview/types/api.js +0 -0
  99. package/dist/runtime/file-preview/types/file.d.ts +77 -0
  100. package/dist/runtime/file-preview/types/file.js +0 -0
  101. package/dist/runtime/file-preview/types/index.d.ts +4 -0
  102. package/dist/runtime/file-preview/types/index.js +0 -0
  103. package/dist/runtime/file-preview/types/renderer.d.ts +49 -0
  104. package/dist/runtime/file-preview/types/renderer.js +0 -0
  105. package/dist/runtime/file-preview/types/shell.d.ts +12 -0
  106. package/dist/runtime/file-preview/types/shell.js +0 -0
  107. package/dist/runtime/file-preview/utils/container.d.ts +4 -0
  108. package/dist/runtime/file-preview/utils/container.js +10 -0
  109. package/dist/runtime/file-preview/utils/csv.d.ts +17 -0
  110. package/dist/runtime/file-preview/utils/csv.js +53 -0
  111. package/dist/runtime/file-preview/utils/download.d.ts +8 -0
  112. package/dist/runtime/file-preview/utils/download.js +40 -0
  113. package/dist/runtime/file-preview/utils/files.d.ts +7 -0
  114. package/dist/runtime/file-preview/utils/files.js +53 -0
  115. package/dist/runtime/file-preview/utils/format.d.ts +13 -0
  116. package/dist/runtime/file-preview/utils/format.js +40 -0
  117. package/dist/runtime/file-preview/utils/instance.d.ts +9 -0
  118. package/dist/runtime/file-preview/utils/instance.js +57 -0
  119. package/dist/runtime/file-preview/utils/renderers.d.ts +36 -0
  120. package/dist/runtime/file-preview/utils/renderers.js +247 -0
  121. package/dist/runtime/form/components/page/form-page-header.d.vue.ts +3 -1
  122. package/dist/runtime/form/components/page/form-page-header.vue +4 -4
  123. package/dist/runtime/form/components/page/form-page-header.vue.d.ts +3 -1
  124. package/dist/runtime/form/components/page/form-page-navigation.vue +2 -1
  125. package/dist/runtime/form/components/page/form-page-section.vue +15 -7
  126. package/dist/runtime/form/components/page/form-page.vue +0 -1
  127. package/dist/runtime/form/components/utils/form-file-preview.vue +5 -38
  128. package/dist/runtime/form/composables/use-field-control.d.ts +1 -1
  129. package/dist/runtime/form/composables/use-form-api.d.ts +8 -0
  130. package/dist/runtime/form/composables/use-form-api.js +4 -4
  131. package/dist/runtime/form/composables/use-form-page.js +3 -1
  132. package/dist/runtime/form/composables/use-form-runtime.js +3 -0
  133. package/dist/runtime/form/composables/use-form-upload-registry.d.ts +2 -0
  134. package/dist/runtime/form/composables/use-form-upload-registry.js +11 -6
  135. package/dist/runtime/form/composables/use-form-validation.js +5 -1
  136. package/dist/runtime/form/fields/array-list/use-array-items.d.ts +1 -1
  137. package/dist/runtime/form/fields/array-table/array-table-cell.d.vue.ts +15 -0
  138. package/dist/runtime/form/fields/array-table/array-table-cell.vue +58 -0
  139. package/dist/runtime/form/fields/array-table/array-table-cell.vue.d.ts +15 -0
  140. package/dist/runtime/form/fields/array-table/component.vue +42 -26
  141. package/dist/runtime/form/fields/choice-card/use-choice-card.js +4 -1
  142. package/dist/runtime/form/fields/date/types.d.ts +6 -1
  143. package/dist/runtime/form/fields/date-family/component.vue +7 -1
  144. package/dist/runtime/form/fields/date-family/utils.d.ts +8 -1
  145. package/dist/runtime/form/fields/date-family/utils.js +10 -2
  146. package/dist/runtime/form/fields/hidden/types.d.ts +8 -1
  147. package/dist/runtime/form/fields/upload/component.vue +55 -176
  148. package/dist/runtime/form/fields/upload/types.d.ts +68 -3
  149. package/dist/runtime/form/fields/upload/upload-item.d.vue.ts +24 -0
  150. package/dist/runtime/form/fields/upload/upload-item.vue +158 -0
  151. package/dist/runtime/form/fields/upload/upload-item.vue.d.ts +24 -0
  152. package/dist/runtime/form/fields/upload/use-upload-items.d.ts +44 -0
  153. package/dist/runtime/form/fields/upload/use-upload-items.js +317 -0
  154. package/dist/runtime/form/plugins/form-api.d.ts +10 -0
  155. package/dist/runtime/form/plugins/form-api.js +10 -0
  156. package/dist/runtime/form/types/callbacks.d.ts +8 -2
  157. package/dist/runtime/form/types/controller.d.ts +6 -3
  158. package/dist/runtime/form/types/field-base.d.ts +5 -5
  159. package/dist/runtime/form/types/field-output-utils.d.ts +12 -1
  160. package/dist/runtime/form/types/field-output.d.ts +1 -1
  161. package/dist/runtime/form/types/field.d.ts +3 -2
  162. package/dist/runtime/form/types/form-api.d.ts +4 -2
  163. package/dist/runtime/form/types/options-runtime.d.ts +8 -4
  164. package/dist/runtime/form/types/options.d.ts +11 -11
  165. package/dist/runtime/form/types/output.d.ts +33 -10
  166. package/dist/runtime/form/types/page.d.ts +7 -3
  167. package/dist/runtime/form/types/transform.d.ts +5 -2
  168. package/dist/runtime/form/types/ui.d.ts +7 -3
  169. package/dist/runtime/form/types/upload-runtime.d.ts +4 -0
  170. package/dist/runtime/form/utils/page.js +24 -5
  171. package/dist/runtime/form/utils/state.js +14 -11
  172. package/dist/runtime/i18n/locales/en.js +108 -1
  173. package/dist/runtime/i18n/locales/fr.js +108 -1
  174. package/dist/runtime/i18n/types.d.ts +127 -0
  175. package/dist/runtime/query-state/composables.d.ts +2 -2
  176. package/dist/runtime/shared/styles/tokens.css +1 -1
  177. package/dist/runtime/shared/types/utils.d.ts +12 -4
  178. package/dist/runtime/shared/utils/file.d.ts +11 -0
  179. package/dist/runtime/shared/utils/file.js +50 -0
  180. package/dist/runtime/table/components/table/table-renderer.vue +23 -3
  181. package/dist/runtime/table/components/utils/search-query-input.d.vue.ts +5 -1
  182. package/dist/runtime/table/components/utils/search-query-input.vue +17 -3
  183. package/dist/runtime/table/components/utils/search-query-input.vue.d.ts +5 -1
  184. package/package.json +56 -29
  185. package/dist/runtime/form/components/renderer/form-field-error.vue +0 -16
package/dist/module.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "configKey": "nuxtUiTools",
6
6
  "docs": "https://ui.nuxt.com/docs/getting-started/installation/nuxt",
7
7
  "name": "nuxt-ui-tools",
8
- "version": "1.6.5",
8
+ "version": "1.8.0",
9
9
  "builder": {
10
10
  "@nuxt/module-builder": "1.0.3",
11
11
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -55,6 +55,7 @@ function getPublicComponents(runtimeDir, options) {
55
55
  "ViewTabs",
56
56
  "Stat",
57
57
  "Stats",
58
+ "Details",
58
59
  "Gauge",
59
60
  "Tabs",
60
61
  "Refresh",
@@ -80,6 +81,17 @@ function getPublicComponents(runtimeDir, options) {
80
81
  global: options.global,
81
82
  name: `${prefix}Dashboard${part}`
82
83
  })),
84
+ // File preview
85
+ {
86
+ filePath: `${runtimeDir}/file-preview/components/provider/file-preview-provider.vue`,
87
+ global: options.global,
88
+ name: `${prefix}FilePreviewProvider`
89
+ },
90
+ {
91
+ filePath: `${runtimeDir}/file-preview/components/shell/file-preview-tools.vue`,
92
+ global: options.global,
93
+ name: `${prefix}FilePreviewTools`
94
+ },
83
95
  // Form
84
96
  {
85
97
  filePath: `${runtimeDir}/form/components/root/form.vue`,
@@ -172,6 +184,11 @@ function setupImports(runtimeDir) {
172
184
  { from: "dashboard", name: "useDashboardView" },
173
185
  { from: "dashboard", name: "resolveDashboardComparisonRange" }
174
186
  ]),
187
+ // File preview
188
+ ...withRuntime(runtimeDir, [
189
+ { from: "file-preview", name: "defineFilePreviewRenderer" },
190
+ { from: "file-preview", name: "useFilePreview" }
191
+ ]),
175
192
  // Form
176
193
  ...withRuntime(runtimeDir, [
177
194
  { from: "form", name: "defineFormField" },
@@ -354,6 +371,7 @@ const viewportDefaults = {
354
371
  };
355
372
  const publicRuntimeDomains = [
356
373
  "dashboard",
374
+ "file-preview",
357
375
  "form",
358
376
  "i18n",
359
377
  "query-prefetch",
@@ -423,6 +441,8 @@ const module$1 = defineNuxtModule({
423
441
  setupImports(resolve("./runtime"));
424
442
  setupComponents(resolve("./runtime"), options);
425
443
  addPlugin(resolve("./runtime/query-prefetch/plugins/link-prefetch.client"));
444
+ addPlugin(resolve("./runtime/form/plugins/form-api"));
445
+ addPlugin(resolve("./runtime/file-preview/plugins/file-preview-api"));
426
446
  addVitePlugin({
427
447
  enforce: "pre",
428
448
  name: "nuxt-ui-tools:query-prefetch-macro",
@@ -196,6 +196,18 @@ const funnelWidths = computed(
196
196
  </div>
197
197
  </div>
198
198
 
199
+ <!-- Details: labels over values in a grid -->
200
+ <div
201
+ v-else-if="kind === 'details'"
202
+ class="grid gap-x-8 gap-y-4"
203
+ :style="{ gridTemplateColumns: `repeat(${Math.max(1, columns)}, minmax(0, 1fr))` }"
204
+ >
205
+ <div v-for="(width, index) in widths" :key="index" class="flex flex-col gap-2">
206
+ <div class="nut-dash-ghost h-2.5 w-[32%] rounded-sm opacity-70" />
207
+ <div class="nut-dash-ghost h-3.5 rounded-sm" :style="{ width: `${width + 12}%` }" />
208
+ </div>
209
+ </div>
210
+
199
211
  <!-- Gauge: an open arc and its scale -->
200
212
  <div v-else-if="kind === 'gauge'" class="flex flex-col items-center gap-3 pt-1">
201
213
  <div
@@ -0,0 +1,38 @@
1
+ import type { DashboardBlockBaseProps, DashboardBlockUi, DashboardDetailsItem, DashboardDetailsUi, DashboardSourceLike } from '../types/index.js';
2
+ declare const __VLS_export: <TData>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
3
+ props: import("vue").PublicProps & __VLS_PrettifyLocal<Omit<DashboardBlockBaseProps, "ui"> & {
4
+ source: DashboardSourceLike<TData>;
5
+ /** Fields read from the source data, in display order. */
6
+ items: readonly DashboardDetailsItem<TData & ({} | null)>[];
7
+ /** Column count, responsive: `"1 md:2 xl:3"`. Defaults to `"1 sm:2"`. */
8
+ columns?: string;
9
+ /** `stacked`: each label over its value. `inline`: the label beside its value, on one row. */
10
+ layout?: "stacked" | "inline";
11
+ ui?: DashboardBlockUi & DashboardDetailsUi;
12
+ }> & (typeof globalThis extends {
13
+ __VLS_PROPS_FALLBACK: infer P;
14
+ } ? P : {});
15
+ expose: (exposed: {}) => void;
16
+ attrs: any;
17
+ slots: {
18
+ [x: `item-${string}`]: ((props: {
19
+ data: TData & ({} | null);
20
+ item: DashboardDetailsItem<TData & ({} | null)>;
21
+ value: string | undefined;
22
+ }) => unknown) | undefined;
23
+ } & {
24
+ 'header-right'?: () => unknown;
25
+ toolbar?: () => unknown;
26
+ footer?: () => unknown;
27
+ };
28
+ emit: {};
29
+ }>) => import("vue").VNode & {
30
+ __ctx?: NonNullable<Awaited<typeof __VLS_setup>>;
31
+ };
32
+ declare const _default: typeof __VLS_export;
33
+ export default _default;
34
+ type __VLS_PrettifyLocal<T> = (T extends any ? {
35
+ [K in keyof T]: T[K];
36
+ } : {
37
+ [K in keyof T as K]: T[K];
38
+ }) & {};
@@ -0,0 +1,194 @@
1
+ <script setup>
2
+ import UButton from "@nuxt/ui/components/Button.vue";
3
+ import { useClipboard } from "@vueuse/core";
4
+ import { computed } from "vue";
5
+ import { useUiToolsLocale } from "#ui-tools/i18n";
6
+ import { useResponsiveValue } from "#ui-tools/shared/composables/use-responsive-value";
7
+ import { isNumber } from "#ui-tools/shared/utils/predicate";
8
+ import { resolveTextValue } from "#ui-tools/shared/utils/render";
9
+ import { useDashboardFormat } from "../composables/use-dashboard-format";
10
+ import { useDashboardUi } from "../composables/use-dashboard-ui";
11
+ import { toDashboardCell } from "../utils/export";
12
+ import { resolveDashboardClasses } from "../utils/ui";
13
+ import DashboardSkeleton from "./block/dashboard-skeleton.vue";
14
+ import DashboardCard from "./dashboard-card.vue";
15
+ const {
16
+ source,
17
+ card = true,
18
+ menu = void 0,
19
+ freshness = void 0,
20
+ items,
21
+ columns = "1 sm:2",
22
+ layout = "stacked",
23
+ ui,
24
+ ...block
25
+ } = defineProps({
26
+ title: { type: [String, Number, Function], required: false },
27
+ subtitle: { type: [String, Number, Function], required: false },
28
+ size: { type: String, required: false },
29
+ card: { type: Boolean, required: false },
30
+ activation: { type: String, required: false },
31
+ empty: { type: Object, required: false },
32
+ menu: { type: [Boolean, Array, Function], required: false },
33
+ actions: { type: Array, required: false },
34
+ filters: { type: Array, required: false },
35
+ freshness: { type: Boolean, required: false },
36
+ source: { type: Object, required: true },
37
+ items: { type: Array, required: true },
38
+ columns: { type: String, required: false },
39
+ layout: { type: String, required: false },
40
+ ui: { type: Object, required: false }
41
+ });
42
+ defineSlots();
43
+ const { t } = useUiToolsLocale();
44
+ const formats = useDashboardFormat();
45
+ const appUi = useDashboardUi();
46
+ const clipboard = useClipboard({ copiedDuring: 1600, legacy: true });
47
+ const layouts = {
48
+ inline: {
49
+ item: "grid grid-cols-[minmax(7rem,38%)_minmax(0,1fr)] items-baseline gap-x-4",
50
+ label: "text-[12.5px]"
51
+ },
52
+ stacked: { item: "flex flex-col gap-1", label: "text-xs" }
53
+ };
54
+ const classes = computed(
55
+ () => resolveDashboardClasses(
56
+ {
57
+ copy: "ms-0.5 -my-1 opacity-0 transition-opacity group-hover/detail:opacity-100 focus-visible:opacity-100 data-[copied]:opacity-100",
58
+ grid: layout === "inline" ? "grid gap-x-8 gap-y-2.5" : "grid gap-x-8 gap-y-4",
59
+ hint: "mt-0.5 text-[11.5px] text-muted",
60
+ item: `group/detail min-w-0 ${layouts[layout].item}`,
61
+ label: `min-w-0 truncate text-muted ${layouts[layout].label}`,
62
+ link: "h-auto min-w-0 p-0 text-[length:inherit] font-normal underline decoration-primary/35 underline-offset-[3px] transition-colors hover:decoration-primary",
63
+ placeholder: "text-dimmed",
64
+ value: "flex min-w-0 items-center gap-1 text-[13.5px] leading-snug break-words text-highlighted"
65
+ },
66
+ appUi.value.details,
67
+ ui
68
+ )
69
+ );
70
+ const gridColumns = useResponsiveValue(() => columns, "grid-cols");
71
+ const entries = computed(() => {
72
+ const data = source.data;
73
+ if (data === void 0 || data === null) return [];
74
+ return items.filter((item) => !item.hidden?.(data)).map((item) => {
75
+ const raw = item.value(data);
76
+ const text = raw === null || raw === void 0 ? void 0 : isNumber(raw) ? formats.resolve(item.format)(raw) : resolveTextValue(raw);
77
+ const value = text === "" ? void 0 : text;
78
+ const hint = item.hint?.(data);
79
+ return {
80
+ data,
81
+ hint: hint === null || hint === void 0 ? void 0 : resolveTextValue(hint),
82
+ item,
83
+ key: item.key,
84
+ label: resolveTextValue(item.label),
85
+ placeholder: item.placeholder === void 0 ? "\u2014" : resolveTextValue(item.placeholder),
86
+ span: item.span === "full" ? "1 / -1" : item.span ? `span ${item.span}` : void 0,
87
+ target: value === void 0 ? void 0 : item.to?.(data) ?? void 0,
88
+ value
89
+ };
90
+ });
91
+ });
92
+ function copy(value) {
93
+ void clipboard.copy(value);
94
+ }
95
+ function tabulate() {
96
+ return {
97
+ columns: [
98
+ { key: "label", label: t("dashboard.table.label"), numeric: false },
99
+ { key: "value", label: t("dashboard.table.value"), numeric: false }
100
+ ],
101
+ rows: entries.value.map((entry) => [
102
+ toDashboardCell(entry.label),
103
+ toDashboardCell(entry.value ?? "")
104
+ ])
105
+ };
106
+ }
107
+ </script>
108
+
109
+ <template>
110
+ <DashboardCard
111
+ v-bind="block"
112
+ :card
113
+ :menu
114
+ :freshness
115
+ :ui
116
+ :source
117
+ :is-empty="entries.length === 0"
118
+ :tabulate
119
+ >
120
+ <template #skeleton>
121
+ <DashboardSkeleton
122
+ kind="details"
123
+ :count="items.length"
124
+ :columns="layout === 'inline' ? 1 : 2"
125
+ />
126
+ </template>
127
+ <template v-if="$slots['header-right']" #header-right>
128
+ <slot name="header-right" />
129
+ </template>
130
+ <template v-if="$slots.toolbar" #toolbar>
131
+ <slot name="toolbar" />
132
+ </template>
133
+ <template v-if="$slots.footer" #footer>
134
+ <slot name="footer" />
135
+ </template>
136
+
137
+ <dl :class="classes.grid" :style="gridColumns ?? void 0" :data-layout="layout">
138
+ <div
139
+ v-for="entry in entries"
140
+ :key="entry.key"
141
+ :class="classes.item"
142
+ :style="entry.span ? { gridColumn: entry.span } : void 0"
143
+ :data-empty="entry.value === void 0 ? '' : void 0"
144
+ data-details-item
145
+ >
146
+ <dt :class="classes.label">{{ entry.label }}</dt>
147
+ <dd class="m-0 min-w-0">
148
+ <div
149
+ :class="[classes.value, entry.item.mono && 'font-mono text-[12.5px] tracking-tight']"
150
+ >
151
+ <slot
152
+ :name="`item-${entry.key}`"
153
+ :data="entry.data"
154
+ :item="entry.item"
155
+ :value="entry.value"
156
+ >
157
+ <span v-if="entry.value === void 0" :class="classes.placeholder">
158
+ {{ entry.placeholder }}
159
+ </span>
160
+ <UButton
161
+ v-else-if="entry.target !== void 0"
162
+ :to="entry.target"
163
+ :label="entry.value"
164
+ color="primary"
165
+ variant="link"
166
+ :class="classes.link"
167
+ data-details-link
168
+ />
169
+ <span v-else class="min-w-0">{{ entry.value }}</span>
170
+ </slot>
171
+ <UButton
172
+ v-if="entry.item.copy && entry.value !== void 0"
173
+ :icon="
174
+ clipboard.copied.value && clipboard.text.value === entry.value ? 'i-lucide-check' : 'i-lucide-copy'
175
+ "
176
+ :aria-label="
177
+ clipboard.copied.value && clipboard.text.value === entry.value ? t('dashboard.details.copied') : t('dashboard.details.copy')
178
+ "
179
+ :data-copied="
180
+ clipboard.copied.value && clipboard.text.value === entry.value || void 0
181
+ "
182
+ color="neutral"
183
+ variant="ghost"
184
+ size="xs"
185
+ :class="classes.copy"
186
+ @click="copy(entry.value)"
187
+ />
188
+ </div>
189
+ <p v-if="entry.hint" :class="classes.hint">{{ entry.hint }}</p>
190
+ </dd>
191
+ </div>
192
+ </dl>
193
+ </DashboardCard>
194
+ </template>
@@ -0,0 +1,38 @@
1
+ import type { DashboardBlockBaseProps, DashboardBlockUi, DashboardDetailsItem, DashboardDetailsUi, DashboardSourceLike } from '../types/index.js';
2
+ declare const __VLS_export: <TData>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
3
+ props: import("vue").PublicProps & __VLS_PrettifyLocal<Omit<DashboardBlockBaseProps, "ui"> & {
4
+ source: DashboardSourceLike<TData>;
5
+ /** Fields read from the source data, in display order. */
6
+ items: readonly DashboardDetailsItem<TData & ({} | null)>[];
7
+ /** Column count, responsive: `"1 md:2 xl:3"`. Defaults to `"1 sm:2"`. */
8
+ columns?: string;
9
+ /** `stacked`: each label over its value. `inline`: the label beside its value, on one row. */
10
+ layout?: "stacked" | "inline";
11
+ ui?: DashboardBlockUi & DashboardDetailsUi;
12
+ }> & (typeof globalThis extends {
13
+ __VLS_PROPS_FALLBACK: infer P;
14
+ } ? P : {});
15
+ expose: (exposed: {}) => void;
16
+ attrs: any;
17
+ slots: {
18
+ [x: `item-${string}`]: ((props: {
19
+ data: TData & ({} | null);
20
+ item: DashboardDetailsItem<TData & ({} | null)>;
21
+ value: string | undefined;
22
+ }) => unknown) | undefined;
23
+ } & {
24
+ 'header-right'?: () => unknown;
25
+ toolbar?: () => unknown;
26
+ footer?: () => unknown;
27
+ };
28
+ emit: {};
29
+ }>) => import("vue").VNode & {
30
+ __ctx?: NonNullable<Awaited<typeof __VLS_setup>>;
31
+ };
32
+ declare const _default: typeof __VLS_export;
33
+ export default _default;
34
+ type __VLS_PrettifyLocal<T> = (T extends any ? {
35
+ [K in keyof T]: T[K];
36
+ } : {
37
+ [K in keyof T as K]: T[K];
38
+ }) & {};
@@ -33,6 +33,14 @@ declare const __VLS_export: <TDashboard extends DashboardPageTarget>(__VLS_props
33
33
  }) => unknown;
34
34
  title?: () => unknown;
35
35
  description?: () => unknown;
36
+ /** Above the title, across the header: breadcrumbs or a back link. */
37
+ eyebrow?: () => unknown;
38
+ /** Before the title and its line: an avatar or a logo, for a page about one entity. */
39
+ leading?: () => unknown;
40
+ /** A full-width row closing the header: key facts of the entity the page is about. */
41
+ details?: () => unknown;
42
+ /** Between the header and the pinned tabs: a note or a notice about the whole page. */
43
+ banner?: () => unknown;
36
44
  /** Replaces the page actions and the refresh control. */
37
45
  actions?: () => unknown;
38
46
  /** Replaces the view tabs. */
@@ -43,9 +43,13 @@ const classes = computed(
43
43
  {
44
44
  actions: "flex min-h-[34px] items-center gap-2",
45
45
  body: "flex flex-col gap-4 px-4 pt-1 pb-12 lg:gap-6 lg:px-7 lg:pb-15",
46
- description: "col-span-full mt-1 text-[12.5px] font-light text-muted md:text-[13.5px]",
46
+ description: "col-span-full mt-1 text-[12.5px] font-light text-muted in-data-[leading]:col-start-2 md:text-[13.5px]",
47
+ banner: "px-4 pt-4 lg:px-7 lg:pt-6",
48
+ details: "col-span-full mt-4 lg:mt-5",
49
+ eyebrow: "col-span-full mb-2 min-w-0",
47
50
  filters: "px-4 py-3 lg:px-7 lg:py-4",
48
- header: "grid grid-cols-[minmax(0,1fr)_auto] items-center gap-x-4 bg-default px-4 pt-4 pb-3.5 lg:px-7 lg:pt-[22px] lg:pb-[18px]",
51
+ header: "grid grid-cols-[minmax(0,1fr)_auto] items-center gap-x-4 bg-default px-4 pt-4 pb-3.5 data-[leading]:grid-cols-[auto_minmax(0,1fr)_auto] lg:px-7 lg:pt-[22px] lg:pb-[18px]",
52
+ leading: "row-span-2 flex items-center self-center",
49
53
  root: "h-full overflow-auto bg-muted",
50
54
  tabs: "bg-default px-2 lg:px-7",
51
55
  title: "m-0 min-w-0 truncate text-2xl leading-[1.1] font-light tracking-[-0.04em] text-highlighted md:text-[30px]",
@@ -102,7 +106,13 @@ function actionUi(action) {
102
106
  <template>
103
107
  <!-- Without a dashboard (its schema threw during setup), render nothing: that error stays the one reported. -->
104
108
  <div v-if="dashboard" :class="classes.root" data-dashboard-page>
105
- <header :class="classes.header">
109
+ <header :class="classes.header" :data-leading="$slots.leading ? '' : void 0">
110
+ <div v-if="$slots.eyebrow" :class="classes.eyebrow">
111
+ <slot name="eyebrow" />
112
+ </div>
113
+ <div v-if="$slots.leading" :class="classes.leading">
114
+ <slot name="leading" />
115
+ </div>
106
116
  <h1 :class="classes.title">
107
117
  <slot name="title">{{ title === void 0 ? "" : resolveTextValue(title) }}</slot>
108
118
  </h1>
@@ -133,8 +143,15 @@ function actionUi(action) {
133
143
  </template>
134
144
  </slot>
135
145
  </p>
146
+ <div v-if="$slots.details" :class="classes.details">
147
+ <slot name="details" />
148
+ </div>
136
149
  </header>
137
150
 
151
+ <div v-if="$slots.banner" :class="classes.banner">
152
+ <slot name="banner" />
153
+ </div>
154
+
138
155
  <div ref="marker" aria-hidden="true" />
139
156
  <div :class="classes.toolbar" :data-stuck="stuck || void 0">
140
157
  <slot name="tabs">
@@ -33,6 +33,14 @@ declare const __VLS_export: <TDashboard extends DashboardPageTarget>(__VLS_props
33
33
  }) => unknown;
34
34
  title?: () => unknown;
35
35
  description?: () => unknown;
36
+ /** Above the title, across the header: breadcrumbs or a back link. */
37
+ eyebrow?: () => unknown;
38
+ /** Before the title and its line: an avatar or a logo, for a page about one entity. */
39
+ leading?: () => unknown;
40
+ /** A full-width row closing the header: key facts of the entity the page is about. */
41
+ details?: () => unknown;
42
+ /** Between the header and the pinned tabs: a note or a notice about the whole page. */
43
+ banner?: () => unknown;
36
44
  /** Replaces the page actions and the refresh control. */
37
45
  actions?: () => unknown;
38
46
  /** Replaces the view tabs. */
@@ -1,4 +1,4 @@
1
- import type { DashboardViewController, DashboardViewTabsUi } from '../types/index.js';
1
+ import type { DashboardViewController, DashboardViewItem, DashboardViewTabsUi } from '../types/index.js';
2
2
  declare const __VLS_export: <TKey extends string>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
3
3
  props: import("vue").PublicProps & __VLS_PrettifyLocal<{
4
4
  dashboard: {
@@ -13,10 +13,7 @@ declare const __VLS_export: <TKey extends string>(__VLS_props: NonNullable<Await
13
13
  slots: {
14
14
  /** Content of one tab. */
15
15
  tab?: (props: {
16
- item: {
17
- value: TKey;
18
- label: string;
19
- };
16
+ item: DashboardViewItem<TKey>;
20
17
  active: boolean;
21
18
  }) => unknown;
22
19
  };
@@ -15,8 +15,9 @@ const strip = useTemplateRef("strip");
15
15
  const classes = computed(
16
16
  () => resolveDashboardClasses(
17
17
  {
18
+ badge: "ms-1.5 inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-elevated px-1.5 text-[11px] leading-none font-semibold text-muted tabular-nums group-data-[active]/tab:bg-primary/12 group-data-[active]/tab:text-primary",
18
19
  root: "flex min-w-0 gap-0.5 overflow-x-auto border-b border-default [scrollbar-width:none]",
19
- tab: "relative inline-flex h-10 flex-none items-center px-3 text-sm font-medium whitespace-nowrap text-muted transition-colors outline-none hover:text-default focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-inset data-[active]:text-highlighted data-[active]:after:absolute data-[active]:after:inset-x-2 data-[active]:after:-bottom-px data-[active]:after:h-0.5 data-[active]:after:rounded-full data-[active]:after:bg-primary"
20
+ tab: "group/tab relative inline-flex h-10 flex-none items-center px-3 text-sm font-medium whitespace-nowrap text-muted transition-colors outline-none hover:text-default focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-inset data-[active]:text-highlighted data-[active]:after:absolute data-[active]:after:inset-x-2 data-[active]:after:-bottom-px data-[active]:after:h-0.5 data-[active]:after:rounded-full data-[active]:after:bg-primary"
20
21
  },
21
22
  appUi.value.viewTabs,
22
23
  ui
@@ -60,6 +61,9 @@ watch(
60
61
  >
61
62
  <slot name="tab" :item :active="dashboard.view.current === item.value">
62
63
  {{ item.label }}
64
+ <span v-if="item.badge !== void 0" :class="classes.badge" data-dashboard-view-badge>
65
+ {{ item.badge }}
66
+ </span>
63
67
  </slot>
64
68
  </button>
65
69
  </nav>
@@ -1,4 +1,4 @@
1
- import type { DashboardViewController, DashboardViewTabsUi } from '../types/index.js';
1
+ import type { DashboardViewController, DashboardViewItem, DashboardViewTabsUi } from '../types/index.js';
2
2
  declare const __VLS_export: <TKey extends string>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
3
3
  props: import("vue").PublicProps & __VLS_PrettifyLocal<{
4
4
  dashboard: {
@@ -13,10 +13,7 @@ declare const __VLS_export: <TKey extends string>(__VLS_props: NonNullable<Await
13
13
  slots: {
14
14
  /** Content of one tab. */
15
15
  tab?: (props: {
16
- item: {
17
- value: TKey;
18
- label: string;
19
- };
16
+ item: DashboardViewItem<TKey>;
20
17
  active: boolean;
21
18
  }) => unknown;
22
19
  };
@@ -25,6 +25,9 @@ export function useDashboardApi(params) {
25
25
  get label() {
26
26
  return view.label();
27
27
  },
28
+ get badge() {
29
+ return view.badge();
30
+ },
28
31
  get opened() {
29
32
  return view.opened.value;
30
33
  }
@@ -91,7 +94,7 @@ export function useDashboardApi(params) {
91
94
  },
92
95
  get items() {
93
96
  return views.views.flatMap(
94
- (view) => view.enabled.value ? [{ label: view.label(), value: view.key }] : []
97
+ (view) => view.enabled.value ? [{ badge: view.badge(), label: view.label(), value: view.key }] : []
95
98
  );
96
99
  }
97
100
  })
@@ -1,5 +1,5 @@
1
1
  import type { ComputedRef } from 'vue';
2
- import type { DashboardSourceLike } from '../types/index.js';
2
+ import type { DashboardSourceLike, DashboardViewBadge } from '../types/index.js';
3
3
  import type { DashboardRuntimeSchema } from '../types/runtime.js';
4
4
  import type { DashboardEnvironment } from '../utils/environment.js';
5
5
  import type { DashboardReadTracker } from '../utils/tracker.js';
@@ -23,6 +23,7 @@ export declare function useDashboardViews(params: {
23
23
  current: import("vue").WritableComputedRef<string, string>;
24
24
  keys: string[];
25
25
  views: {
26
+ badge: () => string | number | undefined;
26
27
  enabled: ComputedRef<boolean>;
27
28
  input: import("../types/runtime.js").DashboardRuntimeScopeInput;
28
29
  key: string;
@@ -54,5 +55,7 @@ export declare function useDashboardViews(params: {
54
55
  };
55
56
  }[];
56
57
  };
58
+ /** A badge worth showing: a non-zero finite number or a non-empty text. */
59
+ export declare function resolveDashboardViewBadge(value: DashboardViewBadge): string | number | undefined;
57
60
  /** No filter may take the URL key of the current view. */
58
61
  export declare function assertDashboardViewUrlKeys(owner: string, urlKeys: readonly string[]): void;
@@ -36,6 +36,15 @@ export function useDashboardViews(params) {
36
36
  }
37
37
  });
38
38
  const rootSources = new Map(params.root.members);
39
+ const rootData = Object.defineProperties(
40
+ {},
41
+ Object.fromEntries(
42
+ [...rootSources].map(([key, source]) => [key, { enumerable: true, get: () => source.data }])
43
+ )
44
+ );
45
+ const badges = computed(
46
+ () => schema.badges?.({ data: rootData, filters: params.root.filterScope.values }) ?? {}
47
+ );
39
48
  const views = schema.views.map(([key, input]) => {
40
49
  const enabled = conditions.get(key) ?? computed(() => true);
41
50
  const opened = shallowRef(false);
@@ -60,10 +69,15 @@ export function useDashboardViews(params) {
60
69
  });
61
70
  assertDashboardViewUrlKeys(key, scope.filterScope.urlKeys);
62
71
  const label = () => resolveTextValue(input.label, key);
63
- return { enabled, input, key, label, opened, scope };
72
+ const badge = () => resolveDashboardViewBadge(badges.value[key]);
73
+ return { badge, enabled, input, key, label, opened, scope };
64
74
  });
65
75
  return { current, keys, views };
66
76
  }
77
+ export function resolveDashboardViewBadge(value) {
78
+ if (typeof value === "number") return Number.isFinite(value) && value !== 0 ? value : void 0;
79
+ return value ? value : void 0;
80
+ }
67
81
  export function assertDashboardViewUrlKeys(owner, urlKeys) {
68
82
  if (urlKeys.includes(DASHBOARD_VIEW_URL_KEY))
69
83
  throw new Error(
@@ -1,5 +1,5 @@
1
1
  import type { LazyTextValue } from '../../shared/types/utils.js';
2
- import type { DashboardCondition, DashboardDeriveMap, DashboardEmptyMap, DashboardFilterMap, DashboardKeyError, DashboardReservedKey, DashboardSchema, DashboardScopeGuard, DashboardScopeInput, DashboardSourceMap, DashboardView, DashboardViewMap } from '../types/index.js';
2
+ import type { DashboardCondition, DashboardDeriveMap, DashboardEmptyMap, DashboardFilterMap, DashboardKeyError, DashboardReservedKey, DashboardSchema, DashboardScopeGuard, DashboardScopeInput, DashboardSourceMap, DashboardView, DashboardBadgesContext, DashboardDerivedResources, DashboardViewBadges, DashboardViewMap } from '../types/index.js';
3
3
  type DashboardViewsGuard<TQueries, TDerive, TViews> = [
4
4
  Extract<keyof TViews, DashboardReservedKey | keyof TQueries | keyof TDerive>
5
5
  ] extends [never] ? unknown : {
@@ -47,6 +47,20 @@ export declare function defineDashboardSchema<const TFilters extends DashboardFi
47
47
  views?: TViews;
48
48
  /** View shown when the URL does not select one. Defaults to the first declared view. */
49
49
  defaultView?: NoInfer<keyof TViews & string>;
50
+ /**
51
+ * Counts or short texts shown after the view tabs' labels, read from the root queries and
52
+ * derived values, so a tab can count what waits in it before it is opened. Read lazily: badges
53
+ * follow the data. `null`, `undefined`, `0`, and `''` show nothing.
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * badges: ({ data }) => ({
58
+ * invoices: data.account?.unpaidInvoices,
59
+ * access: data.account?.pendingInvitations,
60
+ * })
61
+ * ```
62
+ */
63
+ badges?: (context: DashboardBadgesContext<TFilters, TQueries & DashboardDerivedResources<TDerive>>) => DashboardViewBadges<NoInfer<keyof TViews & string>>;
50
64
  } & DashboardScopeInput<TFilters, TQueries, TDerive> & DashboardScopeGuard<TQueries, TDerive> & DashboardViewsGuard<TQueries, TDerive, TViews>): DashboardSchema<NoInfer<TFilters>, NoInfer<TQueries>, NoInfer<TDerive>, NoInfer<TViews>>;
51
65
  /**
52
66
  * Declares one view (tab), self-contained: its label, its filters, its queries and derived values.
@@ -86,7 +86,7 @@ export interface DashboardReferenceLine {
86
86
  position?: 'start' | 'end';
87
87
  }
88
88
  /** Shape of the loading skeleton, one per block family. */
89
- export type DashboardSkeletonKind = 'stat' | 'bars' | 'lines' | 'donut' | 'rows' | 'hbars' | 'funnel' | 'stack' | 'paired' | 'feed' | 'table' | 'stats' | 'gauge';
89
+ export type DashboardSkeletonKind = 'stat' | 'bars' | 'lines' | 'donut' | 'rows' | 'hbars' | 'funnel' | 'stack' | 'paired' | 'feed' | 'table' | 'stats' | 'gauge' | 'details';
90
90
  /**
91
91
  * Built-in card menu actions: `table` switches the content to a data table, `csv` downloads that
92
92
  * data, `expand` opens the card in a large dialog.
@@ -259,6 +259,32 @@ export interface DashboardStatsItem<TData> {
259
259
  /** Badge next to the value ("Good", "At risk"). `null` hides it. */
260
260
  status?: (data: TData) => DashboardStatus | null | undefined;
261
261
  }
262
+ /** One field of `UiDashboardDetails`: a label over its value, read from the source data. */
263
+ export interface DashboardDetailsItem<TData> {
264
+ /** Stable identity, and the name of its slot: `#item-vatNumber`. */
265
+ key: string;
266
+ label: LazyTextValue;
267
+ /**
268
+ * Numbers go through `format`. `null`, `undefined`, and `''` read as empty: a muted dash, or
269
+ * `placeholder`.
270
+ */
271
+ value: (data: TData) => LazyTextValue | number | null | undefined;
272
+ format?: DashboardValueFormat;
273
+ /** Text under the value (a date of change, a unit, a source). */
274
+ hint?: (data: TData) => LazyTextValue | null | undefined;
275
+ /** Shown when the value is empty. Defaults to a dash. */
276
+ placeholder?: LazyTextValue;
277
+ /** Monospace value, for identifiers and codes. */
278
+ mono?: boolean;
279
+ /** A copy button after a non-empty value, for identifiers people paste elsewhere. */
280
+ copy?: boolean;
281
+ /** Makes the value a link. */
282
+ to?: (data: TData) => RouteLocationRaw | null | undefined;
283
+ /** Columns the field spans: a number, or `'full'` for the whole row. */
284
+ span?: number | 'full';
285
+ /** Leaves the field out, e.g. a field that only applies to some records. */
286
+ hidden?: (data: TData) => boolean;
287
+ }
262
288
  /** One tab of `UiDashboardTabs`. */
263
289
  export interface DashboardTab<TValue extends string | number = string | number> {
264
290
  value: TValue;