nuxt-ui-tools 1.20.0 → 1.21.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/runtime/form/components/page/form-page-section.vue +61 -6
- package/dist/runtime/form/components/page/form-page-sections.d.vue.ts +4 -3
- package/dist/runtime/form/components/page/form-page-sections.vue +7 -5
- package/dist/runtime/form/components/page/form-page-sections.vue.d.ts +4 -3
- package/dist/runtime/form/composables/use-form-page.d.ts +6 -0
- package/dist/runtime/form/composables/use-form-page.js +4 -0
- package/dist/runtime/form/fields/auto-complete/component.vue +2 -0
- package/dist/runtime/form/fields/select/component.vue +5 -0
- package/dist/runtime/form/types/page.d.ts +11 -0
- package/dist/runtime/form/types/ui.d.ts +11 -1
- package/dist/runtime/form/utils/options.d.ts +6 -0
- package/dist/runtime/form/utils/options.js +7 -0
- package/dist/runtime/form/utils/page-surface.d.ts +4 -0
- package/dist/runtime/form/utils/page-surface.js +13 -0
- package/dist/runtime/form/utils/page.d.ts +2 -0
- package/dist/runtime/form/utils/page.js +5 -0
- package/dist/runtime/i18n/locales/en.js +1 -0
- package/dist/runtime/i18n/locales/fr.js +1 -0
- package/dist/runtime/i18n/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -23,6 +23,37 @@ const { runtime } = page.root;
|
|
|
23
23
|
const pageUi = computed(() => formUi.ui.value.page?.ui);
|
|
24
24
|
const titleId = useId();
|
|
25
25
|
const dirty = computed(() => page.dirtyCheck.value && props.section.dirty);
|
|
26
|
+
const status = computed(() => {
|
|
27
|
+
if (!page.statusVisible.value) {
|
|
28
|
+
return void 0;
|
|
29
|
+
}
|
|
30
|
+
if (props.section.status === "optional") {
|
|
31
|
+
return { color: "neutral", icon: void 0, label: t("form.page.status.optional") };
|
|
32
|
+
}
|
|
33
|
+
if (props.section.status === "complete") {
|
|
34
|
+
return {
|
|
35
|
+
color: "success",
|
|
36
|
+
icon: "i-lucide-check",
|
|
37
|
+
label: t("form.page.status.complete")
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (props.section.status === "invalid") {
|
|
41
|
+
return {
|
|
42
|
+
color: "error",
|
|
43
|
+
icon: "i-lucide-circle-alert",
|
|
44
|
+
label: t("form.page.status.invalid")
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const { missing, required } = props.section;
|
|
48
|
+
return {
|
|
49
|
+
color: "neutral",
|
|
50
|
+
icon: void 0,
|
|
51
|
+
label: t("form.page.progress", { filled: required - missing, required })
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
const spanClass = computed(
|
|
55
|
+
() => page.spanned.value ? "col-span-12 @3xl/form-sections:col-span-(--nut-form-page-section-span)" : ""
|
|
56
|
+
);
|
|
26
57
|
const layout = computed(
|
|
27
58
|
() => resolveFormLayoutConfig(runtime.currentLayout.value, props.entry.section.layout)
|
|
28
59
|
);
|
|
@@ -47,18 +78,22 @@ onBeforeUnmount(() => unregister?.());
|
|
|
47
78
|
:data-state="section.status"
|
|
48
79
|
:data-dirty="dirty || void 0"
|
|
49
80
|
:style="{
|
|
50
|
-
scrollMarginTop: `calc(${page.stickyOffset.value}px + var(--nut-form-page-gap, 24px))
|
|
81
|
+
scrollMarginTop: `calc(${page.stickyOffset.value}px + var(--nut-form-page-gap, 24px))`,
|
|
82
|
+
'--nut-form-page-section-span': page.spanned.value ? String(section.span) : void 0
|
|
51
83
|
}"
|
|
52
84
|
:class="
|
|
53
85
|
mergeFormUiClass(
|
|
54
|
-
`flex min-w-0 flex-col ${page.surfaceClasses.value.section}`,
|
|
86
|
+
`flex min-w-0 flex-col ${spanClass} ${page.surfaceClasses.value.section}`,
|
|
55
87
|
pageUi?.section
|
|
56
88
|
)
|
|
57
89
|
"
|
|
58
90
|
>
|
|
59
91
|
<header
|
|
60
92
|
:class="
|
|
61
|
-
mergeFormUiClass(
|
|
93
|
+
mergeFormUiClass(
|
|
94
|
+
`flex flex-wrap items-center gap-x-3 gap-y-1 ${page.surfaceClasses.value.sectionHeader}`,
|
|
95
|
+
pageUi?.sectionHeader
|
|
96
|
+
)
|
|
62
97
|
"
|
|
63
98
|
>
|
|
64
99
|
<span
|
|
@@ -99,7 +134,7 @@ onBeforeUnmount(() => unregister?.());
|
|
|
99
134
|
{{ section.description }}
|
|
100
135
|
</p>
|
|
101
136
|
<UBadge
|
|
102
|
-
v-if="section.status === 'optional'"
|
|
137
|
+
v-if="section.status === 'optional' && !status"
|
|
103
138
|
color="neutral"
|
|
104
139
|
variant="soft"
|
|
105
140
|
:label="t('form.page.status.optional')"
|
|
@@ -107,9 +142,24 @@ onBeforeUnmount(() => unregister?.());
|
|
|
107
142
|
data-form-page-optional
|
|
108
143
|
/>
|
|
109
144
|
<div
|
|
110
|
-
v-if="page.dirtyCheck.value || $slots.actions"
|
|
145
|
+
v-if="status || page.dirtyCheck.value || $slots.actions"
|
|
111
146
|
:class="mergeFormUiClass('ms-auto -my-1.5 flex items-center gap-2', pageUi?.sectionActions)"
|
|
112
147
|
>
|
|
148
|
+
<UBadge
|
|
149
|
+
v-if="status"
|
|
150
|
+
:color="status.color"
|
|
151
|
+
variant="soft"
|
|
152
|
+
:icon="status.icon"
|
|
153
|
+
:label="status.label"
|
|
154
|
+
:class="
|
|
155
|
+
mergeFormUiClass(
|
|
156
|
+
section.status === 'optional' ? pageUi?.sectionOptional : void 0,
|
|
157
|
+
pageUi?.sectionStatus
|
|
158
|
+
)
|
|
159
|
+
"
|
|
160
|
+
:data-form-page-status="section.status"
|
|
161
|
+
:data-form-page-optional="section.status === 'optional' ? '' : void 0"
|
|
162
|
+
/>
|
|
113
163
|
<slot name="actions" :section />
|
|
114
164
|
<UButton
|
|
115
165
|
v-if="page.dirtyCheck.value"
|
|
@@ -125,7 +175,12 @@ onBeforeUnmount(() => unregister?.());
|
|
|
125
175
|
/>
|
|
126
176
|
</div>
|
|
127
177
|
</header>
|
|
128
|
-
<div
|
|
178
|
+
<div
|
|
179
|
+
:class="
|
|
180
|
+
mergeFormUiClass(`min-w-0 ${page.surfaceClasses.value.sectionBody}`, pageUi?.sectionBody)
|
|
181
|
+
"
|
|
182
|
+
:style="grid.style.value"
|
|
183
|
+
>
|
|
129
184
|
<FormFieldRenderer
|
|
130
185
|
v-for="field in entry.section.fields"
|
|
131
186
|
:key="field.key"
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { FormPageSectionState } from '../../types/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Sections of a form page, one per visible section, each with its title, description, fields, and,
|
|
4
|
-
* with `controls.dirtyCheck`, a mark and a reset button while it is modified. They are cards,
|
|
5
|
-
* plain blocks with the `page.surface` UI option.
|
|
6
|
-
*
|
|
4
|
+
* with `controls.dirtyCheck`, a mark and a reset button while it is modified. They are cards,
|
|
5
|
+
* panels, or plain blocks with the `page.surface` UI option. Once a section sets a `span`, they lay
|
|
6
|
+
* out in a twelve-column grid, which is also the container their spans query. Skeletons stand in
|
|
7
|
+
* while the schema context loads.
|
|
7
8
|
*/
|
|
8
9
|
type __VLS_Slots = {
|
|
9
10
|
/** Extra buttons in a section header, before its reset button. */
|
|
@@ -14,20 +14,22 @@ const formUi = useFormUi();
|
|
|
14
14
|
const { t } = useUiToolsLocale();
|
|
15
15
|
const pageUi = computed(() => formUi.ui.value.page?.ui);
|
|
16
16
|
const { contextError, contextLoading, contextPending, refreshContext } = page.root;
|
|
17
|
+
const flow = computed(
|
|
18
|
+
() => page.spanned.value ? "@container/form-sections grid grid-cols-12 items-stretch" : "flex flex-col"
|
|
19
|
+
);
|
|
20
|
+
const fullRow = computed(() => page.spanned.value ? "col-span-12" : "");
|
|
17
21
|
</script>
|
|
18
22
|
|
|
19
23
|
<template>
|
|
20
24
|
<div
|
|
21
25
|
:class="
|
|
22
|
-
mergeFormUiClass(
|
|
23
|
-
`flex min-w-0 flex-col ${page.surfaceClasses.value.sections}`,
|
|
24
|
-
pageUi?.sections
|
|
25
|
-
)
|
|
26
|
+
mergeFormUiClass(`${flow} min-w-0 ${page.surfaceClasses.value.sections}`, pageUi?.sections)
|
|
26
27
|
"
|
|
27
28
|
data-form-page-sections
|
|
28
29
|
>
|
|
29
30
|
<UAlert
|
|
30
31
|
v-if="contextError"
|
|
32
|
+
:class="fullRow"
|
|
31
33
|
color="error"
|
|
32
34
|
variant="soft"
|
|
33
35
|
icon="i-lucide-circle-alert"
|
|
@@ -53,7 +55,7 @@ const { contextError, contextLoading, contextPending, refreshContext } = page.ro
|
|
|
53
55
|
:key="section.key"
|
|
54
56
|
:class="
|
|
55
57
|
mergeFormUiClass(
|
|
56
|
-
`grid gap-4 ${page.surfaceClasses.value.sectionSkeleton}`,
|
|
58
|
+
`grid gap-4 ${fullRow} ${page.surfaceClasses.value.sectionSkeleton}`,
|
|
57
59
|
pageUi?.sectionSkeleton
|
|
58
60
|
)
|
|
59
61
|
"
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { FormPageSectionState } from '../../types/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Sections of a form page, one per visible section, each with its title, description, fields, and,
|
|
4
|
-
* with `controls.dirtyCheck`, a mark and a reset button while it is modified. They are cards,
|
|
5
|
-
* plain blocks with the `page.surface` UI option.
|
|
6
|
-
*
|
|
4
|
+
* with `controls.dirtyCheck`, a mark and a reset button while it is modified. They are cards,
|
|
5
|
+
* panels, or plain blocks with the `page.surface` UI option. Once a section sets a `span`, they lay
|
|
6
|
+
* out in a twelve-column grid, which is also the container their spans query. Skeletons stand in
|
|
7
|
+
* while the schema context loads.
|
|
7
8
|
*/
|
|
8
9
|
type __VLS_Slots = {
|
|
9
10
|
/** Extra buttons in a section header, before its reset button. */
|
|
@@ -43,6 +43,8 @@ export declare function useFormPage(params: {
|
|
|
43
43
|
};
|
|
44
44
|
scrollTo: (key: string, options?: import("../types/index.js").FormPageScrollOptions) => boolean;
|
|
45
45
|
sections: import("vue").ComputedRef<readonly FormPageSectionState[]>;
|
|
46
|
+
spanned: import("vue").ComputedRef<boolean>;
|
|
47
|
+
statusVisible: import("vue").ComputedRef<boolean>;
|
|
46
48
|
stickyOffset: import("vue").Ref<number, number>;
|
|
47
49
|
surface: import("vue").ComputedRef<import("../types/index.js").FormPageSurface>;
|
|
48
50
|
surfaceClasses: import("vue").ComputedRef<import("../utils/page-surface.js").FormPageSurfaceClasses>;
|
|
@@ -80,6 +82,8 @@ export declare function provideFormPage(page: ReturnType<typeof useFormPage>): {
|
|
|
80
82
|
};
|
|
81
83
|
scrollTo: (key: string, options?: import("../types/index.js").FormPageScrollOptions) => boolean;
|
|
82
84
|
sections: import("vue").ComputedRef<readonly FormPageSectionState[]>;
|
|
85
|
+
spanned: import("vue").ComputedRef<boolean>;
|
|
86
|
+
statusVisible: import("vue").ComputedRef<boolean>;
|
|
83
87
|
stickyOffset: import("vue").Ref<number, number>;
|
|
84
88
|
surface: import("vue").ComputedRef<import("../types/index.js").FormPageSurface>;
|
|
85
89
|
surfaceClasses: import("vue").ComputedRef<import("../utils/page-surface.js").FormPageSurfaceClasses>;
|
|
@@ -118,6 +122,8 @@ export declare function useFormPageContext(): {
|
|
|
118
122
|
};
|
|
119
123
|
scrollTo: (key: string, options?: import("../types/index.js").FormPageScrollOptions) => boolean;
|
|
120
124
|
sections: import("vue").ComputedRef<readonly FormPageSectionState[]>;
|
|
125
|
+
spanned: import("vue").ComputedRef<boolean>;
|
|
126
|
+
statusVisible: import("vue").ComputedRef<boolean>;
|
|
121
127
|
stickyOffset: import("vue").Ref<number, number>;
|
|
122
128
|
surface: import("vue").ComputedRef<import("../types/index.js").FormPageSurface>;
|
|
123
129
|
surfaceClasses: import("vue").ComputedRef<import("../utils/page-surface.js").FormPageSurfaceClasses>;
|
|
@@ -43,6 +43,8 @@ export function useFormPage(params) {
|
|
|
43
43
|
const navigationVisible = computed(() => sections.value.length >= navigationMinSections.value);
|
|
44
44
|
const surface = computed(() => resolveFormPageSurface(root.formUi.ui.value.page?.surface));
|
|
45
45
|
const surfaceClasses = computed(() => getFormPageSurfaceClasses(surface.value));
|
|
46
|
+
const statusVisible = computed(() => root.formUi.ui.value.page?.status === true);
|
|
47
|
+
const spanned = computed(() => sections.value.some((section) => section.span < 12));
|
|
46
48
|
const remaining = computed(() => sections.value.filter(isFormPageSectionBlocking).length);
|
|
47
49
|
const modified = computed(() => sections.value.filter((section) => section.dirty).length);
|
|
48
50
|
const stickyOffset = ref(0);
|
|
@@ -72,6 +74,8 @@ export function useFormPage(params) {
|
|
|
72
74
|
root,
|
|
73
75
|
scrollTo: scroll.scrollTo,
|
|
74
76
|
sections,
|
|
77
|
+
spanned,
|
|
78
|
+
statusVisible,
|
|
75
79
|
stickyOffset,
|
|
76
80
|
surface,
|
|
77
81
|
surfaceClasses
|
|
@@ -10,6 +10,7 @@ import FormOptionSuffix from "../../components/utils/form-option-suffix.vue";
|
|
|
10
10
|
import FormOptionValueOverlay from "../../components/utils/form-option-value-overlay.vue";
|
|
11
11
|
import { useFieldControl } from "../../composables/use-field-control";
|
|
12
12
|
import { useOptionMenu } from "../../composables/use-option-menu";
|
|
13
|
+
import { leadingVisualPadding } from "../../utils/options";
|
|
13
14
|
import { isBoolean, isNumber, isString } from "../../utils/predicate";
|
|
14
15
|
import { mergeFormUiClass } from "../../utils/ui";
|
|
15
16
|
const props = defineProps({
|
|
@@ -61,6 +62,7 @@ const controlUi = computed(() => ({
|
|
|
61
62
|
...controlProps.value.ui,
|
|
62
63
|
base: mergeFormUiClass(
|
|
63
64
|
controlProps.value.ui?.base,
|
|
65
|
+
menu.leadingOption.value ? leadingVisualPadding(controlProps.value.size) : void 0,
|
|
64
66
|
overlayActive.value ? "text-transparent" : void 0
|
|
65
67
|
),
|
|
66
68
|
content: mergeFormUiClass(controlProps.value.ui?.content, interactionOwnerClass.value),
|
|
@@ -11,6 +11,7 @@ import FormOptionMenuFooter from "../../components/utils/form-option-menu-footer
|
|
|
11
11
|
import FormOptionSuffix from "../../components/utils/form-option-suffix.vue";
|
|
12
12
|
import { useFieldControl } from "../../composables/use-field-control";
|
|
13
13
|
import { useOptionMenu } from "../../composables/use-option-menu";
|
|
14
|
+
import { leadingVisualPadding } from "../../utils/options";
|
|
14
15
|
import { isBoolean, isNumber, isString } from "../../utils/predicate";
|
|
15
16
|
import { mergeFormUiClass } from "../../utils/ui";
|
|
16
17
|
const props = defineProps({
|
|
@@ -65,6 +66,10 @@ const menu = useOptionMenu({
|
|
|
65
66
|
});
|
|
66
67
|
const controlUi = computed(() => ({
|
|
67
68
|
...controlProps.value.ui,
|
|
69
|
+
base: mergeFormUiClass(
|
|
70
|
+
controlProps.value.ui?.base,
|
|
71
|
+
menu.leadingOption.value ? leadingVisualPadding(controlProps.value.size) : void 0
|
|
72
|
+
),
|
|
68
73
|
content: mergeFormUiClass(controlProps.value.ui?.content, interactionOwnerClass.value),
|
|
69
74
|
itemDescription: mergeFormUiClass(
|
|
70
75
|
menu.renderOption.value ? "hidden" : "text-[12.5px]",
|
|
@@ -54,6 +54,13 @@ export interface FormPageSection<TContext = NonNullable<unknown>, TFields extend
|
|
|
54
54
|
optional?: boolean;
|
|
55
55
|
/** Grid of the section's fields, merged over the schema `layout`. */
|
|
56
56
|
layout?: FormLayoutConfig;
|
|
57
|
+
/**
|
|
58
|
+
* Columns, out of twelve, the section takes once the sections area is at least 48rem wide, so
|
|
59
|
+
* short sections sit side by side: `span: 4` next to `span: 8`. Sections fill the rows in order
|
|
60
|
+
* and the ones sharing a row stretch to the same height. Below that width, or without a `span`,
|
|
61
|
+
* the section takes the whole row.
|
|
62
|
+
*/
|
|
63
|
+
span?: number;
|
|
57
64
|
/** Raw dependency paths read before evaluating `condition`. */
|
|
58
65
|
dependencies?: readonly (string | readonly [string, string])[];
|
|
59
66
|
/** Hides the section, its navigation entry, and its fields while it returns `false`. */
|
|
@@ -186,6 +193,10 @@ export interface FormPageSectionState {
|
|
|
186
193
|
optional: boolean;
|
|
187
194
|
/** Number of required fields without a value, the fields of array items included. */
|
|
188
195
|
missing: number;
|
|
196
|
+
/** Number of required fields, the fields of array items included. */
|
|
197
|
+
required: number;
|
|
198
|
+
/** Columns, out of twelve, the section takes on a wide sections area: 12 without a `span`. */
|
|
199
|
+
span: number;
|
|
189
200
|
/** Where the section stands, see `FormPageSectionStatus`. */
|
|
190
201
|
status: FormPageSectionStatus;
|
|
191
202
|
/** True when a value of the section differs from the form's baseline. */
|
|
@@ -197,10 +197,11 @@ export interface FormOverlayUi {
|
|
|
197
197
|
* How the sections of a form page are drawn:
|
|
198
198
|
*
|
|
199
199
|
* - `card`: each section is a bordered card on the gray canvas of the page (default)
|
|
200
|
+
* - `panel`: a card whose heading sits in a bar across its top, above the fields
|
|
200
201
|
* - `plain`: no box. A section is its heading, description, and fields on the page itself,
|
|
201
202
|
* separated from the next by spacing and a hairline
|
|
202
203
|
*/
|
|
203
|
-
export type FormPageSurface = 'card' | 'plain';
|
|
204
|
+
export type FormPageSurface = 'card' | 'panel' | 'plain';
|
|
204
205
|
/**
|
|
205
206
|
* The `page` part of the form UI config: the surface of the sections and the `ui` slots of the
|
|
206
207
|
* page parts. Both follow the usual layering: app config, then schema `ui`, then the `ui` prop.
|
|
@@ -208,6 +209,13 @@ export type FormPageSurface = 'card' | 'plain';
|
|
|
208
209
|
export interface FormPageUiConfig extends FormUiPartConfig<FormPageUi> {
|
|
209
210
|
/** Defaults to `card`. */
|
|
210
211
|
surface?: FormPageSurface;
|
|
212
|
+
/**
|
|
213
|
+
* Shows where each section stands at the end of its heading: the required fields filled out of
|
|
214
|
+
* the total while some are missing ("2 sur 3"), "Complété" once nothing is, "À corriger" while a
|
|
215
|
+
* field shows an error, and "Facultatif" for an optional section with nothing entered, which
|
|
216
|
+
* then carries its tag there instead of after the title. Off by default.
|
|
217
|
+
*/
|
|
218
|
+
status?: boolean;
|
|
211
219
|
}
|
|
212
220
|
/**
|
|
213
221
|
* Slots of a form page and its parts. Data attributes carry the live state, so a slot can
|
|
@@ -274,6 +282,8 @@ export interface FormPageUi {
|
|
|
274
282
|
sectionDescription?: FormUiClass;
|
|
275
283
|
/** The "Optional" badge of an optional section with nothing entered. */
|
|
276
284
|
sectionOptional?: FormUiClass;
|
|
285
|
+
/** Badge of where the section stands, with `page.status`. Carries `data-form-page-status`. */
|
|
286
|
+
sectionStatus?: FormUiClass;
|
|
277
287
|
sectionActions?: FormUiClass;
|
|
278
288
|
sectionReset?: FormUiClass;
|
|
279
289
|
sectionBody?: FormUiClass;
|
|
@@ -12,6 +12,12 @@ export interface FormOptionKeys {
|
|
|
12
12
|
export type { ResolvedFormOption } from '../types/index.js';
|
|
13
13
|
/** The one visual an option draws before its label: its icon, else its avatar, else its chip. */
|
|
14
14
|
export type OptionLeading = 'icon' | 'avatar' | 'chip';
|
|
15
|
+
/**
|
|
16
|
+
* Start padding of a select or auto-complete that draws the chosen option's visual in its leading
|
|
17
|
+
* slot. Nuxt UI decides that padding from the slot once, so a visual that appears after mount (a
|
|
18
|
+
* value picked later) would otherwise draw over the text.
|
|
19
|
+
*/
|
|
20
|
+
export declare function leadingVisualPadding(size: unknown): "ps-10" | "ps-9" | "ps-8" | "ps-11" | "ps-7";
|
|
15
21
|
export declare function resolveOptionLeading(option: ResolvedFormOption): OptionLeading | undefined;
|
|
16
22
|
export declare function resolveOptionDescriptionIndent(option: ResolvedFormOption): string | undefined;
|
|
17
23
|
export declare function normalizeOptionItem(option: FormValue, keys?: FormOptionKeys): ResolvedFormOption;
|
|
@@ -12,6 +12,13 @@ const CHIP_COLORS = [
|
|
|
12
12
|
"error",
|
|
13
13
|
"neutral"
|
|
14
14
|
];
|
|
15
|
+
const LEADING_PADDING = { lg: "ps-10", md: "ps-9", sm: "ps-8", xl: "ps-11", xs: "ps-7" };
|
|
16
|
+
export function leadingVisualPadding(size) {
|
|
17
|
+
return LEADING_PADDING[isLeadingSize(size) ? size : "md"];
|
|
18
|
+
}
|
|
19
|
+
function isLeadingSize(size) {
|
|
20
|
+
return isString(size) && Object.hasOwn(LEADING_PADDING, size);
|
|
21
|
+
}
|
|
15
22
|
export function resolveOptionLeading(option) {
|
|
16
23
|
if (option.icon) return "icon";
|
|
17
24
|
if (option.avatar) return "avatar";
|
|
@@ -10,6 +10,10 @@ export interface FormPageSurfaceClasses {
|
|
|
10
10
|
sections: string;
|
|
11
11
|
/** The box of a card, or the spacing, hairline, and modified marker of a plain section. */
|
|
12
12
|
section: string;
|
|
13
|
+
/** The bar of a panel heading. */
|
|
14
|
+
sectionHeader: string;
|
|
15
|
+
/** Padding of the fields under a panel bar. */
|
|
16
|
+
sectionBody: string;
|
|
13
17
|
/** Where the description sits against the title. */
|
|
14
18
|
sectionDescription: string;
|
|
15
19
|
/** Placeholder shown while the schema context loads. */
|
|
@@ -5,14 +5,27 @@ const FORM_PAGE_SURFACES = {
|
|
|
5
5
|
card: {
|
|
6
6
|
root: "bg-muted",
|
|
7
7
|
section: "gap-[18px] rounded-xl border border-default bg-default px-5 pt-5 pb-6 transition-[border-color,box-shadow] duration-200 @3xl/form-page:px-6 @3xl/form-page:pt-[22px] data-[dirty]:border-primary data-[dirty]:ring-4 data-[dirty]:ring-primary/20 data-[dirty]:ring-inset",
|
|
8
|
+
sectionBody: "",
|
|
8
9
|
sectionDescription: "order-last basis-full @3xl/form-page:order-none @3xl/form-page:basis-auto",
|
|
10
|
+
sectionHeader: "",
|
|
9
11
|
sectionSkeleton: "rounded-xl border border-default bg-default px-6 pt-[22px] pb-6",
|
|
10
12
|
sections: "gap-[18px]"
|
|
11
13
|
},
|
|
14
|
+
panel: {
|
|
15
|
+
root: "bg-muted",
|
|
16
|
+
section: "overflow-hidden rounded-xl border border-default bg-default transition-[border-color,box-shadow] duration-200 focus-within:border-accented data-[dirty]:border-primary data-[dirty]:ring-4 data-[dirty]:ring-primary/20 data-[dirty]:ring-inset",
|
|
17
|
+
sectionBody: "px-5 pt-5 pb-6 @3xl/form-page:px-6",
|
|
18
|
+
sectionDescription: "order-last basis-full",
|
|
19
|
+
sectionHeader: "min-h-13 border-b border-default bg-elevated/60 px-4 py-2.5 @3xl/form-page:px-5",
|
|
20
|
+
sectionSkeleton: "rounded-xl border border-default bg-default px-6 pt-[22px] pb-6",
|
|
21
|
+
sections: "gap-4"
|
|
22
|
+
},
|
|
12
23
|
plain: {
|
|
13
24
|
root: "bg-default",
|
|
14
25
|
section: "relative gap-[18px] border-t border-default py-8 first:border-t-0 first:pt-0 last:pb-0 before:pointer-events-none before:absolute before:inset-y-8 before:-start-3 before:w-0.5 before:rounded-full before:transition-colors first:before:top-0 last:before:bottom-0 data-[dirty]:before:bg-primary",
|
|
26
|
+
sectionBody: "",
|
|
15
27
|
sectionDescription: "order-last basis-full",
|
|
28
|
+
sectionHeader: "",
|
|
16
29
|
sectionSkeleton: "border-t border-default py-8 first:border-t-0 first:pt-0",
|
|
17
30
|
sections: "gap-0"
|
|
18
31
|
}
|
|
@@ -38,3 +38,5 @@ export declare function resolveFormPageSectionState(params: {
|
|
|
38
38
|
/** False when the validation mode does not enforce required fields. */
|
|
39
39
|
requiredEnforced: boolean;
|
|
40
40
|
}): FormPageSectionState;
|
|
41
|
+
/** Columns a section takes out of twelve: a whole number from 1 to 12, the full row otherwise. */
|
|
42
|
+
export declare function resolveFormPageSectionSpan(value: FormValue): number;
|
|
@@ -76,9 +76,14 @@ export function resolveFormPageSectionState(params) {
|
|
|
76
76
|
label: resolveFormText(entry.section.label) ?? entry.section.key,
|
|
77
77
|
missing,
|
|
78
78
|
optional,
|
|
79
|
+
required: required.length,
|
|
80
|
+
span: resolveFormPageSectionSpan(entry.section.span),
|
|
79
81
|
status: resolveFormPageSectionStatus({ invalid, missing, optional, provided })
|
|
80
82
|
};
|
|
81
83
|
}
|
|
84
|
+
export function resolveFormPageSectionSpan(value) {
|
|
85
|
+
return isNumber(value) && value >= 1 ? Math.min(12, Math.trunc(value)) : 12;
|
|
86
|
+
}
|
|
82
87
|
function collectVisibleLeaves(fields, parentPath, runtime, item = false) {
|
|
83
88
|
return fields.flatMap((field) => {
|
|
84
89
|
if (field.ignore === true) {
|
|
@@ -206,6 +206,7 @@ export default defineUiToolsLocale({
|
|
|
206
206
|
},
|
|
207
207
|
page: {
|
|
208
208
|
complete: "Tout est pr\xEAt.",
|
|
209
|
+
progress: "{filled} sur {required}",
|
|
209
210
|
modifiedOne: "{count} modifi\xE9e.",
|
|
210
211
|
modifiedOther: "{count} modifi\xE9es.",
|
|
211
212
|
navigation: "Sections du formulaire",
|
|
@@ -464,6 +464,8 @@ export interface UiToolsFormMessages {
|
|
|
464
464
|
remaining: string;
|
|
465
465
|
/** Navigation summary once every required section is complete. */
|
|
466
466
|
complete: string;
|
|
467
|
+
/** Status of a section with required fields left, with `page.status`: "{filled} of {required}". */
|
|
468
|
+
progress: string;
|
|
467
469
|
/** Navigation summary of a form with dirty checking: "{count} modified." */
|
|
468
470
|
modifiedOne: string;
|
|
469
471
|
modifiedOther: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-ui-tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.21.0",
|
|
4
4
|
"description": "Nuxt module and runtime toolkit for UI-heavy Nuxt applications",
|
|
5
5
|
"homepage": "https://github.com/ChronicStone/nuxt-ui-tools#readme",
|
|
6
6
|
"bugs": "https://github.com/ChronicStone/nuxt-ui-tools/issues",
|