mirror of
https://github.com/umami-software/umami.git
synced 2026-02-04 04:37:11 +01:00
Refactored layout. Added NavBar component.
This commit is contained in:
parent
fad38dc180
commit
1d9c3133f0
56 changed files with 601 additions and 429 deletions
|
|
@ -1,22 +1,25 @@
|
|||
import { Container } from 'react-basics';
|
||||
import Head from 'next/head';
|
||||
import Header from 'components/layout/Header';
|
||||
import Footer from 'components/layout/Footer';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import NavBar from 'components/layout/NavBar';
|
||||
import useRequireLogin from 'hooks/useRequireLogin';
|
||||
import styles from './AppLayout.module.css';
|
||||
|
||||
export default function AppLayout({ title, children }) {
|
||||
useRequireLogin();
|
||||
const { dir } = useLocale();
|
||||
|
||||
return (
|
||||
<Container dir={dir} style={{ maxWidth: 1140 }}>
|
||||
<div className={styles.layout}>
|
||||
<Head>
|
||||
<title>{title ? `${title} | umami` : 'umami'}</title>
|
||||
</Head>
|
||||
<Header />
|
||||
<main>{children}</main>
|
||||
<Footer />
|
||||
</Container>
|
||||
<div className={styles.nav}>
|
||||
<NavBar />
|
||||
</div>
|
||||
<div className={styles.body}>
|
||||
<Container>
|
||||
<main>{children}</main>
|
||||
</Container>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,16 @@
|
|||
.layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-columns: minmax(60px, 200px) 1fr;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nav {
|
||||
grid-row: 1 / 3;
|
||||
}
|
||||
|
||||
.body {
|
||||
grid-area: 1 / 2;
|
||||
overflow: auto;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,38 @@
|
|||
import { Row, Column } from 'react-basics';
|
||||
import { useRouter } from 'next/router';
|
||||
import Script from 'next/script';
|
||||
import classNames from 'classnames';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { useIntl, defineMessages } from 'react-intl';
|
||||
import Link from 'components/common/Link';
|
||||
import styles from './Footer.module.css';
|
||||
import { CURRENT_VERSION, HOMEPAGE_URL, REPO_URL } from 'lib/constants';
|
||||
import styles from './Footer.module.css';
|
||||
|
||||
export default function Footer() {
|
||||
const messages = defineMessages({
|
||||
poweredBy: { id: 'message.powered-by', defaultMessage: 'Powered by {name}' },
|
||||
});
|
||||
|
||||
export default function Footer({ className }) {
|
||||
const { pathname } = useRouter();
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<footer className={classNames(styles.footer, 'row')}>
|
||||
<div className="col-12 col-md-4" />
|
||||
<div className="col-12 col-md-4">
|
||||
<FormattedMessage
|
||||
id="message.powered-by"
|
||||
defaultMessage="Powered by {name}"
|
||||
values={{
|
||||
name: (
|
||||
<Link href={HOMEPAGE_URL}>
|
||||
<b>umami</b>
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={classNames(styles.version, 'col-12 col-md-4')}>
|
||||
<Link href={REPO_URL}>{`v${CURRENT_VERSION}`}</Link>
|
||||
</div>
|
||||
<footer className={classNames(styles.footer, className)}>
|
||||
<Row>
|
||||
<Column>
|
||||
<div>
|
||||
{formatMessage(messages.poweredBy, {
|
||||
name: (
|
||||
<Link href={HOMEPAGE_URL}>
|
||||
<b>umami</b>
|
||||
</Link>
|
||||
),
|
||||
})}
|
||||
</div>
|
||||
</Column>
|
||||
<Column className={styles.version}>
|
||||
<Link href={REPO_URL}>{`v${CURRENT_VERSION}`}</Link>
|
||||
</Column>
|
||||
</Row>
|
||||
{!pathname.includes('/share/') && <Script src={`/telemetry.js`} />}
|
||||
</footer>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
import Logo from 'assets/logo.svg';
|
||||
import HamburgerButton from 'components/common/HamburgerButton';
|
||||
import Link from 'components/common/Link';
|
||||
import UpdateNotice from 'components/common/UpdateNotice';
|
||||
import LanguageButton from 'components/settings/LanguageButton';
|
||||
import ThemeButton from 'components/settings/ThemeButton';
|
||||
import UserButton from 'components/settings/UserButton';
|
||||
import LanguageButton from 'components/buttons/LanguageButton';
|
||||
import ThemeButton from 'components/buttons/ThemeButton';
|
||||
import UserButton from 'components/buttons/UserButton';
|
||||
import useConfig from 'hooks/useConfig';
|
||||
import useUser from 'hooks/useUser';
|
||||
import { HOMEPAGE_URL } from 'lib/constants';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Column, Icon, Row } from 'react-basics';
|
||||
import SettingsButton from '../settings/SettingsButton';
|
||||
import { Column, Row } from 'react-basics';
|
||||
import SettingsButton from '../buttons/SettingsButton';
|
||||
import styles from './Header.module.css';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export default function Header() {
|
||||
export default function Header({ className }) {
|
||||
const user = useUser();
|
||||
const { pathname } = useRouter();
|
||||
const { updatesDisabled, adminDisabled } = useConfig();
|
||||
|
|
@ -23,14 +21,9 @@ export default function Header() {
|
|||
return (
|
||||
<>
|
||||
{allowUpdate && <UpdateNotice />}
|
||||
<header className={styles.header}>
|
||||
<header className={classNames(styles.header, className)}>
|
||||
<Row>
|
||||
<Column className={styles.title}>
|
||||
<Icon size="lg" className={styles.logo}>
|
||||
<Logo />
|
||||
</Icon>
|
||||
<Link href={isSharePage ? HOMEPAGE_URL : '/'}>umami</Link>
|
||||
</Column>
|
||||
<Column className={styles.title}></Column>
|
||||
<HamburgerButton />
|
||||
<Column className={styles.buttons}>
|
||||
<ThemeButton />
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 100px;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
border-bottom: 1px solid var(--base300);
|
||||
}
|
||||
|
||||
.title {
|
||||
|
|
|
|||
48
components/layout/NavBar.js
Normal file
48
components/layout/NavBar.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { useState } from 'react';
|
||||
import { Icon, Text, Icons } from 'react-basics';
|
||||
import classNames from 'classnames';
|
||||
import { Dashboard, Logo, Profile, User, Users, Clock, Globe } from 'components/icons';
|
||||
import NavGroup from './NavGroup';
|
||||
import styles from './NavBar.module.css';
|
||||
|
||||
const { ChevronDown, Search } = Icons;
|
||||
|
||||
const analytics = [
|
||||
{ key: 'dashboard', label: 'Dashboard', url: '/dashboard', icon: <Dashboard /> },
|
||||
{ key: 'realtime', label: 'Realtime', url: '/realtime', icon: <Clock /> },
|
||||
{ key: 'queries', label: 'Queries', url: '/queries', icon: <Search /> },
|
||||
];
|
||||
|
||||
const settings = [
|
||||
{ key: 'websites', label: 'Websites', url: '/settings/websites', icon: <Globe /> },
|
||||
{ key: 'users', label: 'Users', url: '/settings/users', icon: <User /> },
|
||||
{ key: 'teams', label: 'Teams', url: '/settings/teams', icon: <Users /> },
|
||||
];
|
||||
|
||||
export default function NavBar() {
|
||||
const [minimized, setMinimized] = useState(false);
|
||||
|
||||
const handleMinimize = () => setMinimized(state => !state);
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.navbar, { [styles.minimized]: minimized })}>
|
||||
<div className={styles.header} onClick={handleMinimize}>
|
||||
<Icon size="lg">
|
||||
<Logo />
|
||||
</Icon>
|
||||
<Text className={styles.text}>umami</Text>
|
||||
<Icon size="sm" rotate={minimized ? -90 : 90} className={styles.icon}>
|
||||
<ChevronDown />
|
||||
</Icon>
|
||||
</div>
|
||||
<NavGroup title="Analytics" items={analytics} minimized={minimized} />
|
||||
<NavGroup title="Settings" items={settings} minimized={minimized} />
|
||||
<div className={styles.footer}>
|
||||
<Icon>
|
||||
<Profile />
|
||||
</Icon>
|
||||
<Text>Profile</Text>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
47
components/layout/NavBar.module.css
Normal file
47
components/layout/NavBar.module.css
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
.navbar {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: var(--base75);
|
||||
height: 100%;
|
||||
width: 200px;
|
||||
border-right: 2px solid var(--base200);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
padding: 20px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header:hover .icon {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.minimized.navbar {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.minimized .text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.footer {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
padding: 20px;
|
||||
}
|
||||
51
components/layout/NavGroup.js
Normal file
51
components/layout/NavGroup.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import { useState } from 'react';
|
||||
import { Icon, Text, Icons } from 'react-basics';
|
||||
import classNames from 'classnames';
|
||||
import { useRouter } from 'next/router';
|
||||
import Link from 'next/link';
|
||||
import styles from './NavGroup.module.css';
|
||||
|
||||
const { ChevronDown } = Icons;
|
||||
|
||||
export default function NavGroup({
|
||||
title,
|
||||
items,
|
||||
defaultExpanded = true,
|
||||
allowExpand = true,
|
||||
minimized = false,
|
||||
}) {
|
||||
const { pathname } = useRouter();
|
||||
const [expanded, setExpanded] = useState(defaultExpanded);
|
||||
|
||||
const handleExpand = () => setExpanded(state => !state);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(styles.group, {
|
||||
[styles.expanded]: expanded,
|
||||
[styles.minimized]: minimized,
|
||||
})}
|
||||
>
|
||||
<div className={styles.header} onClick={allowExpand ? handleExpand : undefined}>
|
||||
<Text>{title}</Text>
|
||||
<Icon size="sm" rotate={expanded ? 0 : -90}>
|
||||
<ChevronDown />
|
||||
</Icon>
|
||||
</div>
|
||||
<div className={styles.body}>
|
||||
{items.map(({ key, label, url, icon }) => {
|
||||
return (
|
||||
<Link key={key} href={url}>
|
||||
<a
|
||||
className={classNames(styles.item, { [styles.selected]: pathname.startsWith(url) })}
|
||||
>
|
||||
<Icon>{icon}</Icon>
|
||||
<Text className={styles.text}>{label}</Text>
|
||||
</a>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
76
components/layout/NavGroup.module.css
Normal file
76
components/layout/NavGroup.module.css
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
.group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: var(--base600);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 10px 20px;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.body {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.expanded .body {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
margin-right: -2px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-right: 2px solid var(--base200);
|
||||
padding: 1rem 2rem;
|
||||
gap: var(--size500);
|
||||
font-weight: 600;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.item.selected {
|
||||
color: var(--base900);
|
||||
border-right-color: var(--primary400);
|
||||
background: var(--blue100);
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
color: var(--base900);
|
||||
}
|
||||
|
||||
.item.disabled {
|
||||
color: var(--base500) !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
a.item {
|
||||
color: var(--base600);
|
||||
}
|
||||
|
||||
.minimized .text,
|
||||
.minimized .header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.minimized .item {
|
||||
width: 60px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import classNames from 'classnames';
|
||||
import { Button, Icon } from 'react-basics';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue