mirror of
https://github.com/umami-software/umami.git
synced 2025-12-06 01:18:00 +01:00
Added user button and menu.
This commit is contained in:
parent
a5930f1772
commit
6e23a8a53b
21 changed files with 268 additions and 83 deletions
15
components/Account.js
Normal file
15
components/Account.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import Page from './Page';
|
||||
import styles from './Account.module.css';
|
||||
|
||||
export default function Account() {
|
||||
const user = useSelector(state => state.user);
|
||||
return (
|
||||
<Page>
|
||||
<h2>Account</h2>
|
||||
<div className={styles.label}>username</div>
|
||||
<div>{user.username}</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
4
components/Account.module.css
Normal file
4
components/Account.module.css
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
.label {
|
||||
font-size: var(--font-size-normal);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
|
@ -2,7 +2,8 @@
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: #f5f5f5;
|
||||
font-size: var(--font-size-normal);
|
||||
background: var(--gray100);
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
border: 0;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,18 @@
|
|||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import React, { useState, useRef } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Menu from './Menu';
|
||||
import useDocumentClick from 'hooks/useDocumentClick';
|
||||
import Chevron from 'assets/chevron-down.svg';
|
||||
import styles from './Dropdown.module.css';
|
||||
import Icon from './Icon';
|
||||
|
||||
export default function DropDown({ value, options = [], onChange, className }) {
|
||||
export default function DropDown({
|
||||
value,
|
||||
className,
|
||||
menuClassName,
|
||||
options = [],
|
||||
onChange = () => {},
|
||||
}) {
|
||||
const [showMenu, setShowMenu] = useState(false);
|
||||
const ref = useRef();
|
||||
|
||||
|
|
@ -16,35 +26,19 @@ export default function DropDown({ value, options = [], onChange, className }) {
|
|||
onChange(value);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
function hideMenu(e) {
|
||||
if (!ref.current.contains(e.target)) {
|
||||
setShowMenu(false);
|
||||
}
|
||||
useDocumentClick(e => {
|
||||
if (!ref.current.contains(e.target)) {
|
||||
setShowMenu(false);
|
||||
}
|
||||
|
||||
document.addEventListener('click', hideMenu);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', hideMenu);
|
||||
};
|
||||
}, [ref]);
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={ref} className={classNames(styles.dropdown, className)} onClick={handleShowMenu}>
|
||||
<div className={styles.value}>
|
||||
{options.find(e => e.value === value).label}
|
||||
<div className={styles.caret} />
|
||||
{options.find(e => e.value === value)?.label}
|
||||
<Icon icon={<Chevron />} size="S" className={styles.icon} />
|
||||
</div>
|
||||
{showMenu && (
|
||||
<div className={styles.menu}>
|
||||
{options.map(({ label, value }) => (
|
||||
<div key={value} className={styles.option} onClick={e => handleSelect(value, e)}>
|
||||
{label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{showMenu && <Menu className={menuClassName} options={options} onSelect={handleSelect} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,40 +8,14 @@
|
|||
white-space: nowrap;
|
||||
position: relative;
|
||||
padding: 4px 32px 4px 16px;
|
||||
border: 1px solid #b3b3b3;
|
||||
border: 1px solid var(--gray500);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu {
|
||||
.icon {
|
||||
position: absolute;
|
||||
min-width: 100px;
|
||||
top: 100%;
|
||||
margin-top: 4px;
|
||||
border: 1px solid #b3b3b3;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.option {
|
||||
background: #fff;
|
||||
padding: 4px 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.option:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.caret {
|
||||
position: absolute;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-right: 2px solid #8e8e8e;
|
||||
border-bottom: 2px solid #8e8e8e;
|
||||
transform: rotate(45deg);
|
||||
top: -4px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 12px;
|
||||
margin: auto;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import { useSelector } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import Link from 'components/Link';
|
||||
import UserButton from './UserButton';
|
||||
import styles from './Header.module.css';
|
||||
|
||||
export default function Header() {
|
||||
|
|
@ -11,16 +12,14 @@ export default function Header() {
|
|||
<header className={classNames(styles.header, 'container')}>
|
||||
<div className="row align-items-center">
|
||||
<div className="col">
|
||||
<Link href="/" className={styles.title}>
|
||||
umami
|
||||
</Link>
|
||||
<div className={styles.title}>{user ? <Link href="/">umami</Link> : 'umami'}</div>
|
||||
</div>
|
||||
{user && (
|
||||
<div className="col">
|
||||
<div className={styles.nav}>
|
||||
<Link href="/">Dashboard</Link>
|
||||
<Link href="/dashboard">Dashboard</Link>
|
||||
<Link href="/settings">Settings</Link>
|
||||
<Link href="/logout">Logout</Link>
|
||||
<UserButton />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,16 @@ import React from 'react';
|
|||
import classNames from 'classnames';
|
||||
import styles from './Icon.module.css';
|
||||
|
||||
export default function Icon({ icon, className }) {
|
||||
return <div className={classNames(styles.icon, className)}>{icon}</div>;
|
||||
export default function Icon({ icon, className, size = 'M' }) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(styles.icon, className, {
|
||||
[styles.large]: size === 'L',
|
||||
[styles.medium]: size === 'M',
|
||||
[styles.small]: size === 'S',
|
||||
})}
|
||||
>
|
||||
{icon}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,21 @@
|
|||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.icon > svg {
|
||||
.icon svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.large > svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.medium > svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.small > svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
.form {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
|
|
|||
24
components/Menu.js
Normal file
24
components/Menu.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import styles from './Menu.module.css';
|
||||
|
||||
export default function Menu({ options = [], className, align = 'left', onSelect = () => {} }) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(styles.menu, className, {
|
||||
[styles.left]: align === 'left',
|
||||
[styles.right]: align === 'right',
|
||||
})}
|
||||
>
|
||||
{options.map(({ label, value, className: optionClassName }) => (
|
||||
<div
|
||||
key={value}
|
||||
className={classNames(styles.option, optionClassName)}
|
||||
onClick={e => onSelect(value, e)}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
31
components/Menu.module.css
Normal file
31
components/Menu.module.css
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
.menu {
|
||||
position: absolute;
|
||||
min-width: 100px;
|
||||
top: 100%;
|
||||
margin-top: 4px;
|
||||
border: 1px solid var(--gray500);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.option {
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: normal;
|
||||
background: #fff;
|
||||
padding: 4px 16px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.option:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.right {
|
||||
right: 0;
|
||||
}
|
||||
56
components/UserButton.js
Normal file
56
components/UserButton.js
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import React, { useState, useRef } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouter } from 'next/router';
|
||||
import Menu from './Menu';
|
||||
import Icon from './Icon';
|
||||
import useDocumentClick from 'hooks/useDocumentClick';
|
||||
import User from 'assets/user.svg';
|
||||
import Chevron from 'assets/chevron-down.svg';
|
||||
import styles from './UserButton.module.css';
|
||||
|
||||
export default function UserButton() {
|
||||
const [showMenu, setShowMenu] = useState(false);
|
||||
const user = useSelector(state => state.user);
|
||||
const ref = useRef();
|
||||
const router = useRouter();
|
||||
|
||||
const menuOptions = [
|
||||
{
|
||||
label: (
|
||||
<>
|
||||
Logged in as <b>{user.username}</b>
|
||||
</>
|
||||
),
|
||||
value: 'username',
|
||||
className: styles.username,
|
||||
},
|
||||
{ label: 'Account', value: 'account' },
|
||||
{ label: 'Logout', value: 'logout' },
|
||||
];
|
||||
|
||||
function handleSelect(value) {
|
||||
setShowMenu(false);
|
||||
|
||||
if (value === 'account') {
|
||||
router.push('/account');
|
||||
} else if (value === 'logout') {
|
||||
router.push('/logout');
|
||||
}
|
||||
}
|
||||
|
||||
useDocumentClick(e => {
|
||||
if (!ref.current.contains(e.target)) {
|
||||
setShowMenu(false);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={ref} className={styles.container}>
|
||||
<div onClick={() => setShowMenu(state => !state)}>
|
||||
<Icon icon={<User />} size="L" className={styles.icon} />
|
||||
<Icon icon={<Chevron />} size="S" />
|
||||
</div>
|
||||
{showMenu && <Menu options={menuOptions} onSelect={handleSelect} align="right" />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
17
components/UserButton.module.css
Normal file
17
components/UserButton.module.css
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
.container {
|
||||
display: flex;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.username {
|
||||
border-bottom: 1px solid var(--gray500);
|
||||
}
|
||||
|
||||
.username:hover {
|
||||
background: var(--gray50);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue