From a535b805461c46228fa72b4b29cfc272e131f955 Mon Sep 17 00:00:00 2001 From: mkljczk Date: Fri, 6 Dec 2024 10:43:11 +0100 Subject: [PATCH] pl-fe: use theme css hook Signed-off-by: mkljczk --- .../pl-fe-config/components/site-preview.tsx | 15 ++-- packages/pl-fe/src/hooks/use-theme-css.ts | 85 +++++++++++++++++++ packages/pl-fe/src/init/pl-fe-head.tsx | 8 +- .../src/normalizers/pl-fe/pl-fe-config.ts | 65 +------------- packages/pl-fe/src/utils/theme.ts | 4 +- 5 files changed, 101 insertions(+), 76 deletions(-) create mode 100644 packages/pl-fe/src/hooks/use-theme-css.ts diff --git a/packages/pl-fe/src/features/pl-fe-config/components/site-preview.tsx b/packages/pl-fe/src/features/pl-fe-config/components/site-preview.tsx index a3881e058..bb159e55e 100644 --- a/packages/pl-fe/src/features/pl-fe-config/components/site-preview.tsx +++ b/packages/pl-fe/src/features/pl-fe-config/components/site-preview.tsx @@ -1,22 +1,21 @@ import clsx from 'clsx'; -import React, { useMemo } from 'react'; +import React from 'react'; import { FormattedMessage } from 'react-intl'; -import * as v from 'valibot'; import BackgroundShapes from 'pl-fe/features/ui/components/background-shapes'; import { useSystemTheme } from 'pl-fe/hooks/use-system-theme'; -import { plFeConfigSchema } from 'pl-fe/normalizers/pl-fe/pl-fe-config'; +import { useThemeCss } from 'pl-fe/hooks/use-theme-css'; import { useSettingsStore } from 'pl-fe/stores/settings'; -import { generateThemeCss } from 'pl-fe/utils/theme'; + +import type { PlFeConfig } from 'pl-fe/normalizers/pl-fe/pl-fe-config'; interface ISitePreview { /** Raw pl-fe configuration. */ - plFe: any; + plFe: PlFeConfig; } /** Renders a preview of the website's style with the configuration applied. */ const SitePreview: React.FC = ({ plFe }) => { - const plFeConfig = useMemo(() => v.parse(plFeConfigSchema, plFe), [plFe]); const { defaultSettings } = useSettingsStore(); const userTheme = defaultSettings.themeMode; @@ -24,6 +23,8 @@ const SitePreview: React.FC = ({ plFe }) => { const dark = ['dark', 'black'].includes(userTheme as string) || (userTheme === 'system' && systemTheme === 'black'); + const themeCss = useThemeCss(plFe); + const bodyClass = clsx( 'site-preview', 'align-center relative flex justify-center text-base', @@ -36,7 +37,7 @@ const SitePreview: React.FC = ({ plFe }) => { return (
- +
diff --git a/packages/pl-fe/src/hooks/use-theme-css.ts b/packages/pl-fe/src/hooks/use-theme-css.ts new file mode 100644 index 000000000..a811e61e7 --- /dev/null +++ b/packages/pl-fe/src/hooks/use-theme-css.ts @@ -0,0 +1,85 @@ +import { useMemo } from 'react'; + +import { toTailwind } from 'pl-fe/utils/tailwind'; +import { generateAccent, generateThemeCss } from 'pl-fe/utils/theme'; + +import { usePlFeConfig } from './use-pl-fe-config'; +import { useSettings } from './use-settings'; + +import type { PlFeConfig } from 'pl-fe/normalizers/pl-fe/pl-fe-config'; + +const DEFAULT_COLORS = { + success: { + 50: '#f0fdf4', + 100: '#dcfce7', + 200: '#bbf7d0', + 300: '#86efac', + 400: '#4ade80', + 500: '#22c55e', + 600: '#16a34a', + 700: '#15803d', + 800: '#166534', + 900: '#14532d', + }, + danger: { + 50: '#fef2f2', + 100: '#fee2e2', + 200: '#fecaca', + 300: '#fca5a5', + 400: '#f87171', + 500: '#ef4444', + 600: '#dc2626', + 700: '#b91c1c', + 800: '#991b1b', + 900: '#7f1d1d', + }, + 'greentext': '#789922', +}; + +const normalizeColors = (theme: Partial>) => { + const brandColor: string = theme.brandColor || theme.colors?.primary?.['500'] || '#d80482'; + const accentColor: string = theme.accentColor || theme.colors?.accent?.['500'] || '' || generateAccent(brandColor); + + const colors = { + ...theme.colors, + ...Object.fromEntries(Object.entries(DEFAULT_COLORS).map(([key, value]) => [key, typeof value === 'string' ? value : { ...value, ...theme.colors?.[key] }])), + }; + + const normalizedColors = toTailwind({ + brandColor, + accentColor, + colors, + }); + + return { + // @ts-ignore + 'gradient-start': normalizedColors.primary?.['500'], + // @ts-ignore + 'gradient-end': normalizedColors.accent?.['500'], + // @ts-ignore + 'accent-blue': normalizedColors.primary?.['600'], + ...normalizedColors, + } as typeof normalizedColors; +}; + +const useThemeCss = (overwriteConfig?: PlFeConfig) => { + const { demo, theme } = useSettings(); + const plFeConfig = usePlFeConfig(); + + return useMemo(() => { + try { + let baseTheme: Partial; + if (overwriteConfig) baseTheme = overwriteConfig; + else if (demo) baseTheme = {}; + else baseTheme = theme || plFeConfig; + + const colors = normalizeColors(baseTheme); + + return generateThemeCss(colors); + } catch (_) { + return generateThemeCss({}); + } + }, [demo, plFeConfig, theme]); +}; + +export { useThemeCss }; diff --git a/packages/pl-fe/src/init/pl-fe-head.tsx b/packages/pl-fe/src/init/pl-fe-head.tsx index 91a365d27..d0676f930 100644 --- a/packages/pl-fe/src/init/pl-fe-head.tsx +++ b/packages/pl-fe/src/init/pl-fe-head.tsx @@ -1,15 +1,13 @@ import clsx from 'clsx'; import React, { useEffect } from 'react'; -import * as v from 'valibot'; import { useLocale, useLocaleDirection } from 'pl-fe/hooks/use-locale'; import { usePlFeConfig } from 'pl-fe/hooks/use-pl-fe-config'; import { useSettings } from 'pl-fe/hooks/use-settings'; import { useTheme } from 'pl-fe/hooks/use-theme'; -import { plFeConfigSchema } from 'pl-fe/normalizers/pl-fe/pl-fe-config'; +import { useThemeCss } from 'pl-fe/hooks/use-theme-css'; import { startSentry } from 'pl-fe/sentry'; import { useModalsStore } from 'pl-fe/stores/modals'; -import { generateThemeCss } from 'pl-fe/utils/theme'; const Helmet = React.lazy(() => import('pl-fe/components/helmet')); @@ -17,13 +15,13 @@ const Helmet = React.lazy(() => import('pl-fe/components/helmet')); const PlFeHead = () => { const locale = useLocale(); const direction = useLocaleDirection(locale); - const { demo, reduceMotion, underlineLinks, demetricator, systemFont } = useSettings(); + const { reduceMotion, underlineLinks, demetricator, systemFont } = useSettings(); const plFeConfig = usePlFeConfig(); const theme = useTheme(); const withModals = useModalsStore().modals.length > 0; - const themeCss = generateThemeCss(demo ? v.parse(plFeConfigSchema, { brandColor: '#d80482' }) : plFeConfig); + const themeCss = useThemeCss(); const dsn = plFeConfig.sentryDsn; const bodyClass = clsx('h-full bg-white text-base antialiased black:bg-black dark:bg-gray-800', { diff --git a/packages/pl-fe/src/normalizers/pl-fe/pl-fe-config.ts b/packages/pl-fe/src/normalizers/pl-fe/pl-fe-config.ts index 0cec38d38..b59abfe2d 100644 --- a/packages/pl-fe/src/normalizers/pl-fe/pl-fe-config.ts +++ b/packages/pl-fe/src/normalizers/pl-fe/pl-fe-config.ts @@ -2,36 +2,6 @@ import trimStart from 'lodash/trimStart'; import * as v from 'valibot'; import { coerceObject, filteredArray } from 'pl-fe/schemas/utils'; -import { toTailwind } from 'pl-fe/utils/tailwind'; -import { generateAccent } from 'pl-fe/utils/theme'; - -const DEFAULT_COLORS = { - success: { - 50: '#f0fdf4', - 100: '#dcfce7', - 200: '#bbf7d0', - 300: '#86efac', - 400: '#4ade80', - 500: '#22c55e', - 600: '#16a34a', - 700: '#15803d', - 800: '#166534', - 900: '#14532d', - }, - danger: { - 50: '#fef2f2', - 100: '#fee2e2', - 200: '#fecaca', - 300: '#fca5a5', - 400: '#f87171', - 500: '#ef4444', - 600: '#dc2626', - 700: '#b91c1c', - 800: '#991b1b', - 900: '#7f1d1d', - }, - 'greentext': '#789922', -}; const promoPanelItemSchema = coerceObject({ icon: v.fallback(v.string(), ''), @@ -67,11 +37,11 @@ const cryptoAddressSchema = v.pipe(coerceObject({ type CryptoAddress = v.InferOutput; -const plFeConfigSchema = v.pipe(coerceObject({ +const plFeConfigSchema = coerceObject({ appleAppId: v.fallback(v.nullable(v.string()), null), logo: v.fallback(v.string(), ''), logoDarkMode: v.fallback(v.nullable(v.string()), null), - brandColor: v.fallback(v.string(), '#d80482'), + brandColor: v.fallback(v.string(), ''), accentColor: v.fallback(v.string(), ''), colors: v.any(), copyright: v.fallback(v.string(), `♥${new Date().getFullYear()}. Copying is an act of love. Please copy and share.`), @@ -122,36 +92,7 @@ const plFeConfigSchema = v.pipe(coerceObject({ */ mediaPreview: v.fallback(v.boolean(), false), sentryDsn: v.fallback(v.optional(v.string()), undefined), -}), v.transform((config) => { - const brandColor: string = config.brandColor || config.colors?.primary?.['500'] || ''; - const accentColor: string = config.accentColor || config.colors?.accent?.['500'] || '' || generateAccent(brandColor); - - const colors = { - ...config.colors, - ...Object.fromEntries(Object.entries(DEFAULT_COLORS).map(([key, value]) => [key, typeof value === 'string' ? value : { ...value, ...config.colors?.[key] }])), - }; - - const normalizedColors = toTailwind({ - brandColor, - accentColor, - colors, - }); - - return { - ...config, - brandColor, - accentColor, - colors: { - // @ts-ignore - 'gradient-start': normalizedColors.primary?.['500'], - // @ts-ignore - 'gradient-end': normalizedColors.accent?.['500'], - // @ts-ignore - 'accent-blue': normalizedColors.primary?.['600'], - ...normalizedColors, - } as typeof normalizedColors, - }; -})); +}); type PlFeConfig = v.InferOutput; diff --git a/packages/pl-fe/src/utils/theme.ts b/packages/pl-fe/src/utils/theme.ts index 1f04713ab..b57dcb774 100644 --- a/packages/pl-fe/src/utils/theme.ts +++ b/packages/pl-fe/src/utils/theme.ts @@ -110,8 +110,8 @@ const colorsToCss = (colors: TailwindColorPalette): string => { return Object.keys(parsed).reduce((css, variable) => css + `${variable}:${parsed[variable]};`, ''); }; -const generateThemeCss = (plFeConfig: PlFeConfig): string => - colorsToCss(plFeConfig.colors); +const generateThemeCss = (colors: TailwindColorPalette): string => + colorsToCss(colors); const hexToHsl = (hex: string): Hsl | null => { const rgb = hexToRgb(hex);