diff --git a/frontend/src/components/AddToPlaylist.tsx b/frontend/src/components/AddToPlaylist.tsx index 5260163..a3b174e 100644 --- a/frontend/src/components/AddToPlaylist.tsx +++ b/frontend/src/components/AddToPlaylist.tsx @@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { Check, ListPlus, Plus, Youtube } from "lucide-react"; import { api, type Playlist } from "../lib/api"; +import { useDismiss } from "../lib/useDismiss"; // A small popover (portaled to body, so the feed grid can't clip it) for toggling a // video's membership in the user's playlists, with inline "new playlist" creation. @@ -59,25 +60,13 @@ export default function AddToPlaylist({ setOpen((o) => !o); } + useDismiss(open, () => setOpen(false), [panelRef, triggerRef]); + // Keep the panel anchored to the trigger as the page resizes/scrolls while open. useEffect(() => { if (!open) return; - function onDoc(e: MouseEvent) { - if ( - !panelRef.current?.contains(e.target as Node) && - !triggerRef.current?.contains(e.target as Node) - ) - setOpen(false); - } - function onKey(e: KeyboardEvent) { - if (e.key === "Escape") setOpen(false); - } - document.addEventListener("mousedown", onDoc); - document.addEventListener("keydown", onKey); window.addEventListener("resize", place); window.addEventListener("scroll", place, true); return () => { - document.removeEventListener("mousedown", onDoc); - document.removeEventListener("keydown", onKey); window.removeEventListener("resize", place); window.removeEventListener("scroll", place, true); }; diff --git a/frontend/src/components/AdminUsers.tsx b/frontend/src/components/AdminUsers.tsx index 078ae9c..356b99d 100644 --- a/frontend/src/components/AdminUsers.tsx +++ b/frontend/src/components/AdminUsers.tsx @@ -6,6 +6,7 @@ import { api, type AdminUserRow, type Invite, type Me } from "../lib/api"; import { notify } from "../lib/notifications"; import { LS } from "../lib/storage"; import Tooltip from "./Tooltip"; +import { Section } from "./ui/form"; import { useConfirm } from "./ConfirmProvider"; import Tabs, { usePersistedTab } from "./Tabs"; @@ -41,15 +42,6 @@ export default function AdminUsers({ me }: { me: Me }) { ); } -function Section({ title, children }: { title: string; children: React.ReactNode }) { - return ( -
{t("users.roles.intro")}
{rows.length === 0 ? ({t("users.roles.empty")}
@@ -266,7 +258,7 @@ function AdminInvites() { const decided = list.filter((i) => i.status !== "pending"); return ( -{t("settings.invites.noPending")}
) : ( @@ -360,7 +352,7 @@ function AdminDemo() { const rows = list.data ?? []; return ( -{t("settings.demo.intro")}
{rows.length > 0 && ( diff --git a/frontend/src/components/Channels.tsx b/frontend/src/components/Channels.tsx index ddcdfa4..385448d 100644 --- a/frontend/src/components/Channels.tsx +++ b/frontend/src/components/Channels.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "react"; +import { useRef, useState } from "react"; import { Trans, useTranslation } from "react-i18next"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { @@ -14,6 +14,7 @@ import { UserMinus, } from "lucide-react"; import { api, HttpError, type ManagedChannel, type Tag } from "../lib/api"; +import { useDismiss } from "../lib/useDismiss"; import { formatEta, formatViews, relativeTime } from "../lib/format"; import { notify } from "../lib/notifications"; import Tooltip from "./Tooltip"; @@ -616,19 +617,7 @@ function TagsCell({ const { t } = useTranslation(); const [open, setOpen] = useState(false); const ref = useRef