import {
cloneElement,
ComponentPropsWithoutRef,
isValidElement,
ReactNode,
} from 'react';
import { Icon } from '../Icon/Icon';
import './agent-avatar.css';
// Stand-in for the v2 `mergeClasses` util, which the published package does
// not export. The port uses the real one.
const mergeClasses = (classes: Array<string | null | undefined>) =>
classes.filter(Boolean).join(' ');
export interface AgentAvatarProps extends ComponentPropsWithoutRef<'div'> {
/**
* Aria label for the avatar when used as a link. This is required if using a link.
*/
ariaLabel?: string;
/**
* Additional class names
*/
className?: string;
/**
* Show the AI-gradient ring (halo) around the avatar. Without it the avatar
* is the dark disc and icon only.
*/
hasHalo?: boolean;
/**
* Icon inside the avatar. `zappy` for Pantheon agents, `sparklesSolid` for other
* (third-party) agents.
*/
iconName?: 'sparklesSolid' | 'zappy';
/**
* Screen-reader text that says this is an agent, for example "Agent". The
* visual treatment alone is not enough for assistive tech. Provide it
* translated.
*/
label: string;
/**
* A link element using the router of your choice. Leave empty for no link.
*/
linkContent?: ReactNode;
/**
* Avatar shape.
*/
shape?: 'circle' | 'square';
/**
* Avatar size. `xs` = 16px, `s` = 20px (default), `m` = 24px, `l` = 32px,
* `xl` = 40px.
*/
size?: 'xs' | 's' | 'm' | 'l' | 'xl';
}
/**
* Agent Avatar UI component
*/
export const AgentAvatar = ({
ariaLabel,
className,
hasHalo = true,
iconName = 'zappy',
label,
linkContent,
shape = 'circle',
size = 's',
...props
}: AgentAvatarProps) => {
// Set up classes.
const baseClass = 'pds-agent-avatar';
const sizeClass = `${baseClass}--${size}`;
const shapeClass = shape !== 'circle' ? `${baseClass}--${shape}` : null;
const haloClass = hasHalo ? `${baseClass}--halo` : null;
// When used as a link, the link's aria-label names it instead.
const agentContent = (
<>
<Icon
className={`${baseClass}__icon`}
iconName={iconName}
aria-hidden='true'
/>
{!linkContent && <span className='visually-hidden'>{label}</span>}
</>
);
const interactiveContent =
linkContent && isValidElement(linkContent)
? cloneElement(
linkContent,
Object.assign({}, linkContent.props, {
className: `${baseClass}__content ${baseClass}__content--link`,
'aria-label': ariaLabel ?? label,
}),
agentContent,
)
: null;
// Render the output
return (
<div
className={mergeClasses([
baseClass,
sizeClass,
haloClass,
shapeClass,
className,
])}
{...props}
>
{linkContent ? (
interactiveContent
) : (
<span className={`${baseClass}__content`}>{agentContent}</span>
)}
</div>
);
};
Agent avatar
Start from the PDS v2 Avatar (src/components/identity/Avatar, branch v2.x), renamed AgentAvatar (class pds-agent-avatar).
Net-new componentOpen in Storybook