refactor(ui): useDismiss hook for outside-click/Escape close

lib/useDismiss.ts replaces the identical mousedown-outside + Escape effect that
DataTable (filter popover), Channels (tag picker) and AddToPlaylist each hand-rolled.
Positioning stays with each caller (it genuinely varies); AddToPlaylist keeps its
own resize/scroll reposition effect.
This commit is contained in:
npeter83 2026-06-29 00:11:44 +02:00
parent f6b9ac2dd1
commit e68e0d3f7a
4 changed files with 40 additions and 43 deletions

View file

@ -1,6 +1,7 @@
import { useEffect, useRef, useState, type ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { ArrowDown, ArrowUp, ChevronLeft, ChevronRight, ListFilter, X } from "lucide-react";
import { useDismiss } from "../lib/useDismiss";
// A reusable, client-side data table: per-column sort + filter (in the header) + pagination,
// with its sort/filter/page state optionally persisted to localStorage so a reload (F5) keeps
@ -121,21 +122,7 @@ export default function DataTable<T>({
setPage(0);
}, [resetFiltersToken]);
useEffect(() => {
if (!openFilter) return;
function onDown(e: MouseEvent) {
if (!popRef.current?.contains(e.target as Node)) setOpenFilter(null);
}
function onKey(e: KeyboardEvent) {
if (e.key === "Escape") setOpenFilter(null);
}
document.addEventListener("mousedown", onDown);
document.addEventListener("keydown", onKey);
return () => {
document.removeEventListener("mousedown", onDown);
document.removeEventListener("keydown", onKey);
};
}, [openFilter]);
useDismiss(!!openFilter, () => setOpenFilter(null), popRef);
const filtered = rows.filter((row) =>
columns.every((col) => {