@instructure/ui-color-picker 9.9.0 → 9.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [9.10.0](https://github.com/instructure/instructure-ui/compare/v9.9.0...v9.10.0) (2024-11-19)
7
+
8
+
9
+ ### Features
10
+
11
+ * **ui-color-picker,ui-color-utils:** add callback for contrast validation information and export validation methods ([3ac2582](https://github.com/instructure/instructure-ui/commit/3ac2582bd0b71ac8709bbe3e4c6ce481c53a239d))
12
+
13
+
14
+
15
+
16
+
6
17
  # [9.9.0](https://github.com/instructure/instructure-ui/compare/v9.8.1...v9.9.0) (2024-11-12)
7
18
 
8
19
 
@@ -29,8 +29,7 @@ import React, { Component } from 'react';
29
29
  import { omitProps } from '@instructure/ui-react-utils';
30
30
  import { testable } from '@instructure/ui-testable';
31
31
  import { error } from '@instructure/console';
32
- import { contrast as getContrast } from '@instructure/ui-color-utils';
33
- import conversions from '@instructure/ui-color-utils';
32
+ import { contrastWithAlpha, validateContrast } from '@instructure/ui-color-utils';
34
33
  import { withStyle, jsx } from '@instructure/emotion';
35
34
  import { Text } from '@instructure/ui-text';
36
35
  import { Pill } from '@instructure/ui-pill';
@@ -82,32 +81,42 @@ let ColorContrast = (_dec = withStyle(generateStyle, generateComponentTheme), _d
82
81
  css: (_this$props$styles3 = this.props.styles) === null || _this$props$styles3 === void 0 ? void 0 : _this$props$styles3.pickedColorHex
83
82
  }, color)));
84
83
  };
85
- this.calcBlendedColor = (c1, c2) => {
86
- const alpha = 1 - (1 - c1.a) * (1 - c2.a);
87
- return {
88
- r: c2.r * c2.a / alpha + c1.r * c1.a * (1 - c2.a) / alpha,
89
- g: c2.g * c2.a / alpha + c1.g * c1.a * (1 - c2.a) / alpha,
90
- b: c2.b * c2.a / alpha + c1.b * c1.a * (1 - c2.a) / alpha,
91
- a: 1
92
- };
84
+ this.state = {
85
+ contrast: 1,
86
+ isValidNormalText: false,
87
+ isValidLargeText: false,
88
+ isValidGraphicsText: false
93
89
  };
94
90
  }
95
91
  componentDidMount() {
96
92
  var _this$props$makeStyle, _this$props2;
97
93
  (_this$props$makeStyle = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props2);
94
+ this.calcState();
98
95
  }
99
- componentDidUpdate() {
100
- var _this$props$makeStyle2, _this$props3;
96
+ componentDidUpdate(prevProps) {
97
+ var _this$props$makeStyle2, _this$props3, _this$props4, _this$props5, _this$props6;
101
98
  (_this$props$makeStyle2 = (_this$props3 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props3);
99
+ if ((prevProps === null || prevProps === void 0 ? void 0 : prevProps.firstColor) !== ((_this$props4 = this.props) === null || _this$props4 === void 0 ? void 0 : _this$props4.firstColor) || (prevProps === null || prevProps === void 0 ? void 0 : prevProps.secondColor) !== ((_this$props5 = this.props) === null || _this$props5 === void 0 ? void 0 : _this$props5.secondColor) || (prevProps === null || prevProps === void 0 ? void 0 : prevProps.validationLevel) !== ((_this$props6 = this.props) === null || _this$props6 === void 0 ? void 0 : _this$props6.validationLevel)) {
100
+ var _this$props7, _this$props7$onContra;
101
+ const newState = this.calcState();
102
+ (_this$props7 = this.props) === null || _this$props7 === void 0 ? void 0 : (_this$props7$onContra = _this$props7.onContrastChange) === null || _this$props7$onContra === void 0 ? void 0 : _this$props7$onContra.call(_this$props7, {
103
+ contrast: newState.contrast,
104
+ isValidNormalText: newState.isValidNormalText,
105
+ isValidLargeText: newState.isValidLargeText,
106
+ isValidGraphicsText: newState.isValidGraphicsText,
107
+ firstColor: this.props.firstColor,
108
+ secondColor: this.props.secondColor
109
+ });
110
+ }
102
111
  }
103
112
  renderPreview() {
104
- const _this$props4 = this.props,
105
- styles = _this$props4.styles,
106
- withoutColorPreview = _this$props4.withoutColorPreview,
107
- firstColor = _this$props4.firstColor,
108
- secondColor = _this$props4.secondColor,
109
- firstColorLabel = _this$props4.firstColorLabel,
110
- secondColorLabel = _this$props4.secondColorLabel;
113
+ const _this$props8 = this.props,
114
+ styles = _this$props8.styles,
115
+ withoutColorPreview = _this$props8.withoutColorPreview,
116
+ firstColor = _this$props8.firstColor,
117
+ secondColor = _this$props8.secondColor,
118
+ firstColorLabel = _this$props8.firstColorLabel,
119
+ secondColorLabel = _this$props8.secondColorLabel;
111
120
  if (withoutColorPreview) {
112
121
  return null;
113
122
  }
@@ -122,28 +131,27 @@ let ColorContrast = (_dec = withStyle(generateStyle, generateComponentTheme), _d
122
131
  css: styles === null || styles === void 0 ? void 0 : styles.secondColorPreview
123
132
  }, this.renderColorIndicator(secondColor, secondColorLabel || '')));
124
133
  }
