mirror of
https://github.com/umami-software/umami.git
synced 2026-02-07 22:27:16 +01:00
Consolidate WebsiteNavSection into WebsiteNav and fix scrollbar flash.
Move sidebar rendering logic into WebsiteNav with isCollapsed prop so SideNav uses WebsiteNav directly instead of duplicating code. Add overflow hidden to prevent horizontal scrollbar during expand transition. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
dea3c41ecc
commit
3467184b8f
2 changed files with 91 additions and 114 deletions
|
|
@ -1,5 +1,4 @@
|
|||
import {
|
||||
Box,
|
||||
Button,
|
||||
type ButtonProps,
|
||||
Column,
|
||||
|
|
@ -13,24 +12,17 @@ import {
|
|||
} from '@umami/react-zen';
|
||||
import Link from 'next/link';
|
||||
import type { Key } from 'react';
|
||||
import { WebsiteNav } from '@/app/(main)/websites/[websiteId]/WebsiteNav';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
import { useGlobalState, useMessages, useNavigation, useWebsiteNavItems } from '@/components/hooks';
|
||||
import {
|
||||
ArrowLeft,
|
||||
Globe,
|
||||
Grid2x2,
|
||||
LayoutDashboard,
|
||||
LinkIcon,
|
||||
PanelLeft,
|
||||
} from '@/components/icons';
|
||||
import { useGlobalState, useMessages, useNavigation } from '@/components/hooks';
|
||||
import { Globe, Grid2x2, LayoutDashboard, LinkIcon, PanelLeft } from '@/components/icons';
|
||||
import { LanguageButton } from '@/components/input/LanguageButton';
|
||||
import { NavButton } from '@/components/input/NavButton';
|
||||
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
||||
import { Logo } from '@/components/svg';
|
||||
|
||||
export function SideNav(props: any) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { pathname, renderUrl, websiteId, teamId, router } = useNavigation();
|
||||
const { pathname, renderUrl, websiteId, router } = useNavigation();
|
||||
const [isCollapsed, setIsCollapsed] = useGlobalState('sidenav-collapsed');
|
||||
|
||||
const hasNav = !!(websiteId || pathname.startsWith('/admin') || pathname.includes('/settings'));
|
||||
|
|
@ -66,10 +58,6 @@ export function SideNav(props: any) {
|
|||
router.push(id === 'user' ? '/websites' : `/teams/${id}/websites`);
|
||||
};
|
||||
|
||||
const handleWebsiteChange = (value: string) => {
|
||||
router.push(renderUrl(`/websites/${value}`));
|
||||
};
|
||||
|
||||
return (
|
||||
<Column
|
||||
{...props}
|
||||
|
|
@ -83,9 +71,10 @@ export function SideNav(props: any) {
|
|||
style={{
|
||||
width: isCollapsed ? '55px' : '240px',
|
||||
transition: 'width 0.2s ease-in-out',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Column overflowY="auto" style={{ minHeight: 0 }}>
|
||||
<Column style={{ minHeight: 0, overflowY: 'auto', overflowX: 'hidden' }}>
|
||||
<Row
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
|
|
@ -105,12 +94,7 @@ export function SideNav(props: any) {
|
|||
<NavButton showText={!isCollapsed} onAction={handleSelect} />
|
||||
</Row>
|
||||
{websiteId ? (
|
||||
<WebsiteNavSection
|
||||
websiteId={websiteId}
|
||||
teamId={teamId}
|
||||
isCollapsed={isCollapsed}
|
||||
onWebsiteChange={handleWebsiteChange}
|
||||
/>
|
||||
<WebsiteNav websiteId={websiteId} isCollapsed={isCollapsed} />
|
||||
) : (
|
||||
<Column gap="2">
|
||||
{links.map(({ id, path, label, icon }) => {
|
||||
|
|
@ -143,95 +127,6 @@ export function SideNav(props: any) {
|
|||
);
|
||||
}
|
||||
|
||||
function WebsiteNavSection({
|
||||
websiteId,
|
||||
teamId,
|
||||
isCollapsed,
|
||||
onWebsiteChange,
|
||||
}: {
|
||||
websiteId: string;
|
||||
teamId: string;
|
||||
isCollapsed: boolean;
|
||||
onWebsiteChange: (value: string) => void;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { renderUrl } = useNavigation();
|
||||
const { items, selectedKey } = useWebsiteNavItems(websiteId);
|
||||
|
||||
const renderValue = (value: any) => {
|
||||
return (
|
||||
<Text truncate style={{ maxWidth: 160, lineHeight: 1 }}>
|
||||
{value?.selectedItem?.name}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Column gap="2">
|
||||
<Link href={renderUrl('/websites', false)} role="button">
|
||||
<TooltipTrigger isDisabled={!isCollapsed} delay={0}>
|
||||
<Focusable>
|
||||
<Row
|
||||
alignItems="center"
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
borderRadius
|
||||
minHeight="40px"
|
||||
>
|
||||
<IconLabel
|
||||
icon={<ArrowLeft />}
|
||||
label={isCollapsed ? '' : formatMessage(labels.back)}
|
||||
padding
|
||||
/>
|
||||
</Row>
|
||||
</Focusable>
|
||||
<Tooltip placement="right">{formatMessage(labels.back)}</Tooltip>
|
||||
</TooltipTrigger>
|
||||
</Link>
|
||||
{!isCollapsed && (
|
||||
<Box marginBottom="2">
|
||||
<WebsiteSelect
|
||||
websiteId={websiteId}
|
||||
teamId={teamId}
|
||||
onChange={onWebsiteChange}
|
||||
renderValue={renderValue}
|
||||
buttonProps={{ style: { outline: 'none' } }}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{items.map(({ label: sectionLabel, items: sectionItems }, index) => (
|
||||
<Column key={`${sectionLabel}${index}`} gap="1" marginBottom="1">
|
||||
{!isCollapsed && (
|
||||
<Row padding>
|
||||
<Text weight="bold">{sectionLabel}</Text>
|
||||
</Row>
|
||||
)}
|
||||
{sectionItems.map(({ id, path, label, icon }) => {
|
||||
const isSelected = selectedKey === id;
|
||||
return (
|
||||
<Link key={id} href={path} role="button">
|
||||
<TooltipTrigger isDisabled={!isCollapsed} delay={0}>
|
||||
<Focusable>
|
||||
<Row
|
||||
alignItems="center"
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
backgroundColor={isSelected ? 'surface-sunken' : undefined}
|
||||
borderRadius
|
||||
minHeight="40px"
|
||||
>
|
||||
<IconLabel icon={icon} label={isCollapsed ? '' : label} padding />
|
||||
</Row>
|
||||
</Focusable>
|
||||
<Tooltip placement="right">{label}</Tooltip>
|
||||
</TooltipTrigger>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</Column>
|
||||
))}
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
const PanelButton = (props: ButtonProps) => {
|
||||
const [isCollapsed, setIsCollapsed] = useGlobalState('sidenav-collapsed');
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,15 +1,30 @@
|
|||
import { Column, Text } from '@umami/react-zen';
|
||||
import {
|
||||
Box,
|
||||
Column,
|
||||
Focusable,
|
||||
Label,
|
||||
Row,
|
||||
Text,
|
||||
Tooltip,
|
||||
TooltipTrigger,
|
||||
} from '@umami/react-zen';
|
||||
import Link from 'next/link';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
import { NavMenu } from '@/components/common/NavMenu';
|
||||
import { useNavigation, useWebsiteNavItems } from '@/components/hooks';
|
||||
import { useMessages, useNavigation, useWebsiteNavItems } from '@/components/hooks';
|
||||
import { ArrowLeft } from '@/components/icons';
|
||||
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
||||
|
||||
export function WebsiteNav({
|
||||
websiteId,
|
||||
isCollapsed,
|
||||
onItemClick,
|
||||
}: {
|
||||
websiteId: string;
|
||||
isCollapsed?: boolean;
|
||||
onItemClick?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { teamId, router, renderUrl } = useNavigation();
|
||||
const { items, selectedKey } = useWebsiteNavItems(websiteId);
|
||||
|
||||
|
|
@ -25,6 +40,73 @@ export function WebsiteNav({
|
|||
);
|
||||
};
|
||||
|
||||
if (isCollapsed !== undefined) {
|
||||
return (
|
||||
<Column gap="2">
|
||||
<Link href={renderUrl('/websites', false)} role="button">
|
||||
<TooltipTrigger isDisabled={!isCollapsed} delay={0}>
|
||||
<Focusable>
|
||||
<Row
|
||||
alignItems="center"
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
borderRadius
|
||||
minHeight="40px"
|
||||
>
|
||||
<IconLabel
|
||||
icon={<ArrowLeft />}
|
||||
label={isCollapsed ? '' : formatMessage(labels.back)}
|
||||
padding
|
||||
/>
|
||||
</Row>
|
||||
</Focusable>
|
||||
<Tooltip placement="right">{formatMessage(labels.back)}</Tooltip>
|
||||
</TooltipTrigger>
|
||||
</Link>
|
||||
{!isCollapsed && (
|
||||
<Box marginBottom="2">
|
||||
<WebsiteSelect
|
||||
websiteId={websiteId}
|
||||
teamId={teamId}
|
||||
onChange={handleChange}
|
||||
renderValue={renderValue}
|
||||
buttonProps={{ style: { outline: 'none' } }}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{items.map(({ label: sectionLabel, items: sectionItems }, index) => (
|
||||
<Column key={`${sectionLabel}${index}`} gap="1" marginBottom="1">
|
||||
{!isCollapsed && (
|
||||
<Row padding>
|
||||
<Text weight="bold">{sectionLabel}</Text>
|
||||
</Row>
|
||||
)}
|
||||
{sectionItems.map(({ id, path, label, icon }) => {
|
||||
const isSelected = selectedKey === id;
|
||||
return (
|
||||
<Link key={id} href={path} role="button">
|
||||
<TooltipTrigger isDisabled={!isCollapsed} delay={0}>
|
||||
<Focusable>
|
||||
<Row
|
||||
alignItems="center"
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
backgroundColor={isSelected ? 'surface-sunken' : undefined}
|
||||
borderRadius
|
||||
minHeight="40px"
|
||||
>
|
||||
<IconLabel icon={icon} label={isCollapsed ? '' : label} padding />
|
||||
</Row>
|
||||
</Focusable>
|
||||
<Tooltip placement="right">{label}</Tooltip>
|
||||
</TooltipTrigger>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</Column>
|
||||
))}
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Column padding="2" position="sticky" top="0" gap>
|
||||
<WebsiteSelect
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue