@ -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<ISitePreview> = ({ plFe }) => {
|
||||
const plFeConfig = useMemo(() => v.parse(plFeConfigSchema, plFe), [plFe]);
|
||||
const { defaultSettings } = useSettingsStore();
|
||||
|
||||
const userTheme = defaultSettings.themeMode;
|
||||
@ -24,6 +23,8 @@ const SitePreview: React.FC<ISitePreview> = ({ 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<ISitePreview> = ({ plFe }) => {
|
||||
|
||||
return (
|
||||
<div className={bodyClass}>
|
||||
<style>{`.site-preview {${generateThemeCss(plFeConfig)}}`}</style>
|
||||
<style>{`.site-preview {${themeCss}}`}</style>
|
||||
<BackgroundShapes position='absolute' />
|
||||
|
||||
<div className='absolute z-[2] self-center overflow-hidden rounded-lg bg-accent-500 p-2 text-white'>
|
||||
|
||||
85
packages/pl-fe/src/hooks/use-theme-css.ts
Normal file
85
packages/pl-fe/src/hooks/use-theme-css.ts
Normal file
@ -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<Pick<PlFeConfig, 'brandColor' | 'accentColor' | 'colors'>>) => {
|
||||
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<PlFeConfig>;
|
||||
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 };
|
||||
@ -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', {
|
||||
|
||||
@ -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<typeof cryptoAddressSchema>;
|
||||
|
||||
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<typeof plFeConfigSchema>;
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
Reference in New Issue
Block a user