125
- //We project the firstColor onto an opaque white background, then we project the secondColor onto
126
- //the projected first color. We calculate the contrast of these two, projected colors.
127
- get calcContrast() {
128
- const c1RGBA = conversions.colorToRGB(this.props.firstColor);
129
- const c2RGBA = conversions.colorToRGB(this.props.secondColor);
130
- const c1OnWhite = this.calcBlendedColor({
131
- r: 255,
132
- g: 255,
133
- b: 255,
134
- a: 1
135
- }, c1RGBA);
136
- const c2OnC1OnWhite = this.calcBlendedColor(c1OnWhite, c2RGBA);
137
- return getContrast(conversions.colorToHex8(c1OnWhite), conversions.colorToHex8(c2OnC1OnWhite), 2);
134
+ calcState() {
135
+ const contrast = contrastWithAlpha(this.props.firstColor, this.props.secondColor);
136
+ const newState = {
137
+ contrast,
138
+ ...validateContrast(contrast, this.props.validationLevel)
139
+ };
140
+ this.setState(newState);
141
+ return newState;
138
142
  }
139
143
  render() {
140
- const _this$props5 = this.props,
141
- styles = _this$props5.styles,
142
- label = _this$props5.label,
143
- normalTextLabel = _this$props5.normalTextLabel,
144
- largeTextLabel = _this$props5.largeTextLabel,
145
- graphicsTextLabel = _this$props5.graphicsTextLabel;
146
- const contrast = this.calcContrast;
144
+ const _this$props9 = this.props,
145
+ styles = _this$props9.styles,
146
+ label = _this$props9.label,
147
+ normalTextLabel = _this$props9.normalTextLabel,
148
+ largeTextLabel = _this$props9.largeTextLabel,
149
+ graphicsTextLabel = _this$props9.graphicsTextLabel;
150
+ const _this$state = this.state,
151
+ contrast = _this$state.contrast,
152
+ isValidNormalText = _this$state.isValidNormalText,
153
+ isValidLargeText = _this$state.isValidLargeText,
154
+ isValidGraphicsText = _this$state.isValidGraphicsText;
147
155
  return jsx("div", Object.assign({}, omitProps(this.props, ColorContrast.allowedProps), {
148
156
  ref: this.handleRef,
149
157
  css: styles === null || styles === void 0 ? void 0 : styles.colorContrast
@@ -154,10 +162,11 @@ let ColorContrast = (_dec = withStyle(generateStyle, generateComponentTheme), _d
154
162
  as: "div"
155
163
  }, label)), jsx(Text, {
156
164
  size: "x-large"
157
- }, contrast, ":1"), this.renderPreview(), this.renderStatus(contrast >= 4.5, normalTextLabel), this.renderStatus(contrast >= 3, largeTextLabel), this.renderStatus(contrast >= 3, graphicsTextLabel));
165
+ }, contrast, ":1"), this.renderPreview(), this.renderStatus(isValidNormalText, normalTextLabel), this.renderStatus(isValidLargeText, largeTextLabel), this.renderStatus(isValidGraphicsText, graphicsTextLabel));
158
166
  }
