179 lines
5.4 KiB
TypeScript
179 lines
5.4 KiB
TypeScript
import React, { useState } from 'react';
|
|
import {
|
|
Navbar,
|
|
Stack,
|
|
UnstyledButton,
|
|
Text,
|
|
ThemeIcon,
|
|
createStyles,
|
|
Collapse,
|
|
rem,
|
|
Divider,
|
|
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`,
|
|
},
|
|
}));
|
|
|
|
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() {
|
|
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));
|
|
|
|
return (
|
|
<div key={item.label}>
|
|
<UnstyledButton
|
|
className={cx(classes.link, { [classes.active]: isActive })}
|
|
style={{ paddingLeft: rem(16 + depth * 16) }}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
if (hasChildren) toggle(item.label);
|
|
else if (item.href) Inertia.visit(item.href);
|
|
}}
|
|
>
|
|
{item.icon && <ThemeIcon size="sm" variant="light" mr="sm">{item.icon}</ThemeIcon>}
|
|
<Text>{item.label}</Text>
|
|
{hasChildren && (
|
|
<IconChevronDown
|
|
size={16}
|
|
className={classes.toggleIcon}
|
|
style={{
|
|
transform: opened[item.label] ? 'rotate(180deg)' : 'rotate(0deg)',
|
|
}}
|
|
/>
|
|
)}
|
|
</UnstyledButton>
|
|
|
|
{hasChildren && (
|
|
<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: 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%',
|
|
}}
|
|
>
|
|
<Stack spacing="xs">
|
|
{sections.map((section) => (
|
|
<div key={section}>
|
|
<Text className={classes.sectionTitle}>{section}</Text>
|
|
{items
|
|
.filter((item) => item.section === section)
|
|
.map((item) => renderItem(item))}
|
|
<Divider my="xs" />
|
|
</div>
|
|
))}
|
|
</Stack>
|
|
</Navbar>
|
|
);
|
|
}
|