@innertia-solutions/nuxt-theme-spark 0.1.87 → 0.1.89

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.
@@ -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
- const exportableColumns = computed(() => props.columns.filter(c => c.label))
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: IconFileTypeXls },
34
- { value: 'csv', label: 'CSV', icon: IconFileTypeCsv },
35
- { value: 'pdf', label: 'PDF', icon: IconFileTypePdf },
36
- { value: 'json', label: 'JSON', icon: IconCodeDots },
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 === exportableColumns.value.length)
42
+ if (selectedColumns.value.length === props.columns.length)
47
43
  selectedColumns.value = []
48
44
  else
49
- selectedColumns.value = exportableColumns.value.map(c => c.key)
45
+ selectedColumns.value = props.columns.map(c => c.key)
50
46
  }
51
47
 
52
- const allSelected = computed(() => selectedColumns.value.length === exportableColumns.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 (!props.tableRef) return
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 class="relative">
64
+ <div>
104
65
  <button
105
- ref="btnRef"
106
66
  type="button"
107
- @click="toggle"
108
- :class="[
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
- <!-- Dropdown panel — teleported to body to avoid overflow-hidden clipping -->
74
+ <!-- Modal -->
120
75
  <Teleport to="body">
121
76
  <Transition
122
- enter-active-class="transition ease-out duration-150"
123
- enter-from-class="opacity-0 translate-y-1 scale-95"
124
- enter-to-class="opacity-100 translate-y-0 scale-100"
125
- leave-active-class="transition ease-in duration-100"
126
- leave-from-class="opacity-100 translate-y-0 scale-100"
127
- leave-to-class="opacity-0 translate-y-1 scale-95"
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
- ref="panelRef"
132
- :style="panelStyle"
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
- <div class="p-3 space-y-4">
136
- <!-- Format -->
137
- <div>
138
- <p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-2">Formato</p>
139
- <div class="grid grid-cols-4 gap-1.5">
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="format = f.value"
145
- :class="[
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
- <component :is="f.icon" class="size-4" stroke="1.5" />
153
- {{ f.label }}
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
- <!-- Filename -->
159
- <div>
160
- <p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1.5">Archivo</p>
161
- <div class="flex items-center gap-1.5">
162
- <Forms.Input v-model="filename" type="text" placeholder="nombre" class="flex-1" />
163
- <span class="text-sm text-slate-400 shrink-0">.{{ format }}</span>
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
- <!-- Columns -->
168
- <div v-if="exportableColumns.length > 0">
169
- <div class="flex items-center justify-between mb-1.5">
170
- <p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest">Columnas</p>
171
- <button type="button" @click="toggleAll" class="text-[10px] text-blue-600 dark:text-blue-400 hover:underline font-medium">
172
- {{ allSelected ? 'Quitar todas' : 'Todas' }}
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
- </div>
175
- <div class="grid grid-cols-2 gap-1 max-h-36 overflow-y-auto">
176
- <label
177
- v-for="col in exportableColumns"
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
- <input
182
- type="checkbox"
183
- :checked="selectedColumns.includes(col.key)"
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
- </div>
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@innertia-solutions/nuxt-theme-spark",
3
- "version": "0.1.87",
3
+ "version": "0.1.89",
4
4
  "description": "Innertia Solutions — Spark theme: backoffice, landing and mobile components and layouts",
5
5
  "keywords": [
6
6
  "nuxt",
package/spark.css CHANGED
@@ -8,11 +8,6 @@
8
8
  @source "./shared/**/*.{js,ts}";
9
9
  @source "./plugins/**/*.{js,ts}";
10
10
 
11
- /* Safelist: clases dinámicas del colorMap (Admin/Page, Nav/Tabs, etc.) */
12
- @source inline("{text,bg,border}-{blue,green,red,amber,purple,teal,slate}-{50,100,200,400,500,600,700,800,900}");
13
- @source inline("dark:{text,bg,border}-{blue,green,red,amber,purple,teal,slate}-{50,100,200,400,500,600,700,800,900}");
14
- @source inline("dark:bg-{blue,green,red,amber,purple,teal}-500/10 dark:border-{blue,green,red,amber,purple,teal}-500/30");
15
-
16
11
  @custom-variant dark (&:where(.dark, .dark *));
17
12
  @custom-variant hover (&:hover);
18
13
 
@@ -118,9 +113,9 @@
118
113
 
119
114
  /* ─── Dark theme ────────────────────────────────────────────── */
120
115
  .dark {
121
- --background: #020617; /* slate-950 — base page */
122
- --background-1: #0f172a; /* slate-900 — panels */
123
- --background-2: #1e293b; /* slate-800 — elevated */
116
+ --background: #020617;
117
+ --background-1: #0f172a;
118
+ --background-2: #111827;
124
119
  --foreground: #f8fafc;
125
120
  --foreground-inverse: #020617;
126
121
 
@@ -130,35 +125,35 @@
130
125
  --primary-focus: #93c5fd;
131
126
  --primary-line: #3b82f6;
132
127
 
133
- --card: #0f172a;
134
- --card-line: #1e293b;
135
- --card-divider: #1e293b;
128
+ --card: #111827;
129
+ --card-line: #1f2937;
130
+ --card-divider: #1f2937;
136
131
 
137
- --layer: #0f172a;
138
- --layer-line: #1e293b;
132
+ --layer: #111827;
133
+ --layer-line: #1f2937;
139
134
  --layer-hover: #1e293b;
140
135
  --layer-focus: #334155;
141
136
 
142
- --surface: #1e293b;
137
+ --surface: #1f2937;
143
138
  --surface-1: #334155;
144
- --surface-foreground: #e2e8f0;
139
+ --surface-foreground: #e5e7eb;
145
140
 
146
- --muted: #0f172a;
141
+ --muted: #111827;
147
142
  --muted-foreground: #94a3b8;
148
143
  --muted-foreground-1: #cbd5e1;
149
144
  --muted-foreground-2: #64748b;
150
145
  --muted-hover: #1e293b;
151
146
  --muted-focus: #334155;
152
147
 
153
- --navbar: #0f172a;
154
- --navbar-line: #1e293b;
155
- --sidebar: #0f172a;
156
- --sidebar-line: #1e293b;
148
+ --navbar: #111827;
149
+ --navbar-line: #1f2937;
150
+ --sidebar: #111827;
151
+ --sidebar-line: #1f2937;
157
152
 
158
- --dropdown: #0f172a;
159
- --dropdown-line: #1e293b;
160
- --dropdown-divider: #1e293b;
161
- --dropdown-item-foreground: #e2e8f0;
153
+ --dropdown: #111827;
154
+ --dropdown-line: #1f2937;
155
+ --dropdown-divider: #1f2937;
156
+ --dropdown-item-foreground: #e5e7eb;
162
157
  --dropdown-item-hover: #1e293b;
163
158
  --dropdown-item-focus: #334155;
164
159
 
@@ -166,7 +161,7 @@
166
161
  --tooltip-line: #f8fafc;
167
162
  --tooltip-foreground: #020617;
168
163
 
169
- --scrollbar-track: #0f172a;
164
+ --scrollbar-track: #111827;
170
165
  --scrollbar-thumb: #475569;
171
166
  }
172
167
 
Binary file
package/public/icon.png DELETED
Binary file
Binary file
Binary file