mirror of
https://github.com/umami-software/umami.git
synced 2026-02-08 22:57:12 +01:00
New mobile menu.
This commit is contained in:
parent
18efd4d101
commit
34ad1d9c39
14 changed files with 189 additions and 153 deletions
44
components/common/HamburgerButton.js
Normal file
44
components/common/HamburgerButton.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import Button from 'components/common/Button';
|
||||
import XMark from 'assets/xmark.svg';
|
||||
import Bars from 'assets/bars.svg';
|
||||
import { useState } from 'react';
|
||||
import styles from './HamburgerButton.module.css';
|
||||
import MobileMenu from './MobileMenu';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
label: <FormattedMessage id="label.dashboard" defaultMessage="Dashboard" />,
|
||||
value: '/dashboard',
|
||||
},
|
||||
{ label: <FormattedMessage id="label.realtime" defaultMessage="Realtime" />, value: '/realtime' },
|
||||
{ label: <FormattedMessage id="label.settings" defaultMessage="Settings" />, value: '/settings' },
|
||||
{
|
||||
label: <FormattedMessage id="label.profile" defaultMessage="Profile" />,
|
||||
value: '/settings/profile',
|
||||
},
|
||||
{ label: <FormattedMessage id="label.logout" defaultMessage="Logout" />, value: '/logout' },
|
||||
];
|
||||
|
||||
export default function HamburgerButton() {
|
||||
const [active, setActive] = useState(false);
|
||||
|
||||
function handleClick() {
|
||||
setActive(state => !state);
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
setActive(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
className={styles.button}
|
||||
icon={active ? <XMark /> : <Bars />}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
{active && <MobileMenu items={menuItems} onClose={handleClose} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
9
components/common/HamburgerButton.module.css
Normal file
9
components/common/HamburgerButton.module.css
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
.button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.button {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ import NextLink from 'next/link';
|
|||
import Icon from './Icon';
|
||||
import styles from './Link.module.css';
|
||||
|
||||
function Link({ className, icon, children, size, iconRight, ...props }) {
|
||||
function Link({ className, icon, children, size, iconRight, onClick, ...props }) {
|
||||
return (
|
||||
<NextLink {...props}>
|
||||
<a
|
||||
|
|
@ -15,6 +15,7 @@ function Link({ className, icon, children, size, iconRight, ...props }) {
|
|||
[styles.xsmall]: size === 'xsmall',
|
||||
[styles.iconRight]: iconRight,
|
||||
})}
|
||||
onClick={onClick}
|
||||
>
|
||||
{icon && <Icon className={styles.icon} icon={icon} size={size} />}
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -8,20 +8,14 @@ a.link:visited {
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
a.link:before {
|
||||
a.link:hover:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
width: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: var(--primary400);
|
||||
opacity: 0.5;
|
||||
transition: width 100ms;
|
||||
}
|
||||
|
||||
a.link:hover:before {
|
||||
width: 100%;
|
||||
transition: width 100ms;
|
||||
}
|
||||
|
||||
a.link.large {
|
||||
|
|
|
|||
21
components/common/MobileMenu.js
Normal file
21
components/common/MobileMenu.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import Link from './Link';
|
||||
import Button from './Button';
|
||||
import XMark from 'assets/xmark.svg';
|
||||
import styles from './MobileMenu.module.css';
|
||||
|
||||
export default function MobileMenu({ items = [], onClose }) {
|
||||
return (
|
||||
<div className={styles.menu}>
|
||||
<div className={styles.header}>
|
||||
<Button className={styles.button} icon={<XMark />} onClick={onClose} />
|
||||
</div>
|
||||
<div className={styles.items}>
|
||||
{items.map(({ label, value }) => (
|
||||
<Link key={value} href={value} className={styles.item} onClick={onClose}>
|
||||
{label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
40
components/common/MobileMenu.module.css
Normal file
40
components/common/MobileMenu.module.css
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
.menu {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--gray50);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.items {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.item {
|
||||
font-size: var(--font-size-large);
|
||||
}
|
||||
|
||||
.item + .item {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
}
|
||||
|
|
@ -4,32 +4,29 @@ import { FormattedMessage } from 'react-intl';
|
|||
import Link from 'components/common/Link';
|
||||
import styles from './Footer.module.css';
|
||||
import useVersion from 'hooks/useVersion';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import { HOMEPAGE_URL, VERSION_URL } from 'lib/constants';
|
||||
|
||||
export default function Footer() {
|
||||
const { current } = useVersion();
|
||||
const { dir } = useLocale();
|
||||
|
||||
return (
|
||||
<footer className="container" dir={dir}>
|
||||
<div 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="https://umami.is">
|
||||
<b>umami</b>
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={classNames(styles.version, 'col-12 col-md-4')}>
|
||||
<Link href={`https://github.com/mikecao/umami/releases`}>{`v${current}`}</Link>
|
||||
</div>
|
||||
<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={VERSION_URL}>{`v${current}`}</Link>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: var(--font-size-small);
|
||||
min-height: 100px;
|
||||
text-align: center;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.version {
|
||||
|
|
|
|||
|
|
@ -1,71 +1,48 @@
|
|||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import Link from 'components/common/Link';
|
||||
import Icon from 'components/common/Icon';
|
||||
import LanguageButton from 'components/settings/LanguageButton';
|
||||
import ThemeButton from 'components/settings/ThemeButton';
|
||||
import HamburgerButton from 'components/common/HamburgerButton';
|
||||
import UpdateNotice from 'components/common/UpdateNotice';
|
||||
import UserButton from 'components/settings/UserButton';
|
||||
import Button from 'components/common/Button';
|
||||
import Logo from 'assets/logo.svg';
|
||||
import styles from './Header.module.css';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import XMark from 'assets/xmark.svg';
|
||||
import Bars from 'assets/bars.svg';
|
||||
import useUser from 'hooks/useUser';
|
||||
import { HOMEPAGE_URL } from 'lib/constants';
|
||||
|
||||
export default function Header() {
|
||||
const { user } = useUser();
|
||||
const [active, setActive] = useState(false);
|
||||
const { dir } = useLocale();
|
||||
|
||||
function handleClick() {
|
||||
setActive(state => !state);
|
||||
}
|
||||
|
||||
return (
|
||||
<nav className="container" dir={dir}>
|
||||
<>
|
||||
{user?.is_admin && <UpdateNotice />}
|
||||
<div className={classNames(styles.header, 'row align-items-center')}>
|
||||
<div className={styles.nav}>
|
||||
<div className="">
|
||||
<div className={styles.title}>
|
||||
<Icon icon={<Logo />} size="large" className={styles.logo} />
|
||||
<Link href={user ? '/' : 'https://umami.is'}>umami</Link>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className={styles.burger}
|
||||
icon={active ? <XMark /> : <Bars />}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
{user && (
|
||||
<div className={styles.items}>
|
||||
<div className={active ? classNames(styles.active) : ''}>
|
||||
<Link href="/dashboard">
|
||||
<FormattedMessage id="label.dashboard" defaultMessage="Dashboard" />
|
||||
</Link>
|
||||
<Link href="/realtime">
|
||||
<FormattedMessage id="label.realtime" defaultMessage="Realtime" />
|
||||
</Link>
|
||||
<Link href="/settings">
|
||||
<FormattedMessage id="label.settings" defaultMessage="Settings" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.items}>
|
||||
<div className={active ? classNames(styles.active) : ''}>
|
||||
<div className={styles.buttons}>
|
||||
<ThemeButton />
|
||||
<LanguageButton menuAlign="right" />
|
||||
{user && <UserButton />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<header className={classNames(styles.header, 'row')}>
|
||||
<div className={styles.title}>
|
||||
<Icon icon={<Logo />} size="large" className={styles.logo} />
|
||||
<Link href={user ? '/' : HOMEPAGE_URL}>umami</Link>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<HamburgerButton />
|
||||
{user && (
|
||||
<div className={styles.links}>
|
||||
<Link href="/dashboard">
|
||||
<FormattedMessage id="label.dashboard" defaultMessage="Dashboard" />
|
||||
</Link>
|
||||
<Link href="/realtime">
|
||||
<FormattedMessage id="label.realtime" defaultMessage="Realtime" />
|
||||
</Link>
|
||||
<Link href="/settings">
|
||||
<FormattedMessage id="label.settings" defaultMessage="Settings" />
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.buttons}>
|
||||
<ThemeButton />
|
||||
<LanguageButton menuAlign="right" />
|
||||
{user && <UserButton />}
|
||||
</div>
|
||||
</header>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,6 @@
|
|||
.navbar {
|
||||
align-items: stretch;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.burger {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 100px;
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -27,16 +16,8 @@
|
|||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: var(--font-size-normal);
|
||||
font-weight: 600;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.items {
|
||||
.links {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
|
@ -44,7 +25,7 @@
|
|||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nav a + a {
|
||||
.links a + a {
|
||||
margin-left: 40px;
|
||||
}
|
||||
|
||||
|
|
@ -55,13 +36,13 @@
|
|||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
.nav {
|
||||
font-size: var(--font-size-large);
|
||||
justify-content: space-between;
|
||||
margin: 20px 0;
|
||||
.header .buttons {
|
||||
flex: 1;
|
||||
}
|
||||
.items {
|
||||
flex-wrap: wrap;
|
||||
.links {
|
||||
order: 2;
|
||||
margin: 20px 0;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,47 +51,14 @@
|
|||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.title {
|
||||
padding: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.nav {
|
||||
font-size: var(--font-size-normal);
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.items {
|
||||
display: flex;
|
||||
justify-content: unset;
|
||||
font-size: var(--font-size-normal);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.items > div {
|
||||
.buttons,
|
||||
.links {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.header .active {
|
||||
display: inherit;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.items a {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.burger {
|
||||
display: block;
|
||||
background: none;
|
||||
border: 1px solid var(--gray900);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
.title {
|
||||
flex: 1;
|
||||
padding: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue