UNPKG

@workday/canvas-kit-labs-react-menu

Version:

A container for navigation or action items

253 lines (252 loc) 11.2 kB
var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(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); }; 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 * as React from 'react'; import styled from '@emotion/styled'; import uuid from 'uuid/v4'; import { Card } from '@workday/canvas-kit-react-card'; import { commonColors, spacing, borderRadius } from '@workday/canvas-kit-react-core'; import { hideMouseFocus } from '@workday/canvas-kit-react-common'; var List = styled('ul')(__assign({ background: commonColors.background, borderRadius: borderRadius.m, padding: 0, margin: spacing.xxs + " 0", '&:focus': { outline: 'none', } }, hideMouseFocus)); var Menu = (function (_super) { __extends(Menu, _super); function Menu(props) { var _this = _super.call(this, props) || this; _this.id = uuid(); _this.getNormalizedItemIndex = function (index) { var itemCount = React.Children.count(_this.props.children); var firstItem = 0; var lastItem = itemCount - 1; if (!index) { return firstItem; } return index < 0 ? firstItem : index >= itemCount ? lastItem : index; }; _this.setNormalizedItemIndex = function (index) { _this.setState({ selectedItemIndex: _this.getNormalizedItemIndex(index) }); }; _this.handleKeyboardShortcuts = function (event) { if (event.ctrlKey || event.altKey || event.metaKey) { return; } var children = React.Children.toArray(_this.props.children); var nextSelectedIndex = 0; var isShortcut = false; var itemCount = children.length; var firstItem = 0; var lastItem = itemCount - 1; if (event.key.length === 1 && event.key.match(/\S/)) { var start = _this.state.selectedItemIndex + 1; var searchIndex = void 0; if (start === children.length) { start = 0; } searchIndex = _this.getIndexFirstChars(start, event.key.toLowerCase()); if (searchIndex === -1) { searchIndex = _this.getIndexFirstChars(0, event.key.toLowerCase(), start); } if (searchIndex > -1) { isShortcut = true; nextSelectedIndex = searchIndex; } } else { switch (event.key) { case 'ArrowUp': case 'ArrowDown': case 'Down': case 'Up': var direction = event.key === 'ArrowUp' ? -1 : 1; isShortcut = true; var nextIndex = _this.state.selectedItemIndex + direction; nextSelectedIndex = nextIndex < 0 ? lastItem : nextIndex >= itemCount ? firstItem : nextIndex; break; case 'Home': case 'End': var skipTo = event.key === 'Home' ? firstItem : lastItem; isShortcut = true; nextSelectedIndex = skipTo; break; case 'Tab': if (_this.props.onClose) { _this.props.onClose(); } break; case 'Escape': case 'Esc': isShortcut = true; if (_this.props.onClose) { _this.props.onClose(); } break; case 'Spacebar': case ' ': case 'Enter': nextSelectedIndex = _this.state.selectedItemIndex; var child = children[_this.state.selectedItemIndex]; _this.handleClick(event, child.props); isShortcut = true; break; default: } } if (isShortcut) { _this.setNormalizedItemIndex(nextSelectedIndex); event.stopPropagation(); event.preventDefault(); } }; _this.handleClick = function (event, menuItemProps) { if (menuItemProps.isDisabled) { return; } if (menuItemProps.onClick) { menuItemProps.onClick(event); } if (_this.props.onSelect) { _this.props.onSelect(); } if (_this.props.onClose) { if (menuItemProps.shouldClose) { _this.props.onClose(); } } }; _this.getIndexFirstChars = function (startIndex, character, lastIndex) { if (lastIndex === void 0) { lastIndex = _this.firstCharacters.length; } for (var i = startIndex; i < lastIndex; i++) { if (character === _this.firstCharacters[i]) { return i; } } return -1; }; _this.setFirstCharacters = function () { var getFirstCharacter = function (child) { var character = ''; if (!child || typeof child === 'boolean' || child === {}) { character = ''; } else if (typeof child === 'string' || typeof child === 'number') { character = child .toString() .trim() .substring(0, 1) .toLowerCase(); } else if (Array.isArray(child) && child[0]) { character = getFirstCharacter(child[0]); } else if ('props' in child) { var children = child.props.children; if (Array.isArray(children)) { character = getFirstCharacter(children[0]); } else { character = getFirstCharacter(children); } } return character; }; var firstCharacters = React.Children.map(_this.props.children, function (child) { return getFirstCharacter(child); }); _this.firstCharacters = firstCharacters; }; _this.getInitialSelectedItem = function () { var selected = _this.props.initialSelectedItem || 0; selected = selected < 0 ? React.Children.count(_this.props.children) + selected : selected; if (selected < 0) { selected = 0; } else if (selected > React.Children.count(_this.props.children) - 1) { selected = React.Children.count(_this.props.children) - 1; } return selected; }; _this.setInitialSelectedItem = function () { var selected = _this.getInitialSelectedItem(); _this.setState({ selectedItemIndex: selected }); }; _this.menuRef = React.createRef(); var selected = _this.getInitialSelectedItem(); _this.state = { selectedItemIndex: selected, }; return _this; } Menu.prototype.componentDidUpdate = function (prevProps) { var _this = this; if (this.props.children !== prevProps.children) { this.setFirstCharacters(); this.setInitialSelectedItem(); } if (this.props.isOpen && !prevProps.isOpen) { this.setInitialSelectedItem(); } this.animateId = requestAnimationFrame(function () { if (_this.props.isOpen && _this.menuRef.current) { _this.menuRef.current.focus(); } }); }; Menu.prototype.componentDidMount = function () { this.setFirstCharacters(); this.setInitialSelectedItem(); }; Menu.prototype.componentWillUnmount = function () { cancelAnimationFrame(this.animateId); }; Menu.prototype.render = function () { var _this = this; var _a = this.props, _b = _a.id, id = _b === void 0 ? this.id : _b, _c = _a.isOpen, isOpen = _c === void 0 ? true : _c, children = _a.children, ariaLabelledby = _a["aria-labelledby"], grow = _a.grow, width = _a.width, onSelect = _a.onSelect, onClose = _a.onClose, initialSelectedItem = _a.initialSelectedItem, elemProps = __rest(_a, ["id", "isOpen", "children", 'aria-labelledby', "grow", "width", "onSelect", "onClose", "initialSelectedItem"]); var selectedItemIndex = this.state.selectedItemIndex; var cardWidth = grow ? '100%' : width; return (React.createElement(Card, { style: { display: 'inline-block' }, padding: spacing.zero, width: cardWidth }, React.createElement(List, __assign({ role: "menu", tabIndex: 0, id: id, "aria-labelledby": ariaLabelledby, "aria-activedescendant": id + "-" + selectedItemIndex, onKeyDown: this.handleKeyboardShortcuts, ref: this.menuRef }, elemProps), React.Children.map(children, function (menuItem, index) { var itemId = id + "-" + index; return (React.createElement(React.Fragment, { key: itemId }, React.cloneElement(menuItem, { onClick: function (event) { return _this.handleClick(event, menuItem.props); }, id: itemId, isFocused: selectedItemIndex === index, }))); })))); }; return Menu; }(React.Component)); export default Menu;