@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
package/rollup.config.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const babel = require('@rollup/plugin-babel').default;
|
|
2
|
+
const resolve = require('@rollup/plugin-node-resolve');
|
|
3
|
+
const external = require('rollup-plugin-peer-deps-external');
|
|
4
|
+
const alias = require('@rollup/plugin-alias');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const commonjs = require('@rollup/plugin-commonjs');
|
|
7
|
+
const replace = require('@rollup/plugin-replace');
|
|
8
|
+
const dotenv = require('dotenv');
|
|
9
|
+
const postcss = require('rollup-plugin-postcss');
|
|
10
|
+
dotenv.config();
|
|
11
|
+
|
|
12
|
+
const plugins = [
|
|
13
|
+
external(),
|
|
14
|
+
resolve(),
|
|
15
|
+
postcss({
|
|
16
|
+
extensions: ['.css'],
|
|
17
|
+
extract: 'globals.css',
|
|
18
|
+
minimize: true,
|
|
19
|
+
plugins: [
|
|
20
|
+
require('tailwindcss'),
|
|
21
|
+
require('autoprefixer')
|
|
22
|
+
],
|
|
23
|
+
}),
|
|
24
|
+
replace({
|
|
25
|
+
preventAssignment: true,
|
|
26
|
+
'process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY': JSON.stringify(process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY),
|
|
27
|
+
'process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_URL': JSON.stringify(process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_URL),
|
|
28
|
+
'process.env.NEXT_PUBLIC_HC_API_BASE_URL': JSON.stringify(process.env.NEXT_PUBLIC_HC_API_BASE_URL),
|
|
29
|
+
}),
|
|
30
|
+
alias({
|
|
31
|
+
entries: [{ find: '~', replacement: path.resolve(__dirname, 'src') }],
|
|
32
|
+
}),
|
|
33
|
+
babel({
|
|
34
|
+
babelHelpers: 'bundled',
|
|
35
|
+
exclude: 'node_modules/**',
|
|
36
|
+
presets: ['@babel/preset-env', '@babel/preset-react']
|
|
37
|
+
}),
|
|
38
|
+
commonjs(),
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const externalDependencies = [
|
|
42
|
+
'react', 'react-dom', 'react-router-dom', 'form-data', 'axios', 'next/router',
|
|
43
|
+
'next/image', 'react/jsx-runtime', 'tailwind-merge', '@rollup/plugin-alias'
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
module.exports = [
|
|
47
|
+
{
|
|
48
|
+
input: 'src/index.js',
|
|
49
|
+
output: {
|
|
50
|
+
file: 'dist/index.js',
|
|
51
|
+
format: 'es',
|
|
52
|
+
exports: 'named'
|
|
53
|
+
},
|
|
54
|
+
plugins,
|
|
55
|
+
external: externalDependencies
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
input: 'src/services/listingService.js',
|
|
59
|
+
output: {
|
|
60
|
+
file: 'dist/services/listingService.js',
|
|
61
|
+
format: 'es',
|
|
62
|
+
exports: 'named'
|
|
63
|
+
},
|
|
64
|
+
plugins,
|
|
65
|
+
external: externalDependencies
|
|
66
|
+
},
|
|
67
|
+
];
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
|
|
2
|
+
import { getClientAuthKey } from '~/clientToken.js'
|
|
3
|
+
const baseURL = `${process.env.NEXT_PUBLIC_HC_API_BASE_URL}`
|
|
4
|
+
|
|
5
|
+
const login = async () => {
|
|
6
|
+
const clientAuthKey = getClientAuthKey();
|
|
7
|
+
try {
|
|
8
|
+
|
|
9
|
+
const response = await fetch(`${baseURL}/auth/login`, {
|
|
10
|
+
method: 'POST',
|
|
11
|
+
headers: {
|
|
12
|
+
'Content-Type': 'application/json'
|
|
13
|
+
},
|
|
14
|
+
body: JSON.stringify({
|
|
15
|
+
clientAuthKey: clientAuthKey
|
|
16
|
+
})
|
|
17
|
+
});
|
|
18
|
+
const data = await response.json();
|
|
19
|
+
if (data.token && data.expiration) {
|
|
20
|
+
sessionStorage.setItem('authToken', data.token);
|
|
21
|
+
sessionStorage.setItem('tokenExpiration', data.expiration);
|
|
22
|
+
return { token: data.token, expiration: data.expiration };
|
|
23
|
+
}
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error('Login failed:', error);
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const fetchWithAuth = async (url, options = {}) => {
|
|
31
|
+
let token = sessionStorage.getItem('authToken');
|
|
32
|
+
const expirationDateTime = sessionStorage.getItem('tokenExpiration');
|
|
33
|
+
const currentTime = new Date();
|
|
34
|
+
|
|
35
|
+
if (!token || !expirationDateTime || new Date(expirationDateTime) <= currentTime) {
|
|
36
|
+
const authResponse = await login();
|
|
37
|
+
token = authResponse.token;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const headers = new Headers(options.headers || {});
|
|
41
|
+
headers.append('Authorization', `Bearer ${token}`);
|
|
42
|
+
|
|
43
|
+
const finalOptions = {
|
|
44
|
+
...options,
|
|
45
|
+
headers
|
|
46
|
+
};
|
|
47
|
+
const response = await fetch(`${baseURL}${url}`, finalOptions);
|
|
48
|
+
|
|
49
|
+
if (!response.ok) throw new Error('Network response was not ok.');
|
|
50
|
+
return response;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export default {
|
|
54
|
+
get: async (url) => {
|
|
55
|
+
const response = await fetchWithAuth(url);
|
|
56
|
+
return response.json();
|
|
57
|
+
},
|
|
58
|
+
post: async (url, data) => {
|
|
59
|
+
const response = await fetchWithAuth(url, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: {
|
|
62
|
+
'Content-Type': 'application/json'
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify(data)
|
|
65
|
+
});
|
|
66
|
+
return response.json();
|
|
67
|
+
}
|
|
68
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import NavSocial from '~/components/modules/navigation/social';
|
|
2
|
+
import Button from '~/components/modules/buttons/default';
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import site from '~/data/site.json';
|
|
5
|
+
|
|
6
|
+
const Footer = () => {
|
|
7
|
+
return (
|
|
8
|
+
<footer className="px-4 py-3 bg-slate-100 text-gray-700 text-center text-xs">
|
|
9
|
+
<div className="container flex justify-between items-center">
|
|
10
|
+
<p className="m-0">
|
|
11
|
+
© {new Date().getFullYear()}. <a href={`${site.copyright.url}`} target="_blank">{site.copyright.name}</a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<div className="flex items-center gap-3">
|
|
15
|
+
<NavSocial />
|
|
16
|
+
|
|
17
|
+
<Button.Scroll
|
|
18
|
+
href="top"
|
|
19
|
+
variant="icon"
|
|
20
|
+
size="sq"
|
|
21
|
+
>
|
|
22
|
+
<span className="sr-only">To the top!</span>
|
|
23
|
+
<Button.Icon
|
|
24
|
+
icon="mdi:arrow-collapse-up"
|
|
25
|
+
size="w-4 h-4"
|
|
26
|
+
/>
|
|
27
|
+
</Button.Scroll>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</footer>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default Footer;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import Headroom from 'react-headroom';
|
|
4
|
+
|
|
5
|
+
import Navbar from '~/components/modules/navigation/navbar';
|
|
6
|
+
|
|
7
|
+
const Header = () => {
|
|
8
|
+
const [isPinned, setIsPinned] = useState(false);
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<Headroom
|
|
12
|
+
style={{ zIndex: 50 }}
|
|
13
|
+
onPin={() => setIsPinned(true)}
|
|
14
|
+
onUnfix={() => setIsPinned(false)}
|
|
15
|
+
>
|
|
16
|
+
<header>
|
|
17
|
+
<Navbar isPinned={isPinned} />
|
|
18
|
+
</header>
|
|
19
|
+
</Headroom>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default Header;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { SectionContext } from '~/components/modules/sections/sectionContext';
|
|
4
|
+
|
|
5
|
+
import { Provider } from '~/util/provider';
|
|
6
|
+
import SkipLink from '~/components/modules/navigation/skip-link';
|
|
7
|
+
|
|
8
|
+
const RootLayout = ({
|
|
9
|
+
children
|
|
10
|
+
}) => {
|
|
11
|
+
const [currentSection, setCurrentSection] = useState(null);
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div
|
|
15
|
+
id="top"
|
|
16
|
+
className="relative"
|
|
17
|
+
>
|
|
18
|
+
<Provider>
|
|
19
|
+
<SectionContext.Provider
|
|
20
|
+
value={{
|
|
21
|
+
currentSection,
|
|
22
|
+
setCurrentSection
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
<SkipLink />
|
|
26
|
+
|
|
27
|
+
<main id="start-of-content">
|
|
28
|
+
{children}
|
|
29
|
+
</main>
|
|
30
|
+
</SectionContext.Provider>
|
|
31
|
+
</Provider>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default RootLayout;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import Accordion from "~/components/modules/accordions/default";
|
|
2
|
+
import ListItem from "~/components/modules/maps/list/list-item";
|
|
3
|
+
import ItemExpandCard from "~/components/modules/maps/list/item-expand-card";
|
|
4
|
+
import { useMap } from "~/contexts/mapContext";
|
|
5
|
+
import { setStorageObject } from "~/util/localStorageUtil";
|
|
6
|
+
import { useEffect } from "react";
|
|
7
|
+
import { useMapList } from "~/contexts/mapListContext";
|
|
8
|
+
import React from 'react'
|
|
9
|
+
const MapAccordionItem = ({
|
|
10
|
+
item,
|
|
11
|
+
itemRefs,
|
|
12
|
+
itemExpandedContent,
|
|
13
|
+
fieldsShown,
|
|
14
|
+
showMap,
|
|
15
|
+
hasListItemSelected,
|
|
16
|
+
specialFeatures,
|
|
17
|
+
isActive
|
|
18
|
+
}) => {
|
|
19
|
+
const {
|
|
20
|
+
mapItems,
|
|
21
|
+
recruiters,
|
|
22
|
+
setMobileTab,
|
|
23
|
+
favorites,
|
|
24
|
+
handleSettingFavorites
|
|
25
|
+
} = useMapList();
|
|
26
|
+
const { selectItem } = useMap();
|
|
27
|
+
const setSelectedItemAndZoomMap = item => {
|
|
28
|
+
if (isActive) {
|
|
29
|
+
localStorage.removeItem("selectedListItem");
|
|
30
|
+
mapItems.find(x => x.items.hasOwnProperty(item.id)) || null;
|
|
31
|
+
selectItem(null, null, 9, { lat: 39.8283, lng: -98.5795 });
|
|
32
|
+
} else {
|
|
33
|
+
setStorageObject("selectedListItem", item);
|
|
34
|
+
let location = mapItems.find(x => x.items.hasOwnProperty(item.id)) || null;
|
|
35
|
+
selectItem(item, location, 12, {
|
|
36
|
+
lat: location?.latitude,
|
|
37
|
+
lng: location?.longitude
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
return (
|
|
42
|
+
<Accordion.Item key={item.id} id={item.id}>
|
|
43
|
+
<Accordion.Trigger.Blank>
|
|
44
|
+
<ListItem
|
|
45
|
+
ref={el => (itemRefs.current[item.id] = el)}
|
|
46
|
+
id={item.id}
|
|
47
|
+
key={item.id}
|
|
48
|
+
item={item}
|
|
49
|
+
fieldsShown={fieldsShown}
|
|
50
|
+
onClick={() => setSelectedItemAndZoomMap(item)}
|
|
51
|
+
showMap={showMap}
|
|
52
|
+
isActive={isActive}
|
|
53
|
+
setMobileTab={setMobileTab}
|
|
54
|
+
specialFeatures={specialFeatures}
|
|
55
|
+
className={hasListItemSelected ? isActive ? "!border-secondary border border-b-0" : "opacity-85" : ""}
|
|
56
|
+
favorites={favorites}
|
|
57
|
+
setFavorites={handleSettingFavorites}
|
|
58
|
+
/>
|
|
59
|
+
</Accordion.Trigger.Blank>
|
|
60
|
+
{ isActive &&
|
|
61
|
+
<Accordion.Content bodyClassName="px-2 py-2 pt-0 bg-uiAccent/5 border-secondary border border-t-0">
|
|
62
|
+
<ItemExpandCard content={itemExpandedContent(item, recruiters)} />
|
|
63
|
+
</Accordion.Content>
|
|
64
|
+
}
|
|
65
|
+
</Accordion.Item>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export default MapAccordionItem;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import * as RadixAccordion from '@radix-ui/react-accordion';
|
|
3
|
+
import { twMerge } from 'tailwind-merge';
|
|
4
|
+
import Icon from '~/components/modules/icon';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import trackEvent from '~/hooks/useEventTracker';
|
|
7
|
+
|
|
8
|
+
const Accordion = ({
|
|
9
|
+
className,
|
|
10
|
+
type = 'single',
|
|
11
|
+
defaultValue,
|
|
12
|
+
collapsible = true,
|
|
13
|
+
children
|
|
14
|
+
}) => {
|
|
15
|
+
return (
|
|
16
|
+
<RadixAccordion.Root
|
|
17
|
+
type={type}
|
|
18
|
+
value={defaultValue ?? null}
|
|
19
|
+
collapsible={collapsible}
|
|
20
|
+
className={className ?? ''}
|
|
21
|
+
onValueChange={value => trackEvent('Engagement', 'Open Accordion', value)}
|
|
22
|
+
>
|
|
23
|
+
{children}
|
|
24
|
+
</RadixAccordion.Root>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const AccordionItem = forwardRef((
|
|
29
|
+
{
|
|
30
|
+
id,
|
|
31
|
+
children,
|
|
32
|
+
className,
|
|
33
|
+
...props
|
|
34
|
+
},
|
|
35
|
+
forwardedRef
|
|
36
|
+
) => {
|
|
37
|
+
return (
|
|
38
|
+
<RadixAccordion.Item
|
|
39
|
+
ref={forwardedRef}
|
|
40
|
+
value={id}
|
|
41
|
+
className={className ?? ''}
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
44
|
+
{children}
|
|
45
|
+
</RadixAccordion.Item>
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export const AccordionTrigger = forwardRef((
|
|
50
|
+
{
|
|
51
|
+
children,
|
|
52
|
+
className,
|
|
53
|
+
...props
|
|
54
|
+
},
|
|
55
|
+
forwardedRef
|
|
56
|
+
) => (
|
|
57
|
+
<RadixAccordion.Header asChild>
|
|
58
|
+
<RadixAccordion.Trigger
|
|
59
|
+
ref={forwardedRef}
|
|
60
|
+
className={twMerge(
|
|
61
|
+
'group flex justify-between w-full p-4 font-bold text-left text-uiText focus:outline-none focus-visible:ring focus-visible:ring-uiAccent focus-visible:ring-opacity-75 hover:text-primary focus:text-primary transition data-[state=open]:text-primary',
|
|
62
|
+
className ?? ''
|
|
63
|
+
)}
|
|
64
|
+
{...props}
|
|
65
|
+
>
|
|
66
|
+
{children}
|
|
67
|
+
<Icon
|
|
68
|
+
icon="uil:angle-down"
|
|
69
|
+
size="w-5 h-5"
|
|
70
|
+
className="transition-transform group-data-[state=open]:!rotate-180"
|
|
71
|
+
aria-hidden="true"
|
|
72
|
+
/>
|
|
73
|
+
</RadixAccordion.Trigger>
|
|
74
|
+
</RadixAccordion.Header>
|
|
75
|
+
));
|
|
76
|
+
|
|
77
|
+
export const AccordionTriggerHasHeader = forwardRef((
|
|
78
|
+
{
|
|
79
|
+
header,
|
|
80
|
+
headerClassName,
|
|
81
|
+
iconClassName,
|
|
82
|
+
children,
|
|
83
|
+
className,
|
|
84
|
+
...props
|
|
85
|
+
},
|
|
86
|
+
forwardedRef
|
|
87
|
+
) => (
|
|
88
|
+
<RadixAccordion.Header
|
|
89
|
+
className={twMerge(
|
|
90
|
+
'group flex items-start justify-between w-full p-4 pr-3 font-bold text-left text-uiText focus:outline-none focus-visible:ring focus-visible:ring-uiAccent focus-visible:ring-opacity-75 hover:text-primary focus:text-primary transition data-[state=open]:text-primary',
|
|
91
|
+
headerClassName ?? ''
|
|
92
|
+
)}
|
|
93
|
+
>
|
|
94
|
+
<RadixAccordion.Trigger
|
|
95
|
+
ref={forwardedRef}
|
|
96
|
+
className={twMerge('flex items-center justify-between w-full', className ?? '')}
|
|
97
|
+
{...props}
|
|
98
|
+
>
|
|
99
|
+
{children}
|
|
100
|
+
<Icon
|
|
101
|
+
icon="uil:angle-down"
|
|
102
|
+
size="w-5 h-5"
|
|
103
|
+
className={twMerge(
|
|
104
|
+
'transition-transform group-data-[state=open]:!rotate-180',
|
|
105
|
+
iconClassName ?? ''
|
|
106
|
+
)}
|
|
107
|
+
aria-hidden="true"
|
|
108
|
+
/>
|
|
109
|
+
{header}
|
|
110
|
+
</RadixAccordion.Trigger>
|
|
111
|
+
</RadixAccordion.Header>
|
|
112
|
+
));
|
|
113
|
+
|
|
114
|
+
export const AccordionTriggerBlank = forwardRef((
|
|
115
|
+
{
|
|
116
|
+
children,
|
|
117
|
+
className,
|
|
118
|
+
...props
|
|
119
|
+
},
|
|
120
|
+
forwardedRef
|
|
121
|
+
) => (
|
|
122
|
+
<RadixAccordion.Header asChild>
|
|
123
|
+
<RadixAccordion.Trigger
|
|
124
|
+
asChild
|
|
125
|
+
ref={forwardedRef}
|
|
126
|
+
{...props}
|
|
127
|
+
>
|
|
128
|
+
{children}
|
|
129
|
+
</RadixAccordion.Trigger>
|
|
130
|
+
</RadixAccordion.Header>
|
|
131
|
+
));
|
|
132
|
+
|
|
133
|
+
export const AccordionContent = forwardRef((
|
|
134
|
+
{
|
|
135
|
+
children,
|
|
136
|
+
className,
|
|
137
|
+
bodyClassName,
|
|
138
|
+
...props
|
|
139
|
+
},
|
|
140
|
+
forwardedRef
|
|
141
|
+
) => (
|
|
142
|
+
<RadixAccordion.Content
|
|
143
|
+
ref={forwardedRef}
|
|
144
|
+
className={twMerge(
|
|
145
|
+
'data-[state=open]:animate-slideDown data-[state=closed]:animate-slideUp overflow-hidden',
|
|
146
|
+
className ?? ''
|
|
147
|
+
)}
|
|
148
|
+
{...props}
|
|
149
|
+
>
|
|
150
|
+
<div
|
|
151
|
+
className={twMerge(
|
|
152
|
+
'p-4 pt-0.5',
|
|
153
|
+
bodyClassName ?? ''
|
|
154
|
+
)}
|
|
155
|
+
>
|
|
156
|
+
{children}
|
|
157
|
+
</div>
|
|
158
|
+
</RadixAccordion.Content>
|
|
159
|
+
));
|
|
160
|
+
|
|
161
|
+
Accordion.Item = AccordionItem;
|
|
162
|
+
Accordion.Trigger = AccordionTrigger;
|
|
163
|
+
Accordion.Trigger.Blank = AccordionTriggerBlank;
|
|
164
|
+
Accordion.Trigger.HasHeader = AccordionTriggerHasHeader;
|
|
165
|
+
Accordion.Content = AccordionContent;
|
|
166
|
+
|
|
167
|
+
AccordionItem.displayName = 'AccordionItem';
|
|
168
|
+
AccordionTrigger.displayName = 'AccordionTrigger';
|
|
169
|
+
AccordionTriggerBlank.displayName = 'AccordionTriggerBlank';
|
|
170
|
+
AccordionTriggerHasHeader.displayName = 'AccordionTriggerHasHeader';
|
|
171
|
+
AccordionContent.displayName = 'AccordionContent';
|
|
172
|
+
|
|
173
|
+
export default Accordion;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { memo } from "react";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import Accordion from "~/components/modules/accordions/default";
|
|
4
|
+
import FilterItem from "~/components/modules/filter/item";
|
|
5
|
+
import ItemsPill from "~/components/modules/buttons/items-pill";
|
|
6
|
+
|
|
7
|
+
const AccordionFilterItem = memo(({ filter, setDefaultValue, setSelectedFilters, selectedFilters }) => {
|
|
8
|
+
const fieldKey = filter.id;
|
|
9
|
+
const activeItemsCount = selectedFilters != null && selectedFilters[fieldKey]
|
|
10
|
+
? Object.keys(selectedFilters[fieldKey]).length
|
|
11
|
+
: 0;
|
|
12
|
+
|
|
13
|
+
const handleClearFilters = () => {
|
|
14
|
+
setSelectedFilters(prevFilters => {
|
|
15
|
+
const updatedFilters = { ...prevFilters };
|
|
16
|
+
delete updatedFilters[fieldKey];
|
|
17
|
+
return updatedFilters;
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
return (
|
|
21
|
+
<Accordion.Item key={filter.id} id={filter.id}>
|
|
22
|
+
<Accordion.Trigger.HasHeader
|
|
23
|
+
onClick={() => setDefaultValue(filter.id)}
|
|
24
|
+
className="stretched-link text-left"
|
|
25
|
+
iconClassName="order-last"
|
|
26
|
+
headerClassName="relative py-2 rounded border border-uiAccent/20 bg-white text-sm transition data-[state=open]:border-b-transparent data-[state=open]:rounded-b-none"
|
|
27
|
+
header={
|
|
28
|
+
activeItemsCount > 0 && (
|
|
29
|
+
<ItemsPill
|
|
30
|
+
activeItemsCount={activeItemsCount}
|
|
31
|
+
onClick={handleClearFilters}
|
|
32
|
+
/>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
>
|
|
36
|
+
{filter.title}
|
|
37
|
+
</Accordion.Trigger.HasHeader>
|
|
38
|
+
<Accordion.Content bodyClassName="px-2 py-1 bg-white rounded-b border border-uiAccent/20 border-t-0 max-h-[20vh] md:max-h-[25vh] overflow-auto">
|
|
39
|
+
{filter.items.sort().map(item => (
|
|
40
|
+
<FilterItem
|
|
41
|
+
key={item.name}
|
|
42
|
+
item={item}
|
|
43
|
+
field={filter.id}
|
|
44
|
+
selectedFilters={selectedFilters}
|
|
45
|
+
setSelectedFilters={setSelectedFilters}
|
|
46
|
+
/>
|
|
47
|
+
))}
|
|
48
|
+
</Accordion.Content>
|
|
49
|
+
</Accordion.Item>
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
AccordionFilterItem.displayName = 'AccordionItem';
|
|
53
|
+
export default AccordionFilterItem;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import Accordion from "~/components/modules/accordions/default";
|
|
2
|
+
import AccordionFilterItem from "./filterItem";
|
|
3
|
+
import FilterCard from "~/components/modules/cards/filter";
|
|
4
|
+
import Loading from "~/util/loading";
|
|
5
|
+
import { useMapList } from "~/contexts/mapListContext";
|
|
6
|
+
import React from 'react'
|
|
7
|
+
const AccordionFilters = ({
|
|
8
|
+
className,
|
|
9
|
+
defaultValue,
|
|
10
|
+
setDefaultValue,
|
|
11
|
+
setLocation,
|
|
12
|
+
setSelectedListItem
|
|
13
|
+
}) => {
|
|
14
|
+
const { filterOptions, selectedFilters, setSelectedFilters, favorites, filterByFavorites, setFilterByFavorites } = useMapList();
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<FilterCard className={className ?? ""}>
|
|
18
|
+
<FilterCard.Title icon="fa-solid:sliders-h">
|
|
19
|
+
<span>
|
|
20
|
+
Filter <span className="md:hidden lg:inline">your search</span>
|
|
21
|
+
</span>
|
|
22
|
+
</FilterCard.Title>
|
|
23
|
+
|
|
24
|
+
<Accordion defaultValue={defaultValue} className="space-y-4" >
|
|
25
|
+
{!filterOptions?.filters && (
|
|
26
|
+
<Accordion.Item>
|
|
27
|
+
<Loading />
|
|
28
|
+
</Accordion.Item>
|
|
29
|
+
)}
|
|
30
|
+
{filterOptions?.filters && filterOptions?.filters.map(filter => (
|
|
31
|
+
<AccordionFilterItem
|
|
32
|
+
key={filter.id}
|
|
33
|
+
filter={filter}
|
|
34
|
+
setDefaultValue={setDefaultValue}
|
|
35
|
+
selectedFilters={selectedFilters}
|
|
36
|
+
setSelectedFilters={prevFilters => { setSelectedFilters(prevFilters); setLocation(null); setSelectedListItem(null); }}
|
|
37
|
+
/>
|
|
38
|
+
))}
|
|
39
|
+
</Accordion>
|
|
40
|
+
</FilterCard>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default AccordionFilters;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useRef } from 'react';
|
|
2
|
+
import { motion, useScroll, useSpring, useTransform } from 'framer-motion';
|
|
3
|
+
import React from 'react'
|
|
4
|
+
const SlideIn = ({
|
|
5
|
+
as = "div",
|
|
6
|
+
children,
|
|
7
|
+
delay = 0.05,
|
|
8
|
+
opacity = 1,
|
|
9
|
+
scale = 0.95,
|
|
10
|
+
y = 50,
|
|
11
|
+
...rest
|
|
12
|
+
}) => {
|
|
13
|
+
const ref = useRef();
|
|
14
|
+
|
|
15
|
+
const { scrollYProgress } = useScroll({
|
|
16
|
+
target: ref,
|
|
17
|
+
offset: ["start end", `start 0.85`]
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const scaleContained = useTransform(scrollYProgress, [0, 1], [scale, 1]);
|
|
21
|
+
|
|
22
|
+
const scaleSpring = useSpring(scaleContained, {
|
|
23
|
+
damping: 100,
|
|
24
|
+
stiffness: 1000
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<motion.div
|
|
29
|
+
ref={ref}
|
|
30
|
+
className="h-full"
|
|
31
|
+
style={{
|
|
32
|
+
scale: scaleSpring
|
|
33
|
+
}}
|
|
34
|
+
{...rest}
|
|
35
|
+
>
|
|
36
|
+
{children}
|
|
37
|
+
</motion.div>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default SlideIn;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { twMerge } from 'tailwind-merge';
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import Button from '~/components/modules/buttons/default';
|
|
4
|
+
import ApplyDialog from '../dialogs/apply-dialog';
|
|
5
|
+
|
|
6
|
+
const ButtonGroupApply = ({
|
|
7
|
+
applyUrl,
|
|
8
|
+
useDetailsPostMessage,
|
|
9
|
+
applyText = 'Apply Now',
|
|
10
|
+
detailsUrl,
|
|
11
|
+
detailsText = 'View Details',
|
|
12
|
+
className,
|
|
13
|
+
buttonSize = 'default',
|
|
14
|
+
applyButtonVariant = 'primary',
|
|
15
|
+
detailsButtonVariant = 'outline',
|
|
16
|
+
includeDialog = false,
|
|
17
|
+
internalApplyLink,
|
|
18
|
+
itemId,
|
|
19
|
+
companyName
|
|
20
|
+
}) => {
|
|
21
|
+
|
|
22
|
+
const applyButton = () => {
|
|
23
|
+
return <Button.Anchor
|
|
24
|
+
variant={applyButtonVariant}
|
|
25
|
+
size={buttonSize}
|
|
26
|
+
>
|
|
27
|
+
{applyText}
|
|
28
|
+
</Button.Anchor>;;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<nav
|
|
33
|
+
className={twMerge`
|
|
34
|
+
flex flex-row justify-between gap-2 w-full
|
|
35
|
+
${className ?? ''}
|
|
36
|
+
`}
|
|
37
|
+
>
|
|
38
|
+
{detailsUrl && useDetailsPostMessage !== true &&
|
|
39
|
+
<Button.Anchor
|
|
40
|
+
href={detailsUrl}
|
|
41
|
+
variant={detailsButtonVariant}
|
|
42
|
+
size={buttonSize}
|
|
43
|
+
>
|
|
44
|
+
{detailsText}
|
|
45
|
+
</Button.Anchor>
|
|
46
|
+
}
|
|
47
|
+
{useDetailsPostMessage === true &&
|
|
48
|
+
<Button.Btn
|
|
49
|
+
onClick={() => { window.parent.postMessage({ itemId: itemId, type:'LISTING_ID' }, '*');}}
|
|
50
|
+
variant={detailsButtonVariant}
|
|
51
|
+
size={buttonSize}
|
|
52
|
+
>
|
|
53
|
+
{detailsText}
|
|
54
|
+
</Button.Btn>}
|
|
55
|
+
{applyUrl && !includeDialog &&
|
|
56
|
+
<>
|
|
57
|
+
<Button.Anchor
|
|
58
|
+
href={applyUrl}
|
|
59
|
+
variant={applyButtonVariant}
|
|
60
|
+
size={buttonSize}
|
|
61
|
+
>
|
|
62
|
+
{applyText}
|
|
63
|
+
</Button.Anchor>
|
|
64
|
+
</>
|
|
65
|
+
}
|
|
66
|
+
{applyUrl && includeDialog &&
|
|
67
|
+
<ApplyDialog applyUrl={applyUrl} internalApplyLink={internalApplyLink} companyName={companyName}>
|
|
68
|
+
{applyButton()}
|
|
69
|
+
</ApplyDialog>
|
|
70
|
+
}
|
|
71
|
+
</nav>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export default ButtonGroupApply;
|