@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,98 @@
1
+ import React, { createContext, useContext, useEffect, useState } from 'react';
2
+ import { markerIconProps } from '~/util/mapIconUtil';
3
+ import { useMap } from '~/contexts/mapContext';
4
+ import { searchNearbyPlaces } from '~/services/googlePlacesNearbyService';
5
+ const PlacesContext = createContext();
6
+
7
+ export const usePlaces = () => useContext(PlacesContext);
8
+
9
+ export const PlacesProvider = ({ children, placeMappings, markerColors }) => {
10
+ const { selectedPlaces, zoom, center } = useMap();
11
+ const [poiMarkers, setPoiMarkers] = useState({ markers: [], icon: null });
12
+ const [currentCenter, setCurrentCenter] = useState(center);
13
+ const [currentZoom, setCurrentZoom] = useState(zoom);
14
+ const [placesWindow, setPlacesWindow] = useState(false);
15
+ const [selectedPlaceMarker, setSelectedPlaceMarker] = useState(null);
16
+
17
+ const getRadiusForZoom = () => {
18
+ if (currentZoom >= 18) return 1000;
19
+ if (currentZoom <= 10) return 0;
20
+
21
+ let tempZoom = Math.pow(19 - currentZoom, 4.85);
22
+ let radius = tempZoom;
23
+ let minRadius = 1500;
24
+ let maxRadius = 800000;
25
+
26
+ if (radius < minRadius) radius = minRadius;
27
+ else if (radius > maxRadius) radius = maxRadius;
28
+
29
+ return radius;
30
+ };
31
+
32
+ useEffect(() => {
33
+ if (!selectedPlaces || (!selectedPlaces.length > 0) || !center || currentZoom < 12) {
34
+ setPoiMarkers({ markers: [], icon: null });
35
+ return;
36
+ }
37
+ const fetchPlaces = async () => {
38
+ let poiTypes = [];
39
+ const selectedPOICategories = selectedPlaces;
40
+ selectedPOICategories.forEach(pointOfInterest => {
41
+ poiTypes = poiTypes.concat(placeMappings[pointOfInterest]);
42
+ });
43
+
44
+ const radius = getRadiusForZoom();
45
+ const location = { latitude: currentCenter.lat, longitude: currentCenter.lng };
46
+
47
+ try {
48
+ const response = await searchNearbyPlaces(poiTypes, location, radius);
49
+ const newMarkers = response.places.map(place => {
50
+ const getParentCategory = types => {
51
+ const selectedTypes = selectedPOICategories.reduce((acc, category) => {
52
+ return acc.concat(placeMappings[category]);
53
+ }, []);
54
+
55
+ for (const type of types) {
56
+ if (!selectedTypes.includes(type)) continue;
57
+ for (const category in placeMappings) {
58
+ if (placeMappings[category].includes(type)) {
59
+ return category;
60
+ }
61
+ }
62
+
63
+ }
64
+ };
65
+
66
+ const icon = markerIconProps(markerColors.placeMarkers, getParentCategory(place.types));
67
+
68
+ return {
69
+ position: { lat: place.location.latitude, lng: place.location.longitude },
70
+ title: place.displayName.text,
71
+ icon: icon
72
+ };
73
+ });
74
+ setPoiMarkers({ markers: newMarkers, icon: null });
75
+ } catch (error) {
76
+ console.error('Failed to fetch places:', error);
77
+ }
78
+ };
79
+
80
+ fetchPlaces();
81
+ }, [selectedPlaces, currentZoom, currentCenter]);
82
+
83
+ return (
84
+ <PlacesContext.Provider value={{
85
+ poiMarkers,
86
+ setCurrentCenter,
87
+ currentCenter,
88
+ setCurrentZoom,
89
+ currentZoom,
90
+ placesWindow,
91
+ setPlacesWindow,
92
+ selectedPlaceMarker,
93
+ setSelectedPlaceMarker
94
+ }}>
95
+ {children}
96
+ </PlacesContext.Provider>
97
+ );
98
+ };
@@ -0,0 +1,16 @@
1
+ import useEventListener from '~/hooks/useEventListener';
2
+
3
+ const isBrowser = typeof window !== "undefined";
4
+
5
+ const useClickOutside = (ref, cb) => {
6
+ useEventListener(
7
+ 'click',
8
+ e => {
9
+ if (ref.current == null || ref.current.contains(e.target)) return;
10
+ cb(e);
11
+ },
12
+ isBrowser ? document : null
13
+ );
14
+ };
15
+
16
+ export default useClickOutside;
@@ -0,0 +1,25 @@
1
+ import { useEffect, useRef } from 'react';
2
+
3
+ const isBrowser = typeof window !== "undefined";
4
+
5
+ const useEventListener = (
6
+ eventType,
7
+ callback,
8
+ element = isBrowser ?? window
9
+ ) => {
10
+ const callbackRef = useRef(callback);
11
+
12
+ useEffect(() => {
13
+ callbackRef.current = callback;
14
+ }, [callback]);
15
+
16
+ useEffect(() => {
17
+ if (element == null) return;
18
+ const handler = e => callbackRef.current(e);
19
+ element.addEventListener(eventType, handler);
20
+
21
+ return () => element.removeEventListener(eventType, handler);
22
+ }, [eventType, element]);
23
+ };
24
+
25
+ export default useEventListener;
@@ -0,0 +1,19 @@
1
+ const isBrowser = typeof window !== 'undefined';
2
+
3
+ if (isBrowser) {
4
+ window.dataLayer = window.dataLayer || [];
5
+ }
6
+
7
+ const trackEvent = (category, action, label, value) => {
8
+ if (isBrowser && window.dataLayer) {
9
+ window.dataLayer.push({
10
+ 'event': 'eventTracking',
11
+ 'category': category,
12
+ 'action': action,
13
+ 'label': label,
14
+ 'value': value
15
+ });
16
+ }
17
+ };
18
+
19
+ export default trackEvent;
@@ -0,0 +1,102 @@
1
+ import { useEffect, useState, useRef } from "react";
2
+ import { getStorageObject } from "~/util/localStorageUtil";
3
+ import { dynamicSort } from "~/util/sortUtil";
4
+ import { useMapList } from '~/contexts/mapListContext';
5
+
6
+ const getDefaultItemId = () => {
7
+ let item = getStorageObject("selectedListItem");
8
+ if(item?.expanded == true){
9
+ return item.id;
10
+ }else{
11
+ return null;
12
+ }
13
+ };
14
+
15
+ const useListLogic = () => {
16
+ const [itemLimit, setItemLimit] = useState(100);
17
+ const [expandedId] = useState(getDefaultItemId());
18
+ const [sortSetting, setSortSetting] = useState(getStorageObject('sortSetting', null));
19
+ const [scrollPosition, setScrollPosition] = useState(getStorageObject('scrollPosition',0));
20
+ const loader = useRef(null);
21
+ const scrollContainerRef = useRef(null);
22
+ const itemRefs = useRef({});
23
+ const observer = useRef(null);
24
+ const { filteredListings, setFilteredListings } = useMapList();
25
+
26
+ useEffect(() => {
27
+ if(!sortSetting) return;
28
+ localStorage.setItem('sortSetting', JSON.stringify(sortSetting));
29
+ let listingFiltered = dynamicSort(filteredListings, sortSetting.field, sortSetting.type);
30
+ setFilteredListings(listingFiltered);
31
+ },[sortSetting]);
32
+
33
+ useEffect(() => {
34
+ observer.current = new IntersectionObserver(handleObserver, {
35
+ root: scrollContainerRef.current,
36
+ rootMargin: "100px 0px",
37
+ threshold: 0.5
38
+ });
39
+ if (loader.current) {
40
+ observer.current.observe(loader.current);
41
+ }
42
+ return () => {
43
+ if (observer.current && loader.current) {
44
+ observer.current.unobserve(loader.current);
45
+ }
46
+ };
47
+ }, [loader.current, itemLimit, filteredListings.length]);
48
+
49
+ useEffect(() => {
50
+ localStorage.setItem('scrollPosition', scrollPosition.toString());
51
+ }, [scrollPosition]);
52
+
53
+ useEffect(() => {
54
+ const savedScrollPosition = scrollPosition;
55
+ if(parseInt(savedScrollPosition) > 3000){
56
+ setItemLimit(savedScrollPosition / 10);
57
+ }
58
+ if (savedScrollPosition && scrollPosition != null && scrollContainerRef.current) {
59
+ let scrollContainerRefCurrent = scrollContainerRef.current;
60
+ setTimeout(() => {
61
+ scrollContainerRefCurrent = parseInt(savedScrollPosition, 10);
62
+ }, 300);
63
+ }
64
+ }, []);
65
+
66
+ useEffect(() => {
67
+ const scrollContainer = scrollContainerRef.current;
68
+ if (scrollContainer) {
69
+ scrollContainer.addEventListener('scroll', handleScroll);
70
+ }
71
+ return () => {
72
+ if (scrollContainer) {
73
+ scrollContainer.removeEventListener('scroll', handleScroll);
74
+ }
75
+ };
76
+ }, [scrollContainerRef.current]);
77
+
78
+ const handleObserver = entities => {
79
+ const target = entities[0];
80
+ if (!target.isIntersecting) return;
81
+ if (filteredListings.length > itemLimit) {
82
+ setItemLimit(prevLimit => prevLimit + 100);
83
+ } else if (observer.current) {
84
+ observer.current.disconnect();
85
+ }
86
+
87
+ };
88
+
89
+ if(sortSetting){
90
+ dynamicSort(filteredListings, sortSetting.field, sortSetting.type);
91
+ }
92
+
93
+ const handleScroll = () => {
94
+ if (scrollContainerRef.current) {
95
+ setScrollPosition(scrollContainerRef.current.scrollTop);
96
+ }
97
+ };
98
+
99
+ return { itemLimit, expandedId, sortSetting, scrollPosition, loader, scrollContainerRef, itemRefs, setSortSetting, setScrollPosition, dynamicSort, filteredListings };
100
+ };
101
+
102
+ export default useListLogic;
@@ -0,0 +1,24 @@
1
+ import { useState, useEffect } from 'react';
2
+ import useWindowSize from './useWindowSize';
3
+
4
+ export default function useRefScrollProgress({ inputRef }) {
5
+ const ref = inputRef;
6
+ const [start, setStart] = useState(null);
7
+ const [end, setEnd] = useState(null);
8
+ const size = useWindowSize();
9
+
10
+ useEffect(() => {
11
+ if (!ref.current) {
12
+ return;
13
+ }
14
+
15
+ const rect = ref.current.getBoundingClientRect();
16
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
17
+ const offsetTop = rect.top + scrollTop;
18
+
19
+ setStart(offsetTop / document.body.clientHeight);
20
+ setEnd((offsetTop + rect.height) / document.body.clientHeight);
21
+ }, [ref, size]);
22
+
23
+ return { ref, start, end };
24
+ }
@@ -0,0 +1,63 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ const isBrowser = typeof window !== 'undefined';
4
+
5
+ function useScript(src) {
6
+ // Keep track of script status ("idle", "loading", "ready", "error")
7
+ const [status, setStatus] = useState(src ? "loading" : "idle");
8
+ useEffect(
9
+ () => {
10
+ // Allow falsy src value if waiting on other data needed for
11
+ // constructing the script URL passed to this hook.
12
+ if (!isBrowser || !src) {
13
+ setStatus("idle");
14
+ return;
15
+ }
16
+ // Fetch existing script element by src
17
+ // It may have been added by another intance of this hook
18
+ let script = document.querySelector(`script[src="${src}"]`);
19
+ if (!script) {
20
+ // Create script
21
+ script = document.createElement("script");
22
+ script.src = src;
23
+ script.async = true;
24
+ script.setAttribute("data-status", "loading");
25
+ // Add script to document body
26
+ document.body.appendChild(script);
27
+ // Store status in attribute on script
28
+ // This can be read by other instances of this hook
29
+ const setAttributeFromEvent = event => {
30
+ script.setAttribute(
31
+ "data-status",
32
+ event.type === "load" ? "ready" : "error"
33
+ );
34
+ };
35
+ script.addEventListener("load", setAttributeFromEvent);
36
+ script.addEventListener("error", setAttributeFromEvent);
37
+ } else {
38
+ // Grab existing script status from attribute and set to state.
39
+ setStatus(script.getAttribute("data-status"));
40
+ }
41
+ // Script event handler to update status in state
42
+ // Note: Even if the script already exists we still need to add
43
+ // event handlers to update the state for *this* hook instance.
44
+ const setStateFromEvent = event => {
45
+ setStatus(event.type === "load" ? "ready" : "error");
46
+ };
47
+ // Add event listeners
48
+ script.addEventListener("load", setStateFromEvent);
49
+ script.addEventListener("error", setStateFromEvent);
50
+ // Remove event listeners on cleanup
51
+ return () => {
52
+ if (script) {
53
+ script.removeEventListener("load", setStateFromEvent);
54
+ script.removeEventListener("error", setStateFromEvent);
55
+ }
56
+ };
57
+ },
58
+ [src] // Only re-run effect if script src changes
59
+ );
60
+ return status;
61
+ }
62
+
63
+ export default useScript;
@@ -0,0 +1,39 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ export const useScrollDirection = () => {
4
+ const threshold = 1;
5
+ const [scrollDir, setScrollDir] = useState('up');
6
+
7
+ useEffect(() => {
8
+ let previousScrollYPosition = window.scrollY;
9
+
10
+ const scrolledMoreThanThreshold = currentScrollYPosition =>
11
+ Math.abs(currentScrollYPosition - previousScrollYPosition) > threshold;
12
+
13
+ const isScrollingUp = currentScrollYPosition =>
14
+ currentScrollYPosition > previousScrollYPosition &&
15
+ !(previousScrollYPosition > 0 && currentScrollYPosition === 0) &&
16
+ !(currentScrollYPosition > 0 && previousScrollYPosition === 0);
17
+
18
+ const updateScrollDirection = () => {
19
+ const currentScrollYPosition = window.scrollY;
20
+
21
+ if (scrolledMoreThanThreshold(currentScrollYPosition)) {
22
+ const newScrollDirection = isScrollingUp(currentScrollYPosition)
23
+ ? 'down'
24
+ : 'up';
25
+ setScrollDir(newScrollDirection);
26
+ previousScrollYPosition =
27
+ currentScrollYPosition > 0 ? currentScrollYPosition : 0;
28
+ }
29
+ };
30
+
31
+ const onScroll = () => window.requestAnimationFrame(updateScrollDirection);
32
+
33
+ window.addEventListener("scroll", onScroll);
34
+
35
+ return () => window.removeEventListener("scroll", onScroll);
36
+ }, []);
37
+
38
+ return scrollDir;
39
+ };
@@ -0,0 +1,95 @@
1
+ import { useState, useContext } from 'react';
2
+ import trackEvent from '~/hooks/useEventTracker';
3
+
4
+ import { SectionContext } from '~/components/modules/sections/sectionContext';
5
+
6
+ const isBrowser = typeof window !== 'undefined';
7
+
8
+ const ignore = ['intro'];
9
+ const RATIO = 0.33;
10
+ // let first = true;
11
+
12
+ const useSectionTracker = () => {
13
+ const { setCurrentSection } = useContext(SectionContext);
14
+
15
+ const [lastSection, setLastSection] = useState('');
16
+ const [sections, setSections] = useState([]);
17
+ let timeout = null;
18
+
19
+ // Allows a blank hash or ensures there is a # in the hash and replaces current state
20
+ const setHash = hash => {
21
+ if (hash !== ' ' && hash.indexOf('#') === -1) {
22
+ hash = `#${hash}`;
23
+ }
24
+ if (window.history.replaceState) {
25
+ window.history.replaceState(window.history.state, null, hash);
26
+ } else {
27
+ window.location.replace(hash);
28
+ }
29
+ };
30
+
31
+ // Called when a section is intersecting
32
+ const sectionIsIntersecting = (id, ratio, threshold) => {
33
+ // Ignore sections we don't want to track
34
+ if (ignore.indexOf(id) !== -1) {
35
+ return;
36
+ }
37
+
38
+ const newThreshold = threshold;
39
+ let found = false;
40
+
41
+ // Update sections we've seen before
42
+ sections.forEach(section => {
43
+ if (section.id === id) {
44
+ if (newThreshold < RATIO) {
45
+ section.active = false;
46
+ } else {
47
+ section.active = true;
48
+ }
49
+ section.threshold = newThreshold;
50
+ found = true;
51
+ }
52
+ });
53
+
54
+ // Otherwise, add the section to the list
55
+ if (!found) {
56
+ setSections(sections => {
57
+ sections.push({ id, threshold: newThreshold, active: newThreshold < RATIO ? false : true });
58
+ return sections;
59
+ });
60
+ }
61
+
62
+ let maxThreshold = 0;
63
+ let sectionId = '';
64
+
65
+ clearTimeout(timeout);
66
+
67
+ timeout = setTimeout(() => {
68
+ // Find the section with the largest threshold
69
+ sections.forEach(section => {
70
+ if (section.active && section.threshold > maxThreshold) {
71
+ maxThreshold = section.threshold;
72
+ sectionId = section.id;
73
+ }
74
+ });
75
+
76
+ // Set the current section (hash, track event)
77
+ if (isBrowser && sectionId && sectionId !== lastSection) {
78
+ setHash(ignore.some(id => id === sectionId) ? ' ' : sectionId);
79
+ trackEvent('Engagement', 'View Section', sectionId);
80
+ setCurrentSection(sectionId);
81
+ // } else {
82
+ } else if (isBrowser && window.scrollY < 100) {
83
+ setCurrentSection(' ');
84
+ setHash(' ');
85
+ }
86
+
87
+ // Remember this section for next time so we don't set it again if not necessary
88
+ setLastSection(sectionId);
89
+ }, 500);
90
+ };
91
+
92
+ return sectionIsIntersecting;
93
+ };
94
+
95
+ export default useSectionTracker;
@@ -0,0 +1,43 @@
1
+ import { useState, useEffect } from 'react';
2
+ import UAParser from 'ua-parser-js';
3
+
4
+ const isBrowser = () => typeof window !== 'undefined';
5
+
6
+ const useUserAgent = () => {
7
+ const [state, setState] = useState(null);
8
+
9
+ useEffect(() => {
10
+ if (!isBrowser) {
11
+ return null;
12
+ }
13
+
14
+ let didRun = true;
15
+
16
+ try {
17
+ const uaParser = new UAParser.UAParser();
18
+ uaParser.setUA(window.navigator.userAgent);
19
+ const payload = {
20
+ os: uaParser.getOS(),
21
+ browser: uaParser.getBrowser(),
22
+ cpu: uaParser.getCPU(),
23
+ device: uaParser.getDevice(),
24
+ engine: uaParser.getEngine()
25
+ };
26
+ if (didRun) {
27
+ setState(payload);
28
+ }
29
+ } catch (err) {
30
+ if (didRun) {
31
+ setState(null);
32
+ }
33
+ }
34
+
35
+ return () => {
36
+ didRun = false;
37
+ };
38
+ }, []);
39
+
40
+ return state;
41
+ };
42
+
43
+ export default useUserAgent;
@@ -0,0 +1,28 @@
1
+ import { useState, useEffect } from 'react';
2
+
3
+ export default function useWindowSize() {
4
+ // Initialize state with undefined width/height so server and client renders match
5
+ // Learn more here: https://joshwcomeau.com/react/the-perils-of-rehydration/
6
+ const [windowSize, setWindowSize] = useState({
7
+ width: undefined,
8
+ height: undefined
9
+ });
10
+
11
+ useEffect(() => {
12
+ // Handler to call on window resize
13
+ function handleResize() {
14
+ // Set window width/height to state
15
+ setWindowSize({
16
+ width: window.innerWidth,
17
+ height: window.innerHeight
18
+ });
19
+ }
20
+ // Add event listener
21
+ window.addEventListener("resize", () => {
22
+ window.requestAnimationFrame(handleResize);
23
+ });
24
+ // Remove event listener on cleanup
25
+ return () => window.removeEventListener("resize", handleResize);
26
+ }, []); // Empty array ensures that effect is only run on mount
27
+ return windowSize;
28
+ }
package/src/index.css ADDED
@@ -0,0 +1,25 @@
1
+ @config "./tailwind/tailwind.config.js";
2
+ @tailwind base;
3
+ @tailwind components;
4
+ @tailwind utilities;
5
+
6
+
7
+ /* @layer base {
8
+ html {
9
+ @apply text-400 text-uiText [scroll-behavior:smooth];
10
+ }
11
+ }
12
+
13
+ @layer components {
14
+ .track * {
15
+ @apply pointer-events-none;
16
+ }
17
+
18
+ .stretched-link::after {
19
+ @apply content-[''] absolute inset-0 z-[1] pointer-events-auto bg-transparent;
20
+ }
21
+ }
22
+
23
+ .fit-content{
24
+ height:fit-content;
25
+ } */