Skip to content

Instantly share code, notes, and snippets.

@tedlin182
Created November 8, 2022 18:39
Show Gist options
  • Save tedlin182/ff1117bfe23bb9016bef4955623aa471 to your computer and use it in GitHub Desktop.
Save tedlin182/ff1117bfe23bb9016bef4955623aa471 to your computer and use it in GitHub Desktop.
General SidebarNav component with accordion toggle functionality
import { createContext, useContext, useState } from 'react'
import clsx from 'clsx'
// Styling
import styles from './SidebarNav.module.scss'
// Images
import ChevronIcon from '@/components/images/chevron-down.svg'
// Main
export const AccordionContext = createContext()
export function AccordionProvider({
children,
defaultOpenTabIndex = null,
}) {
const [currentOpenTabIndex, setCurrentOpenTabIndex] = useState(
defaultOpenTabIndex,
)
const toggleTabPanel = (tabIndex) =>
setCurrentOpenTabIndex((currentTabIndex) =>
currentTabIndex === tabIndex ? null : tabIndex,
)
return (
<AccordionContext.Provider
value={{
currentOpenTabIndex,
toggleTabPanel,
setCurrentOpenTabIndex,
}}>
{children}
</AccordionContext.Provider>
)
}
function TabPanel({ children }) {
return <div className={styles.tab_panel}>{children}</div>
}
// TODO: This will not be part of the SidebarNav and just be
// custom content you can insert into the TabPanel
function SubMenu({ menuItems = [] }) {
return (
<ul className={styles.submenu}>
{menuItems.map(({ onClick = () => {}, id, name }) => (
<li
key={`submenu_item_${id}`}
onClick={onClick}
className={styles.submenu_item}>
{name}
</li>
))}
</ul>
)
}
function Tab({ name, index, submenu, renderCustomContent }) {
const { currentOpenTabIndex, toggleTabPanel } =
useContext(AccordionContext)
const onTabClick = () => toggleTabPanel(index)
return (
<li
className={clsx(styles.tab, {
[styles.is_open]: currentOpenTabIndex === index,
})}
onClick={onTabClick}>
<div className={styles.tab_toggle_wrapper}>
<span className={styles.tab_title}>{name}</span>
<ChevronIcon className={styles.tab_toggle_icon} />
</div>
<TabPanel>
{/*
TODO: This will just be renderCustomContent as subMenu
will just be rendered in renderCustomContent
*/}
{(renderCustomContent && renderCustomContent()) ||
(submenu && <SubMenu menuItems={submenu} />) ||
undefined}
</TabPanel>
</li>
)
}
function NavItems({ navItems }) {
return (
<ul className={styles.tab_list}>
{navItems.map((itemData, index) => (
<Tab
key={`tab_${itemData.id}_${index}`}
index={index}
{...itemData}
/>
))}
</ul>
)
}
export default function SidebarNav({ navItems = [] }) {
return (
<AccordionProvider>
<nav className={styles.stick_nav}>
<NavItems navItems={navItems} />
</nav>
</AccordionProvider>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment