Skip to content

Commit

Permalink
feat: make navigation bar collapsible (#527)
Browse files Browse the repository at this point in the history
  • Loading branch information
wesbillman authored Oct 30, 2023
1 parent 496e516 commit c0ceb23
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 29 deletions.
13 changes: 5 additions & 8 deletions console/client/src/hooks/use-local-storage.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import React, { useEffect, useState } from 'react'
import { useEffect, useState } from 'react'

export default function useLocalStorage(
key: string,
initialValue: string,
): [string, React.Dispatch<React.SetStateAction<string>>] {
const [value, setValue] = useState<string>(() => {
export default function useLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() => {
const jsonValue = localStorage.getItem(key)
if (jsonValue != null) return JSON.parse(jsonValue) as string
if (jsonValue != null) return JSON.parse(jsonValue) as T
return initialValue
})

useEffect(() => {
localStorage.setItem(key, JSON.stringify(value))
}, [key, value])

return [value, setValue]
return [value, setValue] as const
}
8 changes: 6 additions & 2 deletions console/client/src/layout/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Bars3Icon } from '@heroicons/react/24/outline'
import { useState } from 'react'
import { Outlet } from 'react-router-dom'
import useLocalStorage from '../hooks/use-local-storage'
import { bgColor, textColor } from '../utils'
import { Notification } from './Notification'
import { SidePanel } from './SidePanel'
Expand All @@ -9,6 +10,7 @@ import { Navigation } from './navigation/Navigation'

export const Layout = () => {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
const [isCollapsed, setIsCollapsed] = useLocalStorage('isNavCollapsed', false)

return (
<>
Expand All @@ -17,9 +19,11 @@ export const Layout = () => {
</div>

<div
className={`grid h-screen ${bgColor} ${textColor} sm:grid-cols-[13rem,1fr] sm:grid-rows-[100vh] hidden sm:grid`}
className={`grid h-screen ${bgColor} ${textColor}
${isCollapsed ? 'sm:grid-cols-[4rem,1fr]' : 'sm:grid-cols-[13rem,1fr]'}
sm:grid-rows-[100vh] hidden sm:grid`}
>
<Navigation />
<Navigation isCollapsed={isCollapsed} setIsCollapsed={setIsCollapsed} />
<main className='overflow-hidden'>
<Outlet />
</main>
Expand Down
60 changes: 46 additions & 14 deletions console/client/src/layout/navigation/Navigation.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,52 @@
import { ChevronDoubleLeftIcon, ChevronDoubleRightIcon } from '@heroicons/react/24/outline'
import { useContext } from 'react'
import { Link, NavLink } from 'react-router-dom'
import { DarkModeSwitch } from '../../components/DarkModeSwitch'
import { modulesContext } from '../../providers/modules-provider'
import { classNames } from '../../utils'
import { navigation } from './navigation-items'

export const Navigation = () => {
export const Navigation = ({
isCollapsed,
setIsCollapsed,
}: {
isCollapsed: boolean
setIsCollapsed: React.Dispatch<React.SetStateAction<boolean>>
}) => {
const modules = useContext(modulesContext)

return (
<div className='bg-gray-800 flex-shrink-0 w-52 h-full'>
<div className={`bg-gray-800 flex-shrink-0 h-full ${isCollapsed ? '' : 'w-52'}`}>
<aside className={`flex flex-col h-full`}>
<div className='flex flex-col h-full overflow-y-auto bg-indigo-700'>
<div className='flex grow flex-col overflow-y-auto bg-indigo-700 px-4'>
<Link to='/events'>
<div className='-mx-2 space-y-1'>
<div className={`${isCollapsed ? '-mx-3' : '-mx-2'} space-y-1`}>
<div className='flex shrink-0 items-center p-2 rounded-md hover:bg-indigo-700'>
<span className='text-2xl font-medium text-white'>FTL</span>
<span className='px-2 text-pink-400 text-2xl font-medium'></span>
{!isCollapsed && (
<>
<span className='text-2xl font-medium text-white'>FTL</span>
<span className='px-2 text-pink-400 text-2xl font-medium'></span>
<button
onClick={() => setIsCollapsed(true)}
className='hover:bg-indigo-600 p-1 ml-auto -mr-2 rounded'
>
<ChevronDoubleLeftIcon className='h-6 w-6 text-gray-300' />
</button>
</>
)}
{isCollapsed && (
<button
onClick={() => setIsCollapsed(false)}
className='hover:bg-indigo-600 p-1 rounded w-full flex justify-center'
>
<ChevronDoubleRightIcon className='h-6 w-6 text-gray-300' />
</button>
)}
</div>
</div>
</Link>

<nav className='flex flex-1 flex-col pt-4'>
<ul role='list' className='flex flex-1 flex-col gap-y-7'>
<li>
Expand All @@ -35,26 +61,32 @@ export const Navigation = () => {
? 'bg-indigo-600 text-white'
: 'text-indigo-200 hover:text-white hover:bg-indigo-600',
'group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold',
isCollapsed ? 'justify-center' : '',
)
}
>
{({ isActive }) => (
<>
<item.icon
title={item.name}
className={classNames(
isActive ? 'text-white' : 'text-indigo-200 group-hover:text-white',
'h-6 w-6 shrink-0',
)}
aria-hidden='true'
/>
{item.name}
{['/modules', '/deployments'].includes(item.href) && (
<span
className='ml-auto w-9 min-w-max whitespace-nowrap rounded-full bg-indigo-600 px-2.5 py-0.5 text-center text-xs font-medium leading-5 text-white ring-1 ring-inset ring-indigo-500'
aria-hidden='true'
>
{modules.modules.length}
</span>
{!isCollapsed && item.name && (
<>
{item.name}
{['/modules', '/deployments'].includes(item.href) && (
<span
className='ml-auto w-9 min-w-max whitespace-nowrap rounded-full bg-indigo-600 px-2.5 py-0.5 text-center text-xs font-medium leading-5 text-white ring-1 ring-inset ring-indigo-500'
aria-hidden='true'
>
{modules.modules.length}
</span>
)}
</>
)}
</>
)}
Expand All @@ -63,7 +95,7 @@ export const Navigation = () => {
))}
</ul>
</li>
<li className='pb-2 mt-auto'>
<li className={`pb-2 mt-auto ${isCollapsed ? '-mx-1.5' : ''}`}>
<DarkModeSwitch />
</li>
</ul>
Expand Down
8 changes: 3 additions & 5 deletions console/client/src/providers/dark-mode-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ export const useDarkMode = () => {
}

export const DarkModeProvider = ({ children }: PropsWithChildren) => {
const [isDarkMode, setDarkMode] = useLocalStorage('dark-mode', 'false')
const setMode = (val: boolean) => {
setDarkMode(`${val}`)
}
const [isDarkMode, setDarkMode] = useLocalStorage('dark-mode', false)

return (
<DarkModeContext.Provider value={{ isDarkMode: isDarkMode === 'true', setDarkMode: setMode }}>
<DarkModeContext.Provider value={{ isDarkMode: isDarkMode, setDarkMode: setDarkMode }}>
{children}
</DarkModeContext.Provider>
)
Expand Down

0 comments on commit c0ceb23

Please sign in to comment.