diff options
-rw-r--r-- | app/javascript/flavours/glitch/features/compose/components/compose_form.js | 5 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/features/compose/components/dropdown.js (renamed from app/javascript/flavours/glitch/features/composer/options/dropdown/index.js) | 4 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js | 219 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/features/compose/components/options.js (renamed from app/javascript/flavours/glitch/features/composer/options/index.js) | 99 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js | 146 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/features/composer/options/dropdown/content/item/index.js | 129 |
6 files changed, 260 insertions, 342 deletions
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js index ae22f4d6d..a9be9c751 100644 --- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js +++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js @@ -5,7 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; // Components. -import ComposerOptions from '../../composer/options'; +import Options from './options'; import ComposerPublisher from '../../composer/publisher'; import TextareaIcons from './textarea_icons'; import UploadFormContainer from '../containers/upload_form_container'; @@ -372,7 +372,7 @@ class ComposeForm extends ImmutablePureComponent { <PollFormContainer /> </div> - <ComposerOptions + <Options acceptContentTypes={acceptContentTypes} advancedOptions={advancedOptions} disabled={isSubmitting} @@ -382,7 +382,6 @@ class ComposeForm extends ImmutablePureComponent { hasMedia={media && !!media.size} allowPoll={!(media && !!media.size)} hasPoll={!!poll} - intl={intl} onChangeAdvancedOption={onChangeAdvancedOption} onChangeSensitivity={onChangeSensitivity} onChangeVisibility={onChangeVisibility} diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js b/app/javascript/flavours/glitch/features/compose/components/dropdown.js index 7817cc964..8d982208f 100644 --- a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js +++ b/app/javascript/flavours/glitch/features/compose/components/dropdown.js @@ -6,7 +6,7 @@ import Overlay from 'react-overlays/lib/Overlay'; // Components. import IconButton from 'flavours/glitch/components/icon_button'; -import ComposerOptionsDropdownContent from './content'; +import DropdownMenu from './dropdown_menu'; // Utils. import { isUserTouching } from 'flavours/glitch/util/is_mobile'; @@ -196,7 +196,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent { show={open} target={this} > - <ComposerOptionsDropdownContent + <DropdownMenu items={items} onChange={onChange} onClose={handleClose} diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js new file mode 100644 index 000000000..b4e2ec07b --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js @@ -0,0 +1,219 @@ +// Package imports. +import PropTypes from 'prop-types'; +import React from 'react'; +import spring from 'react-motion/lib/spring'; +import Toggle from 'react-toggle'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import classNames from 'classnames'; + +// Components. +import Icon from 'flavours/glitch/components/icon'; + +// Utils. +import { withPassive } from 'flavours/glitch/util/dom_helpers'; +import Motion from 'flavours/glitch/util/optional_motion'; +import { assignHandlers } from 'flavours/glitch/util/react_helpers'; + +class ComposerOptionsDropdownContentItem extends ImmutablePureComponent { + + static propTypes = { + active: PropTypes.bool, + name: PropTypes.string, + onChange: PropTypes.func, + onClose: PropTypes.func, + options: PropTypes.shape({ + icon: PropTypes.string, + meta: PropTypes.node, + on: PropTypes.bool, + text: PropTypes.node, + }), + }; + + handleActivate = (e) => { + const { + name, + onChange, + onClose, + options: { on }, + } = this.props; + + // If the escape key was pressed, we close the dropdown. + if (e.key === 'Escape' && onClose) { + onClose(); + + // Otherwise, we both close the dropdown and change the value. + } else if (onChange && (!e.key || e.key === 'Enter')) { + e.preventDefault(); // Prevents change in focus on click + if ((on === null || typeof on === 'undefined') && onClose) { + onClose(); + } + onChange(name); + } + } + + // Rendering. + render () { + const { + active, + options: { + icon, + meta, + on, + text, + }, + } = this.props; + const computedClass = classNames('composer--options--dropdown--content--item', { + active, + lengthy: meta, + 'toggled-off': !on && on !== null && typeof on !== 'undefined', + 'toggled-on': on, + 'with-icon': icon, + }); + + let prefix = null; + + if (on !== null && typeof on !== 'undefined') { + prefix = <Toggle checked={on} onChange={this.handleActivate} />; + } else if (icon) { + prefix = <Icon className='icon' fullwidth icon={icon} /> + } + + // The result. + return ( + <div + className={computedClass} + onClick={this.handleActivate} + onKeyDown={this.handleActivate} + role='button' + tabIndex='0' + > + {prefix} + + <div className='content'> + <strong>{text}</strong> + {meta ? meta : nil} + </div> + </div> + ); + } + +}; + +// The spring to use with our motion. +const springMotion = spring(1, { + damping: 35, + stiffness: 400, +}); + +// The component. +export default class ComposerOptionsDropdownContent extends React.PureComponent { + + static propTypes = { + items: PropTypes.arrayOf(PropTypes.shape({ + icon: PropTypes.string, + meta: PropTypes.node, + name: PropTypes.string.isRequired, + on: PropTypes.bool, + text: PropTypes.node, + })), + onChange: PropTypes.func, + onClose: PropTypes.func, + style: PropTypes.object, + value: PropTypes.string, + }; + + static defaultProps = { + style: {}, + }; + + state = { + mounted: false, + }; + + // When the document is clicked elsewhere, we close the dropdown. + handleDocumentClick = ({ target }) => { + const { node } = this; + const { onClose } = this.props; + if (onClose && node && !node.contains(target)) { + onClose(); + } + } + + // Stores our node in `this.node`. + handleRef = (node) => { + this.node = node; + } + + // On mounting, we add our listeners. + componentDidMount () { + document.addEventListener('click', this.handleDocumentClick, false); + document.addEventListener('touchend', this.handleDocumentClick, withPassive); + this.setState({ mounted: true }); + } + + // On unmounting, we remove our listeners. + componentWillUnmount () { + document.removeEventListener('click', this.handleDocumentClick, false); + document.removeEventListener('touchend', this.handleDocumentClick, withPassive); + } + + // Rendering. + render () { + const { mounted } = this.state; + const { + items, + onChange, + onClose, + style, + value, + } = this.props; + + // The result. + return ( + <Motion + defaultStyle={{ + opacity: 0, + scaleX: 0.85, + scaleY: 0.75, + }} + style={{ + opacity: springMotion, + scaleX: springMotion, + scaleY: springMotion, + }} + > + {({ opacity, scaleX, scaleY }) => ( + // It should not be transformed when mounting because the resulting + // size will be used to determine the coordinate of the menu by + // react-overlays + <div + className='composer--options--dropdown--content' + ref={this.handleRef} + style={{ + ...style, + opacity: opacity, + transform: mounted ? `scale(${scaleX}, ${scaleY})` : null, + }} + > + {items ? items.map( + ({ + name, + ...rest + }) => ( + <ComposerOptionsDropdownContentItem + active={name === value} + key={name} + name={name} + onChange={onChange} + onClose={onClose} + options={rest} + /> + ) + ) : null} + </div> + )} + </Motion> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/composer/options/index.js b/app/javascript/flavours/glitch/features/compose/components/options.js index 7c7f01dc2..8a760bd15 100644 --- a/app/javascript/flavours/glitch/features/composer/options/index.js +++ b/app/javascript/flavours/glitch/features/compose/components/options.js @@ -2,23 +2,17 @@ import PropTypes from 'prop-types'; import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { - FormattedMessage, - defineMessages, -} from 'react-intl'; +import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import spring from 'react-motion/lib/spring'; // Components. import IconButton from 'flavours/glitch/components/icon_button'; import TextIconButton from 'flavours/glitch/components/text_icon_button'; import Dropdown from './dropdown'; +import ImmutablePureComponent from 'react-immutable-pure-component'; // Utils. import Motion from 'flavours/glitch/util/optional_motion'; -import { - assignHandlers, - hiddenComponent, -} from 'flavours/glitch/util/react_helpers'; import { pollLimits } from 'flavours/glitch/util/initial_state'; // Messages. @@ -109,19 +103,43 @@ const messages = defineMessages({ }, }); -// Handlers. -const handlers = { +export default @injectIntl +class ComposerOptions extends ImmutablePureComponent { + + static propTypes = { + acceptContentTypes: PropTypes.string, + advancedOptions: ImmutablePropTypes.map, + disabled: PropTypes.bool, + allowMedia: PropTypes.bool, + hasMedia: PropTypes.bool, + allowPoll: PropTypes.bool, + hasPoll: PropTypes.bool, + intl: PropTypes.object.isRequired, + onChangeAdvancedOption: PropTypes.func, + onChangeSensitivity: PropTypes.func, + onChangeVisibility: PropTypes.func, + onTogglePoll: PropTypes.func, + onDoodleOpen: PropTypes.func, + onModalClose: PropTypes.func, + onModalOpen: PropTypes.func, + onToggleSpoiler: PropTypes.func, + onUpload: PropTypes.func, + privacy: PropTypes.string, + resetFileKey: PropTypes.number, + sensitive: PropTypes.bool, + spoiler: PropTypes.bool, + }; // Handles file selection. - handleChangeFiles ({ target: { files } }) { + handleChangeFiles = ({ target: { files } }) => { const { onUpload } = this.props; if (files.length && onUpload) { onUpload(files); } - }, + } // Handles attachment clicks. - handleClickAttach (name) { + handleClickAttach = (name) => { const { fileElement } = this; const { onDoodleOpen } = this.props; @@ -138,34 +156,16 @@ const handlers = { } return; } - }, + } // Handles a ref to the file input. - handleRefFileElement (fileElement) { + handleRefFileElement = (fileElement) => { this.fileElement = fileElement; - }, -}; - -// The component. -export default class ComposerOptions extends React.PureComponent { - - // Constructor. - constructor (props) { - super(props); - assignHandlers(this, handlers); - - // Instance variables. - this.fileElement = null; } // Rendering. render () { const { - handleChangeFiles, - handleClickAttach, - handleRefFileElement, - } = this.handlers; - const { acceptContentTypes, advancedOptions, disabled, @@ -223,11 +223,11 @@ export default class ComposerOptions extends React.PureComponent { accept={acceptContentTypes} disabled={disabled || !allowMedia} key={resetFileKey} - onChange={handleChangeFiles} - ref={handleRefFileElement} + onChange={this.handleChangeFiles} + ref={this.handleRefFileElement} type='file' multiple - {...hiddenComponent} + style={{ display: 'none' }} /> <Dropdown disabled={disabled || !allowMedia} @@ -244,7 +244,7 @@ export default class ComposerOptions extends React.PureComponent { text: <FormattedMessage {...messages.doodle} />, }, ]} - onChange={handleClickAttach} + onChange={this.handleClickAttach} onModalClose={onModalClose} onModalOpen={onModalOpen} title={intl.formatMessage(messages.attach)} @@ -350,28 +350,3 @@ export default class ComposerOptions extends React.PureComponent { } } - -// Props. -ComposerOptions.propTypes = { - acceptContentTypes: PropTypes.string, - advancedOptions: ImmutablePropTypes.map, - disabled: PropTypes.bool, - allowMedia: PropTypes.bool, - hasMedia: PropTypes.bool, - allowPoll: PropTypes.bool, - hasPoll: PropTypes.bool, - intl: PropTypes.object.isRequired, - onChangeAdvancedOption: PropTypes.func, - onChangeSensitivity: PropTypes.func, - onChangeVisibility: PropTypes.func, - onTogglePoll: PropTypes.func, - onDoodleOpen: PropTypes.func, - onModalClose: PropTypes.func, - onModalOpen: PropTypes.func, - onToggleSpoiler: PropTypes.func, - onUpload: PropTypes.func, - privacy: PropTypes.string, - resetFileKey: PropTypes.number, - sensitive: PropTypes.bool, - spoiler: PropTypes.bool, -}; diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js b/app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js deleted file mode 100644 index b76410561..000000000 --- a/app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js +++ /dev/null @@ -1,146 +0,0 @@ -// Package imports. -import PropTypes from 'prop-types'; -import React from 'react'; -import spring from 'react-motion/lib/spring'; - -// Components. -import ComposerOptionsDropdownContentItem from './item'; - -// Utils. -import { withPassive } from 'flavours/glitch/util/dom_helpers'; -import Motion from 'flavours/glitch/util/optional_motion'; -import { assignHandlers } from 'flavours/glitch/util/react_helpers'; - -// Handlers. -const handlers = { - // When the document is clicked elsewhere, we close the dropdown. - handleDocumentClick ({ target }) { - const { node } = this; - const { onClose } = this.props; - if (onClose && node && !node.contains(target)) { - onClose(); - } - }, - - // Stores our node in `this.node`. - handleRef (node) { - this.node = node; - }, -}; - -// The spring to use with our motion. -const springMotion = spring(1, { - damping: 35, - stiffness: 400, -}); - -// The component. -export default class ComposerOptionsDropdownContent extends React.PureComponent { - - // Constructor. - constructor (props) { - super(props); - assignHandlers(this, handlers); - - // Instance variables. - this.node = null; - - this.state = { - mounted: false, - }; - } - - // On mounting, we add our listeners. - componentDidMount () { - const { handleDocumentClick } = this.handlers; - document.addEventListener('click', handleDocumentClick, false); - document.addEventListener('touchend', handleDocumentClick, withPassive); - this.setState({ mounted: true }); - } - - // On unmounting, we remove our listeners. - componentWillUnmount () { - const { handleDocumentClick } = this.handlers; - document.removeEventListener('click', handleDocumentClick, false); - document.removeEventListener('touchend', handleDocumentClick, withPassive); - } - - // Rendering. - render () { - const { mounted } = this.state; - const { handleRef } = this.handlers; - const { - items, - onChange, - onClose, - style, - value, - } = this.props; - - // The result. - return ( - <Motion - defaultStyle={{ - opacity: 0, - scaleX: 0.85, - scaleY: 0.75, - }} - style={{ - opacity: springMotion, - scaleX: springMotion, - scaleY: springMotion, - }} - > - {({ opacity, scaleX, scaleY }) => ( - // It should not be transformed when mounting because the resulting - // size will be used to determine the coordinate of the menu by - // react-overlays - <div - className='composer--options--dropdown--content' - ref={handleRef} - style={{ - ...style, - opacity: opacity, - transform: mounted ? `scale(${scaleX}, ${scaleY})` : null, - }} - > - {items ? items.map( - ({ - name, - ...rest - }) => ( - <ComposerOptionsDropdownContentItem - active={name === value} - key={name} - name={name} - onChange={onChange} - onClose={onClose} - options={rest} - /> - ) - ) : null} - </div> - )} - </Motion> - ); - } - -} - -// Props. -ComposerOptionsDropdownContent.propTypes = { - items: PropTypes.arrayOf(PropTypes.shape({ - icon: PropTypes.string, - meta: PropTypes.node, - name: PropTypes.string.isRequired, - on: PropTypes.bool, - text: PropTypes.node, - })), - onChange: PropTypes.func, - onClose: PropTypes.func, - style: PropTypes.object, - value: PropTypes.string, -}; - -// Default props. -ComposerOptionsDropdownContent.defaultProps = { style: {} }; diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/content/item/index.js b/app/javascript/flavours/glitch/features/composer/options/dropdown/content/item/index.js deleted file mode 100644 index 68a52083f..000000000 --- a/app/javascript/flavours/glitch/features/composer/options/dropdown/content/item/index.js +++ /dev/null @@ -1,129 +0,0 @@ -// Package imports. -import classNames from 'classnames'; -import PropTypes from 'prop-types'; -import React from 'react'; -import Toggle from 'react-toggle'; - -// Components. -import Icon from 'flavours/glitch/components/icon'; - -// Utils. -import { assignHandlers } from 'flavours/glitch/util/react_helpers'; - -// Handlers. -const handlers = { - - // This function activates the dropdown item. - handleActivate (e) { - const { - name, - onChange, - onClose, - options: { on }, - } = this.props; - - // If the escape key was pressed, we close the dropdown. - if (e.key === 'Escape' && onClose) { - onClose(); - - // Otherwise, we both close the dropdown and change the value. - } else if (onChange && (!e.key || e.key === 'Enter')) { - e.preventDefault(); // Prevents change in focus on click - if ((on === null || typeof on === 'undefined') && onClose) { - onClose(); - } - onChange(name); - } - }, -}; - -// The component. -export default class ComposerOptionsDropdownContentItem extends React.PureComponent { - - // Constructor. - constructor (props) { - super(props); - assignHandlers(this, handlers); - } - - // Rendering. - render () { - const { handleActivate } = this.handlers; - const { - active, - options: { - icon, - meta, - on, - text, - }, - } = this.props; - const computedClass = classNames('composer--options--dropdown--content--item', { - active, - lengthy: meta, - 'toggled-off': !on && on !== null && typeof on !== 'undefined', - 'toggled-on': on, - 'with-icon': icon, - }); - - // The result. - return ( - <div - className={computedClass} - onClick={handleActivate} - onKeyDown={handleActivate} - role='button' - tabIndex='0' - > - {function () { - - // We render a `<Toggle>` if we were provided an `on` - // property, and otherwise show an `<Icon>` if available. - switch (true) { - case on !== null && typeof on !== 'undefined': - return ( - <Toggle - checked={on} - onChange={handleActivate} - /> - ); - case !!icon: - return ( - <Icon - className='icon' - fullwidth - icon={icon} - /> - ); - default: - return null; - } - }()} - {meta ? ( - <div className='content'> - <strong>{text}</strong> - {meta} - </div> - ) : - <div className='content'> - <strong>{text}</strong> - </div>} - </div> - ); - } - -}; - -// Props. -ComposerOptionsDropdownContentItem.propTypes = { - active: PropTypes.bool, - name: PropTypes.string, - onChange: PropTypes.func, - onClose: PropTypes.func, - options: PropTypes.shape({ - icon: PropTypes.string, - meta: PropTypes.node, - on: PropTypes.bool, - text: PropTypes.node, - }), -}; |