From 3c29f5740447270a4122b334281a907ecbdd4165 Mon Sep 17 00:00:00 2001 From: kibigo! Date: Tue, 26 Dec 2017 16:54:28 -0800 Subject: [PATCH] WIP Refactor; ed. --- .../glitch/features/composer/index.js | 15 +- .../composer/options/dropdown/index.js | 2 +- .../drawer/components/navigation_bar.js | 38 --- .../features/drawer/components/search.js | 129 --------- .../drawer/components/search_results.js | 65 ----- .../drawer/containers/navigation_container.js | 11 - .../drawer/containers/search_container.js | 35 --- .../containers/search_results_container.js | 8 - .../glitch/features/drawer/header/index.js | 117 ++++++++ .../flavours/glitch/features/drawer/index.js | 268 +++++++----------- .../features/drawer/pager/account/index.js | 70 +++++ .../glitch/features/drawer/pager/index.js | 43 +++ .../glitch/features/drawer/results/index.js | 114 ++++++++ .../glitch/features/drawer/search/index.js | 149 ++++++++++ .../features/drawer/search/popout/index.js | 95 +++++++ .../flavours/glitch/util/dom_helpers.js | 8 + .../flavours/glitch/util/react_helpers.js | 2 +- .../flavours/glitch/util/redux_helpers.js | 9 + 18 files changed, 723 insertions(+), 455 deletions(-) delete mode 100644 app/javascript/flavours/glitch/features/drawer/components/navigation_bar.js delete mode 100644 app/javascript/flavours/glitch/features/drawer/components/search.js delete mode 100644 app/javascript/flavours/glitch/features/drawer/components/search_results.js delete mode 100644 app/javascript/flavours/glitch/features/drawer/containers/navigation_container.js delete mode 100644 app/javascript/flavours/glitch/features/drawer/containers/search_container.js delete mode 100644 app/javascript/flavours/glitch/features/drawer/containers/search_results_container.js create mode 100644 app/javascript/flavours/glitch/features/drawer/header/index.js create mode 100644 app/javascript/flavours/glitch/features/drawer/pager/account/index.js create mode 100644 app/javascript/flavours/glitch/features/drawer/pager/index.js create mode 100644 app/javascript/flavours/glitch/features/drawer/results/index.js create mode 100644 app/javascript/flavours/glitch/features/drawer/search/index.js create mode 100644 app/javascript/flavours/glitch/features/drawer/search/popout/index.js diff --git a/app/javascript/flavours/glitch/features/composer/index.js b/app/javascript/flavours/glitch/features/composer/index.js index 25c2622d8..506c668a7 100644 --- a/app/javascript/flavours/glitch/features/composer/index.js +++ b/app/javascript/flavours/glitch/features/composer/index.js @@ -2,9 +2,6 @@ import PropTypes from 'prop-types'; import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; -import { withRouter } from 'react-router'; // Actions. import { @@ -43,7 +40,7 @@ import { countableText } from 'flavours/glitch/util/counter'; import { me } from 'flavours/glitch/util/initial_state'; import { isMobile } from 'flavours/glitch/util/is_mobile'; import { assignHandlers } from 'flavours/glitch/util/react_helpers'; -import { mergeProps } from 'flavours/glitch/util/redux_helpers'; +import { wrap } from 'flavours/glitch/util/redux_helpers'; // State mapping. function mapStateToProps (state) { @@ -204,9 +201,7 @@ const handlers = { }; // The component. -@injectIntl -@connect(mapStateToProps, mapDispatchToProps, mergeProps) -export default class Composer extends React.Component { +class Composer extends React.Component { // Constructor. constructor (props) { @@ -408,7 +403,7 @@ export default class Composer extends React.Component { // Context Composer.contextTypes = { history: PropTypes.object, -} +}; // Props. Composer.propTypes = { @@ -438,3 +433,7 @@ Composer.propTypes = { text: PropTypes.string, }).isRequired, }; + +// Connecting and export. +export { Composer as WrappedComponent }; +export default wrap(Composer, mapStateToProps, mapDispatchToProps, true); diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js b/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js index 0f304bc88..ee52008a7 100644 --- a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js +++ b/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js @@ -70,7 +70,7 @@ const handlers = { // dropdown. if (onModalClose && isUserTouching()) { if (open) { - onModalClose() + onModalClose(); } else if (onChange && onModalOpen) { onModalOpen({ actions: items.map( diff --git a/app/javascript/flavours/glitch/features/drawer/components/navigation_bar.js b/app/javascript/flavours/glitch/features/drawer/components/navigation_bar.js deleted file mode 100644 index 1b6d74123..000000000 --- a/app/javascript/flavours/glitch/features/drawer/components/navigation_bar.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import Avatar from 'flavours/glitch/components/avatar'; -import IconButton from 'flavours/glitch/components/icon_button'; -import Permalink from 'flavours/glitch/components/permalink'; -import { FormattedMessage } from 'react-intl'; -import ImmutablePureComponent from 'react-immutable-pure-component'; - -export default class NavigationBar extends ImmutablePureComponent { - - static propTypes = { - account: ImmutablePropTypes.map.isRequired, - onClose: PropTypes.func.isRequired, - }; - - render () { - return ( -
- - {this.props.account.get('acct')} - - - -
- - @{this.props.account.get('acct')} - - - -
- - -
- ); - } - -} diff --git a/app/javascript/flavours/glitch/features/drawer/components/search.js b/app/javascript/flavours/glitch/features/drawer/components/search.js deleted file mode 100644 index 1ce66b19d..000000000 --- a/app/javascript/flavours/glitch/features/drawer/components/search.js +++ /dev/null @@ -1,129 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import Overlay from 'react-overlays/lib/Overlay'; -import Motion from 'flavours/glitch/util/optional_motion'; -import spring from 'react-motion/lib/spring'; - -const messages = defineMessages({ - placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }, -}); - -class SearchPopout extends React.PureComponent { - - static propTypes = { - style: PropTypes.object, - }; - - render () { - const { style } = this.props; - - return ( -
- - {({ opacity, scaleX, scaleY }) => ( -
-

- -
    -
  • #example
  • -
  • @username@domain
  • -
  • URL
  • -
  • URL
  • -
- - -
- )} -
-
- ); - } - -} - -@injectIntl -export default class Search extends React.PureComponent { - - static propTypes = { - value: PropTypes.string.isRequired, - submitted: PropTypes.bool, - onChange: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, - onClear: PropTypes.func.isRequired, - onShow: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - state = { - expanded: false, - }; - - handleChange = (e) => { - this.props.onChange(e.target.value); - } - - handleClear = (e) => { - e.preventDefault(); - - if (this.props.value.length > 0 || this.props.submitted) { - this.props.onClear(); - } - } - - handleKeyDown = (e) => { - if (e.key === 'Enter') { - e.preventDefault(); - this.props.onSubmit(); - } else if (e.key === 'Escape') { - document.querySelector('.ui').parentElement.focus(); - } - } - - noop () { - - } - - handleFocus = () => { - this.setState({ expanded: true }); - this.props.onShow(); - } - - handleBlur = () => { - this.setState({ expanded: false }); - } - - render () { - const { intl, value, submitted } = this.props; - const { expanded } = this.state; - const hasValue = value.length > 0 || submitted; - - return ( -
- - -
- - -
- - - - -
- ); - } - -} diff --git a/app/javascript/flavours/glitch/features/drawer/components/search_results.js b/app/javascript/flavours/glitch/features/drawer/components/search_results.js deleted file mode 100644 index 2a4818d4e..000000000 --- a/app/javascript/flavours/glitch/features/drawer/components/search_results.js +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { FormattedMessage } from 'react-intl'; -import AccountContainer from 'flavours/glitch/containers/account_container'; -import StatusContainer from 'flavours/glitch/containers/status_container'; -import { Link } from 'react-router-dom'; -import ImmutablePureComponent from 'react-immutable-pure-component'; - -export default class SearchResults extends ImmutablePureComponent { - - static propTypes = { - results: ImmutablePropTypes.map.isRequired, - }; - - render () { - const { results } = this.props; - - let accounts, statuses, hashtags; - let count = 0; - - if (results.get('accounts') && results.get('accounts').size > 0) { - count += results.get('accounts').size; - accounts = ( -
- {results.get('accounts').map(accountId => )} -
- ); - } - - if (results.get('statuses') && results.get('statuses').size > 0) { - count += results.get('statuses').size; - statuses = ( -
- {results.get('statuses').map(statusId => )} -
- ); - } - - if (results.get('hashtags') && results.get('hashtags').size > 0) { - count += results.get('hashtags').size; - hashtags = ( -
- {results.get('hashtags').map(hashtag => - - #{hashtag} - - )} -
- ); - } - - return ( -
-
- -
- - {accounts} - {statuses} - {hashtags} -
- ); - } - -} diff --git a/app/javascript/flavours/glitch/features/drawer/containers/navigation_container.js b/app/javascript/flavours/glitch/features/drawer/containers/navigation_container.js deleted file mode 100644 index eb630ffbb..000000000 --- a/app/javascript/flavours/glitch/features/drawer/containers/navigation_container.js +++ /dev/null @@ -1,11 +0,0 @@ -import { connect } from 'react-redux'; -import NavigationBar from '../components/navigation_bar'; -import { me } from 'flavours/glitch/util/initial_state'; - -const mapStateToProps = state => { - return { - account: state.getIn(['accounts', me]), - }; -}; - -export default connect(mapStateToProps)(NavigationBar); diff --git a/app/javascript/flavours/glitch/features/drawer/containers/search_container.js b/app/javascript/flavours/glitch/features/drawer/containers/search_container.js deleted file mode 100644 index 8f4bfcf08..000000000 --- a/app/javascript/flavours/glitch/features/drawer/containers/search_container.js +++ /dev/null @@ -1,35 +0,0 @@ -import { connect } from 'react-redux'; -import { - changeSearch, - clearSearch, - submitSearch, - showSearch, -} from 'flavours/glitch/actions/search'; -import Search from '../components/search'; - -const mapStateToProps = state => ({ - value: state.getIn(['search', 'value']), - submitted: state.getIn(['search', 'submitted']), -}); - -const mapDispatchToProps = dispatch => ({ - - onChange (value) { - dispatch(changeSearch(value)); - }, - - onClear () { - dispatch(clearSearch()); - }, - - onSubmit () { - dispatch(submitSearch()); - }, - - onShow () { - dispatch(showSearch()); - }, - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(Search); diff --git a/app/javascript/flavours/glitch/features/drawer/containers/search_results_container.js b/app/javascript/flavours/glitch/features/drawer/containers/search_results_container.js deleted file mode 100644 index 16d95d417..000000000 --- a/app/javascript/flavours/glitch/features/drawer/containers/search_results_container.js +++ /dev/null @@ -1,8 +0,0 @@ -import { connect } from 'react-redux'; -import SearchResults from '../components/search_results'; - -const mapStateToProps = state => ({ - results: state.getIn(['search', 'results']), -}); - -export default connect(mapStateToProps)(SearchResults); diff --git a/app/javascript/flavours/glitch/features/drawer/header/index.js b/app/javascript/flavours/glitch/features/drawer/header/index.js new file mode 100644 index 000000000..fd79b6e18 --- /dev/null +++ b/app/javascript/flavours/glitch/features/drawer/header/index.js @@ -0,0 +1,117 @@ +// Package imports. +import PropTypes from 'prop-types'; +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { defineMessages } from 'react-intl'; +import { Link } from 'react-router-dom'; + +// Components. +import Icon from 'flavours/glitch/components/icon'; + +// Utils. +import { conditionalRender } from 'flavours/glitch/util/react_helpers'; + +// Messages. +const messages = defineMessages({ + community: { + defaultMessage: 'Local timeline', + id: 'navigation_bar.community_timeline', + }, + home_timeline: { + defaultMessage: 'Home', + id: 'tabs_bar.home', + }, + logout: { + defaultMessage: 'Logout', + id: 'navigation_bar.logout', + }, + notifications: { + defaultMessage: 'Notifications', + id: 'tabs_bar.notifications', + }, + public: { + defaultMessage: 'Federated timeline', + id: 'navigation_bar.public_timeline', + }, + settings: { + defaultMessage: 'App settings', + id: 'navigation_bar.app_settings', + }, + start: { + defaultMessage: 'Getting started', + id: 'getting_started.heading', + }, +}); + +// The component. +export default function DrawerHeader ({ + columns, + intl, + onSettingsClick, +}) { + + // Only renders the component if the column isn't being shown. + const renderForColumn = conditionalRender.bind( + columnId => !columns || !columns.some( + column => column.get('id') === columnId + ) + ); + + // The result. + return ( + + ); +} + +DrawerHeader.propTypes = { + columns: ImmutablePropTypes.list, + intl: PropTypes.object, + onSettingsClick: PropTypes.func, +}; diff --git a/app/javascript/flavours/glitch/features/drawer/index.js b/app/javascript/flavours/glitch/features/drawer/index.js index 8386ae47c..01ec18fc5 100644 --- a/app/javascript/flavours/glitch/features/drawer/index.js +++ b/app/javascript/flavours/glitch/features/drawer/index.js @@ -2,197 +2,147 @@ import PropTypes from 'prop-types'; import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { injectIntl, defineMessages } from 'react-intl'; -import spring from 'react-motion/lib/spring'; -import { connect } from 'react-redux'; -import { Link } from 'react-router-dom'; // Actions. import { changeComposing } from 'flavours/glitch/actions/compose'; -import { changeLocalSetting } from 'flavours/glitch/actions/local_settings'; import { openModal } from 'flavours/glitch/actions/modal'; +import { + changeSearch, + clearSearch, + showSearch, + submitSearch, +} from 'flavours/glitch/actions/search'; // Components. -import Icon from 'flavours/glitch/components/icon'; -import Compose from 'flavours/glitch/features/compose'; -import NavigationContainer from './containers/navigation_container'; -import SearchContainer from './containers/search_container'; -import SearchResultsContainer from './containers/search_results_container'; +import DrawerHeader from './header'; +import DrawerPager from './pager'; +import DrawerResults from './results'; +import DrawerSearch from './search'; // Utils. -import Motion from 'flavours/glitch/util/optional_motion'; -import { - assignHandlers, - conditionalRender, -} from 'flavours/glitch/util/react_helpers'; - -// Messages. -const messages = defineMessages({ - community: { - defaultMessage: 'Local timeline', - id: 'navigation_bar.community_timeline', - }, - home_timeline: { - defaultMessage: 'Home', - id: 'tabs_bar.home', - }, - logout: { - defaultMessage: 'Logout', - id: 'navigation_bar.logout', - }, - notifications: { - defaultMessage: 'Notifications', - id: 'tabs_bar.notifications', - }, - public: { - defaultMessage: 'Federated timeline', - id: 'navigation_bar.public_timeline', - }, - settings: { - defaultMessage: 'App settings', - id: 'navigation_bar.app_settings', - }, - start: { - defaultMessage: 'Getting started', - id: 'getting_started.heading', - }, -}); +import { me } from 'flavours/glitch/util/initial_state'; +import { wrap } from 'flavours/glitch/util/redux_helpers'; // State mapping. const mapStateToProps = state => ({ + account: state.getIn(['accounts', me]), columns: state.getIn(['settings', 'columns']), - showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']), + isComposing: state.getIn(['compose', 'is_composing']), + results: state.getIn(['search', 'results']), + searchHidden: state.getIn(['search', 'hidden']), + searchValue: state.getIn(['search', 'value']), + submitted: state.getIn(['search', 'submitted']), }); // Dispatch mapping. const mapDispatchToProps = dispatch => ({ - onBlur () { + change (value) { + dispatch(changeSearch(value)); + }, + changeComposingOff () { dispatch(changeComposing(false)); }, - onFocus () { + changeComposingOn () { dispatch(changeComposing(true)); }, - onSettingsOpen () { + clear () { + dispatch(clearSearch()); + }, + show () { + dispatch(showSearch()); + }, + submit () { + dispatch(submitSearch()); + }, + openSettings () { dispatch(openModal('SETTINGS', {})); }, }); // The component. -@connect(mapStateToProps, mapDispatchToProps) -@injectIntl -export default function Drawer ({ - columns, - intl, - multiColumn, - onBlur, - onFocus, - onSettingsOpen, - showSearch, -}) { +class Drawer extends React.Component { - // Only renders the component if the column isn't being shown. - const renderForColumn = conditionalRender.bind( - columnId => !columns.some(column => column.get('id') === columnId) - ); + // Constructor. + constructor (props) { + super(props); + } - // The result. - return ( -
- {multiColumn ? ( - - ) : null} - -
-
- - -
- - {({ x }) => ( -
- )} -
+ // Rendering. + render () { + const { + dispatch: { + change, + changeComposingOff, + changeComposingOn, + clear, + openSettings, + show, + submit, + }, + intl, + multiColumn, + state: { + account, + columns, + isComposing, + results, + searchHidden, + searchValue, + submitted, + }, + } = this.props; + + // The result. + return ( +
+ {multiColumn ? ( + + ) : null} + + +
-
- ); + ); + } + } // Props. Drawer.propTypes = { dispatch: PropTypes.func.isRequired, - columns: ImmutablePropTypes.list.isRequired, - multiColumn: PropTypes.bool, - showSearch: PropTypes.bool, intl: PropTypes.object.isRequired, + multiColumn: PropTypes.bool, + state: PropTypes.shape({ + account: ImmutablePropTypes.map, + columns: ImmutablePropTypes.list, + isComposing: PropTypes.bool, + results: ImmutablePropTypes.map, + searchHidden: PropTypes.bool, + searchValue: PropTypes.string, + submitted: PropTypes.bool, + }).isRequired, }; + +// Connecting and export. +export { Drawer as WrappedComponent }; +export default wrap(Drawer, mapStateToProps, mapDispatchToProps, true); diff --git a/app/javascript/flavours/glitch/features/drawer/pager/account/index.js b/app/javascript/flavours/glitch/features/drawer/pager/account/index.js new file mode 100644 index 000000000..2ee95d5b9 --- /dev/null +++ b/app/javascript/flavours/glitch/features/drawer/pager/account/index.js @@ -0,0 +1,70 @@ +// Package imports. +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { + FormattedMessage, + defineMessages, +} from 'react-intl'; + +// Components. +import Avatar from 'flavours/glitch/components/avatar'; +import Permalink from 'flavours/glitch/components/permalink'; + +// Utils. +import { hiddenComponent } from 'flavours/glitch/util/react_helpers'; + +// Messages. +const messages = defineMessages({ + edit: { + defaultMessage: 'Edit profile', + id: 'navigation_bar.edit_profile', + }, +}); + +// The component. +export default function DrawerPagerAccount ({ account }) { + + // We need an account to render. + if (!account) { + return ( +
+ + + +
+ ); + } + + // The result. + return ( +
+ + {account.get('acct')} + + + + @{account.get('acct')} + + +
+ ); +} + +DrawerPagerAccount.propTypes = { account: ImmutablePropTypes.map }; diff --git a/app/javascript/flavours/glitch/features/drawer/pager/index.js b/app/javascript/flavours/glitch/features/drawer/pager/index.js new file mode 100644 index 000000000..8dc2d3ee9 --- /dev/null +++ b/app/javascript/flavours/glitch/features/drawer/pager/index.js @@ -0,0 +1,43 @@ +// Package imports. +import classNames from 'classnames'; +import PropTypes from 'prop-types'; +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; + +// Components. +import IconButton from 'flavours/glitch/components/icon_button'; +import Composer from 'flavours/glitch/features/composer'; +import DrawerPagerAccount from './account'; + +// The component. +export default function DrawerPager ({ + account, + active, + onClose, + onFocus, +}) { + const computedClass = classNames('drawer--pager', { active }); + + // The result. + return ( +
+ + + +
+ ); +} + +DrawerPager.propTypes = { + account: ImmutablePropTypes.map, + active: PropTypes.bool, + onClose: PropTypes.func, + onFocus: PropTypes.func, +}; diff --git a/app/javascript/flavours/glitch/features/drawer/results/index.js b/app/javascript/flavours/glitch/features/drawer/results/index.js new file mode 100644 index 000000000..559d56da5 --- /dev/null +++ b/app/javascript/flavours/glitch/features/drawer/results/index.js @@ -0,0 +1,114 @@ +// Package imports. +import PropTypes from 'prop-types'; +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { + FormattedMessage, + defineMessages, +} from 'react-intl'; +import spring from 'react-motion/lib/spring'; +import { Link } from 'react-router-dom'; + +// Components. +import AccountContainer from 'flavours/glitch/containers/account_container'; +import StatusContainer from 'flavours/glitch/containers/status_container'; + +// Utils. +import Motion from 'flavours/glitch/util/optional_motion'; + +// Messages. +const messages = defineMessages({ + total: { + defaultMessage: '{count, number} {count, plural, one {result} other {results}}', + id: 'search_results.total', + }, +}); + +// The component. +export default function DrawerPager ({ + results, + visible, +}) { + const accounts = results ? results.get('accounts') : null; + const statuses = results ? results.get('statuses') : null; + const hashtags = results ? results.get('hashtags') : null; + + const count = [accounts, statuses, hashtags].reduce(function (size, item) { + if (item && item.size) { + return size + item.size; + } + return size; + }, 0); + + // The result. + return ( + + {({ x }) => ( +
+
+ +
+ {accounts && accounts.size ? ( +
+ {accounts.map( + accountId => ( + + ) + )} +
+ ) : null} + {statuses && statuses.size ? ( +
+ {statuses.map( + statusId => ( + + ) + )} +
+ ) : null} + {hashtags && hashtags.size ? ( +
+ {hashtags.map( + hashtag => ( + #{hashtag} + ) + )} +
+ ) : null} +
+ )} +
+ ); +} + +DrawerPager.propTypes = { + results: ImmutablePropTypes.map, + visible: PropTypes.bool, +}; diff --git a/app/javascript/flavours/glitch/features/drawer/search/index.js b/app/javascript/flavours/glitch/features/drawer/search/index.js new file mode 100644 index 000000000..ccb2ba859 --- /dev/null +++ b/app/javascript/flavours/glitch/features/drawer/search/index.js @@ -0,0 +1,149 @@ +// Package imports. +import classNames from 'classnames'; +import PropTypes from 'prop-types'; +import React from 'react'; +import { + FormattedMessage, + defineMessages, +} from 'react-intl'; +import Overlay from 'react-overlays/lib/Overlay'; + +// Components. +import Icon from 'flavours/glitch/components/icon'; +import DrawerSearchPopout from './popout'; + +// Utils. +import { focusRoot } from 'flavours/glitch/util/dom_helpers'; +import { + assignHandlers, + hiddenComponent, +} from 'flavours/glitch/util/react_helpers'; + +// Messages. +const messages = defineMessages({ + placeholder: { + defaultMessage: 'Search', + id: 'search.placeholder', + }, +}); + +// Handlers. +const handlers = { + + blur () { + this.setState({ expanded: false }); + }, + + change ({ target: { value } }) { + const { onChange } = this.props; + if (onChange) { + onChange(value); + } + }, + + clear (e) { + const { + onClear, + submitted, + value: { length }, + } = this.props; + e.preventDefault(); // Prevents focus change ?? + if (onClear && (submitted || length)) { + onClear(); + } + }, + + focus () { + const { onShow } = this.props; + this.setState({ expanded: true }); + if (onShow) { + onShow(); + } + }, + + keyUp (e) { + const { onSubmit } = this.props; + switch (e.key) { + case 'Enter': + if (onSubmit) { + onSubmit(); + } + break; + case 'Escape': + focusRoot(); + } + }, +}; + +// The component. +export default class DrawerSearch extends React.PureComponent { + + constructor (props) { + super(props); + assignHandlers(this, handlers); + this.state = { expanded: false }; + } + + render () { + const { + blur, + change, + clear, + focus, + keyUp, + } = this.handlers; + const { + intl, + submitted, + value, + } = this.props; + const { expanded } = this.state; + const computedClass = classNames('drawer--search', { active: value.length || submitted }); + + return ( +
+ +
+ + +
+ + +
+ ); + } + +} + +DrawerSearch.propTypes = { + value: PropTypes.string, + submitted: PropTypes.bool, + onChange: PropTypes.func, + onSubmit: PropTypes.func, + onClear: PropTypes.func, + onShow: PropTypes.func, + intl: PropTypes.object, +}; diff --git a/app/javascript/flavours/glitch/features/drawer/search/popout/index.js b/app/javascript/flavours/glitch/features/drawer/search/popout/index.js new file mode 100644 index 000000000..bd36275f5 --- /dev/null +++ b/app/javascript/flavours/glitch/features/drawer/search/popout/index.js @@ -0,0 +1,95 @@ +// Package imports. +import PropTypes from 'prop-types'; +import React from 'react'; +import { + FormattedMessage, + defineMessages, +} from 'react-intl'; +import spring from 'react-motion/lib/spring'; + +// Utils. +import Motion from 'flavours/glitch/util/optional_motion'; + +// Messages. +const messages = defineMessages({ + format: { + defaultMessage: 'Advanced search format', + id: 'search_popout.search_format', + }, + hashtag: { + defaultMessage: 'hashtag', + id: 'search_popout.tips.hashtag', + }, + status: { + defaultMessage: 'status', + id: 'search_popout.tips.status', + }, + text: { + defaultMessage: 'Simple text returns matching display names, usernames and hashtags', + id: 'search_popout.tips.text', + }, + user: { + defaultMessage: 'user', + id: 'search_popout.tips.user', + }, +}); + +const motionSpring = spring(1, { damping: 35, stiffness: 400 }); + +export default function DrawerSearchPopout ({ style }) { + return ( + + {({ opacity, scaleX, scaleY }) => ( +
+

