Merge branch 'maps/layer-selector-component' into 'main'

added layer selector in global maps

See merge request vkuchenik/website-framework!69
This commit is contained in:
2025-08-19 23:48:22 +00:00
7 changed files with 170 additions and 13 deletions
+10
View File
@@ -14,6 +14,7 @@
"@tailwindcss/vite": "^4.1.11",
"framer-motion": "^12.23.0",
"leaflet": "^1.9.4",
"lucide-react": "^0.539.0",
"next-themes": "^0.4.6",
"react": "^19.1.0",
"react-dom": "^19.1.0",
@@ -3894,6 +3895,15 @@
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true
},
"node_modules/lucide-react": {
"version": "0.539.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.539.0.tgz",
"integrity": "sha512-VVISr+VF2krO91FeuCrm1rSOLACQUYVy7NQkzrOty52Y8TlTPcXcMdQFj9bYzBgXbWCiywlwSZ3Z8u6a+6bMlg==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/magic-string": {
"version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
+1
View File
@@ -16,6 +16,7 @@
"@tailwindcss/vite": "^4.1.11",
"framer-motion": "^12.23.0",
"leaflet": "^1.9.4",
"lucide-react": "^0.539.0",
"next-themes": "^0.4.6",
"react": "^19.1.0",
"react-dom": "^19.1.0",
@@ -0,0 +1,47 @@
import { useMap } from "react-leaflet";
import L from "leaflet";
import { useEffect, useState } from "react";
import { createPortal } from "react-dom";
// props contain title of btn (which can be an icon), the onClick fn (what should happen)
// when the settings btn is clicked, and it's positioning with the default being topright
export default function MapButtonControls({settingsBtn, position = "topright"}) {
const map = useMap(); // retrieves the map instance that is mounted
const [container, setContainer] = useState(null);
useEffect(() => {
// extending the leaflet control class
const customControl = L.Control.extend({
onAdd: () => {
// create button element with className 'leaflet-bar'
const dv = L.DomUtil.create("div", "layer-selector");
dv.style.cursor = "pointer";
// prevent map interactions when clicking on settings btn
L.DomEvent.disableClickPropagation(dv);
// L.DomEvent.on(dv, "click", onClick); // pass your own onClick fn
// pass button to container for react to render
setContainer(dv);
return dv;
},
onRemove: () => {
setContainer(null);
}
});
// instantiate new customControl object
const control = new customControl({ position });
// method inside og class that we extended above
control.addTo(map);
return () => {
control.remove();
};
}, [map, position, settingsBtn ]);
return container ? createPortal(
settingsBtn,
container)
: null;
}
@@ -0,0 +1,84 @@
import { Box, Checkbox, Separator , CloseButton, Text, chakra, Field, IconButton, Icon, Input, Fieldset, Flex } from "@chakra-ui/react";
import { useState } from "react";
import { FiSettings } from "react-icons/fi";
import { IoIosArrowDown } from "react-icons/io";
import { IoIosArrowUp } from "react-icons/io";
export default function MapSettingsButton() {
const [open, setOpen] = useState(false);
return open ? (
<Box width="240px" display="flex" flexDir="column" gap="2" p="2" bg="white" borderRadius="5px" boxShadow="0 0 0 2px rgba(119, 119, 119, 0.4)">
<Header {...{open, setOpen}}/>
<Separator />
<ThermalAnomalies />
<Lightning />
</Box>
):(
<IconButton boxShadow="0 0 0 2px rgba(119, 119, 119, 0.4)" size="sm" variant="subtle" onClick={() => setOpen(true)}>
<FiSettings />
</IconButton>
);
}
function Header(props) {
const { setOpen } = props;
return (
<chakra.div display="flex" dir="row" alignItems="center" justifyContent="space-between" pl="12">
<Text textStyle="lg">Select Layers</Text>
<CloseButton size="sm" colorPalette='red' onClick={() => setOpen(false)}/>
</chakra.div>
);
}
function ThermalAnomalies() {
const [checked, setChecked] = useState(false);
return (
<Box p="2" borderRadius="3px" bg={checked ? "blue.200":"blue.100"} color={checked ? "blue.700":"text"} border="1px solid" borderColor={checked ? "blue.600":"border"} >
<Checkbox.Root colorPalette="blue">
<Checkbox.HiddenInput onClick={() => setChecked(!checked)}/>
<Checkbox.Control bg={checked ? undefined:"white"}/>
<Checkbox.Label >Thermal Anomalies</Checkbox.Label>
</Checkbox.Root>
</Box>
);
}
function Lightning() {
const [open, setOpen] = useState(false);
const [checked, setChecked] = useState(false);
return (
<Box p="2" borderRadius="3px" bg={checked ? "blue.200":"blue.100"} color={checked ? "blue.700":"text"} border="1px solid" borderColor={checked ? "blue.600":"border"} >
<Checkbox.Root colorPalette="blue">
<Checkbox.HiddenInput onClick={() => {setChecked(!checked); console.log("I'm clicked!")}}/>
<Checkbox.Control bg={checked ? undefined:"white"}/>
<Checkbox.Label >Lightning</Checkbox.Label>
</Checkbox.Root>
<Icon pt="1" size="md" variant="none" onClick={() => setOpen(!open)}>
{open ? (<IoIosArrowUp />):(<IoIosArrowDown />)}
</Icon>
{open && (<LightningOptions />)}
</Box>
);
}
function LightningOptions() {
return (
<Fieldset.Root size="xs">
<Field.Root pl="10" gap={0} >
<Field.Label>Radius (km)</Field.Label>
<Input height="1.25rem" width="4rem" bg="white" border="none" borderRadius="6px" fontStyle="italic" placeholder="max"/>
</Field.Root>
<Field.Root pl="10" gap={0} >
<Field.Label>Date Range</Field.Label>
<Flex dir="row" gap="1">
<Input height="1.25rem" width="4rem" bg="white" border="none" borderRadius="6px" fontStyle="italic" placeholder="min"/>
<Text fontStyle="italic">to</Text>
<Input height="1.25rem" width="4rem" bg="white" border="none" borderRadius="6px" fontStyle="italic" placeholder="max"/>
</Flex>
</Field.Root>
</Fieldset.Root>
);
}
@@ -1,19 +1,23 @@
import React from "react";
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
import MapButtonControls from "../CanvasComponents/MapButtonControls";
import MapSettingsButton from "../CanvasComponents/MapSettingsButton";
export default function GlobalMap() {
const position = [45.61142478054226, -122.49630033167544]; // [latitude, longitude]
return (
<MapContainer center={position} zoom={3} style={{ height: "100%", width: "100%" }}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='&copy; OpenStreetMap contributors'
/>
<Marker position={position}>
<Popup>
CVO Home of the volcanoligists
</Popup>
</Marker>
</MapContainer>
<MapContainer center={position} zoom={3} style={{ height: "100%", width: "100%" }}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='&copy; OpenStreetMap contributors'
/>
<MapButtonControls settingsBtn = {<MapSettingsButton />} position="topright" />
<Marker position={position}>
<Popup>
CVO Home of the volcanoligists
</Popup>
</Marker>
</MapContainer>
);
}
+2 -1
View File
@@ -11,6 +11,7 @@ import { MdOutlineSecurity } from "react-icons/md";
import { IoPersonCircleSharp } from "react-icons/io5";
import { useState, useEffect, useMemo } from "react";
import { DASHBOARD_VIEWS } from "@/constants/viewKeys";
import { KeyRound } from 'lucide-react';
export default function Sidebar({ view, onChangeView, darkMode, setDarkMode }) {
const [isOpen, setIsOpen] = useState(true);
@@ -23,7 +24,7 @@ export default function Sidebar({ view, onChangeView, darkMode, setDarkMode }) {
if (["settings", "personal", "security"].includes(view)) {
return [
{icon: IoPersonCircleSharp, name: "Personal Info", view: "personal"},
{icon: MdOutlineSecurity, name: "Security", view: "security"},
{icon: KeyRound, name: "Security", view: "security"},
];
}
return [
+11 -1
View File
@@ -149,10 +149,20 @@ const config = defineConfig({
}
},
'.leaflet-container': {
fontFamily: "georgia, system-ui, sans-serif !important",
_dark: {
filter: "invert(100%) hue-rotate(180deg) brightness(95%) contrast(90%)",
}
},
".leaflet-popup-content": {
fontFamily: "georgia, system-ui, sans-serif !important"
},
".leaflet-control a": {
fontFamily: "georgia, system-ui, sans-serif !important"
},
".leaflet-tooltip": {
fontFamily: "georgia, system-ui, sans-serif !important"
},
".react-grid-item": {
transition: "none !important"
},
@@ -166,7 +176,7 @@ const config = defineConfig({
primary: { value: {base: "#2B98F8", _dark: "#2B98F8"}},
primaryLt: { value: {base: "#98cdffff", _dark: "#98cdffff"}},
secondaryBG: { value: {base: "#EDF1F1", _dark: "#121212"}},
border: {value: {base: "#000000", _dark: "#CBCBCB"}},
border: {value: {base: "#000000", _dark: "#cbcbcb84"}},
text: {value: {base: "#000000", _dark: "lightgrey"}},
textInverted: {value: "white"},
icon: {value: {base: "#000000", _dark: "lightgrey"}},