kupos-ui-components-lib 10.4.13 → 10.4.14

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.
Files changed (56) hide show
  1. package/dist/assets/images/anims/service_list/60_percent_anim.json +1 -0
  2. package/dist/assets/images/filter/arrow-down.png +0 -0
  3. package/dist/assets/images/filter/rapido.svg +14 -0
  4. package/dist/components/FilterSortBar/FilterChip.d.ts +6 -2
  5. package/dist/components/FilterSortBar/FilterChip.js +10 -32
  6. package/dist/components/FilterSortBar/FilterSortBarMobile.d.ts +1 -1
  7. package/dist/components/FilterSortBar/FilterSortBarMobile.js +71 -38
  8. package/dist/components/FilterSortBar/FiltersMasterSheet.d.ts +3 -1
  9. package/dist/components/FilterSortBar/FiltersMasterSheet.js +28 -97
  10. package/dist/components/FilterSortBar/OptionCheckboxList.js +5 -16
  11. package/dist/components/FilterSortBar/PriceRangeSlider.js +17 -55
  12. package/dist/components/FilterSortBar/SheetFooter.js +3 -30
  13. package/dist/components/FilterSortBar/defaultIcons.js +9 -0
  14. package/dist/components/FilterSortBar/icons.d.ts +7 -0
  15. package/dist/components/FilterSortBar/icons.js +8 -1
  16. package/dist/components/FilterSortBar/index.d.ts +1 -1
  17. package/dist/components/FilterSortBar/types.d.ts +34 -0
  18. package/dist/components/PaymentOptions/PaymentOptionsDesktop.d.ts +4 -0
  19. package/dist/components/PaymentOptions/PaymentOptionsDesktop.js +5 -0
  20. package/dist/components/PaymentOptions/PaymentOptionsMobile.d.ts +4 -0
  21. package/dist/components/PaymentOptions/PaymentOptionsMobile.js +5 -0
  22. package/dist/components/PaymentOptions/ResponsivePaymentOptions.d.ts +3 -0
  23. package/dist/components/PaymentOptions/ResponsivePaymentOptions.js +10 -0
  24. package/dist/components/PaymentOptions/index.d.ts +5 -0
  25. package/dist/components/PaymentOptions/index.js +4 -0
  26. package/dist/components/PaymentOptions/mobileTypes.d.ts +0 -0
  27. package/dist/components/PaymentOptions/mobileTypes.js +0 -0
  28. package/dist/components/PaymentOptions/type.d.ts +4 -0
  29. package/dist/components/PaymentOptions/type.js +1 -0
  30. package/dist/components/PaymentSideBar/PaymentSideBarMobile.js +1 -1
  31. package/dist/components/ServiceItem/ExpandedDropdown.d.ts +19 -0
  32. package/dist/components/ServiceItem/ExpandedDropdown.js +28 -0
  33. package/dist/components/ServiceItem/ServiceItemDesktopPeru.d.ts +6 -0
  34. package/dist/components/ServiceItem/ServiceItemDesktopPeru.js +612 -0
  35. package/dist/index.d.ts +1 -1
  36. package/dist/styles.css +233 -0
  37. package/dist/ui/PaymentCardMobile/PaymentCardMobile.js +18 -4
  38. package/dist/ui/TopAmenities/PromoCountdown.d.ts +18 -0
  39. package/dist/ui/TopAmenities/PromoCountdown.js +55 -0
  40. package/package.json +1 -1
  41. package/src/assets/images/filter/arrow-down.png +0 -0
  42. package/src/assets/images/filter/rapido.svg +14 -0
  43. package/src/components/FilterSortBar/FilterChip.tsx +31 -35
  44. package/src/components/FilterSortBar/FilterSortBarMobile.tsx +106 -66
  45. package/src/components/FilterSortBar/FiltersMasterSheet.tsx +34 -118
  46. package/src/components/FilterSortBar/OptionCheckboxList.tsx +5 -16
  47. package/src/components/FilterSortBar/PriceRangeSlider.tsx +20 -61
  48. package/src/components/FilterSortBar/SheetFooter.tsx +4 -32
  49. package/src/components/FilterSortBar/defaultIcons.ts +11 -0
  50. package/src/components/FilterSortBar/icons.tsx +38 -2
  51. package/src/components/FilterSortBar/index.ts +2 -0
  52. package/src/components/FilterSortBar/types.ts +36 -0
  53. package/src/components/PaymentSideBar/PaymentSideBarMobile.tsx +4 -1
  54. package/src/index.ts +2 -0
  55. package/src/ui/PaymentCardMobile/PaymentCardMobile.tsx +101 -31
  56. package/src/components/FilterSortBar/SingleFilterSheet.tsx +0 -82
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
- import { ChevronDownIcon, FilterIcon } from "./icons";
3
- import { FilterSortBarColors, FilterSortBarIcons } from "./types";
2
+ import { ChevronDownIcon, FilterIcon, SortArrowIcon } from "./icons";
3
+ import { FilterSortBarColors, FilterSortBarIcons, SortDirection } from "./types";
4
4
 
