UNPKG

@procore/core-react

Version:
412 lines (405 loc) • 19.3 kB
var _excluded = ["value"], _excluded2 = ["block", "children", "className", "clearRef", "disabled", "error", "focus", "label", "loading", "onClear", "open", "placeholder", "qa", "tabIndex", "isMenuOpened", "isListboxOnly", "menuId", "aria-expanded", "style"], _excluded3 = ["onFocus", "onBlur", "aria-labelledby"], _excluded4 = ["onSearch", "header", "footer", "emptyMessage", "optionsRef", "children", "i18nScope", "onSelect", "menuRef", "menuId"], _excluded5 = ["afterHide", "afterShow", "beforeHide", "beforeShow", "block", "children", "className", "container", "disabled", "emptyMessage", "error", "header", "footer", "hideDelay", "i18nScope", "label", "loading", "onClear", "onKeyDown", "onScrollBottom", "onSearch", "onSelect", "optionsRef", "placeholder", "placement", "restoreFocusOnHide", "showDelay", "tabIndex", "qa"]; 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 _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; } 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); } import { Clear } from '@procore/core-icons/dist'; import { useId } from '@react-aria/utils'; import classNames from 'classnames'; import { isNil } from 'ramda'; import React from 'react'; import { Box } from '../Box'; import { MenuImperative, useMenuImperativeControlNavigation } from '../MenuImperative/MenuImperative'; import { Notation } from '../Notation'; import { OverlayTrigger, useOverlayTriggerContext } from '../OverlayTrigger/OverlayTrigger'; import { Spinner } from '../Spinner'; import { Tooltip } from '../Tooltip'; import { useI18nContext } from '../_hooks/I18n'; import { OverflowObserver } from '../_hooks/OverflowObserver'; import { addSubcomponents } from '../_utils/addSubcomponents'; import { StyledSelectArrow, StyledSelectArrowContainer, StyledSelectButton, StyledSelectButtonLabel, StyledSelectButtonWrapper, StyledSelectClearIcon, StyledSelectMenu, StyledSelectSpinner } from './Select.styles'; function noop() {} function isFunction(obj) { return typeof obj === 'function'; } function isOpen(_ref) { var _ref$open = _ref.open, open = _ref$open === void 0 ? false : _ref$open; return open; } export var SelectButtonFocusContext = /*#__PURE__*/React.createContext(false); export var useSelectButtonFocused = function useSelectButtonFocused() { return React.useContext(SelectButtonFocusContext); }; export var OptGroup = /*#__PURE__*/React.forwardRef(function (props, ref) { return /*#__PURE__*/React.createElement(MenuImperative.Group, _extends({}, props, { ref: ref })); }); export var Option = /*#__PURE__*/React.forwardRef(function (_ref2, ref) { var value = _ref2.value, props = _objectWithoutProperties(_ref2, _excluded); return /*#__PURE__*/React.createElement(MenuImperative.Item, _extends({ ref: ref }, props, { item: value })); }); export var Button = /*#__PURE__*/React.forwardRef(function Button(_ref3, ref) { var _ref3$block = _ref3.block, block = _ref3$block === void 0 ? false : _ref3$block, children = _ref3.children, className = _ref3.className, clearRef = _ref3.clearRef, _ref3$disabled = _ref3.disabled, disabled = _ref3$disabled === void 0 ? false : _ref3$disabled, _ref3$error = _ref3.error, error = _ref3$error === void 0 ? false : _ref3$error, _ref3$focus = _ref3.focus, focus = _ref3$focus === void 0 ? false : _ref3$focus, label = _ref3.label, _ref3$loading = _ref3.loading, loading = _ref3$loading === void 0 ? false : _ref3$loading, onClear = _ref3.onClear, _ref3$open = _ref3.open, open = _ref3$open === void 0 ? false : _ref3$open, placeholder = _ref3.placeholder, qa = _ref3.qa, _ref3$tabIndex = _ref3.tabIndex, tabIndex = _ref3$tabIndex === void 0 ? 0 : _ref3$tabIndex, _ref3$isMenuOpened = _ref3.isMenuOpened, isMenuOpened = _ref3$isMenuOpened === void 0 ? false : _ref3$isMenuOpened, _ref3$isListboxOnly = _ref3.isListboxOnly, isListboxOnly = _ref3$isListboxOnly === void 0 ? false : _ref3$isListboxOnly, menuId = _ref3.menuId, ariaExpandedProp = _ref3['aria-expanded'], style = _ref3.style, props = _objectWithoutProperties(_ref3, _excluded2); var i18n = useI18nContext(); var hasClearIcon = Boolean(onClear); var showClearIcon = !disabled && !loading && Boolean(label); var labelId = useId(); var triggerRole = isListboxOnly ? 'combobox' : 'button'; var content = children || label || placeholder; var ariaExpanded = disabled ? undefined : isListboxOnly ? ariaExpandedProp : open; var _React$useState = React.useState(false), _React$useState2 = _slicedToArray(_React$useState, 2), isFocused = _React$useState2[0], setIsFocused = _React$useState2[1]; var _ref4 = props, onFocusProp = _ref4.onFocus, onBlurProp = _ref4.onBlur, ariaLabelledByProp = _ref4['aria-labelledby'], restProps = _objectWithoutProperties(_ref4, _excluded3); var ariaLabelledBy = isListboxOnly ? ariaLabelledByProp || labelId || undefined : [ariaLabelledByProp, labelId].filter(Boolean).join(' ') || undefined; var handleFocus = React.useCallback(function (event) { setIsFocused(true); onFocusProp === null || onFocusProp === void 0 ? void 0 : onFocusProp(event); }, [onFocusProp]); var handleBlur = React.useCallback(function (event) { setIsFocused(false); onBlurProp === null || onBlurProp === void 0 ? void 0 : onBlurProp(event); }, [onBlurProp]); var button = /*#__PURE__*/React.createElement(StyledSelectButton, _extends({ ref: ref, role: triggerRole, "aria-disabled": disabled, tabIndex: disabled ? -1 : tabIndex, "aria-expanded": ariaExpanded, "aria-labelledby": ariaLabelledBy, $block: true // Fill wrapper container , $error: error, $disabled: disabled, $hasClearIcon: hasClearIcon, $showClearIcon: showClearIcon, $loading: loading, $open: open, $placeholder: !label }, restProps, { onFocus: handleFocus, onBlur: handleBlur }), /*#__PURE__*/React.createElement(SelectButtonFocusContext.Provider, { value: isFocused }, /*#__PURE__*/React.createElement(OverflowObserver, null, function (_ref5) { var isOverflowingX = _ref5.isOverflowingX, overflowRef = _ref5.ref; var showTooltip = !disabled && isOverflowingX && !isMenuOpened; var trigger = /*#__PURE__*/React.createElement(StyledSelectButtonLabel, { id: labelId, "data-qa": qa === null || qa === void 0 ? void 0 : qa.label, $hoverable: showTooltip, ref: overflowRef }, content); return showTooltip ? /*#__PURE__*/React.createElement(Tooltip, { key: isFocused ? 'focused' : 'hover-only', trigger: isFocused ? 'always' : 'hover', overlay: content }, trigger) : trigger; })), loading ? /*#__PURE__*/React.createElement(StyledSelectSpinner, null, /*#__PURE__*/React.createElement(Spinner, { color: "blue50", size: "xs" })) : /*#__PURE__*/React.createElement(StyledSelectArrowContainer, null, /*#__PURE__*/React.createElement(StyledSelectArrow, null))); return /*#__PURE__*/React.createElement(StyledSelectButtonWrapper, { $block: block, className: classNames(className, { focus: isFunction(focus) ? focus({ open: open }) : focus }), style: style }, button, onClear ? /*#__PURE__*/React.createElement(StyledSelectClearIcon, { "aria-label": i18n.t('core.select.clear'), ref: clearRef, "data-qa": (qa === null || qa === void 0 ? void 0 : qa.clear) || 'core-select-clear', disabled: disabled, size: "sm", variant: "tertiary", icon: /*#__PURE__*/React.createElement(Clear, null), onClick: disabled ? undefined : onClear, onMouseDown: function onMouseDown(e) { return e.preventDefault(); } // prevents an element from getting the focus , tabIndex: showClearIcon ? 0 : -1 }) : null); }); var SelectMenu = /*#__PURE__*/React.forwardRef(function SelectMenu(_ref6, ref) { var onSearch_ = _ref6.onSearch, header = _ref6.header, footer = _ref6.footer, emptyMessage = _ref6.emptyMessage, optionsRef = _ref6.optionsRef, children = _ref6.children, i18nScope = _ref6.i18nScope, _ref6$onSelect = _ref6.onSelect, onSelect_ = _ref6$onSelect === void 0 ? noop : _ref6$onSelect, menuRef = _ref6.menuRef, menuId = _ref6.menuId, props = _objectWithoutProperties(_ref6, _excluded4); var ctx = useOverlayTriggerContext(); var _React$useState3 = React.useState(''), _React$useState4 = _slicedToArray(_React$useState3, 2), searchValue = _React$useState4[0], setSearchValue = _React$useState4[1]; var _useMenuImperativeCon = useMenuImperativeControlNavigation(menuRef, Boolean(onSearch_)), menuProps = _useMenuImperativeCon.menuProps, menuNavigationTriggerProps = _useMenuImperativeCon.menuNavigationTriggerProps; React.useEffect(function () { var _menuRef$current; (_menuRef$current = menuRef.current) === null || _menuRef$current === void 0 ? void 0 : _menuRef$current.highlightFirst(); }, [menuRef, searchValue]); React.useEffect(function () { var _menuRef$current2, _menuRef$current3; (_menuRef$current2 = menuRef.current) === null || _menuRef$current2 === void 0 ? void 0 : _menuRef$current2.highlightSuggested(); (_menuRef$current3 = menuRef.current) === null || _menuRef$current3 === void 0 ? void 0 : _menuRef$current3.highlightSelected(); }, [menuRef]); function onKeyDown(e) { var _props$onKeyDown; (_props$onKeyDown = props.onKeyDown) === null || _props$onKeyDown === void 0 ? void 0 : _props$onKeyDown.call(props, e); // This is disconnected from the OverlayTrigger closing on Escape. // Closing a dropdown with Escape will not close a Modal, because stopP. // It is called only once because the menu component is unmounted. // The next Escape will propagate. if (e.key === 'Escape' || e.key === 'Esc') { e.stopPropagation(); } } function onSelect(selection) { onSelect_(selection); ctx.hide(selection.event); } function onSearch(event) { setSearchValue(event.target.value); onSearch_ === null || onSearch_ === void 0 ? void 0 : onSearch_(event); } return /*#__PURE__*/React.createElement(StyledSelectMenu, { ref: ref, shadowStrength: 2 }, /*#__PURE__*/React.createElement(MenuImperative, _extends({}, props, menuProps, { ref: menuRef, role: "listbox", onKeyDown: onKeyDown, onSelect: onSelect, id: menuId, circular: true }), onSearch_ && /*#__PURE__*/React.createElement(MenuImperative.Search, _extends({ autoComplete: "false", i18nScope: i18nScope, onChange: onSearch }, menuNavigationTriggerProps)), header && /*#__PURE__*/React.createElement(MenuImperative.Header, null, header), React.Children.count(children) ? /*#__PURE__*/React.createElement(MenuImperative.Options, { ref: optionsRef }, children) : /*#__PURE__*/React.createElement(Box, { padding: "md lg" }, /*#__PURE__*/React.createElement(Notation, { variant: "pagination" }, emptyMessage)), footer && /*#__PURE__*/React.createElement(MenuImperative.Footer, null, footer))); }); var Select_ = /*#__PURE__*/React.forwardRef(function Select(_ref7, forwardRef) { var _ref7$afterHide = _ref7.afterHide, afterHide_ = _ref7$afterHide === void 0 ? noop : _ref7$afterHide, _ref7$afterShow = _ref7.afterShow, afterShow_ = _ref7$afterShow === void 0 ? noop : _ref7$afterShow, _ref7$beforeHide = _ref7.beforeHide, _beforeHide = _ref7$beforeHide === void 0 ? function () { return true; } : _ref7$beforeHide, _ref7$beforeShow = _ref7.beforeShow, beforeShow = _ref7$beforeShow === void 0 ? function () { return true; } : _ref7$beforeShow, _ref7$block = _ref7.block, block = _ref7$block === void 0 ? false : _ref7$block, children = _ref7.children, className = _ref7.className, container = _ref7.container, _ref7$disabled = _ref7.disabled, disabled = _ref7$disabled === void 0 ? false : _ref7$disabled, emptyMessage = _ref7.emptyMessage, _ref7$error = _ref7.error, error = _ref7$error === void 0 ? false : _ref7$error, header = _ref7.header, footer = _ref7.footer, _ref7$hideDelay = _ref7.hideDelay, hideDelay = _ref7$hideDelay === void 0 ? 100 : _ref7$hideDelay, _ref7$i18nScope = _ref7.i18nScope, i18nScope = _ref7$i18nScope === void 0 ? 'core.select' : _ref7$i18nScope, _ref7$label = _ref7.label, label = _ref7$label === void 0 ? '' : _ref7$label, _ref7$loading = _ref7.loading, loading = _ref7$loading === void 0 ? false : _ref7$loading, onClear_ = _ref7.onClear, _ref7$onKeyDown = _ref7.onKeyDown, onKeyDown = _ref7$onKeyDown === void 0 ? noop : _ref7$onKeyDown, onScrollBottom = _ref7.onScrollBottom, onSearch = _ref7.onSearch, _ref7$onSelect = _ref7.onSelect, onSelect = _ref7$onSelect === void 0 ? noop : _ref7$onSelect, optionsRef = _ref7.optionsRef, placeholder = _ref7.placeholder, _ref7$placement = _ref7.placement, placement = _ref7$placement === void 0 ? 'bottom-left' : _ref7$placement, _ref7$restoreFocusOnH = _ref7.restoreFocusOnHide, restoreFocusOnHide = _ref7$restoreFocusOnH === void 0 ? 'core-react' : _ref7$restoreFocusOnH, _ref7$showDelay = _ref7.showDelay, showDelay = _ref7$showDelay === void 0 ? 0 : _ref7$showDelay, _ref7$tabIndex = _ref7.tabIndex, tabIndex = _ref7$tabIndex === void 0 ? 0 : _ref7$tabIndex, qa = _ref7.qa, props = _objectWithoutProperties(_ref7, _excluded5); var i18n = useI18nContext(); var menuRef = React.useRef(null); var overlayTriggerRef = React.useRef(null); var ref = forwardRef || overlayTriggerRef; var _React$useState5 = React.useState(false), _React$useState6 = _slicedToArray(_React$useState5, 2), isMenuOpened = _React$useState6[0], setIsMenuOpened = _React$useState6[1]; var clearRef = React.useRef(null); var menuId = useId(); var isListboxOnly = !onSearch && !header && !footer; var emptyMessageText = emptyMessage !== null && emptyMessage !== void 0 ? emptyMessage : i18n.t('noResult', { scope: i18nScope }); function afterHide() { afterHide_(); setIsMenuOpened(false); onSearch === null || onSearch === void 0 ? void 0 : onSearch({ target: { value: '' } }); } var onClear = React.useCallback(function (e) { var _overlayTriggerRef$cu; onClear_ === null || onClear_ === void 0 ? void 0 : onClear_(e); // IE 11 fix // Return focus back to trigger instead of the clear button overlayTriggerRef === null || overlayTriggerRef === void 0 ? void 0 : (_overlayTriggerRef$cu = overlayTriggerRef.current) === null || _overlayTriggerRef$cu === void 0 ? void 0 : _overlayTriggerRef$cu.focus(); }, [onClear_]); function afterShow() { afterShow_(); setIsMenuOpened(true); } return /*#__PURE__*/React.createElement(OverlayTrigger, { autoFocus: true, afterHide: afterHide, afterShow: afterShow, beforeHide: function beforeHide(e) { if (e.target === clearRef.current) { return false; } return _beforeHide(e); }, beforeShow: beforeShow, canFlip: true, container: container, hideDelay: hideDelay, restoreFocusOnHide: restoreFocusOnHide, role: isListboxOnly ? 'listbox' : undefined, passA11yPropsToOverlay: isListboxOnly, overlayId: menuId, overlay: /*#__PURE__*/React.createElement(SelectMenu, { onSearch: onSearch, header: header, footer: footer, emptyMessage: emptyMessageText, optionsRef: optionsRef, i18nScope: i18nScope, onSelect: onSelect, onScrollBottom: onScrollBottom, menuRef: menuRef, menuId: menuId }, children), placement: placement, ref: ref, showDelay: showDelay, trigger: disabled ? 'none' : 'click' }, /*#__PURE__*/React.createElement(Button, _extends({}, props, { block: block, className: className, clearRef: clearRef, disabled: disabled, error: error, focus: onSearch ? false : isOpen, label: label, loading: loading, placeholder: placeholder, onKeyDown: onKeyDown, onClear: isNil(onClear_) ? onClear_ : onClear, isMenuOpened: isMenuOpened, tabIndex: tabIndex, isListboxOnly: isListboxOnly, menuId: menuId, qa: qa }))); }); Select_.displayName = 'Select'; Button.displayName = 'Select.Button'; Option.displayName = 'Select.Option'; OptGroup.displayName = 'Select.OptGroup'; /** We use single selects to allow our users to choose a single option from a list, presented in a dropdown. We typically see these selects on forms. If you want users to select multiple options, use a multi select, group select, and tiered select if you want users to select from a tiered set of options, use a tiered select. @since 10.19.0 @see [Storybook](https://stories.core.procore.com/?path=/story/core-react_demos-select--demo) @see [Design Guidelines](https://design.procore.com/select) */ export var Select = addSubcomponents({ Button: Button, Option: Option, OptGroup: OptGroup }, Select_); //# sourceMappingURL=Select.js.map