@groupteknology/vuno 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +215 -0
  3. package/dist/module.d.mts +10 -0
  4. package/dist/module.json +9 -0
  5. package/dist/module.mjs +28 -0
  6. package/dist/runtime/app/assets/css/main.css +1 -0
  7. package/dist/runtime/app/components/Confirm/Confirm.d.vue.ts +21 -0
  8. package/dist/runtime/app/components/Confirm/Confirm.vue +65 -0
  9. package/dist/runtime/app/components/Confirm/Confirm.vue.d.ts +21 -0
  10. package/dist/runtime/app/components/Field/FieldArray.d.vue.ts +5 -0
  11. package/dist/runtime/app/components/Field/FieldArray.vue +128 -0
  12. package/dist/runtime/app/components/Field/FieldArray.vue.d.ts +5 -0
  13. package/dist/runtime/app/components/Field/FieldCheckbox.d.vue.ts +5 -0
  14. package/dist/runtime/app/components/Field/FieldCheckbox.vue +30 -0
  15. package/dist/runtime/app/components/Field/FieldCheckbox.vue.d.ts +5 -0
  16. package/dist/runtime/app/components/Field/FieldColor.d.vue.ts +5 -0
  17. package/dist/runtime/app/components/Field/FieldColor.vue +57 -0
  18. package/dist/runtime/app/components/Field/FieldColor.vue.d.ts +5 -0
  19. package/dist/runtime/app/components/Field/FieldEmail.d.vue.ts +5 -0
  20. package/dist/runtime/app/components/Field/FieldEmail.vue +37 -0
  21. package/dist/runtime/app/components/Field/FieldEmail.vue.d.ts +5 -0
  22. package/dist/runtime/app/components/Field/FieldFile.d.vue.ts +5 -0
  23. package/dist/runtime/app/components/Field/FieldFile.vue +93 -0
  24. package/dist/runtime/app/components/Field/FieldFile.vue.d.ts +5 -0
  25. package/dist/runtime/app/components/Field/FieldNumber.d.vue.ts +5 -0
  26. package/dist/runtime/app/components/Field/FieldNumber.vue +38 -0
  27. package/dist/runtime/app/components/Field/FieldNumber.vue.d.ts +5 -0
  28. package/dist/runtime/app/components/Field/FieldPassword.d.vue.ts +5 -0
  29. package/dist/runtime/app/components/Field/FieldPassword.vue +35 -0
  30. package/dist/runtime/app/components/Field/FieldPassword.vue.d.ts +5 -0
  31. package/dist/runtime/app/components/Field/FieldRadio.d.vue.ts +5 -0
  32. package/dist/runtime/app/components/Field/FieldRadio.vue +33 -0
  33. package/dist/runtime/app/components/Field/FieldRadio.vue.d.ts +5 -0
  34. package/dist/runtime/app/components/Field/FieldSelect.d.vue.ts +5 -0
  35. package/dist/runtime/app/components/Field/FieldSelect.vue +38 -0
  36. package/dist/runtime/app/components/Field/FieldSelect.vue.d.ts +5 -0
  37. package/dist/runtime/app/components/Field/FieldSlug.d.vue.ts +5 -0
  38. package/dist/runtime/app/components/Field/FieldSlug.vue +92 -0
  39. package/dist/runtime/app/components/Field/FieldSlug.vue.d.ts +5 -0
  40. package/dist/runtime/app/components/Field/FieldText.d.vue.ts +5 -0
  41. package/dist/runtime/app/components/Field/FieldText.vue +38 -0
  42. package/dist/runtime/app/components/Field/FieldText.vue.d.ts +5 -0
  43. package/dist/runtime/app/components/Field/FieldTextarea.d.vue.ts +5 -0
  44. package/dist/runtime/app/components/Field/FieldTextarea.vue +36 -0
  45. package/dist/runtime/app/components/Field/FieldTextarea.vue.d.ts +5 -0
  46. package/dist/runtime/app/components/Form/Form.d.vue.ts +48 -0
  47. package/dist/runtime/app/components/Form/Form.vue +137 -0
  48. package/dist/runtime/app/components/Form/Form.vue.d.ts +48 -0
  49. package/dist/runtime/app/components/Form/FormField.d.vue.ts +7 -0
  50. package/dist/runtime/app/components/Form/FormField.vue +61 -0
  51. package/dist/runtime/app/components/Form/FormField.vue.d.ts +7 -0
  52. package/dist/runtime/app/components/Form/FormModal.d.vue.ts +54 -0
  53. package/dist/runtime/app/components/Form/FormModal.vue +82 -0
  54. package/dist/runtime/app/components/Form/FormModal.vue.d.ts +54 -0
  55. package/dist/runtime/app/components/Form/FormSection.d.vue.ts +12 -0
  56. package/dist/runtime/app/components/Form/FormSection.vue +43 -0
  57. package/dist/runtime/app/components/Form/FormSection.vue.d.ts +12 -0
  58. package/dist/runtime/app/components/Page/Page.d.vue.ts +24 -0
  59. package/dist/runtime/app/components/Page/Page.vue +63 -0
  60. package/dist/runtime/app/components/Page/Page.vue.d.ts +24 -0
  61. package/dist/runtime/app/components/State/EmptyState.d.vue.ts +20 -0
  62. package/dist/runtime/app/components/State/EmptyState.vue +36 -0
  63. package/dist/runtime/app/components/State/EmptyState.vue.d.ts +20 -0
  64. package/dist/runtime/app/components/State/FailureState.d.vue.ts +10 -0
  65. package/dist/runtime/app/components/State/FailureState.vue +34 -0
  66. package/dist/runtime/app/components/State/FailureState.vue.d.ts +10 -0
  67. package/dist/runtime/app/components/Table/Table.d.vue.ts +70 -0
  68. package/dist/runtime/app/components/Table/Table.vue +432 -0
  69. package/dist/runtime/app/components/Table/Table.vue.d.ts +70 -0
  70. package/dist/runtime/app/components/Table/TableCell.d.vue.ts +8 -0
  71. package/dist/runtime/app/components/Table/TableCell.vue +84 -0
  72. package/dist/runtime/app/components/Table/TableCell.vue.d.ts +8 -0
  73. package/dist/runtime/app/composables/useVunoCrud.d.ts +23 -0
  74. package/dist/runtime/app/composables/useVunoCrud.js +117 -0
  75. package/dist/runtime/app/composables/useVunoField.d.ts +10 -0
  76. package/dist/runtime/app/composables/useVunoField.js +34 -0
  77. package/dist/runtime/app/composables/useVunoForm.d.ts +2 -0
  78. package/dist/runtime/app/composables/useVunoForm.js +9 -0
  79. package/dist/runtime/app/composables/useVunoTableQuery.d.ts +51 -0
  80. package/dist/runtime/app/composables/useVunoTableQuery.js +110 -0
  81. package/dist/runtime/app/composables/useVunoTheme.d.ts +12 -0
  82. package/dist/runtime/app/composables/useVunoTheme.js +35 -0
  83. package/dist/runtime/app/theme/index.d.ts +14 -0
  84. package/dist/runtime/app/theme/index.js +210 -0
  85. package/dist/runtime/app/types/app-config.d.ts +8 -0
  86. package/dist/runtime/app/types/app-config.js +1 -0
  87. package/dist/runtime/app/types/confirm.d.ts +11 -0
  88. package/dist/runtime/app/types/confirm.js +0 -0
  89. package/dist/runtime/app/types/crud.d.ts +64 -0
  90. package/dist/runtime/app/types/crud.js +0 -0
  91. package/dist/runtime/app/types/field.d.ts +121 -0
  92. package/dist/runtime/app/types/field.js +0 -0
  93. package/dist/runtime/app/types/form.d.ts +39 -0
  94. package/dist/runtime/app/types/form.js +1 -0
  95. package/dist/runtime/app/types/index.d.ts +9 -0
  96. package/dist/runtime/app/types/index.js +1 -0
  97. package/dist/runtime/app/types/page.d.ts +6 -0
  98. package/dist/runtime/app/types/page.js +0 -0
  99. package/dist/runtime/app/types/responsive.d.ts +8 -0
  100. package/dist/runtime/app/types/responsive.js +0 -0
  101. package/dist/runtime/app/types/table.d.ts +130 -0
  102. package/dist/runtime/app/types/table.js +0 -0
  103. package/dist/runtime/app/types/theme.d.ts +77 -0
  104. package/dist/runtime/app/types/theme.js +0 -0
  105. package/dist/runtime/app/utils/format.d.ts +9 -0
  106. package/dist/runtime/app/utils/format.js +30 -0
  107. package/dist/runtime/app/utils/grid.d.ts +9 -0
  108. package/dist/runtime/app/utils/grid.js +91 -0
  109. package/dist/runtime/app/utils/path.d.ts +16 -0
  110. package/dist/runtime/app/utils/path.js +74 -0
  111. package/dist/types.d.mts +5 -0
  112. package/package.json +83 -0
