Rename SideMenu to NavMenu, fix tooltips, and update react-zen.

- Rename SideMenu to NavMenu with visible group title labels and selected item highlighting
- Update react-zen to 0.242.0 and fix responsive breakpoints (xs -> base)
- Style floating tooltips with inverted background across WorldMap, charts, and WeeklyTraffic
- Add CSS variables for primary color and use IconLabel consistently
- Remove stray console.log from LoadingPanel

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mike Cao 2026-02-06 01:58:55 -08:00
parent 7cafc3e61d
commit c6dd3fb6ff
36 changed files with 107 additions and 171 deletions

View file

@ -11,7 +11,7 @@
},
"type": "module",
"scripts": {
"dev": "next dev -p 4444 --turbo",
"dev": "next dev -p 3001 --turbo",
"build": "npm-run-all check-env build-db check-db build-tracker build-geo build-app",
"start": "next start",
"build-docker": "npm-run-all build-db build-tracker build-geo build-app",
@ -72,7 +72,7 @@
"@react-spring/web": "^10.0.3",
"@svgr/cli": "^8.1.0",
"@tanstack/react-query": "^5.90.20",
"@umami/react-zen": "^0.239.0",
"@umami/react-zen": "^0.242.0",
"@umami/redis-client": "^0.30.0",
"bcryptjs": "^3.0.2",
"chalk": "^5.6.2",

79
pnpm-lock.yaml generated
View file

@ -42,8 +42,8 @@ importers:
specifier: ^5.90.20
version: 5.90.20(react@19.2.4)
'@umami/react-zen':
specifier: ^0.239.0
version: 0.239.0(@types/react@19.2.13)(immer@10.2.0)(react-aria-components@1.14.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.1.18)(use-sync-external-store@1.6.0(react@19.2.4))
specifier: ^0.242.0
version: 0.242.0(@types/react@19.2.13)(immer@10.2.0)(react-aria-components@1.14.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.1.18)(use-sync-external-store@1.6.0(react@19.2.4))
'@umami/redis-client':
specifier: ^0.30.0
version: 0.30.0
@ -1469,9 +1469,6 @@ packages:
cpu: [x64]
os: [win32]
'@internationalized/date@3.10.1':
resolution: {integrity: sha512-oJrXtQiAXLvT9clCf1K4kxp3eKsQhIaZqxEyowkBcsvZDdZkbWrVmnGknxs5flTD0VGsxrxKgBCZty1EzoiMzA==}
'@internationalized/date@3.11.0':
resolution: {integrity: sha512-BOx5huLAWhicM9/ZFs84CzP+V3gBW6vlpM02yzsdYC7TGlZJX1OJiEEHcSayF00Z+3jLlm4w79amvSt6RqKN3Q==}
@ -1834,12 +1831,6 @@ packages:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
'@react-aria/focus@3.21.3':
resolution: {integrity: sha512-FsquWvjSCwC2/sBk4b+OqJyONETUIXQ2vM0YdPAuC+QFQh2DT6TIBo6dOZVSezlhudDla69xFBd6JvCFq1AbUw==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
'@react-aria/focus@3.21.4':
resolution: {integrity: sha512-6gz+j9ip0/vFRTKJMl3R30MHopn4i19HqqLfSQfElxJD+r9hBnYG1Q6Wd/kl/WRR1+CALn2F+rn06jUnf5sT8Q==}
peerDependencies:
@ -1870,12 +1861,6 @@ packages:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
'@react-aria/interactions@3.26.0':
resolution: {integrity: sha512-AAEcHiltjfbmP1i9iaVw34Mb7kbkiHpYdqieWufldh4aplWgsF11YQZOfaCJW4QoR2ML4Zzoa9nfFwLXA52R7Q==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
'@react-aria/interactions@3.27.0':
resolution: {integrity: sha512-D27pOy+0jIfHK60BB26AgqjjRFOYdvVSkwC31b2LicIzRCSPOSP06V4gMHuGmkhNTF4+YWDi1HHYjxIvMeiSlA==}
peerDependencies:
@ -2053,12 +2038,6 @@ packages:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
'@react-aria/utils@3.32.0':
resolution: {integrity: sha512-/7Rud06+HVBIlTwmwmJa2W8xVtgxgzm0+kLbuFooZRzKDON6hhozS1dOMR/YLMxyJOaYOTpImcP4vRR9gL1hEg==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
'@react-aria/utils@3.33.0':
resolution: {integrity: sha512-yvz7CMH8d2VjwbSa5nGXqjU031tYhD8ddax95VzJsHSPyqHDEGfxul8RkhGV6oO7bVqZxVs6xY66NIgae+FHjw==}
peerDependencies:
@ -2391,11 +2370,6 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
'@react-types/shared@3.32.1':
resolution: {integrity: sha512-famxyD5emrGGpFuUlgOP6fVW2h/ZaF405G5KDi3zPHzyjAWys/8W6NAVJtNbkCkhedmvL0xOhvt8feGXyXaw5w==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
'@react-types/shared@3.33.0':
resolution: {integrity: sha512-xuUpP6MyuPmJtzNOqF5pzFUIHH2YogyOQfUQHag54PRmWB7AbjuGWBUv0l1UDmz6+AbzAYGmDVAzcRDOu2PFpw==}
peerDependencies:
@ -2914,8 +2888,8 @@ packages:
'@types/yauzl@2.10.3':
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
'@umami/react-zen@0.239.0':
resolution: {integrity: sha512-6CSPYPnpCnau+Ie8nyC1BDVFrNoes5XOuy+b6hb6htDTvqTKIB8nvky0hJHe6kLz6Kp7xTbknjP+PQ5EnLBC6w==}
'@umami/react-zen@0.242.0':
resolution: {integrity: sha512-dahKoYDrFg1MVjww6M0RpUacFidHoXBRuFKOe68AZ9PWQaXX/kUweo/5m1YzmUMcnouwwCer/rAzCfHJ2Vso9w==}
peerDependencies:
react: ^18.0.0 || ^19.0.0
react-aria-components: ^1.0.0
@ -8181,10 +8155,6 @@ snapshots:
'@img/sharp-win32-x64@0.34.5':
optional: true
'@internationalized/date@3.10.1':
dependencies:
'@swc/helpers': 0.5.18
'@internationalized/date@3.11.0':
dependencies:
'@swc/helpers': 0.5.18
@ -8768,16 +8738,6 @@ snapshots:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
'@react-aria/focus@3.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@react-aria/interactions': 3.26.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@react-aria/utils': 3.32.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@react-types/shared': 3.32.1(react@19.2.4)
'@swc/helpers': 0.5.18
clsx: 2.1.1
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
'@react-aria/focus@3.21.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@react-aria/interactions': 3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@ -8844,16 +8804,6 @@ snapshots:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
'@react-aria/interactions@3.26.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@react-aria/ssr': 3.9.10(react@19.2.4)
'@react-aria/utils': 3.32.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@react-stately/flags': 3.1.2
'@react-types/shared': 3.32.1(react@19.2.4)
'@swc/helpers': 0.5.18
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
'@react-aria/interactions@3.27.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@react-aria/ssr': 3.9.10(react@19.2.4)
@ -9216,17 +9166,6 @@ snapshots:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
'@react-aria/utils@3.32.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@react-aria/ssr': 3.9.10(react@19.2.4)
'@react-stately/flags': 3.1.2
'@react-stately/utils': 3.11.0(react@19.2.4)
'@react-types/shared': 3.32.1(react@19.2.4)
'@swc/helpers': 0.5.18
clsx: 2.1.1
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
'@react-aria/utils@3.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@react-aria/ssr': 3.9.10(react@19.2.4)
@ -9696,10 +9635,6 @@ snapshots:
'@react-types/shared': 3.33.0(react@19.2.4)
react: 19.2.4
'@react-types/shared@3.32.1(react@19.2.4)':
dependencies:
react: 19.2.4
'@react-types/shared@3.33.0(react@19.2.4)':
dependencies:
react: 19.2.4
@ -10157,10 +10092,10 @@ snapshots:
'@types/node': 24.10.11
optional: true
'@umami/react-zen@0.239.0(@types/react@19.2.13)(immer@10.2.0)(react-aria-components@1.14.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.1.18)(use-sync-external-store@1.6.0(react@19.2.4))':
'@umami/react-zen@0.242.0(@types/react@19.2.13)(immer@10.2.0)(react-aria-components@1.14.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.1.18)(use-sync-external-store@1.6.0(react@19.2.4))':
dependencies:
'@internationalized/date': 3.10.1
'@react-aria/focus': 3.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@internationalized/date': 3.11.0
'@react-aria/focus': 3.21.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@react-spring/web': 9.7.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
clsx: 2.1.1
lucide-react: 0.555.0(react@19.2.4)

View file

@ -13,9 +13,9 @@ export function AdminLayout({ children }: { children: ReactNode }) {
}
return (
<Grid columns={{ xs: '1fr', lg: 'auto 1fr' }} width="100%" height="100%">
<Grid columns={{ base: '1fr', lg: 'auto 1fr' }} width="100%" height="100%">
<Column
display={{ xs: 'none', lg: 'flex' }}
display={{ base: 'none', lg: 'flex' }}
width="240px"
height="100%"
border="right"

View file

@ -1,4 +1,4 @@
import { SideMenu } from '@/components/common/SideMenu';
import { NavMenu } from '@/components/common/NavMenu';
import { useMessages, useNavigation } from '@/components/hooks';
import { Globe, User, Users } from '@/components/icons';
@ -37,7 +37,7 @@ export function AdminNav({ onItemClick }: { onItemClick?: () => void }) {
?.find(({ path }) => path && pathname.startsWith(path))?.id;
return (
<SideMenu
<NavMenu
items={items}
title={formatMessage(labels.admin)}
selectedKey={selectedKey}

View file

@ -76,7 +76,7 @@ function BoardEditHeader() {
return (
<Grid
columns={{ xs: '1fr', md: '1fr 1fr' }}
columns={{ base: '1fr', md: '1fr 1fr' }}
paddingY="4"
marginBottom="6"
border="bottom"

View file

@ -6,9 +6,9 @@ import { SettingsNav } from './SettingsNav';
export function SettingsLayout({ children }: { children: ReactNode }) {
return (
<Grid columns={{ xs: '1fr', lg: 'auto 1fr' }} width="100%" height="100%">
<Grid columns={{ base: '1fr', lg: 'auto 1fr' }} width="100%" height="100%">
<Column
display={{ xs: 'none', lg: 'flex' }}
display={{ base: 'none', lg: 'flex' }}
width="240px"
height="100%"
border="right"

View file

@ -1,4 +1,4 @@
import { SideMenu } from '@/components/common/SideMenu';
import { NavMenu } from '@/components/common/NavMenu';
import { useMessages, useNavigation } from '@/components/hooks';
import { Settings2, UserCircle, Users } from '@/components/icons';
@ -42,7 +42,7 @@ export function SettingsNav({ onItemClick }: { onItemClick?: () => void }) {
.find(({ path }) => path && pathname.includes(path.split('?')[0]))?.id;
return (
<SideMenu
<NavMenu
items={items}
title={formatMessage(labels.settings)}
selectedKey={selectedKey}

View file

@ -95,7 +95,7 @@ export function Attribution({
})}
</MetricsBar>
<SectionHeader title={formatMessage(labels.sources)} />
<Grid columns={{ xs: '1fr', md: '1fr 1fr' }} gap>
<Grid columns={{ base: '1fr', md: '1fr 1fr' }} gap>
<Panel>
<AttributionTable data={data?.referrer} title={formatMessage(labels.referrer)} />
</Panel>
@ -104,7 +104,7 @@ export function Attribution({
</Panel>
</Grid>
<SectionHeader title="UTM" />
<Grid columns={{ xs: '1fr', md: '1fr 1fr' }} gap>
<Grid columns={{ base: '1fr', md: '1fr 1fr' }} gap>
<Panel>
<AttributionTable data={data?.utm_source} title={formatMessage(labels.sources)} />
</Panel>

View file

@ -17,7 +17,7 @@ export function AttributionPage({ websiteId }: { websiteId: string }) {
return (
<Column gap="6">
<WebsiteControls websiteId={websiteId} />
<Grid columns={{ xs: '1fr', md: '1fr 1fr 1fr' }} gap>
<Grid columns={{ base: '1fr', md: '1fr 1fr 1fr' }} gap>
<Column>
<Select
label={formatMessage(labels.model)}

View file

@ -26,7 +26,7 @@ export function GoalsPage({ websiteId }: { websiteId: string }) {
)}
<LoadingPanel data={data} isLoading={isLoading} error={error}>
{data && (
<Grid columns={{ xs: '1fr', md: '1fr 1fr' }} gap>
<Grid columns={{ base: '1fr', md: '1fr 1fr' }} gap>
{data.data.map((report: any) => (
<Panel key={report.id}>
<Goal {...report} startDate={startDate} endDate={endDate} />

View file

@ -53,7 +53,7 @@ export function Retention({ websiteId, days = DAYS, startDate, endDate }: Retent
<Panel allowFullscreen height="900px">
<Column
paddingY="6"
paddingX={{ xs: '3', md: '6' }}
paddingX={{ base: '3', md: '6' }}
position="absolute"
top="40px"
left="0"

View file

@ -43,7 +43,7 @@ export function UTM({ websiteId, startDate, endDate }: UTMProps) {
return (
<Panel key={param}>
<Grid columns={{ xs: '1fr', md: '1fr 1fr' }} gap="6">
<Grid columns={{ base: '1fr', md: '1fr 1fr' }} gap="6">
<Column>
<Heading>
<Text transform="capitalize">{param.replace(/^utm_/, '')}</Text>

View file

@ -23,11 +23,11 @@ export function WebsiteControls({
}) {
return (
<Column gap>
<Grid columns={{ xs: '1fr', md: 'auto 1fr' }} gap>
<Grid columns={{ base: '1fr', md: 'auto 1fr' }} gap>
<Row alignItems="center" justifyContent="flex-start" gap="4">
{allowFilter && <WebsiteFilterButton websiteId={websiteId} />}
</Row>
<Row alignItems="center" justifyContent={{ xs: 'flex-start', md: 'flex-end' }}>
<Row alignItems="center" justifyContent={{ base: 'flex-start', md: 'flex-end' }}>
{allowDateFilter && (
<WebsiteDateFilter websiteId={websiteId} allowCompare={allowCompare} />
)}

View file

@ -1,4 +1,4 @@
import { SideMenu } from '@/components/common/SideMenu';
import { NavMenu } from '@/components/common/NavMenu';
import { useMessages, useNavigation } from '@/components/hooks';
import {
AppWindow,
@ -226,5 +226,5 @@ export function WebsiteExpandedMenu({
},
];
return <SideMenu items={items} selectedKey={view} onItemClick={onItemClick} />;
return <NavMenu items={items} selectedKey={view} onItemClick={onItemClick} />;
}

View file

@ -20,7 +20,7 @@ export function WebsiteExpandedView({
return (
<Column height="100%" overflow="hidden" gap>
<Row id="expanded-mobile-menu-button" display={{ xs: 'flex', md: 'none' }}>
<Row id="expanded-mobile-menu-button" display={{ base: 'flex', md: 'none' }}>
<MobileMenuButton>
{({ close }) => {
return (
@ -31,10 +31,10 @@ export function WebsiteExpandedView({
}}
</MobileMenuButton>
</Row>
<Grid columns={{ xs: '1fr', md: 'auto 1fr' }} gap="6" overflow="hidden">
<Grid columns={{ base: '1fr', md: 'auto 1fr' }} gap="6" overflow="hidden">
<Column
id="metrics-expanded-menu"
display={{ xs: 'none', md: 'flex' }}
display={{ base: 'none', md: 'flex' }}
width="240px"
gap="6"
border="right"

View file

@ -1,5 +1,6 @@
import { Icon, Row, Text } from '@umami/react-zen';
import { Favicon } from '@/components/common/Favicon';
import { IconLabel } from '@/components/common/IconLabel';
import { LinkButton } from '@/components/common/LinkButton';
import { PageHeader } from '@/components/common/PageHeader';
import { useMessages, useNavigation, useWebsite } from '@/components/hooks';
@ -34,10 +35,7 @@ export function WebsiteHeader({
{showActions && (
<LinkButton href={renderUrl(`/websites/${website.id}/settings`, false)}>
<Icon>
<Edit />
</Icon>
<Text>{formatMessage(labels.edit)}</Text>
<IconLabel icon={<Edit />}>{formatMessage(labels.edit)}</IconLabel>
</LinkButton>
)}
</Row>

View file

@ -9,14 +9,8 @@ import { WebsiteNav } from './WebsiteNav';
export function WebsiteLayout({ websiteId, children }: { websiteId: string; children: ReactNode }) {
return (
<WebsiteProvider websiteId={websiteId}>
<Grid columns={{ xs: '1fr', lg: 'auto 1fr' }} width="100%">
<Column
display={{ xs: 'none', lg: 'flex' }}
width="240px"
border="right"
backgroundColor
marginRight="2"
>
<Grid columns={{ base: '1fr', lg: 'auto 1fr' }} width="100%">
<Column display={{ base: 'none', lg: 'flex' }} width="240px" border="right" marginRight="2">
<WebsiteNav websiteId={websiteId} />
</Column>
<PageBody gap>

View file

@ -1,5 +1,5 @@
import { Column, Text } from '@umami/react-zen';
import { SideMenu } from '@/components/common/SideMenu';
import { NavMenu } from '@/components/common/NavMenu';
import { useMessages, useNavigation } from '@/components/hooks';
import {
AlignEndHorizontal,
@ -163,7 +163,7 @@ export function WebsiteNav({
.find(({ path }) => path && pathname.endsWith(path.split('?')[0]))?.id;
return (
<Column padding="2" position="sticky" top="0" gap backgroundColor="transparent">
<Column padding="2" position="sticky" top="0" gap>
<WebsiteSelect
websiteId={websiteId}
teamId={teamId}
@ -171,7 +171,7 @@ export function WebsiteNav({
renderValue={renderValue}
buttonProps={{ style: { outline: 'none' } }}
/>
<SideMenu
<NavMenu
items={items}
selectedKey={selectedKey}
allowMinimize={false}

View file

@ -1,7 +1,7 @@
import { Grid, Heading, Row, Tab, TabList, TabPanel, Tabs } from '@umami/react-zen';
import { GridRow } from '@/components/common/GridRow';
import { Panel } from '@/components/common/Panel';
import { useMessages } from '@/components/hooks';
import { useMessages, useMobile } from '@/components/hooks';
import { MetricsTable } from '@/components/metrics/MetricsTable';
import { WeeklyTraffic } from '@/components/metrics/WeeklyTraffic';
import { WorldMap } from '@/components/metrics/WorldMap';
@ -16,6 +16,7 @@ export function WebsitePanels({ websiteId }: { websiteId: string }) {
metric: formatMessage(labels.visitors),
};
const rowProps = { minHeight: '570px' };
const { isMobile } = useMobile();
return (
<Grid gap="3">
@ -103,7 +104,7 @@ export function WebsitePanels({ websiteId }: { websiteId: string }) {
</GridRow>
<GridRow layout="two-one" {...rowProps}>
<Panel gridColumn={{ xs: 'span 1', md: 'span 2' }} paddingX="0" paddingY="0">
<Panel paddingX="0" paddingY="0" style={{ gridColumn: isMobile ? 'span 1' : 'span 2' }}>
<WorldMap websiteId={websiteId} />
</Panel>

View file

@ -80,7 +80,7 @@ export function CohortEditForm({
<Column>
<Label>{formatMessage(labels.action)}</Label>
<Grid columns={{ xs: '1fr', md: '1fr 1fr' }} gap>
<Grid columns={{ base: '1fr', md: '1fr 1fr' }} gap>
<Column>
<FormField
name="parameters.action.type"

View file

@ -166,7 +166,7 @@ export function CompareTables({ websiteId }: { websiteId: string }) {
</Select>
</Row>
<Panel minHeight="300px">
<Grid columns={{ xs: '1fr', lg: '1fr 1fr' }} gap="6" height="100%">
<Grid columns={{ base: '1fr', lg: '1fr 1fr' }} gap="6" height="100%">
<Column gap="6">
<Row alignItems="center" justifyContent="space-between">
<Heading size="base">{formatMessage(labels.previous)}</Heading>

View file

@ -1,5 +1,7 @@
:root {
--font-family: var(--font-inter), sans-serif;
--primary: oklch(62.3% 0.214 259.815);
--primary-foreground: oklch(98.5% 0 0);
}
html,

View file

@ -1,5 +1,5 @@
import { Button, Column, Icon, Row, Text, ThemeButton } from '@umami/react-zen';
import { SideMenu } from '@/components/common/SideMenu';
import { NavMenu } from '@/components/common/NavMenu';
import { useMessages, useNavigation, useShare } from '@/components/hooks';
import { AlignEndHorizontal, Clock, Eye, PanelLeft, Sheet, Tag, User } from '@/components/icons';
import { LanguageButton } from '@/components/input/LanguageButton';
@ -147,7 +147,6 @@ export function ShareNav({
maxHeight="100dvh"
height="100dvh"
border={isMobile ? undefined : 'right'}
borderColor={isMobile ? undefined : '4'}
>
<Row as="header" gap alignItems="center" justifyContent="space-between">
{!collapsed && (
@ -174,7 +173,7 @@ export function ShareNav({
</Row>
{!collapsed && (
<Column flexGrow={1} overflowY="auto">
<SideMenu
<NavMenu
items={items}
selectedKey={selectedKey}
allowMinimize={false}

View file

@ -79,15 +79,15 @@ export function SharePage() {
const PageComponent = PAGE_COMPONENTS[pageKey] || WebsitePage;
return (
<Grid columns={{ xs: '1fr', lg: `${navCollapsed ? '60px' : '240px'} 1fr` }} width="100%">
<Row display={{ xs: 'flex', lg: 'none' }} alignItems="center" gap padding="3">
<Grid columns={{ base: '1fr', lg: `${navCollapsed ? '60px' : '240px'} 1fr` }} width="100%">
<Row display={{ base: 'flex', lg: 'none' }} alignItems="center" gap padding="3">
<MobileMenuButton>
{({ close }) => {
return <ShareNav onItemClick={close} />;
}}
</MobileMenuButton>
</Row>
<Column display={{ xs: 'none', lg: 'flex' }} marginRight="2">
<Column display={{ base: 'none', lg: 'flex' }} marginRight="2">
<ShareNav collapsed={navCollapsed} onCollapse={setNavCollapsed} />
</Column>
<PageBody gap>

View file

@ -65,7 +65,7 @@ export function FilterRecord({
<Column>
<Label>{fields.find(f => f.name === name)?.label}</Label>
<Grid columns="1fr auto" gap>
<Grid columns={{ xs: '1fr', md: '200px 1fr' }} gap>
<Grid columns={{ base: '1fr', md: '200px 1fr' }} gap>
<Select
items={operators.filter(({ type }) => type === 'string')}
value={operator}

View file

@ -4,18 +4,18 @@ const LAYOUTS = {
one: { columns: '1fr' },
two: {
columns: {
xs: '1fr',
base: '1fr',
md: 'repeat(auto-fill, minmax(560px, 1fr))',
},
},
three: {
columns: {
xs: '1fr',
base: '1fr',
md: 'repeat(auto-fill, minmax(360px, 1fr))',
},
},
'one-two': { columns: { xs: '1fr', md: 'repeat(3, 1fr)' } },
'two-one': { columns: { xs: '1fr', md: 'repeat(3, 1fr)' } },
'one-two': { columns: { base: '1fr', md: 'repeat(3, 1fr)' } },
'two-one': { columns: { base: '1fr', md: 'repeat(3, 1fr)' } },
};
export function GridRow(props: {

View file

@ -29,8 +29,6 @@ export function LoadingPanel({
}: LoadingPanelProps): ReactNode {
const empty = isEmpty ?? checkEmpty(data);
console.log({ empty, isEmpty, data });
// Show loading spinner only if no data exists
if (isLoading || isFetching) {
return (

View file

@ -2,39 +2,46 @@ import { Column, Heading, Row, Text } from '@umami/react-zen';
import Link from 'next/link';
import { IconLabel } from '@/components/common/IconLabel';
interface SideMenuData {
interface NavMenuData {
id: string;
label: string;
icon?: any;
path: string;
}
interface SideMenuItems {
interface NavMenuItems {
label?: string;
items: SideMenuData[];
items: NavMenuData[];
}
export interface SideMenuProps {
items: SideMenuItems[];
export interface NavMenuProps {
items: NavMenuItems[];
title?: string;
selectedKey?: string;
allowMinimize?: boolean;
onItemClick?: () => void;
}
export function SideMenu({
export function NavMenu({
items = [],
title,
selectedKey,
allowMinimize,
onItemClick,
...props
}: SideMenuProps) {
const renderItems = (items: SideMenuData[]) => {
}: NavMenuProps) {
const renderItems = (items: NavMenuData[]) => {
return items?.map(({ id, label, icon, path }) => {
const isSelected = selectedKey === id;
return (
<Link key={id} href={path}>
<Row padding borderRadius hover={{ backgroundColor: 'surface-raised' }}>
<Link key={id} href={path} onClick={onItemClick}>
<Row
padding
borderRadius
hover={{ backgroundColor: 'surface-sunken' }}
backgroundColor={isSelected ? 'surface-sunken' : undefined}
>
<IconLabel icon={icon}>
<Text weight={isSelected ? 'bold' : 'normal'}>{label}</Text>
</IconLabel>
@ -45,13 +52,7 @@ export function SideMenu({
};
return (
<Column
gap
overflowY="auto"
justifyContent="space-between"
position="sticky"
backgroundColor="surface-base"
>
<Column gap overflowY="auto" justifyContent="space-between" position="sticky">
{title && (
<Row padding>
<Heading size="sm">{title}</Heading>
@ -61,13 +62,10 @@ export function SideMenu({
{items?.map(({ label, items }, index) => {
if (label) {
return (
<Column
title={label}
key={`${label}${index}`}
gap="1"
marginBottom="3"
minHeight="40px"
>
<Column key={`${label}${index}`} gap="1" marginBottom="3" minHeight="40px">
<Row padding>
<Text weight="bold">{label}</Text>
</Row>
{renderItems(items)}
</Column>
);

View file

@ -34,7 +34,7 @@ export function PageBody({
minHeight="100vh"
paddingBottom="6"
maxWidth={maxWidth}
paddingX={{ xs: '3', md: '6' }}
paddingX={{ base: '3', md: '6' }}
style={{ margin: '0 auto' }}
>
{children}

View file

@ -2,8 +2,8 @@ import { useBreakpoint } from '@umami/react-zen';
export function useMobile() {
const breakpoint = useBreakpoint();
const isMobile = ['xs', 'sm', 'md'].includes(breakpoint);
const isPhone = ['xs', 'sm'].includes(breakpoint);
const isMobile = ['base', 'sm', 'md'].includes(breakpoint);
const isPhone = ['base', 'sm'].includes(breakpoint);
return { breakpoint, isMobile, isPhone };
}

View file

@ -1,4 +1,4 @@
import { Button, Icon, Tooltip, TooltipTrigger } from '@umami/react-zen';
import { Button, Icon, Text, Tooltip, TooltipTrigger } from '@umami/react-zen';
import Papa from 'papaparse';
import { useMessages } from '@/components/hooks';
import { Download } from '@/components/icons';
@ -24,7 +24,9 @@ export function DownloadButton({
<Download />
</Icon>
</Button>
<Tooltip>{formatMessage(labels.download)}</Tooltip>
<Tooltip>
<Text size="sm">{formatMessage(labels.download)}</Text>
</Tooltip>
</TooltipTrigger>
);
}

View file

@ -69,8 +69,8 @@ export function FieldFilters({ websiteId, value, exclude = [], onChange }: Field
};
return (
<Grid columns={{ xs: '1fr', md: '180px 1fr' }} overflow="hidden" gapY="6">
<Row display={{ xs: 'flex', md: 'none' }}>
<Grid columns={{ base: '1fr', md: '180px 1fr' }} overflow="hidden" gapY="6">
<Row display={{ base: 'flex', md: 'none' }}>
<MenuTrigger>
<Button>
<Icon>
@ -101,7 +101,12 @@ export function FieldFilters({ websiteId, value, exclude = [], onChange }: Field
</Popover>
</MenuTrigger>
</Row>
<Column display={{ xs: 'none', md: 'flex' }} border="right" paddingRight="3" marginRight="6">
<Column
display={{ base: 'none', md: 'flex' }}
border="right"
paddingRight="3"
marginRight="6"
>
<List onAction={handleAdd}>
{groupLabels.map(({ key: groupKey, label }) => {
const groupFields = groupedFields[groupKey];

View file

@ -1,5 +1,6 @@
import { Grid, Icon, Row, Text } from '@umami/react-zen';
import { Grid, Row } from '@umami/react-zen';
import { useEffect, useMemo } from 'react';
import { IconLabel } from '@/components/common/IconLabel';
import { LinkButton } from '@/components/common/LinkButton';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { useMessages, useNavigation, useWebsiteMetricsQuery } from '@/components/hooks';
@ -82,10 +83,7 @@ export function MetricsTable({
{showMore && limit && (
<Row justifyContent="center" alignItems="flex-end">
<LinkButton href={updateParams({ view: type })} variant="quiet">
<Icon size="sm">
<Maximize />
</Icon>
<Text>{formatMessage(labels.more)}</Text>
<IconLabel icon={<Maximize />}>{formatMessage(labels.more)}</IconLabel>
</LinkButton>
</Row>
)}

View file

@ -95,9 +95,9 @@ export function WeeklyTraffic({ websiteId }: { websiteId: string }) {
/>
</Row>
</Focusable>
<Tooltip placement="right">{`${formatMessage(
labels.visitors,
)}: ${count}`}</Tooltip>
<Tooltip placement="right">
<Text size="sm">{`${formatMessage(labels.visitors)}: ${count}`}</Text>
</Tooltip>
</TooltipTrigger>
);
})}

View file

@ -1,4 +1,4 @@
import { Column, type ColumnProps, FloatingTooltip, useTheme } from '@umami/react-zen';
import { Box, Column, type ColumnProps, FloatingTooltip, Text, useTheme } from '@umami/react-zen';
import { colord } from 'colord';
import { useMemo, useState } from 'react';
import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps';
@ -99,7 +99,13 @@ export function WorldMap({ websiteId, data, ...props }: WorldMapProps) {
</Geographies>
</ZoomableGroup>
</ComposableMap>
{tooltip && <FloatingTooltip>{tooltip}</FloatingTooltip>}
{tooltip && (
<FloatingTooltip>
<Box backgroundColor="surface-inverted" color="inverted" padding borderRadius="md">
<Text size="sm">{tooltip}</Text>
</Box>
</FloatingTooltip>
)}
</Column>
);
}

View file

@ -61,12 +61,12 @@ export * from '@/components/common/ExternalLink';
export * from '@/components/common/Favicon';
export * from '@/components/common/LinkButton';
export * from '@/components/common/LoadingPanel';
export * from '@/components/common/NavMenu';
export * from '@/components/common/PageBody';
export * from '@/components/common/PageHeader';
export * from '@/components/common/Pager';
export * from '@/components/common/Panel';
export * from '@/components/common/SectionHeader';
export * from '@/components/common/SideMenu';
export * from '@/components/common/TypeConfirmationForm';
export * from '@/components/hooks';
export * from '@/components/input/DateFilter';