mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-22 08:16:09 +08:00
polish(account-dropdown): Use CSS (vs styles) to hide/show switcher, animate with CSS
This commit is contained in:
parent
3fb2754c95
commit
761a533c9a
|
@ -21,59 +21,48 @@ class AccountSwitcher extends React.Component
|
||||||
|
|
||||||
render: =>
|
render: =>
|
||||||
return false unless @state.account
|
return false unless @state.account
|
||||||
<div id="account-switcher" tabIndex={-1} onBlur={@_onBlur} ref="button">
|
|
||||||
{@_renderAccount(@state.account, true)}
|
classnames = ""
|
||||||
|
classnames += "open" if @state.showing
|
||||||
|
|
||||||
|
<div id="account-switcher"
|
||||||
|
tabIndex={-1}
|
||||||
|
onBlur={@_onBlur}
|
||||||
|
ref="button"
|
||||||
|
className={classnames}>
|
||||||
|
{@_renderPrimaryItem()}
|
||||||
{@_renderDropdown()}
|
{@_renderDropdown()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
_renderAccount: (account, isPrimaryItem) =>
|
_renderPrimaryItem: =>
|
||||||
classes = classNames
|
<div className="item primary-item" onClick={@_toggleDropdown}>
|
||||||
"account": true
|
{@_renderGravatarForAccount(@state.account)}
|
||||||
"item": true
|
<div style={float: 'right', marginTop: -2}>
|
||||||
"dropdown-item-padding": not isPrimaryItem
|
<RetinaImg className="toggle"
|
||||||
"active": account is @state.account
|
name="account-switcher-dropdown.png"
|
||||||
"bg-color-hover": not isPrimaryItem
|
|
||||||
"primary-item": isPrimaryItem
|
|
||||||
"account-option": not isPrimaryItem
|
|
||||||
|
|
||||||
email = account.emailAddress.trim().toLowerCase()
|
|
||||||
|
|
||||||
if isPrimaryItem
|
|
||||||
dropdownClasses = classNames
|
|
||||||
"account-switcher-dropdown": true,
|
|
||||||
"account-switcher-dropdown-hidden": @state.showing
|
|
||||||
|
|
||||||
dropdownArrow = <div style={float: 'right', marginTop: -2}>
|
|
||||||
<RetinaImg className={dropdownClasses} name="account-switcher-dropdown.png"
|
|
||||||
mode={RetinaImg.Mode.ContentPreserve} />
|
mode={RetinaImg.Mode.ContentPreserve} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
onClick = @_toggleDropdown
|
|
||||||
|
|
||||||
else
|
|
||||||
onClick = =>
|
|
||||||
@_onSwitchAccount account
|
|
||||||
|
|
||||||
<div className={classes}
|
|
||||||
onClick={onClick}
|
|
||||||
key={email}>
|
|
||||||
<div style={float: 'left'}>
|
|
||||||
<div className="gravatar" style={backgroundImage: @_gravatarUrl(email)}></div>
|
|
||||||
<RetinaImg name={"ic-settings-account-#{account.provider}@2x.png"}
|
|
||||||
style={width: 28, height: 28, marginTop: -10}
|
|
||||||
fallback="ic-settings-account-imap.png"
|
|
||||||
mode={RetinaImg.Mode.ContentPreserve} />
|
|
||||||
</div>
|
|
||||||
{dropdownArrow}
|
|
||||||
<div className="name" style={lineHeight: "110%"}>
|
<div className="name" style={lineHeight: "110%"}>
|
||||||
{email}
|
{@state.account.emailAddress.trim().toLowerCase()}
|
||||||
</div>
|
</div>
|
||||||
<div style={clear: "both"}>
|
<div style={clear: "both"}></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
_renderAccount: (account) =>
|
||||||
|
email = account.emailAddress.trim().toLowerCase()
|
||||||
|
classes = classNames
|
||||||
|
"active": account is @state.account
|
||||||
|
"item": true
|
||||||
|
"secondary-item": true
|
||||||
|
|
||||||
|
<div className={classes} onClick={ => @_onSwitchAccount(account)} key={email}>
|
||||||
|
{@_renderGravatarForAccount(account)}
|
||||||
|
<div className="name" style={lineHeight: "110%"}>{email}</div>
|
||||||
|
<div style={clear: "both"}></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
_renderNewAccountOption: =>
|
_renderNewAccountOption: =>
|
||||||
<div className="account item dropdown-item-padding bg-color-hover new-account-option"
|
<div className="item secondary-item new-account-option"
|
||||||
onClick={@_onAddAccount}
|
onClick={@_onAddAccount}
|
||||||
tabIndex={999}>
|
tabIndex={999}>
|
||||||
<div style={float: 'left'}>
|
<div style={float: 'left'}>
|
||||||
|
@ -85,31 +74,33 @@ class AccountSwitcher extends React.Component
|
||||||
<div className="name" style={lineHeight: "110%", textTransform: 'none'}>
|
<div className="name" style={lineHeight: "110%", textTransform: 'none'}>
|
||||||
Add account…
|
Add account…
|
||||||
</div>
|
</div>
|
||||||
<div style={clear: "both"}>
|
<div style={clear: "both"}></div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
_renderDropdown: =>
|
_renderDropdown: =>
|
||||||
display = if @state.showing then "block" else "none"
|
<div className="dropdown">
|
||||||
# display = "block"
|
<div className="inner">
|
||||||
|
{@state.accounts.map(@_renderAccount)}
|
||||||
accounts = @state.accounts.map (a) =>
|
|
||||||
@_renderAccount(a)
|
|
||||||
|
|
||||||
<div style={display: display}
|
|
||||||
ref="account-switcher-dropdown"
|
|
||||||
className="dropdown dropdown-positioning dropdown-colors">
|
|
||||||
{accounts}
|
|
||||||
{@_renderNewAccountOption()}
|
{@_renderNewAccountOption()}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
_renderGravatarForAccount: (account) =>
|
||||||
|
email = account.emailAddress.trim().toLowerCase()
|
||||||
|
hash = crypto.createHash('md5').update(email, 'utf8').digest('hex')
|
||||||
|
url = "url(http://www.gravatar.com/avatar/#{hash}?d=blank&s=56)"
|
||||||
|
|
||||||
|
<div style={float: 'left'}>
|
||||||
|
<div className="gravatar" style={backgroundImage:url}></div>
|
||||||
|
<RetinaImg name={"ic-settings-account-#{account.provider}@2x.png"}
|
||||||
|
style={width: 28, height: 28, marginTop: -10}
|
||||||
|
fallback="ic-settings-account-imap.png"
|
||||||
|
mode={RetinaImg.Mode.ContentPreserve} />
|
||||||
|
</div>
|
||||||
|
|
||||||
_toggleDropdown: =>
|
_toggleDropdown: =>
|
||||||
@setState showing: !@state.showing
|
@setState showing: !@state.showing
|
||||||
|
|
||||||
_gravatarUrl: (email) =>
|
|
||||||
hash = crypto.createHash('md5').update(email, 'utf8').digest('hex')
|
|
||||||
"url(http://www.gravatar.com/avatar/#{hash}?d=blank&s=56)"
|
|
||||||
|
|
||||||
_onStoreChange: =>
|
_onStoreChange: =>
|
||||||
@setState @_getStateFromStores()
|
@setState @_getStateFromStores()
|
||||||
|
|
||||||
|
@ -132,5 +123,4 @@ class AccountSwitcher extends React.Component
|
||||||
accounts: AccountStore.items()
|
accounts: AccountStore.items()
|
||||||
account: AccountStore.current()
|
account: AccountStore.current()
|
||||||
|
|
||||||
|
|
||||||
module.exports = AccountSwitcher
|
module.exports = AccountSwitcher
|
||||||
|
|
|
@ -22,39 +22,30 @@ describe "AccountSidebar", ->
|
||||||
)
|
)
|
||||||
|
|
||||||
it "doesn't render the dropdown if nothing clicked", ->
|
it "doesn't render the dropdown if nothing clicked", ->
|
||||||
dropdown = TestUtils.findRenderedDOMComponentWithClass sidebar, "dropdown"
|
openDropdown = TestUtils.scryRenderedDOMComponentsWithClass sidebar, 'open'
|
||||||
dropdownNode = React.findDOMNode dropdown, "account-switcher-dropdown"
|
expect(openDropdown.length).toBe 0
|
||||||
|
|
||||||
expect(dropdownNode.style.display).toBe "none"
|
it "shows the dropdown on click", ->
|
||||||
|
|
||||||
it "renders the dropdown if clicking the arrow btn", ->
|
|
||||||
toggler = TestUtils.findRenderedDOMComponentWithClass sidebar, 'primary-item'
|
toggler = TestUtils.findRenderedDOMComponentWithClass sidebar, 'primary-item'
|
||||||
TestUtils.Simulate.click toggler
|
TestUtils.Simulate.click toggler
|
||||||
dropdown = TestUtils.findRenderedDOMComponentWithClass sidebar, "dropdown"
|
openDropdown = TestUtils.scryRenderedDOMComponentsWithClass sidebar, 'open'
|
||||||
dropdownNode = React.findDOMNode dropdown, "account-switcher-dropdown"
|
expect(openDropdown.length).toBe 1
|
||||||
|
|
||||||
expect(dropdownNode.style.display).toBe "block"
|
it "hides the dropdown on blur", ->
|
||||||
|
toggler = TestUtils.findRenderedDOMComponentWithClass sidebar, 'primary-item'
|
||||||
it "removes the dropdown when clicking elsewhere", ->
|
TestUtils.Simulate.click toggler
|
||||||
toggler = TestUtils.findRenderedDOMComponentWithClass sidebar, 'primary-item'
|
toggler = TestUtils.findRenderedDOMComponentWithClass sidebar, 'primary-item'
|
||||||
TestUtils.Simulate.blur toggler
|
TestUtils.Simulate.blur toggler
|
||||||
dropdown = TestUtils.findRenderedDOMComponentWithClass sidebar, "dropdown"
|
openDropdown = TestUtils.scryRenderedDOMComponentsWithClass sidebar, 'open'
|
||||||
dropdownNode = React.findDOMNode dropdown, "account-switcher-dropdown"
|
expect(openDropdown.length).toBe 0
|
||||||
|
|
||||||
expect(dropdownNode.style.display).toBe "none"
|
it "shows other accounts and the 'Add Account' button", ->
|
||||||
|
|
||||||
it "shows all the accounts in the dropdown", ->
|
|
||||||
toggler = TestUtils.findRenderedDOMComponentWithClass sidebar, 'primary-item'
|
toggler = TestUtils.findRenderedDOMComponentWithClass sidebar, 'primary-item'
|
||||||
TestUtils.Simulate.click toggler
|
TestUtils.Simulate.click toggler
|
||||||
|
|
||||||
dropdown = TestUtils.findRenderedDOMComponentWithClass sidebar, "dropdown"
|
dropdown = TestUtils.findRenderedDOMComponentWithClass sidebar, "dropdown"
|
||||||
accounts = TestUtils.scryRenderedDOMComponentsWithClass dropdown, "account-option"
|
items = TestUtils.scryRenderedDOMComponentsWithClass dropdown, "secondary-item"
|
||||||
|
newAccountButton = TestUtils.scryRenderedDOMComponentsWithClass dropdown, "new-account-option"
|
||||||
|
|
||||||
expect(accounts.length).toBe 2
|
expect(items.length).toBe 3
|
||||||
|
expect(newAccountButton.length).toBe 1
|
||||||
it "shows the 'Add Account' button too", ->
|
|
||||||
toggler = TestUtils.findRenderedDOMComponentWithClass sidebar, 'primary-item'
|
|
||||||
TestUtils.Simulate.click toggler
|
|
||||||
dropdown = TestUtils.findRenderedDOMComponentWithClass sidebar, "dropdown"
|
|
||||||
accounts = TestUtils.scryRenderedDOMComponentsWithClass dropdown, "new-account-option"
|
|
||||||
|
|
||||||
expect(accounts.length).toBe 1
|
|
||||||
|
|
|
@ -86,7 +86,47 @@
|
||||||
padding-bottom: @padding-base-vertical;
|
padding-bottom: @padding-base-vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
.account {
|
.name {
|
||||||
|
text-transform: lowercase;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle {
|
||||||
|
transform: rotateX(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transform:scale(1, 0.2);
|
||||||
|
transform-origin: top;
|
||||||
|
transition: transform 125ms cubic-bezier(0.18, 0.89, 0.32, 1.12), opacity 100ms linear;
|
||||||
|
.inner {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 25ms linear;
|
||||||
|
transition-delay: 0;
|
||||||
|
}
|
||||||
|
margin-top: -7px;
|
||||||
|
background: lighten(@source-list-bg, 5%);
|
||||||
|
border: 1px solid @border-color-divider;
|
||||||
|
border-radius: @border-radius-base;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.21);
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 54px;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 999;
|
||||||
|
|
||||||
|
.account .gravatar {
|
||||||
|
top: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
display:block;
|
display:block;
|
||||||
|
@ -100,41 +140,12 @@
|
||||||
top: -2px;
|
top: -2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.name {
|
|
||||||
text-transform: lowercase;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.account-switcher-dropdown {
|
|
||||||
margin-top: -7px;
|
|
||||||
transform: scale(1, -1);
|
|
||||||
|
|
||||||
&.account-switcher-dropdown-hidden {
|
.secondary-item {
|
||||||
transform: scale(1, 1);
|
&:hover {
|
||||||
}
|
background: @list-hover-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-positioning {
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
top: 45px;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 999;
|
|
||||||
|
|
||||||
.account .gravatar {
|
|
||||||
top: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-colors {
|
|
||||||
background: lighten(@source-list-bg, 5%);
|
|
||||||
border: 1px solid @border-color-divider;
|
|
||||||
border-radius: @border-radius-base;
|
|
||||||
box-shadow: @standard-shadow;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-item-padding {
|
|
||||||
padding: 6px 5px 0 14px;
|
padding: 6px 5px 0 14px;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
|
@ -148,11 +159,22 @@
|
||||||
border-bottom-right-radius: @list-border-radius;
|
border-bottom-right-radius: @list-border-radius;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.bg-color-hover {
|
|
||||||
|
|
||||||
&:hover {
|
#account-switcher.open {
|
||||||
background: @list-hover-bg;
|
.dropdown {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: initial;
|
||||||
|
transform:scale(1, 1);
|
||||||
|
transform-origin: top;
|
||||||
|
.inner {
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 50ms linear;
|
||||||
|
transition-delay: 50ms;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.toggle {
|
||||||
|
transform: rotateX(180deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,7 +138,7 @@ describe 'TokenizingTextField', ->
|
||||||
ReactTestUtils.Simulate.dragStart(token, dragStartEvent)
|
ReactTestUtils.Simulate.dragStart(token, dragStartEvent)
|
||||||
|
|
||||||
expect(dragStartEventData).toEqual({
|
expect(dragStartEventData).toEqual({
|
||||||
'nylas-token-item': '{"client_id":"123","server_id":"2","name":"Nylas Burger Basket","email":"burgers@nylas.com","id":"2","__constructorName":"Contact"}'
|
'nylas-token-item': '{"client_id":"123","server_id":"2","name":"Nylas Burger Basket","email":"burgers@nylas.com","thirdPartyData":{},"id":"2","__constructorName":"Contact"}'
|
||||||
'text/plain': 'Nylas Burger Basket <burgers@nylas.com>'
|
'text/plain': 'Nylas Burger Basket <burgers@nylas.com>'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue