diff --git a/dashboard/src/components/sidebar/sidebar-item.tsx b/dashboard/src/components/sidebar/sidebar-item.tsx
@@ -1,38 +1,50 @@
-
-import { Link } from '@tanstack/react-router'
-import { FC } from 'react'
-import { SidebarItem as SidebarItemType } from './types'
-import { cn } from '@marzneshin/utils';
-import { VariantProps, cva } from 'class-variance-authority';
-import { useSidebarContext } from './sidebar-provider';
+import { Link } from "@tanstack/react-router";
+import type { FC } from "react";
+import type { SidebarItem as SidebarItemType } from "./types";
+import { cn } from "@marzneshin/utils";
+import { type VariantProps, cva } from "class-variance-authority";
+import { useSidebarContext } from "./sidebar-provider";
const sidebarItemVariants = cva("w-full rounded-lg border p-2", {
variants: {
variant: {
default: "bg-primary-foreground text-primary hover:bg-accent",
- active: "bg-primary text-primary-foreground"
+ active: "bg-primary text-primary-foreground",
},
size: {
default: "",
- collapsed: ""
- }
- }
+ collapsed: "",
+ },
+ },
});
-
-export interface SidebarItemProps extends
- React.HTMLAttributes<HTMLLinkElement>,
+export interface SidebarItemProps
+ extends React.HTMLAttributes<HTMLLinkElement>,
VariantProps<typeof sidebarItemVariants> {
item: SidebarItemType;
}
-export const SidebarItem: FC<SidebarItemProps> = ({ item, className, variant }) => {
- const { collapsed } = useSidebarContext();
+export const SidebarItem: FC<SidebarItemProps> = ({
+ item,
+ className,
+ variant,
+}) => {
+ const { collapsed, setOpen } = useSidebarContext();
return (
- <li key={item.title} className={cn(sidebarItemVariants({ variant, className }))}>
- <Link to={item.to} className={cn("flex flex-row items-center justify-center", { "-justify-center gap-2": !collapsed })}>
- {item.icon}{!collapsed && item.title}
+ <li
+ key={item.title}
+ className={cn(sidebarItemVariants({ variant, className }))}
+ >
+ <Link
+ to={item.to}
+ onClick={() => setOpen?.(false)}
+ className={cn("flex flex-row items-center justify-center", {
+ "-justify-center gap-2": !collapsed,
+ })}
+ >
+ {item.icon}
+ {!collapsed && item.title}
</Link>
</li>
- )
-}
+ );
+};
diff --git a/dashboard/src/components/sidebar/sidebar-provider.tsx b/dashboard/src/components/sidebar/sidebar-provider.tsx
@@ -1,10 +1,12 @@
import { createContext, useContext } from "react";
-import { SidebarItemGroup } from "./types";
+import type { SidebarItemGroup } from "./types";
interface SidebarContextProps {
sidebar: SidebarItemGroup;
collapsed: boolean;
setCollapsed: (state: boolean) => void;
+ open?: boolean;
+ setOpen?: (state: boolean) => void;
}
export const SidebarContext = createContext<SidebarContextProps | null>(null);
@@ -12,6 +14,8 @@ export const SidebarContext = createContext<SidebarContextProps | null>(null);
export const useSidebarContext = () => {
const ctx = useContext(SidebarContext);
if (!ctx)
- throw new Error('Sidebar.* component must be rendered as child of Sidebar ');
+ throw new Error(
+ "Sidebar.* component must be rendered as child of Sidebar ",
+ );
return ctx;
-}
+};
diff --git a/dashboard/src/components/sidebar/sidebar.tsx b/dashboard/src/components/sidebar/sidebar.tsx
@@ -1,7 +1,8 @@
-import { FC, PropsWithChildren } from "react";
-import { SidebarContext } from "./sidebar-provider"
-import { SidebarItemGroup } from "./types"
-import { VariantProps, cva } from "class-variance-authority"
+import type { FC, PropsWithChildren } from "react";
+import { useMemo } from "react";
+import { SidebarContext } from "./sidebar-provider";
+import type { SidebarItemGroup } from "./types";
+import { type VariantProps, cva } from "class-variance-authority";
import { cn } from "@marzneshin/utils";
import { SidebarHeader } from "./sidebar-header";
import { SidebarFooter } from "./sidebar-footer";
@@ -10,45 +11,60 @@ import { SidebarItem } from "./sidebar-item";
import { SidebarGroup } from "./sidebar-group";
const sidebarVariants = cva("flex flex-col justify-between h-full w-full", {
- variants: {
- variant: {
- default: ""
- }
- }
+ variants: {
+ variant: {
+ default: "",
+ },
+ },
});
interface ContentComposition {
- Header: typeof SidebarHeader;
- Footer: typeof SidebarFooter;
- Body: typeof SidebarBody;
- Group: typeof SidebarGroup;
- Item: typeof SidebarItem;
+ Header: typeof SidebarHeader;
+ Footer: typeof SidebarFooter;
+ Body: typeof SidebarBody;
+ Group: typeof SidebarGroup;
+ Item: typeof SidebarItem;
}
export interface SidebarProps
- extends React.HTMLAttributes<HTMLDivElement>,
+ extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof sidebarVariants> {
- sidebar: SidebarItemGroup;
- collapsed: boolean;
- setCollapsed: (state: boolean) => void;
+ sidebar: SidebarItemGroup;
+ collapsed: boolean;
+ setCollapsed: (state: boolean) => void;
+ open?: boolean;
+ setOpen?: (state: boolean) => void;
}
-const Sidebar: FC<SidebarProps & PropsWithChildren> & ContentComposition =
- ({ collapsed, setCollapsed, sidebar, variant, className, children, ...props }) => {
- return (
- <SidebarContext.Provider value={{ sidebar, collapsed, setCollapsed }} >
- <div className={cn(sidebarVariants({ variant, className }))} {...props}>
- {children}
- </div>
- </SidebarContext.Provider>
- )
- }
+const Sidebar: FC<SidebarProps & PropsWithChildren> & ContentComposition = ({
+ collapsed,
+ setCollapsed,
+ sidebar,
+ variant,
+ className,
+ setOpen,
+ open,
+ children,
+ ...props
+}) => {
+ const contextValue = useMemo(
+ () => ({ sidebar, collapsed, setCollapsed, open, setOpen }),
+ [sidebar, collapsed, setCollapsed, open, setOpen],
+ );
+ return (
+ <SidebarContext.Provider value={contextValue}>
+ <div className={cn(sidebarVariants({ variant, className }))} {...props}>
+ {children}
+ </div>
+ </SidebarContext.Provider>
+ );
+};
Sidebar.Header = SidebarHeader;
Sidebar.Footer = SidebarFooter;
Sidebar.Body = SidebarBody;
Sidebar.Item = SidebarItem;
Sidebar.Group = SidebarGroup;
-Sidebar.displayName = "Sidebar"
+Sidebar.displayName = "Sidebar";
-export { Sidebar, sidebarVariants }
+export { Sidebar, sidebarVariants };
diff --git a/dashboard/src/features/sidebar/sidebar.tsx b/dashboard/src/features/sidebar/sidebar.tsx
@@ -5,48 +5,54 @@ import { sidebarItems } from ".";
import { cn } from "@marzneshin/utils";
interface DashboardSidebarProps {
- collapsed: boolean;
- setCollapsed: (state: boolean) => void;
+ collapsed: boolean;
+ setCollapsed: (state: boolean) => void;
+ open?: boolean;
+ setOpen?: (state: boolean) => void;
}
export const DashboardSidebar: FC<DashboardSidebarProps> = ({
- collapsed,
- setCollapsed,
+ collapsed,
+ setCollapsed,
+ setOpen,
+ open,
}) => {
- const router = useRouterState();
- const currentActivePath = router.location.pathname;
+ const router = useRouterState();
+ const currentActivePath = router.location.pathname;
- return (
- <aside>
- <nav className="w-full">
- <Sidebar
- sidebar={sidebarItems}
- collapsed={collapsed}
- setCollapsed={setCollapsed}
- >
- <div className="flex flex-col px-4 w-full h-full">
- <Sidebar.Body className="my-3">
- {Object.keys(sidebarItems).map((key) => (
- <div className="w-full" key={key}>
- <Sidebar.Group>{key}</Sidebar.Group>
- {sidebarItems[key].map((item: SidebarItem) => (
- <Sidebar.Item
- variant={
- currentActivePath === item.to ? "active" : "default"
- }
- className={cn("my-2 border-transparent", {
- "w-10 h-10": collapsed,
- })}
- item={item}
- key={item.title}
- />
- ))}
- </div>
- ))}
- </Sidebar.Body>
- </div>
- </Sidebar>
- </nav>
- </aside>
- );
+ return (
+ <aside>
+ <nav className="w-full">
+ <Sidebar
+ sidebar={sidebarItems}
+ collapsed={collapsed}
+ setCollapsed={setCollapsed}
+ open={open}
+ setOpen={setOpen}
+ >
+ <div className="flex flex-col px-4 w-full h-full">
+ <Sidebar.Body className="my-3">
+ {Object.keys(sidebarItems).map((key) => (
+ <div className="w-full" key={key}>
+ <Sidebar.Group>{key}</Sidebar.Group>
+ {sidebarItems[key].map((item: SidebarItem) => (
+ <Sidebar.Item
+ variant={
+ currentActivePath === item.to ? "active" : "default"
+ }
+ className={cn("my-2 border-transparent", {
+ "w-10 h-10": collapsed,
+ })}
+ item={item}
+ key={item.title}
+ />
+ ))}
+ </div>
+ ))}
+ </Sidebar.Body>
+ </div>
+ </Sidebar>
+ </nav>
+ </aside>
+ );
};
diff --git a/dashboard/src/features/sidebar/use-panel-toggle.ts b/dashboard/src/features/sidebar/use-panel-toggle.ts
@@ -1,5 +1,5 @@
import { useRef, useState } from "react";
-import { ImperativePanelHandle } from "react-resizable-panels";
+import type { ImperativePanelHandle } from "react-resizable-panels";
export const usePanelToggle = (isDesktop: boolean) => {
const [collapsed, setCollapsed] = useState(false);
@@ -22,5 +22,13 @@ export const usePanelToggle = (isDesktop: boolean) => {
}
};
- return { collapsed, open, panelRef, setCollapsed, setOpen, toggleCollapse, toggleOpen };
+ return {
+ collapsed,
+ open,
+ panelRef,
+ setCollapsed,
+ setOpen,
+ toggleCollapse,
+ toggleOpen,
+ };
};
diff --git a/dashboard/src/routes/_dashboard.tsx b/dashboard/src/routes/_dashboard.tsx
@@ -1,103 +1,102 @@
import {
- Drawer,
- DrawerContent,
- Header,
- ResizableHandle,
- ResizablePanel,
- ResizablePanelGroup,
- Toaster,
+ Drawer,
+ DrawerContent,
+ Header,
+ ResizableHandle,
+ ResizablePanel,
+ ResizablePanelGroup,
+ Toaster,
} from "@marzneshin/components";
import { useAuth } from "@marzneshin/features/auth";
-import {
- DashboardSidebar,
- ToggleButton,
-} from "@marzneshin/features/sidebar";
+import { DashboardSidebar, ToggleButton } from "@marzneshin/features/sidebar";
import { usePanelToggle } from "@marzneshin/features/sidebar/use-panel-toggle";
import { useScreenBreakpoint } from "@marzneshin/hooks/use-screen-breakpoint";
import { cn } from "@marzneshin/utils";
import { Outlet, createFileRoute, redirect } from "@tanstack/react-router";
export const DashboardLayout = () => {
- const isDesktop = useScreenBreakpoint("md");
- const {
- collapsed,
- panelRef,
- open,
- setCollapsed,
- setOpen,
- toggleCollapse,
- toggleOpen,
- } = usePanelToggle(isDesktop);
+ const isDesktop = useScreenBreakpoint("md");
+ const {
+ collapsed,
+ panelRef,
+ open,
+ setCollapsed,
+ setOpen,
+ toggleCollapse,
+ toggleOpen,
+ } = usePanelToggle(isDesktop);
- return (
- <div className="flex flex-col w-screen h-screen">
- <header className="h-[3.5rem]">
- <Header>
- <ToggleButton
- isDesktop={isDesktop}
- collapsed={collapsed}
- open={open}
- onToggle={isDesktop ? toggleCollapse : toggleOpen}
- />
- </Header>
- </header>
- {isDesktop ? (
- <ResizablePanelGroup direction="horizontal" className="block sm:hidden">
- <ResizablePanel
- collapsible
- collapsedSize={2}
- onCollapse={() => setCollapsed(true)}
- onExpand={() => setCollapsed(false)}
- minSize={15}
- className={cn("w-[120px] min-w-[70px]")}
- defaultSize={20}
- ref={panelRef}
- maxSize={30}
- >
- <DashboardSidebar
- collapsed={collapsed}
- setCollapsed={setCollapsed}
- />
- </ResizablePanel>
- <ResizableHandle withHandle className="w-[2px]" />
- <ResizablePanel>
- <main>
- <Outlet />
- <Toaster position="top-center" />
- </main>
- </ResizablePanel>
- </ResizablePanelGroup>
- ) : (
- <div>
- <aside>
- <Drawer open={open} onOpenChange={setOpen}>
- <DrawerContent>
- <DashboardSidebar
- collapsed={collapsed}
- setCollapsed={setCollapsed}
- />
- </DrawerContent>
- </Drawer>
- </aside>
- <main className="sm:block">
- <Outlet />
- <Toaster position="top-center" />
- </main>
- </div>
- )}
+ return (
+ <div className="flex flex-col w-screen h-screen">
+ <header className="h-[3.5rem]">
+ <Header>
+ <ToggleButton
+ isDesktop={isDesktop}
+ collapsed={collapsed}
+ open={open}
+ onToggle={isDesktop ? toggleCollapse : toggleOpen}
+ />
+ </Header>
+ </header>
+ {isDesktop ? (
+ <ResizablePanelGroup direction="horizontal" className="block sm:hidden">
+ <ResizablePanel
+ collapsible
+ collapsedSize={2}
+ onCollapse={() => setCollapsed(true)}
+ onExpand={() => setCollapsed(false)}
+ minSize={15}
+ className={cn("w-[120px] min-w-[70px]")}
+ defaultSize={20}
+ ref={panelRef}
+ maxSize={30}
+ >
+ <DashboardSidebar
+ collapsed={collapsed}
+ setCollapsed={setCollapsed}
+ />
+ </ResizablePanel>
+ <ResizableHandle withHandle className="w-[2px]" />
+ <ResizablePanel>
+ <main>
+ <Outlet />
+ <Toaster position="top-center" />
+ </main>
+ </ResizablePanel>
+ </ResizablePanelGroup>
+ ) : (
+ <div>
+ <aside>
+ <Drawer open={open} onOpenChange={setOpen}>
+ <DrawerContent>
+ <DashboardSidebar
+ collapsed={collapsed}
+ setCollapsed={setCollapsed}
+ setOpen={setOpen}
+ open={open}
+ />
+ </DrawerContent>
+ </Drawer>
+ </aside>
+ <main className="sm:block">
+ <Outlet />
+ <Toaster position="top-center" />
+ </main>
</div>
- );
+ )}
+ </div>
+ );
};
export const Route = createFileRoute("/_dashboard")({
- component: () => <DashboardLayout />,
+ component: () => <DashboardLayout />,
- beforeLoad: async () => {
- const loggedIn = await useAuth.getState().isLoggedIn();
- if (!loggedIn) {
- throw redirect({
- to: "/login",
- });
- }
- },
+ beforeLoad: async () => {
+ const loggedIn = await useAuth.getState().isLoggedIn();
+ if (!loggedIn) {
+ throw redirect({
+ to: "/login",
+ });
+ }
+ },
});