← All proposals

Panel

The PDS v2 Panel (src/components/panels/Panel, branch v2.x) with one addition: the agentic-default, agentic-raised and agentic-sunken variants: each existing surface with the AI gradient as the border.

Extends a PDS componentOpen in Storybook

proposals/Panel/Panel.tsx120 lines · View on GitHub
import { ComponentPropsWithoutRef, ReactNode } from 'react';

import { mergeClasses } from '../Button/utils';

import './panel.css';

// Stand-in for v2's `PaddingScale` (`@libs/types/layout-types`), which the
// published package does not export.
export type PanelPadding = 'condensed' | 'none' | 'spacious' | 'standard';

export type PanelVariant =
	| 'agentic-default'
	| 'agentic-raised'
	| 'agentic-sunken'
	| 'critical'
	| 'default'
	| 'discovery'
	| 'info'
	| 'overlay'
	| 'raised'
	| 'success'
	| 'sunken'
	| 'warning';

export type PanelAgenticVariant =
	'agentic-default' | 'agentic-raised' | 'agentic-sunken';

interface PanelBaseProps extends Omit<
	ComponentPropsWithoutRef<'div'>,
	'children'
> {
	/**
	 * Panel content.
	 */
	children: ReactNode;
	/**
	 * Additional class names
	 */
	className?: string;
	/**
	 * Padding for the panel.
	 */
	padding?: PanelPadding;
}

interface PanelAgenticProps extends PanelBaseProps {
	/**
	 * Shimmer the agentic border, to show the agent is working. Set to `false`
	 * once the agent has finished. Only available on the `agentic-*` variants.
	 * Off for reduced motion.
	 */
	isAnimated?: boolean;
	/**
	 * Which variant of panel to render. The `agentic-*` variants have the fill
	 * and shadow of the matching variant (`default`, `raised`, `sunken`) with
	 * the AI gradient (`--pds-gradient-ai-active`) as the border, to mark
	 * content an agent produced or is acting on.
	 */
	variant: PanelAgenticVariant;
}

interface PanelStandardProps extends PanelBaseProps {
	isAnimated?: never;
	/**
	 * Which variant of panel to render.
	 */
	variant?: Exclude<PanelVariant, PanelAgenticVariant>;
}

/**
 * Prop types for Panel. `isAnimated` is only accepted with an `agentic-*`
 * variant.
 */
export type PanelProps = PanelAgenticProps | PanelStandardProps;

/**
 * Panel UI component
 */
export const Panel = ({
	children,
	className,
	isAnimated,
	padding = 'standard',
	variant = 'default',
	...props
}: PanelProps) => {
	// Set up classes.
	const baseClass = 'pds-panel';
	// `agentic-<surface>` renders the surface's own class plus `--agentic`, so
	// the fill and shadow come from the existing variant.
	const isAgentic = variant.startsWith('agentic-');
	const surface = isAgentic ? variant.replace('agentic-', '') : variant;
	const variantClass =
		surface !== 'default' ? `${baseClass}--${surface}` : null;
	const agenticClass = isAgentic ? `${baseClass}--agentic` : null;
	const animatedClass =
		isAgentic && isAnimated !== false ? `${baseClass}--animated` : null;
	const contentClass = `${baseClass}__content`;
	const paddingClass =
		padding !== 'standard' ? `${contentClass}--pad-${padding}` : null;

	// Render the output
	return (
		<div
			className={mergeClasses([
				baseClass,
				variantClass,
				agenticClass,
				animatedClass,
				className ?? '',
			])}
			{...props}
		>
			<div className={mergeClasses([contentClass, paddingClass])}>
				{children}
			</div>
		</div>
	);
};