@innertia-solutions/nuxt-theme-spark 0.1.74 → 0.1.76
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/Table/Standard.vue +60 -36
- package/components/TableFilter.vue +37 -44
- package/package.json +1 -1
|
@@ -24,6 +24,7 @@ const slots = useSlots()
|
|
|
24
24
|
const search = ref('')
|
|
25
25
|
const activeFilters = ref({})
|
|
26
26
|
const showFilterPanel = ref(false)
|
|
27
|
+
const filterPanelRef = ref(null)
|
|
27
28
|
const tableRef = ref(null)
|
|
28
29
|
|
|
29
30
|
// ─── Filter config ─────────────────────────────────────────────────────────────
|
|
@@ -51,10 +52,8 @@ const previewEnabled = ref(false)
|
|
|
51
52
|
const closePreview = () => { previewRow.value = null }
|
|
52
53
|
|
|
53
54
|
const handleRowClick = (row) => {
|
|
54
|
-
console.log('[Standard] handleRowClick | previewEnabled:', previewEnabled.value, '| row:', row?.name ?? row?.id)
|
|
55
55
|
if (previewEnabled.value) {
|
|
56
56
|
previewRow.value = previewRow.value?.id === row.id ? null : row
|
|
57
|
-
console.log('[Standard] previewRow =', previewRow.value?.name ?? previewRow.value?.id)
|
|
58
57
|
} else {
|
|
59
58
|
emit('row-click', row)
|
|
60
59
|
}
|
|
@@ -79,7 +78,6 @@ const startResize = (e) => {
|
|
|
79
78
|
const onEsc = (e) => { if (e.key === 'Escape' && previewRow.value) closePreview() }
|
|
80
79
|
onMounted(() => {
|
|
81
80
|
previewEnabled.value = !!slots.preview
|
|
82
|
-
console.log('[Standard] mounted | previewEnabled:', previewEnabled.value, '| slot keys:', Object.keys(slots))
|
|
83
81
|
window.addEventListener('keydown', onEsc)
|
|
84
82
|
})
|
|
85
83
|
onBeforeUnmount(() => window.removeEventListener('keydown', onEsc))
|
|
@@ -115,15 +113,24 @@ const onDrop = (key) => {
|
|
|
115
113
|
dragOverKey.value = null
|
|
116
114
|
}
|
|
117
115
|
|
|
118
|
-
const
|
|
116
|
+
const onColumnPanelOutsideClick = (e) => {
|
|
119
117
|
if (columnPanelRef.value && !columnPanelRef.value.contains(e.target)) {
|
|
120
118
|
showColumnPanel.value = false
|
|
121
119
|
}
|
|
122
120
|
}
|
|
121
|
+
const onFilterPanelOutsideClick = (e) => {
|
|
122
|
+
if (filterPanelRef.value && !filterPanelRef.value.contains(e.target)) {
|
|
123
|
+
showFilterPanel.value = false
|
|
124
|
+
}
|
|
125
|
+
}
|
|
123
126
|
|
|
124
127
|
watch(showColumnPanel, (v) => {
|
|
125
|
-
if (v) document.addEventListener('mousedown',
|
|
126
|
-
else document.removeEventListener('mousedown',
|
|
128
|
+
if (v) document.addEventListener('mousedown', onColumnPanelOutsideClick)
|
|
129
|
+
else document.removeEventListener('mousedown', onColumnPanelOutsideClick)
|
|
130
|
+
})
|
|
131
|
+
watch(showFilterPanel, (v) => {
|
|
132
|
+
if (v) document.addEventListener('mousedown', onFilterPanelOutsideClick)
|
|
133
|
+
else document.removeEventListener('mousedown', onFilterPanelOutsideClick)
|
|
127
134
|
})
|
|
128
135
|
|
|
129
136
|
// ─── Expose ───────────────────────────────────────────────────────────────────
|
|
@@ -159,8 +166,7 @@ defineExpose({ getSelectedRows, reload, clearCache, exportTable, tableRef })
|
|
|
159
166
|
]"
|
|
160
167
|
>
|
|
161
168
|
<IconAdjustmentsHorizontal class="size-4" stroke="1.5" />
|
|
162
|
-
Filtros
|
|
163
|
-
<span v-if="activeFilterCount > 0" class="inline-flex items-center justify-center size-5 rounded-full bg-blue-600 text-white text-xs font-bold">{{ activeFilterCount }}</span>
|
|
169
|
+
Filtros{{ activeFilterCount > 0 ? ` (${activeFilterCount})` : '' }}
|
|
164
170
|
</button>
|
|
165
171
|
|
|
166
172
|
<slot name="actions" />
|
|
@@ -182,26 +188,13 @@ defineExpose({ getSelectedRows, reload, clearCache, exportTable, tableRef })
|
|
|
182
188
|
<TableExportable v-if="showExport" :table-ref="tableRef" :name="name" :columns="columns" />
|
|
183
189
|
</div>
|
|
184
190
|
|
|
185
|
-
<!-- Filter panel -->
|
|
186
|
-
<Transition
|
|
187
|
-
enter-active-class="transition ease-out duration-150"
|
|
188
|
-
enter-from-class="opacity-0 -translate-y-2"
|
|
189
|
-
enter-to-class="opacity-100 translate-y-0"
|
|
190
|
-
leave-active-class="transition ease-in duration-100"
|
|
191
|
-
leave-from-class="opacity-100 translate-y-0"
|
|
192
|
-
leave-to-class="opacity-0 -translate-y-2"
|
|
193
|
-
>
|
|
194
|
-
<div v-if="showFilterPanel && hasFilterableColumns" class="px-4 py-3 border-b border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-900/50">
|
|
195
|
-
<TableFilter v-model="activeFilters" :columns="filtersConfig" />
|
|
196
|
-
</div>
|
|
197
|
-
</Transition>
|
|
198
|
-
|
|
199
191
|
<!-- Contenido: tabla + preview en flex -->
|
|
200
|
-
<div :class="
|
|
192
|
+
<div :class="previewEnabled ? 'flex items-stretch overflow-hidden' : ''">
|
|
201
193
|
|
|
202
194
|
<!-- Tabla -->
|
|
203
195
|
<div
|
|
204
|
-
|
|
196
|
+
class="min-w-0 transition-[width] duration-200 ease-out"
|
|
197
|
+
:style="previewRow && previewEnabled ? { width: currentRatio + '%', flexShrink: 0 } : {}"
|
|
205
198
|
>
|
|
206
199
|
<Table
|
|
207
200
|
ref="tableRef"
|
|
@@ -225,25 +218,56 @@ defineExpose({ getSelectedRows, reload, clearCache, exportTable, tableRef })
|
|
|
225
218
|
</Table>
|
|
226
219
|
</div>
|
|
227
220
|
|
|
228
|
-
<!-- Divider + preview -->
|
|
229
|
-
<
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
221
|
+
<!-- Divider + preview con animación slide -->
|
|
222
|
+
<Transition
|
|
223
|
+
enter-active-class="transition ease-out duration-200"
|
|
224
|
+
enter-from-class="opacity-0 translate-x-6"
|
|
225
|
+
enter-to-class="opacity-100 translate-x-0"
|
|
226
|
+
leave-active-class="transition ease-in duration-150"
|
|
227
|
+
leave-from-class="opacity-100 translate-x-0"
|
|
228
|
+
leave-to-class="opacity-0 translate-x-6"
|
|
229
|
+
>
|
|
236
230
|
<div
|
|
237
|
-
|
|
238
|
-
|
|
231
|
+
v-if="previewRow && previewEnabled"
|
|
232
|
+
class="flex items-stretch shrink-0 min-w-0"
|
|
233
|
+
:style="{ width: (100 - currentRatio) + '%' }"
|
|
239
234
|
>
|
|
240
|
-
|
|
235
|
+
<!-- Resize handle -->
|
|
236
|
+
<div
|
|
237
|
+
class="w-1 shrink-0 cursor-col-resize bg-slate-100 dark:bg-slate-700/60 hover:bg-indigo-300 dark:hover:bg-indigo-600 transition-colors"
|
|
238
|
+
@mousedown="startResize"
|
|
239
|
+
/>
|
|
240
|
+
<!-- Preview -->
|
|
241
|
+
<div class="flex flex-col overflow-y-auto border-l border-slate-200 dark:border-slate-700 flex-1">
|
|
242
|
+
<slot name="preview" :row="previewRow" :close="closePreview" />
|
|
243
|
+
</div>
|
|
241
244
|
</div>
|
|
242
|
-
</
|
|
245
|
+
</Transition>
|
|
243
246
|
|
|
244
247
|
</div>
|
|
245
248
|
</div>
|
|
246
249
|
|
|
250
|
+
<!-- Filter panel — outside overflow-hidden so never clipped -->
|
|
251
|
+
<Transition
|
|
252
|
+
enter-active-class="transition ease-out duration-150"
|
|
253
|
+
enter-from-class="opacity-0 translate-y-1 scale-95"
|
|
254
|
+
enter-to-class="opacity-100 translate-y-0 scale-100"
|
|
255
|
+
leave-active-class="transition ease-in duration-100"
|
|
256
|
+
leave-from-class="opacity-100 translate-y-0 scale-100"
|
|
257
|
+
leave-to-class="opacity-0 translate-y-1 scale-95"
|
|
258
|
+
>
|
|
259
|
+
<div
|
|
260
|
+
v-if="showFilterPanel && hasFilterableColumns"
|
|
261
|
+
ref="filterPanelRef"
|
|
262
|
+
class="absolute top-12 left-0 z-50 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl shadow-2xl p-3 min-w-64 max-h-96 overflow-y-auto"
|
|
263
|
+
>
|
|
264
|
+
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-3 px-1">
|
|
265
|
+
Filtros
|
|
266
|
+
</p>
|
|
267
|
+
<TableFilter v-model="activeFilters" :columns="filtersConfig" />
|
|
268
|
+
</div>
|
|
269
|
+
</Transition>
|
|
270
|
+
|
|
247
271
|
<!-- Column panel — outside overflow-hidden so never clipped -->
|
|
248
272
|
<Transition
|
|
249
273
|
enter-active-class="transition ease-out duration-150"
|
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
// Reusable filter panel — reads filterType from column definitions
|
|
3
|
-
// Column definition: { key, label, filterType: 'text' | 'select' | 'daterange', filterOptions?: [{value, label}] }
|
|
4
|
-
|
|
5
2
|
const props = defineProps({
|
|
6
|
-
modelValue: { type: Object, default: () => ({}) },
|
|
7
|
-
columns:
|
|
3
|
+
modelValue: { type: Object, default: () => ({}) },
|
|
4
|
+
columns: { type: Array, required: true },
|
|
8
5
|
})
|
|
9
6
|
|
|
10
7
|
const emit = defineEmits(['update:modelValue'])
|
|
11
8
|
|
|
12
|
-
const filterableColumns = computed(() =>
|
|
13
|
-
props.columns.filter(c => c.filterType)
|
|
14
|
-
)
|
|
9
|
+
const filterableColumns = computed(() => props.columns.filter(c => c.filterType))
|
|
15
10
|
|
|
16
11
|
const localFilters = ref({ ...props.modelValue })
|
|
17
12
|
|
|
@@ -20,7 +15,7 @@ watch(() => props.modelValue, (v) => {
|
|
|
20
15
|
}, { deep: true })
|
|
21
16
|
|
|
22
17
|
const updateFilter = (key, value) => {
|
|
23
|
-
localFilters.value[key] = value
|
|
18
|
+
localFilters.value[key] = value || null
|
|
24
19
|
emit('update:modelValue', { ...localFilters.value })
|
|
25
20
|
}
|
|
26
21
|
|
|
@@ -35,66 +30,64 @@ const activeCount = computed(() =>
|
|
|
35
30
|
</script>
|
|
36
31
|
|
|
37
32
|
<template>
|
|
38
|
-
<div class="
|
|
33
|
+
<div class="space-y-3">
|
|
39
34
|
<template v-for="col in filterableColumns" :key="col.key">
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
|
|
36
|
+
<!-- text -->
|
|
37
|
+
<div v-if="col.filterType === 'text'">
|
|
38
|
+
<label class="block text-xs font-medium text-slate-500 dark:text-slate-400 mb-1">{{ col.label }}</label>
|
|
43
39
|
<input
|
|
44
40
|
type="text"
|
|
45
41
|
:value="localFilters[col.key] ?? ''"
|
|
46
42
|
@input="updateFilter(col.key, $event.target.value)"
|
|
47
43
|
:placeholder="`Filtrar ${col.label.toLowerCase()}...`"
|
|
48
|
-
class="rounded-lg border border-gray-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-900 dark:text-white py-
|
|
44
|
+
class="w-full rounded-lg border border-gray-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-900 dark:text-white py-1.5 px-3 text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
|
49
45
|
/>
|
|
50
46
|
</div>
|
|
51
47
|
|
|
52
|
-
<!-- select
|
|
53
|
-
<div v-else-if="col.filterType === 'select'"
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
:value="
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<option value="">Todos</option>
|
|
61
|
-
<option
|
|
62
|
-
v-for="opt in col.filterOptions ?? []"
|
|
63
|
-
:key="opt.value"
|
|
64
|
-
:value="opt.value"
|
|
65
|
-
>{{ opt.label }}</option>
|
|
66
|
-
</select>
|
|
48
|
+
<!-- select -->
|
|
49
|
+
<div v-else-if="col.filterType === 'select'">
|
|
50
|
+
<Forms.Select
|
|
51
|
+
:model-value="localFilters[col.key] ?? ''"
|
|
52
|
+
@update:model-value="updateFilter(col.key, $event)"
|
|
53
|
+
:options="[{ value: '', label: 'Todos' }, ...(col.filterOptions ?? [])]"
|
|
54
|
+
:label="col.label"
|
|
55
|
+
/>
|
|
67
56
|
</div>
|
|
68
57
|
|
|
69
|
-
<!-- daterange
|
|
70
|
-
<div v-else-if="col.filterType === 'daterange'"
|
|
71
|
-
<label class="text-xs text-slate-500 dark:text-slate-400">{{ col.label }}</label>
|
|
58
|
+
<!-- daterange -->
|
|
59
|
+
<div v-else-if="col.filterType === 'daterange'">
|
|
60
|
+
<label class="block text-xs font-medium text-slate-500 dark:text-slate-400 mb-1">{{ col.label }}</label>
|
|
72
61
|
<div class="flex items-center gap-1.5">
|
|
73
62
|
<input
|
|
74
63
|
type="date"
|
|
75
64
|
:value="localFilters[col.key]?.from ?? ''"
|
|
76
65
|
@change="updateFilter(col.key, { ...localFilters[col.key], from: $event.target.value || null })"
|
|
77
|
-
class="rounded-lg border border-gray-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-900 dark:text-white py-
|
|
66
|
+
class="flex-1 rounded-lg border border-gray-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-900 dark:text-white py-1.5 px-2 text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
|
78
67
|
/>
|
|
79
|
-
<span class="text-slate-400 text-
|
|
68
|
+
<span class="text-slate-400 text-xs shrink-0">—</span>
|
|
80
69
|
<input
|
|
81
70
|
type="date"
|
|
82
71
|
:value="localFilters[col.key]?.to ?? ''"
|
|
83
72
|
@change="updateFilter(col.key, { ...localFilters[col.key], to: $event.target.value || null })"
|
|
84
|
-
class="rounded-lg border border-gray-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-900 dark:text-white py-
|
|
73
|
+
class="flex-1 rounded-lg border border-gray-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-900 dark:text-white py-1.5 px-2 text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
|
85
74
|
/>
|
|
86
75
|
</div>
|
|
87
76
|
</div>
|
|
77
|
+
|
|
88
78
|
</template>
|
|
89
79
|
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
80
|
+
<div class="pt-2 border-t border-slate-100 dark:border-slate-700/60">
|
|
81
|
+
<button
|
|
82
|
+
v-if="activeCount > 0"
|
|
83
|
+
type="button"
|
|
84
|
+
@click="clearAll"
|
|
85
|
+
class="w-full py-1.5 px-3 text-sm text-rose-600 dark:text-rose-400 hover:bg-rose-50 dark:hover:bg-rose-500/10 rounded-lg transition-colors flex items-center justify-center gap-1.5"
|
|
86
|
+
>
|
|
87
|
+
<svg class="size-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
|
88
|
+
Limpiar filtros
|
|
89
|
+
</button>
|
|
90
|
+
<p v-else class="text-xs text-center text-slate-400 dark:text-slate-500 py-0.5">Sin filtros activos</p>
|
|
91
|
+
</div>
|
|
99
92
|
</div>
|
|
100
93
|
</template>
|