@innertia-solutions/nuxt-theme-spark 0.1.72 → 0.1.74
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/Admin/Base.vue +62 -50
- package/components/Admin/Header.vue +32 -0
- package/components/App/PageLoadingSpinner.vue +1 -1
- package/components/Layout/Admin.vue +2 -51
- package/components/Table/Standard.vue +90 -117
- package/components/Table.vue +3 -5
- package/components/TableExportable.vue +127 -147
- package/package.json +1 -1
- package/components/App/DevEnvironmentBar.vue +0 -43
- package/public/favicon.png +0 -0
- package/public/icon.png +0 -0
- package/public/isologo-dark.png +0 -0
- package/public/isologo-light.png +0 -0
|
@@ -1,61 +1,73 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
}
|
|
2
|
+
const isOpen = ref(false)
|
|
3
|
+
const open = () => { isOpen.value = true }
|
|
4
|
+
const close = () => { isOpen.value = false }
|
|
5
|
+
|
|
6
|
+
provide('vantage:sidebar', { isOpen, open, close })
|
|
5
7
|
</script>
|
|
6
8
|
|
|
7
9
|
<template>
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
10
|
+
<div class="bg-slate-50 dark:bg-slate-900 min-h-screen">
|
|
11
|
+
|
|
12
|
+
<!-- Backdrop mobile -->
|
|
13
|
+
<Transition enter-from-class="opacity-0" enter-active-class="transition-opacity duration-300"
|
|
14
|
+
leave-to-class="opacity-0" leave-active-class="transition-opacity duration-300">
|
|
15
|
+
<div v-if="isOpen" class="lg:hidden fixed inset-0 z-50 bg-black/40 backdrop-blur-sm" @click="close" />
|
|
16
|
+
</Transition>
|
|
17
|
+
|
|
18
|
+
<!-- Sidebar -->
|
|
19
|
+
<aside
|
|
20
|
+
tabindex="-1"
|
|
21
|
+
aria-label="Sidebar"
|
|
22
|
+
:class="[
|
|
23
|
+
'fixed inset-y-0 start-0 z-60 w-65 h-full',
|
|
24
|
+
'bg-white dark:bg-slate-800 border-e border-slate-200 dark:border-slate-700',
|
|
25
|
+
'transition-transform duration-300',
|
|
26
|
+
'lg:translate-x-0',
|
|
27
|
+
isOpen ? 'translate-x-0' : 'max-lg:-translate-x-full',
|
|
28
|
+
]"
|
|
29
|
+
>
|
|
30
|
+
<div class="flex flex-col h-full pt-3 lg:pt-6">
|
|
31
|
+
|
|
32
|
+
<!-- Logo + close mobile -->
|
|
33
|
+
<header class="h-11.5 ps-2 pe-2 lg:ps-5 flex items-center gap-x-1 shrink-0">
|
|
34
|
+
<slot name="logo" />
|
|
35
|
+
<div class="lg:hidden ms-auto">
|
|
36
|
+
<button type="button"
|
|
37
|
+
class="w-6 h-7 inline-flex justify-center items-center rounded-md border border-slate-200 bg-white text-slate-500 hover:bg-slate-50 dark:bg-slate-800 dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-700"
|
|
38
|
+
@click="close">
|
|
39
|
+
<svg class="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
|
|
40
|
+
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
41
|
+
<polyline points="7 8 3 12 7 16" />
|
|
42
|
+
<line x1="21" x2="11" y1="12" y2="12" />
|
|
43
|
+
<line x1="21" x2="11" y1="6" y2="6" />
|
|
44
|
+
<line x1="21" x2="11" y1="18" y2="18" />
|
|
45
|
+
</svg>
|
|
46
|
+
</button>
|
|
47
|
+
</div>
|
|
48
|
+
</header>
|
|
49
|
+
|
|
50
|
+
<!-- Menu scrollable -->
|
|
51
|
+
<div class="flex-1 min-h-0 mt-1.5 overflow-y-auto
|
|
52
|
+
[&::-webkit-scrollbar]:w-2
|
|
53
|
+
[&::-webkit-scrollbar-thumb]:rounded-full
|
|
54
|
+
[&::-webkit-scrollbar-track]:bg-slate-100
|
|
55
|
+
[&::-webkit-scrollbar-thumb]:bg-slate-300
|
|
56
|
+
dark:[&::-webkit-scrollbar-track]:bg-slate-700
|
|
57
|
+
dark:[&::-webkit-scrollbar-thumb]:bg-slate-500">
|
|
58
|
+
<slot name="menu" />
|
|
39
59
|
</div>
|
|
40
|
-
</header>
|
|
41
60
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
61
|
+
<!-- User footer -->
|
|
62
|
+
<div class="shrink-0 border-t border-slate-200 dark:border-slate-700 p-5">
|
|
63
|
+
<slot name="user-footer" />
|
|
64
|
+
</div>
|
|
46
65
|
|
|
47
|
-
<!-- Nav content - scrollable -->
|
|
48
|
-
<div class="flex-1 min-h-0 mt-1.5 overflow-y-auto [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-track]:bg-scrollbar-track [&::-webkit-scrollbar-thumb]:bg-scrollbar-thumb">
|
|
49
|
-
<slot name="menu" />
|
|
50
66
|
</div>
|
|
67
|
+
</aside>
|
|
51
68
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<slot name="user-footer" />
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
57
|
-
</aside>
|
|
69
|
+
<!-- Main content -->
|
|
70
|
+
<slot />
|
|
58
71
|
|
|
59
|
-
|
|
60
|
-
<slot />
|
|
72
|
+
</div>
|
|
61
73
|
</template>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineProps<{
|
|
3
|
+
title?: string
|
|
4
|
+
}>()
|
|
5
|
+
|
|
6
|
+
const sidebar = inject('vantage:sidebar', null) as any
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<header class="sticky top-0 z-40 bg-white dark:bg-slate-800 border-b border-slate-200 dark:border-slate-700 px-4 lg:px-6 h-14 flex items-center gap-x-3">
|
|
11
|
+
<!-- Hamburger mobile -->
|
|
12
|
+
<button type="button"
|
|
13
|
+
class="lg:hidden size-8 flex items-center justify-center text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200"
|
|
14
|
+
@click="sidebar?.open()">
|
|
15
|
+
<svg class="size-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
16
|
+
<line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="18" x2="21" y2="18" />
|
|
17
|
+
</svg>
|
|
18
|
+
</button>
|
|
19
|
+
|
|
20
|
+
<!-- Left slot -->
|
|
21
|
+
<div class="flex-1 flex items-center gap-x-3">
|
|
22
|
+
<slot name="left">
|
|
23
|
+
<span v-if="title" class="text-sm font-medium text-slate-700 dark:text-slate-300">{{ title }}</span>
|
|
24
|
+
</slot>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<!-- Right slot -->
|
|
28
|
+
<div class="flex items-center gap-x-2">
|
|
29
|
+
<slot name="right" />
|
|
30
|
+
</div>
|
|
31
|
+
</header>
|
|
32
|
+
</template>
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
const props = defineProps({
|
|
3
|
-
floating: { type: Boolean, default: false },
|
|
4
|
-
})
|
|
5
|
-
|
|
6
2
|
const showAnimation = ref(false)
|
|
7
3
|
onMounted(() => {
|
|
8
4
|
const seen = sessionStorage.getItem('auth-entered')
|
|
@@ -15,57 +11,12 @@ onMounted(() => {
|
|
|
15
11
|
|
|
16
12
|
<template>
|
|
17
13
|
<div :class="{ 'animate-entrance': showAnimation }">
|
|
18
|
-
|
|
19
|
-
<header class="lg:ms-65 fixed top-0 inset-x-0 flex flex-wrap md:justify-start md:flex-nowrap z-50 bg-navbar border-b border-navbar-line">
|
|
20
|
-
<div class="flex justify-between xl:grid xl:grid-cols-3 basis-full items-center w-full py-2.5 px-2 sm:px-5">
|
|
21
|
-
|
|
22
|
-
<!-- Left col: mobile toggle + desktop search -->
|
|
23
|
-
<div class="xl:col-span-1 flex items-center md:gap-x-3">
|
|
24
|
-
<!-- Mobile sidebar toggle -->
|
|
25
|
-
<div class="lg:hidden">
|
|
26
|
-
<button
|
|
27
|
-
type="button"
|
|
28
|
-
class="w-7 h-9.5 inline-flex justify-center items-center gap-x-2 text-sm font-medium rounded-lg bg-layer border border-layer-line text-layer-foreground shadow-2xs hover:bg-layer-hover disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden focus:bg-layer-focus"
|
|
29
|
-
aria-haspopup="dialog"
|
|
30
|
-
aria-expanded="false"
|
|
31
|
-
aria-controls="hs-pro-sidebar"
|
|
32
|
-
aria-label="Toggle navigation"
|
|
33
|
-
data-hs-overlay="#hs-pro-sidebar"
|
|
34
|
-
>
|
|
35
|
-
<svg class="shrink-0 size-4" 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">
|
|
36
|
-
<path d="M17 8L21 12L17 16M3 12H13M3 6H13M3 18H13" />
|
|
37
|
-
</svg>
|
|
38
|
-
</button>
|
|
39
|
-
</div>
|
|
40
|
-
|
|
41
|
-
<!-- Desktop search -->
|
|
42
|
-
<div class="hidden lg:block min-w-80 xl:w-full">
|
|
43
|
-
<slot name="header-left" />
|
|
44
|
-
</div>
|
|
45
|
-
</div>
|
|
46
|
-
|
|
47
|
-
<!-- Right col: actions -->
|
|
48
|
-
<div class="xl:col-span-2 flex justify-end items-center gap-x-2">
|
|
49
|
-
<div class="flex items-center">
|
|
50
|
-
<!-- Mobile search icon -->
|
|
51
|
-
<div class="lg:hidden">
|
|
52
|
-
<slot name="header-search-mobile" />
|
|
53
|
-
</div>
|
|
54
|
-
|
|
55
|
-
<slot name="header-right" />
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
|
|
59
|
-
</div>
|
|
60
|
-
</header>
|
|
61
|
-
|
|
62
|
-
<!-- Sidebar + main content -->
|
|
63
|
-
<AdminBase :floating="floating">
|
|
14
|
+
<AdminBase>
|
|
64
15
|
<template #logo><slot name="logo" /></template>
|
|
65
16
|
<template #menu><slot name="menu" /></template>
|
|
66
17
|
<template #user-footer><slot name="user-footer" /></template>
|
|
67
18
|
|
|
68
|
-
<div
|
|
19
|
+
<div class="lg:ps-65">
|
|
69
20
|
<slot />
|
|
70
21
|
</div>
|
|
71
22
|
</AdminBase>
|
|
@@ -137,138 +137,111 @@ defineExpose({ getSelectedRows, reload, clearCache, exportTable, tableRef })
|
|
|
137
137
|
|
|
138
138
|
<template>
|
|
139
139
|
<div class="relative" ref="containerRef">
|
|
140
|
-
<div :class="previewRow && previewEnabled ? 'flex items-stretch gap-3' : ''">
|
|
141
140
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
class="relative bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-2xl shadow-sm overflow-hidden"
|
|
145
|
-
:style="previewRow && previewEnabled ? { width: currentRatio + '%', minWidth: 0, flexShrink: 0 } : {}"
|
|
146
|
-
>
|
|
147
|
-
<!-- Toolbar -->
|
|
148
|
-
<div class="flex flex-wrap items-center gap-3 px-4 py-3 border-b border-slate-200 dark:border-slate-700">
|
|
149
|
-
<!-- Search -->
|
|
150
|
-
<div v-if="showSearch" class="flex-1 min-w-48">
|
|
151
|
-
<Forms.Input
|
|
152
|
-
v-model="search"
|
|
153
|
-
type="search"
|
|
154
|
-
:placeholder="searchPlaceholder"
|
|
155
|
-
:icon-left="IconSearch"
|
|
156
|
-
/>
|
|
157
|
-
</div>
|
|
141
|
+
<!-- Card único -->
|
|
142
|
+
<div class="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-2xl shadow-sm overflow-hidden">
|
|
158
143
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
@click="showFilterPanel = !showFilterPanel"
|
|
164
|
-
:class="[
|
|
165
|
-
'py-1.5 px-3 inline-flex items-center gap-2 text-sm font-medium rounded-lg border transition-colors',
|
|
166
|
-
showFilterPanel || activeFilterCount > 0
|
|
167
|
-
? 'border-blue-500 bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:border-blue-500 dark:text-blue-300'
|
|
168
|
-
: 'border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700'
|
|
169
|
-
]"
|
|
170
|
-
>
|
|
171
|
-
<IconAdjustmentsHorizontal class="size-4" stroke="1.5" />
|
|
172
|
-
Filtros
|
|
173
|
-
<span
|
|
174
|
-
v-if="activeFilterCount > 0"
|
|
175
|
-
class="inline-flex items-center justify-center size-5 rounded-full bg-blue-600 text-white text-xs font-bold"
|
|
176
|
-
>{{ activeFilterCount }}</span>
|
|
177
|
-
</button>
|
|
178
|
-
|
|
179
|
-
<slot name="actions" />
|
|
180
|
-
|
|
181
|
-
<!-- Column visibility toggle -->
|
|
182
|
-
<button
|
|
183
|
-
type="button"
|
|
184
|
-
@click="showColumnPanel = !showColumnPanel"
|
|
185
|
-
:class="[
|
|
186
|
-
'py-1.5 px-3 inline-flex items-center gap-2 text-sm font-medium rounded-lg border transition-colors',
|
|
187
|
-
showColumnPanel
|
|
188
|
-
? 'border-blue-500 bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:border-blue-500 dark:text-blue-300'
|
|
189
|
-
: 'border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700'
|
|
190
|
-
]"
|
|
191
|
-
>
|
|
192
|
-
<IconLayoutColumns class="size-4" />
|
|
193
|
-
Columnas
|
|
194
|
-
</button>
|
|
195
|
-
|
|
196
|
-
<!-- Export -->
|
|
197
|
-
<TableExportable
|
|
198
|
-
v-if="showExport"
|
|
199
|
-
:table-ref="tableRef"
|
|
200
|
-
:name="name"
|
|
201
|
-
:columns="columns"
|
|
202
|
-
/>
|
|
144
|
+
<!-- Toolbar -->
|
|
145
|
+
<div class="flex flex-wrap items-center gap-3 px-4 py-3 border-b border-slate-200 dark:border-slate-700">
|
|
146
|
+
<div v-if="showSearch" class="flex-1 min-w-48">
|
|
147
|
+
<Forms.Input v-model="search" type="search" :placeholder="searchPlaceholder" :icon-left="IconSearch" />
|
|
203
148
|
</div>
|
|
204
149
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
150
|
+
<button
|
|
151
|
+
v-if="showFilters && hasFilterableColumns"
|
|
152
|
+
type="button"
|
|
153
|
+
@click="showFilterPanel = !showFilterPanel"
|
|
154
|
+
:class="[
|
|
155
|
+
'py-1.5 px-3 inline-flex items-center gap-2 text-sm font-medium rounded-lg border transition-colors',
|
|
156
|
+
showFilterPanel || activeFilterCount > 0
|
|
157
|
+
? 'border-blue-500 bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:border-blue-500 dark:text-blue-300'
|
|
158
|
+
: 'border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700'
|
|
159
|
+
]"
|
|
213
160
|
>
|
|
214
|
-
<
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
:checkable="checkable"
|
|
231
|
-
:cached="cached"
|
|
232
|
-
:show-reload-button="showReloadButton"
|
|
233
|
-
:click-row-to-open="clickRowToOpen"
|
|
234
|
-
:preview-row-id="previewRow?.id ?? null"
|
|
235
|
-
:preview-mode="!!previewEnabled"
|
|
236
|
-
@row-click="handleRowClick"
|
|
237
|
-
@loaded="emit('loaded', $event)"
|
|
161
|
+
<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>
|
|
164
|
+
</button>
|
|
165
|
+
|
|
166
|
+
<slot name="actions" />
|
|
167
|
+
|
|
168
|
+
<button
|
|
169
|
+
type="button"
|
|
170
|
+
@click="showColumnPanel = !showColumnPanel"
|
|
171
|
+
:class="[
|
|
172
|
+
'py-1.5 px-3 inline-flex items-center gap-2 text-sm font-medium rounded-lg border transition-colors',
|
|
173
|
+
showColumnPanel
|
|
174
|
+
? 'border-blue-500 bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:border-blue-500 dark:text-blue-300'
|
|
175
|
+
: 'border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700'
|
|
176
|
+
]"
|
|
238
177
|
>
|
|
239
|
-
<
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
</Table>
|
|
243
|
-
</div>
|
|
178
|
+
<IconLayoutColumns class="size-4" />
|
|
179
|
+
Columnas
|
|
180
|
+
</button>
|
|
244
181
|
|
|
245
|
-
|
|
246
|
-
<div
|
|
247
|
-
v-if="previewRow && previewEnabled"
|
|
248
|
-
class="w-3 flex items-center justify-center cursor-col-resize shrink-0 group"
|
|
249
|
-
@mousedown="startResize"
|
|
250
|
-
>
|
|
251
|
-
<div class="w-px h-12 bg-slate-200 dark:bg-slate-600 rounded-full group-hover:bg-indigo-400 dark:group-hover:bg-indigo-500 transition-colors" />
|
|
182
|
+
<TableExportable v-if="showExport" :table-ref="tableRef" :name="name" :columns="columns" />
|
|
252
183
|
</div>
|
|
253
184
|
|
|
254
|
-
<!--
|
|
185
|
+
<!-- Filter panel -->
|
|
255
186
|
<Transition
|
|
256
|
-
enter-active-class="transition ease-out duration-
|
|
257
|
-
enter-from-class="opacity-0 translate-
|
|
258
|
-
enter-to-class="opacity-100 translate-
|
|
259
|
-
leave-active-class="transition ease-in duration-
|
|
260
|
-
leave-from-class="opacity-100 translate-
|
|
261
|
-
leave-to-class="opacity-0 translate-
|
|
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"
|
|
262
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
|
+
<!-- Contenido: tabla + preview en flex -->
|
|
200
|
+
<div :class="previewRow && previewEnabled ? 'flex items-stretch' : ''">
|
|
201
|
+
|
|
202
|
+
<!-- Tabla -->
|
|
263
203
|
<div
|
|
264
|
-
|
|
265
|
-
class="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-2xl shadow-sm overflow-hidden flex flex-col"
|
|
266
|
-
:style="{ width: (100 - currentRatio) + '%', minWidth: 0, flexShrink: 0 }"
|
|
204
|
+
:style="previewRow && previewEnabled ? { width: currentRatio + '%', flexShrink: 0, minWidth: 0 } : {}"
|
|
267
205
|
>
|
|
268
|
-
<
|
|
206
|
+
<Table
|
|
207
|
+
ref="tableRef"
|
|
208
|
+
:endpoint="endpoint"
|
|
209
|
+
:columns="columns"
|
|
210
|
+
:name="name"
|
|
211
|
+
:params="mergedParams"
|
|
212
|
+
:search="search"
|
|
213
|
+
:checkable="checkable"
|
|
214
|
+
:cached="cached"
|
|
215
|
+
:show-reload-button="showReloadButton"
|
|
216
|
+
:click-row-to-open="clickRowToOpen"
|
|
217
|
+
:preview-row-id="previewRow?.id ?? null"
|
|
218
|
+
:preview-mode="!!previewEnabled"
|
|
219
|
+
@row-click="handleRowClick"
|
|
220
|
+
@loaded="emit('loaded', $event)"
|
|
221
|
+
>
|
|
222
|
+
<template v-for="(_, name) in $slots" #[name]="slotProps">
|
|
223
|
+
<slot :name="name" v-bind="slotProps ?? {}" />
|
|
224
|
+
</template>
|
|
225
|
+
</Table>
|
|
269
226
|
</div>
|
|
270
|
-
</Transition>
|
|
271
227
|
|
|
228
|
+
<!-- Divider + preview -->
|
|
229
|
+
<template v-if="previewRow && previewEnabled">
|
|
230
|
+
<!-- Resize handle -->
|
|
231
|
+
<div
|
|
232
|
+
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"
|
|
233
|
+
@mousedown="startResize"
|
|
234
|
+
/>
|
|
235
|
+
<!-- Preview -->
|
|
236
|
+
<div
|
|
237
|
+
class="flex flex-col overflow-y-auto border-l border-slate-200 dark:border-slate-700"
|
|
238
|
+
:style="{ width: (100 - currentRatio) + '%', flexShrink: 0, minWidth: 0 }"
|
|
239
|
+
>
|
|
240
|
+
<slot name="preview" :row="previewRow" :close="closePreview" />
|
|
241
|
+
</div>
|
|
242
|
+
</template>
|
|
243
|
+
|
|
244
|
+
</div>
|
|
272
245
|
</div>
|
|
273
246
|
|
|
274
247
|
<!-- Column panel — outside overflow-hidden so never clipped -->
|
package/components/Table.vue
CHANGED
|
@@ -318,13 +318,11 @@ const measureText = (text, font) => {
|
|
|
318
318
|
|
|
319
319
|
const autoSizeColumn = (header) => {
|
|
320
320
|
const colId = header.column.id
|
|
321
|
-
const pad = 32
|
|
321
|
+
const pad = 32
|
|
322
322
|
|
|
323
|
-
// Measure header label
|
|
324
323
|
const label = header.column.columnDef.meta?.label ?? header.id
|
|
325
|
-
let max = measureText(label, '500 12px ui-sans-serif,system-ui,sans-serif') + pad + 20
|
|
324
|
+
let max = measureText(label, '500 12px ui-sans-serif,system-ui,sans-serif') + pad + 20
|
|
326
325
|
|
|
327
|
-
// Measure all visible data cells
|
|
328
326
|
if (tableBodyRef.value) {
|
|
329
327
|
tableBodyRef.value.querySelectorAll(`td[data-col-id="${colId}"]`).forEach(td => {
|
|
330
328
|
const w = measureText(td.textContent?.trim(), '14px ui-sans-serif,system-ui,sans-serif') + pad
|
|
@@ -332,7 +330,7 @@ const autoSizeColumn = (header) => {
|
|
|
332
330
|
})
|
|
333
331
|
}
|
|
334
332
|
|
|
335
|
-
|
|
333
|
+
table.setColumnSizing(prev => ({ ...prev, [colId]: Math.ceil(max) }))
|
|
336
334
|
}
|
|
337
335
|
|
|
338
336
|
const onHeaderDragStart = (colId) => { draggedHeaderId = colId }
|
|
@@ -7,33 +7,29 @@ import {
|
|
|
7
7
|
IconDownload,
|
|
8
8
|
} from '@tabler/icons-vue'
|
|
9
9
|
|
|
10
|
+
// Modal with format selector, pre-filled filename, columns checkboxes
|
|
10
11
|
const props = defineProps({
|
|
11
12
|
tableRef: { type: Object, default: null },
|
|
12
13
|
name: { type: String, default: 'export' },
|
|
13
|
-
columns: { type: Array, default: () => [] },
|
|
14
|
+
columns: { type: Array, default: () => [] }, // [{ key, label }]
|
|
14
15
|
})
|
|
15
16
|
|
|
16
17
|
const isOpen = ref(false)
|
|
17
18
|
const format = ref('xlsx')
|
|
18
19
|
const filename = ref(props.name)
|
|
19
20
|
const selectedColumns = ref([])
|
|
20
|
-
const btnRef = ref(null)
|
|
21
|
-
const panelRef = ref(null)
|
|
22
|
-
const panelStyle = ref({})
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
watch(exportableColumns, (cols) => {
|
|
22
|
+
watch(() => props.columns, (cols) => {
|
|
27
23
|
selectedColumns.value = cols.map(c => c.key)
|
|
28
24
|
}, { immediate: true })
|
|
29
25
|
|
|
30
26
|
watch(() => props.name, (v) => { filename.value = v })
|
|
31
27
|
|
|
32
28
|
const formats = [
|
|
33
|
-
{ value: 'xlsx', label: 'Excel', icon:
|
|
34
|
-
{ value: 'csv',
|
|
35
|
-
{ value: 'pdf',
|
|
36
|
-
{ value: 'json', label: 'JSON',
|
|
29
|
+
{ value: 'xlsx', label: 'Excel', icon: 'xlsx' },
|
|
30
|
+
{ value: 'csv', label: 'CSV', icon: 'csv' },
|
|
31
|
+
{ value: 'pdf', label: 'PDF', icon: 'pdf' },
|
|
32
|
+
{ value: 'json', label: 'JSON', icon: 'json' },
|
|
37
33
|
]
|
|
38
34
|
|
|
39
35
|
const toggleColumn = (key) => {
|
|
@@ -43,181 +39,165 @@ const toggleColumn = (key) => {
|
|
|
43
39
|
}
|
|
44
40
|
|
|
45
41
|
const toggleAll = () => {
|
|
46
|
-
if (selectedColumns.value.length ===
|
|
42
|
+
if (selectedColumns.value.length === props.columns.length)
|
|
47
43
|
selectedColumns.value = []
|
|
48
44
|
else
|
|
49
|
-
selectedColumns.value =
|
|
45
|
+
selectedColumns.value = props.columns.map(c => c.key)
|
|
50
46
|
}
|
|
51
47
|
|
|
52
|
-
const allSelected = computed(() => selectedColumns.value.length ===
|
|
53
|
-
|
|
54
|
-
const open = () => {
|
|
55
|
-
if (!btnRef.value) return
|
|
56
|
-
const rect = btnRef.value.getBoundingClientRect()
|
|
57
|
-
panelStyle.value = {
|
|
58
|
-
position: 'fixed',
|
|
59
|
-
top: (rect.bottom + 6) + 'px',
|
|
60
|
-
right: (window.innerWidth - rect.right) + 'px',
|
|
61
|
-
}
|
|
62
|
-
isOpen.value = true
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const toggle = () => isOpen.value ? isOpen.value = false : open()
|
|
66
|
-
|
|
67
|
-
const onOutsideClick = (e) => {
|
|
68
|
-
if (
|
|
69
|
-
panelRef.value && !panelRef.value.contains(e.target) &&
|
|
70
|
-
btnRef.value && !btnRef.value.contains(e.target)
|
|
71
|
-
) {
|
|
72
|
-
isOpen.value = false
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
watch(isOpen, (v) => {
|
|
77
|
-
if (v) document.addEventListener('mousedown', onOutsideClick)
|
|
78
|
-
else document.removeEventListener('mousedown', onOutsideClick)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
const selectedRows = computed(() => {
|
|
82
|
-
if (!props.tableRef) return { meta: { all: false }, rows: [] }
|
|
83
|
-
return props.tableRef.getSelectedRows() ?? { meta: { all: false }, rows: [] }
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
const hasSelection = computed(() => selectedRows.value.rows.length > 0)
|
|
48
|
+
const allSelected = computed(() => selectedColumns.value.length === props.columns.length)
|
|
49
|
+
const indeterminate = computed(() => selectedColumns.value.length > 0 && !allSelected.value)
|
|
87
50
|
|
|
88
51
|
const doExport = () => {
|
|
89
|
-
if (
|
|
90
|
-
if (hasSelection.value) {
|
|
91
|
-
const ids = selectedRows.value.rows.map(r => r.id)
|
|
92
|
-
props.tableRef.exportTable(format.value, false, false, ids)
|
|
93
|
-
} else {
|
|
52
|
+
if (props.tableRef) {
|
|
94
53
|
props.tableRef.exportTable(format.value, true, true)
|
|
95
54
|
}
|
|
96
55
|
isOpen.value = false
|
|
97
56
|
}
|
|
98
57
|
|
|
58
|
+
const open = () => { isOpen.value = true }
|
|
59
|
+
|
|
99
60
|
defineExpose({ open })
|
|
100
61
|
</script>
|
|
101
62
|
|
|
102
63
|
<template>
|
|
103
|
-
<div
|
|
64
|
+
<div>
|
|
104
65
|
<button
|
|
105
|
-
ref="btnRef"
|
|
106
66
|
type="button"
|
|
107
|
-
@click="
|
|
108
|
-
|
|
109
|
-
'py-1.5 px-3 inline-flex items-center gap-2 text-sm font-medium rounded-lg border transition-colors',
|
|
110
|
-
isOpen
|
|
111
|
-
? 'border-blue-500 bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:border-blue-500 dark:text-blue-300'
|
|
112
|
-
: 'border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700'
|
|
113
|
-
]"
|
|
67
|
+
@click="isOpen = true"
|
|
68
|
+
class="py-1.5 sm:py-2 px-2.5 inline-flex items-center gap-x-1.5 text-sm font-medium rounded-lg border border-slate-200 bg-white text-slate-800 shadow-2xs hover:bg-slate-50 disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden dark:bg-slate-800 dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-700"
|
|
114
69
|
>
|
|
115
|
-
<IconDownload class="size-4" stroke="1.5" />
|
|
70
|
+
<IconDownload class="shrink-0 size-4" stroke="1.5" />
|
|
116
71
|
Exportar
|
|
117
72
|
</button>
|
|
118
73
|
|
|
119
|
-
<!--
|
|
74
|
+
<!-- Modal -->
|
|
120
75
|
<Teleport to="body">
|
|
121
76
|
<Transition
|
|
122
|
-
enter-active-class="transition ease-out duration-
|
|
123
|
-
enter-from-class="opacity-0
|
|
124
|
-
enter-to-class="opacity-100
|
|
125
|
-
leave-active-class="transition ease-in duration-
|
|
126
|
-
leave-from-class="opacity-100
|
|
127
|
-
leave-to-class="opacity-0
|
|
77
|
+
enter-active-class="transition ease-out duration-200"
|
|
78
|
+
enter-from-class="opacity-0"
|
|
79
|
+
enter-to-class="opacity-100"
|
|
80
|
+
leave-active-class="transition ease-in duration-150"
|
|
81
|
+
leave-from-class="opacity-100"
|
|
82
|
+
leave-to-class="opacity-0"
|
|
128
83
|
>
|
|
129
84
|
<div
|
|
130
85
|
v-if="isOpen"
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
class="z-50 w-72 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl shadow-2xl"
|
|
86
|
+
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/40 backdrop-blur-sm"
|
|
87
|
+
@click.self="isOpen = false"
|
|
134
88
|
>
|
|
135
|
-
<
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
89
|
+
<Transition
|
|
90
|
+
enter-active-class="transition ease-out duration-200"
|
|
91
|
+
enter-from-class="opacity-0 scale-95"
|
|
92
|
+
enter-to-class="opacity-100 scale-100"
|
|
93
|
+
>
|
|
94
|
+
<div
|
|
95
|
+
v-if="isOpen"
|
|
96
|
+
class="bg-white dark:bg-slate-800 rounded-2xl shadow-2xl w-full max-w-md border border-slate-200 dark:border-slate-700"
|
|
97
|
+
>
|
|
98
|
+
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100 dark:border-slate-700">
|
|
99
|
+
<h3 class="font-semibold text-slate-800 dark:text-slate-100">Exportar tabla</h3>
|
|
140
100
|
<button
|
|
141
|
-
v-for="f in formats"
|
|
142
|
-
:key="f.value"
|
|
143
101
|
type="button"
|
|
144
|
-
@click="
|
|
145
|
-
|
|
146
|
-
'flex flex-col items-center gap-1 py-2 px-1 rounded-lg border text-xs font-medium transition-colors',
|
|
147
|
-
format === f.value
|
|
148
|
-
? 'border-blue-500 bg-blue-50 text-blue-700 dark:bg-blue-900/30 dark:border-blue-500 dark:text-blue-300'
|
|
149
|
-
: 'border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700'
|
|
150
|
-
]"
|
|
102
|
+
@click="isOpen = false"
|
|
103
|
+
class="p-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors"
|
|
151
104
|
>
|
|
152
|
-
<
|
|
153
|
-
|
|
105
|
+
<svg class="size-4 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
106
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
107
|
+
</svg>
|
|
154
108
|
</button>
|
|
155
109
|
</div>
|
|
156
|
-
</div>
|
|
157
110
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
111
|
+
<div class="px-6 py-5 space-y-5">
|
|
112
|
+
<!-- Format selector -->
|
|
113
|
+
<div>
|
|
114
|
+
<p class="text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-2">Formato</p>
|
|
115
|
+
<div class="grid grid-cols-4 gap-2">
|
|
116
|
+
<button
|
|
117
|
+
v-for="f in formats"
|
|
118
|
+
:key="f.value"
|
|
119
|
+
type="button"
|
|
120
|
+
@click="format = f.value"
|
|
121
|
+
:class="[
|
|
122
|
+
'flex flex-col items-center gap-1.5 p-3 rounded-xl border text-sm font-medium transition-colors',
|
|
123
|
+
format === f.value
|
|
124
|
+
? 'border-indigo-500 bg-indigo-50 text-indigo-700 dark:bg-indigo-900/30 dark:border-indigo-500 dark:text-indigo-300'
|
|
125
|
+
: 'border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700'
|
|
126
|
+
]"
|
|
127
|
+
>
|
|
128
|
+
<IconFileTypeXls v-if="f.value === 'xlsx'" class="size-5" stroke="1.5" />
|
|
129
|
+
<IconFileTypeCsv v-else-if="f.value === 'csv'" class="size-5" stroke="1.5" />
|
|
130
|
+
<IconFileTypePdf v-else-if="f.value === 'pdf'" class="size-5" stroke="1.5" />
|
|
131
|
+
<IconCodeDots v-else class="size-5" stroke="1.5" />
|
|
132
|
+
{{ f.label }}
|
|
133
|
+
</button>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<!-- Filename -->
|
|
138
|
+
<div>
|
|
139
|
+
<label class="text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider block mb-2">
|
|
140
|
+
Nombre de archivo
|
|
141
|
+
</label>
|
|
142
|
+
<div class="flex items-center gap-2">
|
|
143
|
+
<input
|
|
144
|
+
v-model="filename"
|
|
145
|
+
type="text"
|
|
146
|
+
class="flex-1 rounded-lg border border-gray-200 dark:border-slate-700 bg-white dark:bg-slate-900 text-slate-900 dark:text-white py-2 px-3 text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
|
147
|
+
/>
|
|
148
|
+
<span class="text-sm text-slate-400">.{{ format }}</span>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<!-- Columns -->
|
|
153
|
+
<div v-if="columns.length > 0">
|
|
154
|
+
<div class="flex items-center justify-between mb-2">
|
|
155
|
+
<p class="text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider">Columnas</p>
|
|
156
|
+
<button
|
|
157
|
+
type="button"
|
|
158
|
+
@click="toggleAll"
|
|
159
|
+
class="text-xs text-indigo-600 dark:text-indigo-400 hover:underline"
|
|
160
|
+
>
|
|
161
|
+
{{ allSelected ? 'Deseleccionar todas' : 'Seleccionar todas' }}
|
|
162
|
+
</button>
|
|
163
|
+
</div>
|
|
164
|
+
<div class="grid grid-cols-2 gap-1.5 max-h-40 overflow-y-auto pr-1">
|
|
165
|
+
<label
|
|
166
|
+
v-for="col in columns"
|
|
167
|
+
:key="col.key"
|
|
168
|
+
class="flex items-center gap-2 py-1.5 px-2 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-700 cursor-pointer"
|
|
169
|
+
>
|
|
170
|
+
<input
|
|
171
|
+
type="checkbox"
|
|
172
|
+
:checked="selectedColumns.includes(col.key)"
|
|
173
|
+
@change="toggleColumn(col.key)"
|
|
174
|
+
class="rounded border-gray-300 dark:bg-slate-700 dark:border-slate-600 text-indigo-600"
|
|
175
|
+
/>
|
|
176
|
+
<span class="text-sm text-slate-700 dark:text-slate-200 truncate">{{ col.label }}</span>
|
|
177
|
+
</label>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
164
180
|
</div>
|
|
165
|
-
</div>
|
|
166
181
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
182
|
+
<div class="flex justify-end gap-2 px-6 py-4 border-t border-slate-100 dark:border-slate-700">
|
|
183
|
+
<button
|
|
184
|
+
type="button"
|
|
185
|
+
@click="isOpen = false"
|
|
186
|
+
class="py-2 px-4 text-sm font-medium rounded-lg border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors"
|
|
187
|
+
>
|
|
188
|
+
Cancelar
|
|
173
189
|
</button>
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
:key="col.key"
|
|
179
|
-
class="flex items-center gap-2 py-1 px-2 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-700 cursor-pointer"
|
|
190
|
+
<button
|
|
191
|
+
type="button"
|
|
192
|
+
@click="doExport"
|
|
193
|
+
class="py-2 px-4 text-sm font-medium rounded-lg bg-indigo-600 text-white hover:bg-indigo-700 transition-colors inline-flex items-center gap-2"
|
|
180
194
|
>
|
|
181
|
-
<
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
@change="toggleColumn(col.key)"
|
|
185
|
-
class="rounded border-gray-300 dark:bg-slate-700 dark:border-slate-600 text-blue-600"
|
|
186
|
-
/>
|
|
187
|
-
<span class="text-xs text-slate-700 dark:text-slate-200 truncate">{{ col.label }}</span>
|
|
188
|
-
</label>
|
|
195
|
+
<IconDownload class="size-4" stroke="1.5" />
|
|
196
|
+
Exportar
|
|
197
|
+
</button>
|
|
189
198
|
</div>
|
|
190
199
|
</div>
|
|
191
|
-
</
|
|
192
|
-
|
|
193
|
-
<!-- Selection info -->
|
|
194
|
-
<div class="px-3 pb-2">
|
|
195
|
-
<p v-if="hasSelection" class="text-xs text-blue-600 dark:text-blue-400 font-medium">
|
|
196
|
-
Se exportarán {{ selectedRows.rows.length }} fila{{ selectedRows.rows.length !== 1 ? 's' : '' }} seleccionada{{ selectedRows.rows.length !== 1 ? 's' : '' }}
|
|
197
|
-
</p>
|
|
198
|
-
<p v-else class="text-xs text-slate-400 dark:text-slate-500">
|
|
199
|
-
Se exportarán todos los registros
|
|
200
|
-
</p>
|
|
201
|
-
</div>
|
|
202
|
-
|
|
203
|
-
<!-- Footer -->
|
|
204
|
-
<div class="flex gap-2 px-3 pb-3">
|
|
205
|
-
<button
|
|
206
|
-
type="button"
|
|
207
|
-
@click="isOpen = false"
|
|
208
|
-
class="flex-1 py-1.5 text-sm font-medium rounded-lg border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors"
|
|
209
|
-
>
|
|
210
|
-
Cancelar
|
|
211
|
-
</button>
|
|
212
|
-
<button
|
|
213
|
-
type="button"
|
|
214
|
-
@click="doExport"
|
|
215
|
-
class="flex-1 py-1.5 text-sm font-medium rounded-lg bg-blue-600 text-white hover:bg-blue-700 transition-colors inline-flex items-center justify-center gap-1.5"
|
|
216
|
-
>
|
|
217
|
-
<IconDownload class="size-4" stroke="1.5" />
|
|
218
|
-
Exportar
|
|
219
|
-
</button>
|
|
220
|
-
</div>
|
|
200
|
+
</Transition>
|
|
221
201
|
</div>
|
|
222
202
|
</Transition>
|
|
223
203
|
</Teleport>
|
package/package.json
CHANGED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
const props = defineProps({ sidebar: { type: Boolean, default: false } })
|
|
3
|
-
|
|
4
|
-
const config = useRuntimeConfig()
|
|
5
|
-
const appEnv = config.public.appEnv
|
|
6
|
-
|
|
7
|
-
const isVisible = computed(() => appEnv && appEnv !== 'production')
|
|
8
|
-
|
|
9
|
-
useHead({
|
|
10
|
-
htmlAttrs: { class: (!props.sidebar && isVisible.value) ? 'has-env-bar' : '' },
|
|
11
|
-
style: [{ children: (!props.sidebar && isVisible.value) ? ':root { --env-bar-height: 1.5rem; }' : ':root { --env-bar-height: 0px; }' }],
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
const envColor = computed(() => {
|
|
15
|
-
const env = appEnv.toLowerCase()
|
|
16
|
-
if (env === 'staging') return props.sidebar ? 'bg-amber-50 text-amber-600 border border-amber-200 dark:bg-amber-500/10 dark:text-amber-400 dark:border-amber-500/30' : 'bg-amber-500 text-white'
|
|
17
|
-
if (env === 'local' || env === 'dev') return props.sidebar ? 'bg-blue-50 text-blue-600 border border-blue-200 dark:bg-blue-500/10 dark:text-blue-400 dark:border-blue-500/30' : 'bg-blue-600 text-white'
|
|
18
|
-
return props.sidebar ? 'bg-red-50 text-red-600 border border-red-200 dark:bg-red-500/10 dark:text-red-400 dark:border-red-500/30' : 'bg-red-600 text-white'
|
|
19
|
-
})
|
|
20
|
-
</script>
|
|
21
|
-
|
|
22
|
-
<template>
|
|
23
|
-
<!-- Sidebar inline variant -->
|
|
24
|
-
<div v-if="isVisible && sidebar"
|
|
25
|
-
:class="['w-full rounded-lg px-3 py-1.5 flex items-center justify-center gap-x-1.5 select-none', envColor]"
|
|
26
|
-
>
|
|
27
|
-
<span class="text-[9px] font-black tracking-widest uppercase">ENTORNO: {{ appEnv }}</span>
|
|
28
|
-
</div>
|
|
29
|
-
|
|
30
|
-
<!-- Full-width fixed bar -->
|
|
31
|
-
<div v-else-if="isVisible && !sidebar"
|
|
32
|
-
class="fixed bottom-0 left-0 right-0 z-[100] h-6 overflow-hidden select-none pointer-events-none"
|
|
33
|
-
>
|
|
34
|
-
<div
|
|
35
|
-
class="flex items-center justify-center w-full h-full text-[10px] font-bold tracking-widest uppercase opacity-90 shadow-lg border-t border-white/10"
|
|
36
|
-
:class="envColor"
|
|
37
|
-
>
|
|
38
|
-
<span class="mr-2">━━━━━</span>
|
|
39
|
-
ENTORNO: {{ appEnv }}
|
|
40
|
-
<span class="ml-2">━━━━━</span>
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
</template>
|
package/public/favicon.png
DELETED
|
Binary file
|
package/public/icon.png
DELETED
|
Binary file
|
package/public/isologo-dark.png
DELETED
|
Binary file
|
package/public/isologo-light.png
DELETED
|
Binary file
|