import React from 'react'; import PropTypes from 'prop-types'; import { forbidExtraProps } from 'airbnb-prop-types'; import { css, withStyles, withStylesPropTypes } from 'react-with-styles'; import { DayPickerKeyboardShortcutsPhrases } from '../defaultPhrases'; import getPhrasePropTypes from '../utils/getPhrasePropTypes'; import KeyboardShortcutRow from './KeyboardShortcutRow'; import CloseButton from './CloseButton'; export const TOP_LEFT = 'top-left'; export const TOP_RIGHT = 'top-right'; export const BOTTOM_RIGHT = 'bottom-right'; const propTypes = forbidExtraProps({ ...withStylesPropTypes, block: PropTypes.bool, // TODO: rename button location to be direction-agnostic buttonLocation: PropTypes.oneOf([TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT]), showKeyboardShortcutsPanel: PropTypes.bool, openKeyboardShortcutsPanel: PropTypes.func, closeKeyboardShortcutsPanel: PropTypes.func, phrases: PropTypes.shape(getPhrasePropTypes(DayPickerKeyboardShortcutsPhrases)), renderKeyboardShortcutsButton: PropTypes.func, renderKeyboardShortcutsPanel: PropTypes.func, }); const defaultProps = { block: false, buttonLocation: BOTTOM_RIGHT, showKeyboardShortcutsPanel: false, openKeyboardShortcutsPanel() {}, closeKeyboardShortcutsPanel() {}, phrases: DayPickerKeyboardShortcutsPhrases, renderKeyboardShortcutsButton: undefined, renderKeyboardShortcutsPanel: undefined, }; function getKeyboardShortcuts(phrases) { return [ { unicode: '↵', label: phrases.enterKey, action: phrases.selectFocusedDate, }, { unicode: '←/→', label: phrases.leftArrowRightArrow, action: phrases.moveFocusByOneDay, }, { unicode: '↑/↓', label: phrases.upArrowDownArrow, action: phrases.moveFocusByOneWeek, }, { unicode: 'PgUp/PgDn', label: phrases.pageUpPageDown, action: phrases.moveFocusByOneMonth, }, { unicode: 'Home/End', label: phrases.homeEnd, action: phrases.moveFocustoStartAndEndOfWeek, }, { unicode: 'Esc', label: phrases.escape, action: phrases.returnFocusToInput, }, { unicode: '?', label: phrases.questionMark, action: phrases.openThisPanel, }, ]; } class DayPickerKeyboardShortcuts extends React.PureComponent { constructor(...args) { super(...args); const { phrases } = this.props; this.keyboardShortcuts = getKeyboardShortcuts(phrases); this.onShowKeyboardShortcutsButtonClick = this.onShowKeyboardShortcutsButtonClick.bind(this); this.setShowKeyboardShortcutsButtonRef = this.setShowKeyboardShortcutsButtonRef.bind(this); this.setHideKeyboardShortcutsButtonRef = this.setHideKeyboardShortcutsButtonRef.bind(this); this.handleFocus = this.handleFocus.bind(this); this.onKeyDown = this.onKeyDown.bind(this); } componentWillReceiveProps(nextProps) { const { phrases } = this.props; if (nextProps.phrases !== phrases) { this.keyboardShortcuts = getKeyboardShortcuts(nextProps.phrases); } } componentDidUpdate() { this.handleFocus(); } onKeyDown(e) { e.stopPropagation(); const { closeKeyboardShortcutsPanel } = this.props; // Because the close button is the only focusable element inside of the panel, this // amounts to a very basic focus trap. The user can exit the panel by "pressing" the // close button or hitting escape switch (e.key) { case 'Escape': closeKeyboardShortcutsPanel(); break; // do nothing - this allows the up and down arrows continue their // default behavior of scrolling the content of the Keyboard Shortcuts Panel // which is needed when only a single month is shown for instance. case 'ArrowUp': case 'ArrowDown': break; // completely block the rest of the keys that have functionality outside of this panel case 'Tab': case 'Home': case 'End': case 'PageUp': case 'PageDown': case 'ArrowLeft': case 'ArrowRight': e.preventDefault(); break; default: break; } } onShowKeyboardShortcutsButtonClick() { const { openKeyboardShortcutsPanel } = this.props; // we want to return focus to this button after closing the keyboard shortcuts panel openKeyboardShortcutsPanel(() => { this.showKeyboardShortcutsButton.focus(); }); } setShowKeyboardShortcutsButtonRef(ref) { this.showKeyboardShortcutsButton = ref; } setHideKeyboardShortcutsButtonRef(ref) { this.hideKeyboardShortcutsButton = ref; } handleFocus() { if (this.hideKeyboardShortcutsButton) { // automatically move focus into the dialog by moving // to the only interactive element, the hide button this.hideKeyboardShortcutsButton.focus(); } } render() { const { block, buttonLocation, showKeyboardShortcutsPanel, closeKeyboardShortcutsPanel, styles, phrases, renderKeyboardShortcutsButton, renderKeyboardShortcutsPanel, } = this.props; const toggleButtonText = showKeyboardShortcutsPanel ? phrases.hideKeyboardShortcutsPanel : phrases.showKeyboardShortcutsPanel; const bottomRight = buttonLocation === BOTTOM_RIGHT; const topRight = buttonLocation === TOP_RIGHT; const topLeft = buttonLocation === TOP_LEFT; return (