import {
ComponentPropsWithoutRef,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import { Tooltip } from '@pantheon-systems/pds-toolkit-react';
import { AgentAvatar, AgentAvatarProps } from '../AgentAvatar/AgentAvatar';
import { mergeClasses } from '../Button/utils';
import './identity-block.css';
/**
* Prop types for IdentityBlock
*/
export interface IdentityBlockProps extends ComponentPropsWithoutRef<'div'> {
/**
* Props to pass to the AgentAvatar component. `label` (the screen-reader
* text that says it is an agent) is required. `size` is set automatically
* (`m`, or `s` when `isSingleLine`) and cannot be overridden here.
*/
avatarProps?: Omit<AgentAvatarProps, 'ariaLabel' | 'linkContent' | 'size'>;
/**
* Additional class names
*/
className?: string;
/**
* When true, only the avatar is shown (text is hidden).
*/
isSidebarCollapsed?: boolean;
/**
* When true, displays a smaller avatar with only the primary text.
* Secondary text is not rendered even if provided.
*/
isSingleLine?: boolean;
/**
* First line of text (e.g., organization name).
*/
primaryText?: string;
/**
* Second line of text (e.g., user name).
*/
secondaryText?: string;
}
/**
* IdentityBlock UI component
*/
export const IdentityBlock = ({
avatarProps,
className,
isSidebarCollapsed,
isSingleLine,
primaryText,
secondaryText,
...props
}: IdentityBlockProps) => {
const baseClass = 'pds-identity-block';
const collapsedClass = isSidebarCollapsed ? `${baseClass}--collapsed` : null;
const singleLineClass = isSingleLine ? `${baseClass}--single-line` : null;
const resolvedAvatarProps = avatarProps
? {
...avatarProps,
size: isSingleLine ? ('s' as const) : ('m' as const),
}
: undefined;
const showSecondaryText = secondaryText && !isSingleLine;
const primaryRef = useRef<HTMLSpanElement>(null);
const secondaryRef = useRef<HTMLSpanElement>(null);
const [isPrimaryTruncated, setIsPrimaryTruncated] = useState(false);
const [isSecondaryTruncated, setIsSecondaryTruncated] = useState(false);
const checkTruncation = useCallback(() => {
if (primaryRef.current) {
setIsPrimaryTruncated(
primaryRef.current.scrollWidth > primaryRef.current.clientWidth,
);
}
if (secondaryRef.current) {
setIsSecondaryTruncated(
secondaryRef.current.scrollWidth > secondaryRef.current.clientWidth,
);
}
}, []);
useEffect(() => {
window.addEventListener('resize', checkTruncation);
return () => window.removeEventListener('resize', checkTruncation);
}, [checkTruncation]);
useEffect(() => {
if (isSidebarCollapsed) {
return;
}
const id = setTimeout(checkTruncation, 250);
return () => clearTimeout(id);
}, [checkTruncation, isSidebarCollapsed, primaryText, secondaryText]);
const collapsedTooltipContent = [
primaryText,
showSecondaryText ? secondaryText : null,
]
.filter(Boolean)
.join(' — ');
const avatarElement = resolvedAvatarProps ? (
<div className={`${baseClass}__avatar`}>
<AgentAvatar {...resolvedAvatarProps} />
</div>
) : null;
return (
<div
className={mergeClasses([
baseClass,
collapsedClass,
singleLineClass,
className ?? '',
])}
{...props}
>
{avatarElement &&
(isSidebarCollapsed && collapsedTooltipContent ? (
<Tooltip
content={collapsedTooltipContent}
customTrigger={avatarElement}
preferredPlacement='right'
/>
) : (
avatarElement
))}
{(primaryText || showSecondaryText) && (
<div className={`${baseClass}__text`}>
<span
ref={primaryRef}
className={`${baseClass}__primary-text`}
title={isPrimaryTruncated ? primaryText : undefined}
>
{primaryText}
</span>
{showSecondaryText && (
<span
ref={secondaryRef}
className={`${baseClass}__secondary-text`}
title={isSecondaryTruncated ? secondaryText : undefined}
>
{secondaryText}
</span>
)}
</div>
)}
</div>
);
};
Identity Block
Start from the PDS v2 Identity Block and use the Agent Avatar in place of the human Avatar, as Avatar Label does.
Extends a PDS componentOpen in Storybook