@abcagency/hc-ui-components 1.6.6 → 1.6.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/components/HireControlMap.js +43 -2
- package/dist/components/HireControlMap.js.map +1 -1
- package/dist/components/containers/accordions/filter-item-container.js +3 -1
- package/dist/components/containers/accordions/filter-item-container.js.map +1 -1
- package/dist/components/containers/filter/filter-item-container.js +29 -15
- package/dist/components/containers/filter/filter-item-container.js.map +1 -1
- package/dist/components/modules/accordions/filterItem.js +8 -3
- package/dist/components/modules/accordions/filterItem.js.map +1 -1
- package/dist/components/modules/filter/index.js +87 -20
- package/dist/components/modules/filter/index.js.map +1 -1
- package/dist/components/modules/filter/item.js +8 -3
- package/dist/components/modules/filter/item.js.map +1 -1
- package/dist/components/modules/filter/search.js +6 -1
- package/dist/components/modules/filter/search.js.map +1 -1
- package/dist/contexts/mapListContext.js +48 -3
- package/dist/contexts/mapListContext.js.map +1 -1
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +51 -1
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -1
- package/dist/styles/index.css +1 -1
- package/dist/types/util/filterUtil.d.ts +9 -3
- package/dist/util/filterUtil.js +77 -14
- package/dist/util/filterUtil.js.map +1 -1
- package/dist/util/twMerge.js +9 -0
- package/dist/util/twMerge.js.map +1 -0
- package/package.json +1 -1
- package/src/components/HireControlMap.js +48 -2
- package/src/components/containers/accordions/filter-item-container.js +1 -1
- package/src/components/containers/filter/filter-item-container.js +27 -14
- package/src/components/modules/accordions/filterItem.js +16 -3
- package/src/components/modules/filter/index.js +114 -41
- package/src/components/modules/filter/item.js +15 -3
- package/src/components/modules/filter/search.js +7 -1
- package/src/contexts/mapListContext.tsx +96 -3
- package/src/util/filterUtil.js +63 -17
- package/src/util/twMerge.js +6 -0
|
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|
|
2
2
|
import { useTrackEvent } from '~/contexts/trackEventContext';
|
|
3
3
|
import FilterItem from '~/components/modules/filter/item';
|
|
4
4
|
import { useMapList } from '~/contexts/mapListContext';
|
|
5
|
+
import { twMerge } from '~/util/twMerge';
|
|
5
6
|
|
|
6
7
|
const FilterItemContainer = ({
|
|
7
8
|
className,
|
|
@@ -17,18 +18,37 @@ const FilterItemContainer = ({
|
|
|
17
18
|
subcategoryRequireCategory = false,
|
|
18
19
|
...rest
|
|
19
20
|
}) => {
|
|
21
|
+
// All hooks MUST be called before any early returns
|
|
20
22
|
const { trackEvent, eventTypes } = useTrackEvent();
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
const { filteredListings, filterConfig } = useMapList();
|
|
24
|
+
|
|
25
|
+
// Initialize itemName and itemKey safely
|
|
26
|
+
const itemName = item?.name ? item.name : item;
|
|
27
|
+
const safeItemKey = itemKey === null ? itemName : itemKey;
|
|
28
|
+
const isActive = selectedFilters != undefined && !!selectedFilters[field]?.[safeItemKey];
|
|
24
29
|
const [activeItem, setActiveItem] = useState(isActive);
|
|
25
30
|
|
|
26
31
|
useEffect(() => {
|
|
27
32
|
if (!selectedFilters || isExternalLink) return;
|
|
28
|
-
setActiveItem(!!selectedFilters[field]?.[
|
|
29
|
-
}, [selectedFilters, field,
|
|
33
|
+
setActiveItem(!!selectedFilters[field]?.[safeItemKey]);
|
|
34
|
+
}, [selectedFilters, field, safeItemKey, isExternalLink]);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (selectedFilters && selectedFilters[field] && Object.keys(selectedFilters[field])?.length > 0) return;
|
|
38
|
+
else if (activeItem === true) {
|
|
39
|
+
setActiveItem(false);
|
|
40
|
+
}
|
|
41
|
+
}, [selectedFilters, activeItem, field]);
|
|
42
|
+
|
|
43
|
+
// NOW we can do the guard check after all hooks
|
|
44
|
+
if (!item) {
|
|
45
|
+
console.warn('FilterItemContainer received undefined item for field:', field);
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Re-assign itemKey for the rest of the component
|
|
50
|
+
itemKey = safeItemKey;
|
|
30
51
|
|
|
31
|
-
const { filteredListings } = useMapList();
|
|
32
52
|
const changeHandler = () => {
|
|
33
53
|
if (!isActive || isExternalLink) {
|
|
34
54
|
trackEvent(eventTypes.FILTER_APPLIED, { filterType: field, filterChecked: itemKey });
|
|
@@ -87,16 +107,9 @@ const FilterItemContainer = ({
|
|
|
87
107
|
});
|
|
88
108
|
};
|
|
89
109
|
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
if (selectedFilters && selectedFilters[field] && Object.keys(selectedFilters[field])?.length > 0) return;
|
|
92
|
-
else if (activeItem === true) {
|
|
93
|
-
setActiveItem(false);
|
|
94
|
-
}
|
|
95
|
-
}, [selectedFilters]);
|
|
96
|
-
|
|
97
110
|
return (
|
|
98
111
|
<FilterItem
|
|
99
|
-
className={className}
|
|
112
|
+
className={twMerge(className, filterConfig?.classNames?.filterItem)}
|
|
100
113
|
item={item}
|
|
101
114
|
type={type}
|
|
102
115
|
itemKey={itemKey}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import Accordion from '~/components/modules/accordions/default';
|
|
3
|
+
import { twMerge } from '~/util/twMerge';
|
|
4
|
+
import { useMapList } from '~/contexts/mapListContext';
|
|
3
5
|
|
|
4
6
|
const AccordionFilterItem = ({
|
|
5
7
|
id,
|
|
@@ -7,17 +9,28 @@ const AccordionFilterItem = ({
|
|
|
7
9
|
header,
|
|
8
10
|
body
|
|
9
11
|
}) => {
|
|
12
|
+
const { filterConfig } = useMapList();
|
|
13
|
+
|
|
10
14
|
return (
|
|
11
15
|
<Accordion.Item key={id} id={id}>
|
|
12
16
|
<Accordion.Trigger.HasHeader
|
|
13
17
|
onClick={() => setDefaultValue(id)}
|
|
14
|
-
className=
|
|
18
|
+
className={twMerge(
|
|
19
|
+
"hc-stretched-link hc-text-left",
|
|
20
|
+
filterConfig?.classNames?.filterAccordion
|
|
21
|
+
)}
|
|
15
22
|
iconClassName="hc-order-last"
|
|
16
|
-
headerClassName=
|
|
23
|
+
headerClassName={twMerge(
|
|
24
|
+
"hc-relative hc-py-2 hc-rounded hc-border hc-border-uiAccent/20 hc-bg-white hc-text-sm hc-transition data-[state=open]:hc-border-b-transparent data-[state=open]:hc-rounded-b-none",
|
|
25
|
+
filterConfig?.classNames?.filterAccordionHeader
|
|
26
|
+
)}
|
|
17
27
|
>
|
|
18
28
|
{header}
|
|
19
29
|
</Accordion.Trigger.HasHeader>
|
|
20
|
-
<Accordion.Content bodyClassName=
|
|
30
|
+
<Accordion.Content bodyClassName={twMerge(
|
|
31
|
+
"hc-px-2 hc-py-1 hc-bg-white hc-rounded-b hc-border hc-border-uiAccent/20 hc-border-t-0 hc-max-h-[20vh] md:hc-max-h-[25vh] hc-overflow-auto",
|
|
32
|
+
filterConfig?.classNames?.filterAccordionContent
|
|
33
|
+
)}>
|
|
21
34
|
{body}
|
|
22
35
|
</Accordion.Content>
|
|
23
36
|
</Accordion.Item>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import Button from "~/components/modules/buttons/default";
|
|
3
3
|
import React, { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { twMerge } from '~/util/twMerge';
|
|
5
|
+
import { useMapList } from "~/contexts/mapListContext";
|
|
6
|
+
|
|
4
7
|
const Filter = ({
|
|
5
8
|
className,
|
|
6
9
|
hasActiveFilters,
|
|
@@ -12,36 +15,83 @@ const Filter = ({
|
|
|
12
15
|
style,
|
|
13
16
|
children
|
|
14
17
|
}) => {
|
|
18
|
+
const { filterConfig } = useMapList();
|
|
15
19
|
const contentRef = useRef(null);
|
|
20
|
+
const containerRef = useRef(null);
|
|
16
21
|
const [hasOverflow, setHasOverflow] = useState(false);
|
|
22
|
+
const [showFixedFooter, setShowFixedFooter] = useState(false);
|
|
23
|
+
const transitionTimeoutRef = useRef(null);
|
|
17
24
|
|
|
18
25
|
useEffect(() => {
|
|
19
26
|
const checkOverflow = () => {
|
|
20
27
|
if (contentRef.current) {
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
// Add a small tolerance (5px) to avoid false positives from rounding/padding
|
|
29
|
+
const hasScroll = contentRef.current.scrollHeight > contentRef.current.clientHeight + 5;
|
|
30
|
+
|
|
31
|
+
if (hasScroll !== hasOverflow) {
|
|
32
|
+
setHasOverflow(hasScroll);
|
|
33
|
+
|
|
34
|
+
// Clear any pending transitions
|
|
35
|
+
if (transitionTimeoutRef.current) {
|
|
36
|
+
clearTimeout(transitionTimeoutRef.current);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Add a delay when transitioning to fixed footer to smooth the transition
|
|
40
|
+
if (hasScroll) {
|
|
41
|
+
transitionTimeoutRef.current = setTimeout(() => setShowFixedFooter(true), 200);
|
|
42
|
+
} else {
|
|
43
|
+
// Delay hiding fixed footer to allow animation to complete and prevent flashing
|
|
44
|
+
transitionTimeoutRef.current = setTimeout(() => setShowFixedFooter(false), 200);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
23
47
|
}
|
|
24
48
|
};
|
|
25
49
|
|
|
26
|
-
|
|
50
|
+
// Delay initial check to ensure layout is complete
|
|
51
|
+
setTimeout(checkOverflow, 100);
|
|
52
|
+
|
|
53
|
+
// Check on resize
|
|
27
54
|
window.addEventListener('resize', checkOverflow);
|
|
28
|
-
|
|
55
|
+
|
|
56
|
+
// Use ResizeObserver to detect container size changes
|
|
57
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
58
|
+
setTimeout(checkOverflow, 50);
|
|
59
|
+
});
|
|
60
|
+
if (containerRef.current) {
|
|
61
|
+
resizeObserver.observe(containerRef.current);
|
|
62
|
+
}
|
|
63
|
+
if (contentRef.current) {
|
|
64
|
+
resizeObserver.observe(contentRef.current);
|
|
65
|
+
}
|
|
66
|
+
|
|
29
67
|
// Use MutationObserver to detect content changes
|
|
30
|
-
const
|
|
68
|
+
const mutationObserver = new MutationObserver(() => {
|
|
69
|
+
setTimeout(checkOverflow, 50);
|
|
70
|
+
});
|
|
31
71
|
if (contentRef.current) {
|
|
32
|
-
|
|
72
|
+
mutationObserver.observe(contentRef.current, { childList: true, subtree: true, attributes: true });
|
|
33
73
|
}
|
|
34
74
|
|
|
35
75
|
return () => {
|
|
36
76
|
window.removeEventListener('resize', checkOverflow);
|
|
37
|
-
|
|
77
|
+
resizeObserver.disconnect();
|
|
78
|
+
mutationObserver.disconnect();
|
|
79
|
+
// Clear timeout on unmount
|
|
80
|
+
if (transitionTimeoutRef.current) {
|
|
81
|
+
clearTimeout(transitionTimeoutRef.current);
|
|
82
|
+
}
|
|
38
83
|
};
|
|
39
|
-
}, [children]);
|
|
84
|
+
}, [children, hasOverflow]);
|
|
40
85
|
|
|
41
86
|
// Reusable button component
|
|
42
87
|
const MobileButtons = () => (
|
|
43
88
|
<>
|
|
44
|
-
<Button.Btn
|
|
89
|
+
<Button.Btn
|
|
90
|
+
onClick={handleReset}
|
|
91
|
+
variant="outline"
|
|
92
|
+
size="sm"
|
|
93
|
+
className={filterConfig?.classNames?.resetButton}
|
|
94
|
+
>
|
|
45
95
|
Reset All
|
|
46
96
|
</Button.Btn>
|
|
47
97
|
{selectedFilters && Object.keys(selectedFilters).length > 0 && (
|
|
@@ -49,9 +99,10 @@ const Filter = ({
|
|
|
49
99
|
onClick={() => setMobileTab("listTab")}
|
|
50
100
|
variant="primary"
|
|
51
101
|
size="sm"
|
|
52
|
-
className={
|
|
53
|
-
|
|
54
|
-
|
|
102
|
+
className={twMerge(
|
|
103
|
+
`${hasActiveFilters ? "hc-opacity-0 hc-pointer-events-none" : "hc-opacity-100"}`,
|
|
104
|
+
filterConfig?.classNames?.showJobsButton
|
|
105
|
+
)}
|
|
55
106
|
>
|
|
56
107
|
<Button.Body>
|
|
57
108
|
<Button.Icon icon="fluent:search-12-filled" size="hc-size-3.5" />
|
|
@@ -64,58 +115,80 @@ const Filter = ({
|
|
|
64
115
|
|
|
65
116
|
return (
|
|
66
117
|
<div
|
|
118
|
+
ref={containerRef}
|
|
67
119
|
style={isDesktop ? style : undefined}
|
|
68
|
-
className={
|
|
69
|
-
|
|
120
|
+
className={twMerge(
|
|
121
|
+
`/* Mobile layout */
|
|
70
122
|
hc-relative hc-w-full hc-h-full hc-flex hc-flex-col
|
|
71
123
|
|
|
72
124
|
/* Desktop layout */
|
|
73
|
-
md:hc-
|
|
125
|
+
md:hc-relative md:hc-flex md:hc-flex-col md:hc-max-h-[100%]
|
|
74
126
|
|
|
75
|
-
${className ?? ""}
|
|
76
|
-
|
|
127
|
+
${className ?? ""}`,
|
|
128
|
+
filterConfig?.classNames?.filterContainer
|
|
129
|
+
)}
|
|
77
130
|
>
|
|
78
131
|
{/* Content area with scroll */}
|
|
79
|
-
<div
|
|
132
|
+
<div
|
|
80
133
|
ref={contentRef}
|
|
81
|
-
className={
|
|
134
|
+
className={twMerge(
|
|
135
|
+
`hc-w-full hc-flex-grow hc-max-h-full hc-overflow-auto ${hasOverflow ? 'hc-pb-16' : ''} md:hc-overflow-y-auto ${hasOverflow ? 'md:hc-pb-16' : 'md:hc-pb-0'}`,
|
|
136
|
+
filterConfig?.classNames?.filterContent
|
|
137
|
+
)}
|
|
82
138
|
>
|
|
83
139
|
<div className="hc-px-4 md:hc-pt-4 hc-space-y-4">
|
|
84
140
|
{children}
|
|
85
|
-
|
|
86
|
-
{/*
|
|
87
|
-
{!hasOverflow && (
|
|
88
|
-
<div className=
|
|
89
|
-
|
|
141
|
+
|
|
142
|
+
{/* Inline buttons when no overflow - both mobile and desktop */}
|
|
143
|
+
{!hasOverflow && !showFixedFooter && (
|
|
144
|
+
<div className={twMerge(
|
|
145
|
+
"hc-flex hc-items-center hc-justify-between hc-gap-2 hc-py-2 hc-border-t hc-border-gray-200 hc-animate-in hc-fade-in hc-duration-300",
|
|
146
|
+
filterConfig?.classNames?.filterFooter
|
|
147
|
+
)}>
|
|
148
|
+
{/* Mobile buttons */}
|
|
149
|
+
<div className="hc-flex hc-items-center hc-justify-between hc-gap-2 hc-w-full md:hc-hidden">
|
|
150
|
+
<MobileButtons />
|
|
151
|
+
</div>
|
|
152
|
+
{/* Desktop button */}
|
|
153
|
+
<div className="hc-hidden md:hc-flex">
|
|
154
|
+
<Button.Btn
|
|
155
|
+
onClick={handleReset}
|
|
156
|
+
variant="outline"
|
|
157
|
+
size="sm"
|
|
158
|
+
className={filterConfig?.classNames?.resetButton}
|
|
159
|
+
>
|
|
160
|
+
Reset All
|
|
161
|
+
</Button.Btn>
|
|
162
|
+
</div>
|
|
90
163
|
</div>
|
|
91
164
|
)}
|
|
92
165
|
</div>
|
|
93
166
|
</div>
|
|
94
167
|
|
|
95
|
-
{/* Desktop Reset Button - left aligned */}
|
|
96
|
-
<div className="hc-hidden md:hc-flex md:hc-justify-start hc-px-4 hc-py-4">
|
|
97
|
-
<Button.Btn onClick={handleReset} variant="outline" size="sm">
|
|
98
|
-
Reset All
|
|
99
|
-
</Button.Btn>
|
|
100
|
-
</div>
|
|
101
|
-
|
|
102
168
|
{/* Mobile Footer - fixed at bottom (only when overflow) */}
|
|
103
|
-
{
|
|
169
|
+
{showFixedFooter && (
|
|
104
170
|
<div
|
|
105
|
-
className="
|
|
106
|
-
hc-w-full
|
|
107
|
-
hc-absolute hc-bottom-0 hc-left-0 hc-right-0
|
|
108
|
-
hc-flex hc-items-center hc-justify-between hc-gap-2 hc-py-2 hc-px-4
|
|
109
|
-
hc-bg-white hc-border-t hc-border-gray-200
|
|
110
|
-
hc-z-10
|
|
111
|
-
md:hc-hidden
|
|
112
|
-
"
|
|
171
|
+
className="hc-w-full hc-absolute hc-bottom-0 hc-left-0 hc-right-0 hc-flex hc-items-center hc-justify-between hc-gap-2 hc-py-2 hc-px-4 hc-bg-gray-100 hc-border-t hc-border-gray-200 hc-z-50 md:hc-hidden hc-animate-in hc-fade-in hc-slide-in-from-bottom-2 hc-duration-200"
|
|
113
172
|
>
|
|
114
173
|
<MobileButtons />
|
|
115
174
|
</div>
|
|
116
175
|
)}
|
|
176
|
+
|
|
177
|
+
{/* Desktop Footer - fixed at bottom (only when overflow) */}
|
|
178
|
+
{showFixedFooter && (
|
|
179
|
+
<div className="hc-hidden md:hc-flex md:hc-justify-start hc-px-4 hc-py-4 hc-absolute hc-bottom-0 hc-left-0 hc-right-0 hc-bg-gray-100 hc-border-t hc-border-gray-200 hc-z-50 hc-animate-in hc-fade-in hc-slide-in-from-bottom-2 hc-duration-200">
|
|
180
|
+
<Button.Btn
|
|
181
|
+
onClick={handleReset}
|
|
182
|
+
variant="outline"
|
|
183
|
+
size="sm"
|
|
184
|
+
className={filterConfig?.classNames?.resetButton}
|
|
185
|
+
>
|
|
186
|
+
Reset All
|
|
187
|
+
</Button.Btn>
|
|
188
|
+
</div>
|
|
189
|
+
)}
|
|
117
190
|
</div>
|
|
118
191
|
);
|
|
119
192
|
};
|
|
120
193
|
|
|
121
|
-
export default Filter;
|
|
194
|
+
export default Filter;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import Icon from '~/components/modules/icon';
|
|
3
|
+
import { twMerge } from '~/util/twMerge';
|
|
4
|
+
import { useMapList } from '~/contexts/mapListContext';
|
|
3
5
|
|
|
4
6
|
const FilterItem = ({
|
|
5
7
|
className,
|
|
@@ -17,6 +19,7 @@ const FilterItem = ({
|
|
|
17
19
|
eventTypes,
|
|
18
20
|
...rest
|
|
19
21
|
}) => {
|
|
22
|
+
const { filterConfig } = useMapList();
|
|
20
23
|
const itemName = item.name ? item.name : item;
|
|
21
24
|
|
|
22
25
|
return (
|
|
@@ -47,7 +50,10 @@ const FilterItem = ({
|
|
|
47
50
|
disabled={item.count === 0}
|
|
48
51
|
value={itemName}
|
|
49
52
|
type={type}
|
|
50
|
-
className=
|
|
53
|
+
className={twMerge(
|
|
54
|
+
"hc-size-4 hc-mt-px hc-text-primary hc-border-uiAccent/30 hc-transition-colors hc-rounded-sm",
|
|
55
|
+
filterConfig?.classNames?.filterItemCheckbox
|
|
56
|
+
)}
|
|
51
57
|
checked={activeItem}
|
|
52
58
|
onChange={() => {
|
|
53
59
|
setActiveItem(!activeItem);
|
|
@@ -56,9 +62,15 @@ const FilterItem = ({
|
|
|
56
62
|
/>
|
|
57
63
|
)}
|
|
58
64
|
|
|
59
|
-
<span className=
|
|
65
|
+
<span className={twMerge(
|
|
66
|
+
"hc-text-left hc-font-medium",
|
|
67
|
+
filterConfig?.classNames?.filterItemLabel
|
|
68
|
+
)}>{itemName}</span>
|
|
60
69
|
{hasCount && !isExternalLink && (
|
|
61
|
-
<span className=
|
|
70
|
+
<span className={twMerge(
|
|
71
|
+
"hc-inline-block hc-mt-1 hc-ml-auto hc-text-xs hc-leading-none hc-text-primary",
|
|
72
|
+
filterConfig?.classNames?.filterItemCount
|
|
73
|
+
)}>
|
|
62
74
|
({item.count})
|
|
63
75
|
</span>
|
|
64
76
|
)}
|
|
@@ -2,6 +2,8 @@ import React, { useRef } from 'react';
|
|
|
2
2
|
import Button from '~/components/modules/buttons/default';
|
|
3
3
|
import Icon from '~/components/modules/icon';
|
|
4
4
|
import FilterCard from '~/components/modules/cards/filter';
|
|
5
|
+
import { twMerge } from '~/util/twMerge';
|
|
6
|
+
import { useMapList } from '~/contexts/mapListContext';
|
|
5
7
|
|
|
6
8
|
const Search = ({
|
|
7
9
|
inputPlaceholder,
|
|
@@ -14,6 +16,7 @@ const Search = ({
|
|
|
14
16
|
handleReset,
|
|
15
17
|
label
|
|
16
18
|
}) => {
|
|
19
|
+
const { filterConfig } = useMapList();
|
|
17
20
|
const inputRef = useRef(null);
|
|
18
21
|
const buttonRef = useRef(null);
|
|
19
22
|
|
|
@@ -47,7 +50,10 @@ const Search = ({
|
|
|
47
50
|
}}
|
|
48
51
|
placeholder={inputPlaceholder}
|
|
49
52
|
value={inputValue}
|
|
50
|
-
className=
|
|
53
|
+
className={twMerge(
|
|
54
|
+
"hc-w-full hc-px-0 hc-py-2 hc-text-sm hc-border-0 hc-transition-colors placeholder:hc-text-uiText/50 focus:hc-ring-0 focus:hc-outline-none",
|
|
55
|
+
filterConfig?.classNames?.searchInput
|
|
56
|
+
)}
|
|
51
57
|
onChange={handleInputChange}
|
|
52
58
|
/>
|
|
53
59
|
|
|
@@ -55,6 +55,28 @@ interface MapListContextProps {
|
|
|
55
55
|
containerStyle?: any;
|
|
56
56
|
ExpandListComponent?: React.ComponentType<{ listing: any }> | ((listing: any) => JSX.Element) | null;
|
|
57
57
|
noEntities?: boolean;
|
|
58
|
+
filterConfig?: {
|
|
59
|
+
hideZeroResults?: boolean;
|
|
60
|
+
dynamicCounts?: boolean;
|
|
61
|
+
showFavorites?: boolean;
|
|
62
|
+
collapsedByDefault?: boolean;
|
|
63
|
+
sortAlphabetically?: boolean;
|
|
64
|
+
classNames?: {
|
|
65
|
+
filterContainer?: string;
|
|
66
|
+
filterContent?: string;
|
|
67
|
+
filterAccordion?: string;
|
|
68
|
+
filterAccordionHeader?: string;
|
|
69
|
+
filterAccordionContent?: string;
|
|
70
|
+
filterItem?: string;
|
|
71
|
+
filterItemLabel?: string;
|
|
72
|
+
filterItemCheckbox?: string;
|
|
73
|
+
filterItemCount?: string;
|
|
74
|
+
searchInput?: string;
|
|
75
|
+
resetButton?: string;
|
|
76
|
+
showJobsButton?: string;
|
|
77
|
+
filterFooter?: string;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
58
80
|
}
|
|
59
81
|
|
|
60
82
|
const MapListContext = createContext<MapListContextProps | undefined>(undefined);
|
|
@@ -101,6 +123,28 @@ interface MapListProviderProps {
|
|
|
101
123
|
hideMap?: boolean;
|
|
102
124
|
hideFilters?: boolean;
|
|
103
125
|
noEntities?: boolean;
|
|
126
|
+
filterConfig?: {
|
|
127
|
+
hideZeroResults?: boolean;
|
|
128
|
+
dynamicCounts?: boolean;
|
|
129
|
+
showFavorites?: boolean;
|
|
130
|
+
collapsedByDefault?: boolean;
|
|
131
|
+
sortAlphabetically?: boolean;
|
|
132
|
+
classNames?: {
|
|
133
|
+
filterContainer?: string;
|
|
134
|
+
filterContent?: string;
|
|
135
|
+
filterAccordion?: string;
|
|
136
|
+
filterAccordionHeader?: string;
|
|
137
|
+
filterAccordionContent?: string;
|
|
138
|
+
filterItem?: string;
|
|
139
|
+
filterItemLabel?: string;
|
|
140
|
+
filterItemCheckbox?: string;
|
|
141
|
+
filterItemCount?: string;
|
|
142
|
+
searchInput?: string;
|
|
143
|
+
resetButton?: string;
|
|
144
|
+
showJobsButton?: string;
|
|
145
|
+
filterFooter?: string;
|
|
146
|
+
};
|
|
147
|
+
};
|
|
104
148
|
}
|
|
105
149
|
|
|
106
150
|
export const MapListProvider: React.FC<MapListProviderProps> = ({
|
|
@@ -128,7 +172,29 @@ export const MapListProvider: React.FC<MapListProviderProps> = ({
|
|
|
128
172
|
localStorageKey,
|
|
129
173
|
hideMap = false,
|
|
130
174
|
hideFilters = false,
|
|
131
|
-
noEntities = false
|
|
175
|
+
noEntities = false,
|
|
176
|
+
filterConfig = {
|
|
177
|
+
hideZeroResults: false,
|
|
178
|
+
dynamicCounts: true,
|
|
179
|
+
showFavorites: true,
|
|
180
|
+
collapsedByDefault: false,
|
|
181
|
+
sortAlphabetically: false,
|
|
182
|
+
classNames: {
|
|
183
|
+
filterContainer: '',
|
|
184
|
+
filterContent: '',
|
|
185
|
+
filterAccordion: '',
|
|
186
|
+
filterAccordionHeader: '',
|
|
187
|
+
filterAccordionContent: '',
|
|
188
|
+
filterItem: '',
|
|
189
|
+
filterItemLabel: '',
|
|
190
|
+
filterItemCheckbox: '',
|
|
191
|
+
filterItemCount: '',
|
|
192
|
+
searchInput: '',
|
|
193
|
+
resetButton: '',
|
|
194
|
+
showJobsButton: '',
|
|
195
|
+
filterFooter: ''
|
|
196
|
+
}
|
|
197
|
+
}
|
|
132
198
|
}) => {
|
|
133
199
|
const firstLoadFilters = () =>{
|
|
134
200
|
let urlData = filtersFromURL(window.location);
|
|
@@ -318,6 +384,31 @@ export const MapListProvider: React.FC<MapListProviderProps> = ({
|
|
|
318
384
|
if (tempSelectedFilters) {
|
|
319
385
|
const keys = Object.keys(tempSelectedFilters);
|
|
320
386
|
const lastKey = keys[keys.length - 1];
|
|
387
|
+
|
|
388
|
+
// Ensure all filterConfig properties have values
|
|
389
|
+
const normalizedFilterConfig = {
|
|
390
|
+
hideZeroResults: filterConfig?.hideZeroResults ?? false,
|
|
391
|
+
dynamicCounts: filterConfig?.dynamicCounts ?? true,
|
|
392
|
+
showFavorites: filterConfig?.showFavorites ?? true,
|
|
393
|
+
collapsedByDefault: filterConfig?.collapsedByDefault ?? false,
|
|
394
|
+
sortAlphabetically: filterConfig?.sortAlphabetically ?? false,
|
|
395
|
+
classNames: {
|
|
396
|
+
filterContainer: filterConfig?.classNames?.filterContainer ?? '',
|
|
397
|
+
filterContent: filterConfig?.classNames?.filterContent ?? '',
|
|
398
|
+
filterAccordion: filterConfig?.classNames?.filterAccordion ?? '',
|
|
399
|
+
filterAccordionHeader: filterConfig?.classNames?.filterAccordionHeader ?? '',
|
|
400
|
+
filterAccordionContent: filterConfig?.classNames?.filterAccordionContent ?? '',
|
|
401
|
+
filterItem: filterConfig?.classNames?.filterItem ?? '',
|
|
402
|
+
filterItemLabel: filterConfig?.classNames?.filterItemLabel ?? '',
|
|
403
|
+
filterItemCheckbox: filterConfig?.classNames?.filterItemCheckbox ?? '',
|
|
404
|
+
filterItemCount: filterConfig?.classNames?.filterItemCount ?? '',
|
|
405
|
+
searchInput: filterConfig?.classNames?.searchInput ?? '',
|
|
406
|
+
resetButton: filterConfig?.classNames?.resetButton ?? '',
|
|
407
|
+
showJobsButton: filterConfig?.classNames?.showJobsButton ?? '',
|
|
408
|
+
filterFooter: filterConfig?.classNames?.filterFooter ?? ''
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
|
|
321
412
|
const options = generateFilterOptions(
|
|
322
413
|
filteredListings,
|
|
323
414
|
allListings,
|
|
@@ -325,7 +416,8 @@ export const MapListProvider: React.FC<MapListProviderProps> = ({
|
|
|
325
416
|
filterOptions,
|
|
326
417
|
lastKey,
|
|
327
418
|
favorites,
|
|
328
|
-
tempSelectedFilters
|
|
419
|
+
tempSelectedFilters,
|
|
420
|
+
normalizedFilterConfig
|
|
329
421
|
);
|
|
330
422
|
if (options) {
|
|
331
423
|
setFilterOptions(options);
|
|
@@ -403,7 +495,8 @@ export const MapListProvider: React.FC<MapListProviderProps> = ({
|
|
|
403
495
|
hiddenFilters,
|
|
404
496
|
containerStyle,
|
|
405
497
|
ExpandListComponent,
|
|
406
|
-
noEntities
|
|
498
|
+
noEntities,
|
|
499
|
+
filterConfig
|
|
407
500
|
}}>
|
|
408
501
|
{children}
|
|
409
502
|
</MapListContext.Provider>
|