All files / blong-browser/src/components/Theme Theme.tsx

80.99% Statements 98/121
37.5% Branches 6/16
100% Functions 1/1
80.99% Lines 98/121

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 1221x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x       1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x   1x 1x 1x 1x 1x 1x 1x 1x 1x               1x               1x            
/**
 * Theme — PrimeReact theming provider.
 *
 *   light, dark, big         → lara-light-blue / lara-dark-blue
 *   compact, light-compact   → saga-blue / vela-blue
 *   dark-compact             → vela-blue / saga-blue
 *
 * CSS for PrimeReact themes must be loaded by the consumer. Use
 * PRIMEREACT_PALETTE_THEMES to look up which CSS file to load for a
 * given palette + dark-mode combination.
 */
import 'primeflex/primeflex.css';
import 'primeicons/primeicons.css';
import 'primereact/resources/primereact.min.css';
 
import {addLocale, locale} from '../../primereact/index.js';
 
import {type ReactNode, useEffect} from 'react';
import {useAppStore} from '../../state/appStore.js';
 
export type PaletteType = 'light' | 'dark';
export type ThemeType = 'big' | 'compact';
 
/**
 * Maps each palette to the PrimeReact theme name for light and dark modes.
 * Theme CSS is at: `primereact/resources/themes/<name>/theme.css`
 */
export const PRIMEREACT_PALETTE_THEMES: Record<ThemeType, Record<PaletteType, string>> = {
    big: {light: 'lara-light-blue', dark: 'lara-dark-blue'},
    compact: {light: 'saga-blue', dark: 'vela-blue'},
};
 
/** Font size in px for each palette — compact variants use 14, others 16. */
export const PALETTE_FONT_SIZES: Record<ThemeType, number> = {
    big: 16,
    compact: 14,
};
 
export interface IThemeConfig {
    type?: ThemeType;
    palette?: PaletteType;
    direction?: 'ltr' | 'rtl';
    primary?: string;
    /** Override the font size (px). Defaults to palette-based size. */
    fontSize?: number;
    /**
     * Custom PrimeReact locale data per language code.
     * Each entry is registered via addLocale(lang, options).
     * Only needed for locales not already bundled with PrimeReact.
     */
    languages?: Record<string, object>;
}
 
interface IThemeProps {
    theme: IThemeConfig;
    children: ReactNode;
}
 
export function Theme({theme, children}: IThemeProps) {
    const language = useAppStore(s => s.language);
 
    useEffect(() => {
        if (theme.languages) {
            for (const [lang, options] of Object.entries(theme.languages)) {
                addLocale(lang, options as Parameters<typeof addLocale>[1]);
            }
        }
    }, [theme.languages]);
 
    useEffect(() => {
        locale(language && language !== 'en' ? language : 'en');
    }, [language]);
 
    const palette = theme.palette ?? 'dark';
    const type = theme.type ?? 'compact';
    useEffect(() => {
        const fontSize = theme.fontSize ?? PALETTE_FONT_SIZES[type];
 
        // Apply direction
        document.documentElement.dir = theme.direction ?? 'ltr';
        document.documentElement.lang = theme.direction === 'rtl' ? 'ar' : 'en';
 
        // Apply font size
        document.documentElement.style.fontSize = `${fontSize}px`;
 
        // Apply custom primary color via CSS variable
        if (theme.primary) {
            document.documentElement.style.setProperty('--p-primary-color', theme.primary);
        }
        switch (PRIMEREACT_PALETTE_THEMES[type][palette] || 'vela-blue') {
            case 'vela-blue':
                import('primereact/resources/themes/vela-blue/theme.css');
                break;
            case 'saga-blue':
                import('primereact/resources/themes/saga-blue/theme.css');
                break;
            case 'lara-light-blue':
                import('primereact/resources/themes/lara-light-blue/theme.css');
                break;
            case 'lara-dark-blue':
                import('primereact/resources/themes/lara-dark-blue/theme.css');
                break;
        }
    }, [palette, theme.direction, theme.fontSize, theme.primary, type]);

    return (
        <div
            className={[
                'blong-app',
                `blong-app-${palette}`,
                `blong-app-${type}`,
                theme.direction === 'rtl' ? 'blong-app-rtl' : '',
            ]
                .filter(Boolean)
                .join(' ')}
            dir={theme.direction ?? 'ltr'}
        >
            {children}
        </div>
    );
}