From 3796d360c8a4d3c98981bfcf6712db9aca22b729 Mon Sep 17 00:00:00 2001 From: Levi Buzolic Date: Tue, 31 Oct 2023 00:13:45 +1100 Subject: [PATCH] Normalize all colors that React Native supports --- package.json | 2 + src/__tests__/normalizer.spec.ts | 126 ++++++++++++++++++++----------- src/utils/module.d.ts | 3 + src/utils/normalizer.ts | 38 ++++------ yarn.lock | 4 +- 5 files changed, 106 insertions(+), 67 deletions(-) create mode 100644 src/utils/module.d.ts diff --git a/package.json b/package.json index 65f5a5b1..7d8cffd2 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "devDependencies": { "@commitlint/config-conventional": "17.8.0", "@react-native/eslint-config": "0.74.0", + "@react-native/normalize-colors": "0.74.1", "@release-it/conventional-changelog": "5.1.1", "@testing-library/react-hooks": "8.0.1", "@types/jest": "29.5.5", @@ -75,6 +76,7 @@ "typescript": "5.2.2" }, "peerDependencies": { + "@react-native/normalize-colors": "*", "react": "*", "react-native": "*", "react-native-web": "*" diff --git a/src/__tests__/normalizer.spec.ts b/src/__tests__/normalizer.spec.ts index dbcf47ef..53923d6e 100644 --- a/src/__tests__/normalizer.spec.ts +++ b/src/__tests__/normalizer.spec.ts @@ -68,16 +68,26 @@ describe('Normalizer', () => { height: -10 }, shadowOpacity: 0.5 + }, + { + shadowRadius: 3, + shadowColor: 'blue', + shadowOffset: { + width: 0, + height: -10 + }, + shadowOpacity: 0.5 } ] const results: Array = [ '3px 2px 3px rgba(0,0,0,0.4)', - '0 0 1px rgba(171,202,188,1)', + '0 0 1px #abcabc', '0 1px 0 rgba(127,17,224,0.3)', '-4px -3px 3px rgba(255,87,51,0.5)', '0 0 0 rgba(0,0,0,0)', '50px 50px 20px orange', - '0 -10px 3px rgba(255,87,51,0.8)' + '0 -10px 3px rgba(255,87,51,0.4)', + '0 -10px 3px rgba(0,0,255,0.5)' ] styles.forEach((style, index) => { @@ -131,11 +141,11 @@ describe('Normalizer', () => { } ] const results: Array = [ - '4px 5px 2px rgba(255,255,255,1)', - '3px 4px 5px rgba(0,0,0,1)', - '-1px -1px 0 rgba(255,87,51,1)', + '4px 5px 2px #fff', + '3px 4px 5px #000000', + '-1px -1px 0 #FF5733', '0 0 0 red', - '-5px -5px 0 rgba(255,87,51,0.8)' + '-5px -5px 0 #FF5733CC' ] styles.forEach((style, index) => { @@ -237,7 +247,7 @@ describe('Normalizer', () => { }) describe('normalizeColor', () => { - it('should not normalize string colors', () => { + it('should not normalize string colors when no opacity is defined', () => { const values: Array = [ 'red', 'orange', @@ -245,9 +255,37 @@ describe('Normalizer', () => { 'purple', 'black' ] + const results: Array = [ + 'red', + 'orange', + 'pink', + 'purple', + 'black' + ] values.forEach((value, index) => { - expect(normalizeColor(value)).toEqual(values[index]) + expect(normalizeColor(value)).toEqual(results[index]) + }) + }) + + it('should normalize string colors when opacity is defined', () => { + const values: Array = [ + 'red', + 'orange', + 'pink', + 'purple', + 'black' + ] + const results: Array = [ + 'rgba(255,0,0,0.5)', + 'rgba(255,165,0,0.5)', + 'rgba(255,192,203,0.5)', + 'rgba(128,0,128,0.5)', + 'rgba(0,0,0,0.5)' + ] + + values.forEach((value, index) => { + expect(normalizeColor(value, 0.5)).toEqual(results[index]) }) }) @@ -265,20 +303,20 @@ describe('Normalizer', () => { '#456' ] const results: Array = [ - 'rgba(255,0,0,1)', - 'rgba(0,255,0,1)', - 'rgba(0,0,255,1)', - 'rgba(255,255,0,1)', - 'rgba(255,0,255,1)', - 'rgba(0,255,255,1)', - 'rgba(255,255,255,1)', - 'rgba(170,187,204,1)', - 'rgba(17,34,51,1)', - 'rgba(68,85,102,1)' + 'rgba(255,0,0,0.2)', + 'rgba(0,255,0,0.2)', + 'rgba(0,0,255,0.2)', + 'rgba(255,255,0,0.2)', + 'rgba(255,0,255,0.2)', + 'rgba(0,255,255,0.2)', + 'rgba(255,255,255,0.2)', + 'rgba(170,187,204,0.2)', + 'rgba(17,34,51,0.2)', + 'rgba(68,85,102,0.2)' ] values.forEach((value, index) => { - expect(normalizeColor(value)).toEqual(results[index]) + expect(normalizeColor(value, 0.2)).toEqual(results[index]) }) }) @@ -296,20 +334,20 @@ describe('Normalizer', () => { '#445566' ] const results: Array = [ - 'rgba(255,0,0,1)', - 'rgba(0,255,0,1)', - 'rgba(0,0,255,1)', - 'rgba(255,255,0,1)', - 'rgba(255,0,255,1)', - 'rgba(0,255,255,1)', - 'rgba(255,255,255,1)', - 'rgba(170,187,204,1)', - 'rgba(17,34,51,1)', - 'rgba(68,85,102,1)' + 'rgba(255,0,0,0.99)', + 'rgba(0,255,0,0.99)', + 'rgba(0,0,255,0.99)', + 'rgba(255,255,0,0.99)', + 'rgba(255,0,255,0.99)', + 'rgba(0,255,255,0.99)', + 'rgba(255,255,255,0.99)', + 'rgba(170,187,204,0.99)', + 'rgba(17,34,51,0.99)', + 'rgba(68,85,102,0.99)' ] values.forEach((value, index) => { - expect(normalizeColor(value)).toEqual(results[index]) + expect(normalizeColor(value, 0.99)).toEqual(results[index]) }) }) @@ -327,20 +365,20 @@ describe('Normalizer', () => { '#44556677' ] const results: Array = [ - 'rgba(255,0,0,1)', - 'rgba(0,255,0,1)', - 'rgba(0,0,255,1)', - 'rgba(255,255,0,1)', - 'rgba(255,0,255,1)', - 'rgba(0,255,255,1)', - 'rgba(255,255,255,1)', - 'rgba(170,187,204,0.8666666666666667)', - 'rgba(17,34,51,0.26666666666666666)', - 'rgba(68,85,102,0.4666666666666667)' + 'rgba(255,0,0,0.1)', + 'rgba(0,255,0,0.1)', + 'rgba(0,0,255,0.1)', + 'rgba(255,255,0,0.1)', + 'rgba(255,0,255,0.1)', + 'rgba(0,255,255,0.1)', + 'rgba(255,255,255,0.1)', + 'rgba(170,187,204,0.08666666666666667)', + 'rgba(17,34,51,0.02666666666666667)', + 'rgba(68,85,102,0.04666666666666667)' ] values.forEach((value, index) => { - expect(normalizeColor(value)).toEqual(results[index]) + expect(normalizeColor(value, 0.1)).toEqual(results[index]) }) }) }) @@ -398,11 +436,11 @@ describe('Normalizer', () => { width: 0, height: 0 }, - shadowOpacity: 1, + shadowOpacity: 0.5, shadowRadius: 5 } expect(normalizeStylesWeb(styles)).toEqual({ - boxShadow: '0 0 5px rgba(0,0,0,1)', + boxShadow: '0 0 5px rgba(0,0,0,0.5)', shadowColor: undefined, shadowOffset: undefined, shadowOpacity: undefined, @@ -420,7 +458,7 @@ describe('Normalizer', () => { textShadowRadius: 12 } expect(normalizeStylesWeb(styles)).toEqual({ - textShadow: '-5px 0 12px rgba(255,255,255,1)', + textShadow: '-5px 0 12px #fff', textShadowColor: undefined, textShadowOffset: undefined, textShadowRadius: undefined diff --git a/src/utils/module.d.ts b/src/utils/module.d.ts new file mode 100644 index 00000000..cea8e230 --- /dev/null +++ b/src/utils/module.d.ts @@ -0,0 +1,3 @@ +declare module '@react-native/normalize-colors' { + export default function normalizeColor(color: string): number | null +} diff --git a/src/utils/normalizer.ts b/src/utils/normalizer.ts index 6ca3c011..195f8659 100644 --- a/src/utils/normalizer.ts +++ b/src/utils/normalizer.ts @@ -1,5 +1,6 @@ // based on react-native-web normalizer // https://github.com/necolas/react-native-web +import normalizeColors from '@react-native/normalize-colors' import type { TextShadow, Transforms, BoxShadow } from '../types' type Preprocessor = { @@ -8,38 +9,31 @@ type Preprocessor = { createTransformValue(transforms: Required): string, } -// for now supports -// hex colors (3, 6, 8) chars -// colors like orange red etc. export const normalizeColor = (color: string, opacity: number = 1) => { - if (!color.startsWith('#')) { + // If the opacity is 1 there's no need to normalize the color + if (opacity === 1) { return color } - if (color.length === 9) { - const [r, g, b, a] = color - .slice(1) + const integer = normalizeColors(color) as number | null + + // If the colour is an unknown format, the return value is null + if (integer === null) { + return color + } + + const hex = integer.toString(16).padStart(8, '0') + + if (hex.length === 8) { + const [r = 0, g = 0, b = 0, a = 1] = hex .split(/(?=(?:..)*$)/) .map(x => parseInt(x, 16)) .filter(num => !isNaN(num)) - return `rgba(${r},${g},${b},${(a as number) / 255})` + return `rgba(${r},${g},${b},${((a as number) / 255) * opacity})` } - const sanitizedHex = color.length === 4 - ? color - .slice(1) - .split('') - .map(char => `${char}${char}`) - .join('') - : color.slice(1) - - return sanitizedHex - .split(/(?=(?:..)*$)/) - .map(x => parseInt(x, 16)) - .filter(num => !isNaN(num)) - .reduce((acc, color) => `${acc}${color},`, 'rgba(') - .concat(`${opacity})`) + return color } export const normalizeNumericValue = (value: number) => value ? `${value}px` : value diff --git a/yarn.lock b/yarn.lock index 61cb27e0..7558f6ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3917,7 +3917,7 @@ __metadata: languageName: node linkType: hard -"@react-native/normalize-colors@npm:*": +"@react-native/normalize-colors@npm:*, @react-native/normalize-colors@npm:0.74.1": version: 0.74.1 resolution: "@react-native/normalize-colors@npm:0.74.1" checksum: a8625a2ed4f2595c9e1a0b0877ca8ab02dab243ced6bf98c82c328c2c125ca31dd3afd1f2940f2c114af2c309b28ad24da98aa9519a761a2df796c6968c055ec @@ -18017,6 +18017,7 @@ __metadata: dependencies: "@commitlint/config-conventional": 17.8.0 "@react-native/eslint-config": 0.74.0 + "@react-native/normalize-colors": 0.74.1 "@release-it/conventional-changelog": 5.1.1 "@testing-library/react-hooks": 8.0.1 "@types/jest": 29.5.5 @@ -18050,6 +18051,7 @@ __metadata: release-it: 16.2.1 typescript: 5.2.2 peerDependencies: + "@react-native/normalize-colors": "*" react: "*" react-native: "*" react-native-web: "*"