diff --git a/apps/client/src/common/components/input/colour-input/SwatchPicker.tsx b/apps/client/src/common/components/input/colour-input/SwatchPicker.tsx new file mode 100644 index 000000000..feaa26ddc --- /dev/null +++ b/apps/client/src/common/components/input/colour-input/SwatchPicker.tsx @@ -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 ( +
+ } + hasInput + /> +
+ ); +} diff --git a/apps/client/src/common/components/input/colour-input/SwatchSelect.tsx b/apps/client/src/common/components/input/colour-input/SwatchSelect.tsx index 7f78a7047..bdb4aac7b 100644 --- a/apps/client/src/common/components/input/colour-input/SwatchSelect.tsx +++ b/apps/client/src/common/components/input/colour-input/SwatchSelect.tsx @@ -1,6 +1,7 @@ import { useCallback } from 'react'; import Swatch from './Swatch'; +import SwatchPicker from './SwatchPicker'; import style from './SwatchSelect.module.scss'; @@ -43,6 +44,7 @@ export default function SwatchSelect(props: ColourInputProps) { {colours.map((colour) => ( ))} + ); } diff --git a/apps/client/src/common/components/input/popover-picker/PopoverPicker.module.scss b/apps/client/src/common/components/input/popover-picker/PopoverPicker.module.scss index 9b235a43e..b2ea1f7cb 100644 --- a/apps/client/src/common/components/input/popover-picker/PopoverPicker.module.scss +++ b/apps/client/src/common/components/input/popover-picker/PopoverPicker.module.scss @@ -1,4 +1,7 @@ .swatch { + display: flex; + align-items: center; + justify-content: center; width: 24px; height: 24px; border-radius: 12px; @@ -14,3 +17,23 @@ 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; + } +} diff --git a/apps/client/src/common/components/input/popover-picker/PopoverPicker.tsx b/apps/client/src/common/components/input/popover-picker/PopoverPicker.tsx index 9611e5489..e877ab537 100644 --- a/apps/client/src/common/components/input/popover-picker/PopoverPicker.tsx +++ b/apps/client/src/common/components/input/popover-picker/PopoverPicker.tsx @@ -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 { Popover, PopoverContent, PopoverTrigger } from '@chakra-ui/react'; import { ViewSettings } from 'ontime-types'; @@ -16,19 +17,24 @@ export function PopoverPickerRHF(props: UseControllerProps) { interface PopoverPickerProps { color: string; + icon?: ReactNode; + hasInput?: boolean; onChange: (color: string) => void; } export default function PopoverPicker(props: PopoverPickerProps) { - const { color, onChange } = props; + const { color, icon, hasInput, onChange } = props; return ( -
+
+ {icon ?? null} +
+ {hasInput && } ); diff --git a/apps/client/src/common/hooks/useFadeOutOnInactivity.tsx b/apps/client/src/common/hooks/useFadeOutOnInactivity.tsx index 44e589245..bbd05a40b 100644 --- a/apps/client/src/common/hooks/useFadeOutOnInactivity.tsx +++ b/apps/client/src/common/hooks/useFadeOutOnInactivity.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; -import { debounce } from '../../common/utils/debounce'; +import { throttle } from '../../common/utils/throttle'; export const useFadeOutOnInactivity = () => { const [isMouseMoved, setIsMouseMoved] = useState(false); @@ -16,11 +16,11 @@ export const useFadeOutOnInactivity = () => { fadeOut = setTimeout(() => setIsMouseMoved(false), 3000); }; - const debouncedShowMenu = debounce(setShowMenuTrue, 1000); + const throttledShowMenu = throttle(setShowMenuTrue, 1000); - document.addEventListener('mousemove', debouncedShowMenu); + document.addEventListener('mousemove', throttledShowMenu); return () => { - document.removeEventListener('mousemove', debouncedShowMenu); + document.removeEventListener('mousemove', throttledShowMenu); if (fadeOut) { clearTimeout(fadeOut); } diff --git a/apps/client/src/common/utils/debounce.ts b/apps/client/src/common/utils/debounce.ts index 4b15d83d4..66e2a6896 100644 --- a/apps/client/src/common/utils/debounce.ts +++ b/apps/client/src/common/utils/debounce.ts @@ -1,12 +1,12 @@ -export function debounce(callback: () => void, wait: number) { +export function debounce(callback: (...args: T) => void, wait: number) { let timeout: NodeJS.Timeout | null; - return () => { + return (...args: T) => { if (timeout) { - return; + clearTimeout(timeout); } timeout = setTimeout(() => { timeout = null; - callback(); + callback(...args); }, wait); }; } diff --git a/apps/client/src/common/utils/throttle.ts b/apps/client/src/common/utils/throttle.ts new file mode 100644 index 000000000..469cd8f96 --- /dev/null +++ b/apps/client/src/common/utils/throttle.ts @@ -0,0 +1,12 @@ +export function throttle(callback: (...args: T) => void, wait: number) { + let timeout: NodeJS.Timeout | null; + return (...args: T) => { + if (timeout) { + return; + } + timeout = setTimeout(() => { + timeout = null; + callback(...args); + }, wait); + }; +} diff --git a/apps/client/src/features/operator/Operator.tsx b/apps/client/src/features/operator/Operator.tsx index ef13e3652..57d9278fb 100644 --- a/apps/client/src/features/operator/Operator.tsx +++ b/apps/client/src/features/operator/Operator.tsx @@ -12,7 +12,7 @@ import useCustomFields from '../../common/hooks-query/useCustomFields'; import useProjectData from '../../common/hooks-query/useProjectData'; import useRundown from '../../common/hooks-query/useRundown'; 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 { 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 = () => { if (timeoutId.current) { @@ -97,7 +97,7 @@ export default function Operator() { setShowEditPrompt(true); - debouncedHandleScroll(); + throttledHandleScroll(); }; const handleEdit = useCallback((event: EditEvent) => {