Skip to content

Commit 9ea19f2

Browse files
devongovettdannify
andauthored
Bootstrap @react-stately/color package (#979)
* Bootstrap @react-stately/color package * Fix lint of plop template Co-authored-by: Danni <[email protected]>
1 parent 64d545d commit 9ea19f2

File tree

8 files changed

+303
-2
lines changed

8 files changed

+303
-2
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# @react-stately/color
2+
3+
This package is part of [react-spectrum](https://github.com/adobe/react-spectrum). See the repo for more details.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Copyright 2020 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
export * from './src';
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "@react-stately/color",
3+
"version": "3.0.0-alpha.1",
4+
"description": "Spectrum UI components in React",
5+
"license": "Apache-2.0",
6+
"private": true,
7+
"main": "dist/main.js",
8+
"module": "dist/module.js",
9+
"types": "dist/types.d.ts",
10+
"source": "src/index.ts",
11+
"files": ["dist", "src"],
12+
"sideEffects": false,
13+
"repository": {
14+
"type": "git",
15+
"url": "https://github.com/adobe/react-spectrum"
16+
},
17+
"dependencies": {
18+
"@babel/runtime": "^7.6.2"
19+
},
20+
"peerDependencies": {
21+
"react": "^16.8.0"
22+
},
23+
"publishConfig": {
24+
"access": "public"
25+
}
26+
}
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
/*
2+
* Copyright 2020 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
/** A list of supported color formats. */
14+
type ColorFormat = 'hex' | 'hexa' | 'rgb' | 'rgba' | 'hsl' | 'hsla' | 'hsb' | 'hsba';
15+
16+
/** A list of color channels. */
17+
type ColorChannel = 'hue' | 'saturation' | 'brightness' | 'lightness' | 'red' | 'green' | 'blue' | 'alpha';
18+
19+
export class Color {
20+
private value: ColorValue;
21+
22+
constructor(value: string) {
23+
let parsed: ColorValue | void = RGBColor.parse(value);
24+
if (parsed) {
25+
this.value = parsed;
26+
} else {
27+
throw new Error('Invalid color value: ' + value);
28+
}
29+
}
30+
31+
private static fromColorValue(value: ColorValue): Color {
32+
let x: Color = Object.create(Color.prototype);
33+
x.value = value;
34+
return x;
35+
}
36+
37+
toFormat(format: ColorFormat): Color {
38+
switch (format) {
39+
case 'hex':
40+
case 'hexa':
41+
case 'rgb':
42+
case 'rgba':
43+
return Color.fromColorValue(this.value.toRGB());
44+
case 'hsl':
45+
case 'hsla':
46+
return Color.fromColorValue(this.value.toHSL());
47+
case 'hsb':
48+
case 'hsba':
49+
return Color.fromColorValue(this.value.toHSB());
50+
default:
51+
throw new Error('Invalid color format: ' + format);
52+
}
53+
}
54+
55+
toString(format: ColorFormat) {
56+
switch (format) {
57+
case 'hex':
58+
case 'hexa':
59+
case 'rgb':
60+
case 'rgba':
61+
return this.value.toRGB().toString(format);
62+
case 'hsl':
63+
case 'hsla':
64+
return this.value.toHSL().toString(format);
65+
case 'hsb':
66+
case 'hsba':
67+
return this.value.toHSB().toString(format);
68+
default:
69+
throw new Error('Invalid color format: ' + format);
70+
}
71+
}
72+
73+
getChannelValue(channel: ColorChannel): number {
74+
if (channel in this.value) {
75+
return this.value[channel];
76+
}
77+
78+
throw new Error('Unsupported color channel: ' + channel);
79+
}
80+
}
81+
82+
interface ColorValue {
83+
toRGB(): ColorValue,
84+
toHSB(): ColorValue,
85+
toHSL(): ColorValue,
86+
toString(format: ColorFormat): string
87+
}
88+
89+
const HEX_REGEX = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i;
90+
91+
class RGBColor implements ColorValue {
92+
constructor(private red: number, private green: number, private blue: number, private alpha: number) {}
93+
94+
static parse(value: string): RGBColor | void {
95+
let m;
96+
if ((m = value.match(HEX_REGEX))) {
97+
if (m[1]) {
98+
let r = parseInt(m[1][0] + m[1][0], 16);
99+
let g = parseInt(m[1][1] + m[1][1], 16);
100+
let b = parseInt(m[1][2] + m[1][2], 16);
101+
return new RGBColor(r, g, b, 1);
102+
} else if (m[2]) {
103+
let r = parseInt(m[2][0] + m[2][1], 16);
104+
let g = parseInt(m[2][2] + m[2][3], 16);
105+
let b = parseInt(m[2][4] + m[2][5], 16);
106+
return new RGBColor(r, g, b, 1);
107+
}
108+
} else {
109+
// TODO: check rgb and rgba strings
110+
}
111+
}
112+
113+
toString(format: ColorFormat) {
114+
switch (format) {
115+
case 'hex':
116+
return '#' + (1 << 24 | this.red << 16 | this.green << 8 | this.blue).toString(16).slice(1).toUpperCase();
117+
case 'rgb':
118+
return `rgb(${this.red}, ${this.green}, ${this.blue})`;
119+
case 'rgba':
120+
return `rgba(${this.red}, ${this.green}, ${this.blue}, ${this.alpha})`;
121+
default:
122+
throw new Error('Unsupported color format: ' + format);
123+
}
124+
}
125+
126+
toRGB(): ColorValue {
127+
return this;
128+
}
129+
130+
toHSB(): ColorValue {
131+
throw new Error('Not implemented');
132+
}
133+
134+
toHSL(): ColorValue {
135+
throw new Error('Not implemented');
136+
}
137+
}
138+
139+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
140+
class HSBColor implements ColorValue {
141+
constructor(private hue: number, private saturation: number, private brightness: number, private alpha: number) {}
142+
143+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
144+
static parse(value: string): HSBColor | void {
145+
// TODO
146+
}
147+
148+
toString(format: ColorFormat) {
149+
switch (format) {
150+
case 'hsb':
151+
return `hsb(${this.hue}, ${this.saturation}%, ${this.brightness}%)`;
152+
case 'hsba':
153+
return `hsba(${this.hue}, ${this.saturation}%, ${this.brightness}%, ${this.alpha})`;
154+
default:
155+
throw new Error('Unsupported color format: ' + format);
156+
}
157+
}
158+
159+
toRGB(): ColorValue {
160+
throw new Error('Not implemented');
161+
}
162+
163+
toHSB(): ColorValue {
164+
return this;
165+
}
166+
167+
toHSL(): ColorValue {
168+
throw new Error('Not implemented');
169+
}
170+
}
171+
172+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
173+
class HSLColor implements ColorValue {
174+
constructor(private hue: number, private saturation: number, private lightness: number, private alpha: number) {}
175+
176+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
177+
static parse(value: string): HSLColor | void {
178+
// TODO
179+
}
180+
181+
toString(format: ColorFormat) {
182+
switch (format) {
183+
case 'hsl':
184+
return `hsl(${this.hue}, ${this.saturation}%, ${this.lightness}%)`;
185+
case 'hsla':
186+
return `hsla(${this.hue}, ${this.saturation}%, ${this.lightness}%, ${this.alpha})`;
187+
default:
188+
throw new Error('Unsupported color format: ' + format);
189+
}
190+
}
191+
192+
toRGB(): ColorValue {
193+
throw new Error('Not implemented');
194+
}
195+
196+
toHSB(): ColorValue {
197+
throw new Error('Not implemented');
198+
}
199+
200+
toHSL(): ColorValue {
201+
return this;
202+
}
203+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Copyright 2020 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
export * from './Color';
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2020 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
import {Color} from '../src/Color';
14+
15+
describe('Color', function () {
16+
describe('hex', function () {
17+
it('should parse a short hex color', function () {
18+
let color = new Color('#abc');
19+
expect(color.getChannelValue('red')).toBe(170);
20+
expect(color.getChannelValue('green')).toBe(187);
21+
expect(color.getChannelValue('blue')).toBe(204);
22+
expect(color.getChannelValue('alpha')).toBe(1);
23+
expect(color.toString('hex')).toBe('#AABBCC');
24+
expect(color.toString('rgb')).toBe('rgb(170, 187, 204)');
25+
expect(color.toString('rgba')).toBe('rgba(170, 187, 204, 1)');
26+
});
27+
28+
it('should parse a long hex color', function () {
29+
let color = new Color('#abcdef');
30+
expect(color.getChannelValue('red')).toBe(171);
31+
expect(color.getChannelValue('green')).toBe(205);
32+
expect(color.getChannelValue('blue')).toBe(239);
33+
expect(color.getChannelValue('alpha')).toBe(1);
34+
expect(color.toString('hex')).toBe('#ABCDEF');
35+
expect(color.toString('rgb')).toBe('rgb(171, 205, 239)');
36+
expect(color.toString('rgba')).toBe('rgba(171, 205, 239, 1)');
37+
});
38+
39+
it('should throw on invalid hex value', function () {
40+
expect(() => new Color('#ggg')).toThrow('Invalid color value: #ggg');
41+
});
42+
});
43+
});

plop-templates/@react-spectrum/package.json.hbs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"module": "dist/module.js",
99
"types": "dist/types.d.ts",
1010
"source": "src/index.ts",
11-
"files": ["dist"],
11+
"files": ["dist", "src"],
1212
"sideEffects": [
1313
"*.css"
1414
],

plop-templates/@react-stately/package.json.hbs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"module": "dist/module.js",
99
"types": "dist/types.d.ts",
1010
"source": "src/index.ts",
11-
"files": ["dist"],
11+
"files": ["dist", "src"],
1212
"sideEffects": false,
1313
"repository": {
1414
"type": "git",

0 commit comments

Comments
 (0)