spartan-ui
Version:
React component library that focusses on getting the basics right leaving the design up to you
251 lines (226 loc) • 11.3 kB
JavaScript
import React, { useState, useEffect, useRef } from 'react';
import FocusLock from 'react-focus-lock';
const Accordion = ({ as: Element = 'div', children, onClick, multi = false, defaultOpen = [], ...rest }) => {
const [{ open }, setState] = useState({
open: defaultOpen,
});
const handleClick = (id) => (e) => {
if (open.includes(id)) {
setState({ open: open.filter((openId) => openId !== id) });
}
else {
setState({ open: multi ? [...open, id] : [id] });
}
if (onClick) {
onClick(e, open);
}
e.preventDefault();
};
return (React.createElement(Element, Object.assign({}, rest), React.Children.map(children, child => React.isValidElement(child)
? React.cloneElement(child, {
onClick: handleClick(child.props.id),
open: open.includes(child.props.id),
})
: child)));
};
var detectKeyPress = (match, callback) => (e) => {
if (match.filter((key) => (e.key && e.key === key.name) || (e.keyCode && e.keyCode === key.code)).length > 0) {
callback(e);
}
};
const KEY = {
ENTER: {
name: 'Enter',
code: 13,
},
ESCAPE: {
name: 'Escape',
code: 27,
},
SPACE: {
name: 'Space',
code: 32,
},
};
const Accordion$1 = ({ as: Element = 'div', children, id, open = false, onClick, ...rest }) => {
const handleClick = (e) => {
onClick(e, !open);
e.preventDefault();
};
const handleKeyDown = (e) => {
handleClick(e);
e.preventDefault();
};
const headingProps = {
id: `${id}-heading`,
onClick: handleClick,
onKeyDown: detectKeyPress([KEY.ENTER, KEY.SPACE], handleKeyDown),
role: 'button',
tabIndex: 0,
'aria-controls': `${id}-body`,
'aria-expanded': open,
};
const bodyProps = {
hidden: !open,
id: `${id}-body`,
'aria-hidden': !open,
'aria-labelledby': `${id}-heading`,
};
return (React.createElement(Element, Object.assign({}, rest, { id: id }), children({ open, headingProps, bodyProps })));
};
const Alert = ({ as: Element = 'div', children, minor, ...rest }) => (React.createElement(Element, Object.assign({}, rest, { role: "alert", "aria-atomic": "true", "aria-live": minor ? 'polite' : undefined }), children));
const Breadcrumb = ({ as: Element = 'nav', children, 'aria-label': label = 'Breadcrumb', ...rest }) => (React.createElement(Element, Object.assign({}, rest, { role: "list", "aria-label": label }), children));
const BreadcrumbItem = ({ as: Element = 'span', children, active, ...rest }) => (React.createElement(Element, Object.assign({}, rest, { role: "listitem", "aria-current": active ? 'page' : undefined }), children));
const Button = ({ as: Element = 'button', children, active, type = 'button', disabled, ...rest }) => (React.createElement(Element, Object.assign({}, rest, { role: "button", type: type, disabled: disabled, "aria-disabled": disabled || undefined, "aria-pressed": active }), children));
const Dropdown = ({ as: Element = 'div', children, id, defaultOpen = false, ...rest }) => {
const [{ open }, setState] = useState({
open: defaultOpen,
});
const buttonProps = {
id: `${id}-button`,
onClick: () => {
setState({ open: !open });
},
'aria-controls': `${id}-menu`,
'aria-expanded': open,
'aria-haspopup': true,
};
const menuProps = {
hidden: !open,
id: `${id}-menu`,
role: 'list',
'aria-hidden': !open,
'aria-labelledby': `${id}-button`,
};
const menuItemProps = {
role: 'listitem',
};
return (React.createElement(Element, Object.assign({}, rest, { id: id }), children({ open, buttonProps, menuProps, menuItemProps })));
};
const Field = ({ as = 'div', children, name, error = false, info = false, required = false, optional = false, optionList = false, ...rest }) => {
const Element = as === 'div' && optionList ? 'fieldset' : as;
const inputProps = {
name,
'aria-describedby': [
error && `field-${name}-error`,
info && `field-${name}-info`,
]
.filter(Boolean)
.join(' '),
'aria-invalid': error || undefined,
required: required || (optional ? false : undefined),
};
const labelProps = {
as: optionList ? 'legend' : undefined,
htmlFor: !optionList ? name : undefined,
};
const ids = {
error: `${name}-error`,
info: `${name}-info`,
};
return (React.createElement(Element, Object.assign({}, rest, { id: `field-${name}` }), children({ ids, inputProps, labelProps })));
};
const Input = ({ as: Element = 'input', name, id, type = 'text', defaultValue, disabled = false, placeholder, required, value, onBlur, onChange, ...rest }) => (React.createElement(Element, Object.assign({}, rest, { name: name, id: id, type: type, defaultValue: defaultValue, disabled: disabled, placeholder: placeholder, required: required, value: value, "aria-disabled": disabled || undefined, "aria-required": typeof required !== 'undefined' ? required : undefined, onBlur: onBlur, onChange: onChange })));
const InputOption = ({ children, type = 'checkbox', name, id, value, ...rest }) => React.createElement(Input, Object.assign({}, rest, { name: name, id: id || `${name}-${value}`, type: type }));
const Select = ({ as: Element = 'select', name, id, defaultValue, disabled = false, multiple, options, placeholder, required, size, value, onBlur, onChange, ...rest }) => (React.createElement(Element, Object.assign({}, rest, { name: name, id: id, defaultValue: defaultValue, disabled: disabled, multiple: multiple, placeholder: placeholder, required: required, size: size, value: value, "aria-disabled": disabled || undefined, "aria-required": typeof required !== 'undefined' ? required : undefined, onBlur: onBlur, onChange: onChange }), options.map(option => (React.createElement("option", { key: option.value, value: option.value }, option.label)))));
const TextArea = ({ as: Element = 'textarea', name, id, cols, defaultValue, disabled = false, placeholder, required, rows, spellCheck, value, wrap, onBlur, onChange, ...rest }) => (React.createElement(Element, Object.assign({}, rest, { name: name, id: id, cols: cols, defaultValue: defaultValue, disabled: disabled, placeholder: placeholder, required: required, rows: rows, spellCheck: spellCheck, value: value, wrap: wrap, "aria-disabled": disabled || undefined, "aria-required": typeof required !== 'undefined' ? required : undefined, onBlur: onBlur, onChange: onChange })));
const Label = ({ as: Element = 'label', children, htmlFor, ...rest }) => (React.createElement(Element, Object.assign({}, rest, { htmlFor: htmlFor }), children));
var useBodyScrollLock = (trigger) => {
useEffect(() => {
document.body.style.overflow = trigger ? 'hidden' : '';
return () => {
document.body.style.overflow = '';
};
}, [trigger]);
};
var useClickAway = (ref, handler, disabled = true) => {
useEffect(() => {
if (disabled) {
return;
}
const listener = (e) => {
if (!ref.current || ref.current.contains(e.target)) {
return;
}
handler(e);
};
document.addEventListener('mousedown', listener);
return () => {
document.removeEventListener('mousedown', listener);
};
}, [ref, handler, disabled]);
};
var useOnKeyPress = (key, handler, disabled = false) => {
useEffect(() => {
if (disabled) {
return;
}
const handleKeyPress = detectKeyPress([key], handler);
window.addEventListener('keydown', handleKeyPress);
return () => {
window.removeEventListener('keydown', handleKeyPress);
};
}, [key, handler, disabled]);
};
var useSetFocus = (ref, trigger) => {
const originalFocusRef = useRef();
useEffect(() => {
const { current } = ref;
const { current: originalFocus } = originalFocusRef;
if (!current) {
return;
}
if (document.activeElement !== current) {
originalFocusRef.current = document.activeElement;
}
if (trigger) {
current.focus();
}
else if (originalFocus) {
originalFocus.focus();
}
return () => {
if (document.activeElement === current && originalFocus) {
originalFocus.focus();
}
};
}, [ref, trigger]);
};
const Modal = ({ as: Element = 'div', children, id, open = false, alert = false, onClose, focusLockOptions, ...rest }) => {
const modalRef = useRef(null);
useBodyScrollLock(open);
useClickAway(modalRef, onClose, alert);
useOnKeyPress(KEY.ESCAPE, onClose, alert);
const headingProps = {
id: `${id}-heading`,
};
return (React.createElement(React.Fragment, null, open && (React.createElement(FocusLock, Object.assign({ returnFocus: true }, focusLockOptions),
React.createElement(Element, Object.assign({}, rest, { ref: modalRef, role: alert ? 'alertdialog' : 'dialog', id: id, hidden: !open, tabIndex: open ? 0 : -1, "aria-hidden": !open, "aria-labelledby": `${id}-heading` }), children({ headingProps }))))));
};
const Progress = ({ as: Element = 'div', children, id, loading = false, value, min = 0, max = 100, label, ...rest }) => {
const indicatorRef = useRef(null);
useSetFocus(indicatorRef, loading);
const hasMessage = !label;
const isDeterminate = typeof value !== 'undefined';
const indicatorProps = {
ref: indicatorRef,
tabIndex: loading ? 0 : undefined,
'aria-busy': loading,
'aria-label': hasMessage ? undefined : label,
'aria-labelledby': hasMessage ? `${id}-message` : undefined,
'aria-valuemax': isDeterminate ? max : undefined,
'aria-valuemin': isDeterminate ? min : undefined,
'aria-valuenow': isDeterminate ? value : undefined,
};
const messageProps = {
id: `${id}-message`,
role: 'staus',
'aria-atomic': true,
'aria-live': 'polite',
};
return (React.createElement(Element, Object.assign({}, rest, { id: id }), loading && children({ indicatorProps, messageProps })));
};
const SRLabel = ({ as: Element = 'span', children, label, ...rest }) => (React.createElement(Element, Object.assign({}, rest, { "aria-label": label }), children));
const SRNumber = ({ as: Element = 'span', children, ...rest }) => (React.createElement(Element, Object.assign({}, rest, { "aria-label": children.replace(/[\s|\-|,]/g, '.').replace(/\d/g, ' $&') }), children));
export { Accordion, Accordion$1 as AccordionPanel, Alert, Breadcrumb, BreadcrumbItem, Button, Dropdown, Field, Input, InputOption, Label, Modal, Progress, SRLabel, SRNumber, Select, TextArea };
//# sourceMappingURL=index.dev.js.map