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}
)}
>
);
}