UNPKG

@cimpress/react-components

Version:
211 lines (209 loc) 9.46 kB
var __rest = (this && this.__rest) || function (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; }; import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; import Tether from 'react-tether'; import debounce from 'lodash.debounce'; import { css, cx } from '@emotion/css'; import cvar from './theme/cvar'; const attachments = { top: 'bottom center', bottom: 'top center', left: 'middle right', right: 'middle left', }; const targetAttachments = { top: 'top center', bottom: 'bottom center', left: 'middle left', right: 'middle right', }; // The tooltip arrows sit inside their container's padding space, so they must use the same value const tooltipArrowSize = cvar('spacing-8'); const tooltipColor = cvar('color-tooltip-default'); const tetherStyle = css ` z-index: 2000; pointer-events: none; * { pointer-events: auto; } `; const baseTooltipStyle = (direction) => css ` display: block; ${`margin-${direction}`}: 3px; padding: ${direction === 'top' || direction === 'bottom' ? `${tooltipArrowSize} 0` : `0 ${tooltipArrowSize}`}; `; const tooltipContentStyle = css ` background-color: ${tooltipColor}; border-radius: 2px; box-shadow: 0 4px 4px rgba(0, 0, 0, 0.2); color: #fff; font: ${cvar('text-label-default')}; max-width: 200px; padding: ${cvar('spacing-12')} ${cvar('spacing-16')}; text-align: center; `; const popoverContentStyle = css ` background-color: ${cvar('color-background')}; border-radius: 2px; border: 1px solid ${cvar('color-border-default')}; box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); font: ${cvar('text-paragraph-default')}; padding: ${cvar('spacing-8')} ${cvar('spacing-16')}; `; const directedArrowStyle = (direction, isPopover) => { const opposite = { left: 'right', right: 'left', top: 'bottom', bottom: 'top', }[direction]; const centerDirection = direction === 'top' || direction === 'bottom' ? 'left' : 'top'; const borderColor = isPopover ? cvar('color-border-default') : tooltipColor; return css ` border-width: ${tooltipArrowSize}; border-color: transparent; border-style: solid; position: absolute; ${opposite}: 1px; ${`border-${opposite}-width: 0px;`} ${`border-${direction}-color: ${borderColor};`} ${centerDirection}: 50%; ${`margin-${centerDirection}: calc(-1 * ${tooltipArrowSize})`}; ${isPopover ? ` &::after { content: ' '; border: 7px solid transparent; position: absolute; bottom: 1px; ${`border-${opposite}-width: 0px;`} ${`border-${direction}-color: ${cvar('color-background')};`} ${`margin-${centerDirection}: calc(-1 * calc(${tooltipArrowSize} - 1px))`}; }` : null} `; }; export const Tooltip = (_a) => { var { children, contents = '', direction = 'top', style = {}, className = '', containerClassName = '', variety, variant = 'tooltip', tooltipStyle = {}, tooltipInnerStyle = {}, show, delay = 0, constraints = [ { to: 'window', attachment: 'together', pin: ['left', 'right'], }, ], onClickOutside, // Default to maintain compatibility with react-onclickoutside API outsideClickIgnoreClass = 'ignore-react-onclickoutside' } = _a, rest = __rest(_a, ["children", "contents", "direction", "style", "className", "containerClassName", "variety", "variant", "tooltipStyle", "tooltipInnerStyle", "show", "delay", "constraints", "onClickOutside", "outsideClickIgnoreClass"]); const targetRef = useRef(); const elementRef = useRef(); const tether = useRef(null); const [showState, setShowState] = useState(show || false); // only used to prevent Tether from rendering on the first render due to perf issues. const [showInitialState, setShowInitialState] = useState(show || false); const toggleTether = (enable) => { if (tether.current && tether.current.getTetherInstance()) { if (enable) { tether.current.enable(); tether.current.position(); } else { tether.current.disable(); } } }; const setShowFlag = (value) => { const newShowState = show !== undefined ? show : value; toggleTether(newShowState); setShowState(newShowState); }; const debouncedClose = debounce(close, 50); const debouncedOpen = debounce(open, delay); function open() { debouncedClose.cancel(); setShowFlag(true); } function close() { debouncedOpen.cancel(); setShowFlag(false); } useEffect(() => { if (show !== undefined) { setShowState(show); } }, [show, setShowState]); useEffect(() => { if (!showState) { toggleTether(false); } }, [showState, toggleTether]); const setShowInitial = () => { setShowInitialState(true); }; function attachTarget(ref) { if (ref.current) { targetRef.current = ref.current; } return ref; } function attachElement(ref) { if (ref.current) { elementRef.current = ref.current; } return ref; } const onClickOutsideRef = useRef(onClickOutside); useLayoutEffect(() => { onClickOutsideRef.current = onClickOutside; }, [onClickOutside]); const outsideClickIgnoreClassRef = useRef(outsideClickIgnoreClass); useLayoutEffect(() => { outsideClickIgnoreClassRef.current = outsideClickIgnoreClass; }, [outsideClickIgnoreClass]); useEffect(() => { const controller = new AbortController(); const { signal } = controller; if (onClickOutsideRef.current && showState) { document.addEventListener('click', e => { var _a, _b; const shouldIgnoreClickOnIgnoredClass = !(e.target instanceof HTMLElement) || e.target.classList.contains(outsideClickIgnoreClassRef.current); if (elementRef.current && !elementRef.current.contains(e.target) && !((_a = targetRef.current) === null || _a === void 0 ? void 0 : _a.contains(e.target)) && !shouldIgnoreClickOnIgnoredClass) { (_b = onClickOutsideRef.current) === null || _b === void 0 ? void 0 : _b.call(onClickOutsideRef); } }, { signal }); } return () => { controller.abort(); }; }, [showState]); const isPopover = (variant || variety) === 'popover'; const popover = isPopover ? { display: 'block' } : {}; if (showInitialState || showState) { return (React.createElement(Tether, { style: style, ref: tether, classPrefix: "tether-tooltip", attachment: attachments[direction], targetAttachment: targetAttachments[direction], constraints: constraints, className: cx('crc-tooltip-tether', tetherStyle), renderTarget: ref => (React.createElement("div", { ref: attachTarget(ref), style: Object.assign({ display: 'inline-block' }, style), onMouseEnter: debouncedOpen, onMouseOver: debouncedOpen, onFocus: debouncedOpen, onMouseOut: debouncedClose, onMouseLeave: debouncedClose, onBlur: debouncedClose, className: cx(isPopover ? 'crc-tooltip__container crc-tooltip__container--popover' : 'crc-tooltip__container', containerClassName) }, children)), renderElement: ref => (React.createElement("div", { ref: attachElement(ref), className: cx(isPopover ? 'crc-tooltip crc-tooltip--popover' : 'crc-tooltip', baseTooltipStyle(direction), className), role: "tooltip", style: Object.assign(Object.assign({ visibility: showState ? 'visible' : 'hidden' }, popover), tooltipStyle), onMouseEnter: debouncedOpen, onMouseOver: debouncedOpen, onFocus: debouncedOpen, onMouseOut: debouncedClose, onMouseLeave: debouncedClose, onBlur: debouncedClose }, React.createElement("div", { className: cx(isPopover ? 'crc-tooltip__arrow crc-tooltip__arrow--popover' : 'crc-tooltip__arrow', directedArrowStyle(direction, isPopover)) }), React.createElement("div", { className: isPopover ? cx('crc-tooltip__content crc-tooltip__content--popover', popoverContentStyle) : cx('crc-tooltip__content', tooltipContentStyle), style: Object.assign({}, tooltipInnerStyle) }, contents))) })); } const propsWithoutTooltipSpecificOnes = Object.fromEntries(Object.entries(rest).filter(k => ![ 'disableOnClickOutside', 'stopPropagation', 'enableOnClickOutside', 'preventDefault', 'outsideClickIgnoreClass', 'eventTypes', 'onClickOutside', ].includes(k[0]))); return (React.createElement("div", Object.assign({ style: Object.assign({ display: 'inline-block' }, style), onMouseEnter: setShowInitial, onMouseOver: setShowInitial, onFocus: debouncedOpen, className: containerClassName }, propsWithoutTooltipSpecificOnes), children)); }; //# sourceMappingURL=Tooltip.js.map