mirror of
https://github.com/kremalicious/umami.git
synced 2025-02-14 21:10:34 +01:00
More deletes. Fixed sticky header.
This commit is contained in:
parent
87bbaa7f1d
commit
45c13da262
@ -1,28 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import styles from './Icon.module.css';
|
|
||||||
|
|
||||||
function Icon({ icon, className, size = 'medium', ...props }) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classNames(styles.icon, className, {
|
|
||||||
[styles.xlarge]: size === 'xlarge',
|
|
||||||
[styles.large]: size === 'large',
|
|
||||||
[styles.medium]: size === 'medium',
|
|
||||||
[styles.small]: size === 'small',
|
|
||||||
[styles.xsmall]: size === 'xsmall',
|
|
||||||
})}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{icon}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Icon.propTypes = {
|
|
||||||
className: PropTypes.string,
|
|
||||||
icon: PropTypes.node.isRequired,
|
|
||||||
size: PropTypes.oneOf(['xlarge', 'large', 'medium', 'small', 'xsmall']),
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Icon;
|
|
@ -1,35 +0,0 @@
|
|||||||
.icon {
|
|
||||||
display: inline-flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon svg {
|
|
||||||
fill: currentColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xlarge > svg {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.large > svg {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.medium > svg {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.small > svg {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xsmall > svg {
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import NextLink from 'next/link';
|
|
||||||
import { Icon } from 'react-basics';
|
|
||||||
import styles from './Link.module.css';
|
|
||||||
|
|
||||||
function Link({ className, icon, children, size, iconRight, onClick, ...props }) {
|
|
||||||
return (
|
|
||||||
<NextLink {...props}>
|
|
||||||
<a
|
|
||||||
className={classNames(styles.link, className, {
|
|
||||||
[styles.large]: size === 'large',
|
|
||||||
[styles.small]: size === 'small',
|
|
||||||
[styles.xsmall]: size === 'xsmall',
|
|
||||||
[styles.iconRight]: iconRight,
|
|
||||||
})}
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
{icon && <Icon className={styles.icon} icon={icon} size={size} />}
|
|
||||||
{children}
|
|
||||||
</a>
|
|
||||||
</NextLink>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Link.propTypes = {
|
|
||||||
className: PropTypes.string,
|
|
||||||
icon: PropTypes.node,
|
|
||||||
children: PropTypes.node,
|
|
||||||
size: PropTypes.oneOf(['large', 'small', 'xsmall']),
|
|
||||||
iconRight: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Link;
|
|
@ -1,42 +0,0 @@
|
|||||||
a.link,
|
|
||||||
a.link:active,
|
|
||||||
a.link:visited {
|
|
||||||
position: relative;
|
|
||||||
color: var(--base900);
|
|
||||||
text-decoration: none;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.link span {
|
|
||||||
border-bottom: 2px solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.link:hover span {
|
|
||||||
border-bottom: 2px solid var(--primary400);
|
|
||||||
}
|
|
||||||
|
|
||||||
a.link.large {
|
|
||||||
font-size: var(--font-size-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
a.link.small {
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
a.link.xsmall {
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
a.link .icon + * {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.link.iconRight .icon {
|
|
||||||
order: 1;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.link.iconRight .icon + * {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import styles from './Menu.module.css';
|
|
||||||
|
|
||||||
function Menu({
|
|
||||||
options = [],
|
|
||||||
selectedOption,
|
|
||||||
className,
|
|
||||||
float,
|
|
||||||
align = 'left',
|
|
||||||
optionClassName,
|
|
||||||
selectedClassName,
|
|
||||||
onSelect = () => {},
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classNames(styles.menu, className, {
|
|
||||||
[styles.float]: float,
|
|
||||||
[styles.top]: float === 'top',
|
|
||||||
[styles.bottom]: float === 'bottom',
|
|
||||||
[styles.left]: align === 'left',
|
|
||||||
[styles.right]: align === 'right',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{options
|
|
||||||
.filter(({ hidden }) => !hidden)
|
|
||||||
.map(option => {
|
|
||||||
const { label, value, className: customClassName, render, divider } = option;
|
|
||||||
|
|
||||||
return render ? (
|
|
||||||
render(option)
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
key={value}
|
|
||||||
className={classNames(styles.option, optionClassName, customClassName, {
|
|
||||||
[selectedClassName]: selectedOption === option,
|
|
||||||
[styles.selected]: selectedOption === option,
|
|
||||||
[styles.divider]: divider,
|
|
||||||
})}
|
|
||||||
onClick={e => onSelect(value, e)}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Menu.propTypes = {
|
|
||||||
options: PropTypes.arrayOf(
|
|
||||||
PropTypes.shape({
|
|
||||||
label: PropTypes.node,
|
|
||||||
value: PropTypes.any,
|
|
||||||
className: PropTypes.string,
|
|
||||||
render: PropTypes.func,
|
|
||||||
divider: PropTypes.bool,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
selectedOption: PropTypes.any,
|
|
||||||
className: PropTypes.string,
|
|
||||||
float: PropTypes.oneOf(['top', 'bottom']),
|
|
||||||
align: PropTypes.oneOf(['left', 'right']),
|
|
||||||
optionClassName: PropTypes.string,
|
|
||||||
selectedClassName: PropTypes.string,
|
|
||||||
onSelect: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Menu;
|
|
@ -1,49 +0,0 @@
|
|||||||
.menu {
|
|
||||||
background: var(--base50);
|
|
||||||
border: 1px solid var(--base500);
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option {
|
|
||||||
background: var(--base50);
|
|
||||||
padding: 4px 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option:hover {
|
|
||||||
background: var(--base100);
|
|
||||||
}
|
|
||||||
|
|
||||||
.float {
|
|
||||||
position: absolute;
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top {
|
|
||||||
bottom: 100%;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom {
|
|
||||||
top: 100%;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left {
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.divider {
|
|
||||||
border-top: 1px solid var(--base300);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
@ -1,87 +0,0 @@
|
|||||||
import { useState, useRef } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import Menu from 'components/common/Menu';
|
|
||||||
import useDocumentClick from 'hooks/useDocumentClick';
|
|
||||||
import styles from './MenuButton.module.css';
|
|
||||||
import { Button } from 'react-basics';
|
|
||||||
|
|
||||||
function MenuButton({
|
|
||||||
children,
|
|
||||||
value,
|
|
||||||
options,
|
|
||||||
buttonClassName,
|
|
||||||
buttonVariant,
|
|
||||||
menuClassName,
|
|
||||||
menuPosition = 'bottom',
|
|
||||||
menuAlign = 'right',
|
|
||||||
onSelect,
|
|
||||||
renderValue,
|
|
||||||
hideLabel,
|
|
||||||
}) {
|
|
||||||
const [showMenu, setShowMenu] = useState(false);
|
|
||||||
const ref = useRef();
|
|
||||||
const selectedOption = options.find(e => e.value === value);
|
|
||||||
|
|
||||||
function handleSelect(value) {
|
|
||||||
onSelect(value);
|
|
||||||
setShowMenu(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleMenu() {
|
|
||||||
setShowMenu(state => !state);
|
|
||||||
}
|
|
||||||
|
|
||||||
useDocumentClick(e => {
|
|
||||||
if (!ref.current?.contains(e.target)) {
|
|
||||||
setShowMenu(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.container} ref={ref}>
|
|
||||||
<Button
|
|
||||||
className={classNames(styles.button, buttonClassName, { [styles.open]: showMenu })}
|
|
||||||
onClick={toggleMenu}
|
|
||||||
variant={buttonVariant}
|
|
||||||
>
|
|
||||||
{!hideLabel && (
|
|
||||||
<div className={styles.text}>{renderValue ? renderValue(selectedOption) : value}</div>
|
|
||||||
)}
|
|
||||||
{children}
|
|
||||||
</Button>
|
|
||||||
{showMenu && (
|
|
||||||
<Menu
|
|
||||||
className={menuClassName}
|
|
||||||
options={options}
|
|
||||||
selectedOption={selectedOption}
|
|
||||||
onSelect={handleSelect}
|
|
||||||
float={menuPosition}
|
|
||||||
align={menuAlign}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuButton.propTypes = {
|
|
||||||
icon: PropTypes.node,
|
|
||||||
value: PropTypes.any,
|
|
||||||
options: PropTypes.arrayOf(
|
|
||||||
PropTypes.shape({
|
|
||||||
label: PropTypes.node,
|
|
||||||
value: PropTypes.any,
|
|
||||||
className: PropTypes.string,
|
|
||||||
render: PropTypes.func,
|
|
||||||
divider: PropTypes.bool,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
buttonClassName: PropTypes.string,
|
|
||||||
menuClassName: PropTypes.string,
|
|
||||||
menuPosition: PropTypes.oneOf(['top', 'bottom']),
|
|
||||||
menuAlign: PropTypes.oneOf(['left', 'right']),
|
|
||||||
onSelect: PropTypes.func,
|
|
||||||
renderValue: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MenuButton;
|
|
@ -1,20 +0,0 @@
|
|||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.open,
|
|
||||||
.open:hover {
|
|
||||||
background: var(--base50);
|
|
||||||
border: 1px solid var(--base500);
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import styles from './NavMenu.module.css';
|
|
||||||
|
|
||||||
function NavMenu({ options = [], className, onSelect = () => {} }) {
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classNames(styles.menu, className)}>
|
|
||||||
{options
|
|
||||||
.filter(({ hidden }) => !hidden)
|
|
||||||
.map(option => {
|
|
||||||
const { label, value, className: customClassName, render } = option;
|
|
||||||
|
|
||||||
return render ? (
|
|
||||||
render(option)
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
key={value}
|
|
||||||
className={classNames(styles.option, customClassName, {
|
|
||||||
[styles.selected]: router.asPath === value,
|
|
||||||
})}
|
|
||||||
onClick={e => onSelect(value, e)}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
NavMenu.propTypes = {
|
|
||||||
options: PropTypes.arrayOf(
|
|
||||||
PropTypes.shape({
|
|
||||||
label: PropTypes.node,
|
|
||||||
value: PropTypes.any,
|
|
||||||
className: PropTypes.string,
|
|
||||||
render: PropTypes.func,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
className: PropTypes.string,
|
|
||||||
onSelect: PropTypes.func,
|
|
||||||
};
|
|
||||||
export default NavMenu;
|
|
@ -1,22 +0,0 @@
|
|||||||
.menu {
|
|
||||||
color: var(--base800);
|
|
||||||
border: 1px solid var(--base500);
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option {
|
|
||||||
padding: 8px 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option:hover {
|
|
||||||
background: var(--base75);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected {
|
|
||||||
color: var(--base900);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
@ -1,46 +1,15 @@
|
|||||||
import { useState, useRef, useEffect } from 'react';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import useSticky from 'hooks/useSticky';
|
||||||
|
|
||||||
export default function StickyHeader({
|
export default function StickyHeader({ className, stickyClassName, stickyStyle, children }) {
|
||||||
className,
|
const { ref, isSticky } = useSticky();
|
||||||
stickyClassName,
|
|
||||||
stickyStyle,
|
|
||||||
children,
|
|
||||||
enabled = true,
|
|
||||||
}) {
|
|
||||||
const [sticky, setSticky] = useState(false);
|
|
||||||
const ref = useRef();
|
|
||||||
const top = useRef(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const checkPosition = () => {
|
|
||||||
if (ref.current) {
|
|
||||||
if (!top.current) {
|
|
||||||
top.current = ref.current.offsetTop + ref.current.offsetHeight;
|
|
||||||
}
|
|
||||||
const state = window.pageYOffset > top.current;
|
|
||||||
if (sticky !== state) {
|
|
||||||
setSticky(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (enabled) {
|
|
||||||
checkPosition();
|
|
||||||
window.addEventListener('scroll', checkPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('scroll', checkPosition);
|
|
||||||
};
|
|
||||||
}, [sticky, enabled]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
data-sticky={sticky}
|
data-sticky={isSticky}
|
||||||
className={classNames(className, { [stickyClassName]: sticky })}
|
className={classNames(className, { [stickyClassName]: isSticky })}
|
||||||
style={sticky ? { ...stickyStyle, width: ref?.current?.clientWidth } : null}
|
style={isSticky ? { ...stickyStyle, width: ref?.current?.clientWidth } : null}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 1fr;
|
grid-template-rows: 1fr;
|
||||||
grid-template-columns: max-content 1fr;
|
grid-template-columns: max-content 1fr;
|
||||||
height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav {
|
.nav {
|
||||||
grid-row: 1 / 3;
|
grid-row: 1 / 3;
|
||||||
|
height: 100vh;
|
||||||
|
position: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
|
@ -3,7 +3,6 @@ import { useRouter } from 'next/router';
|
|||||||
import Script from 'next/script';
|
import Script from 'next/script';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useIntl, defineMessages } from 'react-intl';
|
import { useIntl, defineMessages } from 'react-intl';
|
||||||
import Link from 'components/common/Link';
|
|
||||||
import { CURRENT_VERSION, HOMEPAGE_URL, REPO_URL } from 'lib/constants';
|
import { CURRENT_VERSION, HOMEPAGE_URL, REPO_URL } from 'lib/constants';
|
||||||
import styles from './Footer.module.css';
|
import styles from './Footer.module.css';
|
||||||
|
|
||||||
@ -22,15 +21,15 @@ export default function Footer({ className }) {
|
|||||||
<div>
|
<div>
|
||||||
{formatMessage(messages.poweredBy, {
|
{formatMessage(messages.poweredBy, {
|
||||||
name: (
|
name: (
|
||||||
<Link href={HOMEPAGE_URL}>
|
<a href={HOMEPAGE_URL}>
|
||||||
<b>umami</b>
|
<b>umami</b>
|
||||||
</Link>
|
</a>
|
||||||
),
|
),
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</Column>
|
</Column>
|
||||||
<Column className={styles.version}>
|
<Column className={styles.version}>
|
||||||
<Link href={REPO_URL}>{`v${CURRENT_VERSION}`}</Link>
|
<a href={REPO_URL}>{`v${CURRENT_VERSION}`}</a>
|
||||||
</Column>
|
</Column>
|
||||||
</Row>
|
</Row>
|
||||||
{!pathname.includes('/share/') && <Script src={`/telemetry.js`} />}
|
{!pathname.includes('/share/') && <Script src={`/telemetry.js`} />}
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
import classNames from 'classnames';
|
|
||||||
import styles from './GridLayout.module.css';
|
|
||||||
|
|
||||||
export default function GridLayout({ className, children }) {
|
|
||||||
return <div className={classNames(styles.grid, className)}>{children}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GridRow = ({ className, children }) => {
|
|
||||||
return <div className={classNames(styles.row, className, 'row')}>{children}</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GridColumn = ({ xs, sm, md, lg, xl, className, children }) => {
|
|
||||||
const classes = [];
|
|
||||||
|
|
||||||
classes.push(xs ? `col-${xs}` : 'col-12');
|
|
||||||
|
|
||||||
if (sm) {
|
|
||||||
classes.push(`col-sm-${sm}`);
|
|
||||||
}
|
|
||||||
if (md) {
|
|
||||||
classes.push(`col-md-${md}`);
|
|
||||||
}
|
|
||||||
if (lg) {
|
|
||||||
classes.push(`col-lg-${lg}`);
|
|
||||||
}
|
|
||||||
if (xl) {
|
|
||||||
classes.push(`col-lg-${xl}`);
|
|
||||||
}
|
|
||||||
return <div className={classNames(styles.col, classes, className)}>{children}</div>;
|
|
||||||
};
|
|
@ -1,18 +0,0 @@
|
|||||||
import { Row, cloneChildren } from 'react-basics';
|
|
||||||
import styles from './GridRow.module.css';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
export default function GridRow(props) {
|
|
||||||
const { children, className, ...rowProps } = props;
|
|
||||||
return (
|
|
||||||
<Row {...rowProps} className={className}>
|
|
||||||
{breakpoint =>
|
|
||||||
cloneChildren(children, () => {
|
|
||||||
return {
|
|
||||||
className: classNames(styles.column, styles[breakpoint]),
|
|
||||||
};
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
.column {
|
|
||||||
padding: 20px;
|
|
||||||
border-top: 1px solid var(--base200);
|
|
||||||
border-left: 1px solid var(--base200);
|
|
||||||
}
|
|
||||||
|
|
||||||
.column:first-child {
|
|
||||||
padding-left: 0;
|
|
||||||
border-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.column:last-child {
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.column.xs,
|
|
||||||
.column.sm,
|
|
||||||
.column.md {
|
|
||||||
border-left: 0;
|
|
||||||
border-right: 0;
|
|
||||||
}
|
|
@ -6,10 +6,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.value {
|
.value {
|
||||||
font-size: var(--font-size-2xl);
|
min-height: 36px;
|
||||||
line-height: 40px;
|
font-size: 36px;
|
||||||
min-height: 40px;
|
font-weight: 700;
|
||||||
font-weight: 600;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { Loading, Icons } from 'react-basics';
|
import { Loading, Icon, Text, Button } from 'react-basics';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
import Link from 'next/link';
|
||||||
import firstBy from 'thenby';
|
import firstBy from 'thenby';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Link from 'components/common/Link';
|
|
||||||
import useApi from 'hooks/useApi';
|
import useApi from 'hooks/useApi';
|
||||||
import { percentFilter } from 'lib/filters';
|
import { percentFilter } from 'lib/filters';
|
||||||
import useDateRange from 'hooks/useDateRange';
|
import useDateRange from 'hooks/useDateRange';
|
||||||
@ -11,6 +11,7 @@ import usePageQuery from 'hooks/usePageQuery';
|
|||||||
import ErrorMessage from 'components/common/ErrorMessage';
|
import ErrorMessage from 'components/common/ErrorMessage';
|
||||||
import DataTable from './DataTable';
|
import DataTable from './DataTable';
|
||||||
import { DEFAULT_ANIMATION_DURATION } from 'lib/constants';
|
import { DEFAULT_ANIMATION_DURATION } from 'lib/constants';
|
||||||
|
import Icons from 'components/icons';
|
||||||
import styles from './MetricsTable.module.css';
|
import styles from './MetricsTable.module.css';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
@ -78,14 +79,15 @@ export default function MetricsTable({
|
|||||||
{data && !error && <DataTable {...props} data={filteredData} className={className} />}
|
{data && !error && <DataTable {...props} data={filteredData} className={className} />}
|
||||||
<div className={styles.footer}>
|
<div className={styles.footer}>
|
||||||
{data && !error && limit && (
|
{data && !error && limit && (
|
||||||
<Link
|
<Link href={router.pathname} as={resolve({ view: type })}>
|
||||||
icon={<Icons.ArrowRight />}
|
<a>
|
||||||
href={router.pathname}
|
<Button variant="quiet">
|
||||||
as={resolve({ view: type })}
|
<Text>{formatMessage(messages.more)}</Text>
|
||||||
size="small"
|
<Icon size="sm">
|
||||||
iconRight
|
<Icons.ArrowRight />
|
||||||
>
|
</Icon>
|
||||||
{formatMessage(messages.more)}
|
</Button>
|
||||||
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -39,7 +39,7 @@ export default function WebsiteChart({
|
|||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
|
|
||||||
const { data, isLoading, error } = useQuery(
|
const { data, isLoading, error } = useQuery(
|
||||||
['websites:pageviews', { websiteId, modified, url, referrer, os, browser, device, country }],
|
['websites:pageviews', websiteId, modified, url, referrer, os, browser, device, country],
|
||||||
() =>
|
() =>
|
||||||
get(`/websites/${websiteId}/pageviews`, {
|
get(`/websites/${websiteId}/pageviews`, {
|
||||||
startAt: +startDate,
|
startAt: +startDate,
|
||||||
@ -82,10 +82,6 @@ export default function WebsiteChart({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return <Loading icon="dots" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WebsiteHeader websiteId={websiteId} title={title} domain={domain}>
|
<WebsiteHeader websiteId={websiteId} title={title} domain={domain}>
|
||||||
|
@ -40,7 +40,7 @@ export default function WebsiteDetails({ websiteId }) {
|
|||||||
domain={data?.domain}
|
domain={data?.domain}
|
||||||
onDataLoad={handleDataLoad}
|
onDataLoad={handleDataLoad}
|
||||||
showLink={false}
|
showLink={false}
|
||||||
stickyHeader
|
stickyHeader={true}
|
||||||
/>
|
/>
|
||||||
{!chartLoaded && <Loading icon="dots" />}
|
{!chartLoaded && <Loading icon="dots" />}
|
||||||
{chartLoaded && (
|
{chartLoaded && (
|
||||||
|
@ -9,6 +9,7 @@ import WorldMap from 'components/common/WorldMap';
|
|||||||
import CountriesTable from 'components/metrics/CountriesTable';
|
import CountriesTable from 'components/metrics/CountriesTable';
|
||||||
import EventsTable from 'components/metrics/EventsTable';
|
import EventsTable from 'components/metrics/EventsTable';
|
||||||
import EventsChart from 'components/metrics/EventsChart';
|
import EventsChart from 'components/metrics/EventsChart';
|
||||||
|
import styles from './WebsiteTableView.module.css';
|
||||||
|
|
||||||
export default function WebsiteTableView({ websiteId }) {
|
export default function WebsiteTableView({ websiteId }) {
|
||||||
const [countryData, setCountryData] = useState();
|
const [countryData, setCountryData] = useState();
|
||||||
@ -19,38 +20,38 @@ export default function WebsiteTableView({ websiteId }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Row>
|
<Row className={styles.row}>
|
||||||
<Column variant="two">
|
<Column className={styles.col} variant="two">
|
||||||
<PagesTable {...tableProps} />
|
<PagesTable {...tableProps} />
|
||||||
</Column>
|
</Column>
|
||||||
<Column variant="two">
|
<Column className={styles.col} variant="two">
|
||||||
<ReferrersTable {...tableProps} />
|
<ReferrersTable {...tableProps} />
|
||||||
</Column>
|
</Column>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row className={styles.row}>
|
||||||
<Column variant="three">
|
<Column className={styles.col} variant="three">
|
||||||
<BrowsersTable {...tableProps} />
|
<BrowsersTable {...tableProps} />
|
||||||
</Column>
|
</Column>
|
||||||
<Column variant="three">
|
<Column className={styles.col} variant="three">
|
||||||
<OSTable {...tableProps} />
|
<OSTable {...tableProps} />
|
||||||
</Column>
|
</Column>
|
||||||
<Column variant="three">
|
<Column className={styles.col} variant="three">
|
||||||
<DevicesTable {...tableProps} />
|
<DevicesTable {...tableProps} />
|
||||||
</Column>
|
</Column>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row className={styles.row}>
|
||||||
<Column xs={12} sm={12} md={12} defaultSize={8}>
|
<Column className={styles.col} xs={12} sm={12} md={12} defaultSize={8}>
|
||||||
<WorldMap data={countryData} />
|
<WorldMap data={countryData} />
|
||||||
</Column>
|
</Column>
|
||||||
<Column xs={12} sm={12} md={12} defaultSize={4}>
|
<Column className={styles.col} xs={12} sm={12} md={12} defaultSize={4}>
|
||||||
<CountriesTable {...tableProps} onDataLoad={setCountryData} />
|
<CountriesTable {...tableProps} onDataLoad={setCountryData} />
|
||||||
</Column>
|
</Column>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row className={styles.row}>
|
||||||
<Column xs={12} md={12} lg={4} defaultSize={4}>
|
<Column className={styles.col} xs={12} md={12} lg={4} defaultSize={4}>
|
||||||
<EventsTable {...tableProps} />
|
<EventsTable {...tableProps} />
|
||||||
</Column>
|
</Column>
|
||||||
<Column xs={12} md={12} lg={8} defaultSize={8}>
|
<Column className={styles.col} xs={12} md={12} lg={8} defaultSize={8}>
|
||||||
<EventsChart websiteId={websiteId} />
|
<EventsChart websiteId={websiteId} />
|
||||||
</Column>
|
</Column>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -1,8 +1,3 @@
|
|||||||
.grid {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.col {
|
.col {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
27
hooks/useSticky.js
Normal file
27
hooks/useSticky.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { useState, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
export default function useSticky(defaultSticky = false) {
|
||||||
|
const [isSticky, setIsSticky] = useState(defaultSticky);
|
||||||
|
const ref = useRef(null);
|
||||||
|
const initialTop = useRef(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleScroll = () => {
|
||||||
|
if (window.pageYOffset > initialTop.current) {
|
||||||
|
setIsSticky(true);
|
||||||
|
} else {
|
||||||
|
setIsSticky(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
initialTop.current = ref.current.offsetTop;
|
||||||
|
|
||||||
|
window.addEventListener('scroll', handleScroll);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('scroll', handleScroll);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { ref, isSticky };
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user