+
    +
  • + #example + {' '} + +
  • +
  • + @username@domain + {' '} + +
  • +
  • + URL + {' '} + +
  • +
  • + URL + {' '} + +
  • +
+ +
+ )} +
+ ); +} + +// Props. +DrawerSearchPopout.propTypes = { style: PropTypes.object }; diff --git a/app/javascript/flavours/glitch/util/dom_helpers.js b/app/javascript/flavours/glitch/util/dom_helpers.js index ee95ef8dd..3e1f4a26d 100644 --- a/app/javascript/flavours/glitch/util/dom_helpers.js +++ b/app/javascript/flavours/glitch/util/dom_helpers.js @@ -4,3 +4,11 @@ import detectPassiveEvents from 'detect-passive-events'; // This will either be a passive lister options object (if passive // events are supported), or `false`. export const withPassive = detectPassiveEvents.hasSupport ? { passive: true } : false; + +// Focuses the root element. +export function focusRoot () { + let e; + if (document && (e = document.querySelector('.ui')) && (e = e.parentElement)) { + e.focus(); + } +} diff --git a/app/javascript/flavours/glitch/util/react_helpers.js b/app/javascript/flavours/glitch/util/react_helpers.js index 0826f3584..087e3969d 100644 --- a/app/javascript/flavours/glitch/util/react_helpers.js +++ b/app/javascript/flavours/glitch/util/react_helpers.js @@ -14,7 +14,7 @@ export function assignHandlers (target, handlers) { // This function only returns the component if the result of calling // `test` with `data` is `true`. Useful with funciton binding. export function conditionalRender (test, data, component) { - return test ? component : null; + return test(data) ? component : null; } // This object provides props to make the component not visible. diff --git a/app/javascript/flavours/glitch/util/redux_helpers.js b/app/javascript/flavours/glitch/util/redux_helpers.js index 3bc8bc86f..c0f5eeb28 100644 --- a/app/javascript/flavours/glitch/util/redux_helpers.js +++ b/app/javascript/flavours/glitch/util/redux_helpers.js @@ -1,3 +1,6 @@ +import { injectIntl } from 'react-intl'; +import { connect } from 'react-redux'; + // Merges react-redux props. export function mergeProps (stateProps, dispatchProps, ownProps) { Object.assign({}, ownProps, { @@ -5,3 +8,9 @@ export function mergeProps (stateProps, dispatchProps, ownProps) { state: Object.assign({}, stateProps, ownProps.state || {}), }); } + +// Connects a component. +export function wrap (Component, mapStateToProps, mapDispatchToProps, options) { + const withIntl = typeof options === 'object' ? options.withIntl : !!options; + return (withIntl ? injectIntl : i => i)(connect(mapStateToProps, mapDispatchToProps, mergeProps)(Component)); +}