From 7fc4950387c8c98357dc8ad7c4bdd0849300c24f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 23 Mar 2022 14:28:51 -0500 Subject: [PATCH] utils/tailwind.ts: Tailwind/SoapboxConfig conversion functions --- app/soapbox/types/colors.ts | 2 +- app/soapbox/utils/__tests__/tailwind-test.js | 189 +++++++++++++++++++ app/soapbox/utils/tailwind.ts | 44 +++++ 3 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 app/soapbox/utils/__tests__/tailwind-test.js create mode 100644 app/soapbox/utils/tailwind.ts diff --git a/app/soapbox/types/colors.ts b/app/soapbox/types/colors.ts index 01f97eba0..75d9a3eb6 100644 --- a/app/soapbox/types/colors.ts +++ b/app/soapbox/types/colors.ts @@ -6,5 +6,5 @@ export type TailwindColorObject = { }; export type TailwindColorPalette = { - [key: string]: TailwindColorObject, + [key: string]: TailwindColorObject | string, } diff --git a/app/soapbox/utils/__tests__/tailwind-test.js b/app/soapbox/utils/__tests__/tailwind-test.js new file mode 100644 index 000000000..7a791e408 --- /dev/null +++ b/app/soapbox/utils/__tests__/tailwind-test.js @@ -0,0 +1,189 @@ +import { Map as ImmutableMap } from 'immutable'; + +import { toTailwind, fromLegacyColors, expandPalette } from '../tailwind'; + +describe('toTailwind()', () => { + it('converts brandColor into a Tailwind color palette', () => { + const soapboxConfig = ImmutableMap({ brandColor: '#0482d8' }); + + const expected = { + primary: { + 50: '#f2f9fd', + 100: '#e6f3fb', + 200: '#c0e0f5', + 300: '#9bcdef', + 400: '#4fa8e4', + 500: '#0482d8', + 600: '#0475c2', + 700: '#0362a2', + 800: '#024e82', + 900: '#02406a', + }, + }; + + const result = toTailwind(soapboxConfig); + expect(result.toJS()).toMatchObject(expected); + }); + + it('prefers Tailwind colors object over legacy colors', () => { + const soapboxConfig = ImmutableMap({ + brandColor: '#0482d8', + colors: ImmutableMap({ + primary: ImmutableMap({ + 300: '#ff0000', + }), + }), + }); + + const expected = { + primary: { + 50: '#f2f9fd', + 100: '#e6f3fb', + 200: '#c0e0f5', + 300: '#ff0000', // <-- + 400: '#4fa8e4', + 500: '#0482d8', + 600: '#0475c2', + 700: '#0362a2', + 800: '#024e82', + 900: '#02406a', + }, + }; + + const result = toTailwind(soapboxConfig); + expect(result.toJS()).toMatchObject(expected); + }); +}); + +describe('fromLegacyColors()', () => { + it('converts only brandColor', () => { + const soapboxConfig = ImmutableMap({ brandColor: '#0482d8' }); + + const expected = { + primary: { + 50: '#f2f9fd', + 100: '#e6f3fb', + 200: '#c0e0f5', + 300: '#9bcdef', + 400: '#4fa8e4', + 500: '#0482d8', + 600: '#0475c2', + 700: '#0362a2', + 800: '#024e82', + 900: '#02406a', + }, + }; + + const result = fromLegacyColors(soapboxConfig); + expect(result).toEqual(expected); + }); + + it('converts both legacy colors', () => { + const soapboxConfig = ImmutableMap({ + brandColor: '#0482d8', + accentColor: '#2bd110', + }); + + const expected = { + primary: { + 50: '#f2f9fd', + 100: '#e6f3fb', + 200: '#c0e0f5', + 300: '#9bcdef', + 400: '#4fa8e4', + 500: '#0482d8', + 600: '#0475c2', + 700: '#0362a2', + 800: '#024e82', + 900: '#02406a', + }, + accent: { + 50: '#f4fdf3', + 100: '#eafae7', + 200: '#caf4c3', + 300: '#aaed9f', + 400: '#6bdf58', + 500: '#2bd110', + 600: '#27bc0e', + 700: '#209d0c', + 800: '#1a7d0a', + 900: '#156608', + }, + }; + + const result = fromLegacyColors(soapboxConfig); + expect(result).toEqual(expected); + }); +}); + +describe('expandPalette()', () => { + it('expands one color', () => { + const palette = { primary: '#0482d8' }; + + const expected = { + primary: { + 50: '#f2f9fd', + 100: '#e6f3fb', + 200: '#c0e0f5', + 300: '#9bcdef', + 400: '#4fa8e4', + 500: '#0482d8', + 600: '#0475c2', + 700: '#0362a2', + 800: '#024e82', + 900: '#02406a', + }, + }; + + const result = expandPalette(palette); + expect(result).toEqual(expected); + }); + + it('expands mixed palette', () => { + const palette = { + primary: { + 50: '#f2f9fd', + 100: '#e6f3fb', + 200: '#c0e0f5', + 300: '#9bcdef', + 400: '#4fa8e4', + 500: '#0482d8', + 600: '#0475c2', + 700: '#0362a2', + 800: '#024e82', + 900: '#02406a', + }, + accent: '#2bd110', + }; + + const expected = { + primary: { + 50: '#f2f9fd', + 100: '#e6f3fb', + 200: '#c0e0f5', + 300: '#9bcdef', + 400: '#4fa8e4', + 500: '#0482d8', + 600: '#0475c2', + 700: '#0362a2', + 800: '#024e82', + 900: '#02406a', + }, + accent: { + 50: '#f4fdf3', + 100: '#eafae7', + 200: '#caf4c3', + 300: '#aaed9f', + 400: '#6bdf58', + 500: '#2bd110', + 600: '#27bc0e', + 700: '#209d0c', + 800: '#1a7d0a', + 900: '#156608', + }, + }; + + const result = expandPalette(palette); + expect(result).toEqual(expected); + }); +}); diff --git a/app/soapbox/utils/tailwind.ts b/app/soapbox/utils/tailwind.ts new file mode 100644 index 000000000..798f1cfe9 --- /dev/null +++ b/app/soapbox/utils/tailwind.ts @@ -0,0 +1,44 @@ +import { Map as ImmutableMap, fromJS } from 'immutable'; + +import tintify from 'soapbox/utils/colors'; + +import type { TailwindColorPalette } from 'soapbox/types/colors'; + +type SoapboxConfig = ImmutableMap; +type SoapboxColors = ImmutableMap; + +/** Check if the value is a valid hex color */ +const isHex = (value: any): boolean => /^#([0-9A-F]{3}){1,2}$/i.test(value); + +/** Expand hex colors into tints */ +export const expandPalette = (palette: TailwindColorPalette): TailwindColorPalette => { + // Generate palette only for present colors + return Object.entries(palette).reduce((result: TailwindColorPalette, colorData) => { + const [colorName, color] = colorData; + + // Conditionally handle hex color and Tailwind color object + if (typeof color === 'string' && isHex(color)) { + result[colorName] = tintify(color); + } else if (typeof color === 'object') { + result[colorName] = color; + } + + return result; + }, {}); +}; + +/** Build a color object from legacy colors */ +export const fromLegacyColors = (soapboxConfig: SoapboxConfig): TailwindColorPalette => { + return expandPalette({ + primary: soapboxConfig.get('brandColor'), + accent: soapboxConfig.get('accentColor'), + }); +}; + +/** Convert Soapbox Config into Tailwind colors */ +export const toTailwind = (soapboxConfig: SoapboxConfig): SoapboxColors => { + const colors: SoapboxColors = ImmutableMap(soapboxConfig.get('colors')); + const legacyColors: SoapboxColors = ImmutableMap(fromJS(fromLegacyColors(soapboxConfig))); + + return legacyColors.mergeDeep(colors); +};