Refactored layout. Added NavBar component.

This commit is contained in:
Mike Cao 2023-01-18 15:05:39 -08:00
parent fad38dc180
commit 1d9c3133f0
56 changed files with 601 additions and 429 deletions

View file

@ -0,0 +1,30 @@
import { languages } from 'lib/lang';
import useLocale from 'hooks/useLocale';
import MenuButton from 'components/common/MenuButton';
import Globe from 'assets/globe.svg';
import styles from './LanguageButton.module.css';
import { Icon } from 'react-basics';
export default function LanguageButton() {
const { locale, saveLocale } = useLocale();
const menuOptions = Object.keys(languages).map(key => ({ ...languages[key], value: key }));
function handleSelect(value) {
saveLocale(value);
}
return (
<MenuButton
options={menuOptions}
value={locale}
menuClassName={styles.menu}
buttonVariant="light"
onSelect={handleSelect}
hideLabel
>
<Icon>
<Globe />
</Icon>
</MenuButton>
);
}

View file

@ -0,0 +1,25 @@
.menu {
display: flex;
flex-flow: row wrap;
min-width: 560px;
max-width: 100vw;
padding: 10px;
}
.menu div {
border-radius: 5px;
min-width: calc(100% / 3);
}
@media only screen and (max-width: 992px) {
.menu {
min-width: 90vw;
transform: translateX(calc(40vw));
}
}
@media only screen and (max-width: 768px) {
.menu div {
min-width: 50%;
}
}

View file

@ -0,0 +1,49 @@
import { useRef, useState } from 'react';
import { FormattedMessage } from 'react-intl';
import TimezoneSetting from '../pages/settings/profile/TimezoneSetting';
import DateRangeSetting from '../pages/settings/profile/DateRangeSetting';
import { Button, Icon } from 'react-basics';
import styles from './SettingsButton.module.css';
import Gear from 'assets/gear.svg';
import useDocumentClick from '../../hooks/useDocumentClick';
export default function SettingsButton() {
const [show, setShow] = useState(false);
const ref = useRef();
function handleClick() {
setShow(state => !state);
}
useDocumentClick(e => {
if (!ref.current?.contains(e.target)) {
setShow(false);
}
});
return (
<div className={styles.button} ref={ref}>
<Button variant="light" onClick={handleClick}>
<Icon>
<Gear />
</Icon>
</Button>
{show && (
<div className={styles.panel}>
<dt>
<FormattedMessage id="label.timezone" defaultMessage="Timezone" />
</dt>
<dd>
<TimezoneSetting />
</dd>
<dt>
<FormattedMessage id="label.default-date-range" defaultMessage="Default date range" />
</dt>
<dd>
<DateRangeSetting />
</dd>
</div>
)}
</div>
);
}

View file

@ -0,0 +1,20 @@
.button {
position: relative;
}
.panel {
background: var(--base50);
border: 1px solid var(--base500);
border-radius: 4px;
display: flex;
flex-direction: column;
position: absolute;
top: 100%;
right: 0;
padding: 20px;
z-index: 100;
}
.panel dd {
display: flex;
}

View file

@ -0,0 +1,37 @@
import { useTransition, animated } from 'react-spring';
import useTheme from 'hooks/useTheme';
import Sun from 'assets/sun.svg';
import Moon from 'assets/moon.svg';
import styles from './ThemeButton.module.css';
import { Icon } from 'react-basics';
export default function ThemeButton() {
const [theme, setTheme] = useTheme();
const transitions = useTransition(theme, {
initial: { opacity: 1 },
from: {
opacity: 0,
transform: `translateY(${theme === 'light' ? '20px' : '-20px'}) scale(0.5)`,
},
enter: { opacity: 1, transform: 'translateY(0px) scale(1)' },
leave: {
opacity: 0,
transform: `translateY(${theme === 'light' ? '-20px' : '20px'}) scale(0.5)`,
},
});
function handleClick() {
setTheme(theme === 'light' ? 'dark' : 'light');
}
return (
<div className={styles.button} onClick={handleClick}>
{transitions((styles, item) => (
<animated.div key={item} style={styles}>
<Icon>{item === 'light' ? <Sun /> : <Moon />}</Icon>
</animated.div>
))}
</div>
);
}

