forked from akai/readest
feat(settings): add custom highlight color picker (#2273)
Add ability to customize highlight colors with hex color picker. Users can now set custom colors for all five highlight styles (red, violet, blue, green, yellow) in the settings panel. Fixes #2271
This commit is contained in:
@@ -142,7 +142,8 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => {
|
||||
const detail = (event as CustomEvent).detail;
|
||||
const { draw, annotation, doc, range } = detail;
|
||||
const { style, color } = annotation as BookNote;
|
||||
const hexColor = color ? HIGHLIGHT_COLOR_HEX[color] : color;
|
||||
const customColors = settings.globalReadSettings.customHighlightColors;
|
||||
const hexColor = color && customColors ? customColors[color] : color ? HIGHLIGHT_COLOR_HEX[color] : color;
|
||||
if (style === 'highlight') {
|
||||
draw(Overlayer.highlight, { color: hexColor });
|
||||
} else if (['underline', 'squiggly'].includes(style as string)) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { FaCheckCircle } from 'react-icons/fa';
|
||||
import { HighlightColor, HighlightStyle } from '@/types/book';
|
||||
import { useSettingsStore } from '@/store/settingsStore';
|
||||
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
|
||||
import { DEFAULT_CUSTOM_HIGHLIGHT_COLORS } from '@/services/constants';
|
||||
|
||||
const styles = ['highlight', 'underline', 'squiggly'] as HighlightStyle[];
|
||||
const colors = ['red', 'violet', 'blue', 'green', 'yellow'] as HighlightColor[];
|
||||
@@ -25,6 +26,7 @@ const HighlightOptions: React.FC<HighlightOptionsProps> = ({
|
||||
}) => {
|
||||
const { settings, setSettings } = useSettingsStore();
|
||||
const globalReadSettings = settings.globalReadSettings;
|
||||
const customColors = globalReadSettings.customHighlightColors || DEFAULT_CUSTOM_HIGHLIGHT_COLORS;
|
||||
const [selectedStyle, setSelectedStyle] = React.useState<HighlightStyle>(_selectedStyle);
|
||||
const [selectedColor, setSelectedColor] = React.useState<HighlightColor>(_selectedColor);
|
||||
const size16 = useResponsiveSize(16);
|
||||
@@ -65,24 +67,26 @@ const HighlightOptions: React.FC<HighlightOptionsProps> = ({
|
||||
style={{ width: size28, height: size28, minHeight: size28 }}
|
||||
>
|
||||
<div
|
||||
style={{ width: size16, height: style === 'squiggly' ? size18 : size16 }}
|
||||
className={clsx(
|
||||
'w-4 p-0 text-center leading-none',
|
||||
style === 'highlight' &&
|
||||
(selectedStyle === 'highlight'
|
||||
? `bg-${selectedColor}-300 pt-[2px]`
|
||||
: `bg-gray-300 pt-[2px]`),
|
||||
(style === 'underline' || style === 'squiggly') &&
|
||||
'text-gray-300 underline decoration-2',
|
||||
style === 'underline' &&
|
||||
(selectedStyle === 'underline'
|
||||
? `decoration-${selectedColor}-300`
|
||||
: `decoration-gray-300`),
|
||||
style === 'squiggly' &&
|
||||
(selectedStyle === 'squiggly'
|
||||
? `decoration-wavy decoration-${selectedColor}-300`
|
||||
: `decoration-gray-300 decoration-wavy`),
|
||||
)}
|
||||
style={{
|
||||
width: size16,
|
||||
height: style === 'squiggly' ? size18 : size16,
|
||||
...(style === 'highlight' && selectedStyle === 'highlight' && {
|
||||
backgroundColor: customColors[selectedColor],
|
||||
paddingTop: '2px'
|
||||
}),
|
||||
...(style === 'highlight' && selectedStyle !== 'highlight' && {
|
||||
backgroundColor: '#d1d5db',
|
||||
paddingTop: '2px'
|
||||
}),
|
||||
...((style === 'underline' || style === 'squiggly') && {
|
||||
color: '#d1d5db',
|
||||
textDecoration: 'underline',
|
||||
textDecorationThickness: '2px',
|
||||
textDecorationColor: selectedStyle === style ? customColors[selectedColor] : '#d1d5db',
|
||||
...(style === 'squiggly' && { textDecorationStyle: 'wavy' })
|
||||
})
|
||||
}}
|
||||
className='w-4 p-0 text-center leading-none'
|
||||
>
|
||||
A
|
||||
</div>
|
||||
@@ -101,11 +105,15 @@ const HighlightOptions: React.FC<HighlightOptionsProps> = ({
|
||||
<button
|
||||
key={color}
|
||||
onClick={() => handleSelectColor(color)}
|
||||
style={{ width: size16, height: size16 }}
|
||||
className={clsx(`rounded-full p-0`, selectedColor !== color && `bg-${color}-300`)}
|
||||
style={{
|
||||
width: size16,
|
||||
height: size16,
|
||||
backgroundColor: selectedColor !== color ? customColors[color] : 'transparent'
|
||||
}}
|
||||
className='rounded-full p-0'
|
||||
>
|
||||
{selectedColor === color && (
|
||||
<FaCheckCircle size={size16} className={clsx(`fill-${color}-300`)} />
|
||||
<FaCheckCircle size={size16} style={{ fill: customColors[color] }} />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
|
||||
@@ -5,9 +5,11 @@ type ColorInputProps = {
|
||||
label: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
compact?: boolean;
|
||||
pickerPosition?: 'left' | 'center' | 'right';
|
||||
};
|
||||
|
||||
const ColorInput: React.FC<ColorInputProps> = ({ label, value, onChange }) => {
|
||||
const ColorInput: React.FC<ColorInputProps> = ({ label, value, onChange, compact = false, pickerPosition = 'left' }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const pickerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -29,6 +31,44 @@ const ColorInput: React.FC<ColorInputProps> = ({ label, value, onChange }) => {
|
||||
onChange(colorResult.hex);
|
||||
};
|
||||
|
||||
const getPickerPositionClass = () => {
|
||||
if (pickerPosition === 'right') {
|
||||
return 'right-0';
|
||||
} else if (pickerPosition === 'center') {
|
||||
return 'left-1/2 -translate-x-1/2';
|
||||
}
|
||||
return 'left-0';
|
||||
};
|
||||
|
||||
if (compact) {
|
||||
return (
|
||||
<div className='relative'>
|
||||
<input
|
||||
type='text'
|
||||
value={value}
|
||||
spellCheck={false}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className='bg-base-100 text-base-content border-base-200/75 w-16 cursor-pointer rounded border px-1 py-0.5 text-center font-mono text-xs'
|
||||
/>
|
||||
|
||||
{isOpen && (
|
||||
<div
|
||||
ref={pickerRef}
|
||||
className={`absolute top-full z-50 mt-1 ${getPickerPositionClass()}`}
|
||||
>
|
||||
<SketchPicker
|
||||
width='200px'
|
||||
color={value}
|
||||
onChange={handlePickerChange}
|
||||
disableAlpha={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mb-3'>
|
||||
<label className='mb-1 block text-sm font-medium'>{label}</label>
|
||||
|
||||
@@ -27,6 +27,9 @@ import { useFileSelector } from '@/hooks/useFileSelector';
|
||||
import { PREDEFINED_TEXTURES } from '@/styles/textures';
|
||||
import Select from '@/components/Select';
|
||||
import ThemeEditor from './ThemeEditor';
|
||||
import ColorInput from './ColorInput';
|
||||
import { HighlightColor } from '@/types/book';
|
||||
import { DEFAULT_CUSTOM_HIGHLIGHT_COLORS } from '@/services/constants';
|
||||
|
||||
const ColorPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset }) => {
|
||||
const _ = useTranslation();
|
||||
@@ -53,6 +56,10 @@ const ColorPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
|
||||
const [backgroundOpacity, setBackgroundOpacity] = useState(viewSettings.backgroundOpacity);
|
||||
const [backgroundSize, setBackgroundSize] = useState(viewSettings.backgroundSize);
|
||||
|
||||
const [customHighlightColors, setCustomHighlightColors] = useState<Record<HighlightColor, string>>(
|
||||
settings.globalReadSettings.customHighlightColors || DEFAULT_CUSTOM_HIGHLIGHT_COLORS,
|
||||
);
|
||||
|
||||
const {
|
||||
textures: customTextures,
|
||||
addTexture,
|
||||
@@ -78,6 +85,7 @@ const ColorPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
|
||||
setSelectedTextureId('none');
|
||||
setBackgroundOpacity(0.4);
|
||||
setBackgroundSize('2048px');
|
||||
setCustomHighlightColors(DEFAULT_CUSTOM_HIGHLIGHT_COLORS);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -433,6 +441,37 @@ const ColorPanel: React.FC<SettingsPanelPanelProp> = ({ bookKey, onRegisterReset
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 className='mb-2 font-medium'>{_('Custom Highlight Colors')}</h2>
|
||||
<div className='card border-base-200 bg-base-100 border p-4 shadow overflow-visible'>
|
||||
<div className='flex items-center justify-around gap-2'>
|
||||
{(['red', 'violet', 'blue', 'green', 'yellow'] as HighlightColor[]).map((color, index, array) => {
|
||||
const position = index === 0 ? 'left' : index === array.length - 1 ? 'right' : 'center';
|
||||
return (
|
||||
<div key={color} className='flex flex-col items-center gap-2'>
|
||||
<div
|
||||
className='h-8 w-8 rounded-full border-2 border-base-300 shadow-sm'
|
||||
style={{ backgroundColor: customHighlightColors[color] }}
|
||||
/>
|
||||
<ColorInput
|
||||
label=''
|
||||
value={customHighlightColors[color]}
|
||||
compact={true}
|
||||
pickerPosition={position}
|
||||
onChange={(value: string) => {
|
||||
customHighlightColors[color] = value;
|
||||
setCustomHighlightColors({ ...customHighlightColors });
|
||||
settings.globalReadSettings.customHighlightColors = customHighlightColors;
|
||||
setSettings(settings);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='w-full'>
|
||||
<h2 className='mb-2 font-medium'>{_('Code Highlighting')}</h2>
|
||||
<div className='card border-base-200 bg-base-100 border shadow'>
|
||||
|
||||
@@ -78,6 +78,14 @@ export const DEFAULT_SYSTEM_SETTINGS: Partial<SystemSettings> = {
|
||||
lastSyncedAtNotes: 0,
|
||||
};
|
||||
|
||||
export const DEFAULT_CUSTOM_HIGHLIGHT_COLORS: Record<HighlightColor, string> = {
|
||||
red: '#fca5a5', // red-300
|
||||
yellow: '#fde047', // yellow-300
|
||||
green: '#86efac', // green-300
|
||||
blue: '#93c5fd', // blue-300
|
||||
violet: '#c4b5fd', // violet-300
|
||||
};
|
||||
|
||||
export const DEFAULT_READSETTINGS: ReadSettings = {
|
||||
sideBarWidth: '15%',
|
||||
isSideBarPinned: true,
|
||||
@@ -94,6 +102,7 @@ export const DEFAULT_READSETTINGS: ReadSettings = {
|
||||
underline: 'green',
|
||||
squiggly: 'blue',
|
||||
},
|
||||
customHighlightColors: DEFAULT_CUSTOM_HIGHLIGHT_COLORS,
|
||||
};
|
||||
|
||||
export const DEFAULT_MOBILE_READSETTINGS: Partial<ReadSettings> = {
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface ReadSettings {
|
||||
|
||||
highlightStyle: HighlightStyle;
|
||||
highlightStyles: Record<HighlightStyle, HighlightColor>;
|
||||
customHighlightColors: Record<HighlightColor, string>;
|
||||
customThemes: CustomTheme[];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user