@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,210 @@
1
+ export const defaultTheme = {
2
+ confirm: {
3
+ slots: {
4
+ body: "flex items-start gap-3",
5
+ content: "max-w-md",
6
+ footer: "justify-end",
7
+ icon: "shrink-0 size-5"
8
+ }
9
+ },
10
+ fieldArray: {
11
+ slots: {
12
+ empty: "text-muted border-default rounded-lg border border-dashed px-4 py-6 text-center text-sm",
13
+ item: "border-default flex flex-col gap-3 rounded-lg border p-4",
14
+ itemActions: "flex items-center gap-1",
15
+ itemFields: "grid w-full grid-cols-12 gap-x-4 gap-y-5",
16
+ itemHeader: "flex items-center justify-between gap-2",
17
+ itemLabel: "text-muted text-xs font-medium",
18
+ root: "flex w-full flex-col gap-3"
19
+ }
20
+ },
21
+ fieldCheckbox: {
22
+ slots: { root: "w-full" }
23
+ },
24
+ fieldColor: {
25
+ slots: {
26
+ root: "flex w-full items-center gap-2",
27
+ swatch: "border-default size-4 rounded-full border"
28
+ }
29
+ },
30
+ fieldEmail: {
31
+ slots: { root: "w-full" }
32
+ },
33
+ fieldFile: {
34
+ slots: { base: "min-w-0", root: "w-full" }
35
+ },
36
+ fieldNumber: {
37
+ slots: { root: "w-full" }
38
+ },
39
+ fieldPassword: {
40
+ slots: { root: "w-full" }
41
+ },
42
+ fieldRadio: {
43
+ slots: { root: "w-full" }
44
+ },
45
+ fieldSelect: {
46
+ // USelectMenu no tiene slot `root`: su raíz es `base`.
47
+ slots: { base: "w-full" }
48
+ },
49
+ fieldSlug: {
50
+ slots: { root: "w-full", trailing: "pe-1" }
51
+ },
52
+ fieldText: {
53
+ slots: { root: "w-full" }
54
+ },
55
+ fieldTextarea: {
56
+ slots: { root: "w-full" }
57
+ },
58
+ form: {
59
+ slots: {
60
+ base: "@container relative flex w-full flex-col gap-4",
61
+ footer: "flex w-full items-center justify-between gap-4 ",
62
+ sections: "grid w-full grid-cols-12 gap-x-4 gap-y-5",
63
+ tabs: "w-full",
64
+ // Separa el panel de la barra de pestañas, siguiendo el mismo
65
+ // ritmo vertical que el `gap-y-5` entre filas de campos.
66
+ tabsContent: "w-full pt-5"
67
+ }
68
+ },
69
+ formField: {
70
+ slots: { root: "w-full" }
71
+ },
72
+ formModal: {
73
+ slots: {
74
+ // Un formulario no cabe cómodo en el `max-w-lg` que trae UModal.
75
+ content: "max-w-3xl",
76
+ // Acciones a los extremos: descartar a la izquierda, guardar a la derecha.
77
+ footer: "justify-between"
78
+ }
79
+ },
80
+ formSection: {
81
+ slots: {
82
+ description: "mt-1 text-muted",
83
+ header: "col-span-12 flex min-w-0 flex-col",
84
+ /**
85
+ * El hueco horizontal es fijo y estrecho por geometría: doce
86
+ * columnas son once huecos, así que el grid nunca puede medir
87
+ * menos de `11 × gap-x`. Con `gap-6` eso son 264px que no caben
88
+ * en una sección a media anchura dentro de un modal, por mucho
89
+ * que su contenido pueda encoger; con `gap-x-4` son 176px, que sí.
90
+ *
91
+ * Deliberadamente no se escala con una container query sobre la
92
+ * propia sección: el ancho de la sección depende entonces de un
93
+ * gap que depende del ancho, y el navegador entra en un bucle de
94
+ * layout. El hueco vertical, que no está sujeto a esa cuenta, sí
95
+ * puede ser mayor.
96
+ */
97
+ /** Sección dentro de una pestaña: sin marco, porque ya lo pone el panel. */
98
+ plain: "grid w-full grid-cols-12 gap-x-4 gap-y-5",
99
+ root: "border-default grid w-full grid-cols-12 gap-x-4 gap-y-5 rounded-lg border p-4",
100
+ title: "text-highlighted font-semibold"
101
+ }
102
+ },
103
+ page: {
104
+ slots: {
105
+ body: "flex w-full flex-col gap-7 px-4 py-6 sm:px-6 sm:py-8 lg:px-10 lg:py-10",
106
+ breadcrumb: "hidden min-w-0 sm:block",
107
+ // Medida acotada: a pantalla completa, un párrafo a todo lo ancho
108
+ // pasa de las 150 letras por línea y deja de leerse.
109
+ description: "text-muted max-w-prose text-sm",
110
+ heading: "flex flex-col gap-2",
111
+ navbar: "border-default bg-default/90 border-b backdrop-blur-xl",
112
+ navbarLeft: "min-w-0 gap-1",
113
+ // El panel no pone padding: lo pone `body`, que es quien conoce el ritmo.
114
+ panelBody: "p-0 sm:p-0",
115
+ title: "text-highlighted text-2xl font-bold tracking-tight sm:text-3xl"
116
+ }
117
+ },
118
+ state: {
119
+ slots: {
120
+ actions: "mt-3 empty:hidden",
121
+ hint: "text-muted max-w-prose text-sm",
122
+ icon: "size-8 shrink-0",
123
+ root: "flex flex-col items-center gap-1 px-4 py-10 text-center",
124
+ title: "text-highlighted font-medium"
125
+ }
126
+ },
127
+ table: {
128
+ slots: {
129
+ chips: "flex flex-wrap items-center gap-2",
130
+ filtersBody: "flex flex-col gap-4",
131
+ filtersFooter: "flex items-center justify-between gap-2",
132
+ footer: "flex flex-wrap items-center justify-between gap-4 px-1",
133
+ meta: "text-muted flex items-center gap-2 text-sm",
134
+ root: "flex w-full flex-col gap-4",
135
+ search: "w-full sm:max-w-xs",
136
+ selection: "bg-elevated/50 flex items-center justify-between gap-2 rounded-lg px-3 py-2 text-sm",
137
+ table: "min-w-full",
138
+ /**
139
+ * Nuxt UI solo aplica hover a las filas con `data-selectable`, que
140
+ * son las que reciben un manejador de selección. Aquí se aplica
141
+ * siempre, y con su misma utilidad semántica para que el color lo
142
+ * siga decidiendo el tema del proyecto.
143
+ */
144
+ tbody: "[&>tr]:hover:bg-elevated/50 [&>tr]:transition-colors",
145
+ td: "",
146
+ th: "",
147
+ toolbar: "flex flex-wrap items-center justify-between gap-2",
148
+ toolbarActions: "flex items-center gap-2"
149
+ }
150
+ }
151
+ };
152
+ export const defaultIcons = {
153
+ actions: "lucide:ellipsis-vertical",
154
+ addItem: "lucide:plus",
155
+ clearSearch: "lucide:x",
156
+ confirm: "lucide:triangle-alert",
157
+ empty: "lucide:inbox",
158
+ failure: "lucide:triangle-alert",
159
+ fileMoveDown: "lucide:chevron-down",
160
+ fileMoveUp: "lucide:chevron-up",
161
+ fileRemove: "lucide:x",
162
+ fileUpload: "lucide:image",
163
+ filter: "lucide:list-filter",
164
+ removeItem: "lucide:trash-2",
165
+ retry: "lucide:refresh-cw",
166
+ search: "lucide:search",
167
+ slugEdit: "lucide:pencil",
168
+ slugReset: "lucide:x",
169
+ sort: "lucide:chevrons-up-down",
170
+ sortAsc: "lucide:arrow-up",
171
+ sortDesc: "lucide:arrow-down"
172
+ };
173
+ export const defaultMessages = {
174
+ addItem: "A\xF1adir",
175
+ applyFilters: "Aplicar",
176
+ cancel: "Cancelar",
177
+ clearFilters: "Limpiar filtros",
178
+ clearSearch: "Limpiar b\xFAsqueda",
179
+ clearSelection: "Limpiar",
180
+ confirm: "Confirmar",
181
+ confirmDescription: "Esta acci\xF3n no se puede deshacer.",
182
+ confirmTitle: "\xBFQuieres continuar?",
183
+ created: "Creado correctamente",
184
+ deleted: "Eliminado correctamente",
185
+ deleteFailed: "No se pudo eliminar",
186
+ emptyArray: "Todav\xEDa no hay elementos",
187
+ emptyHint: "",
188
+ emptyTable: "No hay resultados",
189
+ failureHint: "Vuelve a intentarlo en un momento.",
190
+ failureTitle: "No pudimos cargar los datos",
191
+ fileMoveDown: "Bajar",
192
+ fileMoveUp: "Subir",
193
+ fileRemove: "Eliminar",
194
+ filters: "Filtros",
195
+ filtersTitle: "Filtrar resultados",
196
+ of: "de",
197
+ removeItem: "Eliminar elemento",
198
+ retry: "Reintentar",
199
+ rowActions: "Acciones",
200
+ rowSelected: "fila seleccionada",
201
+ rowsSelected: "filas seleccionadas",
202
+ save: "Guardar",
203
+ saveFailed: "No se pudo guardar",
204
+ searchPlaceholder: "Buscar...",
205
+ selectPlaceholder: "Selecciona una opci\xF3n",
206
+ showing: "Mostrando",
207
+ slugEdit: "Editar el slug a mano",
208
+ slugReset: "Volver al slug autom\xE1tico",
209
+ updated: "Guardado correctamente"
210
+ };
@@ -0,0 +1,8 @@
1
+ import type { VunoAppConfig } from './theme.js';
2
+ declare module '@nuxt/schema' {
3
+ interface AppConfigInput {
4
+ /** Configuración del módulo: tema por componente, iconos y textos. */
5
+ vuno?: VunoAppConfig;
6
+ }
7
+ }
8
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ import type { VunoColor } from './table.js';
2
+ export type VunoConfirmOptions = {
3
+ /** Texto del botón de cancelar. */
4
+ cancelLabel?: string;
5
+ /** Color del botón que confirma; `error` para acciones destructivas. */
6
+ color?: VunoColor;
7
+ confirmLabel?: string;
8
+ description?: string;
9
+ icon?: string;
10
+ title?: string;
11
+ };
File without changes
@@ -0,0 +1,64 @@
1
+ import type { ComputedRef, Ref } from 'vue';
2
+ import type { VunoTableFailure, VunoTableQuery, VunoTableRow } from './table.js';
3
+ /** Respuesta de un listado paginado en servidor. */
4
+ export type VunoCrudPage<TRow> = {
5
+ data: TRow[];
6
+ total: number;
7
+ };
8
+ export type VunoCrudMessages = {
9
+ created: string;
10
+ createFailed: string;
11
+ deleted: string;
12
+ deleteFailed: string;
13
+ updated: string;
14
+ updateFailed: string;
15
+ };
16
+ export type VunoCrudOptions<TRow extends VunoTableRow, TForm> = {
17
+ /**
18
+ * Clave base de la caché. La consulta se añade automáticamente, así que
19
+ * cada página, filtro y orden se cachean por separado.
20
+ */
21
+ key: readonly unknown[] | string;
22
+ messages?: Partial<VunoCrudMessages>;
23
+ /** Estado de consulta compartido con la tabla. */
24
+ query?: Ref<VunoTableQuery>;
25
+ /** Campo que identifica la fila. Por defecto `id`. */
26
+ rowKey?: string;
27
+ create?: (form: TForm) => Promise<unknown>;
28
+ /** Estado inicial del formulario al dar de alta. */
29
+ emptyForm: () => TForm;
30
+ list: (query: VunoTableQuery) => Promise<VunoCrudPage<TRow>>;
31
+ remove?: (row: TRow) => Promise<unknown>;
32
+ /** Convierte una fila en el estado del formulario al editar. */
33
+ toForm?: (row: TRow) => TForm;
34
+ update?: (id: number | string, form: TForm) => Promise<unknown>;
35
+ };
36
+ /**
37
+ * Lo que devuelve `useVunoCrud`. Se declara en vez de inferirse porque el tipo
38
+ * inferido arrastra internos de Vue y no se puede publicar.
39
+ */
40
+ export type VunoCrud<TRow extends VunoTableRow, TForm> = {
41
+ confirmOpen: Ref<boolean>;
42
+ /** Fila pendiente de confirmar su borrado. */
43
+ deleting: Ref<null | TRow>;
44
+ /** Fila en edición; `null` significa alta. */
45
+ editing: Ref<null | TRow>;
46
+ failure: ComputedRef<undefined | VunoTableFailure>;
47
+ formOpen: Ref<boolean>;
48
+ isDeleting: Ref<boolean>;
49
+ isPending: Ref<boolean>;
50
+ isSaving: Ref<boolean>;
51
+ items: ComputedRef<TRow[]>;
52
+ query: Ref<VunoTableQuery>;
53
+ state: Ref<TForm>;
54
+ total: ComputedRef<number>;
55
+ /** Pide confirmación para borrar una fila. */
56
+ askDelete: (row: TRow) => void;
57
+ confirmDelete: () => void;
58
+ /** Fuerza el refresco del listado. */
59
+ invalidate: () => void;
60
+ openCreate: () => void;
61
+ openEdit: (row: TRow) => void;
62
+ refetch: () => unknown;
63
+ submit: () => void;
64
+ };
File without changes
@@ -0,0 +1,121 @@
1
+ import type { ResponsiveColumns, ResponsiveColumnSpan } from './responsive.js';
2
+ type WithType<TField, TType extends string> = TField & {
3
+ type: TType;
4
+ };
5
+ export type VunoFieldColumns = ResponsiveColumns | ResponsiveColumnSpan;
6
+ export type VunoFieldSize = 'lg' | 'md' | 'sm' | 'xl' | 'xs';
7
+ export type VunoFieldOrientation = 'horizontal' | 'vertical';
8
+ export type VunoFieldSlots = Record<string, string | string[] | undefined>;
9
+ /** Valor que puede tomar una opción de lista. */
10
+ export type VunoFieldOptionValue = boolean | null | number | string;
11
+ /** Opción de los campos con lista (select, radio). */
12
+ export type VunoFieldOption = {
13
+ description?: string;
14
+ disabled?: boolean;
15
+ icon?: string;
16
+ label: string;
17
+ value: VunoFieldOptionValue;
18
+ };
19
+ /**
20
+ * Props comunes a todos los campos: layout dentro del grid, metadatos de
21
+ * `UFormField` y los dos puntos de escape de estilo (`class` y `ui`).
22
+ */
23
+ export type VunoFieldBase = {
24
+ name: string;
25
+ class?: string;
26
+ columns?: VunoFieldColumns;
27
+ description?: string;
28
+ disabled?: boolean;
29
+ help?: string;
30
+ hint?: string;
31
+ label?: string;
32
+ required?: boolean;
33
+ size?: VunoFieldSize;
34
+ ui?: VunoFieldSlots;
35
+ };
36
+ /**
37
+ * Lista de grupos de campos repetibles (variantes, líneas, contactos...).
38
+ *
39
+ * El tipo es recursivo a propósito: dentro de un array puede haber otro, y los
40
+ * valores se guardan en el estado como `nombre.0.campo`, que es el mismo
41
+ * formato de ruta que usan los errores de validación.
42
+ */
43
+ export type VunoFieldArray = VunoFieldBase & {
44
+ addLabel?: string;
45
+ fields: VunoField[];
46
+ /** Número máximo de elementos; al alcanzarlo se desactiva añadir. */
47
+ max?: number;
48
+ /** Número mínimo; por debajo no se puede eliminar. */
49
+ min?: number;
50
+ sortable?: boolean;
51
+ };
52
+ export type VunoFieldCheckbox = VunoFieldBase & {
53
+ indeterminate?: boolean;
54
+ };
55
+ export type VunoFieldColor = VunoFieldBase & {
56
+ placeholder?: string;
57
+ };
58
+ export type VunoFieldEmail = VunoFieldBase & {
59
+ autocomplete?: string;
60
+ autofocus?: boolean;
61
+ icon?: string;
62
+ placeholder?: string;
63
+ };
64
+ export type VunoFieldFile = VunoFieldBase & {
65
+ accept?: string;
66
+ dropzone?: boolean;
67
+ layout?: 'grid' | 'list';
68
+ multiple?: boolean;
69
+ reorderable?: boolean;
70
+ };
71
+ export type VunoFieldNumber = VunoFieldBase & {
72
+ max?: number;
73
+ min?: number;
74
+ orientation?: VunoFieldOrientation;
75
+ placeholder?: string;
76
+ step?: number;
77
+ };
78
+ export type VunoFieldPassword = VunoFieldBase & {
79
+ autocomplete?: string;
80
+ autofocus?: boolean;
81
+ placeholder?: string;
82
+ };
83
+ export type VunoFieldRadio = VunoFieldBase & {
84
+ items: VunoFieldOption[];
85
+ orientation?: VunoFieldOrientation;
86
+ };
87
+ export type VunoFieldSelect = VunoFieldBase & {
88
+ items: VunoFieldOption[];
89
+ multiple?: boolean;
90
+ placeholder?: string;
91
+ searchable?: boolean;
92
+ };
93
+ export type VunoFieldSlug = VunoFieldBase & {
94
+ placeholder?: string;
95
+ /** Campo del que se deriva el slug automáticamente. */
96
+ source: string;
97
+ };
98
+ export type VunoFieldText = VunoFieldBase & {
99
+ autocomplete?: string;
100
+ autofocus?: boolean;
101
+ icon?: string;
102
+ maxlength?: number;
103
+ placeholder?: string;
104
+ };
105
+ export type VunoFieldTextarea = VunoFieldBase & {
106
+ autoresize?: boolean;
107
+ maxrows?: number;
108
+ placeholder?: string;
109
+ rows?: number;
110
+ };
111
+ /**
112
+ * Unión discriminada por `type`. Es lo que consume `VunoFormDefinition`, de modo
113
+ * que cada campo de la definición queda tipado con sus propios props.
114
+ */
115
+ export type VunoField = WithType<VunoFieldArray, 'array'> | WithType<VunoFieldCheckbox, 'checkbox'> | WithType<VunoFieldColor, 'color'> | WithType<VunoFieldEmail, 'email'> | WithType<VunoFieldFile, 'file'> | WithType<VunoFieldNumber, 'number'> | WithType<VunoFieldPassword, 'password'> | WithType<VunoFieldRadio, 'radio'> | WithType<VunoFieldSelect, 'select'> | WithType<VunoFieldSlug, 'slug'> | WithType<VunoFieldText, 'text'> | WithType<VunoFieldTextarea, 'textarea'>;
116
+ export type VunoFieldType = VunoField['type'];
117
+ /** Extrae los props de un tipo concreto de campo: `VunoFieldOf<'slug'>`. */
118
+ export type VunoFieldOf<TType extends VunoFieldType> = Extract<VunoField, {
119
+ type: TType;
120
+ }>;
121
+ export {};
File without changes
@@ -0,0 +1,39 @@
1
+ import type { ComputedRef, InjectionKey } from 'vue';
2
+ import type { VunoField, VunoFieldColumns, VunoFieldSize } from './field.js';
3
+ /** Cómo se disponen las secciones del formulario. */
4
+ export type VunoFormLayout = 'sections' | 'tabs';
5
+ export type VunoFormSection = {
6
+ id: string;
7
+ columns?: VunoFieldColumns;
8
+ description?: string;
9
+ /** Campos de la sección, tipados por la unión discriminada `VunoField`. */
10
+ fields: VunoField[];
11
+ /** Icono de la pestaña cuando el formulario usa `layout: 'tabs'`. */
12
+ icon?: string;
13
+ title?: string;
14
+ };
15
+ export type VunoFormDefinition = {
16
+ /**
17
+ * `tabs` convierte cada sección en una pestaña, usando su `title` como
18
+ * rótulo. Útil en formularios largos y dentro de un modal.
19
+ */
20
+ layout?: VunoFormLayout;
21
+ sections: VunoFormSection[];
22
+ /** Tamaño aplicado a los campos que no declaran el suyo. */
23
+ size?: VunoFieldSize;
24
+ };
25
+ export type VunoFormState = Record<string, unknown>;
26
+ export interface VunoFormContext<TState extends VunoFormState = VunoFormState> {
27
+ dirty: ComputedRef<boolean>;
28
+ initialState: TState;
29
+ size: ComputedRef<undefined | VunoFieldSize>;
30
+ touched: Record<string, boolean>;
31
+ getValue<TValue = unknown>(name: string): TValue | undefined;
32
+ isDirty(name?: string): boolean;
33
+ isTouched(name: string): boolean;
34
+ reset(): void;
35
+ resetField(name: string): void;
36
+ setTouched(name: string, touched?: boolean): void;
37
+ setValue<TValue = unknown>(name: string, value: TValue): void;
38
+ }
39
+ export declare const vunoFormInjectionKey: InjectionKey<VunoFormContext>;
@@ -0,0 +1 @@
1
+ export const vunoFormInjectionKey = Symbol("vuno-form");
@@ -0,0 +1,9 @@
1
+ export * from './app-config.js';
2
+ export type * from './confirm';
3
+ export type * from './crud';
4
+ export type * from './field';
5
+ export type * from './form';
6
+ export type * from './page';
7
+ export type * from './responsive';
8
+ export type * from './table';
9
+ export type * from './theme';
@@ -0,0 +1 @@
1
+ export * from "./app-config.js";
@@ -0,0 +1,6 @@
1
+ /** Elemento de la miga de pan; se corresponde con el de Nuxt UI. */
2
+ export type VunoBreadcrumbItem = {
3
+ icon?: string;
4
+ label?: string;
5
+ to?: string;
6
+ };
File without changes
@@ -0,0 +1,8 @@
1
+ export type ResponsiveColumnSpan = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
2
+ export type ResponsiveColumns = {
3
+ xs?: ResponsiveColumnSpan;
4
+ sm?: ResponsiveColumnSpan;
5
+ md?: ResponsiveColumnSpan;
6
+ lg?: ResponsiveColumnSpan;
7
+ xl?: ResponsiveColumnSpan;
8
+ };
File without changes
@@ -0,0 +1,130 @@
1
+ import type { VunoField, VunoFieldSlots } from './field.js';
2
+ type WithType<TColumn, TType extends string> = TColumn & {
3
+ type: TType;
4
+ };
5
+ export type VunoTableSortDirection = 'asc' | 'desc';
6
+ export type VunoTableSort = {
7
+ column: string;
8
+ direction: VunoTableSortDirection;
9
+ };
10
+ /**
11
+ * Estado de consulta de la tabla. Es el `v-model:query` y la única fuente de
12
+ * verdad: en modo servidor el consumidor observa este objeto, pide su página
13
+ * y devuelve `data` y `total`.
14
+ */
15
+ export type VunoTableFilters = Record<string, unknown>;
16
+ export type VunoTableQuery = {
17
+ /** Valores del panel de filtros, indexados por el `name` de cada campo. */
18
+ filters: VunoTableFilters;
19
+ page: number;
20
+ pageSize: number;
21
+ search: string;
22
+ sort: null | VunoTableSort;
23
+ };
24
+ /**
25
+ * Fallo de la consulta, con el reintento DENTRO. Es un objeto y no dos props
26
+ * sueltas a propósito: así no se puede pintar un error sin ofrecer salida.
27
+ *
28
+ * Existe porque sin él una consulta fallida se normaliza a lista vacía
29
+ * (`data ?? []`) y la tabla dice «No hay resultados»: afirma que no hay nada
30
+ * cuando lo que pasa es que no se pudo saber.
31
+ */
32
+ export type VunoTableFailure = {
33
+ hint?: string;
34
+ title?: string;
35
+ retry: () => void;
36
+ };
37
+ /** Qué mostrar cuando la consulta va bien y no hay ninguna fila. */
38
+ export type VunoTableEmpty = {
39
+ hint?: string;
40
+ icon?: string;
41
+ title?: string;
42
+ };
43
+ export type VunoTableAlign = 'center' | 'end' | 'start';
44
+ export type VunoTableRow = Record<string, unknown>;
45
+ /** Color semántico de Nuxt UI; el proyecto consumidor decide qué tono es cada uno. */
46
+ export type VunoColor = 'error' | 'info' | 'neutral' | 'primary' | 'secondary' | 'success' | 'warning';
47
+ /**
48
+ * Nota: no hay `searchable` por columna. La búsqueda la resuelve el endpoint,
49
+ * que es quien sabe sobre qué campos puede buscar; declararlo aquí sugeriría
50
+ * que la tabla filtra, y no lo hace.
51
+ */
52
+ export type VunoTableColumnBase = {
53
+ align?: VunoTableAlign;
54
+ class?: string;
55
+ /** Ruta del valor en la fila. Admite anidado: `'proveedor.nombre'`. */
56
+ key: string;
57
+ label?: string;
58
+ sortable?: boolean;
59
+ ui?: VunoFieldSlots;
60
+ width?: string;
61
+ };
62
+ export type VunoTableColumnText = VunoTableColumnBase & {
63
+ truncate?: boolean;
64
+ };
65
+ export type VunoTableColumnNumber = VunoTableColumnBase & {
66
+ decimals?: number;
67
+ };
68
+ export type VunoTableColumnCurrency = VunoTableColumnBase & {
69
+ currency?: string;
70
+ decimals?: number;
71
+ };
72
+ export type VunoTableColumnDate = VunoTableColumnBase & {
73
+ dateStyle?: 'full' | 'long' | 'medium' | 'short';
74
+ timeStyle?: 'full' | 'long' | 'medium' | 'short';
75
+ };
76
+ export type VunoTableColumnBoolean = VunoTableColumnBase & {
77
+ falseLabel?: string;
78
+ trueLabel?: string;
79
+ };
80
+ /** Traduce el valor crudo a una etiqueta con color, p. ej. estados. */
81
+ export type VunoTableBadgeMap = Record<string, {
82
+ color?: VunoColor;
83
+ label?: string;
84
+ }>;
85
+ export type VunoTableColumnBadge = VunoTableColumnBase & {
86
+ color?: VunoColor;
87
+ map?: VunoTableBadgeMap;
88
+ };
89
+ export type VunoTableColumnImage = VunoTableColumnBase & {
90
+ alt?: string;
91
+ /** Clave de la fila de la que sacar el texto alternativo. */
92
+ altKey?: string;
93
+ size?: 'lg' | 'md' | 'sm' | 'xs';
94
+ };
95
+ /** Delega el contenido en el slot `cell-<key>`. */
96
+ export type VunoTableColumnCustom = VunoTableColumnBase;
97
+ export type VunoTableAction<TRow extends VunoTableRow = VunoTableRow> = {
98
+ onSelect: (row: TRow) => void;
99
+ color?: VunoColor;
100
+ icon?: string;
101
+ label: string;
102
+ /** Dibuja un separador encima de la acción. */
103
+ separator?: boolean;
104
+ disabled?: (row: TRow) => boolean;
105
+ /** Oculta la acción para esa fila concreta. */
106
+ hidden?: (row: TRow) => boolean;
107
+ };
108
+ export type VunoTableColumnActions<TRow extends VunoTableRow = VunoTableRow> = VunoTableColumnBase & {
109
+ actions: VunoTableAction<TRow>[];
110
+ };
111
+ /** Unión discriminada por `type`, igual que `VunoField` en los formularios. */
112
+ export type VunoTableColumn<TRow extends VunoTableRow = VunoTableRow> = WithType<VunoTableColumnActions<TRow>, 'actions'> | WithType<VunoTableColumnBadge, 'badge'> | WithType<VunoTableColumnBoolean, 'boolean'> | WithType<VunoTableColumnCurrency, 'currency'> | WithType<VunoTableColumnCustom, 'custom'> | WithType<VunoTableColumnDate, 'date'> | WithType<VunoTableColumnImage, 'image'> | WithType<VunoTableColumnNumber, 'number'> | WithType<VunoTableColumnText, 'text'>;
113
+ export type VunoTableColumnType = VunoTableColumn['type'];
114
+ export type VunoTableDefinition<TRow extends VunoTableRow = VunoTableRow> = {
115
+ columns: VunoTableColumn<TRow>[];
116
+ /** Texto e icono cuando no hay ninguna fila. */
117
+ empty?: VunoTableEmpty;
118
+ /**
119
+ * Campos del panel lateral de filtros. Son los mismos de un formulario, así
120
+ * que un filtro se declara igual que se declara un campo.
121
+ */
122
+ filters?: VunoField[];
123
+ /** Tamaños ofrecidos en el selector de filas por página. */
124
+ pageSizes?: number[];
125
+ /** Campo que identifica la fila; se usa como clave de selección. */
126
+ rowKey?: string;
127
+ searchPlaceholder?: string;
128
+ selectable?: boolean;
129
+ };
130
+ export {};
File without changes