mirror of
https://github.com/kremalicious/umami.git
synced 2024-12-24 02:06:19 +01:00
Updated navigation.
This commit is contained in:
parent
611169c65f
commit
fc2a8f3d9f
@ -1,14 +1,11 @@
|
||||
import { Icon, Button, PopupTrigger, Popup, Tooltip, Text } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Icon, Button, PopupTrigger, Popup, Text } from 'react-basics';
|
||||
import classNames from 'classnames';
|
||||
import { languages } from 'lib/lang';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import Icons from 'components/icons';
|
||||
import { labels } from 'components/messages';
|
||||
import styles from './LanguageButton.module.css';
|
||||
|
||||
export default function LanguageButton({ tooltipPosition = 'top', menuPosition = 'right' }) {
|
||||
const { formatMessage } = useIntl();
|
||||
export default function LanguageButton() {
|
||||
const { locale, saveLocale } = useLocale();
|
||||
const items = Object.keys(languages).map(key => ({ ...languages[key], value: key }));
|
||||
|
||||
@ -18,14 +15,12 @@ export default function LanguageButton({ tooltipPosition = 'top', menuPosition =
|
||||
|
||||
return (
|
||||
<PopupTrigger>
|
||||
<Tooltip label={formatMessage(labels.language)} position={tooltipPosition}>
|
||||
<Button variant="quiet">
|
||||
<Icon>
|
||||
<Icons.Globe />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Popup position={menuPosition} alignment="end">
|
||||
<Button variant="quiet">
|
||||
<Icon>
|
||||
<Icons.Globe />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Popup position="bottom" alignment="end">
|
||||
<div className={styles.menu}>
|
||||
{items.map(({ value, label }) => {
|
||||
return (
|
||||
|
53
components/input/ProfileButton.js
Normal file
53
components/input/ProfileButton.js
Normal file
@ -0,0 +1,53 @@
|
||||
import { Icon, Button, PopupTrigger, Popup, Menu, Item, Text } from 'react-basics';
|
||||
import { useRouter } from 'next/router';
|
||||
import Icons from 'components/icons';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
import useUser from 'hooks/useUser';
|
||||
import styles from './ProfileButton.module.css';
|
||||
|
||||
export default function ProfileButton() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { user } = useUser();
|
||||
const router = useRouter();
|
||||
|
||||
const handleSelect = key => {
|
||||
if (key === 'profile') {
|
||||
router.push('/settings/profile');
|
||||
}
|
||||
if (key === 'logout') {
|
||||
router.push('/logout');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PopupTrigger>
|
||||
<Button variant="quiet">
|
||||
<Icon>
|
||||
<Icons.Profile />
|
||||
</Icon>
|
||||
<Icon size="sm">
|
||||
<Icons.ChevronDown />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Popup position="bottom" alignment="end">
|
||||
<Menu variant="popup" onSelect={handleSelect} className={styles.menu}>
|
||||
<Item key="user" className={styles.item}>
|
||||
<Text>{user.username}</Text>
|
||||
</Item>
|
||||
<Item key="profile" className={styles.item} divider={true}>
|
||||
<Icon>
|
||||
<Icons.User />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.profile)}</Text>
|
||||
</Item>
|
||||
<Item key="logout" className={styles.item}>
|
||||
<Icon>
|
||||
<Icons.Logout />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.logout)}</Text>
|
||||
</Item>
|
||||
</Menu>
|
||||
</Popup>
|
||||
</PopupTrigger>
|
||||
);
|
||||
}
|
9
components/input/ProfileButton.module.css
Normal file
9
components/input/ProfileButton.module.css
Normal file
@ -0,0 +1,9 @@
|
||||
.menu {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
background: var(--base50);
|
||||
}
|
@ -1,14 +1,12 @@
|
||||
import { useTransition, animated } from 'react-spring';
|
||||
import { Button, Icon, Tooltip } from 'react-basics';
|
||||
import { Button, Icon } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import useTheme from 'hooks/useTheme';
|
||||
import Icons from 'components/icons';
|
||||
import { labels } from 'components/messages';
|
||||
import styles from './ThemeButton.module.css';
|
||||
|
||||
export default function ThemeButton({ tooltipPosition = 'top' }) {
|
||||
export default function ThemeButton() {
|
||||
const [theme, setTheme] = useTheme();
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const transitions = useTransition(theme, {
|
||||
initial: { opacity: 1 },
|
||||
@ -28,14 +26,12 @@ export default function ThemeButton({ tooltipPosition = 'top' }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip label={formatMessage(labels.theme)} position={tooltipPosition}>
|
||||
<Button variant="quiet" className={styles.button} onClick={handleClick}>
|
||||
{transitions((style, item) => (
|
||||
<animated.div key={item} style={style}>
|
||||
<Icon className={styles.icon}>{item === 'light' ? <Icons.Sun /> : <Icons.Moon />}</Icon>
|
||||
</animated.div>
|
||||
))}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button variant="quiet" className={styles.button} onClick={handleClick}>
|
||||
{transitions((style, item) => (
|
||||
<animated.div key={item} style={style}>
|
||||
<Icon className={styles.icon}>{item === 'light' ? <Icons.Sun /> : <Icons.Moon />}</Icon>
|
||||
</animated.div>
|
||||
))}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
@ -1,15 +1,21 @@
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-columns: max-content 1fr;
|
||||
grid-template-rows: max-content 1fr;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.nav {
|
||||
grid-row: 1 / 3;
|
||||
height: 60px;
|
||||
width: 100vw;
|
||||
z-index: 100;
|
||||
grid-column: 1;
|
||||
grid-row: 1 / 2;
|
||||
}
|
||||
|
||||
.body {
|
||||
grid-area: 1 / 2;
|
||||
overflow: auto;
|
||||
height: 100vh;
|
||||
grid-column: 1;
|
||||
grid-row: 2 / 3;
|
||||
min-height: 0;
|
||||
max-height: calc(100vh - 60px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
@ -1,69 +1,50 @@
|
||||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Icon, Text } from 'react-basics';
|
||||
import Link from 'next/link';
|
||||
import classNames from 'classnames';
|
||||
import Icons from 'components/icons';
|
||||
import ThemeButton from 'components/input/ThemeButton';
|
||||
import LanguageButton from 'components/input/LanguageButton';
|
||||
import LogoutButton from 'components/input/LogoutButton';
|
||||
import { labels } from 'components/messages';
|
||||
import useUser from 'hooks/useUser';
|
||||
import NavGroup from './NavGroup';
|
||||
import ProfileButton from 'components/input/ProfileButton';
|
||||
import styles from './NavBar.module.css';
|
||||
import useConfig from 'hooks/useConfig';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function NavBar() {
|
||||
const { user } = useUser();
|
||||
const { cloudMode } = useConfig();
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const [minimized, setMinimized] = useState(false);
|
||||
const tooltipPosition = minimized ? 'right' : 'top';
|
||||
|
||||
const analytics = [
|
||||
const links = [
|
||||
{ label: formatMessage(labels.dashboard), url: '/dashboard', icon: <Icons.Dashboard /> },
|
||||
{ label: formatMessage(labels.realtime), url: '/realtime', icon: <Icons.Clock /> },
|
||||
];
|
||||
|
||||
const settings = [
|
||||
!cloudMode && {
|
||||
label: formatMessage(labels.websites),
|
||||
url: '/settings/websites',
|
||||
icon: <Icons.Globe />,
|
||||
},
|
||||
user?.isAdmin && {
|
||||
label: formatMessage(labels.users),
|
||||
url: '/settings/users',
|
||||
icon: <Icons.User />,
|
||||
},
|
||||
!cloudMode && {
|
||||
label: formatMessage(labels.teams),
|
||||
url: '/settings/teams',
|
||||
icon: <Icons.Users />,
|
||||
},
|
||||
{ label: formatMessage(labels.profile), url: '/settings/profile', icon: <Icons.Profile /> },
|
||||
!cloudMode && { label: formatMessage(labels.settings), url: '/settings', icon: <Icons.Gear /> },
|
||||
].filter(n => n);
|
||||
|
||||
const handleMinimize = () => setMinimized(state => !state);
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.navbar, { [styles.minimized]: minimized })}>
|
||||
<div className={styles.header} onClick={handleMinimize}>
|
||||
<div className={styles.logo} onClick={handleMinimize}>
|
||||
<Icon size="lg">
|
||||
<Icons.Logo />
|
||||
</Icon>
|
||||
<Text className={styles.text}>umami</Text>
|
||||
<Icon size="sm" rotate={minimized ? -90 : 90} className={styles.icon}>
|
||||
<Icons.ChevronDown />
|
||||
</Icon>
|
||||
</div>
|
||||
<NavGroup title={formatMessage(labels.analytics)} items={analytics} minimized={minimized} />
|
||||
<NavGroup title={formatMessage(labels.settings)} items={settings} minimized={minimized} />
|
||||
<div className={styles.footer}>
|
||||
<div className={styles.buttons}>
|
||||
<ThemeButton tooltipPosition={tooltipPosition} />
|
||||
<LanguageButton tooltipPosition={tooltipPosition} />
|
||||
{!cloudMode && <LogoutButton tooltipPosition={tooltipPosition} />}
|
||||
</div>
|
||||
<div className={styles.links}>
|
||||
{links.map(({ url, icon, label }) => {
|
||||
return (
|
||||
<Link key={url} href={url}>
|
||||
<Icon>{icon}</Icon>
|
||||
<Text>{label}</Text>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
<ThemeButton />
|
||||
<LanguageButton />
|
||||
{!cloudMode && <ProfileButton />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,62 +1,49 @@
|
||||
.navbar {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 60px;
|
||||
background: var(--base75);
|
||||
height: 100%;
|
||||
width: 200px;
|
||||
border-right: 2px solid var(--base200);
|
||||
border-bottom: 1px solid var(--base200);
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
padding: 20px 0;
|
||||
cursor: pointer;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.header:hover .icon {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.icon {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
border-radius: 100%;
|
||||
color: var(--base50);
|
||||
background: var(--base800);
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.minimized.navbar {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.minimized .text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.footer {
|
||||
flex: 1;
|
||||
.links {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
padding: 20px;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
padding: 0 40px;
|
||||
flex: 1;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
.links a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
color: var(--font-color100);
|
||||
}
|
||||
|
||||
.minimized .buttons {
|
||||
flex-direction: column;
|
||||
.links a:hover {
|
||||
color: var(--primary400);
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
min-width: 0;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import styles from './Page.module.css';
|
||||
import { Banner, Loading } from 'react-basics';
|
||||
import styles from './Page.module.css';
|
||||
|
||||
export default function Page({ className, error, loading, children }) {
|
||||
if (error) {
|
||||
|
@ -84,7 +84,7 @@ export default function WebsiteChart({
|
||||
<StickyHeader
|
||||
stickyClassName={styles.sticky}
|
||||
enabled={stickyHeader}
|
||||
scrollElement={document.getElementById(UI_LAYOUT_BODY) || document}
|
||||
scrollElement={document.getElementById(UI_LAYOUT_BODY)}
|
||||
>
|
||||
<Row className={styles.header}>
|
||||
<Column>
|
||||
|
@ -1,15 +1,14 @@
|
||||
import { Form, FormRow } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import TimezoneSetting from 'components/pages/settings/profile/TimezoneSetting';
|
||||
import DateRangeSetting from 'components/pages/settings/profile/DateRangeSetting';
|
||||
import LanguageSetting from 'components/pages/settings/profile/LanguageSetting';
|
||||
import ThemeSetting from 'components/pages/settings/profile/ThemeSetting';
|
||||
import useUser from 'hooks/useUser';
|
||||
import { labels } from 'components/messages';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
|
||||
export default function ProfileDetails() {
|
||||
const { user } = useUser();
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
@ -20,7 +19,9 @@ export default function ProfileDetails() {
|
||||
return (
|
||||
<Form>
|
||||
<FormRow label={formatMessage(labels.username)}>{username}</FormRow>
|
||||
<FormRow label={formatMessage(labels.role)}>{role}</FormRow>
|
||||
<FormRow label={formatMessage(labels.role)}>
|
||||
{formatMessage(labels[role] || labels.unknown)}
|
||||
</FormRow>
|
||||
<FormRow label={formatMessage(labels.defaultDateRange)}>
|
||||
<DateRangeSetting />
|
||||
</FormRow>
|
||||
|
8
hooks/useMessages.js
Normal file
8
hooks/useMessages.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { useIntl } from 'react-intl';
|
||||
import { messages, labels } from 'components/messages';
|
||||
|
||||
export default function useMessages() {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return { formatMessage, messages, labels };
|
||||
}
|
@ -29,7 +29,7 @@ export default function App({ Component, pageProps }) {
|
||||
|
||||
const Wrapper = ({ children }) => <span className={locale}>{children}</span>;
|
||||
|
||||
if (!config || config.uiDisabled) {
|
||||
if (config?.uiDisabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,3 @@
|
||||
html {
|
||||
overflow-x: hidden;
|
||||
margin-right: calc(-1 * (100vw - 100%));
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
font-family: Inter, -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans,
|
||||
@ -20,6 +15,7 @@ body {
|
||||
flex: 1;
|
||||
color: var(--font-color100);
|
||||
background: var(--base50);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
*,
|
||||
@ -64,7 +60,8 @@ svg {
|
||||
#__next {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user