159
167
  }, _ColorContrast.displayName = "ColorContrast", _ColorContrast.propTypes = propTypes, _ColorContrast.allowedProps = allowedProps, _ColorContrast.componentId = 'ColorContrast', _ColorContrast.defaultProps = {
160
- withoutColorPreview: false
168
+ withoutColorPreview: false,
169
+ validationLevel: 'AA'
161
170
  }, _ColorContrast)) || _class) || _class);
162
171
  export { ColorContrast };
163
172
  export default ColorContrast;
@@ -35,7 +35,9 @@ const propTypes = {
35
35
  normalTextLabel: PropTypes.string.isRequired,
36
36
  secondColor: PropTypes.string.isRequired,
37
37
  secondColorLabel: PropTypes.string,
38
- successLabel: PropTypes.string.isRequired
38
+ successLabel: PropTypes.string.isRequired,
39
+ onContrastChange: PropTypes.func,
40
+ validationLevel: PropTypes.oneOf(['AA', 'AAA'])
39
41
  };
40
- const allowedProps = ['elementRef', 'failureLabel', 'firstColor', 'firstColorLabel', 'graphicsTextLabel', 'withoutColorPreview', 'label', 'largeTextLabel', 'normalTextLabel', 'secondColor', 'secondColorLabel', 'successLabel'];
42
+ const allowedProps = ['elementRef', 'failureLabel', 'firstColor', 'firstColorLabel', 'graphicsTextLabel', 'withoutColorPreview', 'label', 'largeTextLabel', 'normalTextLabel', 'secondColor', 'secondColorLabel', 'successLabel', 'onContrastChange', 'validationLevel'];
41
43
  export { propTypes, allowedProps };
@@ -171,7 +171,8 @@ let ColorPicker = (_dec = withStyle(generateStyle, generateComponentTheme), _dec
171
171
  largeTextLabel: this.props.colorMixerSettings.colorContrast.largeTextLabel,
172
172
  graphicsTextLabel: this.props.colorMixerSettings.colorContrast.graphicsTextLabel,
173
173
  firstColorLabel: this.props.colorMixerSettings.colorContrast.firstColorLabel,
174
- secondColorLabel: this.props.colorMixerSettings.colorContrast.secondColorLabel
174
+ secondColorLabel: this.props.colorMixerSettings.colorContrast.secondColorLabel,
175
+ onContrastChange: this.props.colorMixerSettings.colorContrast.onContrastChange
175
176
  }))), jsx("div", {
176
177
  css: (_this$props$styles5 = this.props.styles) === null || _this$props$styles5 === void 0 ? void 0 : _this$props$styles5.popoverFooter
177
178
  }, jsx(Button, {
@@ -57,7 +57,8 @@ const propTypes = {
57
57
  largeTextLabel: PropTypes.string.isRequired,
58
58
  graphicsTextLabel: PropTypes.string.isRequired,
59
59
  firstColorLabel: PropTypes.string.isRequired,
60
- secondColorLabel: PropTypes.string.isRequired
60
+ secondColorLabel: PropTypes.string.isRequired,
61
+ onContrastChange: PropTypes.func
61
62
  })
62
63
  }),
63
64
  children: PropTypes.func,
@@ -10,8 +10,8 @@ var _react = _interopRequireWildcard(require("react"));
10
10
  var _omitProps = require("@instructure/ui-react-utils/lib/omitProps.js");
11
11
  var _testable = require("@instructure/ui-testable/lib/testable.js");
12
12
  var _console = require("@instructure/console");
13
- var _contrast = require("@instructure/ui-color-utils/lib/contrast.js");
14
- var _uiColorUtils = _interopRequireDefault(require("@instructure/ui-color-utils"));
13
+ var _contrastWithAlpha = require("@instructure/ui-color-utils/lib/contrastWithAlpha.js");
14
+ var _validateContrast = require("@instructure/ui-color-utils/lib/validateContrast.js");
15
15
  var _emotion = require("@instructure/emotion");
