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 (
+
+ );
+}
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) => {