Initial commit for new panel
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
import { title } from "process";
|
||||
import * as Icons from "../icons";
|
||||
import { z } from "zod";
|
||||
const navSubItemSchema: z.ZodType<any> = z.lazy(() =>
|
||||
z.object({
|
||||
title: z.string(),
|
||||
url: z.string(),
|
||||
icon: z.any(),
|
||||
items: z.array(navSubItemSchema).default([]),
|
||||
}),
|
||||
);
|
||||
|
||||
const navItemSchema = z.object({
|
||||
label: z.string(),
|
||||
items: z.array(navSubItemSchema),
|
||||
});
|
||||
|
||||
const navDataSchema = z.array(navItemSchema);
|
||||
export type NavSubItem = z.infer<typeof navSubItemSchema>;
|
||||
export type NavData = z.infer<typeof navDataSchema>;
|
||||
|
||||
export const NAV_DATA: NavData = [
|
||||
{
|
||||
label: "MAIN MENU",
|
||||
items: [
|
||||
{
|
||||
title: "Dashboard",
|
||||
url: "/",
|
||||
icon: Icons.HomeIcon,
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: "Admins",
|
||||
url: "/admins",
|
||||
icon: Icons.User,
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: "companies",
|
||||
url: "/companies",
|
||||
icon: Icons.Building,
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: "Customers",
|
||||
url: "/customers",
|
||||
icon: Icons.User,
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: "Tenders",
|
||||
url: "/tenders",
|
||||
icon: Icons.Table,
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
// label: "MAIN MENU",
|
||||
// items: [
|
||||
// {
|
||||
// title: "Dashboard",
|
||||
// icon: Icons.HomeIcon,
|
||||
// items: [
|
||||
// {
|
||||
// title: "eCommerce",
|
||||
// url: "/",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// title: "Calendar",
|
||||
// url: "/calendar",
|
||||
// icon: Icons.Calendar,
|
||||
// items: [],
|
||||
// },
|
||||
// {
|
||||
// title: "Profile",
|
||||
// url: "/profile",
|
||||
// icon: Icons.User,
|
||||
// items: [],
|
||||
// },
|
||||
// {
|
||||
// title: "Forms",
|
||||
// icon: Icons.Alphabet,
|
||||
// items: [
|
||||
// {
|
||||
// title: "Form Elements",
|
||||
// url: "/forms/form-elements",
|
||||
// },
|
||||
// {
|
||||
// title: "Form Layout",
|
||||
// url: "/forms/form-layout",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// title: "Tables",
|
||||
// url: "/tables",
|
||||
// icon: Icons.Table,
|
||||
// items: [
|
||||
// {
|
||||
// title: "Tables",
|
||||
// url: "/tables",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// title: "Pages",
|
||||
// icon: Icons.Alphabet,
|
||||
// items: [
|
||||
// {
|
||||
// title: "Settings",
|
||||
// url: "/pages/settings",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// label: "OTHERS",
|
||||
// items: [
|
||||
// {
|
||||
// title: "Charts",
|
||||
// icon: Icons.PieChart,
|
||||
// items: [
|
||||
// {
|
||||
// title: "Basic Chart",
|
||||
// url: "/charts/basic-chart",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// title: "UI Elements",
|
||||
// icon: Icons.FourCircle,
|
||||
// items: [
|
||||
// {
|
||||
// title: "Alerts",
|
||||
// url: "/ui-elements/alerts",
|
||||
// },
|
||||
// {
|
||||
// title: "Buttons",
|
||||
// url: "/ui-elements/buttons",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// title: "Authentication",
|
||||
// icon: Icons.Authentication,
|
||||
// items: [
|
||||
// {
|
||||
// title: "Sign In",
|
||||
// url: "/auth/sign-in",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
];
|
||||
@@ -0,0 +1,7 @@
|
||||
export const UI_ELEMENTS = ["Alerts", "Buttons"]
|
||||
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
|
||||
.map((value) => ({
|
||||
title: value,
|
||||
url: "/ui-elements/" + value.split(" ").join("-").toLowerCase(),
|
||||
isPro: !["Alerts", "Buttons"].includes(value),
|
||||
}));
|
||||
@@ -0,0 +1,202 @@
|
||||
import { SVGProps } from "react";
|
||||
|
||||
export type PropsType = SVGProps<SVGSVGElement>;
|
||||
|
||||
export function ChevronUp(props: PropsType) {
|
||||
return (
|
||||
<svg
|
||||
width={16}
|
||||
height={8}
|
||||
viewBox="0 0 16 8"
|
||||
fill="currentColor"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.553.728a.687.687 0 01.895 0l6.416 5.5a.688.688 0 01-.895 1.044L8 2.155 2.03 7.272a.688.688 0 11-.894-1.044l6.417-5.5z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function Building(props: PropsType) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 -960 960 960"
|
||||
fill="currentColor"
|
||||
{...props}
|
||||
>
|
||||
<path d="M80-120v-720h400v160h400v560H80Zm80-80h240v-80H160v80Zm0-160h240v-80H160v80Zm0-160h240v-80H160v80Zm0-160h240v-80H160v80Zm320 480h320v-400H480v400Zm80-240v-80h160v80H560Zm0 160v-80h160v80H560Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
export function HomeIcon(props: PropsType) {
|
||||
return (
|
||||
<svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
{...props}
|
||||
>
|
||||
<path d="M9 17.25a.75.75 0 000 1.5h6a.75.75 0 000-1.5H9z" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12 1.25c-.725 0-1.387.2-2.11.537-.702.327-1.512.81-2.528 1.415l-1.456.867c-1.119.667-2.01 1.198-2.686 1.706C2.523 6.3 2 6.84 1.66 7.551c-.342.711-.434 1.456-.405 2.325.029.841.176 1.864.36 3.146l.293 2.032c.237 1.65.426 2.959.707 3.978.29 1.05.702 1.885 1.445 2.524.742.64 1.63.925 2.716 1.062 1.056.132 2.387.132 4.066.132h2.316c1.68 0 3.01 0 4.066-.132 1.086-.137 1.974-.422 2.716-1.061.743-.64 1.155-1.474 1.445-2.525.281-1.02.47-2.328.707-3.978l.292-2.032c.185-1.282.332-2.305.36-3.146.03-.87-.062-1.614-.403-2.325C22 6.84 21.477 6.3 20.78 5.775c-.675-.508-1.567-1.039-2.686-1.706l-1.456-.867c-1.016-.605-1.826-1.088-2.527-1.415-.724-.338-1.386-.537-2.111-.537zM8.096 4.511c1.057-.63 1.803-1.073 2.428-1.365.609-.284 1.047-.396 1.476-.396.43 0 .867.112 1.476.396.625.292 1.37.735 2.428 1.365l1.385.825c1.165.694 1.986 1.184 2.59 1.638.587.443.91.809 1.11 1.225.199.416.282.894.257 1.626-.026.75-.16 1.691-.352 3.026l-.28 1.937c-.246 1.714-.422 2.928-.675 3.845-.247.896-.545 1.415-.977 1.787-.433.373-.994.593-1.925.71-.951.119-2.188.12-3.93.12h-2.213c-1.743 0-2.98-.001-3.931-.12-.93-.117-1.492-.337-1.925-.71-.432-.372-.73-.891-.977-1.787-.253-.917-.43-2.131-.676-3.845l-.279-1.937c-.192-1.335-.326-2.277-.352-3.026-.025-.732.058-1.21.258-1.626.2-.416.521-.782 1.11-1.225.603-.454 1.424-.944 2.589-1.638l1.385-.825z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function Calendar(props: PropsType) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M17 14a1 1 0 100-2 1 1 0 000 2zM17 18a1 1 0 100-2 1 1 0 000 2zM13 13a1 1 0 11-2 0 1 1 0 012 0zM13 17a1 1 0 11-2 0 1 1 0 012 0zM7 14a1 1 0 100-2 1 1 0 000 2zM7 18a1 1 0 100-2 1 1 0 000 2z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7 1.75a.75.75 0 01.75.75v.763c.662-.013 1.391-.013 2.193-.013h4.113c.803 0 1.532 0 2.194.013V2.5a.75.75 0 011.5 0v.827c.26.02.506.045.739.076 1.172.158 2.121.49 2.87 1.238.748.749 1.08 1.698 1.238 2.87.153 1.14.153 2.595.153 4.433v2.112c0 1.838 0 3.294-.153 4.433-.158 1.172-.49 2.121-1.238 2.87-.749.748-1.698 1.08-2.87 1.238-1.14.153-2.595.153-4.433.153H9.944c-1.838 0-3.294 0-4.433-.153-1.172-.158-2.121-.49-2.87-1.238-.748-.749-1.08-1.698-1.238-2.87-.153-1.14-.153-2.595-.153-4.433v-2.112c0-1.838 0-3.294.153-4.433.158-1.172.49-2.121 1.238-2.87.749-.748 1.698-1.08 2.87-1.238.233-.031.48-.056.739-.076V2.5A.75.75 0 017 1.75zM5.71 4.89c-1.005.135-1.585.389-2.008.812-.423.423-.677 1.003-.812 2.009-.023.17-.042.35-.058.539h18.336c-.016-.19-.035-.369-.058-.54-.135-1.005-.389-1.585-.812-2.008-.423-.423-1.003-.677-2.009-.812-1.027-.138-2.382-.14-4.289-.14h-4c-1.907 0-3.261.002-4.29.14zM2.75 12c0-.854 0-1.597.013-2.25h18.474c.013.653.013 1.396.013 2.25v2c0 1.907-.002 3.262-.14 4.29-.135 1.005-.389 1.585-.812 2.008-.423.423-1.003.677-2.009.812-1.027.138-2.382.14-4.289.14h-4c-1.907 0-3.261-.002-4.29-.14-1.005-.135-1.585-.389-2.008-.812-.423-.423-.677-1.003-.812-2.009-.138-1.027-.14-2.382-.14-4.289v-2z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function User(props: PropsType) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12 1.25a4.75 4.75 0 100 9.5 4.75 4.75 0 000-9.5zM8.75 6a3.25 3.25 0 116.5 0 3.25 3.25 0 01-6.5 0zM12 12.25c-2.313 0-4.445.526-6.024 1.414C4.42 14.54 3.25 15.866 3.25 17.5v.102c-.001 1.162-.002 2.62 1.277 3.662.629.512 1.51.877 2.7 1.117 1.192.242 2.747.369 4.773.369s3.58-.127 4.774-.369c1.19-.24 2.07-.605 2.7-1.117 1.279-1.042 1.277-2.5 1.276-3.662V17.5c0-1.634-1.17-2.96-2.725-3.836-1.58-.888-3.711-1.414-6.025-1.414zM4.75 17.5c0-.851.622-1.775 1.961-2.528 1.316-.74 3.184-1.222 5.29-1.222 2.104 0 3.972.482 5.288 1.222 1.34.753 1.961 1.677 1.961 2.528 0 1.308-.04 2.044-.724 2.6-.37.302-.99.597-2.05.811-1.057.214-2.502.339-4.476.339-1.974 0-3.42-.125-4.476-.339-1.06-.214-1.68-.509-2.05-.81-.684-.557-.724-1.293-.724-2.601z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function Alphabet(props: PropsType) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M2.25 7A.75.75 0 013 6.25h10a.75.75 0 010 1.5H3A.75.75 0 012.25 7zm14.25-.75a.75.75 0 01.684.442l4.5 10a.75.75 0 11-1.368.616l-1.437-3.194H14.12l-1.437 3.194a.75.75 0 11-1.368-.616l4.5-10a.75.75 0 01.684-.442zm-1.704 6.364h3.408L16.5 8.828l-1.704 3.786zM2.25 12a.75.75 0 01.75-.75h7a.75.75 0 010 1.5H3a.75.75 0 01-.75-.75zm0 5a.75.75 0 01.75-.75h5a.75.75 0 010 1.5H3a.75.75 0 01-.75-.75z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function Table(props: PropsType) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M18.29 4.89c-1.028-.138-2.383-.14-4.29-.14h-4c-1.907 0-3.261.002-4.29.14-1.005.135-1.585.389-2.008.812-.423.423-.677 1.003-.812 2.009-.138 1.028-.14 2.382-.14 4.289 0 1.907.002 3.261.14 4.29.135 1.005.389 1.585.812 2.008.423.423 1.003.677 2.009.812 1.028.138 2.382.14 4.289.14h4c1.907 0 3.262-.002 4.29-.14 1.005-.135 1.585-.389 2.008-.812.423-.423.677-1.003.812-2.009.138-1.028.14-2.382.14-4.289 0-1.907-.002-3.261-.14-4.29-.135-1.005-.389-1.585-.812-2.008-.423-.423-1.003-.677-2.009-.812zm.199-1.487c1.172.158 2.121.49 2.87 1.238.748.749 1.08 1.698 1.238 2.87.153 1.14.153 2.595.153 4.433v.112c0 1.838 0 3.294-.153 4.433-.158 1.172-.49 2.121-1.238 2.87-.749.748-1.698 1.08-2.87 1.238-1.14.153-2.595.153-4.433.153H9.944c-1.838 0-3.294 0-4.433-.153-1.172-.158-2.121-.49-2.87-1.238-.748-.749-1.08-1.698-1.238-2.87-.153-1.14-.153-2.595-.153-4.433v-.112c0-1.838 0-3.294.153-4.433.158-1.172.49-2.121 1.238-2.87.749-.748 1.698-1.08 2.87-1.238 1.14-.153 2.595-.153 4.433-.153h4.112c1.838 0 3.294 0 4.433.153zM8.25 17a.75.75 0 01.75-.75h6a.75.75 0 010 1.5H9a.75.75 0 01-.75-.75z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function PieChart(props: PropsType) {
|
||||
return (
|
||||
<svg width={24} height={24} viewBox="0 0 24 24" fill="none" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M14.254 1.365c-1.096-.306-2.122.024-2.851.695-.719.66-1.153 1.646-1.153 2.7v6.695a2.295 2.295 0 002.295 2.295h6.694c1.055 0 2.042-.434 2.701-1.153.67-.729 1.001-1.755.695-2.851a12.102 12.102 0 00-8.38-8.381zM11.75 4.76c0-.652.27-1.232.668-1.597.386-.355.886-.508 1.433-.355 3.55.991 6.349 3.79 7.34 7.34.153.548 0 1.048-.355 1.434-.365.397-.945.667-1.597.667h-6.694a.795.795 0 01-.795-.795V4.761z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M8.672 4.716a.75.75 0 00-.45-1.432C4.183 4.554 1.25 8.328 1.25 12.79c0 5.501 4.46 9.961 9.96 9.961 4.462 0 8.236-2.932 9.505-6.973a.75.75 0 10-1.43-.45 8.465 8.465 0 01-8.074 5.923 8.46 8.46 0 01-8.461-8.46 8.465 8.465 0 015.922-8.074z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function FourCircle(props: PropsType) {
|
||||
return (
|
||||
<svg width={24} height={24} viewBox="0 0 24 24" fill="none" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M6.5 1.75a4.75 4.75 0 100 9.5 4.75 4.75 0 000-9.5zM3.25 6.5a3.25 3.25 0 116.5 0 3.25 3.25 0 01-6.5 0zM17.5 12.75a4.75 4.75 0 100 9.5 4.75 4.75 0 000-9.5zm-3.25 4.75a3.25 3.25 0 116.5 0 3.25 3.25 0 01-6.5 0zM12.75 6.5a4.75 4.75 0 119.5 0 4.75 4.75 0 01-9.5 0zm4.75-3.25a3.25 3.25 0 100 6.5 3.25 3.25 0 000-6.5zM6.5 12.75a4.75 4.75 0 100 9.5 4.75 4.75 0 000-9.5zM3.25 17.5a3.25 3.25 0 116.5 0 3.25 3.25 0 01-6.5 0z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function Authentication(props: PropsType) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
{...props}
|
||||
>
|
||||
<path d="M14.945 1.25c-1.367 0-2.47 0-3.337.117-.9.12-1.658.38-2.26.981-.524.525-.79 1.17-.929 1.928-.135.737-.161 1.638-.167 2.72a.75.75 0 001.5.008c.006-1.093.034-1.868.142-2.457.105-.566.272-.895.515-1.138.277-.277.666-.457 1.4-.556.755-.101 1.756-.103 3.191-.103h1c1.436 0 2.437.002 3.192.103.734.099 1.122.28 1.4.556.276.277.456.665.555 1.4.102.754.103 1.756.103 3.191v8c0 1.435-.001 2.436-.103 3.192-.099.734-.279 1.122-.556 1.399-.277.277-.665.457-1.399.556-.755.101-1.756.103-3.192.103h-1c-1.435 0-2.436-.002-3.192-.103-.733-.099-1.122-.28-1.399-.556-.243-.244-.41-.572-.515-1.138-.108-.589-.136-1.364-.142-2.457a.75.75 0 10-1.5.008c.006 1.082.032 1.983.167 2.72.14.758.405 1.403.93 1.928.601.602 1.36.86 2.26.982.866.116 1.969.116 3.336.116h1.11c1.368 0 2.47 0 3.337-.116.9-.122 1.658-.38 2.26-.982.602-.602.86-1.36.982-2.26.116-.867.116-1.97.116-3.337v-8.11c0-1.367 0-2.47-.116-3.337-.121-.9-.38-1.658-.982-2.26-.602-.602-1.36-.86-2.26-.981-.867-.117-1.97-.117-3.337-.117h-1.11z" />
|
||||
<path d="M2.001 11.249a.75.75 0 000 1.5h11.973l-1.961 1.68a.75.75 0 10.976 1.14l3.5-3a.75.75 0 000-1.14l-3.5-3a.75.75 0 00-.976 1.14l1.96 1.68H2.002z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function ArrowLeftIcon(props: PropsType) {
|
||||
return (
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="currentColor"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.89775 4.10225C8.11742 4.32192 8.11742 4.67808 7.89775 4.89775L4.358 8.4375H15C15.3107 8.4375 15.5625 8.68934 15.5625 9C15.5625 9.31066 15.3107 9.5625 15 9.5625H4.358L7.89775 13.1023C8.11742 13.3219 8.11742 13.6781 7.89775 13.8977C7.67808 14.1174 7.32192 14.1174 7.10225 13.8977L2.60225 9.39775C2.38258 9.17808 2.38258 8.82192 2.60225 8.60225L7.10225 4.10225C7.32192 3.88258 7.67808 3.88258 7.89775 4.10225Z"
|
||||
fill=""
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
"use client";
|
||||
|
||||
import { Logo } from "@/components/logo";
|
||||
import { cn } from "@/lib/utils";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { NAV_DATA, NavSubItem } from "./data";
|
||||
import { ArrowLeftIcon, ChevronUp } from "./icons";
|
||||
import { MenuItem } from "./menu-item";
|
||||
import { useSidebarContext } from "./sidebar-context";
|
||||
import Image from "next/image";
|
||||
|
||||
export function Sidebar() {
|
||||
const pathname = usePathname();
|
||||
const { setIsOpen, isOpen, isMobile, toggleSidebar } = useSidebarContext();
|
||||
const [expandedItems, setExpandedItems] = useState<string[]>([]);
|
||||
|
||||
const toggleExpanded = (title: string) => {
|
||||
// setExpandedItems((prev) => (prev.includes(title) ? [] : [title]));
|
||||
|
||||
// Uncomment the following line to enable multiple expanded items
|
||||
setExpandedItems((prev) =>
|
||||
prev.includes(title) ? prev.filter((t) => t !== title) : [...prev, title],
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Keep collapsible open, when it's subpage is active
|
||||
NAV_DATA.some((section) => {
|
||||
return section.items.some((item) => {
|
||||
return item.items.some((subItem: NavSubItem) => {
|
||||
if (subItem.url === pathname) {
|
||||
if (!expandedItems.includes(item.title)) {
|
||||
toggleExpanded(item.title);
|
||||
}
|
||||
|
||||
// Break the loop
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}, [pathname]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Mobile Overlay */}
|
||||
{isMobile && isOpen && (
|
||||
<div
|
||||
className="fixed inset-0 z-40 bg-black/50 transition-opacity duration-300"
|
||||
onClick={() => setIsOpen(false)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
|
||||
<aside
|
||||
className={cn(
|
||||
"max-w-[290px] overflow-hidden border-r border-gray-200 bg-white transition-[width] duration-200 ease-linear dark:border-gray-800 dark:bg-gray-dark",
|
||||
isMobile ? "fixed bottom-0 top-0 z-50" : "sticky top-0 h-screen",
|
||||
isOpen ? "w-full" : "w-0",
|
||||
)}
|
||||
aria-label="Main navigation"
|
||||
aria-hidden={!isOpen}
|
||||
inert={!isOpen}
|
||||
>
|
||||
<div className="flex h-full flex-col py-10 pl-[25px] pr-[7px]">
|
||||
<div className="relative pr-4.5">
|
||||
<Link
|
||||
href={"/"}
|
||||
onClick={() => isMobile && toggleSidebar()}
|
||||
className="px-0 py-2.5 min-[850px]:py-0"
|
||||
>
|
||||
<Logo />
|
||||
</Link>
|
||||
|
||||
{isMobile && (
|
||||
<button
|
||||
onClick={toggleSidebar}
|
||||
className="absolute left-3/4 right-4.5 top-1/2 -translate-y-1/2 text-right"
|
||||
>
|
||||
<span className="sr-only">Close Menu</span>
|
||||
|
||||
<ArrowLeftIcon className="ml-auto size-7" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="custom-scrollbar mt-6 flex-1 overflow-y-auto pr-3 min-[850px]:mt-10">
|
||||
{NAV_DATA.map((section) => (
|
||||
<div key={section.label} className="mb-6">
|
||||
<h2 className="mb-5 text-sm font-medium text-dark-4 dark:text-dark-6">
|
||||
{section.label}
|
||||
</h2>
|
||||
|
||||
<nav role="navigation" aria-label={section.label}>
|
||||
<ul className="space-y-2">
|
||||
{section.items.map((item) => (
|
||||
<li key={item.title}>
|
||||
{item.items.length ? (
|
||||
<div>
|
||||
<MenuItem
|
||||
isActive={item.items.some(
|
||||
({ url }: { url: string }) => url === pathname,
|
||||
)}
|
||||
onClick={() => toggleExpanded(item.title)}
|
||||
>
|
||||
<item.icon
|
||||
className="size-6 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
<span>{item.title}</span>
|
||||
|
||||
<ChevronUp
|
||||
className={cn(
|
||||
"ml-auto rotate-180 transition-transform duration-200",
|
||||
expandedItems.includes(item.title) &&
|
||||
"rotate-0",
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</MenuItem>
|
||||
|
||||
{expandedItems.includes(item.title) && (
|
||||
<ul
|
||||
className="ml-9 mr-0 space-y-1.5 pb-[15px] pr-0 pt-2"
|
||||
role="menu"
|
||||
>
|
||||
{item.items.map((subItem: NavSubItem) => (
|
||||
<li key={subItem.title} role="none">
|
||||
<MenuItem
|
||||
as="link"
|
||||
href={subItem.url}
|
||||
isActive={pathname === subItem.url}
|
||||
>
|
||||
<span>{subItem.title}</span>
|
||||
</MenuItem>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
(() => {
|
||||
const href =
|
||||
"url" in item
|
||||
? item.url + ""
|
||||
: "/" +
|
||||
item.title.toLowerCase().split(" ").join("-");
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
className="flex items-center gap-3 py-3"
|
||||
as="link"
|
||||
href={href}
|
||||
isActive={pathname === href}
|
||||
>
|
||||
<item.icon
|
||||
className="size-6 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
<span>{item.title}</span>
|
||||
</MenuItem>
|
||||
);
|
||||
})()
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cva } from "class-variance-authority";
|
||||
import Link from "next/link";
|
||||
import { useSidebarContext } from "./sidebar-context";
|
||||
|
||||
const menuItemBaseStyles = cva(
|
||||
"rounded-lg px-3.5 font-medium text-dark-4 transition-all duration-200 dark:text-dark-6",
|
||||
{
|
||||
variants: {
|
||||
isActive: {
|
||||
true: "bg-[rgba(87,80,241,0.07)] text-primary hover:bg-[rgba(87,80,241,0.07)] dark:bg-[#FFFFFF1A] dark:text-white",
|
||||
false:
|
||||
"hover:bg-gray-100 hover:text-dark hover:dark:bg-[#FFFFFF1A] hover:dark:text-white",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
isActive: false,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export function MenuItem(
|
||||
props: {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
isActive: boolean;
|
||||
} & ({ as?: "button"; onClick: () => void } | { as: "link"; href: string }),
|
||||
) {
|
||||
const { toggleSidebar, isMobile } = useSidebarContext();
|
||||
|
||||
if (props.as === "link") {
|
||||
return (
|
||||
<Link
|
||||
href={props.href}
|
||||
// Close sidebar on clicking link if it's mobile
|
||||
onClick={() => isMobile && toggleSidebar()}
|
||||
className={cn(
|
||||
menuItemBaseStyles({
|
||||
isActive: props.isActive,
|
||||
className: "relative block py-2",
|
||||
}),
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={props.onClick}
|
||||
aria-expanded={props.isActive}
|
||||
className={menuItemBaseStyles({
|
||||
isActive: props.isActive,
|
||||
className: "flex w-full items-center gap-3 py-3",
|
||||
})}
|
||||
>
|
||||
{props.children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
"use client";
|
||||
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
import { createContext, useContext, useEffect, useState } from "react";
|
||||
|
||||
type SidebarState = "expanded" | "collapsed";
|
||||
|
||||
type SidebarContextType = {
|
||||
state: SidebarState;
|
||||
isOpen: boolean;
|
||||
setIsOpen: (open: boolean) => void;
|
||||
isMobile: boolean;
|
||||
toggleSidebar: () => void;
|
||||
};
|
||||
|
||||
const SidebarContext = createContext<SidebarContextType | null>(null);
|
||||
|
||||
export function useSidebarContext() {
|
||||
const context = useContext(SidebarContext);
|
||||
if (!context) {
|
||||
throw new Error("useSidebarContext must be used within a SidebarProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
export function SidebarProvider({
|
||||
children,
|
||||
defaultOpen = true,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
defaultOpen?: boolean;
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(defaultOpen);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
useEffect(() => {
|
||||
if (isMobile) {
|
||||
setIsOpen(false);
|
||||
} else {
|
||||
setIsOpen(true);
|
||||
}
|
||||
}, [isMobile]);
|
||||
|
||||
function toggleSidebar() {
|
||||
setIsOpen((prev) => !prev);
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarContext.Provider
|
||||
value={{
|
||||
state: isOpen ? "expanded" : "collapsed",
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
isMobile,
|
||||
toggleSidebar,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SidebarContext.Provider>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user