16
16
  var _Text = require("@instructure/ui-text/lib/Text");
17
17
  var _Pill = require("@instructure/ui-pill/lib/Pill");
@@ -88,32 +88,42 @@ let ColorContrast = exports.ColorContrast = (_dec = (0, _emotion.withStyle)(_sty
88
88
  css: (_this$props$styles3 = this.props.styles) === null || _this$props$styles3 === void 0 ? void 0 : _this$props$styles3.pickedColorHex
89
89
  }, color)));
90
90
  };
91
- this.calcBlendedColor = (c1, c2) => {
92
- const alpha = 1 - (1 - c1.a) * (1 - c2.a);
93
- return {
94
- r: c2.r * c2.a / alpha + c1.r * c1.a * (1 - c2.a) / alpha,
95
- g: c2.g * c2.a / alpha + c1.g * c1.a * (1 - c2.a) / alpha,
96
- b: c2.b * c2.a / alpha + c1.b * c1.a * (1 - c2.a) / alpha,
97
- a: 1
98
- };
91
+ this.state = {
92
+ contrast: 1,
93
+ isValidNormalText: false,
94
+ isValidLargeText: false,
95
+ isValidGraphicsText: false
99
96
  };
100
97
  }
101
98
  componentDidMount() {
102
99
  var _this$props$makeStyle, _this$props2;
103
100
  (_this$props$makeStyle = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props2);
101
+ this.calcState();
104
102
  }
