Compare commits
2 Commits
01a8d2ca02
...
develop/cm
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b26e1ff167 | ||
![]() |
de415a37bc |
@@ -112,9 +112,15 @@ export function ContactsPopover({ anchorEl, onClose, open = false }: ContactsPop
|
|||||||
<Typography variant="h6">Contacts</Typography>
|
<Typography variant="h6">Contacts</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ maxHeight: '400px', overflowY: 'auto', px: 1, pb: 2 }}>
|
<Box sx={{ maxHeight: '400px', overflowY: 'auto', px: 1, pb: 2 }}>
|
||||||
<List disablePadding sx={{ '& .MuiListItemButton-root': { borderRadius: 1 } }}>
|
<List
|
||||||
|
disablePadding
|
||||||
|
sx={{ '& .MuiListItemButton-root': { borderRadius: 1 } }}
|
||||||
|
>
|
||||||
{contacts.map((contact) => (
|
{contacts.map((contact) => (
|
||||||
<ListItem disablePadding key={contact.id}>
|
<ListItem
|
||||||
|
disablePadding
|
||||||
|
key={contact.id}
|
||||||
|
>
|
||||||
<ListItemButton>
|
<ListItemButton>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<Avatar src={contact.avatar} />
|
<Avatar src={contact.avatar} />
|
||||||
@@ -122,14 +128,28 @@ export function ContactsPopover({ anchorEl, onClose, open = false }: ContactsPop
|
|||||||
<ListItemText
|
<ListItemText
|
||||||
disableTypography
|
disableTypography
|
||||||
primary={
|
primary={
|
||||||
<Link color="text.primary" noWrap underline="none" variant="subtitle2">
|
<Link
|
||||||
|
color="text.primary"
|
||||||
|
noWrap
|
||||||
|
underline="none"
|
||||||
|
variant="subtitle2"
|
||||||
|
>
|
||||||
{contact.name}
|
{contact.name}
|
||||||
</Link>
|
</Link>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{contact.status !== 'offline' ? <Presence size="small" status={contact.status} /> : null}
|
{contact.status !== 'offline' ? (
|
||||||
|
<Presence
|
||||||
|
size="small"
|
||||||
|
status={contact.status}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
{contact.status === 'offline' && Boolean(contact.lastActivity) ? (
|
{contact.status === 'offline' && Boolean(contact.lastActivity) ? (
|
||||||
<Typography color="text.secondary" sx={{ whiteSpace: 'nowrap' }} variant="caption">
|
<Typography
|
||||||
|
color="text.secondary"
|
||||||
|
sx={{ whiteSpace: 'nowrap' }}
|
||||||
|
variant="caption"
|
||||||
|
>
|
||||||
{dayjs(contact.lastActivity).fromNow()}
|
{dayjs(contact.lastActivity).fromNow()}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : null}
|
) : null}
|
||||||
|
@@ -34,6 +34,7 @@ export function HorizontalLayout({ children }: HorizontalLayoutProps): React.JSX
|
|||||||
color={settings.navColor}
|
color={settings.navColor}
|
||||||
items={layoutConfig.navItems}
|
items={layoutConfig.navItems}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
component="main"
|
component="main"
|
||||||
sx={{
|
sx={{
|
||||||
|
@@ -36,15 +36,15 @@ import { Logo } from '@/components/core/logo';
|
|||||||
import { SearchDialog } from '@/components/dashboard/layout/search-dialog';
|
import { SearchDialog } from '@/components/dashboard/layout/search-dialog';
|
||||||
import type { ColorScheme } from '@/styles/theme/types';
|
import type { ColorScheme } from '@/styles/theme/types';
|
||||||
|
|
||||||
import { ContactsPopover } from '../contacts-popover';
|
import { ContactsPopover } from '../../contacts-popover';
|
||||||
import { languageFlags, LanguagePopover } from '../language-popover';
|
import { languageFlags, LanguagePopover } from '../../language-popover';
|
||||||
import type { Language } from '../language-popover';
|
import type { Language } from '../../language-popover';
|
||||||
import { MobileNav } from '../mobile-nav';
|
import { MobileNav } from '../../mobile-nav';
|
||||||
import { icons } from '../nav-icons';
|
import { icons } from '../../nav-icons';
|
||||||
import { NotificationsPopover } from '../notifications-popover';
|
import { NotificationsPopover } from '../../notifications-popover';
|
||||||
import { UserPopover } from '../user-popover/user-popover';
|
import { UserPopover } from '../../user-popover/user-popover';
|
||||||
import { WorkspacesSwitch } from '../workspaces-switch';
|
import { WorkspacesSwitch } from '../../workspaces-switch';
|
||||||
import { navColorStyles } from './styles';
|
import { navColorStyles } from '../styles';
|
||||||
|
|
||||||
const logoColors = {
|
const logoColors = {
|
||||||
dark: { blend_in: 'light', discrete: 'light', evident: 'light' },
|
dark: { blend_in: 'light', discrete: 'light', evident: 'light' },
|
||||||
|
@@ -9,9 +9,12 @@ import type { User } from '@/types/user';
|
|||||||
import { usePopover } from '@/hooks/use-popover';
|
import { usePopover } from '@/hooks/use-popover';
|
||||||
|
|
||||||
import { UserPopover } from '../../user-popover/user-popover';
|
import { UserPopover } from '../../user-popover/user-popover';
|
||||||
|
import { useUser } from '@/hooks/use-user';
|
||||||
|
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
|
||||||
// import { NotificationsButton } from './notifications-button';
|
// import { NotificationsButton } from './notifications-button';
|
||||||
|
|
||||||
const user = {
|
// TODO:remove me
|
||||||
|
const user1 = {
|
||||||
id: 'USR-000',
|
id: 'USR-000',
|
||||||
name: 'Sofia Rivers',
|
name: 'Sofia Rivers',
|
||||||
avatar: '/assets/avatar.png',
|
avatar: '/assets/avatar.png',
|
||||||
@@ -20,6 +23,9 @@ const user = {
|
|||||||
|
|
||||||
export function UserButton(): React.JSX.Element {
|
export function UserButton(): React.JSX.Element {
|
||||||
const popover = usePopover<HTMLButtonElement>();
|
const popover = usePopover<HTMLButtonElement>();
|
||||||
|
const { user, error, isLoading } = useUser();
|
||||||
|
|
||||||
|
if (!user) return <>loading</>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
@@ -44,7 +50,7 @@ export function UserButton(): React.JSX.Element {
|
|||||||
}}
|
}}
|
||||||
variant="dot"
|
variant="dot"
|
||||||
>
|
>
|
||||||
<Avatar src={user.avatar} />
|
<Avatar src={getImageUrlFromFile(user.collectionId, user.id, user.avatar)} />
|
||||||
</Badge>
|
</Badge>
|
||||||
</Box>
|
</Box>
|
||||||
<UserPopover
|
<UserPopover
|
||||||
|
@@ -0,0 +1,98 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import RouterLink from 'next/link';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Chip from '@mui/material/Chip';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { ArrowSquareOut as ArrowSquareOutIcon } from '@phosphor-icons/react/dist/ssr/ArrowSquareOut';
|
||||||
|
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
|
||||||
|
import { CaretRight as CaretRightIcon } from '@phosphor-icons/react/dist/ssr/CaretRight';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import type { NavItemConfig } from '@/types/nav';
|
||||||
|
import type { NavColor } from '@/types/settings';
|
||||||
|
import { paths } from '@/paths';
|
||||||
|
import { isNavItemActive } from '@/lib/is-nav-item-active';
|
||||||
|
import { useSettings } from '@/hooks/use-settings';
|
||||||
|
import { Logo } from '@/components/core/logo';
|
||||||
|
import type { ColorScheme } from '@/styles/theme/types';
|
||||||
|
|
||||||
|
import { icons } from '../../nav-icons';
|
||||||
|
import { WorkspacesSwitch } from '../../workspaces-switch';
|
||||||
|
import { navColorStyles } from '../styles';
|
||||||
|
import { RenderNavGroups } from './render-nav-groups';
|
||||||
|
|
||||||
|
const logoColors = {
|
||||||
|
dark: { blend_in: 'light', discrete: 'light', evident: 'light' },
|
||||||
|
light: { blend_in: 'dark', discrete: 'dark', evident: 'light' },
|
||||||
|
} as Record<ColorScheme, Record<NavColor, 'dark' | 'light'>>;
|
||||||
|
|
||||||
|
export interface SideNavProps {
|
||||||
|
color?: NavColor;
|
||||||
|
items?: NavItemConfig[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SideNav({ color = 'evident', items = [] }: SideNavProps): React.JSX.Element {
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
const {
|
||||||
|
settings: { colorScheme = 'light' },
|
||||||
|
} = useSettings();
|
||||||
|
|
||||||
|
const styles = navColorStyles[colorScheme][color];
|
||||||
|
const logoColor = logoColors[colorScheme][color];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
...styles,
|
||||||
|
bgcolor: 'var(--SideNav-background)',
|
||||||
|
borderRight: 'var(--SideNav-border)',
|
||||||
|
color: 'var(--SideNav-color)',
|
||||||
|
display: { xs: 'none', lg: 'flex' },
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100%',
|
||||||
|
left: 0,
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
width: 'var(--SideNav-width)',
|
||||||
|
zIndex: 'var(--SideNav-zIndex)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
spacing={2}
|
||||||
|
sx={{ p: 2 }}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Box
|
||||||
|
component={RouterLink}
|
||||||
|
href={paths.home}
|
||||||
|
sx={{ display: 'inline-flex' }}
|
||||||
|
>
|
||||||
|
<Logo
|
||||||
|
color={logoColor}
|
||||||
|
height={32}
|
||||||
|
width={122}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
<WorkspacesSwitch />
|
||||||
|
</Stack>
|
||||||
|
<Box
|
||||||
|
component="nav"
|
||||||
|
sx={{
|
||||||
|
flex: '1 1 auto',
|
||||||
|
overflowY: 'auto',
|
||||||
|
p: 2,
|
||||||
|
scrollbarWidth: 'none',
|
||||||
|
'&::-webkit-scrollbar': { display: 'none' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{RenderNavGroups({ items, pathname })}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
@@ -2,10 +2,8 @@
|
|||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import RouterLink from 'next/link';
|
import RouterLink from 'next/link';
|
||||||
import { usePathname } from 'next/navigation';
|
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Chip from '@mui/material/Chip';
|
import Chip from '@mui/material/Chip';
|
||||||
import Stack from '@mui/material/Stack';
|
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import { ArrowSquareOut as ArrowSquareOutIcon } from '@phosphor-icons/react/dist/ssr/ArrowSquareOut';
|
import { ArrowSquareOut as ArrowSquareOutIcon } from '@phosphor-icons/react/dist/ssr/ArrowSquareOut';
|
||||||
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
|
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
|
||||||
@@ -13,136 +11,9 @@ import { CaretRight as CaretRightIcon } from '@phosphor-icons/react/dist/ssr/Car
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import type { NavItemConfig } from '@/types/nav';
|
import type { NavItemConfig } from '@/types/nav';
|
||||||
import type { NavColor } from '@/types/settings';
|
|
||||||
import { paths } from '@/paths';
|
|
||||||
import { isNavItemActive } from '@/lib/is-nav-item-active';
|
import { isNavItemActive } from '@/lib/is-nav-item-active';
|
||||||
import { useSettings } from '@/hooks/use-settings';
|
|
||||||
import { Logo } from '@/components/core/logo';
|
|
||||||
import type { ColorScheme } from '@/styles/theme/types';
|
|
||||||
|
|
||||||
import { icons } from '../nav-icons';
|
import { icons } from '../../nav-icons';
|
||||||
import { WorkspacesSwitch } from '../workspaces-switch';
|
|
||||||
import { navColorStyles } from './styles';
|
|
||||||
|
|
||||||
const logoColors = {
|
|
||||||
dark: { blend_in: 'light', discrete: 'light', evident: 'light' },
|
|
||||||
light: { blend_in: 'dark', discrete: 'dark', evident: 'light' },
|
|
||||||
} as Record<ColorScheme, Record<NavColor, 'dark' | 'light'>>;
|
|
||||||
|
|
||||||
export interface SideNavProps {
|
|
||||||
color?: NavColor;
|
|
||||||
items?: NavItemConfig[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SideNav({ color = 'evident', items = [] }: SideNavProps): React.JSX.Element {
|
|
||||||
const pathname = usePathname();
|
|
||||||
|
|
||||||
const {
|
|
||||||
settings: { colorScheme = 'light' },
|
|
||||||
} = useSettings();
|
|
||||||
|
|
||||||
const styles = navColorStyles[colorScheme][color];
|
|
||||||
const logoColor = logoColors[colorScheme][color];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
...styles,
|
|
||||||
bgcolor: 'var(--SideNav-background)',
|
|
||||||
borderRight: 'var(--SideNav-border)',
|
|
||||||
color: 'var(--SideNav-color)',
|
|
||||||
display: { xs: 'none', lg: 'flex' },
|
|
||||||
flexDirection: 'column',
|
|
||||||
height: '100%',
|
|
||||||
left: 0,
|
|
||||||
position: 'fixed',
|
|
||||||
top: 0,
|
|
||||||
width: 'var(--SideNav-width)',
|
|
||||||
zIndex: 'var(--SideNav-zIndex)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack spacing={2} sx={{ p: 2 }}>
|
|
||||||
<div>
|
|
||||||
<Box component={RouterLink} href={paths.home} sx={{ display: 'inline-flex' }}>
|
|
||||||
<Logo color={logoColor} height={32} width={122} />
|
|
||||||
</Box>
|
|
||||||
</div>
|
|
||||||
<WorkspacesSwitch />
|
|
||||||
</Stack>
|
|
||||||
<Box
|
|
||||||
component="nav"
|
|
||||||
sx={{
|
|
||||||
flex: '1 1 auto',
|
|
||||||
overflowY: 'auto',
|
|
||||||
p: 2,
|
|
||||||
scrollbarWidth: 'none',
|
|
||||||
'&::-webkit-scrollbar': { display: 'none' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{RenderNavGroups({ items, pathname })}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function RenderNavGroups({ items, pathname }: { items: NavItemConfig[]; pathname: string }): React.JSX.Element {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const children = items.reduce((acc: React.ReactNode[], curr: NavItemConfig): React.ReactNode[] => {
|
|
||||||
acc.push(
|
|
||||||
<Stack component="li" key={curr.key} spacing={1.5}>
|
|
||||||
{curr.title ? (
|
|
||||||
<div>
|
|
||||||
<Typography sx={{ color: 'var(--NavGroup-title-color)', fontSize: '0.875rem', fontWeight: 500 }}>
|
|
||||||
{t(curr.title)}
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<div>{renderNavItems({ depth: 0, items: curr.items, pathname })}</div>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack component="ul" spacing={2} sx={{ listStyle: 'none', m: 0, p: 0 }}>
|
|
||||||
{children}
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderNavItems({
|
|
||||||
depth = 0,
|
|
||||||
items = [],
|
|
||||||
pathname,
|
|
||||||
}: {
|
|
||||||
depth: number;
|
|
||||||
items?: NavItemConfig[];
|
|
||||||
pathname: string;
|
|
||||||
}): React.JSX.Element {
|
|
||||||
const children = items.reduce((acc: React.ReactNode[], curr: NavItemConfig): React.ReactNode[] => {
|
|
||||||
const { items: childItems, key, ...item } = curr;
|
|
||||||
|
|
||||||
const forceOpen = childItems
|
|
||||||
? Boolean(childItems.find((childItem) => childItem.href && pathname.startsWith(childItem.href)))
|
|
||||||
: false;
|
|
||||||
|
|
||||||
acc.push(
|
|
||||||
<NavItem depth={depth} forceOpen={forceOpen} key={key} pathname={pathname} {...item}>
|
|
||||||
{childItems ? renderNavItems({ depth: depth + 1, pathname, items: childItems }) : null}
|
|
||||||
</NavItem>
|
|
||||||
);
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack component="ul" data-depth={depth} spacing={1} sx={{ listStyle: 'none', m: 0, p: 0 }}>
|
|
||||||
{children}
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NavItemProps extends Omit<NavItemConfig, 'items'> {
|
interface NavItemProps extends Omit<NavItemConfig, 'items'> {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
@@ -151,7 +22,7 @@ interface NavItemProps extends Omit<NavItemConfig, 'items'> {
|
|||||||
pathname: string;
|
pathname: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function NavItem({
|
export function NavItem({
|
||||||
children,
|
children,
|
||||||
depth,
|
depth,
|
||||||
disabled,
|
disabled,
|
||||||
@@ -173,7 +44,11 @@ function NavItem({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box component="li" data-depth={depth} sx={{ userSelect: 'none' }}>
|
<Box
|
||||||
|
component="li"
|
||||||
|
data-depth={depth}
|
||||||
|
sx={{ userSelect: 'none' }}
|
||||||
|
>
|
||||||
<Box
|
<Box
|
||||||
{...(isBranch
|
{...(isBranch
|
||||||
? {
|
? {
|
||||||
@@ -254,15 +129,27 @@ function NavItem({
|
|||||||
{t(title || '')}
|
{t(title || '')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
{label ? <Chip color="primary" label={label} size="small" /> : null}
|
{label ? (
|
||||||
|
<Chip
|
||||||
|
color="primary"
|
||||||
|
label={label}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
{external ? (
|
{external ? (
|
||||||
<Box sx={{ alignItems: 'center', display: 'flex', flex: '0 0 auto' }}>
|
<Box sx={{ alignItems: 'center', display: 'flex', flex: '0 0 auto' }}>
|
||||||
<ArrowSquareOutIcon color="var(--NavItem-icon-color)" fontSize="var(--icon-fontSize-sm)" />
|
<ArrowSquareOutIcon
|
||||||
|
color="var(--NavItem-icon-color)"
|
||||||
|
fontSize="var(--icon-fontSize-sm)"
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
{isBranch ? (
|
{isBranch ? (
|
||||||
<Box sx={{ alignItems: 'center', display: 'flex', flex: '0 0 auto' }}>
|
<Box sx={{ alignItems: 'center', display: 'flex', flex: '0 0 auto' }}>
|
||||||
<ExpandIcon color="var(--NavItem-expand-color)" fontSize="var(--icon-fontSize-sm)" />
|
<ExpandIcon
|
||||||
|
color="var(--NavItem-expand-color)"
|
||||||
|
fontSize="var(--icon-fontSize-sm)"
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
</Box>
|
</Box>
|
@@ -0,0 +1,45 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import type { NavItemConfig } from '@/types/nav';
|
||||||
|
|
||||||
|
import { renderNavItems } from './render-nav-items';
|
||||||
|
|
||||||
|
export function RenderNavGroups({ items, pathname }: { items: NavItemConfig[]; pathname: string }): React.JSX.Element {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const children = items.reduce((acc: React.ReactNode[], curr: NavItemConfig): React.ReactNode[] => {
|
||||||
|
acc.push(
|
||||||
|
<Stack
|
||||||
|
component="li"
|
||||||
|
key={curr.key}
|
||||||
|
spacing={1.5}
|
||||||
|
>
|
||||||
|
{curr.title ? (
|
||||||
|
<div>
|
||||||
|
<Typography sx={{ color: 'var(--NavGroup-title-color)', fontSize: '0.875rem', fontWeight: 500 }}>
|
||||||
|
{t(curr.title)}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div>{renderNavItems({ depth: 0, items: curr.items, pathname })}</div>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
component="ul"
|
||||||
|
spacing={2}
|
||||||
|
sx={{ listStyle: 'none', m: 0, p: 0 }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
@@ -0,0 +1,51 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
|
||||||
|
import type { NavItemConfig } from '@/types/nav';
|
||||||
|
|
||||||
|
import { NavItem } from './nav-item';
|
||||||
|
|
||||||
|
export function renderNavItems({
|
||||||
|
depth = 0,
|
||||||
|
items = [],
|
||||||
|
pathname,
|
||||||
|
}: {
|
||||||
|
depth: number;
|
||||||
|
items?: NavItemConfig[];
|
||||||
|
pathname: string;
|
||||||
|
}): React.JSX.Element {
|
||||||
|
const children = items.reduce((acc: React.ReactNode[], curr: NavItemConfig): React.ReactNode[] => {
|
||||||
|
const { items: childItems, key, ...item } = curr;
|
||||||
|
|
||||||
|
const forceOpen = childItems
|
||||||
|
? Boolean(childItems.find((childItem) => childItem.href && pathname.startsWith(childItem.href)))
|
||||||
|
: false;
|
||||||
|
|
||||||
|
acc.push(
|
||||||
|
<NavItem
|
||||||
|
depth={depth}
|
||||||
|
forceOpen={forceOpen}
|
||||||
|
key={key}
|
||||||
|
pathname={pathname}
|
||||||
|
{...item}
|
||||||
|
>
|
||||||
|
{childItems ? renderNavItems({ depth: depth + 1, pathname, items: childItems }) : null}
|
||||||
|
</NavItem>
|
||||||
|
);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
component="ul"
|
||||||
|
data-depth={depth}
|
||||||
|
spacing={1}
|
||||||
|
sx={{ listStyle: 'none', m: 0, p: 0 }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
Reference in New Issue
Block a user