@abcagency/hc-ui-components 1.0.0
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/globals.css +3 -0
- package/dist/index.js +4644 -0
- package/dist/output.css +784 -0
- package/dist/services/globals.css +3 -0
- package/dist/services/listingService.js +606 -0
- package/package.json +38 -0
- package/postcss.config.js +15 -0
- package/rollup.config.js +67 -0
- package/src/apis/hcApi.js +68 -0
- package/src/clientToken.js +9 -0
- package/src/components/layout/footer.js +34 -0
- package/src/components/layout/header.js +23 -0
- package/src/components/layout/layout.js +36 -0
- package/src/components/modules/accordions/MapAccordionItem.js +69 -0
- package/src/components/modules/accordions/default.js +173 -0
- package/src/components/modules/accordions/filterItem.js +53 -0
- package/src/components/modules/accordions/filters.js +44 -0
- package/src/components/modules/animations/slidein.js +41 -0
- package/src/components/modules/buttons/button-group-apply.js +75 -0
- package/src/components/modules/buttons/commute-pill.js +21 -0
- package/src/components/modules/buttons/default.js +196 -0
- package/src/components/modules/buttons/items-pill.js +31 -0
- package/src/components/modules/buttons/pill-wrapper.js +26 -0
- package/src/components/modules/buttons/show-all-button.js +20 -0
- package/src/components/modules/cards/default.js +168 -0
- package/src/components/modules/cards/filter.js +55 -0
- package/src/components/modules/dialogs/apply-dialog.js +47 -0
- package/src/components/modules/filter/commute.js +149 -0
- package/src/components/modules/filter/index.js +86 -0
- package/src/components/modules/filter/item.js +77 -0
- package/src/components/modules/filter/location.js +69 -0
- package/src/components/modules/filter/points-of-interest.js +43 -0
- package/src/components/modules/filter/radio-item.js +51 -0
- package/src/components/modules/filter/search.js +89 -0
- package/src/components/modules/filter/search.js.rej +9 -0
- package/src/components/modules/filter/sort.js +83 -0
- package/src/components/modules/form.js +362 -0
- package/src/components/modules/grid.js +75 -0
- package/src/components/modules/icon.js +33 -0
- package/src/components/modules/jobListing/listing-details.js +87 -0
- package/src/components/modules/jumbotron.js +81 -0
- package/src/components/modules/maps/info-window-card.js +17 -0
- package/src/components/modules/maps/info-window-content.js +60 -0
- package/src/components/modules/maps/list/field-mapper.js +113 -0
- package/src/components/modules/maps/list/header-item.js +90 -0
- package/src/components/modules/maps/list/header.js +46 -0
- package/src/components/modules/maps/list/index.js +104 -0
- package/src/components/modules/maps/list/item-expand-card/index.js +21 -0
- package/src/components/modules/maps/list/item-expand-card/recruiter-contact-nav.js +48 -0
- package/src/components/modules/maps/list/item-expand-card/recruiter-details.js +67 -0
- package/src/components/modules/maps/list/item-expand-card/recruiter-headshot.js +22 -0
- package/src/components/modules/maps/list/list-item/index.js +133 -0
- package/src/components/modules/maps/map-list.js +73 -0
- package/src/components/modules/maps/map-marker.js +84 -0
- package/src/components/modules/maps/map.js +218 -0
- package/src/components/modules/maps/place-marker.js +41 -0
- package/src/components/modules/maps/tabs.js +79 -0
- package/src/components/modules/navigation/nav-link.js +65 -0
- package/src/components/modules/navigation/navbar.js +109 -0
- package/src/components/modules/navigation/skip-link.js +21 -0
- package/src/components/modules/navigation/social.js +29 -0
- package/src/components/modules/sections/default.js +59 -0
- package/src/components/modules/sections/sectionContext.js +4 -0
- package/src/components/modules/video-player.js +126 -0
- package/src/constants/placeTypes.js +8 -0
- package/src/contexts/mapContext.js +116 -0
- package/src/contexts/mapListContext.js +212 -0
- package/src/contexts/placesContext.js +98 -0
- package/src/hooks/useClickOutside.js +16 -0
- package/src/hooks/useEventListener.js +25 -0
- package/src/hooks/useEventTracker.js +19 -0
- package/src/hooks/useList.js +102 -0
- package/src/hooks/useRefScrollProgress.js +24 -0
- package/src/hooks/useScript.js +63 -0
- package/src/hooks/useScrollDirection.js +39 -0
- package/src/hooks/useSectionTracker.js +95 -0
- package/src/hooks/useUserAgent.js +43 -0
- package/src/hooks/useWindowSize.js +28 -0
- package/src/index.css +25 -0
- package/src/index.js +116 -0
- package/src/services/configService.js +16 -0
- package/src/services/googlePlacesNearbyService.js +33 -0
- package/src/services/listingAggregatorService.js +42 -0
- package/src/services/listingEntityService.js +14 -0
- package/src/services/listingService.js +28 -0
- package/src/services/recruiterService.js +17 -0
- package/src/styles/fonts.js +0 -0
- package/src/styles/globals.css +25 -0
- package/src/tailwind/preset.default.js +15 -0
- package/src/tailwind/tailwind.config.js +126 -0
- package/src/util/arrayUtil.js +3 -0
- package/src/util/fieldMapper.js +19 -0
- package/src/util/filterUtil.js +195 -0
- package/src/util/loading.js +17 -0
- package/src/util/localStorageUtil.js +27 -0
- package/src/util/mapIconUtil.js +179 -0
- package/src/util/mapUtil.js +91 -0
- package/src/util/page-head.js +62 -0
- package/src/util/provider.js +12 -0
- package/src/util/sortUtil.js +33 -0
- package/src/util/stringUtils.js +6 -0
- package/src/util/urlFilterUtil.js +91 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DistanceMatrixService } from "@react-google-maps/api";
|
|
3
|
+
import { useMapList } from "~/contexts/mapListContext";
|
|
4
|
+
import { useMap } from "~/contexts/mapContext";
|
|
5
|
+
import CommutePill from "../buttons/commute-pill";
|
|
6
|
+
|
|
7
|
+
const InfoWindowContent = ({
|
|
8
|
+
item,
|
|
9
|
+
filterListingsByLocation
|
|
10
|
+
}) => {
|
|
11
|
+
const { setSelectedFilters, setQuery, setMobileTab, selectedFilters } = useMapList();
|
|
12
|
+
let items = item && item.items ? Object.values(item.items) : null;
|
|
13
|
+
|
|
14
|
+
if (!items || items.length < 0) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const addressParts = [
|
|
18
|
+
items[0].mapDetails?.address?.street,
|
|
19
|
+
items[0].mapDetails?.address?.city,
|
|
20
|
+
items[0].mapDetails?.address?.state,
|
|
21
|
+
items[0].mapDetails?.address?.zip
|
|
22
|
+
];
|
|
23
|
+
const fullAddress = addressParts.filter(Boolean).join(', ');
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className="flex-auto p-2">
|
|
27
|
+
<div>
|
|
28
|
+
<div>
|
|
29
|
+
<h4 className="text-lg font-bold leading-tight mb-3">
|
|
30
|
+
{items[0].mapDetails?.entityDisplayName}
|
|
31
|
+
</h4>
|
|
32
|
+
<p>
|
|
33
|
+
{fullAddress}
|
|
34
|
+
</p>
|
|
35
|
+
<div className="pt-2">
|
|
36
|
+
<button
|
|
37
|
+
className="cursor-pointer hover:opacity-70 text-primary"
|
|
38
|
+
onClick={() => {
|
|
39
|
+
setMobileTab("listTab");
|
|
40
|
+
setSelectedFilters({
|
|
41
|
+
...selectedFilters,
|
|
42
|
+
cityState: {
|
|
43
|
+
[items[0].fields.cityState]: true
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
filterListingsByLocation();
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
{items.length > 1
|
|
50
|
+
? `View All ${items.length} jobs at this location`
|
|
51
|
+
: `View job at this location`}
|
|
52
|
+
</button>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default InfoWindowContent;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import Grid from '~/components/modules/grid';
|
|
2
|
+
import Icon from '~/components/modules/icon';
|
|
3
|
+
import { capitalize } from '~/util/stringUtils';
|
|
4
|
+
import PillWrapper from '~/components/modules/buttons/pill-wrapper';
|
|
5
|
+
import CommutePill from "~/components/modules/buttons/commute-pill";
|
|
6
|
+
import React from 'react'
|
|
7
|
+
|
|
8
|
+
const FieldMapper = ({
|
|
9
|
+
item,
|
|
10
|
+
fieldsShown,
|
|
11
|
+
specialFeatures,
|
|
12
|
+
handleFavouriteClick,
|
|
13
|
+
isFavorite
|
|
14
|
+
}) => {
|
|
15
|
+
const orderedFields = fieldsShown.filter(field => field in item.fields);
|
|
16
|
+
|
|
17
|
+
const specialFeaturePills = (field, mobile = false) =>
|
|
18
|
+
{
|
|
19
|
+
return field === 'position' && specialFeatures &&
|
|
20
|
+
Object.entries(specialFeatures).map(([featureKey, featureLabel]) =>
|
|
21
|
+
{
|
|
22
|
+
return item.fields[featureKey] === 1 && (
|
|
23
|
+
<PillWrapper key={featureKey}>{featureLabel}</PillWrapper>
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<>
|
|
30
|
+
{orderedFields.map(field => {
|
|
31
|
+
let value = item.fields[field];
|
|
32
|
+
return (
|
|
33
|
+
<Grid.Item
|
|
34
|
+
key={field}
|
|
35
|
+
className="hidden md:block"
|
|
36
|
+
>
|
|
37
|
+
<span className="sr-only">{capitalize(field)}</span>
|
|
38
|
+
{value}
|
|
39
|
+
<br/>
|
|
40
|
+
{specialFeaturePills(field)}
|
|
41
|
+
</Grid.Item>
|
|
42
|
+
);
|
|
43
|
+
})}
|
|
44
|
+
<Grid.Item className="md:hidden">
|
|
45
|
+
{fieldsShown.includes("position") &&
|
|
46
|
+
<> <div className="flex items-start">
|
|
47
|
+
<div className="flex justify-between items-center min-w-[100%]"> {/* Use percentage for minimum width */}
|
|
48
|
+
<h3 className="font-bold mb-3 flex-1">{item.fields.position}</h3>
|
|
49
|
+
<div className="flex justify-end pb-2">
|
|
50
|
+
<Icon
|
|
51
|
+
icon={isFavorite ? "mdi:heart" : "mdi:heart-outline"}
|
|
52
|
+
size="w-3.5 h-3.5"
|
|
53
|
+
iconClasses={isFavorite ? "text-primary" : ""}
|
|
54
|
+
className={`transition-opacity duration-300 cursor-pointer
|
|
55
|
+
${isFavorite ? "opacity-100" : "text-uiText/60 group-hover:opacity-100"}
|
|
56
|
+
`}
|
|
57
|
+
onClick={e => handleFavouriteClick(e)}
|
|
58
|
+
/>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
</div>
|
|
63
|
+
{specialFeatures && <div className='pb-4'>{specialFeaturePills("position", true)} </div>}
|
|
64
|
+
|
|
65
|
+
</>
|
|
66
|
+
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
<ul className="space-y-2 text-xs">
|
|
70
|
+
|
|
71
|
+
{[
|
|
72
|
+
{
|
|
73
|
+
field: "category",
|
|
74
|
+
name: "Category",
|
|
75
|
+
icon: "icon-park-solid:tree-list"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
field: "schedule",
|
|
79
|
+
name: "Schedule",
|
|
80
|
+
icon: "gravity-ui:clock-fill"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
field: "cityState",
|
|
84
|
+
name: "Location",
|
|
85
|
+
icon: "fluent:location-16-filled"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
field: "travelTime",
|
|
89
|
+
name: "Commute",
|
|
90
|
+
icon: "ri:pin-distance-fill"
|
|
91
|
+
}
|
|
92
|
+
].map(listItem => (
|
|
93
|
+
(fieldsShown.includes(listItem.field) && item.fields[listItem.field]) &&
|
|
94
|
+
<li
|
|
95
|
+
key={listItem.field}
|
|
96
|
+
className="flex gap-2"
|
|
97
|
+
>
|
|
98
|
+
<Icon
|
|
99
|
+
icon={listItem.icon}
|
|
100
|
+
size="w-3.5 h-3.5"
|
|
101
|
+
className="text-uiAccent/30"
|
|
102
|
+
/>
|
|
103
|
+
<span className="sr-only">{listItem.name}</span>
|
|
104
|
+
{item.fields[listItem.field]}
|
|
105
|
+
</li>
|
|
106
|
+
))}
|
|
107
|
+
</ul>
|
|
108
|
+
</Grid.Item>
|
|
109
|
+
</>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export default FieldMapper;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import Button from "~/components/modules/buttons/default";
|
|
2
|
+
import { useState, useEffect } from "react";
|
|
3
|
+
import React from 'react'
|
|
4
|
+
|
|
5
|
+
const SORT_STATE = {
|
|
6
|
+
notSorted: "not-sorted",
|
|
7
|
+
sortedAsc: "sorted-asc",
|
|
8
|
+
sortedDesc: "sorted-desc"
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const HeaderItem = ({
|
|
12
|
+
className,
|
|
13
|
+
children,
|
|
14
|
+
field,
|
|
15
|
+
setSortSetting,
|
|
16
|
+
sortSetting,
|
|
17
|
+
isSortable,
|
|
18
|
+
...rest
|
|
19
|
+
}) => {
|
|
20
|
+
const [isSorted, setIsSorted] = useState(SORT_STATE.notSorted);
|
|
21
|
+
|
|
22
|
+
const handleChange = field => {
|
|
23
|
+
if(isSortable === false)
|
|
24
|
+
return;
|
|
25
|
+
let setting = {
|
|
26
|
+
field: field,
|
|
27
|
+
type: isSorted === SORT_STATE.sortedAsc ? "desc" : "asc"
|
|
28
|
+
};
|
|
29
|
+
setSortSetting(setting);
|
|
30
|
+
isSorted === SORT_STATE.notSorted
|
|
31
|
+
? setIsSorted(SORT_STATE.sortedAsc)
|
|
32
|
+
: isSorted === SORT_STATE.sortedAsc
|
|
33
|
+
? setIsSorted(SORT_STATE.sortedDesc)
|
|
34
|
+
: setIsSorted(SORT_STATE.sortedAsc);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (sortSetting?.field != null && sortSetting.field != field) {
|
|
39
|
+
setIsSorted(SORT_STATE.notSorted);
|
|
40
|
+
} else if (
|
|
41
|
+
sortSetting?.field != null &&
|
|
42
|
+
isSorted == SORT_STATE.notSorted &&
|
|
43
|
+
sortSetting.field == field
|
|
44
|
+
) {
|
|
45
|
+
setIsSorted(
|
|
46
|
+
sortSetting.type == "asc"
|
|
47
|
+
? SORT_STATE.sortedAsc
|
|
48
|
+
: SORT_STATE.sortedDesc,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}, [sortSetting, isSorted, field]);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Button.Btn
|
|
55
|
+
onClick={() => handleChange(field)}
|
|
56
|
+
variant="none"
|
|
57
|
+
className={`
|
|
58
|
+
p-2 rounded-none text-left normal-case font-bold hover:bg-uiAccent/5 focus:bg-uiAccent/5
|
|
59
|
+
${className ?? ""}
|
|
60
|
+
`}
|
|
61
|
+
{...rest}
|
|
62
|
+
>
|
|
63
|
+
<Button.Body>
|
|
64
|
+
{children}
|
|
65
|
+
{isSortable && (
|
|
66
|
+
<div className="flex flex-col pr-2">
|
|
67
|
+
<Button.Icon
|
|
68
|
+
icon="bi:caret-up-fill"
|
|
69
|
+
size="w-2.5 h-2.5"
|
|
70
|
+
className={`
|
|
71
|
+
transition-opacity
|
|
72
|
+
${isSorted === "sorted-asc" ? "opacity-100 text-primary" : "opacity-30"}
|
|
73
|
+
`}
|
|
74
|
+
/>
|
|
75
|
+
<Button.Icon
|
|
76
|
+
icon="bi:caret-down-fill"
|
|
77
|
+
size="w-2.5 h-2.5"
|
|
78
|
+
className={`
|
|
79
|
+
transition-opacity
|
|
80
|
+
${isSorted === "sorted-desc" ? "opacity-100" : "opacity-30"}
|
|
81
|
+
`}
|
|
82
|
+
/>
|
|
83
|
+
</div>
|
|
84
|
+
)}
|
|
85
|
+
</Button.Body>
|
|
86
|
+
</Button.Btn>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export default HeaderItem;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { twMerge } from "tailwind-merge";
|
|
2
|
+
import Grid from "~/components/modules/grid";
|
|
3
|
+
import HeaderItem from "~/components/modules/maps/list/header-item";
|
|
4
|
+
import React from 'react'
|
|
5
|
+
|
|
6
|
+
const ListHeader = ({
|
|
7
|
+
className,
|
|
8
|
+
fieldsShown,
|
|
9
|
+
fieldNames,
|
|
10
|
+
fieldIsSortable = true,
|
|
11
|
+
setSortSetting,
|
|
12
|
+
sortSetting
|
|
13
|
+
}) => {
|
|
14
|
+
return (
|
|
15
|
+
<Grid
|
|
16
|
+
columns="grid-flow-col auto-cols-fr"
|
|
17
|
+
gap="gap-0"
|
|
18
|
+
isAnimated={false}
|
|
19
|
+
className={twMerge`
|
|
20
|
+
pl-6 pr-8 hidden md:grid flex-auto bg-uiAccent/10 border-b border-uiAccent/10 sticky top-0 z-10 padding-bottom:20px
|
|
21
|
+
${className ?? ""}
|
|
22
|
+
`}
|
|
23
|
+
>
|
|
24
|
+
{fieldsShown.map(field => (
|
|
25
|
+
<HeaderItem
|
|
26
|
+
key={field}
|
|
27
|
+
isSortable={fieldIsSortable}
|
|
28
|
+
sortSetting={sortSetting}
|
|
29
|
+
field={field}
|
|
30
|
+
setSortSetting={setSortSetting}
|
|
31
|
+
>
|
|
32
|
+
{fieldNames[field]}
|
|
33
|
+
</HeaderItem>
|
|
34
|
+
))}
|
|
35
|
+
<HeaderItem key={"favorite"}
|
|
36
|
+
isSortable={false}
|
|
37
|
+
field={"favorite"}
|
|
38
|
+
>
|
|
39
|
+
Favorite
|
|
40
|
+
</HeaderItem>
|
|
41
|
+
|
|
42
|
+
</Grid>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default ListHeader;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Icon } from "@iconify/react";
|
|
3
|
+
import Accordion from "~/components/modules/accordions/default";
|
|
4
|
+
import Header from "~/components/modules/maps/list/header";
|
|
5
|
+
import MapAccordionItem from '~/components/modules/accordions/MapAccordionItem';
|
|
6
|
+
import useListLogic from '~/hooks/useList';
|
|
7
|
+
import Sort from '~/components/modules/filter/sort';
|
|
8
|
+
import Loading from "~/util/loading";
|
|
9
|
+
import { useMap } from '~/contexts/mapContext';
|
|
10
|
+
import { useMapList } from '~/contexts/mapListContext';
|
|
11
|
+
import ListingDetails from "~/components/modules/jobListing/listing-details";
|
|
12
|
+
|
|
13
|
+
const ItemsList = ({
|
|
14
|
+
fieldNames,
|
|
15
|
+
showMap,
|
|
16
|
+
fieldsShown,
|
|
17
|
+
specialFeatures
|
|
18
|
+
}) => {
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
itemLimit,
|
|
22
|
+
sortSetting,
|
|
23
|
+
loader,
|
|
24
|
+
scrollContainerRef,
|
|
25
|
+
itemRefs,
|
|
26
|
+
setSortSetting
|
|
27
|
+
} = useListLogic();
|
|
28
|
+
const { selectedListItem } = useMap();
|
|
29
|
+
const { mapItems, filteredListings, loading, commuteLocation } = useMapList();
|
|
30
|
+
const itemExpandedContent = (item, recruiters) =>
|
|
31
|
+
item ? <ListingDetails item={item} recruiters={recruiters} /> : null;
|
|
32
|
+
if(!fieldsShown.includes('travelTime') && commuteLocation != null && Object.entries(commuteLocation).length > 0){
|
|
33
|
+
fieldsShown.push('travelTime');
|
|
34
|
+
fieldNames['travelTime'] = 'Commute';
|
|
35
|
+
}else if(fieldsShown.includes('travelTime') && !commuteLocation){
|
|
36
|
+
fieldsShown = fieldsShown.filter(x => x !== 'travelTime');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className="relative bg-white md:px-4 flex flex-col">
|
|
41
|
+
<div className="flex flex-wrap items-center justify-between gap-4 md:mb-2 p-3 md:p-0 bg-uiAccent/10 md:bg-transparent border-b md:border-none border-uiAccent/20">
|
|
42
|
+
<h2 className="text-gray-500 font-semibold text-xs md:text-sm">
|
|
43
|
+
{loading &&
|
|
44
|
+
<span>Loading...</span>
|
|
45
|
+
}
|
|
46
|
+
{!loading &&
|
|
47
|
+
<span>{filteredListings.length} results</span>
|
|
48
|
+
}
|
|
49
|
+
</h2>
|
|
50
|
+
<div className="block md:hidden">
|
|
51
|
+
<Sort
|
|
52
|
+
fields={fieldsShown}
|
|
53
|
+
setSortSetting={setSortSetting}
|
|
54
|
+
fieldNames={fieldNames}
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
<div>
|
|
59
|
+
<Header
|
|
60
|
+
setSortSetting={setSortSetting}
|
|
61
|
+
sortSetting={sortSetting}
|
|
62
|
+
fieldsShown={fieldsShown}
|
|
63
|
+
fieldNames={fieldNames}
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
<div
|
|
67
|
+
ref={scrollContainerRef}
|
|
68
|
+
className={`
|
|
69
|
+
flex-grow overflow-y-auto
|
|
70
|
+
${showMap ? "md:max-h-45vh max-h-[100vh]" : "md:max-h-95vh max-h-[95vh]"}
|
|
71
|
+
`}
|
|
72
|
+
>
|
|
73
|
+
{loading ? (
|
|
74
|
+
<div className="flex justify-center items-center pt-20">
|
|
75
|
+
<Loading />
|
|
76
|
+
</div>
|
|
77
|
+
) : (
|
|
78
|
+
<Accordion className="divide-y divide-uiAccent/10 z10000" defaultValue={selectedListItem?.id}>
|
|
79
|
+
{filteredListings.slice(0, itemLimit).map(item => {
|
|
80
|
+
return (<MapAccordionItem
|
|
81
|
+
key={item.id}
|
|
82
|
+
showMap={showMap}
|
|
83
|
+
item={item}
|
|
84
|
+
itemRefs={itemRefs}
|
|
85
|
+
fieldsShown={fieldsShown}
|
|
86
|
+
itemExpandedContent={itemExpandedContent}
|
|
87
|
+
specialFeatures={specialFeatures}
|
|
88
|
+
mapItems={mapItems}
|
|
89
|
+
isActive={selectedListItem?.id == item.id}
|
|
90
|
+
hasListItemSelected={selectedListItem != null}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
})}
|
|
94
|
+
</Accordion>
|
|
95
|
+
)}
|
|
96
|
+
<div ref={loader} style={{ height: "100px", textAlign: "center" }}>
|
|
97
|
+
{filteredListings.length >= itemLimit && <Loading />}
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export default ItemsList;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { twMerge } from 'tailwind-merge';
|
|
2
|
+
import React from 'react'
|
|
3
|
+
const CardItemExpand = ({
|
|
4
|
+
className,
|
|
5
|
+
content,
|
|
6
|
+
...props
|
|
7
|
+
}) => {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
className={twMerge(
|
|
11
|
+
'flex flex-col lg:flex-row flex-wrap items-stretch gap-x-4 w-full lg:pt-2 border-t border-uiAccent/20',
|
|
12
|
+
className ?? ''
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
>
|
|
16
|
+
{content}
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default CardItemExpand;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import Button from '~/components/modules/buttons/default';
|
|
2
|
+
import React from 'react'
|
|
3
|
+
const RecruiterContactNav = ({
|
|
4
|
+
children,
|
|
5
|
+
className
|
|
6
|
+
}) => {
|
|
7
|
+
return (
|
|
8
|
+
<nav
|
|
9
|
+
className={`
|
|
10
|
+
inline-flex items-center justify-between gap-2 mt-2
|
|
11
|
+
${className ?? ''}
|
|
12
|
+
`}
|
|
13
|
+
>
|
|
14
|
+
{children}
|
|
15
|
+
</nav>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const NavButton = ({
|
|
20
|
+
className,
|
|
21
|
+
href,
|
|
22
|
+
target = '_self',
|
|
23
|
+
title,
|
|
24
|
+
icon,
|
|
25
|
+
...props
|
|
26
|
+
}) => {
|
|
27
|
+
return (
|
|
28
|
+
<Button.Anchor
|
|
29
|
+
href={href}
|
|
30
|
+
variant="icon"
|
|
31
|
+
size="sq"
|
|
32
|
+
target={target}
|
|
33
|
+
title={title}
|
|
34
|
+
className={className ?? ''}
|
|
35
|
+
{...props}
|
|
36
|
+
>
|
|
37
|
+
<span className="sr-only">{title}</span>
|
|
38
|
+
<Button.Icon
|
|
39
|
+
icon={icon}
|
|
40
|
+
size="w-8 h-8"
|
|
41
|
+
/>
|
|
42
|
+
</Button.Anchor>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
RecruiterContactNav.Button = NavButton;
|
|
47
|
+
|
|
48
|
+
export default RecruiterContactNav;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { twMerge } from 'tailwind-merge';
|
|
2
|
+
import React from 'react'
|
|
3
|
+
const RecruiterDetails = ({
|
|
4
|
+
contactNav,
|
|
5
|
+
className,
|
|
6
|
+
children
|
|
7
|
+
}) => {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
className={twMerge`
|
|
11
|
+
grow
|
|
12
|
+
${className ?? ''}
|
|
13
|
+
`}
|
|
14
|
+
>
|
|
15
|
+
{children}
|
|
16
|
+
|
|
17
|
+
{contactNav &&
|
|
18
|
+
<div className="inline-flex">
|
|
19
|
+
{contactNav}
|
|
20
|
+
</div>
|
|
21
|
+
}
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const Title = ({
|
|
27
|
+
as = 'h4',
|
|
28
|
+
className,
|
|
29
|
+
children
|
|
30
|
+
}) => {
|
|
31
|
+
const Container = as;
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Container
|
|
35
|
+
className={twMerge`
|
|
36
|
+
text-base font-medium
|
|
37
|
+
${className ?? ''}
|
|
38
|
+
`}
|
|
39
|
+
>
|
|
40
|
+
{children}
|
|
41
|
+
</Container>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const Text = ({
|
|
46
|
+
as = 'p',
|
|
47
|
+
className,
|
|
48
|
+
children
|
|
49
|
+
}) => {
|
|
50
|
+
const Container = as;
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<Container
|
|
54
|
+
className={twMerge`
|
|
55
|
+
text-sm text-uiText/60
|
|
56
|
+
${className ?? ''}
|
|
57
|
+
`}
|
|
58
|
+
>
|
|
59
|
+
{children}
|
|
60
|
+
</Container>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
RecruiterDetails.Title = Title;
|
|
65
|
+
RecruiterDetails.Text = Text;
|
|
66
|
+
|
|
67
|
+
export default RecruiterDetails;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
const RecruiterHeadshot = ({
|
|
4
|
+
image,
|
|
5
|
+
alt,
|
|
6
|
+
className,
|
|
7
|
+
imageClassName
|
|
8
|
+
}) => {
|
|
9
|
+
return (
|
|
10
|
+
<div className={className ?? ''}>
|
|
11
|
+
<img
|
|
12
|
+
src={image}
|
|
13
|
+
width="96"
|
|
14
|
+
height="96"
|
|
15
|
+
alt={alt}
|
|
16
|
+
className={imageClassName ?? ''}
|
|
17
|
+
/>
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default RecruiterHeadshot;
|