105
- componentDidUpdate() {
106
- var _this$props$makeStyle2, _this$props3;
103
+ componentDidUpdate(prevProps) {
104
+ var _this$props$makeStyle2, _this$props3, _this$props4, _this$props5, _this$props6;
107
105
  (_this$props$makeStyle2 = (_this$props3 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props3);
106
+ if ((prevProps === null || prevProps === void 0 ? void 0 : prevProps.firstColor) !== ((_this$props4 = this.props) === null || _this$props4 === void 0 ? void 0 : _this$props4.firstColor) || (prevProps === null || prevProps === void 0 ? void 0 : prevProps.secondColor) !== ((_this$props5 = this.props) === null || _this$props5 === void 0 ? void 0 : _this$props5.secondColor) || (prevProps === null || prevProps === void 0 ? void 0 : prevProps.validationLevel) !== ((_this$props6 = this.props) === null || _this$props6 === void 0 ? void 0 : _this$props6.validationLevel)) {
107
+ var _this$props7, _this$props7$onContra;
108
+ const newState = this.calcState();
109
+ (_this$props7 = this.props) === null || _this$props7 === void 0 ? void 0 : (_this$props7$onContra = _this$props7.onContrastChange) === null || _this$props7$onContra === void 0 ? void 0 : _this$props7$onContra.call(_this$props7, {
110
+ contrast: newState.contrast,
111
+ isValidNormalText: newState.isValidNormalText,
112
+ isValidLargeText: newState.isValidLargeText,
113
+ isValidGraphicsText: newState.isValidGraphicsText,
114
+ firstColor: this.props.firstColor,
115
+ secondColor: this.props.secondColor
116
+ });
117
+ }
108
118
  }
109
119
  renderPreview() {
110
- const _this$props4 = this.props,
111
- styles = _this$props4.styles,
112
- withoutColorPreview = _this$props4.withoutColorPreview,
113
- firstColor = _this$props4.firstColor,
114
- secondColor = _this$props4.secondColor,
115
- firstColorLabel = _this$props4.firstColorLabel,
116
- secondColorLabel = _this$props4.secondColorLabel;
120
+ const _this$props8 = this.props,
121
+ styles = _this$props8.styles,
122
+ withoutColorPreview = _this$props8.withoutColorPreview,
123
+ firstColor = _this$props8.firstColor,
124
+ secondColor = _this$props8.secondColor,
125
+ firstColorLabel = _this$props8.firstColorLabel,
126
+ secondColorLabel = _this$props8.secondColorLabel;
117
127
  if (withoutColorPreview) {
118
128
  return null;
119
129
  }
@@ -128,28 +138,27 @@ let ColorContrast = exports.ColorContrast = (_dec = (0, _emotion.withStyle)(_sty
128
138
  css: styles === null || styles === void 0 ? void 0 : styles.secondColorPreview
129
139
  }, this.renderColorIndicator(secondColor, secondColorLabel || '')));
130
140
  }
131
- //We project the firstColor onto an opaque white background, then we project the secondColor onto
132
- //the projected first color. We calculate the contrast of these two, projected colors.
133
- get calcContrast() {
134
- const c1RGBA = _uiColorUtils.default.colorToRGB(this.props.firstColor);
135
- const c2RGBA = _uiColorUtils.default.colorToRGB(this.props.secondColor);
136
- const c1OnWhite = this.calcBlendedColor({
137
- r: 255,
138
- g: 255,
139
- b: 255,
140
- a: 1
141
- }, c1RGBA);
142
- const c2OnC1OnWhite = this.calcBlendedColor(c1OnWhite, c2RGBA);
143
- return (0, _contrast.contrast)(_uiColorUtils.default.colorToHex8(c1OnWhite), _uiColorUtils.default.colorToHex8(c2OnC1OnWhite), 2);
141
+ calcState() {
142
+ const contrast = (0, _contrastWithAlpha.contrastWithAlpha)(this.props.firstColor, this.props.secondColor);
143
+ const newState = {
144
+ contrast,
145
+ ...(0, _validateContrast.validateContrast)(contrast, this.props.validationLevel)
146
+ };
147
+ this.setState(newState);
148
+ return newState;
144
149
  }
145
150
  render() {
146
- const _this$props5 = this.props,
147
- styles = _this$props5.styles,
148
- label = _this$props5.label,
149
- normalTextLabel = _this$props5.normalTextLabel,
150
- largeTextLabel = _this$props5.largeTextLabel,
151
- graphicsTextLabel = _this$props5.graphicsTextLabel;
152
- const contrast = this.calcContrast;
151
+ const _this$props9 = this.props,
152
+ styles = _this$props9.styles,
153
+ label = _this$props9.label,
154
+ normalTextLabel = _this$props9.normalTextLabel,
155
+ largeTextLabel = _this$props9.largeTextLabel,
156
+ graphicsTextLabel = _this$props9.graphicsTextLabel;
157
+ const _this$state = this.state,
158
+ contrast = _this$state.contrast,
159
+ isValidNormalText = _this$state.isValidNormalText,
160
+ isValidLargeText = _this$state.isValidLargeText,
161
+ isValidGraphicsText = _this$state.isValidGraphicsText;
153
162
  return (0, _emotion.jsx)("div", Object.assign({}, (0, _omitProps.omitProps)(this.props, ColorContrast.allowedProps), {
154
163
  ref: this.handleRef,
155
164
  css: styles === null || styles === void 0 ? void 0 : styles.colorContrast
@@ -160,9 +169,10 @@ let ColorContrast = exports.ColorContrast = (_dec = (0, _emotion.withStyle)(_sty
160
169
  as: "div"
161
170
  }, label)), (0, _emotion.jsx)(_Text.Text, {
162
171
  size: "x-large"
163
- }, contrast, ":1"), this.renderPreview(), this.renderStatus(contrast >= 4.5, normalTextLabel), this.renderStatus(contrast >= 3, largeTextLabel), this.renderStatus(contrast >= 3, graphicsTextLabel));
172
+ }, contrast, ":1"), this.renderPreview(), this.renderStatus(isValidNormalText, normalTextLabel), this.renderStatus(isValidLargeText, largeTextLabel), this.renderStatus(isValidGraphicsText, graphicsTextLabel));
164
173
  }
165
174
  }, _ColorContrast.displayName = "ColorContrast", _ColorContrast.propTypes = _props.propTypes, _ColorContrast.allowedProps = _props.allowedProps, _ColorContrast.componentId = 'ColorContrast', _ColorContrast.defaultProps = {
166
- withoutColorPreview: false
175
+ withoutColorPreview: false,
176
+ validationLevel: 'AA'
167
177
  }, _ColorContrast)) || _class) || _class);
