@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,149 @@
1
+ import { Fragment, useRef, useState, useEffect } from "react";
2
+ import { Combobox, Transition } from "@headlessui/react";
3
+ import Button from "~/components/modules/buttons/default";
4
+ import Icon from "~/components/modules/icon";
5
+ import usePlacesAutocomplete, {
6
+ getGeocode,
7
+ getLatLng
8
+ } from "use-places-autocomplete";
9
+ import { useMap } from "~/contexts/mapContext";
10
+ import { useMapList } from "~/contexts/mapListContext";
11
+ import { getStorageItem } from "~/util/localStorageUtil";
12
+ import React from 'react'
13
+
14
+ const FilterCommute = ({ className }) => {
15
+ const {
16
+ ready,
17
+ value,
18
+ suggestions: { status, data },
19
+ setValue,
20
+ clearSuggestions
21
+ } = usePlacesAutocomplete();
22
+
23
+ const [selected, setSelected] = useState(getStorageItem('selectedCommute', ''));
24
+ const inputRef = useRef(null);
25
+ const { setCommuteLocation, commuteLocation } = useMapList();
26
+ const [isCurrentLocation, setIsCurrentLocation] = useState(getStorageItem('isCurrentLocation', false));
27
+
28
+ useEffect(() => {
29
+ if(commuteLocation !== null && commuteLocation != '') return;
30
+ setIsCurrentLocation(false);
31
+ localStorage.removeItem('isCurrentLocation');
32
+ localStorage.removeItem('selectedCommute');
33
+ setSelected("");
34
+ },[commuteLocation]);
35
+
36
+ const handleSelect = async (val, isCurrLocation = false) => {
37
+ setValue(val, false);
38
+ setSelected(val);
39
+ localStorage.setItem('selectedCommute', val);
40
+ clearSuggestions();
41
+ if (isCurrLocation) return;
42
+ try {
43
+ const results = await getGeocode({ address: val });
44
+ const { lat, lng } = await getLatLng(results[0]);
45
+ setCommuteLocation({ lat, lng });
46
+ } catch (error) { }
47
+ };
48
+
49
+ const fetchLocation = () => {
50
+ if (!navigator.geolocation) {
51
+ console.error("Geolocation is not supported by this browser.");
52
+ return;
53
+ }
54
+ navigator.geolocation.getCurrentPosition(
55
+ position => {
56
+ setCommuteLocation({
57
+ lat: position.coords.latitude,
58
+ lng: position.coords.longitude
59
+ });
60
+ handleSelect("Current Location");
61
+ },
62
+ error => {
63
+ console.error("Error fetching location", error);
64
+ },
65
+ );
66
+ };
67
+
68
+ return (
69
+ <div className={`relative ${className ?? ""}`}>
70
+ <label
71
+ htmlFor="commute"
72
+ className="flex items-center gap-2 mb-2 text-xs uppercase font-bold text-uiText"
73
+ >
74
+ <Icon
75
+ icon="ri:pin-distance-fill"
76
+ size="w-5 h-5"
77
+ className="text-uiAccent/30"
78
+ />
79
+ <span>
80
+ <span className="md:hidden lg:inline">Calculate your</span> commute
81
+ </span>
82
+ </label>
83
+
84
+ <Combobox value={selected} onChange={handleSelect}>
85
+ <div className="relative mt-1">
86
+ <div className="relative flex items-center w-full pr-2 cursor-default overflow-hidden rounded bg-white text-left border border-uiAccent/20 focus-within:ring-1 focus-within:ring-uiAccent focus:outline-none">
87
+ <Combobox.Input
88
+ className="w-full border-none py-2 pl-4 pr-1 text-sm leading-5 text-gray-900 focus:ring-0 placeholder:text-gray-400"
89
+ onChange={e => {
90
+ setValue(e.target.value);
91
+ setSelected(e.target.value);
92
+ }}
93
+ value={selected}
94
+ disabled={!ready}
95
+ placeholder="Starting point"
96
+ ref={inputRef}
97
+ />
98
+ <Button.Btn
99
+ variant="icon"
100
+ size="sqsm"
101
+ onClick={() => {setIsCurrentLocation(!isCurrentLocation);
102
+ localStorage.setItem('isCurrentLocation', !isCurrentLocation);
103
+ if(isCurrentLocation || commuteLocation){
104
+ setCommuteLocation("");
105
+ setSelected("");
106
+ }else if(!commuteLocation){fetchLocation();}}}
107
+
108
+ className=""
109
+ >
110
+ <span className=" sr-only">Use your location</span>
111
+ {commuteLocation ? <Icon icon="mdi:times"></Icon> : <Button.Icon className={isCurrentLocation ? 'text-blue-500' : 'text-current'} icon="mdi:my-location" />}
112
+ </Button.Btn>
113
+ </div>
114
+ <Transition
115
+ as={Fragment}
116
+ leave="transition ease-in duration-100"
117
+ leaveFrom="opacity-100"
118
+ leaveTo="opacity-0"
119
+ afterLeave={clearSuggestions}
120
+ >
121
+ <Combobox.Options className="absolute z-20 mt-1 max-h-60 w-full overflow-auto rounded bg-white py-1 text-base shadow ring-1 ring-uiAccent/10 focus:outline-none sm:text-sm">
122
+ {status === "OK" &&
123
+ data.map(data => (
124
+ <Combobox.Option
125
+ key={data.place_id}
126
+ value={data.description}
127
+ className={({ active }) =>
128
+ `relative cursor-default select-none px-4 py-2 ${active ? "bg-primary text-white" : "text-uiText"
129
+ }`
130
+ }
131
+ >
132
+ {({ selected, active }) => (
133
+ <span
134
+ className={`block truncate ${selected ? "font-bold" : "font-medium"}`}
135
+ >
136
+ {data.description}
137
+ </span>
138
+ )}
139
+ </Combobox.Option>
140
+ ))}
141
+ </Combobox.Options>
142
+ </Transition>
143
+ </div>
144
+ </Combobox>
145
+ </div>
146
+ );
147
+ };
148
+
149
+ export default FilterCommute;
@@ -0,0 +1,86 @@
1
+ import { useState } from "react";
2
+ import FilterSearch from "~/components/modules/filter/search";
3
+ import FiltersAccordion from "~/components/modules/accordions/filters";
4
+ import FilterLocations from "~/components/modules/filter/location";
5
+ import Button from "~/components/modules/buttons/default";
6
+ import { useMap } from "~/contexts/mapContext";
7
+ import { useMapList } from "~/contexts/mapListContext";
8
+ import React from 'react'
9
+
10
+ const Filter = ({
11
+ className,
12
+ showMap
13
+ }) => {
14
+ const [hasActiveFilters, setHasActiveFilters] = useState(false);
15
+ const [defaultValue, setDefaultValue] = useState(null);
16
+ const { setSelectedListItem, setLocation, filterReset } = useMap();
17
+ const {
18
+ filteredListings,
19
+ selectedFilters,
20
+ setSelectedFilters,
21
+ setMobileTab,
22
+ handleSettingFavorites,
23
+ setQuery,
24
+ siteConfig
25
+ } = useMapList();
26
+
27
+ return (
28
+ <div
29
+ className={`
30
+ relative max-h-[95vh] md:max-h-screen overflow-y-auto overflow-x-auto
31
+ ${className ?? ""}
32
+ `}
33
+ >
34
+ <div className="px-4 md:pt-4 space-y-4">
35
+ <FiltersAccordion
36
+ setHasActiveFilters={setHasActiveFilters}
37
+ defaultValue={defaultValue}
38
+ setDefaultValue={value => {setDefaultValue(value == defaultValue ? "" : value);}}
39
+ setLocation={setLocation}
40
+ setSelectedListItem={setSelectedListItem}
41
+ />
42
+ <FilterSearch/>
43
+ {siteConfig.hideLocations !== true &&
44
+ <FilterLocations
45
+ setHasActiveFilters={setHasActiveFilters}
46
+ defaultValue={defaultValue}
47
+ showMap={showMap}
48
+ setDefaultValue={value => {setDefaultValue(value == defaultValue ? "" : value);}}
49
+ setLocation={setLocation}
50
+ setSelectedListItem={setSelectedListItem}
51
+ />
52
+ }
53
+ </div>
54
+ <div className="sticky bottom-0 inset-x-0 flex items-center justify-between gap-2 py-2 px-4 mt-2 bg-white md:bg-gray-100">
55
+ <Button.Btn
56
+ onClick={() => { filterReset(); setSelectedFilters({}); setQuery(null); handleSettingFavorites(null);}}
57
+ variant="outline"
58
+ size="sm"
59
+ >
60
+ Reset
61
+ </Button.Btn>
62
+ {selectedFilters && Object.keys(selectedFilters).length > 0 &&
63
+ <Button.Btn
64
+ onClick={() => setMobileTab("listTab")}
65
+ variant="primary"
66
+ size="sm"
67
+ className={`
68
+ md:hidden
69
+ ${hasActiveFilters ? "opacity-0 pointer-events-none" : "opacity-100"}
70
+ `}
71
+ >
72
+ <Button.Body>
73
+ <Button.Icon
74
+ icon="fluent:search-12-filled"
75
+ size="w-3.5 h-3.5"
76
+ />
77
+ Show {filteredListings.length} Jobs
78
+ </Button.Body>
79
+ </Button.Btn>
80
+ }
81
+ </div>
82
+ </div>
83
+ );
84
+ };
85
+
86
+ export default Filter;
@@ -0,0 +1,77 @@
1
+ import { useState, useEffect, useRef } from "react";
2
+ import React from 'react'
3
+
4
+ const FilterItem = ({
5
+ className,
6
+ item,
7
+ type = 'checkbox',
8
+ itemKey = null,
9
+ hasCount = true,
10
+ field,
11
+ selectedFilters,
12
+ setSelectedFilters,
13
+ ...rest
14
+ }) => {
15
+ const itemName = item.name ? item.name : item;
16
+ itemKey = itemKey === null ? itemName : itemKey;
17
+ var isActive =
18
+ selectedFilters != undefined && !!selectedFilters[field]?.[itemKey];
19
+
20
+ const changeHandler = () => {
21
+ setSelectedFilters(prevFilters => {
22
+ const updatedFilters = { ...prevFilters };
23
+ if (!isActive) {
24
+ if (!updatedFilters[field]) {
25
+ updatedFilters[field] = {};
26
+ }
27
+ updatedFilters[field][itemKey] = true;
28
+ return updatedFilters;
29
+ }
30
+ delete updatedFilters[field][itemKey];
31
+ if (Object.keys(updatedFilters[field]).length === 0) {
32
+ delete updatedFilters[field];
33
+ }
34
+ return updatedFilters;
35
+ });
36
+ };
37
+ const [activeItem, setActiveItem] = useState(isActive);
38
+
39
+ useEffect(() => {
40
+ if(selectedFilters && selectedFilters[field] && Object.keys(selectedFilters[field])?.length > 0) return;
41
+ else if(activeItem === true){
42
+ setActiveItem(false);
43
+ }
44
+ },[selectedFilters]);
45
+
46
+ return (
47
+ <label
48
+ className={`
49
+ flex items-start gap-2 px-2 py-1.5 rounded-sm text-sm cursor-pointer transition hover:bg-uiAccent/5
50
+ ${className ?? ""}
51
+ `}
52
+ {...rest}
53
+ >
54
+ <input
55
+ id={itemKey}
56
+ name={field}
57
+ disabled={item.count == 0}
58
+ value={itemName}
59
+ type={type}
60
+ className={`h-4 w-4 mt-px text-primary border-uiAccent/30 transition-colors rounded-sm`}
61
+ checked = {activeItem }
62
+ onChange={() => {
63
+ setActiveItem(!activeItem);
64
+ changeHandler();
65
+ }}
66
+ />
67
+ <span className="font-medium">{itemName}</span>
68
+ {hasCount && (
69
+ <span className="inline-block mt-1 ml-auto text-xs leading-none text-primary">
70
+ ({item.count})
71
+ </span>
72
+ )}
73
+ </label>
74
+ );
75
+ };
76
+
77
+ export default FilterItem;
@@ -0,0 +1,69 @@
1
+ import Accordion from "~/components/modules/accordions/default";
2
+ import FilterItem from "~/components/modules/filter/item";
3
+ import React from 'react';
4
+ import FilterCommute from "~/components/modules/filter/commute";
5
+ import FilterPointsOfInterest from "~/components/modules/filter/points-of-interest";
6
+ import ItemsPill from "~/components/modules/buttons/items-pill";
7
+ import FilterCard from "~/components/modules/cards/filter";
8
+ import Loading from "~/util/loading";
9
+ import AccordionFilterItem from "~/components/modules/accordions/filterItem";
10
+ import { useMapList } from "~/contexts/mapListContext";
11
+
12
+ const FilterLocation = ({
13
+ className,
14
+ defaultValue,
15
+ setDefaultValue,
16
+ showMap = false,
17
+ setLocation,
18
+ setSelectedListItem
19
+ }) => {
20
+ const { setSelectedFilters, selectedFilters, filterOptions } = useMapList();
21
+ const activeItemsCount = selectedFilters != null && selectedFilters["cityState"]
22
+ ? Object.keys(selectedFilters["cityState"]).length
23
+ : 0;
24
+
25
+ const handleClearFilters = () => {
26
+ setSelectedFilters(prevFilters => {
27
+ const updatedFilters = { ...prevFilters };
28
+ delete updatedFilters["cityState"];
29
+ return updatedFilters;
30
+ });
31
+ };
32
+ var locations = filterOptions?.locations;
33
+ var pointsOfInterest = filterOptions?.pointsOfInterest;
34
+ return (
35
+ <FilterCard className={className ?? ""}>
36
+ <FilterCard.Title icon="fluent:location-16-filled">
37
+ <span>
38
+ <span className="hidden lg:inline">Job</span> location
39
+ </span>
40
+ </FilterCard.Title>
41
+ {!locations && (
42
+ <Loading />
43
+ )}
44
+ {locations && <Accordion defaultValue={defaultValue} className="space-y-4">
45
+ {locations?.map(filter =>
46
+ (<AccordionFilterItem
47
+ key={filter.id}
48
+ filter={filter}
49
+ setDefaultValue={setDefaultValue}
50
+ selectedFilters={selectedFilters}
51
+ setSelectedFilters={prevFilters => { setSelectedFilters(prevFilters); setLocation(null); setSelectedListItem(null); }}
52
+ />
53
+ ))}
54
+ {showMap && <FilterCommute className="mt-6" />}
55
+ {showMap && (
56
+ <FilterPointsOfInterest
57
+ className="mt-4"
58
+ pointsOfInterest={pointsOfInterest}
59
+ setDefaultValue={setDefaultValue}
60
+ defaultValue={defaultValue}
61
+ />
62
+ )}
63
+ </Accordion>
64
+ }
65
+ </FilterCard>
66
+ );
67
+ };
68
+
69
+ export default FilterLocation;
@@ -0,0 +1,43 @@
1
+ import Accordion from "~/components/modules/accordions/default";
2
+ import RadioItem from "~/components/modules/filter/radio-item";
3
+ import React from 'react'
4
+
5
+ const FilterPointsOfInterest = ({
6
+ title = "Points of interest",
7
+ pointsOfInterest,
8
+ setDefaultValue,
9
+ className,
10
+ defaultValue
11
+ }) => {
12
+ return (
13
+ <div
14
+ className={`
15
+ relative
16
+ ${className ?? ""}
17
+ `}
18
+ >
19
+ <Accordion defaultValue={defaultValue} className="space-y-4">
20
+ <Accordion.Item id="points-of-interest">
21
+ <Accordion.Trigger.HasHeader
22
+ onClick={() => setDefaultValue("points-of-interest")}
23
+ className="stretched-link text-left"
24
+ iconClassName="order-last"
25
+ 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"
26
+ >
27
+ {title}
28
+ </Accordion.Trigger.HasHeader>
29
+ <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">
30
+ {pointsOfInterest.items.map((item, index) => {
31
+ return <RadioItem
32
+ key={item.key + index}
33
+ item={item}
34
+ field={item.id} />;
35
+ })}
36
+ </Accordion.Content>
37
+ </Accordion.Item>
38
+ </Accordion>
39
+ </div>
40
+ );
41
+ };
42
+
43
+ export default FilterPointsOfInterest;
@@ -0,0 +1,51 @@
1
+ import Icon from "~/components/modules/icon";
2
+ import { useMap } from "~/contexts/mapContext";
3
+ import React from 'react'
4
+
5
+ const RadioItem = ({
6
+ className,
7
+ item,
8
+ field,
9
+ ...rest
10
+ }) => {
11
+ const { selectedPlaces, setSelectedPlaces } = useMap();
12
+ const chosenPlace = item.key.toLowerCase();
13
+ let isActive = selectedPlaces.includes(chosenPlace);
14
+
15
+ const changeHandler = () => {
16
+ setSelectedPlaces([ isActive ? '' : chosenPlace]);
17
+ };
18
+
19
+ return (
20
+ <label key ={item.key}
21
+ className={`
22
+ flex items-start gap-2 px-2 py-1.5 rounded-sm text-sm cursor-pointer transition hover:bg-uiAccent/5
23
+ ${item.count === 0 ? 'text-uiDisabled cursor-not-allowed' : 'hover:bg-uiAccent/5'}
24
+ ${className ?? ""}
25
+ `}
26
+ {...rest}
27
+ >
28
+ <input
29
+ key ={item.key}
30
+ id={item.Key}
31
+ name={field}
32
+ value={item.key}
33
+ type={"radio"}
34
+ className={`h-4 w-4 mt-px text-primary border-uiAccent/30 transition-colors rounded-full`}
35
+ checked={isActive}
36
+ onClick={() => {
37
+ changeHandler();
38
+ }}
39
+ onChange={()=> {}}
40
+ />
41
+ <span className="font-medium">{item.key}</span>
42
+ {isActive &&
43
+ <div className=" w-full unselect-div">
44
+ <Icon className="float-right" icon="mdi:times"></Icon>
45
+ </div>
46
+ }
47
+ </label>
48
+ );
49
+ };
50
+
51
+ export default RadioItem;
@@ -0,0 +1,89 @@
1
+ import { useRef, useState, useEffect } from "react";
2
+ import Button from "~/components/modules/buttons/default";
3
+ import Icon from "~/components/modules/icon";
4
+ import FilterCard from "~/components/modules/cards/filter";
5
+ import { useMapList } from "~/contexts/mapListContext";
6
+ import React from 'react'
7
+
8
+ const Search = ({
9
+ inputPlaceholder = "Keywords",
10
+ showSearchIcon = false,
11
+ className,
12
+ labelClassName
13
+ }) => {
14
+ const [inputValue, setInputValue] = useState(query != null ? query: "");
15
+ const debounceTimer = useRef(null);
16
+ const {
17
+ query,
18
+ setQuery,
19
+ siteConfig
20
+ } = useMapList();
21
+ const handleInputChange = e => {
22
+ setInputValue(e.target.value);
23
+ if (debounceTimer.current) {
24
+ clearTimeout(debounceTimer.current);
25
+ }
26
+ debounceTimer.current = setTimeout(() => {
27
+ setQuery(e.target.value);
28
+ }, 500);
29
+ };
30
+
31
+ const handleReset = () => {
32
+ setInputValue("");
33
+ setQuery("");
34
+ };
35
+
36
+ useEffect(() =>{
37
+ if(query == null){
38
+ setInputValue("");
39
+ }
40
+ },[query]);
41
+ inputPlaceholder = siteConfig.searchConfig.placeholder;
42
+ return (
43
+ <FilterCard as="form" className={className ?? ""}>
44
+ <FilterCard.Title
45
+ as="label"
46
+ icon="fluent:search-12-filled"
47
+ className={labelClassName ?? ""}
48
+ >
49
+ <span>
50
+ {siteConfig.searchConfig.label}
51
+ </span>
52
+ </FilterCard.Title>
53
+
54
+ <div className="relative flex items-center px-2 rounded bg-white border border-uiAccent/20 focus-within:ring-1 focus-within:ring-uiAccent">
55
+ {showSearchIcon && (
56
+ <Icon
57
+ icon="fluent:search-12-filled"
58
+ size="w-4 h-4"
59
+ className="mr-2 text-uiAccent/50"
60
+ />
61
+ )}
62
+ <input
63
+ type="text"
64
+ name="search"
65
+ onKeyDown={e => { if (e.key === 'Enter') {
66
+ e.preventDefault();
67
+ }}}
68
+ placeholder={inputPlaceholder}
69
+ value={inputValue}
70
+ className="w-full px-0 py-2 text-sm border-0 transition-colors placeholder:text-uiText/50 focus:ring-0 focus:outline-none"
71
+ onChange={handleInputChange}
72
+ />
73
+
74
+ <Button.Btn
75
+ type="reset"
76
+ variant="icon"
77
+ size="sqsm"
78
+ className={`transition-opacity ${inputValue ? "opacity-100" : "opacity-0 pointer-events-none"}`}
79
+ onClick={handleReset}
80
+ >
81
+ <span className="sr-only">Clear</span>
82
+ <Button.Icon icon="uil:times" className="text-uiAccent" />
83
+ </Button.Btn>
84
+ </div>
85
+ </FilterCard>
86
+ );
87
+ };
88
+
89
+ export default Search;
@@ -0,0 +1,9 @@
1
+ diff a/components/modules/filter/search.js b/components/modules/filter/search.js (rejected hunks)
2
+ @@ -59,7 +59,6 @@
3
+ )}
4
+ <input
5
+ type="text"
6
+ - name="search"
7
+ onKeyDown={(e) => { if (e.key === 'Enter') {
8
+ e.preventDefault();
9
+ }}}
@@ -0,0 +1,83 @@
1
+ import { forwardRef } from "react";
2
+ import * as Select from "@radix-ui/react-select";
3
+ import { twMerge } from "tailwind-merge";
4
+ import Icon from "~/components/modules/icon";
5
+ import Button from "~/components/modules/buttons/default";
6
+ import React from 'react'
7
+
8
+ const FilterSort = ({ className, fields, setSortSetting, fieldNames }) => {
9
+ const handleSortChange = value => {
10
+ const [field, direction] = value.split("-");
11
+ setSortSetting({ field, type: direction });
12
+ };
13
+
14
+ return (
15
+ <div className={className ?? ""}>
16
+ <Select.Root onValueChange={handleSortChange}>
17
+ <Select.Trigger asChild aria-label="Sort">
18
+ <Button.Btn variant="outline" size="sm" className="normal-case">
19
+ <Button.Body className="justify-center">
20
+ <Select.Value placeholder={
21
+ <span className="flex items-center gap-2">
22
+ Sort
23
+ <Icon icon="mdi:sort" />
24
+ </span>
25
+ }/>
26
+ <Select.Icon>
27
+ <Button.Icon icon="mdi:chevron-down" />
28
+ </Select.Icon>
29
+ </Button.Body>
30
+ </Button.Btn>
31
+ </Select.Trigger>
32
+ <Select.Portal>
33
+ <Select.Content className={`relative z-[200] overflow-hidden bg-white rounded-md `}>
34
+ <Select.ScrollUpButton className="flex items-center justify-center h-5 bg-white text-primary cursor-default">
35
+ <Icon icon="mdi:chevron-up" />
36
+ </Select.ScrollUpButton>
37
+ <Select.Viewport className="p-1">
38
+ {fields.map(field => (
39
+ <Select.Group key={field}>
40
+ <SelectItem value={`${field}-asc`}>
41
+ <span className="flex items-center justify-between gap-2 w-full">
42
+ {fieldNames[field] ?? field}
43
+ <Icon icon="mdi:sort-ascending" />
44
+ </span>
45
+ </SelectItem>
46
+ <SelectItem value={`${field}-desc`}>
47
+ <span className="flex items-center justify-between gap-2 w-full">
48
+ {fieldNames[field] ?? field}
49
+ <Icon icon="mdi:sort-descending" />
50
+ </span>
51
+ </SelectItem>
52
+ </Select.Group>
53
+ ))}
54
+ </Select.Viewport>
55
+ <Select.ScrollDownButton className="flex items-center justify-center h-5 bg-white text-primary cursor-default">
56
+ <Icon icon="mdi:chevron-down" />
57
+ </Select.ScrollDownButton>
58
+ </Select.Content>
59
+ </Select.Portal>
60
+ </Select.Root>
61
+ </div>
62
+ );
63
+ };
64
+
65
+ const SelectItem = forwardRef(({ children, className, ...props }, forwardedRef) => (
66
+ <Select.Item
67
+ className={twMerge(
68
+ "text-sm leading-none rounded-sm flex items-center w-full py-1.5 pr-4 pl-7 relative select-none cursor-pointer data-[disabled]:text-gray-500 data-[disabled]:pointer-events-none data-[highlighted]:outline-none data-[highlighted]:bg-primary data-[highlighted]:text-white",
69
+ className
70
+ )}
71
+ {...props}
72
+ ref={forwardedRef}
73
+ >
74
+ <Select.ItemText>{children}</Select.ItemText>
75
+ <Select.ItemIndicator className="absolute left-0 w-8 inline-flex items-center justify-center">
76
+ <Icon icon="mdi:check" />
77
+ </Select.ItemIndicator>
78
+ </Select.Item>
79
+ ));
80
+
81
+ SelectItem.displayName = "SelectItem";
82
+
83
+ export default FilterSort;