lau-ecom-design-system 1.0.22 → 1.0.26

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.
@@ -1,323 +1,446 @@
1
- <script lang="ts" setup>
2
- import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from "vue";
3
- import {
4
- LauEcomUpcIconSearch,
5
- LauEcomCoreIconNavClose as LauEcomUpcIconClose,
6
- } from "../LauEcomIcon";
7
-
8
- // Log de versión del componente
9
- console.log('🔍 InputSearch Version: 1.0.20 - Debug Mode');
10
-
11
- interface Props {
12
- placeholder?: string;
13
- isDisabled?: boolean;
14
- modelValue?: string;
15
- forceClose?: boolean;
16
- buttonColorClass?: string;
17
- buttonTextColorClass?: string;
18
- buttonClass?: string;
19
- inputClass?: string;
20
- containerClass?: string;
21
- }
22
-
23
- const props = withDefaults(defineProps<Props>(), {
24
- placeholder: "Quiero aprender...",
25
- isDisabled: false,
26
- modelValue: "",
27
- forceClose: false,
28
- buttonColorClass: "dsEcom-bg-primary-60 hover:dsEcom-bg-primary-70",
29
- buttonTextColorClass: "dsEcom-text-white",
30
- buttonClass: "",
31
- inputClass: "dsEcom-h-10 dsEcom-rounded-lg",
32
- containerClass: ""
33
- });
34
-
35
- // Log de props iniciales
36
- console.log('🔍 InputSearch Props:', {
37
- placeholder: props.placeholder,
38
- isDisabled: props.isDisabled,
39
- modelValue: props.modelValue,
40
- forceClose: props.forceClose
41
- });
42
-
43
- const emit = defineEmits({
44
- "update:modelValue": (value: string) => true,
45
- "search": (value: string) => true,
46
- "click:search-icon": () => true
47
- });
48
-
49
- const searchQuery = ref(props.modelValue);
50
- const isExpanded = ref(false);
51
- const showOverlay = ref(false);
52
-
53
- const handleSearch = () => {
54
- console.log('🔍 Search triggered:', {
55
- query: searchQuery.value,
56
- isExpanded: isExpanded.value,
57
- showOverlay: showOverlay.value
58
- });
59
-
60
- if (searchQuery.value && searchQuery.value.trim()) {
61
- emit("search", searchQuery.value);
62
- emit("update:modelValue", searchQuery.value);
63
- emit("click:search-icon");
64
- closeSearch();
65
- }
66
- };
67
-
68
- const handleInput = () => {
69
- console.log('🔍 Input changed:', {
70
- value: searchQuery.value,
71
- length: searchQuery.value.length,
72
- isExpanded: isExpanded.value
73
- });
74
- emit("update:modelValue", searchQuery.value);
75
- };
76
-
77
- const clearSearch = () => {
78
- console.log('🔍 Clear search triggered');
79
- searchQuery.value = "";
80
- emit("update:modelValue", "");
81
- };
82
-
83
- const closeSearch = () => {
84
- console.log('🔍 Close search triggered');
85
- showOverlay.value = false;
86
- setTimeout(() => {
87
- console.log('🔍 Closing search after timeout');
88
- isExpanded.value = false;
89
- }, 300);
90
- };
91
-
92
- const handleFocus = () => {
93
- console.log('🔍 Focus triggered:', {
94
- currentValue: searchQuery.value,
95
- wasExpanded: isExpanded.value
96
- });
97
- isExpanded.value = true;
98
- showOverlay.value = true;
99
- };
100
-
101
- const containerClasses = computed(() => {
102
- const classes = [
103
- "dsEcom-transition-transform dsEcom-duration-300 dsEcom-ease-in-out dsEcom-relative",
104
- {
105
- "dsEcom-w-[250px] md:dsEcom-w-[350px]": !isExpanded.value
106
- }
107
- ];
108
- console.log('🔍 Container classes updated:', { isExpanded: isExpanded.value });
109
- return classes;
110
- });
111
-
112
- const overlayClasses = computed(() => [
113
- "dsEcom-fixed dsEcom-inset-0 dsEcom-bg-black dsEcom-transition-opacity dsEcom-duration-300 dsEcom-ease-in-out dsEcom-z-40",
114
- {
115
- "dsEcom-opacity-50": showOverlay.value,
116
- "dsEcom-opacity-0 dsEcom-pointer-events-none": !showOverlay.value
117
- }
118
- ]);
119
-
120
- const originalContainer = ref<HTMLElement | null>(null);
121
- const expandedContainer = ref<HTMLElement | null>(null);
122
-
123
- const updateExpandedPosition = () => {
124
- if (originalContainer.value && expandedContainer.value) {
125
- const rect = originalContainer.value.getBoundingClientRect();
126
- const viewportHeight = window.innerHeight;
127
-
128
- // Si el input original está fuera de la vista (arriba o abajo), posicionamos el expandido en una posición visible
129
- let topPosition;
130
- if (rect.top < 0) {
131
- topPosition = '20px'; // Si está arriba de la vista, lo ponemos cerca del tope
132
- } else if (rect.top > viewportHeight) {
133
- topPosition = '20px'; // Si está abajo de la vista, también lo ponemos cerca del tope
134
- } else {
135
- topPosition = `${rect.top}px`; // Si está visible, mantenemos su posición actual
136
- }
137
-
138
- expandedContainer.value.style.position = 'fixed';
139
- expandedContainer.value.style.top = topPosition;
140
- expandedContainer.value.style.left = '50%';
141
- expandedContainer.value.style.transform = 'translateX(-50%)';
142
- expandedContainer.value.style.width = '656px';
143
- expandedContainer.value.style.maxWidth = '90vw';
144
- }
145
- };
146
-
147
- onMounted(() => {
148
- console.log('🔍 Component mounted');
149
- window.addEventListener('resize', updateExpandedPosition);
150
- });
151
-
152
- onBeforeUnmount(() => {
153
- console.log('🔍 Component will unmount');
154
- window.removeEventListener('resize', updateExpandedPosition);
155
- });
156
-
157
- watch(isExpanded, (newValue) => {
158
- console.log('🔍 isExpanded changed:', {
159
- newValue,
160
- searchQuery: searchQuery.value,
161
- showOverlay: showOverlay.value
162
- });
163
- if (newValue) {
164
- nextTick(updateExpandedPosition);
165
- }
166
- });
167
-
168
- watch(() => props.forceClose, (newValue) => {
169
- console.log('🔍 forceClose prop changed:', newValue);
170
- if (newValue) {
171
- closeSearch();
172
- }
173
- });
174
-
175
- watch(() => props.modelValue, (newValue) => {
176
- console.log('🔍 modelValue prop changed:', {
177
- newValue,
178
- currentSearchQuery: searchQuery.value
179
- });
180
- if (newValue !== searchQuery.value) {
181
- searchQuery.value = newValue;
182
- }
183
- });
184
-
185
- const buttonClasses = computed(() => {
186
- const defaultClasses = "dsEcom-absolute dsEcom-right-0 dsEcom-top-1/2 -dsEcom-translate-y-1/2 dsEcom-p-2 dsEcom-rounded-r-lg dsEcom-transition-all dsEcom-duration-300";
187
-
188
- // Si hay buttonClass, usamos esas clases directamente
189
- if (props.buttonClass) {
190
- return [defaultClasses, props.buttonClass];
191
- }
192
-
193
- // Si no, usamos las clases de color tradicionales
194
- return [
195
- defaultClasses,
196
- props.buttonColorClass,
197
- props.buttonTextColorClass
198
- ];
199
- });
200
- </script>
201
-
202
- <template>
203
- <div class="dsEcom-relative">
204
- <div
205
- :class="[containerClasses, props.containerClass]"
206
- ref="originalContainer"
207
- >
208
- <div class="dsEcom-relative" :class="{ 'dsEcom-invisible': isExpanded }">
209
- <input
210
- v-model="searchQuery"
211
- type="text"
212
- :placeholder="placeholder"
213
- :disabled="isDisabled"
214
- :class="[
215
- 'lau-ecom-input dsEcom-border dsEcom-border-neutral-80 dsEcom-pl-4 dsEcom-pr-24 dsEcom-w-full dsEcom-focus:outline-none dsEcom-focus:ring-2 dsEcom-focus:ring-primary-60',
216
- props.inputClass,
217
- { 'dsEcom-opacity-0': isExpanded }
218
- ]"
219
- @focus="handleFocus"
220
- @input="handleInput"
221
- @keyup.enter="handleSearch"
222
- />
223
- <div class="dsEcom-absolute dsEcom-right-0 dsEcom-inset-y-0 dsEcom-flex dsEcom-items-stretch">
224
- <button
225
- v-if="searchQuery.length >= 3"
226
- @click="clearSearch"
227
- :class="[
228
- 'dsEcom-flex dsEcom-items-center dsEcom-px-1.5 dsEcom-text-neutral-100 hover:dsEcom-text-neutral-80 dsEcom-transition-all dsEcom-duration-300',
229
- props.inputClass
230
- ]"
231
- >
232
- <LauEcomUpcIconClose width="16" height="16" />
233
- </button>
234
-
235
- <button
236
- @click="handleSearch"
237
- :class="[
238
- 'dsEcom-flex dsEcom-items-center dsEcom-px-3 dsEcom-transition-all dsEcom-duration-300',
239
- props.inputClass?.includes('rounded') ? props.inputClass : 'dsEcom-rounded-r-lg',
240
- props.buttonColorClass,
241
- props.buttonTextColorClass,
242
- 'dsEcom-border-0'
243
- ]"
244
- style="margin: 1px; height: calc(100% - 2px);"
245
- >
246
- <LauEcomUpcIconSearch width="20" height="20" color="currentColor" />
247
- </button>
248
- </div>
249
- </div>
250
- </div>
251
-
252
- <!-- Overlay -->
253
- <div
254
- v-show="isExpanded"
255
- :class="overlayClasses"
256
- @click="closeSearch"
257
- ></div>
258
-
259
- <!-- Versión expandida -->
260
- <div
261
- v-show="isExpanded"
262
- class="dsEcom-fixed dsEcom-z-50 dsEcom-bg-white dsEcom-shadow-lg dsEcom-overflow-hidden"
263
- :class="[props.inputClass?.includes('rounded') ? props.inputClass : 'dsEcom-rounded-lg']"
264
- ref="expandedContainer"
265
- >
266
- <div class="dsEcom-relative">
267
- <input
268
- v-model="searchQuery"
269
- type="text"
270
- :placeholder="placeholder"
271
- :disabled="isDisabled"
272
- :class="[
273
- 'lau-ecom-input dsEcom-border dsEcom-border-neutral-80 dsEcom-pl-4 dsEcom-pr-24 dsEcom-w-full dsEcom-focus:outline-none dsEcom-focus:ring-2 dsEcom-focus:ring-primary-60',
274
- props.inputClass
275
- ]"
276
- @input="handleInput"
277
- @keyup.enter="handleSearch"
278
- autofocus
279
- />
280
- <div class="dsEcom-absolute dsEcom-right-0 dsEcom-inset-y-0 dsEcom-flex dsEcom-items-stretch">
281
- <button
282
- v-if="searchQuery.length >= 3"
283
- @click="clearSearch"
284
- :class="[
285
- 'dsEcom-flex dsEcom-items-center dsEcom-px-1.5 dsEcom-text-neutral-100 hover:dsEcom-text-neutral-80 dsEcom-transition-all dsEcom-duration-300',
286
- props.inputClass
287
- ]"
288
- >
289
- <LauEcomUpcIconClose width="16" height="16" />
290
- </button>
291
-
292
- <button
293
- @click="handleSearch"
294
- :class="[
295
- 'dsEcom-flex dsEcom-items-center dsEcom-px-3 dsEcom-transition-all dsEcom-duration-300',
296
- { 'dsEcom-rounded-r-lg': !props.inputClass?.includes('rounded') },
297
- props.buttonColorClass,
298
- props.buttonTextColorClass,
299
- 'dsEcom-border-0'
300
- ]"
301
- style="margin: 1px; height: calc(100% - 2px);"
302
- >
303
- <LauEcomUpcIconSearch width="20" height="20" color="currentColor" />
304
- </button>
305
- </div>
306
- </div>
307
- </div>
308
- </div>
309
- </template>
310
-
311
- <style scoped>
312
- .lau-ecom-input {
313
- transition: all 0.3s ease-in-out;
314
- }
315
-
316
- .dsEcom-transform-gpu {
317
- transform: translateZ(0);
318
- backface-visibility: hidden;
319
- perspective: 1000px;
320
- }
321
- </style>
322
-
1
+ <script lang="ts" setup>
2
+ import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from "vue";
3
+ import {
4
+ LauEcomUpcIconSearch,
5
+ LauEcomCoreIconNavClose as LauEcomUpcIconClose,
6
+ LauEcomUpcIconNavArrow,
7
+ } from "../LauEcomIcon";
8
+
9
+ interface Props {
10
+ placeholder?: string;
11
+ isDisabled?: boolean;
12
+ modelValue?: string;
13
+ forceClose?: boolean;
14
+ buttonColorClass?: string;
15
+ buttonTextColorClass?: string;
16
+ buttonClass?: string;
17
+ inputClass?: string;
18
+ containerClass?: string;
19
+ expandedWidth?: string;
20
+ isMobileSearch?: boolean;
21
+ arrowColorClass?: string;
22
+ arrowButtonClass?: string;
23
+ mobileInputWidth?: string;
24
+ mobileInputHeight?: string;
25
+ mobilePanelClass?: string;
26
+ expandedBackgroundClass?: string;
27
+ overlayClass?: string;
28
+ }
29
+
30
+ const props = withDefaults(defineProps<Props>(), {
31
+ placeholder: "Quiero aprender...",
32
+ isDisabled: false,
33
+ modelValue: "",
34
+ forceClose: false,
35
+ buttonColorClass: "bg-base-color-primary-20",
36
+ buttonTextColorClass: "text-base-color-primary-60",
37
+ buttonClass: "",
38
+ inputClass: "dsEcom-h-10 dsEcom-rounded-lg",
39
+ containerClass: "",
40
+ expandedWidth: "50vw",
41
+ isMobileSearch: false,
42
+ arrowColorClass: "text-neutral-100",
43
+ arrowButtonClass: "",
44
+ mobileInputWidth: "240px",
45
+ mobileInputHeight: "40px",
46
+ mobilePanelClass: "bg-white",
47
+ expandedBackgroundClass: "dsEcom-bg-white",
48
+ overlayClass: "dsEcom-bg-black dsEcom-opacity-50"
49
+ });
50
+
51
+ const emit = defineEmits({
52
+ "update:modelValue": (value: string) => true,
53
+ "search": (value: string) => true,
54
+ "click:search-icon": () => true
55
+ });
56
+
57
+ const searchQuery = ref(props.modelValue);
58
+ const isExpanded = ref(false);
59
+ const showOverlay = ref(false);
60
+
61
+ const isMobileView = ref(false);
62
+ const isMobileSearchOpen = ref(false);
63
+
64
+ const handleSearch = () => {
65
+ if (searchQuery.value && searchQuery.value.trim()) {
66
+ emit("search", searchQuery.value);
67
+ emit("update:modelValue", searchQuery.value);
68
+ emit("click:search-icon");
69
+ closeSearch();
70
+ }
71
+ };
72
+
73
+ const handleInput = () => {
74
+ emit("update:modelValue", searchQuery.value);
75
+ };
76
+
77
+ const clearSearch = () => {
78
+ searchQuery.value = "";
79
+ emit("update:modelValue", "");
80
+ };
81
+
82
+ const closeSearch = () => {
83
+ showOverlay.value = false;
84
+ setTimeout(() => {
85
+ isExpanded.value = false;
86
+ }, 300);
87
+ };
88
+
89
+ const handleFocus = () => {
90
+ isExpanded.value = true;
91
+ showOverlay.value = true;
92
+ };
93
+
94
+ const containerClasses = computed(() => {
95
+ return [
96
+ "dsEcom-transition-transform dsEcom-duration-300 dsEcom-ease-in-out dsEcom-relative",
97
+ {
98
+ "dsEcom-w-[250px] md:dsEcom-w-[350px]": !isExpanded.value
99
+ }
100
+ ];
101
+ });
102
+
103
+ const overlayClasses = computed(() => [
104
+ "dsEcom-fixed dsEcom-inset-0 dsEcom-transition-opacity dsEcom-duration-300 dsEcom-ease-in-out dsEcom-z-40",
105
+ props.overlayClass,
106
+ {
107
+ "dsEcom-opacity-100": showOverlay.value,
108
+ "dsEcom-opacity-0 dsEcom-pointer-events-none": !showOverlay.value
109
+ }
110
+ ]);
111
+
112
+ const originalContainer = ref<HTMLElement | null>(null);
113
+ const expandedContainer = ref<HTMLElement | null>(null);
114
+
115
+ const updateExpandedPosition = () => {
116
+ if (originalContainer.value && expandedContainer.value) {
117
+ const rect = originalContainer.value.getBoundingClientRect();
118
+ const viewportHeight = window.innerHeight;
119
+ const viewportWidth = window.innerWidth;
120
+
121
+ let topPosition;
122
+ if (rect.top < 0) {
123
+ topPosition = '20px';
124
+ } else if (rect.top > viewportHeight) {
125
+ topPosition = '20px';
126
+ } else {
127
+ topPosition = `${rect.top}px`;
128
+ }
129
+
130
+ // Mantenemos la posición original del input
131
+ const leftPosition = `${rect.left}px`;
132
+
133
+ expandedContainer.value.style.position = 'fixed';
134
+ expandedContainer.value.style.top = topPosition;
135
+ expandedContainer.value.style.left = leftPosition;
136
+ expandedContainer.value.style.transform = 'none';
137
+ expandedContainer.value.style.width = props.expandedWidth;
138
+ expandedContainer.value.style.maxWidth = '90vw';
139
+
140
+ // Ajustamos si se sale por la derecha
141
+ const expandedWidth = parseInt(props.expandedWidth);
142
+ if (rect.left + expandedWidth > viewportWidth) {
143
+ // Si se sale por la derecha, ajustamos el ancho para que quepa
144
+ const availableWidth = viewportWidth - rect.left - 32; // 32px de margen
145
+ expandedContainer.value.style.width = `${availableWidth}px`;
146
+ }
147
+ }
148
+ };
149
+
150
+ const checkMobileView = () => {
151
+ isMobileView.value = window.innerWidth < 768;
152
+ };
153
+
154
+ const handleMobileSearchClick = () => {
155
+ isMobileSearchOpen.value = true;
156
+ nextTick(() => {
157
+ if (expandedContainer.value) {
158
+ const input = expandedContainer.value.querySelector('input');
159
+ input?.focus();
160
+ }
161
+ });
162
+ };
163
+
164
+ const handleMobileSearchClose = () => {
165
+ // Primero ocultamos con la transición
166
+ const panel = document.querySelector('.dsEcom-fixed') as HTMLElement;
167
+ if (panel) {
168
+ panel.style.opacity = '0';
169
+ panel.style.transform = 'translateY(100%)';
170
+ }
171
+
172
+ // Luego actualizamos el estado después de la transición
173
+ setTimeout(() => {
174
+ isMobileSearchOpen.value = false;
175
+ searchQuery.value = '';
176
+ emit('update:modelValue', '');
177
+ }, 300);
178
+ };
179
+
180
+ const handleMobileSearch = () => {
181
+ if (searchQuery.value && searchQuery.value.trim()) {
182
+ emit("search", searchQuery.value);
183
+ emit("update:modelValue", searchQuery.value);
184
+ emit("click:search-icon");
185
+ handleMobileSearchClose();
186
+ }
187
+ };
188
+
189
+ const handleMobileClear = () => {
190
+ clearSearch();
191
+ };
192
+
193
+ onMounted(() => {
194
+ checkMobileView();
195
+ window.addEventListener('resize', updateExpandedPosition);
196
+ window.addEventListener('resize', checkMobileView);
197
+ });
198
+
199
+ onBeforeUnmount(() => {
200
+ window.removeEventListener('resize', updateExpandedPosition);
201
+ window.removeEventListener('resize', checkMobileView);
202
+ });
203
+
204
+ watch(isExpanded, (newValue) => {
205
+ if (newValue) {
206
+ nextTick(updateExpandedPosition);
207
+ }
208
+ });
209
+
210
+ watch(() => props.forceClose, (newValue) => {
211
+ if (newValue) {
212
+ closeSearch();
213
+ }
214
+ });
215
+
216
+ watch(() => props.modelValue, (newValue) => {
217
+ if (newValue !== searchQuery.value) {
218
+ searchQuery.value = newValue;
219
+ }
220
+ });
221
+ </script>
222
+
223
+ <template>
224
+ <div class="dsEcom-relative">
225
+ <!-- Versión móvil - Solo botón de búsqueda -->
226
+ <button
227
+ v-show="isMobileView && !isMobileSearchOpen"
228
+ @click="handleMobileSearchClick"
229
+ class="dsEcom-p-2 dsEcom-rounded-lg dsEcom-transition-all dsEcom-duration-300 dsEcom-transform-gpu"
230
+ :class="[buttonColorClass]"
231
+ >
232
+ <LauEcomUpcIconSearch
233
+ width="24"
234
+ height="24"
235
+ :class="[buttonTextColorClass]"
236
+ />
237
+ </button>
238
+
239
+ <!-- Capa blanca móvil -->
240
+ <div
241
+ v-show="isMobileView && isMobileSearchOpen"
242
+ class="dsEcom-fixed dsEcom-inset-0 dsEcom-z-50 dsEcom-transition-all dsEcom-duration-300 dsEcom-transform-gpu"
243
+ :class="[mobilePanelClass]"
244
+ :style="{
245
+ opacity: isMobileSearchOpen ? '1' : '0',
246
+ transform: isMobileSearchOpen ? 'translateY(0)' : 'translateY(100%)'
247
+ }"
248
+ >
249
+ <div class="dsEcom-flex dsEcom-items-center dsEcom-gap-2 dsEcom-p-4">
250
+ <!-- Input y botones -->
251
+ <div class="dsEcom-flex dsEcom-items-center dsEcom-gap-2 dsEcom-flex-1 dsEcom-transform-gpu">
252
+ <button
253
+ @click="handleMobileSearchClose"
254
+ class="dsEcom-p-2 dsEcom-rounded-lg dsEcom-transition-all dsEcom-duration-300 dsEcom-transform-gpu"
255
+ :class="[arrowButtonClass]"
256
+ >
257
+ <LauEcomUpcIconNavArrow
258
+ width="24"
259
+ height="24"
260
+ class="dsEcom-transform dsEcom-rotate-90 dsEcom-transition-all dsEcom-duration-300 dsEcom-transform-gpu"
261
+ :class="[arrowColorClass]"
262
+ color="currentColor"
263
+ />
264
+ </button>
265
+
266
+ <div class="dsEcom-flex dsEcom-items-center dsEcom-gap-0 dsEcom-flex-1 dsEcom-transform-gpu">
267
+ <input
268
+ v-model="searchQuery"
269
+ type="text"
270
+ :placeholder="placeholder"
271
+ :disabled="isDisabled"
272
+ :style="{
273
+ width: props.mobileInputWidth,
274
+ height: props.mobileInputHeight
275
+ }"
276
+ class="lau-ecom-input dsEcom-pl-4 dsEcom-pr-4 dsEcom-border dsEcom-border-neutral-80 dsEcom-rounded-l-lg dsEcom-focus:outline-none dsEcom-focus:ring-2 dsEcom-focus:ring-primary-60 dsEcom-flex-1 dsEcom-transition-all dsEcom-duration-300 dsEcom-transform-gpu"
277
+ @input="handleInput"
278
+ @keyup.enter="handleMobileSearch"
279
+ />
280
+ <button
281
+ @click="handleMobileSearch"
282
+ :style="{ height: props.mobileInputHeight }"
283
+ :class="[
284
+ 'dsEcom-px-3 dsEcom-rounded-r-lg dsEcom-border-0 dsEcom-transition-all dsEcom-duration-300 dsEcom-transform-gpu',
285
+ buttonColorClass,
286
+ buttonTextColorClass
287
+ ]"
288
+ >
289
+ <LauEcomUpcIconSearch width="20" height="20" color="currentColor" />
290
+ </button>
291
+ </div>
292
+
293
+ <button
294
+ v-show="searchQuery.length >= 3"
295
+ @click="handleMobileClear"
296
+ class="dsEcom-p-2 dsEcom-text-neutral-100 hover:dsEcom-text-neutral-80 dsEcom-transition-all dsEcom-duration-300 dsEcom-transform-gpu"
297
+ >
298
+ <LauEcomUpcIconClose width="16" height="16" />
299
+ </button>
300
+ </div>
301
+ </div>
302
+ </div>
303
+
304
+ <!-- Versión desktop -->
305
+ <div v-show="!isMobileView" class="dsEcom-transition-all dsEcom-duration-300 dsEcom-transform-gpu">
306
+ <div
307
+ :class="[containerClasses, props.containerClass]"
308
+ ref="originalContainer"
309
+ >
310
+ <div class="dsEcom-relative" :class="{ 'dsEcom-invisible': isExpanded }">
311
+ <input
312
+ v-model="searchQuery"
313
+ type="text"
314
+ :placeholder="placeholder"
315
+ :disabled="isDisabled"
316
+ :class="[
317
+ 'lau-ecom-input dsEcom-border dsEcom-border-neutral-80 dsEcom-pl-4 dsEcom-pr-24 dsEcom-w-full dsEcom-focus:outline-none dsEcom-focus:ring-2 dsEcom-focus:ring-primary-60 dsEcom-transition-all dsEcom-duration-300',
318
+ props.inputClass,
319
+ { 'dsEcom-opacity-0': isExpanded }
320
+ ]"
321
+ @focus="handleFocus"
322
+ @input="handleInput"
323
+ @keyup.enter="handleSearch"
324
+ />
325
+ <div class="dsEcom-absolute dsEcom-right-0 dsEcom-inset-y-0 dsEcom-flex dsEcom-items-stretch">
326
+ <button
327
+ v-if="searchQuery.length >= 3"
328
+ @click="clearSearch"
329
+ :class="[
330
+ 'dsEcom-flex dsEcom-items-center dsEcom-px-1.5 dsEcom-text-neutral-100 hover:dsEcom-text-neutral-80 dsEcom-transition-all dsEcom-duration-300',
331
+ props.inputClass
332
+ ]"
333
+ >
334
+ <LauEcomUpcIconClose width="16" height="16" />
335
+ </button>
336
+
337
+ <button
338
+ @click="handleSearch"
339
+ :class="[
340
+ 'dsEcom-flex dsEcom-items-center dsEcom-px-3 dsEcom-transition-all dsEcom-duration-300',
341
+ props.inputClass?.includes('rounded') ? props.inputClass : 'dsEcom-rounded-r-lg',
342
+ props.buttonColorClass,
343
+ props.buttonTextColorClass,
344
+ 'dsEcom-border-0'
345
+ ]"
346
+ style="margin: 1px; height: calc(100% - 2px);"
347
+ >
348
+ <LauEcomUpcIconSearch width="20" height="20" color="currentColor" />
349
+ </button>
350
+ </div>
351
+ </div>
352
+ </div>
353
+
354
+ <!-- Overlay -->
355
+ <div
356
+ v-show="isExpanded"
357
+ :class="[
358
+ overlayClasses,
359
+ 'dsEcom-transition-opacity dsEcom-duration-300 dsEcom-ease-in-out'
360
+ ]"
361
+ @click="closeSearch"
362
+ ></div>
363
+
364
+ <!-- Versión expandida -->
365
+ <div
366
+ v-show="isExpanded"
367
+ class="dsEcom-fixed dsEcom-z-50 dsEcom-shadow-lg dsEcom-overflow-hidden dsEcom-transition-all dsEcom-duration-300"
368
+ :class="[
369
+ props.inputClass?.includes('rounded') ? props.inputClass : 'dsEcom-rounded-lg',
370
+ expandedBackgroundClass
371
+ ]"
372
+ ref="expandedContainer"
373
+ >
374
+ <div class="dsEcom-relative">
375
+ <input
376
+ v-model="searchQuery"
377
+ type="text"
378
+ :placeholder="placeholder"
379
+ :disabled="isDisabled"
380
+ :class="[
381
+ 'lau-ecom-input dsEcom-border dsEcom-border-neutral-80 dsEcom-pl-4 dsEcom-pr-24 dsEcom-w-full dsEcom-focus:outline-none dsEcom-focus:ring-2 dsEcom-focus:ring-primary-60 dsEcom-transition-all dsEcom-duration-300',
382
+ props.inputClass
383
+ ]"
384
+ @input="handleInput"
385
+ @keyup.enter="handleSearch"
386
+ autofocus
387
+ />
388
+ <div class="dsEcom-absolute dsEcom-right-0 dsEcom-inset-y-0 dsEcom-flex dsEcom-items-stretch">
389
+ <button
390
+ v-if="searchQuery.length >= 3"
391
+ @click="clearSearch"
392
+ :class="[
393
+ 'dsEcom-flex dsEcom-items-center dsEcom-px-1.5 dsEcom-text-neutral-100 hover:dsEcom-text-neutral-80 dsEcom-transition-all dsEcom-duration-300',
394
+ props.inputClass
395
+ ]"
396
+ >
397
+ <LauEcomUpcIconClose width="16" height="16" />
398
+ </button>
399
+
400
+ <button
401
+ @click="handleSearch"
402
+ :class="[
403
+ 'dsEcom-flex dsEcom-items-center dsEcom-px-3 dsEcom-transition-all dsEcom-duration-300',
404
+ { 'dsEcom-rounded-r-lg': !props.inputClass?.includes('rounded') },
405
+ props.buttonColorClass,
406
+ props.buttonTextColorClass,
407
+ 'dsEcom-border-0'
408
+ ]"
409
+ style="margin: 1px; height: calc(100% - 2px);"
410
+ >
411
+ <LauEcomUpcIconSearch width="20" height="20" color="currentColor" />
412
+ </button>
413
+ </div>
414
+ </div>
415
+ </div>
416
+ </div>
417
+ </div>
418
+ </template>
419
+
420
+ <style scoped>
421
+ .lau-ecom-input {
422
+ transition: all 0.3s ease-in-out;
423
+ backface-visibility: hidden;
424
+ transform: translateZ(0);
425
+ -webkit-font-smoothing: antialiased;
426
+ -moz-osx-font-smoothing: grayscale;
427
+ will-change: transform, opacity;
428
+ }
429
+
430
+ .dsEcom-transform-gpu {
431
+ transform: translateZ(0);
432
+ backface-visibility: hidden;
433
+ perspective: 1000px;
434
+ will-change: transform, opacity;
435
+ }
436
+
437
+ /* Agregamos transiciones específicas para móvil */
438
+ @media (max-width: 768px) {
439
+ .dsEcom-fixed {
440
+ transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
441
+ will-change: transform, opacity;
442
+ }
443
+ }
444
+ </style>
445
+
323
446