@innertia-solutions/ui 0.1.0

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.
@@ -0,0 +1,89 @@
1
+ <script setup lang="ts">
2
+ import { ref, watch, nextTick } from "vue";
3
+
4
+ const props = defineProps<{
5
+ options: { value: string; label: string }[];
6
+ }>();
7
+
8
+ const modelValue = defineModel<string | null>({ default: null });
9
+
10
+ const selectRef = ref<HTMLSelectElement | null>(null);
11
+
12
+ const reinitHsSelect = async () => {
13
+ await nextTick();
14
+
15
+ const el = selectRef.value;
16
+ if (!el) return;
17
+
18
+ const instance = window.HSSelect?.getInstance?.(el);
19
+ if (instance?.destroy) instance.destroy();
20
+
21
+ new window.HSSelect(el);
22
+ };
23
+
24
+ watch(
25
+ () => props.options,
26
+ async () => {
27
+ await reinitHsSelect();
28
+ },
29
+ { deep: true }
30
+ );
31
+
32
+ onMounted(() => {
33
+ reinitHsSelect();
34
+ });
35
+ </script>
36
+
37
+ <template>
38
+ <ClientOnly>
39
+ <!-- Fallback mientras se monta en el cliente -->
40
+ <template #fallback>
41
+ <div
42
+ class="h-12 bg-slate-100 dark:bg-slate-800 animate-pulse rounded-lg"
43
+ ></div>
44
+ </template>
45
+ <div class="relative">
46
+ <select
47
+ ref="selectRef"
48
+ class="hs-select w-full"
49
+ :value="modelValue"
50
+ @change="(e) => modelValue?.value ? modelValue.value = (e.target as HTMLSelectElement).value : null"
51
+ data-hs-select='{
52
+ "placeholder": "Select option...",
53
+ "toggleTag": "<button type=\"button\" aria-expanded=\"false\"></button>",
54
+ "toggleClasses": "hs-select-disabled:pointer-events-none hs-select-disabled:opacity-50 relative py-2 ps-4 pe-9 flex gap-x-2 text-nowrap w-full cursor-pointer bg-white border border-slate-200 rounded-lg text-start text-sm focus:outline-hidden focus:ring-2 focus:ring-blue-500 dark:bg-slate-900 dark:border-slate-700 dark:text-slate-400 dark:focus:outline-hidden dark:focus:ring-1 dark:focus:ring-slate-600",
55
+ "dropdownClasses": "mt-1 z-50 w-full max-h-72 p-1 space-y-0.5 bg-white border border-slate-200 rounded-lg overflow-hidden overflow-y-auto dark:bg-slate-900 dark:border-slate-700",
56
+ "optionClasses": "py-2 px-4 w-full text-sm text-slate-800 cursor-pointer hover:bg-slate-100 rounded-lg focus:outline-hidden focus:bg-slate-100 dark:bg-slate-900 dark:hover:bg-slate-800 dark:text-slate-200 dark:focus:bg-slate-800",
57
+ "optionTemplate": "<div class=\"flex justify-between items-center w-full\"><span data-title></span><span class=\"hidden hs-selected:block\"><svg class=\"shrink-0 size-3.5 text-blue-600 dark:text-blue-500 \" xmlns=\"http:.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\"><polyline points=\"20 6 9 17 4 12\"/></svg></span></div>"
58
+ }'
59
+ >
60
+ <option disabled value="">Selecciona</option>
61
+ <option
62
+ v-for="option in options"
63
+ :key="option.value"
64
+ :value="option.value"
65
+ >
66
+ {{ option.label }}
67
+ </option>
68
+ </select>
69
+
70
+ <div class="absolute top-1/2 end-2.5 -translate-y-1/2">
71
+ <svg
72
+ class="shrink-0 size-4 text-slate-500 dark:text-slate-500"
73
+ xmlns="http://www.w3.org/2000/svg"
74
+ width="24"
75
+ height="24"
76
+ viewBox="0 0 24 24"
77
+ fill="none"
78
+ stroke="currentColor"
79
+ stroke-width="2"
80
+ stroke-linecap="round"
81
+ stroke-linejoin="round"
82
+ >
83
+ <path d="m7 15 5 5 5-5"></path>
84
+ <path d="m7 9 5-5 5 5"></path>
85
+ </svg>
86
+ </div>
87
+ </div>
88
+ </ClientOnly>
89
+ </template>
@@ -0,0 +1,163 @@
1
+ <template>
2
+ <Modal
3
+ :model-value="modelValue"
4
+ @update:model-value="$emit('update:modelValue', $event)"
5
+ size="sm"
6
+ :closable="!loading"
7
+ :backdrop-dismiss="!loading"
8
+ :show-header="false"
9
+ :show-footer="false"
10
+ >
11
+ <!-- Close Button -->
12
+ <div class="absolute top-3 right-3">
13
+ <button
14
+ type="button"
15
+ :disabled="loading"
16
+ class="size-8 shrink-0 flex justify-center items-center gap-x-2 rounded-full border border-transparent bg-gray-100 text-gray-800 hover:bg-gray-200 disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden focus:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 dark:text-gray-400 dark:focus:bg-gray-600"
17
+ aria-label="Close"
18
+ @click="closeModal"
19
+ >
20
+ <span class="sr-only">Cerrar</span>
21
+ <svg
22
+ class="shrink-0 size-4"
23
+ xmlns="http://www.w3.org/2000/svg"
24
+ width="24"
25
+ height="24"
26
+ viewBox="0 0 24 24"
27
+ fill="none"
28
+ stroke="currentColor"
29
+ stroke-width="2"
30
+ stroke-linecap="round"
31
+ stroke-linejoin="round"
32
+ >
33
+ <path d="M18 6 6 18" />
34
+ <path d="m6 6 12 12" />
35
+ </svg>
36
+ </button>
37
+ </div>
38
+
39
+ <!-- Body -->
40
+ <div class="p-5 sm:p-10">
41
+ <h3 class="text-lg font-medium text-gray-800 dark:text-gray-200">
42
+ {{ title || "Confirmar eliminación" }}
43
+ </h3>
44
+ <p class="mt-2 text-sm text-gray-500 dark:text-gray-500">
45
+ {{
46
+ message ||
47
+ "Esta acción es irreversible. ¿Estás seguro de que deseas continuar?"
48
+ }}
49
+ </p>
50
+ </div>
51
+
52
+ <!-- Footer -->
53
+ <div class="pb-5 px-5 sm:px-10 flex justify-center items-center gap-x-3">
54
+ <button
55
+ type="button"
56
+ :disabled="loading"
57
+ class="py-2.5 px-3 w-full inline-flex justify-center items-center gap-x-1.5 text-sm font-medium rounded-xl border border-gray-200 bg-white text-gray-800 shadow-2xs hover:bg-gray-50 disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden focus:bg-gray-50 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-700 dark:focus:bg-gray-700"
58
+ @click="closeModal"
59
+ >
60
+ {{ cancelText || "Cancelar" }}
61
+ </button>
62
+ <button
63
+ type="button"
64
+ :class="[
65
+ 'py-2.5 px-3 w-full inline-flex justify-center items-center gap-x-1.5 text-sm font-medium rounded-xl border border-transparent text-white disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden',
66
+ dangerMode
67
+ ? 'bg-red-500 hover:bg-red-600 focus:bg-red-600'
68
+ : 'bg-purple-500 hover:bg-purple-600 focus:bg-purple-600',
69
+ ]"
70
+ :disabled="loading"
71
+ @click="handleConfirm"
72
+ >
73
+ <svg
74
+ v-if="loading"
75
+ class="w-4 h-4 animate-spin mr-1"
76
+ fill="none"
77
+ stroke="currentColor"
78
+ viewBox="0 0 24 24"
79
+ >
80
+ <path
81
+ stroke-linecap="round"
82
+ stroke-linejoin="round"
83
+ stroke-width="2"
84
+ d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
85
+ ></path>
86
+ </svg>
87
+ {{ loading ? loadingText : confirmText || "Confirmar" }}
88
+ </button>
89
+ </div>
90
+ </Modal>
91
+ </template>
92
+
93
+ <script setup>
94
+ import { ref } from "vue";
95
+
96
+ // Props
97
+ const props = defineProps({
98
+ modelValue: {
99
+ type: Boolean,
100
+ default: false,
101
+ },
102
+ title: {
103
+ type: String,
104
+ default: "",
105
+ },
106
+ message: {
107
+ type: String,
108
+ default: "",
109
+ },
110
+ confirmText: {
111
+ type: String,
112
+ default: "",
113
+ },
114
+ cancelText: {
115
+ type: String,
116
+ default: "",
117
+ },
118
+ loadingText: {
119
+ type: String,
120
+ default: "Procesando...",
121
+ },
122
+ dangerMode: {
123
+ type: Boolean,
124
+ default: true,
125
+ },
126
+ });
127
+
128
+ // Emits
129
+ const emit = defineEmits(["update:modelValue", "confirm", "cancel"]);
130
+
131
+ // State
132
+ const loading = ref(false);
133
+
134
+ // Exponer loading para que el padre pueda controlarlo
135
+ defineExpose({
136
+ loading,
137
+ setLoading: (value) => { loading.value = value },
138
+ closeModal: () => {
139
+ loading.value = false;
140
+ emit("update:modelValue", false);
141
+ }
142
+ });
143
+
144
+ // Methods
145
+ const closeModal = () => {
146
+ if (!loading.value) {
147
+ emit("update:modelValue", false);
148
+ emit("cancel");
149
+ }
150
+ };
151
+
152
+ const handleConfirm = () => {
153
+ if (loading.value) return; // Prevenir doble click
154
+
155
+ loading.value = true;
156
+
157
+ // Emitir el evento confirm
158
+ emit("confirm");
159
+
160
+ // El loading se mantiene activo
161
+ // Para desactivarlo, el padre debe llamar a una función o cerrar el modal
162
+ };
163
+ </script>
@@ -0,0 +1,111 @@
1
+ <script setup>
2
+ import {
3
+ IconFileTypeXls,
4
+ IconCodeDots,
5
+ IconFileTypePdf,
6
+ IconFileTypeCsv,
7
+ IconDownload,
8
+ } from "@tabler/icons-vue";
9
+
10
+ const props = defineProps({
11
+ tableRef: { type: Object, required: true },
12
+ });
13
+
14
+ const exportAllPages = ref(true);
15
+ const exportFilteredRows = ref(true);
16
+
17
+ const exportTable = (format) => {
18
+ if (props.tableRef) {
19
+ props.tableRef.exportTable(
20
+ format,
21
+ exportAllPages.value,
22
+ exportFilteredRows.value
23
+ );
24
+ }
25
+ };
26
+ </script>
27
+ <template>
28
+ <div
29
+ class="hs-dropdown [--auto-close:inside] [--placement:bottom-right] relative inline-block"
30
+ >
31
+ <button
32
+ id="hs-as-table-table-export-dropdown"
33
+ type="button"
34
+ class="py-1.5 sm:py-2 px-2.5 inline-flex items-center gap-x-1.5 text-sm sm:text-xs 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 focus:bg-slate-50 dark:bg-slate-800 dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-700 dark:focus:bg-slate-700"
35
+ aria-haspopup="menu"
36
+ aria-expanded="false"
37
+ aria-label="Dropdown"
38
+ >
39
+ <IconDownload class="shrink-0 size-4" stroke="1.5" />
40
+ Exportar
41
+ </button>
42
+ <div
43
+ class="hs-dropdown-menu transition-[opacity,margin] duration hs-dropdown-open:opacity-100 opacity-0 hidden divide-y divide-slate-200 min-w-48 z-10 bg-white shadow-md rounded-lg p-2 mt-2 dark:divide-slate-700 dark:bg-slate-800 dark:border dark:border-slate-700 border-t border-slate-200 dark:border-slate-700"
44
+ role="menu"
45
+ aria-orientation="vertical"
46
+ aria-labelledby="hs-as-table-table-export-dropdown"
47
+ >
48
+ <div class="py-2 first:pt-0 last:pb-0">
49
+ <a
50
+ class="flex items-center gap-x-3 py-2 px-3 rounded-lg text-sm text-slate-800 hover:bg-slate-100 focus:outline-hidden focus:bg-slate-100 dark:text-slate-400 dark:hover:bg-slate-700 dark:hover:text-slate-300 dark:focus:bg-slate-700 dark:focus:text-slate-300"
51
+ href="#"
52
+ @click="exportTable('xlsx')"
53
+ >
54
+ <IconFileTypeXls class="shrink-0 size-5" stroke="1.5" />
55
+ Excel
56
+ </a>
57
+ <a
58
+ class="flex items-center gap-x-3 py-2 px-3 rounded-lg text-sm text-slate-800 hover:bg-slate-100 focus:outline-hidden focus:bg-slate-100 dark:text-slate-400 dark:hover:bg-slate-700 dark:hover:text-slate-300 dark:focus:bg-slate-700 dark:focus:text-slate-300"
59
+ href="#"
60
+ @click="exportTable('csv')"
61
+ >
62
+ <IconFileTypeCsv class="shrink-0 size-5" stroke="1.5" />
63
+ CSV
64
+ </a>
65
+ <a
66
+ class="flex items-center gap-x-3 py-2 px-3 rounded-lg text-sm text-slate-800 hover:bg-slate-100 focus:outline-hidden focus:bg-slate-100 dark:text-slate-400 dark:hover:bg-slate-700 dark:hover:text-slate-300 dark:focus:bg-slate-700 dark:focus:text-slate-300"
67
+ href="#"
68
+ @click="exportTable('pdf')"
69
+ >
70
+ <IconFileTypePdf class="shrink-0 size-5" stroke="1.5" />
71
+ PDF
72
+ </a>
73
+ <a
74
+ class="flex items-center gap-x-3 py-2 px-3 rounded-lg text-sm text-slate-800 hover:bg-slate-100 focus:outline-hidden focus:bg-slate-100 dark:text-slate-400 dark:hover:bg-slate-700 dark:hover:text-slate-300 dark:focus:bg-slate-700 dark:focus:text-slate-300"
75
+ href="#"
76
+ @click="exportTable('json')"
77
+ >
78
+ <IconCodeDots class="shrink-0 size-5" stroke="1.5" />
79
+ JSON
80
+ </a>
81
+ </div>
82
+ <!-- checkbox - todas las paginas -->
83
+ <div
84
+ class="flex flex-col gap-y-2 py-2 px-3"
85
+ data-hs-dropdown-ignore-click
86
+ >
87
+ <label
88
+ class="inline-flex items-center gap-2 text-sm text-slate-700 dark:text-slate-200"
89
+ >
90
+ <input
91
+ type="checkbox"
92
+ v-model="exportAllPages"
93
+ class="shrink-0 border-slate-300 rounded-sm text-blue-600 focus:ring-blue-500 dark:bg-slate-800 dark:border-slate-600"
94
+ />
95
+ Todas las páginas
96
+ </label>
97
+
98
+ <label
99
+ class="inline-flex items-center gap-2 text-sm text-slate-700 dark:text-slate-200"
100
+ >
101
+ <input
102
+ type="checkbox"
103
+ v-model="exportFilteredRows"
104
+ class="shrink-0 border-slate-300 rounded-sm text-blue-600 focus:ring-blue-500 dark:bg-slate-800 dark:border-slate-600"
105
+ />
106
+ Solo filas filtradas
107
+ </label>
108
+ </div>
109
+ </div>
110
+ </div>
111
+ </template>
@@ -0,0 +1,226 @@
1
+ <script setup>
2
+ const props = defineProps({
3
+ modelValue: {
4
+ type: [String, Number],
5
+ default: null,
6
+ },
7
+ options: {
8
+ type: Array,
9
+ default: () => [],
10
+ },
11
+ placeholder: {
12
+ type: String,
13
+ default: "Seleccionar...",
14
+ },
15
+ optionLabelKey: {
16
+ type: String,
17
+ default: "label",
18
+ },
19
+ optionValueKey: {
20
+ type: String,
21
+ default: "value",
22
+ },
23
+ clearable: {
24
+ type: Boolean,
25
+ default: true,
26
+ },
27
+ disabled: {
28
+ type: Boolean,
29
+ default: false,
30
+ },
31
+ menuClass: {
32
+ type: String,
33
+ default: "",
34
+ },
35
+ });
36
+
37
+ const emit = defineEmits(["update:modelValue", "change"]);
38
+
39
+ const isOpen = ref(false);
40
+ const dropdownRef = ref(null);
41
+
42
+ const getOptionLabel = (option) => option?.[props.optionLabelKey] ?? option?.label ?? "";
43
+ const getOptionValue = (option) => option?.[props.optionValueKey] ?? option?.value ?? null;
44
+
45
+ const hasSelection = computed(() => props.modelValue !== null && props.modelValue !== undefined && props.modelValue !== "");
46
+
47
+ const selectedOption = computed(() =>
48
+ props.options.find((option) => getOptionValue(option) === props.modelValue),
49
+ );
50
+
51
+ const displayText = computed(() => {
52
+ if (!selectedOption.value) return props.placeholder;
53
+ return getOptionLabel(selectedOption.value) || props.placeholder;
54
+ });
55
+
56
+ const selectClasses = computed(() => {
57
+ const base =
58
+ "relative w-full rounded-lg border bg-white dark:bg-slate-800 transition-colors cursor-pointer text-slate-900 dark:text-white py-2 px-3 text-sm focus:outline-none focus:ring-0 focus:border-gray-400";
59
+ const validation = "border-gray-200 dark:border-slate-700";
60
+ const disabled = props.disabled ? "opacity-50 cursor-not-allowed" : "";
61
+ return `${base} ${validation} ${disabled}`;
62
+ });
63
+
64
+ const closeDropdown = () => {
65
+ isOpen.value = false;
66
+ };
67
+
68
+ const toggleDropdown = () => {
69
+ if (props.disabled) return;
70
+ isOpen.value = !isOpen.value;
71
+ };
72
+
73
+ const selectOption = (option) => {
74
+ const selectedValue = getOptionValue(option);
75
+ const nextValue = props.modelValue === selectedValue ? null : selectedValue;
76
+ emit("update:modelValue", nextValue);
77
+ emit("change", nextValue);
78
+ closeDropdown();
79
+ };
80
+
81
+ const clearSelection = () => {
82
+ emit("update:modelValue", null);
83
+ emit("change", null);
84
+ closeDropdown();
85
+ };
86
+
87
+ const isOptionSelected = (option) => getOptionValue(option) === props.modelValue;
88
+
89
+ const handleClickOutside = (event) => {
90
+ if (dropdownRef.value && !dropdownRef.value.contains(event.target)) {
91
+ closeDropdown();
92
+ }
93
+ };
94
+
95
+ onMounted(() => document.addEventListener("mousedown", handleClickOutside));
96
+ onUnmounted(() => document.removeEventListener("mousedown", handleClickOutside));
97
+ </script>
98
+
99
+ <template>
100
+ <div ref="dropdownRef" class="relative w-full">
101
+ <button
102
+ type="button"
103
+ :class="selectClasses"
104
+ :disabled="disabled"
105
+ :aria-expanded="isOpen"
106
+ :aria-haspopup="true"
107
+ @click="toggleDropdown"
108
+ >
109
+ <div class="flex items-center justify-between w-full">
110
+ <span
111
+ class="truncate flex-1 text-left pr-10"
112
+ :class="{
113
+ 'text-gray-400 dark:text-slate-500': !hasSelection,
114
+ 'text-slate-900 dark:text-white': hasSelection,
115
+ }"
116
+ >
117
+ <slot name="display" :selected-option="selectedOption" :display-text="displayText">
118
+ {{ displayText }}
119
+ </slot>
120
+ </span>
121
+
122
+ <span
123
+ v-if="clearable && hasSelection && !disabled"
124
+ class="absolute end-8 top-1/2 -translate-y-1/2 hover:bg-slate-100 dark:hover:bg-slate-700 rounded-full p-1 transition-colors focus:outline-none focus:ring-1 focus:ring-slate-400"
125
+ role="button"
126
+ tabindex="0"
127
+ @click.stop="clearSelection"
128
+ @keydown.enter.prevent="clearSelection"
129
+ @keydown.space.prevent="clearSelection"
130
+ >
131
+ <svg class="size-3.5 text-slate-400" fill="currentColor" viewBox="0 0 20 20">
132
+ <path
133
+ fill-rule="evenodd"
134
+ d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
135
+ clip-rule="evenodd"
136
+ />
137
+ </svg>
138
+ </span>
139
+
140
+ <div class="absolute top-1/2 end-3 -translate-y-1/2">
141
+ <svg
142
+ class="shrink-0 size-3.5 text-gray-500 dark:text-gray-500 transition-transform"
143
+ :class="{ 'rotate-180': isOpen }"
144
+ xmlns="http://www.w3.org/2000/svg"
145
+ width="24"
146
+ height="24"
147
+ viewBox="0 0 24 24"
148
+ fill="none"
149
+ stroke="currentColor"
150
+ stroke-width="2"
151
+ stroke-linecap="round"
152
+ stroke-linejoin="round"
153
+ >
154
+ <path d="m7 15 5 5 5-5" />
155
+ <path d="m7 9 5-5 5 5" />
156
+ </svg>
157
+ </div>
158
+ </div>
159
+ </button>
160
+
161
+ <Transition
162
+ enter-active-class="transition ease-out duration-100"
163
+ enter-from-class="transform opacity-0 scale-95"
164
+ enter-to-class="transform opacity-100 scale-100"
165
+ leave-active-class="transition ease-in duration-75"
166
+ leave-from-class="transform opacity-100 scale-100"
167
+ leave-to-class="transform opacity-0 scale-95"
168
+ >
169
+ <div
170
+ v-show="isOpen"
171
+ :class="[
172
+ 'absolute z-50 w-full mt-1 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl shadow-xl max-h-60 overflow-auto',
173
+ menuClass,
174
+ ]"
175
+ >
176
+ <div v-if="options.length">
177
+ <button
178
+ v-for="option in options"
179
+ :key="getOptionValue(option)"
180
+ type="button"
181
+ :class="[
182
+ 'w-full px-3 py-2 text-left hover:bg-slate-50 dark:hover:bg-slate-700 flex items-center text-sm',
183
+ isOptionSelected(option) ? 'bg-slate-50 dark:bg-slate-700/50' : '',
184
+ ]"
185
+ @click="selectOption(option)"
186
+ >
187
+ <slot name="option" :option="option" :selected="isOptionSelected(option)">
188
+ <div class="flex items-center flex-1 min-w-0">
189
+ <span
190
+ v-if="option.dot"
191
+ class="relative flex-shrink-0 size-2.5 flex items-center justify-center mr-2.5"
192
+ >
193
+ <span
194
+ v-if="option.pulse"
195
+ :class="['animate-ping absolute inline-flex h-full w-full rounded-full opacity-75', option.dot]"
196
+ />
197
+ <span :class="['relative inline-flex rounded-full size-2.5', option.dot]" />
198
+ </span>
199
+ <span class="font-bold text-slate-800 dark:text-slate-200 truncate">
200
+ {{ getOptionLabel(option) }}
201
+ </span>
202
+ </div>
203
+ </slot>
204
+
205
+ <svg
206
+ v-if="isOptionSelected(option)"
207
+ class="w-4 h-4 text-slate-500 ml-2 shrink-0"
208
+ fill="currentColor"
209
+ viewBox="0 0 20 20"
210
+ >
211
+ <path
212
+ fill-rule="evenodd"
213
+ d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
214
+ clip-rule="evenodd"
215
+ />
216
+ </svg>
217
+ </button>
218
+ </div>
219
+
220
+ <div v-else class="px-3 py-4 text-center text-gray-500 dark:text-gray-400 text-sm">
221
+ Sin opciones
222
+ </div>
223
+ </div>
224
+ </Transition>
225
+ </div>
226
+ </template>