@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
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
/**
|
|
3
|
+
* <DataPreview> — Panel de preview overlay compartido por Table.Standard y Tree.Standard.
|
|
4
|
+
*
|
|
5
|
+
* Encapsula:
|
|
6
|
+
* - Panel absoluto a la derecha del container padre (position:relative)
|
|
7
|
+
* - Resize por drag del handle vertical en el borde izquierdo
|
|
8
|
+
* - Header con título + acciones + botones (abrir, eliminar, minimizar, cerrar)
|
|
9
|
+
* - Tabs inferior cuando se pasa `historyEndpoint` (Datos / Bitácora)
|
|
10
|
+
* - Cache de la fila previewada en sessionStorage cuando `cached`
|
|
11
|
+
* - Chip flotante (Teleport to body) compartido vía `useDockedPreviews` Pinia store
|
|
12
|
+
*
|
|
13
|
+
* El parent es dueño de `row` (v-model) — DataPreview lo seteoa a null al cerrar.
|
|
14
|
+
* El parent también maneja el click fuera + ESC porque conoce su containerRef.
|
|
15
|
+
*
|
|
16
|
+
* Slots:
|
|
17
|
+
* - header ({ row, close }) — título del panel
|
|
18
|
+
* - actions ({ row, close }) — botones extra al lado de los built-in
|
|
19
|
+
* - default ({ row, close }) — body del preview
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
IconExternalLink, IconTrash, IconMinus, IconX, IconMaximize,
|
|
24
|
+
} from '@tabler/icons-vue'
|
|
25
|
+
|
|
26
|
+
const props = defineProps({
|
|
27
|
+
/** Activa el preview (típicamente derivado de !!slots.preview en el parent). */
|
|
28
|
+
enabled: { type: Boolean, default: false },
|
|
29
|
+
/** Nombre único para cache + dock id. */
|
|
30
|
+
name: { type: String, default: 'default' },
|
|
31
|
+
/** Si true, persiste la fila previewada en sessionStorage entre navegaciones. */
|
|
32
|
+
cached: { type: Boolean, default: false },
|
|
33
|
+
/** URL fija o (row) => url para botón "abrir externa". */
|
|
34
|
+
previewHref: { type: [String, Function], default: null },
|
|
35
|
+
/** Muestra botón eliminar (emite @delete). */
|
|
36
|
+
previewDeletable: { type: Boolean, default: false },
|
|
37
|
+
/** Porcentaje inicial de la tabla (0-100). */
|
|
38
|
+
splitRatio: { type: Number, default: 55 },
|
|
39
|
+
/** Endpoint del historial. Cuando se pasa, muestra tabs Datos/Bitácora. */
|
|
40
|
+
historyEndpoint: { type: String, default: null },
|
|
41
|
+
/** Container ref (HTMLElement) para medir el width al hacer resize. */
|
|
42
|
+
containerRef: { type: Object, default: null },
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const emit = defineEmits(['delete'])
|
|
46
|
+
|
|
47
|
+
// v-model:row → parent es dueño de la fila activa.
|
|
48
|
+
const row = defineModel('row', { default: null })
|
|
49
|
+
|
|
50
|
+
const previewFromCache = ref(false)
|
|
51
|
+
const currentRatio = ref(props.splitRatio)
|
|
52
|
+
const panelRef = ref(null)
|
|
53
|
+
const previewTab = ref('datos')
|
|
54
|
+
|
|
55
|
+
const cacheKey = computed(() => `data-preview-${props.name}`)
|
|
56
|
+
|
|
57
|
+
const resolvedPreviewHref = computed(() => {
|
|
58
|
+
if (!props.previewHref || !row.value) return null
|
|
59
|
+
return typeof props.previewHref === 'function'
|
|
60
|
+
? props.previewHref(row.value)
|
|
61
|
+
: props.previewHref
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const hasHistory = computed(() => !!props.historyEndpoint)
|
|
65
|
+
|
|
66
|
+
// Resetear tab al cambiar de fila + reinit tooltips de Preline
|
|
67
|
+
watch(row, async (r) => {
|
|
68
|
+
previewTab.value = 'datos'
|
|
69
|
+
if (r) {
|
|
70
|
+
await nextTick()
|
|
71
|
+
if (typeof window !== 'undefined') window.HSStaticMethods?.autoInit?.(['Tooltip'])
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
// Persistir fila en sessionStorage
|
|
76
|
+
watch(row, (r) => {
|
|
77
|
+
if (!props.cached) return
|
|
78
|
+
if (r) sessionStorage.setItem(cacheKey.value, JSON.stringify(r))
|
|
79
|
+
else sessionStorage.removeItem(cacheKey.value)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const close = () => { row.value = null }
|
|
83
|
+
|
|
84
|
+
const startResize = (e) => {
|
|
85
|
+
e.preventDefault()
|
|
86
|
+
const onMove = (ev) => {
|
|
87
|
+
const el = props.containerRef
|
|
88
|
+
if (!el) return
|
|
89
|
+
const rect = el.getBoundingClientRect()
|
|
90
|
+
const ratio = ((ev.clientX - rect.left) / rect.width) * 100
|
|
91
|
+
currentRatio.value = Math.min(80, Math.max(25, ratio))
|
|
92
|
+
}
|
|
93
|
+
const onUp = () => {
|
|
94
|
+
window.removeEventListener('mousemove', onMove)
|
|
95
|
+
window.removeEventListener('mouseup', onUp)
|
|
96
|
+
}
|
|
97
|
+
window.addEventListener('mousemove', onMove)
|
|
98
|
+
window.addEventListener('mouseup', onUp)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ─── Dock (chip flotante) ─────────────────────────────────────────────────────
|
|
102
|
+
const {
|
|
103
|
+
docked,
|
|
104
|
+
dock, undock: undockItem,
|
|
105
|
+
activeDockId, activeDockRect,
|
|
106
|
+
collapseDock,
|
|
107
|
+
} = useDockedPreviews()
|
|
108
|
+
const route = useRoute()
|
|
109
|
+
|
|
110
|
+
const minimize = () => {
|
|
111
|
+
if (!row.value) return
|
|
112
|
+
const label = row.value.name ?? row.value.title ?? row.value.email ?? String(row.value.id)
|
|
113
|
+
const subtitle = row.value.email ?? row.value.description ?? null
|
|
114
|
+
dock({
|
|
115
|
+
id: `${props.name}-${row.value.id}`,
|
|
116
|
+
label,
|
|
117
|
+
subtitle,
|
|
118
|
+
row: { ...row.value },
|
|
119
|
+
tableName: props.name,
|
|
120
|
+
route: route.path,
|
|
121
|
+
})
|
|
122
|
+
close()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const floatingItem = computed(() =>
|
|
126
|
+
activeDockId.value
|
|
127
|
+
? docked.value.find(d => d.id === activeDockId.value && d.tableName === props.name) ?? null
|
|
128
|
+
: null
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
const floatingPanelStyle = computed(() => {
|
|
132
|
+
const rect = activeDockRect.value
|
|
133
|
+
const panelW = 384
|
|
134
|
+
const bottom = 52
|
|
135
|
+
if (!rect || typeof window === 'undefined') return { bottom: bottom + 'px', right: '16px' }
|
|
136
|
+
const tabCenter = rect.left + rect.width / 2
|
|
137
|
+
let right = window.innerWidth - tabCenter - panelW / 2
|
|
138
|
+
right = Math.max(8, Math.min(right, window.innerWidth - panelW - 8))
|
|
139
|
+
return { bottom: bottom + 'px', right: right + 'px' }
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const expandToFull = (item) => {
|
|
143
|
+
row.value = item.row
|
|
144
|
+
undockItem(item.id)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Restore from cache on mount
|
|
148
|
+
onMounted(async () => {
|
|
149
|
+
if (props.cached && props.enabled) {
|
|
150
|
+
try {
|
|
151
|
+
const raw = sessionStorage.getItem(cacheKey.value)
|
|
152
|
+
if (raw) {
|
|
153
|
+
previewFromCache.value = true
|
|
154
|
+
row.value = JSON.parse(raw)
|
|
155
|
+
await nextTick()
|
|
156
|
+
previewFromCache.value = false
|
|
157
|
+
}
|
|
158
|
+
} catch {}
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
defineExpose({ panelRef })
|
|
163
|
+
</script>
|
|
164
|
+
|
|
165
|
+
<template>
|
|
166
|
+
<!-- Panel overlay (absolute right, dentro del container del parent) -->
|
|
167
|
+
<Transition
|
|
168
|
+
:enter-active-class="previewFromCache ? '' : 'transition ease-out duration-200'"
|
|
169
|
+
:enter-from-class="previewFromCache ? '' : 'opacity-0 translate-x-6'"
|
|
170
|
+
:enter-to-class="previewFromCache ? '' : 'opacity-100 translate-x-0'"
|
|
171
|
+
leave-active-class="transition ease-in duration-150"
|
|
172
|
+
leave-from-class="opacity-100 translate-x-0"
|
|
173
|
+
leave-to-class="opacity-0 translate-x-6"
|
|
174
|
+
>
|
|
175
|
+
<div
|
|
176
|
+
v-if="row && enabled"
|
|
177
|
+
ref="panelRef"
|
|
178
|
+
class="absolute top-0 right-0 bottom-0 z-30 flex flex-col bg-card border border-card-line rounded-card shadow-xl overflow-hidden"
|
|
179
|
+
:style="{ width: (100 - currentRatio) + '%' }"
|
|
180
|
+
>
|
|
181
|
+
<!-- Resize handle -->
|
|
182
|
+
<div
|
|
183
|
+
class="absolute left-1 top-1/2 -translate-y-1/2 h-12 w-1 cursor-col-resize rounded-full bg-border hover:bg-primary/50 transition-colors z-10"
|
|
184
|
+
@mousedown="startResize"
|
|
185
|
+
/>
|
|
186
|
+
|
|
187
|
+
<!-- Header -->
|
|
188
|
+
<div class="shrink-0 flex items-center gap-3 px-5 py-4 border-b border-card-line">
|
|
189
|
+
<div class="flex-1 min-w-0">
|
|
190
|
+
<slot name="header" :row="row" :close="close" />
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
<div class="flex items-center gap-0.5 shrink-0">
|
|
194
|
+
<slot name="actions" :row="row" :close="close" />
|
|
195
|
+
|
|
196
|
+
<!-- Abrir externa -->
|
|
197
|
+
<div v-if="resolvedPreviewHref" class="hs-tooltip [--placement:top] inline-block">
|
|
198
|
+
<NuxtLink
|
|
199
|
+
:to="resolvedPreviewHref"
|
|
200
|
+
class="hs-tooltip-toggle inline-flex items-center justify-center size-7 rounded-control text-muted-foreground hover:text-foreground hover:bg-muted-hover transition-colors"
|
|
201
|
+
>
|
|
202
|
+
<IconExternalLink class="size-3.5" />
|
|
203
|
+
</NuxtLink>
|
|
204
|
+
<span class="hs-tooltip-content hs-tooltip-shown:opacity-100 hs-tooltip-shown:visible opacity-0 transition-opacity inline-block absolute invisible z-10 py-1 px-2 bg-tooltip border border-tooltip-line text-xs font-medium text-tooltip-foreground rounded-popover shadow-2xs" role="tooltip">Abrir</span>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<!-- Eliminar -->
|
|
208
|
+
<div v-if="previewDeletable" class="hs-tooltip [--placement:top] inline-block">
|
|
209
|
+
<button
|
|
210
|
+
type="button"
|
|
211
|
+
class="hs-tooltip-toggle inline-flex items-center justify-center size-7 rounded-control text-muted-foreground hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors"
|
|
212
|
+
@click.stop="emit('delete', row)"
|
|
213
|
+
>
|
|
214
|
+
<IconTrash class="size-3.5" />
|
|
215
|
+
</button>
|
|
216
|
+
<span class="hs-tooltip-content hs-tooltip-shown:opacity-100 hs-tooltip-shown:visible opacity-0 transition-opacity inline-block absolute invisible z-10 py-1 px-2 bg-tooltip border border-tooltip-line text-xs font-medium text-tooltip-foreground rounded-popover shadow-2xs" role="tooltip">Eliminar</span>
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
<!-- Minimizar -->
|
|
220
|
+
<div class="hs-tooltip [--placement:top] inline-block">
|
|
221
|
+
<button
|
|
222
|
+
type="button"
|
|
223
|
+
class="hs-tooltip-toggle inline-flex items-center justify-center size-7 rounded-control text-muted-foreground hover:text-foreground hover:bg-muted-hover transition-colors"
|
|
224
|
+
@click.stop="minimize"
|
|
225
|
+
>
|
|
226
|
+
<IconMinus class="size-3.5" />
|
|
227
|
+
</button>
|
|
228
|
+
<span class="hs-tooltip-content hs-tooltip-shown:opacity-100 hs-tooltip-shown:visible opacity-0 transition-opacity inline-block absolute invisible z-10 py-1 px-2 bg-tooltip border border-tooltip-line text-xs font-medium text-tooltip-foreground rounded-popover shadow-2xs" role="tooltip">Minimizar</span>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<!-- Cerrar -->
|
|
232
|
+
<div class="hs-tooltip [--placement:top] inline-block">
|
|
233
|
+
<button
|
|
234
|
+
type="button"
|
|
235
|
+
class="hs-tooltip-toggle inline-flex items-center justify-center size-7 rounded-control text-muted-foreground hover:text-foreground hover:bg-muted-hover transition-colors"
|
|
236
|
+
@click.stop="close"
|
|
237
|
+
>
|
|
238
|
+
<IconX class="size-3.5" />
|
|
239
|
+
</button>
|
|
240
|
+
<span class="hs-tooltip-content hs-tooltip-shown:opacity-100 hs-tooltip-shown:visible opacity-0 transition-opacity inline-block absolute invisible z-10 py-1 px-2 bg-tooltip border border-tooltip-line text-xs font-medium text-tooltip-foreground rounded-popover shadow-2xs" role="tooltip">Cerrar</span>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
<!-- Body -->
|
|
246
|
+
<div class="flex-1 overflow-y-auto min-h-0">
|
|
247
|
+
<slot v-if="previewTab === 'datos'" :row="row" :close="close" />
|
|
248
|
+
<TablePreviewTimeline
|
|
249
|
+
v-else-if="previewTab === 'bitacora' && historyEndpoint"
|
|
250
|
+
:endpoint="historyEndpoint"
|
|
251
|
+
/>
|
|
252
|
+
</div>
|
|
253
|
+
|
|
254
|
+
<!-- Tabs (cuando hay historyEndpoint) -->
|
|
255
|
+
<div v-if="hasHistory" class="shrink-0 flex border-t border-card-line">
|
|
256
|
+
<button
|
|
257
|
+
type="button"
|
|
258
|
+
@click="previewTab = 'datos'"
|
|
259
|
+
:class="[
|
|
260
|
+
'flex-1 py-2.5 text-xs font-semibold transition-colors border-r border-card-line border-t-2 -mt-px',
|
|
261
|
+
previewTab === 'datos'
|
|
262
|
+
? 'border-t-card text-foreground'
|
|
263
|
+
: 'border-t-transparent text-muted-foreground hover:text-foreground hover:bg-muted-hover'
|
|
264
|
+
]"
|
|
265
|
+
>
|
|
266
|
+
Datos
|
|
267
|
+
</button>
|
|
268
|
+
<button
|
|
269
|
+
type="button"
|
|
270
|
+
@click="previewTab = 'bitacora'"
|
|
271
|
+
:class="[
|
|
272
|
+
'flex-1 py-2.5 text-xs font-semibold transition-colors border-t-2 -mt-px',
|
|
273
|
+
previewTab === 'bitacora'
|
|
274
|
+
? 'border-t-card text-foreground'
|
|
275
|
+
: 'border-t-transparent text-muted-foreground hover:text-foreground hover:bg-muted-hover'
|
|
276
|
+
]"
|
|
277
|
+
>
|
|
278
|
+
Bitácora
|
|
279
|
+
</button>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
</Transition>
|
|
283
|
+
|
|
284
|
+
<!-- Floating chip (dock minimizado) — siempre teleportado a body -->
|
|
285
|
+
<Teleport to="body">
|
|
286
|
+
<Transition
|
|
287
|
+
enter-active-class="transition ease-out duration-200"
|
|
288
|
+
enter-from-class="opacity-0 translate-y-4"
|
|
289
|
+
enter-to-class="opacity-100 translate-y-0"
|
|
290
|
+
leave-active-class="transition ease-in duration-150"
|
|
291
|
+
leave-from-class="opacity-100 translate-y-0"
|
|
292
|
+
leave-to-class="opacity-0 translate-y-4"
|
|
293
|
+
>
|
|
294
|
+
<div
|
|
295
|
+
v-if="floatingItem"
|
|
296
|
+
class="fixed z-[60] w-96 flex flex-col bg-card border border-card-line rounded-t-card shadow-2xl overflow-hidden"
|
|
297
|
+
:style="{ ...floatingPanelStyle, maxHeight: 'min(480px, calc(100vh - 60px))' }"
|
|
298
|
+
>
|
|
299
|
+
<div class="flex items-center gap-2 px-3 py-2.5 border-b border-card-line shrink-0 bg-surface select-none">
|
|
300
|
+
<span class="size-6 rounded-avatar bg-primary flex items-center justify-center text-[10px] font-bold text-primary-foreground shrink-0">
|
|
301
|
+
{{ (floatingItem.label?.[0] ?? '?').toUpperCase() }}
|
|
302
|
+
</span>
|
|
303
|
+
<div class="flex-1 min-w-0">
|
|
304
|
+
<p class="text-sm font-semibold text-foreground truncate leading-tight">{{ floatingItem.label }}</p>
|
|
305
|
+
<p v-if="floatingItem.subtitle" class="text-xs text-muted-foreground truncate">{{ floatingItem.subtitle }}</p>
|
|
306
|
+
</div>
|
|
307
|
+
<button type="button" title="Expandir" class="inline-flex items-center justify-center size-6 rounded-control text-muted-foreground hover:text-foreground hover:bg-muted-hover transition-colors" @click.stop="expandToFull(floatingItem)">
|
|
308
|
+
<IconMaximize class="size-3.5" />
|
|
309
|
+
</button>
|
|
310
|
+
<button type="button" title="Minimizar" class="inline-flex items-center justify-center size-6 rounded-control text-muted-foreground hover:text-foreground hover:bg-muted-hover transition-colors" @click.stop="collapseDock()">
|
|
311
|
+
<IconMinus class="size-3.5" />
|
|
312
|
+
</button>
|
|
313
|
+
<button type="button" title="Cerrar" class="inline-flex items-center justify-center size-6 rounded-control text-muted-foreground hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors" @click.stop="undockItem(floatingItem.id)">
|
|
314
|
+
<IconX class="size-3.5" />
|
|
315
|
+
</button>
|
|
316
|
+
</div>
|
|
317
|
+
<div class="flex-1 overflow-y-auto min-h-0">
|
|
318
|
+
<slot :row="floatingItem.row" :close="() => undockItem(floatingItem.id)" />
|
|
319
|
+
</div>
|
|
320
|
+
</div>
|
|
321
|
+
</Transition>
|
|
322
|
+
</Teleport>
|
|
323
|
+
</template>
|
package/components/DataTable.vue
CHANGED
|
@@ -436,7 +436,7 @@ const handleRowKeydown = (row, event) => {
|
|
|
436
436
|
type="checkbox"
|
|
437
437
|
:checked="isAllVisibleSelected"
|
|
438
438
|
@change="toggleSelectAll"
|
|
439
|
-
class="mx-2 shrink-0 border-gray-300 rounded-
|
|
439
|
+
class="mx-2 shrink-0 border-gray-300 rounded-control text-blue-900 focus:ring-blue-900 dark:bg-card border-card-line"
|
|
440
440
|
/>
|
|
441
441
|
</th>
|
|
442
442
|
<th
|
|
@@ -538,7 +538,7 @@ const handleRowKeydown = (row, event) => {
|
|
|
538
538
|
<!-- Empty overlays -->
|
|
539
539
|
<div
|
|
540
540
|
v-if="!loading && data.length === 0 && !search"
|
|
541
|
-
class="absolute inset-0 z-10 pointer-events-none flex items-center justify-center backdrop-blur-sm bg-card/60 transition-all rounded-
|
|
541
|
+
class="absolute inset-0 z-10 pointer-events-none flex items-center justify-center backdrop-blur-sm bg-card/60 transition-all rounded-card"
|
|
542
542
|
>
|
|
543
543
|
<slot name="empty">
|
|
544
544
|
<p class="text-muted-foreground text-lg font-medium italic">No hay registros</p>
|
|
@@ -547,7 +547,7 @@ const handleRowKeydown = (row, event) => {
|
|
|
547
547
|
|
|
548
548
|
<div
|
|
549
549
|
v-if="!loading && data.length === 0 && search"
|
|
550
|
-
class="absolute inset-0 z-10 pointer-events-none flex items-center justify-center backdrop-blur-sm bg-card/60 transition-all rounded-
|
|
550
|
+
class="absolute inset-0 z-10 pointer-events-none flex items-center justify-center backdrop-blur-sm bg-card/60 transition-all rounded-card"
|
|
551
551
|
>
|
|
552
552
|
<slot name="empty-search">
|
|
553
553
|
<p class="text-muted-foreground text-lg font-medium italic">No hay registros en la búsqueda</p>
|
|
@@ -561,7 +561,7 @@ const handleRowKeydown = (row, event) => {
|
|
|
561
561
|
<div v-if="loading" :class="gridClass">
|
|
562
562
|
<div v-for="(_, index) in skeletonRows" :key="'grid-skeleton-' + index" class="animate-pulse">
|
|
563
563
|
<slot name="grid-skeleton">
|
|
564
|
-
<div class="bg-card rounded-
|
|
564
|
+
<div class="bg-card rounded-card border border-card-line p-4">
|
|
565
565
|
<div class="space-y-3">
|
|
566
566
|
<div class="h-4 bg-surface-1 rounded w-3/4"></div>
|
|
567
567
|
<div class="h-4 bg-surface-1 rounded w-1/2"></div>
|
|
@@ -583,7 +583,7 @@ const handleRowKeydown = (row, event) => {
|
|
|
583
583
|
:checkable="checkable"
|
|
584
584
|
:toggleRow="() => toggleRow(row)"
|
|
585
585
|
>
|
|
586
|
-
<div class="bg-card rounded-
|
|
586
|
+
<div class="bg-card rounded-card border border-card-line p-4 hover:shadow-md transition-shadow relative">
|
|
587
587
|
<div v-if="checkable" class="absolute top-2 left-2 z-10">
|
|
588
588
|
<input
|
|
589
589
|
type="checkbox"
|
|
@@ -636,11 +636,11 @@ const handleRowKeydown = (row, event) => {
|
|
|
636
636
|
<p class="text-sm text-foreground font-medium">{{ meta.total }} registros</p>
|
|
637
637
|
|
|
638
638
|
<div v-if="isDataFromCache && cached" class="group relative flex items-center">
|
|
639
|
-
<div class="flex items-center gap-x-1.5 py-1 px-2.5 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 rounded-
|
|
639
|
+
<div class="flex items-center gap-x-1.5 py-1 px-2.5 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 rounded-badge cursor-help transition-colors hover:bg-emerald-500/20">
|
|
640
640
|
<IconBolt class="size-3.5 fill-current" />
|
|
641
641
|
<span class="text-[10px] font-bold uppercase tracking-wider">Instant</span>
|
|
642
642
|
</div>
|
|
643
|
-
<div class="absolute bottom-full mb-2 left-0 hidden group-hover:block w-48 p-2.5 bg-slate-900 text-white text-[11px] leading-relaxed rounded-
|
|
643
|
+
<div class="absolute bottom-full mb-2 left-0 hidden group-hover:block w-48 p-2.5 bg-slate-900 text-white text-[11px] leading-relaxed rounded-popover shadow-2xl z-50 animate-in fade-in zoom-in duration-200">
|
|
644
644
|
<div class="font-bold mb-1 flex items-center gap-x-1.5 text-emerald-400">
|
|
645
645
|
<IconBolt class="size-3" />
|
|
646
646
|
Datos en Caché
|
|
@@ -659,7 +659,7 @@ const handleRowKeydown = (row, event) => {
|
|
|
659
659
|
v-if="!isCustomPerPage"
|
|
660
660
|
:value="perPage"
|
|
661
661
|
@change="(e) => handlePerPageChange(e.target.value)"
|
|
662
|
-
class="bg-surface border-none text-[11px] font-bold text-muted-foreground-1 rounded-
|
|
662
|
+
class="bg-surface border-none text-[11px] font-bold text-muted-foreground-1 rounded-control focus:ring-0 cursor-pointer py-1 pl-2 pr-8"
|
|
663
663
|
>
|
|
664
664
|
<option :value="10">10</option>
|
|
665
665
|
<option :value="25">25</option>
|
|
@@ -673,7 +673,7 @@ const handleRowKeydown = (row, event) => {
|
|
|
673
673
|
v-model.number="perPage"
|
|
674
674
|
min="1"
|
|
675
675
|
max="500"
|
|
676
|
-
class="w-14 bg-surface border-none text-[11px] font-bold text-muted-foreground-1 rounded-
|
|
676
|
+
class="w-14 bg-surface border-none text-[11px] font-bold text-muted-foreground-1 rounded-control focus:ring-2 focus:ring-indigo-500/20 py-1 px-2"
|
|
677
677
|
@blur="perPage = perPage || 10"
|
|
678
678
|
/>
|
|
679
679
|
<button @click="resetPerPage" class="text-[10px] text-indigo-500 font-bold hover:underline">Volver</button>
|
|
@@ -683,7 +683,7 @@ const handleRowKeydown = (row, event) => {
|
|
|
683
683
|
<nav class="flex justify-end items-center gap-x-1" aria-label="Pagination">
|
|
684
684
|
<button
|
|
685
685
|
type="button"
|
|
686
|
-
class="size-8 flex items-center justify-center rounded-
|
|
686
|
+
class="size-8 flex items-center justify-center rounded-control text-foreground hover:bg-muted-hover disabled:opacity-30"
|
|
687
687
|
:disabled="page <= 1"
|
|
688
688
|
@click="goToPreviousPage"
|
|
689
689
|
>
|
|
@@ -692,13 +692,13 @@ const handleRowKeydown = (row, event) => {
|
|
|
692
692
|
</svg>
|
|
693
693
|
</button>
|
|
694
694
|
<div class="flex items-center gap-x-1 mx-2">
|
|
695
|
-
<span class="size-8 flex items-center justify-center text-xs font-bold rounded-
|
|
695
|
+
<span class="size-8 flex items-center justify-center text-xs font-bold rounded-control bg-surface text-foreground">{{ meta.current_page }}</span>
|
|
696
696
|
<span class="text-[10px] font-bold text-muted-foreground uppercase mx-1">de</span>
|
|
697
697
|
<span class="text-[10px] font-bold text-muted-foreground">{{ meta.last_page }}</span>
|
|
698
698
|
</div>
|
|
699
699
|
<button
|
|
700
700
|
type="button"
|
|
701
|
-
class="size-8 flex items-center justify-center rounded-
|
|
701
|
+
class="size-8 flex items-center justify-center rounded-control text-foreground hover:bg-muted-hover disabled:opacity-30"
|
|
702
702
|
:disabled="page >= meta.last_page"
|
|
703
703
|
@click="goToNextPage"
|
|
704
704
|
>
|
|
@@ -180,76 +180,101 @@ const clear = () => {
|
|
|
180
180
|
<template>
|
|
181
181
|
<div class="relative w-full">
|
|
182
182
|
<div class="relative group">
|
|
183
|
-
<div class="absolute inset-y-0 start-0 flex items-center pointer-events-none z-20 ps-3.5">
|
|
184
|
-
<IconCalendar class="size-4 text-gray-400 group-focus-within:text-blue-500 transition-colors" />
|
|
185
|
-
</div>
|
|
186
|
-
|
|
187
183
|
<input
|
|
188
184
|
ref="inputEl"
|
|
189
185
|
type="text"
|
|
190
186
|
:value="internalValue"
|
|
191
187
|
:disabled="disabled"
|
|
192
188
|
:placeholder="placeholder"
|
|
193
|
-
class="
|
|
189
|
+
class="innertia-field pe-10 cursor-pointer"
|
|
194
190
|
readonly
|
|
195
191
|
/>
|
|
196
192
|
|
|
197
|
-
<!-- Clear button -->
|
|
193
|
+
<!-- Clear button (cuando hay valor) o ícono calendario (cuando está vacío) — ambos a la derecha -->
|
|
198
194
|
<button
|
|
199
195
|
v-if="clearable && internalValue && !disabled"
|
|
200
196
|
@click.stop="clear"
|
|
201
197
|
type="button"
|
|
202
|
-
class="absolute inset-y-0 end-0 flex items-center z-20 pe-3 text-
|
|
198
|
+
class="absolute inset-y-0 end-0 flex items-center z-20 pe-3 text-muted-foreground hover:text-destructive transition-colors focus:outline-none"
|
|
203
199
|
>
|
|
204
200
|
<IconX class="size-4" />
|
|
205
201
|
</button>
|
|
202
|
+
<div
|
|
203
|
+
v-else
|
|
204
|
+
class="absolute inset-y-0 end-0 flex items-center pointer-events-none z-20 pe-3"
|
|
205
|
+
>
|
|
206
|
+
<IconCalendar class="size-4 text-muted-foreground-2 group-focus-within:text-primary transition-colors" />
|
|
207
|
+
</div>
|
|
206
208
|
</div>
|
|
207
209
|
</div>
|
|
208
210
|
</template>
|
|
209
211
|
|
|
210
212
|
<style>
|
|
211
213
|
/*
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
[data-vc-theme=light]
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
214
|
+
* Vanilla Calendar v3 compila Tailwind directo en sus themes. Overridemos
|
|
215
|
+
* solo los acentos del brand color para que sigan los tokens del DS.
|
|
216
|
+
*
|
|
217
|
+
* Light y dark usan las mismas variables — `--color-primary` ya cambia
|
|
218
|
+
* automáticamente entre claro/oscuro a través de la cascada del tema.
|
|
219
|
+
*
|
|
220
|
+
* Las clases de selected / today / hover aplican tanto en
|
|
221
|
+
* `[data-vc-theme=light]` como `[data-vc-theme=dark]`.
|
|
222
|
+
*/
|
|
223
|
+
|
|
224
|
+
/* Día seleccionado / mes seleccionado / año seleccionado */
|
|
225
|
+
.vc-months__month[data-vc-months-month-selected],
|
|
226
|
+
.vc-years__year[data-vc-years-year-selected],
|
|
227
|
+
.vc-date[data-vc-date-selected=middle][data-vc-date-selected] .vc-date__btn,
|
|
228
|
+
.vc-date[data-vc-date-selected] .vc-date__btn {
|
|
229
|
+
background-color: var(--color-primary) !important;
|
|
230
|
+
color: var(--color-primary-foreground) !important;
|
|
231
|
+
border-radius: var(--radius-control) !important;
|
|
223
232
|
}
|
|
224
233
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
background-color:
|
|
234
|
+
.vc-months__month[data-vc-months-month-selected]:hover,
|
|
235
|
+
.vc-years__year[data-vc-years-year-selected]:hover,
|
|
236
|
+
.vc-date[data-vc-date-selected=middle][data-vc-date-selected] .vc-date__btn:hover,
|
|
237
|
+
.vc-date[data-vc-date-selected] .vc-date__btn:hover {
|
|
238
|
+
background-color: var(--color-primary-hover) !important;
|
|
230
239
|
}
|
|
231
240
|
|
|
232
|
-
|
|
233
|
-
|
|
241
|
+
/* Hoy (sin seleccionar): color del texto en brand */
|
|
242
|
+
.vc-date[data-vc-date-today] .vc-date__btn {
|
|
243
|
+
color: var(--color-primary) !important;
|
|
244
|
+
font-weight: 600;
|
|
234
245
|
}
|
|
235
246
|
|
|
236
|
-
/*
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
247
|
+
/* Hover de cualquier día (no seleccionado) */
|
|
248
|
+
.vc-date:not([data-vc-date-selected]) .vc-date__btn:hover,
|
|
249
|
+
.vc-months__month:not([data-vc-months-month-selected]):hover,
|
|
250
|
+
.vc-years__year:not([data-vc-years-year-selected]):hover {
|
|
251
|
+
background-color: var(--color-muted-hover) !important;
|
|
252
|
+
border-radius: var(--radius-control) !important;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* Días del mes anterior / siguiente — más opacos */
|
|
256
|
+
.vc-date[data-vc-date-month=prev] .vc-date__btn,
|
|
257
|
+
.vc-date[data-vc-date-month=next] .vc-date__btn {
|
|
258
|
+
color: var(--color-muted-foreground-2) !important;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/* Botones de navegación (mes anterior / siguiente) */
|
|
262
|
+
.vc-arrow {
|
|
263
|
+
color: var(--color-foreground) !important;
|
|
264
|
+
}
|
|
265
|
+
.vc-arrow:hover {
|
|
266
|
+
background-color: var(--color-muted-hover) !important;
|
|
267
|
+
border-radius: var(--radius-control) !important;
|
|
243
268
|
}
|
|
244
269
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
background-color: #2563eb !important; /* blue-600 */
|
|
270
|
+
/* Header del mes/año en el centro */
|
|
271
|
+
.vc-month,
|
|
272
|
+
.vc-year {
|
|
273
|
+
color: var(--color-foreground) !important;
|
|
250
274
|
}
|
|
251
275
|
|
|
252
|
-
|
|
253
|
-
|
|
276
|
+
/* Nombres de los días (Lun, Mar, …) */
|
|
277
|
+
.vc-week__day {
|
|
278
|
+
color: var(--color-muted-foreground) !important;
|
|
254
279
|
}
|
|
255
280
|
</style>
|
|
@@ -10,6 +10,7 @@ const props = defineProps<{
|
|
|
10
10
|
label?: string
|
|
11
11
|
hint?: string
|
|
12
12
|
iconLeft?: object | Function | null
|
|
13
|
+
iconRight?: object | Function | null
|
|
13
14
|
autocomplete?: string
|
|
14
15
|
size?: 'sm' | 'md'
|
|
15
16
|
}>()
|
|
@@ -24,7 +25,7 @@ const inputType = computed(() => {
|
|
|
24
25
|
})
|
|
25
26
|
|
|
26
27
|
const baseClasses = computed(() =>
|
|
27
|
-
|
|
28
|
+
`innertia-field${props.size === 'sm' ? ' innertia-field-sm' : ''}`
|
|
28
29
|
)
|
|
29
30
|
</script>
|
|
30
31
|
|
|
@@ -49,11 +50,16 @@ const baseClasses = computed(() =>
|
|
|
49
50
|
:class="[
|
|
50
51
|
baseClasses,
|
|
51
52
|
iconLeft ? 'ps-9' : '',
|
|
52
|
-
type === 'password' ? 'pe-10' : '',
|
|
53
|
+
(iconRight || type === 'password') ? 'pe-10' : '',
|
|
53
54
|
error ? '!border-red-400 dark:!border-red-500' : '',
|
|
54
55
|
]"
|
|
55
56
|
/>
|
|
56
57
|
|
|
58
|
+
<!-- Ícono derecho (no se renderiza si type=password — ahí va el toggle) -->
|
|
59
|
+
<div v-if="iconRight && type !== 'password'" class="absolute inset-y-0 end-0 flex items-center pe-3 pointer-events-none text-muted-foreground">
|
|
60
|
+
<component :is="iconRight" class="size-4" />
|
|
61
|
+
</div>
|
|
62
|
+
|
|
57
63
|
<!-- Toggle contraseña -->
|
|
58
64
|
<button
|
|
59
65
|
v-if="type === 'password'"
|