import { RgbaColorPicker, RgbaColor } from 'react-colorful'; import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { FaUndo, FaTrash, FaDownload, FaUpload } from 'react-icons/fa'; import { LocalizeText, PRESET_COLORS, THEME_PRESETS, useUiSettings } from '../../api'; import { Flex, Text } from '../../common'; const hexToRgba = (hex: string, a = 1): RgbaColor => { const num = parseInt(hex.replace('#', ''), 16); return { r: (num >> 16) & 0xFF, g: (num >> 8) & 0xFF, b: num & 0xFF, a }; }; const rgbaToHex = (rgba: RgbaColor): string => { return '#' + ((1 << 24) + (rgba.r << 16) + (rgba.g << 8) + rgba.b).toString(16).slice(1); }; export const InterfaceColorTabView: FC<{}> = () => { const { settings, updateSettings, resetSettings } = useUiSettings(); const [ color, setColor ] = useState(() => hexToRgba(settings.headerColor, settings.headerAlpha / 100)); const [ importValue, setImportValue ] = useState(''); const [ showImport, setShowImport ] = useState(false); const [ copyFeedback, setCopyFeedback ] = useState(false); const previewTimerRef = useRef>(null); const hexColor = useMemo(() => rgbaToHex(color), [ color ]); const alphaPercent = useMemo(() => Math.round((color.a ?? 1) * 100), [ color ]); // Live preview con debounce useEffect(() => { if(previewTimerRef.current) clearTimeout(previewTimerRef.current); previewTimerRef.current = setTimeout(() => { updateSettings({ colorMode: 'color', headerColor: hexColor, headerAlpha: alphaPercent }); }, 50); return () => { if(previewTimerRef.current) clearTimeout(previewTimerRef.current); }; }, [ hexColor, alphaPercent ]); const onHexInput = useCallback((value: string) => { const clean = value.replace(/[^0-9a-fA-F]/g, '').slice(0, 6); if(clean.length === 6) { const rgba = hexToRgba('#' + clean, color.a); setColor(rgba); } }, [ color.a ]); const onRgbInput = useCallback((channel: 'r' | 'g' | 'b', value: number) => { const clamped = Math.max(0, Math.min(255, value || 0)); setColor(prev => ({ ...prev, [channel]: clamped })); }, []); const onAlphaInput = useCallback((value: number) => { const clamped = Math.max(0, Math.min(100, value || 0)); setColor(prev => ({ ...prev, a: clamped / 100 })); }, []); const onPresetClick = useCallback((presetHex: string) => { setColor(hexToRgba(presetHex, color.a)); }, [ color.a ]); const onThemeClick = useCallback((themeColor: string, themeAlpha: number) => { setColor(hexToRgba(themeColor, themeAlpha / 100)); }, []); const onReset = useCallback(() => { resetSettings(); setColor(hexToRgba('#1E7295', 1)); }, [ resetSettings ]); const onDelete = useCallback(() => { updateSettings({ colorMode: 'default' }); setColor(hexToRgba('#1E7295', 1)); }, [ updateSettings ]); const onExport = useCallback(() => { const data = JSON.stringify({ color: hexColor, alpha: alphaPercent, mode: settings.colorMode, image: settings.headerImageUrl }); navigator.clipboard.writeText(data); setCopyFeedback(true); setTimeout(() => setCopyFeedback(false), 2000); }, [ hexColor, alphaPercent, settings ]); const onImport = useCallback(() => { try { const data = JSON.parse(importValue); if(data.color) { const alpha = data.alpha ?? 100; setColor(hexToRgba(data.color, alpha / 100)); updateSettings({ colorMode: data.mode || 'color', headerColor: data.color, headerAlpha: alpha, headerImageUrl: data.image || '' }); } setImportValue(''); setShowImport(false); } catch(e) {} }, [ importValue, updateSettings ]); return ( {/* Color picker */}
{/* Color preview swatch */}
{/* Hex/RGB/A inputs */} onHexInput(e.target.value) } maxLength={ 6 } /> Hex onRgbInput('r', parseInt(e.target.value)) } min={ 0 } max={ 255 } /> R onRgbInput('g', parseInt(e.target.value)) } min={ 0 } max={ 255 } /> G onRgbInput('b', parseInt(e.target.value)) } min={ 0 } max={ 255 } /> B onAlphaInput(parseInt(e.target.value)) } min={ 0 } max={ 100 } /> A {/* Preset colors */}
{ PRESET_COLORS.map((presetHex, i) => (
onPresetClick(presetHex) } /> )) }
{/* Theme presets */} { LocalizeText('interface.settings.color.themes') }
{ THEME_PRESETS.map((theme) => (
onThemeClick(theme.color, theme.alpha) } >
{ LocalizeText(`interface.settings.theme.${ theme.name }`) }
)) }
{/* Action buttons */} {/* Import panel */} { showImport && ( setImportValue(e.target.value) } /> ) } ); };