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';
<PollFormContainer />
</div>
- <ComposerOptions
+ <Options
acceptContentTypes={acceptContentTypes}
advancedOptions={advancedOptions}
disabled={isSubmitting}
hasMedia={media && !!media.size}
allowPoll={!(media && !!media.size)}
hasPoll={!!poll}
- intl={intl}
onChangeAdvancedOption={onChangeAdvancedOption}
onChangeSensitivity={onChangeSensitivity}
onChangeVisibility={onChangeVisibility}
// 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';
show={open}
target={this}
>
- <ComposerOptionsDropdownContent
+ <DropdownMenu
items={items}
onChange={onChange}
onClose={handleClose}
--- /dev/null
+// 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>
+ );
+ }
+
+}
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.
},
});
-// 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;
}
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,
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}
text: <FormattedMessage {...messages.doodle} />,
},
]}
- onChange={handleClickAttach}
+ onChange={this.handleClickAttach}
onModalClose={onModalClose}
onModalOpen={onModalOpen}
title={intl.formatMessage(messages.attach)}
}
}
-
-// 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,
-};
+++ /dev/null
-// 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: {} };
+++ /dev/null
-// 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,
- }),
-};