168
178
  var _default = exports.default = ColorContrast;
@@ -42,6 +42,8 @@ const propTypes = exports.propTypes = {
42
42
  normalTextLabel: _propTypes.default.string.isRequired,
43
43
  secondColor: _propTypes.default.string.isRequired,
44
44
  secondColorLabel: _propTypes.default.string,
45
- successLabel: _propTypes.default.string.isRequired
45
+ successLabel: _propTypes.default.string.isRequired,
46
+ onContrastChange: _propTypes.default.func,
47
+ validationLevel: _propTypes.default.oneOf(['AA', 'AAA'])
46
48
  };
47
- const allowedProps = exports.allowedProps = ['elementRef', 'failureLabel', 'firstColor', 'firstColorLabel', 'graphicsTextLabel', 'withoutColorPreview', 'label', 'largeTextLabel', 'normalTextLabel', 'secondColor', 'secondColorLabel', 'successLabel'];
49
+ const allowedProps = exports.allowedProps = ['elementRef', 'failureLabel', 'firstColor', 'firstColorLabel', 'graphicsTextLabel', 'withoutColorPreview', 'label', 'largeTextLabel', 'normalTextLabel', 'secondColor', 'secondColorLabel', 'successLabel', 'onContrastChange', 'validationLevel'];
@@ -183,7 +183,8 @@ let ColorPicker = exports.ColorPicker = (_dec = (0, _emotion.withStyle)(_styles.
183
183
  largeTextLabel: this.props.colorMixerSettings.colorContrast.largeTextLabel,
184
184
  graphicsTextLabel: this.props.colorMixerSettings.colorContrast.graphicsTextLabel,
185
185
  firstColorLabel: this.props.colorMixerSettings.colorContrast.firstColorLabel,
186
- secondColorLabel: this.props.colorMixerSettings.colorContrast.secondColorLabel
186
+ secondColorLabel: this.props.colorMixerSettings.colorContrast.secondColorLabel,
187
+ onContrastChange: this.props.colorMixerSettings.colorContrast.onContrastChange
187
188
  }))), (0, _emotion.jsx)("div", {
188
189
  css: (_this$props$styles5 = this.props.styles) === null || _this$props$styles5 === void 0 ? void 0 : _this$props$styles5.popoverFooter
189
190
  }, (0, _emotion.jsx)(_Button.Button, {
@@ -64,7 +64,8 @@ const propTypes = exports.propTypes = {
64
64
  largeTextLabel: _propTypes.default.string.isRequired,
65
65
  graphicsTextLabel: _propTypes.default.string.isRequired,
66
66
  firstColorLabel: _propTypes.default.string.isRequired,
67
- secondColorLabel: _propTypes.default.string.isRequired
67
+ secondColorLabel: _propTypes.default.string.isRequired,
68
+ onContrastChange: _propTypes.default.func
68
69
  })
69
70
  }),
70
71
  children: _propTypes.default.func,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-color-picker",
3
- "version": "9.9.0",
3
+ "version": "9.10.0",
4
4
  "description": "A UI component library made by Instructure Inc.",
5
5
  "author": "Instructure, Inc. Engineering and Product Design",
6
6
  "module": "./es/index.js",
@@ -24,33 +24,33 @@
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
26
  "@babel/runtime": "^7.24.5",
