kupos-ui-components-lib 10.4.5 → 10.4.7

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/dist/styles.css CHANGED
@@ -189,9 +189,6 @@
189
189
  .m-\[auto\] {
190
190
  margin: auto;
191
191
  }
192
- .mx-\[6px\] {
193
- margin-inline: 6px;
194
- }
195
192
  .mx-auto {
196
193
  margin-inline: auto;
197
194
  }
@@ -375,12 +372,12 @@
375
372
  .h-\[12px\] {
376
373
  height: 12px;
377
374
  }
378
- .h-\[13px\] {
379
- height: 13px;
380
- }
381
375
  .h-\[14px\] {
382
376
  height: 14px;
383
377
  }
378
+ .h-\[15px\] {
379
+ height: 15px;
380
+ }
384
381
  .h-\[16px\] {
385
382
  height: 16px;
386
383
  }
@@ -432,12 +429,12 @@
432
429
  .w-\[12px\] {
433
430
  width: 12px;
434
431
  }
435
- .w-\[13px\] {
436
- width: 13px;
437
- }
438
432
  .w-\[14px\] {
439
433
  width: 14px;
440
434
  }
435
+ .w-\[15px\] {
436
+ width: 15px;
437
+ }
441
438
  .w-\[16px\] {
442
439
  width: 16px;
443
440
  }
@@ -1,7 +1,6 @@
1
1
  import React from "react";
2
2
  import LottiePlayer from "../../assets/LottiePlayer";
3
3
  import commonService from "../../utils/CommonService";
