feat(composer): new composer and button styles

Summary:
initial styling of image attachments

more styles for composer overflow

style composer toolbar

toolbar styling

Fixes to inline composer

Test Plan: edgehill --test

Reviewers: bengotow

Reviewed By: bengotow

Differential Revision: https://phab.nylas.com/D1647
This commit is contained in:
Evan Morikawa 2015-06-17 16:03:50 -07:00
parent e44a7e28b6
commit e2fa53f45d
23 changed files with 205 additions and 67 deletions

View file

@ -30,7 +30,7 @@ class AttachmentComponent extends React.Component
</span>
<span className="attachment-file-actions">
{@_fileActions()}
{@_renderFileActions()}
</span>
<span className="attachment-file-and-name" onClick={@_onClickView}>
@ -44,18 +44,18 @@ class AttachmentComponent extends React.Component
</div>
_fileActions: =>
_renderFileActions: =>
if @props.removable
<div className="attachment-icon" onClick={@_onClickRemove}>
<RetinaImg className="remove-icon" name="remove-attachment.png"/>
{@_renderRemoveIcon()}
</div>
else if @_isDownloading() and @_canAbortDownload()
<div className="attachment-icon" onClick={@_onClickAbort}>
<RetinaImg className="remove-icon" name="remove-attachment.png"/>
{@_renderRemoveIcon()}
</div>
else
<div className="attachment-icon" onClick={@_onClickDownload}>
<i className="fa fa-download" style={position: "relative", top: "2px"}></i>
{@_renderDownloadButton()}
</div>
_downloadProgressStyle: =>
@ -68,6 +68,12 @@ class AttachmentComponent extends React.Component
_canAbortDownload: -> true
_renderRemoveIcon: ->
<RetinaImg className="remove-icon" name="remove-attachment.png"/>
_renderDownloadButton: ->
<RetinaImg className="download-icon" name="icon-attachment-download.png"/>
_onClickView: => Actions.fetchAndOpenFile(@props.file) if @_canClickToView()
_onClickDownload: => Actions.fetchAndSaveFile(@props.file)

View file

@ -1,7 +1,7 @@
path = require 'path'
React = require 'react'
AttachmentComponent = require './attachment-component'
{Spinner, DraggableImg} = require 'nylas-component-kit'
{RetinaImg, Spinner, DraggableImg} = require 'nylas-component-kit'
class ImageAttachmentComponent extends AttachmentComponent
@displayName: 'ImageAttachmentComponent'
@ -13,11 +13,13 @@ class ImageAttachmentComponent extends AttachmentComponent
<span className="attachment-download-progress" style={@_downloadProgressStyle()}></span>
</span>
<span className="attachment-file-actions">
<div className="attachment-file-actions">
{@_fileActions()}
</span>
</div>
<div className="attachment-preview" onClick={@_onClickView}>
<div className="attachment-name-bg"></div>
<div className="attachment-name">{@props.file.filename}</div>
{@_imgOrLoader()}
</div>
@ -25,6 +27,12 @@ class ImageAttachmentComponent extends AttachmentComponent
_canAbortDownload: -> false
_renderRemoveIcon: ->
<RetinaImg className="image-remove-icon" name="image-cancel-button.png"/>
_renderDownloadButton: ->
<RetinaImg className="image-download-icon" name="image-download-button.png"/>
_imgOrLoader: ->
if @props.download
if @props.download.percent <= 5

View file

