mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-21 15:04:25 +00:00
# Which Problems Are Solved This PR introduces a comprehensive theme customization system for the login application with responsive behavior and enhanced visual options. <img width="1122" height="578" alt="Screenshot 2025-08-19 at 09 55 24" src="https://github.com/user-attachments/assets/cdcc8948-533d-4e13-bf45-fdcc24acfb2b" /> # How the Problems Are Solved ## ✨ Features Added - **🔄 Responsive Layout System**: Automatic switching between side-by-side and top-to-bottom layouts based on screen size - **🖼️ Background Image Support**: Custom background images configurable via environment variables - **⚙️ Theme Configuration**: Complete theme system with roundness, spacing, appearance, and layout options - **📱 Mobile-First Design**: Intelligent layout adaptation for different screen sizes - **🎯 Enhanced Typography**: Improved visual hierarchy with larger titles in side-by-side mode ## 🏗️ Architecture - **Server-Safe Theme Functions**: Theme configuration accessible on both server and client - **SSR-Safe Hooks**: Proper hydration handling for responsive layouts - **Component Separation**: Clear boundaries between server and client components - **Two-Section Layout**: Consistent content structure across all login pages ## 🔧 Configuration Options All theme options are configurable via environment variables: - `NEXT_PUBLIC_THEME_ROUNDNESS`: `edgy` | `mid` | `full` - `NEXT_PUBLIC_THEME_LAYOUT`: `side-by-side` | `top-to-bottom` - `NEXT_PUBLIC_THEME_APPEARANCE`: `flat` | `material` - `NEXT_PUBLIC_THEME_SPACING`: `regular` | `compact` - `NEXT_PUBLIC_THEME_BACKGROUND_IMAGE`: Custom background image URL ## 📄 Pages Updated Updated all major login pages to use the new two-section responsive layout: - Login name entry - Password verification - MFA verification - User registration - Account selection - Device authorization - Logout confirmation ## 📚 Documentation - **THEME_ARCHITECTURE.md**: Complete technical documentation of the theme system - **THEME_CUSTOMIZATION.md**: User-friendly guide with examples and troubleshooting ## 🚀 Benefits - **Better UX**: Responsive design that works seamlessly across all devices - **Brand Flexibility**: Easy customization to match any brand identity - **Maintainable Code**: Clean separation of concerns and well-documented architecture - **Future-Proof**: Extensible system for additional theme options <img width="580" height="680" alt="Screenshot 2025-08-19 at 09 22 23" src="https://github.com/user-attachments/assets/9de8da37-6d56-4fe9-b337-5d8ad2a3ba59" /> <img width="599" height="689" alt="Screenshot 2025-08-19 at 09 23 45" src="https://github.com/user-attachments/assets/26a30cc7-4017-4f4b-8b87-a49466c42b94" /> <img width="595" height="681" alt="Screenshot 2025-08-19 at 09 23 17" src="https://github.com/user-attachments/assets/a3d31088-4545-4f36-aafe-1aae1253d677" />
155 lines
5.5 KiB
TypeScript
155 lines
5.5 KiB
TypeScript
// Theme configuration system for customizable login experience
|
|
|
|
export type ThemeRoundness = "edgy" | "mid" | "full";
|
|
export type ThemeLayout = "side-by-side" | "top-to-bottom";
|
|
export type ThemeAppearance = "flat" | "material" | "glass";
|
|
export type ThemeSpacing = "regular" | "compact";
|
|
|
|
export interface ComponentRoundnessConfig {
|
|
card: ThemeRoundness;
|
|
button: ThemeRoundness;
|
|
input: ThemeRoundness;
|
|
image: ThemeRoundness;
|
|
avatar: ThemeRoundness;
|
|
avatarContainer: ThemeRoundness;
|
|
themeSwitch: ThemeRoundness;
|
|
}
|
|
|
|
export interface ThemeConfig {
|
|
roundness: ThemeRoundness; // Global fallback
|
|
componentRoundness?: ComponentRoundnessConfig; // Component-specific overrides
|
|
layout: ThemeLayout;
|
|
backgroundImage?: string;
|
|
appearance: ThemeAppearance;
|
|
spacing: ThemeSpacing;
|
|
}
|
|
|
|
// Default component-specific roundness configuration
|
|
export const DEFAULT_COMPONENT_ROUNDNESS: ComponentRoundnessConfig = {
|
|
card: "mid",
|
|
button: "mid",
|
|
input: "mid",
|
|
image: "mid",
|
|
avatar: "full", // Avatars default to full roundness
|
|
avatarContainer: "full", // Avatar containers default to full roundness
|
|
themeSwitch: "full", // Theme switch defaults to full roundness
|
|
};
|
|
|
|
// Default theme configuration
|
|
export const DEFAULT_THEME: ThemeConfig = {
|
|
roundness: "mid",
|
|
componentRoundness: DEFAULT_COMPONENT_ROUNDNESS,
|
|
layout: "top-to-bottom",
|
|
appearance: "flat",
|
|
spacing: "regular",
|
|
};
|
|
|
|
// Get theme configuration from environment variables
|
|
export function getThemeConfig(): ThemeConfig {
|
|
const globalRoundness = process.env.NEXT_PUBLIC_THEME_ROUNDNESS as ThemeRoundness;
|
|
|
|
// If global roundness is set via env var, use it for all components
|
|
// Otherwise, use component-specific defaults
|
|
const componentRoundness = globalRoundness
|
|
? {
|
|
card: globalRoundness,
|
|
button: globalRoundness,
|
|
input: globalRoundness,
|
|
image: globalRoundness,
|
|
avatar: globalRoundness,
|
|
avatarContainer: globalRoundness,
|
|
themeSwitch: globalRoundness,
|
|
}
|
|
: DEFAULT_COMPONENT_ROUNDNESS;
|
|
|
|
return {
|
|
roundness: globalRoundness || DEFAULT_THEME.roundness,
|
|
componentRoundness: componentRoundness,
|
|
layout: (process.env.NEXT_PUBLIC_THEME_LAYOUT as ThemeLayout) || DEFAULT_THEME.layout,
|
|
backgroundImage: process.env.NEXT_PUBLIC_THEME_BACKGROUND_IMAGE || undefined,
|
|
appearance: (process.env.NEXT_PUBLIC_THEME_APPEARANCE as ThemeAppearance) || DEFAULT_THEME.appearance,
|
|
spacing: (process.env.NEXT_PUBLIC_THEME_SPACING as ThemeSpacing) || DEFAULT_THEME.spacing,
|
|
};
|
|
}
|
|
|
|
// Roundness CSS classes
|
|
export const ROUNDNESS_CLASSES = {
|
|
edgy: {
|
|
card: "rounded-none",
|
|
button: "rounded-none",
|
|
input: "rounded-none",
|
|
image: "rounded-none",
|
|
avatar: "rounded-none",
|
|
avatarContainer: "rounded-none",
|
|
themeSwitch: "rounded-none",
|
|
},
|
|
mid: {
|
|
card: "rounded-lg",
|
|
button: "rounded-md",
|
|
input: "rounded-md",
|
|
image: "rounded-lg",
|
|
avatar: "rounded-lg",
|
|
avatarContainer: "rounded-md",
|
|
themeSwitch: "rounded-md",
|
|
},
|
|
full: {
|
|
card: "rounded-3xl",
|
|
button: "rounded-full",
|
|
input: "rounded-full pl-4",
|
|
image: "rounded-full",
|
|
avatar: "rounded-full",
|
|
avatarContainer: "rounded-full",
|
|
themeSwitch: "rounded-full",
|
|
},
|
|
} as const;
|
|
|
|
// Helper function to get component-specific roundness
|
|
export function getComponentRoundness(componentType: keyof ComponentRoundnessConfig): string {
|
|
const themeConfig = getThemeConfig();
|
|
|
|
// Use component-specific roundness if available, otherwise fall back to global roundness
|
|
const roundnessLevel = themeConfig.componentRoundness?.[componentType] || themeConfig.roundness;
|
|
|
|
return ROUNDNESS_CLASSES[roundnessLevel][componentType];
|
|
}
|
|
|
|
// Spacing configuration
|
|
export const SPACING_STYLES = {
|
|
regular: {
|
|
spacing: "space-y-6",
|
|
padding: "p-6 py-8",
|
|
},
|
|
compact: {
|
|
spacing: "space-y-4",
|
|
padding: "p-4",
|
|
},
|
|
} as const;
|
|
|
|
// Appearance styling (complete design philosophies)
|
|
export const APPEARANCE_STYLES = {
|
|
flat: {
|
|
card: "bg-background-light-400 dark:bg-background-dark-500 border border-opacity-20 border border-black/10 dark:border-white/10",
|
|
button: "border border-button-light-border dark:border-button-dark-border", // No shadows for flat design
|
|
"idp-button": "border border-button-light-border dark:border-button-dark-border", // No shadows for flat design
|
|
typography: "font-normal",
|
|
background: "bg-background-light-500 dark:bg-background-dark-500", // Same as usual background
|
|
},
|
|
material: {
|
|
card: "bg-background-light-400 dark:bg-background-dark-500 shadow-sm border-0",
|
|
button: "shadow hover:shadow-xl active:shadow-xl", // Material shadows for buttons
|
|
"idp-button":
|
|
"!bg-background-[#00000020] !dark:bg-background-[#ffffff50] transition shadow shadow-md hover:shadow-lg active:shadow-xl", // Material shadows for IDP buttons
|
|
typography: "font-medium",
|
|
background: "bg-background-light-400 dark:bg-background-dark-500", // Current system (shade 400)
|
|
},
|
|
glass: {
|
|
card: "backdrop-blur-md bg-white/10 dark:bg-black/10 border border-white/20 dark:border-white/10 shadow-xl",
|
|
button:
|
|
"backdrop-blur-sm bg-white/20 dark:bg-black/20 border border-white/30 dark:border-white/20 shadow-lg hover:shadow-xl", // Glass effect for buttons
|
|
"idp-button":
|
|
"backdrop-blur-sm bg-white/20 dark:bg-black/20 border border-white/30 dark:border-white/20 shadow-lg hover:shadow-xl", // Glass effect for IDP buttons
|
|
typography: "font-medium",
|
|
background: "bg-transparent", // Transparent background to show blur effect
|
|
},
|
|
} as const;
|