diff options
author | Starfall <us@starfall.systems> | 2023-04-14 19:22:47 -0500 |
---|---|---|
committer | Starfall <us@starfall.systems> | 2023-04-14 19:22:47 -0500 |
commit | 4fe1689de43f4404eb9530fcfbcbfb26d6c1c13a (patch) | |
tree | 6811b845bb7f4966b10dcefa3dea404246f161c7 /app/javascript/flavours/glitch/features/compose/components/dropdown.jsx | |
parent | 65c1e53a32cabcdbb7bca57002bb0f6acdebe07e (diff) | |
parent | bed63f6dae0879ac840066b031229e0d139089cd (diff) |
Diffstat (limited to 'app/javascript/flavours/glitch/features/compose/components/dropdown.jsx')
-rw-r--r-- | app/javascript/flavours/glitch/features/compose/components/dropdown.jsx | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown.jsx b/app/javascript/flavours/glitch/features/compose/components/dropdown.jsx new file mode 100644 index 000000000..fe4ab36f5 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/components/dropdown.jsx @@ -0,0 +1,243 @@ +// Package imports. +import classNames from 'classnames'; +import PropTypes from 'prop-types'; +import React from 'react'; +import Overlay from 'react-overlays/Overlay'; + +// Components. +import IconButton from 'flavours/glitch/components/icon_button'; +import DropdownMenu from './dropdown_menu'; + +// Utils. +import { assignHandlers } from 'flavours/glitch/utils/react_helpers'; + +// The component. +export default class ComposerOptionsDropdown extends React.PureComponent { + + static propTypes = { + isUserTouching: PropTypes.func, + disabled: PropTypes.bool, + icon: PropTypes.string, + items: PropTypes.arrayOf(PropTypes.shape({ + icon: PropTypes.string, + meta: PropTypes.string, + name: PropTypes.string.isRequired, + text: PropTypes.string, + })).isRequired, + onModalOpen: PropTypes.func, + onModalClose: PropTypes.func, + title: PropTypes.string, + value: PropTypes.string, + onChange: PropTypes.func, + container: PropTypes.func, + renderItemContents: PropTypes.func, + closeOnChange: PropTypes.bool, + }; + + static defaultProps = { + closeOnChange: true, + }; + + state = { + open: false, + openedViaKeyboard: undefined, + placement: 'bottom', + }; + + // Toggles opening and closing the dropdown. + handleToggle = ({ type }) => { + const { onModalOpen } = this.props; + const { open } = this.state; + + if (this.props.isUserTouching && this.props.isUserTouching()) { + if (this.state.open) { + this.props.onModalClose(); + } else { + const modal = this.handleMakeModal(); + if (modal && onModalOpen) { + onModalOpen(modal); + } + } + } else { + if (this.state.open && this.activeElement) { + this.activeElement.focus({ preventScroll: true }); + } + this.setState({ open: !this.state.open, openedViaKeyboard: type !== 'click' }); + } + }; + + 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; + } + }; + + handleKeyPress = (e) => { + switch(e.key) { + case ' ': + case 'Enter': + this.handleToggle(e); + e.stopPropagation(); + e.preventDefault(); + break; + } + }; + + handleClose = () => { + if (this.state.open && this.activeElement) { + this.activeElement.focus({ preventScroll: true }); + } + this.setState({ open: false }); + }; + + handleItemClick = (e) => { + const { + items, + onChange, + onModalClose, + closeOnChange, + } = this.props; + + const i = Number(e.currentTarget.getAttribute('data-index')); + + const { name } = items[i]; + + e.preventDefault(); // Prevents focus from changing + if (closeOnChange) onModalClose(); + onChange(name); + }; + + // Creates an action modal object. + handleMakeModal = () => { + const { + items, + onChange, + onModalOpen, + onModalClose, + value, + } = this.props; + + // Required props. + if (!(onChange && onModalOpen && onModalClose && items)) { + return null; + } + + // The object. + return { + renderItemContents: this.props.renderItemContents, + onClick: this.handleItemClick, + actions: items.map( + ({ + name, + ...rest + }) => ({ + ...rest, + active: value && name === value, + name, + }), + ), + }; + }; + + setTargetRef = c => { + this.target = c; + }; + + findTarget = () => { + return this.target; + }; + + handleOverlayEnter = (state) => { + this.setState({ placement: state.placement }); + }; + + // Rendering. + render () { + const { + disabled, + title, + icon, + items, + onChange, + value, + container, + renderItemContents, + closeOnChange, + } = this.props; + const { open, placement } = this.state; + + const active = value && items.findIndex(({ name }) => name === value) === (placement === 'bottom' ? 0 : (items.length - 1)); + + return ( + <div + className={classNames('privacy-dropdown', placement, { active: open })} + onKeyDown={this.handleKeyDown} + ref={this.setTargetRef} + > + <div className={classNames('privacy-dropdown__value', { active })}> + <IconButton + active={open} + className='privacy-dropdown__value-icon' + disabled={disabled} + icon={icon} + inverted + onClick={this.handleToggle} + onMouseDown={this.handleMouseDown} + onKeyDown={this.handleButtonKeyDown} + onKeyPress={this.handleKeyPress} + size={18} + style={{ + height: null, + lineHeight: '27px', + }} + title={title} + /> + </div> + + <Overlay + containerPadding={20} + placement={placement} + show={open} + flip + target={this.findTarget} + container={container} + popperConfig={{ strategy: 'fixed', onFirstUpdate: this.handleOverlayEnter }} + > + {({ props, placement }) => ( + <div {...props}> + <div className={`dropdown-animation privacy-dropdown__dropdown ${placement}`}> + <DropdownMenu + items={items} + renderItemContents={renderItemContents} + onChange={onChange} + onClose={this.handleClose} + value={value} + openedViaKeyboard={this.state.openedViaKeyboard} + closeOnChange={closeOnChange} + /> + </div> + </div> + )} + </Overlay> + </div> + ); + } + +} |