merge design protal and backoffice

This commit is contained in:
mengstabketemaw
2026-06-17 13:50:20 +03:00
parent 36234a2948
commit 85e070db33
25 changed files with 2618 additions and 847 deletions

View File

@@ -0,0 +1,196 @@
import {
AppShell,
NavLink,
ScrollArea,
Stack,
Text,
Tooltip,
UnstyledButton,
rem,
} from '@mantine/core';
import {
IconChevronLeft,
IconChevronRight,
} from '@tabler/icons-react';
import type { Icon } from '@tabler/icons-react';
import { useTranslation } from 'react-i18next';
import { BrandMark } from '@ema-platform/auth';
export interface NavItem {
label: string;
icon: Icon;
to?: string;
soon?: boolean;
}
interface AppSidebarProps {
navItems: NavItem[];
collapsed: boolean;
activePath: string;
onToggleCollapse: () => void;
onNavigate: (item: NavItem) => void;
brandName: string;
brandSubtitle: string;
}
export function AppSidebar({
navItems,
collapsed,
activePath,
onToggleCollapse,
onNavigate,
brandName,
brandSubtitle,
}: AppSidebarProps) {
const { t } = useTranslation();
const activeNavItem = (item: NavItem) =>
!!item.to &&
(activePath === item.to || activePath.startsWith(`${item.to}/`));
return (
<>
{/* Brand header */}
<div
style={{
display: 'flex',
alignItems: 'center',
gap: rem(12),
height: 74,
padding: collapsed ? '0 12px' : '0 20px',
borderBottom: '1px solid var(--mantine-color-gray-2)',
justifyContent: collapsed ? 'center' : 'flex-start',
flexShrink: 0,
}}
>
<BrandMark size={32} />
{!collapsed && (
<div style={{ minWidth: 0 }}>
<Text
fw={800}
size="xs"
style={{
textTransform: 'uppercase',
letterSpacing: '0.08em',
lineHeight: 1.2,
color: 'var(--mantine-color-primary-7)',
}}
>
{brandName}
</Text>
<Text
size="xs"
c="dimmed"
style={{
textTransform: 'uppercase',
letterSpacing: '0.05em',
fontSize: rem(10),
lineHeight: 1.3,
}}
>
{brandSubtitle}
</Text>
</div>
)}
</div>
{/* Navigation items */}
<AppShell.Section grow component={ScrollArea} p="md">
<Stack gap={4}>
{navItems.map((item) => {
const ItemIcon = item.icon;
const active = activeNavItem(item);
if (collapsed) {
return (
<Tooltip key={item.label} label={item.label} position="right" withArrow>
<UnstyledButton
onClick={() => onNavigate(item)}
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
height: rem(40),
borderRadius: rem(10),
color: active ? 'var(--mantine-color-blue-6)' : undefined,
backgroundColor: active ? 'var(--mantine-color-blue-light)' : undefined,
}}
>
<ItemIcon size={20} stroke={1.6} />
</UnstyledButton>
</Tooltip>
);
}
return (
<NavLink
key={item.label}
active={active}
label={item.label}
leftSection={<ItemIcon size={19} stroke={1.6} />}
onClick={() => onNavigate(item)}
variant="light"
styles={{ root: { borderRadius: rem(10) }, label: { fontWeight: 500 } }}
/>
);
})}
</Stack>
</AppShell.Section>
{/* Collapse toggle */}
<div
style={{
padding: rem(8),
borderTop: '1px solid var(--mantine-color-gray-2)',
flexShrink: 0,
}}
>
<Tooltip
label={collapsed ? t('nav.expandSidebar', 'Expand sidebar') : t('nav.collapseSidebar', 'Collapse sidebar')}
position="right"
withArrow
disabled={!collapsed}
>
<UnstyledButton
onClick={onToggleCollapse}
visibleFrom="sm"
style={{
display: 'flex',
alignItems: 'center',
justifyContent: collapsed ? 'center' : 'flex-start',
gap: rem(10),
width: '100%',
padding: '10px 12px',
borderRadius: rem(12),
color: 'var(--mantine-color-gray-5)',
transition: 'all 150ms ease',
cursor: 'pointer',
border: 'none',
background: 'transparent',
fontSize: rem(13),
fontWeight: 500,
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'var(--mantine-color-gray-0)';
e.currentTarget.style.color = 'var(--mantine-color-gray-7)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'transparent';
e.currentTarget.style.color = 'var(--mantine-color-gray-5)';
}}
>
{collapsed ? (
<IconChevronRight size={18} stroke={1.6} />
) : (
<>
<IconChevronLeft size={18} stroke={1.6} />
<span>{t('nav.collapseSidebar', 'Collapse')}</span>
</>
)}
</UnstyledButton>
</Tooltip>
</div>
</>
);
}