@britannica/ui
Version:
Consumer style system
732 lines (698 loc) • 24.9 kB
JavaScript
import classnames from 'classnames';
import React, { useRef, useEffect, createContext, useState, useCallback, useMemo, useContext, createRef } from 'react';
import { createPortal } from 'react-dom';
import { createPopper } from '@popperjs/core';
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
var __assign = function () {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
}
return t;
}
function __spreadArray(to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
var Breadcrumb = function (_a) {
var className = _a.className,
_b = _a.tag,
Tag = _b === void 0 ? 'nav' : _b,
rest = __rest(_a, ["className", "tag"]);
return /*#__PURE__*/React.createElement(Tag, __assign({
className: classnames('breadcrumb', className),
"data-testid": "Breadcrumb"
}, rest));
};
function BreadcrumbItem(_a) {
var className = _a.className,
// @ts-ignore todo: fix this
_b = _a.tag,
// @ts-ignore todo: fix this
Tag = _b === void 0 ? 'span' : _b,
rest = __rest(_a, ["className", "tag"]);
return (
/*#__PURE__*/
// @ts-ignore todo: fix this
React.createElement(Tag, __assign({
className: classnames('breadcrumb-item', className),
"data-testid": "BreadcrumbItem"
}, rest))
);
}
function Button(_a) {
var className = _a.className,
_b = _a.circle,
circle = _b === void 0 ? false : _b,
color = _a.color,
innerRef = _a.innerRef,
label = _a.label,
_c = _a.outline,
outline = _c === void 0 ? false : _c,
_d = _a.rounded,
rounded = _d === void 0 ? false : _d,
size = _a.size,
// @ts-ignore todo: fix this
_e = _a.tag,
// @ts-ignore todo: fix this
Tag = _e === void 0 ? 'button' : _e,
props = __rest(_a, ["className", "circle", "color", "innerRef", "label", "outline", "rounded", "size", "tag"]);
var type = Tag === 'button' ? 'button' : null;
var colorString = color ? "btn-".concat(color) : null;
var sizeString = size ? "btn-".concat(size) : null;
var outlineString = outline ? "btn-outline-".concat(color) : null;
return (
/*#__PURE__*/
// @ts-ignore todo: fix this
React.createElement(Tag, __assign({
className: classnames('btn', !outline && colorString, outlineString, sizeString, circle && 'btn-circle', rounded && 'btn-rounded', className),
"data-testid": "Button",
type: type,
"aria-label": label,
ref: innerRef
}, props))
);
}
var Card = function (_a) {
var className = _a.className,
size = _a.size,
borderless = _a.borderless,
_b = _a.tag,
Tag = _b === void 0 ? 'div' : _b,
innerRef = _a.innerRef,
rest = __rest(_a, ["className", "size", "borderless", "tag", "innerRef"]);
return /*#__PURE__*/React.createElement(Tag, __assign({
className: classnames('card', size && "card-".concat(size), borderless && "card-borderless", className),
"data-testid": "Card",
ref: innerRef
}, rest));
};
var CardBody = function (_a) {
var className = _a.className,
_b = _a.tag,
Tag = _b === void 0 ? 'div' : _b,
rest = __rest(_a, ["className", "tag"]);
return /*#__PURE__*/React.createElement(Tag, __assign({
className: classnames('card-body', className),
"data-testid": "CardBody"
}, rest));
};
/**
* @deprecated This component will be removed in a future major version release
*/
var CardFooter = function (_a) {
var className = _a.className,
_b = _a.tag,
Tag = _b === void 0 ? 'div' : _b,
rest = __rest(_a, ["className", "tag"]);
return /*#__PURE__*/React.createElement(Tag, __assign({
className: classnames('card-footer', className),
"data-testid": "CardFooter"
}, rest));
};
var CardMedia = function (_a) {
var className = _a.className,
_b = _a.tag,
Tag = _b === void 0 ? 'div' : _b,
props = __rest(_a, ["className", "tag"]);
return /*#__PURE__*/React.createElement(Tag, __assign({
className: classnames('card-media', className),
"data-testid": "CardMedia"
}, props));
};
var ClientPortal = function (_a) {
var children = _a.children,
_b = _a.selector,
selector = _b === void 0 ? 'body' : _b;
var ref = useRef(null);
useEffect(function () {
ref.current = document.querySelector(selector);
}, [selector]);
return ref.current ? /*#__PURE__*/createPortal(children, ref.current) : null;
};
var Container = function (_a) {
var className = _a.className,
_b = _a.size,
size = _b === void 0 ? 'base' : _b,
props = __rest(_a, ["className", "size"]);
return /*#__PURE__*/React.createElement("div", __assign({
className: classnames(size !== 'base' ? "container-".concat(size) : 'container', className),
"data-testid": "Container"
}, props));
};
var Drawer = function (_a) {
var className = _a.className,
_b = _a.open,
open = _b === void 0 ? false : _b,
_c = _a.tag,
Tag = _c === void 0 ? 'div' : _c,
props = __rest(_a, ["className", "open", "tag"]);
return /*#__PURE__*/React.createElement(Tag, __assign({
className: classnames('drawer', {
open: open
}, className),
"data-testid": "Drawer"
}, props));
};
var dropdownContext = null;
function getContext() {
if (!dropdownContext) {
dropdownContext = /*#__PURE__*/createContext({
isOpen: false,
toggle: function () {}
});
}
return dropdownContext;
}
var DropdownContext = /*#__PURE__*/new Proxy({}, /*#__PURE__*/new Proxy({}, {
get: function (_, handler) {
var target = getContext();
// @ts-ignore
return function (_target) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
return Reflect[handler].apply(Reflect, __spreadArray([target], args, false));
};
}
}));
var Dropdown = function (_a) {
var children = _a.children;
var container = useRef(null);
var _b = useState(false),
isOpen = _b[0],
setIsOpen = _b[1];
var toggle = useCallback(function () {
return setIsOpen(function (prevIsOpen) {
return !prevIsOpen;
});
}, [setIsOpen]);
var value = useMemo(function () {
return {
isOpen: isOpen,
toggle: toggle
};
}, [isOpen, toggle]);
useEffect(function () {
// todo: fix this use of `any`
function handleDocumentClick(e) {
var _a, _b;
if (((_a = container.current) === null || _a === void 0 ? void 0 : _a.contains(e.target)) && container.current !== e.target) {
return;
}
if (!((_b = container.current) === null || _b === void 0 ? void 0 : _b.contains(e.target)) && container.current !== e.target) {
setIsOpen(false);
return;
}
toggle();
}
['click'].forEach(function (eventName) {
return document.addEventListener(eventName, handleDocumentClick, true);
});
return function () {
['click'].forEach(function (eventName) {
return document.removeEventListener(eventName, handleDocumentClick, true);
});
};
}, [toggle, container]);
return /*#__PURE__*/React.createElement(DropdownContext.Provider, {
value: value
}, /*#__PURE__*/React.createElement("div", {
ref: container,
"data-testid": "Dropdown"
}, typeof children === 'function' ? children(value) : children));
};
var DropdownMenu = function (_a) {
var className = _a.className,
_b = _a.tag,
Tag = _b === void 0 ? 'div' : _b,
props = __rest(_a, ["className", "tag"]);
var isOpen = useContext(DropdownContext).isOpen;
return isOpen ? (/*#__PURE__*/React.createElement(Tag, __assign({
className: classnames('dropdown-menu', className),
"data-testid": "DropdownMenu"
}, props))) : null;
};
var DropdownToggle = function (_a) {
var children = _a.children,
className = _a.className,
_b = _a.tag,
Tag = _b === void 0 ? 'div' : _b,
props = __rest(_a, ["children", "className", "tag"]);
var value = useContext(DropdownContext);
return /*#__PURE__*/React.createElement(Tag, __assign({
className: classnames({
active: value.isOpen
}, className),
"data-testid": "DropdownToggle",
onClick: value.toggle
}, props), typeof children === 'function' ? children(value) : children);
};
var FormFeedback = function (_a) {
var className = _a.className,
innerRef = _a.innerRef,
rest = __rest(_a, ["className", "innerRef"]);
return /*#__PURE__*/React.createElement("div", __assign({
className: classnames('text-red font-12 mt-5', className),
"data-testid": "FormFeedback",
ref: innerRef
}, rest));
};
var Input = function (_a) {
var className = _a.className,
innerRef = _a.innerRef,
_b = _a.isInvalid,
isInvalid = _b === void 0 ? false : _b,
size = _a.size,
_c = _a.type,
type = _c === void 0 ? 'text' : _c,
props = __rest(_a, ["className", "innerRef", "isInvalid", "size", "type"]);
return /*#__PURE__*/React.createElement("input", __assign({
ref: innerRef,
type: type,
className: classnames({
'border-red': isInvalid
}, 'form-control', size && "form-control-".concat(size), className),
"data-testid": "Input"
}, props));
};
var _a, _b, _c, _d;
/**
* todo:
* - calculate intrinsic dimensions of image without using <img> height and width attributes https://amp.dev/documentation/guides-and-tutorials/learn/amp-html-layout/layouts_demonstrated/?format=websites#intrinsic
* - automatically generate a srcset attribute, if one isn't passed, based on style system breakpoints
* - figure out a better way to set CDN_URL. context? requires wrapping of app in provider... this might be ok if we ever allow theming. other options?
*/
var CDN_URL;
// CRA and Next.js
if (typeof process !== 'undefined') {
CDN_URL = (_b = (_a = process.env.REACT_APP_CDN_URL) !== null && _a !== void 0 ? _a : process.env.NEXT_PUBLIC_CDN_URL) !== null && _b !== void 0 ? _b : 'https://cdn.britannica.com';
}
// Mendel and a default (this can't be a long term solution :grimace:)
else {
// @ts-ignore
CDN_URL = (_d = (_c = window.Mendel) === null || _c === void 0 ? void 0 : _c.config.cdnUrl) !== null && _d !== void 0 ? _d : 'https://cdn.britannica.com';
}
var IrisImage = function (_a) {
var children = _a.children,
src = _a.src,
alt = _a.alt,
_b = _a.loading,
loading = _b === void 0 ? 'lazy' : _b,
quality = _a.quality,
height = _a.height,
width = _a.width,
size = _a.size,
command = _a.command,
imgHeight = _a.imgHeight,
imgWidth = _a.imgWidth,
rest = __rest(_a, ["children", "src", "alt", "loading", "quality", "height", "width", "size", "command", "imgHeight", "imgWidth"]);
var irisParams = new URLSearchParams();
if (command) {
irisParams.append('c', command);
}
if (height) {
irisParams.append('h', height.toString());
}
if (width) {
irisParams.append('w', width.toString());
}
if (size) {
irisParams.append('s', size);
}
if (quality) {
irisParams.append('q', quality.toString());
}
var originPathname;
try {
var _c = new URL(src),
origin_1 = _c.origin,
pathname = _c.pathname;
originPathname = origin_1 + pathname;
} catch (_d) {
originPathname = CDN_URL + src;
}
var irisParamsString = irisParams.toString();
var computedSrc = originPathname + (irisParamsString === '' ? '' : "?".concat(irisParamsString));
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("img", __assign({
loading: loading,
src: computedSrc,
alt: alt,
height: imgHeight,
width: imgWidth,
"data-testid": "IrisImage"
}, rest)), children === null || children === void 0 ? void 0 : children({
computedSrc: computedSrc
}));
};
var Modal = function (_a) {
var _b = _a.centered,
centered = _b === void 0 ? false : _b,
className = _a.className,
isOpen = _a.isOpen,
toggle = _a.toggle,
props = __rest(_a, ["centered", "className", "isOpen", "toggle"]);
var ref = useRef(null);
// Close the modal if the Escape key is pressed
useEffect(function () {
function handleKeyDown(event) {
if (isOpen && event.key === 'Escape') {
toggle(event);
}
}
document.addEventListener('keydown', handleKeyDown);
return function () {
return document.removeEventListener('keydown', handleKeyDown);
};
}, [isOpen, toggle]);
// Close the modal if the backdrop is clicked
useEffect(function () {
var current = ref.current;
function handleBackdropClick(event) {
if (event.target === ref.current) {
toggle(event);
}
}
if (current) {
current.addEventListener('click', handleBackdropClick);
}
return function () {
return current === null || current === void 0 ? void 0 : current.removeEventListener('click', handleBackdropClick);
};
}, [ref, toggle]);
// Toggle the .modal-open class on the body
useEffect(function () {
document.body.classList.toggle('modal-open', isOpen);
}, [isOpen]);
return /*#__PURE__*/React.createElement(ClientPortal, null, isOpen && (/*#__PURE__*/React.createElement("div", {
className: classnames('modal', {
'modal-centered': centered
}),
"data-testid": "Modal",
ref: ref
}, /*#__PURE__*/React.createElement("div", __assign({
className: classnames('modal-dialog', className)
}, props)))));
};
var ModalBody = function (_a) {
var className = _a.className,
props = __rest(_a, ["className"]);
return /*#__PURE__*/React.createElement("div", __assign({
className: classnames('modal-body', className),
"data-testid": "ModalBody"
}, props));
};
var ModalFooter = function (_a) {
var className = _a.className,
props = __rest(_a, ["className"]);
return /*#__PURE__*/React.createElement("div", __assign({
className: classnames('modal-footer', className),
"data-testid": "ModalFooter"
}, props));
};
var ModalHeader = function (_a) {
var children = _a.children,
className = _a.className,
toggle = _a.toggle,
props = __rest(_a, ["children", "className", "toggle"]);
return /*#__PURE__*/React.createElement("div", __assign({
className: classnames('modal-header', className),
"data-testid": "ModalHeader"
}, props), children, /*#__PURE__*/React.createElement("button", {
"aria-label": "Close button",
type: "button",
className: "modal-close-button p-10 btn btn-link",
onClick: toggle
}));
};
var Popover = function (_a) {
var _b = _a.arrow,
arrow = _b === void 0 ? false : _b,
children = _a.children,
className = _a.className,
_c = _a.hideEvents,
hideEvents = _c === void 0 ? ['click'] : _c,
_d = _a.innerRef,
innerRef = _d === void 0 ? /*#__PURE__*/createRef() : _d,
_e = _a.modifiers,
modifiers = _e === void 0 ? [] : _e,
onFirstUpdate = _a.onFirstUpdate,
_f = _a.placement,
placement = _f === void 0 ? 'bottom' : _f,
_g = _a.showEvents,
showEvents = _g === void 0 ? ['click'] : _g,
size = _a.size,
_h = _a.strategy,
strategy = _h === void 0 ? 'absolute' : _h,
triggerSelector = _a.triggerSelector;
var _j = useState(false),
isVisible = _j[0],
setIsVisible = _j[1];
var sizeString = size ? "popover-".concat(size) : null;
var triggerRef = useRef(null);
// Toggle "visibility" css property as "isVisible" changes
useEffect(function () {
if (innerRef.current) {
// eslint-disable-next-line no-param-reassign
innerRef.current.style.visibility = isVisible ? 'visible' : '';
}
}, [innerRef, isVisible]);
// Set the "triggerRef" when "triggerSelector" changes
useEffect(function () {
triggerRef.current = document.querySelector(triggerSelector);
}, [triggerSelector]);
// Create event listeners to toggle "isVisible" based on showEvents/hideEvents (defaults to click event).
useEffect(function () {
function handleShowEvent() {
setIsVisible(true);
}
function handleHideEvent() {
setIsVisible(false);
}
function handleDocumentClick(e) {
var _a, _b;
if (!((_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.contains(e.target)) && !((_b = innerRef.current) === null || _b === void 0 ? void 0 : _b.contains(e.target))) {
setIsVisible(false);
}
}
// showEvents
showEvents.forEach(function (showEvent) {
var _a;
(_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.addEventListener(showEvent, handleShowEvent);
});
// hideEvent
hideEvents.forEach(function (hideEvent) {
var _a;
(_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.addEventListener(hideEvent, handleHideEvent);
});
document.addEventListener('click', handleDocumentClick);
return function () {
// showEvents
showEvents.forEach(function (showEvent) {
var _a;
(_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.removeEventListener(showEvent, handleShowEvent);
});
// hideEvent
hideEvents.forEach(function (hideEvent) {
var _a;
(_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.removeEventListener(hideEvent, handleHideEvent);
});
document.removeEventListener('click', handleDocumentClick);
};
}, [innerRef]);
// Create popover
useEffect(function () {
var popper;
if (triggerRef.current && innerRef.current) {
popper = createPopper(triggerRef.current, innerRef.current, {
modifiers: __spreadArray([{
name: 'offset',
options: {
offset: [0, arrow ? 15 : 0]
}
}], modifiers, true),
placement: placement,
strategy: strategy,
onFirstUpdate: onFirstUpdate
});
}
return function () {
return popper === null || popper === void 0 ? void 0 : popper.destroy();
};
}, [arrow, innerRef, modifiers, onFirstUpdate, placement, strategy]);
return /*#__PURE__*/React.createElement("div", {
className: classnames('popover', sizeString, className),
ref: innerRef,
"data-testid": "Popover"
}, children, arrow && /*#__PURE__*/React.createElement("div", {
"data-popper-arrow": true
}));
};
var Tab = function (_a) {
var _b = _a.active,
active = _b === void 0 ? false : _b,
className = _a.className,
_c = _a.tag,
Tag = _c === void 0 ? 'div' : _c,
props = __rest(_a, ["active", "className", "tag"]);
return /*#__PURE__*/React.createElement(Tag, __assign({
className: classnames('tab', {
active: active
}, className),
"data-testid": "Tab"
}, props));
};
var Table = function (_a) {
var className = _a.className;
return /*#__PURE__*/React.createElement("table", {
className: classnames('table', className),
"data-testid": "Table"
});
};
var Tabs = function (_a) {
var className = _a.className,
_b = _a.tag,
Tag = _b === void 0 ? 'div' : _b,
props = __rest(_a, ["className", "tag"]);
return /*#__PURE__*/React.createElement(Tag, __assign({
className: classnames('tabs', className),
"data-testid": "Tabs"
}, props));
};
var Tag = function (_a) {
var className = _a.className,
_b = _a.tag,
HtmlTag = _b === void 0 ? 'div' : _b;
return /*#__PURE__*/React.createElement(HtmlTag, {
className: classnames('tag', className),
"data-testid": "Tag"
});
};
var Textarea = function (_a) {
var className = _a.className,
innerRef = _a.innerRef,
_b = _a.isInvalid,
isInvalid = _b === void 0 ? false : _b,
rest = __rest(_a, ["className", "innerRef", "isInvalid"]);
return /*#__PURE__*/React.createElement("textarea", __assign({
className: classnames('form-control', {
'border-red': isInvalid
}, className),
ref: innerRef,
"data-testid": "Textarea"
}, rest));
};
var Tooltip = function (_a) {
var children = _a.children,
className = _a.className,
_b = _a.delay,
delay = _b === void 0 ? 150 : _b,
_c = _a.innerRef,
innerRef = _c === void 0 ? /*#__PURE__*/createRef() : _c,
_d = _a.modifiers,
modifiers = _d === void 0 ? [] : _d,
onFirstUpdate = _a.onFirstUpdate,
_e = _a.placement,
placement = _e === void 0 ? 'bottom' : _e,
_f = _a.strategy,
strategy = _f === void 0 ? 'absolute' : _f,
triggerSelector = _a.triggerSelector;
var _g = useState(false),
isVisible = _g[0],
setIsVisible = _g[1];
var triggerRef = useRef(null);
var show = useCallback(function () {
window.setTimeout(function () {
return setIsVisible(true);
}, delay);
}, [delay]);
var hide = useCallback(function () {
window.setTimeout(function () {
return setIsVisible(false);
}, delay);
}, [delay]);
// Toggle "visibility" css property as "isVisible" changes
useEffect(function () {
if (innerRef.current) {
// eslint-disable-next-line no-param-reassign
innerRef.current.style.visibility = isVisible ? 'visible' : '';
}
}, [innerRef, isVisible]);
// Set the "triggerRef" when "triggerSelector" changes
useEffect(function () {
triggerRef.current = document.querySelector(triggerSelector);
}, [triggerSelector]);
// Create event listeners to toggle "isVisible" based on whether a click is within the target or outside of it
useEffect(function () {
var _a, _b;
(_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.addEventListener('mouseenter', show);
(_b = triggerRef.current) === null || _b === void 0 ? void 0 : _b.addEventListener('mouseleave', hide);
return function () {
var _a, _b;
(_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.removeEventListener('mouseenter', show);
(_b = triggerRef.current) === null || _b === void 0 ? void 0 : _b.removeEventListener('mouseleave', hide);
};
}, [hide, innerRef, show]);
// Create tooltip
useEffect(function () {
var popper;
if (triggerRef.current && innerRef.current) {
popper = createPopper(triggerRef.current, innerRef.current, {
modifiers: __spreadArray([{
name: 'offset',
options: {
offset: [0, 15]
}
}], modifiers, true),
onFirstUpdate: onFirstUpdate,
placement: placement,
strategy: strategy
});
}
return function () {
return popper === null || popper === void 0 ? void 0 : popper.destroy();
};
}, [innerRef, modifiers, onFirstUpdate, placement, strategy]);
return /*#__PURE__*/React.createElement("div", {
className: classnames('tooltip', className),
ref: innerRef,
"data-testid": "Tooltip"
}, children, /*#__PURE__*/React.createElement("div", {
"data-popper-arrow": true
}));
};
export { Breadcrumb, BreadcrumbItem, Button, Card, CardBody, CardFooter, CardMedia, ClientPortal, Container, Drawer, Dropdown, DropdownMenu, DropdownToggle, FormFeedback, Input, IrisImage, Modal, ModalBody, ModalFooter, ModalHeader, Popover, Tab, Table, Tabs, Tag, Textarea, Tooltip };
//# sourceMappingURL=britannica-ui.es.js.map