5
5
  interface FilterChipProps {
6
6
  label: string;
@@ -16,6 +16,10 @@ interface FilterChipProps {
16
16
  badgeCount?: number;
17
17
  /** When true, the label text is hidden and only the icon + badge + chevron show (used for the Filtros button once the strip is scrolled). */
18
18
  collapsed?: boolean;
19
+ /** Renders a directional arrow instead of the dropdown chevron — used for base sort chips (Horario, Precio, Rápido). */
20
+ sortDirection?: SortDirection;
21
+ /** Suppresses the trailing chevron/arrow entirely — used for cohort sort chips (Después de las 6 PM, Salón Cama), which just toggle on/off. */
22
+ hideTrailingIcon?: boolean;
19
23
  colors: Required<
20
24
  Pick<
21
25
  FilterSortBarColors,
@@ -35,6 +39,8 @@ const FilterChip = ({
35
39
  activeIconSrc,
36
40
  badgeCount = 0,
37
41
  collapsed,
42
+ sortDirection,
43
+ hideTrailingIcon,
38
44
  colors,
39
45
  icons,
40
46
  }: FilterChipProps) => {
@@ -43,22 +49,11 @@ const FilterChip = ({
43
49
  return (
44
50
  <button
45
51
  onClick={onClick}
52
+ className="flex flex-shrink-0 items-center gap-[6px] px-[14px] py-[9px] rounded-[20px] text-[13px] font-medium whitespace-nowrap cursor-pointer transition-[padding] duration-200 ease-in-out [-webkit-tap-highlight-color:transparent]"
46
53
  style={{
47
- flexShrink: 0,
48
- display: "flex",
49
- alignItems: "center",
50
- gap: 6,
51
- padding: "9px 14px",
52
- borderRadius: 20,
53
54
  border: `1px solid ${isHighlighted ? colors.primaryColor : colors.chipBorderColor}`,
54
55
  backgroundColor: isHighlighted ? colors.primaryColor : "#fff",
55
56
  color: isHighlighted ? colors.primaryTextColor : colors.chipTextColor,
56
- fontSize: 13,
57
- fontWeight: 500,
58
- whiteSpace: "nowrap",
59
- cursor: "pointer",
60
- transition: "padding 0.2s ease",
61
- WebkitTapHighlightColor: "transparent",
62
57
  }}
63
58
  >
64
59
  {isFiltersButton && (
@@ -75,37 +70,38 @@ const FilterChip = ({
75
70
  width={14}
76
71
  height={14}
77
72
  alt=""
78
- style={isHighlighted && !activeIconSrc ? { filter: "brightness(0) invert(1)" } : undefined}
73
+ className={isHighlighted && !activeIconSrc ? "brightness-0 invert" : undefined}
79
74
  />
80
75
  )}
76
+ {!collapsed && <span>{label}</span>}
81
77
  {badgeCount > 0 && (
82
78
  <span
79
+ className="inline-flex flex-shrink-0 items-center justify-center w-[18px] h-[18px] rounded-full box-border text-[10px] font-bold leading-none"
83
80
  style={{
84
- display: "inline-flex",
85
- flexShrink: 0,
86
- alignItems: "center",
87
- justifyContent: "center",
88
- width: 16,
89
- height: 16,
90
- borderRadius: "50%",
91
- boxSizing: "border-box",
92
- fontSize: 10,
93
- fontWeight: 700,
94
- lineHeight: 1,
95
- backgroundColor: colors.badgeBackgroundColor,
96
- color: colors.badgeTextColor,
81
+ backgroundColor: !isFiltersButton && isHighlighted ? "#FFFFFF" : colors.badgeBackgroundColor,
82
+ color: !isFiltersButton && isHighlighted ? colors.primaryColor : colors.badgeTextColor,
97
83
  }}
98
84
  >
99
85
  {badgeCount}
100
86
  </span>
101
87
  )}
102
- {!collapsed && <span>{label}</span>}
103
- <ChevronDownIcon
104
- size={8}
105
- color={isHighlighted ? colors.primaryTextColor : colors.chipTextColor}
106
- open={isOpen}
107
- src={icons?.chevronDownIcon}
108
- />
88
+ {!hideTrailingIcon &&
89
+ (sortDirection ? (
90
+ <SortArrowIcon
91
+ size={9}
92
+ color={isHighlighted ? colors.primaryTextColor : colors.chipTextColor}
93
+ direction={sortDirection}
94
+ src={icons?.sortArrowIcon}
95
+ active={isHighlighted}
96
+ />
97
+ ) : (
98
+ <ChevronDownIcon
99
+ size={8}
100
+ color={isHighlighted ? colors.primaryTextColor : colors.chipTextColor}
101
+ open={isOpen}
102
+ src={icons?.chevronDownIcon}
103
+ />
104
+ ))}
109
105
  </button>
110
106
  );
111
107
  };
@@ -1,9 +1,22 @@
1
1
  import React, { useEffect, useRef, useState } from "react";
2
2
  import FilterChip from "./FilterChip";
3
- import SingleFilterSheet from "./SingleFilterSheet";
4
3
  import FiltersMasterSheet from "./FiltersMasterSheet";
5
4
  import { DEFAULT_ICONS } from "./defaultIcons";
6
- import { FilterCategory, FilterCategoryKey, FilterSortBarMobileProps } from "./types";
5
+ import { FilterCategory, FilterCategoryKey, FilterSortBarMobileProps, SortOption } from "./types";
6
+
7
+ // Sort chips reuse an existing category or time-of-day icon where the
8
+ // concept already has one (Horario→clock, Empresas→bus, Precio→piggy bank,
9
+ // Después de las 6 PM→moon, Antes de las 6 AM→sunrise, Salón Cama→seat);
10
+ // only keys with no such match (e.g. "duration" for Rápido) fall back to
11
+ // icons.sortIcons.
12
+ const SORT_ICON_SOURCE: Partial<Record<string, { category?: FilterCategoryKey; timeOfDay?: "sunrise" | "morning" | "sunset" | "night" }>> = {
13
+ time: { category: "time" },
14
+ company: { category: "company" },
15
+ price: { category: "price" },
16
+ after_6pm: { timeOfDay: "night" },
17
+ before_6am: { timeOfDay: "sunrise" },
18
+ cama: { category: "seatType" },
19
+ };
7
20
 
8
21
  const DEFAULT_COLORS = {
9
22
  primaryColor: "#FF5C60",
@@ -12,8 +25,6 @@ const DEFAULT_COLORS = {
12
25
  chipTextColor: "#464647",
13
26
  badgeBackgroundColor: "#FFCBCC",
14
27
  badgeTextColor: "#FF5C60",
15
- // Falls back to primaryColor/primaryTextColor below when the consumer
16
- // doesn't pass a dedicated CTA button color.
17
28
  buttonColor: "#FF5C60",
18
29
  buttonTextColor: "#FFFFFF",
19
30
  };
@@ -25,7 +36,10 @@ const getCategoryActiveCount = (category: FilterCategory) => {
25
36
  ? 1
26
37
  : 0;
27
38
  }
28
- return category.options?.filter((o) => o.selected).length || 0;
39
+ // checkedViaSort options are a display mirror of an active sort chip, not
40
+ // a real selection — the sort chip's own active count already covers it,
41
+ // so counting it here too would double it.
42
+ return category.options?.filter((o) => o.selected && !o.checkedViaSort).length || 0;
29
43
  };
30
44
 
31
45
  const isCategoryActive = (category: FilterCategory) => getCategoryActiveCount(category) > 0;
@@ -40,6 +54,8 @@ const FilterSortBarMobile = ({
40
54
  categories,
41
55
  onApply,
42
56
  onClearFilter,
57
+ sortOptions,
58
+ onSortChange,
43
59
  currencySign = "$",
44
60
  colors,
45
61
  icons,
@@ -55,6 +71,14 @@ const FilterSortBarMobile = ({
55
71
  categoryIcons: { ...DEFAULT_ICONS.categoryIcons, ...icons?.categoryIcons },
56
72
  categoryIconsActive: { ...DEFAULT_ICONS.categoryIconsActive, ...icons?.categoryIconsActive },
57
73
  timeOfDayIcons: { ...DEFAULT_ICONS.timeOfDayIcons, ...icons?.timeOfDayIcons },
74
+ sortIcons: { ...DEFAULT_ICONS.sortIcons, ...icons?.sortIcons },
75
+ };
76
+
77
+ const resolveSortIcon = (key: string): string | undefined => {
78
+ const source = SORT_ICON_SOURCE[key];
79
+ if (source?.category) return resolvedIcons.categoryIcons?.[source.category];
80
+ if (source?.timeOfDay) return resolvedIcons.timeOfDayIcons?.[source.timeOfDay];
81
+ return resolvedIcons.sortIcons?.[key];
58
82
  };
59
83
 
60
84
  // Re-sync the draft whenever the committed categories change from the
@@ -63,9 +87,14 @@ const FilterSortBarMobile = ({
63
87
  setDraftCategories(categories);
64
88
  }, [categories]);
65
89
 
66
- const openCategory = draftCategories.find((c) => c.key === openSheet) || null;
67
- const isMasterOpen = openSheet === "all";
68
- const totalActiveCategories = categories.filter(isCategoryActive).length;
90
+ // Every filter chip opens the same master sheet, pre-selecting whichever
91
+ // category was tapped ("all" — the Filtros button — leaves it unset so
92
+ // FiltersMasterSheet falls back to its first category).
93
+ const isMasterOpen = openSheet !== null;
94
+ const initialCategoryKey = openSheet && openSheet !== "all" ? openSheet : undefined;
95
+ const totalActiveCategories =
96
+ categories.filter(isCategoryActive).length +
97
+ (sortOptions || []).filter((s) => !s.direction && s.active).length;
69
98
 
70
99
  const handleScroll = () => {
71
100
  setIsScrolled((scrollRef.current?.scrollLeft ?? 0) > 4);
@@ -103,15 +132,6 @@ const FilterSortBarMobile = ({
103
132
  );
104
133
  };
105
134
 
106
- const handleApplySingle = () => {
107
- if (openSheet && openSheet !== "all") onApply(openSheet, draftCategories);
108
- closeSheet();
109
- };
110
-
111
- const handleClearSingle = () => {
112
- if (openSheet && openSheet !== "all") onClearFilter(openSheet);
113
- };
114
-
115
135
  const handleApplyAll = () => {
116
136
  onApply("all", draftCategories);
117
137
  closeSheet();
@@ -137,9 +157,29 @@ const FilterSortBarMobile = ({
137
157
  onApply(categoryKey, updated);
138
158
  };
139
159
 
160
+ // Directional sorts (Horario, Precio, Rápido) are mutually exclusive —
161
+ // picking one deactivates the others, and re-tapping the active one flips
162
+ // its direction. Cohort sorts (no `direction`, e.g. Después de las 6 PM,
163
+ // Salón Cama) are independent toggles that layer on top of whichever
164
+ // directional sort is active.
165
+ const handleSortChipClick = (key: string) => {
166
+ if (!sortOptions || !onSortChange) return;
167
+ const target = sortOptions.find((s) => s.key === key);
168
+ if (!target) return;
169
+
170
+ const updated: SortOption[] = sortOptions.map((s) => {
171
+ if (s.direction) {
172
+ if (s.key !== key) return s.active ? { ...s, active: false } : s;
173
+ return s.active ? { ...s, direction: s.direction === "asc" ? "desc" : "asc" } : { ...s, active: true };
174
+ }
175
+ return s.key === key ? { ...s, active: !s.active } : s;
176
+ });
177
+ onSortChange(updated);
178
+ };
179
+
140
180
  return (
141
181
  <div>
142
- <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
182
+ <div className="flex items-center gap-[8px]">
143
183
  <FilterChip
144
184
  label="Filtros"
145
185
  active={totalActiveCategories > 0}
@@ -152,73 +192,73 @@ const FilterSortBarMobile = ({
152
192
  icons={resolvedIcons}
153
193
  />
154
194
  <div
155
- style={{
156
- width: 1,
157
- alignSelf: "stretch",
158
- backgroundColor: resolvedColors.chipBorderColor,
159
- opacity: 0.5,
160
- flexShrink: 0,
161
- }}
195
+ className="w-px self-stretch flex-shrink-0 opacity-50"
196
+ style={{ backgroundColor: resolvedColors.chipBorderColor }}
162
197
  />
163
198
  <div
164
199
  ref={scrollRef}
165
200
  onScroll={handleScroll}
166
- style={{
167
- display: "flex",
168
- alignItems: "center",
169
- gap: 8,
170
- overflowX: "auto",
171
- scrollbarWidth: "none",
172
- paddingBottom: 2,
173
- }}
201
+ className="flex items-center gap-[8px] overflow-x-auto pb-[2px] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
174
202
  >
175
- {categories.map((category) => (
203
+ {sortOptions?.map((sort) => (
176
204
  <FilterChip
177
- key={category.key}
178
- label={category.label}
179
- active={isCategoryActive(category)}
180
- isOpen={openSheet === category.key}
181
- onClick={() => openSheetFor(category.key)}
182
- iconSrc={resolvedIcons.categoryIcons?.[category.key]}
183
- activeIconSrc={resolvedIcons.categoryIconsActive?.[category.key]}
184
- badgeCount={getCategoryActiveCount(category)}
205
+ key={`sort:${sort.key}`}
206
+ label={sort.label}
207
+ active={sort.active}
208
+ isOpen={false}
209
+ onClick={() => handleSortChipClick(sort.key)}
210
+ iconSrc={resolveSortIcon(sort.key)}
211
+ sortDirection={sort.direction && sort.active ? sort.direction : undefined}
212
+ hideTrailingIcon={!(sort.direction && sort.active)}
213
+ badgeCount={sort.active ? sort.count : undefined}
185
214
  colors={resolvedColors}
186
215
  icons={resolvedIcons}
187
216
  />
188
217
  ))}
218
+ {categories
219
+ // hideChip categories never get a standalone top-bar chip, even
220
+ // once something is selected inside the Filtros sheet — surfacing
221
+ // it there duplicated the dedicated sort chip for the same
222
+ // concept (e.g. "Horarios" filter next to the "Horario" sort chip).
223
+ .filter((category) => !category.hideChip)
224
+ .map((category) => (
225
+ <FilterChip
226
+ key={category.key}
227
+ label={category.label}
228
+ active={isCategoryActive(category)}
229
+ isOpen={openSheet === category.key}
230
+ onClick={() => openSheetFor(category.key)}
231
+ iconSrc={resolvedIcons.categoryIcons?.[category.key]}
232
+ activeIconSrc={resolvedIcons.categoryIconsActive?.[category.key]}
233
+ badgeCount={getCategoryActiveCount(category)}
234
+ colors={resolvedColors}
235
+ icons={resolvedIcons}
236
+ />
237
+ ))}
189
238
  {categories
190
239
  .filter((category) => category.inlineOptions && category.options?.length)
191
240
  .flatMap((category) =>
192
- (category.options || []).map((option) => (
193
- <FilterChip
194
- key={`${category.key}:${option.id}`}
195
- label={option.label}
196
- active={option.selected}
197
- isOpen={false}
198
- onClick={() => handleInlineOptionToggle(category.key, option.id)}
199
- iconSrc={option.icon ? resolvedIcons.timeOfDayIcons?.[option.icon] : undefined}
200
- colors={resolvedColors}
201
- icons={resolvedIcons}
202
- />
203
- )),
241
+ (category.options || [])
242
+ .filter((option) => !option.excludeFromInline)
243
+ .map((option) => (
244
+ <FilterChip
245
+ key={`${category.key}:${option.id}`}
246
+ label={option.label}
247
+ active={option.selected}
248
+ isOpen={false}
249
+ onClick={() => handleInlineOptionToggle(category.key, option.id)}
250
+ iconSrc={option.icon ? resolvedIcons.timeOfDayIcons?.[option.icon] : undefined}
251
+ colors={resolvedColors}
252
+ icons={resolvedIcons}
253
+ />
254
+ )),
204
255
  )}
205
256
  </div>
206
257
  </div>
207
258
 
208
- <SingleFilterSheet
209
- category={openCategory}
210
- onClose={closeSheet}
211
- onToggleOption={(optionId) => openSheet && openSheet !== "all" && handleDraftToggleOption(openSheet, optionId)}
212
- onPriceRangeChange={(min, max) => openSheet && openSheet !== "all" && handleDraftPriceRangeChange(openSheet, min, max)}
213
- onClear={handleClearSingle}
214
- onApply={handleApplySingle}
215
- currencySign={currencySign}
216
- colors={resolvedColors}
217
- icons={resolvedIcons}
218
- />
219
-
220
259
  <FiltersMasterSheet
221
260
  isOpen={isMasterOpen}
261
+ initialCategoryKey={initialCategoryKey}
222
262
  categories={draftCategories}
223
263
  onClose={closeSheet}
224
264
  onToggleOption={handleDraftToggleOption}
@@ -1,6 +1,6 @@
1
- import React, { useMemo, useState } from "react";
1
+ import React, { useEffect, useState } from "react";
2
2
  import { BottomSheet } from "../../ui/BottomSheet";
3
- import { CloseIcon, SearchIcon } from "./icons";
3
+ import { CloseIcon } from "./icons";
4
4
  import OptionCheckboxList from "./OptionCheckboxList";
5
5
  import PriceRangeSlider from "./PriceRangeSlider";
6
6
  import SheetFooter from "./SheetFooter";
@@ -8,6 +8,8 @@ import { FilterCategory, FilterCategoryKey, FilterSortBarColors, FilterSortBarIc
8
8
 
9
9
  interface FiltersMasterSheetProps {
10
10
  isOpen: boolean;
11
+ /** Category to pre-select when the sheet opens — set when a specific filter chip (rather than the Filtros button) was tapped. */
12
+ initialCategoryKey?: FilterCategoryKey;
11
13
  categories: FilterCategory[];
12
14
  onClose: () => void;
13
15
  onToggleOption: (categoryKey: FilterCategoryKey, optionId: string) => void;
@@ -21,6 +23,7 @@ interface FiltersMasterSheetProps {
21
23
 
22
24
  const FiltersMasterSheet = ({
23
25
  isOpen,
26
+ initialCategoryKey,
24
27
  categories,
25
28
  onClose,
26
29
  onToggleOption,
@@ -31,124 +34,49 @@ const FiltersMasterSheet = ({
31
34
  colors,
32
35
  icons,
33
36
  }: FiltersMasterSheetProps) => {
34
- const [activeKey, setActiveKey] = useState<FilterCategoryKey>(categories[0]?.key ?? "time");
35
- const [searchQuery, setSearchQuery] = useState("");
37
+ const [activeKey, setActiveKey] = useState<FilterCategoryKey>(initialCategoryKey ?? categories[0]?.key ?? "time");
36
38
 
37
- const activeCategory = categories.find((c) => c.key === activeKey) || categories[0];
39
+ // Reset the pre-selected tab every time the sheet is (re)opened, whether
40
+ // via a specific filter chip or the general Filtros button.
41
+ useEffect(() => {
42
+ if (isOpen) {
43
+ setActiveKey(initialCategoryKey ?? categories[0]?.key ?? "time");
44
+ }
45
+ // eslint-disable-next-line react-hooks/exhaustive-deps
46
+ }, [isOpen]);
38
47
 
39
- const filteredOptions = useMemo(() => {
40
- if (!activeCategory?.options) return [];
41
- if (!searchQuery.trim()) return activeCategory.options;
42
- const q = searchQuery.trim().toLowerCase();
43
- return activeCategory.options.filter((o) => o.label.toLowerCase().includes(q));
44
- }, [activeCategory, searchQuery]);
48
+ const activeCategory = categories.find((c) => c.key === activeKey) || categories[0];
45
49
 
46
50
  if (!isOpen) return null;
47
51
 
48
52
  return (
49
- <BottomSheet isOpen={isOpen} onClose={onClose} showBackdrop height="75vh" padding="20px 0 0px">
50
- <div style={{ display: "flex", flexDirection: "column", height: "calc(75vh - 40px)" }}>
51
- <div
52
- style={{
53
- display: "flex",
54
- alignItems: "center",
55
- justifyContent: "space-between",
56
- padding: "0 20px 14px",
57
- }}
58
- >
59
- <span style={{ fontSize: 16, fontWeight: 700, color: "#464647" }}>Filtros</span>
60
- <div
61
- onClick={onClose}
62
- style={{ cursor: "pointer", padding: 4, WebkitTapHighlightColor: "transparent" }}
63
- >
64
- <CloseIcon size={14} src={icons?.closeIcon} />
65
- </div>
66
- </div>
67
-
68
- <div style={{ padding: "0 20px 14px" }}>
69
- <div
70
- style={{
71
- display: "flex",
72
- alignItems: "center",
73
- gap: 8,
74
- border: "1px solid #E5E5E5",
75
- borderRadius: 10,
76
- padding: "9px 12px",
77
- }}
78
- >
79
- <SearchIcon size={15} src={icons?.searchIcon} />
80
- <input
81
- value={searchQuery}
82
- onChange={(e) => setSearchQuery(e.target.value)}
83
- placeholder="Buscar filtro"
84
- style={{
85
- border: "none",
86
- outline: "none",
87
- fontSize: 13,
88
- flex: 1,
89
- color: "#464647",
90
- background: "transparent",
91
- }}
92
- />
53
+ <BottomSheet isOpen={isOpen} onClose={onClose} showBackdrop height="65vh" padding="20px 0 0px">
54
+ <div className="flex flex-col h-[calc(65vh-40px)]">
55
+ <div className="flex items-center justify-between px-[20px] pb-[14px]">
56
+ <span className="text-[16px] font-bold text-[#464647] font-['DMSans-Bold',sans-serif]">Filtros</span>
57
+ <div onClick={onClose} className="cursor-pointer p-[4px] [-webkit-tap-highlight-color:transparent]">
58
+ <CloseIcon size={22} src={icons?.closeIcon} />
93
59
  </div>
94
60
  </div>
95
61
 
96
- <div style={{ display: "flex", flex: 1, overflow: "hidden", position: "relative", borderTop: "1px solid #E5E5E5", borderBottom: "1px solid #E5E5E5" }}>
97
- <div
98
- style={{
99
- position: "absolute",
100
- top: 0,
101
- bottom: 0,
102
- left: 0,
103
- width: 130,
104
- backgroundColor: "#F3F4F6",
105
- zIndex: 0,
106
- }}
107
- />
108
- <div
109
- style={{
110
- width: 130,
111
- flexShrink: 0,
112
- position: "relative",
113
- zIndex: 1,
114
- }}
115
- >
62
+ <div className="flex flex-1 overflow-hidden relative border-t border-b border-[#E5E5E5]">
63
+ <div className="absolute top-0 bottom-0 left-0 w-[120px] bg-[#F9FAFB] z-0" />
64
+ <div className="w-[120px] flex-shrink-0 relative z-[1] bg-[#F9FAFB]">
116
65
  {categories.map((cat) => {
117
66
  const isActive = cat.key === activeKey;
118
67
  return (
119
68
  <div
120
69
  key={cat.key}
121
70
  onClick={() => setActiveKey(cat.key)}
122
- style={{
123
- position: "relative",
124
- display: "flex",
125
- alignItems: "center",
126
- minHeight: 44,
127
- boxSizing: "border-box",
128
- padding: "8px 8px 8px 20px",
129
- fontSize: 13,
130
- lineHeight: 1.25,
131
- fontWeight: isActive ? 700 : 400,
132
- color: isActive ? colors.primaryColor : "#464647",
133
- backgroundColor: isActive ? "#fff" : "transparent",
134
- cursor: "pointer",
135
- WebkitTapHighlightColor: "transparent",
136
- userSelect: "none",
137
- WebkitUserSelect: "none",
138
- }}
71
+ className={`relative flex items-center min-h-[44px] box-border py-[8px] pr-[8px] pl-[20px] text-[13px] leading-[1.25] cursor-pointer select-none [-webkit-tap-highlight-color:transparent] ${
72
+ isActive ? "font-bold bg-[#FFFFFF]" : "font-normal bg-transparent"
73
+ }`}
74
+ style={{ color: isActive ? colors.primaryColor : "#464647" }}
139
75
  >
140
76
  {isActive && (
141
77
  <div
142
- style={{
143
- position: "absolute",
144
- left: 0,
145
- top: 8,
146
- bottom: 8,
147
- width: 4,
148
- borderTopRightRadius: 4,
149
- borderBottomRightRadius: 4,
150
- backgroundColor: colors.primaryColor,
151
- }}
78
+ className="absolute left-[0px] top-[8px] bottom-[8px] w-[4px] rounded-tr-[4px] rounded-br-[4px]"
79
+ style={{ backgroundColor: colors.primaryColor }}
152
80
  />
153
81
  )}
154
82
  {cat.label}
@@ -157,21 +85,9 @@ const FiltersMasterSheet = ({
157
85
  })}
158
86
  </div>
159
87
 
160
- <div
161
- style={{ flex: 1, overflowY: "auto", paddingLeft: 16, paddingRight: 20, position: "relative", zIndex: 1, backgroundColor: "#fff" }}
162
- >
88
+ <div className="flex-1 overflow-y-auto pl-[16px] pr-[20px] relative z-[1] bg-white">
163
89
  {activeCategory && (
164
- <div
165
- style={{
166
- fontSize: 11,
167
- fontWeight: 700,
168
- color: "#9B9B9B",
169
- textTransform: "uppercase",
170
- letterSpacing: 0.4,
171
- marginBottom: 5,
172
- marginTop: 15,
173
- }}
174
- >
90
+ <div className="text-[11px] font-bold text-[#9B9B9B] uppercase tracking-[0.4px] mb-[5px] mt-[15px]">
175
91
  {activeCategory.sectionLabel || activeCategory.label}
176
92
  </div>
177
93
  )}
@@ -186,7 +102,7 @@ const FiltersMasterSheet = ({
186
102
  />
187
103
  ) : (
188
104
  <OptionCheckboxList
189
- options={filteredOptions}
105
+ options={activeCategory?.options || []}
190
106
  kind={activeCategory?.kind || "checkbox"}
191
107
  onToggle={(optionId) => onToggleOption(activeCategory.key, optionId)}
192
108
  primaryColor={colors.primaryColor}
@@ -197,7 +113,7 @@ const FiltersMasterSheet = ({
197
113
  </div>
198
114
  </div>
199
115
 
200
- <div style={{ padding: "0px 20px 0" }}>
116
+ <div className="px-[20px]">
201
117
  <SheetFooter onClear={onClearAll} onApply={onApplyAll} colors={colors} />
202
118
  </div>
203
119
  </div>
@@ -23,7 +23,7 @@ const RatingLabel = ({
23
23
  starColor: string;
24
24
  icons?: FilterSortBarIcons;
25
25
  }) => (
26
- <div style={{ display: "flex", alignItems: "center", gap: 1, whiteSpace: "nowrap" }}>
26
+ <div className="flex items-center gap-[1px] whitespace-nowrap">
27
27
  {Array.from({ length: 5 }, (_, i) => (
28
28
  <StarIcon
29
29
  key={i}
@@ -34,14 +34,14 @@ const RatingLabel = ({
34
34
  emptySrc={icons?.starEmptyIcon}
35
35
  />
36
36
  ))}
37
- <span style={{ marginLeft: 6 }}>{label}</span>
37
+ <span className="ml-[6px]">{label}</span>
38
38
  </div>
39
39
  );
40
40
 
41
41
  const OptionCheckboxList = ({ options, kind, onToggle, primaryColor, starColor, icons }: OptionCheckboxListProps) => {
42
42
  if (!options?.length) {
43
43
  return (
44
- <div style={{ padding: "24px 0", textAlign: "center", color: "#9B9B9B", fontSize: 13 }}>
44
+ <div className="py-[24px] text-center text-[#9B9B9B] text-[13px]">
45
45
  No hay opciones disponibles
46
46
  </div>
47
47
  );
@@ -53,23 +53,12 @@ const OptionCheckboxList = ({ options, kind, onToggle, primaryColor, starColor,
53
53
  <div
54
54
  key={option.id}
55
55
  onClick={() => onToggle(option.id)}
56
- style={{
57
- display: "flex",
58
- alignItems: "center",
59
- justifyContent: "space-between",
60
- padding: "12px 2px",
61
- cursor: "pointer",
62
- fontSize: 13,
63
- color: "#464647",
64
- WebkitTapHighlightColor: "transparent",
65
- userSelect: "none",
66
- WebkitUserSelect: "none",
67
- }}
56
+ className="flex items-center justify-between py-[12px] px-[2px] cursor-pointer text-[13px] text-[#464647] select-none [-webkit-tap-highlight-color:transparent]"
68
57
  >
69
58
  {kind === "rating" ? (
70
59
  <RatingLabel stars={option.stars} label={option.label} starColor={starColor} icons={icons} />
71
60
  ) : option.icon ? (
72
- <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
61
+ <div className="flex items-center gap-[10px]">
73
62
  <TimeOfDayIcon variant={option.icon} size={16} src={icons?.timeOfDayIcons?.[option.icon]} />
74
63
  <span>{option.label}</span>
75
64
  </div>