Added more UI improvements - Hide/Show Sidebar

This commit is contained in:
Mitchell Magro 2025-10-27 14:20:43 +01:00
parent 7c716f5b27
commit fe6ed86a76
8 changed files with 154 additions and 29 deletions

View File

@ -1,12 +1,15 @@
"use client"; "use client";
import React from "react"; import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { LayoutWrapper } from "../features/dashboard/layout/layoutWrapper"; import { LayoutWrapper } from "../features/dashboard/layout/layoutWrapper";
import { MainContent } from "../features/dashboard/layout/mainContent"; import { MainContent } from "../features/dashboard/layout/mainContent";
import SideBar from "../features/dashboard/sidebar/Sidebar"; import SideBar from "../features/dashboard/sidebar/Sidebar";
import Header from "../features/dashboard/header/Header"; import Header from "../features/dashboard/header/Header";
import { useTokenExpiration } from "../hooks/useTokenExpiration"; import { useTokenExpiration } from "../hooks/useTokenExpiration";
import TokenExpirationInfo from "../components/TokenExpirationInfo"; import TokenExpirationInfo from "../components/TokenExpirationInfo";
import { toggleSidebar } from "../redux/ui/uiSlice";
import { RootState } from "../redux/types";
const DashboardLayout: React.FC<{ children: React.ReactNode }> = ({ const DashboardLayout: React.FC<{ children: React.ReactNode }> = ({
children, children,
@ -14,9 +17,16 @@ const DashboardLayout: React.FC<{ children: React.ReactNode }> = ({
// Monitor token expiration and auto-logout // Monitor token expiration and auto-logout
useTokenExpiration(); useTokenExpiration();
const dispatch = useDispatch();
const isSidebarOpen = useSelector((state: RootState) => state.ui.sidebarOpen);
const handleToggleSidebar = () => {
dispatch(toggleSidebar());
};
return ( return (
<LayoutWrapper> <LayoutWrapper>
<SideBar /> <SideBar isOpen={isSidebarOpen} onClose={handleToggleSidebar} />
<div style={{ flexGrow: 1, display: "flex", flexDirection: "column" }}> <div style={{ flexGrow: 1, display: "flex", flexDirection: "column" }}>
<MainContent> <MainContent>
<Header /> <Header />

View File

@ -1,23 +1,10 @@
import React from "react"; import React from "react";
import { AppBar, Toolbar, IconButton } from "@mui/material"; import { AppBar, Toolbar } from "@mui/material";
import MenuIcon from "@mui/icons-material/Menu";
import Dropdown from "./dropDown/DropDown"; import Dropdown from "./dropDown/DropDown";
import AccountMenu from "./accountMenu/AccountMenu"; import AccountMenu from "./accountMenu/AccountMenu";
import "./Header.scss"; import "./Header.scss";
const Header = () => { const Header = () => {
// const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
// // Handle menu open
// const handleMenuClick = (event: React.MouseEvent<HTMLElement>) => {
// setAnchorEl(event.currentTarget);
// };
// // Handle menu close
// const handleMenuClose = () => {
// setAnchorEl(null);
// };
const handleChange = () => {}; const handleChange = () => {};
return ( return (
@ -30,9 +17,6 @@ const Header = () => {
> >
<Toolbar className="header__toolbar"> <Toolbar className="header__toolbar">
<div className="header__left-group"> <div className="header__left-group">
<IconButton edge="start" color="inherit" aria-label="menu">
<MenuIcon />
</IconButton>
<Dropdown onChange={handleChange} /> <Dropdown onChange={handleChange} />
</div> </div>

View File

@ -1,8 +0,0 @@
import { styled } from "@mui/system";
export const MainContent = styled("div")(({ theme }) => ({
marginLeft: "240px",
padding: theme.spacing(3),
minHeight: "100vh",
width: "calc(100% - 240px)",
}));

View File

@ -0,0 +1,21 @@
import React from "react";
import { useSelector } from "react-redux";
import { RootState } from "../../redux/types";
interface MainContentProps {
children: React.ReactNode;
}
export const MainContent = ({ children }: MainContentProps) => {
const isSidebarOpen = useSelector((state: RootState) => state.ui.sidebarOpen);
const style: React.CSSProperties = {
marginLeft: isSidebarOpen ? "240px" : "30px",
padding: "24px",
minHeight: "100vh",
width: isSidebarOpen ? "calc(100% - 240px)" : "calc(100% - 30px)",
transition: "margin-left 0.3s ease-in-out, width 0.3s ease-in-out",
};
return <div style={style}>{children}</div>;
};

View File

@ -7,8 +7,15 @@ import PageLinks from "../../../components/PageLinks/PageLinks";
import "./sideBar.scss"; import "./sideBar.scss";
import KeyboardArrowRightIcon from "@mui/icons-material/KeyboardArrowRight"; import KeyboardArrowRightIcon from "@mui/icons-material/KeyboardArrowRight";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
const SideBar = () => { interface SidebarProps {
isOpen?: boolean;
onClose?: () => void;
}
const SideBar = ({ isOpen = true, onClose }: SidebarProps) => {
const [openMenus, setOpenMenus] = useState<Record<string, boolean>>({}); const [openMenus, setOpenMenus] = useState<Record<string, boolean>>({});
const toggleMenu = (title: string) => { const toggleMenu = (title: string) => {
@ -16,10 +23,13 @@ const SideBar = () => {
}; };
return ( return (
<aside className="sidebar"> <aside className={`sidebar ${!isOpen ? "sidebar--collapsed" : ""}`}>
<button className="sidebar__toggle-button" onClick={onClose}>
{isOpen ? <ChevronLeftIcon /> : <ChevronRightIcon />}
</button>
<div className="sidebar__header"> <div className="sidebar__header">
<span> <span>
Betrise cashir Betrise cashier
<DashboardIcon fontSize="small" className="sidebar__icon-spacing" /> <DashboardIcon fontSize="small" className="sidebar__icon-spacing" />
</span> </span>
</div> </div>

View File

@ -11,6 +11,55 @@
padding: 16px; padding: 16px;
z-index: 1100; z-index: 1100;
border-right: 1px solid #333; border-right: 1px solid #333;
transition: transform 0.3s ease-in-out;
overflow: hidden;
&--collapsed {
transform: translateX(-210px); // Hide 90% (210px out of 240px)
.sidebar__header,
.sidebar__dropdown-button,
.sidebar__submenu,
a {
opacity: 0;
pointer-events: none;
}
}
&__toggle-button {
position: absolute;
top: 12px;
right: 0;
background: var(--background-primary);
border: 1px solid rgba(255, 255, 255, 0.2);
color: white;
cursor: pointer;
padding: 8px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50% 0 0 50%;
transition: all 0.2s ease;
z-index: 1101;
width: 36px;
height: 36px;
border-left: 1px solid #333;
&:hover {
background-color: rgba(255, 255, 255, 0.1);
transform: scale(1.05);
}
svg {
font-size: 20px;
}
}
&--collapsed &__toggle-button {
border-radius: 50%;
border: 1px solid rgba(255, 255, 255, 0.2);
border-left: 1px solid #333;
}
&__header { &__header {
font-size: 20px; font-size: 20px;
@ -71,4 +120,36 @@
} }
} }
} }
// Mobile responsiveness
@media (max-width: 768px) {
z-index: 1101;
}
}
// Sidebar overlay for mobile
.sidebar-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1100;
opacity: 0;
visibility: hidden;
pointer-events: none;
transition:
opacity 0.3s ease-in-out,
visibility 0.3s ease-in-out;
&--visible {
opacity: 1;
visibility: visible;
pointer-events: all;
}
@media (min-width: 768px) {
display: none;
}
} }

View File

@ -8,6 +8,7 @@ import { createTransform, persistReducer, persistStore } from "redux-persist";
import { createEpicMiddleware, combineEpics } from "redux-observable"; import { createEpicMiddleware, combineEpics } from "redux-observable";
import advancedSearchReducer from "./advanedSearch/advancedSearchSlice"; import advancedSearchReducer from "./advanedSearch/advancedSearchSlice";
import authReducer from "./auth/authSlice"; import authReducer from "./auth/authSlice";
import uiReducer from "./ui/uiSlice";
import userEpics from "./user/epic"; import userEpics from "./user/epic";
import authEpics from "./auth/epic"; import authEpics from "./auth/epic";
@ -36,6 +37,7 @@ const persistConfig = {
const rootReducer = combineReducers({ const rootReducer = combineReducers({
advancedSearch: advancedSearchReducer, advancedSearch: advancedSearchReducer,
auth: authReducer, auth: authReducer,
ui: uiReducer,
}); });
const rootEpic = combineEpics(...userEpics, ...authEpics); const rootEpic = combineEpics(...userEpics, ...authEpics);

25
app/redux/ui/uiSlice.ts Normal file
View File

@ -0,0 +1,25 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface UIState {
sidebarOpen: boolean;
}
const initialState: UIState = {
sidebarOpen: true,
};
const uiSlice = createSlice({
name: "ui",
initialState,
reducers: {
toggleSidebar: state => {
state.sidebarOpen = !state.sidebarOpen;
},
setSidebarOpen: (state, action: PayloadAction<boolean>) => {
state.sidebarOpen = action.payload;
},
},
});
export const { toggleSidebar, setSidebarOpen } = uiSlice.actions;
export default uiSlice.reducer;