@abcagency/hc-ui-components 1.6.5 → 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/containers/list/item-list-container.js +1 -1
- package/dist/components/containers/list/item-list-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 +123 -19
- 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/components/modules/list/list-item/list-item.js.map +1 -1
- package/dist/components/modules/maps/tabs.js +1 -1
- package/dist/components/modules/maps/tabs.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/algoliaSearchUtil.js.map +1 -1
- 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/containers/list/item-list-container.tsx +1 -1
- package/src/components/modules/accordions/filterItem.js +16 -3
- package/src/components/modules/filter/index.js +161 -43
- package/src/components/modules/filter/item.js +15 -3
- package/src/components/modules/filter/search.js +7 -1
- package/src/components/modules/list/list-item/list-item.jsx +1 -1
- package/src/components/modules/maps/tabs.js +10 -10
- package/src/contexts/mapListContext.tsx +103 -10
- package/src/util/algoliaSearchUtil.js +7 -7
- package/src/util/filterUtil.js +63 -17
- package/src/util/twMerge.js +6 -0
|
@@ -122,7 +122,52 @@ export const HireControlMap = ({
|
|
|
122
122
|
// Algolia search configuration (optional)
|
|
123
123
|
algoliaAppId = null,
|
|
124
124
|
algoliaApiKey = null,
|
|
125
|
-
algoliaIndexName = null
|
|
125
|
+
algoliaIndexName = null,
|
|
126
|
+
/**
|
|
127
|
+
* Filter configuration (optional)
|
|
128
|
+
* @param {boolean} hideZeroResults - Hide filter options with 0 results (default: false)
|
|
129
|
+
* @param {boolean} dynamicCounts - Update counts based on active filters.
|
|
130
|
+
* If false, shows initial/total counts (default: true)
|
|
131
|
+
* @param {boolean} showFavorites - Show favorites special feature filter (default: true)
|
|
132
|
+
* @param {boolean} collapsedByDefault - Start with all filter sections collapsed (default: false)
|
|
133
|
+
* @param {boolean} sortAlphabetically - Sort filter options alphabetically instead of by count (default: false)
|
|
134
|
+
* @param {object} classNames - Custom Tailwind classes to apply to filter components
|
|
135
|
+
* @param {string} classNames.filterContainer - Classes for the main filter container wrapper
|
|
136
|
+
* @param {string} classNames.filterContent - Classes for the scrollable filter content area
|
|
137
|
+
* @param {string} classNames.filterAccordion - Classes for each filter accordion section
|
|
138
|
+
* @param {string} classNames.filterAccordionHeader - Classes for accordion header/trigger
|
|
139
|
+
* @param {string} classNames.filterAccordionContent - Classes for accordion content area
|
|
140
|
+
* @param {string} classNames.filterItem - Classes for individual filter items (checkboxes/options)
|
|
141
|
+
* @param {string} classNames.filterItemLabel - Classes for filter item labels
|
|
142
|
+
* @param {string} classNames.filterItemCheckbox - Classes for filter item checkboxes
|
|
143
|
+
* @param {string} classNames.filterItemCount - Classes for filter item count badges
|
|
144
|
+
* @param {string} classNames.searchInput - Classes for search input field
|
|
145
|
+
* @param {string} classNames.resetButton - Classes for reset all button
|
|
146
|
+
* @param {string} classNames.showJobsButton - Classes for show jobs button
|
|
147
|
+
* @param {string} classNames.filterFooter - Classes for the footer containing buttons
|
|
148
|
+
*/
|
|
149
|
+
filterConfig = {
|
|
150
|
+
hideZeroResults: false, // Hide filter options with 0 results
|
|
151
|
+
dynamicCounts: true, // Update counts based on other active filters (false = show initial counts only)
|
|
152
|
+
showFavorites: true, // Show favorites filter
|
|
153
|
+
collapsedByDefault: false, // Start with all filter sections collapsed
|
|
154
|
+
sortAlphabetically: false, // Sort filter options alphabetically instead of by count
|
|
155
|
+
classNames: {
|
|
156
|
+
filterContainer: '', // Main filter container wrapper
|
|
157
|
+
filterContent: '', // Scrollable content area
|
|
158
|
+
filterAccordion: '', // Filter accordion section
|
|
159
|
+
filterAccordionHeader: '', // Accordion header/trigger
|
|
160
|
+
filterAccordionContent: '', // Accordion content body
|
|
161
|
+
filterItem: '', // Individual filter items
|
|
162
|
+
filterItemLabel: '', // Filter item labels
|
|
163
|
+
filterItemCheckbox: '', // Filter item checkboxes
|
|
164
|
+
filterItemCount: '', // Filter item count badges
|
|
165
|
+
searchInput: '', // Search input field
|
|
166
|
+
resetButton: '', // Reset all button
|
|
167
|
+
showJobsButton: '', // Show jobs button
|
|
168
|
+
filterFooter: '' // Footer with buttons
|
|
169
|
+
}
|
|
170
|
+
}
|
|
126
171
|
}) => {
|
|
127
172
|
// Load site configuration
|
|
128
173
|
const { siteConfig, error } = useSiteConfig(clientToken, siteConfiguration);
|
|
@@ -190,7 +235,8 @@ export const HireControlMap = ({
|
|
|
190
235
|
localStorageKey,
|
|
191
236
|
hideMap,
|
|
192
237
|
hideFilters,
|
|
193
|
-
noEntities
|
|
238
|
+
noEntities,
|
|
239
|
+
filterConfig
|
|
194
240
|
};
|
|
195
241
|
|
|
196
242
|
// Calculate effective showMap value (hideMap overrides siteConfig.showMap)
|
|
@@ -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}
|
|
@@ -59,7 +59,7 @@ const ItemsListContainer: React.FC<ItemsListContainerProps> = ({
|
|
|
59
59
|
selectedListItem={selectedListItem}
|
|
60
60
|
includeFavorite={true}
|
|
61
61
|
>
|
|
62
|
-
<Accordion className="hc-divide-y hc-divide-uiAccent/10
|
|
62
|
+
<Accordion className="hc-divide-y hc-divide-uiAccent/10" defaultValue={selectedListItem?.id}>
|
|
63
63
|
{(sortSetting ? dynamicSort(filteredListings, sortSetting.field, sortSetting.type, favorites as any) : filteredListings)
|
|
64
64
|
.slice(0, itemLimit)
|
|
65
65
|
.map((item: Listing) => (
|
|
@@ -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
|
-
import React from "react";
|
|
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,65 +15,180 @@ const Filter = ({
|
|
|
12
15
|
style,
|
|
13
16
|
children
|
|
14
17
|
}) => {
|
|
18
|
+
const { filterConfig } = useMapList();
|
|
19
|
+
const contentRef = useRef(null);
|
|
20
|
+
const containerRef = useRef(null);
|
|
21
|
+
const [hasOverflow, setHasOverflow] = useState(false);
|
|
22
|
+
const [showFixedFooter, setShowFixedFooter] = useState(false);
|
|
23
|
+
const transitionTimeoutRef = useRef(null);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const checkOverflow = () => {
|
|
27
|
+
if (contentRef.current) {
|
|
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
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Delay initial check to ensure layout is complete
|
|
51
|
+
setTimeout(checkOverflow, 100);
|
|
52
|
+
|
|
53
|
+
// Check on resize
|
|
54
|
+
window.addEventListener('resize', checkOverflow);
|
|
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
|
+
|
|
67
|
+
// Use MutationObserver to detect content changes
|
|
68
|
+
const mutationObserver = new MutationObserver(() => {
|
|
69
|
+
setTimeout(checkOverflow, 50);
|
|
70
|
+
});
|
|
71
|
+
if (contentRef.current) {
|
|
72
|
+
mutationObserver.observe(contentRef.current, { childList: true, subtree: true, attributes: true });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return () => {
|
|
76
|
+
window.removeEventListener('resize', checkOverflow);
|
|
77
|
+
resizeObserver.disconnect();
|
|
78
|
+
mutationObserver.disconnect();
|
|
79
|
+
// Clear timeout on unmount
|
|
80
|
+
if (transitionTimeoutRef.current) {
|
|
81
|
+
clearTimeout(transitionTimeoutRef.current);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}, [children, hasOverflow]);
|
|
85
|
+
|
|
86
|
+
// Reusable button component
|
|
87
|
+
const MobileButtons = () => (
|
|
88
|
+
<>
|
|
89
|
+
<Button.Btn
|
|
90
|
+
onClick={handleReset}
|
|
91
|
+
variant="outline"
|
|
92
|
+
size="sm"
|
|
93
|
+
className={filterConfig?.classNames?.resetButton}
|
|
94
|
+
>
|
|
95
|
+
Reset All
|
|
96
|
+
</Button.Btn>
|
|
97
|
+
{selectedFilters && Object.keys(selectedFilters).length > 0 && (
|
|
98
|
+
<Button.Btn
|
|
99
|
+
onClick={() => setMobileTab("listTab")}
|
|
100
|
+
variant="primary"
|
|
101
|
+
size="sm"
|
|
102
|
+
className={twMerge(
|
|
103
|
+
`${hasActiveFilters ? "hc-opacity-0 hc-pointer-events-none" : "hc-opacity-100"}`,
|
|
104
|
+
filterConfig?.classNames?.showJobsButton
|
|
105
|
+
)}
|
|
106
|
+
>
|
|
107
|
+
<Button.Body>
|
|
108
|
+
<Button.Icon icon="fluent:search-12-filled" size="hc-size-3.5" />
|
|
109
|
+
Show {filteredListings.length} Jobs
|
|
110
|
+
</Button.Body>
|
|
111
|
+
</Button.Btn>
|
|
112
|
+
)}
|
|
113
|
+
</>
|
|
114
|
+
);
|
|
115
|
+
|
|
15
116
|
return (
|
|
16
117
|
<div
|
|
118
|
+
ref={containerRef}
|
|
17
119
|
style={isDesktop ? style : undefined}
|
|
18
|
-
className={
|
|
19
|
-
|
|
120
|
+
className={twMerge(
|
|
121
|
+
`/* Mobile layout */
|
|
20
122
|
hc-relative hc-w-full hc-h-full hc-flex hc-flex-col
|
|
21
123
|
|
|
22
124
|
/* Desktop layout */
|
|
23
|
-
md:hc-
|
|
125
|
+
md:hc-relative md:hc-flex md:hc-flex-col md:hc-max-h-[100%]
|
|
24
126
|
|
|
25
|
-
${className ?? ""}
|
|
26
|
-
|
|
127
|
+
${className ?? ""}`,
|
|
128
|
+
filterConfig?.classNames?.filterContainer
|
|
129
|
+
)}
|
|
27
130
|
>
|
|
28
131
|
{/* Content area with scroll */}
|
|
29
|
-
<div
|
|
30
|
-
|
|
31
|
-
|
|
132
|
+
<div
|
|
133
|
+
ref={contentRef}
|
|
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
|
+
)}
|
|
138
|
+
>
|
|
139
|
+
<div className="hc-px-4 md:hc-pt-4 hc-space-y-4">
|
|
140
|
+
{children}
|
|
32
141
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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>
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
38
166
|
</div>
|
|
39
167
|
|
|
40
|
-
{/* Mobile Footer - fixed at bottom */}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
>
|
|
53
|
-
<Button.Btn onClick={handleReset} variant="outline" size="sm">
|
|
54
|
-
Reset All
|
|
55
|
-
</Button.Btn>
|
|
56
|
-
{selectedFilters && Object.keys(selectedFilters).length > 0 && (
|
|
168
|
+
{/* Mobile Footer - fixed at bottom (only when overflow) */}
|
|
169
|
+
{showFixedFooter && (
|
|
170
|
+
<div
|
|
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"
|
|
172
|
+
>
|
|
173
|
+
<MobileButtons />
|
|
174
|
+
</div>
|
|
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">
|
|
57
180
|
<Button.Btn
|
|
58
|
-
onClick={
|
|
59
|
-
variant="
|
|
181
|
+
onClick={handleReset}
|
|
182
|
+
variant="outline"
|
|
60
183
|
size="sm"
|
|
61
|
-
className={
|
|
62
|
-
${hasActiveFilters ? "hc-opacity-0 hc-pointer-events-none" : "hc-opacity-100"}
|
|
63
|
-
`}
|
|
184
|
+
className={filterConfig?.classNames?.resetButton}
|
|
64
185
|
>
|
|
65
|
-
|
|
66
|
-
<Button.Icon icon="fluent:search-12-filled" size="hc-size-3.5" />
|
|
67
|
-
Show {filteredListings.length} Jobs
|
|
68
|
-
</Button.Body>
|
|
186
|
+
Reset All
|
|
69
187
|
</Button.Btn>
|
|
70
|
-
|
|
71
|
-
|
|
188
|
+
</div>
|
|
189
|
+
)}
|
|
72
190
|
</div>
|
|
73
191
|
);
|
|
74
192
|
};
|
|
75
193
|
|
|
76
|
-
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
|
|
|
@@ -61,17 +61,17 @@ const MapTabs = ({
|
|
|
61
61
|
</Button.Body>
|
|
62
62
|
</Button.Btn>
|
|
63
63
|
</Tabs.Trigger>
|
|
64
|
-
|
|
64
|
+
</Tabs.List>
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
66
|
+
{/* Tab content */}
|
|
67
|
+
<div className="hc-flex-grow hc-h-[calc(100vh-180px)] md:hc-h-[calc(100%-48px)]">
|
|
68
|
+
{/* List Tab Content */}
|
|
69
|
+
<Tabs.Content
|
|
70
|
+
className="hc-h-full hc-bg-white hc-outline-none"
|
|
71
|
+
value="listTab"
|
|
72
|
+
>
|
|
73
|
+
{list}
|
|
74
|
+
</Tabs.Content>
|
|
75
75
|
|
|
76
76
|
{/* Map Tab Content */}
|
|
77
77
|
{showMap && (
|