@procore/core-react
Version:
React library of Procore Design Guidelines
285 lines (279 loc) • 13.6 kB
JavaScript
var _excluded = ["option", "highlighted", "expanded", "optionRenderer"],
_excluded2 = ["children", "icon"],
_excluded3 = ["disabled", "icon", "label", "loading", "options", "onClick", "optionRenderer", "children", "placement", "onKeyDown", "onFocus", "onBlur", "onMouseDown", "onMouseEnter", "onMouseLeave", "size", "variant"];
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
import { EllipsisVertical } from '@procore/core-icons/dist';
import React, { useCallback } from 'react';
import { Card } from '../Card';
import { DropdownButton } from '../Dropdown/Dropdown';
import { Portal } from '../Portal';
import { useClickOutside } from '../_hooks/ClickOutside';
import { useI18nContext } from '../_hooks/I18n';
import { mergeRefs } from '../_utils/mergeRefs';
import { defaultOptionRenderer, DropdownFlyoutContext, hasChildren, noop, transformOption, useDropdownFlyout, useDropdownFlyoutContext } from './DropdownFlyout.helpers';
import { StyledDropdownFlyout, StyledDropdownFlyoutItem } from './DropdownFlyout.styles';
import { rootId } from './DropdownFlyout.types';
import { useDropdownFlyoutOverlay } from './useDropdownFlyoutOverlay';
export var FlyoutCaption = /*#__PURE__*/React.forwardRef(function FlyoutCaption(_ref, ref) {
var option = _ref.option,
highlighted = _ref.highlighted,
expanded = _ref.expanded,
optionRenderer = _ref.optionRenderer,
props = _objectWithoutProperties(_ref, _excluded);
var renderedOption = optionRenderer(option);
return /*#__PURE__*/React.createElement(StyledDropdownFlyoutItem, _extends({}, props, {
ref: ref,
"aria-expanded": expanded,
"data-highlighted": highlighted,
"data-flyout": true
}), renderedOption);
});
FlyoutCaption.displayName = 'FlyoutCaption';
export function FlyoutItem(_ref2) {
var option = _ref2.option;
var overlayElRef = React.useRef(null);
var _useDropdownFlyoutCon = useDropdownFlyoutContext(),
isHighlighted = _useDropdownFlyoutCon.isHighlighted,
isExpanded = _useDropdownFlyoutCon.isExpanded,
_onClick = _useDropdownFlyoutCon.onClick,
expand = _useDropdownFlyoutCon.expand,
collapse = _useDropdownFlyoutCon.collapse,
optionRenderer = _useDropdownFlyoutCon.optionRenderer;
var onMouseEnter = React.useCallback(function () {
return expand(option);
}, [option, expand]);
var onMouseLeave = React.useCallback(function (e) {
if (e.relatedTarget instanceof Node && overlayElRef.current && !overlayElRef.current.contains(e.relatedTarget)) {
collapse(option);
}
}, [collapse, option]);
var onClick = React.useCallback(function () {
return _onClick(option);
}, [_onClick, option]);
var _React$useState = React.useState(null),
_React$useState2 = _slicedToArray(_React$useState, 2),
referenceEl = _React$useState2[0],
setReferenceEl = _React$useState2[1];
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(FlyoutCaption, {
ref: setReferenceEl,
role: "listitem",
option: option.origin,
onClick: onClick,
optionRenderer: optionRenderer,
highlighted: isHighlighted(option),
expanded: hasChildren(option.origin) ? isExpanded(option) : undefined,
onMouseEnter: onMouseEnter,
onMouseLeave: onMouseLeave
}), Array.isArray(option.children) && isExpanded(option) ? /*#__PURE__*/React.createElement(Portal, null, /*#__PURE__*/React.createElement(FlyoutList, {
referenceEl: referenceEl,
options: option.children,
overlayRef: overlayElRef,
placement: "right-top"
})) : null);
}
export function FlyoutList(_ref3) {
var options = _ref3.options,
placement = _ref3.placement,
referenceEl = _ref3.referenceEl,
overlayElRef = _ref3.overlayRef,
_ref3$offset = _ref3.offset,
offset = _ref3$offset === void 0 ? 0 : _ref3$offset;
var _useDropdownFlyoutOve = useDropdownFlyoutOverlay({
placement: placement,
offset: {
mainAxis: offset
}
}),
overlayStyle = _useDropdownFlyoutOve.overlayStyle,
referenceRef = _useDropdownFlyoutOve.referenceRef,
overlayRef = _useDropdownFlyoutOve.overlayRef;
React.useEffect(function () {
referenceRef(referenceEl);
}, [referenceEl, referenceRef]);
return /*#__PURE__*/React.createElement(Card, {
ref: mergeRefs(overlayRef, overlayElRef),
shadowStrength: 2,
style: overlayStyle
}, /*#__PURE__*/React.createElement(StyledDropdownFlyout, {
role: "list"
}, options.map(function (option) {
return /*#__PURE__*/React.createElement(FlyoutItem, {
key: option.id,
option: option
});
})));
}
var DefaultButton = /*#__PURE__*/React.forwardRef(function DefaultButton(_ref4, ref) {
var children = _ref4.children,
icon = _ref4.icon,
props = _objectWithoutProperties(_ref4, _excluded2);
return /*#__PURE__*/React.createElement(DropdownButton, _extends({}, props, {
icon: props.loading ? undefined : !icon && !children ? /*#__PURE__*/React.createElement(EllipsisVertical, null) : icon,
arrow: Boolean(React.Children.count(children)),
ref: ref
}), children);
});
/**
The dropdown flyout allows for additional menu items to be nested within
parent items, which allows more menu options for users.
@a11y WARN: DropdownFlyotus hold no value and take an action. Focus is expected
to be moved from a Dropdown to something else, to continue the user flow.
If the action is inert and does not move focus, this is left to be added
by the consumer.
@since 10.19.0
@see [Storybook](https://stories.core.procore.com/?path=/story/core-react_demos-dropdownflyout--demo)
@see [Design Guidelines](https://design.procore.com/dropdown-flyout)
*/
export var DropdownFlyout = /*#__PURE__*/React.forwardRef(function DropdownFlyout(_ref5, ref) {
var disabled = _ref5.disabled,
icon = _ref5.icon,
label = _ref5.label,
loading = _ref5.loading,
_options = _ref5.options,
_ref5$onClick = _ref5.onClick,
_onClick = _ref5$onClick === void 0 ? noop : _ref5$onClick,
_ref5$optionRenderer = _ref5.optionRenderer,
optionRenderer = _ref5$optionRenderer === void 0 ? defaultOptionRenderer : _ref5$optionRenderer,
children = _ref5.children,
_ref5$placement = _ref5.placement,
placement = _ref5$placement === void 0 ? 'right-bottom' : _ref5$placement,
_onKeyDown = _ref5.onKeyDown,
onFocus_ = _ref5.onFocus,
onBlur_ = _ref5.onBlur,
onMouseDown_ = _ref5.onMouseDown,
onMouseEnter_ = _ref5.onMouseEnter,
onMouseLeave_ = _ref5.onMouseLeave,
size = _ref5.size,
_ref5$variant = _ref5.variant,
variant = _ref5$variant === void 0 ? 'secondary' : _ref5$variant,
props = _objectWithoutProperties(_ref5, _excluded3);
var innerRef = React.useRef(null);
var containerRef = ref || innerRef;
var overlayElRef = React.useRef(null);
var targetRef = React.useRef(null);
var flyoutOptions = React.useMemo(function () {
return transformOption(_options, rootId);
}, [_options]);
var _useDropdownFlyout = useDropdownFlyout({
flyoutOptions: flyoutOptions,
onClick: _onClick,
onKeyDown: _onKeyDown
}),
options = _useDropdownFlyout.options,
collapse = _useDropdownFlyout.collapse,
expand = _useDropdownFlyout.expand,
closeSelectedDropdown = _useDropdownFlyout.closeSelectedDropdown,
openDropdown = _useDropdownFlyout.openDropdown,
isOpen = _useDropdownFlyout.isOpen,
onClick = _useDropdownFlyout.onClick,
onKeyDown = _useDropdownFlyout.onKeyDown,
setMouseOver = _useDropdownFlyout.setMouseOver,
setFocused = _useDropdownFlyout.setFocused,
isExpanded = _useDropdownFlyout.isExpanded,
isHighlighted = _useDropdownFlyout.isHighlighted,
closeDropdown = _useDropdownFlyout.closeDropdown;
useClickOutside({
onClickOutside: isOpen ? closeSelectedDropdown : noop,
refs: [containerRef]
});
var element = typeof children === 'function' ? children({
isOpen: isOpen
}) : children;
var I18n = useI18nContext();
var ariaLabel = props['aria-label'] || label || I18n.t("core.dropdown.".concat(loading ? 'loading' : 'moreOptions'));
var trigger = /*#__PURE__*/React.isValidElement(element) ? element : /*#__PURE__*/React.createElement(DefaultButton, {
"aria-label": ariaLabel,
"aria-expanded": isOpen,
children: label,
disabled: disabled,
loading: loading,
icon: icon,
size: size,
variant: variant
});
function onBlur(e) {
setFocused(false);
closeSelectedDropdown(e);
onBlur_ === null || onBlur_ === void 0 ? void 0 : onBlur_(e);
}
function onFocus(e) {
setFocused(true);
onFocus_ === null || onFocus_ === void 0 ? void 0 : onFocus_(e);
}
function onMouseDown(e) {
var _containerRef$current;
// Discard firing of blur event
// when click was inside the dropdown
if (e.target.dataset.flyout) {
e.preventDefault();
return;
}
if (e.target instanceof Node && (_containerRef$current = containerRef.current) !== null && _containerRef$current !== void 0 && _containerRef$current.contains(e.target)) {
var _targetRef$current;
e.preventDefault();
(_targetRef$current = targetRef.current) === null || _targetRef$current === void 0 ? void 0 : _targetRef$current.focus();
}
onMouseDown_ === null || onMouseDown_ === void 0 ? void 0 : onMouseDown_(e);
}
function onMouseLeave(e) {
setMouseOver(false);
onMouseLeave_ === null || onMouseLeave_ === void 0 ? void 0 : onMouseLeave_(e);
}
function onMouseEnter(e) {
setMouseOver(true);
onMouseEnter_ === null || onMouseEnter_ === void 0 ? void 0 : onMouseEnter_(e);
}
var _React$useState3 = React.useState(null),
_React$useState4 = _slicedToArray(_React$useState3, 2),
referenceEl = _React$useState4[0],
setReferenceEl = _React$useState4[1];
// mergeRefs function is from shared utils.
// eslint-disable-next-line react-hooks/exhaustive-deps
var memoRef = useCallback(trigger.ref ? mergeRefs(trigger.ref, targetRef, setReferenceEl) : mergeRefs(targetRef, setReferenceEl), [targetRef, setReferenceEl, trigger.ref]);
return /*#__PURE__*/React.createElement("div", _extends({}, props, {
"aria-label": undefined,
ref: containerRef,
onKeyDown: onKeyDown,
onMouseEnter: onMouseEnter,
onMouseLeave: onMouseLeave,
onFocus: onFocus,
onMouseDown: onMouseDown,
onBlur: onBlur
}), /*#__PURE__*/React.cloneElement(trigger, {
ref: memoRef,
onClick: function onClick(e) {
var _trigger$props$onClic, _trigger$props;
(_trigger$props$onClic = (_trigger$props = trigger.props).onClick) === null || _trigger$props$onClic === void 0 ? void 0 : _trigger$props$onClic.call(_trigger$props, e);
if (isOpen) {
closeDropdown();
} else {
openDropdown();
}
},
tabIndex: 0
}), /*#__PURE__*/React.createElement(DropdownFlyoutContext.Provider, {
value: {
expand: expand,
collapse: collapse,
onClick: onClick,
isExpanded: isExpanded,
isHighlighted: isHighlighted,
optionRenderer: optionRenderer
}
}, isOpen && /*#__PURE__*/React.createElement(Portal, null, /*#__PURE__*/React.createElement(FlyoutList, {
options: options,
overlayRef: overlayElRef,
placement: placement,
referenceEl: referenceEl,
offset: 4
}))));
});
//# sourceMappingURL=DropdownFlyout.js.map