@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.
Files changed (102) hide show
  1. package/dist/globals.css +3 -0
  2. package/dist/index.js +4644 -0
  3. package/dist/output.css +784 -0
  4. package/dist/services/globals.css +3 -0
  5. package/dist/services/listingService.js +606 -0
  6. package/package.json +38 -0
  7. package/postcss.config.js +15 -0
  8. package/rollup.config.js +67 -0
  9. package/src/apis/hcApi.js +68 -0
  10. package/src/clientToken.js +9 -0
  11. package/src/components/layout/footer.js +34 -0
  12. package/src/components/layout/header.js +23 -0
  13. package/src/components/layout/layout.js +36 -0
  14. package/src/components/modules/accordions/MapAccordionItem.js +69 -0
  15. package/src/components/modules/accordions/default.js +173 -0
  16. package/src/components/modules/accordions/filterItem.js +53 -0
  17. package/src/components/modules/accordions/filters.js +44 -0
  18. package/src/components/modules/animations/slidein.js +41 -0
  19. package/src/components/modules/buttons/button-group-apply.js +75 -0
  20. package/src/components/modules/buttons/commute-pill.js +21 -0
  21. package/src/components/modules/buttons/default.js +196 -0
  22. package/src/components/modules/buttons/items-pill.js +31 -0
  23. package/src/components/modules/buttons/pill-wrapper.js +26 -0
  24. package/src/components/modules/buttons/show-all-button.js +20 -0
  25. package/src/components/modules/cards/default.js +168 -0
  26. package/src/components/modules/cards/filter.js +55 -0
  27. package/src/components/modules/dialogs/apply-dialog.js +47 -0
  28. package/src/components/modules/filter/commute.js +149 -0
  29. package/src/components/modules/filter/index.js +86 -0
  30. package/src/components/modules/filter/item.js +77 -0
  31. package/src/components/modules/filter/location.js +69 -0
  32. package/src/components/modules/filter/points-of-interest.js +43 -0
  33. package/src/components/modules/filter/radio-item.js +51 -0
  34. package/src/components/modules/filter/search.js +89 -0
  35. package/src/components/modules/filter/search.js.rej +9 -0
  36. package/src/components/modules/filter/sort.js +83 -0
  37. package/src/components/modules/form.js +362 -0
  38. package/src/components/modules/grid.js +75 -0
  39. package/src/components/modules/icon.js +33 -0
  40. package/src/components/modules/jobListing/listing-details.js +87 -0
  41. package/src/components/modules/jumbotron.js +81 -0
  42. package/src/components/modules/maps/info-window-card.js +17 -0
  43. package/src/components/modules/maps/info-window-content.js +60 -0
  44. package/src/components/modules/maps/list/field-mapper.js +113 -0
  45. package/src/components/modules/maps/list/header-item.js +90 -0
  46. package/src/components/modules/maps/list/header.js +46 -0
  47. package/src/components/modules/maps/list/index.js +104 -0
  48. package/src/components/modules/maps/list/item-expand-card/index.js +21 -0
  49. package/src/components/modules/maps/list/item-expand-card/recruiter-contact-nav.js +48 -0
  50. package/src/components/modules/maps/list/item-expand-card/recruiter-details.js +67 -0
  51. package/src/components/modules/maps/list/item-expand-card/recruiter-headshot.js +22 -0
  52. package/src/components/modules/maps/list/list-item/index.js +133 -0
  53. package/src/components/modules/maps/map-list.js +73 -0
  54. package/src/components/modules/maps/map-marker.js +84 -0
  55. package/src/components/modules/maps/map.js +218 -0
  56. package/src/components/modules/maps/place-marker.js +41 -0
  57. package/src/components/modules/maps/tabs.js +79 -0
  58. package/src/components/modules/navigation/nav-link.js +65 -0
  59. package/src/components/modules/navigation/navbar.js +109 -0
  60. package/src/components/modules/navigation/skip-link.js +21 -0
  61. package/src/components/modules/navigation/social.js +29 -0
  62. package/src/components/modules/sections/default.js +59 -0
  63. package/src/components/modules/sections/sectionContext.js +4 -0
  64. package/src/components/modules/video-player.js +126 -0
  65. package/src/constants/placeTypes.js +8 -0
  66. package/src/contexts/mapContext.js +116 -0
  67. package/src/contexts/mapListContext.js +212 -0
  68. package/src/contexts/placesContext.js +98 -0
  69. package/src/hooks/useClickOutside.js +16 -0
  70. package/src/hooks/useEventListener.js +25 -0
  71. package/src/hooks/useEventTracker.js +19 -0
  72. package/src/hooks/useList.js +102 -0
  73. package/src/hooks/useRefScrollProgress.js +24 -0
  74. package/src/hooks/useScript.js +63 -0
  75. package/src/hooks/useScrollDirection.js +39 -0
  76. package/src/hooks/useSectionTracker.js +95 -0
  77. package/src/hooks/useUserAgent.js +43 -0
  78. package/src/hooks/useWindowSize.js +28 -0
  79. package/src/index.css +25 -0
  80. package/src/index.js +116 -0
  81. package/src/services/configService.js +16 -0
  82. package/src/services/googlePlacesNearbyService.js +33 -0
  83. package/src/services/listingAggregatorService.js +42 -0
  84. package/src/services/listingEntityService.js +14 -0
  85. package/src/services/listingService.js +28 -0
  86. package/src/services/recruiterService.js +17 -0
  87. package/src/styles/fonts.js +0 -0
  88. package/src/styles/globals.css +25 -0
  89. package/src/tailwind/preset.default.js +15 -0
  90. package/src/tailwind/tailwind.config.js +126 -0
  91. package/src/util/arrayUtil.js +3 -0
  92. package/src/util/fieldMapper.js +19 -0
  93. package/src/util/filterUtil.js +195 -0
  94. package/src/util/loading.js +17 -0
  95. package/src/util/localStorageUtil.js +27 -0
  96. package/src/util/mapIconUtil.js +179 -0
  97. package/src/util/mapUtil.js +91 -0
  98. package/src/util/page-head.js +62 -0
  99. package/src/util/provider.js +12 -0
  100. package/src/util/sortUtil.js +33 -0
  101. package/src/util/stringUtils.js +6 -0
  102. 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;