diff --git a/app/soapbox/actions/soapbox.js b/app/soapbox/actions/soapbox.js index ac8749221..2871e2aae 100644 --- a/app/soapbox/actions/soapbox.js +++ b/app/soapbox/actions/soapbox.js @@ -41,7 +41,47 @@ export const makeDefaultConfig = features => { banner: '', brandColor: '', // Empty accentColor: '', - colors: ImmutableMap(), + colors: ImmutableMap({ + gray: ImmutableMap({ + 50: '#f9fafb', + 100: '#f3f4f6', + 200: '#e5e7eb', + 300: '#d1d5db', + 400: '#9ca3af', + 500: '#6b7280', + 600: '#4b5563', + 700: '#374151', + 800: '#1f2937', + 900: '#111827', + }), + success: ImmutableMap({ + 50: '#f0fdf4', + 100: '#dcfce7', + 200: '#bbf7d0', + 300: '#86efac', + 400: '#4ade80', + 500: '#22c55e', + 600: '#16a34a', + 700: '#15803d', + 800: '#166534', + 900: '#14532d', + }), + danger: ImmutableMap({ + 50: '#fef2f2', + 100: '#fee2e2', + 200: '#fecaca', + 300: '#fca5a5', + 400: '#f87171', + 500: '#ef4444', + 600: '#dc2626', + 700: '#b91c1c', + 800: '#991b1b', + 900: '#7f1d1d', + }), + 'gradient-purple': '#b8a3f9', + 'gradient-blue': '#9bd5ff', + 'sea-blue': '#2feecc', + }), customCss: ImmutableList(), promoPanel: ImmutableMap({ items: ImmutableList(), @@ -73,7 +113,8 @@ export const getSoapboxConfig = createSelector([ state => state.get('soapbox'), state => getFeatures(state.get('instance')), ], (soapbox, features) => { - return makeDefaultConfig(features).merge(soapbox); + const defaultConfig = makeDefaultConfig(features); + return soapbox.mergeDeepWith((o, n) => o || n, defaultConfig); }); export function rememberSoapboxConfig(host) { diff --git a/app/soapbox/containers/soapbox.js b/app/soapbox/containers/soapbox.js index 3571a687d..4dcfdafb7 100644 --- a/app/soapbox/containers/soapbox.js +++ b/app/soapbox/containers/soapbox.js @@ -25,7 +25,7 @@ import { createGlobals } from 'soapbox/globals'; import messages from 'soapbox/locales/messages'; import { makeGetAccount } from 'soapbox/selectors'; import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types'; -import { themeColorsToCSS } from 'soapbox/utils/theme'; +import { generateThemeCss } from 'soapbox/utils/theme'; import { INTRODUCTION_VERSION } from '../actions/onboarding'; import { preload } from '../actions/preload'; @@ -84,7 +84,7 @@ const mapStateToProps = (state) => { dyslexicFont: settings.get('dyslexicFont'), demetricator: settings.get('demetricator'), locale: validLocale(locale) ? locale : 'en', - themeCss: themeColorsToCSS(soapboxConfig.get('brandColor') || '#0482d8', soapboxConfig.get('accentColor', '')), + themeCss: generateThemeCss(soapboxConfig), brandColor: soapboxConfig.get('brandColor'), themeMode: settings.get('themeMode'), singleUserMode, diff --git a/app/soapbox/features/soapbox_config/components/site_preview.js b/app/soapbox/features/soapbox_config/components/site_preview.js index 47a4c9ae1..b4506dd81 100644 --- a/app/soapbox/features/soapbox_config/components/site_preview.js +++ b/app/soapbox/features/soapbox_config/components/site_preview.js @@ -3,7 +3,7 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { defaultSettings } from 'soapbox/actions/settings'; -import { themeColorsToCSS } from 'soapbox/utils/theme'; +import { generateThemeCss } from 'soapbox/utils/theme'; export default function SitePreview({ soapbox }) { @@ -18,7 +18,7 @@ export default function SitePreview({ soapbox }) { return (
- +
diff --git a/app/soapbox/utils/__tests__/tailwind-test.js b/app/soapbox/utils/__tests__/tailwind-test.js index 7a791e408..93f928b73 100644 --- a/app/soapbox/utils/__tests__/tailwind-test.js +++ b/app/soapbox/utils/__tests__/tailwind-test.js @@ -3,21 +3,31 @@ import { Map as ImmutableMap } from 'immutable'; import { toTailwind, fromLegacyColors, expandPalette } from '../tailwind'; describe('toTailwind()', () => { + it('handles empty Soapbox config', () => { + const soapboxConfig = ImmutableMap(); + const result = toTailwind(soapboxConfig); + const expected = ImmutableMap({ colors: ImmutableMap() }); + expect(result).toEqual(expected); + }); + 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', + brandColor: '#0482d8', + colors: { + primary: { + 50: '#f2f9fd', + 100: '#e6f3fb', + 200: '#c0e0f5', + 300: '#9bcdef', + 400: '#4fa8e4', + 500: '#0482d8', + 600: '#0475c2', + 700: '#0362a2', + 800: '#024e82', + 900: '#02406a', + }, }, }; @@ -36,17 +46,20 @@ describe('toTailwind()', () => { }); const expected = { - primary: { - 50: '#f2f9fd', - 100: '#e6f3fb', - 200: '#c0e0f5', - 300: '#ff0000', // <-- - 400: '#4fa8e4', - 500: '#0482d8', - 600: '#0475c2', - 700: '#0362a2', - 800: '#024e82', - 900: '#02406a', + brandColor: '#0482d8', + colors: { + primary: { + 50: '#f2f9fd', + 100: '#e6f3fb', + 200: '#c0e0f5', + 300: '#ff0000', // <-- + 400: '#4fa8e4', + 500: '#0482d8', + 600: '#0475c2', + 700: '#0362a2', + 800: '#024e82', + 900: '#02406a', + }, }, }; @@ -72,6 +85,19 @@ describe('fromLegacyColors()', () => { 800: '#024e82', 900: '#02406a', }, + // Accent color is generated from brandColor + accent: { + 50: '#f3fbfd', + 100: '#e7f7fa', + 200: '#c3ecf4', + 300: '#9fe1ed', + 400: '#58cadf', + 500: '#10b3d1', + 600: '#0ea1bc', + 700: '#0c869d', + 800: '#0a6b7d', + 900: '#085866', + }, }; const result = fromLegacyColors(soapboxConfig); diff --git a/app/soapbox/utils/tailwind.ts b/app/soapbox/utils/tailwind.ts index 798f1cfe9..af55da8d6 100644 --- a/app/soapbox/utils/tailwind.ts +++ b/app/soapbox/utils/tailwind.ts @@ -1,6 +1,7 @@ import { Map as ImmutableMap, fromJS } from 'immutable'; import tintify from 'soapbox/utils/colors'; +import { generateAccent } from 'soapbox/utils/theme'; import type { TailwindColorPalette } from 'soapbox/types/colors'; @@ -19,7 +20,7 @@ export const expandPalette = (palette: TailwindColorPalette): TailwindColorPalet // Conditionally handle hex color and Tailwind color object if (typeof color === 'string' && isHex(color)) { result[colorName] = tintify(color); - } else if (typeof color === 'object') { + } else if (color && typeof color === 'object') { result[colorName] = color; } @@ -27,18 +28,26 @@ export const expandPalette = (palette: TailwindColorPalette): TailwindColorPalet }, {}); }; +// Generate accent color only if brandColor is present +const maybeGenerateAccentColor = (brandColor: any): string | null => { + return isHex(brandColor) ? generateAccent(brandColor) : null; +}; + /** Build a color object from legacy colors */ export const fromLegacyColors = (soapboxConfig: SoapboxConfig): TailwindColorPalette => { + const brandColor = soapboxConfig.get('brandColor'); + const accentColor = soapboxConfig.get('accentColor'); + return expandPalette({ - primary: soapboxConfig.get('brandColor'), - accent: soapboxConfig.get('accentColor'), + primary: isHex(brandColor) ? brandColor : null, + accent: isHex(accentColor) ? accentColor : maybeGenerateAccentColor(brandColor), }); }; /** Convert Soapbox Config into Tailwind colors */ -export const toTailwind = (soapboxConfig: SoapboxConfig): SoapboxColors => { +export const toTailwind = (soapboxConfig: SoapboxConfig): SoapboxConfig => { const colors: SoapboxColors = ImmutableMap(soapboxConfig.get('colors')); const legacyColors: SoapboxColors = ImmutableMap(fromJS(fromLegacyColors(soapboxConfig))); - return legacyColors.mergeDeep(colors); + return soapboxConfig.set('colors', legacyColors.mergeDeep(colors)); }; diff --git a/app/soapbox/utils/theme.ts b/app/soapbox/utils/theme.ts index b12be1abb..d6225378e 100644 --- a/app/soapbox/utils/theme.ts +++ b/app/soapbox/utils/theme.ts @@ -1,6 +1,8 @@ -import tintify, { hexToRgb } from './colors'; +import { hexToRgb } from './colors'; +import { toTailwind } from './tailwind'; -import type { Rgb, Hsl, TailwindColorPalette } from 'soapbox/types/colors'; +import type { Map as ImmutableMap } from 'immutable'; +import type { Rgb, Hsl, TailwindColorPalette, TailwindColorObject } from 'soapbox/types/colors'; // Taken from chromatism.js // https://github.com/graypegg/chromatism/blob/master/src/conversions/rgb.js @@ -59,9 +61,7 @@ function hslToHex(color: Hsl): string { } // Generate accent color from brand color -const generateAccent = (brandColor: string): string => { - console.log(brandColor); - console.log(hexToRgb(brandColor)); +export const generateAccent = (brandColor: string): string => { const { h } = rgbToHsl(hexToRgb(brandColor)); return hslToHex({ h: h - 15, s: 86, l: 44 }); }; @@ -81,7 +81,7 @@ const parseShades = (obj: Record, color: string, shades: Record { return Object.keys(colors).reduce((obj, color) => { - parseShades(obj, color, colors[color]); + parseShades(obj, color, colors[color] as TailwindColorObject); return obj; }, {}); }; @@ -93,27 +93,6 @@ export const colorsToCss = (colors: TailwindColorPalette): string => { }, ''); }; -const legacyColorsToTailwind = (brandColor: string, accentColor: string): TailwindColorPalette => { - return { - primary: tintify(brandColor), - accent: tintify(accentColor ? accentColor : generateAccent(brandColor)), - gray: { - 50: '#f9fafb', - 100: '#f3f4f6', - 200: '#e5e7eb', - 300: '#d1d5db', - 400: '#9ca3af', - 500: '#6b7280', - 600: '#4b5563', - 700: '#374151', - 800: '#1f2937', - 900: '#111827', - }, - 'sea-blue': '#2feecc', - }; -}; - -export const themeColorsToCSS = (brandColor: string, accentColor: string): string => { - const colors = legacyColorsToTailwind(brandColor, accentColor); - return colorsToCss(colors); +export const generateThemeCss = (soapboxConfig: ImmutableMap): string => { + return colorsToCss(toTailwind(soapboxConfig).get('colors').toJS() as TailwindColorPalette); };