mirror of
https://github.com/kremalicious/umami.git
synced 2024-12-24 18:26:20 +01:00
Responsive updates.
This commit is contained in:
parent
53b23420a4
commit
94ed67e339
@ -3,11 +3,12 @@ import { useState } from 'react';
|
||||
import MobileMenu from './MobileMenu';
|
||||
import Icons from 'components/icons';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
import styles from './HamburgerButton.module.css';
|
||||
import useConfig from 'hooks/useConfig';
|
||||
|
||||
export default function HamburgerButton() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const [active, setActive] = useState(false);
|
||||
const { cloudMode } = useConfig();
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
@ -15,7 +16,7 @@ export default function HamburgerButton() {
|
||||
value: '/dashboard',
|
||||
},
|
||||
{ label: formatMessage(labels.realtime), value: '/realtime' },
|
||||
{
|
||||
!cloudMode && {
|
||||
label: formatMessage(labels.settings),
|
||||
value: '/settings',
|
||||
},
|
||||
@ -23,20 +24,15 @@ export default function HamburgerButton() {
|
||||
label: formatMessage(labels.profile),
|
||||
value: '/settings/profile',
|
||||
},
|
||||
{ label: formatMessage(labels.logout), value: '/logout' },
|
||||
];
|
||||
!cloudMode && { label: formatMessage(labels.logout), value: '/logout' },
|
||||
].filter(n => n);
|
||||
|
||||
function handleClick() {
|
||||
setActive(state => !state);
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
setActive(false);
|
||||
}
|
||||
const handleClick = () => setActive(state => !state);
|
||||
const handleClose = () => setActive(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button className={styles.button} onClick={handleClick}>
|
||||
<Button variant="quiet" onClick={handleClick}>
|
||||
<Icon>{active ? <Icons.Close /> : <Icons.Menu />}</Icon>
|
||||
</Button>
|
||||
{active && <MobileMenu items={menuItems} onClose={handleClose} />}
|
||||
|
@ -1,19 +1,10 @@
|
||||
import classNames from 'classnames';
|
||||
import Link from 'next/link';
|
||||
import { Button, Icon } from 'react-basics';
|
||||
import Icons from 'components/icons';
|
||||
import styles from './MobileMenu.module.css';
|
||||
|
||||
export default function MobileMenu({ items = [], onClose }) {
|
||||
return (
|
||||
<div className={classNames(styles.menu)}>
|
||||
<div className={styles.header}>
|
||||
<Button onClick={onClose}>
|
||||
<Icon>
|
||||
<Icons.Close />
|
||||
</Icon>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.items}>
|
||||
{items.map(({ label, value }) => (
|
||||
<Link key={value} href={value} className={styles.item} onClick={onClose}>
|
||||
|
@ -1,6 +1,6 @@
|
||||
.menu {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
top: 60px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
@ -13,29 +13,17 @@
|
||||
}
|
||||
|
||||
.items {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.item {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 700;
|
||||
line-height: 80px;
|
||||
padding: 0 40px;
|
||||
}
|
||||
|
||||
.item + .item {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.item:last-child {
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
padding: 0 30px;
|
||||
a.item {
|
||||
color: var(--base900);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import styles from './NavBar.module.css';
|
||||
import useConfig from 'hooks/useConfig';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
import { useRouter } from 'next/router';
|
||||
import HamburgerButton from '../common/HamburgerButton';
|
||||
|
||||
export default function NavBar() {
|
||||
const { pathname } = useRouter();
|
||||
@ -52,6 +53,9 @@ export default function NavBar() {
|
||||
<LanguageButton />
|
||||
<ProfileButton />
|
||||
</div>
|
||||
<div className={styles.mobile}>
|
||||
<HamburgerButton />
|
||||
</div>
|
||||
</Column>
|
||||
</Row>
|
||||
</div>
|
||||
|
@ -59,7 +59,8 @@
|
||||
border-bottom: 2px solid var(--primary400);
|
||||
}
|
||||
|
||||
.actions {
|
||||
.actions,
|
||||
.mobile {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
@ -67,9 +68,17 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.links,
|
||||
.actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
@ -6,3 +6,9 @@
|
||||
position: relative;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.page {
|
||||
padding: 10px 0;
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,15 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { useBreakpoint } from 'react-basics';
|
||||
import styles from './PageHeader.module.css';
|
||||
|
||||
export default function PageHeader({ title, children, className, style }) {
|
||||
export default function PageHeader({ title, children }) {
|
||||
const breakPoint = useBreakpoint();
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.header, className)} style={style}>
|
||||
<div className={classNames(styles.header, { [styles[breakPoint]]: true })}>
|
||||
<div className={styles.title}>{title}</div>
|
||||
{children}
|
||||
<div className={styles.actions}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
align-content: center;
|
||||
align-self: stretch;
|
||||
margin-bottom: 40px;
|
||||
height: 50px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.header a {
|
||||
@ -22,4 +22,10 @@
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
gap: 20px;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
.xs .actions,
|
||||
.sm .actions {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ export const labels = defineMessages({
|
||||
password: { id: 'label.password', defaultMessage: 'Password' },
|
||||
role: { id: 'label.role', defaultMessage: 'Role' },
|
||||
user: { id: 'label.user', defaultMessage: 'User' },
|
||||
administrator: { id: 'label.administrator', defaultMessage: 'Administrator' },
|
||||
admin: { id: 'label.administrator', defaultMessage: 'Administrator' },
|
||||
confirm: { id: 'label.confirm', defaultMessage: 'Confirm' },
|
||||
details: { id: 'label.details', defaultMessage: 'Details' },
|
||||
websites: { id: 'label.websites', defaultMessage: 'Websites' },
|
||||
@ -112,6 +112,7 @@ export const labels = defineMessages({
|
||||
toggleCharts: { id: 'label.toggle-charts', defaultMessage: 'Toggle charts' },
|
||||
editDashboard: { id: 'label.edit-dashboard', defaultMessage: 'Edit dashboard' },
|
||||
title: { id: 'label.title', defaultMessage: 'Title' },
|
||||
view: { id: 'label.view', defaultMessage: 'View' },
|
||||
});
|
||||
|
||||
export const messages = defineMessages({
|
||||
|
@ -10,12 +10,13 @@ const MetricCard = ({
|
||||
reverseColors = false,
|
||||
format = formatNumber,
|
||||
hideComparison = false,
|
||||
className,
|
||||
}) => {
|
||||
const props = useSpring({ x: Number(value) || 0, from: { x: 0 } });
|
||||
const changeProps = useSpring({ x: Number(change) || 0, from: { x: 0 } });
|
||||
|
||||
return (
|
||||
<div className={styles.card}>
|
||||
<div className={classNames(styles.card, className)}>
|
||||
<animated.div className={styles.value}>{props.x.to(x => format(x))}</animated.div>
|
||||
<div className={styles.label}>
|
||||
{label}
|
||||
|
@ -6,8 +6,8 @@ import useDateRange from 'hooks/useDateRange';
|
||||
import usePageQuery from 'hooks/usePageQuery';
|
||||
import { formatShortTime, formatNumber, formatLongNumber } from 'lib/format';
|
||||
import MetricCard from './MetricCard';
|
||||
import styles from './MetricsBar.module.css';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
import styles from './MetricsBar.module.css';
|
||||
|
||||
export default function MetricsBar({ websiteId }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
@ -58,18 +58,21 @@ export default function MetricsBar({ websiteId }) {
|
||||
{data && !error && isFetched && (
|
||||
<>
|
||||
<MetricCard
|
||||
className={styles.card}
|
||||
label={formatMessage(labels.views)}
|
||||
value={pageviews.value}
|
||||
change={pageviews.change}
|
||||
format={formatFunc}
|
||||
/>
|
||||
<MetricCard
|
||||
className={styles.card}
|
||||
label={formatMessage(labels.visitors)}
|
||||
value={uniques.value}
|
||||
change={uniques.change}
|
||||
format={formatFunc}
|
||||
/>
|
||||
<MetricCard
|
||||
className={styles.card}
|
||||
label={formatMessage(labels.bounceRate)}
|
||||
value={uniques.value ? (num / uniques.value) * 100 : 0}
|
||||
change={
|
||||
@ -82,6 +85,7 @@ export default function MetricsBar({ websiteId }) {
|
||||
reverseColors
|
||||
/>
|
||||
<MetricCard
|
||||
className={styles.card}
|
||||
label={formatMessage(labels.averageVisitTime)}
|
||||
value={
|
||||
totaltime.value && pageviews.value
|
||||
|
@ -3,11 +3,15 @@
|
||||
cursor: pointer;
|
||||
min-height: 80px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.card {
|
||||
justify-self: flex-start;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
.bar {
|
||||
justify-content: space-between;
|
||||
overflow: auto;
|
||||
.card {
|
||||
flex-basis: calc(50% - 20px);
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,9 @@ import useTimezone from 'hooks/useTimezone';
|
||||
import usePageQuery from 'hooks/usePageQuery';
|
||||
import { getDateArray, getDateLength } from 'lib/date';
|
||||
import Icons from 'components/icons';
|
||||
import styles from './WebsiteChart.module.css';
|
||||
import useSticky from 'hooks/useSticky';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
import styles from './WebsiteChart.module.css';
|
||||
|
||||
export default function WebsiteChart({
|
||||
websiteId,
|
||||
@ -91,12 +91,14 @@ export default function WebsiteChart({
|
||||
[styles.isSticky]: isSticky,
|
||||
})}
|
||||
>
|
||||
<Column>
|
||||
<Column defaultSize={12} xl={8}>
|
||||
<MetricsBar websiteId={websiteId} />
|
||||
</Column>
|
||||
<Column className={styles.actions}>
|
||||
<RefreshButton websiteId={websiteId} isLoading={isLoading} />
|
||||
<DateFilter websiteId={websiteId} value={value} className={styles.dropdown} />
|
||||
<Column defaultSize={12} xl={4}>
|
||||
<div className={styles.actions}>
|
||||
<RefreshButton websiteId={websiteId} isLoading={isLoading} />
|
||||
<DateFilter websiteId={websiteId} value={value} className={styles.dropdown} />
|
||||
</div>
|
||||
</Column>
|
||||
</Row>
|
||||
<Row>
|
||||
|
@ -27,23 +27,31 @@
|
||||
z-index: var(--z-index-above);
|
||||
}
|
||||
|
||||
.sticky {
|
||||
position: sticky;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.isSticky {
|
||||
border-bottom: 1px solid var(--base300);
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 992px) {
|
||||
.sticky {
|
||||
position: sticky;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.isSticky {
|
||||
border-bottom: 1px solid var(--base300);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1200px) {
|
||||
.actions {
|
||||
margin-top: 40px;
|
||||
}
|
||||
}
|
||||
|
@ -6,11 +6,11 @@ import styles from './WebsiteHeader.module.css';
|
||||
export default function WebsiteHeader({ websiteId, name, domain, children }) {
|
||||
return (
|
||||
<Row className={styles.header} justifyContent="center">
|
||||
<Column className={styles.name} variant="two">
|
||||
<Column className={styles.title} variant="two">
|
||||
<Favicon domain={domain} />
|
||||
<Text>{name}</Text>
|
||||
</Column>
|
||||
<Column className={styles.body} variant="two">
|
||||
<Column className={styles.info} variant="two">
|
||||
<ActiveUsers websiteId={websiteId} />
|
||||
{children}
|
||||
</Column>
|
||||
|
@ -1,8 +1,4 @@
|
||||
.header {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.name {
|
||||
.title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
@ -10,12 +6,14 @@
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.body {
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 30px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ export default function Dashboard({ userId }) {
|
||||
{!editing && hasData && <DashboardSettingsButton />}
|
||||
</PageHeader>
|
||||
{!hasData && (
|
||||
<EmptyPlaceholder message={formatMessage(messages.noWebsites)}>
|
||||
<EmptyPlaceholder message={formatMessage(messages.noWebsitesConfigured)}>
|
||||
<Link href="/settings/websites">
|
||||
<Button>
|
||||
<Icon>
|
||||
|
@ -50,7 +50,7 @@ export default function UsersTable({ data = [], onDelete }) {
|
||||
addSuffix: true,
|
||||
}),
|
||||
role: formatMessage(
|
||||
labels[Object.keys(ROLES).find(key => ROLES[key] === row.role) || labels.unknown],
|
||||
labels[Object.keys(ROLES).find(key => ROLES[key] === row.role)] || labels.unknown,
|
||||
),
|
||||
action: (
|
||||
<>
|
||||
|
@ -45,7 +45,7 @@ export default function WebsitesList() {
|
||||
<PageHeader title={formatMessage(labels.websites)}>{addButton}</PageHeader>
|
||||
{hasData && <WebsitesTable data={data} />}
|
||||
{!hasData && (
|
||||
<EmptyPlaceholder message={formatMessage(messages.noWebsites)}>
|
||||
<EmptyPlaceholder message={formatMessage(messages.noWebsitesConfigured)}>
|
||||
{addButton}
|
||||
</EmptyPlaceholder>
|
||||
)}
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
Icon,
|
||||
Icons,
|
||||
Flexbox,
|
||||
useBreakpoint,
|
||||
} from 'react-basics';
|
||||
import useMessages from 'hooks/useMessages';
|
||||
import useConfig from 'hooks/useConfig';
|
||||
@ -18,11 +19,12 @@ import useConfig from 'hooks/useConfig';
|
||||
export default function WebsitesTable({ data = [] }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { openExternal } = useConfig();
|
||||
const breakPoint = useBreakpoint();
|
||||
|
||||
const columns = [
|
||||
{ name: 'name', label: formatMessage(labels.name), style: { flex: 2 } },
|
||||
{ name: 'domain', label: formatMessage(labels.domain) },
|
||||
{ name: 'action', label: ' ' },
|
||||
{ name: 'action', label: ' ', style: { flexBasis: '100%' } },
|
||||
];
|
||||
|
||||
return (
|
||||
|
Loading…
Reference in New Issue
Block a user