@nteract/dropdown-menu
Version:
Dropdown Menu for nteract apps
192 lines (186 loc) • 6.89 kB
JavaScript
"use strict";
/* eslint jsx-a11y/no-static-element-interactions: 0 */
/* eslint jsx-a11y/click-events-have-key-events: 0 */
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
// react-hot-loader uses proxies to the original elements so we need to use
// their comparison function in case a consumer of these components is
// using hot module reloading
const React = __importStar(require("react"));
const react_hot_loader_1 = require("react-hot-loader");
const styled_components_1 = __importDefault(require("styled-components"));
const DropdownDiv = styled_components_1.default.div `
z-index: 10000;
display: inline-block;
`;
DropdownDiv.displayName = "DropdownDiv";
class DropdownMenu extends React.PureComponent {
constructor(props) {
super(props);
this.listRef = React.createRef();
this.handleKeyUp = (ev) => {
if (!this.state.menuHidden) {
if (ev.key === "Escape") {
this.setState({ menuHidden: true });
}
else if (ev.key === "ArrowDown") {
this.moveListChildFocus(1);
}
else if (ev.key === "ArrowUp") {
this.moveListChildFocus(-1);
}
}
};
this.state = {
menuHidden: true
};
}
componentDidUpdate(prevProps, prevState) {
if (prevState.menuHidden !== this.state.menuHidden &&
this.props.onDisplayChanged) {
this.props.onDisplayChanged(!this.state.menuHidden);
}
}
/***
* Looks at the children of the ul, finds the focused child, and moves the focus the specified amount
*/
moveListChildFocus(amount) {
let ulEl = this.listRef.current;
if (ulEl) {
let activeIndex;
for (let i = 0; i < ulEl.children.length; i++) {
let li = ulEl.children[i];
if (li == document.activeElement) {
activeIndex = i;
break;
}
}
let nextActiveIndex = activeIndex === undefined
? 0
: (activeIndex + amount) % ulEl.children.length;
// Because JS mod can produce negative numbers, we need a negative check also
nextActiveIndex =
nextActiveIndex < 0
? ulEl.children.length + nextActiveIndex
: nextActiveIndex;
let nextItem = ulEl.children[nextActiveIndex];
nextItem.focus();
}
}
render() {
return (React.createElement(DropdownDiv, { onKeyUp: this.handleKeyUp }, React.Children.map(this.props.children, child => {
const childElement = child;
if (react_hot_loader_1.areComponentsEqual(childElement.type, DropdownTrigger)) {
return React.cloneElement(childElement, {
onClick: () => {
this.setState({ menuHidden: !this.state.menuHidden });
}
});
}
else if (react_hot_loader_1.areComponentsEqual(childElement.type, DropdownContent)) {
if (this.state.menuHidden) {
return null;
}
else {
// DropdownContent child will pass down an onItemClick so that
// the menu will collapse
return React.cloneElement(childElement, {
onItemClick: () => {
this.setState({ menuHidden: true });
},
ulRef: this.listRef
});
}
}
else {
// fallback
return child;
}
})));
}
}
exports.DropdownMenu = DropdownMenu;
const DropdownTriggerDiv = styled_components_1.default.div `
user-select: none;
margin: 0px;
padding: 0px;
`;
DropdownTriggerDiv.displayName = "DropdownTriggerDiv";
// tslint:disable max-classes-per-file
class DropdownTrigger extends React.PureComponent {
render() {
return (React.createElement(DropdownTriggerDiv, { onClick: this.props.onClick }, this.props.children));
}
}
exports.DropdownTrigger = DropdownTrigger;
const DropdownContentDiv = styled_components_1.default.div `
user-select: none;
margin: 0px;
padding: 0px;
width: 200px;
opacity: 1;
position: absolute;
top: 0.2em;
right: 0;
border-style: none;
padding: 0;
font-family: var(--nt-font-family-normal);
font-size: var(--nt-font-size-m);
line-height: 1.5;
margin: 20px 0;
background-color: var(--theme-cell-menu-bg);
ul {
list-style: none;
text-align: left;
padding: 0;
margin: 0;
opacity: 1;
}
ul li {
padding: 0.5rem;
}
ul li:hover {
background-color: var(--theme-cell-menu-bg-hover, #e2dfe3);
cursor: pointer;
}
`;
DropdownContentDiv.displayName = "DropdownContentDiv";
class DropdownContent extends React.PureComponent {
render() {
return (React.createElement(DropdownContentDiv, null,
React.createElement("ul", { role: "listbox", "aria-label": "dropdown-content", ref: this.props.ulRef }, React.Children.map(this.props.children, child => {
const childElement = child;
const elRef = React.createRef();
return React.cloneElement(childElement, {
onClick: (ev) => {
childElement.props.onClick(ev);
// Hide the menu
this.props.onItemClick(ev);
},
onKeyUp: (ev) => {
if (childElement.props.onKeyUp) {
childElement.props.onKeyUp(ev); //forward the event
}
if (ev.key === "Enter" && elRef.current) {
elRef.current.click();
}
},
ref: elRef
});
}))));
}
}
exports.DropdownContent = DropdownContent;
DropdownContent.defaultProps = {
// Completely silly standalone, because DropdownMenu injects the onItemClick handler
onItemClick: () => { }
};