From ab4387b248703e2a13927575fd7cb2e6ae1a1fb4 Mon Sep 17 00:00:00 2001 From: Pete Miller Date: Tue, 9 Jan 2018 20:01:51 +1300 Subject: [PATCH 01/15] Tab previewing: tab gets visual state, content gets gradient opacity mask. Also tighten up some tab styles / spacing as discussed with design. Fix favicon getting cut off at small tab sizes with new spacing. Tab preview and hover timings which feel more natural for exploring open tabs - fast in and slow out Tab preview fades out active tab, for focus Cross-fade of frame content was considered, but removed due to performance / low animation framerate. This can be fixed and re-implemented with single-webview, which will require a different css architecture for this potential feature anyway. --- .../state/tabContentState/titleState.js | 7 +- app/common/state/tabUIState.js | 43 +--- app/renderer/components/frame/frame.js | 7 +- app/renderer/components/main/main.js | 6 +- app/renderer/components/styles/global.js | 10 +- app/renderer/components/styles/theme.js | 17 +- .../components/tabs/content/favIcon.js | 3 +- .../components/tabs/content/tabIcon.js | 1 + .../components/tabs/content/tabTitle.js | 4 +- app/renderer/components/tabs/pinnedTabs.js | 3 + app/renderer/components/tabs/tab.js | 216 +++++++++++------- app/renderer/components/tabs/tabs.js | 12 +- app/renderer/lib/observerUtil.js | 2 +- less/window.less | 56 +++-- .../tabContentStateTest/audioStateTest.js | 2 +- .../tabContentStateTest/closeStateTest.js | 2 +- test/unit/app/common/state/tabUIStateTest.js | 92 -------- 17 files changed, 229 insertions(+), 254 deletions(-) diff --git a/app/common/state/tabContentState/titleState.js b/app/common/state/tabContentState/titleState.js index 6bdc5b75f47..1c21e294c35 100644 --- a/app/common/state/tabContentState/titleState.js +++ b/app/common/state/tabContentState/titleState.js @@ -8,6 +8,7 @@ const partitionState = require('../tabContentState/partitionState') const privateState = require('../tabContentState/privateState') const frameStateUtil = require('../../../../js/state/frameStateUtil') + const tabUIState = require('../tabUIState') // Utils const {isEntryIntersected} = require('../../../../app/renderer/lib/observerUtil') @@ -26,11 +27,13 @@ const isActive = frameStateUtil.isFrameKeyActive(state, frameKey) const isPartition = partitionState.isPartitionTab(state, frameKey) const isPrivate = privateState.isPrivateTab(state, frameKey) - const secondaryIconVisible = !isNewTabPage && (isPartition || isPrivate || isActive) + const secondaryIconVisible = !isNewTabPage && + (isPartition || isPrivate || isActive) && + tabUIState.showTabEndIcon(state, frameKey) // If title is being intersected by ~half with other icons visible // such as closeTab (activeTab) or session icons, do not show it - if (isEntryIntersected(state, 'tabs', intersection.at45) && secondaryIconVisible) { + if (isEntryIntersected(state, 'tabs', intersection.at46) && secondaryIconVisible) { return false } diff --git a/app/common/state/tabUIState.js b/app/common/state/tabUIState.js index 072ff4f0d04..55159538bc9 100644 --- a/app/common/state/tabUIState.js +++ b/app/common/state/tabUIState.js @@ -6,8 +6,6 @@ const settings = require('../../../js/constants/settings') // State helpers -const partitionState = require('../../common/state/tabContentState/partitionState') -const privateState = require('../../common/state/tabContentState/privateState') const closeState = require('../../common/state/tabContentState/closeState') const frameStateUtil = require('../../../js/state/frameStateUtil') @@ -20,7 +18,6 @@ const {getSetting} = require('../../../js/settings') // Styles const {intersection} = require('../../renderer/components/styles/global') -const {theme} = require('../../renderer/components/styles/theme') module.exports.getThemeColor = (state, frameKey) => { const frame = frameStateUtil.getFrameByKey(state, frameKey) @@ -76,7 +73,7 @@ module.exports.showTabEndIcon = (state, frameKey) => { return ( !closeState.hasFixedCloseIcon(state, frameKey) && !closeState.hasRelativeCloseIcon(state, frameKey) && - !isEntryIntersected(state, 'tabs', intersection.at40) + !isEntryIntersected(state, 'tabs', intersection.at46) ) } @@ -99,41 +96,3 @@ module.exports.centralizeTabIcons = (state, frameKey, isPinned) => { return isPinned || isEntryIntersected(state, 'tabs', intersection.at40) } - -module.exports.getTabEndIconBackgroundColor = (state, frameKey) => { - const frame = frameStateUtil.getFrameByKey(state, frameKey) - - if (frame == null) { - return false - } - - const themeColor = module.exports.getThemeColor(state, frameKey) - const isPrivate = privateState.isPrivateTab(state, frameKey) - const isPartition = partitionState.isPartitionTab(state, frameKey) - const isHover = frameStateUtil.getTabHoverState(state, frameKey) - const isActive = frameStateUtil.isFrameKeyActive(state, frameKey) - const hasCloseIcon = closeState.showCloseTabIcon(state, frameKey) - const isIntersecting = isEntryIntersected(state, 'tabs', intersection.at40) - - let backgroundColor = theme.tab.background - - if (isActive && themeColor) { - backgroundColor = themeColor - } - if (isActive && !themeColor) { - backgroundColor = theme.tab.active.background - } - if (isIntersecting) { - backgroundColor = 'transparent' - } - if (!isActive && isPrivate) { - backgroundColor = theme.tab.private.background - } - if ((isActive || isHover) && isPrivate) { - backgroundColor = theme.tab.active.private.background - } - - return isPartition || isPrivate || hasCloseIcon - ? `linear-gradient(to left, ${backgroundColor} 10px, transparent 40px)` - : `linear-gradient(to left, ${backgroundColor} 0, transparent 12px)` -} diff --git a/app/renderer/components/frame/frame.js b/app/renderer/components/frame/frame.js index 1ce7897ee17..230de5213a8 100644 --- a/app/renderer/components/frame/frame.js +++ b/app/renderer/components/frame/frame.js @@ -869,12 +869,14 @@ class Frame extends React.Component { const contextMenu = currentWindow.get('contextMenuDetail') const tab = tabId && tabId > -1 && tabState.getByTabId(state, tabId) + const previewFrameKey = currentWindow.get('previewFrameKey') + const props = {} // used in renderer props.transitionState = ownProps.transitionState props.partition = frameStateUtil.getPartition(frame) props.isFullScreen = frame.get('isFullScreen') - props.isPreview = frame.get('key') === currentWindow.get('previewFrameKey') + props.isPreview = frame.get('key') === previewFrameKey props.isActive = frameStateUtil.isFrameKeyActive(currentWindow, frame.get('key')) props.showFullScreenWarning = frame.get('showFullScreenWarning') props.location = location @@ -956,8 +958,7 @@ class Frame extends React.Component { }
{ this.webviewContainer = node }} className={cx({ - webviewContainer: true, - isPreview: this.props.isPreview + webviewContainer: true })} /> { diff --git a/app/renderer/components/main/main.js b/app/renderer/components/main/main.js index 8dd21193cb1..b050ece37a9 100644 --- a/app/renderer/components/main/main.js +++ b/app/renderer/components/main/main.js @@ -574,6 +574,7 @@ class Main extends React.Component { item.get('frameOrigin') ? activeOrigin === item.get('frameOrigin') : true).size > 0 props.showFindBar = activeFrame.get('findbarShown') && !activeFrame.get('isFullScreen') props.sortedFrames = frameStateUtil.getSortedFrameKeys(currentWindow) + props.hasFramePreview = currentWindow.get('previewFrameKey') != null props.showDownloadBar = currentWindow.getIn(['ui', 'downloadsToolbar', 'isVisible']) && state.get('downloads') && state.get('downloads').size > 0 props.title = activeFrame.get('title') @@ -736,7 +737,10 @@ class Main extends React.Component { }
- + { this.props.sortedFrames.map((frameKey) => { this.tabNode = node }} className={css( styles.tabArea__tab, - // tab icon only (on pinned tab / small tab) this.props.isPinnedTab && styles.tabArea__tab_pinned, this.props.centralizeTabIcons && styles.tabArea__tab_centered, - this.props.showAudioTopBorder && styles.tabArea__tab_audioTopBorder, - - // Windows specific style (color) - isWindows && styles.tabArea__tab_forWindows, - - // Set background-color and color to active tab and private tab - this.props.isActive && styles.tabArea__tab_active, - this.props.isPrivateTab && styles.tabArea__tab_private, - (this.props.isPrivateTab && this.props.isActive) && styles.tabArea__tab_private_active, - - // Apply themeColor if tab is active and not private - isThemed && styles.tabArea__tab_themed + this.props.showAudioTopBorder && styles.tabArea__tab_audioTopBorder )} - style={instanceStyles} data-test-id='tab' data-test-active-tab={this.props.isActive} data-test-pinned-tab={this.props.isPinnedTab} data-test-private-tab={this.props.isPrivateTab} data-frame-key={this.props.frameKey} draggable - title={this.props.title} + title={this.props.isPreview ? null : this.props.title} onDrag={this.onDrag} onDragStart={this.onDragStart} onDragEnd={this.onDragEnd} @@ -400,20 +405,53 @@ class Tab extends React.Component { const styles = StyleSheet.create({ tabArea: { boxSizing: 'border-box', - display: 'inline-block', position: 'relative', - verticalAlign: 'top', overflow: 'hidden', - height: '-webkit-fill-available', flex: '1 1 0', - + '--tab-margin-top': `-${theme.tab.borderWidth}px`, + // put the top border underneath tab-stip top border, and + // the left border underneath the previous tab's right border + margin: `var(--tab-margin-top) 0 0 -${theme.tab.borderWidth}px`, + border: `solid var(--tab-border-width, ${theme.tab.borderWidth}px) var(--tab-border-color)`, + // Border bottom is added to the tabArea__tab so that we do not get + // 45-degree angles when the bottom border is different color from the side borders. + // This could change when we can put the tab's background on this element, + // which can happen when tab dragging does not introduce a left/right 'space' when a tab + // is dragged over. + borderBottomWidth: `0 !important`, // aphrodite puts this above the border defined in the previous line, so use important :-( + zIndex: 100, + transformOrigin: 'bottom center', + minWidth: 0, + width: 0, // no-drag is applied to the button and tab area // ref: tabs__tabStrip__newTabButton on tabs.js WebkitAppRegion: 'no-drag', - // There's a special case that tabs should span the full width // if there are a full set of them. - maxWidth: '184px' + maxWidth: '184px', + // Use css variables for some transition options so that we can change them + // with other classes below, without having to re-define the whole property. + // Avoid aphrodite bug which will change css variables + // to --tab--webkit-transition-duration by calling it 'transit'. + '--tab-transit-duration': theme.tab.transitionDurationOut, + '--tab-transit-easing': theme.tab.transitionEasingOut, + // z-index should be delayed when it changes, so that preview tab stays on top until + // its scale transition has completed + '--tab-zindex-delay': theme.tab.transitionDurationOut, + transition: ['box-shadow', 'transform', 'border', 'margin', 'opacity'] + .map(prop => `${prop} var(--tab-transit-duration) var(--tab-transit-easing) 0s`) + .join(',') + + ', z-index var(--tab-zindex-duration, 0s) linear var(--tab-zindex-delay)', + '--tab-background': theme.tab.background, + '--tab-color': theme.tab.color, + '--tab-border-color': theme.tab.borderColor, + ':hover': { + '--tab-background': `var(--tab-background-hover, ${theme.tab.hover.background})`, + '--tab-color': `var(--tab-color-hover, ${theme.tab.color})`, + '--tab-border-color': `var(--tab-border-color-hover, ${theme.tab.borderColor})`, + '--tab-transit-duration': theme.tab.transitionDurationIn, + '--tab-transit-easing': theme.tab.transitionEasingIn + } }, tabArea_dragging_left: { @@ -431,30 +469,91 @@ const styles = StyleSheet.create({ }, tabArea_isPinned: { - flex: 'initial' + flex: 'initial', + width: 'auto' }, tabArea_partOfFullPageSet: { maxWidth: 'initial' }, + tabArea_isActive: { + '--tab-background': theme.tab.active.background, + '--border-bottom-color': theme.tab.active.background, + '--tab-border-color-bottom': 'var(--tab-background)', + '--tab-transit-duration': theme.tab.transitionDurationIn, + '--tab-transit-easing': theme.tab.transitionEasingIn + }, + + tabArea_isPreview: { + '--tab-background': 'white', + '--tab-background-hover': 'white', + '--tab-color': theme.tab.color, + '--tab-color-hover': theme.tab.color, + '--tab-border-color': 'white', + '--tab-border-color-hover': 'white', + zIndex: 110, + transform: 'scale(1.08)', + boxShadow: '0 4px 8px rgba(0, 0, 0, 0.22)', + // want the zindex to change immediately when previewing, but delay when un-previewing + '--tab-zindex-delay': '0s', + '--tab-zindex-duration': '0s', + '--tab-transit-duration': theme.tab.transitionDurationIn, + '--tab-transit-easing': theme.tab.transitionEasingIn + }, + + tabArea_siblingIsPreview: { + // when un-previewing, if there's still another tab previewed + // then we want to immediately have that tab on top of the last-previewed tab + // but have the last previewed tab wait to be underneath the next tab in the DOM + '--tab-zindex-delay': '0s', + '--tab-zindex-duration': '2s', + willChange: 'transform' + }, + + tabArea_isActive_siblingIsPreview: { + opacity: '.5' + }, + + tabArea_forWindows: { + '--tab-color': theme.tab.forWindows.color + }, + + tabArea_private: { + '--tab-background': theme.tab.private.background, + '--tab-background-hover': theme.tab.active.private.background, + '--tab-color-hover': theme.tab.active.private.color, + '--tab-border-color-hover': theme.tab.hover.private.borderColor + }, + + tabArea_private_active: { + '--tab-background': theme.tab.active.private.background, + '--tab-color': theme.tab.active.private.color, + '--tab-background-hover': theme.tab.active.private.background, + '--tab-color-hover': theme.tab.active.private.color + }, + + tabArea_themed: { + '--tab-color': `var(--theme-color-fg)`, + '--tab-background': `var(--theme-color-bg)`, + '--tab-background-hover': 'var(--theme-color-bg)', + '--tab-color-hover': 'var(--theme-color-fg)' + }, + tabArea__tab: { - borderWidth: '0 1px 0 0', - borderStyle: 'solid', - borderColor: theme.tab.borderColor, boxSizing: 'border-box', - color: theme.tab.color, + background: `var(--tab-background, ${theme.tab.background})`, display: 'flex', - transition: theme.tab.transition, - height: '-webkit-fill-available', - width: '-webkit-fill-available', + paddingBottom: 0, // explicitly defined for transition on active + transition: ['background-color', 'color', 'border'] + .map(prop => `${prop} var(--tab-transit-duration) var(--tab-transit-easing) 0s`) + .join(','), + height: '100%', alignItems: 'center', justifyContent: 'space-between', position: 'relative', - - ':hover': { - background: theme.tab.hover.background - } + color: `var(--tab-color, ${theme.tab.color})`, + borderBottom: `solid var(--tab-border-width, ${theme.tab.borderWidth}px) var(--tab-border-color-bottom, var(--tab-border-color))` }, tabArea__tab_audioTopBorder: { @@ -484,47 +583,6 @@ const styles = StyleSheet.create({ margin: 0 }, - // Windows specific style - tabArea__tab_forWindows: { - color: theme.tab.forWindows.color - }, - - tabArea__tab_active: { - background: theme.tab.active.background, - - ':hover': { - background: theme.tab.active.background - } - }, - - tabArea__tab_private: { - background: theme.tab.private.background, - - ':hover': { - color: theme.tab.active.private.color, - background: theme.tab.active.private.background - } - }, - - tabArea__tab_private_active: { - background: theme.tab.active.private.background, - color: theme.tab.active.private.color, - - ':hover': { - background: theme.tab.active.private.background - } - }, - - tabArea__tab_themed: { - color: `var(--theme-color-fg, inherit)`, - background: `var(--theme-color-bg, inherit)`, - - ':hover': { - color: `var(--theme-color-fg, inherit)`, - background: `var(--theme-color-bg, inherit)` - } - }, - // The sentinel is responsible to respond to tabs // intersection state. This is an empty hidden element // which `width` value shouldn't be changed unless the intersection @@ -544,7 +602,9 @@ const styles = StyleSheet.create({ display: 'flex', flex: '1', minWidth: '0', // @see https://bugzilla.mozilla.org/show_bug.cgi?id=1108514#c5 - margin: `0 ${globalStyles.spacing.defaultTabMargin}` + // can't do 'ancestor:hover child' selector in aphrodite, so cascade a variable + margin: `0 6px 0 ${globalStyles.spacing.defaultTabMargin}`, // bring the right margin closer as we do fade-out + overflow: 'visible' }, tabArea__tab__identity_centered: { diff --git a/app/renderer/components/tabs/tabs.js b/app/renderer/components/tabs/tabs.js index afee41bdf53..ef61aef08f0 100644 --- a/app/renderer/components/tabs/tabs.js +++ b/app/renderer/components/tabs/tabs.js @@ -149,6 +149,7 @@ class Tabs extends React.Component { const props = {} // used in renderer props.previewTabPageIndex = currentWindow.getIn(['ui', 'tabs', 'previewTabPageIndex']) + props.previewTabFrameKey = frameStateUtil.getPreviewFrameKey(currentWindow) props.currentTabs = currentTabs props.partOfFullPageSet = currentTabs.size === tabsPerTabPage props.onNextPage = currentTabs.size >= tabsPerTabPage && totalPages > pageIndex + 1 @@ -165,6 +166,7 @@ class Tabs extends React.Component { } render () { + const isTabPreviewing = this.props.previewTabFrameKey != null this.tabRefs = [] return
element // (in renderer main.js). However, there is no reason to set it higher than that. - transition: visibility 0s linear 150ms; - // 1000 - z-index: @zindexWindow; + --visibility-delay: .15s; + --frame-zIndex-delay: 0s; + transition: visibility 0s linear var(--visibility-delay), z-index 0s linear var(--frame-zIndex-delay); + // active frame z-index + z-index: 1000; + + .hasFramePreview &.isActive { + // Move to below preview frame, but do so as part of 'preview' entry animation + // so that it is clear which frame is being previewed and which is active. + z-index: 993; + --frame-zIndex-delay: .2s; + } + &:not(.isActive) { - // 900 - z-index: @zindexWindowNotActive; + // non-active always lower than active + z-index: 990; } &.isPreview { background: #222; - // 1100 - z-index: @zindexWindowIsPreview; + z-index: 995; } // show frame when active or preview &.isActive, @@ -111,7 +140,7 @@ html, &.isExiting { visibility: visible; // show instantly - transition-delay: 0s; + --visibility-delay: 0s; } // hide frame whilst it's figuring out which location to be // note: that isEntering is timed-out at a value set in renderer main.js @@ -119,7 +148,8 @@ html, // has an actual location &.isEntering.isBlankLocation { visibility: hidden; - transition-delay: 0s; + // hide instantly (at first) + --visibility-delay: 0s; } webview { @@ -133,12 +163,6 @@ html, .webviewContainer { height: 100%; width: 100%; - &.isPreview { - will-change: opacity; - opacity: 0.5; - animation: tabFadeIn 0.75s ease-in-out; - animation-fill-mode: forwards; - } } } diff --git a/test/unit/app/common/state/tabContentStateTest/audioStateTest.js b/test/unit/app/common/state/tabContentStateTest/audioStateTest.js index 19aec879344..0ac91b32b9f 100644 --- a/test/unit/app/common/state/tabContentStateTest/audioStateTest.js +++ b/test/unit/app/common/state/tabContentStateTest/audioStateTest.js @@ -156,7 +156,7 @@ describe('audioState unit tests', function () { const state = defaultState .setIn(['frames', index, 'audioPlaybackActive'], false) .setIn(['frames', index, 'audioMuted'], false) - .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at45) + .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at46) const result = audioState.showAudioTopBorder(state, frameKey, false) assert.equal(result, false) }) diff --git a/test/unit/app/common/state/tabContentStateTest/closeStateTest.js b/test/unit/app/common/state/tabContentStateTest/closeStateTest.js index 32f44f565c4..bd9619a6fb7 100644 --- a/test/unit/app/common/state/tabContentStateTest/closeStateTest.js +++ b/test/unit/app/common/state/tabContentStateTest/closeStateTest.js @@ -150,7 +150,7 @@ describe('closeState unit tests', function () { it('returns false if tab is intersected and not active', function * () { const state = defaultState .set('activeFrameKey', 1337) - .setIn(['ui', 'tabs', 'intersectionRatio', intersection.at45]) + .setIn(['ui', 'tabs', 'intersectionRatio', intersection.at46]) const result = closeState.showCloseTabIcon(state, frameKey) assert.equal(result, false) }) diff --git a/test/unit/app/common/state/tabUIStateTest.js b/test/unit/app/common/state/tabUIStateTest.js index 58a11a189bb..25de62604f2 100644 --- a/test/unit/app/common/state/tabUIStateTest.js +++ b/test/unit/app/common/state/tabUIStateTest.js @@ -8,7 +8,6 @@ const assert = require('assert') const Immutable = require('immutable') const mockery = require('mockery') const fakeElectron = require('../../../lib/fakeElectron') -const {theme} = require('../../../../../app/renderer/components/styles/theme') const {intersection} = require('../../../../../app/renderer/components/styles/global') const frameKey = 1 @@ -307,95 +306,4 @@ describe('tabUIState unit tests', function () { assert.equal(result, true) }) }) - - describe('getTabEndIconBackgroundColor', function () { - before(function () { - // just a helper for results - this.defaultResult = (bgColor, color1Size, color2Size) => - `linear-gradient(to left, ${bgColor} ${color1Size}, transparent ${color2Size})` - }) - - describe('when tab is private', function () { - it('returns `tab.private.background` color if not active', function * () { - const state = defaultState - .set('activeFrameKey', 1337) - .mergeIn(['frames', index], { - themeColor: '#c0ff33', - isPrivate: true - }) - const result = tabUIState.getTabEndIconBackgroundColor(state, frameKey) - const expected = this.defaultResult(theme.tab.private.background, '10px', '40px') - assert.equal(result, expected) - }) - - it('returns `tab.active.private.background` if tab is active', function * () { - const state = defaultState - .mergeIn(['frames', index], { - themeColor: '#c0ff33', - isPrivate: true - }) - const result = tabUIState.getTabEndIconBackgroundColor(state, frameKey) - const expected = this.defaultResult(theme.tab.active.private.background, '10px', '40px') - assert.equal(result, expected) - }) - - it('retuns active private color if tab is being hovered', function * () { - const state = defaultState - .mergeIn(['frames', index], { - themeColor: '#c0ff33', - isPrivate: true - }) - .setIn(['ui', 'tabs', 'hoverTabIndex'], index) - const result = tabUIState.getTabEndIconBackgroundColor(state, frameKey) - const expected = this.defaultResult(theme.tab.active.private.background, '10px', '40px') - assert.equal(result, expected) - }) - }) - - describe('when tab is not private', function () { - it('returns the themeColor if tab is active', function * () { - const state = defaultState - .setIn(['frames', index, 'themeColor'], '#c0ff33') - const result = tabUIState.getTabEndIconBackgroundColor(state, frameKey) - const expected = this.defaultResult('#c0ff33', '0', '12px') - assert.equal(result, expected) - }) - it('returns `theme.tab.background` if tab is not active', function * () { - const state = defaultState - .set('activeFrameKey', 1337) - .setIn(['frames', index, 'themeColor'], '#c0ff33') - const result = tabUIState.getTabEndIconBackgroundColor(state, frameKey) - const expected = this.defaultResult(theme.tab.background, '0', '12px') - assert.equal(result, expected) - }) - }) - - describe('returns `linear gradient` size', function () { - it('at 10px/40px if tab is partitioned', function * () { - const state = defaultState - .mergeIn(['frames', index], { - partitionNumber: 1337, - themeColor: '#c0ff33' - }) - const result = tabUIState.getTabEndIconBackgroundColor(state, frameKey) - const expected = this.defaultResult('#c0ff33', '10px', '40px') - assert.equal(result, expected) - }) - it('at 10px/40px gradient size if tab has a visible close icon', function * () { - const state = defaultState - .setIn(['frames', index, 'themeColor'], '#c0ff33') - .setIn(['ui', 'tabs', 'hoverTabIndex'], index) - const result = tabUIState.getTabEndIconBackgroundColor(state, frameKey) - const expected = this.defaultResult('#c0ff33', '10px', '40px') - assert.equal(result, expected) - }) - it('at 0/12px gradient size if is neither private, partition or has close icon visible', function * () { - const state = defaultState - .setIn(['frames', index, 'themeColor'], '#c0ff33') - const result = tabUIState.getTabEndIconBackgroundColor(state, frameKey) - const expected = this.defaultResult('#c0ff33', '0', '12px') - assert.equal(result, expected) - }) - }) - }) }) From 1cf17b553436c20769407bc951b25d0aea79c1ef Mon Sep 17 00:00:00 2001 From: Pete Miller Date: Thu, 18 Jan 2018 16:25:35 -0800 Subject: [PATCH 02/15] Improve tab icon animation in order to account for the fact that items take up space before they fade in Makes transitions / animations faster and with appropriate timing-function for appearing Further imrpovement could be done by animating the space the icons take up, so that the tab title text does not jump to take up less room before the icon fades in, and also animating the icon's out, on unmount, and not just when they appear. --- app/renderer/components/tabs/content/closeTabIcon.js | 6 +++--- app/renderer/components/tabs/content/newSessionIcon.js | 4 ++-- app/renderer/components/tabs/content/privateIcon.js | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/renderer/components/tabs/content/closeTabIcon.js b/app/renderer/components/tabs/content/closeTabIcon.js index d6e90ee2d8c..89eba27edc6 100644 --- a/app/renderer/components/tabs/content/closeTabIcon.js +++ b/app/renderer/components/tabs/content/closeTabIcon.js @@ -74,8 +74,8 @@ class CloseTabIcon extends React.Component { ) if (shouldTransitionIn) { this.element.animate(opacityIncreaseElementKeyframes, { - duration: 200, - easing: 'linear' + duration: 120, + easing: 'ease-out' }) } } @@ -116,7 +116,7 @@ const styles = StyleSheet.create({ height: globalStyles.spacing.closeIconSize, // mask icon to gray to avoid calling another icon on hover - transition: 'filter 150ms linear', + transition: 'filter 120ms ease', filter: theme.tab.icon.close.filter, ':hover': { diff --git a/app/renderer/components/tabs/content/newSessionIcon.js b/app/renderer/components/tabs/content/newSessionIcon.js index f759a1e3b4b..104e3391ed2 100644 --- a/app/renderer/components/tabs/content/newSessionIcon.js +++ b/app/renderer/components/tabs/content/newSessionIcon.js @@ -66,8 +66,8 @@ class NewSessionIcon extends React.Component { ) if (shouldTransitionIn) { this.element.animate(opacityIncreaseElementKeyframes, { - duration: 200, - easing: 'linear' + duration: 120, + easing: 'ease-out' }) } } diff --git a/app/renderer/components/tabs/content/privateIcon.js b/app/renderer/components/tabs/content/privateIcon.js index be2187a0e2b..2f583340a7b 100644 --- a/app/renderer/components/tabs/content/privateIcon.js +++ b/app/renderer/components/tabs/content/privateIcon.js @@ -61,8 +61,8 @@ class PrivateIcon extends React.Component { ) if (shouldTransitionIn) { this.element.animate(opacityIncreaseElementKeyframes, { - duration: 200, - easing: 'linear' + duration: 120, + easing: 'ease-out' }) } } From dfe7bf2786a53a5fb47ccbb60ff5d75bcf9c3b31 Mon Sep 17 00:00:00 2001 From: Pete Miller Date: Wed, 17 Jan 2018 16:37:03 -0800 Subject: [PATCH 03/15] Navigation Bar and Tab theme color updates Colors chosen as per design team. Move as many style decisions as possible to css rather than state util. Remove more occurances of creating a new stylesheet on every render cycle. --- app/renderer/components/styles/theme.js | 49 ++++++++--------- .../components/tabs/content/favIcon.js | 9 +--- .../components/tabs/content/newSessionIcon.js | 14 ++--- .../components/tabs/content/privateIcon.js | 17 +++--- app/renderer/components/tabs/tab.js | 54 ++++++++++--------- js/lib/color.js | 8 ++- less/window.less | 2 +- 7 files changed, 76 insertions(+), 77 deletions(-) diff --git a/app/renderer/components/styles/theme.js b/app/renderer/components/styles/theme.js index 13618465b68..2248e42aa1a 100644 --- a/app/renderer/components/styles/theme.js +++ b/app/renderer/components/styles/theme.js @@ -48,7 +48,7 @@ }, tabsToolbar: { - backgroundColor: '#ddd', + backgroundColor: '#CDD1D5', border: { color: '#bbb' @@ -141,37 +141,46 @@ transitionDurationIn: '200ms', transitionEasingOut: 'ease-in', transitionEasingIn: 'ease-out', - background: '#ddd', + background: 'rgb(205,209,213)', borderColor: '#bbb', borderWidth: 1, - color: '#5a5a5a', + color: '#222', + defaultFaviconColor: globalStyles.color.mediumGray, + defaultFaviconColorLight: '#fff', hover: { - background: '#eaeaea', - + background: 'rgb(219,221,223)', + active: { + background: 'rgb(243,243,243)' + }, private: { + background: 'rgb(225,223,238)', borderColor: 'rgba(75, 60, 110, .7)' } }, - forWindows: { - color: '#555' - }, - active: { - background: '#f8f8f8', - + background: 'rgb(233,233,234)', + colorLight: 'rgb(255, 255, 255)', + colorDark: '#222', private: { - background: '#4b3c6e', - color: '#fff' + background: 'rgb(75,60,110)', + color: '#fff', + defaultFaviconColor: '#fff' } }, private: { - background: '#d9d6e0', + background: 'rgb(217,213,228)', color: '#4b3c6e' }, + preview: { + background: 'rgb(240,240,240)', + boxShadow: '0 -2px 12px rgba(0, 0, 0, 0.22)', + scale: '1.06' + }, + icon: { default: { primary: '#fff', @@ -186,7 +195,7 @@ }, audio: { - color: '#69B9F9' + color: '#256ea9' }, close: { @@ -194,15 +203,7 @@ }, symbol: { - color: globalStyles.color.black100, - - default: { - backgroundColor: globalStyles.color.mediumGray, - - light: { - backgroundColor: globalStyles.color.white100 - } - } + color: globalStyles.color.black100 } } }, diff --git a/app/renderer/components/tabs/content/favIcon.js b/app/renderer/components/tabs/content/favIcon.js index d4d96c7daca..aba6c9b973b 100644 --- a/app/renderer/components/tabs/content/favIcon.js +++ b/app/renderer/components/tabs/content/favIcon.js @@ -120,8 +120,7 @@ class Favicon extends React.Component { !this.props.favicon && css( styles.icon__symbol_default, - this.props.showIconAtReducedSize && styles.icon__symbol_default_reducedSize, - themeLight && styles.icon__symbol_default_colorLight + this.props.showIconAtReducedSize && styles.icon__symbol_default_reducedSize ) ) } /> @@ -171,15 +170,11 @@ const styles = StyleSheet.create({ WebkitMaskPosition: 'center', WebkitMaskImage: `url(${defaultIconSvg})`, WebkitMaskSize: '14px', - backgroundColor: theme.tab.icon.symbol.default.backgroundColor + backgroundColor: 'var(--tab-default-icon-color)' }, icon__symbol_default_reducedSize: { WebkitMaskSize: '10px' - }, - - icon__symbol_default_colorLight: { - backgroundColor: theme.tab.icon.symbol.default.light.backgroundColor } }) diff --git a/app/renderer/components/tabs/content/newSessionIcon.js b/app/renderer/components/tabs/content/newSessionIcon.js index 104e3391ed2..575e34db3f7 100644 --- a/app/renderer/components/tabs/content/newSessionIcon.js +++ b/app/renderer/components/tabs/content/newSessionIcon.js @@ -85,19 +85,11 @@ class NewSessionIcon extends React.Component { return null } - const newSessionProps = StyleSheet.create({ - newSession__indicator: { - filter: this.props.isActive && this.props.textIsWhite - ? 'invert(100%)' - : 'none' - } - }) - return @@ -106,7 +98,12 @@ const styles = StyleSheet.create({ // Override default properties backgroundSize: 0, height: globalStyles.spacing.sessionIconSize, - width: globalStyles.spacing.sessionIconSize + width: globalStyles.spacing.sessionIconSize, + backgroundColor: theme.tab.icon.private.background.notActive + }, + + icon_private_active: { + backgroundColor: theme.tab.icon.private.background.active } }) diff --git a/app/renderer/components/tabs/tab.js b/app/renderer/components/tabs/tab.js index 6a24dfbb12a..e5cdb4be80a 100644 --- a/app/renderer/components/tabs/tab.js +++ b/app/renderer/components/tabs/tab.js @@ -38,13 +38,12 @@ const globalStyles = require('../styles/global') const {theme} = require('../styles/theme') // Utils -const {getTextColorForBackground} = require('../../../../js/lib/color') +const {backgroundRequiresLightText} = require('../../../../js/lib/color') const {isIntermediateAboutPage} = require('../../../../js/lib/appUrlUtil') const contextMenus = require('../../../../js/contextMenus') const dnd = require('../../../../js/dnd') const frameStateUtil = require('../../../../js/state/frameStateUtil') const {hasTabAsRelatedTarget} = require('../../lib/tabUtil') -const isWindows = require('../../../common/lib/platformUtil').isWindows() const {getCurrentWindowId} = require('../../currentWindow') const {setObserver} = require('../../lib/observerUtil') const UrlUtil = require('../../../../js/lib/urlutil') @@ -316,8 +315,18 @@ class Tab extends React.Component { const isThemed = !this.props.isPrivateTab && this.props.isActive && this.props.themeColor const instanceStyles = { } if (isThemed) { - instanceStyles['--theme-color-fg'] = getTextColorForBackground(this.props.themeColor) + const lightText = backgroundRequiresLightText(this.props.themeColor) instanceStyles['--theme-color-bg'] = this.props.themeColor + // complementing foreground color + instanceStyles['--theme-color-fg'] = + lightText + ? theme.tab.active.colorLight + : theme.tab.active.colorDark + // complementing icon color + instanceStyles['--theme-color-default-icon'] = + lightText + ? theme.tab.defaultFaviconColorLight + : theme.tab.defaultFaviconColor } if (this.props.tabWidth) { instanceStyles.flex = `0 0 ${this.props.tabWidth}px` @@ -334,8 +343,6 @@ class Tab extends React.Component { this.props.isPreview && styles.tabArea_isPreview, !this.props.isPreview && this.props.anyTabIsPreview && styles.tabArea_siblingIsPreview, this.props.isActive && this.props.anyTabIsPreview && styles.tabArea_isActive_siblingIsPreview, - // Windows specific style (color) - isWindows && styles.tabArea__tab_forWindows, // Set background-color and color to active tab and private tab this.props.isActive && styles.tabArea_isActive, this.props.isPrivateTab && styles.tabArea_private, @@ -445,9 +452,11 @@ const styles = StyleSheet.create({ '--tab-background': theme.tab.background, '--tab-color': theme.tab.color, '--tab-border-color': theme.tab.borderColor, + '--tab-default-icon-color': theme.tab.defaultFaviconColor, ':hover': { '--tab-background': `var(--tab-background-hover, ${theme.tab.hover.background})`, '--tab-color': `var(--tab-color-hover, ${theme.tab.color})`, + '--tab-default-icon-color': `var(--tab-default-icon-color-hover, ${theme.tab.defaultFaviconColor})`, '--tab-border-color': `var(--tab-border-color-hover, ${theme.tab.borderColor})`, '--tab-transit-duration': theme.tab.transitionDurationIn, '--tab-transit-easing': theme.tab.transitionEasingIn @@ -478,23 +487,24 @@ const styles = StyleSheet.create({ }, tabArea_isActive: { + '--tab-color': theme.tab.active.colorDark, '--tab-background': theme.tab.active.background, - '--border-bottom-color': theme.tab.active.background, + '--tab-background-hover': theme.tab.hover.active.background, '--tab-border-color-bottom': 'var(--tab-background)', '--tab-transit-duration': theme.tab.transitionDurationIn, '--tab-transit-easing': theme.tab.transitionEasingIn }, tabArea_isPreview: { - '--tab-background': 'white', - '--tab-background-hover': 'white', - '--tab-color': theme.tab.color, - '--tab-color-hover': theme.tab.color, - '--tab-border-color': 'white', - '--tab-border-color-hover': 'white', + '--tab-background': theme.tab.preview.background, + '--tab-background-hover': theme.tab.preview.background, + '--tab-color': theme.tab.active.colorDark, + '--tab-color-hover': theme.tab.active.colorDark, + '--tab-border-color': theme.tab.preview.background, + '--tab-border-color-hover': theme.tab.preview.background, zIndex: 110, - transform: 'scale(1.08)', - boxShadow: '0 4px 8px rgba(0, 0, 0, 0.22)', + transform: `scale(${theme.tab.preview.scale})`, + boxShadow: theme.tab.preview.boxShadow, // want the zindex to change immediately when previewing, but delay when un-previewing '--tab-zindex-delay': '0s', '--tab-zindex-duration': '0s', @@ -515,29 +525,25 @@ const styles = StyleSheet.create({ opacity: '.5' }, - tabArea_forWindows: { - '--tab-color': theme.tab.forWindows.color - }, - tabArea_private: { '--tab-background': theme.tab.private.background, - '--tab-background-hover': theme.tab.active.private.background, - '--tab-color-hover': theme.tab.active.private.color, - '--tab-border-color-hover': theme.tab.hover.private.borderColor + '--tab-background-hover': theme.tab.hover.private.background }, tabArea_private_active: { '--tab-background': theme.tab.active.private.background, '--tab-color': theme.tab.active.private.color, '--tab-background-hover': theme.tab.active.private.background, - '--tab-color-hover': theme.tab.active.private.color + '--tab-color-hover': theme.tab.active.private.color, + '--tab-default-icon-color': theme.tab.active.private.defaultFaviconColor }, tabArea_themed: { '--tab-color': `var(--theme-color-fg)`, '--tab-background': `var(--theme-color-bg)`, '--tab-background-hover': 'var(--theme-color-bg)', - '--tab-color-hover': 'var(--theme-color-fg)' + '--tab-color-hover': 'var(--theme-color-fg)', + '--tab-default-icon-color': 'var(--theme-color-default-icon)' }, tabArea__tab: { @@ -566,7 +572,7 @@ const styles = StyleSheet.create({ left: 0, right: 0, height: '2px', - background: 'lightskyblue' + background: theme.tab.icon.audio.color } }, diff --git a/js/lib/color.js b/js/lib/color.js index 4fa57ea2b19..b77bd5f2f60 100644 --- a/js/lib/color.js +++ b/js/lib/color.js @@ -8,7 +8,7 @@ module.exports.parseColor = (color) => { return div.style.color.split('(')[1].split(')')[0].split(',') } -module.exports.getTextColorForBackground = (color) => { +module.exports.backgroundRequiresLightText = (color) => { // Calculate text color based on contrast with background: // https://24ways.org/2010/calculating-color-contrast/ const rgb = module.exports.parseColor(color) @@ -17,5 +17,9 @@ module.exports.getTextColorForBackground = (color) => { } const [r, g, b] = rgb const yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000 - return yiq >= 128 ? 'black' : 'white' + return yiq < 128 +} + +module.exports.getTextColorForBackground = (color) => { + return module.exports.backgroundRequiresLightText(color) ? 'white' : 'black' } diff --git a/less/window.less b/less/window.less index 0e3b4df0d7f..6848c79f42a 100644 --- a/less/window.less +++ b/less/window.less @@ -40,7 +40,7 @@ html, } .top { - background: linear-gradient(to bottom, #eaeaea, #f2f2f4); + background: linear-gradient(to top, #e9e9ea 0%, #e9e9ea 27px, #f6f6f7 100%); &.allowDragging { -webkit-app-region: drag; } From b4e278887acaa52a1a0d9d106b15af686f94f17d Mon Sep 17 00:00:00 2001 From: Pete Miller Date: Thu, 18 Jan 2018 20:47:32 -0800 Subject: [PATCH 04/15] Tab title now supports nice subpixel antialiasing Don't use background mask text, and render on the CPU for better looking text. Make sure tab is not rendered on GPU when it's not doing anything by not specifying will-change css properties, so text is rendered with subpixel antialising. Smoother visual transition between selected tabs Ensures that the gradient used for hiding the title is not visible in the tab's previous colors when a tab switches between active and inactive. Still allows for transitioning the color of the tab title text. Tab title text no longer needs different weights for different platforms - rendering is consistent --- .../components/tabs/content/favIcon.js | 1 - .../components/tabs/content/tabIcon.js | 1 - .../components/tabs/content/tabTitle.js | 88 ++++++++++--------- app/renderer/components/tabs/tab.js | 3 + 4 files changed, 49 insertions(+), 44 deletions(-) diff --git a/app/renderer/components/tabs/content/favIcon.js b/app/renderer/components/tabs/content/favIcon.js index aba6c9b973b..fc564aede3a 100644 --- a/app/renderer/components/tabs/content/favIcon.js +++ b/app/renderer/components/tabs/content/favIcon.js @@ -151,7 +151,6 @@ const styles = StyleSheet.create({ icon__symbol_loading: { position: 'absolute', left: 0, - willChange: 'transform', backgroundImage: `url(${loadingIconSvg})`, backgroundRepeat: 'no-repeat', backgroundPosition: 'top left', diff --git a/app/renderer/components/tabs/content/tabIcon.js b/app/renderer/components/tabs/content/tabIcon.js index 224b802021e..cada96f3037 100644 --- a/app/renderer/components/tabs/content/tabIcon.js +++ b/app/renderer/components/tabs/content/tabIcon.js @@ -75,7 +75,6 @@ const styles = StyleSheet.create({ backgroundRepeat: 'no-repeat', // Default animation properties - willChange: 'opacity', animationFillMode: 'forwards' }, diff --git a/app/renderer/components/tabs/content/tabTitle.js b/app/renderer/components/tabs/content/tabTitle.js index 575b485066c..510d55dc6f5 100644 --- a/app/renderer/components/tabs/content/tabTitle.js +++ b/app/renderer/components/tabs/content/tabTitle.js @@ -14,11 +14,6 @@ const frameStateUtil = require('../../../../../js/state/frameStateUtil') const tabUIState = require('../../../../common/state/tabUIState') const tabState = require('../../../../common/state/tabState') -// Utils -const platformUtil = require('../../../../common/lib/platformUtil') -const isWindows = platformUtil.isWindows() -const isDarwin = platformUtil.isDarwin() - // Styles const globalStyles = require('../../styles/global') @@ -29,14 +24,10 @@ class TabTitle extends React.Component { const frameKey = frameStateUtil.getFrameKeyByTabId(currentWindow, tabId) const props = {} - props.isWindows = isWindows - props.isDarwin = isDarwin props.isPinned = tabState.isTabPinned(state, tabId) props.showTabTitle = titleState.showTabTitle(currentWindow, frameKey) props.displayTitle = titleState.getDisplayTitle(currentWindow, frameKey) props.addExtraGutter = tabUIState.addExtraGutterToTitle(currentWindow, frameKey) - props.isTextWhite = tabUIState.checkIfTextColor(currentWindow, frameKey, 'white') - props.gradientColor = tabUIState.getTabEndIconBackgroundColor(currentWindow, frameKey) props.tabId = tabId return props @@ -46,22 +37,12 @@ class TabTitle extends React.Component { if (this.props.isPinned || !this.props.showTabTitle) { return null } - const perPageGradient = StyleSheet.create({ - tab__title_gradient: { - '::after': { - background: this.props.gradientColor - } - } - }) - - return
{this.props.displayTitle}
@@ -79,34 +60,57 @@ const styles = StyleSheet.create({ userSelect: 'none', fontSize: globalStyles.fontSize.tabTitle, lineHeight: '1', + fontWeight: 400, minWidth: 0, // see https://stackoverflow.com/a/36247448/4902448 + width: '-webkit-fill-available', marginLeft: '6px', + // Fade any overflow text out, + // but use a technique which preserves: + // 1. Sub-pixel colored antialized text - e.g. background-clip: text does not use this. + // (with color - zoom in 20x on mac and you'll see) + // 2. Background and text color transition with no artifacts left over due to a + // pseudo element gradient fade which cannot transition (cannot transition linear gradient color) overflow: 'hidden', - - // this enable us to have gradient text - '::after': { - zIndex: globalStyles.zindex.zindexTabs, - content: '""', + position: 'relative', + color: 'transparent', + // the text, rendered as normal, but cut off early + '::before': { position: 'absolute', + display: 'block', + overflow: 'hidden', + top: 0, + left: 0, + right: 'calc(18% - 1px)', bottom: 0, + fontWeight: 'inherit', + content: 'attr(data-text)', + color: 'var(--tab-color)', + transition: `color var(--tab-transit-duration) var(--tab-transit-easing)` + }, + // the fade-out using background gradient clipped to text + // and only starting off where actual text is cut off + '::after': { + position: 'absolute', + display: 'block', + top: 0, + left: 0, right: 0, - width: '-webkit-fill-available', - height: '-webkit-fill-available', - // add a pixel margin so the box-shadow of the - // webview is not covered by the gradient - marginBottom: '1px' + bottom: 0, + fontWeight: 'inherit', + content: 'attr(data-text)', + // restrict background-size to a tiny portion of the text as background-clip: text means + // no sub-pixel antializing + background: `linear-gradient( + to right, + var(--tab-color) 0, + transparent 100% + ) right top / 18% 100% no-repeat`, + WebkitBackgroundClip: 'text !important', // !important is neccessary because aphrodite will put this at top of ruleset :-( + color: 'transparent', + transition: `background 0s var(--tab-transit-easing) var(--tab-transit-duration)` } }, - tab__title_isDarwin: { - fontWeight: '400' - }, - - tab__title_isWindows: { - fontWeight: '500', - fontSize: globalStyles.fontSize.tabTitle - }, - tab__title_extraGutter: { margin: '0 2px' } diff --git a/app/renderer/components/tabs/tab.js b/app/renderer/components/tabs/tab.js index e5cdb4be80a..f278d1cedec 100644 --- a/app/renderer/components/tabs/tab.js +++ b/app/renderer/components/tabs/tab.js @@ -549,6 +549,9 @@ const styles = StyleSheet.create({ tabArea__tab: { boxSizing: 'border-box', background: `var(--tab-background, ${theme.tab.background})`, + // make sure the tab element which contains the background color + // has a new layer, so that the tab title text is rendered with subpixel antialiasing + // that knows about both the foreground and background colors display: 'flex', paddingBottom: 0, // explicitly defined for transition on active transition: ['background-color', 'color', 'border'] From f57d68e7a584b8e3e4491885df6c749a8bdb6a20 Mon Sep 17 00:00:00 2001 From: Pete Miller Date: Thu, 15 Mar 2018 14:37:34 -0700 Subject: [PATCH 05/15] Tab Mouse Cursor (radial gradient) --- app/renderer/components/styles/global.js | 1 - app/renderer/components/tabs/tab.js | 54 +++++++++++++++++++++--- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/app/renderer/components/styles/global.js b/app/renderer/components/styles/global.js index d4e86b4732d..9f33bd01450 100644 --- a/app/renderer/components/styles/global.js +++ b/app/renderer/components/styles/global.js @@ -231,7 +231,6 @@ const globalStyles = { zindexWindowIsPreview: '1100', zindexDownloadsBar: '1000', zindexTabs: '1000', - zindexTabsAudioTopBorder: '1001', zindexTabsThumbnail: '1100', zindexNavigationBar: '2000', zindexUrlbarNotLegend: '2100', diff --git a/app/renderer/components/tabs/tab.js b/app/renderer/components/tabs/tab.js index f278d1cedec..5c592ed7062 100644 --- a/app/renderer/components/tabs/tab.js +++ b/app/renderer/components/tabs/tab.js @@ -166,9 +166,14 @@ class Tab extends React.Component { // In case there's a tab preview happening, cancel the preview // when mouse is over a tab windowActions.setTabPageHoverState(this.props.tabPageIndex, false) + // cache offset position for hover radial grandient + if (this.tabNode) { + const tabBounds = this.tabNode.getBoundingClientRect() + this.tabOffsetLeft = tabBounds.left + } } - onMouseMove () { + onMouseMove (e) { // dispatch a message to the store so it can delay // and preview the tab based on mouse idle time clearTimeout(this.mouseTimeout) @@ -177,6 +182,17 @@ class Tab extends React.Component { windowActions.setTabHoverState(this.props.frameKey, true, true) }, getSetting(settings.TAB_PREVIEW_TIMING)) + // fancy radial gradient mouse tracker + if (this.elementRef) { + // only update position once per render frame + if (!this.nextFrameSetTabMouseX) { + var x = e.pageX - this.tabOffsetLeft + this.nextFrameSetTabMouseX = window.requestAnimationFrame(() => { + this.nextFrameSetTabMouseX = null + this.elementRef.style.setProperty('--tab-mouse-x', `${x}px`) + }) + } + } } onAuxClick (e) { @@ -452,14 +468,16 @@ const styles = StyleSheet.create({ '--tab-background': theme.tab.background, '--tab-color': theme.tab.color, '--tab-border-color': theme.tab.borderColor, + '--tab-background-hover': theme.tab.hover.background, '--tab-default-icon-color': theme.tab.defaultFaviconColor, ':hover': { - '--tab-background': `var(--tab-background-hover, ${theme.tab.hover.background})`, + '--tab-background': `var(--tab-background-hover)`, '--tab-color': `var(--tab-color-hover, ${theme.tab.color})`, '--tab-default-icon-color': `var(--tab-default-icon-color-hover, ${theme.tab.defaultFaviconColor})`, '--tab-border-color': `var(--tab-border-color-hover, ${theme.tab.borderColor})`, '--tab-transit-duration': theme.tab.transitionDurationIn, - '--tab-transit-easing': theme.tab.transitionEasingIn + '--tab-transit-easing': theme.tab.transitionEasingIn, + '--tab-mouse-opacity': '1' } }, @@ -491,6 +509,7 @@ const styles = StyleSheet.create({ '--tab-background': theme.tab.active.background, '--tab-background-hover': theme.tab.hover.active.background, '--tab-border-color-bottom': 'var(--tab-background)', + '--tab-mouse-opacity': '0 !important' '--tab-transit-duration': theme.tab.transitionDurationIn, '--tab-transit-easing': theme.tab.transitionEasingIn }, @@ -562,12 +581,35 @@ const styles = StyleSheet.create({ justifyContent: 'space-between', position: 'relative', color: `var(--tab-color, ${theme.tab.color})`, - borderBottom: `solid var(--tab-border-width, ${theme.tab.borderWidth}px) var(--tab-border-color-bottom, var(--tab-border-color))` + borderBottom: `solid var(--tab-border-width, ${theme.tab.borderWidth}px) var(--tab-border-color-bottom, var(--tab-border-color))`, + + // mouse-tracking radial gradient + '::before': { + content: '" "', + position: 'absolute', + left: 'var(--tab-mouse-x)', + top: 0, + bottom: 0, + width: 'calc(190px * var(--tab-mouse-opacity, 0))', + background: `radial-gradient( + circle farthest-corner, + var(--tab-background-hover), + transparent + )`, + filter: 'brightness(var(--tab-mouse-brightness, 106%))', + transform: 'translateX(-50%)', + transition: 'opacity var(--tab-transit-duration) ease, width 0s linear var(--tab-transit-duration)', + opacity: 'var(--tab-mouse-opacity, 0)' + }, + ':hover:before': { + // Show immediately, and fade-in opacity, + // but when leaving, wait for fade-out to finish before hiding. + transitionDelay: '0s' + } }, tabArea__tab_audioTopBorder: { - '::before': { - zIndex: globalStyles.zindex.zindexTabsAudioTopBorder, + '::after': { content: `''`, display: 'block', position: 'absolute', From 8f25269123ad9dffb12c48e708aefecf55007db4 Mon Sep 17 00:00:00 2001 From: Pete Miller Date: Thu, 18 Jan 2018 20:49:15 -0800 Subject: [PATCH 06/15] Adjust shadow filter on favicons on dark-color tabs Fix #12722 This was previously causing irregularities in brand icons. --- app/renderer/components/styles/theme.js | 2 +- app/renderer/components/tabs/tab.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/renderer/components/styles/theme.js b/app/renderer/components/styles/theme.js index 2248e42aa1a..d15850a4f49 100644 --- a/app/renderer/components/styles/theme.js +++ b/app/renderer/components/styles/theme.js @@ -44,7 +44,7 @@ filter: { makeWhite: 'brightness(0) invert(1)', - whiteShadow: 'drop-shadow(0px 0px 2px rgb(255, 255, 255))' + whiteShadow: 'drop-shadow(-10px 0px 12px rgb(255, 255, 255))' }, tabsToolbar: { diff --git a/app/renderer/components/tabs/tab.js b/app/renderer/components/tabs/tab.js index 5c592ed7062..477e4e97bff 100644 --- a/app/renderer/components/tabs/tab.js +++ b/app/renderer/components/tabs/tab.js @@ -649,7 +649,7 @@ const styles = StyleSheet.create({ tabArea__tab__identity: { justifyContent: 'flex-start', alignItems: 'center', - overflow: 'hidden', + overflow: 'visible', display: 'flex', flex: '1', minWidth: '0', // @see https://bugzilla.mozilla.org/show_bug.cgi?id=1108514#c5 From 3d927bc55e3dbec7e54feb58bee40e7750f82ee9 Mon Sep 17 00:00:00 2001 From: Pete Miller Date: Mon, 22 Jan 2018 10:09:25 -0800 Subject: [PATCH 07/15] New 'close tab' icon with no background until hover. Background only shows on hover, has 'active' state. Better UI for focusing on tab content. Remove fade in and out, to improve UX around knowing that clicking in the close button's location will result in an immediate destructive action. --- app/extensions/brave/img/tabs/close_btn.svg | 1 - app/renderer/components/styles/theme.js | 12 ++ .../components/tabs/content/closeTabIcon.js | 104 ++++++++---------- .../tabs/content/closeTabIconTest.js | 1 - 4 files changed, 56 insertions(+), 62 deletions(-) delete mode 100644 app/extensions/brave/img/tabs/close_btn.svg diff --git a/app/extensions/brave/img/tabs/close_btn.svg b/app/extensions/brave/img/tabs/close_btn.svg deleted file mode 100644 index 926636ea455..00000000000 --- a/app/extensions/brave/img/tabs/close_btn.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/renderer/components/styles/theme.js b/app/renderer/components/styles/theme.js index d15850a4f49..2247aa2905b 100644 --- a/app/renderer/components/styles/theme.js +++ b/app/renderer/components/styles/theme.js @@ -148,6 +148,18 @@ defaultFaviconColor: globalStyles.color.mediumGray, defaultFaviconColorLight: '#fff', + closeButton: { + background: 'transparent', + borderRadius: '2px', + active: { + background: '#cb2c00' + }, + hover: { + color: 'white', + background: '#fd4f01' + } + }, + hover: { background: 'rgb(219,221,223)', active: { diff --git a/app/renderer/components/tabs/content/closeTabIcon.js b/app/renderer/components/tabs/content/closeTabIcon.js index 89eba27edc6..11b1917648a 100644 --- a/app/renderer/components/tabs/content/closeTabIcon.js +++ b/app/renderer/components/tabs/content/closeTabIcon.js @@ -4,11 +4,10 @@ const React = require('react') const ReactDOM = require('react-dom') -const {StyleSheet} = require('aphrodite/no-important') +const {StyleSheet, css} = require('aphrodite/no-important') // Components const ReduxComponent = require('../../reduxComponent') -const TabIcon = require('./tabIcon') // State helpers const tabState = require('../../../../common/state/tabState') @@ -19,9 +18,6 @@ const frameStateUtil = require('../../../../../js/state/frameStateUtil') // Styles const globalStyles = require('../../styles/global') const {theme} = require('../../styles/theme') -const {opacityIncreaseElementKeyframes} = require('../../styles/animations') - -const closeTabSvg = require('../../../../extensions/brave/img/tabs/close_btn.svg') class CloseTabIcon extends React.Component { constructor (props) { @@ -49,88 +45,76 @@ class CloseTabIcon extends React.Component { return props } - componentDidMount (props) { - this.transitionIfRequired() - } - - componentDidUpdate (prevProps) { - this.transitionIfRequired(prevProps) - } - - transitionIfRequired (prevProps) { - const shouldTransitionIn = ( - // need to have the element created already - this.element && - // no icon is showing if pinned tab - !this.props.isPinned && - // should show the icon - // TODO: if we want to animate the unmounting of the component (when - // tab is unhovered), then we should use https://github.com/reactjs/react-transition-group - // For now, we'll just not do anything since we can't - the element - // will have already been removed - this.props.showCloseIcon && - // state has changed - (!prevProps || this.props.showCloseIcon !== prevProps.showCloseIcon) - ) - if (shouldTransitionIn) { - this.element.animate(opacityIncreaseElementKeyframes, { - duration: 120, - easing: 'ease-out' - }) - } - } - setRef (ref) { this.element = ReactDOM.findDOMNode(ref) } render () { - if (this.props.isPinned || !this.props.showCloseIcon) { + if (this.props.isPinned || !this.props.showCloseIcon) { // <-- comment out to always show, in order to view in inspector return null } - return + > + + + +
} } const styles = StyleSheet.create({ - icon_close: { - marginRight: globalStyles.spacing.defaultTabMargin, - backgroundImage: `url(${closeTabSvg})`, - - // Override default properties - backgroundSize: globalStyles.spacing.closeIconSize, + closeIcon: { + '--close-line-color': 'var(--tab-color)', + boxSizing: 'border-box', + alignSelf: 'center', + position: 'relative', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: theme.tab.closeButton.borderRadius, + background: theme.tab.closeButton.background, + marginRight: `calc(${globalStyles.spacing.defaultTabMargin} - 2px)`, width: globalStyles.spacing.closeIconSize, height: globalStyles.spacing.closeIconSize, - - // mask icon to gray to avoid calling another icon on hover - transition: 'filter 120ms ease', - filter: theme.tab.icon.close.filter, - + zIndex: globalStyles.zindex.zindexTabsThumbnail, ':hover': { - filter: 'none' + '--close-line-color': theme.tab.closeButton.hover.color, + background: theme.tab.closeButton.hover.background, + '--close-transit-duration': theme.tab.transitionDurationIn, + '--close-transit-timing': theme.tab.transitionEasingIn + }, + ':active': { + background: theme.tab.closeButton.active.background } }, - icon_close_centered: { + closeIcon__graphic: { + flex: 1 + }, + + closeIcon__line: { + stroke: 'var(--close-line-color)' + }, + + closeIcon_centered: { position: 'absolute', - left: 0, + left: `calc(50% - (${globalStyles.spacing.closeIconSize} / 2))`, right: 0, - top: 0, + top: `calc(50% - (${globalStyles.spacing.closeIconSize} / 2))`, bottom: 0, - margin: 'auto' + margin: 0 } }) diff --git a/test/unit/app/renderer/components/tabs/content/closeTabIconTest.js b/test/unit/app/renderer/components/tabs/content/closeTabIconTest.js index 76388f87a67..4c091b8b97c 100644 --- a/test/unit/app/renderer/components/tabs/content/closeTabIconTest.js +++ b/test/unit/app/renderer/components/tabs/content/closeTabIconTest.js @@ -64,7 +64,6 @@ describe('Tabs content - CloseTabIcon', function () { useCleanCache: true }) mockery.registerMock('electron', fakeElectron) - mockery.registerMock('../../../../extensions/brave/img/tabs/close_btn.svg') windowStore = require('../../../../../../../js/stores/windowStore') appStore = require('../../../../../../../js/stores/appStoreRenderer') CloseTabIcon = require('../../../../../../../app/renderer/components/tabs/content/closeTabIcon') From 1327dfc45a2dc9d4468ee7409c8bb024ed9ebb9c Mon Sep 17 00:00:00 2001 From: Pete Miller Date: Tue, 23 Jan 2018 00:56:24 -0800 Subject: [PATCH 08/15] Do not allow transparency in a tab's theme colors, which can prevent the theme color rendering the same as in the content frame. Blend's the theme color with a white background, which is common in web content. Fix #12803 Also do not throw error when parsing color and no valid value is found --- js/lib/color.js | 32 +++++++++++++++++++++++++++++++- js/stores/windowStore.js | 8 ++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/js/lib/color.js b/js/lib/color.js index b77bd5f2f60..e5707383f2d 100644 --- a/js/lib/color.js +++ b/js/lib/color.js @@ -5,7 +5,14 @@ module.exports.parseColor = (color) => { const div = document.createElement('div') div.style.color = color - return div.style.color.split('(')[1].split(')')[0].split(',') + const normalizedColor = div.style.color + if (typeof normalizedColor === 'string' && + normalizedColor.includes('(') && + normalizedColor.includes(')') && + normalizedColor.includes(',')) { + return div.style.color.split('(')[1].split(')')[0].split(',') + } + return null } module.exports.backgroundRequiresLightText = (color) => { @@ -23,3 +30,26 @@ module.exports.backgroundRequiresLightText = (color) => { module.exports.getTextColorForBackground = (color) => { return module.exports.backgroundRequiresLightText(color) ? 'white' : 'black' } + +module.exports.removeAlphaChannelForBackground = (color, bR, bG, bB) => { + const rgba = module.exports.parseColor(color) + if (!rgba) { + return null + } + // handle no alpha channel + if (rgba.length !== 4 || Number.isNaN(rgba[3])) { + return color + } + + // remove alpha channel, blending color with background + const [oR, oG, oB, oA] = rgba + + const newR = blendChannel(oR, bR, oA) + const newG = blendChannel(oG, bG, oA) + const newB = blendChannel(oB, bB, oA) + return `rgb(${newR}, ${newG}, ${newB})` +} + +function blendChannel (original, background, alpha) { + return Math.round((original * alpha) + ((1 - alpha) * background)) +} diff --git a/js/stores/windowStore.js b/js/stores/windowStore.js index baa9ab4ece9..3b15b436ddf 100644 --- a/js/stores/windowStore.js +++ b/js/stores/windowStore.js @@ -16,6 +16,7 @@ const messages = require('../constants/messages') const debounce = require('../lib/debounce') const getSetting = require('../settings').getSetting const UrlUtil = require('../lib/urlutil') +const color = require('../lib/color') const {l10nErrorText} = require('../../app/common/lib/httpUtil') const { makeImmutable } = require('../../app/common/state/immutableUtil') const {aboutUrls, getTargetAboutUrl, newFrameUrl} = require('../lib/appUrlUtil') @@ -397,10 +398,13 @@ const doAction = (action) => { { const frameKey = action.frameProps.get('key') if (action.themeColor !== undefined) { - windowState = windowState.setIn(frameStateUtil.frameStatePath(windowState, frameKey).concat(['themeColor']), action.themeColor) + // remove alpha channel + const solidColor = color.removeAlphaChannelForBackground(action.themeColor, 255, 255, 255) + windowState = windowState.setIn(frameStateUtil.frameStatePath(windowState, frameKey).concat(['themeColor']), solidColor) } if (action.computedThemeColor !== undefined) { - windowState = windowState.setIn(frameStateUtil.frameStatePath(windowState, frameKey).concat(['computedThemeColor']), action.computedThemeColor) + const solidColor = color.removeAlphaChannelForBackground(action.computedThemeColor, 255, 255, 255) + windowState = windowState.setIn(frameStateUtil.frameStatePath(windowState, frameKey).concat(['computedThemeColor']), solidColor) } break } From 7412dfc66da098500cddb915dea3cad9efae5342 Mon Sep 17 00:00:00 2001 From: Pete Miller Date: Sat, 3 Mar 2018 01:13:36 -0800 Subject: [PATCH 09/15] Increase tab bar height slightly to reduce crammed space and fix tab icon / text vertical centering --- app/renderer/components/styles/global.js | 3 ++- app/renderer/components/tabs/tab.js | 3 +-- app/renderer/components/tabs/tabsToolbar.js | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/renderer/components/styles/global.js b/app/renderer/components/styles/global.js index 9f33bd01450..4f940990d24 100644 --- a/app/renderer/components/styles/global.js +++ b/app/renderer/components/styles/global.js @@ -150,7 +150,8 @@ const globalStyles = { buttonWidth: '25px', navbarHeight: '36px', downloadsBarHeight: '60px', - tabsToolbarHeight: '26px', + // This includes the toolbar's borders + tabsToolbarHeight: '30px', tabPagesHeight: '7px', bookmarkHangerMaxWidth: '350px', bookmarksToolbarHeight: '24px', diff --git a/app/renderer/components/tabs/tab.js b/app/renderer/components/tabs/tab.js index 477e4e97bff..286a33873d4 100644 --- a/app/renderer/components/tabs/tab.js +++ b/app/renderer/components/tabs/tab.js @@ -572,7 +572,6 @@ const styles = StyleSheet.create({ // has a new layer, so that the tab title text is rendered with subpixel antialiasing // that knows about both the foreground and background colors display: 'flex', - paddingBottom: 0, // explicitly defined for transition on active transition: ['background-color', 'color', 'border'] .map(prop => `${prop} var(--tab-transit-duration) var(--tab-transit-easing) 0s`) .join(','), @@ -654,7 +653,7 @@ const styles = StyleSheet.create({ flex: '1', minWidth: '0', // @see https://bugzilla.mozilla.org/show_bug.cgi?id=1108514#c5 // can't do 'ancestor:hover child' selector in aphrodite, so cascade a variable - margin: `0 6px 0 ${globalStyles.spacing.defaultTabMargin}`, // bring the right margin closer as we do fade-out + margin: `calc(var(--tab-border-width, 0) * -1px) 6px 0 ${globalStyles.spacing.defaultTabMargin}`, // bring the right margin closer as we do fade-out overflow: 'visible' }, diff --git a/app/renderer/components/tabs/tabsToolbar.js b/app/renderer/components/tabs/tabsToolbar.js index fb4a008a916..9dd320858dd 100644 --- a/app/renderer/components/tabs/tabsToolbar.js +++ b/app/renderer/components/tabs/tabsToolbar.js @@ -102,8 +102,9 @@ const styles = StyleSheet.create({ // This element is set as border-box so it does not // take into account the borders as width gutter, so we - // increase its size by 1px to include the top border - height: `calc(${globalStyles.spacing.tabsToolbarHeight} + 1px)` + // increase its size by 1px to include the top border. + // This MUST result in an even number so we support veritcal centering. + height: globalStyles.spacing.tabsToolbarHeight }, tabsToolbar__button_menu: { From 206458bde4858359f7884a42e11580e039c05af8 Mon Sep 17 00:00:00 2001 From: Pete Miller Date: Sat, 3 Mar 2018 01:17:11 -0800 Subject: [PATCH 10/15] Remove tab color animations when not desired: - on switch between active <-> inactive as it is not appropriate - on first load, as state changes very quickly --- app/renderer/components/tabs/tab.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/app/renderer/components/tabs/tab.js b/app/renderer/components/tabs/tab.js index 286a33873d4..b6cf9f34605 100644 --- a/app/renderer/components/tabs/tab.js +++ b/app/renderer/components/tabs/tab.js @@ -324,6 +324,19 @@ class Tab extends React.Component { easing: 'ease-in-out' }) } + // no transition between: + // - active <-> inactive state + // - no theme color and first theme color + if (this.elementRef && prevProps && ( + prevProps.isActive !== this.props.isActive || + (!prevProps.themeColor && this.props.themeColor) + )) { + const className = css(styles.tabArea_instantTransition) + this.elementRef.classList.add(className) + window.requestAnimationFrame(() => { + this.elementRef.classList.remove(className) + }) + } } render () { @@ -481,6 +494,10 @@ const styles = StyleSheet.create({ } }, + tabArea_instantTransition: { + '--tab-transit-duration': '0 !important' + }, + tabArea_dragging_left: { paddingLeft: globalStyles.spacing.dragSpacing }, @@ -510,8 +527,6 @@ const styles = StyleSheet.create({ '--tab-background-hover': theme.tab.hover.active.background, '--tab-border-color-bottom': 'var(--tab-background)', '--tab-mouse-opacity': '0 !important' - '--tab-transit-duration': theme.tab.transitionDurationIn, - '--tab-transit-easing': theme.tab.transitionEasingIn }, tabArea_isPreview: { From e4ad15e445be740b1483f206ccb222035e95e3f2 Mon Sep 17 00:00:00 2001 From: Pete Miller Date: Wed, 14 Mar 2018 18:44:55 -0700 Subject: [PATCH 11/15] Ensure tab title text is vertically centered in tab Even with large emojis --- app/renderer/components/styles/global.js | 2 +- app/renderer/components/styles/theme.js | 1 + app/renderer/components/tabs/content/tabTitle.js | 2 +- app/renderer/components/tabs/tab.js | 13 +++++++------ 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/app/renderer/components/styles/global.js b/app/renderer/components/styles/global.js index 4f940990d24..69fd552365f 100644 --- a/app/renderer/components/styles/global.js +++ b/app/renderer/components/styles/global.js @@ -151,7 +151,7 @@ const globalStyles = { navbarHeight: '36px', downloadsBarHeight: '60px', // This includes the toolbar's borders - tabsToolbarHeight: '30px', + tabsToolbarHeight: '29px', tabPagesHeight: '7px', bookmarkHangerMaxWidth: '350px', bookmarksToolbarHeight: '24px', diff --git a/app/renderer/components/styles/theme.js b/app/renderer/components/styles/theme.js index 2247aa2905b..01f82ab4e90 100644 --- a/app/renderer/components/styles/theme.js +++ b/app/renderer/components/styles/theme.js @@ -145,6 +145,7 @@ borderColor: '#bbb', borderWidth: 1, color: '#222', + identityHeight: globalStyles.spacing.iconSize, defaultFaviconColor: globalStyles.color.mediumGray, defaultFaviconColorLight: '#fff', diff --git a/app/renderer/components/tabs/content/tabTitle.js b/app/renderer/components/tabs/content/tabTitle.js index 510d55dc6f5..885c902277e 100644 --- a/app/renderer/components/tabs/content/tabTitle.js +++ b/app/renderer/components/tabs/content/tabTitle.js @@ -59,9 +59,9 @@ const styles = StyleSheet.create({ flex: 1, userSelect: 'none', fontSize: globalStyles.fontSize.tabTitle, - lineHeight: '1', fontWeight: 400, minWidth: 0, // see https://stackoverflow.com/a/36247448/4902448 + lineHeight: globalStyles.spacing.tabsToolbarHeight, width: '-webkit-fill-available', marginLeft: '6px', // Fade any overflow text out, diff --git a/app/renderer/components/tabs/tab.js b/app/renderer/components/tabs/tab.js index b6cf9f34605..0945bf06e8e 100644 --- a/app/renderer/components/tabs/tab.js +++ b/app/renderer/components/tabs/tab.js @@ -661,20 +661,21 @@ const styles = StyleSheet.create({ }, tabArea__tab__identity: { - justifyContent: 'flex-start', - alignItems: 'center', - overflow: 'visible', - display: 'flex', flex: '1', minWidth: '0', // @see https://bugzilla.mozilla.org/show_bug.cgi?id=1108514#c5 - // can't do 'ancestor:hover child' selector in aphrodite, so cascade a variable margin: `calc(var(--tab-border-width, 0) * -1px) 6px 0 ${globalStyles.spacing.defaultTabMargin}`, // bring the right margin closer as we do fade-out + // make sure title text is not cut off, but is also vertically centered + // by giving it full height of favicon + height: theme.tab.identityHeight, + display: 'flex', + justifyContent: 'flex-start', + alignItems: 'center', overflow: 'visible' }, tabArea__tab__identity_centered: { - justifyContent: 'center', flex: 'auto', + justifyContent: 'center', padding: 0, margin: 0 } From a80741ab35f9b316f538267973cabad2939ccb0d Mon Sep 17 00:00:00 2001 From: Pete Miller Date: Thu, 15 Mar 2018 01:53:47 -0700 Subject: [PATCH 12/15] tab tooltip matches tab title text in all circumstances --- app/renderer/components/tabs/tab.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/renderer/components/tabs/tab.js b/app/renderer/components/tabs/tab.js index 0945bf06e8e..38bbe5d579b 100644 --- a/app/renderer/components/tabs/tab.js +++ b/app/renderer/components/tabs/tab.js @@ -28,6 +28,7 @@ const privateState = require('../../../common/state/tabContentState/privateState const audioState = require('../../../common/state/tabContentState/audioState') const tabUIState = require('../../../common/state/tabUIState') const tabState = require('../../../common/state/tabState') +const titleState = require('../../../common/state/tabContentState/titleState') // Constants const settings = require('../../../../js/constants/settings') @@ -296,7 +297,7 @@ class Tab extends React.Component { props.anyTabIsPreview = previewFrameKey != null props.tabWidth = isPinned ? null : currentWindow.getIn(['ui', 'tabs', 'fixTabWidth']) props.themeColor = tabUIState.getThemeColor(currentWindow, frameKey) - props.title = frame.get('title') + props.title = titleState.getDisplayTitle(currentWindow, frameKey) props.tabPageIndex = frameStateUtil.getTabPageIndex(currentWindow) props.partOfFullPageSet = partOfFullPageSet props.showAudioTopBorder = audioState.showAudioTopBorder(currentWindow, frameKey, isPinned) From 5960919713084227cfe8e9cc2750a58871c8ffaf Mon Sep 17 00:00:00 2001 From: Pete Miller Date: Thu, 15 Mar 2018 02:00:15 -0700 Subject: [PATCH 13/15] Workaround for aphrodite causing initial css values to be transitioned when they should not be. Aphrodite does not inject style rules to DOM until some time after they are first used. Unfortunately that creates elements with 0 css rules with rules that are then injected after the elements are added to the DOM, causing any transitions to fire on the initial values, which normally would not be the case. See https://codepen.io/petemill/pen/rdeqqv for demo. --- app/renderer/components/tabs/tab.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/renderer/components/tabs/tab.js b/app/renderer/components/tabs/tab.js index 38bbe5d579b..c5406a825dc 100644 --- a/app/renderer/components/tabs/tab.js +++ b/app/renderer/components/tabs/tab.js @@ -4,6 +4,7 @@ const React = require('react') const {StyleSheet, css} = require('aphrodite/no-important') +const aphroditeInject = require('aphrodite/lib/inject') const Immutable = require('immutable') // Components @@ -232,6 +233,14 @@ class Tab extends React.Component { } componentDidMount () { + // Workaround the fact that aphrodite will not inject style rules until some time + // after css([rules]) is called. + // This causes CSS transitions to fire on the changes from the default values to the + // specified initial values, which definitely should not happen. + // Ensuring styles are written to DOM before this element is rendered + // means the element will not be rendered first with 0 rules. + // See https://codepen.io/petemill/pen/rdeqqv for a reproduction. + aphroditeInject.flushToStyleTag() // unobserve tabs that we don't need. This will // likely be made by onObserve method but added again as // just to double-check From 43c04f6faf5b3a44c35c2fbd2c7ac793af45a326 Mon Sep 17 00:00:00 2001 From: Pete Miller Date: Thu, 15 Mar 2018 13:23:34 -0700 Subject: [PATCH 14/15] audio icon color and size --- app/renderer/components/styles/theme.js | 3 ++- app/renderer/components/tabs/content/audioTabIcon.js | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/renderer/components/styles/theme.js b/app/renderer/components/styles/theme.js index 01f82ab4e90..21e46bce836 100644 --- a/app/renderer/components/styles/theme.js +++ b/app/renderer/components/styles/theme.js @@ -208,7 +208,8 @@ }, audio: { - color: '#256ea9' + color: '#2377bb', + hoverColor: '#3b566b' }, close: { diff --git a/app/renderer/components/tabs/content/audioTabIcon.js b/app/renderer/components/tabs/content/audioTabIcon.js index 84067b48e53..b25a09e6c20 100644 --- a/app/renderer/components/tabs/content/audioTabIcon.js +++ b/app/renderer/components/tabs/content/audioTabIcon.js @@ -116,12 +116,12 @@ class AudioTabIcon extends React.Component { const styles = StyleSheet.create({ icon_audio: { overflow: 'hidden', - margin: '0 -2px 0 2px', + margin: '1px -2px 0 2px', // get centered with funky font awesome sizing color: theme.tab.icon.audio.color, - fontSize: '13px', - - // Override default properties - zIndex: globalStyles.zindex.zindexTabsAudioTopBorder + fontSize: '14px', + ':hover': { + color: theme.tab.icon.audio.hoverColor + } } }) From c82515917abbe7e6b56505adda706419b3c24a73 Mon Sep 17 00:00:00 2001 From: Pete Miller Date: Thu, 15 Mar 2018 13:49:18 -0700 Subject: [PATCH 15/15] tab audio button tooltip --- app/renderer/components/tabs/content/audioTabIcon.js | 2 ++ app/renderer/components/tabs/content/tabIcon.js | 1 + 2 files changed, 3 insertions(+) diff --git a/app/renderer/components/tabs/content/audioTabIcon.js b/app/renderer/components/tabs/content/audioTabIcon.js index b25a09e6c20..7e6cc84d843 100644 --- a/app/renderer/components/tabs/content/audioTabIcon.js +++ b/app/renderer/components/tabs/content/audioTabIcon.js @@ -5,6 +5,7 @@ const React = require('react') const ReactDOM = require('react-dom') const {StyleSheet} = require('aphrodite/no-important') +const locale = require('../../../../../js/l10n') // Components const ReduxComponent = require('../../reduxComponent') @@ -108,6 +109,7 @@ class AudioTabIcon extends React.Component { className={styles.icon_audio} symbol={this.audioIcon} onClick={this.toggleMute} + title={locale.translation(this.props.audioPlaying ? 'muteTab' : 'unmuteTab')} ref={this.setRef} /> } diff --git a/app/renderer/components/tabs/content/tabIcon.js b/app/renderer/components/tabs/content/tabIcon.js index cada96f3037..586efdc05ab 100644 --- a/app/renderer/components/tabs/content/tabIcon.js +++ b/app/renderer/components/tabs/content/tabIcon.js @@ -32,6 +32,7 @@ class TabIcon extends ImmutableComponent { draggable={this.props.draggable} onClick={this.props.onClick} style={this.props.style} + title={this.props.title} {...altProps} > {