mirror of
https://github.com/umami-software/umami.git
synced 2026-02-15 18:15:35 +01:00
Move website selector into top navigation
This commit is contained in:
parent
400a35d7af
commit
dd2d6bca45
4 changed files with 71 additions and 64 deletions
|
|
@ -4,8 +4,8 @@ import Script from 'next/script';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { MobileNav } from '@/app/(main)/MobileNav';
|
import { MobileNav } from '@/app/(main)/MobileNav';
|
||||||
import { SideNav } from '@/app/(main)/SideNav';
|
import { SideNav } from '@/app/(main)/SideNav';
|
||||||
|
import { TopNav } from '@/app/(main)/TopNav';
|
||||||
import { useConfig, useLoginQuery, useNavigation } from '@/components/hooks';
|
import { useConfig, useLoginQuery, useNavigation } from '@/components/hooks';
|
||||||
import { TeamsButton } from '@/components/input/TeamsButton';
|
|
||||||
import { LAST_TEAM_CONFIG } from '@/lib/constants';
|
import { LAST_TEAM_CONFIG } from '@/lib/constants';
|
||||||
import { removeItem, setItem } from '@/lib/storage';
|
import { removeItem, setItem } from '@/lib/storage';
|
||||||
import { UpdateNotice } from './UpdateNotice';
|
import { UpdateNotice } from './UpdateNotice';
|
||||||
|
|
@ -51,18 +51,7 @@ export function App({ children }) {
|
||||||
<SideNav />
|
<SideNav />
|
||||||
</Column>
|
</Column>
|
||||||
<Column overflowX="hidden" position="relative">
|
<Column overflowX="hidden" position="relative">
|
||||||
<Row
|
<TopNav />
|
||||||
position="sticky"
|
|
||||||
top="0"
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="space-between"
|
|
||||||
paddingY="2"
|
|
||||||
zIndex={100}
|
|
||||||
>
|
|
||||||
<Row backgroundColor="surface-raised">
|
|
||||||
<TeamsButton />
|
|
||||||
</Row>
|
|
||||||
</Row>
|
|
||||||
<Column alignItems="center">{children}</Column>
|
<Column alignItems="center">{children}</Column>
|
||||||
</Column>
|
</Column>
|
||||||
<UpdateNotice user={user} config={config} />
|
<UpdateNotice user={user} config={config} />
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,47 @@
|
||||||
import { Row, ThemeButton } from '@umami/react-zen';
|
'use client';
|
||||||
import { LanguageButton } from '@/components/input/LanguageButton';
|
import { Icon, Row } from '@umami/react-zen';
|
||||||
import { ProfileButton } from '@/components/input/ProfileButton';
|
import { useNavigation } from '@/components/hooks';
|
||||||
|
import { Slash } from '@/components/icons';
|
||||||
|
import { TeamsButton } from '@/components/input/TeamsButton';
|
||||||
|
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
||||||
|
|
||||||
export function TopNav() {
|
export function TopNav() {
|
||||||
|
const { websiteId, teamId, router, renderUrl } = useNavigation();
|
||||||
|
|
||||||
|
const handleWebsiteChange = (value: string) => {
|
||||||
|
router.push(renderUrl(`/websites/${value}`));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row
|
<Row
|
||||||
position="absolute"
|
position="sticky"
|
||||||
top="0"
|
top="0"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="flex-end"
|
justifyContent="flex-start"
|
||||||
paddingY="2"
|
paddingY="2"
|
||||||
paddingX="3"
|
paddingX="3"
|
||||||
paddingRight="5"
|
paddingRight="5"
|
||||||
width="100%"
|
width="100%"
|
||||||
style={{ position: 'sticky', top: 0 }}
|
zIndex={100}
|
||||||
zIndex={1}
|
|
||||||
>
|
>
|
||||||
<Row
|
<Row alignItems="center" backgroundColor="surface-raised" borderRadius>
|
||||||
alignItems="center"
|
<TeamsButton />
|
||||||
justifyContent="flex-end"
|
{websiteId && (
|
||||||
backgroundColor="surface-raised"
|
<>
|
||||||
borderRadius
|
<Icon size="sm" color="muted" style={{ opacity: 0.7, margin: '0 6px' }}>
|
||||||
>
|
<Slash />
|
||||||
<ThemeButton />
|
</Icon>
|
||||||
<LanguageButton />
|
<WebsiteSelect
|
||||||
<ProfileButton />
|
websiteId={websiteId}
|
||||||
|
teamId={teamId}
|
||||||
|
onChange={handleWebsiteChange}
|
||||||
|
buttonProps={{
|
||||||
|
variant: 'quiet',
|
||||||
|
style: { minHeight: 40, minWidth: 200, maxWidth: 200 },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,8 @@
|
||||||
import {
|
import { Column, Focusable, Row, Text, Tooltip, TooltipTrigger } from '@umami/react-zen';
|
||||||
Box,
|
|
||||||
Column,
|
|
||||||
Focusable,
|
|
||||||
Label,
|
|
||||||
Row,
|
|
||||||
Text,
|
|
||||||
Tooltip,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from '@umami/react-zen';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { IconLabel } from '@/components/common/IconLabel';
|
import { IconLabel } from '@/components/common/IconLabel';
|
||||||
import { NavMenu } from '@/components/common/NavMenu';
|
|
||||||
import { useMessages, useNavigation, useWebsiteNavItems } from '@/components/hooks';
|
import { useMessages, useNavigation, useWebsiteNavItems } from '@/components/hooks';
|
||||||
import { ArrowLeft } from '@/components/icons';
|
import { ArrowLeft } from '@/components/icons';
|
||||||
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
|
||||||
|
|
||||||
export function WebsiteNav({
|
export function WebsiteNav({
|
||||||
websiteId,
|
websiteId,
|
||||||
|
|
@ -25,16 +14,12 @@ export function WebsiteNav({
|
||||||
onItemClick?: () => void;
|
onItemClick?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { t, labels } = useMessages();
|
const { t, labels } = useMessages();
|
||||||
const { teamId, router, renderUrl } = useNavigation();
|
const { renderUrl } = useNavigation();
|
||||||
const { items, selectedKey } = useWebsiteNavItems(websiteId);
|
const { items, selectedKey } = useWebsiteNavItems(websiteId);
|
||||||
|
|
||||||
const handleChange = (value: string) => {
|
|
||||||
router.push(renderUrl(`/websites/${value}`));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column gap="2">
|
<Column gap="2">
|
||||||
<Link href={renderUrl('/websites', false)} role="button">
|
<Link href={renderUrl('/websites', false)} role="button" onClick={onItemClick}>
|
||||||
<TooltipTrigger isDisabled={!isCollapsed} delay={0}>
|
<TooltipTrigger isDisabled={!isCollapsed} delay={0}>
|
||||||
<Focusable>
|
<Focusable>
|
||||||
<Row
|
<Row
|
||||||
|
|
@ -49,15 +34,6 @@ export function WebsiteNav({
|
||||||
<Tooltip placement="right">{t(labels.back)}</Tooltip>
|
<Tooltip placement="right">{t(labels.back)}</Tooltip>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
</Link>
|
</Link>
|
||||||
<Box marginBottom="2">
|
|
||||||
<WebsiteSelect
|
|
||||||
websiteId={websiteId}
|
|
||||||
teamId={teamId}
|
|
||||||
onChange={handleChange}
|
|
||||||
buttonProps={{ style: { outline: 'none' } }}
|
|
||||||
isCollapsed={isCollapsed}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
{items.map(({ label: sectionLabel, items: sectionItems }, index) => (
|
{items.map(({ label: sectionLabel, items: sectionItems }, index) => (
|
||||||
<Column key={`${sectionLabel}${index}`} gap="1" marginBottom="1">
|
<Column key={`${sectionLabel}${index}`} gap="1" marginBottom="1">
|
||||||
{!isCollapsed && (
|
{!isCollapsed && (
|
||||||
|
|
@ -68,7 +44,7 @@ export function WebsiteNav({
|
||||||
{sectionItems.map(({ id, path, label, icon }) => {
|
{sectionItems.map(({ id, path, label, icon }) => {
|
||||||
const isSelected = selectedKey === id;
|
const isSelected = selectedKey === id;
|
||||||
return (
|
return (
|
||||||
<Link key={id} href={path} role="button">
|
<Link key={id} href={path} role="button" onClick={onItemClick}>
|
||||||
<TooltipTrigger isDisabled={!isCollapsed} delay={0}>
|
<TooltipTrigger isDisabled={!isCollapsed} delay={0}>
|
||||||
<Focusable>
|
<Focusable>
|
||||||
<Row
|
<Row
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { Icon, ListItem, Row, Select, type SelectProps, Text } from '@umami/react-zen';
|
import { Icon, ListItem, Row, Select, type SelectProps, Text } from '@umami/react-zen';
|
||||||
import { ChevronRight } from 'lucide-react';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Empty } from '@/components/common/Empty';
|
import { Empty } from '@/components/common/Empty';
|
||||||
import {
|
import {
|
||||||
|
|
@ -8,6 +7,7 @@ import {
|
||||||
useUserWebsitesQuery,
|
useUserWebsitesQuery,
|
||||||
useWebsiteQuery,
|
useWebsiteQuery,
|
||||||
} from '@/components/hooks';
|
} from '@/components/hooks';
|
||||||
|
import { Globe } from '@/components/icons';
|
||||||
|
|
||||||
export function WebsiteSelect({
|
export function WebsiteSelect({
|
||||||
websiteId,
|
websiteId,
|
||||||
|
|
@ -15,6 +15,8 @@ export function WebsiteSelect({
|
||||||
onChange,
|
onChange,
|
||||||
includeTeams,
|
includeTeams,
|
||||||
isCollapsed,
|
isCollapsed,
|
||||||
|
buttonProps,
|
||||||
|
listProps,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
websiteId?: string;
|
websiteId?: string;
|
||||||
|
|
@ -47,7 +49,18 @@ export function WebsiteSelect({
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderValue = () => {
|
const renderValue = () => {
|
||||||
return isCollapsed ? '' : name;
|
if (isCollapsed) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row alignItems="center" gap>
|
||||||
|
<Icon>
|
||||||
|
<Globe />
|
||||||
|
</Icon>
|
||||||
|
<Text truncate>{name}</Text>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -61,12 +74,24 @@ export function WebsiteSelect({
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onOpenChange={handleOpenChange}
|
onOpenChange={handleOpenChange}
|
||||||
renderValue={renderValue}
|
renderValue={renderValue}
|
||||||
listProps={{
|
|
||||||
renderEmptyState: () => <Empty message={t(messages.noResultsFound)} />,
|
|
||||||
style: { maxHeight: 'calc(42vh - 65px)', width: 280 },
|
|
||||||
}}
|
|
||||||
buttonProps={{
|
buttonProps={{
|
||||||
style: { minHeight: 40, gap: 0, justifyContent: isCollapsed ? 'start' : undefined },
|
...buttonProps,
|
||||||
|
style: {
|
||||||
|
minHeight: 40,
|
||||||
|
gap: 0,
|
||||||
|
justifyContent: isCollapsed ? 'start' : undefined,
|
||||||
|
...buttonProps?.style,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
listProps={{
|
||||||
|
...listProps,
|
||||||
|
renderEmptyState:
|
||||||
|
listProps?.renderEmptyState || (() => <Empty message={t(messages.noResultsFound)} />),
|
||||||
|
style: {
|
||||||
|
maxHeight: 'calc(42vh - 65px)',
|
||||||
|
width: 280,
|
||||||
|
...listProps?.style,
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{listItems.map(({ id, name }) => (
|
{listItems.map(({ id, name }) => (
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue