pl-fe: use adoptedStyleSheets and break a bunch of other stuff

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2025-06-29 19:27:54 +02:00
parent c85158f4ea
commit 8efed59d52
7 changed files with 93 additions and 25 deletions

View File

@ -0,0 +1,46 @@
import React, { useEffect, useRef } from 'react';
interface IInlineStyle {
children: string;
}
const InlineStyle: React.FC<IInlineStyle> = ({ children }) => {
// eslint-disable-next-line compat/compat
const sheet = useRef(document.adoptedStyleSheets ? new CSSStyleSheet() : document.createElement('style'));
useEffect(() => {
if (sheet.current) {
const stylesheet = sheet.current;
if (stylesheet instanceof CSSStyleSheet) {
stylesheet.replaceSync(children);
} else {
stylesheet.textContent = children;
}
}
}, [children]);
useEffect(() => {
const stylesheet = sheet.current;
if (stylesheet) {
if (stylesheet instanceof CSSStyleSheet) {
document.adoptedStyleSheets = [...document.adoptedStyleSheets, stylesheet];
} else {
document.head.appendChild(stylesheet);
}
}
return () => {
if (stylesheet) {
if (stylesheet instanceof CSSStyleSheet) {
document.adoptedStyleSheets = document.adoptedStyleSheets.filter(s => s !== stylesheet);
} else {
document.head.removeChild(stylesheet);
}
}
};
}, []);
return <></>;
};
export { InlineStyle as default };

View File

@ -2,10 +2,10 @@ import clsx from 'clsx';
import React from 'react';
import { FormattedMessage } from 'react-intl';
import InlineStyle from 'pl-fe/components/inline-style';
import BackgroundShapes from 'pl-fe/features/ui/components/background-shapes';
import { useSystemTheme } from 'pl-fe/hooks/use-system-theme';
import { useThemeCss } from 'pl-fe/hooks/use-theme-css';
import { useSettingsStore } from 'pl-fe/stores/settings';
import type { PlFeConfig } from 'pl-fe/normalizers/pl-fe/pl-fe-config';
@ -16,9 +16,7 @@ interface ISitePreview {
/** Renders a preview of the website's style with the configuration applied. */
const SitePreview: React.FC<ISitePreview> = ({ plFe }) => {
const { defaultSettings } = useSettingsStore();
const userTheme = defaultSettings.themeMode;
const userTheme = plFe.defaultSettings.themeMode;
const systemTheme = useSystemTheme();
const dark = ['dark', 'black'].includes(userTheme as string) || (userTheme === 'system' && systemTheme === 'black');
@ -32,13 +30,14 @@ const SitePreview: React.FC<ISitePreview> = ({ plFe }) => {
'h-40 overflow-hidden rounded-lg',
{
'bg-white': !dark,
'bg-gray-900': dark,
'bg-gray-900': dark && userTheme !== 'black',
'bg-black': userTheme === 'black',
});
return (
<div className={bodyClass}>
<style>{`.site-preview {${themeCss}}`}</style>
<BackgroundShapes position='absolute' />
<InlineStyle>{`.site-preview {${themeCss}}`}</InlineStyle>
<BackgroundShapes position='absolute' hidden={userTheme === 'black'} />
<div className='absolute z-[2] self-center overflow-hidden rounded-lg bg-accent-500 p-2 text-white'>
<FormattedMessage id='site_preview.preview' defaultMessage='Preview' />

View File

@ -4,11 +4,18 @@ import React from 'react';
interface IBackgroundShapes {
/** Whether the shapes should be absolute positioned or fixed. */
position?: 'fixed' | 'absolute';
/** Override visibility. */
hidden?: boolean;
}
/** Gradient that appears in the background of the UI. */
const BackgroundShapes: React.FC<IBackgroundShapes> = ({ position = 'fixed' }) => (
<div className={clsx(position, 'pointer-events-none inset-x-0 top-0 flex justify-center overflow-hidden black:hidden')}>
const BackgroundShapes: React.FC<IBackgroundShapes> = ({ position = 'fixed', hidden }) => (
<div
className={clsx(position, 'pointer-events-none inset-x-0 top-0 flex justify-center overflow-hidden ', {
'hidden': hidden,
'black:hidden': hidden === undefined,
})}
>
<div className='bg-gradient-sm lg:bg-gradient-light lg:dark:bg-gradient-dark h-screen w-screen' />
</div>
);

View File

@ -80,7 +80,7 @@ const useThemeCss = (overwriteConfig?: PlFeConfig) => {
} catch (_) {
return generateThemeCss({});
}
}, [demo, plFeConfig, theme]);
}, [overwriteConfig, demo, plFeConfig, theme]);
};
export { normalizeColors, useThemeCss };

