parent
924ffe81d4
commit
3c29f57404
@ -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 ( |
||||
<div className='navigation-bar'> |
||||
<Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}> |
||||
<span style={{ display: 'none' }}>{this.props.account.get('acct')}</span> |
||||
<Avatar account={this.props.account} size={40} /> |
||||
</Permalink> |
||||
|
||||
<div className='navigation-bar__profile'> |
||||
<Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}> |
||||
<strong className='navigation-bar__profile-account'>@{this.props.account.get('acct')}</strong> |
||||
</Permalink> |
||||
|
||||
<a href='/settings/profile' className='navigation-bar__profile-edit'><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a> |
||||
</div> |
||||
|
||||
<IconButton title='' icon='close' onClick={this.props.onClose} /> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
} |
@ -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 ( |
||||
<div style={{ ...style, position: 'absolute', width: 285 }}> |
||||
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}> |
||||
{({ opacity, scaleX, scaleY }) => ( |
||||
<div className='search-popout' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}> |
||||
<h4><FormattedMessage id='search_popout.search_format' defaultMessage='Advanced search format' /></h4> |
||||
|
||||
<ul> |
||||
<li><em>#example</em> <FormattedMessage id='search_popout.tips.hashtag' defaultMessage='hashtag' /></li> |
||||
<li><em>@username@domain</em> <FormattedMessage id='search_popout.tips.user' defaultMessage='user' /></li> |
||||
<li><em>URL</em> <FormattedMessage id='search_popout.tips.user' defaultMessage='user' /></li> |
||||
<li><em>URL</em> <FormattedMessage id='search_popout.tips.status' defaultMessage='status' /></li> |
||||
</ul> |
||||
|
||||
<FormattedMessage id='search_popout.tips.text' defaultMessage='Simple text returns matching display names, usernames and hashtags' /> |
||||
</div> |
||||
)} |
||||
</Motion> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
} |
||||
|
||||
@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 ( |
||||
<div className='search'> |
||||
<label> |
||||
<span style={{ display: 'none' }}>{intl.formatMessage(messages.placeholder)}</span> |
||||
<input |
||||
className='search__input' |
||||
type='text' |
||||
placeholder={intl.formatMessage(messages.placeholder)} |
||||
value={value} |
||||
onChange={this.handleChange} |
||||
onKeyUp={this.handleKeyDown} |
||||
onFocus={this.handleFocus} |
||||
onBlur={this.handleBlur} |
||||
/> |
||||
</label> |
||||
|
||||
<div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}> |
||||
<i className={`fa fa-search ${hasValue ? '' : 'active'}`} /> |
||||
<i aria-label={intl.formatMessage(messages.placeholder)} className={`fa fa-times-circle ${hasValue ? 'active' : ''}`} /> |
||||
</div> |
||||
|
||||
<Overlay show={expanded && !hasValue} placement='bottom' target={this}> |
||||
<SearchPopout /> |
||||
</Overlay> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
} |
@ -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 = ( |
||||
<div className='search-results__section'> |
||||
{results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)} |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
if (results.get('statuses') && results.get('statuses').size > 0) { |
||||
count += results.get('statuses').size; |
||||
statuses = ( |
||||
<div className='search-results__section'> |
||||
{results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)} |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
if (results.get('hashtags') && results.get('hashtags').size > 0) { |
||||
count += results.get('hashtags').size; |
||||
hashtags = ( |
||||
<div className='search-results__section'> |
||||
{results.get('hashtags').map(hashtag => |
||||
<Link key={hashtag} className='search-results__hashtag' to={`/timelines/tag/${hashtag}`}> |
||||
#{hashtag} |
||||
</Link> |
||||
)} |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
return ( |
||||
<div className='search-results'> |
||||
<div className='search-results__header'> |
||||
<FormattedMessage id='search_results.total' defaultMessage='{count, number} {count, plural, one {result} other {results}}' values={{ count }} /> |
||||
</div> |
||||
|
||||
{accounts} |
||||
{statuses} |
||||
{hashtags} |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
} |
@ -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); |
@ -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); |
@ -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); |
@ -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 ( |
||||
<nav className='drawer--header'> |
||||
<Link |
||||
aria-label={intl.formatMessage(messages.start)} |
||||
title={intl.formatMessage(messages.start)} |
||||
to='/getting-started' |
||||
><Icon icon='asterisk' /></Link> |
||||
{renderForColumn('HOME', ( |
||||
<Link |
||||
aria-label={intl.formatMessage(messages.home_timeline)} |
||||
title={intl.formatMessage(messages.home_timeline)} |
||||
to='/timelines/home' |
||||
><Icon icon='home' /></Link> |
||||
))} |
||||
{renderForColumn('NOTIFICATIONS', ( |
||||
<Link |
||||
aria-label={intl.formatMessage(messages.notifications)} |
||||
title={intl.formatMessage(messages.notifications)} |
||||
to='/notifications' |
||||
><Icon icon='bell' /></Link> |
||||
))} |
||||
{renderForColumn('COMMUNITY', ( |
||||
<Link |
||||
aria-label={intl.formatMessage(messages.community)} |
||||
title={intl.formatMessage(messages.community)} |
||||
to='/timelines/public/local' |
||||
><Icon icon='users' /></Link> |
||||
))} |
||||
{renderForColumn('PUBLIC', ( |
||||
<Link |
||||
aria-label={intl.formatMessage(messages.public)} |
||||
title={intl.formatMessage(messages.public)} |
||||
to='/timelines/public' |
||||
><Icon icon='globe' /></Link> |
||||
))} |
||||
<a |
||||
aria-label={intl.formatMessage(messages.settings)} |
||||
onClick={onSettingsClick} |
||||
role='button' |
||||
title={intl.formatMessage(messages.settings)} |
||||
tabIndex='0' |
||||
><Icon icon='cogs' /></a> |
||||
<a |
||||
aria-label={intl.formatMessage(messages.logout)} |
||||
data-method='delete' |
||||
href='/auth/sign_out' |
||||
title={intl.formatMessage(messages.logout)} |
||||
><Icon icon='sign-out' /></a> |
||||
</nav> |
||||
); |
||||
} |
||||
|
||||
DrawerHeader.propTypes = { |
||||
columns: ImmutablePropTypes.list, |
||||
intl: PropTypes.object, |
||||
onSettingsClick: PropTypes.func, |
||||
}; |
@ -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 ( |
||||
<div className='drawer--pager--account'> |
||||
<a |
||||
className='edit' |
||||
href='/settings/profile' |
||||
> |
||||
<FormattedMessage {...messages.edit} /> |
||||
</a> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
// The result.
|
||||
return ( |
||||
<div className='drawer--pager--account'> |
||||
<Permalink |
||||
className='avatar' |
||||
href={account.get('url')} |
||||
to={`/accounts/${account.get('id')}`} |
||||
> |
||||
<span {...hiddenComponent}>{account.get('acct')}</span> |
||||
<Avatar |
||||
account={account} |
||||
size={40} |
||||
/> |
||||
</Permalink> |
||||
<Permalink |
||||
className='acct' |
||||
href={account.get('url')} |
||||
to={`/accounts/${account.get('id')}`} |
||||
> |
||||
<strong>@{account.get('acct')}</strong> |
||||
</Permalink> |
||||
<a |
||||
className='edit' |
||||
href='/settings/profile' |
||||
><FormattedMessage {...messages.edit} /></a> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
DrawerPagerAccount.propTypes = { account: ImmutablePropTypes.map }; |
@ -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 ( |
||||
<div |
||||
className={computedClass} |
||||
onFocus={onFocus} |
||||
> |
||||
<DrawerPagerAccount account={account} /> |
||||
<IconButton |
||||
icon='close' |
||||
onClick={onClose} |
||||
title='' |
||||
/> |
||||
<Composer /> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
DrawerPager.propTypes = { |
||||
account: ImmutablePropTypes.map, |
||||
active: PropTypes.bool, |
||||
onClose: PropTypes.func, |
||||
onFocus: PropTypes.func, |
||||
}; |
@ -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 ( |
||||
<Motion |
||||
defaultStyle={{ x: -100 }} |
||||
style={{ |
||||
x: spring(visible ? 0 : -100, { |
||||
stiffness: 210, |
||||
damping: 20, |
||||
}), |
||||
}} |
||||
> |
||||
{({ x }) => ( |
||||
<div |
||||
className='drawer--results' |
||||
style={{ |
||||
transform: `translateX(${x}%)`, |
||||
visibility: x === -100 ? 'hidden' : 'visible', |
||||
}} |
||||
> |
||||
<header> |
||||
<FormattedMessage |
||||
{...messages.total} |
||||
values={{ count }} |
||||
/> |
||||
</header> |
||||
{accounts && accounts.size ? ( |
||||
<section> |
||||
{accounts.map( |
||||
accountId => ( |
||||
<AccountContainer |
||||
id={accountId} |
||||
key={accountId} |
||||
/> |
||||
) |
||||
)} |
||||
</section> |
||||
) : null} |
||||
{statuses && statuses.size ? ( |
||||
<section> |
||||
{statuses.map( |
||||
statusId => ( |
||||
<StatusContainer |
||||
id={statusId} |
||||
key={statusId} |
||||
/> |
||||
) |
||||
)} |
||||
</section> |
||||
) : null} |
||||
{hashtags && hashtags.size ? ( |
||||
<section> |
||||
{hashtags.map( |
||||
hashtag => ( |
||||
<Link |
||||
className='hashtag' |
||||
key={hashtag} |
||||
to={`/timelines/tag/${hashtag}`} |
||||
>#{hashtag}</Link> |
||||
) |
||||
)} |
||||
</section> |
||||
) : null} |
||||
</div> |
||||
)} |
||||
</Motion> |
||||
); |
||||
} |
||||
|
||||
DrawerPager.propTypes = { |
||||
results: ImmutablePropTypes.map, |
||||
visible: PropTypes.bool, |
||||
}; |
@ -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 ( |
||||
<div className={computedClass}> |
||||
<label> |
||||
<span {...hiddenComponent}> |
||||
<FormattedMessage {...messages.placeholder} /> |
||||
</span> |
||||
<input |
||||
type='text' |
||||
placeholder={intl.formatMessage(messages.placeholder)} |
||||
value={value} |
||||
onChange={change} |
||||
onKeyUp={keyUp} |
||||
onFocus={focus} |
||||
onBlur={blur} |
||||
/> |
||||
</label> |
||||
<div |
||||
aria-label={intl.formatMessage(messages.placeholder)} |
||||
className='icon' |
||||
onClick={clear} |
||||
role='button' |
||||
tabIndex='0' |
||||
> |
||||
<Icon icon='search' /> |
||||
<Icon icon='fa-times-circle' /> |
||||
</div> |
||||
|
||||
<Overlay |
||||
placement='bottom' |
||||
show={expanded && !value.length && !submitted} |
||||
target={this} |
||||
><DrawerSearchPopout /></Overlay> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
} |
||||
|
||||
DrawerSearch.propTypes = { |
||||
value: PropTypes.string, |
||||
submitted: PropTypes.bool, |
||||
onChange: PropTypes.func, |
||||
onSubmit: PropTypes.func, |
||||
onClear: PropTypes.func, |
||||
onShow: PropTypes.func, |
||||
intl: PropTypes.object, |
||||
}; |
@ -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 ( |
||||
<Motion |
||||
defaultStyle={{ |
||||
opacity: 0, |
||||
scaleX: 0.85, |
||||
scaleY: 0.75, |
||||
}} |
||||
style={{ |
||||
opacity: motionSpring, |
||||
scaleX: motionSpring, |
||||
scaleY: motionSpring, |
||||
}} |
||||
> |
||||
{({ opacity, scaleX, scaleY }) => ( |
||||
<div |
||||
className='drawer--search--popout' |
||||
style={{ |
||||
...style, |
||||
position: 'absolute', |
||||
width: 285, |
||||
opacity: opacity, |
||||
transform: `scale(${scaleX}, ${scaleY})`, |
||||
}} |
||||
> |
||||
<h4><FormattedMessage {...messages.format} /></h4> |
||||
<ul> |
||||
<li> |
||||
<em>#example</em> |
||||
{' '} |
||||
<FormattedMessage {...messages.hashtag} /> |
||||
</li> |
||||
<li> |
||||
<em>@username@domain</em> |
||||
{' '} |
||||
<FormattedMessage {...messages.user} /> |
||||
</li> |
||||
<li> |
||||
<em>URL</em> |
||||
{' '} |
||||
<FormattedMessage {...messages.user} /> |
||||
</li> |
||||
<li> |
||||
<em>URL</em> |
||||
{' '} |
||||
<FormattedMessage {...messages.status} /> |
||||
</li> |
||||
</ul> |
||||
<FormattedMessage {...messages.text} /> |
||||
</div> |
||||
)} |
||||
</Motion> |
||||
); |
||||
} |
||||
|
||||
// Props.
|
||||
DrawerSearchPopout.propTypes = { style: PropTypes.object }; |
Loading…
Reference in new issue