@@ -0,0 +1,137 @@
1
+ <script setup>
2
+ import { computed, provide, reactive, toRaw, useId, useTemplateRef } from "vue";
3
+ import { useVunoTheme } from "#vuno/composables/useVunoTheme";
4
+ import { vunoFormInjectionKey } from "#vuno/types/form";
5
+ import { getColumnSpanClass } from "#vuno/utils/grid";
6
+ import { cloneValue, getPath, isEqual, setPath } from "#vuno/utils/path";
7
+ import FormSection from "./FormSection.vue";
8
+ const props = defineProps({
9
+ id: { type: String, required: false },
10
+ definition: { type: Object, required: true },
11
+ schema: { type: null, required: false },
12
+ ui: { type: Object, required: false }
13
+ });
14
+ const emit = defineEmits(["submit"]);
15
+ const state = defineModel("state", { type: null, ...{ required: true } });
16
+ const slots = defineSlots();
17
+ const ui = useVunoTheme("form", () => props.ui);
18
+ const fallbackId = useId();
19
+ const formId = computed(() => props.id ?? fallbackId);
20
+ const form = useTemplateRef("form");
21
+ const initialState = cloneValue(toRaw(state.value));
22
+ const touched = reactive({});
23
+ const dirty = computed(() => !isEqual(state.value, initialState));
24
+ const size = computed(() => props.definition.size);
25
+ const isTabs = computed(() => props.definition.layout === "tabs");
26
+ const errorNames = computed(() => new Set((form.value?.errors ?? []).map((error) => error.name).filter(Boolean)));
27
+ const tabItems = computed(
28
+ () => props.definition.sections.map((section, index) => {
29
+ const errors = section.fields.filter((field) => errorNames.value.has(field.name) || [...errorNames.value].some((name) => name?.startsWith(`${field.name}.`))).length;
30
+ return {
31
+ // `UTabs` fija `variant="outline"` en su badge, que sobre la
32
+ // pestaña activa queda sin contraste: se pasa `solid` para que
33
+ // el contador se lea sobre cualquier fondo.
34
+ badge: errors > 0 ? { color: "error", label: String(errors), variant: "solid" } : void 0,
35
+ icon: section.icon,
36
+ label: section.title ?? section.id,
37
+ section,
38
+ value: `${section.id}-${index}`
39
+ };
40
+ })
41
+ );
42
+ function getValue(name) {
43
+ return getPath(state.value, name);
44
+ }
45
+ function setValue(name, value) {
46
+ setPath(state.value, name, value);
47
+ }
48
+ function isTouched(name) {
49
+ return touched[name] ?? false;
50
+ }
51
+ function setTouched(name, value = true) {
52
+ touched[name] = value;
53
+ }
54
+ function isDirty(name) {
55
+ if (!name) return dirty.value;
56
+ return !isEqual(getPath(state.value, name), getPath(initialState, name));
57
+ }
58
+ function resetField(name) {
59
+ setPath(state.value, name, cloneValue(getPath(initialState, name)));
60
+ touched[name] = false;
61
+ }
62
+ function reset() {
63
+ state.value = cloneValue(initialState);
64
+ for (const key of Object.keys(touched)) {
65
+ delete touched[key];
66
+ }
67
+ }
68
+ const context = {
69
+ dirty,
70
+ getValue,
71
+ initialState,
72
+ isDirty,
73
+ isTouched,
74
+ reset,
75
+ resetField,
76
+ setTouched,
77
+ setValue,
78
+ size,
79
+ touched
80
+ };
81
+ provide(vunoFormInjectionKey, context);
82
+ function onSubmit(event) {
83
+ emit("submit", event);
84
+ }
85
+ defineExpose({ dirty, errors: errorNames, isDirty, reset, resetField, submit: () => form.value?.submit?.() });
86
+ </script>
87
+
88
+ <template>
89
+ <UForm
90
+ :id="formId"
91
+ ref="form"
92
+ :schema="props.schema"
93
+ :state="state"
94
+ :ui="{ base: ui.base }"
95
+ @submit="onSubmit"
96
+ >
97
+ <UTabs
98
+ v-if="isTabs"
99
+ :defaultValue="tabItems[0]?.value"
100
+ :items="tabItems"
101
+ :size="size"
102
+ :ui="{ root: ui.tabs, content: ui.tabsContent }"
103
+ :unmountOnHide="false"
104
+ >
105
+ <template #content="{ item }">
106
+ <FormSection
107
+ plain
108
+ :section="item.section"
109
+ />
110
+ </template>
111
+ </UTabs>
112
+
113
+ <div
114
+ v-else
115
+ :class="ui.sections"
116
+ >
117
+ <div
118
+ v-for="(section, index) in props.definition.sections"
119
+ :key="`${section.id}-${index}`"
120
+ :class="getColumnSpanClass(section.columns)"
121
+ >
122
+ <FormSection :section="section" />
123
+ </div>
124
+ </div>
125
+
126
+ <div
127
+ v-if="slots.footer"
128
+ :class="ui.footer"
129
+ >
130
+ <slot
131
+ :dirty="dirty"
132
+ name="footer"
133
+ :reset="reset"
134
+ />
135
+ </div>
136
+ </UForm>
137
+ </template>
@@ -0,0 +1,48 @@
1
+ import type { FormSchema, FormSubmitEvent } from '@nuxt/ui';
2
+ import type { VunoFieldSlots } from '#vuno/types/field';
3
+ import { type VunoFormDefinition, type VunoFormState } from '#vuno/types/form';
4
+ declare const __VLS_export: <TState extends VunoFormState>(__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<{
5
+ props: import("vue").PublicProps & __VLS_PrettifyLocal<({
6
+ /**
7
+ * Id del `<form>`. Permite enviarlo desde un botón que vive fuera de
8
+ * él —el pie de un modal, por ejemplo— con `<button form="...">`.
9
+ */
10
+ id?: string;
11
+ definition: VunoFormDefinition;
12
+ /** Opcional: un panel de filtros, por ejemplo, no necesita validación. */
13
+ schema?: FormSchema;
14
+ ui?: VunoFieldSlots;
15
+ } & {
16
+ state: TState;
17
+ }) & {
18
+ onSubmit?: ((event: FormSubmitEvent<TState>) => any) | undefined;
19
+ "onUpdate:state"?: ((value: TState) => any) | undefined;
20
+ }> & (typeof globalThis extends {
21
+ __VLS_PROPS_FALLBACK: infer P;
22
+ } ? P : {});
23
+ expose: (exposed: import("vue").ShallowUnwrapRef<{
24
+ dirty: import("vue").ComputedRef<boolean>;
25
+ errors: import("vue").ComputedRef<Set<string | undefined>>;
26
+ isDirty: (name?: string) => boolean;
27
+ reset: () => void;
28
+ resetField: (name: string) => void;
29
+ submit: () => Promise<void> | undefined;
30
+ }>) => void;
31
+ attrs: any;
32
+ slots: {
33
+ footer?: (props: {
34
+ dirty: boolean;
35
+ reset: () => void;
36
+ }) => unknown;
37
+ };
38
+ emit: ((evt: "submit", event: FormSubmitEvent<TState>) => void) & ((event: "update:state", value: TState) => void);
39
+ }>) => import("vue").VNode & {
40
+ __ctx?: NonNullable<Awaited<typeof __VLS_setup>>;
41
+ };
42
+ declare const _default: typeof __VLS_export;
43
+ export default _default;
44
+ type __VLS_PrettifyLocal<T> = (T extends any ? {
45
+ [K in keyof T]: T[K];
46
+ } : {
47
+ [K in keyof T as K]: T[K];
48
+ }) & {};
@@ -0,0 +1,7 @@
1
+ import type { VunoField } from '#vuno/types/field';
2
+ type __VLS_Props = {
3
+ field: VunoField;
4
+ };
5
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -0,0 +1,61 @@
1
+ <script setup>
2
+ import { computed } from "vue";
3
+ import { useVunoForm } from "#vuno/composables/useVunoForm";
4
+ import { useVunoTheme } from "#vuno/composables/useVunoTheme";
5
+ import FieldArray from "#vuno/components/Field/FieldArray.vue";
6
+ import FieldCheckbox from "#vuno/components/Field/FieldCheckbox.vue";
7
+ import FieldColor from "#vuno/components/Field/FieldColor.vue";
8
+ import FieldEmail from "#vuno/components/Field/FieldEmail.vue";
9
+ import FieldFile from "#vuno/components/Field/FieldFile.vue";
10
+ import FieldNumber from "#vuno/components/Field/FieldNumber.vue";
11
+ import FieldPassword from "#vuno/components/Field/FieldPassword.vue";
12
+ import FieldRadio from "#vuno/components/Field/FieldRadio.vue";
13
+ import FieldSelect from "#vuno/components/Field/FieldSelect.vue";
14
+ import FieldSlug from "#vuno/components/Field/FieldSlug.vue";
15
+ import FieldText from "#vuno/components/Field/FieldText.vue";
16
+ import FieldTextarea from "#vuno/components/Field/FieldTextarea.vue";
17
+ const props = defineProps({
18
+ field: { type: Object, required: true }
19
+ });
20
+ const components = {
21
+ array: FieldArray,
22
+ checkbox: FieldCheckbox,
23
+ color: FieldColor,
24
+ email: FieldEmail,
25
+ file: FieldFile,
26
+ number: FieldNumber,
27
+ password: FieldPassword,
28
+ radio: FieldRadio,
29
+ select: FieldSelect,
30
+ slug: FieldSlug,
31
+ text: FieldText,
32
+ textarea: FieldTextarea
33
+ };
34
+ const form = useVunoForm();
35
+ const ui = useVunoTheme("formField");
36
+ const component = computed(() => components[props.field.type]);
37
+ const size = computed(() => props.field.size ?? form.size.value);
38
+ const fieldProps = computed(() => {
39
+ const { class: _class, columns: _columns, type: _type, ...rest } = props.field;
40
+ return { ...rest, size: size.value };
41
+ });
42
+ </script>
43
+
44
+ <template>
45
+ <UFormField
46
+ :class="props.field.class"
47
+ :description="props.field.description"
48
+ :help="props.field.help"
49
+ :hint="props.field.hint"
50
+ :label="props.field.label"
51
+ :name="props.field.name"
52
+ :required="props.field.required"
53
+ :size="size"
54
+ :ui="ui"
55
+ >
56
+ <component
57
+ :is="component"
58
+ v-bind="fieldProps"
59
+ />
60
+ </UFormField>
61
+ </template>
@@ -0,0 +1,7 @@
1
+ import type { VunoField } from '#vuno/types/field';
2
+ type __VLS_Props = {
3
+ field: VunoField;
4
+ };
5
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ declare const _default: typeof __VLS_export;
7
+ export default _default;
@@ -0,0 +1,54 @@
1
+ import type { FormSchema, FormSubmitEvent } from '@nuxt/ui';
2
+ import type { VunoFieldSlots } from '#vuno/types/field';
3
+ import type { VunoFormDefinition, VunoFormState } from '#vuno/types/form';
4
+ declare const __VLS_export: <TState extends VunoFormState>(__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<{
5
+ props: import("vue").PublicProps & __VLS_PrettifyLocal<({
6
+ /**
7
+ * Cierra el modal nada más enviar. Déjalo apagado si guardas contra una
8
+ * API: querrás cerrar tú al confirmar, y mientras tanto usar `loading`.
9
+ */
10
+ closeOnSubmit?: boolean;
11
+ definition: VunoFormDefinition;
12
+ description?: string;
13
+ /** Slots del formulario que va dentro. */
14
+ formUi?: VunoFieldSlots;
15
+ /** Marca el botón de guardar como ocupado y bloquea el cierre. */
16
+ loading?: boolean;
17
+ schema: FormSchema;
18
+ title?: string;
19
+ /** Slots del modal (`content`, `body`, ...). */
20
+ ui?: VunoFieldSlots;
21
+ } & {
22
+ open?: boolean;
23
+ state: TState;
24
+ }) & {
25
+ onClose?: (() => any) | undefined;
26
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
27
+ onSubmit?: ((event: FormSubmitEvent<TState>) => any) | undefined;
28
+ "onUpdate:state"?: ((value: TState) => any) | undefined;
29
+ }> & (typeof globalThis extends {
30
+ __VLS_PROPS_FALLBACK: infer P;
31
+ } ? P : {});
32
+ expose: (exposed: {}) => void;
33
+ attrs: any;
34
+ slots: {
35
+ footer?: (props: {
36
+ dirty: boolean;
37
+ loading: boolean;
38
+ close: () => void;
39
+ reset: () => void;
40
+ }) => unknown;
41
+ /** Elemento que abre el modal, si prefieres eso a controlar `open`. */
42
+ trigger?: () => unknown;
43
+ };
44
+ emit: (((evt: "close") => void) & ((evt: "submit", event: FormSubmitEvent<TState>) => void)) & (((event: "update:open", value: boolean) => void) & ((event: "update:state", value: TState) => void));
45
+ }>) => import("vue").VNode & {
46
+ __ctx?: NonNullable<Awaited<typeof __VLS_setup>>;
47
+ };
48
+ declare const _default: typeof __VLS_export;
49
+ export default _default;
50
+ type __VLS_PrettifyLocal<T> = (T extends any ? {
51
+ [K in keyof T]: T[K];
52
+ } : {
53
+ [K in keyof T as K]: T[K];
54
+ }) & {};
@@ -0,0 +1,82 @@
1
+ <script setup>
2
+ import { computed, useId, useTemplateRef } from "vue";
3
+ import { useVunoMessages, useVunoTheme } from "#vuno/composables/useVunoTheme";
4
+ import Form from "./Form.vue";
5
+ const props = defineProps({
6
+ closeOnSubmit: { type: Boolean, required: false },
7
+ definition: { type: Object, required: true },
8
+ description: { type: String, required: false },
9
+ formUi: { type: Object, required: false },
10
+ loading: { type: Boolean, required: false },
11
+ schema: { type: null, required: true },
12
+ title: { type: String, required: false },
13
+ ui: { type: Object, required: false }
14
+ });
15
+ const emit = defineEmits(["close", "submit"]);
16
+ const open = defineModel("open", { type: Boolean, ...{ default: false } });
17
+ const state = defineModel("state", { type: null, ...{ required: true } });
18
+ defineSlots();
19
+ const messages = useVunoMessages();
20
+ const ui = useVunoTheme("formModal", () => props.ui);
21
+ const modalUi = computed(() => ({ body: ui.value.body, content: ui.value.content, footer: ui.value.footer }));
22
+ const formId = useId();
23
+ const form = useTemplateRef("form");
24
+ function close() {
25
+ if (props.loading) return;
26
+ open.value = false;
27
+ emit("close");
28
+ }
29
+ function onSubmit(event) {
30
+ emit("submit", event);
31
+ if (props.closeOnSubmit) open.value = false;
32
+ }
33
+ </script>
34
+
35
+ <template>
36
+ <UModal
37
+ v-model:open="open"
38
+ :description="props.description"
39
+ :dismissible="!props.loading"
40
+ scrollable
41
+ :title="props.title"
42
+ :ui="modalUi"
43
+ >
44
+ <slot name="trigger" />
45
+
46
+ <template #body>
47
+ <Form
48
+ :id="formId"
49
+ ref="form"
50
+ v-model:state="state"
51
+ :definition="props.definition"
52
+ :schema="props.schema"
53
+ :ui="props.formUi"
54
+ @submit="onSubmit"
55
+ />
56
+ </template>
57
+
58
+ <template #footer>
59
+ <slot
60
+ :close="close"
61
+ :dirty="form?.dirty ?? false"
62
+ :loading="props.loading ?? false"
63
+ name="footer"
64
+ :reset="() => form?.reset?.()"
65
+ >
66
+ <UButton
67
+ color="neutral"
68
+ :disabled="props.loading"
69
+ :label="messages.cancel"
70
+ variant="ghost"
71
+ @click="close"
72
+ />
73
+ <UButton
74
+ :form="formId"
75
+ :label="messages.save"
76
+ :loading="props.loading"
77
+ type="submit"
78
+ />
79
+ </slot>
80
+ </template>
81
+ </UModal>
82
+ </template>
@@ -0,0 +1,54 @@
1
+ import type { FormSchema, FormSubmitEvent } from '@nuxt/ui';
2
+ import type { VunoFieldSlots } from '#vuno/types/field';
3
+ import type { VunoFormDefinition, VunoFormState } from '#vuno/types/form';
4
+ declare const __VLS_export: <TState extends VunoFormState>(__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<{
5
+ props: import("vue").PublicProps & __VLS_PrettifyLocal<({
6
+ /**
7
+ * Cierra el modal nada más enviar. Déjalo apagado si guardas contra una
8
+ * API: querrás cerrar tú al confirmar, y mientras tanto usar `loading`.
9
+ */
10
+ closeOnSubmit?: boolean;
11
+ definition: VunoFormDefinition;
12
+ description?: string;
13
+ /** Slots del formulario que va dentro. */
14
+ formUi?: VunoFieldSlots;
15
+ /** Marca el botón de guardar como ocupado y bloquea el cierre. */
16
+ loading?: boolean;
17
+ schema: FormSchema;
18
+ title?: string;
19
+ /** Slots del modal (`content`, `body`, ...). */
20
+ ui?: VunoFieldSlots;
21
+ } & {
22
+ open?: boolean;
23
+ state: TState;
24
+ }) & {
25
+ onClose?: (() => any) | undefined;
26
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
27
+ onSubmit?: ((event: FormSubmitEvent<TState>) => any) | undefined;
28
+ "onUpdate:state"?: ((value: TState) => any) | undefined;
29
+ }> & (typeof globalThis extends {
30
+ __VLS_PROPS_FALLBACK: infer P;
31
+ } ? P : {});
32
+ expose: (exposed: {}) => void;
33
+ attrs: any;
34
+ slots: {
35
+ footer?: (props: {
36
+ dirty: boolean;
37
+ loading: boolean;
38
+ close: () => void;
39
+ reset: () => void;
40
+ }) => unknown;
41
+ /** Elemento que abre el modal, si prefieres eso a controlar `open`. */
42
+ trigger?: () => unknown;
43
+ };
44
+ emit: (((evt: "close") => void) & ((evt: "submit", event: FormSubmitEvent<TState>) => void)) & (((event: "update:open", value: boolean) => void) & ((event: "update:state", value: TState) => void));
45
+ }>) => import("vue").VNode & {
46
+ __ctx?: NonNullable<Awaited<typeof __VLS_setup>>;
47
+ };
48
+ declare const _default: typeof __VLS_export;
49
+ export default _default;
50
+ type __VLS_PrettifyLocal<T> = (T extends any ? {
51
+ [K in keyof T]: T[K];
52
+ } : {
53
+ [K in keyof T as K]: T[K];
54
+ }) & {};
@@ -0,0 +1,12 @@
1
+ import type { VunoFormSection } from '#vuno/types/form';
2
+ type __VLS_Props = {
3
+ /**
4
+ * Sin marco ni título: es lo que se usa dentro de una pestaña, donde
5
+ * el rótulo ya lo pone la propia pestaña.
6
+ */
7
+ plain?: boolean;
8
+ section: VunoFormSection;
9
+ };
10
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
11
+ declare const _default: typeof __VLS_export;
12
+ export default _default;
@@ -0,0 +1,43 @@
1
+ <script setup>
2
+ import { computed } from "vue";
3
+ import { useVunoTheme } from "#vuno/composables/useVunoTheme";
4
+ import { getColumnSpanClass } from "#vuno/utils/grid";
5
+ import FormField from "./FormField.vue";
6
+ const props = defineProps({
7
+ plain: { type: Boolean, required: false },
8
+ section: { type: Object, required: true }
9
+ });
10
+ const ui = useVunoTheme("formSection");
11
+ const root = computed(() => props.plain ? ui.value.plain : ui.value.root);
12
+ const showHeader = computed(() => props.plain ? Boolean(props.section.description) : Boolean(props.section.title || props.section.description));
13
+ </script>
14
+
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>
43
+ </template>
@@ -0,0 +1,12 @@
1
+ import type { VunoFormSection } from '#vuno/types/form';
2
+ type __VLS_Props = {
3
+ /**
4
+ * Sin marco ni título: es lo que se usa dentro de una pestaña, donde
5
+ * el rótulo ya lo pone la propia pestaña.
6
+ */
7
+ plain?: boolean;
8
+ section: VunoFormSection;
9
+ };
10
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
11
+ declare const _default: typeof __VLS_export;
12
+ export default _default;
@@ -0,0 +1,24 @@
1
+ import type { VunoFieldSlots } from '#vuno/types/field';
2
+ import type { VunoBreadcrumbItem } from '#vuno/types/page';
3
+ type __VLS_Props = {
4
+ breadcrumb?: VunoBreadcrumbItem[];
5
+ description?: string;
6
+ title: string;
7
+ ui?: VunoFieldSlots;
8
+ };
9
+ type __VLS_Slots = {
10
+ /** Acciones de la pantalla, a la derecha de la barra superior. */
11
+ actions?: () => unknown;
12
+ default?: () => unknown;
13
+ /** Lo que va antes de la miga de pan: un botón de plegado, por ejemplo. */
14
+ leading?: () => unknown;
15
+ };
16
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
20
+ type __VLS_WithSlots<T, S> = T & {
21
+ new (): {
22
+ $slots: S;
23
+ };
24
+ };
@@ -0,0 +1,63 @@
1
+ <script setup>
2
+ import { computed } from "vue";
3
+ import { useVunoTheme } from "#vuno/composables/useVunoTheme";
4
+ const props = defineProps({
5
+ breadcrumb: { type: Array, required: false },
6
+ description: { type: String, required: false },
7
+ title: { type: String, required: true },
8
+ ui: { type: Object, required: false }
9
+ });
10
+ defineSlots();
11
+ const ui = useVunoTheme("page", () => props.ui);
12
+ const panelUi = computed(() => ({ body: ui.value.panelBody }));
13
+ const navbarUi = computed(() => ({ left: ui.value.navbarLeft, root: ui.value.navbar }));
14
+ </script>
15
+
16
+ <template>
17
+ <UDashboardPanel :ui="panelUi">
18
+ <template #header>
19
+ <UDashboardNavbar :ui="navbarUi">
20
+ <!--
21
+ Se ocupa `#left` entero en vez de `#title`: el `#title` de la
22
+ barra vive dentro de un `<h1>`, y una miga de pan ahí sería un
23
+ `<nav>` dentro de un encabezado.
24
+ -->
25
+ <template #left>
26
+ <slot name="leading" />
27
+
28
+ <!-- En `neutral`: en color de marca, el último segmento se lee
29
+ como un enlace a otro sitio. -->
30
+ <UBreadcrumb
31
+ v-if="props.breadcrumb?.length"
32
+ :class="ui.breadcrumb"
33
+ color="neutral"
34
+ :items="props.breadcrumb"
35
+ />
36
+ </template>
37
+
38
+ <template #right>
39
+ <slot name="actions" />
40
+ </template>
41
+ </UDashboardNavbar>
42
+ </template>
43
+
44
+ <template #body>
45
+ <div :class="ui.body">
46
+ <!-- Un `<div>` y no un `<header>`: un `<header>` suelto es un
47
+ landmark `banner`, y solo puede haber uno por página. -->
48
+ <div :class="ui.heading">
49
+ <h1 :class="ui.title">{{ props.title }}</h1>
50
+
51
+ <p
52
+ v-if="props.description"
53
+ :class="ui.description"
54
+ >
55
+ {{ props.description }}
56
+ </p>
57
+ </div>
58
+
59
+ <slot />
60
+ </div>
61
+ </template>
62
+ </UDashboardPanel>
63
+ </template>
@@ -0,0 +1,24 @@
1
+ import type { VunoFieldSlots } from '#vuno/types/field';
2
+ import type { VunoBreadcrumbItem } from '#vuno/types/page';
3
+ type __VLS_Props = {
4
+ breadcrumb?: VunoBreadcrumbItem[];
5
+ description?: string;
6
+ title: string;
7
+ ui?: VunoFieldSlots;
8
+ };
9
+ type __VLS_Slots = {
10
+ /** Acciones de la pantalla, a la derecha de la barra superior. */
11
+ actions?: () => unknown;
12
+ default?: () => unknown;
13
+ /** Lo que va antes de la miga de pan: un botón de plegado, por ejemplo. */
14
+ leading?: () => unknown;
15
+ };
16
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
20
+ type __VLS_WithSlots<T, S> = T & {
21
+ new (): {
22
+ $slots: S;
23
+ };
24
+ };
@@ -0,0 +1,20 @@
1
+ import type { VunoFieldSlots } from '#vuno/types/field';
2
+ type __VLS_Props = {
3
+ hint?: string;
4
+ icon?: string;
5
+ title?: string;
6
+ ui?: VunoFieldSlots;
7
+ };
8
+ type __VLS_Slots = {
9
+ /** La invitación a crear el primer registro va aquí. */
10
+ actions?: () => unknown;
11
+ };
12
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
14
+ declare const _default: typeof __VLS_export;
15
+ export default _default;
16
+ type __VLS_WithSlots<T, S> = T & {
17
+ new (): {
18
+ $slots: S;
19
+ };
20
+ };