Add theme and color config

This commit is contained in:
chrox
2024-11-19 19:28:17 +01:00
parent 6fa043f340
commit 15939ce2fc
42 changed files with 582 additions and 220 deletions
@@ -1,32 +1,87 @@
import React, { useState } from 'react';
import React, { useEffect } from 'react';
import { MdOutlineLightMode, MdOutlineDarkMode } from 'react-icons/md';
import { MdRadioButtonUnchecked, MdRadioButtonChecked } from 'react-icons/md';
import { TbSunMoon } from 'react-icons/tb';
const ColorPanel: React.FC<{ bookKey: string }> = () => {
const [selectedTheme, setSelectedTheme] = useState('Default');
const themes = [
'Default',
'Gray',
'Sepia',
'Grass',
'Cherry',
'Sky',
'Nord',
'Solarized',
'Gruvbox',
];
import { useTheme } from '@/hooks/useTheme';
import { themes } from '@/styles/themes';
import { useReaderStore } from '@/store/readerStore';
import { getStyles } from '@/utils/style';
const ColorPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const { themeMode, themeColor, themeCode, isDarkMode, updateThemeMode, updateThemeColor } =
useTheme();
const { getView, getViewSettings } = useReaderStore();
const view = getView(bookKey);
const viewSettings = getViewSettings(bookKey)!;
useEffect(() => {
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [themeCode]);
return (
<div className='my-4 w-full space-y-6'>
<div className='w-full'>
<h2 className='mb-2 font-medium'>Color Settings</h2>
<div className='mt-4 grid grid-cols-3 gap-2'>
{themes.map((theme) => (
<div className='flex items-center justify-between'>
<h2 className='font-medium'>Theme Mode</h2>
<div className='flex gap-2'>
<div className='tooltip tooltip-bottom' data-tip='Auto Mode'>
<button
key={theme}
className={`btn ${selectedTheme === theme ? 'btn-active' : ''}`}
onClick={() => setSelectedTheme(theme)}
className={`btn btn-ghost btn-circle ${themeMode === 'auto' ? 'btn-active bg-base-300' : ''}`}
onClick={() => updateThemeMode('auto')}
>
{theme}
<TbSunMoon size={20} />
</button>
</div>
<div className='tooltip tooltip-bottom' data-tip='Light Mode'>
<button
className={`btn btn-ghost btn-circle ${themeMode === 'light' ? 'btn-active bg-base-300' : ''}`}
onClick={() => updateThemeMode('light')}
>
<MdOutlineLightMode size={20} />
</button>
</div>
<div className='tooltip tooltip-bottom' data-tip='Dark Mode'>
<button
className={`btn btn-ghost btn-circle ${themeMode === 'dark' ? 'btn-active bg-base-300' : ''}`}
onClick={() => updateThemeMode('dark')}
>
<MdOutlineDarkMode size={20} />
</button>
</div>
</div>
</div>
<div>
<h2 className='mb-2 font-medium'>Theme Color</h2>
<div className='grid grid-cols-3 gap-4'>
{themes.map(({ name, label, colors }) => (
<label
key={name}
className={`relative flex cursor-pointer flex-col items-center justify-center rounded-lg p-4 shadow-md ${
themeColor === name ? 'ring-2 ring-indigo-500 ring-offset-2' : ''
}`}
style={{
backgroundColor: isDarkMode ? colors.dark['base-100'] : colors.light['base-100'],
color: isDarkMode ? colors.dark['base-content'] : colors.light['base-content'],
}}
>
<input
type='radio'
name='theme'
value={name}
checked={themeColor === name}
onChange={() => updateThemeColor(name)}
className='hidden'
/>
{themeColor === name ? (
<MdRadioButtonChecked size={24} />
) : (
<MdRadioButtonUnchecked size={24} />
)}
<span>{label}</span>
</label>
))}
</div>
</div>
@@ -17,10 +17,10 @@ const DialogMenu: React.FC<DialogMenuProps> = ({ toggleDropdown }) => {
return (
<div
tabIndex={0}
className='dropdown-content dropdown-right no-triangle z-20 mt-1 w-44 border bg-white shadow-2xl'
className='dropdown-content dropdown-right no-triangle border-base-200 z-20 mt-1 w-44 border shadow-2xl'
>
<button
className='flex w-full items-center justify-between rounded-md p-2 hover:bg-gray-100'
className='hover:bg-base-200 text-base-content flex w-full items-center justify-between rounded-md p-2'
onClick={handleToggleGlobal}
>
<div className='flex items-center'>
@@ -6,6 +6,7 @@ import FontDropdown from './FontDropDown';
import { MONOSPACE_FONTS, SANS_SERIF_FONTS, SERIF_FONTS } from '@/services/constants';
import { useReaderStore } from '@/store/readerStore';
import { getStyles } from '@/utils/style';
import { useTheme } from '@/hooks/useTheme';
interface FontFaceProps {
className?: string;
@@ -24,7 +25,7 @@ const handleFontFaceFont = (option: string, family: string) => {
const FontFace = ({ className, family, label, options, selected, onSelect }: FontFaceProps) => (
<div className={clsx('config-item', className)}>
<span className='text-gray-700'>{label}</span>
<span className=''>{label}</span>
<FontDropdown
family={family}
options={options}
@@ -40,6 +41,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const { setSettings, getView, getViewSettings, setViewSettings } = useReaderStore();
const view = getView(bookKey);
const viewSettings = getViewSettings(bookKey)!;
const { themeCode } = useTheme();
const [defaultFontSize, setDefaultFontSize] = useState(viewSettings.defaultFontSize!);
const [minFontSize, setMinFontSize] = useState(viewSettings.minimumFontSize!);
@@ -56,7 +58,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.defaultFont = defaultFont;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings));
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [defaultFont]);
useEffect(() => {
@@ -66,7 +68,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.defaultFontSize = defaultFontSize;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings));
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [defaultFontSize]);
useEffect(() => {
@@ -76,7 +78,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.minimumFontSize = minFontSize;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings));
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [minFontSize]);
useEffect(() => {
@@ -86,7 +88,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.serifFont = serifFont;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings));
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [serifFont]);
useEffect(() => {
@@ -96,7 +98,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.sansSerifFont = sansSerifFont;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings));
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [sansSerifFont]);
useEffect(() => {
@@ -106,7 +108,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.monospaceFont = monospaceFont;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings));
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [monospaceFont]);
useEffect(() => {
@@ -116,7 +118,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.overrideFont = overrideFont;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings));
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [overrideFont]);
const handleFontFamilyFont = (option: string) => {
@@ -136,8 +138,8 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
<div className='my-4 w-full space-y-6'>
<div className='w-full'>
<h2 className='mb-2 font-medium'>Font Size</h2>
<div className='card bg-base-100 border shadow'>
<div className='divide-y'>
<div className='card border-base-200 border shadow'>
<div className='divide-base-200 divide-y'>
<NumberInput
className='config-item-top'
label='Default Font Size'
@@ -160,10 +162,10 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
<div className='w-full'>
<h2 className='mb-2 font-medium'>Font Family</h2>
<div className='card bg-base-100 border shadow'>
<div className='divide-y'>
<div className='card border-base-200 border shadow'>
<div className='divide-base-200 divide-y'>
<div className='config-item config-item-top'>
<span className='text-gray-700'>Default Font</span>
<span className=''>Default Font</span>
<FontDropdown
options={fontFamilyOptions}
selected={defaultFont}
@@ -173,7 +175,7 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
</div>
<div className='config-item config-item-bottom'>
<span className='text-gray-700'>Override Publisher Font</span>
<span className=''>Override Publisher Font</span>
<input
type='checkbox'
className='toggle'
@@ -187,8 +189,8 @@ const FontPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
<div className='w-full'>
<h2 className='mb-2 font-medium'>Font Face</h2>
<div className='card bg-base-100 border shadow'>
<div className='divide-y'>
<div className='card border-base-200 border shadow'>
<div className='divide-base-200 divide-y'>
<FontFace
className='config-item-top'
family='serif'
@@ -3,12 +3,14 @@ import { useReaderStore } from '@/store/readerStore';
import { getStyles } from '@/utils/style';
import { ONE_COLUMN_MAX_INLINE_SIZE } from '@/services/constants';
import NumberInput from './NumberInput';
import { useTheme } from '@/hooks/useTheme';
const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const { settings, isFontLayoutSettingsGlobal } = useReaderStore();
const { setSettings, getView, getViewSettings, setViewSettings } = useReaderStore();
const view = getView(bookKey);
const viewSettings = getViewSettings(bookKey)!;
const { themeCode } = useTheme();
const [lineHeight, setLineHeight] = useState(viewSettings.lineHeight!);
const [fullJustification, setFullJustification] = useState(viewSettings.fullJustification!);
@@ -26,7 +28,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.lineHeight = lineHeight;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings));
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [lineHeight]);
useEffect(() => {
@@ -36,7 +38,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.fullJustification = fullJustification;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings));
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [fullJustification]);
useEffect(() => {
@@ -46,7 +48,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.hyphenation = hyphenation;
setSettings(settings);
}
view?.renderer.setStyles?.(getStyles(viewSettings));
view?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
}, [hyphenation]);
useEffect(() => {
@@ -100,8 +102,8 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
<div className='my-4 w-full space-y-6'>
<div className='w-full'>
<h2 className='mb-2 font-medium'>Paragraph</h2>
<div className='card bg-base-100 border shadow'>
<div className='divide-y'>
<div className='card bg-base-100 border-base-200 border shadow'>
<div className='divide-base-200 divide-y'>
<NumberInput
className='config-item-top'
label='Line Height'
@@ -112,7 +114,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
step={0.1}
/>
<div className='config-item config-item-bottom'>
<span className='text-gray-700'>Full Justification</span>
<span className=''>Full Justification</span>
<input
type='checkbox'
className='toggle'
@@ -121,7 +123,7 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
/>
</div>
<div className='config-item config-item-bottom'>
<span className='text-gray-700'>Hyphenation</span>
<span className=''>Hyphenation</span>
<input
type='checkbox'
className='toggle'
@@ -135,8 +137,8 @@ const LayoutPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
<div className='w-full'>
<h2 className='mb-2 font-medium'>Page</h2>
<div className='card bg-base-100 border shadow'>
<div className='divide-y'>
<div className='card bg-base-100 border-base-200 border shadow'>
<div className='divide-base-200 divide-y'>
<NumberInput
className='config-item-top'
label='Margins (px)'
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import { useReaderStore } from '@/store/readerStore';
import cssbeautify from 'cssbeautify';
import { getStyles } from '@/utils/style';
import { useTheme } from '@/hooks/useTheme';
const cssRegex =
/((?:\s*)([\w#.@*,:\-.:>+~\[\]\"=(),*\s]+)\s*{(?:[\s]*)((?:[A-Za-z\- \s]+[:]\s*['"0-9\w .,\/\()\-!#%]+;?)*)*\s*}(?:\s*))/gim;
@@ -10,6 +11,7 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
const { settings, isFontLayoutSettingsGlobal } = useReaderStore();
const { setSettings, getView, getViewSettings, setViewSettings } = useReaderStore();
const viewSettings = getViewSettings(bookKey)!;
const { themeCode } = useTheme();
const [animated, setAnimated] = useState(viewSettings.animated!);
const [userStylesheet, setUserStylesheet] = useState(viewSettings.userStylesheet!);
@@ -43,7 +45,7 @@ const MiscPanel: React.FC<{ bookKey: string }> = ({ bookKey }) => {
settings.globalViewSettings.userStylesheet = formattedCSS;
setSettings(settings);
}
getView(bookKey)?.renderer.setStyles?.(getStyles(viewSettings));
getView(bookKey)?.renderer.setStyles?.(getStyles(viewSettings, themeCode));
} catch (err) {
setError('Invalid CSS: Please check your input.');
console.log('CSS Error:', err);
@@ -55,14 +55,14 @@ const NumberInput: React.FC<NumberInputProps> = ({
return (
<div className={clsx('config-item', className)}>
<span className='text-gray-700'>{label}</span>
<div className='flex items-center gap-2'>
<span className='text-base-content'>{label}</span>
<div className='text-base-content flex items-center gap-2'>
<input
type='text'
value={localValue}
onChange={handleChange}
onBlur={handleOnBlur}
className='input input-ghost settings-content w-20 max-w-xs rounded border-0 bg-transparent px-3 py-1 text-right !outline-none'
className='input input-ghost settings-content text-base-content w-20 max-w-xs rounded border-0 bg-transparent px-3 py-1 text-right !outline-none'
onFocus={(e) => e.target.select()}
/>
<button
@@ -25,28 +25,28 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
<div className='dialog-header bg-base-100 sticky top-0 z-10 flex items-center justify-center px-4 pt-2'>
<div className='dialog-tabs flex h-10 max-w-[80%] flex-grow items-center justify-around'>
<button
className={`btn btn-ghost h-8 min-h-8 px-4 ${activePanel === 'Font' ? 'btn-active' : ''}`}
className={`btn btn-ghost text-base-content h-8 min-h-8 px-4 ${activePanel === 'Font' ? 'btn-active' : ''}`}
onClick={() => setActivePanel('Font')}
>
<RiFontSize size={20} className='mr-0' />
Font
</button>
<button
className={`btn btn-ghost h-8 min-h-8 px-4 ${activePanel === 'Layout' ? 'btn-active' : ''}`}
className={`btn btn-ghost text-base-content h-8 min-h-8 px-4 ${activePanel === 'Layout' ? 'btn-active' : ''}`}
onClick={() => setActivePanel('Layout')}
>
<RiDashboardLine size={20} className='mr-0' />
Layout
</button>
<button
className={`btn btn-ghost h-8 min-h-8 px-4 ${activePanel === 'Color' ? 'btn-active' : ''}`}
className={`btn btn-ghost text-base-content h-8 min-h-8 px-4 ${activePanel === 'Color' ? 'btn-active' : ''}`}
onClick={() => setActivePanel('Color')}
>
<VscSymbolColor size={20} className='mr-0' />
Color
</button>
<button
className={`btn btn-ghost h-8 min-h-8 px-4 ${activePanel === 'Misc' ? 'btn-active' : ''}`}
className={`btn btn-ghost text-base-content h-8 min-h-8 px-4 ${activePanel === 'Misc' ? 'btn-active' : ''}`}
onClick={() => setActivePanel('Misc')}
>
<IoAccessibilityOutline size={20} className='mr-0' />
@@ -70,7 +70,7 @@ const SettingsDialog: React.FC<{ bookKey: string; config: BookConfig }> = ({ boo
</div>
</div>
<div className='mt-2 flex-grow overflow-y-auto px-[10%]'>
<div className='text-base-content mt-2 flex-grow overflow-y-auto px-[10%]'>
{activePanel === 'Font' && <FontPanel bookKey={bookKey} />}
{activePanel === 'Layout' && <LayoutPanel bookKey={bookKey} />}
{activePanel === 'Color' && <ColorPanel bookKey={bookKey} />}