"use client"; import * as React from "react"; import { cn } from "../../lib/utils"; import { ControlItem, ControlList, ControlSection, ControlSectionHeader, PanelTitle, } from "../control-layout"; export type PanelSectionProps = Omit< React.HTMLAttributes, "children" | "title" > & { action?: React.ReactNode; actionGroup?: "primary" | "secondary"; allowCompoundDividers?: boolean; children: React.ReactNode; className?: string; collapsed?: boolean; collapsible?: boolean; flush?: boolean; onCollapsedChange?: (collapsed: boolean) => void; spacing?: "default" | "technical"; title?: React.ReactNode; }; export function PanelSection({ action, actionGroup, allowCompoundDividers, children, className, collapsed = false, collapsible = false, flush = false, onCollapsedChange, spacing = "default", title, ...props }: PanelSectionProps): React.JSX.Element { const isActionSection = actionGroup !== undefined; const childArray = React.Children.toArray(children); const hasTitle = title !== undefined && title !== null && title !== false; const isSectionCollapsed = Boolean(collapsible && collapsed); const shouldRenderInnerDividers = allowCompoundDividers ?? childArray.length > 1; const body = ( {childArray.map((child, index) => ( {child} ))} ); return ( {hasTitle ? ( {title} ) : null} {collapsible ? ( {body} ) : ( body )} ); } function getCompoundDividerPlacement({ childCount, index, shouldRenderInnerDividers, }: { childCount: number; index: number; shouldRenderInnerDividers: boolean; }): "both" | "bottom" | "top" { if (!shouldRenderInnerDividers || childCount <= 1) { return "both"; } if (index === 0) { return "bottom"; } if (index === childCount - 1) { return "top"; } return "both"; } function PanelSectionCollapsibleBody({ collapsed, children, }: { collapsed: boolean; children: React.ReactNode; }): React.JSX.Element | null { const [shouldRender, setShouldRender] = React.useState(!collapsed); const [isVisuallyCollapsed, setIsVisuallyCollapsed] = React.useState(collapsed); const suppressControlTransitions = useInitialPanelSectionControlTransitionSuppression( `${collapsed ? "collapsed" : "expanded"}:${shouldRender ? "mounted" : "unmounted"}`, ); React.useEffect(() => { if (collapsed) { setIsVisuallyCollapsed(true); return; } if (shouldRender) { setIsVisuallyCollapsed(false); return; } setShouldRender(true); setIsVisuallyCollapsed(true); const frame = window.requestAnimationFrame(() => { setIsVisuallyCollapsed(false); }); return () => window.cancelAnimationFrame(frame); }, [collapsed, shouldRender]); function handleTransitionEnd(event: React.TransitionEvent): void { if (event.target !== event.currentTarget || !collapsed) { return; } setShouldRender(false); } if (!shouldRender) { return null; } return (
{children}
); } function useInitialPanelSectionControlTransitionSuppression( dependency: unknown, ): boolean { const [suppressionState, setSuppressionState] = React.useState(() => ({ dependency, isSuppressing: true, })); React.useEffect(() => { setSuppressionState({ dependency, isSuppressing: true }); if (typeof window === "undefined") { return undefined; } if (typeof window.requestAnimationFrame !== "function") { const timeout = window.setTimeout(() => { setSuppressionState({ dependency, isSuppressing: false }); }, 0); return () => window.clearTimeout(timeout); } let secondFrame = 0; const firstFrame = window.requestAnimationFrame(() => { secondFrame = window.requestAnimationFrame(() => { setSuppressionState({ dependency, isSuppressing: false }); }); }); return () => { window.cancelAnimationFrame(firstFrame); window.cancelAnimationFrame(secondFrame); }; }, [dependency]); return ( suppressionState.dependency !== dependency || suppressionState.isSuppressing ); } function getPanelSectionChildKey( child: React.ReactNode, index: number, ): React.Key { return React.isValidElement(child) && child.key !== null ? child.key : index; }