195 lines
6.0 KiB
TypeScript
195 lines
6.0 KiB
TypeScript
'use client';
|
|
|
|
import * as React from 'react';
|
|
import RouterLink from 'next/link';
|
|
import { usePathname } from 'next/navigation';
|
|
import Avatar from '@mui/material/Avatar';
|
|
import Box from '@mui/material/Box';
|
|
import Stack from '@mui/material/Stack';
|
|
import Typography from '@mui/material/Typography';
|
|
import type { Icon } from '@phosphor-icons/react/dist/lib/types';
|
|
import { Bell as BellIcon } from '@phosphor-icons/react/dist/ssr/Bell';
|
|
import { CreditCard as CreditCardIcon } from '@phosphor-icons/react/dist/ssr/CreditCard';
|
|
import { LockKey as LockKeyIcon } from '@phosphor-icons/react/dist/ssr/LockKey';
|
|
import { PlugsConnected as PlugsConnectedIcon } from '@phosphor-icons/react/dist/ssr/PlugsConnected';
|
|
import { UserCircle as UserCircleIcon } from '@phosphor-icons/react/dist/ssr/UserCircle';
|
|
import { UsersThree as UsersThreeIcon } from '@phosphor-icons/react/dist/ssr/UsersThree';
|
|
|
|
import type { NavItemConfig } from '@/types/nav';
|
|
import { paths } from '@/paths';
|
|
import { isNavItemActive } from '@/lib/is-nav-item-active';
|
|
|
|
// NOTE: First level elements are groups.
|
|
|
|
const navItems = [
|
|
{
|
|
key: 'personal',
|
|
title: 'Personal',
|
|
items: [
|
|
{ key: 'account', title: 'Account', href: paths.dashboard.settings.account, icon: 'user-circle' },
|
|
{ key: 'notifications', title: 'Notifications', href: paths.dashboard.settings.notifications, icon: 'bell' },
|
|
{ key: 'security', title: 'Security', href: paths.dashboard.settings.security, icon: 'lock-key' },
|
|
],
|
|
},
|
|
{
|
|
key: 'organization',
|
|
title: 'Organization',
|
|
items: [
|
|
{ key: 'billing', title: 'Billing & plans', href: paths.dashboard.settings.billing, icon: 'credit-card' },
|
|
{ key: 'team', title: 'Team', href: paths.dashboard.settings.team, icon: 'users-three' },
|
|
{
|
|
key: 'integrations',
|
|
title: 'Integrations',
|
|
href: paths.dashboard.settings.integrations,
|
|
icon: 'plugs-connected',
|
|
},
|
|
],
|
|
},
|
|
] satisfies NavItemConfig[];
|
|
|
|
const icons = {
|
|
'credit-card': CreditCardIcon,
|
|
'lock-key': LockKeyIcon,
|
|
'plugs-connected': PlugsConnectedIcon,
|
|
'user-circle': UserCircleIcon,
|
|
'users-three': UsersThreeIcon,
|
|
bell: BellIcon,
|
|
} as Record<string, Icon>;
|
|
|
|
export function SideNav(): React.JSX.Element {
|
|
const pathname = usePathname();
|
|
|
|
return (
|
|
<div>
|
|
<Stack
|
|
spacing={3}
|
|
sx={{
|
|
flex: '0 0 auto',
|
|
flexDirection: { xs: 'column-reverse', md: 'column' },
|
|
position: { md: 'sticky' },
|
|
top: '64px',
|
|
width: { xs: '100%', md: '240px' },
|
|
}}
|
|
>
|
|
<Stack
|
|
component="ul"
|
|
spacing={3}
|
|
sx={{ listStyle: 'none', m: 0, p: 0 }}
|
|
>
|
|
{navItems.map((group) => (
|
|
<Stack
|
|
component="li"
|
|
key={group.key}
|
|
spacing={2}
|
|
>
|
|
{group.title ? (
|
|
<div>
|
|
<Typography
|
|
color="text.secondary"
|
|
variant="caption"
|
|
>
|
|
{group.title}
|
|
</Typography>
|
|
</div>
|
|
) : null}
|
|
<Stack
|
|
component="ul"
|
|
spacing={1}
|
|
sx={{ listStyle: 'none', m: 0, p: 0 }}
|
|
>
|
|
{group.items.map((item) => (
|
|
<NavItem
|
|
{...item}
|
|
key={item.key}
|
|
pathname={pathname}
|
|
/>
|
|
))}
|
|
</Stack>
|
|
</Stack>
|
|
))}
|
|
</Stack>
|
|
<Stack
|
|
direction="row"
|
|
spacing={2}
|
|
sx={{ alignItems: 'center' }}
|
|
>
|
|
<Avatar src="/assets/avatar.png">AV</Avatar>
|
|
<div>
|
|
<Typography variant="subtitle1">Sofia Rivers</Typography>
|
|
<Typography
|
|
color="text.secondary"
|
|
variant="caption"
|
|
>
|
|
sofia@devias.io
|
|
</Typography>
|
|
</div>
|
|
</Stack>
|
|
</Stack>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface NavItemProps extends NavItemConfig {
|
|
pathname: string;
|
|
}
|
|
|
|
function NavItem({ disabled, external, href, icon, pathname, title }: NavItemProps): React.JSX.Element {
|
|
const active = isNavItemActive({ disabled, external, href, pathname });
|
|
const Icon = icon ? icons[icon] : null;
|
|
|
|
return (
|
|
<Box
|
|
component="li"
|
|
sx={{ userSelect: 'none' }}
|
|
>
|
|
<Box
|
|
{...(href
|
|
? {
|
|
component: external ? 'a' : RouterLink,
|
|
href,
|
|
target: external ? '_blank' : undefined,
|
|
rel: external ? 'noreferrer' : undefined,
|
|
}
|
|
: { role: 'button' })}
|
|
sx={{
|
|
alignItems: 'center',
|
|
borderRadius: 1,
|
|
color: 'var(--mui-palette-text-secondary)',
|
|
cursor: 'pointer',
|
|
display: 'flex',
|
|
flex: '0 0 auto',
|
|
gap: 1,
|
|
p: '6px 16px',
|
|
textDecoration: 'none',
|
|
whiteSpace: 'nowrap',
|
|
...(disabled && { color: 'var(--mui-palette-text-disabled)', cursor: 'not-allowed' }),
|
|
...(active && { bgcolor: 'var(--mui-palette-action-selected)', color: 'var(--mui-palette-text-primary)' }),
|
|
'&:hover': {
|
|
...(!active &&
|
|
!disabled && { bgcolor: 'var(--mui-palette-action-hover)', color: 'var(---mui-palette-text-primary)' }),
|
|
},
|
|
}}
|
|
tabIndex={0}
|
|
>
|
|
{Icon ? (
|
|
<Box sx={{ alignItems: 'center', display: 'flex', justifyContent: 'center', flex: '0 0 auto' }}>
|
|
<Icon
|
|
fill={active ? 'var(--mui-palette-text-primary)' : 'var(--mui-palette-text-secondary)'}
|
|
fontSize="var(--icon-fontSize-md)"
|
|
weight={active ? 'fill' : undefined}
|
|
/>
|
|
</Box>
|
|
) : null}
|
|
<Box sx={{ flex: '1 1 auto' }}>
|
|
<Typography
|
|
component="span"
|
|
sx={{ color: 'inherit', fontSize: '0.875rem', fontWeight: 500, lineHeight: '28px' }}
|
|
>
|
|
{title}
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|