View File

@ -1,6 +1,7 @@
import clsx from 'clsx';
import React, { useEffect } from 'react';
import InlineStyle from 'pl-fe/components/inline-style';
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';
@ -39,19 +40,23 @@ const PlFeHead = () => {
}, [dsn]);
return (
<Helmet>
<html
lang={locale}
className={clsx('h-full', `text-${themeSettings?.interfaceSize || 'md'}`, {
'dark': theme === 'dark',
'dark black': theme === 'black',
})}
// @ts-ignore
style={themeCss + (['dark', 'black'].includes(theme) ? 'color-scheme: dark;' : '')}
/>
<body className={bodyClass} dir={direction} />
<meta name='theme-color' content={plFeConfig.brandColor} />
</Helmet>
<>
<Helmet>
<html
lang={locale}
className={clsx('h-full', `text-${themeSettings?.interfaceSize || 'md'}`, {
'dark': theme === 'dark',
'dark black': theme === 'black',
})}
/>
<body className={bodyClass} dir={direction} />
<meta name='theme-color' content={plFeConfig.brandColor} />
</Helmet>
<InlineStyle>{`:root { ${themeCss} }`}</InlineStyle>
{['dark', 'black'].includes(theme) && (
<InlineStyle>{':root { color-scheme: dark; }'}</InlineStyle>
)}
</>
);
};

View File

@ -22,6 +22,7 @@ import Toggle from 'pl-fe/components/ui/toggle';
import CryptoAddressInput from 'pl-fe/features/pl-fe-config/components/crypto-address-input';
import FooterLinkInput from 'pl-fe/features/pl-fe-config/components/footer-link-input';
import PromoPanelInput from 'pl-fe/features/pl-fe-config/components/promo-panel-input';
import SitePreview from 'pl-fe/features/pl-fe-config/components/site-preview';
import ThemeSelector from 'pl-fe/features/ui/components/theme-selector';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
@ -179,7 +180,7 @@ const PlFeConfigEditor: React.FC = () => {
<Column label={intl.formatMessage(messages.heading)}>
<Form onSubmit={handleSubmit}>
<fieldset className='space-y-6' disabled={isLoading}>
{/* <SitePreview plFe={plFe} /> */}
<SitePreview plFe={plFe} />
<CardHeader>
<CardTitle title={<FormattedMessage id='plfe_config.headings.theme' defaultMessage='Theme' />} />

View File

@ -55,6 +55,7 @@ const ThemeEditorPage: React.FC<IThemeEditor> = () => {
const rawConfig = useAppSelector(state => state.plfe);
const [colors, setColors] = useState(normalizeColors(plFeConfig));
const [isDefault, setIsDefault] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [resetKey, setResetKey] = useState(crypto.randomUUID());
@ -63,6 +64,7 @@ const ThemeEditorPage: React.FC<IThemeEditor> = () => {
const updateColors = (key: string) => (newColors: ColorGroup) => {
if (typeof colors[key] === 'string') return;
setIsDefault(false);
setColors({
...colors,
[key]: {
@ -73,6 +75,7 @@ const ThemeEditorPage: React.FC<IThemeEditor> = () => {
};
const updateColor = (key: string) => (hex: string) => {
setIsDefault(false);
setColors({
...colors,
[key]: hex,
@ -81,6 +84,7 @@ const ThemeEditorPage: React.FC<IThemeEditor> = () => {
const setTheme = (theme: any) => {
setResetKey(crypto.randomUUID());
setIsDefault(false);
setTimeout(() => setColors(theme));
};
@ -89,13 +93,19 @@ const ThemeEditorPage: React.FC<IThemeEditor> = () => {
};
const updateTheme = async () => {
const params = { ...rawConfig, colors };
let params;
if (isDefault) {
params = { ...rawConfig, colors: undefined, brandColor: undefined, accentColor: undefined };
} else {
params = { ...rawConfig, colors };
}
await dispatch(updatePlFeConfig(params));
};
const restoreDefaultTheme = () => {
const config = v.parse(plFeConfigSchema, { brandColor: '#d80482' });
setTheme(normalizeColors(config));
setIsDefault(true);
};
const exportTheme = () => {