Skip to content

Latest commit

ย 

History

History
665 lines (576 loc) ยท 17.5 KB

File metadata and controls

665 lines (576 loc) ยท 17.5 KB

Theme Provider Documentation

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.

๐ŸŽฏ Overview

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

๐Ÿ—๏ธ Architecture

Theme Provider Structure

// 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>
  );
};

๐Ÿ”ง Configuration

Default Theme

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,
  },
};

๐Ÿ”„ Theme Processing Flow

1. Global Configuration Extraction

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;
  },
  [],
);

2. Theme Mapping

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;
  },
  [],
);

3. Theme Object Creation

const getThemeObject = useCallback(
  (currentThemeConfig: ThemeDetail): CustomTheme => {
    const colors = getGlobalConfig(currentThemeConfig);
    const themeObject = createThemeMapping(colors, currentThemeConfig?.font);
    return themeObject;
  },
  [getGlobalConfig, createThemeMapping],
);

4. Theme Update Process

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]);

๐ŸŽจ Color Mapping

Server Color Structure

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 Color Mapping

// 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
}

๐Ÿ“ Responsive Scaling

The theme provider uses responsive scaling functions for all dimensional values:

Scaling Functions

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),
}

๐Ÿ”„ State Management

Loading State

const [loading, setLoading] = useState(true);

// Show loading state while theme is being processed
if (loading) {
  return null;
}

Theme State

const [theme, setTheme] = useState<CustomTheme>(defaultTheme);

// Update theme with new configuration
setTheme(prev => {
  const newTheme = {
    ...prev,
    ...themeObject,
  };
  return newTheme;
});

Memoized Context Value

const themeContextValue = useMemo(() => {
  return theme;
}, [theme]);

๐Ÿ› Error Handling

Theme Processing Errors

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]);

Missing Configuration

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;
  },
  [],
);

๐Ÿ” Debugging

Theme Debugging

// 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();
}

Configuration Debugging

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)
      : [],
  });
};

๐Ÿš€ Performance Optimization

Memoization

// 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]);

Conditional Updates

// Only update theme when configuration changes
useEffect(() => {
  if (themeConfig) {
    updateTheme();
  }
}, [updateTheme, themeConfig]);

๐Ÿ”ฎ Future Enhancements

  1. Theme Presets: Pre-defined theme configurations
  2. Custom Themes: User-defined theme creation
  3. Theme Analytics: Theme usage analytics
  4. 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.