@groupteknology/vuno 0.6.0 → 0.6.1
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/LICENSE +21 -21
- package/README.md +45 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +6 -0
- package/dist/runtime/app/assets/css/main.css +1 -1
- package/dist/runtime/app/components/App/App.d.vue.ts +33 -0
- package/dist/runtime/app/components/App/App.vue +127 -0
- package/dist/runtime/app/components/App/App.vue.d.ts +33 -0
- package/dist/runtime/app/components/App/AppContext.d.vue.ts +3 -0
- package/dist/runtime/app/components/App/AppContext.vue +7 -0
- package/dist/runtime/app/components/App/AppContext.vue.d.ts +3 -0
- package/dist/runtime/app/components/App/AppModal.d.vue.ts +38 -0
- package/dist/runtime/app/components/App/AppModal.vue +103 -0
- package/dist/runtime/app/components/App/AppModal.vue.d.ts +38 -0
- package/dist/runtime/app/components/App/AppOverlay.d.vue.ts +7 -0
- package/dist/runtime/app/components/App/AppOverlay.vue +33 -0
- package/dist/runtime/app/components/App/AppOverlay.vue.d.ts +7 -0
- package/dist/runtime/app/components/App/AppToast.d.vue.ts +11 -0
- package/dist/runtime/app/components/App/AppToast.vue +53 -0
- package/dist/runtime/app/components/App/AppToast.vue.d.ts +11 -0
- package/dist/runtime/app/components/App/AppToaster.d.vue.ts +11 -0
- package/dist/runtime/app/components/App/AppToaster.vue +49 -0
- package/dist/runtime/app/components/App/AppToaster.vue.d.ts +11 -0
- package/dist/runtime/app/components/Confirm/Confirm.vue +20 -13
- package/dist/runtime/app/components/Field/FieldArray.vue +68 -68
- package/dist/runtime/app/components/Field/FieldColor.vue +36 -36
- package/dist/runtime/app/components/Form/Form.vue +61 -49
- package/dist/runtime/app/components/Form/FormModal.vue +23 -15
- package/dist/runtime/app/components/Form/FormSection.vue +27 -27
- package/dist/runtime/app/components/Page/Page.vue +46 -46
- package/dist/runtime/app/components/State/EmptyState.vue +20 -20
- package/dist/runtime/app/components/State/FailureState.vue +21 -21
- package/dist/runtime/app/components/Table/Table.vue +18 -114
- package/dist/runtime/app/composables/useVunoConfirm.d.ts +15 -0
- package/dist/runtime/app/composables/useVunoConfirm.js +5 -0
- package/dist/runtime/app/composables/useVunoContext.d.ts +13 -0
- package/dist/runtime/app/composables/useVunoContext.js +9 -0
- package/dist/runtime/app/composables/useVunoModal.d.ts +21 -0
- package/dist/runtime/app/composables/useVunoModal.js +26 -0
- package/dist/runtime/app/composables/useVunoOverlay.d.ts +22 -0
- package/dist/runtime/app/composables/useVunoOverlay.js +10 -0
- package/dist/runtime/app/composables/useVunoTableQuery.js +3 -3
- package/dist/runtime/app/composables/useVunoTheme.js +2 -1
- package/dist/runtime/app/composables/useVunoToast.d.ts +28 -0
- package/dist/runtime/app/composables/useVunoToast.js +13 -0
- package/dist/runtime/app/internal/useVunoTable.d.ts +65 -0
- package/dist/runtime/app/internal/useVunoTable.js +161 -0
- package/dist/runtime/app/theme/index.js +64 -11
- package/dist/runtime/app/types/app.d.ts +83 -0
- package/dist/runtime/app/types/app.js +0 -0
- package/dist/runtime/app/types/confirm.d.ts +8 -0
- package/dist/runtime/app/types/index.d.ts +1 -0
- package/dist/runtime/app/types/theme.d.ts +6 -0
- package/dist/runtime/app/utils/pagination.d.ts +12 -0
- package/dist/runtime/app/utils/pagination.js +6 -0
- package/package.json +6 -3
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { VunoNotification } from '#vuno/types/app';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
notifications: VunoNotification[];
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
6
|
+
dismiss: (id: number) => any;
|
|
7
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
8
|
+
onDismiss?: ((id: number) => any) | undefined;
|
|
9
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
|
+
declare const _default: typeof __VLS_export;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { useVunoTheme } from "#vuno/composables/useVunoTheme";
|
|
3
|
+
import AppToast from "./AppToast.vue";
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
notifications: { type: Array, required: true }
|
|
6
|
+
});
|
|
7
|
+
const emit = defineEmits(["dismiss"]);
|
|
8
|
+
const ui = useVunoTheme("appToaster");
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<!--
|
|
13
|
+
La pila se porta a `body` para que no la recorte ningún `overflow` ni la
|
|
14
|
+
reordene ningún contexto de apilamiento de la pantalla que la abrió.
|
|
15
|
+
|
|
16
|
+
`pointer-events-none` en el contenedor y `auto` en cada aviso: si no, una
|
|
17
|
+
franja invisible a lo alto de la pantalla se come los clics de lo que hay
|
|
18
|
+
detrás aunque no haya ninguna notificación.
|
|
19
|
+
-->
|
|
20
|
+
<Teleport to="body">
|
|
21
|
+
<div
|
|
22
|
+
v-if="props.notifications.length > 0"
|
|
23
|
+
:class="ui.root"
|
|
24
|
+
>
|
|
25
|
+
<!--
|
|
26
|
+
`TransitionGroup` y no `Transition`: hay varias a la vez y al
|
|
27
|
+
quitar una de en medio las de abajo tienen que subir. `move`
|
|
28
|
+
anima justo eso, que si no da un salto.
|
|
29
|
+
-->
|
|
30
|
+
<TransitionGroup
|
|
31
|
+
:class="ui.list"
|
|
32
|
+
:enterActiveClass="ui.enterActive"
|
|
33
|
+
:enterFromClass="ui.enterFrom"
|
|
34
|
+
:leaveActiveClass="ui.leaveActive"
|
|
35
|
+
:leaveToClass="ui.leaveTo"
|
|
36
|
+
:moveClass="ui.move"
|
|
37
|
+
tag="div"
|
|
38
|
+
>
|
|
39
|
+
<AppToast
|
|
40
|
+
v-for="notification in props.notifications"
|
|
41
|
+
:key="notification.id"
|
|
42
|
+
:class="ui.item"
|
|
43
|
+
:notification="notification"
|
|
44
|
+
@dismiss="emit('dismiss', notification.id)"
|
|
45
|
+
/>
|
|
46
|
+
</TransitionGroup>
|
|
47
|
+
</div>
|
|
48
|
+
</Teleport>
|
|
49
|
+
</template>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { VunoNotification } from '#vuno/types/app';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
notifications: VunoNotification[];
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
6
|
+
dismiss: (id: number) => any;
|
|
7
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
8
|
+
onDismiss?: ((id: number) => any) | undefined;
|
|
9
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
|
+
declare const _default: typeof __VLS_export;
|
|
11
|
+
export default _default;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed } from "vue";
|
|
3
|
+
import AppModal from "#vuno/components/App/AppModal.vue";
|
|
3
4
|
import { useVunoIcons, useVunoMessages, useVunoTheme } from "#vuno/composables/useVunoTheme";
|
|
4
5
|
const props = defineProps({
|
|
6
|
+
alert: { type: Boolean, required: false },
|
|
5
7
|
cancelLabel: { type: String, required: false },
|
|
6
8
|
color: { type: String, required: false },
|
|
7
9
|
confirmLabel: { type: String, required: false },
|
|
@@ -30,27 +32,32 @@ function onConfirm() {
|
|
|
30
32
|
</script>
|
|
31
33
|
|
|
32
34
|
<template>
|
|
33
|
-
|
|
35
|
+
<!--
|
|
36
|
+
Sobre `AppModal`, no sobre el de Nuxt UI, y no por gusto: un `<dialog>`
|
|
37
|
+
nativo vive en la capa superior del navegador, que queda por encima de
|
|
38
|
+
cualquier z-index. Mezclando los dos sistemas, un confirm abierto desde
|
|
39
|
+
dentro de un `AppModal` se pintaba detrás de él —estaba en el DOM y no se
|
|
40
|
+
veía—. Nativos los dos, el navegador los apila por orden de apertura.
|
|
41
|
+
-->
|
|
42
|
+
<AppModal
|
|
34
43
|
v-model:open="open"
|
|
35
44
|
:dismissible="!props.loading"
|
|
36
|
-
scrollable
|
|
37
45
|
:title="props.title ?? messages.confirmTitle"
|
|
38
46
|
:ui="modalUi"
|
|
39
47
|
>
|
|
40
|
-
<
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
<
|
|
47
|
-
<slot>{{ props.description ?? messages.confirmDescription }}</slot>
|
|
48
|
-
</div>
|
|
48
|
+
<div :class="ui.body">
|
|
49
|
+
<UIcon
|
|
50
|
+
:class="[ui.icon, `text-${color}`]"
|
|
51
|
+
:name="props.icon ?? icons.confirm"
|
|
52
|
+
/>
|
|
53
|
+
<div :class="ui.description">
|
|
54
|
+
<slot>{{ props.description ?? messages.confirmDescription }}</slot>
|
|
49
55
|
</div>
|
|
50
|
-
</
|
|
56
|
+
</div>
|
|
51
57
|
|
|
52
58
|
<template #footer>
|
|
53
59
|
<UButton
|
|
60
|
+
v-if="!props.alert"
|
|
54
61
|
color="neutral"
|
|
55
62
|
:disabled="props.loading"
|
|
56
63
|
:label="props.cancelLabel ?? messages.cancel"
|
|
@@ -64,5 +71,5 @@ function onConfirm() {
|
|
|
64
71
|
@click="onConfirm"
|
|
65
72
|
/>
|
|
66
73
|
</template>
|
|
67
|
-
</
|
|
74
|
+
</AppModal>
|
|
68
75
|
</template>
|
|
@@ -57,72 +57,72 @@ function move(index, offset) {
|
|
|
57
57
|
</script>
|
|
58
58
|
|
|
59
59
|
<template>
|
|
60
|
-
<div :class="ui.root">
|
|
61
|
-
<div
|
|
62
|
-
v-for="(item, index) in items"
|
|
63
|
-
:key="index"
|
|
64
|
-
:class="ui.item"
|
|
65
|
-
>
|
|
66
|
-
<div :class="ui.itemHeader">
|
|
67
|
-
<span :class="ui.itemLabel">{{ index + 1 }}</span>
|
|
68
|
-
|
|
69
|
-
<div :class="ui.itemActions">
|
|
70
|
-
<UButton
|
|
71
|
-
v-if="props.sortable && index > 0"
|
|
72
|
-
:aria-label="messages.fileMoveUp"
|
|
73
|
-
color="neutral"
|
|
74
|
-
:icon="icons.fileMoveUp"
|
|
75
|
-
size="xs"
|
|
76
|
-
variant="ghost"
|
|
77
|
-
@click="move(index, -1)"
|
|
78
|
-
/>
|
|
79
|
-
<UButton
|
|
80
|
-
v-if="props.sortable && index < items.length - 1"
|
|
81
|
-
:aria-label="messages.fileMoveDown"
|
|
82
|
-
color="neutral"
|
|
83
|
-
:icon="icons.fileMoveDown"
|
|
84
|
-
size="xs"
|
|
85
|
-
variant="ghost"
|
|
86
|
-
@click="move(index, 1)"
|
|
87
|
-
/>
|
|
88
|
-
<UButton
|
|
89
|
-
:aria-label="messages.removeItem"
|
|
90
|
-
color="error"
|
|
91
|
-
:disabled="!canRemove"
|
|
92
|
-
:icon="icons.removeItem"
|
|
93
|
-
size="xs"
|
|
94
|
-
variant="ghost"
|
|
95
|
-
@click="remove(index)"
|
|
96
|
-
/>
|
|
97
|
-
</div>
|
|
98
|
-
</div>
|
|
99
|
-
|
|
100
|
-
<div :class="ui.itemFields">
|
|
101
|
-
<div
|
|
102
|
-
v-for="field in props.fields"
|
|
103
|
-
:key="`${index}-${field.name}`"
|
|
104
|
-
:class="getColumnSpanClass(field.columns)"
|
|
105
|
-
>
|
|
106
|
-
<FormField :field="fieldAt(field, index)" />
|
|
107
|
-
</div>
|
|
108
|
-
</div>
|
|
109
|
-
</div>
|
|
110
|
-
|
|
111
|
-
<div
|
|
112
|
-
v-if="items.length === 0"
|
|
113
|
-
:class="ui.empty"
|
|
114
|
-
>
|
|
115
|
-
{{ messages.emptyArray }}
|
|
116
|
-
</div>
|
|
117
|
-
|
|
118
|
-
<UButton
|
|
119
|
-
class="self-start"
|
|
120
|
-
color="neutral"
|
|
121
|
-
:disabled="!canAdd"
|
|
122
|
-
:icon="icons.addItem"
|
|
123
|
-
:label="props.addLabel ?? messages.addItem"
|
|
124
|
-
variant="outline"
|
|
125
|
-
@click="add"
|
|
126
|
-
/>
|
|
127
|
-
</div>
|
|
60
|
+
<div :class="ui.root">
|
|
61
|
+
<div
|
|
62
|
+
v-for="(item, index) in items"
|
|
63
|
+
:key="index"
|
|
64
|
+
:class="ui.item"
|
|
65
|
+
>
|
|
66
|
+
<div :class="ui.itemHeader">
|
|
67
|
+
<span :class="ui.itemLabel">{{ index + 1 }}</span>
|
|
68
|
+
|
|
69
|
+
<div :class="ui.itemActions">
|
|
70
|
+
<UButton
|
|
71
|
+
v-if="props.sortable && index > 0"
|
|
72
|
+
:aria-label="messages.fileMoveUp"
|
|
73
|
+
color="neutral"
|
|
74
|
+
:icon="icons.fileMoveUp"
|
|
75
|
+
size="xs"
|
|
76
|
+
variant="ghost"
|
|
77
|
+
@click="move(index, -1)"
|
|
78
|
+
/>
|
|
79
|
+
<UButton
|
|
80
|
+
v-if="props.sortable && index < items.length - 1"
|
|
81
|
+
:aria-label="messages.fileMoveDown"
|
|
82
|
+
color="neutral"
|
|
83
|
+
:icon="icons.fileMoveDown"
|
|
84
|
+
size="xs"
|
|
85
|
+
variant="ghost"
|
|
86
|
+
@click="move(index, 1)"
|
|
87
|
+
/>
|
|
88
|
+
<UButton
|
|
89
|
+
:aria-label="messages.removeItem"
|
|
90
|
+
color="error"
|
|
91
|
+
:disabled="!canRemove"
|
|
92
|
+
:icon="icons.removeItem"
|
|
93
|
+
size="xs"
|
|
94
|
+
variant="ghost"
|
|
95
|
+
@click="remove(index)"
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div :class="ui.itemFields">
|
|
101
|
+
<div
|
|
102
|
+
v-for="field in props.fields"
|
|
103
|
+
:key="`${index}-${field.name}`"
|
|
104
|
+
:class="getColumnSpanClass(field.columns)"
|
|
105
|
+
>
|
|
106
|
+
<FormField :field="fieldAt(field, index)" />
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div
|
|
112
|
+
v-if="items.length === 0"
|
|
113
|
+
:class="ui.empty"
|
|
114
|
+
>
|
|
115
|
+
{{ messages.emptyArray }}
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<UButton
|
|
119
|
+
class="self-start"
|
|
120
|
+
color="neutral"
|
|
121
|
+
:disabled="!canAdd"
|
|
122
|
+
:icon="icons.addItem"
|
|
123
|
+
:label="props.addLabel ?? messages.addItem"
|
|
124
|
+
variant="outline"
|
|
125
|
+
@click="add"
|
|
126
|
+
/>
|
|
127
|
+
</div>
|
|
128
128
|
</template>
|
|
@@ -18,40 +18,40 @@ const ui = useVunoTheme("fieldColor", () => props.ui);
|
|
|
18
18
|
</script>
|
|
19
19
|
|
|
20
20
|
<template>
|
|
21
|
-
<div :class="ui.root">
|
|
22
|
-
<!-- El selector nativo y el texto comparten valor: se puede pegar un
|
|
23
|
-
código de la guía de marca sin pelearse con la rueda de color. -->
|
|
24
|
-
<UPopover>
|
|
25
|
-
<UButton
|
|
26
|
-
:aria-label="props.label"
|
|
27
|
-
color="neutral"
|
|
28
|
-
:disabled="props.disabled"
|
|
29
|
-
:size="props.size"
|
|
30
|
-
:ui="{ leadingIcon: 'rounded-full' }"
|
|
31
|
-
variant="outline"
|
|
32
|
-
>
|
|
33
|
-
<span
|
|
34
|
-
:class="ui.swatch"
|
|
35
|
-
:style="{ backgroundColor: value || 'transparent' }"
|
|
36
|
-
/>
|
|
37
|
-
</UButton>
|
|
38
|
-
|
|
39
|
-
<template #content>
|
|
40
|
-
<UColorPicker
|
|
41
|
-
v-model="value"
|
|
42
|
-
class="p-2"
|
|
43
|
-
/>
|
|
44
|
-
</template>
|
|
45
|
-
</UPopover>
|
|
46
|
-
|
|
47
|
-
<UInput
|
|
48
|
-
v-model="value"
|
|
49
|
-
class="font-mono"
|
|
50
|
-
:disabled="props.disabled"
|
|
51
|
-
:name="props.name"
|
|
52
|
-
:placeholder="props.placeholder ?? '#000000'"
|
|
53
|
-
:size="props.size"
|
|
54
|
-
:ui="{ root: 'w-full' }"
|
|
55
|
-
/>
|
|
56
|
-
</div>
|
|
21
|
+
<div :class="ui.root">
|
|
22
|
+
<!-- El selector nativo y el texto comparten valor: se puede pegar un
|
|
23
|
+
código de la guía de marca sin pelearse con la rueda de color. -->
|
|
24
|
+
<UPopover>
|
|
25
|
+
<UButton
|
|
26
|
+
:aria-label="props.label"
|
|
27
|
+
color="neutral"
|
|
28
|
+
:disabled="props.disabled"
|
|
29
|
+
:size="props.size"
|
|
30
|
+
:ui="{ leadingIcon: 'rounded-full' }"
|
|
31
|
+
variant="outline"
|
|
32
|
+
>
|
|
33
|
+
<span
|
|
34
|
+
:class="ui.swatch"
|
|
35
|
+
:style="{ backgroundColor: value || 'transparent' }"
|
|
36
|
+
/>
|
|
37
|
+
</UButton>
|
|
38
|
+
|
|
39
|
+
<template #content>
|
|
40
|
+
<UColorPicker
|
|
41
|
+
v-model="value"
|
|
42
|
+
class="p-2"
|
|
43
|
+
/>
|
|
44
|
+
</template>
|
|
45
|
+
</UPopover>
|
|
46
|
+
|
|
47
|
+
<UInput
|
|
48
|
+
v-model="value"
|
|
49
|
+
class="font-mono"
|
|
50
|
+
:disabled="props.disabled"
|
|
51
|
+
:name="props.name"
|
|
52
|
+
:placeholder="props.placeholder ?? '#000000'"
|
|
53
|
+
:size="props.size"
|
|
54
|
+
:ui="{ root: 'w-full' }"
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
57
|
</template>
|
|
@@ -86,53 +86,65 @@ defineExpose({ dirty, errors: errorNames, isDirty, reset, resetField, submit: ()
|
|
|
86
86
|
</script>
|
|
87
87
|
|
|
88
88
|
<template>
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
>
|
|
118
|
-
<
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
:
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
89
|
+
<!--
|
|
90
|
+
`novalidate` apaga la validación nativa del navegador, no el `required`.
|
|
91
|
+
Sin él conviven dos validaciones: la del navegador corta el envío antes
|
|
92
|
+
de que el esquema llegue a ejecutarse, y avisa con un globo suyo, en el
|
|
93
|
+
idioma del navegador y sin pasar por el tema. Los mensajes del esquema
|
|
94
|
+
no llegaban a verse en los campos obligatorios.
|
|
95
|
+
|
|
96
|
+
El atributo `required` se queda en los inputs, que es lo que anuncia el
|
|
97
|
+
campo como obligatorio a un lector de pantalla y lo que permite
|
|
98
|
+
estilarlo; lo único que se apaga es la interfaz nativa.
|
|
99
|
+
-->
|
|
100
|
+
<UForm
|
|
101
|
+
:id="formId"
|
|
102
|
+
ref="form"
|
|
103
|
+
novalidate
|
|
104
|
+
:schema="props.schema"
|
|
105
|
+
:state="state"
|
|
106
|
+
:ui="{ base: ui.base }"
|
|
107
|
+
@submit="onSubmit"
|
|
108
|
+
>
|
|
109
|
+
<UTabs
|
|
110
|
+
v-if="isTabs"
|
|
111
|
+
:defaultValue="tabItems[0]?.value"
|
|
112
|
+
:items="tabItems"
|
|
113
|
+
:size="size"
|
|
114
|
+
:ui="{ root: ui.tabs, content: ui.tabsContent }"
|
|
115
|
+
:unmountOnHide="false"
|
|
116
|
+
variant="link"
|
|
117
|
+
>
|
|
118
|
+
<template #content="{ item }">
|
|
119
|
+
<FormSection
|
|
120
|
+
plain
|
|
121
|
+
:section="item.section"
|
|
122
|
+
/>
|
|
123
|
+
</template>
|
|
124
|
+
</UTabs>
|
|
125
|
+
|
|
126
|
+
<div
|
|
127
|
+
v-else
|
|
128
|
+
:class="ui.sections"
|
|
129
|
+
>
|
|
130
|
+
<div
|
|
131
|
+
v-for="(section, index) in props.definition.sections"
|
|
132
|
+
:key="`${section.id}-${index}`"
|
|
133
|
+
:class="getColumnSpanClass(section.columns)"
|
|
134
|
+
>
|
|
135
|
+
<FormSection :section="section" />
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<div
|
|
140
|
+
v-if="slots.footer"
|
|
141
|
+
:class="ui.footer"
|
|
142
|
+
>
|
|
143
|
+
<slot
|
|
144
|
+
:dirty="dirty"
|
|
145
|
+
name="footer"
|
|
146
|
+
:reset="reset"
|
|
147
|
+
/>
|
|
148
|
+
</div>
|
|
149
|
+
</UForm>
|
|
138
150
|
</template>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, useId, useTemplateRef } from "vue";
|
|
3
3
|
import { useVunoMessages, useVunoTheme } from "#vuno/composables/useVunoTheme";
|
|
4
|
+
import AppModal from "#vuno/components/App/AppModal.vue";
|
|
4
5
|
import Form from "./Form.vue";
|
|
5
6
|
const props = defineProps({
|
|
6
7
|
closeOnSubmit: { type: Boolean, required: false },
|
|
@@ -18,7 +19,7 @@ const state = defineModel("state", { type: null, ...{ required: true } });
|
|
|
18
19
|
defineSlots();
|
|
19
20
|
const messages = useVunoMessages();
|
|
20
21
|
const ui = useVunoTheme("formModal", () => props.ui);
|
|
21
|
-
const modalUi = computed(() => ({ body: ui.value.body, content: ui.value.content, footer: ui.value.footer }));
|
|
22
|
+
const modalUi = computed(() => ({ body: ui.value.body, content: ui.value.content, footer: ui.value.footer, root: ui.value.root }));
|
|
22
23
|
const formId = useId();
|
|
23
24
|
const form = useTemplateRef("form");
|
|
24
25
|
function close() {
|
|
@@ -33,27 +34,34 @@ function onSubmit(event) {
|
|
|
33
34
|
</script>
|
|
34
35
|
|
|
35
36
|
<template>
|
|
36
|
-
|
|
37
|
+
<!--
|
|
38
|
+
Sobre `AppModal`: un `<dialog>` nativo, no el de Nuxt UI. El navegador lo
|
|
39
|
+
pone en su capa superior, que atrapa el foco, lo devuelve al cerrar y
|
|
40
|
+
apila los diálogos por orden de apertura.
|
|
41
|
+
|
|
42
|
+
Importa que sean todos del mismo sistema: la capa superior queda por
|
|
43
|
+
encima de cualquier z-index, así que un formulario posicionado con
|
|
44
|
+
z-index abierto sobre un diálogo nativo se pintaría detrás de él, en el
|
|
45
|
+
DOM y sin verse.
|
|
46
|
+
-->
|
|
47
|
+
<AppModal
|
|
37
48
|
v-model:open="open"
|
|
38
49
|
:description="props.description"
|
|
39
50
|
:dismissible="!props.loading"
|
|
40
|
-
scrollable
|
|
41
51
|
:title="props.title"
|
|
42
52
|
:ui="modalUi"
|
|
43
53
|
>
|
|
44
54
|
<slot name="trigger" />
|
|
45
55
|
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
/>
|
|
56
|
-
</template>
|
|
56
|
+
<Form
|
|
57
|
+
:id="formId"
|
|
58
|
+
ref="form"
|
|
59
|
+
v-model:state="state"
|
|
60
|
+
:definition="props.definition"
|
|
61
|
+
:schema="props.schema"
|
|
62
|
+
:ui="props.formUi"
|
|
63
|
+
@submit="onSubmit"
|
|
64
|
+
/>
|
|
57
65
|
|
|
58
66
|
<template #footer>
|
|
59
67
|
<slot
|
|
@@ -79,5 +87,5 @@ function onSubmit(event) {
|
|
|
79
87
|
/>
|
|
80
88
|
</slot>
|
|
81
89
|
</template>
|
|
82
|
-
</
|
|
90
|
+
</AppModal>
|
|
83
91
|
</template>
|
|
@@ -13,31 +13,31 @@ const showHeader = computed(() => props.plain ? Boolean(props.section.descriptio
|
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
15
|
<template>
|
|
16
|
-
<div :class="root">
|
|
17
|
-
<div
|
|
18
|
-
v-if="showHeader"
|
|
19
|
-
:class="ui.header"
|
|
20
|
-
>
|
|
21
|
-
<p
|
|
22
|
-
v-if="props.section.title && !props.plain"
|
|
23
|
-
:class="ui.title"
|
|
24
|
-
>
|
|
25
|
-
{{ props.section.title }}
|
|
26
|
-
</p>
|
|
27
|
-
<p
|
|
28
|
-
v-if="props.section.description"
|
|
29
|
-
:class="ui.description"
|
|
30
|
-
>
|
|
31
|
-
{{ props.section.description }}
|
|
32
|
-
</p>
|
|
33
|
-
</div>
|
|
34
|
-
|
|
35
|
-
<div
|
|
36
|
-
v-for="(field, index) in props.section.fields"
|
|
37
|
-
:key="`${field.name}-${index}`"
|
|
38
|
-
:class="getColumnSpanClass(field.columns)"
|
|
39
|
-
>
|
|
40
|
-
<FormField :field="field" />
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
16
|
+
<div :class="root">
|
|
17
|
+
<div
|
|
18
|
+
v-if="showHeader"
|
|
19
|
+
:class="ui.header"
|
|
20
|
+
>
|
|
21
|
+
<p
|
|
22
|
+
v-if="props.section.title && !props.plain"
|
|
23
|
+
:class="ui.title"
|
|
24
|
+
>
|
|
25
|
+
{{ props.section.title }}
|
|
26
|
+
</p>
|
|
27
|
+
<p
|
|
28
|
+
v-if="props.section.description"
|
|
29
|
+
:class="ui.description"
|
|
30
|
+
>
|
|
31
|
+
{{ props.section.description }}
|
|
32
|
+
</p>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div
|
|
36
|
+
v-for="(field, index) in props.section.fields"
|
|
37
|
+
:key="`${field.name}-${index}`"
|
|
38
|
+
:class="getColumnSpanClass(field.columns)"
|
|
39
|
+
>
|
|
40
|
+
<FormField :field="field" />
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
43
|
</template>
|