mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-21 07:46:06 +08:00
feat(composer): Show blue focus ring around the composer
This commit is contained in:
parent
7f94680550
commit
024024fbe5
|
@ -5,7 +5,10 @@ div[contenteditable]':
|
|||
'cmd-enter' : 'composer:send-message'
|
||||
'ctrl-enter' : 'composer:send-message'
|
||||
|
||||
'.composer-outer-wrap div[contenteditable]':
|
||||
'.composer-outer-wrap':
|
||||
'delete' : 'composer:no-op'
|
||||
|
||||
'.composer-outer-wrap, .composer-outer-wrap div[contenteditable]':
|
||||
'escape' : 'composer:delete-empty-draft'
|
||||
|
||||
'.composer-outer-wrap .btn.btn-send':
|
||||
|
|
|
@ -23,6 +23,54 @@ ImageFileUpload = require './image-file-upload'
|
|||
ContenteditableComponent = require './contenteditable-component'
|
||||
ParticipantsTextField = require './participants-text-field'
|
||||
|
||||
# Public: FocusTrackingRegion is a small wrap component that renders it's children
|
||||
# and any props it's provided. Whenever the document's focus is inside the
|
||||
# FocusTrackingRegion, it has an additional CSS class: `focused`
|
||||
#
|
||||
class FocusTrackingRegion extends React.Component
|
||||
@displayName: 'FocusTrackingRegion'
|
||||
|
||||
@propTypes:
|
||||
className: React.PropTypes.string
|
||||
children: React.PropTypes.any
|
||||
|
||||
constructor: (@props) ->
|
||||
@state = {focused: false}
|
||||
@_goingout = false
|
||||
|
||||
@_in = =>
|
||||
@_goingout = false
|
||||
@setState(focused: true)
|
||||
|
||||
@_out = =>
|
||||
@_goingout = true
|
||||
setTimeout =>
|
||||
return unless @_goingout
|
||||
# This prevents the strange effect of an input appearing to have focus
|
||||
# when the element receiving focus does not support selection (like a
|
||||
# div with tabIndex=-1)
|
||||
document.getSelection().empty()
|
||||
@setState(focused: false)
|
||||
@_goingout = false
|
||||
, 100
|
||||
|
||||
componentDidMount: ->
|
||||
el = React.findDOMNode(@)
|
||||
el.addEventListener('focusin', @_in)
|
||||
el.addEventListener('focusout', @_out)
|
||||
|
||||
componentWillUnmount: ->
|
||||
el = React.findDOMNode(@)
|
||||
el.removeEventListener('focusin', @_in)
|
||||
el.removeEventListener('focusout', @_out)
|
||||
@_goingout = false
|
||||
|
||||
render: ->
|
||||
className = @props.className
|
||||
className += " focused" if @state.focused
|
||||
otherProps = _.omit(@props, _.keys(@constructor.propTypes))
|
||||
<div className={className} {...otherProps}>{@props.children}</div>
|
||||
|
||||
# The ComposerView is a unique React component because it (currently) is a
|
||||
# singleton. Normally, the React way to do things would be to re-render the
|
||||
# Composer with new props.
|
||||
|
@ -144,11 +192,11 @@ class ComposerView extends React.Component
|
|||
|
||||
render: =>
|
||||
if @props.mode is "inline"
|
||||
<div className={@_wrapClasses()}>
|
||||
<FocusTrackingRegion className={@_wrapClasses()} onFocus={@focus} tabIndex="-1">
|
||||
<ResizableRegion handle={ResizableRegion.Handle.Bottom}>
|
||||
{@_renderComposer()}
|
||||
</ResizableRegion>
|
||||
</div>
|
||||
</FocusTrackingRegion>
|
||||
else
|
||||
<div className={@_wrapClasses()}>
|
||||
{@_renderComposer()}
|
||||
|
@ -176,15 +224,15 @@ class ComposerView extends React.Component
|
|||
<div className="composer-participant-actions">
|
||||
<span className="header-action"
|
||||
style={display: @state.showcc and 'none' or 'inline'}
|
||||
onClick={=> @_showAndFocusCc()}>Cc</span>
|
||||
onClick={@_showAndFocusCc}>Cc</span>
|
||||
|
||||
<span className="header-action"
|
||||
style={display: @state.showbcc and 'none' or 'inline'}
|
||||
onClick={=> @_showAndFocusBcc()}>Bcc</span>
|
||||
onClick={@_showAndFocusBcc}>Bcc</span>
|
||||
|
||||
<span className="header-action"
|
||||
style={display: @state.showsubject and 'none' or 'initial'}
|
||||
onClick={=> @setState {showsubject: true}}>Subject</span>
|
||||
onClick={@_showAndFocusSubject}>Subject</span>
|
||||
|
||||
<span className="header-action"
|
||||
data-tooltip="Popout composer"
|
||||
|
@ -255,8 +303,8 @@ class ComposerView extends React.Component
|
|||
key="subject"
|
||||
name="subject"
|
||||
tabIndex="108"
|
||||
ref="textFieldSubject"
|
||||
placeholder="Subject"
|
||||
disabled={not @state.showsubject}
|
||||
value={@state.subject}
|
||||
onChange={@_onChangeSubject}/>
|
||||
</div>
|
||||
|
@ -299,6 +347,7 @@ class ComposerView extends React.Component
|
|||
onScrollTo={@props.onRequestScrollTo}
|
||||
onScrollToBottom={onScrollToBottom}
|
||||
tabIndex="109" />
|
||||
|
||||
_renderFooterRegions: =>
|
||||
return <div></div> unless @props.localId
|
||||
|
||||
|
@ -423,7 +472,11 @@ class ComposerView extends React.Component
|
|||
@_focusOnUpdate = {field}
|
||||
return
|
||||
|
||||
@refs[field].focus?()
|
||||
if @refs[field].focus
|
||||
@refs[field].focus()
|
||||
else
|
||||
node = React.findDOMNode(@refs[field])
|
||||
node.focus?()
|
||||
|
||||
isForwardedMessage: =>
|
||||
return false if not @_proxy
|
||||
|
@ -518,8 +571,11 @@ class ComposerView extends React.Component
|
|||
_onFilePaste: (path) =>
|
||||
Actions.attachFilePath({path: path, messageLocalId: @props.localId})
|
||||
|
||||
_onChangeParticipants: (changes={}) => @_addToProxy(changes)
|
||||
_onChangeSubject: (event) => @_addToProxy(subject: event.target.value)
|
||||
_onChangeParticipants: (changes={}) =>
|
||||
@_addToProxy(changes)
|
||||
|
||||
_onChangeSubject: (event) =>
|
||||
@_addToProxy(subject: event.target.value)
|
||||
|
||||
_onChangeBody: (event) =>
|
||||
return unless @_proxy
|
||||
|
@ -645,6 +701,10 @@ class ComposerView extends React.Component
|
|||
@setState {showcc: true}
|
||||
@focus('textFieldCc')
|
||||
|
||||
_showAndFocusSubject: =>
|
||||
@setState {showsubject: true}
|
||||
@focus('textFieldSubject')
|
||||
|
||||
_onEmptyCc: =>
|
||||
@setState showcc: false
|
||||
@focus('textFieldTo')
|
||||
|
|
|
@ -249,11 +249,10 @@
|
|||
|
||||
// Overrides for the composer in a message-list
|
||||
#message-list {
|
||||
.message-item-wrap.composer-outer-wrap {
|
||||
padding-top: @spacing-standard;
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(@background-off-primary), to(@background-primary));
|
||||
background-repeat:no-repeat;
|
||||
-webkit-background-size:100% 150px;
|
||||
.message-item-wrap {
|
||||
.message-item-white-wrap.composer-outer-wrap.focused {
|
||||
box-shadow: 0 0 0.5px rgba(0, 0, 0, 0.28), 0 1px 1.5px rgba(0, 0, 0, 0.08), 0 0 3px @accent-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue