Fix mobile responsiveness and implement enhanced theme system
- Fixed mobile gallery login box sizing and layout - Fixed header button layout for mobile screens - Fixed duplicate logo issue on logout - Fixed '0' rendering when upload button is hidden - Fixed horizontal scrolling on small screens - Implemented comprehensive theme system with gallery layouts - Added 6 different gallery layouts: Grid, Masonry, Carousel, Timeline, Hero, Mosaic - Created enhanced theme customizer with layout selection - Added theme presets for different event types - Updated event creation with theme preview and customization - Fixed all TypeScript compilation errors - Added missing translation keys for create event page - Added translations for theme customization features 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,78 +1,6 @@
|
||||
import React, { createContext, useContext, useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface ThemeConfig {
|
||||
primaryColor?: string;
|
||||
accentColor?: string;
|
||||
backgroundColor?: string;
|
||||
textColor?: string;
|
||||
fontFamily?: string;
|
||||
borderRadius?: 'none' | 'sm' | 'md' | 'lg';
|
||||
logoUrl?: string;
|
||||
customCss?: string;
|
||||
}
|
||||
|
||||
export interface EventTheme {
|
||||
name: string;
|
||||
config: ThemeConfig;
|
||||
}
|
||||
|
||||
// Predefined themes
|
||||
export const PRESET_THEMES: Record<string, EventTheme> = {
|
||||
default: {
|
||||
name: 'Default',
|
||||
config: {
|
||||
primaryColor: '#5C8762',
|
||||
accentColor: '#22c55e',
|
||||
backgroundColor: '#fafafa',
|
||||
textColor: '#171717',
|
||||
borderRadius: 'md',
|
||||
}
|
||||
},
|
||||
wedding: {
|
||||
name: 'Wedding',
|
||||
config: {
|
||||
primaryColor: '#c9a961',
|
||||
accentColor: '#e6ddd4',
|
||||
backgroundColor: '#fdfcfb',
|
||||
textColor: '#3f3f3f',
|
||||
borderRadius: 'lg',
|
||||
fontFamily: 'Georgia, serif',
|
||||
}
|
||||
},
|
||||
birthday: {
|
||||
name: 'Birthday',
|
||||
config: {
|
||||
primaryColor: '#ec4899',
|
||||
accentColor: '#fbbf24',
|
||||
backgroundColor: '#fef3c7',
|
||||
textColor: '#451a03',
|
||||
borderRadius: 'lg',
|
||||
}
|
||||
},
|
||||
corporate: {
|
||||
name: 'Corporate',
|
||||
config: {
|
||||
primaryColor: '#3b82f6',
|
||||
accentColor: '#1e40af',
|
||||
backgroundColor: '#f8fafc',
|
||||
textColor: '#0f172a',
|
||||
borderRadius: 'sm',
|
||||
fontFamily: 'Inter, sans-serif',
|
||||
}
|
||||
},
|
||||
minimal: {
|
||||
name: 'Minimal',
|
||||
config: {
|
||||
primaryColor: '#000000',
|
||||
accentColor: '#666666',
|
||||
backgroundColor: '#ffffff',
|
||||
textColor: '#000000',
|
||||
borderRadius: 'none',
|
||||
fontFamily: 'Helvetica, Arial, sans-serif',
|
||||
}
|
||||
}
|
||||
};
|
||||
import { ThemeConfig, EventTheme, GALLERY_THEME_PRESETS } from '../types/theme.types';
|
||||
|
||||
interface ThemeContextType {
|
||||
theme: ThemeConfig;
|
||||
@@ -101,7 +29,7 @@ interface ThemeProviderProps {
|
||||
|
||||
export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
||||
children,
|
||||
initialTheme = PRESET_THEMES.default.config,
|
||||
initialTheme = GALLERY_THEME_PRESETS.default.config,
|
||||
initialThemeName = 'default'
|
||||
}) => {
|
||||
const [theme, setTheme] = useState<ThemeConfig>(initialTheme);
|
||||
@@ -134,6 +62,10 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
||||
root.style.setProperty('--font-family', themeConfig.fontFamily);
|
||||
}
|
||||
|
||||
if (themeConfig.headingFontFamily) {
|
||||
root.style.setProperty('--heading-font-family', themeConfig.headingFontFamily);
|
||||
}
|
||||
|
||||
if (themeConfig.borderRadius) {
|
||||
const radiusMap = {
|
||||
none: '0',
|
||||
@@ -144,6 +76,41 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
||||
root.style.setProperty('--border-radius', radiusMap[themeConfig.borderRadius]);
|
||||
}
|
||||
|
||||
// Apply font size
|
||||
if (themeConfig.fontSize) {
|
||||
const sizeMap = {
|
||||
small: '14px',
|
||||
normal: '16px',
|
||||
large: '18px',
|
||||
};
|
||||
root.style.setProperty('--font-size-base', sizeMap[themeConfig.fontSize]);
|
||||
}
|
||||
|
||||
// Apply shadow style
|
||||
if (themeConfig.shadowStyle) {
|
||||
const shadowMap = {
|
||||
none: 'none',
|
||||
subtle: '0 1px 3px rgba(0,0,0,0.12)',
|
||||
normal: '0 4px 6px rgba(0,0,0,0.1)',
|
||||
dramatic: '0 10px 25px rgba(0,0,0,0.15)',
|
||||
};
|
||||
root.style.setProperty('--shadow-default', shadowMap[themeConfig.shadowStyle]);
|
||||
}
|
||||
|
||||
// Apply background pattern
|
||||
if (themeConfig.backgroundPattern && themeConfig.backgroundPattern !== 'none') {
|
||||
const patternMap = {
|
||||
dots: `radial-gradient(circle, ${themeConfig.textColor}20 1px, transparent 1px)`,
|
||||
grid: `linear-gradient(${themeConfig.textColor}10 1px, transparent 1px), linear-gradient(90deg, ${themeConfig.textColor}10 1px, transparent 1px)`,
|
||||
waves: `url("data:image/svg+xml,%3Csvg width='100' height='20' viewBox='0 0 100 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M21.184 20c.357-.13.72-.264 1.088-.402l1.768-.661C33.64 15.347 39.647 14 50 14c10.271 0 15.362 1.222 24.629 4.928.955.383 1.869.74 2.75 1.072h6.225c-2.51-.73-5.139-1.691-8.233-2.928C65.888 13.278 60.562 12 50 12c-10.626 0-16.855 1.397-26.66 5.063l-1.767.662c-2.475.923-4.66 1.674-6.724 2.275h6.335zm0-20C13.258 2.892 8.077 4 0 4V2c5.744 0 9.951-.574 14.85-2h6.334zM77.38 0C85.239 2.966 90.502 4 100 4V2c-6.842 0-11.386-.542-16.396-2h-6.225zM0 14c8.44 0 13.718-1.21 22.272-4.402l1.768-.661C33.64 5.347 39.647 4 50 4c10.271 0 15.362 1.222 24.629 4.928C84.112 12.722 89.438 14 100 14v-2c-10.271 0-15.362-1.222-24.629-4.928C65.888 3.278 60.562 2 50 2 39.374 2 33.145 3.397 23.34 7.063l-1.767.662C13.223 10.84 8.163 12 0 12v2z' fill='${themeConfig.textColor}' fill-opacity='0.05'/%3E%3C/svg%3E")`,
|
||||
};
|
||||
root.style.setProperty('--background-pattern', patternMap[themeConfig.backgroundPattern]);
|
||||
root.style.setProperty('--background-pattern-size', themeConfig.backgroundPattern === 'dots' ? '20px 20px' : themeConfig.backgroundPattern === 'grid' ? '20px 20px' : '100px 20px');
|
||||
} else {
|
||||
root.style.removeProperty('--background-pattern');
|
||||
root.style.removeProperty('--background-pattern-size');
|
||||
}
|
||||
|
||||
// Apply custom CSS if provided
|
||||
if (themeConfig.customCss) {
|
||||
let styleElement = document.getElementById('custom-theme-styles');
|
||||
@@ -162,7 +129,7 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
||||
}, [applyTheme]);
|
||||
|
||||
const setThemeByName = useCallback((name: string) => {
|
||||
const presetTheme = PRESET_THEMES[name];
|
||||
const presetTheme = GALLERY_THEME_PRESETS[name];
|
||||
if (presetTheme) {
|
||||
setThemeName(name);
|
||||
setTheme(presetTheme.config);
|
||||
@@ -250,4 +217,8 @@ function darkenColor(color: string, percent: number): string {
|
||||
return '#' + (0x1000000 + (R > 0 ? R : 0) * 0x10000 +
|
||||
(G > 0 ? G : 0) * 0x100 +
|
||||
(B > 0 ? B : 0)).toString(16).slice(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Re-export types
|
||||
export type { ThemeConfig, EventTheme };
|
||||
export { GALLERY_THEME_PRESETS };
|
||||
@@ -1,5 +1,6 @@
|
||||
export { GalleryAuthProvider, useGalleryAuth } from './GalleryAuthContext';
|
||||
export { AdminAuthProvider, useAdminAuth } from './AdminAuthContext';
|
||||
export { ThemeProvider, useTheme, PRESET_THEMES } from './ThemeContext';
|
||||
export { ThemeProvider, useTheme, GALLERY_THEME_PRESETS } from './ThemeContext';
|
||||
export type { ThemeConfig, EventTheme } from './ThemeContext';
|
||||
export { GALLERY_THEME_PRESETS as PRESET_THEMES } from './ThemeContext'; // For backward compatibility
|
||||
export { MaintenanceProvider, useMaintenanceMode } from './MaintenanceContext';
|
||||
Reference in New Issue
Block a user