feat(events): Sync from Branding button in gallery theme customizer + clarified default inheritance
This commit is contained in:
@@ -60,6 +60,12 @@ interface ThemeCustomizerEnhancedProps {
|
|||||||
// props are omitted (e.g. event-level theme editor), the section is hidden.
|
// props are omitted (e.g. event-level theme editor), the section is hidden.
|
||||||
forceColorMode?: 'dark' | 'light' | null;
|
forceColorMode?: 'dark' | 'light' | null;
|
||||||
onForceColorModeChange?: (mode: 'dark' | 'light' | null) => void;
|
onForceColorModeChange?: (mode: 'dark' | 'light' | null) => void;
|
||||||
|
// Sync palette from Branding. When provided, a small button appears in
|
||||||
|
// the colour-pickers section header. The caller resolves the active
|
||||||
|
// Branding theme and fires onChange with the merged 8-token values —
|
||||||
|
// only the colour tokens swap, layout/header/typography stay put so an
|
||||||
|
// admin who's already arranged the structure can pull just the palette.
|
||||||
|
onSyncFromBranding?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -167,7 +173,8 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
|||||||
cssTemplateId,
|
cssTemplateId,
|
||||||
onCssTemplateChange,
|
onCssTemplateChange,
|
||||||
forceColorMode,
|
forceColorMode,
|
||||||
onForceColorModeChange
|
onForceColorModeChange,
|
||||||
|
onSyncFromBranding
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [localTheme, setLocalTheme] = useState<ThemeConfig>(value);
|
const [localTheme, setLocalTheme] = useState<ThemeConfig>(value);
|
||||||
@@ -878,10 +885,26 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
|||||||
|
|
||||||
{/* Color Customization */}
|
{/* Color Customization */}
|
||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<h3 className="text-lg font-semibold text-neutral-900 dark:text-neutral-100 mb-4 flex items-center gap-2">
|
<div className="flex items-center justify-between gap-2 mb-4">
|
||||||
<Palette className="w-5 h-5" />
|
<h3 className="text-lg font-semibold text-neutral-900 dark:text-neutral-100 flex items-center gap-2">
|
||||||
{t('branding.colors')}
|
<Palette className="w-5 h-5" />
|
||||||
</h3>
|
{t('branding.colors')}
|
||||||
|
</h3>
|
||||||
|
{/* "Sync from Branding" — caller-supplied so the customizer
|
||||||
|
doesn't have to know how to resolve the Branding theme.
|
||||||
|
Used in event create/edit to reset palette to site colours. */}
|
||||||
|
{onSyncFromBranding && (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
leftIcon={<RotateCcw className="w-4 h-4" />}
|
||||||
|
onClick={onSyncFromBranding}
|
||||||
|
>
|
||||||
|
{t('branding.syncFromBranding', 'Sync from Branding')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Color Mode Selector */}
|
{/* Color Mode Selector */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
|
|||||||
@@ -242,6 +242,9 @@ export const CreateEventPage: React.FC = () => {
|
|||||||
|
|
||||||
// Apply the global Branding default theme on first load so admins who set a
|
// Apply the global Branding default theme on first load so admins who set a
|
||||||
// site-wide default in Branding actually see it on new events (#323).
|
// site-wide default in Branding actually see it on new events (#323).
|
||||||
|
// This is the "always inherit colours from Branding" guarantee — every new
|
||||||
|
// gallery starts with the site palette unless the admin then picks a preset
|
||||||
|
// or hits Sync from Branding inside the customizer to re-pull it later.
|
||||||
const brandingThemeApplied = useRef(false);
|
const brandingThemeApplied = useRef(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (brandingThemeApplied.current) return;
|
if (brandingThemeApplied.current) return;
|
||||||
@@ -597,6 +600,36 @@ export const CreateEventPage: React.FC = () => {
|
|||||||
onPresetChange={handlePresetChange}
|
onPresetChange={handlePresetChange}
|
||||||
showGalleryLayouts={true}
|
showGalleryLayouts={true}
|
||||||
hideActions={true}
|
hideActions={true}
|
||||||
|
onSyncFromBranding={() => {
|
||||||
|
// Pull the 8 colour tokens (+ legacy primary alias) from
|
||||||
|
// the global Branding theme into the current event theme.
|
||||||
|
// Layout / header / typography are kept untouched so an
|
||||||
|
// admin who has already arranged structure can refresh
|
||||||
|
// just the palette.
|
||||||
|
const branding = settings?.theme_config as ThemeConfig | undefined;
|
||||||
|
if (!branding) {
|
||||||
|
toast.error(t('toast.brandingThemeMissing', 'No branding theme has been saved yet.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
theme_preset: 'custom',
|
||||||
|
theme_config: {
|
||||||
|
...prev.theme_config,
|
||||||
|
primaryColor: branding.primaryColor,
|
||||||
|
accentColor: branding.accentColor,
|
||||||
|
accentDarkColor: branding.accentDarkColor,
|
||||||
|
backgroundColor: branding.backgroundColor,
|
||||||
|
surfaceColor: branding.surfaceColor,
|
||||||
|
elevatedColor: branding.elevatedColor,
|
||||||
|
surfaceBorderColor: branding.surfaceBorderColor,
|
||||||
|
textColor: branding.textColor,
|
||||||
|
mutedTextColor: branding.mutedTextColor,
|
||||||
|
colorMode: branding.colorMode ?? prev.theme_config.colorMode,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
toast.success(t('toast.brandingPaletteSynced', 'Palette synced from Branding.'));
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Gallery Preview */}
|
{/* Gallery Preview */}
|
||||||
|
|||||||
@@ -2095,6 +2095,34 @@ export const EventDetailsPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
onSyncFromBranding={() => {
|
||||||
|
// Reset only the 8 colour tokens to the site Branding —
|
||||||
|
// layout, header, typography all stay so the admin doesn't
|
||||||
|
// lose tweaks made for this specific event.
|
||||||
|
const branding = publicSettings?.theme_config as ThemeConfig | undefined;
|
||||||
|
if (!branding) {
|
||||||
|
toast.error(t('toast.brandingThemeMissing', 'No branding theme has been saved yet.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const base = currentTheme || GALLERY_THEME_PRESETS.default.config;
|
||||||
|
const merged: ThemeConfig = {
|
||||||
|
...base,
|
||||||
|
primaryColor: branding.primaryColor,
|
||||||
|
accentColor: branding.accentColor,
|
||||||
|
accentDarkColor: branding.accentDarkColor,
|
||||||
|
backgroundColor: branding.backgroundColor,
|
||||||
|
surfaceColor: branding.surfaceColor,
|
||||||
|
elevatedColor: branding.elevatedColor,
|
||||||
|
surfaceBorderColor: branding.surfaceBorderColor,
|
||||||
|
textColor: branding.textColor,
|
||||||
|
mutedTextColor: branding.mutedTextColor,
|
||||||
|
colorMode: branding.colorMode ?? base.colorMode,
|
||||||
|
};
|
||||||
|
setCurrentTheme(merged);
|
||||||
|
setCurrentPresetName('custom');
|
||||||
|
setEditForm(prev => ({ ...prev, color_theme: JSON.stringify(merged) }));
|
||||||
|
toast.success(t('toast.brandingPaletteSynced', 'Palette synced from Branding.'));
|
||||||
|
}}
|
||||||
isPreviewMode={true}
|
isPreviewMode={true}
|
||||||
showGalleryLayouts={true}
|
showGalleryLayouts={true}
|
||||||
hideActions={true}
|
hideActions={true}
|
||||||
|
|||||||
Reference in New Issue
Block a user