Use css variables.

This commit is contained in:
Mike Cao 2020-08-05 19:04:02 -07:00
parent 5d4ff5cfa4
commit a5930f1772
29 changed files with 131 additions and 85 deletions

View File

@ -3,9 +3,9 @@ import classNames from 'classnames';
import Icon from './Icon'; import Icon from './Icon';
import styles from './Button.module.css'; import styles from './Button.module.css';
export default function Button({ icon, children, className, onClick }) { export default function Button({ icon, type = 'button', children, className, onClick = () => {} }) {
return ( return (
<button type="button" className={classNames(styles.button, className)} onClick={onClick}> <button type={type} className={classNames(styles.button, className)} onClick={onClick}>
{icon && <Icon icon={icon} />} {icon && <Icon icon={icon} />}
{children} {children}
</button> </button>

View File

@ -3,7 +3,7 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background: #f5f5f5; background: #f5f5f5;
padding: 4px 8px; padding: 8px 16px;
border-radius: 4px; border-radius: 4px;
border: 0; border: 0;
outline: none; outline: none;
@ -13,10 +13,3 @@
.button:hover { .button:hover {
background: #eaeaea; background: #eaeaea;
} }
.button svg {
display: block;
width: 16px;
height: 16px;
margin-right: 8px;
}

View File

@ -1,6 +1,6 @@
.dropdown { .dropdown {
position: relative; position: relative;
font-size: 14px; font-size: var(--font-size-small);
min-width: 140px; min-width: 140px;
} }

View File

@ -8,7 +8,7 @@
} }
.title { .title {
font-size: 30px; font-size: var(--font-size-large);
} }
.nav { .nav {
@ -19,7 +19,7 @@
} }
.nav > * { .nav > * {
font-size: 14px; font-size: var(--font-size-normal);
font-weight: 600; font-weight: 600;
margin-left: 40px; margin-left: 40px;
} }

View File

@ -2,6 +2,8 @@ import React, { useState } from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik'; import { Formik, Form, Field, ErrorMessage } from 'formik';
import Router from 'next/router'; import Router from 'next/router';
import { post } from 'lib/web'; import { post } from 'lib/web';
import Button from './Button';
import styles from './Login.module.css';
const validate = ({ username, password }) => { const validate = ({ username, password }) => {
const errors = {}; const errors = {};
@ -39,7 +41,7 @@ export default function Login() {
onSubmit={handleSubmit} onSubmit={handleSubmit}
> >
{() => ( {() => (
<Form> <Form className={styles.form}>
<h3>{message}</h3> <h3>{message}</h3>
<div> <div>
<label htmlFor="username">Username</label> <label htmlFor="username">Username</label>
@ -51,7 +53,7 @@ export default function Login() {
<Field name="password" type="password" /> <Field name="password" type="password" />
<ErrorMessage name="password" /> <ErrorMessage name="password" />
</div> </div>
<button type="submit">Submit</button> <Button type="submit">Submit</Button>
</Form> </Form>
)} )}
</Formik> </Formik>

View File

@ -0,0 +1,6 @@
.form {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}

View File

@ -6,12 +6,12 @@
} }
.value { .value {
font-size: 36px; font-size: var(--font-size-xlarge);
line-height: 40px; line-height: 40px;
min-height: 40px; min-height: 40px;
font-weight: 600; font-weight: 600;
} }
.label { .label {
font-size: 16px; font-size: var(--font-size-normal);
} }

6
components/Page.js Normal file
View File

@ -0,0 +1,6 @@
import React from 'react';
import styles from './Page.module.css';
export default function Page({ children }) {
return <div className={styles.container}>{children}</div>;
}

View File

@ -0,0 +1,8 @@
.container {
flex: 1;
display: flex;
flex-direction: column;
padding: 0 30px;
background: var(--gray50);
height: 100%;
}

View File

@ -12,12 +12,12 @@
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
color: #fff; color: var(--gray50);
text-align: center; text-align: center;
} }
.title { .title {
font-size: 12px; font-size: var(--font-size-xsmall);
font-weight: 600; font-weight: 600;
} }
@ -25,7 +25,7 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
font-size: 14px; font-size: var(--font-size-small);
font-weight: 600; font-weight: 600;
} }
@ -34,7 +34,7 @@
overflow: hidden; overflow: hidden;
border-radius: 100%; border-radius: 100%;
margin-right: 8px; margin-right: 8px;
background: #fff; background: var(--gray50);
} }
.color { .color {

View File

@ -20,7 +20,7 @@ export default function QuickButtons({ value, onChange }) {
{Object.keys(options).map(key => ( {Object.keys(options).map(key => (
<Button <Button
key={key} key={key}
className={classNames({ [styles.active]: value === key })} className={classNames(styles.button, { [styles.active]: value === key })}
onClick={() => handleClick(key)} onClick={() => handleClick(key)}
> >
{options[key]} {options[key]}

View File

@ -10,6 +10,11 @@
margin-left: 10px; margin-left: 10px;
} }
.button {
font-size: var(--font-size-xsmall);
padding: 4px 8px;
}
.active { .active {
font-weight: 600; font-weight: 600;
} }

View File

@ -1,7 +1,7 @@
.container { .container {
position: relative; position: relative;
min-height: 430px; min-height: 430px;
font-size: 14px; font-size: var(--font-size-small);
padding: 20px 0; padding: 20px 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -15,11 +15,11 @@
.title { .title {
flex: 1; flex: 1;
font-weight: 600; font-weight: 600;
font-size: 16px; font-size: var(--font-size-normal);
} }
.heading { .heading {
font-size: 14px; font-size: var(--font-size-small);
text-align: center; text-align: center;
width: 100px; width: 100px;
} }
@ -61,7 +61,7 @@
position: relative; position: relative;
width: 50px; width: 50px;
color: #6e6e6e; color: #6e6e6e;
border-left: 1px solid #8e8e8e; border-left: 1px solid var(--gray600);
padding-left: 10px; padding-left: 10px;
z-index: 1; z-index: 1;
} }
@ -72,7 +72,7 @@
left: 0; left: 0;
height: 30px; height: 30px;
opacity: 0.1; opacity: 0.1;
background: #2680eb; background: var(--primary400);
z-index: -1; z-index: -1;
} }
@ -83,9 +83,15 @@
.body:empty:before { .body:empty:before {
content: 'No data available'; content: 'No data available';
color: #b3b3b3; color: var(--gray500);
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
} }
@media only screen and (max-width: 992px) {
.container {
min-height: auto;
}
}

View File

@ -1,9 +1,10 @@
import React from 'react'; import React from 'react';
import Page from './Page';
export default function Settings() { export default function Settings() {
return ( return (
<div> <Page>
<h2>Settings</h2> <h2>Settings</h2>
</div> </Page>
); );
} }

View File

@ -41,7 +41,7 @@ export default function StickyHeader({
ref={ref} ref={ref}
data-sticky={sticky} data-sticky={sticky}
className={classNames(className, { [stickyClassName]: sticky })} className={classNames(className, { [stickyClassName]: sticky })}
{...(sticky && { style: stickyStyle })} style={sticky ? { ...stickyStyle, width: ref?.current?.clientWidth } : null}
> >
{children} {children}
</div> </div>

View File

@ -20,7 +20,6 @@ export default function WebsiteChart({
const [data, setData] = useState(); const [data, setData] = useState();
const [dateRange, setDateRange] = useState(getDateRange(defaultDateRange)); const [dateRange, setDateRange] = useState(getDateRange(defaultDateRange));
const { startDate, endDate, unit, value } = dateRange; const { startDate, endDate, unit, value } = dateRange;
const container = useRef();
const [pageviews, uniques] = useMemo(() => { const [pageviews, uniques] = useMemo(() => {
if (data) { if (data) {
@ -54,11 +53,10 @@ export default function WebsiteChart({
}, [websiteId, startDate, endDate, unit]); }, [websiteId, startDate, endDate, unit]);
return ( return (
<div ref={container}> <>
<StickyHeader <StickyHeader
className={classNames(styles.header, 'row')} className={classNames(styles.header, 'row')}
stickyClassName={styles.sticky} stickyClassName={styles.sticky}
stickyStyle={{ width: container?.current?.clientWidth }}
enabled={stickyHeader} enabled={stickyHeader}
> >
<MetricsBar <MetricsBar
@ -87,6 +85,6 @@ export default function WebsiteChart({
)} )}
</CheckVisible> </CheckVisible>
</div> </div>
</div> </>
); );
} }

View File

@ -4,7 +4,7 @@
} }
.title { .title {
font-size: 24px; font-size: var(--font-size-large);
line-height: 60px; line-height: 60px;
font-weight: 600; font-weight: 600;
} }
@ -20,7 +20,7 @@
position: fixed; position: fixed;
top: 0; top: 0;
margin: auto; margin: auto;
background: #fff; background: var(--gray50);
border-bottom: 1px solid #e1e1e1; border-bottom: 1px solid var(--gray300);
z-index: 2; z-index: 2;
} }

View File

@ -3,6 +3,7 @@ import classNames from 'classnames';
import WebsiteChart from './WebsiteChart'; import WebsiteChart from './WebsiteChart';
import RankingsChart from './RankingsChart'; import RankingsChart from './RankingsChart';
import WorldMap from './WorldMap'; import WorldMap from './WorldMap';
import Page from './Page';
import { getDateRange } from 'lib/date'; import { getDateRange } from 'lib/date';
import { get } from 'lib/web'; import { get } from 'lib/web';
import { browserFilter, urlFilter, refFilter, deviceFilter, countryFilter } from 'lib/filters'; import { browserFilter, urlFilter, refFilter, deviceFilter, countryFilter } from 'lib/filters';
@ -41,7 +42,7 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
} }
return ( return (
<div className={styles.container}> <Page>
<div className="row"> <div className="row">
<div className={classNames(styles.chart, 'col')}> <div className={classNames(styles.chart, 'col')}>
<h2>{data.label}</h2> <h2>{data.label}</h2>
@ -132,6 +133,6 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
</div> </div>
</> </>
)} )}
</div> </Page>
); );
} }

