import { ComponentPropsWithoutRef, RefObject } from 'react';
import { BoxBackgroundColor } from '@viasat/beam-shared/components/box';
import { PanelDividerColor, PanelDividerColors } from '@viasat/beam-shared/components/panel';
export type { BoxBackgroundColor, PanelDividerColor };
export { PanelDividerColors };
export type PanelKind = 'inline' | 'overlay';
export type ResolvedPanelOffset = {
x: string;
y: string;
};
export type PanelPosition = 'start' | 'end' | 'bottom';
export type PanelModalType = 'nonModal' | 'isModal' | 'alert';
export type PanelWidth = 'sm' | 'md' | 'lg' | (string & {});
export type PanelHeight = string;
export type PanelPadding = 'sm' | 'md';
/**
* Configuration for resizable panels.
* Pass `true` to use defaults, or an object for custom constraints.
*/
export type PanelResizable = boolean | {
/** Minimum panel size in px. Default: 165 for start/end, 100 for bottom. */
minSize?: number;
/** Maximum panel size in px. Default: 50% viewport width for start/end, 90% viewport height for bottom. */
maxSize?: number;
/** localStorage key for persisting the panel's last-used size across sessions. */
storageKey?: string;
};
/**
* The reason a Panel close was requested via user interaction.
*
* - `'escapeKey'` — user pressed Escape (overlay panels only)
* - `'outsidePress'` — user clicked outside the panel (overlay panels only)
* - `'closeButton'` — user clicked the header close button
* - `'programmatic'` — close was triggered by another interaction not covered above
*/
export type PanelCloseReason = 'escapeKey' | 'outsidePress' | 'closeButton' | 'programmatic';
interface BasePanelProps extends ComponentPropsWithoutRef<'aside'> {
/**
* Specify the content of the Panel
*/
'children': React.ReactNode;
/**
* Accessible name for the panel dialog, required when `Panel.Header.Heading` is not rendered.
*
* With a heading present, `aria-labelledby` points to it automatically; without one, a
* `role="dialog"` needs this so screen readers can announce the Panel.
*
* @example
* ```tsx
*
* ```
*/
'aria-label'?: string;
/**
* Controls the Panel's visibility. For inline panels, omitting this prop
* renders the panel as always-visible (uncontrolled).
*/
'open'?: boolean;
/**
* Callback when Panel open state changes
*/
'onOpenChange'?: (open: boolean) => void;
/**
* Width of a side Panel.
*
* Applies only when `position` is `"start"` or `"end"`. Has no effect on
* `position="bottom"` panels; use `height` for bottom panels instead.
*
* Use a named preset (`"sm"`, `"md"`, `"lg"`) or any valid CSS width value
* such as `"480px"` or `"30vw"`.
*
* When omitted, the Panel uses the `"md"` width preset.
* @default 'md'
*/
'width'?: PanelWidth;
/**
* Height of a bottom Panel.
*
* Applies only when `position="bottom"`. Has no effect on side panels
* (`position="start"` or `position="end"`); use `width` for those instead.
*
* Use any valid CSS height value, such as `"50dvh"` or `"480px"`.
* When omitted, the Panel sizes to its content and is capped at `90dvh`.
* Avoid `height="auto"` because it removes the `90dvh` viewport cap.
*/
'height'?: PanelHeight;
/**
* Specify the horizontal padding
* sm=1rem, md=1.5rem
* @default 'md'
*/
'padding'?: PanelPadding;
/**
* Specify whether to show the header close button
* @default true
*/
'dismissible'?: boolean;
/**
* Label for the close button. Used as both the button's `aria-label`
* and the visible tooltip text. Provide a value that works in both contexts.
* @default 'Close panel'
*/
'closeButtonAriaLabel'?: string;
/**
* Specify whether to animate the Panel as it opens and closes
* @default true
*/
'isAnimated'?: boolean;
/**
* Specify whether to show an external divider between panel and page content
* @default true
*/
'divider'?: boolean;
/**
* Specify the background color for all panel surfaces (Header, Body, Footer).
* A `backgroundColor` set on a section takes priority.
*/
'backgroundColor'?: BoxBackgroundColor;
/**
* Specify the border color of the external divider between the panel and page content. Also sets
* the default divider color for the Header and Footer; a `dividerColor` set on a
* section takes priority.
* @default '01'
*/
'dividerColor'?: PanelDividerColor;
/**
* Specify where the Panel is anchored within its container
* @default 'start'
*/
'position'?: PanelPosition;
/**
* Enables user-resizing via a drag handle at the panel edge.
* Pass `true` to use default min/max constraints, or an object to set custom
* values in pixels.
*
* Provide a `storageKey` in the config object to persist the panel's last-used size across sessions.
* @default false
*/
'resizable'?: PanelResizable;
/**
* Runs before a user-initiated close and acts as a gate: return `false` (or
* `Promise`) to keep the Panel open, for example when a form has unsaved changes.
*
* It only fires for UI-triggered closes (close button, Escape, or outside press), never
* when the parent changes the `open` prop programmatically. Under `modalType="alert"`,
* the `'escapeKey'` and `'outsidePress'` reasons are suppressed, so it won't fire for
* those either.
*
* @example
* ```tsx
* // Drive a confirmation dialog from state; avoid window.confirm (blocking, breaks iframes).
* // See the "Preserving and Guarding State" story for a full reference implementation.
* {
* if (!isDirty) return true;
* return new Promise(resolve => {
* setResolveClose(() => resolve);
* setShowConfirmDialog(true);
* });
* }}
* />
* ```
*/
'onBeforeClose'?: (reason: PanelCloseReason) => boolean | Promise;
}
export interface InlinePanelProps extends BasePanelProps {
/**
* Render the Panel inline: it sits in document flow and pushes adjacent content aside
* when open. Required to opt into the inline kind; the Panel defaults to `'overlay'`.
*/
kind: 'inline';
modalType?: never;
offset?: never;
}
export interface OverlayPanelProps extends BasePanelProps {
/**
* Render the Panel as an overlay: portalled and floating above the page.
* @default 'overlay'
*/
kind?: 'overlay';
/**
* Modal behavior (overlay panels only).
*
* - `'isModal'` (default) — backdrop overlay with focus trapped; Escape and a backdrop
* click close it.
* - `'nonModal'` — page stays interactive; Escape closes the panel; outside clicks close
* it only when `closeOnOutsidePress={true}`.
* - `'alert'` — close button hidden; Escape and outside clicks are suppressed.
*
* @default 'isModal'
*/
modalType?: PanelModalType;
/**
* When `modalType="nonModal"`, allows the panel to close when the user
* clicks outside it. Has no effect for `'isModal'` or `'alert'` panels.
* @default false
*/
closeOnOutsidePress?: boolean;
/**
* Insets an overlay Panel from the viewport edge, giving it rounded corners and
* removing the outer divider (regardless of the `divider` prop).
*
* Pass `true` for the default `1rem`, a CSS length like `"2rem"` for a uniform offset,
* or a `{ x, y }` object like `{ x: "2rem", y: "0.5rem" }` to offset the axes
* independently.
* @default false
*/
offset?: boolean | string | {
x: string;
y: string;
};
/**
* Portal target for the overlay. When set, the Panel renders inside this element with
* `position: absolute` instead of `position: fixed`. The container needs to:
* - establish a containing block (e.g. `position: relative`)
* - set an appropriate `z-index`
* - use `overflow: hidden` (or `clip`), so the slide-in/out transform doesn't flash a
* transient scrollbar
*
* Pass the resolved element, or `null` until it exists — resolution is left to the implementer. Use state
* or a callback ref so the value is reactive: when it flips from `null` to the element,
* the Panel re-renders into the container. A plain `useRef` whose `.current` is mutated
* won't trigger that.
*
* Has no effect on `kind="inline"`.
*
* @example
* ```tsx
* const [container, setContainer] = useState(null);
* return (
*
* );
* ```
*/
container?: HTMLElement | null;
/**
* The element that opens the Panel. Clicks on it are excluded from outside-press
* dismissal, so it can toggle the Panel without the close immediately re-firing, and
* focus returns to it when the Panel closes. No effect on `kind="inline"`.
*
* Avoid `triggerRef` when the Panel is nested inside another FloatingUI floating element.
*/
triggerRef?: RefObject;
}
export type PanelProps = InlinePanelProps | OverlayPanelProps;
/**
* Resolved resize configuration passed to internal Panel components.
* Presence signals resizing is enabled; absence signals it is disabled.
*/
export type PanelResizeConfig = {
/** @default 165 for start/end, 100 for bottom */
minSize: number;
/** @default 50dvw for start/end, 90dvh for bottom */
maxSize: number;
/** localStorage key for size persistence */
storageKey: string;
};
export type { PanelHeaderProps } from './Header/Panel.Header';