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)
+ })
+ })
+ })
})