27
- "@instructure/console": "9.9.0",
28
- "@instructure/emotion": "9.9.0",
29
- "@instructure/shared-types": "9.9.0",
30
- "@instructure/ui-a11y-content": "9.9.0",
31
- "@instructure/ui-buttons": "9.9.0",
32
- "@instructure/ui-color-utils": "9.9.0",
33
- "@instructure/ui-dom-utils": "9.9.0",
34
- "@instructure/ui-drilldown": "9.9.0",
35
- "@instructure/ui-form-field": "9.9.0",
36
- "@instructure/ui-icons": "9.9.0",
37
- "@instructure/ui-pill": "9.9.0",
38
- "@instructure/ui-popover": "9.9.0",
39
- "@instructure/ui-react-utils": "9.9.0",
40
- "@instructure/ui-testable": "9.9.0",
41
- "@instructure/ui-text": "9.9.0",
42
- "@instructure/ui-text-input": "9.9.0",
43
- "@instructure/ui-themes": "9.9.0",
44
- "@instructure/ui-tooltip": "9.9.0",
45
- "@instructure/ui-utils": "9.9.0",
46
- "@instructure/ui-view": "9.9.0",
27
+ "@instructure/console": "9.10.0",
28
+ "@instructure/emotion": "9.10.0",
29
+ "@instructure/shared-types": "9.10.0",
30
+ "@instructure/ui-a11y-content": "9.10.0",
31
+ "@instructure/ui-buttons": "9.10.0",
32
+ "@instructure/ui-color-utils": "9.10.0",
33
+ "@instructure/ui-dom-utils": "9.10.0",
34
+ "@instructure/ui-drilldown": "9.10.0",
35
+ "@instructure/ui-form-field": "9.10.0",
36
+ "@instructure/ui-icons": "9.10.0",
37
+ "@instructure/ui-pill": "9.10.0",
38
+ "@instructure/ui-popover": "9.10.0",
39
+ "@instructure/ui-react-utils": "9.10.0",
40
+ "@instructure/ui-testable": "9.10.0",
41
+ "@instructure/ui-text": "9.10.0",
42
+ "@instructure/ui-text-input": "9.10.0",
43
+ "@instructure/ui-themes": "9.10.0",
44
+ "@instructure/ui-tooltip": "9.10.0",
45
+ "@instructure/ui-utils": "9.10.0",
46
+ "@instructure/ui-view": "9.10.0",
47
47
  "prop-types": "^15.8.1"
48
48
  },
49
49
  "devDependencies": {
50
- "@instructure/ui-axe-check": "9.9.0",
51
- "@instructure/ui-babel-preset": "9.9.0",
52
- "@instructure/ui-scripts": "9.9.0",
53
- "@instructure/ui-test-utils": "9.9.0",
50
+ "@instructure/ui-axe-check": "9.10.0",
51
+ "@instructure/ui-babel-preset": "9.10.0",
52
+ "@instructure/ui-scripts": "9.10.0",
53
+ "@instructure/ui-test-utils": "9.10.0",
54
54
  "@testing-library/jest-dom": "^6.4.5",
55
55
  "@testing-library/react": "^15.0.7",
56
56
  "@testing-library/user-event": "^14.5.2",
@@ -32,13 +32,23 @@ type: example
32
32
  super(props)
33
33
  this.state = {
34
34
  selectedForeGround: '#0CBF94',
35
- selectedBackGround: '#35423A'
35
+ selectedBackGround: '#35423A',
36
+ validationLevel: 'AA'
36
37
  }
37
38
  }
38
39
 
