diff --git a/client/components.json b/client/components.json index a94867e9..11f2a95c 100644 --- a/client/components.json +++ b/client/components.json @@ -11,6 +11,6 @@ }, "aliases": { "components": "@/components", - "utils": "@/lib/utils" + "utils": "@/lib/classnames" } } \ No newline at end of file diff --git a/client/e2e/example.spec.ts b/client/e2e/example.spec.ts index 02d1f1fd..28b37c50 100644 --- a/client/e2e/example.spec.ts +++ b/client/e2e/example.spec.ts @@ -4,7 +4,7 @@ test("has title", async ({ page }) => { await page.goto(`/`); // Expect a title "to contain" a substring. - await expect(page).toHaveTitle(/Create Next App/); + await expect(page).toHaveTitle(/CCSA/); }); // test("has title", async ({ page }) => { diff --git a/client/package.json b/client/package.json index 90ddd923..09c53828 100644 --- a/client/package.json +++ b/client/package.json @@ -13,6 +13,10 @@ "postinstall": "cd .. && husky install client/.husky" }, "dependencies": { + "@radix-ui/react-checkbox": "1.0.4", + "@radix-ui/react-label": "2.0.2", + "@radix-ui/react-popover": "1.0.7", + "@radix-ui/react-radio-group": "1.1.3", "@radix-ui/react-tooltip": "1.0.7", "@tanstack/react-query": "4.35.3", "@typescript-eslint/eslint-plugin": "6.7.3", @@ -29,6 +33,8 @@ "react-dom": "18.2.0", "react-icons": "4.11.0", "react-map-gl": "7.1.6", + "recoil": "0.7.7", + "recoil-sync": "0.2.0", "rooks": "7.14.1", "tailwind-merge": "1.14.0", "tailwindcss-animate": "1.0.7", diff --git a/client/public/images/map/light.jpeg b/client/public/images/map/light.jpeg new file mode 100644 index 00000000..83c09d8b Binary files /dev/null and b/client/public/images/map/light.jpeg differ diff --git a/client/public/images/map/satellite.jpeg b/client/public/images/map/satellite.jpeg new file mode 100644 index 00000000..1c22d6ad Binary files /dev/null and b/client/public/images/map/satellite.jpeg differ diff --git a/client/src/app/layout-providers.tsx b/client/src/app/layout-providers.tsx index 0de0afd2..5f19218c 100644 --- a/client/src/app/layout-providers.tsx +++ b/client/src/app/layout-providers.tsx @@ -1,21 +1,42 @@ "use client"; -import { PropsWithChildren, useState } from "react"; +import { PropsWithChildren, useCallback, useState } from "react"; import { MapProvider } from "react-map-gl"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { RecoilRoot } from "recoil"; + +import { RecoilURLSyncNext } from "@/lib/recoil"; +import type { Deserialize, Serialize } from "@/lib/recoil"; +// import RecoilDevTools from "@/lib/recoil/devtools"; import { TooltipProvider } from "@/components/ui/tooltip"; export default function LayoutProviders({ children }: PropsWithChildren) { const [queryClient] = useState(() => new QueryClient()); + const serialize: Serialize = useCallback((x) => { + return x === undefined ? "" : JSON.stringify(x); + }, []); + + //Demo of custom deserialization + const deserialize: Deserialize = useCallback((x: string) => { + return JSON.parse(x); + }, []); return ( - - {children} - + + + + {children} + + + ); } diff --git a/client/src/app/layout.tsx b/client/src/app/layout.tsx index b7fdf189..19c02259 100644 --- a/client/src/app/layout.tsx +++ b/client/src/app/layout.tsx @@ -10,7 +10,7 @@ import LayoutProviders from "@/app/layout-providers"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { - title: "Create Next App", + title: "CCSA", description: "Generated by create next app", }; diff --git a/client/src/components/map/constants.ts b/client/src/components/map/constants.ts index 736469c8..e1a21f23 100644 --- a/client/src/components/map/constants.ts +++ b/client/src/components/map/constants.ts @@ -5,3 +5,70 @@ export const DEFAULT_VIEW_STATE: Partial = { latitude: 0, longitude: 0, }; + +export const BASEMAPS = [ + { + label: "Light", + value: "basemap-light", + preview: `/images/map/light.jpeg`, + settings: { + labels: "labels-dark", + boundaries: "boundaries-dark", + roads: "roads-dark", + }, + }, + { + label: "Satellite", + value: "basemap-satellite", + preview: `/images/map/satellite.jpeg`, + settings: { + labels: "labels-light", + boundaries: "boundaries-light", + roads: "roads-light", + }, + }, +]; + +export const LABELS = [ + { + id: "1059d2b8cfa87b8d894b5373ea556666", + label: "Dark labels", + slug: "labels-dark", + }, + { + id: "5924e7eeda116f817dd89f1d8d418721", + label: "Light labels", + slug: "labels-light", + }, + { + id: "asdfasdfasdfasdf", + label: "No labels", + slug: "labels-none", + }, +]; + +export const BOUNDARIES = [ + { + id: "ae861f3122c21ad7754e66d3cead38e6", + label: "Dark boundaries", + slug: "boundaries-dark", + }, + { + id: "31b240eba06a254ade36f1dde6a3c07e", + label: "Light boundaries", + slug: "boundaries-light", + }, +]; + +export const ROADS = [ + { + id: "4e240a8b884456747dcd07d41b4d5543", + label: "Dark roads", + slug: "roads-dark", + }, + { + id: "edb80ef589e776ec6c2568b2fc6ad74c", + label: "Light roads", + slug: "roads-light", + }, +]; diff --git a/client/src/components/map/controls/settings/index.tsx b/client/src/components/map/controls/settings/index.tsx new file mode 100644 index 00000000..9cdd2b75 --- /dev/null +++ b/client/src/components/map/controls/settings/index.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { FC, HTMLAttributes, PropsWithChildren } from "react"; + +import { PopoverArrow } from "@radix-ui/react-popover"; +import { TooltipPortal } from "@radix-ui/react-tooltip"; +import { LuSettings } from "react-icons/lu"; + +import { cn } from "@/lib/classnames"; + +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { Tooltip, TooltipArrow, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; + +import { CONTROL_BUTTON_STYLES } from "../constants"; + +interface SettingsControlProps { + className?: HTMLAttributes["className"]; +} + +export const SettingsControl: FC> = ({ + className, + children, +}: PropsWithChildren) => { + return ( +
+ + + + + + + + + + +
Map settings
+ + +
+
+ + + {children} + + +
+
+
+ ); +}; + +export default SettingsControl; diff --git a/client/src/components/map/index.tsx b/client/src/components/map/index.tsx index 8045c150..e3966b8d 100644 --- a/client/src/components/map/index.tsx +++ b/client/src/components/map/index.tsx @@ -145,7 +145,7 @@ export const Map: FC = ({ mapboxAccessToken={env.NEXT_PUBLIC_MAPBOX_API_TOKEN} onMove={handleMapMove} onLoad={handleMapLoad} - mapStyle="mapbox://styles/marxan/ckn4fr7d71qg817kgd9vuom4s" + mapStyle="mapbox://styles/layer-manager/clj8fgofm000t01pjcu21agsd" {...mapboxProps} {...localViewState} > diff --git a/client/src/components/ui/checkbox.tsx b/client/src/components/ui/checkbox.tsx new file mode 100644 index 00000000..a02d279b --- /dev/null +++ b/client/src/components/ui/checkbox.tsx @@ -0,0 +1,29 @@ +"use client"; + +import * as React from "react"; + +import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; +import { LuCheck } from "react-icons/lu"; + +import { cn } from "@/lib/classnames"; + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)); +Checkbox.displayName = CheckboxPrimitive.Root.displayName; + +export { Checkbox }; diff --git a/client/src/components/ui/label.tsx b/client/src/components/ui/label.tsx new file mode 100644 index 00000000..467db53e --- /dev/null +++ b/client/src/components/ui/label.tsx @@ -0,0 +1,22 @@ +"use client"; + +import * as React from "react"; + +import * as LabelPrimitive from "@radix-ui/react-label"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/classnames"; + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", +); + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & VariantProps +>(({ className, ...props }, ref) => ( + +)); +Label.displayName = LabelPrimitive.Root.displayName; + +export { Label }; diff --git a/client/src/components/ui/popover.tsx b/client/src/components/ui/popover.tsx new file mode 100644 index 00000000..77dfca53 --- /dev/null +++ b/client/src/components/ui/popover.tsx @@ -0,0 +1,32 @@ +"use client"; + +import * as React from "react"; + +import * as PopoverPrimitive from "@radix-ui/react-popover"; + +import { cn } from "@/lib/classnames"; + +const Popover = PopoverPrimitive.Root; + +const PopoverTrigger = PopoverPrimitive.Trigger; + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)); +PopoverContent.displayName = PopoverPrimitive.Content.displayName; + +export { Popover, PopoverTrigger, PopoverContent }; diff --git a/client/src/components/ui/radio-group.tsx b/client/src/components/ui/radio-group.tsx new file mode 100644 index 00000000..6e673e31 --- /dev/null +++ b/client/src/components/ui/radio-group.tsx @@ -0,0 +1,39 @@ +"use client"; + +import * as React from "react"; + +import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"; +import { LuCircle } from "react-icons/lu"; + +import { cn } from "@/lib/classnames"; + +const RadioGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ; +}); +RadioGroup.displayName = RadioGroupPrimitive.Root.displayName; + +const RadioGroupItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + + + + + ); +}); +RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName; + +export { RadioGroup, RadioGroupItem }; diff --git a/client/src/containers/home/index.tsx b/client/src/containers/home/index.tsx index 2d05ae4f..670919ed 100644 --- a/client/src/containers/home/index.tsx +++ b/client/src/containers/home/index.tsx @@ -1,16 +1,24 @@ "use client"; +import MapSettingsManagerPanel from "@/containers/home/map-settings"; +import MapSettingsManager from "@/containers/home/map-settings/manager"; + import Map from "@/components/map"; import Controls from "@/components/map/controls"; +import SettingsControl from "@/components/map/controls/settings"; import ZoomControl from "@/components/map/controls/zoom"; const Home = (): JSX.Element => { return (
- + + + + +
); diff --git a/client/src/containers/home/map-settings/basemaps/index.tsx b/client/src/containers/home/map-settings/basemaps/index.tsx new file mode 100644 index 00000000..f5c5dc2d --- /dev/null +++ b/client/src/containers/home/map-settings/basemaps/index.tsx @@ -0,0 +1,17 @@ +import { BASEMAPS } from "@/components/map/constants"; + +import BasemapItem from "./item"; + +const Basemaps = () => { + return ( +
    + {BASEMAPS.map((b) => ( +
  • + +
  • + ))} +
+ ); +}; + +export default Basemaps; diff --git a/client/src/containers/home/map-settings/basemaps/item/index.tsx b/client/src/containers/home/map-settings/basemaps/item/index.tsx new file mode 100644 index 00000000..dde5293f --- /dev/null +++ b/client/src/containers/home/map-settings/basemaps/item/index.tsx @@ -0,0 +1,59 @@ +import { useCallback } from "react"; + +import Image from "next/image"; + +import { useRecoilValue, useSetRecoilState } from "recoil"; + +import { cn } from "@/lib/classnames"; + +import { mapSettingsAtom } from "@/store/map"; + +export interface BasemapItemProps { + label: string; + value: string; + preview: string; +} + +const BasemapItem = ({ label, value, preview }: BasemapItemProps) => { + const { basemap } = useRecoilValue(mapSettingsAtom); + const setMapSettings = useSetRecoilState(mapSettingsAtom); + + const handleToggleBasemap = useCallback(() => { + setMapSettings((prev) => ({ + ...prev, + basemap: value, + })); + }, [value, setMapSettings]); + + return ( +
+ +
+ ); +}; + +export default BasemapItem; diff --git a/client/src/containers/home/map-settings/boundaries/index.tsx b/client/src/containers/home/map-settings/boundaries/index.tsx new file mode 100644 index 00000000..755d0c42 --- /dev/null +++ b/client/src/containers/home/map-settings/boundaries/index.tsx @@ -0,0 +1,38 @@ +import { useCallback } from "react"; + +import { useRecoilValue, useSetRecoilState } from "recoil"; + +import { mapSettingsAtom } from "@/store/map"; + +import { Checkbox } from "@/components/ui/checkbox"; +import { Label } from "@/components/ui/label"; + +const Boundaries = () => { + const { boundaries } = useRecoilValue(mapSettingsAtom); + const setMapSettings = useSetRecoilState(mapSettingsAtom); + + const handleChange = useCallback( + (v: boolean) => { + setMapSettings((prev) => ({ + ...prev, + boundaries: v, + })); + }, + [setMapSettings], + ); + + return ( +
+ + + +
+ ); +}; + +export default Boundaries; diff --git a/client/src/containers/home/map-settings/index.tsx b/client/src/containers/home/map-settings/index.tsx new file mode 100644 index 00000000..40f235ff --- /dev/null +++ b/client/src/containers/home/map-settings/index.tsx @@ -0,0 +1,29 @@ +import Basemaps from "./basemaps"; +import Boundaries from "./boundaries"; +import Labels from "./labels"; +import Roads from "./roads"; + +const MapSettings = () => { + return ( +
+

Map settings

+ +
+ + +
+
+ +
+ +
+ + +
+
+
+
+ ); +}; + +export default MapSettings; diff --git a/client/src/containers/home/map-settings/labels/index.tsx b/client/src/containers/home/map-settings/labels/index.tsx new file mode 100644 index 00000000..1b83eb6a --- /dev/null +++ b/client/src/containers/home/map-settings/labels/index.tsx @@ -0,0 +1,42 @@ +import { useCallback } from "react"; + +import { useRecoilValue, useSetRecoilState } from "recoil"; + +import { mapSettingsAtom } from "@/store/map"; + +import { LABELS } from "@/components/map/constants"; +import { Label } from "@/components/ui/label"; +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; + +const Labels = () => { + const { labels } = useRecoilValue(mapSettingsAtom); + const setMapSettings = useSetRecoilState(mapSettingsAtom); + + const handleChange = useCallback( + (v: string) => { + setMapSettings((prev) => ({ + ...prev, + labels: v, + })); + }, + [setMapSettings], + ); + + return ( + + {LABELS.map((l) => ( +
+ + +
+ ))} +
+ ); +}; + +export default Labels; diff --git a/client/src/containers/home/map-settings/manager/index.tsx b/client/src/containers/home/map-settings/manager/index.tsx new file mode 100644 index 00000000..a6a4baf2 --- /dev/null +++ b/client/src/containers/home/map-settings/manager/index.tsx @@ -0,0 +1,103 @@ +import { useCallback, useEffect } from "react"; + +import { useMap } from "react-map-gl"; + +import { AnyLayer } from "mapbox-gl"; +import { useRecoilValue } from "recoil"; + +import { mapSettingsAtom } from "@/store/map"; + +import { BASEMAPS } from "@/components/map/constants"; + +type AnyLayerWithMetadata = AnyLayer & { + metadata: Record; +}; + +const MapSettingsManager = () => { + const { default: mapRef } = useMap(); + const loaded = mapRef?.loaded(); + const { basemap, labels, boundaries, roads } = useRecoilValue(mapSettingsAtom); + + const handleGroup = useCallback( + (groups: string[], groupId: string, visible = true) => { + if (!mapRef) return; + const map = mapRef.getMap(); + const { layers, metadata = {} } = mapRef.getStyle(); + + const lys = layers as AnyLayerWithMetadata[]; + + const GROUPS = Object.keys(metadata["mapbox:groups"] || {}).filter((k) => { + const { name } = metadata["mapbox:groups"][k]; + + const matchedGroups = groups.map((rgr) => name.toLowerCase().includes(rgr)); + + return matchedGroups.some((bool) => bool); + }); + + const GROUPS_META = GROUPS.map((gId) => ({ + ...metadata["mapbox:groups"][gId], + id: gId, + })); + const GROUP_TO_DISPLAY = GROUPS_META.find((_group) => _group.name.includes(groupId)) || {}; + + const GROUPS_LAYERS = lys.filter((l) => { + const { metadata: layerMetadata } = l; + if (!layerMetadata) return false; + + const gr = layerMetadata["mapbox:group"]; + return GROUPS.includes(gr); + }); + + GROUPS_LAYERS.forEach((_layer) => { + const match = _layer.metadata["mapbox:group"] === GROUP_TO_DISPLAY.id && visible; + if (!match) { + map.setLayoutProperty(_layer.id, "visibility", "none"); + } else { + map.setLayoutProperty(_layer.id, "visibility", "visible"); + } + }); + }, + [mapRef], + ); + + const handleStyleLoad = useCallback(() => { + const B = BASEMAPS.find((b) => b.value === basemap); + + handleGroup(["basemap"], basemap); + handleGroup(["labels"], labels); + + if (B) { + handleGroup(["boundaries"], B.settings.boundaries, boundaries); + handleGroup(["roads"], B.settings.roads, roads); + } + }, [basemap, labels, boundaries, roads, handleGroup]); + + // * handle style load + useEffect(() => { + if (!mapRef) return; + mapRef.on("style.load", handleStyleLoad); + + return () => { + mapRef.off("style.load", handleStyleLoad); + }; + }, [mapRef, loaded, handleStyleLoad]); + + // * handle basemap, labels, boundaries, roads + useEffect(() => { + if (!mapRef) return; + + const B = BASEMAPS.find((b) => b.value === basemap); + + handleGroup(["basemap"], basemap); + handleGroup(["labels"], labels); + + if (B) { + handleGroup(["boundaries"], B.settings.boundaries, boundaries); + handleGroup(["roads"], B.settings.roads, roads); + } + }, [mapRef, loaded, basemap, labels, boundaries, roads, handleGroup]); + + return null; +}; + +export default MapSettingsManager; diff --git a/client/src/containers/home/map-settings/roads/index.tsx b/client/src/containers/home/map-settings/roads/index.tsx new file mode 100644 index 00000000..ad801f8d --- /dev/null +++ b/client/src/containers/home/map-settings/roads/index.tsx @@ -0,0 +1,38 @@ +import { useCallback } from "react"; + +import { useRecoilValue, useSetRecoilState } from "recoil"; + +import { mapSettingsAtom } from "@/store/map"; + +import { Checkbox } from "@/components/ui/checkbox"; +import { Label } from "@/components/ui/label"; + +const Roads = () => { + const { roads } = useRecoilValue(mapSettingsAtom); + const setMapSettings = useSetRecoilState(mapSettingsAtom); + + const handleChange = useCallback( + (v: boolean) => { + setMapSettings((prev) => ({ + ...prev, + roads: v, + })); + }, + [setMapSettings], + ); + + return ( +
+ + + +
+ ); +}; + +export default Roads; diff --git a/client/src/lib/recoil/devtools.tsx b/client/src/lib/recoil/devtools.tsx new file mode 100644 index 00000000..b92edd32 --- /dev/null +++ b/client/src/lib/recoil/devtools.tsx @@ -0,0 +1,18 @@ +import { useEffect } from "react"; + +import { useRecoilSnapshot } from "recoil"; + +function RecoilDevTools() { + const snapshot = useRecoilSnapshot(); + + useEffect(() => { + console.debug("The following atoms were modified:"); + for (const node of snapshot.getNodes_UNSTABLE({ isModified: true })) { + console.debug(node.key, snapshot.getLoadable(node)); + } + }, [snapshot]); + + return null; +} + +export default RecoilDevTools; diff --git a/client/src/lib/recoil/index.tsx b/client/src/lib/recoil/index.tsx new file mode 100644 index 00000000..3c37598d --- /dev/null +++ b/client/src/lib/recoil/index.tsx @@ -0,0 +1,31 @@ +import { RecoilURLSync, RecoilURLSyncOptions } from "recoil-sync"; + +import { useSyncURLNext } from "./useSyncURLNext"; + +type Props = Omit & { + decodedQueryParams?: boolean; +}; + +export type Serialize = (data: unknown) => string; + +export type Deserialize = (str: string) => unknown; + +export const RecoilURLSyncNext: React.FC = ({ children, ...options }) => { + const { decodedQueryParams = true } = options; + + const { browserInterface, ...defaultOptions } = useSyncURLNext({ + decodedQueryParams, + }); + + return ( + + {children} + + ); +}; diff --git a/client/src/lib/recoil/useSyncURLNext.ts b/client/src/lib/recoil/useSyncURLNext.ts new file mode 100644 index 00000000..7af40822 --- /dev/null +++ b/client/src/lib/recoil/useSyncURLNext.ts @@ -0,0 +1,58 @@ +"use client"; + +import { useCallback } from "react"; + +import { usePathname, useRouter, useSearchParams } from "next/navigation"; + +import { BrowserInterface, RecoilURLSyncOptions } from "recoil-sync"; + +type UseSyncURLNextOptions = { + decodedQueryParams?: boolean; +}; + +export function useSyncURLNext( + options: UseSyncURLNextOptions, +): Partial> { + const { decodedQueryParams } = options; + + const pathname = usePathname(); + const searchParams = useSearchParams(); + const { push } = useRouter(); + + const browserInterface: BrowserInterface = { + replaceURL: useCallback( + (url: string) => { + const u = decodedQueryParams ? decodeURIComponent(url) : url; + return window.history.replaceState({}, "", u); + }, + [decodedQueryParams], + ), + + pushURL: useCallback( + (url: string) => { + const u = decodedQueryParams ? decodeURIComponent(url) : url; + return push(u); + }, + [decodedQueryParams, push], + ), + + getURL: useCallback(() => { + const url = new URL( + `${pathname}${searchParams ? `?${searchParams.toString()}` : ""}`, + globalThis?.document?.location?.href ?? `http://localhost:${process.env.PORT}`, + ); + + return url.toString(); + }, [pathname, searchParams]), + + listenChangeURL: useCallback((handler2: () => void) => { + return () => { + handler2(); + }; + }, []), + }; + + return { + browserInterface, + }; +} diff --git a/client/src/store/map.ts b/client/src/store/map.ts new file mode 100644 index 00000000..610ffb06 --- /dev/null +++ b/client/src/store/map.ts @@ -0,0 +1,25 @@ +"use client"; + +import { bool, object, string } from "@recoiljs/refine"; +import { atom } from "recoil"; +import { urlSyncEffect } from "recoil-sync"; + +export const mapSettingsAtom = atom({ + key: "map-settings", + default: { + basemap: "basemap-light", + labels: "labels-dark", + boundaries: false, + roads: false, + }, + effects: [ + urlSyncEffect({ + refine: object({ + basemap: string(), + labels: string(), + boundaries: bool(), + roads: bool(), + }), + }), + ], +}); diff --git a/client/yarn.lock b/client/yarn.lock index 4c1e98ac..ee4f82f8 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -446,6 +446,56 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-checkbox@npm:1.0.4": + version: 1.0.4 + resolution: "@radix-ui/react-checkbox@npm:1.0.4" + dependencies: + "@babel/runtime": ^7.13.10 + "@radix-ui/primitive": 1.0.1 + "@radix-ui/react-compose-refs": 1.0.1 + "@radix-ui/react-context": 1.0.1 + "@radix-ui/react-presence": 1.0.1 + "@radix-ui/react-primitive": 1.0.3 + "@radix-ui/react-use-controllable-state": 1.0.1 + "@radix-ui/react-use-previous": 1.0.1 + "@radix-ui/react-use-size": 1.0.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 6dac5bddd9e1c42b149555501440918d9eae70da13b6d8539c3bf46b6c07681119d865d2106a43f729884ae8e2043bedc34c4d00a09a527b3bf0feade088d188 + languageName: node + linkType: hard + +"@radix-ui/react-collection@npm:1.0.3": + version: 1.0.3 + resolution: "@radix-ui/react-collection@npm:1.0.3" + dependencies: + "@babel/runtime": ^7.13.10 + "@radix-ui/react-compose-refs": 1.0.1 + "@radix-ui/react-context": 1.0.1 + "@radix-ui/react-primitive": 1.0.3 + "@radix-ui/react-slot": 1.0.2 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: acfbc9b0b2c553d343c22f02c9f098bc5cfa99e6e48df91c0d671855013f8b877ade9c657b7420a7aa523b5aceadea32a60dd72c23b1291f415684fb45d00cff + languageName: node + linkType: hard + "@radix-ui/react-compose-refs@npm:1.0.1": version: 1.0.1 resolution: "@radix-ui/react-compose-refs@npm:1.0.1" @@ -476,6 +526,21 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-direction@npm:1.0.1": + version: 1.0.1 + resolution: "@radix-ui/react-direction@npm:1.0.1" + dependencies: + "@babel/runtime": ^7.13.10 + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 5336a8b0d4f1cde585d5c2b4448af7b3d948bb63a1aadb37c77771b0e5902dc6266e409cf35fd0edaca7f33e26424be19e64fb8f9d7f7be2d6f1714ea2764210 + languageName: node + linkType: hard + "@radix-ui/react-dismissable-layer@npm:1.0.5": version: 1.0.5 resolution: "@radix-ui/react-dismissable-layer@npm:1.0.5" @@ -500,6 +565,43 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-focus-guards@npm:1.0.1": + version: 1.0.1 + resolution: "@radix-ui/react-focus-guards@npm:1.0.1" + dependencies: + "@babel/runtime": ^7.13.10 + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 1f8ca8f83b884b3612788d0742f3f054e327856d90a39841a47897dbed95e114ee512362ae314177de226d05310047cabbf66b686ae86ad1b65b6b295be24ef7 + languageName: node + linkType: hard + +"@radix-ui/react-focus-scope@npm:1.0.4": + version: 1.0.4 + resolution: "@radix-ui/react-focus-scope@npm:1.0.4" + dependencies: + "@babel/runtime": ^7.13.10 + "@radix-ui/react-compose-refs": 1.0.1 + "@radix-ui/react-primitive": 1.0.3 + "@radix-ui/react-use-callback-ref": 1.0.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 3481db1a641513a572734f0bcb0e47fefeba7bccd6ec8dde19f520719c783ef0b05a55ef0d5292078ed051cc5eda46b698d5d768da02e26e836022f46b376fd1 + languageName: node + linkType: hard + "@radix-ui/react-id@npm:1.0.1": version: 1.0.1 resolution: "@radix-ui/react-id@npm:1.0.1" @@ -516,6 +618,60 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-label@npm:2.0.2": + version: 2.0.2 + resolution: "@radix-ui/react-label@npm:2.0.2" + dependencies: + "@babel/runtime": ^7.13.10 + "@radix-ui/react-primitive": 1.0.3 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: fe3bd8902bc523fb5125fa96167a13b8a60d007413787eae9573e4b00b0edff0487c4c0620ea5dc37e6da13833ebc4f8d7e00b6c846f2a5686e7f173672b8dde + languageName: node + linkType: hard + +"@radix-ui/react-popover@npm:1.0.7": + version: 1.0.7 + resolution: "@radix-ui/react-popover@npm:1.0.7" + dependencies: + "@babel/runtime": ^7.13.10 + "@radix-ui/primitive": 1.0.1 + "@radix-ui/react-compose-refs": 1.0.1 + "@radix-ui/react-context": 1.0.1 + "@radix-ui/react-dismissable-layer": 1.0.5 + "@radix-ui/react-focus-guards": 1.0.1 + "@radix-ui/react-focus-scope": 1.0.4 + "@radix-ui/react-id": 1.0.1 + "@radix-ui/react-popper": 1.1.3 + "@radix-ui/react-portal": 1.0.4 + "@radix-ui/react-presence": 1.0.1 + "@radix-ui/react-primitive": 1.0.3 + "@radix-ui/react-slot": 1.0.2 + "@radix-ui/react-use-controllable-state": 1.0.1 + aria-hidden: ^1.1.1 + react-remove-scroll: 2.5.5 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 3ec15c0923ea457f586aa34f77e17fabffa02dffeab622951560ec21c38df2f43718ff088d24bf9fd1d9cd0db62436fc19cae5b122d90f72de4945a1f508dc59 + languageName: node + linkType: hard + "@radix-ui/react-popper@npm:1.1.3": version: 1.1.3 resolution: "@radix-ui/react-popper@npm:1.1.3" @@ -606,6 +762,63 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-radio-group@npm:1.1.3": + version: 1.1.3 + resolution: "@radix-ui/react-radio-group@npm:1.1.3" + dependencies: + "@babel/runtime": ^7.13.10 + "@radix-ui/primitive": 1.0.1 + "@radix-ui/react-compose-refs": 1.0.1 + "@radix-ui/react-context": 1.0.1 + "@radix-ui/react-direction": 1.0.1 + "@radix-ui/react-presence": 1.0.1 + "@radix-ui/react-primitive": 1.0.3 + "@radix-ui/react-roving-focus": 1.0.4 + "@radix-ui/react-use-controllable-state": 1.0.1 + "@radix-ui/react-use-previous": 1.0.1 + "@radix-ui/react-use-size": 1.0.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 88f7007610817ab30f471a7e1f6605e94cc507a31fb4bb218116d65cc48c9b3149fce500f386716a3ed5fb0089d65faf32d3e01971322cd4a14b51003ec82bc2 + languageName: node + linkType: hard + +"@radix-ui/react-roving-focus@npm:1.0.4": + version: 1.0.4 + resolution: "@radix-ui/react-roving-focus@npm:1.0.4" + dependencies: + "@babel/runtime": ^7.13.10 + "@radix-ui/primitive": 1.0.1 + "@radix-ui/react-collection": 1.0.3 + "@radix-ui/react-compose-refs": 1.0.1 + "@radix-ui/react-context": 1.0.1 + "@radix-ui/react-direction": 1.0.1 + "@radix-ui/react-id": 1.0.1 + "@radix-ui/react-primitive": 1.0.3 + "@radix-ui/react-use-callback-ref": 1.0.1 + "@radix-ui/react-use-controllable-state": 1.0.1 + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 69b1c82c2d9db3ba71549a848f2704200dab1b2cd22d050c1e081a78b9a567dbfdc7fd0403ee010c19b79652de69924d8ca2076cd031d6552901e4213493ffc7 + languageName: node + linkType: hard + "@radix-ui/react-slot@npm:1.0.2": version: 1.0.2 resolution: "@radix-ui/react-slot@npm:1.0.2" @@ -715,6 +928,21 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-use-previous@npm:1.0.1": + version: 1.0.1 + resolution: "@radix-ui/react-use-previous@npm:1.0.1" + dependencies: + "@babel/runtime": ^7.13.10 + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 66b4312e857c58b75f3bf62a2048ef090b79a159e9da06c19a468c93e62336969c33dbef60ff16969f00b20386cc25d138f6a353f1658b35baac0a6eff4761b9 + languageName: node + linkType: hard + "@radix-ui/react-use-rect@npm:1.0.1": version: 1.0.1 resolution: "@radix-ui/react-use-rect@npm:1.0.1" @@ -776,6 +1004,13 @@ __metadata: languageName: node linkType: hard +"@recoiljs/refine@npm:^0.1.1": + version: 0.1.1 + resolution: "@recoiljs/refine@npm:0.1.1" + checksum: 63b3e468f4ce7417cd13470f39a85a7318b902d411121759fc0a7ee427cd8882328a2c999ef0913e086f0c99f732e5478378b4ebe514bd811bb6e96333c747c3 + languageName: node + linkType: hard + "@rushstack/eslint-patch@npm:^1.3.3": version: 1.5.0 resolution: "@rushstack/eslint-patch@npm:1.5.0" @@ -1335,6 +1570,15 @@ __metadata: languageName: node linkType: hard +"aria-hidden@npm:^1.1.1": + version: 1.2.3 + resolution: "aria-hidden@npm:1.2.3" + dependencies: + tslib: ^2.0.0 + checksum: 7d7d211629eef315e94ed3b064c6823d13617e609d3f9afab1c2ed86399bb8e90405f9bdd358a85506802766f3ecb468af985c67c846045a34b973bcc0289db9 + languageName: node + linkType: hard + "aria-query@npm:^5.1.3": version: 5.3.0 resolution: "aria-query@npm:5.3.0" @@ -1733,6 +1977,10 @@ __metadata: resolution: "ccsa-client@workspace:." dependencies: "@playwright/test": 1.38.1 + "@radix-ui/react-checkbox": 1.0.4 + "@radix-ui/react-label": 2.0.2 + "@radix-ui/react-popover": 1.0.7 + "@radix-ui/react-radio-group": 1.1.3 "@radix-ui/react-tooltip": 1.0.7 "@t3-oss/env-nextjs": 0.6.1 "@tanstack/eslint-plugin-query": 4.34.1 @@ -1765,6 +2013,8 @@ __metadata: react-dom: 18.2.0 react-icons: 4.11.0 react-map-gl: 7.1.6 + recoil: 0.7.7 + recoil-sync: 0.2.0 rooks: 7.14.1 tailwind-merge: 1.14.0 tailwindcss: 3.3.3 @@ -2087,6 +2337,13 @@ __metadata: languageName: node linkType: hard +"detect-node-es@npm:^1.1.0": + version: 1.1.0 + resolution: "detect-node-es@npm:1.1.0" + checksum: e46307d7264644975b71c104b9f028ed1d3d34b83a15b8a22373640ce5ea630e5640b1078b8ea15f202b54641da71e4aa7597093bd4b91f113db520a26a37449 + languageName: node + linkType: hard + "didyoumean@npm:^1.2.2": version: 1.2.2 resolution: "didyoumean@npm:1.2.2" @@ -3052,6 +3309,13 @@ __metadata: languageName: node linkType: hard +"get-nonce@npm:^1.0.0": + version: 1.0.1 + resolution: "get-nonce@npm:1.0.1" + checksum: e2614e43b4694c78277bb61b0f04583d45786881289285c73770b07ded246a98be7e1f78b940c80cbe6f2b07f55f0b724e6db6fd6f1bcbd1e8bdac16521074ed + languageName: node + linkType: hard + "get-stream@npm:^6.0.0, get-stream@npm:^6.0.1": version: 6.0.1 resolution: "get-stream@npm:6.0.1" @@ -3249,6 +3513,13 @@ __metadata: languageName: node linkType: hard +"hamt_plus@npm:1.0.2": + version: 1.0.2 + resolution: "hamt_plus@npm:1.0.2" + checksum: af26ea32db03009019cc83dfa9411521a2fa16079443de1a502c9be46d8b3c975acda8ed93fc5750ef08d3186d35901e2d8cfe717dd54bea67b358601fa74e4c + languageName: node + linkType: hard + "has-bigints@npm:^1.0.1, has-bigints@npm:^1.0.2": version: 1.0.2 resolution: "has-bigints@npm:1.0.2" @@ -3478,6 +3749,15 @@ __metadata: languageName: node linkType: hard +"invariant@npm:^2.2.4": + version: 2.2.4 + resolution: "invariant@npm:2.2.4" + dependencies: + loose-envify: ^1.0.0 + checksum: cc3182d793aad82a8d1f0af697b462939cb46066ec48bbf1707c150ad5fad6406137e91a262022c269702e01621f35ef60269f6c0d7fd178487959809acdfb14 + languageName: node + linkType: hard + "ip@npm:^2.0.0": version: 2.0.0 resolution: "ip@npm:2.0.0" @@ -4017,7 +4297,7 @@ __metadata: languageName: node linkType: hard -"loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": +"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" dependencies: @@ -5279,6 +5559,58 @@ __metadata: languageName: node linkType: hard +"react-remove-scroll-bar@npm:^2.3.3": + version: 2.3.4 + resolution: "react-remove-scroll-bar@npm:2.3.4" + dependencies: + react-style-singleton: ^2.2.1 + tslib: ^2.0.0 + peerDependencies: + "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: b5ce5f2f98d65c97a3e975823ae4043a4ba2a3b63b5ba284b887e7853f051b5cd6afb74abde6d57b421931c52f2e1fdbb625dc858b1cb5a32c27c14ab85649d4 + languageName: node + linkType: hard + +"react-remove-scroll@npm:2.5.5": + version: 2.5.5 + resolution: "react-remove-scroll@npm:2.5.5" + dependencies: + react-remove-scroll-bar: ^2.3.3 + react-style-singleton: ^2.2.1 + tslib: ^2.1.0 + use-callback-ref: ^1.3.0 + use-sidecar: ^1.1.2 + peerDependencies: + "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 2c7fe9cbd766f5e54beb4bec2e2efb2de3583037b23fef8fa511ab426ed7f1ae992382db5acd8ab5bfb030a4b93a06a2ebca41377d6eeaf0e6791bb0a59616a4 + languageName: node + linkType: hard + +"react-style-singleton@npm:^2.2.1": + version: 2.2.1 + resolution: "react-style-singleton@npm:2.2.1" + dependencies: + get-nonce: ^1.0.0 + invariant: ^2.2.4 + tslib: ^2.0.0 + peerDependencies: + "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 7ee8ef3aab74c7ae1d70ff34a27643d11ba1a8d62d072c767827d9ff9a520905223e567002e0bf6c772929d8ea1c781a3ba0cc4a563e92b1e3dc2eaa817ecbe8 + languageName: node + linkType: hard + "react@npm:18.2.0": version: 18.2.0 resolution: "react@npm:18.2.0" @@ -5337,6 +5669,34 @@ __metadata: languageName: node linkType: hard +"recoil-sync@npm:0.2.0": + version: 0.2.0 + resolution: "recoil-sync@npm:0.2.0" + dependencies: + "@recoiljs/refine": ^0.1.1 + transit-js: ^0.8.874 + peerDependencies: + recoil: ">=0.7.3" + checksum: a0bd98acbc92ae58099a283056cb0c3cfcc08312bbe8bccd2662ecb3410b856fe3db33df765c7f5e9f030f73c44630e2c0f39266123eb1fcb7285d0f4b53c52a + languageName: node + linkType: hard + +"recoil@npm:0.7.7": + version: 0.7.7 + resolution: "recoil@npm:0.7.7" + dependencies: + hamt_plus: 1.0.2 + peerDependencies: + react: ">=16.13.1" + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + checksum: 65edecbcb8d2cde89bfd61ec679c200483472a6cd343c33e4e9142b6ce524fb17d1fecc2bfd8c392926aaa8178c81457f165b32abce2a9662f51f98822c0a9cc + languageName: node + linkType: hard + "reflect.getprototypeof@npm:^1.0.4": version: 1.0.4 resolution: "reflect.getprototypeof@npm:1.0.4" @@ -6148,6 +6508,13 @@ __metadata: languageName: node linkType: hard +"transit-js@npm:^0.8.874": + version: 0.8.874 + resolution: "transit-js@npm:0.8.874" + checksum: a1d3a78a0ce926320ba32cdd59a74104e1b440855497753b91f8e71831302b23adfb21416cdba30153305cf41cf96b75421a607a1799b676572fe6072ee04798 + languageName: node + linkType: hard + "ts-api-utils@npm:^1.0.1": version: 1.0.3 resolution: "ts-api-utils@npm:1.0.3" @@ -6176,7 +6543,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.0": +"tslib@npm:^2.0.0, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.0": version: 2.6.2 resolution: "tslib@npm:2.6.2" checksum: 329ea56123005922f39642318e3d1f0f8265d1e7fcb92c633e0809521da75eeaca28d2cf96d7248229deb40e5c19adf408259f4b9640afd20d13aecc1430f3ad @@ -6371,6 +6738,37 @@ __metadata: languageName: node linkType: hard +"use-callback-ref@npm:^1.3.0": + version: 1.3.0 + resolution: "use-callback-ref@npm:1.3.0" + dependencies: + tslib: ^2.0.0 + peerDependencies: + "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 7913df383a5a6fcb399212eedefaac2e0c6f843555202d4e3010bac3848afe38ecaa3d0d6500ad1d936fbeffd637e6c517e68edb024af5e6beca7f27f3ce7b21 + languageName: node + linkType: hard + +"use-sidecar@npm:^1.1.2": + version: 1.1.2 + resolution: "use-sidecar@npm:1.1.2" + dependencies: + detect-node-es: ^1.1.0 + tslib: ^2.0.0 + peerDependencies: + "@types/react": ^16.9.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 925d1922f9853e516eaad526b6fed1be38008073067274f0ecc3f56b17bb8ab63480140dd7c271f94150027c996cea4efe83d3e3525e8f3eda22055f6a39220b + languageName: node + linkType: hard + "use-sync-external-store@npm:^1.2.0": version: 1.2.0 resolution: "use-sync-external-store@npm:1.2.0"