Merge pull request #481 from gnarlex/proptyping-common

Add prop-types for "common" components
This commit is contained in:
Mike Cao 2021-02-16 19:04:27 -08:00 committed by GitHub
commit 826facf56a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 312 additions and 36 deletions

View File

@ -1,10 +1,11 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import ReactTooltip from 'react-tooltip'; import ReactTooltip from 'react-tooltip';
import classNames from 'classnames'; 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({ function Button({
type = 'button', type = 'button',
icon, icon,
size, size,
@ -43,3 +44,19 @@ export default function Button({
</button> </button>
); );
} }
Button.propTypes = {
type: PropTypes.oneOf(['button', 'submit', 'reset']),
icon: PropTypes.node,
size: PropTypes.oneOf(['xlarge', 'large', 'medium', 'small', 'xsmall']),
variant: PropTypes.oneOf(['action', 'danger', 'light']),
children: PropTypes.node,
className: PropTypes.string,
tooltip: PropTypes.node,
tooltipId: PropTypes.string,
disabled: PropTypes.bool,
iconRight: PropTypes.bool,
onClick: PropTypes.func,
};
export default Button;

View File

@ -1,16 +1,10 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import Button from './Button'; import Button from './Button';
import styles from './ButtonGroup.module.css'; import styles from './ButtonGroup.module.css';
export default function ButtonGroup({ function ButtonGroup({ items = [], selectedItem, className, size, icon, onClick = () => {} }) {
items = [],
selectedItem,
className,
size,
icon,
onClick = () => {},
}) {
return ( return (
<div className={classNames(styles.group, className)}> <div className={classNames(styles.group, className)}>
{items.map(item => { {items.map(item => {
@ -30,3 +24,19 @@ export default function ButtonGroup({
</div> </div>
); );
} }
ButtonGroup.propTypes = {
items: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.node,
value: PropTypes.any.isRequired,
}),
),
selectedItem: PropTypes.any,
className: PropTypes.string,
size: PropTypes.oneOf(['xlarge', 'large', 'medium', 'small', 'xsmall']),
icon: PropTypes.node,
onClick: PropTypes.func,
};
export default ButtonGroup;

View File

@ -1,9 +1,10 @@
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import PropTypes from 'prop-types';
import Icon from 'components/common/Icon'; import Icon from 'components/common/Icon';
import Check from 'assets/check.svg'; import Check from 'assets/check.svg';
import styles from './Checkbox.module.css'; import styles from './Checkbox.module.css';
export default function Checkbox({ name, value, label, onChange }) { function Checkbox({ name, value, label, onChange }) {
const ref = useRef(); const ref = useRef();
return ( return (
@ -25,3 +26,12 @@ export default function Checkbox({ name, value, label, onChange }) {
</div> </div>
); );
} }
Checkbox.propTypes = {
name: PropTypes.string,
value: PropTypes.any,
label: PropTypes.node,
onChange: PropTypes.func,
};
export default Checkbox;

View File

@ -1,4 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import PropTypes from 'prop-types';
import Button from './Button'; import Button from './Button';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
@ -6,7 +7,7 @@ const defaultText = (
<FormattedMessage id="label.copy-to-clipboard" defaultMessage="Copy to clipboard" /> <FormattedMessage id="label.copy-to-clipboard" defaultMessage="Copy to clipboard" />
); );
export default function CopyButton({ element, ...props }) { function CopyButton({ element, ...props }) {
const [text, setText] = useState(defaultText); const [text, setText] = useState(defaultText);
function handleClick() { function handleClick() {
@ -24,3 +25,13 @@ export default function CopyButton({ element, ...props }) {
</Button> </Button>
); );
} }
CopyButton.propTypes = {
element: PropTypes.shape({
current: PropTypes.shape({
select: PropTypes.func.isRequired,
}),
}),
};
export default CopyButton;

View File

@ -1,4 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { endOfYear, isSameDay } from 'date-fns'; import { endOfYear, isSameDay } from 'date-fns';
import Modal from './Modal'; import Modal from './Modal';
@ -54,7 +55,7 @@ const filterOptions = [
}, },
]; ];
export default function DateFilter({ value, startDate, endDate, onChange, className }) { function DateFilter({ value, startDate, endDate, onChange, className }) {
const [showPicker, setShowPicker] = useState(false); const [showPicker, setShowPicker] = useState(false);
const displayValue = const displayValue =
value === 'custom' ? ( value === 'custom' ? (
@ -117,3 +118,13 @@ const CustomRange = ({ startDate, endDate, onClick }) => {
</> </>
); );
}; };
DateFilter.propTypes = {
value: PropTypes.string,
startDate: PropTypes.instanceOf(Date),
endDate: PropTypes.instanceOf(Date),
onChange: PropTypes.func,
className: PropTypes.string,
};
export default DateFilter;

View File

@ -1,8 +1,9 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import styles from './Dot.module.css'; import styles from './Dot.module.css';
export default function Dot({ color, size, className }) { function Dot({ color, size, className }) {
return ( return (
<div className={styles.wrapper}> <div className={styles.wrapper}>
<div <div
@ -15,3 +16,11 @@ export default function Dot({ color, size, className }) {
</div> </div>
); );
} }
Dot.propTypes = {
color: PropTypes.string,
size: PropTypes.oneOf(['small', 'large']),
className: PropTypes.string,
};
export default Dot;

View File

@ -1,4 +1,5 @@
import React, { useState, useRef } from 'react'; import React, { useState, useRef } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import Menu from './Menu'; import Menu from './Menu';
import useDocumentClick from 'hooks/useDocumentClick'; import useDocumentClick from 'hooks/useDocumentClick';
@ -6,13 +7,7 @@ import Chevron from 'assets/chevron-down.svg';
import styles from './Dropdown.module.css'; import styles from './Dropdown.module.css';
import Icon from './Icon'; import Icon from './Icon';
export default function DropDown({ function DropDown({ value, className, menuClassName, options = [], onChange = () => {} }) {
value,
className,
menuClassName,
options = [],
onChange = () => {},
}) {
const [showMenu, setShowMenu] = useState(false); const [showMenu, setShowMenu] = useState(false);
const ref = useRef(); const ref = useRef();
const selectedOption = options.find(e => e.value === value); const selectedOption = options.find(e => e.value === value);
@ -52,3 +47,18 @@ export default function DropDown({
</div> </div>
); );
} }
DropDown.propTypes = {
value: PropTypes.any,
className: PropTypes.string,
menuClassName: PropTypes.string,
options: PropTypes.arrayOf(
PropTypes.shape({
value: PropTypes.any.isRequired,
label: PropTypes.node,
}),
),
onChange: PropTypes.func,
};
export default DropDown;

View File

@ -1,9 +1,10 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import Icon from 'components/common/Icon'; import Icon from 'components/common/Icon';
import Logo from 'assets/logo.svg'; import Logo from 'assets/logo.svg';
import styles from './EmptyPlaceholder.module.css'; import styles from './EmptyPlaceholder.module.css';
export default function EmptyPlaceholder({ msg, children }) { function EmptyPlaceholder({ msg, children }) {
return ( return (
<div className={styles.placeholder}> <div className={styles.placeholder}>
<Icon className={styles.icon} icon={<Logo />} size="xlarge" /> <Icon className={styles.icon} icon={<Logo />} size="xlarge" />
@ -12,3 +13,10 @@ export default function EmptyPlaceholder({ msg, children }) {
</div> </div>
); );
} }
EmptyPlaceholder.propTypes = {
msg: PropTypes.node,
children: PropTypes.node,
};
export default EmptyPlaceholder;

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import styles from './Favicon.module.css'; import styles from './Favicon.module.css';
function getHostName(url) { function getHostName(url) {
@ -6,7 +7,7 @@ function getHostName(url) {
return match && match.length > 1 ? match[1] : null; return match && match.length > 1 ? match[1] : null;
} }
export default function Favicon({ domain, ...props }) { function Favicon({ domain, ...props }) {
const hostName = domain ? getHostName(domain) : null; const hostName = domain ? getHostName(domain) : null;
return hostName ? ( return hostName ? (
@ -19,3 +20,9 @@ export default function Favicon({ domain, ...props }) {
/> />
) : null; ) : null;
} }
Favicon.propTypes = {
domain: PropTypes.string,
};
export default Favicon;

View File

@ -1,11 +1,25 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import ButtonLayout from 'components/layout/ButtonLayout'; import ButtonLayout from 'components/layout/ButtonLayout';
import ButtonGroup from './ButtonGroup'; import ButtonGroup from './ButtonGroup';
export default function FilterButtons({ buttons, selected, onClick }) { function FilterButtons({ buttons, selected, onClick }) {
return ( return (
<ButtonLayout> <ButtonLayout>
<ButtonGroup size="xsmall" items={buttons} selectedItem={selected} onClick={onClick} /> <ButtonGroup size="xsmall" items={buttons} selectedItem={selected} onClick={onClick} />
</ButtonLayout> </ButtonLayout>
); );
} }
FilterButtons.propTypes = {
buttons: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.node,
value: PropTypes.any.isRequired,
}),
),
selected: PropTypes.any,
onClick: PropTypes.func,
};
export default FilterButtons;

View File

@ -1,8 +1,9 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import styles from './Icon.module.css'; import styles from './Icon.module.css';
export default function Icon({ icon, className, size = 'medium', ...props }) { function Icon({ icon, className, size = 'medium', ...props }) {
return ( return (
<div <div
className={classNames(styles.icon, className, { className={classNames(styles.icon, className, {
@ -18,3 +19,11 @@ export default function Icon({ icon, className, size = 'medium', ...props }) {
</div> </div>
); );
} }
Icon.propTypes = {
className: PropTypes.string,
icon: PropTypes.node.isRequired,
size: PropTypes.oneOf(['xlarge', 'large', 'medium', 'small', 'xsmall']),
};
export default Icon;

View File

@ -1,10 +1,11 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import NextLink from 'next/link'; import NextLink from 'next/link';
import Icon from './Icon'; import Icon from './Icon';
import styles from './Link.module.css'; import styles from './Link.module.css';
export default function Link({ className, icon, children, size, iconRight, ...props }) { function Link({ className, icon, children, size, iconRight, ...props }) {
return ( return (
<NextLink {...props}> <NextLink {...props}>
<a <a
@ -21,3 +22,13 @@ export default function Link({ className, icon, children, size, iconRight, ...pr
</NextLink> </NextLink>
); );
} }
Link.propTypes = {
className: PropTypes.string,
icon: PropTypes.node,
children: PropTypes.node,
size: PropTypes.oneOf(['large', 'small', 'xsmall']),
iconRight: PropTypes.bool,
};
export default Link;

View File

@ -1,8 +1,9 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import styles from './Loading.module.css'; import styles from './Loading.module.css';
export default function Loading({ className }) { function Loading({ className }) {
return ( return (
<div className={classNames(styles.loading, className)}> <div className={classNames(styles.loading, className)}>
<div /> <div />
@ -11,3 +12,9 @@ export default function Loading({ className }) {
</div> </div>
); );
} }
Loading.propTypes = {
className: PropTypes.string,
};
export default Loading;

View File

@ -1,8 +1,9 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import styles from './Menu.module.css'; import styles from './Menu.module.css';
export default function Menu({ function Menu({
options = [], options = [],
selectedOption, selectedOption,
className, className,
@ -46,3 +47,24 @@ export default function Menu({
</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;

View File

@ -1,11 +1,12 @@
import React, { useState, useRef } from 'react'; import React, { useState, useRef } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import Menu from 'components/common/Menu'; import Menu from 'components/common/Menu';
import Button from 'components/common/Button'; import Button from 'components/common/Button';
import useDocumentClick from 'hooks/useDocumentClick'; import useDocumentClick from 'hooks/useDocumentClick';
import styles from './MenuButton.module.css'; import styles from './MenuButton.module.css';
export default function MenuButton({ function MenuButton({
icon, icon,
value, value,
options, options,
@ -58,3 +59,25 @@ export default function MenuButton({
</div> </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;

View File

@ -1,9 +1,10 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { useSpring, animated } from 'react-spring'; import { useSpring, animated } from 'react-spring';
import styles from './Modal.module.css'; import styles from './Modal.module.css';
export default function Modal({ title, children }) { function Modal({ title, children }) {
const props = useSpring({ opacity: 1, from: { opacity: 0 } }); const props = useSpring({ opacity: 1, from: { opacity: 0 } });
return ReactDOM.createPortal( return ReactDOM.createPortal(
@ -16,3 +17,10 @@ export default function Modal({ title, children }) {
document.getElementById('__modals'), document.getElementById('__modals'),
); );
} }
Modal.propTypes = {
title: PropTypes.node,
children: PropTypes.node,
};
export default Modal;

View File

@ -1,9 +1,10 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import classNames from 'classnames'; import classNames from 'classnames';
import styles from './NavMenu.module.css'; import styles from './NavMenu.module.css';
export default function NavMenu({ options = [], className, onSelect = () => {} }) { function NavMenu({ options = [], className, onSelect = () => {} }) {
const router = useRouter(); const router = useRouter();
return ( return (
@ -30,3 +31,17 @@ export default function NavMenu({ options = [], className, onSelect = () => {} }
</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;

View File

@ -1,12 +1,19 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import styles from './NoData.module.css'; import styles from './NoData.module.css';
export default function NoData({ className }) { function NoData({ className }) {
return ( return (
<div className={classNames(styles.container, className)}> <div className={classNames(styles.container, className)}>
<FormattedMessage id="message.no-data-available" defaultMessage="No data available." /> <FormattedMessage id="message.no-data-available" defaultMessage="No data available." />
</div> </div>
); );
} }
NoData.propTypes = {
className: PropTypes.string,
};
export default NoData;

View File

@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { setDateRange } from 'redux/actions/websites'; import { setDateRange } from 'redux/actions/websites';
@ -8,7 +9,7 @@ import Dots from 'assets/ellipsis-h.svg';
import useDateRange from 'hooks/useDateRange'; import useDateRange from 'hooks/useDateRange';
import { getDateRange } from '../../lib/date'; import { getDateRange } from '../../lib/date';
export default function RefreshButton({ websiteId }) { function RefreshButton({ websiteId }) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [dateRange] = useDateRange(websiteId); const [dateRange] = useDateRange(websiteId);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -35,3 +36,9 @@ export default function RefreshButton({ websiteId }) {
/> />
); );
} }
RefreshButton.propTypes = {
websiteId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};
export default RefreshButton;

View File

@ -1,9 +1,10 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import NoData from 'components/common/NoData'; import NoData from 'components/common/NoData';
import styles from './Table.module.css'; import styles from './Table.module.css';
export default function Table({ function Table({
columns, columns,
rows, rows,
empty, empty,
@ -45,6 +46,34 @@ export default function Table({
); );
} }
const styledObject = PropTypes.shape({
className: PropTypes.string,
style: PropTypes.object,
});
Table.propTypes = {
columns: PropTypes.arrayOf(
PropTypes.shape({
cell: styledObject,
className: PropTypes.string,
header: styledObject,
key: PropTypes.string,
label: PropTypes.node,
render: PropTypes.func,
style: PropTypes.object,
}),
),
rows: PropTypes.arrayOf(PropTypes.object),
empty: PropTypes.node,
className: PropTypes.string,
bodyClassName: PropTypes.string,
rowKey: PropTypes.func,
showHeader: PropTypes.bool,
children: PropTypes.node,
};
export default Table;
export const TableRow = ({ columns, row }) => ( export const TableRow = ({ columns, row }) => (
<div className={classNames(styles.row, 'row')}> <div className={classNames(styles.row, 'row')}>
{columns.map(({ key, render, className, style, cell }, index) => ( {columns.map(({ key, render, className, style, cell }, index) => (

View File

@ -1,7 +1,15 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import styles from './Tag.module.css'; import styles from './Tag.module.css';
export default function Tag({ className, children }) { function Tag({ className, children }) {
return <span className={classNames(styles.tag, className)}>{children}</span>; return <span className={classNames(styles.tag, className)}>{children}</span>;
} }
Tag.propTypes = {
className: PropTypes.string,
children: PropTypes.node,
};
export default Tag;

View File

@ -1,11 +1,12 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { useSpring, animated } from 'react-spring'; import { useSpring, animated } from 'react-spring';
import styles from './Toast.module.css'; import styles from './Toast.module.css';
import Icon from 'components/common/Icon'; import Icon from 'components/common/Icon';
import Close from 'assets/times.svg'; import Close from 'assets/times.svg';
export default function Toast({ message, timeout = 3000, onClose }) { function Toast({ message, timeout = 3000, onClose }) {
const props = useSpring({ const props = useSpring({
opacity: 1, opacity: 1,
transform: 'translate3d(0,0px,0)', transform: 'translate3d(0,0px,0)',
@ -24,3 +25,11 @@ export default function Toast({ message, timeout = 3000, onClose }) {
document.getElementById('__modals'), document.getElementById('__modals'),
); );
} }
Toast.propTypes = {
message: PropTypes.node,
timeout: PropTypes.number,
onClose: PropTypes.func,
};
export default Toast;

View File

@ -1,4 +1,5 @@
import React, { useState, useMemo } from 'react'; import React, { useState, useMemo } from 'react';
import PropTypes from 'prop-types';
import ReactTooltip from 'react-tooltip'; import ReactTooltip from 'react-tooltip';
import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps'; import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps';
import classNames from 'classnames'; import classNames from 'classnames';
@ -12,7 +13,7 @@ import { useRouter } from 'next/router';
const geoUrl = '/world-110m.json'; const geoUrl = '/world-110m.json';
export default function WorldMap({ data, className }) { function WorldMap({ data, className }) {
const { basePath } = useRouter(); const { basePath } = useRouter();
const [tooltip, setTooltip] = useState(); const [tooltip, setTooltip] = useState();
const [theme] = useTheme(); const [theme] = useTheme();
@ -89,3 +90,16 @@ export default function WorldMap({ data, className }) {
</div> </div>
); );
} }
WorldMap.propTypes = {
data: PropTypes.arrayOf(
PropTypes.shape({
x: PropTypes.string,
y: PropTypes.number,
z: PropTypes.number,
}),
),
className: PropTypes.string,
};
export default WorldMap;