Skip to content

Commit

Permalink
map setup
Browse files Browse the repository at this point in the history
  • Loading branch information
andresgnlez committed Oct 24, 2024
1 parent b10edd5 commit bb8dcb5
Show file tree
Hide file tree
Showing 13 changed files with 1,251 additions and 10 deletions.
4 changes: 4 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@
"@ts-rest/react-query": "3.51.0",
"class-variance-authority": "0.7.0",
"clsx": "2.1.1",
"d3": "7.9.0",
"framer-motion": "11.11.9",
"jotai": "2.10.1",
"lucide-react": "0.447.0",
"mapbox-expression": "0.0.3",
"mapbox-gl": "3.7.0",
"next": "14.2.10",
"next-auth": "4.24.8",
Expand All @@ -42,6 +44,8 @@
"zod": "catalog:"
},
"devDependencies": {
"@types/d3": "7.4.3",
"@types/geojson": "7946.0.14",
"@types/mapbox-gl": "3.4.0",
"@types/node": "catalog:",
"@types/react": "^18",
Expand Down
2 changes: 1 addition & 1 deletion client/src/app/(projects)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default function Projects() {
</motion.aside>
<div className="flex flex-1 flex-col">
<ProjectsHeader />
<div className="grid flex-grow grid-rows-2">
<div className="grid flex-grow grid-rows-2 gap-3">
<section className="flex-1">
<ProjectsMap />
</section>
Expand Down
6 changes: 6 additions & 0 deletions client/src/app/(projects)/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ export const projectsUIState = atom<{
tableExpanded: "default",
});

export const projectsMapState = atom<{
legendOpen: boolean;
}>({
legendOpen: true,
});

export const projectsFiltersState = atom<{
keyword: string | undefined;
projectSize: (typeof PROJECT_PARAMETERS)[0]["options"][number]["value"];
Expand Down
20 changes: 20 additions & 0 deletions client/src/containers/projects/map/controls/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { PropsWithChildren } from "react";

import { cn } from "@/lib/utils";

type ControlsProps = PropsWithChildren<{
className?: HTMLDivElement["className"];
}>;

export default function Controls({ className, children }: ControlsProps) {
return (
<div
className={cn(
"absolute right-4 top-4 flex flex-col items-center justify-center space-y-2",
className,
)}
>
{children}
</div>
);
}
33 changes: 33 additions & 0 deletions client/src/containers/projects/map/controls/legend/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useSetAtom } from "jotai";
import { Layers } from "lucide-react";

import { cn } from "@/lib/utils";

import { projectsMapState } from "@/app/(projects)/store";

const BUTTON_CLASSES = {
default:
"flex h-8 w-8 items-center justify-center rounded-full border border-white bg-white text-black shadow-md transition-colors",
hover: "hover:border-gray-400 active:border-gray-400",
};

export default function LegendControl() {
const setProjectsMapState = useSetAtom(projectsMapState);
const handleMapLegend = () => {
setProjectsMapState((prev) => ({
...prev,
legendOpen: !prev.legendOpen,
}));
};

return (
<button
className={cn(BUTTON_CLASSES.default, BUTTON_CLASSES.hover)}
aria-label="Toggle legend"
type="button"
onClick={handleMapLegend}
>
<Layers className="h-4 w-4" />
</button>
);
}
82 changes: 82 additions & 0 deletions client/src/containers/projects/map/controls/zoom/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { useCallback, MouseEvent } from "react";

import { useMap, MapRef } from "react-map-gl";

import { Plus, Minus } from "lucide-react";

import { cn } from "@/lib/utils";

const BUTTON_CLASSES = {
default:
"flex h-8 w-8 items-center justify-center rounded-full border border-white bg-white text-black shadow-md transition-colors",
hover: "hover:border-gray-400 active:border-gray-400",
disabled: "opacity-50 cursor-default",
};

export default function ZoomControl({
id = "default",
className,
}: {
id?: string;
className?: HTMLDivElement["className"];
}) {
const { [id]: mapRef } = useMap();

const zoom = mapRef?.getZoom() as NonNullable<ReturnType<MapRef["getZoom"]>>;
const minZoom = mapRef?.getMinZoom() as NonNullable<
ReturnType<MapRef["getMinZoom"]>
>;
const maxZoom = mapRef?.getMaxZoom() as NonNullable<
ReturnType<MapRef["getMaxZoom"]>
>;

const increaseZoom = useCallback(
(e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
if (!mapRef) return null;

mapRef.zoomIn();
},
[mapRef],
);

const decreaseZoom = useCallback(
(e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
if (!mapRef) return null;

mapRef.zoomOut();
},
[mapRef],
);

return (
<div className={cn("inline-flex flex-col space-y-2", className)}>
<button
className={cn(BUTTON_CLASSES.default, {
[BUTTON_CLASSES.hover]: zoom < maxZoom,
[BUTTON_CLASSES.disabled]: zoom >= maxZoom,
})}
aria-label="Zoom in"
type="button"
disabled={zoom >= maxZoom}
onClick={increaseZoom}
>
<Plus className="h-6 w-6" />
</button>

<button
className={cn(BUTTON_CLASSES.default, {
[BUTTON_CLASSES.hover]: zoom > minZoom,
[BUTTON_CLASSES.disabled]: zoom <= minZoom,
})}
aria-label="Zoom out"
type="button"
disabled={zoom <= minZoom}
onClick={decreaseZoom}
>
<Minus className="h-6 w-6" />
</button>
</div>
);
}
41 changes: 32 additions & 9 deletions client/src/containers/projects/map/index.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,45 @@
"use client";

import { ExpandIcon } from "lucide-react";
import { ComponentProps } from "react";

import { useAtomValue } from "jotai";

import { projectsMapState } from "@/app/(projects)/store";

import Controls from "@/containers/projects/map/controls";
import LegendControl from "@/containers/projects/map/controls/legend";
import ZoomControl from "@/containers/projects/map/controls/zoom";
import ProjectsLayer from "@/containers/projects/map/layers/projects";
import { MATRIX_COLORS } from "@/containers/projects/map/layers/projects/utils";
import Legend from "@/containers/projects/map/legend";
import MatrixLegend from "@/containers/projects/map/legend/types/matrix";

import Map from "@/components/map";
import { Button } from "@/components/ui/button";

export default function ProjectsMap() {
const onToggleExpand = () => {};
const { legendOpen } = useAtomValue(projectsMapState);

const matrixItems: ComponentProps<typeof MatrixLegend>["intersections"] =
Object.keys(MATRIX_COLORS).map((key, index) => ({
color: key,
id: index,
}));

return (
<div className="h-full overflow-hidden rounded-2xl">
<Map>
<Button
onClick={onToggleExpand}
className="absolute right-2 top-2 z-50"
>
<ExpandIcon />
</Button>
<Controls>
<ZoomControl />
</Controls>
<Controls className="bottom-8 top-auto">
<LegendControl />
</Controls>
{legendOpen && (
<Legend>
<MatrixLegend items={[]} intersections={matrixItems} />
</Legend>
)}
<ProjectsLayer />
</Map>
</div>
);
Expand Down
Loading

0 comments on commit bb8dcb5

Please sign in to comment.