inspiren-sem-tool/resources/js/components/sidebar.tsx

205 lines
6.3 KiB
TypeScript

import React, { useState } from 'react';
import {
Navbar,
Stack,
UnstyledButton,
Text,
ThemeIcon,
createStyles,
Collapse,
rem,
Divider,
Tooltip,
useMantineTheme,
} from '@mantine/core';
import {
IconHome,
IconUsers,
IconChevronDown,
IconFolder,
IconSettings,
IconUserCircle,
} from '@tabler/icons-react';
import { Inertia } from '@inertiajs/inertia';
const useStyles = createStyles((theme) => ({
link: {
display: 'flex',
alignItems: 'center',
width: '100%',
padding: `${theme.spacing.sm}px ${theme.spacing.md}px`,
borderRadius: theme.radius.sm,
color: theme.colorScheme === 'dark' ? theme.colors.dark[0] : theme.black,
fontWeight: 500,
transition: 'all 0.2s ease',
'&:hover': {
backgroundColor:
theme.colorScheme === 'dark'
? theme.colors.dark[6]
: theme.colors.gray[0],
cursor: 'pointer',
},
},
active: {
backgroundColor:
theme.colorScheme === 'dark'
? theme.colors.dark[5]
: theme.colors.gray[2],
fontWeight: 700,
},
nested: {
paddingLeft: rem(32),
},
toggleIcon: {
marginLeft: 'auto',
transition: 'transform 0.2s ease',
},
sectionTitle: {
color: theme.colorScheme === 'dark' ? theme.colors.dark[2] : theme.colors.gray[7],
fontSize: theme.fontSizes.xs,
fontWeight: 700,
textTransform: 'uppercase',
padding: `${theme.spacing.xs}px ${theme.spacing.md}px`,
},
collapsedLink: {
justifyContent: 'center',
paddingLeft: theme.spacing.xs,
paddingRight: theme.spacing.xs,
},
}));
type SidebarItem = {
label: string;
href?: string;
icon?: React.ReactNode;
children?: SidebarItem[];
section?: string;
};
const items: SidebarItem[] = [
{ label: 'Dashboard', href: route("dashboard"), icon: <IconHome size={16} />, section: 'Main' },
{
label: 'Clients',
icon: <IconFolder size={16} />,
section: 'Main',
href: route("google-ads.accounts.index"),
},
{
label: 'Users',
icon: <IconUsers size={16} />,
section: 'Management',
href: route("management.users.index"),
},
{
label: 'Roles',
icon: <IconUserCircle size={16} />,
section: 'Management',
href: route("management.roles.index"),
},
// { label: 'Settings', href: '/settings', icon: <IconSettings size={16} /> , section: 'Management' },
];
export default function Sidebar({ collapsed = false }: { collapsed?: boolean }) {
const theme = useMantineTheme();
const { classes, cx } = useStyles();
const location = window.location.pathname;
const [opened, setOpened] = useState<Record<string, boolean>>({});
const toggle = (label: string) => {
setOpened((prev) => ({ ...prev, [label]: !prev[label] }));
};
const renderItem = (item: SidebarItem, depth = 0) => {
const hasChildren = !!item.children?.length;
const isActive =
item.href === location ||
(hasChildren && item.children?.some((c) => c.href === location));
const button = (
<UnstyledButton
className={cx(classes.link, {
[classes.active]: isActive,
[classes.collapsedLink]: collapsed,
})}
style={{ paddingLeft: collapsed ? undefined : rem(16 + depth * 16) }}
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
if (hasChildren && !collapsed) toggle(item.label);
else if (item.href) Inertia.visit(item.href);
}}
>
{item.icon && (
<ThemeIcon size="sm" variant="light" mr={collapsed ? 0 : 'sm'}>
{item.icon}
</ThemeIcon>
)}
{!collapsed && <Text>{item.label}</Text>}
{hasChildren && !collapsed && (
<IconChevronDown
size={16}
className={classes.toggleIcon}
style={{
transform: opened[item.label] ? 'rotate(180deg)' : 'rotate(0deg)',
}}
/>
)}
</UnstyledButton>
);
return (
<div key={item.label}>
{collapsed ? (
<Tooltip label={item.label} position="right" withinPortal>
{button}
</Tooltip>
) : (
button
)}
{hasChildren && !collapsed && (
<Collapse in={opened[item.label]}>
{item.children!.map((child) => renderItem(child, depth + 1))}
</Collapse>
)}
</div>
);
};
const sections = Array.from(new Set(items.map((i) => i.section).filter(Boolean)));
return (
<Navbar
width={{ base: collapsed ? 60 : 280 }}
p="xs"
style={{
backgroundColor:
theme.colorScheme === 'dark'
? theme.colors.dark[8]
: theme.colors.gray[0],
borderRight: `1px solid ${
theme.colorScheme === 'dark'
? theme.colors.dark[6]
: theme.colors.gray[2]
}`,
minHeight: '100%',
transition: 'width 160ms ease',
}}
>
<Stack spacing="xs">
{sections.map((section) => (
<div key={section}>
{!collapsed && (
<Text className={classes.sectionTitle}>{section}</Text>
)}
{items
.filter((item) => item.section === section)
.map((item) => renderItem(item))}
{!collapsed && <Divider my="xs" />}
</div>
))}
</Stack>
</Navbar>
);
}