From 5ecd8b41e50804195720729f26d120f9c2e97b32 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Tue, 18 Nov 2025 19:05:20 +0200 Subject: [PATCH] client/note color picker menu item: add support to select a custom color --- .../custom-items/NoteColorPickerMenuItem.tsx | 77 +++++++++++++++++-- 1 file changed, 72 insertions(+), 5 deletions(-) diff --git a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx index 60757aa15..14a908cc8 100644 --- a/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx +++ b/apps/client/src/menus/custom-items/NoteColorPickerMenuItem.tsx @@ -1,5 +1,6 @@ import "./NoteColorPickerMenuItem.css"; -import { useEffect, useState } from "preact/hooks"; +import { useEffect, useRef, useState} from "preact/hooks"; +import {ComponentChildren} from "preact"; import attributes from "../../services/attributes"; import FNote from "../../entities/fnote"; import froca from "../../services/froca"; @@ -55,21 +56,87 @@ export default function NoteColorPickerMenuItem(props: NoteColorPickerMenuItemPr color={color} isSelected={(color === currentColor)} isDisabled={(note === null)} - onClick={() => onColorCellClicked(color)} /> + onSelect={() => onColorCellClicked(color)} /> ))} + + } interface ColorCellProps { + children?: ComponentChildren, + className?: string; color: string | null, isSelected: boolean, isDisabled?: boolean, - onClick?: () => void + onSelect?: (color: string | null) => void } function ColorCell(props: ColorCellProps) { - return
+ onClick={() => props.onSelect?.(props.color)}> + {props.children}
; +} + +function CustomColorCell(props: ColorCellProps) { + const colorInput = useRef(null); + let colorInputDebouncer: Debouncer; + + useEffect(() => { + colorInputDebouncer = new Debouncer(500, (color) => { + props.onSelect?.(color); + }); + + return () => { + colorInputDebouncer.destroy(); + } + }); + + return <> + {colorInput.current?.click()}}> + + {colorInputDebouncer.updateValue(colorInput.current?.value ?? null)}} + style="width: 0; height: 0; opacity: 0" /> + + +} + +type DebouncerCallback = (value: T) => void; + +class Debouncer { + + private debounceInterval: number; + private callback: DebouncerCallback; + private lastValue: T | undefined; + private timeoutId: any | null = null; + + constructor(debounceInterval: number, onUpdate: DebouncerCallback) { + this.debounceInterval = debounceInterval; + this.callback = onUpdate; + } + + updateValue(value: T) { + this.lastValue = value; + this.destroy(); + this.timeoutId = setTimeout(this.reportUpdate.bind(this), this.debounceInterval); + } + + destroy() { + if (this.timeoutId !== null) { + clearTimeout(this.timeoutId); + } + } + + private reportUpdate() { + if (this.lastValue !== undefined) { + this.callback(this.lastValue); + } + } } \ No newline at end of file