import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { qk } from "../../lib/queryKeys"; import { api } from "../../lib/api"; import ChannelAboutContent from "../../features/channel/ChannelAbout"; import { PopoverSurface } from "../../components/ui/AnchoredPopover"; import { useAnchoredPopover } from "../../components/ui/anchoredPopoverHooks"; import { SURFACE } from "../../components/ui/surfaces"; /** * A channel-description cell: one truncated line in the table, revealing the FULL channel "About" * (the same content as the detail page's About tab — description, links, country, language, topics, * keywords) in a glass hover card. The detail is fetched ON DEMAND on hover/focus, sharing the * ["channel", id] react-query cache with the channel page, so the list stays light. The card is * interactive (scrollable, links clickable) — the platform's safePolygon lets the cursor travel the * gap, replacing the hand-rolled 160 ms hide-timer bridge. Shared by the subscribed + discovery * tables. * * The card keeps the platform's default hover role (`tooltip`); making this rich, interactive panel * properly keyboard-reachable belongs to the focus/a11y work, not to the mechanical migration. */ // How long the pointer rests on a cell before its card opens. const ABOUT_REST_MS = 500; export default function AboutCell({ text, channelId }: { text: string | null; channelId: string }) { const preview = (text ?? "").trim(); const [open, setOpen] = useState(false); // Opened by the keyboard's focus: the scroll that brings a focused cell into view is that focus's // own, and would put the card away the moment it opened — so only a pointer's card goes on a scroll. const [byFocus, setByFocus] = useState(false); const pop = useAnchoredPopover({ open: open && !!preview, onOpenChange: (next, _event, reason) => { setByFocus(next && reason === "focus"); setOpen(next); }, mode: "hover", offset: 6, // Opens once the pointer rests on the cell, not as it crosses one: scrolling the table under a // still pointer, or moving down the column, used to pop card after card. The close grace lets // the cursor reach the card; a scroll puts an open one away. hoverDelay: { open: ABOUT_REST_MS, close: 160 }, dismissOnScroll: !byFocus, // Cap the panel to the space between the trigger and the viewport edge (publishes --fit-max-h) so a // long About never runs its top off-screen — at high browser zoom the short viewport made a fixed // max-height overflow above the top edge, where the scroll container can't reach. fitHeight: true, }); const { setReference } = pop.refs; const { data: ch } = useQuery({ queryKey: qk.channel(channelId), queryFn: () => api.channelDetail(channelId), enabled: open && !!channelId, staleTime: 5 * 60_000, }); if (!preview) return —; return ( <> {preview} {/* Full About once the detail loads; until then the list's truncated description. */} {ch ? ( ) : (

{preview}

)}
); }