Website edit functionality.

This commit is contained in:
Mike Cao 2020-08-07 02:27:12 -07:00
parent 30b87bc4c4
commit 00e232fee8
63 changed files with 301 additions and 94 deletions

View file

@ -0,0 +1,24 @@
import React from 'react';
import { getDateRange } from 'lib/date';
import DropDown from './DropDown';
const filterOptions = [
{ label: 'Last 24 hours', value: '24hour' },
{ label: 'Last 7 days', value: '7day' },
{ label: 'Last 30 days', value: '30day' },
{ label: 'Last 90 days', value: '90day' },
{ label: 'Today', value: '1day' },
{ label: 'This week', value: '1week' },
{ label: 'This month', value: '1month' },
{ label: 'This year', value: '1year' },
];
export default function DateFilter({ value, onChange, className }) {
function handleChange(value) {
onChange(getDateRange(value));
}
return (
<DropDown className={className} value={value} options={filterOptions} onChange={handleChange} />
);
}

View file

@ -0,0 +1,44 @@
import React, { useState, useRef } from 'react';
import classNames from 'classnames';
import Menu from '../interface/Menu';
import useDocumentClick from 'hooks/useDocumentClick';
import Chevron from 'assets/chevron-down.svg';
import styles from './Dropdown.module.css';
import Icon from '../interface/Icon';
export default function DropDown({
value,
className,
menuClassName,
options = [],
onChange = () => {},
}) {
const [showMenu, setShowMenu] = useState(false);
const ref = useRef();
function handleShowMenu() {
setShowMenu(state => !state);
}
function handleSelect(value, e) {
e.stopPropagation();
setShowMenu(false);
onChange(value);
}
useDocumentClick(e => {
if (!ref.current.contains(e.target)) {
setShowMenu(false);
}
});
return (
<div ref={ref} className={classNames(styles.dropdown, className)} onClick={handleShowMenu}>
<div className={styles.value}>
{options.find(e => e.value === value)?.label}
<Icon icon={<Chevron />} size="S" className={styles.icon} />
</div>
{showMenu && <Menu className={menuClassName} options={options} onSelect={handleSelect} />}
</div>
);
}

View file

@ -0,0 +1,22 @@
.dropdown {
position: relative;
font-size: var(--font-size-small);
min-width: 140px;
}
.value {
white-space: nowrap;
position: relative;
padding: 4px 32px 4px 16px;
border: 1px solid var(--gray500);
border-radius: 4px;
cursor: pointer;
}
.icon {
position: absolute;
top: 0;
bottom: 0;
right: 12px;
margin: auto;
}

View file

@ -0,0 +1,16 @@
import React from 'react';
import { useSpring, animated } from 'react-spring';
import styles from './Modal.module.css';
export default function Modal({ title, children }) {
const props = useSpring({ opacity: 1, from: { opacity: 0 } });
return (
<animated.div className={styles.modal} style={props}>
<div className={styles.content}>
{title && <div className={styles.header}>{title}</div>}
<div className={styles.body}>{children}</div>
</div>
</animated.div>
);
}

View file

@ -0,0 +1,45 @@
.modal {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
}
.modal:before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
background: var(--gray900);
opacity: 0.1;
}
.content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: var(--gray50);
min-width: 200px;
min-height: 100px;
z-index: 1;
border: 1px solid var(--gray300);
padding: 30px;
border-radius: 4px;
overflow: hidden;
}
.header {
font-weight: 600;
margin-bottom: 20px;
}
.body {
display: flex;
flex-direction: column;
}

View file

@ -0,0 +1,36 @@
import React from 'react';
import classNames from 'classnames';
import styles from './Table.module.css';
export default function Table({ columns, rows }) {
return (
<div className={styles.table}>
<div className={styles.header}>
{columns.map(({ key, label, header }) => (
<div
key={key}
className={classNames(styles.head, header?.className)}
style={header?.style}
>
{label}
</div>
))}
</div>
<div className={styles.body}>
{rows.map((row, rowIndex) => (
<div className={styles.row} key={rowIndex}>
{columns.map(({ key, render, cell }) => (
<div
key={`${rowIndex}${key}`}
className={classNames(styles.cell, cell?.className)}
style={cell?.style}
>
{render ? render(row) : row[key]}
</div>
))}
</div>
))}
</div>
</div>
);
}

View file

@ -0,0 +1,32 @@
.table {
display: flex;
flex-direction: column;
}
.header {
display: flex;
}
.head {
font-size: var(--font-size-small);
font-weight: 600;
line-height: 40px;
flex: 1;
}
.body {
display: flex;
flex-direction: column;
}
.row {
display: flex;
border-bottom: 1px solid var(--gray300);
padding: 10px 0;
}
.cell {
display: flex;
align-items: flex-start;
flex: 1;
}

View file

@ -0,0 +1,71 @@
import React, { useState } from 'react';
import ReactTooltip from 'react-tooltip';
import classNames from 'classnames';
import tinycolor from 'tinycolor2';
import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps';
import styles from './WorldMap.module.css';
const geoUrl = '/world-110m.json';
export default function WorldMap({
data,
className,
baseColor = '#e9f3fd',
fillColor = '#f5f5f5',
strokeColor = '#2680eb',
hoverColor = '#2680eb',
}) {
const [tooltip, setTooltip] = useState();
function getFillColor(code) {
if (code === 'AQ') return '#ffffff';
const country = data?.find(({ x }) => x === code);
return country ? tinycolor(baseColor).darken(country.z) : fillColor;
}
function getStrokeColor(code) {
return code === 'AQ' ? '#ffffff' : strokeColor;
}
function getHoverColor(code) {
return code === 'AQ' ? '#ffffff' : hoverColor;
}
function handleHover({ ISO_A2: code, NAME: name }) {
const country = data?.find(({ x }) => x === code);
setTooltip(`${name}: ${country?.y || 0} visitors`);
}
return (
<div className={classNames(styles.container, className)}>
<ComposableMap data-tip="" projection="geoMercator">
<ZoomableGroup zoom={0.8} minZoom={0.7} center={[0, 40]}>
<Geographies geography={geoUrl}>
{({ geographies }) => {
return geographies.map(geo => {
const code = geo.properties.ISO_A2;
return (
<Geography
key={geo.rsmKey}
geography={geo}
fill={getFillColor(code)}
stroke={getStrokeColor(code)}
style={{
default: { outline: 'none' },
hover: { outline: 'none', fill: getHoverColor(code) },
pressed: { outline: 'none' },
}}
onMouseOver={() => handleHover(geo.properties)}
onMouseOut={() => setTooltip(null)}
/>
);
});
}}
</Geographies>
</ZoomableGroup>
</ComposableMap>
<ReactTooltip>{tooltip}</ReactTooltip>
</div>
);
}

View file

@ -0,0 +1,5 @@
.container {
overflow: hidden;
position: relative;
background: #fff;
}