Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(dashboard): collapsible sidebar
Browse files Browse the repository at this point in the history
grutt committed Feb 7, 2024

Verified

This commit was signed with the committer’s verified signature.
1 parent 6e700db commit 296c447
Showing 5 changed files with 112 additions and 12 deletions.
10 changes: 8 additions & 2 deletions frontend/app/src/components/molecules/nav-bar/nav-bar.tsx
Original file line number Diff line number Diff line change
@@ -14,6 +14,8 @@ import { useNavigate } from 'react-router-dom';
import api, { User } from '@/lib/api';
import { useApiError } from '@/lib/hooks';
import { useMutation } from '@tanstack/react-query';
import hatchet from '@/assets/hatchet_logo.png';
import { useSidebar } from '@/components/sidebar-provider';

interface MainNavProps {
user: User;
@@ -22,6 +24,7 @@ interface MainNavProps {
export default function MainNav({ user }: MainNavProps) {
const navigate = useNavigate();
const { handleApiError } = useApiError({});
const { toggleSidebarOpen } = useSidebar();

const logoutMutation = useMutation({
mutationKey: ['user:update:logout'],
@@ -35,8 +38,11 @@ export default function MainNav({ user }: MainNavProps) {
});

return (
<div className="fixed top-0 w-screen h-16">
<div className="flex h-16 items-center pr-4 pl-7">
<div className="fixed top-0 w-screen h-16 border-b">
<div className="flex h-16 items-center pr-4 pl-4">
<button onClick={() => toggleSidebarOpen()}>
<img src={hatchet} alt="Hatchet" className="h-9 rounded" />
</button>
<div className="ml-auto flex items-center space-x-4">
<DropdownMenu>
<DropdownMenuTrigger asChild>
86 changes: 86 additions & 0 deletions frontend/app/src/components/sidebar-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {
PropsWithChildren,
createContext,
useContext,
useEffect,
useState,
} from 'react';

type SidebarState = 'open' | 'closed';

type SidebarProviderProps = PropsWithChildren & {
defaultSidebarOpen?: SidebarState;
};

type SidebarProviderState = {
sidebarOpen: SidebarState;
setSidebarOpen: (open: SidebarState) => void;
toggleSidebarOpen: () => void;
};

const initialState: SidebarProviderState = {
sidebarOpen: 'closed',
setSidebarOpen: () => null,
toggleSidebarOpen: () => null,
};

const SidebarProviderContext =
createContext<SidebarProviderState>(initialState);

export function SidebarProvider({
children,
defaultSidebarOpen = 'closed',

...props
}: SidebarProviderProps) {
const [sidebarOpen, setSidebarOpen] = useState<SidebarState>(
() => defaultSidebarOpen,
);

useEffect(() => {
const handleResize = () => {
if (window.innerWidth >= 768) {
console.log('open');
setSidebarOpen('open');
} else {
console.log('closed');
setSidebarOpen('closed');
}
};

handleResize();

window.addEventListener('resize', handleResize);

return () => {
window.removeEventListener('resize', handleResize);
};
}, []);

return (
<SidebarProviderContext.Provider
{...props}
value={{
sidebarOpen,
setSidebarOpen: (open: SidebarState) => {
setSidebarOpen(open);
},
toggleSidebarOpen: () => {
setSidebarOpen((state) => (state === 'open' ? 'closed' : 'open'));
},
}}
>
{children}
</SidebarProviderContext.Provider>
);
}

export const useSidebar = () => {
const context = useContext(SidebarProviderContext);

if (context === undefined) {
throw new Error('useSidebar must be used within a SidebarProvider');
}

return context;
};
2 changes: 1 addition & 1 deletion frontend/app/src/pages/authenticated.tsx
Original file line number Diff line number Diff line change
@@ -109,7 +109,7 @@ export default function Authenticated() {
return (
<div className="flex flex-row flex-1 w-full h-full">
<MainNav user={user} />
<div className="pt-12 flex-grow overflow-y-auto overflow-x-hidden">
<div className="pt-16 flex-grow overflow-y-auto overflow-x-hidden">
<Outlet context={ctx} />
</div>
</div>
15 changes: 10 additions & 5 deletions frontend/app/src/pages/main/index.tsx
Original file line number Diff line number Diff line change
@@ -9,7 +9,6 @@ import {
ServerStackIcon,
Squares2X2Icon,
} from '@heroicons/react/24/outline';
import hatchet from '@/assets/hatchet_logo.png';
import invariant from 'tiny-invariant';

import {
@@ -32,14 +31,15 @@ import {
Popover,
PopoverContent,
} from '@radix-ui/react-popover';
import React from 'react';
import React, { useState } from 'react';
import {
MembershipsContextType,
UserContextType,
useContextFromParent,
} from '@/lib/outlet';
import { useTenantContext } from '@/lib/atoms';
import { Loading, Spinner } from '@/components/ui/loading.tsx';
import { useSidebar } from '@/components/sidebar-provider';

function Main() {
const ctx = useOutletContext<UserContextType & MembershipsContextType>();
@@ -61,7 +61,7 @@ function Main() {
return (
<div className="flex flex-row flex-1 w-full h-full">
<Sidebar memberships={memberships} currTenant={currTenant} />
<div className="pt-12 pl-80 flex-grow overflow-y-auto overflow-x-hidden">
<div className="pt-12 flex-grow overflow-y-auto overflow-x-hidden">
<Outlet context={childCtx} />
</div>
</div>
@@ -76,12 +76,17 @@ interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> {
}

function Sidebar({ className, memberships, currTenant }: SidebarProps) {
const { sidebarOpen } = useSidebar();

if (sidebarOpen === 'closed') {
return null;
}

return (
<div className={cn('h-full border-r w-80 absolute top-0', className)}>
<div className={cn('h-full border-r w-80 top-0', className)}>
<div className="flex flex-col justify-between items-start space-y-4 px-4 py-4 h-full">
<div className="grow">
<div className="py-2">
<img src={hatchet} alt="Hatchet" className="h-9 rounded mb-6" />
<h2 className="mb-2 text-lg font-semibold tracking-tight">
Events
</h2>
11 changes: 7 additions & 4 deletions frontend/app/src/pages/root.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { SidebarProvider } from '@/components/sidebar-provider';
import { ThemeProvider } from '@/components/theme-provider';
import { Toaster } from '@/components/ui/toaster';
import { Outlet } from 'react-router-dom';

function Root() {
return (
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
<div className="fixed h-full w-full">
<Toaster />
<Outlet />
</div>
<SidebarProvider>
<div className="fixed h-full w-full">
<Toaster />
<Outlet />
</div>
</SidebarProvider>
</ThemeProvider>
);
}

0 comments on commit 296c447

Please sign in to comment.