import {
Children,
ComponentPropsWithoutRef,
isValidElement,
ReactNode,
} from 'react';
import { TimelineItem } from './TimelineItem';
import './timeline.css';
export type TimelineSize = 's' | 'xs';
export interface TimelineProps extends ComponentPropsWithoutRef<'ol'> {
/**
* What goes in the list. Each child becomes a list item. `TimelineItem`s
* are joined by the rail; anything else (a `LabeledDivider`, a
* `ChainOfThought`, a `ThinkingState`) breaks it.
*/
children?: ReactNode;
/**
* Additional class names
*/
className?: string;
/**
* Draw a vertical rail joining the icons of neighbouring `TimelineItem`s.
* The rail is decorative.
*/
hasRail?: boolean;
/**
* Text size of the items, as a `pds-copy-*` class on the list. Items read
* it by inheritance.
*/
size?: TimelineSize;
}
// 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(' ');
const isItem = (node: ReactNode) =>
isValidElement(node) && node.type === TimelineItem;
/**
* Timeline UI component. A list of events over time: `TimelineItem`s, with
* dividers, a Chain of Thought or a Thinking State in between. It owns the list
* semantics, the spacing and the optional rail.
*/
export const Timeline = ({
children,
className,
hasRail = false,
size = 'xs',
...props
}: TimelineProps) => {
const baseClass = 'pds-timeline';
const nodes = Children.toArray(children);
return (
<ol
className={mergeClasses([
baseClass,
`pds-copy-${size}`,
hasRail ? `${baseClass}--rail` : null,
className,
])}
{...props}
>
{nodes.map((child, index) => {
const isJoined = isItem(child) && isItem(nodes[index + 1]);
return (
<li
className={mergeClasses([
`${baseClass}__entry`,
isJoined ? `${baseClass}__entry--joined` : null,
])}
key={isValidElement(child) ? (child.key ?? index) : index}
>
{child}
</li>
);
})}
</ol>
);
};
Timeline
Net-new, in two parts.
Net-new componentOpen in Storybook