From fa518041b6ac75dfae55d46e905fb71ed896d653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Prod=27homme?= Date: Mon, 28 Oct 2024 14:00:08 +0100 Subject: [PATCH] feat(client): Add boundaries basemap layers --- client/package.json | 3 +- client/src/components/map/constants.ts | 14 ++++++- .../components/panels/map-settings/index.tsx | 36 +++++++++++++++++- client/src/components/ui/button.tsx | 4 +- client/src/components/ui/checkbox.tsx | 28 ++++++++++++++ client/src/hooks/use-apply-map-settings.ts | 11 +++++- client/src/hooks/use-map-basemap-layers.ts | 12 ++++++ client/src/svgs/check.svg | 6 +++ client/yarn.lock | 37 ++++++++++++++++--- 9 files changed, 139 insertions(+), 12 deletions(-) create mode 100644 client/src/components/ui/checkbox.tsx create mode 100644 client/src/hooks/use-map-basemap-layers.ts create mode 100644 client/src/svgs/check.svg diff --git a/client/package.json b/client/package.json index 91cd124..ea940d4 100644 --- a/client/package.json +++ b/client/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@artsy/fresnel": "7.1.4", + "@radix-ui/react-checkbox": "1.1.2", "@radix-ui/react-dialog": "1.1.2", "@radix-ui/react-label": "2.1.0", "@radix-ui/react-popover": "1.1.2", @@ -29,7 +30,7 @@ "express": "4.21.1", "mapbox-gl": "3.7.0", "next": "14.2.15", - "nuqs": "2.0.3", + "nuqs": "2.0.4", "pino-http": "10.3.0", "react": "18.3.1", "react-dom": "18.3.1", diff --git a/client/src/components/map/constants.ts b/client/src/components/map/constants.ts index 1180cca..494f331 100644 --- a/client/src/components/map/constants.ts +++ b/client/src/components/map/constants.ts @@ -44,7 +44,17 @@ export const LABELS: Record = { }, }; -export const DEFAULT_MAP_SETTINGS = { +export const BASEMAP_LAYERS = { + "admin-boundaries": { group: "Boundaries", name: "Administrative boundaries" }, + "hydro-boundaries": { group: "Boundaries", name: "Hydrological basins" }, +} as const; + +export const DEFAULT_MAP_SETTINGS: { + basemap: BasemapStyle; + labels: LabelsStyle; + basemapLayers: readonly (keyof typeof BASEMAP_LAYERS)[]; +} = { basemap: BasemapStyle.Light, - labels: LabelsStyle.Light, + labels: LabelsStyle.Dark, + basemapLayers: ["admin-boundaries", "hydro-boundaries"], }; diff --git a/client/src/components/panels/map-settings/index.tsx b/client/src/components/panels/map-settings/index.tsx index 4cc4474..0899700 100644 --- a/client/src/components/panels/map-settings/index.tsx +++ b/client/src/components/panels/map-settings/index.tsx @@ -1,16 +1,19 @@ import Image from "next/image"; -import { BASEMAPS, LABELS } from "@/components/map/constants"; +import { BASEMAP_LAYERS, BASEMAPS, LABELS } from "@/components/map/constants"; import { BasemapStyle, LabelsStyle } from "@/components/map/types"; +import { Checkbox } from "@/components/ui/checkbox"; import { Label } from "@/components/ui/label"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import useMapBasemap from "@/hooks/use-map-basemap"; +import useMapBasemapLayers from "@/hooks/use-map-basemap-layers"; import useMapLabels from "@/hooks/use-map-labels"; import GlobeFilledIcon from "@/svgs/globe-filled.svg"; const MapSettingsPanel = () => { const [basemap, setBasemap] = useMapBasemap(); const [labels, setLabels] = useMapLabels(); + const [basemapLayers, setBasemapLayers] = useMapBasemapLayers(); return (
@@ -19,6 +22,7 @@ const MapSettingsPanel = () => { Map style + setBasemap(value as BasemapStyle)} @@ -45,6 +49,7 @@ const MapSettingsPanel = () => { ))} +
Labels { ))}
+ + {Array.from(new Set(Object.values(BASEMAP_LAYERS).map(({ group }) => group))).map((group) => ( +
+ {group} + {Object.entries(BASEMAP_LAYERS) + .filter(([, layer]) => layer.group === group) + .map(([key, layer]) => ( +
+ { + if (checked) { + setBasemapLayers((layers) => [...layers, key as keyof typeof BASEMAP_LAYERS]); + } else { + setBasemapLayers((layers) => + layers.filter((layer) => layer !== (key as keyof typeof BASEMAP_LAYERS)), + ); + } + }} + /> + +
+ ))} +
+ ))}
); }; diff --git a/client/src/components/ui/button.tsx b/client/src/components/ui/button.tsx index 696bfa6..fe2446f 100644 --- a/client/src/components/ui/button.tsx +++ b/client/src/components/ui/button.tsx @@ -10,11 +10,11 @@ const buttonVariants = cva( variants: { variant: { default: - "bg-neutral-900 text-neutral-50 hover:bg-neutral-900/90 ring-offset-white focus-visible:ring-neutral-950", + "bg-neutral-900 text-neutral-50 hover:bg-neutral-900/90 ring-offset-white focus-visible:ring-casper-blue-400", yellow: "bg-supernova-yellow-400 hover:bg-supernova-yellow-300 text-casper-blue-950 focus-visible:ring-casper-blue-400 data-[state=open]:bg-rhino-blue-900 data-[state=open]:hover:bg-rhino-blue-950 data-[state=open]:text-supernova-yellow-400", ghost: - "bg-neutral-900 text-neutral-50 hover:bg-neutral-900/90 ring-offset-white focus-visible:ring-neutral-950", + "bg-neutral-900 text-neutral-50 hover:bg-neutral-900/90 ring-offset-white focus-visible:ring-casper-blue-400", }, size: { default: "h-8 w-auto xl:h-10 px-4 xl:py-2", diff --git a/client/src/components/ui/checkbox.tsx b/client/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..0fea592 --- /dev/null +++ b/client/src/components/ui/checkbox.tsx @@ -0,0 +1,28 @@ +"use client"; + +import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; +import CheckIcon from "@/svgs/check.svg"; + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)); +Checkbox.displayName = CheckboxPrimitive.Root.displayName; + +export { Checkbox }; diff --git a/client/src/hooks/use-apply-map-settings.ts b/client/src/hooks/use-apply-map-settings.ts index 51f2951..e520011 100644 --- a/client/src/hooks/use-apply-map-settings.ts +++ b/client/src/hooks/use-apply-map-settings.ts @@ -1,6 +1,7 @@ import { useEffect } from "react"; import { MapRef } from "react-map-gl"; +import useMapBasemapLayers from "@/hooks/use-map-basemap-layers"; import { toggleGroupLayers } from "@/utils/map"; import useMapBasemap from "./use-map-basemap"; @@ -9,11 +10,19 @@ import useMapLabels from "./use-map-labels"; export default function useApplyMapSettings(map: MapRef | null) { const [basemap] = useMapBasemap(); const [labels] = useMapLabels(); + const [basemapLayers] = useMapBasemapLayers(); + + console.log(basemapLayers); useEffect(() => { if (map) { toggleGroupLayers(map, "basemap-", (group) => group === `basemap-${basemap}`); toggleGroupLayers(map, "labels-", (group) => group === `labels-${labels}`); + toggleGroupLayers( + map, + "layer-", + (group) => basemapLayers.findIndex((layer) => `layer-${layer}` === group) !== -1, + ); } - }, [map, basemap, labels]); + }, [map, basemap, labels, basemapLayers]); } diff --git a/client/src/hooks/use-map-basemap-layers.ts b/client/src/hooks/use-map-basemap-layers.ts new file mode 100644 index 0000000..12da575 --- /dev/null +++ b/client/src/hooks/use-map-basemap-layers.ts @@ -0,0 +1,12 @@ +import { parseAsArrayOf, parseAsStringLiteral, useQueryState } from "nuqs"; + +import { BASEMAP_LAYERS, DEFAULT_MAP_SETTINGS } from "@/components/map/constants"; + +export default function useMapBasemapLayers() { + return useQueryState( + "basemap-layers", + parseAsArrayOf(parseAsStringLiteral(DEFAULT_MAP_SETTINGS.basemapLayers)).withDefault( + DEFAULT_MAP_SETTINGS.basemapLayers as (keyof typeof BASEMAP_LAYERS)[], + ), + ); +} diff --git a/client/src/svgs/check.svg b/client/src/svgs/check.svg new file mode 100644 index 0000000..828a9d3 --- /dev/null +++ b/client/src/svgs/check.svg @@ -0,0 +1,6 @@ + + + + + diff --git a/client/yarn.lock b/client/yarn.lock index b2161de..ecc7d67 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -2055,6 +2055,32 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-checkbox@npm:1.1.2": + version: 1.1.2 + resolution: "@radix-ui/react-checkbox@npm:1.1.2" + dependencies: + "@radix-ui/primitive": "npm:1.1.0" + "@radix-ui/react-compose-refs": "npm:1.1.0" + "@radix-ui/react-context": "npm:1.1.1" + "@radix-ui/react-presence": "npm:1.1.1" + "@radix-ui/react-primitive": "npm:2.0.0" + "@radix-ui/react-use-controllable-state": "npm:1.1.0" + "@radix-ui/react-use-previous": "npm:1.1.0" + "@radix-ui/react-use-size": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/3b94434e0988100091eea7905fa939e808b49709be2ec371111829b75873f8820499d95f7e769fa31ba6adc48d6a58afd383d26f2a8a92edf0f88cb68c1de3ed + languageName: node + linkType: hard + "@radix-ui/react-collection@npm:1.1.0": version: 1.1.0 resolution: "@radix-ui/react-collection@npm:1.1.0" @@ -4212,6 +4238,7 @@ __metadata: resolution: "client@workspace:." dependencies: "@artsy/fresnel": "npm:7.1.4" + "@radix-ui/react-checkbox": "npm:1.1.2" "@radix-ui/react-dialog": "npm:1.1.2" "@radix-ui/react-label": "npm:2.1.0" "@radix-ui/react-popover": "npm:1.1.2" @@ -4243,7 +4270,7 @@ __metadata: jiti: "npm:1.21.6" mapbox-gl: "npm:3.7.0" next: "npm:14.2.15" - nuqs: "npm:2.0.3" + nuqs: "npm:2.0.4" orval: "npm:7.2.0" pino-http: "npm:10.3.0" pinst: "npm:3.0.0" @@ -7675,9 +7702,9 @@ __metadata: languageName: node linkType: hard -"nuqs@npm:2.0.3": - version: 2.0.3 - resolution: "nuqs@npm:2.0.3" +"nuqs@npm:2.0.4": + version: 2.0.4 + resolution: "nuqs@npm:2.0.4" dependencies: mitt: "npm:^3.0.1" peerDependencies: @@ -7692,7 +7719,7 @@ __metadata: optional: true react-router-dom: optional: true - checksum: 10c0/d37b95e10738d58351209518d19ba72a1e08733405b39dbcd64e9b28092ca627983506aa4a9f8504f51c2c729323c8fdfc3bf00cc8360e9b1e9bc85fcb44fba0 + checksum: 10c0/5df69ad5676a4e00ba595f226c114106930b908de59305d25df415cd6772a323fc9fbe517b6fde972cc8ca90e0c3f758105c4b96283ccb66d51b479c78e6bd38 languageName: node linkType: hard