"use client"; import * as React from "react"; import { cn } from "../../lib/utils"; import { PanelCollapsibleBodySurface } from "./panel-surface"; import { ControlItem, ControlList, ControlSection, ControlSectionHeader, PanelTitle, } from "../control-layout"; import { cancelBrowserAnimationFrame, requestBrowserAnimationFrame, scheduleBrowserAnimationFrames, } from "../primitives/browser-transport"; import type { SafeComposedHostElementProps } from "../primitives/sanitize-composed-host-props"; export type PanelSectionProps = Omit< SafeComposedHostElementProps<"section">, "children" | "title" > & { action?: React.ReactNode; actionGroup?: "primary" | "secondary"; allowCompoundDividers?: boolean; children: React.ReactNode; className?: string; collapsed?: boolean; collapsible?: boolean; description?: string; flush?: boolean; onCollapsedChange?: (collapsed: boolean) => void; spacing?: "default" | "technical"; title?: React.ReactNode; }; export function PanelSection({ action, actionGroup, allowCompoundDividers, children, className, collapsed = false, collapsible = false, description, 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 = requestBrowserAnimationFrame(() => { setIsVisuallyCollapsed(false); }); return () => cancelBrowserAnimationFrame(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 }); return scheduleBrowserAnimationFrames( () => setSuppressionState({ dependency, isSuppressing: false }), 2, ); }, [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; }