Move website selector into top navigation

This commit is contained in:
Mike Cao 2026-02-12 16:46:01 -08:00
parent 400a35d7af
commit dd2d6bca45
4 changed files with 71 additions and 64 deletions

View file

@ -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} />

View file

@ -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>
); );

View file

@ -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

View file

@ -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 }) => (