Merge branch 'feature/sidebar-transition' into 'main'

Feature/sidebar transition

See merge request vkuchenik/website-framework!8
This commit is contained in:
Dan Hansen
2025-07-15 22:59:24 +00:00
2 changed files with 368 additions and 80 deletions
+3 -3
View File
@@ -2,7 +2,7 @@
// for 'Add Widget', 'Edit Widgets', and 'Remove Widgets'. This component will // for 'Add Widget', 'Edit Widgets', and 'Remove Widgets'. This component will
// control settings related to the Widgets on the main canvas. // control settings related to the Widgets on the main canvas.
import { Button, Menu, Portal } from "@chakra-ui/react"; import { Button, IconButton, Menu, Portal } from "@chakra-ui/react";
import { FiSettings } from "react-icons/fi"; import { FiSettings } from "react-icons/fi";
export function SettingsButton(props) { export function SettingsButton(props) {
@@ -25,9 +25,9 @@ export function SettingsButton(props) {
// This uses Chakra's built in menu component // This uses Chakra's built in menu component
<Menu.Root> <Menu.Root>
<Menu.Trigger asChild> <Menu.Trigger asChild>
<Button variant="ghost" size="md"> <IconButton variant="ghost" size="md">
<FiSettings /> <FiSettings />
</Button> </IconButton>
</Menu.Trigger> </Menu.Trigger>
<Portal> <Portal>
<Menu.Positioner> <Menu.Positioner>
+356 -68
View File
@@ -1,4 +1,4 @@
import { Box, Button, createIcon, CloseButton, IconButton, Stack } from "@chakra-ui/react"; import { Box, Button, createIcon, CloseButton, IconButton, Stack, useDisclosure, Tooltip } from "@chakra-ui/react";
import { FaVolcano } from "react-icons/fa6"; import { FaVolcano } from "react-icons/fa6";
import { RxHamburgerMenu } from "react-icons/rx"; import { RxHamburgerMenu } from "react-icons/rx";
import { useState } from "react"; import { useState } from "react";
@@ -56,87 +56,375 @@ export const Users = createIcon({
), ),
}); });
export default function Sidebar() { export default function Sidebar() {
const [isOpen, setIsOpen] = useState(true); const [isOpen, setIsOpen] = useState(true);
const [activeButton, setActiveButton] = useState("Dashboard") const [hasFinishedClosing, setHasFinishedClosing] = useState(true);
const [activeButton, setActiveButton] = useState("Dashboard");
const toggleSidebar = () => {
if (isOpen) {
setHasFinishedClosing(false);
setTimeout(() => setIsOpen(false), 10); // Kick off transition
setTimeout(() => setHasFinishedClosing(true), 400) // Match sidebar transition duration
} else {
setIsOpen(true);
setHasFinishedClosing(true);
}
};
return ( return (
<> <Box
{isOpen && ( w={isOpen ? "250px" : "70px"}
<Box w="100%" h="100%" h="100vh"
borderRight="1px" transition="width 0.4s ease"
borderColor="black" borderRight="1px solid black"
borderStyle="solid"
display="flex" display="flex"
flexDirection="column" flexDirection="column"
justifyContent="start"
alignItems="start" alignItems="start"
gap={4} paddingTop={4} bg="white"
paddingLeft={14} paddingRight={3} p={4}
gap={4}
> >
{/* <CloseButton color="secondary" _hover={{bg: "#EDF1F1"}} size="sm" variant="ghost" left="-10" onClick={() => setIsOpen(!isOpen)}></CloseButton> */} <IconButton
{/* <RxHamburgerMenu color="secondary" _hover={{bg: "#EDF1F1"}} size="sm" variant="ghost" left="-10" onClick={() => setIsOpen(!isOpen)}></RxHamburgerMenu> */} aria-label="Toggle Sidebar"
variant="ghost"
size="md"
// ml="auto"
// left="0"
// onClick={() => setIsOpen(!isOpen)}
onClick={toggleSidebar}
// icon={<RxHamburgerMenu />}
>
<RxHamburgerMenu />
</IconButton>
{/* Dashboard */}
<Button <Button
aria-lable="Toggle Sidebar" color="#19332D"
color="secondary" fontSize="lg"
_hover={{bg: "#EDF1F1"}} variant="ghost"
size="md" variant="ghost" justifyContent={isOpen || !hasFinishedClosing ? "flex-start" : "center"}
// left="-10" w="100%"
ml="auto" // Margin-left: auto, pushes hamburger menu to right side onClick={() => setActiveButton("Dashboard")}
onClick={() => setIsOpen(!isOpen)} // onClick={toggleSidebar}
bg={activeButton === "Dashboard" ? "#A4B6B6" : "transparent"}
_hover={{
bg: activeButton === "Dashboard" ? "#A4B6B6" : "#EDF1F1",
}}
> >
<RxHamburgerMenu /> {isOpen ? (
</Button> <>
<Button color="#19332D" _hover={activeButton === "Dashboard" ? {bg: "#A4B6B6"}:{bg:"#EDF1F1"}} fontSize="xl" variant="ghost" onClick={() => setActiveButton("Dashboard")} bg={activeButton === "Dashboard" ? "#A4B6B6" : "transparent"}> {/* <PushPin boxSize={5} style={{ marginRight: "8px" }} /> */}
<PushPin fill="white" /> Dashboard
</Button>
<Button color="#19332D" _hover={activeButton === "Global" ? {bg: "#A4B6B6"}:{bg:"#EDF1F1"}} fontSize="xl" variant="ghost" onClick={() => setActiveButton("Global")} bg={activeButton === "Global" ? "#A4B6B6" : "transparent"}>
<Globe fill="white" stroke="black" /> Global Map
</Button>
<Button color="#19332D" _hover={activeButton === "Regional" ? {bg: "#A4B6B6"}:{bg:"#EDF1F1"}} fontSize="xl" variant="ghost" onClick={() => setActiveButton("Regional")} bg={activeButton === "Regional" ? "#A4B6B6" : "transparent"}>
<MapMarker fill="white" stroke="black"/> Regional Map
</Button>
<Button color="#19332D" _hover={activeButton === "Volcano" ? {bg: "#A4B6B6"}:{bg:"#EDF1F1"}} fontSize="xl" variant="ghost" onClick={() => setActiveButton("Volcano")} bg={activeButton === "Volcano" ? "#A4B6B6" : "transparent"}>
<FaVolcano /> Volcano
</Button>
<Button color="#19332D" _hover={activeButton === "Admin" ? {bg: "#A4B6B6"}:{bg:"#EDF1F1"}} fontSize="xl" variant="ghost" onClick={() => setActiveButton("Admin")} bg={activeButton === "Admin" ? "#A4B6B6" : "transparent"}>
<Users fill="white" stroke="black" /> Admin
</Button>
</Box>
)}
{!isOpen && (
<Box w="100%" h="100%"
borderRight="1px"
borderColor="black"
borderStyle="solid"
display="flex"
flexDirection="column"
justifyContent="start"
alignItems="start"
gap={4} padding={4}
>
<IconButton color="#19332D" _hover={{bg: "#EDF1F1"}} size="md" variant="ghost" onClick={() => setIsOpen(!isOpen)}>
<RxHamburgerMenu />
</IconButton>
<IconButton color="#19332D" _hover={activeButton === "Dashboard" ? {bg: "#A4B6B6"}:{bg:"#EDF1F1"}} variant="ghost" onClick={() => setActiveButton("Dashboard")} bg={activeButton === "Dashboard" ? "#A4B6B6" : "transparent"}>
<PushPin fill="white" /> <PushPin fill="white" />
</IconButton>
<IconButton color="#19332D" _hover={activeButton === "Global" ? {bg: "#A4B6B6"}:{bg:"#EDF1F1"}} variant="ghost" onClick={() => setActiveButton("Global")} bg={activeButton === "Global" ? "#A4B6B6" : "transparent"}> {/* Make text appear gradually instead of popping in */}
<Globe fill="white" stroke="black" /> <span
</IconButton> style={{
<IconButton color="#19332D" _hover={activeButton === "Regional" ? {bg: "#A4B6B6"}:{bg:"#EDF1F1"}} variant="ghost" onClick={() => setActiveButton("Regional")} bg={activeButton === "Regional" ? "#A4B6B6" : "transparent"}> marginLeft: "8px",
<MapMarker fill="white" stroke="black" /> opacity: isOpen ? 1 : 0,
</IconButton> maxWidth: isOpen ? "250px" : "0px", // prevent layout from expanding before fade-in
<IconButton color="#19332D" _hover={activeButton === "Volcano" ? {bg: "#A4B6B6"}:{bg:"#EDF1F1"}} variant="ghost" onClick={() => setActiveButton("Volcano")} bg={activeButton === "Volcano" ? "#A4B6B6" : "transparent"}> overflow: "hidden",
<FaVolcano /> whiteSpace: "nowrap",
</IconButton> display: "inline-block",
<IconButton color="#19332D" _hover={activeButton === "Admin" ? {bg: "#A4B6B6"}:{bg:"#EDF1F1"}} variant="ghost" onClick={() => setActiveButton("Admin")} bg={activeButton === "Admin" ? "#A4B6B6" : "transparent"}> transitionProperty: "opacity, max-width",
<Users fill="white" stroke="black" /> transitionDuration: "0.3s",
</IconButton> transitionTimingFunction: "ease",
</Box> transitionDelay: isOpen ? "0.1s" : "0s",
visibility: isOpen || !hasFinishedClosing ? "visible" : "hidden",
}}
>
Dashboard
</span>
</>
) : (
// <PushPin boxSize={5} />
<PushPin fill="white" />
)} )}
</Button>
{/* Global Map */}
<Button
color="#19332D"
fontSize="lg"
variant="ghost"
// justifyContent={isOpen || !hasFinishedClosing ? "flex-start" : "center"}
justifyContent="flex-start"
w="100%"
h="48px"
px={0}
onClick={() => setActiveButton("Global")}
bg={activeButton === "Global" ? "#A4B6B6" : "transparent"}
_hover={{
bg: activeButton === "Global" ? "#A4B6B6" : "#EDF1F1",
}}
>
{isOpen ? (
<>
<Box
display="flex"
alignItems="flex-start"
pl={ isOpen ? "0px" : "24px" }
// w="100%"
>
{/* Fixed-width icon container (works both open & closed) */}
<Box
w="70px" // Match closed sidebar width
display="flex"
justifyContent="center"
alignItems="center"
flexShrink={0}
>
{/* <Globe boxSize={5} style={{ marginRight: "8px" }} /> */}
<Globe fill="white" stroke="black" />
</Box>
<span
style={{
marginLeft: "-10px",
opacity: isOpen ? 1 : 0,
maxWidth: isOpen ? "250px" : "0px", // prevent layout from expanding before fade-in
overflow: "hidden",
whiteSpace: "nowrap",
display: "inline-block",
transitionProperty: "opacity, max-width",
transitionDuration: "0.3s",
transitionTimingFunction: "ease",
transitionDelay: isOpen ? "0.1s" : "0s",
visibility: isOpen || !hasFinishedClosing ? "visible" : "hidden",
}}
>
Global Map
</span>
</Box>
</> </>
) : (
// <Globe boxSize={5} />
<Box
w="70px" // Match closed sidebar width exactly
display="flex"
justifyContent="center"
alignItems="center"
>
<Globe fill="white" stroke="black" />
</Box>
)}
</Button>
{/* Regional Map */}
<Button
color="#19332D"
fontSize="lg"
variant="ghost"
justifyContent={isOpen || !hasFinishedClosing ? "flex-start" : "center"}
w="100%"
onClick={() => setActiveButton("Regional")}
bg={activeButton === "Regional" ? "#A4B6B6" : "transparent"}
_hover={{
bg: activeButton === "Regional" ? "#A4B6B6" : "#EDF1F1",
}}
>
{isOpen ? (
<>
{/* <MapMarker boxSize={5} style={{ marginRight: "8px" }} /> */}
<MapMarker fill="white" stroke="black"/>
{/* Make text appear gradually instead of popping in */}
<span
style={{
marginLeft: "8px",
opacity: isOpen ? 1 : 0,
maxWidth: isOpen ? "250px" : "0px", // prevent layout from expanding before fade-in
overflow: "hidden",
whiteSpace: "nowrap",
display: "inline-block",
transitionProperty: "opacity, max-width",
transitionDuration: "0.3s",
transitionTimingFunction: "ease",
transitionDelay: isOpen ? "0.1s" : "0s",
visibility: isOpen || !hasFinishedClosing ? "visible" : "hidden",
}}
>
Regional Map
</span>
</>
) : (
// <MapMarker boxSize={5} />
<MapMarker fill="white" stroke="black" />
)}
</Button>
{/* Volcano */}
<Button
color="#19332D"
fontSize="lg"
variant="ghost"
justifyContent={isOpen || !hasFinishedClosing ? "flex-start" : "center"}
w="100%"
onClick={() => setActiveButton("Volcano")}
bg={activeButton === "Volcano" ? "#A4B6B6" : "transparent"}
_hover={{
bg: activeButton === "Volcano" ? "#A4B6B6" : "#EDF1F1",
}}
>
{isOpen ? (
<>
<FaVolcano size={20} style={{ marginRight: "8px" }} />
{/* Make text appear gradually instead of popping in */}
<span
style={{
marginLeft: "8px",
opacity: isOpen ? 1 : 0,
maxWidth: isOpen ? "250px" : "0px", // prevent layout from expanding before fade-in
overflow: "hidden",
whiteSpace: "nowrap",
display: "inline-block",
transitionProperty: "opacity, max-width",
transitionDuration: "0.3s",
transitionTimingFunction: "ease",
transitionDelay: isOpen ? "0.1s" : "0s",
visibility: isOpen || !hasFinishedClosing ? "visible" : "hidden",
}}
>
Volcano
</span>
</>
) : (
// <FaVolcano size={20} />
<FaVolcano />
)}
</Button>
{/* Admin */}
<Button
color="#19332D"
fontSize="lg"
variant="ghost"
justifyContent={isOpen || !hasFinishedClosing ? "flex-start" : "center"}
w="100%"
onClick={() => setActiveButton("Admin")}
bg={activeButton === "Admin" ? "#A4B6B6" : "transparent"}
_hover={{
bg: activeButton === "Admin" ? "#A4B6B6" : "#EDF1F1",
}}
>
{isOpen ? (
<>
{/* <Users boxSize={5} style={{ marginRight: "8px" }} /> */}
<Users fill="white" stroke="black" />
{/* Make text appear gradually instead of popping in */}
<span
style={{
marginLeft: "8px",
opacity: isOpen ? 1 : 0,
maxWidth: isOpen ? "200px" : "0px", // prevent layout from expanding before fade-in
overflow: "hidden",
whiteSpace: "nowrap",
display: "inline-block",
transitionProperty: "opacity, max-width",
transitionDuration: "0.3s",
transitionTimingFunction: "ease",
transitionDelay: isOpen ? "0.1s" : "0s",
visibility: isOpen || !hasFinishedClosing ? "visible" : "hidden",
}}
>
Admin
</span>
</>
) : (
// <Users boxSize={5} />
<Users fill="white" stroke="black" />
)}
</Button>
</Box>
); );
} }
// THIS CODE WORKS, SAVE IN CASE OF EMERGENCY
// Needed to refactor in order allow transitions to work properly
// export default function Sidebar () {
// const [isOpen, setIsOpen] = useState(true);
// const [activeButton, setActiveButton] = useState("Dashboard")
// return (
// <>
// {isOpen && (
// <Box w="100%" h="100%"
// borderRight="1px"
// borderColor="black"
// borderStyle="solid"
// display="flex"
// flexDirection="column"
// justifyContent="start"
// alignItems="start"
// gap={4} paddingTop={4}
// paddingLeft={14} paddingRight={3}
// >
// {/* <CloseButton color="secondary" _hover={{bg: "#EDF1F1"}} size="sm" variant="ghost" left="-10" onClick={() => setIsOpen(!isOpen)}></CloseButton> */}
// {/* <RxHamburgerMenu color="secondary" _hover={{bg: "#EDF1F1"}} size="sm" variant="ghost" left="-10" onClick={() => setIsOpen(!isOpen)}></RxHamburgerMenu> */}
// <Button
// aria-lable="Toggle Sidebar"
// color="secondary"
// _hover={{bg: "#EDF1F1"}}
// size="md" variant="ghost"
// left="-10"
// // ml="auto" // Margin-left: auto, pushes hamburger menu to right side
// onClick={() => setIsOpen(!isOpen)}
// >
// <RxHamburgerMenu />
// </Button>
// <Button color="#19332D" _hover={activeButton === "Dashboard" ? {bg: "#A4B6B6"}:{bg:"#EDF1F1"}} fontSize="xl" variant="ghost" onClick={() => setActiveButton("Dashboard")} bg={activeButton === "Dashboard" ? "#A4B6B6" : "transparent"}>
// <PushPin fill="white" /> Dashboard
// </Button>
// <Button color="#19332D" _hover={activeButton === "Global" ? {bg: "#A4B6B6"}:{bg:"#EDF1F1"}} fontSize="xl" variant="ghost" onClick={() => setActiveButton("Global")} bg={activeButton === "Global" ? "#A4B6B6" : "transparent"}>
// <Globe fill="white" stroke="black" /> Global Map
// </Button>
// <Button color="#19332D" _hover={activeButton === "Regional" ? {bg: "#A4B6B6"}:{bg:"#EDF1F1"}} fontSize="xl" variant="ghost" onClick={() => setActiveButton("Regional")} bg={activeButton === "Regional" ? "#A4B6B6" : "transparent"}>
// <MapMarker fill="white" stroke="black"/> Regional Map
// </Button>
// <Button color="#19332D" _hover={activeButton === "Volcano" ? {bg: "#A4B6B6"}:{bg:"#EDF1F1"}} fontSize="xl" variant="ghost" onClick={() => setActiveButton("Volcano")} bg={activeButton === "Volcano" ? "#A4B6B6" : "transparent"}>
// <FaVolcano /> Volcano
// </Button>
// <Button color="#19332D" _hover={activeButton === "Admin" ? {bg: "#A4B6B6"}:{bg:"#EDF1F1"}} fontSize="xl" variant="ghost" onClick={() => setActiveButton("Admin")} bg={activeButton === "Admin" ? "#A4B6B6" : "transparent"}>
// <Users fill="white" stroke="black" /> Admin
// </Button>
// </Box>
// )}
// {!isOpen && (
// <Box w="100%" h="100%"
// borderRight="1px"
// borderColor="black"
// borderStyle="solid"
// display="flex"
// flexDirection="column"
// justifyContent="start"
// alignItems="start"
// gap={4} padding={4}
// >
// <IconButton color="#19332D" _hover={{bg: "#EDF1F1"}} size="md" variant="ghost" onClick={() => setIsOpen(!isOpen)}>
// <RxHamburgerMenu />
// </IconButton>
// <IconButton color="#19332D" _hover={activeButton === "Dashboard" ? {bg: "#A4B6B6"}:{bg:"#EDF1F1"}} variant="ghost" onClick={() => setActiveButton("Dashboard")} bg={activeButton === "Dashboard" ? "#A4B6B6" : "transparent"}>
// <PushPin fill="white"/>
// </IconButton>
// <IconButton color="#19332D" _hover={activeButton === "Global" ? {bg: "#A4B6B6"}:{bg:"#EDF1F1"}} variant="ghost" onClick={() => setActiveButton("Global")} bg={activeButton === "Global" ? "#A4B6B6" : "transparent"}>
// <Globe fill="white" stroke="black" />
// </IconButton>
// <IconButton color="#19332D" _hover={activeButton === "Regional" ? {bg: "#A4B6B6"}:{bg:"#EDF1F1"}} variant="ghost" onClick={() => setActiveButton("Regional")} bg={activeButton === "Regional" ? "#A4B6B6" : "transparent"}>
// <MapMarker fill="white" stroke="black" />
// </IconButton>
// <IconButton color="#19332D" _hover={activeButton === "Volcano" ? {bg: "#A4B6B6"}:{bg:"#EDF1F1"}} variant="ghost" onClick={() => setActiveButton("Volcano")} bg={activeButton === "Volcano" ? "#A4B6B6" : "transparent"}>
// <FaVolcano />
// </IconButton>
// <IconButton color="#19332D" _hover={activeButton === "Admin" ? {bg: "#A4B6B6"}:{bg:"#EDF1F1"}} variant="ghost" onClick={() => setActiveButton("Admin")} bg={activeButton === "Admin" ? "#A4B6B6" : "transparent"}>
// <Users fill="white" stroke="black" />
// </IconButton>
// </Box>
// )}
// </>
// );
// }