]>
cat aescling's git repositories - mastodon.git/blob - app/javascript/mastodon/features/compose/components/privacy_dropdown.js
1 import React
from 'react';
2 import PropTypes
from 'prop-types';
3 import { injectIntl
, defineMessages
} from 'react-intl';
4 import IconButton
from '../../../components/icon_button';
5 import Overlay
from 'react-overlays/lib/Overlay';
6 import Motion
from '../../ui/util/optional_motion';
7 import spring
from 'react-motion/lib/spring';
8 import detectPassiveEvents
from 'detect-passive-events';
9 import classNames
from 'classnames';
11 const messages
= defineMessages({
12 public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
13 public_long: { id: 'privacy.public.long', defaultMessage: 'Post to public timelines' },
14 unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
15 unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Do not show in public timelines' },
16 private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
17 private_long: { id: 'privacy.private.long', defaultMessage: 'Post to followers only' },
18 direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
19 direct_long: { id: 'privacy.direct.long', defaultMessage: 'Post to mentioned users only' },
20 change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
23 const listenerOptions
= detectPassiveEvents
.hasSupport
? { passive: true } : false;
25 class PrivacyDropdownMenu
extends React
.PureComponent
{
28 style: PropTypes
.object
,
29 items: PropTypes
.array
.isRequired
,
30 value: PropTypes
.string
.isRequired
,
31 placement: PropTypes
.string
.isRequired
,
32 onClose: PropTypes
.func
.isRequired
,
33 onChange: PropTypes
.func
.isRequired
,
40 handleDocumentClick
= e
=> {
41 if (this.node
&& !this.node
.contains(e
.target
)) {
46 handleKeyDown
= e
=> {
47 const { items
} = this.props
;
48 const value
= e
.currentTarget
.getAttribute('data-index');
49 const index
= items
.findIndex(item
=> {
50 return (item
.value
=== value
);
62 element
= this.node
.childNodes
[index
+ 1];
65 this.props
.onChange(element
.getAttribute('data-index'));
69 element
= this.node
.childNodes
[index
- 1];
72 this.props
.onChange(element
.getAttribute('data-index'));
76 element
= this.node
.firstChild
;
79 this.props
.onChange(element
.getAttribute('data-index'));
83 element
= this.node
.lastChild
;
86 this.props
.onChange(element
.getAttribute('data-index'));
93 const value
= e
.currentTarget
.getAttribute('data-index');
98 this.props
.onChange(value
);
101 componentDidMount () {
102 document
.addEventListener('click', this.handleDocumentClick
, false);
103 document
.addEventListener('touchend', this.handleDocumentClick
, listenerOptions
);
104 if (this.focusedItem
) this.focusedItem
.focus();
105 this.setState({ mounted: true });
108 componentWillUnmount () {
109 document
.removeEventListener('click', this.handleDocumentClick
, false);
110 document
.removeEventListener('touchend', this.handleDocumentClick
, listenerOptions
);
118 this.focusedItem
= c
;
122 const { mounted
} = this.state
;
123 const { style
, items
, placement
, value
} = this.props
;
126 <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 }) }}>
127 {({ opacity
, scaleX
, scaleY
}) => (
128 // It should not be transformed when mounting because the resulting
129 // size will be used to determine the coordinate of the menu by
131 <div className
={`privacy-dropdown__dropdown ${placement}`} style
={{ ...style
, opacity: opacity
, transform: mounted
? `scale(${scaleX}, ${scaleY})` : null }} role
='listbox' ref
={this.setRef
}>
133 <div role
='option' tabIndex
='0' key
={item
.value
} data
-index
={item
.value
} onKeyDown
={this.handleKeyDown
} onClick
={this.handleClick
} className
={classNames('privacy-dropdown__option', { active: item
.value
=== value
})} aria
-selected
={item
.value
=== value
} ref
={item
.value
=== value
? this.setFocusRef : null}>
134 <div className
='privacy-dropdown__option__icon'>
135 <i className
={`fa fa-fw fa-${item.icon}`} />
138 <div className
='privacy-dropdown__option__content'>
139 <strong
>{item
.text
}</strong
>
153 export default class PrivacyDropdown
extends React
.PureComponent
{
156 isUserTouching: PropTypes
.func
,
157 isModalOpen: PropTypes
.bool
.isRequired
,
158 onModalOpen: PropTypes
.func
,
159 onModalClose: PropTypes
.func
,
160 value: PropTypes
.string
.isRequired
,
161 onChange: PropTypes
.func
.isRequired
,
162 intl: PropTypes
.object
.isRequired
,
170 handleToggle
= ({ target
}) => {
171 if (this.props
.isUserTouching()) {
172 if (this.state
.open
) {
173 this.props
.onModalClose();
175 this.props
.onModalOpen({
176 actions: this.options
.map(option
=> ({ ...option
, active: option
.value
=== this.props
.value
})),
177 onClick: this.handleModalActionClick
,
181 const { top
} = target
.getBoundingClientRect();
182 this.setState({ placement: top
* 2 < innerHeight
? 'bottom' : 'top' });
183 this.setState({ open: !this.state
.open
});
187 handleModalActionClick
= (e
) => {
190 const { value
} = this.options
[e
.currentTarget
.getAttribute('data-index')];
192 this.props
.onModalClose();
193 this.props
.onChange(value
);
196 handleKeyDown
= e
=> {
204 handleClose
= () => {
205 this.setState({ open: false });
208 handleChange
= value
=> {
209 this.props
.onChange(value
);
212 componentWillMount () {
213 const { intl: { formatMessage
} } = this.props
;
216 { icon: 'globe', value: 'public', text: formatMessage(messages
.public_short
), meta: formatMessage(messages
.public_long
) },
217 { icon: 'unlock-alt', value: 'unlisted', text: formatMessage(messages
.unlisted_short
), meta: formatMessage(messages
.unlisted_long
) },
218 { icon: 'lock', value: 'private', text: formatMessage(messages
.private_short
), meta: formatMessage(messages
.private_long
) },
219 { icon: 'envelope', value: 'direct', text: formatMessage(messages
.direct_short
), meta: formatMessage(messages
.direct_long
) },
224 const { value
, intl
} = this.props
;
225 const { open
, placement
} = this.state
;
227 const valueOption
= this.options
.find(item
=> item
.value
=== value
);
230 <div className
={classNames('privacy-dropdown', placement
, { active: open
})} onKeyDown
={this.handleKeyDown
}>
231 <div className
={classNames('privacy-dropdown__value', { active: this.options
.indexOf(valueOption
) === 0 })}>
233 className
='privacy-dropdown__value-icon'
234 icon
={valueOption
.icon
}
235 title
={intl
.formatMessage(messages
.change_privacy
)}
240 onClick
={this.handleToggle
}
241 style
={{ height: null, lineHeight: '27px' }}
245 <Overlay show
={open
} placement
={placement
} target
={this}>
249 onClose
={this.handleClose
}
250 onChange
={this.handleChange
}
251 placement
={placement
}
This page took 0.125766 seconds and 4 git commands to generate.