View File

@ -1,19 +1,14 @@
.container {
background: #fff;
padding: 0 30px;
}
.chart { .chart {
margin-bottom: 30px; margin-bottom: 30px;
} }
.row { .row {
border-top: 1px solid #e1e1e1; border-top: 1px solid var(--gray300);
min-height: 430px; min-height: 430px;
} }
.row > [class*='col-'] { .row > [class*='col-'] {
border-left: 1px solid #e1e1e1; border-left: 1px solid var(--gray300);
padding: 0 20px; padding: 0 20px;
} }
@ -26,13 +21,13 @@
padding-right: 0; padding-right: 0;
} }
@media only screen and (max-width: 1000px) { @media only screen and (max-width: 992px) {
.row { .row {
border: 0; border: 0;
} }
.row > [class*='col-'] { .row > [class*='col-'] {
border-top: 1px solid #e1e1e1; border-top: 1px solid var(--gray300);
border-left: 0; border-left: 0;
padding: 0; padding: 0;
} }

View File

@ -1,6 +1,7 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import Link from './Link'; import Link from './Link';
import WebsiteChart from './WebsiteChart'; import WebsiteChart from './WebsiteChart';
import Page from './Page';
import Icon from './Icon'; import Icon from './Icon';
import { get } from 'lib/web'; import { get } from 'lib/web';
import Arrow from 'assets/arrow-right.svg'; import Arrow from 'assets/arrow-right.svg';
@ -18,7 +19,7 @@ export default function WebsiteList() {
}, []); }, []);
return ( return (
<div className={styles.container}> <Page>
{data && {data &&
data.websites.map(({ website_id, label }) => ( data.websites.map(({ website_id, label }) => (
<div key={website_id} className={styles.website}> <div key={website_id} className={styles.website}>
@ -43,6 +44,6 @@ export default function WebsiteList() {
<WebsiteChart key={website_id} title={label} websiteId={website_id} /> <WebsiteChart key={website_id} title={label} websiteId={website_id} />
</div> </div>
))} ))}
</div> </Page>
); );
} }

