Skip to content

Commit 4f78607

Browse files
authored
Colors - adjust palette to steps of 9 when addDarkestTint is true (#2855)
* Colors - adjust palette to steps of 9 when addDarkestTint is true * hide colorPalette example * Tests - adding and reorganizing * fix tests * adding tests for adjustLightness * add default tests * Adding 'saturationLevels' option * add range check * fix base color saturation * rename * pr comments * rename param
1 parent f3e00eb commit 4f78607

File tree

3 files changed

+289
-83
lines changed

3 files changed

+289
-83
lines changed

demo/src/screens/foundationScreens/ColorsScreen.js

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ import {
1111
Icon,
1212
Button,
1313
TextField,
14-
Incubator
14+
Incubator,
15+
ColorPalette,
16+
ColorPickerDialog
1517
} from 'react-native-ui-lib';
18+
import {renderBooleanOption} from '../ExampleScreenPresenter';
1619

1720
const {Toast} = Incubator;
1821

@@ -42,7 +45,14 @@ class ColorsScreen extends Component {
4245
searchValue: '',
4346
filteredTokens: [],
4447
showToast: false,
45-
message: undefined
48+
message: undefined,
49+
currentColor: Colors.$textPrimary,
50+
showPicker: false,
51+
isDefaultOptions: false,
52+
adjustLightness: false,
53+
adjustSaturation: false,
54+
addDarkestTints: true,
55+
avoidReverseOnDark: true
4656
};
4757

4858
scrollViewRef = React.createRef();
@@ -281,13 +291,102 @@ class ColorsScreen extends Component {
281291
);
282292
}
283293