@ -16,6 +16,14 @@
width: calc(~"50% - 7.5px");
border-radius: 4px;
&.non-image-attachment {
width: calc(~"50% - 23px");
margin-left: 15px;
&:nth-child(even) {
margin-left: 0;
}
}
&.file-upload {
border-radius: 4px;
padding: 13px @spacing-standard 13px @spacing-standard;
@ -53,6 +61,7 @@
.attachment-file-name {
font-weight: @font-weight-medium;
}
margin-left: 15px;
.attachment-file-and-name {
position: relative;
z-index: 2;
@ -121,7 +130,7 @@
.image-attachment-file-wrap, .image-file-upload {
position: relative;
margin: @spacing-standard 0;
margin: 0 0 8px 0;
text-align: center;
.attachment-download-progress,
@ -132,30 +141,56 @@
bottom: -2px;
}
.attachment-file-actions {
position: relative;
z-index: 2;
}
&:hover {
.attachment-file-actions {
.attachment-file-actions, .attachment-name-bg, .attachment-name {
display: block;
}
}
.attachment-file-actions {
.attachment-file-actions, .attachment-name-bg, .attachment-name {
display: none;
}
.attachment-icon {
position: absolute;
z-index: 2;
right: 0;
top: 0;
background: @white;
right: -8px;
top: -8px;
width: 26px;
border-radius: 0 0 0 3px;
}
.attachment-preview img {
.attachment-preview {
position: relative;
z-index: 1;
max-width: 100%;
background: @background-secondary;
.attachment-name-bg {
position: absolute;
bottom: 0;
top: 0;
z-index: 2;
width: 100%;
background: linear-gradient(to top, rgba(0,0,0,0.75) 0%,rgba(0,0,0,0) 23%)
}
.attachment-name {
color: @white;
left: 15px;
bottom: 13px;
position: absolute;
z-index: 3;
}
img {
position: relative;
z-index: 1;
max-width: 100%;
background: @background-secondary;
}
}
}

View file

@ -184,7 +184,9 @@ class ComposerView extends React.Component
{@_renderFields()}
<div className="compose-body">
<div className="compose-body"
ref="composeBody"
onClick={@_onClickComposeBody}>
<ContenteditableComponent ref="contentBody"
html={@state.body}
onChange={@_onChangeBody}
@ -347,18 +349,18 @@ class ComposerView extends React.Component
<button className="btn btn-toolbar btn-trash" style={order: 100}
data-tooltip="Delete draft"
onClick={@_destroyDraft}><RetinaImg name="toolbar-trash.png" mode={RetinaImg.Mode.ContentIsMask} /></button>
onClick={@_destroyDraft}><RetinaImg name="icon-composer-trash.png" mode={RetinaImg.Mode.ContentIsMask} /></button>
<button className="btn btn-toolbar btn-attach" style={order: 50}
data-tooltip="Attach file"
onClick={@_attachFile}><RetinaImg name="toolbar-attach.png" mode={RetinaImg.Mode.ContentIsMask} /></button>
onClick={@_attachFile}><RetinaImg name="icon-composer-attachment.png" mode={RetinaImg.Mode.ContentIsMask} /></button>
<div style={order: 0, flex: 1} />
<button className="btn btn-toolbar btn-emphasis btn-send" style={order: -100}
<button className="btn btn-toolbar btn-emphasis btn-text btn-send" style={order: -100}
data-tooltip="Send message"
ref="sendButton"
onClick={@_sendDraft}><RetinaImg name="toolbar-send.png" mode={RetinaImg.Mode.ContentIsMask} /> Send</button>
onClick={@_sendDraft}><RetinaImg name="icon-composer-send.png" mode={RetinaImg.Mode.ContentIsMask} /><span className="text">Send</span></button>
</InjectedComponentSet>
@ -386,6 +388,12 @@ class ComposerView extends React.Component
draft = @_proxy.draft()
Utils.isForwardedMessage(draft)
# This lets us click outside of the `contenteditable`'s `contentBody`
# and still focus on the contenteditable
_onClickComposeBody: (event) =>
if event.target is React.findDOMNode(@refs.composeBody)
@focus("contentBody")
_onDraftChanged: =>
return unless @_proxy
draft = @_proxy.draft()

View file

@ -11,13 +11,15 @@ class ImageFileUpload extends FileUpload
render: =>
<div className="image-file-upload #{@props.uploadData.state}">
<span className="attachment-file-actions">
<div className="attachment-file-actions">
<div className="attachment-icon" onClick={@_onClickRemove}>
<RetinaImg className="remove-icon" name="remove-attachment.png"/>
<RetinaImg className="image-remove-icon" name="image-cancel-button.png"/>
</div>
</span>
</div>
<div className="attachment-preview" >
<div className="attachment-name-bg"></div>
<div className="attachment-name">{@props.uploadData.fileName}</div>
<DraggableImg src={@props.uploadData.filePath} />
</div>

View file

@ -55,9 +55,10 @@ module.exports =
ComponentRegistry.register ComposeButton,
location: WorkspaceStore.Location.RootSidebar.Toolbar
else
atom.getCurrentWindow().setMinimumSize(600, 400)
atom.getCurrentWindow().setMinimumSize(480, 400)
WorkspaceStore.defineSheet 'Main', {root: true},
list: ['Center']
popout: ['Center']
ComponentRegistry.register ComposerWithWindowProps,
location: WorkspaceStore.Location.Center

View file

@ -34,7 +34,7 @@
margin: 0 auto;
flex-direction:row;
max-width: @compose-width;
padding: @spacing-standard (@spacing-standard + @spacing-standard / 2) @spacing-standard*1.1;
padding: @spacing-standard;
> * {
margin-left: @spacing-standard / 2;
@ -51,7 +51,7 @@
max-width: @compose-width;
margin: 0 auto;
margin-top: @spacing-standard;
padding: 0 @spacing-standard;
padding: 0;
flex: 1;
display: flex;
@ -79,7 +79,7 @@
.composer-participant-actions {
position: absolute;
z-index: 900;
right: @spacing-double;
right: 23px;
.header-action {
position: relative;
top: 1em;
@ -104,8 +104,8 @@
.compose-subject-wrap {
position: relative;
z-index: 2;
padding: 5px @spacing-standard 0 0;
margin: 0 @spacing-standard;
padding: 11px @spacing-standard 11px 0;
margin: 0 23px;
border-bottom: 1px solid @border-color-divider;
flex-shrink:0;
@ -119,8 +119,8 @@
input.compose-field {
display: inline-block;
width: calc(~"100% - 61px");
padding: 6px 0 2px 0;
margin: 0 0 5px 5px;
padding: 0;
margin: 0 0 0 5px;
min-width: 5em;
background-color: transparent;
border: none;
@ -139,10 +139,11 @@
cursor: text;
overflow: auto;
position: relative;
padding: 0 8px;
.quoted-text-control {
position: absolute;
bottom: -25px;
bottom: 10px;
left: 15px;
margin: 0;
}
@ -152,11 +153,12 @@
line-height: 1.4;
min-height: @compose-min-height;
padding: @spacing-standard;
padding-top: @spacing-standard;
padding-top: 20px;
padding-bottom: 0;
margin-bottom: 30px;
}
.contenteditable-container {
display: flex;
width: 100%;
position: relative;
}
@ -171,7 +173,7 @@
// TODO FIXME DRY From stylesheets/message-list.less
.attachments-area {
padding: 0 15px 0 15px;
padding: 0;
}
.token {
@ -182,25 +184,7 @@
color: @text-color-inverse-very-subtle;
}
}
// &:hover {
// background: transparent;
// }
// &.selected,
// &.dragging {
// background: transparent;
// color: @text-color;
//
// .action { color: @text-color-subtle; }
// }
}
// .btn.btn-send {
// font-weight: @font-weight-semi-bold;
// color: @accent-primary-dark;
// border-top: 1px solid fade(@accent-primary, 39%);
// border-right: 1px solid fade(@accent-primary, 39%);
// border-left: 1px solid fade(@accent-primary, 39%);
// }
}
body.is-blurred .composer-inner-wrap .tokenizing-field .token {
background: transparent;
@ -223,12 +207,15 @@ body.is-blurred .composer-inner-wrap .tokenizing-field .token {
border-top:1px solid @border-color-divider;
}
.composer-action-bar-content {
padding: 8px 0.5px;
}
.compose-body {
margin-bottom: 0;
position: relative;
.contenteditable-container {
display: flex;
flex: 1;
width: 100%;
position: relative;
@ -241,6 +228,12 @@ body.is-blurred .composer-inner-wrap .tokenizing-field .token {
}
}
.compose-body {
div[contenteditable] {
min-height: @line-height-computed;
}
}
}
// Overrides for the composer in a message-list
@ -262,6 +255,7 @@ body.is-blurred .composer-inner-wrap .tokenizing-field .token {
position: relative;
z-index: 2;
padding: 5px @spacing-standard 0 @spacing-standard;
margin: 0 8px;
flex-shrink: 0;
.participant {

View file

@ -22,9 +22,10 @@ class TemplatePicker extends React.Component
@unsubscribe() if @unsubscribe
render: =>
button = <button className="btn btn-toolbar">
<RetinaImg name="toolbar-templates.png" mode={RetinaImg.Mode.ContentIsMask}/>
<RetinaImg name="toolbar-chevron.png" mode={RetinaImg.Mode.ContentIsMask}/>
button = <button className="btn btn-toolbar" style={padding: "2px 9px"}>
<RetinaImg name="icon-composer-templates.png" mode={RetinaImg.Mode.ContentIsMask}/>
&nbsp;
<RetinaImg name="icon-composer-dropdown.png" mode={RetinaImg.Mode.ContentIsMask}/>
</button>
headerComponents = [

View file

@ -109,6 +109,7 @@ class RetinaImg extends React.Component
if @props.mode is Mode.ContentIsMask
style.WebkitMaskImage = "url('#{path}')"
style.WebkitMaskRepeat = "no-repeat"
style.objectPosition = "10000px"
className += " content-mask"
else if @props.mode is Mode.ContentDark

View file

@ -327,8 +327,11 @@ class DraftStore
draft: true
pristine: true
namespaceId: namespace.id
DatabaseStore.persistModel(draft).then =>
DatabaseStore.localIdForModel(draft).then(@_onPopoutDraftLocalId)
DatabaseStore.localIdForModel(draft).then (draftLocalId, options={}) =>
options.newDraft = true
@_onPopoutDraftLocalId(draftLocalId, options)
_onPopoutDraftLocalId: (draftLocalId, options = {}) =>
return unless NamespaceStore.current()
@ -337,9 +340,11 @@ class DraftStore
if @_draftSessions[draftLocalId]
save = @_draftSessions[draftLocalId].changes.commit()
title = if options.newDraft then "New Message" else "Message"
save.then =>
atom.newWindow
title: "Message"
title: title
windowType: "composer"
windowProps: _.extend(options, {draftLocalId})

View file

@ -34,7 +34,7 @@ class SheetContainer extends React.Component
sheetElements = @_sheetElements()
<Flexbox direction="column">
<Flexbox direction="column" className="layout-mode-#{@state.mode}">
{@_toolbarContainerElement()}
<div name="Header" style={order:1, zIndex: 2}>
@ -96,6 +96,7 @@ class SheetContainer extends React.Component
_getStateFromStores: =>
stack: WorkspaceStore.sheetStack()
mode: WorkspaceStore.layoutMode()
module.exports = SheetContainer

View file

@ -17,6 +17,21 @@ class ToolbarSpacer extends React.Component
render: =>
<div className="item-spacer" style={flex: 1, order:@props.order ? 0}></div>
class WindowTitle extends React.Component
@displayName: "WindowTitle"
constructor: (@props) ->
@state = atom.getLoadSettings()
componentDidMount: ->
@unlisten = atom.onWindowPropsReceived (windowProps) =>
@setState atom.getLoadSettings()
componentWillUnmount: -> @unlisten()
render: ->
<div className="window-title">{@state.title}</div>
class ToolbarBack extends React.Component
@displayName: 'ToolbarBack'
render: =>
@ -154,6 +169,9 @@ class Toolbar extends React.Component
entries = ComponentRegistry.findComponentsMatching({location: loc.Toolbar.Right, mode: state.mode})
state.columns[state.columns.length - 1]?.push(entries...)
if state.mode is "popout"
state.columns[0]?.push(WindowTitle)
state
module.exports = Toolbar

View file

@ -10,11 +10,12 @@ button, html input[type="button"] {
.btn {
padding: 0.33em 1em;
border:1px solid rgba(0,0,0,0.18);
border:1px solid rgba(0,0,0,0.15);
border-radius: @border-radius-base;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.10);
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
cursor: default;
display:inline-block;
background: linear-gradient(to top, rgba(241,241,241,0.75) 0%, rgba(253,253,253,0.75) 100%);
height: auto;
line-height: 1;
@ -55,12 +56,27 @@ button, html input[type="button"] {
}
&.btn-emphasis {
background-image: -webkit-gradient(linear, left top, left bottom, from(lighten(@btn-emphasis-bg-color,10%)), to(@btn-emphasis-bg-color));
border:1px solid darken(@btn-emphasis-bg-color, 5%);
position: relative;
color: @btn-emphasis-text-color;
font-weight: @font-weight-medium;
img.content-mask { background-color:@btn-emphasis-text-color; }
background: linear-gradient(to bottom, #6bb1f9 0%, #0a80ff 100%);
border: 0;
&:before {
content: ' ';
width: calc(~"100% + 2px");
height: calc(~"100% + 2px");
border-radius: 5px;
padding: 0;
top: -1px;
left: -1px;
position: absolute;
z-index: -1;
background: linear-gradient(to bottom, #4ca2f9 0%, #015cff 100%);
}
}
&.btn-emphasis:active {
@ -68,6 +84,16 @@ button, html input[type="button"] {
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.21);
}
&.btn-text {
font-size: 13px;
padding: 2px 14px 2px 7px;
.text {
position: relative;
top: 1px;
margin-left: 6px;
}
}
&.btn-danger, .btn-destructive {
color: @btn-danger-text-color;
background: @btn-danger-bg-color;
@ -76,7 +102,8 @@ button, html input[type="button"] {
}
.btn-toolbar {
min-height:34px;
min-height: 24px;
padding: 2px 13px;
}
.btn-gradient {

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -124,6 +124,22 @@ body.is-blurred {
.sheet-toolbar-container {
background: @toolbar-background-color;
&.mode-popout {
background: transparent;
}
}
.layout-mode-popout {
.sheet-toolbar {
background: @background-primary;
height: 35px;
min-height: 35px;
max-height: 35px;
}
.toolbar-window-controls {
margin-top: 7px;
}
}
.sheet-toolbar {
@ -149,6 +165,20 @@ body.is-blurred {
.item-spacer {
-webkit-app-region: drag;
}
.item-container {
.window-title {
flex: 2;
text-align: center;
margin-top: 6px;
margin-left: -80px; /* width of ToolbarWindowControls */
-webkit-app-region: drag;
&:hover {
cursor: default;
}
}
}
.item-back {
order:-999;
padding-top: 5px;
@ -265,3 +295,4 @@ body.platform-win32 {
cursor:ew-resize;
}
}