@procore/core-react
Version:
React library of Procore Design Guidelines
378 lines (368 loc) • 17.5 kB
JavaScript
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
var _excluded = ["children", "UNSAFE_closesOnSelect", "isSelectable", "keyHandlerRef", "onScrollBottom", "onSelect", "onSearch", "scrollable", "usingHook"],
_excluded2 = ["children", "item"],
_excluded3 = ["children", "index", "item", "onClick", "selected", "suggested", "onMouseMove"],
_excluded4 = ["className", "i18nScope", "placeholder", "onChange"],
_excluded5 = ["padding"];
function _objectDestructuringEmpty(t) { if (null == t) throw new TypeError("Cannot destructure " + t); }
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 ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
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 React from 'react';
import { StyledFooter, StyledGroup, StyledHeader, StyledItem, StyledMenu, StyledOptions, StyledSearch, StyledWrapper } from '../MenuImperative/MenuImperative.styles';
import { useOverlayTriggerContext } from '../OverlayTrigger/OverlayTrigger';
import { Typeahead } from '../Typeahead';
import { useEventListener } from '../_hooks/EventListener';
import { useI18nContext } from '../_hooks/I18n';
import { useListNavigation } from '../_hooks/ListNavigation';
import { addSubcomponents } from '../_utils/addSubcomponents';
import { usingHookOrDefault } from '../_utils/usingHookOrDefault';
var onScrollBottomThreshold = 8;
export var isItem = function isItem(element) {
return element.type === Item;
};
function scrollIntoView(index, element) {
if (!element.parentElement) {
return;
}
if (index === 0) {
element.parentElement.scrollTop = 0;
return;
}
var offsetTop = element.offsetTop,
clientHeight = element.clientHeight;
var _element$parentElemen = element.parentElement,
scrollTop = _element$parentElemen.scrollTop,
parentHeight = _element$parentElemen.clientHeight;
if (offsetTop < scrollTop) {
element.parentElement.scrollTop = offsetTop;
} else if (offsetTop + clientHeight > scrollTop + parentHeight) {
element.parentElement.scrollTop = offsetTop - parentHeight + clientHeight;
}
}
function checkBottomScrollPosition(callback) {
return function onScroll(e) {
if (callback && e.currentTarget instanceof HTMLElement && e.currentTarget.scrollTop >= e.currentTarget.scrollHeight - e.currentTarget.clientHeight - onScrollBottomThreshold) {
callback(e);
}
};
}
function noop() {}
export function useMenu(_ref) {
var _ref$isSelectable = _ref.isSelectable,
isSelectable = _ref$isSelectable === void 0 ? isItem : _ref$isSelectable,
externalKeyHandlerRef = _ref.keyHandlerRef,
_ref$multiple = _ref.multiple,
multiple = _ref$multiple === void 0 ? false : _ref$multiple,
onScrollBottom = _ref.onScrollBottom,
_ref$onSearch = _ref.onSearch,
onSearch = _ref$onSearch === void 0 ? noop : _ref$onSearch,
_ref$onSelect = _ref.onSelect,
onSelect = _ref$onSelect === void 0 ? noop : _ref$onSelect,
_ref$scrollable = _ref.scrollable,
scrollable = _ref$scrollable === void 0 ? true : _ref$scrollable;
var listNavigation = useListNavigation({
circular: !onScrollBottom,
initialIndex: 0,
size: 0
});
var highlighted = React.useRef(null);
var keyHandlerRef = externalKeyHandlerRef || /*#__PURE__*/React.createRef();
var selectItem = React.useCallback(function (selection) {
onSelect(selection);
}, [onSelect]);
var _onKeyDown = React.useCallback(function (e) {
var key = e.key;
if (key === 'ArrowDown' || key === 'Down') {
listNavigation.increment();
e.preventDefault();
} else if (key === 'ArrowUp' || key === 'Up') {
listNavigation.decrement();
e.preventDefault();
} else if (key === 'Enter') {
if (highlighted.current) {
highlighted.current.click();
}
}
}, [listNavigation]);
// TODO - deprecate keyhandlerRef, this is just for backwards compatibility since we already exposed it
React.useEffect(function () {
var el;
if (keyHandlerRef.current) {
keyHandlerRef.current.addEventListener('keydown', _onKeyDown);
el = keyHandlerRef.current;
}
return function () {
if (el) {
el.removeEventListener('keydown', _onKeyDown);
}
};
}, [keyHandlerRef, _onKeyDown]);
return {
domHandlers: {
onKeyDown: function onKeyDown(e) {
return _onKeyDown(e.nativeEvent);
}
},
highlighted: highlighted,
isSelectable: isSelectable,
listNavigation: listNavigation,
multiple: multiple,
onScrollBottom: onScrollBottom,
onSearch: onSearch,
scrollable: scrollable,
selectItem: selectItem
};
}
export var MenuHookContext = /*#__PURE__*/React.createContext(null);
export function useMenuHookContext() {
var context = React.useContext(MenuHookContext);
if (context === null) {
throw new Error('Cannot find `MenuHook` context, please wrap your component in `<MenuHookContext.Provider>`');
}
return context;
}
export var Menu_ = /*#__PURE__*/React.forwardRef(function Menu(_ref2, ref) {
var children = _ref2.children,
_ref2$UNSAFE_closesOn = _ref2.UNSAFE_closesOnSelect,
UNSAFE_closesOnSelect = _ref2$UNSAFE_closesOn === void 0 ? true : _ref2$UNSAFE_closesOn,
_ref2$isSelectable = _ref2.isSelectable,
isSelectable = _ref2$isSelectable === void 0 ? isItem : _ref2$isSelectable,
keyHandlerRef = _ref2.keyHandlerRef,
onScrollBottom = _ref2.onScrollBottom,
_ref2$onSelect = _ref2.onSelect,
onSelect = _ref2$onSelect === void 0 ? noop : _ref2$onSelect,
onSearch = _ref2.onSearch,
_ref2$scrollable = _ref2.scrollable,
scrollable = _ref2$scrollable === void 0 ? true : _ref2$scrollable,
usingHook = _ref2.usingHook,
props = _objectWithoutProperties(_ref2, _excluded);
// Don't be tempted to put this in useMenu. In order for hooks to be portable
// across the dom tree, they should not contain context consumers.
var overlayTriggerContext = useOverlayTriggerContext();
var menu = usingHookOrDefault(usingHook, useMenu)({
isSelectable: isSelectable,
keyHandlerRef: keyHandlerRef,
onScrollBottom: onScrollBottom,
onSelect: onSelect,
onSearch: onSearch,
scrollable: scrollable
});
React.useEffect(function () {
// When unmounting, reset the listNavigation index and set
// the current highlighted item to null. we only want to run this once,
// so don't pass any dependencies
return function () {
menu.listNavigation.reset();
menu.highlighted.current = null;
};
}, /* eslint-disable */[] /* eslint-enable */);
function selectItem(selection) {
menu.selectItem(selection);
if (UNSAFE_closesOnSelect) {
overlayTriggerContext.hide(selection.event);
}
}
return /*#__PURE__*/React.createElement(MenuHookContext.Provider, {
value: _objectSpread(_objectSpread({}, menu), {}, {
selectItem: selectItem
})
}, /*#__PURE__*/React.createElement(StyledWrapper, _extends({}, menu.domHandlers, {
ref: ref,
role: "menu",
tabIndex: 0
}), /*#__PURE__*/React.createElement(StyledMenu, props, children)));
});
export var Group = /*#__PURE__*/React.forwardRef(function Group(_ref3, ref) {
var children = _ref3.children,
item = _ref3.item,
props = _objectWithoutProperties(_ref3, _excluded2);
return /*#__PURE__*/React.createElement(StyledGroup, _extends({}, props, {
ref: ref
// onClick={(event) => {
// TODO: implement selecting groups?
// }}
}), children);
});
export var Item = /*#__PURE__*/React.forwardRef(function Item(_ref4, ref) {
var children = _ref4.children,
_ref4$index = _ref4.index,
index = _ref4$index === void 0 ? 0 : _ref4$index,
item = _ref4.item,
_ref4$onClick = _ref4.onClick,
_onClick = _ref4$onClick === void 0 ? noop : _ref4$onClick,
_ref4$selected = _ref4.selected,
selected = _ref4$selected === void 0 ? false : _ref4$selected,
_ref4$suggested = _ref4.suggested,
suggested = _ref4$suggested === void 0 ? false : _ref4$suggested,
_onMouseMove = _ref4.onMouseMove,
props = _objectWithoutProperties(_ref4, _excluded3);
var _useMenuHookContext = useMenuHookContext(),
highlighted = _useMenuHookContext.highlighted,
listNavigation = _useMenuHookContext.listNavigation,
multiple = _useMenuHookContext.multiple,
selectItem = _useMenuHookContext.selectItem;
var itemRef =
// eslint-disable-next-line react-hooks/rules-of-hooks
ref || React.useRef();
var isHighlighted = index === listNavigation.index;
React.useEffect(function () {
// When mounting, check if this item is selected or suggested, and if
// it is, then set it as the current index and scroll to it.
// We only want to run this once, so don't pass any dependencies
if (multiple) {
// don't do this behavior in multiple mode
return;
}
if (itemRef.current && (selected || suggested)) {
highlighted.current = itemRef.current;
listNavigation.set(index);
}
}, /* eslint-disable */[] /* eslint-enable */);
React.useEffect(function () {
// If this item has become highlighted, scroll to it
if (itemRef.current && isHighlighted) {
highlighted.current = itemRef.current;
scrollIntoView(index, itemRef.current);
}
}, [highlighted, index, isHighlighted, itemRef]);
return /*#__PURE__*/React.createElement(StyledItem, _extends({}, props, {
onClick: function onClick(event) {
listNavigation.set(index);
selectItem({
event: event.nativeEvent,
item: item,
group: false
});
_onClick(event);
},
onMouseMove: function onMouseMove(event) {
if (_onMouseMove) {
_onMouseMove(event);
}
if (listNavigation.index !== index) {
listNavigation.set(index);
}
},
ref: itemRef,
role: "menuitem",
$highlighted: isHighlighted,
$selected: selected
}), children);
});
export var Options = /*#__PURE__*/React.forwardRef(function Options(_ref5, ref) {
var children = _ref5.children,
className = _ref5.className,
_ref5$scrollable = _ref5.scrollable,
scrollable = _ref5$scrollable === void 0 ? true : _ref5$scrollable;
var _useMenuHookContext2 = useMenuHookContext(),
listNavigation = _useMenuHookContext2.listNavigation,
isSelectable = _useMenuHookContext2.isSelectable,
onScrollBottom = _useMenuHookContext2.onScrollBottom,
menuScrollable = _useMenuHookContext2.scrollable;
var optionsRef = ref || /*#__PURE__*/React.createRef();
var size = React.Children.toArray(children).filter(isSelectable).length;
React.useEffect(function () {
listNavigation.setSize(size);
}, [listNavigation, size]);
useEventListener({
event: 'scroll',
handler: checkBottomScrollPosition(onScrollBottom),
scope: optionsRef
});
var index = 0;
return /*#__PURE__*/React.createElement(StyledOptions, {
className: className,
ref: optionsRef,
$scrollable: menuScrollable || scrollable
}, React.Children.map(children, function (child) {
if ( /*#__PURE__*/React.isValidElement(child) && isSelectable(child)) {
var selectableIndex = index;
index = index + 1;
return /*#__PURE__*/React.cloneElement(child, {
index: selectableIndex
});
}
return child;
}));
});
export var Search = function Search(_ref6) {
var className = _ref6.className,
_ref6$i18nScope = _ref6.i18nScope,
i18nScope = _ref6$i18nScope === void 0 ? 'core.menu' : _ref6$i18nScope,
placeholder = _ref6.placeholder,
_onChange = _ref6.onChange,
props = _objectWithoutProperties(_ref6, _excluded4);
var I18n = useI18nContext();
var menu = useMenuHookContext();
var _React$useState = React.useState(''),
_React$useState2 = _slicedToArray(_React$useState, 2),
value = _React$useState2[0],
setValue = _React$useState2[1];
var onChange = React.useCallback(function (value, event) {
var _menu$onSearch;
setValue(event.target.value);
(_menu$onSearch = menu.onSearch) === null || _menu$onSearch === void 0 ? void 0 : _menu$onSearch.call(menu, event);
menu.listNavigation.set(0);
_onChange === null || _onChange === void 0 ? void 0 : _onChange(event);
}, [_onChange]);
return /*#__PURE__*/React.createElement(StyledSearch, {
className: className
}, /*#__PURE__*/React.createElement(Typeahead, _extends({}, props, {
autoFocus: true,
placeholder: placeholder || I18n.t('search', {
scope: i18nScope
}),
onChange: onChange,
value: value
})));
};
export var Header = /*#__PURE__*/React.forwardRef(function Header(_ref7, ref) {
var props = _extends({}, (_objectDestructuringEmpty(_ref7), _ref7));
return /*#__PURE__*/React.createElement(StyledHeader, _extends({
ref: ref
}, props));
});
export var Footer = /*#__PURE__*/React.forwardRef(function Footer(_ref8, ref) {
var _ref8$padding = _ref8.padding,
padding = _ref8$padding === void 0 ? 'md lg' : _ref8$padding,
props = _objectWithoutProperties(_ref8, _excluded5);
return /*#__PURE__*/React.createElement(StyledFooter, _extends({
ref: ref,
padding: padding
}, props));
});
Menu_.displayName = 'Menu';
Footer.displayName = 'Menu.Footer';
Group.displayName = 'Menu.Group';
Header.displayName = 'Menu.Header';
Item.displayName = 'Menu.Item';
Options.displayName = 'Menu.Options';
Search.displayName = 'Menu.Search';
/**
Menus are used in conjunction with components that contain dropdowns.
For example, multi select, single select, and dropdown.
@since 10.19.0
@see [Storybook](https://stories.core.procore.com/?path=/story/core-react_demos-menu--demo)
@see [Design Guidelines](https://design.procore.com/menu)
*/
export var Menu = addSubcomponents({
Footer: Footer,
Group: Group,
Header: Header,
Item: Item,
Options: Options,
Search: Search
}, Menu_);
//# sourceMappingURL=Menu.js.map