Enhance sidebar menu experience
This commit is contained in:
parent
b9214dfe88
commit
f4ddd8461d
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 */}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ const sidebarMenus: SidebarMenu[] = [
|
||||||
label: "Dashboard",
|
label: "Dashboard",
|
||||||
icon: "TbLayoutDashboard",
|
icon: "TbLayoutDashboard",
|
||||||
allowedPermissions: ["*"],
|
allowedPermissions: ["*"],
|
||||||
|
link: "/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Users",
|
label: "Users",
|
||||||
|
|
|
||||||
11
src/modules/dashboard/types/SidebarMenu.d.ts
vendored
11
src/modules/dashboard/types/SidebarMenu.d.ts
vendored
|
|
@ -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
13
src/utils/areUrlSame.ts
Normal 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;
|
||||||
Loading…
Reference in New Issue
Block a user