This document explains the Theme Provider implementation in the Fynd Commerce App, which is the central component responsible for managing theme data and providing it to the entire application.
The ThemeProvider is the core component that:
- Fetches theme configuration from the FPI store
- Maps server theme data to local theme structure
- Provides theme context to all components
- Handles theme updates and fallbacks
- Manages theme loading states
// src/shared/providers/theme-provider.tsx
export const ThemeProvider: React.FC<{children: React.ReactNode}> = ({
children,
}) => {
const {fpi} = useFpi();
const themeConfig: ThemeDetail = useSelector(fpi?.getters?.THEME);
const [theme, setTheme] = useState<CustomTheme>(defaultTheme);
const [loading, setLoading] = useState(true);
// Theme processing logic
const getGlobalConfig = useCallback(/* ... */);
const createThemeMapping = useCallback(/* ... */);
const getThemeObject = useCallback(/* ... */);
const updateTheme = useCallback(/* ... */);
// Update theme when config changes
useEffect(() => {
updateTheme();
}, [updateTheme, themeConfig]);
if (loading) {
return null;
}
return (
<ThemeContext.Provider value={themeContextValue}>
{children}
</ThemeContext.Provider>
);
};The provider includes a comprehensive default theme that serves as a fallback:
const defaultTheme: CustomTheme = {
color: {} as ColorPalette,
typography: {
fontFamily: {
regular: 'System',
medium: 'System',
bold: 'System',
},
fontSize: {
xs: fontScale(10),
sm: fontScale(12),
base: fontScale(14),
md: fontScale(16),
lg: fontScale(18),
xl: fontScale(20),
xxl: fontScale(24),
heading: fontScale(36),
},
fontWeight: {
regular: 400,
medium: 500,
bold: 700,
},
lineHeight: {
xs: lineHeightScale(10, 1.4),
sm: lineHeightScale(12, 1.33),
base: lineHeightScale(14, 1.43),
md: lineHeightScale(16, 1.375),
lg: lineHeightScale(18, 1.33),
xl: lineHeightScale(20, 1.4),
xxl: lineHeightScale(24, 1.33),
heading: lineHeightScale(36, 1.11),
},
letterSpacing: {
xs: moderateScale(0.2, 0.5),
sm: moderateScale(0.3, 0.5),
base: moderateScale(0.4, 0.5),
md: moderateScale(0.5, 0.5),
lg: moderateScale(0.6, 0.5),
xl: moderateScale(0.8, 0.5),
xxl: moderateScale(1.0, 0.5),
heading: moderateScale(1.2, 0.5),
},
},
spacing: {
none: 0,
xs: normalScale(4),
sm: normalScale(8),
md: normalScale(12),
base: normalScale(16),
lg: normalScale(20),
xl: normalScale(24),
xxl: normalScale(32),
xxxl: normalScale(40),
},
radius: {
button: normalScale(4),
badge: normalScale(4),
image: normalScale(4),
},
layout: {
productImgAspectRatio: 0.8,
},
};const getGlobalConfig = useCallback(
(currentThemeConfig: ThemeDetail): ThemeProviderMergedGlobalConfig => {
const list = currentThemeConfig?.config?.list ?? [];
const currentConfig = currentThemeConfig?.config?.current;
const gSettings = list.find(
(item: ListItem | null | undefined) =>
!!item && item.name === currentConfig,
);
const globalConfig: ThemeProviderMergedGlobalConfig = {
...gSettings?.global_config?.custom?.props,
...gSettings?.global_config?.static?.props,
};
return globalConfig;
},
[],
);const createThemeMapping = useCallback(
(
colors: ThemeProviderMergedGlobalConfig,
fonts: ThemeDetail['font'] | undefined,
): CustomTheme => {
const generalColor = colors?.palette?.general_setting;
const advanceColor = colors?.palette?.advance_setting;
const mappedTheme = {
color: {
// Primary Colors
primaryColor: generalColor?.button?.button_primary || '#4e3f09',
secondaryColor: generalColor?.text?.text_heading || '#26201a',
accentColor: generalColor?.theme?.theme_accent || '#e7dbc2',
// Background Colors
bgColor: generalColor?.theme?.page_background || '#f8f8f8',
pageBackground: generalColor?.theme?.page_background || '#f8f8f8',
// Text Colors
textHeading: generalColor?.text?.text_heading || '#26201a',
textBody: generalColor?.text?.text_body || '#3c3131',
textLabel: generalColor?.text?.text_label || '#7d7676',
// Button Colors
buttonPrimary: generalColor?.button?.button_primary || '#4e3f09',
buttonSecondary: generalColor?.button?.button_secondary || '#ffffff',
// Theme Accent Variants
themeAccent: generalColor?.theme?.theme_accent || '#e7dbc2',
themeAccentD1: generalColor?.theme?.theme_accent_d1 || '#b9af9b',
themeAccentL1: generalColor?.theme?.theme_accent_l1 || '#ece2ce',
// ... more color mappings
},
typography: {
// Typography configuration with responsive scaling
fontFamily: {
regular: 'System',
medium: 'System',
bold: 'System',
},
fontSize: {
xs: fontScale(10),
sm: fontScale(12),
base: fontScale(14),
md: fontScale(16),
lg: fontScale(18),
xl: fontScale(20),
xxl: fontScale(24),
heading: fontScale(32),
},
// ... more typography properties
},
spacing: {
// Spacing configuration with responsive scaling
none: 0,
xs: normalScale(4),
sm: normalScale(8),
md: normalScale(12),
base: normalScale(16),
lg: normalScale(20),
xl: normalScale(24),
xxl: normalScale(32),
xxxl: normalScale(40),
},
radius: {
// Border radius configuration
button: colors?.button_border_radius
? normalScale(parseInt(colors.button_border_radius, 10))
: normalScale(4),
badge: colors?.badge_border_radius
? normalScale(parseInt(colors.badge_border_radius, 10))
: normalScale(4),
image: colors?.image_border_radius
? normalScale(parseInt(colors.image_border_radius, 10))
: normalScale(4),
},
layout: {
productImgAspectRatio:
generalColor?.shape?.product_img_aspect_ratio || 0.8,
},
};
return mappedTheme as CustomTheme;
},
[],
);const getThemeObject = useCallback(
(currentThemeConfig: ThemeDetail): CustomTheme => {
const colors = getGlobalConfig(currentThemeConfig);
const themeObject = createThemeMapping(colors, currentThemeConfig?.font);
return themeObject;
},
[getGlobalConfig, createThemeMapping],
);const updateTheme = useCallback(() => {
try {
const themeObject = getThemeObject(themeConfig);
setTheme(prev => {
const newTheme = {
...prev,
...themeObject,
};
return newTheme;
});
setLoading(false);
} catch (error) {
Logger.error('Failed to update theme state', {
error: error instanceof Error ? error.message : String(error),
themeConfig: themeConfig,
});
}
}, [getThemeObject, themeConfig]);The provider maps server color configuration to local theme colors:
// Server color structure
{
palette: {
general_setting: {
button: {
button_primary: string;
button_secondary: string;
button_link: string;
button_primary_l1: string;
button_primary_l3: string;
button_link_l1: string;
button_link_l2: string;
}
theme: {
theme_accent: string;
theme_accent_d1: string; // Darker variants
theme_accent_d2: string;
theme_accent_d3: string;
theme_accent_d4: string;
theme_accent_d5: string;
theme_accent_l1: string; // Lighter variants
theme_accent_l2: string;
theme_accent_l3: string;
theme_accent_l4: string;
theme_accent_l5: string;
page_background: string;
}
text: {
text_heading: string;
text_body: string;
text_label: string;
text_secondary: string;
}
header: {
header_background: string;
header_nav: string;
header_icon: string;
}
footer: {
footer_background: string;
footer_bottom_background: string;
footer_heading_text: string;
footer_body_text: string;
footer_icon: string;
}
sale_discount: {
sale_badge_background: string;
sale_badge_text: string;
sale_discount_text: string;
sale_timer: string;
}
shape: {
product_img_aspect_ratio: number;
}
}
advance_setting: {
overlay_popup: {
dialog_backgroung: string;
overlay: string;
}
user_alerts: {
success_background: string;
error_background: string;
info_background: string;
success_text: string;
error_text: string;
info_text: string;
}
divider_stroke_highlight: {
divider_strokes: string;
}
highlight: {
highlight_color: string;
}
}
}
button_border_radius: string;
badge_border_radius: string;
image_border_radius: string;
}// Local theme color structure
color: {
// Primary Colors
primaryColor: generalColor?.button?.button_primary || '#4e3f09',
secondaryColor: generalColor?.text?.text_heading || '#26201a',
accentColor: generalColor?.theme?.theme_accent || '#e7dbc2',
linkColor: generalColor?.button?.button_link || '#b1655b',
// Background Colors
bgColor: generalColor?.theme?.page_background || '#f8f8f8',
pageBackground: generalColor?.theme?.page_background || '#f8f8f8',
dialogBackground: advanceColor?.overlay_popup?.dialog_backgroung || '#ffffff',
overlay: advanceColor?.overlay_popup?.overlay || '#14130e',
headerBackground: generalColor?.header?.header_background || '#f3f3ed',
footerBackground: generalColor?.footer?.footer_background || '#2c231e',
// Text Colors
textHeading: generalColor?.text?.text_heading || '#26201a',
textBody: generalColor?.text?.text_body || '#3c3131',
textLabel: generalColor?.text?.text_label || '#7d7676',
textSecondary: generalColor?.text?.text_secondary || '#9c9c9c',
// Button Colors
buttonPrimary: generalColor?.button?.button_primary || '#4e3f09',
buttonPrimaryL1: generalColor?.button?.button_primary_l1 || '#71653a',
buttonPrimaryL3: generalColor?.button?.button_primary_l3 || '#b8b29d',
buttonSecondary: generalColor?.button?.button_secondary || '#ffffff',
buttonLink: generalColor?.button?.button_link || '#b1655b',
// Theme Accent Variants
themeAccent: generalColor?.theme?.theme_accent || '#e7dbc2',
themeAccentD1: generalColor?.theme?.theme_accent_d1 || '#b9af9b',
themeAccentD2: generalColor?.theme?.theme_accent_d2 || '#8b8374',
themeAccentD3: generalColor?.theme?.theme_accent_d3 || '#5c584e',
themeAccentD4: generalColor?.theme?.theme_accent_d4 || '#2e2c27',
themeAccentD5: generalColor?.theme?.theme_accent_d5 || '#000000',
themeAccentL1: generalColor?.theme?.theme_accent_l1 || '#ece2ce',
themeAccentL2: generalColor?.theme?.theme_accent_l2 || '#f1e9da',
themeAccentL3: generalColor?.theme?.theme_accent_l3 || '#f5f1e7',
themeAccentL4: generalColor?.theme?.theme_accent_l4 || '#f9f6f0',
themeAccentL5: generalColor?.theme?.theme_accent_l5 || '#ffffff',
// Alert Colors
successBackground: advanceColor?.user_alerts?.success_background || '#c2dbc9',
errorBackground: advanceColor?.user_alerts?.error_background || '#e6d5d5',
informationBackground: advanceColor?.user_alerts?.info_background || '#ebd3bc',
successText: advanceColor?.user_alerts?.success_text || '#1c958f',
errorText: advanceColor?.user_alerts?.error_text || '#b24141',
informationText: advanceColor?.user_alerts?.info_text || '#d28f51',
// System Colors
white: '#ffffff',
black: '#000000',
gray: '#cccccc',
lightGray: '#eeeeee',
// ... more system colors
}The theme provider uses responsive scaling functions for all dimensional values:
import {
normalScale,
moderateScale,
lineHeightScale,
fontScale,
} from '../utils/scale';
// Font sizes with responsive scaling
fontSize: {
xs: fontScale(10),
sm: fontScale(12),
base: fontScale(14),
md: fontScale(16),
lg: fontScale(18),
xl: fontScale(20),
xxl: fontScale(24),
heading: fontScale(32),
}
// Spacing with responsive scaling
spacing: {
xs: normalScale(4),
sm: normalScale(8),
md: normalScale(12),
base: normalScale(16),
lg: normalScale(20),
xl: normalScale(24),
xxl: normalScale(32),
xxxl: normalScale(40),
}
// Line heights with responsive scaling
lineHeight: {
xs: lineHeightScale(10, 1.4),
sm: lineHeightScale(12, 1.33),
base: lineHeightScale(14, 1.43),
md: lineHeightScale(16, 1.375),
lg: lineHeightScale(18, 1.33),
xl: lineHeightScale(20, 1.4),
xxl: lineHeightScale(24, 1.33),
heading: lineHeightScale(32, 1.25),
}
// Letter spacing with responsive scaling
letterSpacing: {
xs: moderateScale(0.2, 0.5),
sm: moderateScale(0.3, 0.5),
base: moderateScale(0.4, 0.5),
md: moderateScale(0.5, 0.5),
lg: moderateScale(0.6, 0.5),
xl: moderateScale(0.8, 0.5),
xxl: moderateScale(1.0, 0.5),
heading: moderateScale(1.2, 0.5),
}const [loading, setLoading] = useState(true);
// Show loading state while theme is being processed
if (loading) {
return null;
}const [theme, setTheme] = useState<CustomTheme>(defaultTheme);
// Update theme with new configuration
setTheme(prev => {
const newTheme = {
...prev,
...themeObject,
};
return newTheme;
});const themeContextValue = useMemo(() => {
return theme;
}, [theme]);const updateTheme = useCallback(() => {
try {
const themeObject = getThemeObject(themeConfig);
setTheme(prev => ({
...prev,
...themeObject,
}));
setLoading(false);
} catch (error) {
Logger.error('Failed to update theme state', {
error: error instanceof Error ? error.message : String(error),
themeConfig: themeConfig,
});
// Fallback to default theme
setTheme(defaultTheme);
setLoading(false);
}
}, [getThemeObject, themeConfig]);const getGlobalConfig = useCallback(
(currentThemeConfig: ThemeDetail): ThemeProviderMergedGlobalConfig => {
const list = currentThemeConfig?.config?.list ?? [];
const currentConfig = currentThemeConfig?.config?.current;
const gSettings = list.find(
(item: ListItem | null | undefined) =>
!!item && item.name === currentConfig,
);
if (!gSettings) {
Logger.warn('No global settings found for current config', {
currentConfig,
availableConfigs: list.map(item => item?.name).filter(Boolean),
});
}
const globalConfig: ThemeProviderMergedGlobalConfig = {
...gSettings?.global_config?.custom?.props,
...gSettings?.global_config?.static?.props,
};
return globalConfig;
},
[],
);// Enable theme debugging
const debugTheme = () => {
Logger.debug('Theme Provider Debug:', {
hasThemeConfig: !!themeConfig,
themeConfigName: themeConfig?.config?.current,
hasTheme: !!theme,
themeKeys: Object.keys(theme),
hasColors: !!theme.color,
colorCount: Object.keys(theme.color || {}).length,
hasTypography: !!theme.typography,
loading,
});
};
// Use in development
if (__DEV__) {
debugTheme();
}const debugConfiguration = () => {
const colors = getGlobalConfig(themeConfig);
Logger.debug('Configuration Debug:', {
hasGlobalConfig: !!colors,
hasColors: !!colors?.palette,
generalColorKeys: colors?.palette?.general_setting
? Object.keys(colors.palette.general_setting)
: [],
advanceColorKeys: colors?.palette?.advance_setting
? Object.keys(colors.palette.advance_setting)
: [],
});
};// Memoize expensive operations
const getGlobalConfig = useCallback(/* ... */, []);
const createThemeMapping = useCallback(/* ... */, []);
const getThemeObject = useCallback(/* ... */, [getGlobalConfig, createThemeMapping]);
const updateTheme = useCallback(/* ... */, [getThemeObject, themeConfig]);
// Memoize context value
const themeContextValue = useMemo(() => theme, [theme]);// Only update theme when configuration changes
useEffect(() => {
if (themeConfig) {
updateTheme();
}
}, [updateTheme, themeConfig]);- Theme Presets: Pre-defined theme configurations
- Custom Themes: User-defined theme creation
- Theme Analytics: Theme usage analytics
- Advanced Scaling: More sophisticated responsive scaling
This documentation covers the Theme Provider implementation. For more specific use cases or advanced patterns, refer to the other theme documentation sections.