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

0% Statements 0/121
0% Branches 0/1
0% Functions 0/1
0% Lines 0/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 122                                                                                                                                                                                                                                                   
/**
 * 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>
    );
}