From 64071747e874a0dc55c8601dc20e8bda00315909 Mon Sep 17 00:00:00 2001 From: Jordan Wong <42422209+JorWo@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:51:31 -1000 Subject: [PATCH] Improve Navbar for smaller screens (#50) --- .../layout/navbar/mobile-navbar.tsx | 59 ------- .../navbar/{nav-links.ts => navbar-links.ts} | 2 +- .../layout/navbar/navbar-menu-icon.tsx | 21 +++ .../components/layout/navbar/navbar-menu.tsx | 39 +++++ ui/src/components/layout/navbar/navbar.tsx | 21 +-- ui/src/components/ui/sheet.tsx | 154 ++++++++---------- ...e-navbar.test.tsx => navbar-menu.test.tsx} | 18 +- 7 files changed, 146 insertions(+), 168 deletions(-) delete mode 100644 ui/src/components/layout/navbar/mobile-navbar.tsx rename ui/src/components/layout/navbar/{nav-links.ts => navbar-links.ts} (94%) create mode 100644 ui/src/components/layout/navbar/navbar-menu-icon.tsx create mode 100644 ui/src/components/layout/navbar/navbar-menu.tsx rename ui/tests/app/_components/navbar/{mobile-navbar.test.tsx => navbar-menu.test.tsx} (87%) diff --git a/ui/src/components/layout/navbar/mobile-navbar.tsx b/ui/src/components/layout/navbar/mobile-navbar.tsx deleted file mode 100644 index 333d5c08..00000000 --- a/ui/src/components/layout/navbar/mobile-navbar.tsx +++ /dev/null @@ -1,59 +0,0 @@ -'use client'; - -import { - Sheet, - SheetContent, - SheetTrigger, -} from '@/components/ui/sheet' -import User from '@/access/user'; -import Link from 'next/link'; -import { NavLinks } from './nav-links'; -import { useState } from 'react'; -import Role from '@/access/role'; - -const MobileNavbar = ({ - currentUser -} : { - currentUser: User -}) => { - const [isOpen, setIsOpen] = useState(false); - const handleClick = () => { - setIsOpen(!isOpen); - }; - - return ( - - -
- - - -
-
- - - - -
- ); -}; - - -export default MobileNavbar; diff --git a/ui/src/components/layout/navbar/nav-links.ts b/ui/src/components/layout/navbar/navbar-links.ts similarity index 94% rename from ui/src/components/layout/navbar/nav-links.ts rename to ui/src/components/layout/navbar/navbar-links.ts index 63fb9bdb..de2c535f 100644 --- a/ui/src/components/layout/navbar/nav-links.ts +++ b/ui/src/components/layout/navbar/navbar-links.ts @@ -1,6 +1,6 @@ import Role from '@/access/role'; -export const NavLinks = [ +export const NavbarLinks = [ { name: 'Admin', link: '/admin', diff --git a/ui/src/components/layout/navbar/navbar-menu-icon.tsx b/ui/src/components/layout/navbar/navbar-menu-icon.tsx new file mode 100644 index 00000000..ceb694c2 --- /dev/null +++ b/ui/src/components/layout/navbar/navbar-menu-icon.tsx @@ -0,0 +1,21 @@ +const NavbarMenuIcon = ({ open }: { open: boolean }) => ( +
+ + + +
+); + +export default NavbarMenuIcon; diff --git a/ui/src/components/layout/navbar/navbar-menu.tsx b/ui/src/components/layout/navbar/navbar-menu.tsx new file mode 100644 index 00000000..8066d4b7 --- /dev/null +++ b/ui/src/components/layout/navbar/navbar-menu.tsx @@ -0,0 +1,39 @@ +'use client'; + +import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'; +import User from '@/access/user'; +import Link from 'next/link'; +import { NavbarLinks } from './navbar-links'; +import { useState } from 'react'; +import Role from '@/access/role'; +import NavbarMenuIcon from './navbar-menu-icon'; + +const NavbarMenu = ({ currentUser }: { currentUser: User }) => { + const [open, setOpen] = useState(false); + const handleClick = () => { + setOpen(!open); + }; + + return ( + + + + + + + + + ); +}; + +export default NavbarMenu; diff --git a/ui/src/components/layout/navbar/navbar.tsx b/ui/src/components/layout/navbar/navbar.tsx index a386ee02..0c1b4579 100644 --- a/ui/src/components/layout/navbar/navbar.tsx +++ b/ui/src/components/layout/navbar/navbar.tsx @@ -2,9 +2,9 @@ import Link from 'next/link'; import Image from 'next/image'; import Role from '@/access/role'; import { getCurrentUser } from '@/access/authentication'; -import { NavLinks } from './nav-links'; +import { NavbarLinks } from './navbar-links'; import LoginButton from './login-button'; -import MobileNavbar from './mobile-navbar'; +import NavbarMenu from './navbar-menu'; import TimeoutModal from '@/components/modal/timeout-modal'; const Navbar = async () => { @@ -24,7 +24,7 @@ const Navbar = async () => { />
- + {
- {NavLinks - .filter((navLink) => - currentUser.roles.includes(Role.ADMIN) || currentUser.roles.includes(navLink.role)) - .map((navLink) => + {NavbarLinks + .filter((navbarLink) => + currentUser.roles.includes(Role.ADMIN) || + currentUser.roles.includes(navbarLink.role)) + .map((navbarLink) => - {navLink.name} + {navbarLink.name} )}
diff --git a/ui/src/components/ui/sheet.tsx b/ui/src/components/ui/sheet.tsx index da7b8731..721fd760 100644 --- a/ui/src/components/ui/sheet.tsx +++ b/ui/src/components/ui/sheet.tsx @@ -9,26 +9,20 @@ import { cn } from '@/components/ui/utils'; const Sheet = SheetPrimitive.Root; -const SheetClose = SheetPrimitive.Close +const SheetClose = SheetPrimitive.Close; -const SheetPortal = SheetPrimitive.Portal +const SheetPortal = SheetPrimitive.Portal; -interface SheetTriggerProps - extends React.ComponentPropsWithoutRef { - onClick?: () => void +interface SheetTriggerProps extends React.ComponentPropsWithoutRef { + onClick?: () => void; } -const SheetTrigger = React.forwardRef< - React.ElementRef, - SheetTriggerProps ->(({ onClick, className, children, ...props }, ref) => ( - - {children} - -)); +const SheetTrigger = React.forwardRef, SheetTriggerProps>( + ({ onClick, className, children, ...props }, ref) => ( + + {children} + + ) +); SheetTrigger.displayName = SheetPrimitive.Trigger.displayName; const SheetOverlay = React.forwardRef< @@ -44,11 +38,11 @@ const SheetOverlay = React.forwardRef< {...props} ref={ref} /> -)) -SheetOverlay.displayName = SheetPrimitive.Overlay.displayName +)); +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; const sheetVariants = cva( - `fixed z-50 gap-4 bg-white p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in + `fixed z-40 gap-4 bg-white py-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 dark:bg-slate-950`, { @@ -61,80 +55,62 @@ const sheetVariants = cva( left: `inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm`, right: `inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right - data-[state=open]:slide-in-from-right sm:max-w-sm`, + data-[state=open]:slide-in-from-right sm:max-w-sm` } }, defaultVariants: { - side: 'right', - }, + side: 'right' + } } -) +); interface SheetContentProps extends React.ComponentPropsWithoutRef, - VariantProps { - onClickOutside?: () => void, - hasCloseButton?: boolean + VariantProps { + onClickOutside?: () => void; + hasCloseButton?: boolean; } -const SheetContent = React.forwardRef< - React.ElementRef, - SheetContentProps ->(({ side = 'right', hasCloseButton = false, onClickOutside, className, children, ...props }, ref) => ( - - - { - e.preventDefault(); - }} - > - {children} - - {hasCloseButton && ( - , SheetContentProps>( + ({ side = 'right', hasCloseButton = false, onClickOutside, className, children, ...props }, ref) => ( + + + { + e.preventDefault(); + }} + > + {children} + + {hasCloseButton && ( + - - Close - - )} - - -)) -SheetContent.displayName = SheetPrimitive.Content.displayName - -const SheetHeader = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
-) -SheetHeader.displayName = 'SheetHeader' - -const SheetFooter = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
-) -SheetFooter.displayName = 'SheetFooter' + dark:focus:ring-slate-300 dark:data-[state=open]:bg-slate-800`} + > + + Close + + )} + + + ) +); +SheetContent.displayName = SheetPrimitive.Content.displayName; + +const SheetHeader = ({ className, ...props }: React.HTMLAttributes) => ( +
+); +SheetHeader.displayName = 'SheetHeader'; + +const SheetFooter = ({ className, ...props }: React.HTMLAttributes) => ( +
+); +SheetFooter.displayName = 'SheetFooter'; const SheetTitle = React.forwardRef< React.ElementRef, @@ -145,8 +121,8 @@ const SheetTitle = React.forwardRef< className={cn('text-lg font-semibold text-slate-950 dark:text-slate-50', className)} {...props} /> -)) -SheetTitle.displayName = SheetPrimitive.Title.displayName +)); +SheetTitle.displayName = SheetPrimitive.Title.displayName; const SheetDescription = React.forwardRef< React.ElementRef, @@ -157,8 +133,8 @@ const SheetDescription = React.forwardRef< className={cn('text-sm text-slate-500 dark:text-slate-400', className)} {...props} /> -)) -SheetDescription.displayName = SheetPrimitive.Description.displayName +)); +SheetDescription.displayName = SheetPrimitive.Description.displayName; export { Sheet, @@ -170,5 +146,5 @@ export { SheetHeader, SheetFooter, SheetTitle, - SheetDescription, -} + SheetDescription +}; diff --git a/ui/tests/app/_components/navbar/mobile-navbar.test.tsx b/ui/tests/app/_components/navbar/navbar-menu.test.tsx similarity index 87% rename from ui/tests/app/_components/navbar/mobile-navbar.test.tsx rename to ui/tests/app/_components/navbar/navbar-menu.test.tsx index 35b81230..c8011b96 100644 --- a/ui/tests/app/_components/navbar/mobile-navbar.test.tsx +++ b/ui/tests/app/_components/navbar/navbar-menu.test.tsx @@ -1,19 +1,19 @@ import Role from '@/access/role'; import User, { AnonymousUser } from '@/access/user'; -import MobileNavbar from '@/components/layout/navbar/mobile-navbar'; +import NavbarMenu from '@/components/layout/navbar/navbar-menu'; import { fireEvent, render, screen } from '@testing-library/react'; const testUser: User = JSON.parse(process.env.TEST_USER_A as string); -describe('MobileNavbar', () => { - it('should render the MobileNavbar with the sheet closed', () => { - render(); +describe('NavbarMenu', () => { + it('should render the NavbarMenu with the sheet closed', () => { + render(); expect(screen.queryByRole('navigation')).not.toBeInTheDocument(); }); it('should open the drawer on click', () => { - render(); + render(); fireEvent.click(screen.getByRole('button', { name: 'Open navigation menu' })); expect(screen.getByRole('navigation')).toBeInTheDocument(); @@ -21,7 +21,7 @@ describe('MobileNavbar', () => { describe('User is logged-out', () => { it('should render the navbar with only the link to /about', () => { - render(); + render(); fireEvent.click(screen.getByRole('button', { name: 'Open navigation menu' })); expect(screen.getByRole('navigation')).toBeInTheDocument(); @@ -40,7 +40,7 @@ describe('MobileNavbar', () => { it('should render only /memberships, /about, /feedback for the average user', () => { testUser.roles.push(Role.UH); - render(); + render(); fireEvent.click(screen.getByRole('button', { name: 'Open navigation menu' })); expect(screen.getByRole('navigation')).toBeInTheDocument(); @@ -53,7 +53,7 @@ describe('MobileNavbar', () => { it('should render only /memberships, /groupings, /about, /feedback for an owner of a grouping', () => { testUser.roles.push(Role.OWNER, Role.UH); - render(); + render(); fireEvent.click(screen.getByRole('button', { name: 'Open navigation menu' })); expect(screen.getByRole('navigation')).toBeInTheDocument(); @@ -66,7 +66,7 @@ describe('MobileNavbar', () => { it('should render all links for an Admin', () => { testUser.roles.push(Role.ADMIN, Role.UH); - render(); + render(); fireEvent.click(screen.getByRole('button', { name: 'Open navigation menu' })); expect(screen.getByRole('navigation')).toBeInTheDocument();