import PropTypes from "prop-types"; import { PureComponent } from "react"; import { injectIntl, defineMessages } from "react-intl"; import classNames from "classnames"; import { supportsPassiveEvents } from "detect-passive-events"; import Overlay from "react-overlays/Overlay"; import { Icon } from "mastodon/components/icon"; import { IconButton } from "../../../components/icon_button"; const messages = defineMessages({ public_short: { id: "privacy.public.short", defaultMessage: "Public" }, public_long: { id: "privacy.public.long", defaultMessage: "Visible for all" }, unlisted_short: { id: "privacy.unlisted.short", defaultMessage: "Unlisted" }, unlisted_long: { id: "privacy.unlisted.long", defaultMessage: "Visible for all, but opted-out of discovery features" }, private_short: { id: "privacy.private.short", defaultMessage: "Followers only" }, private_long: { id: "privacy.private.long", defaultMessage: "Visible for followers only" }, direct_short: { id: "privacy.direct.short", defaultMessage: "Mentioned people only" }, direct_long: { id: "privacy.direct.long", defaultMessage: "Visible for mentioned users only" }, change_privacy: { id: "privacy.change", defaultMessage: "Adjust status privacy" }, }); const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true; class PrivacyDropdownMenu extends PureComponent { static propTypes = { style: PropTypes.object, items: PropTypes.array.isRequired, value: PropTypes.string.isRequired, onClose: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired, }; handleDocumentClick = e => { if (this.node && !this.node.contains(e.target)) { this.props.onClose(); e.stopPropagation(); } }; handleKeyDown = e => { const { items } = this.props; const value = e.currentTarget.getAttribute("data-index"); const index = items.findIndex(item => { return (item.value === value); }); let element = null; switch(e.key) { case "Escape": this.props.onClose(); break; case "Enter": this.handleClick(e); break; case "ArrowDown": element = this.node.childNodes[index + 1] || this.node.firstChild; break; case "ArrowUp": element = this.node.childNodes[index - 1] || this.node.lastChild; break; case "Tab": if (e.shiftKey) { element = this.node.childNodes[index - 1] || this.node.lastChild; } else { element = this.node.childNodes[index + 1] || this.node.firstChild; } break; case "Home": element = this.node.firstChild; break; case "End": element = this.node.lastChild; break; } if (element) { element.focus(); this.props.onChange(element.getAttribute("data-index")); e.preventDefault(); e.stopPropagation(); } }; handleClick = e => { const value = e.currentTarget.getAttribute("data-index"); e.preventDefault(); this.props.onClose(); this.props.onChange(value); }; componentDidMount () { document.addEventListener("click", this.handleDocumentClick, { capture: true }); document.addEventListener("touchend", this.handleDocumentClick, listenerOptions); if (this.focusedItem) { this.focusedItem.focus({ preventScroll: true }); } } componentWillUnmount () { document.removeEventListener("click", this.handleDocumentClick, { capture: true }); document.removeEventListener("touchend", this.handleDocumentClick, listenerOptions); } setRef = c => { this.node = c; }; setFocusRef = c => { this.focusedItem = c; }; render () { const { style, items, value } = this.props; return (
{items.map(item => (
{item.text} {item.meta}
))}
); } } class PrivacyDropdown extends PureComponent { static propTypes = { isUserTouching: PropTypes.func, onModalOpen: PropTypes.func, onModalClose: PropTypes.func, value: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, noDirect: PropTypes.bool, container: PropTypes.func, disabled: PropTypes.bool, intl: PropTypes.object.isRequired, }; state = { open: false, placement: "bottom", }; handleToggle = () => { if (this.props.isUserTouching && this.props.isUserTouching()) { if (this.state.open) { this.props.onModalClose(); } else { this.props.onModalOpen({ actions: this.options.map(option => ({ ...option, active: option.value === this.props.value })), onClick: this.handleModalActionClick, }); } } else { if (this.state.open && this.activeElement) { this.activeElement.focus({ preventScroll: true }); } this.setState({ open: !this.state.open }); } }; handleModalActionClick = (e) => { e.preventDefault(); const { value } = this.options[e.currentTarget.getAttribute("data-index")]; this.props.onModalClose(); this.props.onChange(value); }; handleKeyDown = e => { switch(e.key) { case "Escape": this.handleClose(); break; } }; handleMouseDown = () => { if (!this.state.open) { this.activeElement = document.activeElement; } }; handleButtonKeyDown = (e) => { switch(e.key) { case " ": case "Enter": this.handleMouseDown(); break; } }; handleClose = () => { if (this.state.open && this.activeElement) { this.activeElement.focus({ preventScroll: true }); } this.setState({ open: false }); }; handleChange = value => { this.props.onChange(value); }; UNSAFE_componentWillMount () { const { intl: { formatMessage } } = this.props; this.options = [ { icon: "globe", value: "public", text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) }, { icon: "unlock", value: "unlisted", text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) }, { icon: "lock", value: "private", text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) }, ]; if (!this.props.noDirect) { this.options.push( { icon: "at", value: "direct", text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) }, ); } } setTargetRef = c => { this.target = c; }; findTarget = () => { return this.target; }; handleOverlayEnter = (state) => { this.setState({ placement: state.placement }); }; render () { const { value, container, disabled, intl } = this.props; const { open, placement } = this.state; const valueOption = this.options.find(item => item.value === value); return (
{({ props, placement }) => (
)}
); } } export default injectIntl(PrivacyDropdown);