Added side nav.

This commit is contained in:
Mike Cao 2025-02-16 01:11:45 -08:00
parent a8a1ccce18
commit b53606d497
13 changed files with 239 additions and 202 deletions

View file

@ -6,6 +6,7 @@ import { UpdateNotice } from './UpdateNotice';
import { NavBar } from '@/app/(main)/NavBar';
import { Page } from '@/components/layout/Page';
import { useLogin, useConfig } from '@/components/hooks';
import { SideNav } from '@/app/(main)/SideNav';
export function App({ children }) {
const { user, isLoading, error } = useLogin();
@ -29,15 +30,18 @@ export function App({ children }) {
}
return (
<Grid rows="auto 1fr">
<NavBar />
<Page>
<UpdateNotice user={user} config={config} />
{children}
{process.env.NODE_ENV === 'production' && !pathname.includes('/share/') && (
<Script src={`${process.env.basePath || ''}/telemetry.js`} />
)}
</Page>
<Grid>
<SideNav />
<Grid rows="auto 1fr">
<NavBar />
<Page>
<UpdateNotice user={user} config={config} />
{children}
{process.env.NODE_ENV === 'production' && !pathname.includes('/share/') && (
<Script src={`${process.env.basePath || ''}/telemetry.js`} />
)}
</Page>
</Grid>
</Grid>
);
}

View file

@ -1,42 +1,13 @@
'use client';
import { Icon, Text, ThemeButton, Row } from '@umami/react-zen';
import Link from 'next/link';
import { ThemeButton, Row } from '@umami/react-zen';
import { LanguageButton } from '@/components/input/LanguageButton';
import { ProfileButton } from '@/components/input/ProfileButton';
import { TeamsButton } from '@/components/input/TeamsButton';
import { Icons } from '@/components/icons';
import { useMessages, useTeamUrl } from '@/components/hooks';
export function NavBar() {
const { formatMessage, labels } = useMessages();
const { renderTeamUrl } = useTeamUrl();
const links = [
{ label: formatMessage(labels.dashboard), url: renderTeamUrl('/dashboard') },
{ label: formatMessage(labels.websites), url: renderTeamUrl('/websites') },
{ label: formatMessage(labels.reports), url: renderTeamUrl('/reports') },
{ label: formatMessage(labels.settings), url: renderTeamUrl('/settings') },
].filter(n => n);
return (
<Row justifyContent="space-between" alignItems="center" paddingX="4" paddingY="3">
<Row alignItems="center" gap="3">
<Icon size="md">
<Icons.Logo />
</Icon>
<Text size="3" weight="bold">
umami
</Text>
</Row>
<Row gap="4">
{links.map(({ url, label }) => {
return (
<Link key={url} href={url} prefetch={url !== '/settings'}>
<Text>{label}</Text>
</Link>
);
})}
</Row>
<div></div>
<Row justifyContent="flex-end">
<TeamsButton />
<ThemeButton />

View file

@ -0,0 +1,46 @@
.sidenav {
position: fixed;
top: 0;
width: 200px;
height: 100vh;
background-color: var(--layer-color-1);
border-right: 1px solid var(--layer-color-2);
}
.header {
display: flex;
align-items: center;
gap: var(--gap);
padding: var(--padding);
}
.name {
font-weight: var(--font-weight-bold);
}
.section {
padding: var(--spacing-3);
gap: var(--spacing-9);
}
.title {
font-weight: var(--font-weight-bold);
}
.items {
display: grid;
gap: var(--gap);
}
.item {
color: var(--font-color-muted) !important;
display: flex;
align-items: center;
gap: var(--gap);
padding: var(--padding);
}
.item:hover {
color: var(--font-color) !important;
background-color: var(--layer-color-2);
}

View file

@ -0,0 +1,81 @@
import { ReactNode } from 'react';
import Link from 'next/link';
import { Icon } from '@umami/react-zen';
import { Icons } from '@/components/icons';
import { useMessages, useTeamUrl } from '@/components/hooks';
import styles from './SideNav.module.css';
export function SideNav() {
const { formatMessage, labels } = useMessages();
const { renderTeamUrl } = useTeamUrl();
const links = [
{
label: formatMessage(labels.boards),
href: renderTeamUrl('/boards'),
icon: <Icons.Dashboard />,
},
{
label: formatMessage(labels.dashboard),
href: renderTeamUrl('/dashboard'),
icon: <Icons.BarChart />,
},
{
label: formatMessage(labels.websites),
href: renderTeamUrl('/websites'),
icon: <Icons.Globe />,
},
{
label: formatMessage(labels.reports),
href: renderTeamUrl('/reports'),
icon: <Icons.Reports />,
},
{
label: formatMessage(labels.settings),
href: renderTeamUrl('/settings'),
icon: <Icons.Gear />,
},
].filter(n => n);
return (
<div className={styles.sidenav}>
<SideNavSection>
<SideNavHeader />
</SideNavSection>
<SideNavSection>
{links.map(props => {
return <SideNavItem key={props.href} {...props} />;
})}
</SideNavSection>
</div>
);
}
const SideNavHeader = () => {
return (
<div className={styles.header}>
<Icon size="sm">
<Icons.Logo />
</Icon>
<div className={styles.name}>umami</div>
</div>
);
};
const SideNavSection = ({ title, children }: { title?: string; children: ReactNode }) => {
return (
<div className={styles.section}>
{title && <div className={styles.title}>{title}</div>}
<div className={styles.items}>{children}</div>
</div>
);
};
const SideNavItem = ({ href, label, icon }: { href: string; label: string; icon: ReactNode }) => {
return (
<Link href={href} className={styles.item}>
<Icon size="sm">{icon}</Icon>
{label}
</Link>
);
};

View file

@ -0,0 +1,9 @@
import { Column, Heading } from '@umami/react-zen';
export function Board() {
return (
<Column>
<Heading>Board title</Heading>
</Column>
);
}

View file

@ -1,3 +1,5 @@
import { Board } from './Board';
export function BoardsPage() {
return <h1>hi.</h1>;
return <Board />;
}

View file

@ -10,7 +10,7 @@ import '@/styles/variables.css';
export default function ({ children }) {
return (
<html lang="en" data-scroll="0">
<html lang="en">
<head>
<link rel="icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />

View file

@ -297,6 +297,7 @@ export const labels = defineMessages({
paidVideo: { id: 'label.paid-video', defaultMessage: 'Paid video' },
grouped: { id: 'label.grouped', defaultMessage: 'Grouped' },
other: { id: 'label.other', defaultMessage: 'Other' },
boards: { id: 'label.boards', defaultMessage: 'Boards' },
});
export const messages = defineMessages({

View file

@ -4,6 +4,7 @@ body {
color: var(--font-color);
font-size: var(--font-size);
background-color: var(--background-color);
min-height: 100vh;
}
a,