Added more UI improvements - Hide/Show Sidebar
This commit is contained in:
parent
7c716f5b27
commit
fe6ed86a76
@ -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 />
|
||||||
|
|||||||
@ -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>
|
||||||
|
|
||||||
|
|||||||
@ -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)",
|
|
||||||
}));
|
|
||||||
21
app/features/dashboard/layout/mainContent.tsx
Normal file
21
app/features/dashboard/layout/mainContent.tsx
Normal 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>;
|
||||||
|
};
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
25
app/redux/ui/uiSlice.ts
Normal 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;
|
||||||
Loading…
x
Reference in New Issue
Block a user