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.
- package/dist/module.json +1 -1
- package/dist/module.mjs +20 -0
- package/dist/runtime/dashboard/components/block/dashboard-skeleton.vue +12 -0
- package/dist/runtime/dashboard/components/dashboard-details.d.vue.ts +38 -0
- package/dist/runtime/dashboard/components/dashboard-details.vue +194 -0
- package/dist/runtime/dashboard/components/dashboard-details.vue.d.ts +38 -0
- package/dist/runtime/dashboard/components/dashboard-page.d.vue.ts +8 -0
- package/dist/runtime/dashboard/components/dashboard-page.vue +20 -3
- package/dist/runtime/dashboard/components/dashboard-page.vue.d.ts +8 -0
- package/dist/runtime/dashboard/components/dashboard-view-tabs.d.vue.ts +2 -5
- package/dist/runtime/dashboard/components/dashboard-view-tabs.vue +5 -1
- package/dist/runtime/dashboard/components/dashboard-view-tabs.vue.d.ts +2 -5
- package/dist/runtime/dashboard/composables/use-dashboard-api.js +4 -1
- package/dist/runtime/dashboard/composables/use-dashboard-views.d.ts +4 -1
- package/dist/runtime/dashboard/composables/use-dashboard-views.js +15 -1
- package/dist/runtime/dashboard/schema/index.d.ts +15 -1
- package/dist/runtime/dashboard/types/blocks.d.ts +27 -1
- package/dist/runtime/dashboard/types/runtime.d.ts +3 -0
- package/dist/runtime/dashboard/types/schema.d.ts +23 -4
- package/dist/runtime/dashboard/types/ui.d.ts +27 -0
- package/dist/runtime/dashboard/utils/schema.js +1 -0
- package/dist/runtime/file-preview/components/provider/file-preview-host.d.vue.ts +7 -0
- package/dist/runtime/file-preview/components/provider/file-preview-host.vue +147 -0
- package/dist/runtime/file-preview/components/provider/file-preview-host.vue.d.ts +7 -0
- package/dist/runtime/file-preview/components/provider/file-preview-provider.d.vue.ts +21 -0
- package/dist/runtime/file-preview/components/provider/file-preview-provider.vue +23 -0
- package/dist/runtime/file-preview/components/provider/file-preview-provider.vue.d.ts +21 -0
- package/dist/runtime/file-preview/components/renderers/audio-renderer.d.vue.ts +10 -0
- package/dist/runtime/file-preview/components/renderers/audio-renderer.vue +83 -0
- package/dist/runtime/file-preview/components/renderers/audio-renderer.vue.d.ts +10 -0
- package/dist/runtime/file-preview/components/renderers/csv-renderer.d.vue.ts +10 -0
- package/dist/runtime/file-preview/components/renderers/csv-renderer.vue +116 -0
- package/dist/runtime/file-preview/components/renderers/csv-renderer.vue.d.ts +10 -0
- package/dist/runtime/file-preview/components/renderers/fallback-renderer.d.vue.ts +10 -0
- package/dist/runtime/file-preview/components/renderers/fallback-renderer.vue +41 -0
- package/dist/runtime/file-preview/components/renderers/fallback-renderer.vue.d.ts +10 -0
- package/dist/runtime/file-preview/components/renderers/file-preview-code.d.vue.ts +9 -0
- package/dist/runtime/file-preview/components/renderers/file-preview-code.vue +83 -0
- package/dist/runtime/file-preview/components/renderers/file-preview-code.vue.d.ts +9 -0
- package/dist/runtime/file-preview/components/renderers/file-preview-text-loading.d.vue.ts +3 -0
- package/dist/runtime/file-preview/components/renderers/file-preview-text-loading.vue +14 -0
- package/dist/runtime/file-preview/components/renderers/file-preview-text-loading.vue.d.ts +3 -0
- package/dist/runtime/file-preview/components/renderers/image-renderer.d.vue.ts +10 -0
- package/dist/runtime/file-preview/components/renderers/image-renderer.vue +182 -0
- package/dist/runtime/file-preview/components/renderers/image-renderer.vue.d.ts +10 -0
- package/dist/runtime/file-preview/components/renderers/markdown-renderer.d.vue.ts +10 -0
- package/dist/runtime/file-preview/components/renderers/markdown-renderer.vue +78 -0
- package/dist/runtime/file-preview/components/renderers/markdown-renderer.vue.d.ts +10 -0
- package/dist/runtime/file-preview/components/renderers/media-controls.d.vue.ts +10 -0
- package/dist/runtime/file-preview/components/renderers/media-controls.vue +145 -0
- package/dist/runtime/file-preview/components/renderers/media-controls.vue.d.ts +10 -0
- package/dist/runtime/file-preview/components/renderers/pdf-renderer.d.vue.ts +10 -0
- package/dist/runtime/file-preview/components/renderers/pdf-renderer.vue +58 -0
- package/dist/runtime/file-preview/components/renderers/pdf-renderer.vue.d.ts +10 -0
- package/dist/runtime/file-preview/components/renderers/text-renderer.d.vue.ts +10 -0
- package/dist/runtime/file-preview/components/renderers/text-renderer.vue +41 -0
- package/dist/runtime/file-preview/components/renderers/text-renderer.vue.d.ts +10 -0
- package/dist/runtime/file-preview/components/renderers/video-renderer.d.vue.ts +10 -0
- package/dist/runtime/file-preview/components/renderers/video-renderer.vue +110 -0
- package/dist/runtime/file-preview/components/renderers/video-renderer.vue.d.ts +10 -0
- package/dist/runtime/file-preview/components/shell/file-preview-button.d.vue.ts +16 -0
- package/dist/runtime/file-preview/components/shell/file-preview-button.vue +40 -0
- package/dist/runtime/file-preview/components/shell/file-preview-button.vue.d.ts +16 -0
- package/dist/runtime/{form/components/renderer/form-field-error.d.vue.ts → file-preview/components/shell/file-preview-details.d.vue.ts} +4 -2
- package/dist/runtime/file-preview/components/shell/file-preview-details.vue +25 -0
- package/dist/runtime/{form/components/renderer/form-field-error.vue.d.ts → file-preview/components/shell/file-preview-details.vue.d.ts} +4 -2
- package/dist/runtime/file-preview/components/shell/file-preview-header.d.vue.ts +44 -0
- package/dist/runtime/file-preview/components/shell/file-preview-header.vue +163 -0
- package/dist/runtime/file-preview/components/shell/file-preview-header.vue.d.ts +44 -0
- package/dist/runtime/file-preview/components/shell/file-preview-message.d.vue.ts +21 -0
- package/dist/runtime/file-preview/components/shell/file-preview-message.vue +49 -0
- package/dist/runtime/file-preview/components/shell/file-preview-message.vue.d.ts +21 -0
- package/dist/runtime/file-preview/components/shell/file-preview-shell.d.vue.ts +17 -0
- package/dist/runtime/file-preview/components/shell/file-preview-shell.vue +280 -0
- package/dist/runtime/file-preview/components/shell/file-preview-shell.vue.d.ts +17 -0
- package/dist/runtime/file-preview/components/shell/file-preview-stage.d.vue.ts +37 -0
- package/dist/runtime/file-preview/components/shell/file-preview-stage.vue +173 -0
- package/dist/runtime/file-preview/components/shell/file-preview-stage.vue.d.ts +37 -0
- package/dist/runtime/file-preview/components/shell/file-preview-thumb.d.vue.ts +14 -0
- package/dist/runtime/file-preview/components/shell/file-preview-thumb.vue +69 -0
- package/dist/runtime/file-preview/components/shell/file-preview-thumb.vue.d.ts +14 -0
- package/dist/runtime/file-preview/components/shell/file-preview-tools.d.vue.ts +13 -0
- package/dist/runtime/file-preview/components/shell/file-preview-tools.vue +10 -0
- package/dist/runtime/file-preview/components/shell/file-preview-tools.vue.d.ts +13 -0
- package/dist/runtime/file-preview/composables/use-file-preview-api.d.ts +21 -0
- package/dist/runtime/file-preview/composables/use-file-preview-api.js +83 -0
- package/dist/runtime/file-preview/composables/use-file-preview-shell.d.ts +13 -0
- package/dist/runtime/file-preview/composables/use-file-preview-shell.js +30 -0
- package/dist/runtime/file-preview/composables/use-file-preview-source.d.ts +17 -0
- package/dist/runtime/file-preview/composables/use-file-preview-source.js +51 -0
- package/dist/runtime/file-preview/composables/use-file-preview-text.d.ts +14 -0
- package/dist/runtime/file-preview/composables/use-file-preview-text.js +79 -0
- package/dist/runtime/file-preview/index.d.ts +5 -0
- package/dist/runtime/file-preview/index.js +14 -0
- package/dist/runtime/file-preview/plugins/file-preview-api.d.ts +11 -0
- package/dist/runtime/file-preview/plugins/file-preview-api.js +10 -0
- package/dist/runtime/file-preview/types/api.d.ts +74 -0
- package/dist/runtime/file-preview/types/api.js +0 -0
- package/dist/runtime/file-preview/types/file.d.ts +77 -0
- package/dist/runtime/file-preview/types/file.js +0 -0
- package/dist/runtime/file-preview/types/index.d.ts +4 -0
- package/dist/runtime/file-preview/types/index.js +0 -0
- package/dist/runtime/file-preview/types/renderer.d.ts +49 -0
- package/dist/runtime/file-preview/types/renderer.js +0 -0
- package/dist/runtime/file-preview/types/shell.d.ts +12 -0
- package/dist/runtime/file-preview/types/shell.js +0 -0
- package/dist/runtime/file-preview/utils/container.d.ts +4 -0
- package/dist/runtime/file-preview/utils/container.js +10 -0
- package/dist/runtime/file-preview/utils/csv.d.ts +17 -0
- package/dist/runtime/file-preview/utils/csv.js +53 -0
- package/dist/runtime/file-preview/utils/download.d.ts +8 -0
- package/dist/runtime/file-preview/utils/download.js +40 -0
- package/dist/runtime/file-preview/utils/files.d.ts +7 -0
- package/dist/runtime/file-preview/utils/files.js +53 -0
- package/dist/runtime/file-preview/utils/format.d.ts +13 -0
- package/dist/runtime/file-preview/utils/format.js +40 -0
- package/dist/runtime/file-preview/utils/instance.d.ts +9 -0
- package/dist/runtime/file-preview/utils/instance.js +57 -0
- package/dist/runtime/file-preview/utils/renderers.d.ts +36 -0
- package/dist/runtime/file-preview/utils/renderers.js +247 -0
- package/dist/runtime/form/components/page/form-page-header.d.vue.ts +3 -1
- package/dist/runtime/form/components/page/form-page-header.vue +4 -4
- package/dist/runtime/form/components/page/form-page-header.vue.d.ts +3 -1
- package/dist/runtime/form/components/page/form-page-navigation.vue +2 -1
- package/dist/runtime/form/components/page/form-page-section.vue +15 -7
- package/dist/runtime/form/components/page/form-page.vue +0 -1
- package/dist/runtime/form/components/utils/form-file-preview.vue +5 -38
- package/dist/runtime/form/composables/use-field-control.d.ts +1 -1
- package/dist/runtime/form/composables/use-form-api.d.ts +8 -0
- package/dist/runtime/form/composables/use-form-api.js +4 -4
- package/dist/runtime/form/composables/use-form-page.js +3 -1
- package/dist/runtime/form/composables/use-form-runtime.js +3 -0
- package/dist/runtime/form/composables/use-form-upload-registry.d.ts +2 -0
- package/dist/runtime/form/composables/use-form-upload-registry.js +11 -6
- package/dist/runtime/form/composables/use-form-validation.js +5 -1
- package/dist/runtime/form/fields/array-list/use-array-items.d.ts +1 -1
- package/dist/runtime/form/fields/array-table/array-table-cell.d.vue.ts +15 -0
- package/dist/runtime/form/fields/array-table/array-table-cell.vue +58 -0
- package/dist/runtime/form/fields/array-table/array-table-cell.vue.d.ts +15 -0
- package/dist/runtime/form/fields/array-table/component.vue +42 -26
- package/dist/runtime/form/fields/choice-card/use-choice-card.js +4 -1
- package/dist/runtime/form/fields/date/types.d.ts +6 -1
- package/dist/runtime/form/fields/date-family/component.vue +7 -1
- package/dist/runtime/form/fields/date-family/utils.d.ts +8 -1
- package/dist/runtime/form/fields/date-family/utils.js +10 -2
- package/dist/runtime/form/fields/hidden/types.d.ts +8 -1
- package/dist/runtime/form/fields/upload/component.vue +55 -176
- package/dist/runtime/form/fields/upload/types.d.ts +68 -3
- package/dist/runtime/form/fields/upload/upload-item.d.vue.ts +24 -0
- package/dist/runtime/form/fields/upload/upload-item.vue +158 -0
- package/dist/runtime/form/fields/upload/upload-item.vue.d.ts +24 -0
- package/dist/runtime/form/fields/upload/use-upload-items.d.ts +44 -0
- package/dist/runtime/form/fields/upload/use-upload-items.js +317 -0
- package/dist/runtime/form/plugins/form-api.d.ts +10 -0
- package/dist/runtime/form/plugins/form-api.js +10 -0
- package/dist/runtime/form/types/callbacks.d.ts +8 -2
- package/dist/runtime/form/types/controller.d.ts +6 -3
- package/dist/runtime/form/types/field-base.d.ts +5 -5
- package/dist/runtime/form/types/field-output-utils.d.ts +12 -1
- package/dist/runtime/form/types/field-output.d.ts +1 -1
- package/dist/runtime/form/types/field.d.ts +3 -2
- package/dist/runtime/form/types/form-api.d.ts +4 -2
- package/dist/runtime/form/types/options-runtime.d.ts +8 -4
- package/dist/runtime/form/types/options.d.ts +11 -11
- package/dist/runtime/form/types/output.d.ts +33 -10
- package/dist/runtime/form/types/page.d.ts +7 -3
- package/dist/runtime/form/types/transform.d.ts +5 -2
- package/dist/runtime/form/types/ui.d.ts +7 -3
- package/dist/runtime/form/types/upload-runtime.d.ts +4 -0
- package/dist/runtime/form/utils/page.js +24 -5
- package/dist/runtime/form/utils/state.js +14 -11
- package/dist/runtime/i18n/locales/en.js +108 -1
- package/dist/runtime/i18n/locales/fr.js +108 -1
- package/dist/runtime/i18n/types.d.ts +127 -0
- package/dist/runtime/query-state/composables.d.ts +2 -2
- package/dist/runtime/shared/styles/tokens.css +1 -1
- package/dist/runtime/shared/types/utils.d.ts +12 -4
- package/dist/runtime/shared/utils/file.d.ts +11 -0
- package/dist/runtime/shared/utils/file.js +50 -0
- package/dist/runtime/table/components/table/table-renderer.vue +23 -3
- package/dist/runtime/table/components/utils/search-query-input.d.vue.ts +5 -1
- package/dist/runtime/table/components/utils/search-query-input.vue +17 -3
- package/dist/runtime/table/components/utils/search-query-input.vue.d.ts +5 -1
- package/package.json +56 -29
- package/dist/runtime/form/components/renderer/form-field-error.vue +0 -16
package/dist/module.json
CHANGED
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
|
-
|
|
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;
|