"use client";
import * as React from "react";
import { ControlItem, ControlList, ControlSection } from "../control-layout";
import { cn } from "../../lib/utils";
import { PanelHeader } from "./panel-header";
import { PanelContentSurface, PanelSurface } from "./panel-surface";
import { PanelSection } from "./panel-section";
export type PanelProps = {
children: React.ReactNode;
className?: string;
collapsed?: boolean;
contentTransitionSuppressionKey?: unknown;
defaultCollapsed?: boolean;
onCollapsedChange?: (collapsed: boolean) => void;
onResetControls?: () => void;
stickyFooterActive?: boolean;
stickyFooterProgress?: number | null;
title: string;
};
export function Panel({
children,
className,
collapsed,
contentTransitionSuppressionKey,
defaultCollapsed = false,
onCollapsedChange,
onResetControls = noop,
stickyFooterActive = false,
stickyFooterProgress = null,
title,
}: PanelProps): React.JSX.Element {
const [internalCollapsed, setInternalCollapsed] =
React.useState(defaultCollapsed);
const resolvedCollapsed = collapsed ?? internalCollapsed;
const hasPanelContentSections = hasDirectPanelContentSections(children);
const panelContent = hasPanelContentSections ? (
children
) : (
{children}
);
const { bodyChildren, stickyFooterChildren } =
splitPanelContentStickyFooter(panelContent);
const suppressContentTransitions = useInitialPanelContentTransitionSuppression(
contentTransitionSuppressionKey === undefined
? resolvedCollapsed
: `${resolvedCollapsed}:${String(contentTransitionSuppressionKey)}`,
);
function toggleCollapsed(): void {
const nextCollapsed = !resolvedCollapsed;
setInternalCollapsed(nextCollapsed);
onCollapsedChange?.(nextCollapsed);
}
return (
{resolvedCollapsed ? null : (
0 ? stickyFooterChildren : undefined
}
stickyFooterActive={stickyFooterActive}
stickyFooterProgress={stickyFooterProgress}
>
{bodyChildren}
)}
);
}
function noop(): void {}
function useInitialPanelContentTransitionSuppression(
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 hasDirectPanelContentSections(children: React.ReactNode): boolean {
return React.Children.toArray(children).some(isPanelContentSection);
}
function isPanelContentSection(child: React.ReactNode): boolean {
if (!React.isValidElement>(child)) {
return false;
}
if (child.type === React.Fragment) {
const props = child.props as { children?: React.ReactNode };
return hasDirectPanelContentSections(props.children);
}
return (
child.type === ControlSection ||
child.type === PanelSection ||
child.props["data-toolcraft-panel-section"] !== undefined
);
}
function splitPanelContentStickyFooter(children: React.ReactNode): {
bodyChildren: React.ReactNode[];
stickyFooterChildren: React.ReactNode[];
} {
const childArray = React.Children.toArray(children);
let stickyFooterStartIndex = childArray.length;
while (
stickyFooterStartIndex > 0 &&
isPanelStickyFooterSection(childArray[stickyFooterStartIndex - 1])
) {
stickyFooterStartIndex -= 1;
}
return {
bodyChildren: childArray.slice(0, stickyFooterStartIndex),
stickyFooterChildren: childArray.slice(stickyFooterStartIndex),
};
}
function isPanelStickyFooterSection(child: React.ReactNode): boolean {
if (!React.isValidElement>(child)) {
return false;
}
return (
child.props["data-toolcraft-section-actions"] !== undefined ||
child.props.actionGroup !== undefined
);
}
function ImplicitPanelSection({
children,
}: {
children: React.ReactNode;
}): React.JSX.Element {
const childArray = React.Children.toArray(children);
const shouldRenderInnerDividers = childArray.length > 1;
return (
{childArray.map((child, index) => (
{child}
))}
);
}
function getPanelChildKey(child: React.ReactNode, index: number): React.Key {
return React.isValidElement(child) && child.key !== null ? child.key : index;
}