import { Icon, Icons } from "@/components/Icon"; import { Spinner } from "@/components/layout/Spinner"; import { ProgressRing } from "@/components/layout/ProgressRing"; import { createRef, useEffect, useRef } from "react"; interface PopoutListEntryBaseTypes { active?: boolean; children: React.ReactNode; onClick?: () => void; isOnDarkBackground?: boolean; } interface PopoutListEntryTypes extends PopoutListEntryBaseTypes { percentageCompleted?: number; loading?: boolean; errored?: boolean; } interface PopoutListEntryRootTypes extends PopoutListEntryBaseTypes { right?: React.ReactNode; noChevron?: boolean; } interface PopoutListActionTypes extends PopoutListEntryBaseTypes { icon?: Icons; right?: React.ReactNode; download?: string; href?: string; noChevron?: boolean; } interface ScrollToActiveProps { children: React.ReactNode; className?: string; } interface PopoutSectionProps { children?: React.ReactNode; className?: string; } export function ScrollToActive(props: ScrollToActiveProps) { const ref = createRef(); const inited = useRef(false); // Scroll to "active" child on first load (AKA mount except React dumb) useEffect(() => { if (inited.current) return; if (!ref.current) return; const el = ref.current as HTMLDivElement; // Find nearest scroll container, or self const wrapper: HTMLDivElement | null = el.classList.contains( "overflow-y-auto" ) ? el : el.closest(".overflow-y-auto"); const active: HTMLDivElement | null | undefined = wrapper?.querySelector(".active"); if (wrapper && active) { let activeYPositionCentered = 0; const setActiveYPositionCentered = () => { activeYPositionCentered = active.getBoundingClientRect().top - wrapper.getBoundingClientRect().top + active.offsetHeight / 2; }; setActiveYPositionCentered(); if (activeYPositionCentered >= wrapper.offsetHeight / 2) { // Check if the active element is below the vertical center line, then scroll it into center wrapper.scrollTo({ top: activeYPositionCentered - wrapper.offsetHeight / 2, }); } setActiveYPositionCentered(); if (activeYPositionCentered > wrapper.offsetHeight / 2) { // If the element is over the vertical center line, scroll to the end wrapper.scrollTo({ top: wrapper.scrollHeight, }); } } inited.current = true; }, [ref]); return (
{props.children}
); } export function PopoutSection(props: PopoutSectionProps) { return ( {props.children} ); } export function PopoutListEntryBase(props: PopoutListEntryRootTypes) { const bg = props.isOnDarkBackground ? "bg-ash-200" : "bg-ash-400"; const hover = props.isOnDarkBackground ? "hover:bg-ash-200" : "hover:bg-ash-400"; return (
{props.active && (
)} {props.children}
{!props.noChevron && ( )} {props.right}
); } export function PopoutListEntry(props: PopoutListEntryTypes) { return ( {props.errored && ( )} {props.loading && !props.errored && ( )} {props.percentageCompleted && !props.loading && !props.errored ? ( 90 ? 100 : props.percentageCompleted } /> ) : ( "" )} } > {props.children} ); } export function PopoutListAction(props: PopoutListActionTypes) { const entry = (
{props.icon ? : null}
{props.children}
); return props.href ? ( {entry} ) : ( entry ); }