feat: more highlight colours (#3062)

* feat(highlight): extend types and constants for custom hex colours

* feat(highlight): update colour to support hex strings

* refactor(annotator): update component to accept hex colours

* feat(highlight): add colour picker with max 4 custom colors

* feat(settings): add custom colour editor with limit

* refactor: custom highlight colors can only be modified in settings

---------

Co-authored-by: Huang Xin <chrox.huang@gmail.com>
This commit is contained in:
Mohammed Efaz
2026-01-25 12:52:29 +06:00
committed by GitHub
parent aba9e87fc1
commit c31ece6742
7 changed files with 172 additions and 44 deletions
@@ -9,7 +9,15 @@ import { useResponsiveSize } from '@/hooks/useResponsiveSize';
import { saveSysSettings } from '@/helpers/settings';
const styles = ['highlight', 'underline', 'squiggly'] as HighlightStyle[];
const colors = ['red', 'violet', 'blue', 'green', 'yellow'] as HighlightColor[];
const defaultColors = ['red', 'violet', 'blue', 'green', 'yellow'] as HighlightColor[];
const getColorHex = (
customColors: Record<HighlightColor, string>,
color: HighlightColor,
): string => {
if (color.startsWith('#')) return color;
return customColors[color as HighlightColor] ?? color;
};
interface HighlightOptionsProps {
isVertical: boolean;
@@ -41,6 +49,7 @@ const HighlightOptions: React.FC<HighlightOptionsProps> = ({
const einkBgColor = isDarkMode ? '#000000' : '#ffffff';
const einkFgColor = isDarkMode ? '#ffffff' : '#000000';
const customColors = globalReadSettings.customHighlightColors;
const userColors = globalReadSettings.userHighlightColors ?? [];
const [selectedStyle, setSelectedStyle] = useState<HighlightStyle>(_selectedStyle);
const [selectedColor, setSelectedColor] = useState<HighlightColor>(_selectedColor);
const size16 = useResponsiveSize(16);
@@ -55,6 +64,7 @@ const HighlightOptions: React.FC<HighlightOptionsProps> = ({
setSelectedColor(globalReadSettings.highlightStyles[style]);
onHandleHighlight(true);
};
const handleSelectColor = (color: HighlightColor) => {
const newGlobalReadSettings = {
...globalReadSettings,
@@ -65,10 +75,11 @@ const HighlightOptions: React.FC<HighlightOptionsProps> = ({
setSelectedColor(color);
onHandleHighlight(true);
};
return (
<div
className={clsx(
'highlight-options absolute flex items-center justify-between',
'highlight-options absolute flex items-center justify-between gap-4',
isVertical ? 'flex-col' : 'flex-row',
)}
style={{
@@ -106,7 +117,9 @@ const HighlightOptions: React.FC<HighlightOptionsProps> = ({
height: size16,
...(style === 'highlight' &&
selectedStyle === 'highlight' && {
backgroundColor: isEink ? einkFgColor : customColors[selectedColor],
backgroundColor: isEink
? einkFgColor
: getColorHex(customColors, selectedColor),
color: isEink ? einkBgColor : '#d1d5db',
paddingTop: '2px',
}),
@@ -123,7 +136,7 @@ const HighlightOptions: React.FC<HighlightOptionsProps> = ({
selectedStyle === style
? isEink
? einkFgColor
: customColors[selectedColor]
: getColorHex(customColors, selectedColor)
: '#d1d5db',
...(style === 'squiggly' && { textDecorationStyle: 'wavy' }),
}),
@@ -138,31 +151,39 @@ const HighlightOptions: React.FC<HighlightOptionsProps> = ({
<div
className={clsx(
'not-eink:bg-gray-700 eink-bordered flex items-center justify-center gap-2 rounded-3xl',
isVertical ? 'flex-col py-2' : 'flex-row px-2',
'not-eink:bg-gray-700 eink-bordered flex items-center gap-2 rounded-3xl',
isVertical ? 'flex-col overflow-y-auto py-2' : 'flex-row overflow-x-auto px-2',
)}
style={isVertical ? { width: size28 } : { height: size28 }}
style={{
...(isVertical ? { width: size28 } : { height: size28 }),
scrollbarWidth: 'none',
msOverflowStyle: 'none',
}}
>
{colors
{defaultColors
.concat(userColors)
.filter((c) => (isEink ? selectedColor === c : true))
.map((color) => (
<button
key={color}
onClick={() => handleSelectColor(color)}
style={{
width: size16,
height: size16,
backgroundColor: selectedColor !== color ? customColors[color] : 'transparent',
}}
className='rounded-full p-0'
>
{selectedColor === color && (
<FaCheckCircle
size={size16}
style={{ fill: isEink ? einkFgColor : customColors[color] }}
/>
)}
</button>
<div key={color} className='flex items-center justify-center'>
<button
key={color}
onClick={() => handleSelectColor(color)}
style={{
width: size16,
height: size16,
backgroundColor:
selectedColor !== color ? customColors[color] || color : 'transparent',
}}
className='rounded-full p-0'
>
{selectedColor === color && (
<FaCheckCircle
size={size16}
style={{ fill: isEink ? einkFgColor : customColors[color] || color }}
/>
)}
</button>
</div>
))}
</div>
</div>