@innertia-solutions/nuxt-theme-spark 0.1.113 → 0.1.115

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.
@@ -49,7 +49,7 @@ const userInitial = computed(() =>
49
49
  ]"
50
50
  >
51
51
  <!-- Logo + mobile close -->
52
- <header class="flex items-center gap-x-1 px-3 pt-4 pb-3 shrink-0">
52
+ <header class="flex items-center gap-x-1 px-3 pt-4 pb-5 shrink-0">
53
53
  <div class="flex-1 min-w-0">
54
54
  <slot name="logo" />
55
55
  </div>
@@ -1,205 +1,133 @@
1
1
  <script setup>
2
2
  import {
3
3
  IconFileTypeXls,
4
- IconCodeDots,
5
- IconFileTypePdf,
6
4
  IconFileTypeCsv,
5
+ IconFileTypePdf,
6
+ IconCodeDots,
7
7
  IconDownload,
8
8
  } from '@tabler/icons-vue'
9
9
 
10
- // Modal with format selector, pre-filled filename, columns checkboxes
11
10
  const props = defineProps({
12
11
  tableRef: { type: Object, default: null },
13
- name: { type: String, default: 'export' },
14
- columns: { type: Array, default: () => [] }, // [{ key, label }]
12
+ name: { type: String, default: 'export' },
13
+ columns: { type: Array, default: () => [] },
15
14
  })
16
15
 
17
- const isOpen = ref(false)
18
- const format = ref('xlsx')
16
+ const isOpen = ref(false)
17
+ const format = ref('xlsx')
19
18
  const filename = ref(props.name)
20
- const selectedColumns = ref([])
21
-
22
- watch(() => props.columns, (cols) => {
23
- selectedColumns.value = cols.map(c => c.key)
24
- }, { immediate: true })
25
19
 
26
20
  watch(() => props.name, (v) => { filename.value = v })
27
21
 
28
22
  const formats = [
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' },
23
+ { value: 'xlsx', label: 'Excel' },
24
+ { value: 'csv', label: 'CSV' },
25
+ { value: 'pdf', label: 'PDF' },
26
+ { value: 'json', label: 'JSON' },
33
27
  ]
34
28
 