4
- import flameAnimation from "../../assets/images/anims/service_list/flame_anim.json";
5
4
  const HARDCODED_OPERATORS = [
6
5
  {
7
6
  logo: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Turbus_logo.svg/320px-Turbus_logo.svg.png",
@@ -23,7 +22,7 @@ const HARDCODED_OPERATORS = [
23
22
  },
24
23
  ];
25
24
  const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut, cityOrigin, cityDestination, renderIcon, viewersConfig, isFeatureDropDownExpand, onToggleExpand, ticketQuantity = 1, onIncreaseTicketQuantity, onDecreaseTicketQuantity, onBookButtonPress, selectedTimeSlot, onTimeSlotChange, isTimeDropdownOpen, onTimeDropdownToggle, wowDealData = undefined, }) => {
26
- var _a, _b, _c, _d, _e, _f, _g;
25
+ var _a, _b, _c, _d, _e, _f, _g, _h;
27
26
  // Use wow_deal data if available, otherwise fall back to serviceItem operators or hardcoded
28
27
  const operators = ((_a = wowDealData === null || wowDealData === void 0 ? void 0 : wowDealData.services) === null || _a === void 0 ? void 0 : _a.length) > 0
29
28
  ? wowDealData.services.slice(0, 3).map((service) => ({
@@ -92,8 +91,32 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
92
91
  selectedSlotService.original_price) *
93
92
  100)
94
93
  : savingsPercent;
94
+ // Convert "HH:MM" (24h) → "H:MM AM/PM"
95
+ const formatTimeAmPm = (time) => {
96
+ const match = time.match(/(\d{1,2}):(\d{2})/);
97
+ if (!match)
98
+ return time;
99
+ let h = parseInt(match[1], 10);
100
+ const m = match[2];
101
+ const period = h >= 12 ? "PM" : "AM";
102
+ h = h % 12 || 12;
103
+ return `${h}:${m} ${period}`;
104
+ };
105
+ // Convert all HH:MM occurrences in a label string to AM/PM
106
+ const formatLabelAmPm = (label) => label.replace(/(\d{1,2}):(\d{2})/g, (_, hh, mm) => {
107
+ let h = parseInt(hh, 10);
108
+ const period = h >= 12 ? "PM" : "AM";
109
+ h = h % 12 || 12;
110
+ return `${h}:${mm} ${period}`;
111
+ });
95
112
  // The label shown on the dropdown button
96
- const departureRange = (activeSlot === null || activeSlot === void 0 ? void 0 : activeSlot.label) || `Entre ${dealWindowFrom} y ${dealWindowTo}`;
113
+ const departureRange = formatLabelAmPm((activeSlot === null || activeSlot === void 0 ? void 0 : activeSlot.label) || `Entre ${dealWindowFrom} y ${dealWindowTo}`);
114
+ const currentWindowFrom = (activeSlot === null || activeSlot === void 0 ? void 0 : activeSlot.label)
115
+ ? activeSlot.label.split(" - ")[0]
116
+ : dealWindowFrom;
117
+ const currentWindowTo = (activeSlot === null || activeSlot === void 0 ? void 0 : activeSlot.label)
118
+ ? activeSlot.label.split(" - ")[1]
119
+ : dealWindowTo;
97
120
  const isItemExpanded = serviceItem.id === isFeatureDropDownExpand ||
98
121
  isFeatureDropDownExpand === true;
99
122
  const isThisTimeDropdownOpen = isTimeDropdownOpen === serviceItem.id;
@@ -104,7 +127,7 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
104
127
  {
105
128
  icon: "flexible",
106
129
  name: "Salida flexible",
107
- text: `Viajas en un horario entre las ${dealWindowFrom} y las ${dealWindowTo} del día elegido.`,
130
+ text: `Viajas en un horario entre las ${currentWindowFrom} y las ${currentWindowTo} del día elegido.`,
108
131
  },
109
132
  {
110
133
  icon: "bus",
@@ -159,7 +182,7 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
159
182
  React.createElement("div", { className: "bold-text font-[9px]", style: {
160
183
  backgroundColor: "#FF5C60",
161
184
  padding: "4px 8px",
162
- borderRadius: "10px",
185
+ borderRadius: "6px",
163
186
  color: "#fff",
164
187
  animation: "pulse-zoom 2s ease-in-out infinite",
165
188
  whiteSpace: "nowrap",
@@ -172,68 +195,77 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
172
195
  React.createElement("div", { id: `service-card-${serviceItem.id}`, className: "bg-[#0C1421] text-white mx-auto relative rounded-[14px] p-[14px] text-[13.33px]" },
173
196
  React.createElement("div", { className: "flex flex-col gap-[10px]" },
174
197
  React.createElement("div", { className: " text-[white]" },
175
- React.createElement("div", { className: "flex flex-col gap-[10px] relative" },
176
- React.createElement("div", { className: "flex items-center gap-[6px]" },
177
- React.createElement("img", { src: (_d = serviceItem.icons) === null || _d === void 0 ? void 0 : _d.whiteOrigin, alt: "origin", className: `w-[13px] h-[13px] shrink-0 ${isSoldOut ? "grayscale" : ""}` }),
178
- React.createElement("span", { className: "text-[14px] bold-text" }, cityOrigin === null || cityOrigin === void 0 ? void 0 : cityOrigin.label.split(",")[0]),
179
- React.createElement("span", { className: "mx-[6px] text-[14px] bold-text" }, "\u2192"),
180
- React.createElement("img", { src: (_e = serviceItem.icons) === null || _e === void 0 ? void 0 : _e.whiteDestination, alt: "destination", className: `w-[13px] h-[13px] shrink-0 ${isSoldOut ? "grayscale" : ""}`, style: { opacity: isSoldOut ? 0.5 : 1 } }),
181
- React.createElement("span", { className: "text-[14px] bold-text" }, cityDestination === null || cityDestination === void 0 ? void 0 : cityDestination.label.split(",")[0])),
182
- React.createElement("div", { className: "flex items-center gap-[6px]" },
183
- React.createElement("div", { className: "kupos-time-dd relative", tabIndex: 0, onBlur: (e) => {
184
- if (!e.currentTarget.contains(e.relatedTarget)) {
185
- onTimeDropdownToggle === null || onTimeDropdownToggle === void 0 ? void 0 : onTimeDropdownToggle(null);
186
- }
187
- }, style: { outline: "none" } },
188
- React.createElement("button", { type: "button", onClick: () => onTimeDropdownToggle === null || onTimeDropdownToggle === void 0 ? void 0 : onTimeDropdownToggle(isThisTimeDropdownOpen ? null : serviceItem.id), className: "flex cursor-pointer select-none items-center gap-[6px] border-none bg-transparent p-0 bold-text text-[13px] text-[white]" },
189
- React.createElement("span", null, departureRange),
190
- React.createElement("img", { src: (_f = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.icons) === null || _f === void 0 ? void 0 : _f.downArrow, alt: "down arrow", className: `kupos-time-chevron transition-transform duration-200 ${isThisTimeDropdownOpen ? "rotate-180" : "rotate-0"}`, style: {
191
- width: "12px",
192
- height: "8px",
193
- filter: "brightness(0) invert(1)",
194
- } })),
195
- isThisTimeDropdownOpen && (React.createElement(React.Fragment, null,
196
- React.createElement("div", { className: "absolute left-0 top-[calc(100%+10px)]", style: {
197
- zIndex: 20,
198
- backgroundColor: "#fff",
199
- borderRadius: "14px",
200
- minWidth: "190px",
201
- boxShadow: "0 8px 32px rgba(0,0,0,0.28)",
202
- overflow: "hidden",
203
- padding: "6px 0",
204
- } }, availableTimeSlots.map((slot) => {
205
- const isActive = slot.label === selectedTimeSlot ||
206
- slot === activeSlot;
207
- // Count services in this slot
208
- const count = services.filter((s) => {
209
- const depMin = commonService.timeToMinutes(s.departure_time);
210
- return depMin >= slot.start && depMin < slot.end;
211
- }).length;
212
- return (React.createElement("button", { key: slot.label, type: "button", onClick: () => {
213
- onTimeSlotChange === null || onTimeSlotChange === void 0 ? void 0 : onTimeSlotChange(slot.label);
214
- onTimeDropdownToggle === null || onTimeDropdownToggle === void 0 ? void 0 : onTimeDropdownToggle(null);
215
- // const slotServices = services.filter((s) => {
216
- // const depMin = commonService.timeToMinutes(
217
- // s.departure_time,
218
- // );
219
- // return (
220
- // depMin >= slot.start && depMin < slot.end
221
- // );
222
- // });
223
- // console.log(
224
- // "Selected slot label:",
225
- // slot.label,
226
- // );
227
- // console.log(
228
- // "Services in selected slot:",
229
- // slotServices,
230
- // );
231
- }, className: `flex w-full cursor-pointer items-center justify-between gap-[10px] border-none px-[12px] py-[9px] text-left text-[13px] ${isActive ? "bg-[#FF5C60] font-bold text-[white]" : "bg-transparent font-normal text-[#1a1a1a]"}` },
232
- React.createElement("span", null, slot.label),
233
- count > 0 && (React.createElement("span", { className: `text-[11px] rounded-full px-[6px] py-[2px] font-bold ${isActive
234
- ? "bg-[white] text-[#FF5C60]"
235
- : "bg-[#FF5C60] text-[white]"}` }, count))));
236
- })))))))),
198
+ React.createElement("div", { className: "flex flex-col gap-[6px] relative" },
199
+ React.createElement("div", { className: "flex items-start gap-[10px]" },
200
+ React.createElement("div", { className: "flex flex-col items-center shrink-0", style: { paddingTop: "2px" } },
201
+ React.createElement("img", { src: (_d = serviceItem.icons) === null || _d === void 0 ? void 0 : _d.whiteOrigin, alt: "origin", className: `w-[14px] h-[14px] shrink-0 ${isSoldOut ? "grayscale" : ""}` }),
202
+ React.createElement("div", { style: {
203
+ width: "1.5px",
204
+ flex: 1,
205
+ minHeight: "8px",
206
+ backgroundColor: "rgba(255,255,255,0.35)",
207
+ margin: "2px 0",
208
+ } }),
209
+ React.createElement("img", { src: (_e = serviceItem.icons) === null || _e === void 0 ? void 0 : _e.whiteDestination, alt: "destination", className: `w-[15px] h-[15px] shrink-0 ${isSoldOut ? "grayscale" : ""}`, style: { opacity: isSoldOut ? 0.5 : 1 } })),
210
+ React.createElement("div", { className: "flex flex-col gap-[6px]" },
211
+ React.createElement("div", { className: "flex items-center gap-[6px]" },
212
+ React.createElement("span", { className: "text-[14px] bold-text" }, cityOrigin === null || cityOrigin === void 0 ? void 0 : cityOrigin.label.split(",")[0]),
213
+ React.createElement("span", { className: "text-[14px] bold-text" }, "\u2192"),
214
+ React.createElement("span", { className: "text-[14px] bold-text" }, cityDestination === null || cityDestination === void 0 ? void 0 : cityDestination.label.split(",")[0])),
215
+ React.createElement("div", { className: "kupos-time-dd relative", tabIndex: 0, onBlur: (e) => {
216
+ if (!e.currentTarget.contains(e.relatedTarget)) {
217
+ onTimeDropdownToggle === null || onTimeDropdownToggle === void 0 ? void 0 : onTimeDropdownToggle(null);
218
+ }
219
+ }, style: { outline: "none", marginTop: "3px" } },
220
+ React.createElement("button", { type: "button", onClick: () => onTimeDropdownToggle === null || onTimeDropdownToggle === void 0 ? void 0 : onTimeDropdownToggle(isThisTimeDropdownOpen ? null : serviceItem.id), className: "flex cursor-pointer select-none items-center gap-[6px] border-none bg-transparent p-0 bold-text text-[13px] text-[white]" },
221
+ React.createElement("span", null, departureRange),
222
+ React.createElement("img", { src: (_f = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.icons) === null || _f === void 0 ? void 0 : _f.downArrow, alt: "down arrow", className: `kupos-time-chevron transition-transform duration-200 ${isThisTimeDropdownOpen ? "rotate-180" : "rotate-0"}`, style: {
223
+ width: "12px",
224
+ height: "8px",
225
+ filter: "brightness(0) invert(1)",
226
+ } })),
227
+ isThisTimeDropdownOpen && (React.createElement(React.Fragment, null,
228
+ React.createElement("div", { className: "absolute left-0 top-[calc(100%+10px)]", style: {
229
+ zIndex: 20,
230
+ backgroundColor: "#fff",
231
+ borderRadius: "14px",
232
+ minWidth: "190px",
233
+ boxShadow: "0 8px 32px rgba(0,0,0,0.28)",
234
+ overflow: "hidden",
235
+ padding: "6px 0",
236
+ } }, availableTimeSlots.map((slot) => {
237
+ const isActive = slot.label === selectedTimeSlot ||
238
+ slot === activeSlot;
239
+ // Count services in this slot
240
+ const count = services.filter((s) => {
241
+ const depMin = commonService.timeToMinutes(s.departure_time);
242
+ return (depMin >= slot.start && depMin < slot.end);
243
+ }).length;
244
+ return (React.createElement("button", { key: slot.label, type: "button", onClick: () => {
245
+ onTimeSlotChange === null || onTimeSlotChange === void 0 ? void 0 : onTimeSlotChange(slot.label);
246
+ onTimeDropdownToggle === null || onTimeDropdownToggle === void 0 ? void 0 : onTimeDropdownToggle(null);
247
+ // const slotServices = services.filter((s) => {
248
+ // const depMin = commonService.timeToMinutes(
249
+ // s.departure_time,
250
+ // );
251
+ // return (
252
+ // depMin >= slot.start && depMin < slot.end
253
+ // );
254
+ // });
255
+ // console.log(
256
+ // "Selected slot label:",
257
+ // slot.label,
258
+ // );
259
+ // console.log(
260
+ // "Services in selected slot:",
261
+ // slotServices,
262
+ // );
263
+ }, className: `flex w-full cursor-pointer items-center justify-between gap-[10px] border-none px-[12px] py-[9px] text-left text-[13px] ${isActive ? "bg-[#FF5C60] font-bold text-[white]" : "bg-transparent font-normal text-[#1a1a1a]"}` },
264
+ React.createElement("span", null, slot.label),
265
+ count > 0 && (React.createElement("span", { className: `text-[11px] rounded-full px-[6px] py-[2px] font-bold ${isActive
266
+ ? "bg-[white] text-[#FF5C60]"
267
+ : "bg-[#FF5C60] text-[white]"}` }, count))));
268
+ }))))))))),
237
269
  React.createElement("div", { className: "border-t border-[#363c48] my-[8px]" }),
238
270
  React.createElement("div", null,
239
271
  React.createElement("span", { className: "block w-full text-[14px] bold-text text-[white] mb-[10px]", style: { textAlign: "center" } },
@@ -244,29 +276,38 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
244
276
  "operadores compitiendo ",
245
277
  React.createElement("br", null),
246
278
  "por tu compra"),
247
- React.createElement("div", { className: "flex gap-[8px] text-[white]", style: { width: "100%" } }, (servicesInActiveSlot.length > 0
248
- ? servicesInActiveSlot.slice(0, 3).map((s) => ({
249
- logo: s.operator_logo_url,
250
- name: s.operator_name,
251
- time: s.departure_time,
252
- seatsAvailable: `${s.available_seats} disponibles`,
253
- }))
254
- : operators).map((op, idx) => (React.createElement("div", { key: idx, className: "flex min-w-0 flex-col items-center justify-center gap-[8px] rounded-[8px]", style: {
255
- flex: 1,
256
- minWidth: 0,
257
- height: "100px",
258
- border: "1px solid #363c48",
259
- backgroundColor: "#1a202e",
260
- padding: "14px 6px",
261
- } },
262
- React.createElement("img", { src: op.logo, alt: op.name, onError: (e) => {
263
- var _a;
264
- e.currentTarget.src =
265
- ((_a = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.operator_details) === null || _a === void 0 ? void 0 : _a[0]) ||
266
- "/images/service-list/bus-icon.svg";
267
- }, className: `h-[24px] max-w-full object-contain ${isSoldOut ? "grayscale" : ""}` }),
268
- React.createElement("span", { className: "text-[12px] truncate max-w-full text-center " }, op.name),
269
- React.createElement("span", { className: "text-[11px] whitespace-nowrap" }, op === null || op === void 0 ? void 0 : op.seatsAvailable))))),
279
+ React.createElement("div", { className: "flex gap-[8px] text-[white]", style: { width: "100%", justifyContent: "center" } }, (() => {
280
+ const displayOps = servicesInActiveSlot.length > 0
281
+ ? servicesInActiveSlot.slice(0, 3).map((s) => ({
282
+ logo: s.operator_logo_url,
283
+ name: s.operator_name,
284
+ time: s.departure_time,
285
+ seatsAvailable: `${s.available_seats} disponibles`,
286
+ }))
287
+ : operators;
288
+ const isSingle = displayOps.length === 1;
289
+ return displayOps.map((op, idx) => (React.createElement("div", { key: idx, className: "flex min-w-0 flex-col items-center justify-center gap-[8px] rounded-[8px]", style: {
290
+ flex: isSingle ? "none" : 1,
291
+ width: isSingle ? "32%" : undefined,
292
+ margin: isSingle ? "0 auto" : undefined,
293
+ minWidth: 0,
294
+ height: "100px",
295
+ border: "1px solid #363c48",
296
+ backgroundColor: "#1a202e",
297
+ padding: "14px 6px",
298
+ } },
299
+ React.createElement("img", { src: op.logo, alt: op.name, onError: (e) => {
300
+ var _a;
301
+ e.currentTarget.src =
302
+ ((_a = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.operator_details) === null || _a === void 0 ? void 0 : _a[0]) ||
303
+ "/images/service-list/bus-icon.svg";
304
+ }, className: `object-contain ${isSoldOut ? "grayscale" : ""}`, style: {
305
+ height: "24px",
306
+ maxWidth: "100%",
307
+ } }),
308
+ React.createElement("span", { className: "text-[12px] truncate max-w-full text-center" }, op.name),
309
+ React.createElement("span", { className: "text-[11px] whitespace-nowrap" }, op === null || op === void 0 ? void 0 : op.seatsAvailable))));
310
+ })()),
270
311
  React.createElement("div", { className: "flex w-full items-center justify-center gap-[6px] text-[12px] mt-[12px]", style: {
271
312
  padding: "4px 14px",
272
313
  } },
@@ -375,19 +416,18 @@ const FeatureServiceUiMobile = ({ serviceItem, showTopLabel, colors, isSoldOut,
375
416
  React.createElement("span", { className: "bold-text" }, step.name),
376
417
  React.createElement("span", null, step.text)))))))))),
377
418
  React.createElement("div", { className: "absolute -top-[13px] left-0 w-full flex items-center justify-center gap-[12px] z-10 " },
378
- React.createElement("div", { className: "flex items-center gap-[6px] py-[5px] px-[14px] rounded-[38px] text-[12.5px] bg-[#FF8F45] text-white whitespace-nowrap" },
419
+ React.createElement("div", { className: "flex items-center gap-[6px] py-[5px] px-[14px] rounded-[38px] text-[12.5px] text-white whitespace-nowrap", style: { backgroundColor: "rgba(255, 222, 223, 0.93)" } },
379
420
  React.createElement("span", { className: "flex items-center" },
380
- React.createElement("div", { className: "mb-[2px]" },
381
- React.createElement(LottiePlayer, { animationData: flameAnimation, width: "16px", height: "16px" })),
382
- React.createElement("span", { className: "bold-text text-[white] ml-[3px]" }, "Remate\u00A0"),
421
+ ((_h = serviceItem === null || serviceItem === void 0 ? void 0 : serviceItem.icons) === null || _h === void 0 ? void 0 : _h.fireIcon) ? (React.createElement("img", { src: serviceItem.icons.fireIcon, alt: "discount", className: "h-[12px] w-[12px] object-contain mr-[5px]", style: { filter: isSoldOut ? "grayscale" : "" } })) : null,
422
+ React.createElement("span", { className: "bold-text text-[#464647] ml-[3px]" }, "Remate\u00A0"),
383
423
  " ",
384
- React.createElement("span", { className: "text-[white]" }, "t\u00E9rmina en"),
424
+ React.createElement("span", { className: "text-[#464647]" }, "t\u00E9rmina en\u00A0"),
385
425
  " ",
386
- React.createElement("span", { className: "bold-text text-end", ref: (node) => commonService.startCountdown(node, getCountdownSeconds()), style: {
426
+ React.createElement("span", { className: "bold-text text-end", ref: (node) => commonService.startDealCountdown(node, getCountdownSeconds()), style: {
387
427
  fontVariantNumeric: "tabular-nums",
388
428
  display: "inline-block",
389
- color: "#fff",
390
- minWidth: "40px",
429
+ color: "#FF5C60",
430
+ minWidth: "50px",
391
431
  } }))))));
392
432
  };
393
433
  export default FeatureServiceUiMobile;
@@ -31,6 +31,7 @@ const HARDCODED_OPERATORS = [
31
31
  seatsAvailable: "3 disponibles",
32
32
  },
33
33
  ];
34
+ const expandedStateMap = {};
34
35
  const FeatureServiceUi = ({ serviceItem, showTopLabel, getAnimationIcon, cityOrigin, cityDestination, renderIcon, viewersConfig, isFeatureDropDownExpand, onToggleExpand, ticketQuantity = 1, onIncreaseTicketQuantity, onDecreaseTicketQuantity, onBookButtonPress, selectedTimeSlot, onTimeSlotChange, isTimeDropdownOpen, onTimeDropdownToggle, wowDealData = undefined, }) => {
35
36
  var _a, _b, _c, _d, _e;
36
37
  console.log("🚀 ~ FeatureServiceUi ~ selectedTimeSlot:", selectedTimeSlot);
@@ -112,8 +113,10 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, getAnimationIcon, cityOri
112
113
  const currentWindowTo = (activeSlot === null || activeSlot === void 0 ? void 0 : activeSlot.label)
113
114
  ? activeSlot.label.split(" - ")[1]
114
115
  : dealWindowTo;
115
- const isItemExpanded = serviceItem.id === isFeatureDropDownExpand ||
116
- isFeatureDropDownExpand === true;
116
+ if (expandedStateMap[serviceItem.id] === undefined) {
117
+ expandedStateMap[serviceItem.id] = true;
118
+ }
119
+ const isItemExpanded = expandedStateMap[serviceItem.id];
117
120
  const isThisTimeDropdownOpen = isTimeDropdownOpen === serviceItem.id;
118
121
  const canDecreaseTicketQuantity = ticketQuantity > 1;
119
122
  const availableSeats = (_c = selectedSlotService === null || selectedSlotService === void 0 ? void 0 : selectedSlotService.available_seats) !== null && _c !== void 0 ? _c : maxSeatsPerBooking;
@@ -403,18 +406,41 @@ const FeatureServiceUi = ({ serviceItem, showTopLabel, getAnimationIcon, cityOri
403
406
  animation: "feature-spin 0.7s linear infinite",
404
407
  flexShrink: 0,
405
408
  } })))),
