diff --git a/app/common/state/tabUIState.js b/app/common/state/tabUIState.js index b656b41bfac..772c014a584 100644 --- a/app/common/state/tabUIState.js +++ b/app/common/state/tabUIState.js @@ -6,6 +6,8 @@ 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') @@ -18,6 +20,7 @@ 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) @@ -114,3 +117,44 @@ 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) { + if (process.env.NODE_ENV !== 'test') { + console.error('Unable to find frame for getTabEndIconBackgroundColor method') + } + 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/styles/global.js b/app/renderer/components/styles/global.js index 0739fcadb00..38621b59a46 100644 --- a/app/renderer/components/styles/global.js +++ b/app/renderer/components/styles/global.js @@ -1,18 +1,18 @@ /* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ +* License, v. 2.0. If a copy of the MPL was not distributed with this file, +* You can obtain one at http://mozilla.org/MPL/2.0/. */ const {opacityIncreaseKeyframes} = require('./animations') /** - * Use this file when the style you need - * is applied in more than one element, or depends on it - * Use theme.js file to include colors that can be customized - * - * TODO: - * remove unnecessary styles properties (as items get refactored) - * migrate customizable options to theme.js - */ +* Use this file when the style you need +* is applied in more than one element, or depends on it +* Use theme.js file to include colors that can be customized +* +* TODO: +* remove unnecessary styles properties (as items get refactored) +* migrate customizable options to theme.js +*/ const globalStyles = { defaultFontFamily: `-apple-system, BlinkMacSystemFont, "Segoe UI"` + @@ -214,7 +214,7 @@ const globalStyles = { zindexWindowIsPreview: '1100', zindexDownloadsBar: '1000', zindexTabs: '1000', - zindexTabsAudioTopBorder: '10001', + zindexTabsAudioTopBorder: '1001', zindexTabsThumbnail: '1100', zindexTabsDragIndicator: '1100', zindexNavigationBar: '2000', diff --git a/app/renderer/components/tabs/content/audioTabIcon.js b/app/renderer/components/tabs/content/audioTabIcon.js index 6389ee27aa6..a8b2ab59d16 100644 --- a/app/renderer/components/tabs/content/audioTabIcon.js +++ b/app/renderer/components/tabs/content/audioTabIcon.js @@ -72,6 +72,7 @@ class AudioTabIcon extends React.Component { const styles = StyleSheet.create({ audioTab__icon: { margin: '0 -2px 0 2px', + zIndex: globalStyles.zindex.zindexTabsAudioTopBorder, color: theme.tab.content.icon.audio.color, fontSize: '13px', height: globalStyles.spacing.iconSize, diff --git a/app/renderer/components/tabs/content/privateIcon.js b/app/renderer/components/tabs/content/privateIcon.js index afd68ea1180..e2fc9cd8e02 100644 --- a/app/renderer/components/tabs/content/privateIcon.js +++ b/app/renderer/components/tabs/content/privateIcon.js @@ -58,12 +58,14 @@ module.exports = ReduxComponent.connect(PrivateIcon) const styles = StyleSheet.create({ private__icon: { + zIndex: 99, boxSizing: 'border-box', WebkitMaskRepeat: 'no-repeat', WebkitMaskPosition: 'center', WebkitMaskImage: `url(${privateSvg})`, WebkitMaskSize: globalStyles.spacing.sessionIconSize, width: globalStyles.spacing.sessionIconSize, - height: globalStyles.spacing.sessionIconSize + height: globalStyles.spacing.sessionIconSize, + marginRight: globalStyles.spacing.defaultTabMargin } }) diff --git a/app/renderer/components/tabs/tab.js b/app/renderer/components/tabs/tab.js index c01a8d0fccc..27be0be0316 100644 --- a/app/renderer/components/tabs/tab.js +++ b/app/renderer/components/tabs/tab.js @@ -55,6 +55,7 @@ class Tab extends React.Component { this.onMouseMove = this.onMouseMove.bind(this) this.onMouseEnter = this.onMouseEnter.bind(this) this.onMouseLeave = this.onMouseLeave.bind(this) + this.onDrag = this.onDrag.bind(this) this.onDragStart = this.onDragStart.bind(this) this.onDragEnd = this.onDragEnd.bind(this) this.onDragOver = this.onDragOver.bind(this) @@ -123,15 +124,18 @@ class Tab extends React.Component { onDragStart (e) { // showing up the sentinel while dragging leads to show the shadow // of the next tab. See 10691#issuecomment-329854096 - // this is added back when dragEnd event happens - this.tabSentinel.style.display = 'none' + // this is added back to original size when onDrag event is happening + this.tabSentinel.style.width = 0 dnd.onDragStart(dragTypes.TAB, this.frame, e) } + onDrag () { + // re-enable the tabSentinel while dragging + this.tabSentinel.style.width = globalStyles.spacing.sentinelSize + } + onDragEnd (e) { - // re-enable the tabSentinel after drag ends - this.tabSentinel.style.display = 'block' dnd.onDragEnd(dragTypes.TAB, this.frame, e) } @@ -260,6 +264,7 @@ class Tab extends React.Component { props.partOfFullPageSet = partOfFullPageSet props.showAudioTopBorder = audioState.showAudioTopBorder(currentWindow, frameKey, isPinned) props.centralizeTabIcons = tabUIState.centralizeTabIcons(currentWindow, frameKey, isPinned) + props.gradientColor = tabUIState.getTabEndIconBackgroundColor(currentWindow, frameKey) // used in other functions props.dragData = state.getIn(['dragData', 'type']) === dragTypes.TAB && state.get('dragData') @@ -281,6 +286,13 @@ class Tab extends React.Component { } } }) + const perPageGradient = StyleSheet.create({ + tab_gradient: { + '::after': { + background: this.props.gradientColor + } + } + }) return
{ this.tabNode = node }} className={css( styles.tab, + !this.props.isPinnedTab && perPageGradient.tab_gradient, // Windows specific style isWindows && styles.tab_forWindows, this.props.isPinnedTab && styles.tab_pinned, this.props.isActive && styles.tab_active, - this.props.showAudioTopBorder && styles.tab_audioTopBorder, this.props.isActive && this.props.themeColor && perPageStyles.tab_themeColor, + this.props.showAudioTopBorder && styles.tab_audioTopBorder, // Private color should override themeColor this.props.isPrivateTab && styles.tab_private, this.props.isActive && this.props.isPrivateTab && styles.tab_active_private, @@ -322,6 +335,7 @@ class Tab extends React.Component { data-frame-key={this.props.frameKey} draggable title={this.props.title} + onDrag={this.onDrag} onDragStart={this.onDragStart} onDragEnd={this.onDragEnd} onDragOver={this.onDragOver} @@ -356,19 +370,26 @@ const styles = StyleSheet.create({ boxSizing: 'border-box', color: theme.tab.color, display: 'flex', - marginTop: '0', transition: theme.tab.transition, - left: '0', - opacity: '1', height: '-webkit-fill-available', width: '-webkit-fill-available', alignItems: 'center', justifyContent: 'space-between', - padding: 0, position: 'relative', ':hover': { background: theme.tab.hover.background + }, + + // this enable us to have gradient text + '::after': { + zIndex: globalStyles.zindex.zindexTabs, + content: '""', + position: 'absolute', + bottom: 0, + right: 0, + width: '-webkit-fill-available', + height: '-webkit-fill-available' } }, @@ -377,6 +398,12 @@ const styles = StyleSheet.create({ color: theme.tab.forWindows.color }, + tab_pinned: { + padding: 0, + width: '28px', + justifyContent: 'center' + }, + tab_active: { background: theme.tab.active.background, @@ -385,20 +412,9 @@ const styles = StyleSheet.create({ } }, - // 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 - // point needs to be edited. - tab__sentinel: { - position: 'absolute', - left: 0, - height: '1px', - background: 'transparent', - width: globalStyles.spacing.sentinelSize - }, - tab_audioTopBorder: { '::before': { + zIndex: globalStyles.zindex.zindexTabsAudioTopBorder, content: `''`, display: 'block', position: 'absolute', @@ -410,13 +426,26 @@ const styles = StyleSheet.create({ } }, + // 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 + // point needs to be edited. + tab__sentinel: { + position: 'absolute', + left: 0, + height: '1px', + background: 'transparent', + width: globalStyles.spacing.sentinelSize + }, + tab__identity: { justifyContent: 'flex-start', alignItems: 'center', + overflow: 'hidden', display: 'flex', flex: '1', minWidth: '0', // @see https://bugzilla.mozilla.org/show_bug.cgi?id=1108514#c5 - marginLeft: globalStyles.spacing.defaultTabMargin + margin: `0 ${globalStyles.spacing.defaultTabMargin}` }, tab__content_centered: { @@ -426,12 +455,6 @@ const styles = StyleSheet.create({ margin: 0 }, - tab_pinned: { - padding: 0, - width: '28px', - justifyContent: 'center' - }, - tab_active_private: { background: theme.tab.active.private.background, color: theme.tab.active.private.color, diff --git a/test/unit/app/common/state/tabUIStateTest.js b/test/unit/app/common/state/tabUIStateTest.js index 7f9cbb8f53b..fe9a9925e3e 100644 --- a/test/unit/app/common/state/tabUIStateTest.js +++ b/test/unit/app/common/state/tabUIStateTest.js @@ -8,6 +8,7 @@ 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 @@ -313,4 +314,95 @@ 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) + }) + }) + }) })