update main-nav and side-nav,
This commit is contained in:
@@ -112,9 +112,15 @@ export function ContactsPopover({ anchorEl, onClose, open = false }: ContactsPop
|
||||
<Typography variant="h6">Contacts</Typography>
|
||||
</Box>
|
||||
<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) => (
|
||||
<ListItem disablePadding key={contact.id}>
|
||||
<ListItem
|
||||
disablePadding
|
||||
key={contact.id}
|
||||
>
|
||||
<ListItemButton>
|
||||
<ListItemAvatar>
|
||||
<Avatar src={contact.avatar} />
|
||||
@@ -122,14 +128,28 @@ export function ContactsPopover({ anchorEl, onClose, open = false }: ContactsPop
|
||||
<ListItemText
|
||||
disableTypography
|
||||
primary={
|
||||
<Link color="text.primary" noWrap underline="none" variant="subtitle2">
|
||||
<Link
|
||||
color="text.primary"
|
||||
noWrap
|
||||
underline="none"
|
||||
variant="subtitle2"
|
||||
>
|
||||
{contact.name}
|
||||
</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) ? (
|
||||
<Typography color="text.secondary" sx={{ whiteSpace: 'nowrap' }} variant="caption">
|
||||
<Typography
|
||||
color="text.secondary"
|
||||
sx={{ whiteSpace: 'nowrap' }}
|
||||
variant="caption"
|
||||
>
|
||||
{dayjs(contact.lastActivity).fromNow()}
|
||||
</Typography>
|
||||
) : null}
|
||||
|
@@ -34,6 +34,7 @@ export function HorizontalLayout({ children }: HorizontalLayoutProps): React.JSX
|
||||
color={settings.navColor}
|
||||
items={layoutConfig.navItems}
|
||||
/>
|
||||
|
||||
<Box
|
||||
component="main"
|
||||
sx={{
|
||||
|
@@ -36,15 +36,15 @@ import { Logo } from '@/components/core/logo';
|
||||
import { SearchDialog } from '@/components/dashboard/layout/search-dialog';
|
||||
import type { ColorScheme } from '@/styles/theme/types';
|
||||
|
||||
import { ContactsPopover } from '../contacts-popover';
|
||||
import { languageFlags, LanguagePopover } from '../language-popover';
|
||||
import type { Language } from '../language-popover';
|
||||
import { MobileNav } from '../mobile-nav';
|
||||
import { icons } from '../nav-icons';
|
||||
import { NotificationsPopover } from '../notifications-popover';
|
||||
import { UserPopover } from '../user-popover/user-popover';
|
||||
import { WorkspacesSwitch } from '../workspaces-switch';
|
||||
import { navColorStyles } from './styles';
|
||||
import { ContactsPopover } from '../../contacts-popover';
|
||||
import { languageFlags, LanguagePopover } from '../../language-popover';
|
||||
import type { Language } from '../../language-popover';
|
||||
import { MobileNav } from '../../mobile-nav';
|
||||
import { icons } from '../../nav-icons';
|
||||
import { NotificationsPopover } from '../../notifications-popover';
|
||||
import { UserPopover } from '../../user-popover/user-popover';
|
||||
import { WorkspacesSwitch } from '../../workspaces-switch';
|
||||
import { navColorStyles } from '../styles';
|
||||
|
||||
const logoColors = {
|
||||
dark: { blend_in: 'light', discrete: 'light', evident: 'light' },
|
||||
|
@@ -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 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';
|
||||
@@ -13,136 +11,9 @@ import { CaretRight as CaretRightIcon } from '@phosphor-icons/react/dist/ssr/Car
|
||||
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';
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
import { icons } from '../../nav-icons';
|
||||
|
||||
interface NavItemProps extends Omit<NavItemConfig, 'items'> {
|
||||
children?: React.ReactNode;
|
||||
@@ -151,7 +22,7 @@ interface NavItemProps extends Omit<NavItemConfig, 'items'> {
|
||||
pathname: string;
|
||||
}
|
||||
|
||||
function NavItem({
|
||||
export function NavItem({
|
||||
children,
|
||||
depth,
|
||||
disabled,
|
||||
@@ -173,7 +44,11 @@ function NavItem({
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Box component="li" data-depth={depth} sx={{ userSelect: 'none' }}>
|
||||
<Box
|
||||
component="li"
|
||||
data-depth={depth}
|
||||
sx={{ userSelect: 'none' }}
|
||||
>
|
||||
<Box
|
||||
{...(isBranch
|
||||
? {
|
||||
@@ -254,15 +129,27 @@ function NavItem({
|
||||
{t(title || '')}
|
||||
</Typography>
|
||||
</Box>
|
||||
{label ? <Chip color="primary" label={label} size="small" /> : null}
|
||||
{label ? (
|
||||
<Chip
|
||||
color="primary"
|
||||
label={label}
|
||||
size="small"
|
||||
/>
|
||||
) : null}
|
||||
{external ? (
|
||||
<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>
|
||||
) : null}
|
||||
{isBranch ? (
|
||||
<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>
|
||||
) : null}
|
||||
</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