406
- React.createElement("div", { className: `absolute bottom-[11px] right-[18px] cursor-pointer transition-transform duration-300 ease-in-out ${isItemExpanded ? "rotate-180" : ""}`, onClick: onToggleExpand },
409
+ React.createElement("div", { id: `feature-arrow-${serviceItem.id}`, className: `absolute bottom-[11px] right-[18px] cursor-pointer transition-transform duration-300 ease-in-out ${isItemExpanded ? "rotate-180" : ""}`, onClick: (e) => {
410
+ expandedStateMap[serviceItem.id] =
411
+ !expandedStateMap[serviceItem.id];
412
+ if (onToggleExpand)
413
+ onToggleExpand(e);
414
+ const contentNode = document.getElementById(`feature-content-${serviceItem.id}`);
415
+ const innerNode = document.getElementById(`feature-inner-content-${serviceItem.id}`);
416
+ const arrowNode = document.getElementById(`feature-arrow-${serviceItem.id}`);
417
+ if (contentNode && innerNode && arrowNode) {
418
+ const isExpanded = expandedStateMap[serviceItem.id];
419
+ if (isExpanded) {
420
+ contentNode.style.gridTemplateRows = "1fr";
421
+ contentNode.style.opacity = "1";
422
+ innerNode.className = `min-h-0 overflow-hidden px-[16px] text-[13.33px] pt-[14px] pb-[6px]`;
423
+ arrowNode.className = `absolute bottom-[11px] right-[18px] cursor-pointer transition-transform duration-300 ease-in-out rotate-180`;
424
+ }
425
+ else {
426
+ contentNode.style.gridTemplateRows = "0fr";
427
+ contentNode.style.opacity = "0";
428
+ innerNode.className = `min-h-0 overflow-hidden px-[16px] text-[13.33px] pt-0 pb-0`;
429
+ arrowNode.className = `absolute bottom-[11px] right-[18px] cursor-pointer transition-transform duration-300 ease-in-out `;
430
+ }
431
+ }
432
+ } },
407
433
  React.createElement("img", { src: (_e = serviceItem.icons) === null || _e === void 0 ? void 0 : _e.downArrow, alt: "down arrow", style: {
408
434
  width: "14px",
409
435
  height: "8px",
410
436
  filter: "brightness(0) invert(1)",
411
437
  } })))),
