@@ -29,9 +29,10 @@ import React, { Component } from 'react'
2929import { omitProps } from '@instructure/ui-react-utils'
3030import { testable } from '@instructure/ui-testable'
3131import { error } from '@instructure/console'
32- import { contrast as getContrast } from '@instructure/ui-color-utils'
33- import conversions from '@instructure/ui-color-utils'
34- import type { RGBAType } from '@instructure/ui-color-utils'
32+ import {
33+ contrastWithAlpha ,
34+ validateContrast
35+ } from '@instructure/ui-color-utils'
3536import { withStyle , jsx } from '@instructure/emotion'
3637
3738import { Text } from '@instructure/ui-text'
@@ -40,7 +41,7 @@ import { Pill } from '@instructure/ui-pill'
4041import ColorIndicator from '../ColorIndicator'
4142
4243import { propTypes , allowedProps } from './props'
43- import type { ColorContrastProps } from './props'
44+ import type { ColorContrastProps , ColorContrastState } from './props'
4445
4546import generateStyle from './styles'
4647import generateComponentTheme from './theme'
@@ -52,17 +53,25 @@ category: components
5253**/
5354@withStyle ( generateStyle , generateComponentTheme )
5455@testable ( )
55- class ColorContrast extends Component < ColorContrastProps > {
56+ class ColorContrast extends Component < ColorContrastProps , ColorContrastState > {
5657 static propTypes = propTypes
5758 static allowedProps = allowedProps
5859 static readonly componentId = 'ColorContrast'
5960
6061 static defaultProps = {
61- withoutColorPreview : false
62+ withoutColorPreview : false ,
63+ validationLevel : 'AA'
6264 }
6365
6466 constructor ( props : ColorContrastProps ) {
6567 super ( props )
68+
69+ this . state = {
70+ contrast : 1 ,
71+ isValidNormalText : false ,
72+ isValidLargeText : false ,
73+ isValidGraphicsText : false
74+ }
6675 }
6776
6877 ref : HTMLDivElement | null = null
@@ -79,10 +88,27 @@ class ColorContrast extends Component<ColorContrastProps> {
7988
8089 componentDidMount ( ) {
8190 this . props . makeStyles ?.( )
91+ this . calcState ( )
8292 }
8393
84- componentDidUpdate ( ) {
94+ componentDidUpdate ( prevProps : ColorContrastProps ) {
8595 this . props . makeStyles ?.( )
96+ if (
97+ prevProps ?. firstColor !== this . props ?. firstColor ||
98+ prevProps ?. secondColor !== this . props ?. secondColor ||
99+ prevProps ?. validationLevel !== this . props ?. validationLevel
100+ ) {
101+ const newState = this . calcState ( )
102+
103+ this . props ?. onContrastChange ?.( {
104+ contrast : newState . contrast ,
105+ isValidNormalText : newState . isValidNormalText ,
106+ isValidLargeText : newState . isValidLargeText ,
107+ isValidGraphicsText : newState . isValidGraphicsText ,
108+ firstColor : this . props . firstColor ,
109+ secondColor : this . props . secondColor
110+ } )
111+ }
86112 }
87113
88114 renderStatus = ( pass : boolean , description : string ) => {
@@ -153,32 +179,17 @@ class ColorContrast extends Component<ColorContrastProps> {
153179 )
154180 }
155181
156- calcBlendedColor = ( c1 : RGBAType , c2 : RGBAType ) => {
157- const alpha = 1 - ( 1 - c1 . a ) * ( 1 - c2 . a )
158- return {
159- r : ( c2 . r * c2 . a ) / alpha + ( c1 . r * c1 . a * ( 1 - c2 . a ) ) / alpha ,
160- g : ( c2 . g * c2 . a ) / alpha + ( c1 . g * c1 . a * ( 1 - c2 . a ) ) / alpha ,
161- b : ( c2 . b * c2 . a ) / alpha + ( c1 . b * c1 . a * ( 1 - c2 . a ) ) / alpha ,
162- a : 1
163- }
164- }
165-
166- //We project the firstColor onto an opaque white background, then we project the secondColor onto
167- //the projected first color. We calculate the contrast of these two, projected colors.
168- get calcContrast ( ) {
169- const c1RGBA = conversions . colorToRGB ( this . props . firstColor )
170- const c2RGBA = conversions . colorToRGB ( this . props . secondColor )
171- const c1OnWhite = this . calcBlendedColor (
172- { r : 255 , g : 255 , b : 255 , a : 1 } ,
173- c1RGBA
174- )
175- const c2OnC1OnWhite = this . calcBlendedColor ( c1OnWhite , c2RGBA )
176-
177- return getContrast (
178- conversions . colorToHex8 ( c1OnWhite ) ,
179- conversions . colorToHex8 ( c2OnC1OnWhite ) ,
180- 2
182+ calcState ( ) {
183+ const contrast = contrastWithAlpha (
184+ this . props . firstColor ,
185+ this . props . secondColor
181186 )
187+ const newState = {
188+ contrast,
189+ ...validateContrast ( contrast , this . props . validationLevel )
190+ }
191+ this . setState ( newState )
192+ return newState
182193 }
183194
184195 render ( ) {
@@ -190,7 +201,12 @@ class ColorContrast extends Component<ColorContrastProps> {
190201 graphicsTextLabel
191202 } = this . props
192203
193- const contrast = this . calcContrast
204+ const {
205+ contrast,
206+ isValidNormalText,
207+ isValidLargeText,
208+ isValidGraphicsText
209+ } = this . state
194210
195211 return (
196212 < div
@@ -205,9 +221,9 @@ class ColorContrast extends Component<ColorContrastProps> {
205221 </ div >
206222 < Text size = "x-large" > { contrast } :1</ Text >
207223 { this . renderPreview ( ) }
208- { this . renderStatus ( contrast >= 4.5 , normalTextLabel ) }
209- { this . renderStatus ( contrast >= 3 , largeTextLabel ) }
210- { this . renderStatus ( contrast >= 3 , graphicsTextLabel ) }
224+ { this . renderStatus ( isValidNormalText , normalTextLabel ) }
225+ { this . renderStatus ( isValidLargeText , largeTextLabel ) }
226+ { this . renderStatus ( isValidGraphicsText , graphicsTextLabel ) }
211227 </ div >
212228 )
213229 }
0 commit comments