refactor(webui): share outside-dismiss behavior
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { i18nMessage } from "../i18n/LanguageContext";import { Palette } from "lucide-react";
|
||||
import { Palette } from "lucide-react";
|
||||
import { useEffect, useRef, useState, type InputHTMLAttributes } from "react";
|
||||
import useOutsideDismiss from "../hooks/useOutsideDismiss";
|
||||
import { i18nMessage } from "../i18n/LanguageContext";
|
||||
|
||||
type ColorPickerFieldProps = Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "value" | "onChange"> & {
|
||||
value: string;
|
||||
@@ -20,27 +22,6 @@ function normalizeColor(value: string): string {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function useOutsideClose(open: boolean, onClose: () => void) {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
function handlePointerDown(event: PointerEvent) {
|
||||
if (!ref.current || ref.current.contains(event.target as Node)) return;
|
||||
onClose();
|
||||
}
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
if (event.key === "Escape") onClose();
|
||||
}
|
||||
document.addEventListener("pointerdown", handlePointerDown);
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener("pointerdown", handlePointerDown);
|
||||
document.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, [open, onClose]);
|
||||
return ref;
|
||||
}
|
||||
|
||||
export default function ColorPickerField({
|
||||
value,
|
||||
onChange,
|
||||
@@ -51,7 +32,7 @@ export default function ColorPickerField({
|
||||
...props
|
||||
}: ColorPickerFieldProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const rootRef = useOutsideClose(open, () => setOpen(false));
|
||||
const rootRef = useOutsideDismiss(open, () => setOpen(false));
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
const normalizedValue = normalizeColor(value || "");
|
||||
const previewColor = HEX_PATTERN.test(normalizedValue) ? normalizedValue : "transparent";
|
||||
@@ -102,4 +83,4 @@ export default function ColorPickerField({
|
||||
}
|
||||
</div>);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CalendarDays, ChevronLeft, ChevronRight, Clock } from "lucide-react";
|
||||
import { useEffect, useMemo, useRef, useState, type InputHTMLAttributes } from "react";
|
||||
import useOutsideDismiss from "../hooks/useOutsideDismiss";
|
||||
|
||||
type BaseProps = Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "value" | "onChange" | "min" | "max"> & {
|
||||
value: string;
|
||||
@@ -48,32 +49,11 @@ function combineDateTime(date: string, time: string): string {
|
||||
return `${date || dateString(new Date())}T${time || "00:00"}`;
|
||||
}
|
||||
|
||||
function useOutsideClose(open: boolean, onClose: () => void) {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
function handlePointerDown(event: PointerEvent) {
|
||||
if (!ref.current || ref.current.contains(event.target as Node)) return;
|
||||
onClose();
|
||||
}
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
if (event.key === "Escape") onClose();
|
||||
}
|
||||
document.addEventListener("pointerdown", handlePointerDown);
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener("pointerdown", handlePointerDown);
|
||||
document.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, [open, onClose]);
|
||||
return ref;
|
||||
}
|
||||
|
||||
export function DateField({ value, onChange, min, max, disabled, className = "", placeholder = "i18n:govoplan-core.yyyy_mm_dd.d3f8f7b8", ...props }: BaseProps) {
|
||||
const selectedDate = parseDate(value);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [visibleMonth, setVisibleMonth] = useState<Date>(() => selectedDate ?? new Date());
|
||||
const rootRef = useOutsideClose(open, () => setOpen(false));
|
||||
const rootRef = useOutsideDismiss(open, () => setOpen(false));
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -228,4 +208,4 @@ export function DateTimeField({ value, onChange, min, max, disabled, className =
|
||||
|
||||
}
|
||||
|
||||
export default DateField;
|
||||
export default DateField;
|
||||
|
||||
30
webui/src/hooks/useOutsideDismiss.ts
Normal file
30
webui/src/hooks/useOutsideDismiss.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export default function useOutsideDismiss<TElement extends HTMLElement = HTMLDivElement>(
|
||||
active: boolean,
|
||||
onDismiss: () => void,
|
||||
) {
|
||||
const rootRef = useRef<TElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!active) return;
|
||||
|
||||
function handlePointerDown(event: PointerEvent) {
|
||||
if (!rootRef.current || rootRef.current.contains(event.target as Node)) return;
|
||||
onDismiss();
|
||||
}
|
||||
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
if (event.key === "Escape") onDismiss();
|
||||
}
|
||||
|
||||
document.addEventListener("pointerdown", handlePointerDown);
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener("pointerdown", handlePointerDown);
|
||||
document.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, [active, onDismiss]);
|
||||
|
||||
return rootRef;
|
||||
}
|
||||
Reference in New Issue
Block a user