412
- React.createElement("div", { className: "grid", style: {
413
- gridTemplateRows: "1fr",
414
- opacity: 1,
438
+ React.createElement("div", { id: `feature-content-${serviceItem.id}`, className: "grid", style: {
439
+ gridTemplateRows: isItemExpanded ? "1fr" : "0fr",
440
+ opacity: isItemExpanded ? 1 : 0,
415
441
  transition: "grid-template-rows 300ms ease-in-out, opacity 250ms ease-in-out",
416
442
  } },
417
- React.createElement("div", { className: `min-h-0 overflow-hidden px-[16px] text-[13.33px] pt-[14px] pb-[6px]`, style: { transition: "padding 300ms ease-in-out" } },
443
+ React.createElement("div", { id: `feature-inner-content-${serviceItem.id}`, className: `min-h-0 overflow-hidden px-[16px] text-[13.33px] ${isItemExpanded ? "pt-[14px] pb-[6px]" : "pt-0 pb-0"}`, style: { transition: "padding 300ms ease-in-out" } },
418
444
  React.createElement("span", { className: "bold-text" }, "\u00BFC\u00F3mo funciona?"),
419
445
  React.createElement("div", { className: "mt-[14px] grid grid-cols-4 gap-[20px] px-[16px] " }, HOW_IT_WORKS_STEPS.map((step) => (React.createElement("div", { key: step.name, className: "flex flex-col items-center text-center text-[#272727]" },
420
446
  React.createElement(FeatureStepIcon, { icon: step.icon }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kupos-ui-components-lib",
3
- "version": "10.4.5",
3
+ "version": "10.4.7",
4
4
  "description": "A reusable UI components package",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -131,9 +131,37 @@ const FeatureServiceUiMobile = ({
131
131
  )
132
132
  : savingsPercent;
133
133
 
134
+ // Convert "HH:MM" (24h) → "H:MM AM/PM"
135
+ const formatTimeAmPm = (time: string): string => {
136
+ const match = time.match(/(\d{1,2}):(\d{2})/);
137
+ if (!match) return time;
138
+ let h = parseInt(match[1], 10);
139
+ const m = match[2];
140
+ const period = h >= 12 ? "PM" : "AM";
141
+ h = h % 12 || 12;
142
+ return `${h}:${m} ${period}`;
143
+ };
144
+
145
+ // Convert all HH:MM occurrences in a label string to AM/PM
146
+ const formatLabelAmPm = (label: string): string =>
147
+ label.replace(/(\d{1,2}):(\d{2})/g, (_, hh, mm) => {
148
+ let h = parseInt(hh, 10);
149
+ const period = h >= 12 ? "PM" : "AM";
150
+ h = h % 12 || 12;
151
+ return `${h}:${mm} ${period}`;
152
+ });
153
+
134
154
  // The label shown on the dropdown button
135
- const departureRange =
136
- activeSlot?.label || `Entre ${dealWindowFrom} y ${dealWindowTo}`;
155
+ const departureRange = formatLabelAmPm(
156
+ activeSlot?.label || `Entre ${dealWindowFrom} y ${dealWindowTo}`,
157
+ );
158
+
159
+ const currentWindowFrom = activeSlot?.label
160
+ ? activeSlot.label.split(" - ")[0]
161
+ : dealWindowFrom;
162
+ const currentWindowTo = activeSlot?.label
163
+ ? activeSlot.label.split(" - ")[1]
164
+ : dealWindowTo;
137
165
 
138
166
  const isItemExpanded =
139
167
  serviceItem.id === isFeatureDropDownExpand ||
@@ -149,7 +177,7 @@ const FeatureServiceUiMobile = ({
149
177
  {
150
178
  icon: "flexible",
151
179
  name: "Salida flexible",
152
- text: `Viajas en un horario entre las ${dealWindowFrom} y las ${dealWindowTo} del día elegido.`,
180
+ text: `Viajas en un horario entre las ${currentWindowFrom} y las ${currentWindowTo} del día elegido.`,
153
181
  },
154
182
  {
155
183
  icon: "bus",
@@ -213,7 +241,7 @@ const FeatureServiceUiMobile = ({
213
241
  style={{
214
242
  backgroundColor: "#FF5C60",
215
243
  padding: "4px 8px",
216
- borderRadius: "10px",
244
+ borderRadius: "6px",
217
245
  color: "#fff",
218
246
  animation: "pulse-zoom 2s ease-in-out infinite",
219
247
  whiteSpace: "nowrap",
@@ -230,141 +258,160 @@ const FeatureServiceUiMobile = ({
230
258
  >
231
259
  <div className="flex flex-col gap-[10px]">
232
260
  <div className=" text-[white]">
233
- <div className="flex flex-col gap-[10px] relative">
234
- <div className="flex items-center gap-[6px]">
235
- <img
236
- src={serviceItem.icons?.whiteOrigin}
237
- alt="origin"
238
- className={`w-[13px] h-[13px] shrink-0 ${
239
- isSoldOut ? "grayscale" : ""
240
- }`}
241
- />
242
-
243
- <span className="text-[14px] bold-text">
244
- {cityOrigin?.label.split(",")[0]}
245
- </span>
246
-
247
- <span className="mx-[6px] text-[14px] bold-text">→</span>
248
-
249
- <img
250
- src={serviceItem.icons?.whiteDestination}
251
- alt="destination"
252
- className={`w-[13px] h-[13px] shrink-0 ${
253
- isSoldOut ? "grayscale" : ""
254
- }`}
255
- style={{ opacity: isSoldOut ? 0.5 : 1 }}
256
- />
257
-
258
- <span className="text-[14px] bold-text">
259
- {cityDestination?.label.split(",")[0]}
260
- </span>
261
- </div>
262
-
263
- <div className="flex items-center gap-[6px]">
261
+ <div className="flex flex-col gap-[6px] relative">
262
+ {/* Origin / Destination row with connecting-line icons */}
263
+ <div className="flex items-start gap-[10px]">
264
+ {/* Icon column with connecting line */}
264
265
  <div
265
- className="kupos-time-dd relative"
266
- tabIndex={0}
267
- onBlur={(e) => {
268
- if (!e.currentTarget.contains(e.relatedTarget as Node)) {
269
- onTimeDropdownToggle?.(null);
270
- }
271
- }}
272
- style={{ outline: "none" }}
266
+ className="flex flex-col items-center shrink-0"
267
+ style={{ paddingTop: "2px" }}
273
268
  >
274
- <button
275
- type="button"
276
- onClick={() =>
277
- onTimeDropdownToggle?.(
278
- isThisTimeDropdownOpen ? null : serviceItem.id,
279
- )
280
- }
281
- className="flex cursor-pointer select-none items-center gap-[6px] border-none bg-transparent p-0 bold-text text-[13px] text-[white]"
269
+ <img
270
+ src={serviceItem.icons?.whiteOrigin}
271
+ alt="origin"
272
+ className={`w-[14px] h-[14px] shrink-0 ${isSoldOut ? "grayscale" : ""}`}
273
+ />
274
+ <div
275
+ style={{
276
+ width: "1.5px",
277
+ flex: 1,
278
+ minHeight: "8px",
279
+ backgroundColor: "rgba(255,255,255,0.35)",
280
+ margin: "2px 0",
281
+ }}
282
+ />
283
+ <img
284
+ src={serviceItem.icons?.whiteDestination}
285
+ alt="destination"
286
+ className={`w-[15px] h-[15px] shrink-0 ${isSoldOut ? "grayscale" : ""}`}
287
+ style={{ opacity: isSoldOut ? 0.5 : 1 }}
288
+ />
289
+ </div>
290
+ {/* Text column */}
291
+ <div className="flex flex-col gap-[6px]">
292
+ <div className="flex items-center gap-[6px]">
293
+ <span className="text-[14px] bold-text">
294
+ {cityOrigin?.label.split(",")[0]}
295
+ </span>
296
+ <span className="text-[14px] bold-text">→</span>
297
+ <span className="text-[14px] bold-text">
298
+ {cityDestination?.label.split(",")[0]}
299
+ </span>
300
+ </div>
301
+
302
+ <div
303
+ className="kupos-time-dd relative"
304
+ tabIndex={0}
305
+ onBlur={(e) => {
306
+ if (
307
+ !e.currentTarget.contains(e.relatedTarget as Node)
308
+ ) {
309
+ onTimeDropdownToggle?.(null);
310
+ }
311
+ }}
312
+ style={{ outline: "none", marginTop: "3px" }}
282
313
  >
283
- <span>{departureRange}</span>
284
- <img
285
- src={serviceItem?.icons?.downArrow}
286
- alt="down arrow"
287
- className={`kupos-time-chevron transition-transform duration-200 ${isThisTimeDropdownOpen ? "rotate-180" : "rotate-0"}`}
288
- style={{
289
- width: "12px",
290
- height: "8px",
291
- filter: "brightness(0) invert(1)",
292
- }}
293
- />
294
- </button>
295
- {isThisTimeDropdownOpen && (
296
- <>
297
- <div
298
- className="absolute left-0 top-[calc(100%+10px)]"
314
+ <button
315
+ type="button"
316
+ onClick={() =>
317
+ onTimeDropdownToggle?.(
318
+ isThisTimeDropdownOpen ? null : serviceItem.id,
319
+ )
320
+ }
321
+ className="flex cursor-pointer select-none items-center gap-[6px] border-none bg-transparent p-0 bold-text text-[13px] text-[white]"
322
+ >
323
+ <span>{departureRange}</span>
324
+ <img
325
+ src={serviceItem?.icons?.downArrow}
326
+ alt="down arrow"
327
+ className={`kupos-time-chevron transition-transform duration-200 ${isThisTimeDropdownOpen ? "rotate-180" : "rotate-0"}`}
299
328
  style={{
300
- zIndex: 20,
301
- backgroundColor: "#fff",
302
- borderRadius: "14px",
303
- minWidth: "190px",
304
- boxShadow: "0 8px 32px rgba(0,0,0,0.28)",
305
- overflow: "hidden",
306
- padding: "6px 0",
329
+ width: "12px",
330
+ height: "8px",
331
+ filter: "brightness(0) invert(1)",
307
332
  }}
308
- >
309
- {availableTimeSlots.map((slot) => {
310
- const isActive =
311
- slot.label === selectedTimeSlot ||
312
- slot === activeSlot;
313
- // Count services in this slot
314
- const count = services.filter((s) => {
315
- const depMin = commonService.timeToMinutes(
316
- s.departure_time,
333
+ />
334
+ </button>
335
+ {isThisTimeDropdownOpen && (
336
+ <>
337
+ <div
338
+ className="absolute left-0 top-[calc(100%+10px)]"
339
+ style={{
340
+ zIndex: 20,
341
+ backgroundColor: "#fff",
342
+ borderRadius: "14px",
343
+ minWidth: "190px",
344
+ boxShadow: "0 8px 32px rgba(0,0,0,0.28)",
345
+ overflow: "hidden",
346
+ padding: "6px 0",
347
+ }}
348
+ >
349
+ {availableTimeSlots.map((slot) => {
350
+ const isActive =
351
+ slot.label === selectedTimeSlot ||
352
+ slot === activeSlot;
353
+ // Count services in this slot
354
+ const count = services.filter((s) => {
355
+ const depMin = commonService.timeToMinutes(
356
+ s.departure_time,
357
+ );
358
+ return (
359
+ depMin >= slot.start && depMin < slot.end
360
+ );
361
+ }).length;
362
+ return (
363
+ <button
364
+ key={slot.label}
365
+ type="button"
366
+ onClick={() => {
367
+ onTimeSlotChange?.(slot.label);
368
+ onTimeDropdownToggle?.(null);
369
+ // const slotServices = services.filter((s) => {
370
+ // const depMin = commonService.timeToMinutes(
371
+ // s.departure_time,
372
+ // );
373
+ // return (
374
+ // depMin >= slot.start && depMin < slot.end
375
+ // );
376
+ // });
377
+ // console.log(
378
+ // "Selected slot label:",
379
+ // slot.label,
380
+ // );
381
+ // console.log(
382
+ // "Services in selected slot:",
383
+ // slotServices,
384
+ // );
385
+ }}
386
+ className={`flex w-full cursor-pointer items-center justify-between gap-[10px] border-none px-[12px] py-[9px] text-left text-[13px] ${isActive ? "bg-[#FF5C60] font-bold text-[white]" : "bg-transparent font-normal text-[#1a1a1a]"}`}
387
+ >
388
+ <span>{slot.label}</span>
389
+ {count > 0 && (
390
+ <span
391
+ className={`text-[11px] rounded-full px-[6px] py-[2px] font-bold ${
392
+ isActive
393
+ ? "bg-[white] text-[#FF5C60]"
394
+ : "bg-[#FF5C60] text-[white]"
395
+ }`}
396
+ >
397
+ {count}
398
+ </span>
399
+ )}
400
+ </button>
317
401
  );
318
- return depMin >= slot.start && depMin < slot.end;
319
- }).length;
320
- return (
321
- <button
322
- key={slot.label}
323
- type="button"
324
- onClick={() => {
325
- onTimeSlotChange?.(slot.label);
326
- onTimeDropdownToggle?.(null);
327
- // const slotServices = services.filter((s) => {
328
- // const depMin = commonService.timeToMinutes(
329
- // s.departure_time,
330
- // );
331
- // return (
332
- // depMin >= slot.start && depMin < slot.end
333
- // );
334
- // });
335
- // console.log(
336
- // "Selected slot label:",
337
- // slot.label,
338
- // );
339
- // console.log(
340
- // "Services in selected slot:",
341
- // slotServices,
342
- // );
343
- }}
344
- className={`flex w-full cursor-pointer items-center justify-between gap-[10px] border-none px-[12px] py-[9px] text-left text-[13px] ${isActive ? "bg-[#FF5C60] font-bold text-[white]" : "bg-transparent font-normal text-[#1a1a1a]"}`}
345
- >
346
- <span>{slot.label}</span>
347
- {count > 0 && (
348
- <span
349
- className={`text-[11px] rounded-full px-[6px] py-[2px] font-bold ${
350
- isActive
351
- ? "bg-[white] text-[#FF5C60]"
352
- : "bg-[#FF5C60] text-[white]"
353
- }`}
354
- >
355
- {count}
356
- </span>
357
- )}
358
- </button>
359
- );
360
- })}
361
- </div>
362
- </>
363
- )}
402
+ })}
403
+ </div>
404
+ </>
405
+ )}
406
+ </div>
364
407
  </div>
408
+ {/* end text column */}
365
409
  </div>
410
+ {/* end flex items-start row */}
366
411
  </div>
412
+ {/* end flex-col gap-6 relative */}
367
413
  </div>
414
+ {/* end text-[white] */}
368
415
 
369
416
  <div className="border-t border-[#363c48] my-[8px]" />
370
417
 
@@ -379,54 +426,62 @@ const FeatureServiceUiMobile = ({
379
426
  operadores compitiendo <br />
380
427
  por tu compra
381
428
  </span>
429
+ {/* Operator boxes: single full-width box when 1 operator, else flex row */}
382
430
  <div
383
431
  className="flex gap-[8px] text-[white]"
384
- style={{ width: "100%" }}
432
+ style={{ width: "100%", justifyContent: "center" }}
385
433
  >
386
- {(servicesInActiveSlot.length > 0
387
- ? servicesInActiveSlot.slice(0, 3).map((s: any) => ({
388
- logo: s.operator_logo_url,
389
- name: s.operator_name,
390
- time: s.departure_time,
391
- seatsAvailable: `${s.available_seats} disponibles`,
392
- }))
393
- : operators
394
- ).map((op, idx) => (
395
- <div
396
- key={idx}
397
- className="flex min-w-0 flex-col items-center justify-center gap-[8px] rounded-[8px]"
398
- style={{
399
- flex: 1,
400
- minWidth: 0,
401
- height: "100px",
402
- border: "1px solid #363c48",
403
- backgroundColor: "#1a202e",
404
- padding: "14px 6px",
405
- }}
406
- >
407
- <img
408
- src={op.logo}
409
- alt={op.name}
410
- onError={(e) => {
411
- e.currentTarget.src =
412
- serviceItem?.operator_details?.[0] ||
413
- "/images/service-list/bus-icon.svg";
434
+ {(() => {
435
+ const displayOps =
436
+ servicesInActiveSlot.length > 0
437
+ ? servicesInActiveSlot.slice(0, 3).map((s: any) => ({
438
+ logo: s.operator_logo_url,
439
+ name: s.operator_name,
440
+ time: s.departure_time,
441
+ seatsAvailable: `${s.available_seats} disponibles`,
442
+ }))
443
+ : operators;
444
+ const isSingle = displayOps.length === 1;
445
+ return displayOps.map((op, idx) => (
446
+ <div
447
+ key={idx}
448
+ className="flex min-w-0 flex-col items-center justify-center gap-[8px] rounded-[8px]"
449
+ style={{
450
+ flex: isSingle ? "none" : 1,
451
+ width: isSingle ? "32%" : undefined,
452
+ margin: isSingle ? "0 auto" : undefined,
453
+ minWidth: 0,
454
+ height: "100px",
455
+ border: "1px solid #363c48",
456
+ backgroundColor: "#1a202e",
457
+ padding: "14px 6px",
414
458
  }}
415
- className={`h-[24px] max-w-full object-contain ${
416
- isSoldOut ? "grayscale" : ""
417
- }`}
418
- />
419
- <span className="text-[12px] truncate max-w-full text-center ">
420
- {op.name}
421
- </span>
422
- {/* <div className="bg-[#FF8F45] text-[12px] font-bold px-[10px] py-[4px] rounded-[4px] bold-text whitespace-nowrap">
423
- <span>{op?.time}</span>
424
- </div> */}
425
- <span className="text-[11px] whitespace-nowrap">
426
- {op?.seatsAvailable}
427
- </span>
428
- </div>
429
- ))}
459
+ >
460
+ <img
461
+ src={op.logo}
462
+ alt={op.name}
463
+ onError={(e) => {
464
+ e.currentTarget.src =
465
+ serviceItem?.operator_details?.[0] ||
466
+ "/images/service-list/bus-icon.svg";
467
+ }}
468
+ className={`object-contain ${
469
+ isSoldOut ? "grayscale" : ""
470
+ }`}
471
+ style={{
472
+ height: "24px",
473
+ maxWidth: "100%",
474
+ }}
475
+ />
476
+ <span className="text-[12px] truncate max-w-full text-center">
477
+ {op.name}
478
+ </span>
479
+ <span className="text-[11px] whitespace-nowrap">
480
+ {op?.seatsAvailable}
481
+ </span>
482
+ </div>
483
+ ));
484
+ })()}
430
485
  </div>
431
486
 
432
487
  <div
@@ -661,7 +716,7 @@ const FeatureServiceUiMobile = ({
661
716
 
662
717
  {/* TOP BADGE — "Remate | Termina en 09:55 min" hardcoded, no countdown hook */}
663
718
  <div className="absolute -top-[13px] left-0 w-full flex items-center justify-center gap-[12px] z-10 ">
664
- <div className="flex items-center gap-[6px] py-[5px] px-[14px] rounded-[38px] text-[12.5px] bg-[#FF8F45] text-white whitespace-nowrap">
719
+ {/* <div className="flex items-center gap-[6px] py-[5px] px-[14px] rounded-[38px] text-[12.5px] bg-[#FF8F45] text-white whitespace-nowrap">
665
720
  <span className="flex items-center">
666
721
  <div className="mb-[2px]">
667
722
  <LottiePlayer
@@ -687,6 +742,44 @@ const FeatureServiceUiMobile = ({
687
742
  }}
688
743
  />
689
744
  </span>
745
+ </div> */}
746
+ <div
747
+ className="flex items-center gap-[6px] py-[5px] px-[14px] rounded-[38px] text-[12.5px] text-white whitespace-nowrap"
748
+ style={{ backgroundColor: "rgba(255, 222, 223, 0.93)" }}
749
+ >
750
+ <span className="flex items-center">
751
+ {serviceItem?.icons?.fireIcon ? (
752
+ <img
753
+ src={serviceItem.icons.fireIcon}
754
+ alt="discount"
755
+ className="h-[12px] w-[12px] object-contain mr-[5px]"
756
+ style={{ filter: isSoldOut ? "grayscale" : "" }}
757
+ />
758
+ ) : null}
759
+ {/* <div className="mb-[2px]">
760
+ <LottiePlayer
761
+ animationData={flameAnimation}
762
+ width="16px"
763
+ height="16px"
764
+ />
765
+ </div> */}
766
+ <span className="bold-text text-[#464647] ml-[3px]">
767
+ Remate&nbsp;
768
+ </span>{" "}
769
+ <span className="text-[#464647]">términa en&nbsp;</span>{" "}
770
+ <span
771
+ className="bold-text text-end"
772
+ ref={(node) =>
773
+ commonService.startDealCountdown(node, getCountdownSeconds())
774
+ }
775
+ style={{
776
+ fontVariantNumeric: "tabular-nums",
777
+ display: "inline-block",
778
+ color: "#FF5C60",
779
+ minWidth: "50px",
780
+ }}
781
+ />
782
+ </span>
690
783
  </div>
691
784
  </div>
692
785
  </div>
@@ -33,6 +33,7 @@ const HARDCODED_OPERATORS = [
33
33
  seatsAvailable: "3 disponibles",
34
34
  },
35
35
  ];
36
+ const expandedStateMap: Record<string, boolean> = {};
36
37
 
37
38
  const FeatureServiceUi = ({
38
39
  serviceItem,
@@ -155,9 +156,10 @@ const FeatureServiceUi = ({
155
156
  ? activeSlot.label.split(" - ")[1]
156
157
  : dealWindowTo;
157
158
 
158
- const isItemExpanded =
159
- serviceItem.id === isFeatureDropDownExpand ||
160
- isFeatureDropDownExpand === true;
159
+ if (expandedStateMap[serviceItem.id] === undefined) {
160
+ expandedStateMap[serviceItem.id] = true;
161
+ }
162
+ const isItemExpanded = expandedStateMap[serviceItem.id];
161
163
  const isThisTimeDropdownOpen = isTimeDropdownOpen === serviceItem.id;
162
164
  const canDecreaseTicketQuantity = ticketQuantity > 1;
163
165
  const availableSeats =
@@ -698,8 +700,38 @@ const FeatureServiceUi = ({
698
700
  </div>
699
701
 
700
702
  <div
703
+ id={`feature-arrow-${serviceItem.id}`}
701
704
  className={`absolute bottom-[11px] right-[18px] cursor-pointer transition-transform duration-300 ease-in-out ${isItemExpanded ? "rotate-180" : ""}`}
702
- onClick={onToggleExpand}
705
+ onClick={(e) => {
706
+ expandedStateMap[serviceItem.id] =
707
+ !expandedStateMap[serviceItem.id];
708
+ if (onToggleExpand) onToggleExpand(e);
709
+
710
+ const contentNode = document.getElementById(
711
+ `feature-content-${serviceItem.id}`,
712
+ );
713
+ const innerNode = document.getElementById(
714
+ `feature-inner-content-${serviceItem.id}`,
715
+ );
716
+ const arrowNode = document.getElementById(
717
+ `feature-arrow-${serviceItem.id}`,
718
+ );
719
+
720
+ if (contentNode && innerNode && arrowNode) {
721
+ const isExpanded = expandedStateMap[serviceItem.id];
722
+ if (isExpanded) {
723
+ contentNode.style.gridTemplateRows = "1fr";
724
+ contentNode.style.opacity = "1";
725
+ innerNode.className = `min-h-0 overflow-hidden px-[16px] text-[13.33px] pt-[14px] pb-[6px]`;
726
+ arrowNode.className = `absolute bottom-[11px] right-[18px] cursor-pointer transition-transform duration-300 ease-in-out rotate-180`;
727
+ } else {
728
+ contentNode.style.gridTemplateRows = "0fr";
729
+ contentNode.style.opacity = "0";
730
+ innerNode.className = `min-h-0 overflow-hidden px-[16px] text-[13.33px] pt-0 pb-0`;
731
+ arrowNode.className = `absolute bottom-[11px] right-[18px] cursor-pointer transition-transform duration-300 ease-in-out `;
732
+ }
733
+ }
734
+ }}
703
735
  >
704
736
  <img
705
737
  src={serviceItem.icons?.downArrow}
@@ -714,16 +746,18 @@ const FeatureServiceUi = ({
714
746
  </div>
715
747
  </div>
716
748
  <div
749
+ id={`feature-content-${serviceItem.id}`}
717
750
  className="grid"
718
751
  style={{
719
- gridTemplateRows: "1fr",
720
- opacity: 1,
752
+ gridTemplateRows: isItemExpanded ? "1fr" : "0fr",
753
+ opacity: isItemExpanded ? 1 : 0,
721
754
  transition:
722
755
  "grid-template-rows 300ms ease-in-out, opacity 250ms ease-in-out",
723
756
  }}
724
757
  >
725
758
  <div
726
- className={`min-h-0 overflow-hidden px-[16px] text-[13.33px] pt-[14px] pb-[6px]`}
759
+ id={`feature-inner-content-${serviceItem.id}`}
760
+ className={`min-h-0 overflow-hidden px-[16px] text-[13.33px] ${isItemExpanded ? "pt-[14px] pb-[6px]" : "pt-0 pb-0"}`}
727
761
  style={{ transition: "padding 300ms ease-in-out" }}
728
762
  >
729
763
  <span className="bold-text">¿Cómo funciona?</span>