@innertia-solutions/innertia-nuxt 0.1.9 → 0.1.11
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/components/DataPreview.vue +323 -0
- package/components/DataTable.vue +12 -12
- package/components/Forms/DatePicker.vue +64 -39
- package/components/Forms/Input.vue +8 -2
- package/components/Forms/Select.vue +56 -20
- package/components/Forms/SelectServer.vue +39 -34
- package/components/PermissionsTree.vue +4 -4
- package/components/Table/Database.vue +5 -5
- package/components/Table/DownloadDropdown.vue +8 -8
- package/components/Table/Enterprise.vue +9 -9
- package/components/Table/FilterDropdown.vue +3 -3
- package/components/Table/Grid.vue +4 -4
- package/components/Table/Kanban.vue +3 -3
- package/components/Table/List.vue +6 -6
- package/components/Table/Standard.vue +69 -324
- package/components/Table/index.vue +13 -13
- package/components/TableExportable.vue +6 -6
- package/components/TableFilter.vue +4 -4
- package/components/Tree/Node.vue +187 -84
- package/components/Tree/Standard.vue +345 -57
- package/composables/useTable.ts +7 -3
- package/css/tokens.css +90 -0
- package/package.json +1 -1
- package/theme.css +2 -0
|
@@ -16,24 +16,43 @@ const emit = defineEmits<{
|
|
|
16
16
|
|
|
17
17
|
const selectRef = ref<HTMLSelectElement | null>(null)
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Init Preline HSSelect una sola vez después del mount.
|
|
21
|
+
* No destruimos ni re-inicializamos en cambios de options — para forzar
|
|
22
|
+
* un re-mount limpio, el consumer puede cambiar el `:key` del componente.
|
|
23
|
+
*
|
|
24
|
+
* Si Preline aún no está disponible (plugin carga async), esperamos.
|
|
25
|
+
*/
|
|
26
|
+
const initHsSelect = async () => {
|
|
20
27
|
await nextTick()
|
|
21
28
|
const el = selectRef.value
|
|
22
29
|
if (!el) return
|
|
23
30
|
|
|
24
|
-
const
|
|
25
|
-
|
|
31
|
+
const tryInit = (attempts = 0) => {
|
|
32
|
+
const HSSelect = (window as any).HSSelect
|
|
33
|
+
if (!HSSelect) {
|
|
34
|
+
if (attempts < 20) {
|
|
35
|
+
// Re-intentar hasta 20 frames (~330ms) esperando que cargue el plugin
|
|
36
|
+
requestAnimationFrame(() => tryInit(attempts + 1))
|
|
37
|
+
}
|
|
38
|
+
return
|
|
39
|
+
}
|
|
26
40
|
|
|
27
|
-
|
|
28
|
-
|
|
41
|
+
// Evitar doble init si ya hay una instancia registrada
|
|
42
|
+
if (HSSelect.getInstance?.(el)) return
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
new HSSelect(el)
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.warn('[FormsSelect] HSSelect init falló:', e)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
29
50
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
await reinitHsSelect()
|
|
33
|
-
}, { deep: true })
|
|
51
|
+
tryInit()
|
|
52
|
+
}
|
|
34
53
|
|
|
35
54
|
onMounted(() => {
|
|
36
|
-
|
|
55
|
+
initHsSelect()
|
|
37
56
|
})
|
|
38
57
|
|
|
39
58
|
const handleChange = (e: Event) => {
|
|
@@ -53,7 +72,7 @@ const handleChange = (e: Event) => {
|
|
|
53
72
|
<!-- Select (HSSelect) -->
|
|
54
73
|
<ClientOnly>
|
|
55
74
|
<template #fallback>
|
|
56
|
-
<div class="
|
|
75
|
+
<div class="innertia-field bg-surface animate-pulse" />
|
|
57
76
|
</template>
|
|
58
77
|
|
|
59
78
|
<div :class="['relative', error ? 'select-error' : '']">
|
|
@@ -66,9 +85,9 @@ const handleChange = (e: Event) => {
|
|
|
66
85
|
data-hs-select='{
|
|
67
86
|
"placeholder": "Seleccionar...",
|
|
68
87
|
"toggleTag": "<button type=\"button\" aria-expanded=\"false\"></button>",
|
|
69
|
-
"toggleClasses": "hs-select-disabled:pointer-events-none hs-select-disabled:opacity-50
|
|
70
|
-
"dropdownClasses": "mt-
|
|
71
|
-
"optionClasses": "py-2 px-
|
|
88
|
+
"toggleClasses": "innertia-field hs-select-disabled:pointer-events-none hs-select-disabled:opacity-50 aria-expanded:!rounded-b-none aria-expanded:!border-b-transparent relative pe-9 flex gap-x-2 items-center text-nowrap cursor-pointer text-start",
|
|
89
|
+
"dropdownClasses": "-mt-px z-50 w-full max-h-72 py-1 bg-[color:var(--field-dropdown-bg)] border border-[color:var(--field-border)] rounded-b-control overflow-hidden overflow-y-auto shadow-lg",
|
|
90
|
+
"optionClasses": "py-2 px-3 w-full text-sm text-foreground cursor-pointer bg-transparent hover:bg-muted-hover focus:outline-hidden focus:bg-muted-hover",
|
|
72
91
|
"optionTemplate": "<div class=\"flex justify-between items-center w-full\"><span data-title></span><span class=\"hidden hs-selected:block\"><svg class=\"shrink-0 size-3.5 text-blue-600 dark:text-blue-500\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg></span></div>"
|
|
73
92
|
}'
|
|
74
93
|
>
|
|
@@ -84,17 +103,34 @@ const handleChange = (e: Event) => {
|
|
|
84
103
|
</div>
|
|
85
104
|
</ClientOnly>
|
|
86
105
|
|
|
87
|
-
<!-- Error -->
|
|
88
|
-
<p v-if="error" class="text-xs text-red-500
|
|
106
|
+
<!-- Error message -->
|
|
107
|
+
<p v-if="error" class="text-xs text-red-500">{{ error }}</p>
|
|
89
108
|
|
|
90
109
|
<!-- Hint -->
|
|
91
110
|
<p v-else-if="hint" class="text-xs text-muted-foreground">{{ hint }}</p>
|
|
92
111
|
</div>
|
|
93
112
|
</template>
|
|
94
113
|
|
|
95
|
-
<style
|
|
96
|
-
/*
|
|
97
|
-
|
|
98
|
-
|
|
114
|
+
<style>
|
|
115
|
+
/*
|
|
116
|
+
* Preline HSSelect: cuando el dropdown está abierto, el trigger pierde
|
|
117
|
+
* sus esquinas inferiores y el borde inferior se vuelve invisible para
|
|
118
|
+
* fusionarse visualmente con el dropdown panel.
|
|
119
|
+
*
|
|
120
|
+
* Estilos globales (sin scoped) porque el HSSelect inyecta DOM fuera
|
|
121
|
+
* del componente Vue (button generado, no del template).
|
|
122
|
+
*/
|
|
123
|
+
.hs-select [aria-expanded="true"].innertia-field {
|
|
124
|
+
border-bottom-left-radius: 0 !important;
|
|
125
|
+
border-bottom-right-radius: 0 !important;
|
|
126
|
+
border-bottom-color: transparent !important;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* Pegar el dropdown al trigger eliminando cualquier offset/margen que
|
|
130
|
+
* Preline pueda inyectar. Target amplio: cualquier div con la clase
|
|
131
|
+
* `rounded-b-control` dentro del wrapper `.hs-select`. */
|
|
132
|
+
.hs-select div[class*="rounded-b-control"] {
|
|
133
|
+
top: 100% !important;
|
|
134
|
+
margin-top: -1px !important;
|
|
99
135
|
}
|
|
100
136
|
</style>
|
|
@@ -20,6 +20,8 @@ const props = defineProps({
|
|
|
20
20
|
multiple: { type: Boolean, default: false },
|
|
21
21
|
searchable: { type: Boolean, default: true },
|
|
22
22
|
disabled: { type: Boolean, default: false },
|
|
23
|
+
loading: { type: Boolean, default: false },
|
|
24
|
+
name: { type: String, default: "" },
|
|
23
25
|
clearable: { type: Boolean, default: false },
|
|
24
26
|
searchPlaceholder: { type: String, default: "Buscar..." },
|
|
25
27
|
minSearchLength: { type: Number, default: 0 },
|
|
@@ -336,23 +338,22 @@ const validationClasses = computed(() => {
|
|
|
336
338
|
|
|
337
339
|
// Combined select classes
|
|
338
340
|
const selectClasses = computed(() => {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
return `${base} ${sizeClass} ${validationClasses.value} ${focus} ${disabled} ${props.class}`;
|
|
341
|
+
// Apariencia unificada con el resto de los fields del DS.
|
|
342
|
+
// En modo tags-multiple desactivamos la altura fija porque el control crece
|
|
343
|
+
// según la cantidad de chips seleccionados.
|
|
344
|
+
const base = props.multiple && props.tagsMode
|
|
345
|
+
? "innertia-field relative cursor-pointer text-start !ps-3.5 !pe-9 flex items-center flex-wrap gap-2 text-nowrap !h-auto min-h-[var(--field-height)] py-2.5"
|
|
346
|
+
: "innertia-field relative cursor-pointer text-start pe-9 flex items-center gap-x-2";
|
|
347
|
+
|
|
348
|
+
// Cuando el dropdown está abierto, el trigger se "funde" visualmente con
|
|
349
|
+
// el panel: deja de redondear sus esquinas inferiores y queda como una sola pieza.
|
|
350
|
+
const openStyles = isOpen.value
|
|
351
|
+
? "!rounded-b-none border-b-transparent"
|
|
352
|
+
: "";
|
|
353
|
+
|
|
354
|
+
const disabled = props.disabled || props.loading ? "opacity-50 cursor-not-allowed" : "";
|
|
355
|
+
|
|
356
|
+
return `${base} ${openStyles} ${validationClasses.value} ${disabled} ${props.class ?? ""}`;
|
|
356
357
|
});
|
|
357
358
|
|
|
358
359
|
// Filtered options — backend does the filtering, serverOptions IS the filteredOptions
|
|
@@ -403,8 +404,9 @@ const selectOption = (option) => {
|
|
|
403
404
|
if (option.disabled) return;
|
|
404
405
|
|
|
405
406
|
if (props.multiple) {
|
|
407
|
+
const target = getOptionValue(option);
|
|
406
408
|
const index = selectedOptions.value.findIndex(
|
|
407
|
-
(opt) => opt
|
|
409
|
+
(opt) => getOptionValue(opt) === target
|
|
408
410
|
);
|
|
409
411
|
if (index > -1) {
|
|
410
412
|
selectedOptions.value.splice(index, 1);
|
|
@@ -428,7 +430,8 @@ const selectOption = (option) => {
|
|
|
428
430
|
|
|
429
431
|
const removeTag = (option) => {
|
|
430
432
|
if (props.disabled) return;
|
|
431
|
-
const
|
|
433
|
+
const target = getOptionValue(option);
|
|
434
|
+
const index = selectedOptions.value.findIndex((opt) => getOptionValue(opt) === target);
|
|
432
435
|
if (index > -1) {
|
|
433
436
|
selectedOptions.value.splice(index, 1);
|
|
434
437
|
localValue.value = selectedOptions.value.map((opt) => getOptionValue(opt));
|
|
@@ -443,7 +446,8 @@ const clearSelection = () => {
|
|
|
443
446
|
};
|
|
444
447
|
|
|
445
448
|
const isOptionSelected = (option) => {
|
|
446
|
-
|
|
449
|
+
const target = getOptionValue(option);
|
|
450
|
+
return selectedOptions.value.some((opt) => getOptionValue(opt) === target);
|
|
447
451
|
};
|
|
448
452
|
|
|
449
453
|
// Keyboard navigation
|
|
@@ -524,27 +528,28 @@ onMounted(() => {
|
|
|
524
528
|
<!-- Selected values display -->
|
|
525
529
|
<div v-else class="flex items-center justify-between w-full">
|
|
526
530
|
<!-- Tags mode for multiple selection -->
|
|
527
|
-
<div v-if="multiple && tagsMode && selectedOptions.length" class="flex flex-wrap items-center gap-1 flex-1">
|
|
531
|
+
<div v-if="multiple && tagsMode && selectedOptions.length" class="flex flex-wrap items-center gap-1.5 flex-1">
|
|
528
532
|
<div v-for="option in selectedOptions" :key="option.id"
|
|
529
|
-
class="flex
|
|
533
|
+
class="inline-flex items-center gap-1.5 bg-muted border border-[color:var(--field-border)] rounded-badge ps-2.5 pe-1 py-1 text-xs leading-none">
|
|
530
534
|
<!-- Avatar/Icon -->
|
|
531
|
-
<div v-if="option.avatar || option.icon" class="size-
|
|
535
|
+
<div v-if="option.avatar || option.icon" class="size-4">
|
|
532
536
|
<img v-if="option.avatar" :src="option.avatar" :alt="getOptionLabel(option)"
|
|
533
|
-
class="inline-block rounded-
|
|
534
|
-
<div v-else-if="option.icon" v-html="option.icon" class="size-
|
|
537
|
+
class="inline-block rounded-avatar size-4" />
|
|
538
|
+
<div v-else-if="option.icon" v-html="option.icon" class="size-4"></div>
|
|
535
539
|
</div>
|
|
536
540
|
|
|
537
541
|
<!-- Label -->
|
|
538
|
-
<
|
|
542
|
+
<span class="whitespace-nowrap text-foreground">
|
|
539
543
|
<slot name="tag" :option="option">
|
|
540
544
|
{{ getOptionLabel(option) }}
|
|
541
545
|
</slot>
|
|
542
|
-
</
|
|
546
|
+
</span>
|
|
543
547
|
|
|
544
548
|
<!-- Remove button -->
|
|
545
549
|
<button type="button" v-if="!disabled" @click.stop="removeTag(option)"
|
|
546
|
-
class="inline-flex shrink-0 justify-center items-center size-
|
|
547
|
-
|
|
550
|
+
class="inline-flex shrink-0 justify-center items-center size-4 rounded-badge text-muted-foreground hover:text-foreground focus:outline-none focus-visible:outline-none cursor-pointer"
|
|
551
|
+
tabindex="-1">
|
|
552
|
+
<svg class="shrink-0 size-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
|
548
553
|
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
549
554
|
<path d="M18 6 6 18" />
|
|
550
555
|
<path d="m6 6 12 12" />
|
|
@@ -592,12 +597,12 @@ onMounted(() => {
|
|
|
592
597
|
enter-to-class="transform opacity-100 scale-100" leave-active-class="transition ease-in duration-75"
|
|
593
598
|
leave-from-class="transform opacity-100 scale-100" leave-to-class="transform opacity-0 scale-95">
|
|
594
599
|
<div v-show="isOpen" ref="optionsListRef" @scroll="handleScroll"
|
|
595
|
-
class="absolute z-50 w-full mt-
|
|
600
|
+
class="absolute z-50 w-full -mt-px bg-[color:var(--field-dropdown-bg)] border border-[color:var(--field-border)] rounded-b-control shadow-lg max-h-60 overflow-auto">
|
|
596
601
|
<!-- Search input -->
|
|
597
602
|
<div v-if="searchable" class="p-2 border-b border-card-line relative">
|
|
598
603
|
<input type="text" :value="searchQuery" :placeholder="searchPlaceholder" @input="handleSearchInput"
|
|
599
604
|
@keydown.enter.prevent
|
|
600
|
-
class="
|
|
605
|
+
class="innertia-field innertia-field-sm pe-8" />
|
|
601
606
|
<button type="button" v-if="searchQuery" @click.prevent="clearSearch"
|
|
602
607
|
class="absolute top-1/2 right-4 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
|
|
603
608
|
<svg class="size-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
@@ -635,7 +640,7 @@ onMounted(() => {
|
|
|
635
640
|
<!-- Multiple selection checkbox -->
|
|
636
641
|
<div v-if="multiple" class="mr-2">
|
|
637
642
|
<div :class="[
|
|
638
|
-
'w-4 h-4 rounded-
|
|
643
|
+
'w-4 h-4 rounded-control border-2 flex items-center justify-center',
|
|
639
644
|
isOptionSelected(option)
|
|
640
645
|
? 'bg-slate-600 border-slate-600 text-white'
|
|
641
646
|
: 'border-card-line',
|
|
@@ -652,7 +657,7 @@ onMounted(() => {
|
|
|
652
657
|
<div class="flex items-center flex-1">
|
|
653
658
|
<!-- Avatar/Icon -->
|
|
654
659
|
<div v-if="option.avatar || option.icon" class="mr-3">
|
|
655
|
-
<img v-if="option.avatar" :src="option.avatar" :alt="option.label" class="w-6 h-6 rounded-
|
|
660
|
+
<img v-if="option.avatar" :src="option.avatar" :alt="option.label" class="w-6 h-6 rounded-avatar" />
|
|
656
661
|
<component v-else-if="option.icon" :is="option.icon" class="w-5 h-5 text-gray-500" />
|
|
657
662
|
</div>
|
|
658
663
|
|
|
@@ -702,7 +707,7 @@ onMounted(() => {
|
|
|
702
707
|
<!-- No data message -->
|
|
703
708
|
<div v-else-if="!serverOptions.length && !isFetching" class="px-3 py-8 text-center">
|
|
704
709
|
<div class="flex flex-col items-center gap-y-2">
|
|
705
|
-
<div class="size-10 rounded-
|
|
710
|
+
<div class="size-10 rounded-avatar bg-muted flex items-center justify-center">
|
|
706
711
|
<svg class="size-5 text-muted-foreground-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
|
707
712
|
stroke-width="1.5" stroke="currentColor">
|
|
708
713
|
<path stroke-linecap="round" stroke-linejoin="round"
|
|
@@ -133,17 +133,17 @@ function groupSelectedCount(group) {
|
|
|
133
133
|
|
|
134
134
|
<!-- Search -->
|
|
135
135
|
<div class="relative">
|
|
136
|
-
<IconSearch class="absolute
|
|
136
|
+
<IconSearch class="absolute right-2.5 top-1/2 -translate-y-1/2 size-3 text-muted-foreground pointer-events-none" />
|
|
137
137
|
<input
|
|
138
138
|
v-model="search"
|
|
139
|
-
type="
|
|
139
|
+
type="text"
|
|
140
140
|
placeholder="Buscar permiso…"
|
|
141
|
-
class="
|
|
141
|
+
class="innertia-field innertia-field-sm pe-7"
|
|
142
142
|
/>
|
|
143
143
|
</div>
|
|
144
144
|
|
|
145
145
|
<!-- Tree -->
|
|
146
|
-
<div class="rounded-
|
|
146
|
+
<div class="rounded-card border border-card-line bg-card overflow-hidden text-sm">
|
|
147
147
|
|
|
148
148
|
<div v-if="visibleApps.length === 0" class="px-3 py-6 text-center text-xs text-slate-400 dark:text-slate-500">
|
|
149
149
|
{{ search ? 'Sin resultados.' : readonly ? 'Sin permisos asignados.' : 'Sin permisos disponibles.' }}
|
|
@@ -82,19 +82,19 @@ defineExpose({ reload })
|
|
|
82
82
|
<template>
|
|
83
83
|
<div class="flex flex-col gap-3">
|
|
84
84
|
<div v-if="showSearch" class="relative max-w-sm">
|
|
85
|
-
<div class="absolute inset-y-0
|
|
86
|
-
<IconSearch class="size-4 text-
|
|
85
|
+
<div class="absolute inset-y-0 end-0 flex items-center pe-3 pointer-events-none">
|
|
86
|
+
<IconSearch class="size-4 text-muted-foreground" stroke="1.5" />
|
|
87
87
|
</div>
|
|
88
88
|
<input
|
|
89
89
|
v-model="search"
|
|
90
|
-
type="
|
|
90
|
+
type="text"
|
|
91
91
|
:placeholder="searchPlaceholder"
|
|
92
|
-
class="
|
|
92
|
+
class="innertia-field innertia-field-sm pe-9"
|
|
93
93
|
/>
|
|
94
94
|
</div>
|
|
95
95
|
|
|
96
96
|
<!-- Dense table wrapper — override Table's default padding with compact styles -->
|
|
97
|
-
<div class="overflow-x-auto border border-card-line rounded-
|
|
97
|
+
<div class="overflow-x-auto border border-card-line rounded-card">
|
|
98
98
|
<Table
|
|
99
99
|
ref="tableRef"
|
|
100
100
|
:endpoint="endpoint"
|
|
@@ -31,7 +31,7 @@ const exportTable = (format) => {
|
|
|
31
31
|
<button
|
|
32
32
|
id="hs-as-table-table-export-dropdown"
|
|
33
33
|
type="button"
|
|
34
|
-
class="py-1.5 sm:py-2 px-2.5 inline-flex items-center gap-x-1.5 text-sm sm:text-xs font-medium rounded-
|
|
34
|
+
class="py-1.5 sm:py-2 px-2.5 inline-flex items-center gap-x-1.5 text-sm sm:text-xs font-medium rounded-control border border-card-line bg-card text-foreground shadow-2xs hover:bg-muted-hover disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden focus:bg-muted-hover"
|
|
35
35
|
aria-haspopup="menu"
|
|
36
36
|
aria-expanded="false"
|
|
37
37
|
aria-label="Dropdown"
|
|
@@ -40,14 +40,14 @@ const exportTable = (format) => {
|
|
|
40
40
|
Exportar
|
|
41
41
|
</button>
|
|
42
42
|
<div
|
|
43
|
-
class="hs-dropdown-menu transition-[opacity,margin] duration hs-dropdown-open:opacity-100 opacity-0 hidden divide-y divide-card-line min-w-48 z-10 bg-dropdown shadow-md rounded-
|
|
43
|
+
class="hs-dropdown-menu transition-[opacity,margin] duration hs-dropdown-open:opacity-100 opacity-0 hidden divide-y divide-card-line min-w-48 z-10 bg-dropdown shadow-md rounded-popover p-2 mt-2 dark:border dark:border-dropdown-line border-t border-card-line"
|
|
44
44
|
role="menu"
|
|
45
45
|
aria-orientation="vertical"
|
|
46
46
|
aria-labelledby="hs-as-table-table-export-dropdown"
|
|
47
47
|
>
|
|
48
48
|
<div class="py-2 first:pt-0 last:pb-0">
|
|
49
49
|
<a
|
|
50
|
-
class="flex items-center gap-x-3 py-2 px-3 rounded-
|
|
50
|
+
class="flex items-center gap-x-3 py-2 px-3 rounded-control text-sm text-foreground hover:bg-muted-hover focus:outline-hidden focus:bg-muted-hover"
|
|
51
51
|
href="#"
|
|
52
52
|
@click="exportTable('xlsx')"
|
|
53
53
|
>
|
|
@@ -55,7 +55,7 @@ const exportTable = (format) => {
|
|
|
55
55
|
Excel
|
|
56
56
|
</a>
|
|
57
57
|
<a
|
|
58
|
-
class="flex items-center gap-x-3 py-2 px-3 rounded-
|
|
58
|
+
class="flex items-center gap-x-3 py-2 px-3 rounded-control text-sm text-foreground hover:bg-muted-hover focus:outline-hidden focus:bg-muted-hover"
|
|
59
59
|
href="#"
|
|
60
60
|
@click="exportTable('csv')"
|
|
61
61
|
>
|
|
@@ -63,7 +63,7 @@ const exportTable = (format) => {
|
|
|
63
63
|
CSV
|
|
64
64
|
</a>
|
|
65
65
|
<a
|
|
66
|
-
class="flex items-center gap-x-3 py-2 px-3 rounded-
|
|
66
|
+
class="flex items-center gap-x-3 py-2 px-3 rounded-control text-sm text-foreground hover:bg-muted-hover focus:outline-hidden focus:bg-muted-hover"
|
|
67
67
|
href="#"
|
|
68
68
|
@click="exportTable('pdf')"
|
|
69
69
|
>
|
|
@@ -71,7 +71,7 @@ const exportTable = (format) => {
|
|
|
71
71
|
PDF
|
|
72
72
|
</a>
|
|
73
73
|
<a
|
|
74
|
-
class="flex items-center gap-x-3 py-2 px-3 rounded-
|
|
74
|
+
class="flex items-center gap-x-3 py-2 px-3 rounded-control text-sm text-foreground hover:bg-muted-hover focus:outline-hidden focus:bg-muted-hover"
|
|
75
75
|
href="#"
|
|
76
76
|
@click="exportTable('json')"
|
|
77
77
|
>
|
|
@@ -90,7 +90,7 @@ const exportTable = (format) => {
|
|
|
90
90
|
<input
|
|
91
91
|
type="checkbox"
|
|
92
92
|
v-model="exportAllPages"
|
|
93
|
-
class="shrink-0 border-card-line rounded-
|
|
93
|
+
class="shrink-0 border-card-line rounded-control text-blue-600 focus:ring-blue-500 dark:bg-card"
|
|
94
94
|
/>
|
|
95
95
|
Todas las páginas
|
|
96
96
|
</label>
|
|
@@ -101,7 +101,7 @@ const exportTable = (format) => {
|
|
|
101
101
|
<input
|
|
102
102
|
type="checkbox"
|
|
103
103
|
v-model="exportFilteredRows"
|
|
104
|
-
class="shrink-0 border-card-line rounded-
|
|
104
|
+
class="shrink-0 border-card-line rounded-control text-blue-600 focus:ring-blue-500 dark:bg-card"
|
|
105
105
|
/>
|
|
106
106
|
Solo filas filtradas
|
|
107
107
|
</label>
|
|
@@ -248,7 +248,7 @@ defineExpose({ getSelectedRows, reload, clearCache, exportTable, tableRef, close
|
|
|
248
248
|
|
|
249
249
|
<template>
|
|
250
250
|
<div class="relative" ref="containerRef">
|
|
251
|
-
<div class="bg-card border border-card-line rounded-
|
|
251
|
+
<div class="bg-card border border-card-line rounded-card shadow-sm overflow-hidden">
|
|
252
252
|
|
|
253
253
|
<!-- ── Toolbar ──────────────────────────────────────────────────────── -->
|
|
254
254
|
<div class="flex flex-wrap items-center gap-2 px-4 py-3 border-b border-card-line">
|
|
@@ -264,7 +264,7 @@ defineExpose({ getSelectedRows, reload, clearCache, exportTable, tableRef, close
|
|
|
264
264
|
type="button"
|
|
265
265
|
@click="showFilterPanel = !showFilterPanel"
|
|
266
266
|
:class="[
|
|
267
|
-
'py-1.5 px-3 inline-flex items-center gap-1.5 text-sm font-medium rounded-
|
|
267
|
+
'py-1.5 px-3 inline-flex items-center gap-1.5 text-sm font-medium rounded-control border transition-colors',
|
|
268
268
|
showFilterPanel || activeFilterCount > 0
|
|
269
269
|
? 'border-primary/50 bg-primary/5 text-primary dark:bg-primary/10'
|
|
270
270
|
: 'border-card-line bg-card text-muted-foreground-1 hover:bg-muted-hover',
|
|
@@ -286,7 +286,7 @@ defineExpose({ getSelectedRows, reload, clearCache, exportTable, tableRef, close
|
|
|
286
286
|
<button
|
|
287
287
|
v-if="showSaveView"
|
|
288
288
|
type="button"
|
|
289
|
-
class="py-1.5 px-3 inline-flex items-center gap-1.5 text-sm font-medium rounded-
|
|
289
|
+
class="py-1.5 px-3 inline-flex items-center gap-1.5 text-sm font-medium rounded-control border border-card-line bg-card text-muted-foreground-1 hover:bg-muted-hover transition-colors"
|
|
290
290
|
@click="emit('save-view')"
|
|
291
291
|
>
|
|
292
292
|
<IconBookmark class="size-4" stroke="1.5" />
|
|
@@ -300,7 +300,7 @@ defineExpose({ getSelectedRows, reload, clearCache, exportTable, tableRef, close
|
|
|
300
300
|
type="button"
|
|
301
301
|
@click="showColumnPanel = !showColumnPanel"
|
|
302
302
|
:class="[
|
|
303
|
-
'py-1.5 px-3 inline-flex items-center gap-1.5 text-sm font-medium rounded-
|
|
303
|
+
'py-1.5 px-3 inline-flex items-center gap-1.5 text-sm font-medium rounded-control border transition-colors',
|
|
304
304
|
showColumnPanel
|
|
305
305
|
? 'border-primary/50 bg-primary/5 text-primary dark:bg-primary/10'
|
|
306
306
|
: 'border-card-line bg-card text-muted-foreground-1 hover:bg-muted-hover',
|
|
@@ -333,7 +333,7 @@ defineExpose({ getSelectedRows, reload, clearCache, exportTable, tableRef, close
|
|
|
333
333
|
type="button"
|
|
334
334
|
@click="openChip = openChip === chip.key ? null : chip.key"
|
|
335
335
|
:class="[
|
|
336
|
-
'inline-flex items-center gap-1 px-2.5 py-1 rounded-
|
|
336
|
+
'inline-flex items-center gap-1 px-2.5 py-1 rounded-badge text-sm border transition-colors',
|
|
337
337
|
chipValues[chip.key]
|
|
338
338
|
? 'border-primary/50 bg-primary/5 text-primary font-medium dark:bg-primary/10'
|
|
339
339
|
: 'border-card-line bg-card text-muted-foreground hover:bg-muted-hover',
|
|
@@ -358,7 +358,7 @@ defineExpose({ getSelectedRows, reload, clearCache, exportTable, tableRef, close
|
|
|
358
358
|
<div
|
|
359
359
|
v-if="openChip === chip.key"
|
|
360
360
|
v-click-outside="() => openChip = null"
|
|
361
|
-
class="absolute top-full left-0 z-50 mt-1 bg-dropdown border border-dropdown-line rounded-
|
|
361
|
+
class="absolute top-full left-0 z-50 mt-1 bg-dropdown border border-dropdown-line rounded-popover shadow-xl py-1 min-w-40"
|
|
362
362
|
>
|
|
363
363
|
<button
|
|
364
364
|
type="button"
|
|
@@ -405,7 +405,7 @@ defineExpose({ getSelectedRows, reload, clearCache, exportTable, tableRef, close
|
|
|
405
405
|
<div
|
|
406
406
|
v-if="showFilterPanel"
|
|
407
407
|
ref="filterPanelRef"
|
|
408
|
-
class="absolute top-14 left-4 z-50 bg-dropdown border border-dropdown-line rounded-
|
|
408
|
+
class="absolute top-14 left-4 z-50 bg-dropdown border border-dropdown-line rounded-popover shadow-2xl p-3 min-w-64 max-h-96 overflow-y-auto"
|
|
409
409
|
>
|
|
410
410
|
<p class="text-[10px] font-bold text-muted-foreground uppercase tracking-widest mb-3 px-1">Filtros avanzados</p>
|
|
411
411
|
<TableFilter v-model="activeFilters" :columns="filtersConfig" />
|
|
@@ -508,7 +508,7 @@ defineExpose({ getSelectedRows, reload, clearCache, exportTable, tableRef, close
|
|
|
508
508
|
<div
|
|
509
509
|
v-if="showColumnPanel"
|
|
510
510
|
ref="columnPanelRef"
|
|
511
|
-
class="fixed z-50 bg-dropdown border border-dropdown-line rounded-
|
|
511
|
+
class="fixed z-50 bg-dropdown border border-dropdown-line rounded-popover shadow-2xl p-3 min-w-56 max-h-80 overflow-y-auto"
|
|
512
512
|
:style="columnPanelStyle"
|
|
513
513
|
>
|
|
514
514
|
<p class="text-[10px] font-bold text-muted-foreground uppercase tracking-widest mb-2 px-1">Columnas visibles</p>
|
|
@@ -520,7 +520,7 @@ defineExpose({ getSelectedRows, reload, clearCache, exportTable, tableRef, close
|
|
|
520
520
|
@dragover="(e) => onDragOver(e, col.key)"
|
|
521
521
|
@dragleave="onDragLeave"
|
|
522
522
|
@drop="onDrop(col.key)"
|
|
523
|
-
class="flex items-center gap-2 py-1.5 px-2 rounded-
|
|
523
|
+
class="flex items-center gap-2 py-1.5 px-2 rounded-control select-none transition-colors"
|
|
524
524
|
:class="dragOverKey === col.key ? 'bg-primary/10 ring-1 ring-primary/30' : 'hover:bg-muted-hover cursor-grab'"
|
|
525
525
|
>
|
|
526
526
|
<IconGripVertical class="size-4 text-muted-foreground-2 shrink-0" />
|
|
@@ -55,7 +55,7 @@ const displayText = computed(() => {
|
|
|
55
55
|
|
|
56
56
|
const selectClasses = computed(() => {
|
|
57
57
|
const base =
|
|
58
|
-
"
|
|
58
|
+
"innertia-field relative cursor-pointer text-foreground";
|
|
59
59
|
const validation = "border-card-line";
|
|
60
60
|
const disabled = props.disabled ? "opacity-50 cursor-not-allowed" : "";
|
|
61
61
|
return `${base} ${validation} ${disabled}`;
|
|
@@ -121,7 +121,7 @@ onUnmounted(() => document.removeEventListener("mousedown", handleClickOutside))
|
|
|
121
121
|
|
|
122
122
|
<span
|
|
123
123
|
v-if="clearable && hasSelection && !disabled"
|
|
124
|
-
class="absolute end-8 top-1/2 -translate-y-1/2 hover:bg-muted-hover rounded-
|
|
124
|
+
class="absolute end-8 top-1/2 -translate-y-1/2 hover:bg-muted-hover rounded-avatar p-1 transition-colors focus:outline-none focus:ring-1 focus:ring-slate-400"
|
|
125
125
|
role="button"
|
|
126
126
|
tabindex="0"
|
|
127
127
|
@click.stop="clearSelection"
|
|
@@ -169,7 +169,7 @@ onUnmounted(() => document.removeEventListener("mousedown", handleClickOutside))
|
|
|
169
169
|
<div
|
|
170
170
|
v-show="isOpen"
|
|
171
171
|
:class="[
|
|
172
|
-
'absolute z-50 w-full mt-
|
|
172
|
+
'absolute z-50 w-full -mt-px bg-[color:var(--field-dropdown-bg)] border border-[color:var(--field-border)] rounded-b-control shadow-lg max-h-60 overflow-auto',
|
|
173
173
|
menuClass,
|
|
174
174
|
]"
|
|
175
175
|
>
|
|
@@ -28,14 +28,14 @@ defineExpose({ reload, clearCache })
|
|
|
28
28
|
<template>
|
|
29
29
|
<div class="flex flex-col gap-4">
|
|
30
30
|
<div v-if="showSearch" class="relative">
|
|
31
|
-
<div class="absolute inset-y-0
|
|
32
|
-
<IconSearch class="size-4 text-
|
|
31
|
+
<div class="absolute inset-y-0 end-0 flex items-center pe-3 pointer-events-none">
|
|
32
|
+
<IconSearch class="size-4 text-muted-foreground" stroke="1.5" />
|
|
33
33
|
</div>
|
|
34
34
|
<input
|
|
35
35
|
v-model="search"
|
|
36
|
-
type="
|
|
36
|
+
type="text"
|
|
37
37
|
:placeholder="searchPlaceholder"
|
|
38
|
-
class="
|
|
38
|
+
class="innertia-field pe-10"
|
|
39
39
|
/>
|
|
40
40
|
</div>
|
|
41
41
|
|
|
@@ -135,7 +135,7 @@ defineExpose({ reload, rows })
|
|
|
135
135
|
<div
|
|
136
136
|
v-for="state in states"
|
|
137
137
|
:key="state.key"
|
|
138
|
-
class="flex-shrink-0 w-72 flex flex-col rounded-
|
|
138
|
+
class="flex-shrink-0 w-72 flex flex-col rounded-card border border-card-line overflow-hidden transition-shadow"
|
|
139
139
|
:class="dragOverState === state.key ? getColors(state).over : ''"
|
|
140
140
|
@dragover="onDragOver($event, state.key)"
|
|
141
141
|
@dragleave="onDragLeave"
|
|
@@ -144,7 +144,7 @@ defineExpose({ reload, rows })
|
|
|
144
144
|
<!-- Column header -->
|
|
145
145
|
<div :class="['px-4 py-3 flex items-center justify-between', getColors(state).header]">
|
|
146
146
|
<span class="font-semibold text-sm">{{ state.label }}</span>
|
|
147
|
-
<span class="text-xs font-bold bg-white/60 dark:bg-black/20 rounded-
|
|
147
|
+
<span class="text-xs font-bold bg-white/60 dark:bg-black/20 rounded-badge px-2 py-0.5">
|
|
148
148
|
{{ columnRows[state.key]?.length ?? 0 }}
|
|
149
149
|
</span>
|
|
150
150
|
</div>
|
|
@@ -156,7 +156,7 @@ defineExpose({ reload, rows })
|
|
|
156
156
|
:key="row.id"
|
|
157
157
|
draggable="true"
|
|
158
158
|
@dragstart="onDragStart(row, state.key)"
|
|
159
|
-
class="bg-card border border-card-line rounded-
|
|
159
|
+
class="bg-card border border-card-line rounded-card p-3 cursor-grab active:cursor-grabbing hover:shadow-md transition-shadow select-none"
|
|
160
160
|
:class="draggedId === row.id ? 'opacity-40' : ''"
|
|
161
161
|
@click="emit('card-click', row)"
|
|
162
162
|
>
|
|
@@ -69,14 +69,14 @@ defineExpose({ reload, rows })
|
|
|
69
69
|
<template>
|
|
70
70
|
<div class="flex flex-col gap-4">
|
|
71
71
|
<div v-if="showSearch" class="relative">
|
|
72
|
-
<div class="absolute inset-y-0
|
|
73
|
-
<IconSearch class="size-4 text-
|
|
72
|
+
<div class="absolute inset-y-0 end-0 flex items-center pe-3 pointer-events-none">
|
|
73
|
+
<IconSearch class="size-4 text-muted-foreground" stroke="1.5" />
|
|
74
74
|
</div>
|
|
75
75
|
<input
|
|
76
76
|
v-model="search"
|
|
77
|
-
type="
|
|
77
|
+
type="text"
|
|
78
78
|
:placeholder="searchPlaceholder"
|
|
79
|
-
class="
|
|
79
|
+
class="innertia-field pe-10"
|
|
80
80
|
/>
|
|
81
81
|
</div>
|
|
82
82
|
|
|
@@ -85,7 +85,7 @@ defineExpose({ reload, rows })
|
|
|
85
85
|
<div
|
|
86
86
|
v-for="i in 8"
|
|
87
87
|
:key="i"
|
|
88
|
-
class="h-16 bg-surface rounded-
|
|
88
|
+
class="h-16 bg-surface rounded-card animate-pulse"
|
|
89
89
|
/>
|
|
90
90
|
</div>
|
|
91
91
|
|
|
@@ -94,7 +94,7 @@ defineExpose({ reload, rows })
|
|
|
94
94
|
<div
|
|
95
95
|
v-for="row in rows"
|
|
96
96
|
:key="row.id ?? JSON.stringify(row)"
|
|
97
|
-
class="bg-card border border-card-line rounded-
|
|
97
|
+
class="bg-card border border-card-line rounded-card px-4 py-3 hover:border-indigo-300 dark:hover:border-indigo-600 transition-colors cursor-pointer"
|
|
98
98
|
@click="emit('row-click', row)"
|
|
99
99
|
>
|
|
100
100
|
<slot name="item" :row="row">
|