39
40
  render() {
40
41
  return (
41
42
  <div>
43
+ <RadioInputGroup
44
+ onChange={(_e, value) => this.setState({ validationLevel: value })}
45
+ name="example1"
46
+ defaultValue="AA"
47
+ description="validationLevel"
48
+ >
49
+ <RadioInput key="AA" value="AA" label="AA" />
50
+ <RadioInput key="AAA" value="AAA" label="AAA" />
51
+ </RadioInputGroup>
42
52
  <ColorPreset
43
53
  label="Background"
44
54
  colors={[
@@ -86,6 +96,8 @@ type: example
86
96
  normalTextLabel="Normal text"
87
97
  largeTextLabel="Large text"
88
98
  graphicsTextLabel="Graphics text"
99
+ validationLevel={this.state.validationLevel}
100
+ onContrastChange={(contrastData) => console.log(contrastData)}
89
101
  />
90
102
  </div>
91
103
  )
@@ -99,9 +111,19 @@ type: example
99
111
  const Example = () => {
100
112
  const [selectedForeGround, setSelectedForeGround] = useState('#0CBF94')
101
113
  const [selectedBackGround, setSelectedBackGround] = useState('#35423A')
114
+ const [validationLevel, setValidationLevel] = useState('AA')
102
115
 
103
116
  return (
104
117
  <div>
118
+ <RadioInputGroup
119
+ onChange={(_e, value) => setValidationLevel(value)}
120
+ name="example1"
121
+ defaultValue="AA"
122
+ description="validationLevel"
123
+ >
124
+ <RadioInput key="AA" value="AA" label="AA" />
125
+ <RadioInput key="AAA" value="AAA" label="AAA" />
126
+ </RadioInputGroup>
105
127
  <ColorPreset
106
128
  label="Background"
107
129
  colors={[
@@ -149,6 +171,8 @@ type: example
149
171
  normalTextLabel="Normal text"
150
172
  largeTextLabel="Large text"
151
173
  graphicsTextLabel="Graphics text"
174
+ validationLevel={validationLevel}
175
+ onContrastChange={(contrastData) => console.log(contrastData)}
152
176
  />
153
177
  </div>
154
178
  )
@@ -29,9 +29,10 @@ import React, { Component } from 'react'
29
29
  import { omitProps } from '@instructure/ui-react-utils'
30
30
  import { testable } from '@instructure/ui-testable'
31
31
  import { 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'
35
36
  import { withStyle, jsx } from '@instructure/emotion'
36
37
 
37
38
  import { Text } from '@instructure/ui-text'
@@ -40,7 +41,7 @@ import { Pill } from '@instructure/ui-pill'
40
41
  import ColorIndicator from '../ColorIndicator'
41
42
 
42
43
  import { propTypes, allowedProps } from './props'
43
- import type { ColorContrastProps } from './props'
44
+ import type { ColorContrastProps, ColorContrastState } from './props'
44
45
 
45
46
  import generateStyle from './styles'
46
47
  import generateComponentTheme from './theme'
@@ -52,17 +53,25 @@ category: components
52
53
  **/
53
54
  @withStyle(generateStyle, generateComponentTheme)
54
55
  @testable()
55
- class ColorContrast extends Component<ColorContrastProps> {
56
+ class ColorContrast extends Component<ColorContrastProps, ColorContrastState> {
56
57
  static propTypes = propTypes
57
58
  static allowedProps = allowedProps
58
59
  static readonly componentId = 'ColorContrast'
59
60
 
60
61
  static defaultProps = {
61
- withoutColorPreview: false
62
+ withoutColorPreview: false,
63
+ validationLevel: 'AA'
62
64
  }
63
65
 
64
66
  constructor(props: ColorContrastProps) {
65
67
  super(props)
68
+
69
+ this.state = {
70
+ contrast: 1,
71
+ isValidNormalText: false,
72
+ isValidLargeText: false,
73
+ isValidGraphicsText: false
74
+ }
66
75
  }
67
76
 
68
77
  ref: HTMLDivElement | null = null
@@ -79,10 +88,27 @@ class ColorContrast extends Component<ColorContrastProps> {
79
88
 
80
89
  componentDidMount() {
81
90
  this.props.makeStyles?.()
91
+ this.calcState()
82
92
  }
83
93
 
84
- componentDidUpdate() {
94
+ componentDidUpdate(prevProps: ColorContrastProps) {
85
95
  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
+ }
86
112
  }
87
113
 
88
114
  renderStatus = (pass: boolean, description: string) => {
@@ -153,32 +179,17 @@ class ColorContrast extends Component<ColorContrastProps> {
153
179
  )
154
180
  }
155
181
 
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
181
186
  )
187
+ const newState = {
188
+ contrast,
189
+ ...validateContrast(contrast, this.props.validationLevel)
190
+ }
191
+ this.setState(newState)
192
+ return newState
182
193
  }
183
194
 
184
195
  render() {
@@ -190,7 +201,12 @@ class ColorContrast extends Component<ColorContrastProps> {
190
201
  graphicsTextLabel
191
202
  } = this.props
192
203
 
193
- const contrast = this.calcContrast
204
+ const {
205
+ contrast,
206
+ isValidNormalText,
207
+ isValidLargeText,
208
+ isValidGraphicsText
209
+ } = this.state
194
210
 
195
211
  return (
196
212
  <div
@@ -205,9 +221,9 @@ class ColorContrast extends Component<ColorContrastProps> {
205
221
  </div>
206
222
  <Text size="x-large">{contrast}:1</Text>
207
223
  {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)}
211
227
  </div>
212
228
  )
213
229
  }