diff --git a/components/common/Icon.js b/components/common/Icon.js
deleted file mode 100644
index 362d5376..00000000
--- a/components/common/Icon.js
+++ /dev/null
@@ -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 (
-
- {icon}
-
- );
-}
-
-Icon.propTypes = {
- className: PropTypes.string,
- icon: PropTypes.node.isRequired,
- size: PropTypes.oneOf(['xlarge', 'large', 'medium', 'small', 'xsmall']),
-};
-
-export default Icon;
diff --git a/components/common/Icon.module.css b/components/common/Icon.module.css
deleted file mode 100644
index 5b431668..00000000
--- a/components/common/Icon.module.css
+++ /dev/null
@@ -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;
-}
diff --git a/components/common/Link.js b/components/common/Link.js
deleted file mode 100644
index 3721a9a7..00000000
--- a/components/common/Link.js
+++ /dev/null
@@ -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 (
-
-
- {icon && }
- {children}
-
-
- );
-}
-
-Link.propTypes = {
- className: PropTypes.string,
- icon: PropTypes.node,
- children: PropTypes.node,
- size: PropTypes.oneOf(['large', 'small', 'xsmall']),
- iconRight: PropTypes.bool,
-};
-
-export default Link;
diff --git a/components/common/Link.module.css b/components/common/Link.module.css
deleted file mode 100644
index 0476bd92..00000000
--- a/components/common/Link.module.css
+++ /dev/null
@@ -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;
-}
diff --git a/components/common/Menu.js b/components/common/Menu.js
deleted file mode 100644
index 771ed05d..00000000
--- a/components/common/Menu.js
+++ /dev/null
@@ -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 (
-
- {options
- .filter(({ hidden }) => !hidden)
- .map(option => {
- const { label, value, className: customClassName, render, divider } = option;
-
- return render ? (
- render(option)
- ) : (
-
onSelect(value, e)}
- >
- {label}
-
- );
- })}
-
- );
-}
-
-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;
diff --git a/components/common/Menu.module.css b/components/common/Menu.module.css
deleted file mode 100644
index d9a23371..00000000
--- a/components/common/Menu.module.css
+++ /dev/null
@@ -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;
-}
diff --git a/components/common/MenuButton.js b/components/common/MenuButton.js
deleted file mode 100644
index 69a448d0..00000000
--- a/components/common/MenuButton.js
+++ /dev/null
@@ -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 (
-
-
- {showMenu && (
-
- )}
-
- );
-}
-
-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;
diff --git a/components/common/MenuButton.module.css b/components/common/MenuButton.module.css
deleted file mode 100644
index a59ae562..00000000
--- a/components/common/MenuButton.module.css
+++ /dev/null
@@ -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);
-}
diff --git a/components/common/NavMenu.js b/components/common/NavMenu.js
deleted file mode 100644
index 8994daa0..00000000
--- a/components/common/NavMenu.js
+++ /dev/null
@@ -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 (
-
- {options
- .filter(({ hidden }) => !hidden)
- .map(option => {
- const { label, value, className: customClassName, render } = option;
-
- return render ? (
- render(option)
- ) : (
-
onSelect(value, e)}
- >
- {label}
-
- );
- })}
-
- );
-}
-
-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;
diff --git a/components/common/NavMenu.module.css b/components/common/NavMenu.module.css
deleted file mode 100644
index e0b01945..00000000
--- a/components/common/NavMenu.module.css
+++ /dev/null
@@ -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;
-}
diff --git a/components/helpers/StickyHeader.js b/components/helpers/StickyHeader.js
index 7f97b12d..f46dd3c2 100644
--- a/components/helpers/StickyHeader.js
+++ b/components/helpers/StickyHeader.js
@@ -1,46 +1,15 @@
-import { useState, useRef, useEffect } from 'react';
import classNames from 'classnames';
+import useSticky from 'hooks/useSticky';
-export default function StickyHeader({
- className,
- 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]);
+export default function StickyHeader({ className, stickyClassName, stickyStyle, children }) {
+ const { ref, isSticky } = useSticky();
return (
{children}
diff --git a/components/layout/AppLayout.module.css b/components/layout/AppLayout.module.css
index 71d61ada..7b83a24a 100644
--- a/components/layout/AppLayout.module.css
+++ b/components/layout/AppLayout.module.css
@@ -2,12 +2,12 @@
display: grid;
grid-template-rows: 1fr;
grid-template-columns: max-content 1fr;
- height: 100vh;
- overflow: hidden;
}
.nav {
grid-row: 1 / 3;
+ height: 100vh;
+ position: fixed;
}
.body {
diff --git a/components/layout/Footer.js b/components/layout/Footer.js
index ec1571fb..452e070b 100644
--- a/components/layout/Footer.js
+++ b/components/layout/Footer.js
@@ -3,7 +3,6 @@ import { useRouter } from 'next/router';
import Script from 'next/script';
import classNames from 'classnames';
import { useIntl, defineMessages } from 'react-intl';
-import Link from 'components/common/Link';
import { CURRENT_VERSION, HOMEPAGE_URL, REPO_URL } from 'lib/constants';
import styles from './Footer.module.css';
@@ -22,15 +21,15 @@ export default function Footer({ className }) {
{formatMessage(messages.poweredBy, {
name: (
-
+
umami
-
+
),
})}
- {`v${CURRENT_VERSION}`}
+ {`v${CURRENT_VERSION}`}
{!pathname.includes('/share/') && }
diff --git a/components/layout/GridLayout.js b/components/layout/GridLayout.js
deleted file mode 100644
index e83655ee..00000000
--- a/components/layout/GridLayout.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import classNames from 'classnames';
-import styles from './GridLayout.module.css';
-
-export default function GridLayout({ className, children }) {
- return {children}
;
-}
-
-export const GridRow = ({ className, children }) => {
- return {children}
;
-};
-
-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 {children}
;
-};
diff --git a/components/layout/GridRow.js b/components/layout/GridRow.js
deleted file mode 100644
index 8f840a84..00000000
--- a/components/layout/GridRow.js
+++ /dev/null
@@ -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 (
-
- {breakpoint =>
- cloneChildren(children, () => {
- return {
- className: classNames(styles.column, styles[breakpoint]),
- };
- })
- }
-
- );
-}
diff --git a/components/layout/GridRow.module.css b/components/layout/GridRow.module.css
deleted file mode 100644
index dc1e31f8..00000000
--- a/components/layout/GridRow.module.css
+++ /dev/null
@@ -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;
-}
diff --git a/components/metrics/MetricCard.module.css b/components/metrics/MetricCard.module.css
index c29d4d41..905f4d86 100644
--- a/components/metrics/MetricCard.module.css
+++ b/components/metrics/MetricCard.module.css
@@ -6,10 +6,9 @@
}
.value {
- font-size: var(--font-size-2xl);
- line-height: 40px;
- min-height: 40px;
- font-weight: 600;
+ min-height: 36px;
+ font-size: 36px;
+ font-weight: 700;
white-space: nowrap;
}
diff --git a/components/metrics/MetricsTable.js b/components/metrics/MetricsTable.js
index 08fb21c3..c1c6b126 100644
--- a/components/metrics/MetricsTable.js
+++ b/components/metrics/MetricsTable.js
@@ -1,9 +1,9 @@
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 Link from 'next/link';
import firstBy from 'thenby';
import classNames from 'classnames';
-import Link from 'components/common/Link';
import useApi from 'hooks/useApi';
import { percentFilter } from 'lib/filters';
import useDateRange from 'hooks/useDateRange';
@@ -11,6 +11,7 @@ import usePageQuery from 'hooks/usePageQuery';
import ErrorMessage from 'components/common/ErrorMessage';
import DataTable from './DataTable';
import { DEFAULT_ANIMATION_DURATION } from 'lib/constants';
+import Icons from 'components/icons';
import styles from './MetricsTable.module.css';
const messages = defineMessages({
@@ -78,14 +79,15 @@ export default function MetricsTable({
{data && !error && }
diff --git a/components/metrics/WebsiteChart.js b/components/metrics/WebsiteChart.js
index da034ba9..8b44439f 100644
--- a/components/metrics/WebsiteChart.js
+++ b/components/metrics/WebsiteChart.js
@@ -39,7 +39,7 @@ export default function WebsiteChart({
const { get, useQuery } = useApi();
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`, {
startAt: +startDate,
@@ -82,10 +82,6 @@ export default function WebsiteChart({
}
}
- if (isLoading) {
- return ;
- }
-
return (
<>
diff --git a/components/pages/websites/WebsiteDetails.js b/components/pages/websites/WebsiteDetails.js
index 3e758599..903a482a 100644
--- a/components/pages/websites/WebsiteDetails.js
+++ b/components/pages/websites/WebsiteDetails.js
@@ -40,7 +40,7 @@ export default function WebsiteDetails({ websiteId }) {
domain={data?.domain}
onDataLoad={handleDataLoad}
showLink={false}
- stickyHeader
+ stickyHeader={true}
/>
{!chartLoaded && }
{chartLoaded && (
diff --git a/components/pages/websites/WebsiteTableView.js b/components/pages/websites/WebsiteTableView.js
index b805bc74..372a8f30 100644
--- a/components/pages/websites/WebsiteTableView.js
+++ b/components/pages/websites/WebsiteTableView.js
@@ -9,6 +9,7 @@ import WorldMap from 'components/common/WorldMap';
import CountriesTable from 'components/metrics/CountriesTable';
import EventsTable from 'components/metrics/EventsTable';
import EventsChart from 'components/metrics/EventsChart';
+import styles from './WebsiteTableView.module.css';
export default function WebsiteTableView({ websiteId }) {
const [countryData, setCountryData] = useState();
@@ -19,38 +20,38 @@ export default function WebsiteTableView({ websiteId }) {
return (
<>
-
-
+
+
-
+
-
-
+
+
-
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
diff --git a/components/layout/GridLayout.module.css b/components/pages/websites/WebsiteTableView.module.css
similarity index 90%
rename from components/layout/GridLayout.module.css
rename to components/pages/websites/WebsiteTableView.module.css
index ee7947b6..73c5facb 100644
--- a/components/layout/GridLayout.module.css
+++ b/components/pages/websites/WebsiteTableView.module.css
@@ -1,8 +1,3 @@
-.grid {
- display: flex;
- flex-direction: column;
-}
-
.col {
display: flex;
flex-direction: column;
diff --git a/hooks/useSticky.js b/hooks/useSticky.js
new file mode 100644
index 00000000..13498a20
--- /dev/null
+++ b/hooks/useSticky.js
@@ -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 };
+}