mirror of
https://github.com/umami-software/umami.git
synced 2026-02-07 06:07:17 +01:00
Code cleanup.
This commit is contained in:
parent
a8534a9d4d
commit
8484fd26e1
27 changed files with 183 additions and 162 deletions
|
|
@ -6,6 +6,10 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||
|
||||
Umami is a privacy-focused web analytics platform built with Next.js 15, React 19, and TypeScript. It serves as an alternative to Google Analytics, storing data in PostgreSQL (primary) with optional ClickHouse for time-series analytics.
|
||||
|
||||
## Development
|
||||
|
||||
Assume a dev server is always running on port 3001. Do not start the dev server.
|
||||
|
||||
## Common Commands
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -182,6 +182,7 @@ export default {
|
|||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
devIndicators: false,
|
||||
async headers() {
|
||||
return headers;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3001 --turbo",
|
||||
"dev": "next dev -p 4444 --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.17",
|
||||
"@umami/react-zen": "^0.231.0",
|
||||
"@umami/react-zen": "^0.239.0",
|
||||
"@umami/redis-client": "^0.30.0",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"chalk": "^5.6.2",
|
||||
|
|
|
|||
64
pnpm-lock.yaml
generated
64
pnpm-lock.yaml
generated
|
|
@ -42,8 +42,8 @@ importers:
|
|||
specifier: ^5.90.17
|
||||
version: 5.90.17(react@19.2.3)
|
||||
'@umami/react-zen':
|
||||
specifier: ^0.231.0
|
||||
version: 0.231.0(@types/react@19.2.8)(immer@10.2.0)(react-aria-components@1.14.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tailwindcss@4.1.18)(use-sync-external-store@1.6.0(react@19.2.3))
|
||||
specifier: ^0.239.0
|
||||
version: 0.239.0(@types/react@19.2.8)(immer@10.2.0)(react-aria-components@1.14.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tailwindcss@4.1.18)(use-sync-external-store@1.6.0(react@19.2.3))
|
||||
'@umami/redis-client':
|
||||
specifier: ^0.30.0
|
||||
version: 0.30.0
|
||||
|
|
@ -331,44 +331,7 @@ importers:
|
|||
specifier: ^5.9.3
|
||||
version: 5.9.3
|
||||
|
||||
dist:
|
||||
dependencies:
|
||||
chart.js:
|
||||
specifier: ^4.5.0
|
||||
version: 4.5.1
|
||||
chartjs-adapter-date-fns:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0(chart.js@4.5.1)(date-fns@2.30.0)
|
||||
colord:
|
||||
specifier: ^2.9.2
|
||||
version: 2.9.3
|
||||
jsonwebtoken:
|
||||
specifier: ^9.0.2
|
||||
version: 9.0.3
|
||||
lucide-react:
|
||||
specifier: ^0.542.0
|
||||
version: 0.542.0(react@19.2.3)
|
||||
pure-rand:
|
||||
specifier: ^7.0.1
|
||||
version: 7.0.1
|
||||
react-simple-maps:
|
||||
specifier: ^2.3.0
|
||||
version: 2.3.0(prop-types@15.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
react-use-measure:
|
||||
specifier: ^2.0.4
|
||||
version: 2.1.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
react-window:
|
||||
specifier: ^1.8.6
|
||||
version: 1.8.11(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
serialize-error:
|
||||
specifier: ^12.0.0
|
||||
version: 12.0.0
|
||||
thenby:
|
||||
specifier: ^1.3.4
|
||||
version: 1.3.4
|
||||
uuid:
|
||||
specifier: ^11.1.0
|
||||
version: 11.1.0
|
||||
dist: {}
|
||||
|
||||
packages:
|
||||
|
||||
|
|
@ -2882,8 +2845,8 @@ packages:
|
|||
'@types/yauzl@2.10.3':
|
||||
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
|
||||
|
||||
'@umami/react-zen@0.231.0':
|
||||
resolution: {integrity: sha512-Pn3UqMqYkeiL0E8R8EwExth5UylPI6HhgciuFc9Nfkk0S7MWBbOAJ1s3aRuiNVa3eR/r0vw6h4k/5W2fQ7MvTA==}
|
||||
'@umami/react-zen@0.239.0':
|
||||
resolution: {integrity: sha512-6CSPYPnpCnau+Ie8nyC1BDVFrNoes5XOuy+b6hb6htDTvqTKIB8nvky0hJHe6kLz6Kp7xTbknjP+PQ5EnLBC6w==}
|
||||
peerDependencies:
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-aria-components: ^1.0.0
|
||||
|
|
@ -5006,11 +4969,6 @@ packages:
|
|||
resolution: {integrity: sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q==}
|
||||
engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'}
|
||||
|
||||
lucide-react@0.542.0:
|
||||
resolution: {integrity: sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==}
|
||||
peerDependencies:
|
||||
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
lucide-react@0.543.0:
|
||||
resolution: {integrity: sha512-fpVfuOQO0V3HBaOA1stIiP/A2fPCXHIleRZL16Mx3HmjTYwNSbimhnFBygs2CAfU1geexMX5ItUcWBGUaqw5CA==}
|
||||
peerDependencies:
|
||||
|
|
@ -7010,10 +6968,6 @@ packages:
|
|||
util-deprecate@1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
|
||||
uuid@11.1.0:
|
||||
resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
|
||||
hasBin: true
|
||||
|
||||
uuid@13.0.0:
|
||||
resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==}
|
||||
hasBin: true
|
||||
|
|
@ -10071,7 +10025,7 @@ snapshots:
|
|||
'@types/node': 24.10.8
|
||||
optional: true
|
||||
|
||||
'@umami/react-zen@0.231.0(@types/react@19.2.8)(immer@10.2.0)(react-aria-components@1.14.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tailwindcss@4.1.18)(use-sync-external-store@1.6.0(react@19.2.3))':
|
||||
'@umami/react-zen@0.239.0(@types/react@19.2.8)(immer@10.2.0)(react-aria-components@1.14.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(tailwindcss@4.1.18)(use-sync-external-store@1.6.0(react@19.2.3))':
|
||||
dependencies:
|
||||
'@internationalized/date': 3.10.1
|
||||
'@react-aria/focus': 3.21.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
|
|
@ -12645,10 +12599,6 @@ snapshots:
|
|||
|
||||
lru.min@1.1.3: {}
|
||||
|
||||
lucide-react@0.542.0(react@19.2.3):
|
||||
dependencies:
|
||||
react: 19.2.3
|
||||
|
||||
lucide-react@0.543.0(react@19.2.3):
|
||||
dependencies:
|
||||
react: 19.2.3
|
||||
|
|
@ -14846,8 +14796,6 @@ snapshots:
|
|||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
uuid@11.1.0: {}
|
||||
|
||||
uuid@13.0.0: {}
|
||||
|
||||
uuid@8.3.2: {}
|
||||
|
|
|
|||
|
|
@ -39,15 +39,14 @@ export function App({ children }) {
|
|||
|
||||
return (
|
||||
<Grid
|
||||
columns={{ xs: '1fr', lg: 'auto 1fr' }}
|
||||
rows={{ xs: 'auto 1fr', lg: '1fr' }}
|
||||
height={{ xs: 'auto', lg: '100vh' }}
|
||||
width="100%"
|
||||
columns={{ base: '1fr', lg: 'auto 1fr' }}
|
||||
rows={{ base: 'auto 1fr', lg: '1fr' }}
|
||||
height="screen"
|
||||
>
|
||||
<Row display={{ xs: 'flex', lg: 'none' }} alignItems="center" gap padding="3">
|
||||
<Row display={{ base: 'flex', lg: 'none' }} alignItems="center" gap padding="3">
|
||||
<MobileNav />
|
||||
</Row>
|
||||
<Column display={{ xs: 'none', lg: 'flex' }}>
|
||||
<Column display={{ base: 'none', lg: 'flex' }}>
|
||||
<SideNav />
|
||||
</Column>
|
||||
<Column alignItems="center" overflowY="auto" overflowX="hidden" position="relative">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Grid, IconLabel, NavMenu, NavMenuItem, Row, Text } from '@umami/react-zen';
|
||||
import { Grid, Row, Text } from '@umami/react-zen';
|
||||
import Link from 'next/link';
|
||||
import { WebsiteNav } from '@/app/(main)/websites/[websiteId]/WebsiteNav';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import { Globe, Grid2x2, LinkIcon } from '@/components/icons';
|
||||
import { MobileMenuButton } from '@/components/input/MobileMenuButton';
|
||||
|
|
@ -42,18 +43,16 @@ export function MobileNav() {
|
|||
{({ close }) => {
|
||||
return (
|
||||
<>
|
||||
<NavMenu padding="3" onItemClick={close} border="bottom">
|
||||
<Row padding="3" onClick={close} border="bottom">
|
||||
<NavButton />
|
||||
{links.map(link => {
|
||||
return (
|
||||
<Link key={link.id} href={renderUrl(link.path)}>
|
||||
<NavMenuItem>
|
||||
<IconLabel icon={link.icon} label={link.label} />
|
||||
</NavMenuItem>
|
||||
<IconLabel icon={link.icon} label={link.label} />
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</NavMenu>
|
||||
</Row>
|
||||
{websiteId && <WebsiteNav websiteId={websiteId} onItemClick={close} />}
|
||||
{isAdmin && <AdminNav onItemClick={close} />}
|
||||
{isSettings && <SettingsNav onItemClick={close} />}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,26 @@
|
|||
import {
|
||||
Box,
|
||||
Button,
|
||||
type ButtonProps,
|
||||
Column,
|
||||
Focusable,
|
||||
Icon,
|
||||
Row,
|
||||
Sidebar,
|
||||
SidebarHeader,
|
||||
SidebarItem,
|
||||
type SidebarProps,
|
||||
SidebarSection,
|
||||
Text,
|
||||
ThemeButton,
|
||||
Tooltip,
|
||||
TooltipTrigger,
|
||||
} from '@umami/react-zen';
|
||||
import Link from 'next/link';
|
||||
import type { Key } from 'react';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
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 { PanelButton } from '@/components/input/PanelButton';
|
||||
import { Logo } from '@/components/svg';
|
||||
|
||||
export function SideNav(props: SidebarProps) {
|
||||
export function SideNav(props: any) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { pathname, renderUrl, websiteId, router } = useNavigation();
|
||||
const [isCollapsed, setIsCollapsed] = useGlobalState('sidenav-collapsed');
|
||||
|
|
@ -55,39 +59,76 @@ export function SideNav(props: SidebarProps) {
|
|||
};
|
||||
|
||||
return (
|
||||
<Sidebar {...props} isCollapsed={isCollapsed || hasNav} backgroundColor>
|
||||
<SidebarSection onClick={() => setIsCollapsed(false)}>
|
||||
<SidebarHeader
|
||||
label="umami"
|
||||
icon={isCollapsed && !hasNav ? <PanelLeft /> : <Logo />}
|
||||
style={{ maxHeight: 40 }}
|
||||
>
|
||||
{!isCollapsed && !hasNav && <PanelButton />}
|
||||
</SidebarHeader>
|
||||
</SidebarSection>
|
||||
<SidebarSection paddingTop="0" paddingBottom="0" justifyContent="center">
|
||||
<NavButton showText={!hasNav && !isCollapsed} onAction={handleSelect} />
|
||||
</SidebarSection>
|
||||
<SidebarSection flexGrow={1}>
|
||||
{links.map(({ id, path, label, icon }) => {
|
||||
return (
|
||||
<Link key={id} href={renderUrl(path, false)} role="button">
|
||||
<SidebarItem
|
||||
label={label}
|
||||
icon={icon}
|
||||
isSelected={pathname.includes(path)}
|
||||
role="button"
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</SidebarSection>
|
||||
<SidebarSection justifyContent="flex-start">
|
||||
<Row wrap="wrap">
|
||||
<LanguageButton />
|
||||
<ThemeButton />
|
||||
<Column
|
||||
{...props}
|
||||
backgroundColor="surface-base"
|
||||
justifyContent="space-between"
|
||||
border
|
||||
borderRadius
|
||||
paddingX="2"
|
||||
height="100%"
|
||||
margin="2"
|
||||
style={{
|
||||
width: isCollapsed ? '55px' : '240px',
|
||||
transition: 'width 0.2s ease-in-out',
|
||||
}}
|
||||
>
|
||||
<Column>
|
||||
<Row alignItems="center" justifyContent="space-between" height="60px">
|
||||
<Row paddingX="3" alignItems="center" justifyContent="space-between" flexGrow={1}>
|
||||
{!isCollapsed && (
|
||||
<IconLabel icon={<Logo />}>
|
||||
<Text weight="bold">umami</Text>
|
||||
</IconLabel>
|
||||
)}
|
||||
<PanelButton />
|
||||
</Row>
|
||||
</Row>
|
||||
</SidebarSection>
|
||||
</Sidebar>
|
||||
<Row marginBottom="4">
|
||||
<NavButton showText={!hasNav && !isCollapsed} onAction={handleSelect} />
|
||||
</Row>
|
||||
<Column gap="2">
|
||||
{links.map(({ id, path, label, icon }) => {
|
||||
return (
|
||||
<Link key={id} href={renderUrl(path, false)} role="button">
|
||||
<TooltipTrigger isDisabled={!isCollapsed} delay={0}>
|
||||
<Focusable>
|
||||
<Row
|
||||
alignItems="center"
|
||||
hover={{ backgroundColor: 'surface-sunken' }}
|
||||
borderRadius
|
||||
minHeight="40px"
|
||||
>
|
||||
<IconLabel icon={icon} label={isCollapsed ? '' : label} padding />
|
||||
</Row>
|
||||
</Focusable>
|
||||
<Tooltip placement="right">{label}</Tooltip>
|
||||
</TooltipTrigger>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</Column>
|
||||
</Column>
|
||||
<Row alignItems="center" justifyContent="center" wrap="wrap" marginBottom="4" gap>
|
||||
<LanguageButton />
|
||||
<ThemeButton />
|
||||
</Row>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
const PanelButton = (props: ButtonProps) => {
|
||||
const [isCollapsed, setIsCollapsed] = useGlobalState('sidenav-collapsed');
|
||||
return (
|
||||
<Button
|
||||
onPress={() => setIsCollapsed(!isCollapsed)}
|
||||
variant="zero"
|
||||
{...props}
|
||||
style={{ padding: 0 }}
|
||||
>
|
||||
<Icon strokeColor="muted">
|
||||
<PanelLeft />
|
||||
</Icon>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
'use client';
|
||||
import { Column, IconLabel } from '@umami/react-zen';
|
||||
import { Column } from '@umami/react-zen';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
import { LinkButton } from '@/components/common/LinkButton';
|
||||
import { PageBody } from '@/components/common/PageBody';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { IconLabel } from '@umami/react-zen';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
import { LinkButton } from '@/components/common/LinkButton';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { useLink, useMessages, useSlug } from '@/components/hooks';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { IconLabel } from '@umami/react-zen';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
import { LinkButton } from '@/components/common/LinkButton';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { useMessages, usePixel, useSlug } from '@/components/hooks';
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import {
|
|||
FormButtons,
|
||||
FormField,
|
||||
FormSubmitButton,
|
||||
IconLabel,
|
||||
Row,
|
||||
TextField,
|
||||
} from '@umami/react-zen';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
import { useMessages, useTeam, useUpdateQuery } from '@/components/hooks';
|
||||
import { RefreshCw } from '@/components/icons';
|
||||
import { getRandomChars } from '@/lib/generate';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { IconLabel, Row } from '@umami/react-zen';
|
||||
import { Row } from '@umami/react-zen';
|
||||
import { WebsiteShareForm } from '@/app/(main)/websites/[websiteId]/settings/WebsiteShareForm';
|
||||
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';
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ export function WebsiteNav({
|
|||
.find(({ path }) => path && pathname.endsWith(path.split('?')[0]))?.id;
|
||||
|
||||
return (
|
||||
<Column padding="3" position="sticky" top="0" gap>
|
||||
<Column padding="2" position="sticky" top="0" gap backgroundColor="transparent">
|
||||
<WebsiteSelect
|
||||
websiteId={websiteId}
|
||||
teamId={teamId}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import {
|
|||
Dialog,
|
||||
DialogTrigger,
|
||||
Icon,
|
||||
IconLabel,
|
||||
Popover,
|
||||
Row,
|
||||
Text,
|
||||
|
|
@ -14,6 +13,7 @@ import {
|
|||
import Link from 'next/link';
|
||||
import { Avatar } from '@/components/common/Avatar';
|
||||
import { DateDistance } from '@/components/common/DateDistance';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
import { TypeIcon } from '@/components/common/TypeIcon';
|
||||
import { useFormat, useMessages, useNavigation } from '@/components/hooks';
|
||||
import { Eye, FileText } from '@/components/icons';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { IconLabel } from '@umami/react-zen';
|
||||
import { useCallback } from 'react';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
import { TypeIcon } from '@/components/common/TypeIcon';
|
||||
import { useCountryNames, useLocale, useMessages } from '@/components/hooks';
|
||||
import { ListTable } from '@/components/metrics/ListTable';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Column, Heading, IconLabel, Row, SearchField, Text } from '@umami/react-zen';
|
||||
import { Column, Heading, Row, SearchField, Text } from '@umami/react-zen';
|
||||
import Link from 'next/link';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
|
|
@ -6,6 +6,7 @@ import { SessionModal } from '@/app/(main)/websites/[websiteId]/sessions/Session
|
|||
import { useFormat } from '@/components//hooks/useFormat';
|
||||
import { Avatar } from '@/components/common/Avatar';
|
||||
import { Empty } from '@/components/common/Empty';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
import {
|
||||
useCountryNames,
|
||||
useLocale,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { IconLabel, Row } from '@umami/react-zen';
|
||||
import { Row } from '@umami/react-zen';
|
||||
import Link from 'next/link';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
import { PageHeader } from '@/components/common/PageHeader';
|
||||
import { useMessages, useNavigation, useWebsite } from '@/components/hooks';
|
||||
import { ArrowLeft, Globe } from '@/components/icons';
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import {
|
|||
Form,
|
||||
FormButtons,
|
||||
FormSubmitButton,
|
||||
IconLabel,
|
||||
Label,
|
||||
Row,
|
||||
Switch,
|
||||
|
|
@ -12,6 +11,7 @@ import {
|
|||
} from '@umami/react-zen';
|
||||
import { RefreshCcw } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
import { useConfig, useMessages, useUpdateQuery } from '@/components/hooks';
|
||||
import { getRandomChars } from '@/lib/generate';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
:root {
|
||||
--font-family: var(--font-inter), sans-serif;
|
||||
--text-primary: oklch(68.5% 0.169 237.323);
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
font-family: var(--font-family), sans-serif;
|
||||
color: var(--text-base);
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
background-color: var(--surface-raised);
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import type { Metadata } from 'next';
|
|||
import { Inter } from 'next/font/google';
|
||||
import { Suspense } from 'react';
|
||||
import { Providers } from './Providers';
|
||||
import './global.css';
|
||||
import '@umami/react-zen/styles.full.css';
|
||||
import './global.css';
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ['latin'],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
import { Icon, type IconProps, Row, type RowProps, Text, type TextProps } from '@umami/react-zen';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
interface IconLabelProps extends RowProps {
|
||||
icon: ReactNode;
|
||||
label?: ReactNode;
|
||||
weight?: TextProps['weight'];
|
||||
iconProps?: Partial<IconProps>;
|
||||
labelProps?: Partial<TextProps>;
|
||||
}
|
||||
|
||||
export function IconLabel({
|
||||
icon,
|
||||
label,
|
||||
weight,
|
||||
iconProps,
|
||||
labelProps,
|
||||
children,
|
||||
...props
|
||||
}: IconLabelProps) {
|
||||
return (
|
||||
<Row alignItems="center" gap="2" {...props}>
|
||||
<Icon {...iconProps}>{icon}</Icon>
|
||||
{label && (
|
||||
<Text weight={weight} {...labelProps}>
|
||||
{label}
|
||||
</Text>
|
||||
)}
|
||||
{children}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,14 +1,6 @@
|
|||
import {
|
||||
Column,
|
||||
Heading,
|
||||
IconLabel,
|
||||
NavMenu,
|
||||
NavMenuGroup,
|
||||
NavMenuItem,
|
||||
type NavMenuProps,
|
||||
Row,
|
||||
} from '@umami/react-zen';
|
||||
import { Column, Heading, Row, Text } from '@umami/react-zen';
|
||||
import Link from 'next/link';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
|
||||
interface SideMenuData {
|
||||
id: string;
|
||||
|
|
@ -22,7 +14,7 @@ interface SideMenuItems {
|
|||
items: SideMenuData[];
|
||||
}
|
||||
|
||||
export interface SideMenuProps extends NavMenuProps {
|
||||
export interface SideMenuProps {
|
||||
items: SideMenuItems[];
|
||||
title?: string;
|
||||
selectedKey?: string;
|
||||
|
|
@ -42,39 +34,47 @@ export function SideMenu({
|
|||
|
||||
return (
|
||||
<Link key={id} href={path}>
|
||||
<NavMenuItem isSelected={isSelected}>
|
||||
<IconLabel icon={icon}>{label}</IconLabel>
|
||||
</NavMenuItem>
|
||||
<Row padding borderRadius hover={{ backgroundColor: 'surface-raised' }}>
|
||||
<IconLabel icon={icon}>
|
||||
<Text weight={isSelected ? 'bold' : 'normal'}>{label}</Text>
|
||||
</IconLabel>
|
||||
</Row>
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Column gap overflowY="auto" justifyContent="space-between" position="sticky" top="20px">
|
||||
<Column
|
||||
gap
|
||||
overflowY="auto"
|
||||
justifyContent="space-between"
|
||||
position="sticky"
|
||||
backgroundColor="surface-base"
|
||||
>
|
||||
{title && (
|
||||
<Row padding>
|
||||
<Heading size="1">{title}</Heading>
|
||||
<Heading size="sm">{title}</Heading>
|
||||
</Row>
|
||||
)}
|
||||
<NavMenu gap="6" {...props}>
|
||||
<Column gap="6" {...props}>
|
||||
{items?.map(({ label, items }, index) => {
|
||||
if (label) {
|
||||
return (
|
||||
<NavMenuGroup
|
||||
<Column
|
||||
title={label}
|
||||
key={`${label}${index}`}
|
||||
gap="1"
|
||||
allowMinimize={allowMinimize}
|
||||
marginBottom="3"
|
||||
minHeight="40px"
|
||||
>
|
||||
{renderItems(items)}
|
||||
</NavMenuGroup>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</NavMenu>
|
||||
</Column>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import {
|
|||
Dialog,
|
||||
type DialogProps,
|
||||
DialogTrigger,
|
||||
IconLabel,
|
||||
Modal,
|
||||
} from '@umami/react-zen';
|
||||
import type { CSSProperties, ReactNode } from 'react';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
import { useMobile } from '@/components/hooks';
|
||||
|
||||
export interface DialogButtonProps extends Omit<ButtonProps, 'children'> {
|
||||
|
|
|
|||
|
|
@ -14,12 +14,12 @@ export function LanguageButton() {
|
|||
return (
|
||||
<MenuTrigger key="language">
|
||||
<Button variant="quiet">
|
||||
<Icon>
|
||||
<Icon color="primary">
|
||||
<Globe />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Popover placement="bottom end">
|
||||
<Dialog variant="menu">
|
||||
<Dialog>
|
||||
<Grid columns="repeat(3, minmax(200px, 1fr))" overflow="hidden">
|
||||
{items.map(({ value, label }) => {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import {
|
||||
Column,
|
||||
Icon,
|
||||
IconLabel,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuSection,
|
||||
|
|
@ -15,6 +14,7 @@ import {
|
|||
} from '@umami/react-zen';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import type { Key } from 'react';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
import {
|
||||
useConfig,
|
||||
useLoginQuery,
|
||||
|
|
@ -77,8 +77,8 @@ export function NavButton({ showText = true }: TeamsButtonProps) {
|
|||
padding
|
||||
border
|
||||
borderRadius
|
||||
shadow="1"
|
||||
maxHeight="40px"
|
||||
shadow="sm"
|
||||
minHeight="40px"
|
||||
role="button"
|
||||
style={{ cursor: 'pointer', textWrap: 'nowrap', overflow: 'hidden', outline: 'none' }}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { IconLabel, List, ListItem } from '@umami/react-zen';
|
||||
import { List, ListItem } from '@umami/react-zen';
|
||||
import { Empty } from '@/components/common/Empty';
|
||||
import { IconLabel } from '@/components/common/IconLabel';
|
||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||
import { useWebsiteSegmentsQuery } from '@/components/hooks';
|
||||
import { ChartPie, UserPlus } from '@/components/icons';
|
||||
|
|
|
|||
|
|
@ -111,14 +111,7 @@ const AnimatedRow = ({
|
|||
});
|
||||
|
||||
return (
|
||||
<Grid
|
||||
columns="1fr 50px 50px"
|
||||
paddingLeft="2"
|
||||
alignItems="center"
|
||||
hoverBackgroundColor="2"
|
||||
borderRadius
|
||||
gap
|
||||
>
|
||||
<Grid columns="1fr 50px 50px" paddingLeft="2" alignItems="center" borderRadius gap>
|
||||
<Row alignItems="center">
|
||||
<Text truncate={true} style={{ maxWidth: isPhone ? '200px' : '400px' }}>
|
||||
{label}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue