refactor: event editor color picker (#1400)

* refactor: event editor color picker

* refactor: throttle and debounce function signature
This commit is contained in:
Shobhit Nagpal
2024-12-27 15:27:48 +05:30
committed by GitHub
parent 7f39d035fd
commit 6bbf8ce795
8 changed files with 111 additions and 14 deletions
@@ -0,0 +1,54 @@
import { useCallback } from 'react';
import { IoEyedrop } from '@react-icons/all-files/io5/IoEyedrop';
import Color from 'color';
import PopoverPicker from '../../../../common/components/input/popover-picker/PopoverPicker';
import { debounce } from '../../../../common/utils/debounce';
import { cx } from '../../../utils/styleUtils';
import style from './SwatchSelect.module.scss';
interface SwatchPickerProps {
color: string;
isSelected?: boolean;
onChange: (name: string) => void;
}
const getIconColor = (color: string, isSelected: boolean) => {
if (isSelected) {
try {
const isLight = Color(color).isLight();
return isLight ? '#000000' : '#ffffff';
} catch (_error) {
/* we are not handling the error here */
}
}
return '#ffffff';
};
export default function SwatchPicker(props: SwatchPickerProps) {
const { color, onChange, isSelected } = props;
const classes = cx([style.swatch, isSelected ? style.selected : null, style.selectable]);
const iconColor = getIconColor(color, isSelected ?? false);
const debouncedOnChange = useCallback(
debounce((newValue: string) => {
onChange(newValue);
}, 500),
[onChange],
);
return (
<div className={classes}>
<PopoverPicker
color={isSelected ? color : ''}
onChange={debouncedOnChange}
icon={<IoEyedrop color={iconColor} />}
hasInput
/>
</div>
);
}
@@ -1,6 +1,7 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import Swatch from './Swatch'; import Swatch from './Swatch';
import SwatchPicker from './SwatchPicker';
import style from './SwatchSelect.module.scss'; import style from './SwatchSelect.module.scss';
@@ -43,6 +44,7 @@ export default function SwatchSelect(props: ColourInputProps) {
{colours.map((colour) => ( {colours.map((colour) => (
<Swatch key={colour} color={colour} onClick={setColour} isSelected={value === colour} /> <Swatch key={colour} color={colour} onClick={setColour} isSelected={value === colour} />
))} ))}
<SwatchPicker color={value} onChange={setColour} isSelected={!colours.includes(value)} />
</div> </div>
); );
} }
@@ -1,4 +1,7 @@
.swatch { .swatch {
display: flex;
align-items: center;
justify-content: center;
width: 24px; width: 24px;
height: 24px; height: 24px;
border-radius: 12px; border-radius: 12px;
@@ -14,3 +17,23 @@
box-shadow: 0 0 0 1px $blue-300; box-shadow: 0 0 0 1px $blue-300;
} }
.input {
color: $gray-200;
border: 1px solid transparent;
border-radius: 0 3px;
background-color: $gray-1200;
padding: 0 0.5rem;
font-size: 1rem;
height: 2rem;
outline: none;
&:hover {
background-color: $gray-1100;
}
&:focus {
background-color: $gray-1000;
color: $gray-50;
border: 1px solid $blue-500;
}
}
@@ -1,4 +1,5 @@
import { HexAlphaColorPicker } from 'react-colorful'; import { ReactNode } from 'react';
import { HexAlphaColorPicker, HexColorInput } from 'react-colorful';
import { useController, UseControllerProps } from 'react-hook-form'; import { useController, UseControllerProps } from 'react-hook-form';
import { Popover, PopoverContent, PopoverTrigger } from '@chakra-ui/react'; import { Popover, PopoverContent, PopoverTrigger } from '@chakra-ui/react';
import { ViewSettings } from 'ontime-types'; import { ViewSettings } from 'ontime-types';
@@ -16,19 +17,24 @@ export function PopoverPickerRHF(props: UseControllerProps<ViewSettings>) {
interface PopoverPickerProps { interface PopoverPickerProps {
color: string; color: string;
icon?: ReactNode;
hasInput?: boolean;
onChange: (color: string) => void; onChange: (color: string) => void;
} }
export default function PopoverPicker(props: PopoverPickerProps) { export default function PopoverPicker(props: PopoverPickerProps) {
const { color, onChange } = props; const { color, icon, hasInput, onChange } = props;
return ( return (
<Popover> <Popover>
<PopoverTrigger> <PopoverTrigger>
<div className={style.swatch} style={{ backgroundColor: color }} /> <div className={style.swatch} style={{ backgroundColor: color }}>
{icon ?? null}
</div>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent style={{ width: 'auto' }}> <PopoverContent style={{ width: 'auto' }}>
<HexAlphaColorPicker color={color} onChange={onChange} /> <HexAlphaColorPicker color={color} onChange={onChange} />
{hasInput && <HexColorInput color={color} onChange={onChange} className={style.input} prefixed />}
</PopoverContent> </PopoverContent>
</Popover> </Popover>
); );
@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { debounce } from '../../common/utils/debounce'; import { throttle } from '../../common/utils/throttle';
export const useFadeOutOnInactivity = () => { export const useFadeOutOnInactivity = () => {
const [isMouseMoved, setIsMouseMoved] = useState(false); const [isMouseMoved, setIsMouseMoved] = useState(false);
@@ -16,11 +16,11 @@ export const useFadeOutOnInactivity = () => {
fadeOut = setTimeout(() => setIsMouseMoved(false), 3000); fadeOut = setTimeout(() => setIsMouseMoved(false), 3000);
}; };
const debouncedShowMenu = debounce(setShowMenuTrue, 1000); const throttledShowMenu = throttle(setShowMenuTrue, 1000);
document.addEventListener('mousemove', debouncedShowMenu); document.addEventListener('mousemove', throttledShowMenu);
return () => { return () => {
document.removeEventListener('mousemove', debouncedShowMenu); document.removeEventListener('mousemove', throttledShowMenu);
if (fadeOut) { if (fadeOut) {
clearTimeout(fadeOut); clearTimeout(fadeOut);
} }
+4 -4
View File
@@ -1,12 +1,12 @@
export function debounce(callback: () => void, wait: number) { export function debounce<T extends any[]>(callback: (...args: T) => void, wait: number) {
let timeout: NodeJS.Timeout | null; let timeout: NodeJS.Timeout | null;
return () => { return (...args: T) => {
if (timeout) { if (timeout) {
return; clearTimeout(timeout);
} }
timeout = setTimeout(() => { timeout = setTimeout(() => {
timeout = null; timeout = null;
callback(); callback(...args);
}, wait); }, wait);
}; };
} }
+12
View File
@@ -0,0 +1,12 @@
export function throttle<T extends any[]>(callback: (...args: T) => void, wait: number) {
let timeout: NodeJS.Timeout | null;
return (...args: T) => {
if (timeout) {
return;
}
timeout = setTimeout(() => {
timeout = null;
callback(...args);
}, wait);
};
}
@@ -12,7 +12,7 @@ import useCustomFields from '../../common/hooks-query/useCustomFields';
import useProjectData from '../../common/hooks-query/useProjectData'; import useProjectData from '../../common/hooks-query/useProjectData';
import useRundown from '../../common/hooks-query/useRundown'; import useRundown from '../../common/hooks-query/useRundown';
import useSettings from '../../common/hooks-query/useSettings'; import useSettings from '../../common/hooks-query/useSettings';
import { debounce } from '../../common/utils/debounce'; import { throttle } from '../../common/utils/throttle';
import { getDefaultFormat } from '../../common/utils/time'; import { getDefaultFormat } from '../../common/utils/time';
import { getPropertyValue, isStringBoolean } from '../viewers/common/viewUtils'; import { getPropertyValue, isStringBoolean } from '../viewers/common/viewUtils';
@@ -85,7 +85,7 @@ export default function Operator() {
} }
} }
}; };
const debouncedHandleScroll = debounce(handleUserScroll, 1000); const throttledHandleScroll = throttle(handleUserScroll, 1000);
const handleScroll = () => { const handleScroll = () => {
if (timeoutId.current) { if (timeoutId.current) {
@@ -97,7 +97,7 @@ export default function Operator() {
setShowEditPrompt(true); setShowEditPrompt(true);
debouncedHandleScroll(); throttledHandleScroll();
}; };
const handleEdit = useCallback((event: EditEvent) => { const handleEdit = useCallback((event: EditEvent) => {