Enhance sidebar menu experience

This commit is contained in:
sianida26 2024-03-29 03:20:56 +07:00
parent b9214dfe88
commit f4ddd8461d
5 changed files with 42 additions and 12 deletions

View File

@ -4,9 +4,11 @@ import { Text } from "@mantine/core";
import classNames from "./styles/sidebarChildMenu.module.css"; import classNames from "./styles/sidebarChildMenu.module.css";
import SidebarMenu from "../types/SidebarMenu"; import SidebarMenu from "../types/SidebarMenu";
import dashboardConfig from "../dashboard.config";
interface Props { interface Props {
item: NonNullable<SidebarMenu["children"]>[number]; item: NonNullable<SidebarMenu["children"]>[number];
active: boolean;
} }
/** /**
@ -26,7 +28,8 @@ export default function ChildMenu(props: Props) {
<Text<"a"> <Text<"a">
component="a" component="a"
className={classNames.link} className={classNames.link}
href={`/dashboard${linkPath}`} href={`${dashboardConfig.baseRoute}${linkPath}`}
fw={props.active ? "bold" : "normal"}
> >
{props.item.label} {props.item.label}
</Text> </Text>

View File

@ -1,4 +1,4 @@
import React, { ReactNode, useState } from "react"; import React, { useState } from "react";
import { import {
Box, Box,
@ -6,7 +6,9 @@ import {
Group, Group,
ThemeIcon, ThemeIcon,
UnstyledButton, UnstyledButton,
alpha,
rem, rem,
useMantineTheme,
} from "@mantine/core"; } from "@mantine/core";
import { TbChevronRight } from "react-icons/tb"; import { TbChevronRight } from "react-icons/tb";
import * as TbIcons from "react-icons/tb"; import * as TbIcons from "react-icons/tb";
@ -14,6 +16,9 @@ import * as TbIcons from "react-icons/tb";
import ChildMenu from "./SidebarChildMenu"; import ChildMenu from "./SidebarChildMenu";
import classNames from "./styles/sidebarMenuItem.module.css"; import classNames from "./styles/sidebarMenuItem.module.css";
import SidebarMenu from "../types/SidebarMenu"; import SidebarMenu from "../types/SidebarMenu";
import dashboardConfig from "../dashboard.config";
import { usePathname } from "next/navigation";
import areURLsSame from "@/utils/areUrlSame";
interface Props { interface Props {
menu: SidebarMenu; menu: SidebarMenu;
@ -30,7 +35,11 @@ interface Props {
export default function MenuItem({ menu }: Props) { export default function MenuItem({ menu }: Props) {
const hasChildren = Array.isArray(menu.children); const hasChildren = Array.isArray(menu.children);
const [opened, setOpened] = useState(false); const pathname = usePathname()
const theme = useMantineTheme();
const [opened, setOpened] = useState(menu.children?.some(child => areURLsSame(`${dashboardConfig.baseRoute}${child.link}`, pathname)) ?? false);
const toggleOpenMenu = () => { const toggleOpenMenu = () => {
setOpened((prev) => !prev); setOpened((prev) => !prev);
@ -38,20 +47,23 @@ export default function MenuItem({ menu }: Props) {
// Mapping children menu items if available // Mapping children menu items if available
const subItems = (hasChildren ? menu.children! : []).map((child, index) => ( const subItems = (hasChildren ? menu.children! : []).map((child, index) => (
<ChildMenu key={index} item={child} /> <ChildMenu key={index} item={child} active={areURLsSame(`${dashboardConfig.baseRoute}${child.link}`, pathname)} />
)); ));
const Icons = TbIcons as any; const Icons = TbIcons as any;
const Icon = typeof menu.icon === "string" ? Icons[menu.icon] : menu.icon; const Icon = typeof menu.icon === "string" ? Icons[menu.icon] : menu.icon;
// const a = typeof menu.icon === "string"
const isActive = areURLsSame(`${dashboardConfig.baseRoute}${menu.link}`, pathname)
return ( return (
<> <>
{/* Main Menu Item */} {/* Main Menu Item */}
<UnstyledButton <UnstyledButton<"a" | "button">
onClick={toggleOpenMenu} onClick={toggleOpenMenu}
className={classNames.control} className={classNames.control}
href={menu.link ? dashboardConfig.baseRoute + menu.link : ""}
component={menu.link ? "a" : "button"}
> >
<Group justify="space-between" gap={0}> <Group justify="space-between" gap={0}>
{/* Icon and Label */} {/* Icon and Label */}
@ -60,7 +72,7 @@ export default function MenuItem({ menu }: Props) {
<Icon style={{ width: rem(18), height: rem(18) }} /> <Icon style={{ width: rem(18), height: rem(18) }} />
</ThemeIcon> </ThemeIcon>
<Box ml="md">{menu.label}</Box> <Box ml="md" fw={isActive ? 700 : 500}>{menu.label}</Box>
</Box> </Box>
{/* Chevron Icon for collapsible items */} {/* Chevron Icon for collapsible items */}

View File

@ -5,6 +5,7 @@ const sidebarMenus: SidebarMenu[] = [
label: "Dashboard", label: "Dashboard",
icon: "TbLayoutDashboard", icon: "TbLayoutDashboard",
allowedPermissions: ["*"], allowedPermissions: ["*"],
link: "/",
}, },
{ {
label: "Users", label: "Users",

View File

@ -7,10 +7,11 @@ export default interface SidebarMenu {
children?: { children?: {
label: string; label: string;
link: string; link: string;
allowedPermissions?: PermissionCode[], allowedPermissions?: PermissionCode[];
allowedRoles?: RoleCode[], allowedRoles?: RoleCode[];
}[]; }[];
link?: string;
color?: ThemeIconProps["color"]; color?: ThemeIconProps["color"];
allowedPermissions?: PermissionCode[], allowedPermissions?: PermissionCode[];
allowedRoles?: RoleCode[] allowedRoles?: RoleCode[];
} }

13
src/utils/areUrlSame.ts Normal file
View File

@ -0,0 +1,13 @@
function areURLsSame(url1: string, url2: string): boolean {
const normalizeUrl = (url: string) => {
// Remove query parameters
url = url.split("?")[0];
// Ensure there is no trailing slash
url = url.endsWith("/") ? url.slice(0, -1) : url;
return url.toLowerCase();
};
return normalizeUrl(url1) === normalizeUrl(url2);
}
export default areURLsSame;