diff options
author | Eugen Rochko <eugen@zeonfederated.com> | 2020-07-07 01:24:03 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-07 01:24:03 +0200 |
commit | c3187411c26aa00eb5844e4a7f9889f2cb19867e (patch) | |
tree | aa5b0676119c27d73680c5e7dd0d709ebbaaadde /app/javascript/mastodon/features/account/components/account_note.js | |
parent | 83fd046107999cc8ce166c997afee74409359002 (diff) |
Change design of account notes in web UI (#14208)
* Change design of account notes in web UI * Fix `for` -> `htmlFor`
Diffstat (limited to 'app/javascript/mastodon/features/account/components/account_note.js')
-rw-r--r-- | app/javascript/mastodon/features/account/components/account_note.js | 183 |
1 files changed, 125 insertions, 58 deletions
diff --git a/app/javascript/mastodon/features/account/components/account_note.js b/app/javascript/mastodon/features/account/components/account_note.js index 832a96a6a..1787ce1ab 100644 --- a/app/javascript/mastodon/features/account/components/account_note.js +++ b/app/javascript/mastodon/features/account/components/account_note.js @@ -3,99 +3,166 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import Icon from 'mastodon/components/icon'; import Textarea from 'react-textarea-autosize'; +import { is } from 'immutable'; const messages = defineMessages({ - placeholder: { id: 'account_note.placeholder', defaultMessage: 'No comment provided' }, + placeholder: { id: 'account_note.placeholder', defaultMessage: 'Click to add a note' }, }); +class InlineAlert extends React.PureComponent { + + static propTypes = { + show: PropTypes.bool, + }; + + state = { + mountMessage: false, + }; + + static TRANSITION_DELAY = 200; + + componentWillReceiveProps (nextProps) { + if (!this.props.show && nextProps.show) { + this.setState({ mountMessage: true }); + } else if (this.props.show && !nextProps.show) { + setTimeout(() => this.setState({ mountMessage: false }), InlineAlert.TRANSITION_DELAY); + } + } + + render () { + const { show } = this.props; + const { mountMessage } = this.state; + + return ( + <span aria-live='polite' role='status' className='inline-alert' style={{ opacity: show ? 1 : 0 }}> + {mountMessage && <FormattedMessage id='generic.saved' defaultMessage='Saved' />} + </span> + ); + } + +} + export default @injectIntl -class Header extends ImmutablePureComponent { +class AccountNote extends ImmutablePureComponent { static propTypes = { account: ImmutablePropTypes.map.isRequired, - isEditing: PropTypes.bool, - isSubmitting: PropTypes.bool, - accountNote: PropTypes.string, - onEditAccountNote: PropTypes.func.isRequired, - onCancelAccountNote: PropTypes.func.isRequired, - onSaveAccountNote: PropTypes.func.isRequired, - onChangeAccountNote: PropTypes.func.isRequired, + value: PropTypes.string, + onSave: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, }; - handleChangeAccountNote = (e) => { - this.props.onChangeAccountNote(e.target.value); + state = { + value: null, + saving: false, + saved: false, }; + componentWillMount () { + this._reset(); + } + + componentWillReceiveProps (nextProps) { + const accountWillChange = !is(this.props.account, nextProps.account); + const newState = {}; + + if (accountWillChange && this._isDirty()) { + this._save(false); + } + + if (accountWillChange || nextProps.value === this.state.value) { + newState.saving = false; + } + + if (this.props.value !== nextProps.value) { + newState.value = nextProps.value; + } + + this.setState(newState); + } + componentWillUnmount () { - if (this.props.isEditing) { - this.props.onCancelAccountNote(); + if (this._isDirty()) { + this._save(false); } } + setTextareaRef = c => { + this.textarea = c; + } + + handleChange = e => { + this.setState({ value: e.target.value, saving: false }); + }; + handleKeyDown = e => { if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { - this.props.onSaveAccountNote(); + e.preventDefault(); + + this._save(); + + if (this.textarea) { + this.textarea.blur(); + } } else if (e.keyCode === 27) { - this.props.onCancelAccountNote(); + e.preventDefault(); + + this._reset(() => { + if (this.textarea) { + this.textarea.blur(); + } + }); } } + handleBlur = () => { + if (this._isDirty()) { + this._save(); + } + } + + _save (showMessage = true) { + this.setState({ saving: true }, () => this.props.onSave(this.state.value)); + + if (showMessage) { + this.setState({ saved: true }, () => setTimeout(() => this.setState({ saved: false }), 2000)); + } + } + + _reset (callback) { + this.setState({ value: this.props.value }, callback); + } + + _isDirty () { + return !this.state.saving && this.props.value !== null && this.state.value !== null && this.state.value !== this.props.value; + } + render () { - const { account, accountNote, isEditing, isSubmitting, intl } = this.props; + const { account, intl } = this.props; + const { value, saved } = this.state; - if (!account || (!accountNote && !isEditing)) { + if (!account) { return null; } - let action_buttons = null; - if (isEditing) { - action_buttons = ( - <div className='account__header__account-note__buttons'> - <button className='text-btn' tabIndex='0' onClick={this.props.onCancelAccountNote} disabled={isSubmitting}> - <Icon id='times' size={15} /> <FormattedMessage id='account_note.cancel' defaultMessage='Cancel' /> - </button> - <div className='flex-spacer' /> - <button className='text-btn' tabIndex='0' onClick={this.props.onSaveAccountNote} disabled={isSubmitting}> - <Icon id='check' size={15} /> <FormattedMessage id='account_note.save' defaultMessage='Save' /> - </button> - </div> - ); - } + return ( + <div className='account__header__account-note'> + <label htmlFor={`account-note-${account.get('id')}`}> + <FormattedMessage id='account.account_note_header' defaultMessage='Note' /> <InlineAlert show={saved} /> + </label> - let note_container = null; - if (isEditing) { - note_container = ( <Textarea + id={`account-note-${account.get('id')}`} className='account__header__account-note__content' - disabled={isSubmitting} + disabled={this.props.value === null || value === null} placeholder={intl.formatMessage(messages.placeholder)} - value={accountNote} - onChange={this.handleChangeAccountNote} + value={value || ''} + onChange={this.handleChange} onKeyDown={this.handleKeyDown} - autoFocus + onBlur={this.handleBlur} + ref={this.setTextareaRef} /> - ); - } else { - note_container = (<div className='account__header__account-note__content'>{accountNote}</div>); - } - - return ( - <div className='account__header__account-note'> - <div className='account__header__account-note__header'> - <strong><FormattedMessage id='account.account_note_header' defaultMessage='Your note for @{name}' values={{ name: account.get('username') }} /></strong> - {!isEditing && ( - <div> - <button className='text-btn' tabIndex='0' onClick={this.props.onEditAccountNote} disabled={isSubmitting}> - <Icon id='pencil' size={15} /> <FormattedMessage id='account_note.edit' defaultMessage='Edit' /> - </button> - </div> - )} - </div> - {note_container} - {action_buttons} </div> ); } |