View File

@ -1,11 +1,6 @@
.container {
background: #fff;
padding: 0 30px;
}
.website { .website {
padding-bottom: 30px; padding-bottom: 30px;
border-bottom: 1px solid #e1e1e1; border-bottom: 1px solid var(--gray300);
margin-bottom: 30px; margin-bottom: 30px;
} }
@ -21,7 +16,7 @@
} }
.title { .title {
color: #2c2c2c !important; color: var(--gray900) !important;
} }
.details { .details {

View File

@ -12,11 +12,11 @@ export async function fetchUser() {
return await res.json(); return await res.json();
} }
export default function useUser() { export default function useRequireLogin() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const storeUser = useSelector(state => state.user); const storeUser = useSelector(state => state.user);
const [loading, setLoading] = useState(!storeUser); const [loading, setLoading] = useState(!storeUser);
const [user, setUser] = useState(storeUser || null); const [user, setUser] = useState(storeUser);
useEffect(() => { useEffect(() => {
if (!loading && user) { if (!loading && user) {
@ -31,7 +31,7 @@ export default function useUser() {
return; return;
} }
await dispatch(updateUser({ user: user })); await dispatch(updateUser({ user }));
setUser(user); setUser(user);
setLoading(false); setLoading(false);

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { useStore } from 'redux/store'; import { useStore } from 'redux/store';
import 'styles/variables.css';
import 'styles/bootstrap-grid.css'; import 'styles/bootstrap-grid.css';
import 'styles/index.css'; import 'styles/index.css';

View File

@ -1,10 +1,10 @@
import React from 'react'; import React from 'react';
import Layout from 'components/Layout'; import Layout from 'components/Layout';
import WebsiteList from 'components/WebsiteList'; import WebsiteList from 'components/WebsiteList';
import useUser from 'hooks/useUser'; import useRequireLogin from 'hooks/useRequireLogin';
export default function HomePage() { export default function HomePage() {
const { loading } = useUser(); const { loading } = useRequireLogin();
if (loading) { if (loading) {
return null; return null;

View File

@ -1,17 +1,11 @@
import React from 'react'; import React from 'react';
import Link from 'next/link';
import Layout from 'components/Layout'; import Layout from 'components/Layout';
import Login from 'components/Login'; import Login from 'components/Login';
export default function LoginPage() { export default function LoginPage() {
return ( return (
<Layout title="Login"> <Layout title="login">
<Login /> <Login />
<p>
<Link href="/test">
<a>Test page 🡒</a>
</Link>
</p>
</Layout> </Layout>
); );
} }

View File

@ -1,10 +1,10 @@
import React from 'react'; import React from 'react';
import Layout from 'components/Layout'; import Layout from 'components/Layout';
import Settings from 'components/Settings'; import Settings from 'components/Settings';
import useUser from 'hooks/useUser'; import useRequireLogin from 'hooks/useRequireLogin';
export default function SettingsPage() { export default function SettingsPage() {
const { loading } = useUser(); const { loading } = useRequireLogin();
if (loading) { if (loading) {
return null; return null;

View File

@ -2,10 +2,10 @@ import React from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Layout from 'components/Layout'; import Layout from 'components/Layout';
import WebsiteDetails from 'components/WebsiteDetails'; import WebsiteDetails from 'components/WebsiteDetails';
import useUser from 'hooks/useUser'; import useRequireLogin from 'hooks/useRequireLogin';
export default function DetailsPage() { export default function DetailsPage() {
const { loading } = useUser(); const { loading } = useRequireLogin();
const router = useRouter(); const router = useRouter();
const { id } = router.query; const { id } = router.query;

View File

@ -1,7 +1,7 @@
html, html,
body { body {
font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif; font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
font-size: 16px; font-size: var(--font-size-normal);
font-weight: 400; font-weight: 400;
line-height: 1.8; line-height: 1.8;
padding: 0; padding: 0;
@ -9,8 +9,8 @@ body {
width: 100%; width: 100%;
height: 100%; height: 100%;
box-sizing: border-box; box-sizing: border-box;
color: #2c2c2c; color: var(--gray900);
background: #fafafa; background: var(--gray75);
} }
*, *,
@ -19,6 +19,10 @@ body {
box-sizing: inherit; box-sizing: inherit;
} }
h2 {
font-weight: 400;
}
#__next { #__next {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -35,12 +39,7 @@ select {
a, a,
a:active, a:active,
a:visited { a:visited {
color: #2680eb; color: var(--primary400);
}
header a {
color: #2c2c2c !important;
text-decoration: none;
} }
form label { form label {
@ -48,13 +47,19 @@ form label {
min-width: 100px; min-width: 100px;
} }
form input { form input,
form textarea {
padding: 4px 8px;
margin-right: 10px; margin-right: 10px;
margin-bottom: 20px;
border: 1px solid var(--gray500);
border-radius: 4px;
outline: none;
} }
select { select {
padding: 4px 8px; padding: 4px 8px;
border: 1px solid #b3b3b3; border: 1px solid var(--gray500);
border-radius: 4px; border-radius: 4px;
} }

29
styles/variables.css Normal file
View File

@ -0,0 +1,29 @@
:root {
--gray50: #ffffff;
--gray75: #fafafa;
--gray100: #f5f5f5;
--gray200: #eaeaea;
--gray300: #e1e1e1;
--gray400: #cacaca;
--gray500: #b3b3b3;
--gray600: #8e8e8e;
--gray6700: #6e6e6e;
--gray800: #4b4b4b;
--gray900: #2c2c2c;
--primary400: #2680eb;
--primary500: #1473e6;
--primary600: #0d66d0;
--primary700: #095aba;
--font-size-xlarge: 36px;
--font-size-large: 24px;
--font-size-normal: 16px;
--font-size-small: 14px;
--font-size-xsmall: 12px;
--grid-size-small: 576px;
--grid-size-medium: 768px;
--grid-size-large: 992px;
--grid-size-xlarge: 1140px;
}