From 44a7d87cb1f5df953b6c14c16c59e2e4ead1bcb9 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Mon, 20 Feb 2023 03:20:59 +0100 Subject: Rename JSX files with proper `.jsx` extension (#23733) --- .../mastodon/components/dropdown_menu.jsx | 335 +++++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 app/javascript/mastodon/components/dropdown_menu.jsx (limited to 'app/javascript/mastodon/components/dropdown_menu.jsx') diff --git a/app/javascript/mastodon/components/dropdown_menu.jsx b/app/javascript/mastodon/components/dropdown_menu.jsx new file mode 100644 index 000000000..c04c513fb --- /dev/null +++ b/app/javascript/mastodon/components/dropdown_menu.jsx @@ -0,0 +1,335 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import IconButton from './icon_button'; +import Overlay from 'react-overlays/Overlay'; +import { supportsPassiveEvents } from 'detect-passive-events'; +import classNames from 'classnames'; +import { CircularProgress } from 'mastodon/components/loading_indicator'; + +const listenerOptions = supportsPassiveEvents ? { passive: true } : false; +let id = 0; + +class DropdownMenu extends React.PureComponent { + + static contextTypes = { + router: PropTypes.object, + }; + + static propTypes = { + items: PropTypes.oneOfType([PropTypes.array, ImmutablePropTypes.list]).isRequired, + loading: PropTypes.bool, + scrollable: PropTypes.bool, + onClose: PropTypes.func.isRequired, + style: PropTypes.object, + openedViaKeyboard: PropTypes.bool, + renderItem: PropTypes.func, + renderHeader: PropTypes.func, + onItemClick: PropTypes.func.isRequired, + }; + + static defaultProps = { + style: {}, + }; + + handleDocumentClick = e => { + if (this.node && !this.node.contains(e.target)) { + this.props.onClose(); + } + }; + + componentDidMount () { + document.addEventListener('click', this.handleDocumentClick, false); + document.addEventListener('keydown', this.handleKeyDown, false); + document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); + + if (this.focusedItem && this.props.openedViaKeyboard) { + this.focusedItem.focus({ preventScroll: true }); + } + } + + componentWillUnmount () { + document.removeEventListener('click', this.handleDocumentClick, false); + document.removeEventListener('keydown', this.handleKeyDown, false); + document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); + } + + setRef = c => { + this.node = c; + }; + + setFocusRef = c => { + this.focusedItem = c; + }; + + handleKeyDown = e => { + const items = Array.from(this.node.querySelectorAll('a, button')); + const index = items.indexOf(document.activeElement); + let element = null; + + switch(e.key) { + case 'ArrowDown': + element = items[index+1] || items[0]; + break; + case 'ArrowUp': + element = items[index-1] || items[items.length-1]; + break; + case 'Tab': + if (e.shiftKey) { + element = items[index-1] || items[items.length-1]; + } else { + element = items[index+1] || items[0]; + } + break; + case 'Home': + element = items[0]; + break; + case 'End': + element = items[items.length-1]; + break; + case 'Escape': + this.props.onClose(); + break; + } + + if (element) { + element.focus(); + e.preventDefault(); + e.stopPropagation(); + } + }; + + handleItemKeyPress = e => { + if (e.key === 'Enter' || e.key === ' ') { + this.handleClick(e); + } + }; + + handleClick = e => { + const { onItemClick } = this.props; + onItemClick(e); + }; + + renderItem = (option, i) => { + if (option === null) { + return
  • ; + } + + const { text, href = '#', target = '_blank', method } = option; + + return ( +
  • + + {text} + +
  • + ); + }; + + render () { + const { items, scrollable, renderHeader, loading } = this.props; + + let renderItem = this.props.renderItem || this.renderItem; + + return ( +
    + {loading && ( + + )} + + {!loading && renderHeader && ( +
    + {renderHeader(items)} +
    + )} + + {!loading && ( +
      + {items.map((option, i) => renderItem(option, i, { onClick: this.handleClick, onKeyPress: this.handleItemKeyPress }))} +
    + )} +
    + ); + } + +} + +export default class Dropdown extends React.PureComponent { + + static contextTypes = { + router: PropTypes.object, + }; + + static propTypes = { + children: PropTypes.node, + icon: PropTypes.string, + items: PropTypes.oneOfType([PropTypes.array, ImmutablePropTypes.list]).isRequired, + loading: PropTypes.bool, + size: PropTypes.number, + title: PropTypes.string, + disabled: PropTypes.bool, + scrollable: PropTypes.bool, + status: ImmutablePropTypes.map, + isUserTouching: PropTypes.func, + onOpen: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired, + openDropdownId: PropTypes.number, + openedViaKeyboard: PropTypes.bool, + renderItem: PropTypes.func, + renderHeader: PropTypes.func, + onItemClick: PropTypes.func, + }; + + static defaultProps = { + title: 'Menu', + }; + + state = { + id: id++, + }; + + handleClick = ({ type }) => { + if (this.state.id === this.props.openDropdownId) { + this.handleClose(); + } else { + this.props.onOpen(this.state.id, this.handleItemClick, type !== 'click'); + } + }; + + handleClose = () => { + if (this.activeElement) { + this.activeElement.focus({ preventScroll: true }); + this.activeElement = null; + } + this.props.onClose(this.state.id); + }; + + handleMouseDown = () => { + if (!this.state.open) { + this.activeElement = document.activeElement; + } + }; + + handleButtonKeyDown = (e) => { + switch(e.key) { + case ' ': + case 'Enter': + this.handleMouseDown(); + break; + } + }; + + handleKeyPress = (e) => { + switch(e.key) { + case ' ': + case 'Enter': + this.handleClick(e); + e.stopPropagation(); + e.preventDefault(); + break; + } + }; + + handleItemClick = e => { + const { onItemClick } = this.props; + const i = Number(e.currentTarget.getAttribute('data-index')); + const item = this.props.items[i]; + + this.handleClose(); + + if (typeof onItemClick === 'function') { + e.preventDefault(); + onItemClick(item, i); + } else if (item && typeof item.action === 'function') { + e.preventDefault(); + item.action(); + } else if (item && item.to) { + e.preventDefault(); + this.context.router.history.push(item.to); + } + }; + + setTargetRef = c => { + this.target = c; + }; + + findTarget = () => { + return this.target; + }; + + componentWillUnmount = () => { + if (this.state.id === this.props.openDropdownId) { + this.handleClose(); + } + }; + + close = () => { + this.handleClose(); + }; + + render () { + const { + icon, + items, + size, + title, + disabled, + loading, + scrollable, + openDropdownId, + openedViaKeyboard, + children, + renderItem, + renderHeader, + } = this.props; + + const open = this.state.id === openDropdownId; + + const button = children ? React.cloneElement(React.Children.only(children), { + onClick: this.handleClick, + onMouseDown: this.handleMouseDown, + onKeyDown: this.handleButtonKeyDown, + onKeyPress: this.handleKeyPress, + }) : ( + + ); + + return ( + + + {button} + + + {({ props, arrowProps, placement }) => ( +
    +
    +
    + +
    +
    + )} + + + ); + } + +} -- cgit From ec0c104bf25f2689c31bb79f9f447be1a9b3da7f Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 4 Apr 2023 10:33:44 -0400 Subject: Ensure tabIndex is number instead of string (#24409) --- .../mastodon/components/admin/ReportReasonSelector.jsx | 4 ++-- .../mastodon/components/autosuggest_input.jsx | 2 +- .../mastodon/components/autosuggest_textarea.jsx | 2 +- .../mastodon/components/column_back_button_slim.jsx | 2 +- app/javascript/mastodon/components/dropdown_menu.jsx | 2 +- app/javascript/mastodon/components/gifv.jsx | 4 ++-- app/javascript/mastodon/components/icon_button.jsx | 4 ++-- .../components/intersection_observer_article.jsx | 4 ++-- .../components/picture_in_picture_placeholder.jsx | 2 +- app/javascript/mastodon/components/poll.jsx | 2 +- app/javascript/mastodon/components/status.jsx | 4 ++-- app/javascript/mastodon/components/status_content.jsx | 6 +++--- app/javascript/mastodon/features/about/index.jsx | 2 +- app/javascript/mastodon/features/audio/index.jsx | 8 ++++---- .../features/compose/components/language_dropdown.jsx | 2 +- .../mastodon/features/compose/components/poll_form.jsx | 2 +- .../features/compose/components/privacy_dropdown.jsx | 2 +- .../mastodon/features/compose/components/search.jsx | 2 +- .../mastodon/features/compose/components/upload.jsx | 2 +- .../direct_timeline/components/conversation.jsx | 2 +- .../mastodon/features/filters/select_filter.jsx | 4 ++-- .../features/list_editor/components/search.jsx | 2 +- app/javascript/mastodon/features/list_editor/index.jsx | 2 +- .../mastodon/features/list_timeline/index.jsx | 4 ++-- .../notifications/components/clear_column_button.jsx | 2 +- .../components/grant_permission_button.jsx | 2 +- .../features/notifications/components/notification.jsx | 18 +++++++++--------- .../mastodon/features/report/components/option.jsx | 2 +- app/javascript/mastodon/features/status/index.jsx | 2 +- .../mastodon/features/ui/components/actions_modal.jsx | 2 +- .../mastodon/features/ui/components/media_modal.jsx | 4 ++-- app/javascript/mastodon/features/video/index.jsx | 6 +++--- 32 files changed, 55 insertions(+), 55 deletions(-) (limited to 'app/javascript/mastodon/components/dropdown_menu.jsx') diff --git a/app/javascript/mastodon/components/admin/ReportReasonSelector.jsx b/app/javascript/mastodon/components/admin/ReportReasonSelector.jsx index 58a861fde..cd14dac4e 100644 --- a/app/javascript/mastodon/components/admin/ReportReasonSelector.jsx +++ b/app/javascript/mastodon/components/admin/ReportReasonSelector.jsx @@ -33,7 +33,7 @@ class Category extends React.PureComponent { const { id, text, disabled, selected, children } = this.props; return ( -
    +
    {selected && }
    @@ -74,7 +74,7 @@ class Rule extends React.PureComponent { const { id, text, disabled, selected } = this.props; return ( -
    +
    {selected && } {text} diff --git a/app/javascript/mastodon/components/autosuggest_input.jsx b/app/javascript/mastodon/components/autosuggest_input.jsx index f9616c581..a68e2a01b 100644 --- a/app/javascript/mastodon/components/autosuggest_input.jsx +++ b/app/javascript/mastodon/components/autosuggest_input.jsx @@ -180,7 +180,7 @@ export default class AutosuggestInput extends ImmutablePureComponent { } return ( -
    +
    {inner}
    ); diff --git a/app/javascript/mastodon/components/autosuggest_textarea.jsx b/app/javascript/mastodon/components/autosuggest_textarea.jsx index c04491298..a627bc1ec 100644 --- a/app/javascript/mastodon/components/autosuggest_textarea.jsx +++ b/app/javascript/mastodon/components/autosuggest_textarea.jsx @@ -186,7 +186,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { } return ( -
    +
    {inner}
    ); diff --git a/app/javascript/mastodon/components/column_back_button_slim.jsx b/app/javascript/mastodon/components/column_back_button_slim.jsx index cc8bfb151..46ac23736 100644 --- a/app/javascript/mastodon/components/column_back_button_slim.jsx +++ b/app/javascript/mastodon/components/column_back_button_slim.jsx @@ -8,7 +8,7 @@ export default class ColumnBackButtonSlim extends ColumnBackButton { render () { return (
    -
    +
    diff --git a/app/javascript/mastodon/components/dropdown_menu.jsx b/app/javascript/mastodon/components/dropdown_menu.jsx index c04c513fb..eaaa72fd8 100644 --- a/app/javascript/mastodon/components/dropdown_menu.jsx +++ b/app/javascript/mastodon/components/dropdown_menu.jsx @@ -119,7 +119,7 @@ class DropdownMenu extends React.PureComponent { return (
  • - + {text}
  • diff --git a/app/javascript/mastodon/components/gifv.jsx b/app/javascript/mastodon/components/gifv.jsx index 9ec201c6c..1ce7e7c29 100644 --- a/app/javascript/mastodon/components/gifv.jsx +++ b/app/javascript/mastodon/components/gifv.jsx @@ -46,7 +46,7 @@ export default class GIFV extends React.PureComponent { width={width} height={height} role='button' - tabIndex='0' + tabIndex={0} aria-label={alt} title={alt} lang={lang} @@ -57,7 +57,7 @@ export default class GIFV extends React.PureComponent {