View file

@ -0,0 +1,13 @@
.button {
width: 50px;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
padding-bottom: 3px;
}
.button svg {
position: absolute;
}

View file

@ -0,0 +1,31 @@
import classNames from 'classnames';
import { Button, Icon } from 'react-basics';
import useTheme from 'hooks/useTheme';
import Sun from 'assets/sun.svg';
import Moon from 'assets/moon.svg';
import styles from './ThemeSetting.module.css';
export default function ThemeSetting() {
const [theme, setTheme] = useTheme();
return (
<div className={styles.buttons}>
<Button
className={classNames({ [styles.active]: theme === 'light' })}
onClick={() => setTheme('light')}
>
<Icon>
<Sun />
</Icon>
</Button>
<Button
className={classNames({ [styles.active]: theme === 'dark' })}
onClick={() => setTheme('dark')}
>
<Icon>
<Moon />
</Icon>
</Button>
</div>
);
}

View file

@ -0,0 +1,8 @@
.buttons {
display: flex;
gap: 10px;
}
.active {
border: 2px solid var(--primary400);
}

View file

@ -0,0 +1,80 @@
import useConfig from 'hooks/useConfig';
import useUser from 'hooks/useUser';
import { AUTH_TOKEN } from 'lib/constants';
import { removeItem } from 'next-basics';
import { useRouter } from 'next/router';
import { useRef, useState } from 'react';
import { Button, Icon, Item, Menu, Popup, Text } from 'react-basics';
import { FormattedMessage } from 'react-intl';
import useDocumentClick from 'hooks/useDocumentClick';
import Profile from 'assets/profile.svg';
import styles from './UserButton.module.css';
export default function UserButton() {
const [show, setShow] = useState(false);
const ref = useRef();
const user = useUser();
const router = useRouter();
const { adminDisabled } = useConfig();
const menuOptions = [
{
label: (
<FormattedMessage
id="label.logged-in-as"
defaultMessage="Logged in as {username}"
values={{ username: <b>{user.username}</b> }}
/>
),
value: 'username',
className: styles.username,
},
{
label: <FormattedMessage id="label.profile" defaultMessage="Profile" />,
value: 'profile',
hidden: adminDisabled,
divider: true,
},
{ label: <FormattedMessage id="label.logout" defaultMessage="Logout" />, value: 'logout' },
];
function handleClick() {
setShow(state => !state);
}
function handleSelect(value) {
if (value === 'logout') {
removeItem(AUTH_TOKEN);
router.push('/login');
} else if (value === 'profile') {
router.push('/profile');
}
}
useDocumentClick(e => {
if (!ref.current?.contains(e.target)) {
setShow(false);
}
});
return (
<div className={styles.button} ref={ref}>
<Button variant="light" onClick={handleClick}>
<Icon className={styles.icon} size="large">
<Profile />
</Icon>
</Button>
{show && (
<Popup className={styles.menu} position="bottom" gap={5}>
<Menu items={menuOptions} onSelect={handleSelect}>
{({ label, value }) => (
<Item key={value}>
<Text>{label}</Text>
</Item>
)}
</Menu>
</Popup>
)}
</div>
);
}

View file

@ -0,0 +1,25 @@
.button {
position: relative;
}
.username {
border-bottom: 1px solid var(--base500);
}
.username:hover {
background: var(--base50);
}
.icon svg {
height: 16px;
width: 16px;
}
.menu {
left: -50%;
background: var(--base50);
border: 1px solid var(--base500);
border-radius: 4px;
overflow: hidden;
z-index: 100;
}