Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invert foreground color when background is light #15230

Merged
merged 6 commits into from
Sep 29, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import styled from 'styled-components'
import { CaratStrongDownIcon } from 'brave-ui/components/icons'
import { getLocale } from '../../../../common/locale'

const Hint = styled('div')`
const Hint = styled('div')<{}>`
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
text-align: center;
font-size: 15px;
color: white;
color: var(--override-readability-color, #FFFFFF);
> p {
margin: 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import styled from 'styled-components'

export const StyledClock = styled('div')<{}>`
color: #FFFFFF;
color: var(--override-readability-color-rgb, #FFFFFF);
box-sizing: border-box;
line-height: 1;
user-select: none;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export default function Icon () {
return (
<svg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24'>
<path
fill='#fff'
fillRule='evenodd'
d='M23.5 20.18a.95.95 0 01-.99-.05l-4.26-2.84v1.09c0 1.05-.86 1.91-1.92 1.91H2.92A1.92 1.92 0 011 18.38V5.92C1 4.86 1.86 4 2.92 4h13.41c1.06 0 1.92.86 1.92 1.92V7l4.26-2.84a.96.96 0 011.49.8v14.37c0 .36-.2.68-.5.85zM22.07 6.75l-4.26 2.84c-.02.02-.05.02-.08.03a.93.93 0 01-.36.11l-.09.02-.1-.02a.91.91 0 01-.64-.34c-.01-.03-.04-.04-.06-.07l-.03-.08a.92.92 0 01-.11-.37l-.02-.08V5.92H2.92v12.5h13.41V15.5l.02-.09a.9.9 0 01.04-.18.91.91 0 01.07-.18l.03-.08.07-.07a.93.93 0 01.64-.34l.1-.02.07.02a.93.93 0 01.37.11l.08.03 4.26 2.84V6.75z'
clipRule='evenodd'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const TileTitle = styled('p')<{}>`
line-height: 17px;
max-width: 100%;
height: 17px;
color: white;
color: var(--override-readability-color, white);
padding: 0 2px;
overflow: hidden;
white-space: nowrap;
Expand Down
27 changes: 20 additions & 7 deletions components/brave_new_tab_ui/components/default/page/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */

import * as React from 'react'
import styled, { css } from 'styled-components'
import styled, { createGlobalStyle, css } from 'styled-components'
import { requestAnimationFrameThrottle } from '../../../../common/throttle'

const breakpointLargeBlocks = '980px'
Expand Down Expand Up @@ -393,7 +393,7 @@ export const IconLink = styled('a')<{}>`
height: 24px;
margin: 8px;
cursor: pointer;
color: #ffffff;
color: var(--override-readability-color, #ffffff);
opacity: 0.7;
transition: opacity 0.15s ease, filter 0.15s ease;

Expand All @@ -412,7 +412,7 @@ export const IconButton = styled('button')<IconButtonProps>`
outline: none;
margin: ${p => p.isClickMenu ? '7' : '0 12'}px;
cursor: pointer;
color: #ffffff;
color: var(--override-readability-color, #ffffff);
background-color: transparent;
opacity: 0.7;
transition: opacity 0.15s ease, filter 0.15s ease;
Expand Down Expand Up @@ -467,13 +467,26 @@ export const IconButtonContainer = styled('div')<IconButtonContainerProps>`
font-family: ${p => p.theme.fontFamily.heading};
font-size: 13px;
font-weight: 600;
color: rgba(255,255,255,0.8);
color: rgba(var(--override-readability-color-rgb, 255, 255, 255), 0.8);
margin-right: ${p => p.textDirection === 'ltr' && '8px'};
margin-left: ${p => p.textDirection === 'rtl' && '8px'};
border-right: ${p => p.textDirection === 'ltr' && '1px solid rgba(255, 255, 255, 0.6)'};
border-left: ${p => p.textDirection === 'rtl' && '1px solid rgba(255, 255, 255, 0.6)'};
border-right: ${p => p.textDirection === 'ltr' && 'rgba(var(--override-readability-color-rgb, 255, 255, 255), 0.6)'};
border-left: ${p => p.textDirection === 'rtl' && 'rgba(var(--override-readability-color-rgb, 255, 255, 255), 0.6)'};

&:hover {
color: #ffffff;
color: ${p => p.color};
}
`

export const OverrideReadabilityColor = createGlobalStyle<{override: boolean}>`
body {
${p => p.override && css`
--override-readability-color-rgb: 0, 0, 0;
--override-readability-color: rgb(0, 0, 0);

// override color property in resets.css. Not sure why this happens but,
// the value in the stylesheet wins over variables above.
color: inherit
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't figure out why this happens. This happens when we declare variables with setProperty(). I tried setting property for body, but it didn't work. As a workaround, I override color here.

The previous trial can be found at 27a0d34

Screen Shot 2022-09-28 at 4 59 33 PM

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I like yours better anyway 😄 It's weird we need to set the color: inherit though - I just tested removing it and it seems to work without

css`
      --override-readability-color-rgb: 0, 0, 0;
      --override-readability-color: rgb(0, 0, 0);
`

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, I found the culprit. I used wrong variable for some UI.

-  color: var(--override-readability-color-rgb, #FFFFFF);  <--! oops, rgb is 0, 0, 0 -->
+  color: var(--override-readability-color, #FFFFFF);

`}
}
`
15 changes: 5 additions & 10 deletions components/brave_new_tab_ui/components/default/stats/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,10 @@ export const StyledStatsItem = styled('li')<{}>`
font-size: inherit;
font-family: inherit;
margin: 10px 16px;

&:first-child {
color: #FB542B;
}
&:nth-child(2) {
color: #A0A5EB;
}
&:last-child {
color: #FFFFFF;
&:first-child { color: var(--override-readability-color-rgb, var(--interactive2)); }
&:nth-child(2) { color: var(--override-readability-color-rgb, var(--interactive9)); }
&:last-child {
color: var(--override-readability-color-rgb, #FFFFFF);
margin-right: 0;
}
`
Expand Down Expand Up @@ -59,7 +54,7 @@ export const StyledStatsItemText = styled('span')<{}>`
export const StyledStatsItemDescription = styled('div')<{}>`
font-size: 16px;
font-weight: 500;
color: #FFFFFF;
color: var(--override-readability-color-rgb, #FFFFFF);
margin-top: 8px;
font-family: ${p => p.theme.fontFamily.heading};
`

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export interface WidgetProps {
widgetTitle?: string
hideMenu?: boolean
isForeground?: boolean
lightWidget?: boolean
paddingType: 'none' | 'right' | 'default'
onLearnMore?: () => void
onDisconnect?: () => void
Expand All @@ -42,7 +41,6 @@ export function Widget ({
widgetTitle,
hideMenu,
isForeground,
lightWidget,
paddingType,
onLearnMore,
onDisconnect,
Expand Down Expand Up @@ -78,7 +76,6 @@ export function Widget ({
hideWidget={hideWidget}
persistWidget={() => setWidgetMenuPersist(true)}
unpersistWidget={() => setWidgetMenuPersist(false)}
lightWidget={lightWidget}
paddingType={paddingType} />}
</StyledWidgetContainer>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as React from 'react'

import { StyledWidgetMenuContainer, StyledWidgetMenu, StyledWidgetButton, StyledWidgetIcon, StyledSpan, StyledWidgetLink, StyledEllipsis } from './styles'
import { IconButton } from '../../default'
import EllipsisIcon from './assets/ellipsis'
import EllipsisIcon from '../../popupMenu/ellipsisIcon'
import HideIcon from './assets/hide'
import AddSiteIcon from './assets/add-site'
import FrecencyIcon from './assets/frecency'
Expand All @@ -32,8 +32,8 @@ interface Props {
onAddSite?: () => void
customLinksEnabled?: boolean
onToggleCustomLinksEnabled?: () => void
lightWidget?: boolean
paddingType: 'none' | 'right' | 'default'
color?: string
}

interface State {
Expand Down Expand Up @@ -100,7 +100,6 @@ export default class WidgetMenu extends React.PureComponent<Props, State> {
widgetMenuPersist,
widgetTitle,
isForeground,
lightWidget,
paddingType,
onLearnMore,
onDisconnect,
Expand All @@ -116,7 +115,7 @@ export default class WidgetMenu extends React.PureComponent<Props, State> {
<StyledWidgetMenuContainer ref={this.settingsMenuRef} paddingType={paddingType}>
<StyledEllipsis widgetMenuPersist={widgetMenuPersist} isForeground={isForeground}>
<IconButton isClickMenu={true} onClick={this.toggleMenu}>
<EllipsisIcon lightWidget={lightWidget} />
<EllipsisIcon />
</IconButton>
</StyledEllipsis>
{showMenu && <StyledWidgetMenu
Expand Down
9 changes: 8 additions & 1 deletion components/brave_new_tab_ui/containers/newTab/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import {
CryptoDotComWidget as CryptoDotCom,
EditTopSite,
SearchPromotion,
EditCards
EditCards,
OverrideReadabilityColor
} from '../../components/default'
import { FTXWidget as FTX } from '../../widgets/ftx/components'
import * as Page from '../../components/default/page'
Expand All @@ -40,6 +41,7 @@ import {
fetchCryptoDotComSupportedPairs
} from '../../api/cryptoDotCom'
import { generateQRData } from '../../binance-utils'
import isReadableOnBackground from '../../helpers/colorUtil'

// Types
import { GeminiAssetAddress } from '../../actions/gemini_actions'
Expand Down Expand Up @@ -217,6 +219,10 @@ class NewTabPage extends React.Component<Props, State> {
}
}

shouldOverrideReadabilityColor (newTabData: NewTab.State) {
return !newTabData.brandedWallpaper && newTabData.backgroundWallpaper?.type === 'color' && !isReadableOnBackground(newTabData.backgroundWallpaper)
}

handleResize () {
this.setState({
forceToHideWidget: GetShouldForceToHideWidget(this.props, this.state.showSearchPromotion)
Expand Down Expand Up @@ -1166,6 +1172,7 @@ class NewTabPage extends React.Component<Props, State> {
imageHasLoaded={this.state.backgroundHasLoaded}
colorForBackground={colorForBackground}
data-show-news-prompt={((this.state.backgroundHasLoaded || colorForBackground) && this.state.isPromptingBraveToday) ? true : undefined}>
<OverrideReadabilityColor override={ this.shouldOverrideReadabilityColor(this.props.newTabData) } />
<Page.Page
hasImage={hasImage}
imageSrc={this.imageSource}
Expand Down
128 changes: 128 additions & 0 deletions components/brave_new_tab_ui/helpers/colorUtil.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright (c) 2022 The Brave Authors. All rights reserved.
// 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/.

import * as ColorUtil from './colorUtil'

describe('ColorData', () => {
describe('constructor', () => {
it('can be constructed from HEX color', () => {
const color = new ColorUtil.ColorData('#123456')
expect(color.type).toBe(ColorUtil.ColorType.HEX)
expect(color.r).toBe(0x12)
expect(color.g).toBe(0x34)
expect(color.b).toBe(0x56)
})

it('can be constructed from HEX shorthand color', () => {
const color = new ColorUtil.ColorData('#123')
expect(color.type).toBe(ColorUtil.ColorType.HEX)
expect(color.r).toBe(0x11)
expect(color.g).toBe(0x22)
expect(color.b).toBe(0x33)
})

it('can be constructed from rgb()', () => {
let color = new ColorUtil.ColorData('rgb(1, 2, 3)')
expect(color.type).toBe(ColorUtil.ColorType.RGB)
expect(color.r).toBe(1)
expect(color.g).toBe(2)
expect(color.b).toBe(3)

color = new ColorUtil.ColorData('rgb(0, 0, 0)')
expect(color.type).toBe(ColorUtil.ColorType.RGB)
expect(color.r).toBe(0)
expect(color.g).toBe(0)
expect(color.b).toBe(0)

color = new ColorUtil.ColorData('rgb(255, 255, 255)')
expect(color.type).toBe(ColorUtil.ColorType.RGB)
expect(color.r).toBe(255)
expect(color.g).toBe(255)
expect(color.b).toBe(255)
})
})

describe('getRelativeLuminance()', () => {
// Can cross check values from https://planetcalc.com/7779/?color=%23FFFFFF
// (modify |color| query value)
it('returns 0.0 for black', () => { expect(new ColorUtil.ColorData('rgb(0, 0, 0)').getRelativeLuminance()).toBe(0) })
it('returns 1.0 for white', () => { expect(new ColorUtil.ColorData('rgb(255, 255, 255)').getRelativeLuminance()).toBe(1) })
it('returns 0.2126 for red', () => { expect(new ColorUtil.ColorData('rgb(255, 0, 0)').getRelativeLuminance()).toBe(0.2126) })
it('returns 0.7152 for green', () => { expect(new ColorUtil.ColorData('rgb(0, 255, 0)').getRelativeLuminance()).toBe(0.7152) })
it('returns 0.0722 for blue', () => { expect(new ColorUtil.ColorData('rgb(0, 0, 255)').getRelativeLuminance()).toBe(0.0722) })
})

describe('luminanceCache', () => {
beforeEach(() => { ColorUtil.ColorData.clearCacheForTesting() })

it('has L for rgb(255, 255, 255) in the beginning', () => { expect(new ColorUtil.ColorData('rgb(255, 255, 255)').hasCachedLuminance()).toBeTruthy() })
it('has L for #ffffff in the beginning', () => { expect(new ColorUtil.ColorData('#ffffff').hasCachedLuminance()).toBeTruthy() })
it('has L for rgb(0, 0, 0) in the beginning', () => { expect(new ColorUtil.ColorData('rgb(0, 0, 0)').hasCachedLuminance()).toBeTruthy() })
it('has L for #000 in the beginning', () => { expect(new ColorUtil.ColorData('#000').hasCachedLuminance()).toBeTruthy() })
it('caches L once a L is calculated', () => {
const color = new ColorUtil.ColorData('rgb(255, 0, 0)')
expect(color.hasCachedLuminance()).toBeFalsy()

// Calculate L and make cache hot
color.getRelativeLuminance()
expect(color.hasCachedLuminance()).toBeTruthy()

// Equivalent color should use the same cache
expect(new ColorUtil.ColorData('#ff0000').hasCachedLuminance).toBeTruthy()
})
})
})

describe('getContrastRatio()', () => {
// Can cross check values from https://planetcalc.com/7779/?color1=%23FFFFFF&color2=%23000000
// (modify |color1| and |color2| query value)
it('returns 1.0 for same color', () => {
expect(ColorUtil.getContrastRatio(new ColorUtil.ColorData('rgb(0, 0, 0)'),
new ColorUtil.ColorData('#000'))).toBe(1)
})

it('returns 21 for black and white', () => {
expect(ColorUtil.getContrastRatio(new ColorUtil.ColorData('#fff'),
new ColorUtil.ColorData('#000'))).toBe(21)
})

it('returns 2.91 for red and green', () => {
expect(ColorUtil.getContrastRatio(new ColorUtil.ColorData('#f00'),
new ColorUtil.ColorData('#0f0'))).toBe(2.9139375476009137)
})

it('returns 6.26 for green and blue', () => {
expect(ColorUtil.getContrastRatio(new ColorUtil.ColorData('#0f0'),
new ColorUtil.ColorData('#00f'))).toBe(6.261865793780687)
})

it('returns 2.14 for blue and red', () => {
expect(ColorUtil.getContrastRatio(new ColorUtil.ColorData('#00f'),
new ColorUtil.ColorData('#f00'))).toBe(2.148936170212766)
})
})

describe('isReadable()', () => {
describe('When background is black, stats are visible', () => {
const background = new ColorUtil.ColorData('#000')
it('returns true for tracker stat color', () => { expect(ColorUtil.isReadable(background, new ColorUtil.ColorData('#FB542B'/* --interactive2 */))).toBeTruthy() })
it('returns true for bandwidth saved stat color', () => { expect(ColorUtil.isReadable(background, new ColorUtil.ColorData('#A0A5EB'/* --interactive9 */))).toBeTruthy() })
it('returns true for time saved stat color', () => { expect(ColorUtil.isReadable(background, new ColorUtil.ColorData('#FFFFFF'))).toBeTruthy() })
})

describe('When background is #F0CB44, stats are not visible', () => {
const background = new ColorUtil.ColorData('#F0CB44')
it('returns true for tracker stat color', () => { expect(ColorUtil.isReadable(background, new ColorUtil.ColorData('#FB542B'/* --interactive2 */))).toBeTruthy() })
it('returns false for bandwidth saved stat color', () => { expect(ColorUtil.isReadable(background, new ColorUtil.ColorData('#A0A5EB'/* --interactive9 */))).toBeFalsy() })
it('returns true for time saved stat color', () => { expect(ColorUtil.isReadable(background, new ColorUtil.ColorData('#FFFFFF'))).toBeTruthy() })
})

describe('When background is #2197F9 stats are not visible', () => {
const background = new ColorUtil.ColorData('#2197F9')
it('returns false for tracker stat color', () => { expect(ColorUtil.isReadable(background, new ColorUtil.ColorData('#FB542B'/* --interactive2 */))).toBeFalsy() })
it('returns false for bandwidth saved stat color', () => { expect(ColorUtil.isReadable(background, new ColorUtil.ColorData('#A0A5EB'/* --interactive9 */))).toBeFalsy() })
it('returns true for time saved stat color', () => { expect(ColorUtil.isReadable(background, new ColorUtil.ColorData('#FFFFFF'))).toBeTruthy() })
})
})
Loading