kupos-ui-components-lib 10.4.13 → 10.4.15
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/assets/images/anims/service_list/60_percent_anim.json +1 -0
- package/dist/assets/images/filter/arrow-down.png +0 -0
- package/dist/assets/images/filter/rapido.svg +14 -0
- package/dist/components/FilterSortBar/FilterChip.d.ts +6 -2
- package/dist/components/FilterSortBar/FilterChip.js +10 -32
- package/dist/components/FilterSortBar/FilterSortBarMobile.d.ts +1 -1
- package/dist/components/FilterSortBar/FilterSortBarMobile.js +86 -38
- package/dist/components/FilterSortBar/FiltersMasterSheet.d.ts +3 -1
- package/dist/components/FilterSortBar/FiltersMasterSheet.js +28 -97
- package/dist/components/FilterSortBar/OptionCheckboxList.js +5 -16
- package/dist/components/FilterSortBar/PriceRangeSlider.js +17 -55
- package/dist/components/FilterSortBar/SheetFooter.js +3 -30
- package/dist/components/FilterSortBar/defaultIcons.js +9 -0
- package/dist/components/FilterSortBar/icons.d.ts +7 -0
- package/dist/components/FilterSortBar/icons.js +8 -1
- package/dist/components/FilterSortBar/index.d.ts +1 -1
- package/dist/components/FilterSortBar/types.d.ts +34 -0
- package/dist/components/PaymentOptions/PaymentOptionsDesktop.d.ts +4 -0
- package/dist/components/PaymentOptions/PaymentOptionsDesktop.js +5 -0
- package/dist/components/PaymentOptions/PaymentOptionsMobile.d.ts +4 -0
- package/dist/components/PaymentOptions/PaymentOptionsMobile.js +5 -0
- package/dist/components/PaymentOptions/ResponsivePaymentOptions.d.ts +3 -0
- package/dist/components/PaymentOptions/ResponsivePaymentOptions.js +10 -0
- package/dist/components/PaymentOptions/index.d.ts +5 -0
- package/dist/components/PaymentOptions/index.js +4 -0
- package/dist/components/PaymentOptions/mobileTypes.d.ts +0 -0
- package/dist/components/PaymentOptions/mobileTypes.js +0 -0
- package/dist/components/PaymentOptions/type.d.ts +4 -0
- package/dist/components/PaymentOptions/type.js +1 -0
- package/dist/components/PaymentSideBar/PaymentSideBarMobile.js +1 -1
- package/dist/components/ServiceItem/ExpandedDropdown.d.ts +19 -0
- package/dist/components/ServiceItem/ExpandedDropdown.js +28 -0
- package/dist/components/ServiceItem/ServiceItemDesktopPeru.d.ts +6 -0
- package/dist/components/ServiceItem/ServiceItemDesktopPeru.js +612 -0
- package/dist/index.d.ts +1 -1
- package/dist/styles.css +233 -0
- package/dist/ui/PaymentCardMobile/PaymentCardMobile.js +40 -51
- package/dist/ui/TopAmenities/PromoCountdown.d.ts +18 -0
- package/dist/ui/TopAmenities/PromoCountdown.js +55 -0
- package/package.json +2 -2
- package/src/assets/images/filter/arrow-down.png +0 -0
- package/src/assets/images/filter/rapido.svg +14 -0
- package/src/components/FilterSortBar/FilterChip.tsx +31 -35
- package/src/components/FilterSortBar/FilterSortBarMobile.tsx +122 -66
- package/src/components/FilterSortBar/FiltersMasterSheet.tsx +34 -118
- package/src/components/FilterSortBar/OptionCheckboxList.tsx +5 -16
- package/src/components/FilterSortBar/PriceRangeSlider.tsx +20 -61
- package/src/components/FilterSortBar/SheetFooter.tsx +4 -32
- package/src/components/FilterSortBar/defaultIcons.ts +11 -0
- package/src/components/FilterSortBar/icons.tsx +38 -2
- package/src/components/FilterSortBar/index.ts +2 -0
- package/src/components/FilterSortBar/types.ts +36 -0
- package/src/components/PaymentSideBar/PaymentSideBarMobile.tsx +4 -1
- package/src/index.ts +2 -0
- package/src/ui/PaymentCardMobile/PaymentCardMobile.tsx +136 -98
- package/src/components/FilterSortBar/SingleFilterSheet.tsx +0 -82
|
@@ -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
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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,45 @@ 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
|
+
// A cohort sort chip's matching checkbox (checkedViaSort: true, option
|
|
180
|
+
// id === sort key) mirrors this chip inside the Filtros sheet. Keep that
|
|
181
|
+
// mirror in sync when the chip is toggled from the bar — otherwise the
|
|
182
|
+
// chip turns off but the underlying filter option stays selected.
|
|
183
|
+
if (!target.direction) {
|
|
184
|
+
const nextSelected = !target.active;
|
|
185
|
+
const hasMirror = categories.some((cat) => cat.options?.some((o) => o.checkedViaSort && o.id === key));
|
|
186
|
+
if (hasMirror) {
|
|
187
|
+
const updatedCategories = categories.map((cat) => ({
|
|
188
|
+
...cat,
|
|
189
|
+
options: cat.options?.map((o) => (o.checkedViaSort && o.id === key ? { ...o, selected: nextSelected } : o)),
|
|
190
|
+
}));
|
|
191
|
+
onApply("all", updatedCategories);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
140
196
|
return (
|
|
141
197
|
<div>
|
|
142
|
-
<div
|
|
198
|
+
<div className="flex items-center gap-[8px]">
|
|
143
199
|
<FilterChip
|
|
144
200
|
label="Filtros"
|
|
145
201
|
active={totalActiveCategories > 0}
|
|
@@ -152,73 +208,73 @@ const FilterSortBarMobile = ({
|
|
|
152
208
|
icons={resolvedIcons}
|
|
153
209
|
/>
|
|
154
210
|
<div
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
alignSelf: "stretch",
|
|
158
|
-
backgroundColor: resolvedColors.chipBorderColor,
|
|
159
|
-
opacity: 0.5,
|
|
160
|
-
flexShrink: 0,
|
|
161
|
-
}}
|
|
211
|
+
className="w-px self-stretch flex-shrink-0 opacity-50"
|
|
212
|
+
style={{ backgroundColor: resolvedColors.chipBorderColor }}
|
|
162
213
|
/>
|
|
163
214
|
<div
|
|
164
215
|
ref={scrollRef}
|
|
165
216
|
onScroll={handleScroll}
|
|
166
|
-
|
|
167
|
-
display: "flex",
|
|
168
|
-
alignItems: "center",
|
|
169
|
-
gap: 8,
|
|
170
|
-
overflowX: "auto",
|
|
171
|
-
scrollbarWidth: "none",
|
|
172
|
-
paddingBottom: 2,
|
|
173
|
-
}}
|
|
217
|
+
className="flex items-center gap-[8px] overflow-x-auto pb-[2px] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
|
174
218
|
>
|
|
175
|
-
{
|
|
219
|
+
{sortOptions?.map((sort) => (
|
|
176
220
|
<FilterChip
|
|
177
|
-
key={
|
|
178
|
-
label={
|
|
179
|
-
active={
|
|
180
|
-
isOpen={
|
|
181
|
-
onClick={() =>
|
|
182
|
-
iconSrc={
|
|
183
|
-
|
|
184
|
-
|
|
221
|
+
key={`sort:${sort.key}`}
|
|
222
|
+
label={sort.label}
|
|
223
|
+
active={sort.active}
|
|
224
|
+
isOpen={false}
|
|
225
|
+
onClick={() => handleSortChipClick(sort.key)}
|
|
226
|
+
iconSrc={resolveSortIcon(sort.key)}
|
|
227
|
+
sortDirection={sort.direction && sort.active ? sort.direction : undefined}
|
|
228
|
+
hideTrailingIcon={!(sort.direction && sort.active)}
|
|
229
|
+
badgeCount={sort.active ? sort.count : undefined}
|
|
185
230
|
colors={resolvedColors}
|
|
186
231
|
icons={resolvedIcons}
|
|
187
232
|
/>
|
|
188
233
|
))}
|
|
234
|
+
{categories
|
|
235
|
+
// hideChip categories never get a standalone top-bar chip, even
|
|
236
|
+
// once something is selected inside the Filtros sheet — surfacing
|
|
237
|
+
// it there duplicated the dedicated sort chip for the same
|
|
238
|
+
// concept (e.g. "Horarios" filter next to the "Horario" sort chip).
|
|
239
|
+
.filter((category) => !category.hideChip)
|
|
240
|
+
.map((category) => (
|
|
241
|
+
<FilterChip
|
|
242
|
+
key={category.key}
|
|
243
|
+
label={category.label}
|
|
244
|
+
active={isCategoryActive(category)}
|
|
245
|
+
isOpen={openSheet === category.key}
|
|
246
|
+
onClick={() => openSheetFor(category.key)}
|
|
247
|
+
iconSrc={resolvedIcons.categoryIcons?.[category.key]}
|
|
248
|
+
activeIconSrc={resolvedIcons.categoryIconsActive?.[category.key]}
|
|
249
|
+
badgeCount={getCategoryActiveCount(category)}
|
|
250
|
+
colors={resolvedColors}
|
|
251
|
+
icons={resolvedIcons}
|
|
252
|
+
/>
|
|
253
|
+
))}
|
|
189
254
|
{categories
|
|
190
255
|
.filter((category) => category.inlineOptions && category.options?.length)
|
|
191
256
|
.flatMap((category) =>
|
|
192
|
-
(category.options || [])
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
257
|
+
(category.options || [])
|
|
258
|
+
.filter((option) => !option.excludeFromInline)
|
|
259
|
+
.map((option) => (
|
|
260
|
+
<FilterChip
|
|
261
|
+
key={`${category.key}:${option.id}`}
|
|
262
|
+
label={option.label}
|
|
263
|
+
active={option.selected}
|
|
264
|
+
isOpen={false}
|
|
265
|
+
onClick={() => handleInlineOptionToggle(category.key, option.id)}
|
|
266
|
+
iconSrc={option.icon ? resolvedIcons.timeOfDayIcons?.[option.icon] : undefined}
|
|
267
|
+
colors={resolvedColors}
|
|
268
|
+
icons={resolvedIcons}
|
|
269
|
+
/>
|
|
270
|
+
)),
|
|
204
271
|
)}
|
|
205
272
|
</div>
|
|
206
273
|
</div>
|
|
207
274
|
|
|
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
275
|
<FiltersMasterSheet
|
|
221
276
|
isOpen={isMasterOpen}
|
|
277
|
+
initialCategoryKey={initialCategoryKey}
|
|
222
278
|
categories={draftCategories}
|
|
223
279
|
onClose={closeSheet}
|
|
224
280
|
onToggleOption={handleDraftToggleOption}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
2
|
import { BottomSheet } from "../../ui/BottomSheet";
|
|
3
|
-
import { CloseIcon
|
|
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
|
-
|
|
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
|
|
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="
|
|
50
|
-
<div
|
|
51
|
-
<div
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
97
|
-
<div
|
|
98
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
143
|
-
|
|
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={
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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>
|
|
@@ -72,66 +72,37 @@ const PriceRangeSlider = ({
|
|
|
72
72
|
};
|
|
73
73
|
|
|
74
74
|
return (
|
|
75
|
-
<div
|
|
76
|
-
<div
|
|
77
|
-
<div
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
border: "1px solid #E5E5E5",
|
|
81
|
-
borderRadius: 8,
|
|
82
|
-
padding: "6px 10px 8px",
|
|
83
|
-
}}
|
|
84
|
-
>
|
|
85
|
-
<div style={{ fontSize: 11, color: "#9B9B9B", marginBottom: 2 }}>{minLabel}</div>
|
|
86
|
-
<div style={{ fontSize: 13, fontWeight: 700, color: "#464647" }}>
|
|
75
|
+
<div className="p-[8px_4px_4px]">
|
|
76
|
+
<div className="flex items-center gap-[8px] mb-[28px]">
|
|
77
|
+
<div className="flex-1 border border-[#E5E5E5] rounded-[8px] p-[6px_10px_8px]">
|
|
78
|
+
<div className="text-[11px] text-[#464647] mb-[2px]">{minLabel}</div>
|
|
79
|
+
<div className="text-[13px] font-bold text-[#464647]">
|
|
87
80
|
{formatCurrency(currentMin, currencySign)}
|
|
88
81
|
</div>
|
|
89
82
|
</div>
|
|
90
|
-
<span
|
|
91
|
-
<div
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
border: "1px solid #E5E5E5",
|
|
95
|
-
borderRadius: 8,
|
|
96
|
-
padding: "6px 10px 8px",
|
|
97
|
-
}}
|
|
98
|
-
>
|
|
99
|
-
<div style={{ fontSize: 11, color: "#9B9B9B", marginBottom: 2 }}>{maxLabel}</div>
|
|
100
|
-
<div style={{ fontSize: 13, fontWeight: 700, color: "#464647" }}>
|
|
83
|
+
<span className="text-[#9B9B9B] text-[13px]">-</span>
|
|
84
|
+
<div className="flex-1 border border-[#E5E5E5] rounded-[8px] p-[6px_10px_8px]">
|
|
85
|
+
<div className="text-[11px] text-[#464647] mb-[2px]">{maxLabel}</div>
|
|
86
|
+
<div className="text-[13px] font-bold text-[#464647]">
|
|
101
87
|
{formatCurrency(currentMax, currencySign)}
|
|
102
88
|
</div>
|
|
103
89
|
</div>
|
|
104
90
|
</div>
|
|
105
91
|
|
|
106
|
-
<div style={{ padding: `0 ${THUMB_RADIUS}px
|
|
92
|
+
<div className="overflow-visible" style={{ padding: `0 ${THUMB_RADIUS}px` }}>
|
|
107
93
|
<div
|
|
108
94
|
ref={trackRef}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
height: THUMB_SIZE,
|
|
112
|
-
display: "flex",
|
|
113
|
-
alignItems: "center",
|
|
114
|
-
touchAction: "none",
|
|
115
|
-
overflow: "visible",
|
|
116
|
-
}}
|
|
95
|
+
className="relative flex items-center overflow-visible [touch-action:none]"
|
|
96
|
+
style={{ height: THUMB_SIZE }}
|
|
117
97
|
>
|
|
98
|
+
<div className="absolute left-0 right-0 h-[3px] rounded-[2px] bg-[#E5E5E5]" />
|
|
99
|
+
{/* Stops right at each thumb's border instead of reaching its center
|
|
100
|
+
(which would run the line underneath half of the circle). */}
|
|
118
101
|
<div
|
|
102
|
+
className="absolute h-[3px] rounded-[2px]"
|
|
119
103
|
style={{
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
right: 0,
|
|
123
|
-
height: 3,
|
|
124
|
-
borderRadius: 2,
|
|
125
|
-
backgroundColor: "#E5E5E5",
|
|
126
|
-
}}
|
|
127
|
-
/>
|
|
128
|
-
<div
|
|
129
|
-
style={{
|
|
130
|
-
position: "absolute",
|
|
131
|
-
left: `${minPct}%`,
|
|
132
|
-
right: `${100 - maxPct}%`,
|
|
133
|
-
height: 3,
|
|
134
|
-
borderRadius: 2,
|
|
104
|
+
left: `calc(${minPct}% + ${THUMB_RADIUS}px)`,
|
|
105
|
+
right: `calc(${100 - maxPct}% + ${THUMB_RADIUS}px)`,
|
|
135
106
|
backgroundColor: primaryColor,
|
|
136
107
|
}}
|
|
137
108
|
/>
|
|
@@ -148,32 +119,20 @@ const PriceRangeSlider = ({
|
|
|
148
119
|
aria-valuemin={min}
|
|
149
120
|
aria-valuemax={max}
|
|
150
121
|
aria-valuenow={thumb === "min" ? currentMin : currentMax}
|
|
122
|
+
className="absolute rounded-full bg-white cursor-grab shadow-[0_1px_4px_rgba(0,0,0,0.25)]"
|
|
151
123
|
style={{
|
|
152
|
-
position: "absolute",
|
|
153
124
|
left: `${pct}%`,
|
|
154
125
|
transform: "translateX(-50%)",
|
|
155
126
|
width: THUMB_SIZE,
|
|
156
127
|
height: THUMB_SIZE,
|
|
157
|
-
borderRadius: "50%",
|
|
158
|
-
backgroundColor: "#fff",
|
|
159
128
|
border: `2px solid ${primaryColor}`,
|
|
160
|
-
boxShadow: "0 1px 4px rgba(0,0,0,0.25)",
|
|
161
|
-
cursor: "grab",
|
|
162
129
|
zIndex: thumb === "min" ? 2 : 3,
|
|
163
130
|
}}
|
|
164
131
|
/>
|
|
165
132
|
))}
|
|
166
133
|
</div>
|
|
167
134
|
</div>
|
|
168
|
-
<div
|
|
169
|
-
style={{
|
|
170
|
-
display: "flex",
|
|
171
|
-
justifyContent: "space-between",
|
|
172
|
-
fontSize: 11,
|
|
173
|
-
color: "#9B9B9B",
|
|
174
|
-
marginTop: 8,
|
|
175
|
-
}}
|
|
176
|
-
>
|
|
135
|
+
<div className="flex justify-between text-[11px] text-[#464647] mt-[8px]">
|
|
177
136
|
<span>{formatCurrency(min, currencySign)}</span>
|
|
178
137
|
<span>{formatCurrency(max, currencySign)}</span>
|
|
179
138
|
</div>
|