294+
/** Color Palette */
295+
296+
showColorPicker = () => {
297+
this.setState({showPicker: true});
298+
};
299+
300+
onValueChange = (color) => {
301+
this.setState({currentColor: color});
302+
};
303+
304+
onSubmit = (color) => {
305+
this.setState({currentColor: color});
306+
};
307+
308+
onDismiss = () => {
309+
this.setState({showPicker: false});
310+
};
311+
312+
setDefaultOptions = () => {
313+
const designKitsOptions = {adjustLightness: false, adjustSaturation: false, addDarkestTints: true, avoidReverseOnDark: true};
314+
const defaultOptions = {adjustLightness: true, adjustSaturation: true, addDarkestTints: false, avoidReverseOnDark: false};
315+
if (this.state.isDefaultOptions) {
316+
this.setState({...designKitsOptions, isDefaultOptions: false});
317+
} else {
318+
this.setState({...defaultOptions, isDefaultOptions: true});
319+
}
320+
};
321+
322+
renderColorPicker = () => {
323+
const {showPicker, currentColor} = this.state;
324+
return (
325+
<ColorPickerDialog
326+
visible={showPicker}
327+
initialColor={currentColor}
328+
key={currentColor}
329+
onDismiss={this.onDismiss}
330+
onSubmit={this.onSubmit}
331+
/>
332+
);
333+
};
334+
335+
renderOptions = () => {
336+
return (
337+
<View padding-20>
338+
{renderBooleanOption.call(this, 'adjustLightness', 'adjustLightness')}
339+
{renderBooleanOption.call(this, 'adjustSaturation', 'adjustSaturation')}
340+
{renderBooleanOption.call(this, 'addDarkestTints', 'addDarkestTints')}
341+
{renderBooleanOption.call(this, 'avoidReverseOnDark', 'avoidReverseOnDark')}
342+
<Button label={this.state.isDefaultOptions ? 'Reset example' : 'Set defaults'} onPress={this.setDefaultOptions}/>
343+
</View>
344+
);
345+
};
346+
347+
renderColorPalette = () => {
348+
const {currentColor, adjustLightness, adjustSaturation, addDarkestTints, avoidReverseOnDark} = this.state;
349+
const paletteOptions = {adjustLightness, adjustSaturation, addDarkestTints, avoidReverseOnDark};
350+
const palette = Colors.generateColorPalette(currentColor, paletteOptions);
351+
return (
352+
<View margin-12 br40 style={{borderWidth: 1}}>
353+
{this.renderOptions()}
354+
<View center row>
355+
<TouchableOpacity
356+
marginH-10
357+
style={[styles.colorBox, {backgroundColor: currentColor}]}
358+
onPress={this.showColorPicker}
359+
/>
360+
<ColorPalette
361+
colors={palette}
362+
value={currentColor}
363+
swatchStyle={styles.swatchStyle}
364+
containerStyle={{marginLeft: -10}}
365+
onValueChange={this.onValueChange}
366+
/>
367+
</View>
368+
</View>
369+
);
370+
};
371+
372+
renderColorPaletteExample = () => {
373+
return (
374+
<>
375+
<Text text50 marginL-20>Generate Color Palette</Text>
376+
{this.renderColorPalette()}
377+
{this.renderColorPicker()}
378+
</>
379+
);
380+
};
381+
284382
render() {
285383
return (
286384
<>
287385
{this.renderSearch()}
288386
<ScrollView ref={this.scrollViewRef}>
289387
{this.renderDesignTokens()}
290388
{this.renderColors(SYSTEM_COLORS, 'SYSTEM COLORS')}
389+
{this.renderColorPaletteExample()}
291390
</ScrollView>
292391
{this.renderToast()}
293392
</>
@@ -303,6 +402,19 @@ const styles = StyleSheet.create({
303402
searchField: {
304403
padding: Spacings.s3,
305404
borderRadius: 8
405+
},
406+
colorBox: {
407+
width: 60,
408+
height: 60,
409+
borderWidth: 1,
410+
borderRadius: 30
411+
},
412+
swatchStyle: {
413+
width: 18,
414+
height: 40,
415+
borderRadius: 10,
416+
marginLeft: 4,
417+
marginRight: 4
306418
}
307419
});
308420

src/style/__tests__/colors.spec.js

Lines changed: 119 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -5,47 +5,51 @@ const SYSTEM_COLORS = ['grey', 'white', 'black'];
55
const GetColorsByHexOptions = {validColors: SYSTEM_COLORS};
66

77
describe('style/Colors', () => {
8-
const logServiceSpy = jest.spyOn(LogService, 'error');
9-
it('should add alpha to hex color value', () => {
10-
expect(uut.rgba(uut.green30, 0.7)).toBe('rgba(0, 168, 126, 0.7)');
11-
expect(uut.rgba(uut.red10, 0.7)).toBe('rgba(213, 39, 18, 0.7)');
12-
expect(uut.rgba(uut.green30, 0.25)).toBe('rgba(0, 168, 126, 0.25)');
13-
// expect(uut.rgba('#ff2442', 0.05)).toBe(`${'#ff2442'}0D`);
14-
// expect(uut.rgba(uut.blue20, 1)).toBe(`${uut.blue20}FF`);
15-
// expect(uut.rgba(uut.blue20)).toBe(`${uut.blue20}FF`);
16-
// expect(uut.rgba(uut.blue20, 2)).toBe(`${uut.blue20}`);
17-
// expect(uut.rgba(uut.blue20, -2)).toBe(`${uut.blue20}`);
18-
// expect(uut.rgba(uut.blue20, '12ddsav')).toBe(`${uut.blue20}`);
19-
});
20-
21-
it('should add alpha to rgb color value', () => {
22-
expect(uut.rgba(101, 200, 136, 0.7)).toBe('rgba(101, 200, 136, 0.7)');
23-
expect(uut.rgba(207, 38, 47, 0.7)).toBe('rgba(207, 38, 47, 0.7)');
24-
expect(uut.rgba(101, 200, 136, 0.25)).toBe('rgba(101, 200, 136, 0.25)');
25-
});
26-
27-
it('should add alpha to 3 digits hex color value', () => {
28-
expect(uut.rgba('#333', 0.7)).toBe('rgba(51, 51, 51, 0.7)');
29-
expect(uut.rgba('#333', 0.1)).toBe('rgba(51, 51, 51, 0.1)');
30-
expect(uut.rgba('#DEF', 0.25)).toBe('rgba(221, 238, 255, 0.25)');
31-
expect(uut.rgba('#F24', 1)).toBe('rgba(255, 34, 68, 1)');
32-
});
33-
34-
it('should handle wrong number of params', () => {
35-
expect(uut.rgba(101, 136, 0.7)).toBe(undefined);
36-
expect(uut.rgba(undefined, 0.2)).toBe(undefined);
37-
expect(logServiceSpy).toHaveBeenNthCalledWith(2, 'Colors.rgba fail due to invalid arguments');
38-
});
39-
40-
it('should handle invalid rgb code', () => {
41-
expect(() => uut.rgba(-12, 128, 136, 0.7)).toThrow(new Error('-12 is invalid rgb code, please use number between 0-255'));
42-
expect(() => uut.rgba(12, 128, 256, 0.7)).toThrow(new Error('256 is invalid rgb code, please use number between 0-255'));
43-
});
44-
45-
it('should handle invalid hex code', () => {
46-
expect(() => uut.rgba('#ff22445', 0.7)).toThrow(new Error('#ff22445 is invalid hex color'));
47-
expect(() => uut.rgba('ff2244', 0.7)).toThrow(new Error('ff2244 is invalid hex color'));
48-
expect(() => uut.rgba('#ff244', 0.7)).toThrow(new Error('#ff244 is invalid hex color'));
8+
9+
describe('rgba', () => {
10+
const logServiceSpy = jest.spyOn(LogService, 'error');
11+
12+
it('should add alpha to hex color value', () => {
13+
expect(uut.rgba(uut.green30, 0.7)).toBe('rgba(0, 168, 126, 0.7)');
14+
expect(uut.rgba(uut.red10, 0.7)).toBe('rgba(213, 39, 18, 0.7)');
15+
expect(uut.rgba(uut.green30, 0.25)).toBe('rgba(0, 168, 126, 0.25)');
16+
// expect(uut.rgba('#ff2442', 0.05)).toBe(`${'#ff2442'}0D`);
17+
// expect(uut.rgba(uut.blue20, 1)).toBe(`${uut.blue20}FF`);
18+
// expect(uut.rgba(uut.blue20)).toBe(`${uut.blue20}FF`);
19+
// expect(uut.rgba(uut.blue20, 2)).toBe(`${uut.blue20}`);
20+
// expect(uut.rgba(uut.blue20, -2)).toBe(`${uut.blue20}`);
21+
// expect(uut.rgba(uut.blue20, '12ddsav')).toBe(`${uut.blue20}`);
22+
});
23+
24+
it('should add alpha to rgb color value', () => {
25+
expect(uut.rgba(101, 200, 136, 0.7)).toBe('rgba(101, 200, 136, 0.7)');
26+
expect(uut.rgba(207, 38, 47, 0.7)).toBe('rgba(207, 38, 47, 0.7)');
27+
expect(uut.rgba(101, 200, 136, 0.25)).toBe('rgba(101, 200, 136, 0.25)');
28+
});
29+
30+
it('should add alpha to 3 digits hex color value', () => {
31+
expect(uut.rgba('#333', 0.7)).toBe('rgba(51, 51, 51, 0.7)');
32+
expect(uut.rgba('#333', 0.1)).toBe('rgba(51, 51, 51, 0.1)');
33+
expect(uut.rgba('#DEF', 0.25)).toBe('rgba(221, 238, 255, 0.25)');
34+
expect(uut.rgba('#F24', 1)).toBe('rgba(255, 34, 68, 1)');
35+
});
36+
37+
it('should handle wrong number of params', () => {
38+
expect(uut.rgba(101, 136, 0.7)).toBe(undefined);
39+
expect(uut.rgba(undefined, 0.2)).toBe(undefined);
40+
expect(logServiceSpy).toHaveBeenNthCalledWith(2, 'Colors.rgba fail due to invalid arguments');
41+
});
42+
43+
it('should handle invalid rgb code', () => {
44+
expect(() => uut.rgba(-12, 128, 136, 0.7)).toThrow(new Error('-12 is invalid rgb code, please use number between 0-255'));
45+
expect(() => uut.rgba(12, 128, 256, 0.7)).toThrow(new Error('256 is invalid rgb code, please use number between 0-255'));
46+
});
47+
48+
it('should handle invalid hex code', () => {
49+
expect(() => uut.rgba('#ff22445', 0.7)).toThrow(new Error('#ff22445 is invalid hex color'));
50+
expect(() => uut.rgba('ff2244', 0.7)).toThrow(new Error('ff2244 is invalid hex color'));
51+
expect(() => uut.rgba('#ff244', 0.7)).toThrow(new Error('#ff244 is invalid hex color'));
52+
});
4953
});
5054

5155
describe('isEmpty', () => {
@@ -112,38 +116,90 @@ describe('style/Colors', () => {
112116
});
113117

114118
describe('generateColorPalette', () => {
119+
const baseColor = '#3F88C5';
120+
const tints = ['#193852', '#255379', '#316EA1', '#3F88C5', '#66A0D1', '#8DB9DD', '#B5D1E9', '#DCE9F4'];
121+
const baseColorLight = '#DCE9F4';
122+
const tintsLight = ['#1A3851', '#265278', '#326D9F', '#4187C3', '#68A0CF', '#8EB8DC', '#B5D1E8', '#DCE9F4'];
123+
const saturationLevels = [-10, -10, -20, -20, -25, -25, -25, -25, -20, -10];
124+
const tintsSaturationLevels = ['#1E384D', '#2D5271', '#466C8C', '#3F88C5', '#7F9EB8', '#A0B7CB', '#C1D0DD', '#E2E9EE'];
125+
const tintsSaturationLevelsDarkest = ['#162837', '#223F58', '#385770', '#486E90', '#3F88C5', '#7C9CB6', '#9AB2C6', '#B7C9D7', '#D3DFE9', '#F0F5F9'];
126+
const tintsAddDarkestTints = ['#12283B', '#1C405E', '#275881', '#3270A5', '#3F88C5', '#629ED0', '#86B4DA', '#A9CAE5', '#CCDFF0', '#EFF5FA'];
127+
115128
it('should memoize calls for generateColorPalette', () => {
116-
uut.getColorTint('#3F88C5', 20);
117-
uut.getColorTint('#3F88C5', 50);
118-
uut.getColorTint('#3F88C5', 70);
119-
const cachedPalette = uut.generateColorPalette.cache.get('#3F88C5');
129+
uut.getColorTint(baseColor, 20);
130+
uut.getColorTint(baseColor, 50);
131+
uut.getColorTint(baseColor, 70);
132+
const cachedPalette = uut.generateColorPalette.cache.get(baseColor);
120133
expect(cachedPalette).toBeDefined();
121-
expect(cachedPalette.length).toBe(8);
122-
expect(cachedPalette.includes('#3F88C5')).toBe(true);
134+
expect(cachedPalette.includes(baseColor)).toBe(true);
123135
});
124136

125-
it('should generateColorPalette', () => {
126-
const palette = uut.generateColorPalette('#3F88C5');
127-
expect(palette).toEqual(['#193852', '#255379', '#316EA1', '#3F88C5', '#66A0D1', '#8DB9DD', '#B5D1E9', '#DCE9F4']);
137+
it('should generateColorPalette return 8 tints with 10 lightness increment', () => {
138+
const palette = uut.generateColorPalette(baseColor);
139+
expect(palette.length).toBe(8);
140+
expect(palette).toContain(baseColor);
141+
expect(palette).toEqual(tints);
128142
});
129143

130-
it('should generateColorPalette with adjusted saturation', () => {
131-
const palette = uut.generateColorPalette('#FFE5FF');
132-
expect(palette).toEqual(['#661A66', '#8F248F', '#B82EB7', '#D148D1', '#DB71DB', '#E699E6', '#F0C2F0', '#FFE5FF']);
144+
it('should generateColorPalette with adjustLightness option false', () => {
145+
const palette = uut.generateColorPalette(baseColor, {adjustLightness: false});
146+
expect(palette.length).toBe(8);
147+
expect(palette).toContain(baseColor);
148+
expect(palette).toEqual(tints);
133149
});
134-
});
135150

136-
it('should generateColorPalette with avoidReverseOnDark option false not reverse on light mode', () => {
137-
const palette = uut.generateColorPalette('#3F88C5', {avoidReverseOnDark: false});
138-
expect(palette).toEqual(['#193852', '#255379', '#316EA1', '#3F88C5', '#66A0D1', '#8DB9DD', '#B5D1E9', '#DCE9F4']);
139-
});
151+
it('should generateColorPalette with adjustSaturation option false', () => {
152+
const palette = uut.generateColorPalette(baseColorLight, {adjustSaturation: false});
153+
expect(palette.length).toBe(8);
154+
expect(palette).toContain(baseColorLight);
155+
expect(palette).toEqual(tintsLight);
156+
});
157+
158+
it('should generateColorPalette with adjustSaturation option true and saturationLevels 8 array', () => {
159+
const palette = uut.generateColorPalette(baseColor, {adjustSaturation: true, saturationLevels});
160+
expect(palette.length).toBe(8);
161+
expect(palette).toContain(baseColor); // adjusting baseColor tint as well
162+
expect(palette).toEqual(tintsSaturationLevels);
163+
});
140164

141-
it('should generateColorPalette with avoidReverseOnDark option true not reverse on light mode', () => {
142-
const palette = uut.generateColorPalette('#3F88C5', {avoidReverseOnDark: true});
143-
expect(palette).toEqual(['#193852', '#255379', '#316EA1', '#3F88C5', '#66A0D1', '#8DB9DD', '#B5D1E9', '#DCE9F4']);
165+
it('should generateColorPalette with adjustSaturation option true and saturationLevels 10 array and addDarkestTints true', () => {
166+
const options = {adjustSaturation: true, saturationLevels, addDarkestTints: true};
167+
const palette = uut.generateColorPalette(baseColor, options);
168+
expect(palette.length).toBe(10);
169+
expect(palette).toContain(baseColor); // adjusting baseColor tint as well
170+
expect(palette).toEqual(tintsSaturationLevelsDarkest);
171+
});
172+
173+
it('should generateColorPalette with avoidReverseOnDark option false not reverse on light mode (default)', () => {
174+
const palette = uut.generateColorPalette(baseColor, {avoidReverseOnDark: false});
175+
expect(palette.length).toBe(8);
176+
expect(palette).toContain(baseColor);
177+
expect(palette).toEqual(tints);
178+
});
179+
180+
it('should generateColorPalette with avoidReverseOnDark option true not reverse on light mode', () => {
181+
const palette = uut.generateColorPalette(baseColor, {avoidReverseOnDark: true});
182+
expect(palette.length).toBe(8);
183+
expect(palette).toContain(baseColor);
184+
expect(palette).toEqual(tints);
185+
});
186+
187+
it('should generateColorPalette with addDarkestTints option false return 8 tints with 10 lightness increment (default)', () => {
188+
const palette = uut.generateColorPalette(baseColor, {addDarkestTints: false});
189+
expect(palette.length).toBe(8);
190+
expect(palette).toContain(baseColor);
191+
expect(palette).toEqual(tints);
192+
});
193+
194+
it('should generateColorPalette with addDarkestTints option true return 10 tints with 9 lightness increment', () => {
195+
const palette = uut.generateColorPalette(baseColor, {addDarkestTints: true});
196+
expect(palette.length).toBe(10);
197+
expect(palette).toContain(baseColor);
198+
expect(palette).toEqual(tintsAddDarkestTints);
199+
});
144200
});
145201

146-
describe('generateDesignTokens(...)', () => {
202+
describe('generateDesignTokens', () => {
147203
it('should generate design tokens from dark color for light theme', () => {
148204
const primaryColor = '#860D86';
149205
expect(uut.isDark(primaryColor)).toEqual(true);
@@ -201,7 +257,7 @@ describe('style/Colors', () => {
201257
});
202258
});
203259

204-
describe('isDesignToken(...)', () => {
260+
describe('isDesignToken', () => {
205261
it('should return true if the color passed is design token', () => {
206262
expect(uut.isDesignToken({semantic: ['$textDefault'], toString: () => {}})).toEqual(true);
207263
expect(uut.isDesignToken({resource_paths: ['@color/textNeutral'], toString: () => {}})).toEqual(true);

0 commit comments

Comments
 (0)