35
- const toggleColumn = (key) => {
36
- const idx = selectedColumns.value.indexOf(key)
37
- if (idx >= 0) selectedColumns.value.splice(idx, 1)
38
- else selectedColumns.value.push(key)
39
- }
40
-
41
- const toggleAll = () => {
42
- if (selectedColumns.value.length === props.columns.length)
43
- selectedColumns.value = []
44
- else
45
- selectedColumns.value = props.columns.map(c => c.key)
29
+ const doExport = () => {
30
+ props.tableRef?.exportTable(format.value, true, true)
31
+ isOpen.value = false
46
32
  }
47
33
 
48
- const allSelected = computed(() => selectedColumns.value.length === props.columns.length)
49
- const indeterminate = computed(() => selectedColumns.value.length > 0 && !allSelected.value)
34
+ const panelRef = ref(null)
35
+ const triggerRef = ref(null)
50
36
 
51
- const doExport = () => {
52
- if (props.tableRef) {
53
- props.tableRef.exportTable(format.value, true, true)
37
+ const onOutsideClick = (e) => {
38
+ if (
39
+ panelRef.value && !panelRef.value.contains(e.target) &&
40
+ triggerRef.value && !triggerRef.value.contains(e.target)
41
+ ) {
42
+ isOpen.value = false
54
43
  }
55
- isOpen.value = false
56
44
  }
57
45
 
58
- const open = () => { isOpen.value = true }
46
+ watch(isOpen, (v) => {
47
+ if (v) document.addEventListener('mousedown', onOutsideClick)
48
+ else document.removeEventListener('mousedown', onOutsideClick)
49
+ })
59
50
 
60
- defineExpose({ open })
51
+ defineExpose({ open: () => { isOpen.value = true } })
61
52
  </script>
62
53
 
63
54
  <template>
64
- <div>
55
+ <div class="relative">
65
56
  <button
57
+ ref="triggerRef"
66
58
  type="button"
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-card-line bg-card text-foreground shadow-2xs hover:bg-muted-hover disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden"
59
+ @click="isOpen = !isOpen"
60
+ :class="[
61
+ 'py-1.5 px-3 inline-flex items-center gap-2 text-sm font-medium rounded-lg border transition-colors',
62
+ isOpen
63
+ ? 'border-blue-500 bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:border-blue-500 dark:text-blue-300'
64
+ : 'border-card-line bg-card text-muted-foreground-1 hover:bg-muted-hover'
65
+ ]"
69
66
  >
70
- <IconDownload class="shrink-0 size-4" stroke="1.5" />
67
+ <IconDownload class="size-4" stroke="1.5" />
71
68
  Exportar
72
69
  </button>
73
70
 
74
- <!-- Modal -->
75
- <Teleport to="body">
76
- <Transition
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"
71
+ <Transition
72
+ enter-active-class="transition ease-out duration-150"
73
+ enter-from-class="opacity-0 translate-y-1 scale-95"
74
+ enter-to-class="opacity-100 translate-y-0 scale-100"
75
+ leave-active-class="transition ease-in duration-100"
76
+ leave-from-class="opacity-100 translate-y-0 scale-100"
77
+ leave-to-class="opacity-0 translate-y-1 scale-95"
78
+ >
79
+ <div
80
+ v-if="isOpen"
81
+ ref="panelRef"
82
+ class="absolute top-full right-0 z-50 mt-1.5 bg-dropdown border border-dropdown-line rounded-xl shadow-2xl p-3 w-64"
83
83
  >
84
- <div
85
- v-if="isOpen"
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"
88
- >
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"
84
+ <p class="text-[10px] font-bold text-muted-foreground uppercase tracking-widest mb-3 px-1">Exportar</p>
85
+
86
+ <!-- Format -->
87
+ <div class="grid grid-cols-4 gap-1.5 mb-3">
88
+ <button
89
+ v-for="f in formats"
90
+ :key="f.value"
91
+ type="button"
92
+ @click="format = f.value"
93
+ :class="[
94
+ 'flex flex-col items-center gap-1 py-2 rounded-lg border text-xs font-medium transition-colors',
95
+ format === f.value
96
+ ? 'border-blue-500 bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:border-blue-500 dark:text-blue-300'
97
+ : 'border-card-line text-muted-foreground-1 hover:bg-muted-hover'
98
+ ]"
93
99
  >
94
- <div
95
- v-if="isOpen"
96
- class="bg-card rounded-2xl shadow-2xl w-full max-w-md border border-card-line"
97
- >
98
- <div class="flex items-center justify-between px-6 py-4 border-b border-card-line">
99
- <h3 class="font-semibold text-foreground">Exportar tabla</h3>
100
- <button
101
- type="button"
102
- @click="isOpen = false"
103
- class="p-1.5 rounded-lg hover:bg-muted-hover transition-colors"
104
- >
105
- <svg class="size-4 text-muted-foreground" 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>
108
- </button>
109
- </div>
110
-
111
- <div class="px-6 py-5 space-y-5">
112
- <!-- Format selector -->
113
- <div>
114
- <p class="text-xs font-semibold text-muted-foreground 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-card-line text-muted-foreground-1 hover:bg-muted-hover'
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-muted-foreground 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-card-line bg-card text-foreground py-2 px-3 text-sm focus:outline-none focus:ring-1 focus:ring-indigo-500"
147
- />
148
- <span class="text-sm text-muted-foreground">.{{ 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-muted-foreground 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-muted-hover cursor-pointer"
169
- >
170
- <input
171
- type="checkbox"
172
- :checked="selectedColumns.includes(col.key)"
173
- @change="toggleColumn(col.key)"
174
- class="rounded border-card-line dark:bg-surface text-indigo-600"
175
- />
176
- <span class="text-sm text-foreground truncate">{{ col.label }}</span>
177
- </label>
178
- </div>
179
- </div>
180
- </div>
100
+ <IconFileTypeXls v-if="f.value === 'xlsx'" class="size-4" stroke="1.5" />
101
+ <IconFileTypeCsv v-else-if="f.value === 'csv'" class="size-4" stroke="1.5" />
102
+ <IconFileTypePdf v-else-if="f.value === 'pdf'" class="size-4" stroke="1.5" />
103
+ <IconCodeDots v-else class="size-4" stroke="1.5" />
104
+ {{ f.label }}
105
+ </button>
106
+ </div>
181
107
 
182
- <div class="flex justify-end gap-2 px-6 py-4 border-t border-card-line">
183
- <button
184
- type="button"
185
- @click="isOpen = false"
186
- class="py-2 px-4 text-sm font-medium rounded-lg border border-card-line text-muted-foreground-1 hover:bg-muted-hover transition-colors"
187
- >
188
- Cancelar
189
- </button>
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"
194
- >
195
- <IconDownload class="size-4" stroke="1.5" />
196
- Exportar
197
- </button>
198
- </div>
199
- </div>
200
- </Transition>
108
+ <!-- Filename -->
109
+ <div class="mb-3 px-1">
110
+ <label class="text-[10px] font-bold text-muted-foreground uppercase tracking-widest block mb-1.5">Archivo</label>
111
+ <div class="flex items-center gap-1.5">
112
+ <input
113
+ v-model="filename"
114
+ type="text"
115
+ class="flex-1 rounded-lg border border-card-line bg-card text-foreground py-1.5 px-2.5 text-xs focus:outline-none focus:ring-1 focus:ring-blue-500 min-w-0"
116
+ />
117
+ <span class="text-xs text-muted-foreground shrink-0">.{{ format }}</span>
118
+ </div>
201
119
  </div>
202
- </Transition>
203
- </Teleport>
120
+
121
+ <!-- Export button -->
122
+ <button
123
+ type="button"
124
+ @click="doExport"
125
+ class="w-full py-1.5 px-3 rounded-lg bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium transition-colors inline-flex items-center justify-center gap-2"
126
+ >
127
+ <IconDownload class="size-4" stroke="1.5" />
128
+ Exportar
129
+ </button>
130
+ </div>
131
+ </Transition>
204
132
  </div>
205
133
  </template>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@innertia-solutions/nuxt-theme-spark",
3
- "version": "0.1.113",
3
+ "version": "0.1.115",
4
4
  "description": "Innertia Solutions — Spark theme: backoffice, landing and mobile components and layouts",
5
5
  "keywords": [
6
6
  "nuxt",