import {
Children,
ComponentPropsWithoutRef,
isValidElement,
ReactNode,
} from 'react';
import type { MilestoneVariant } from './Milestone';
import { MilestoneListContext } from './milestone-list-context';
import './milestone-list.css';
export type MilestoneListSubtitlePosition = 'below' | 'start';
export interface MilestoneListProps extends ComponentPropsWithoutRef<'ol'> {
/**
* The milestones. Each child becomes a list item.
*/
children?: ReactNode;
/**
* Additional class names
*/
className?: string;
/**
* Where each milestone's subtitle goes: `below` the title (default) or
* `start`, at the left of the line, beside the dot. At narrow widths `start`
* falls back to below the title.
*/
subtitlePosition?: MilestoneListSubtitlePosition;
/**
* Put each milestone on a card (`card`, default) or leave the text on the
* line (`flat`). A milestone can override it.
*/
variant?: MilestoneVariant;
}
// 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(' ');
/**
* MilestoneList UI component. A list of `Milestone`s hung off one line that
* runs the full height of the list. It owns the list semantics, the spacing,
* the line and the card style that milestones follow.
*/
export const MilestoneList = ({
children,
className,
subtitlePosition = 'below',
variant,
...props
}: MilestoneListProps) => {
const baseClass = 'pds-milestone-list';
const nodes = Children.toArray(children);
return (
<MilestoneListContext.Provider value={{ variant }}>
<ol
className={mergeClasses([
baseClass,
subtitlePosition === 'start' ? `${baseClass}--subtitle-start` : null,
className,
])}
{...props}
>
{nodes.map((child, index) => (
<li
className={`${baseClass}__entry`}
key={isValidElement(child) ? (child.key ?? index) : index}
>
{child}
</li>
))}
</ol>
</MilestoneListContext.Provider>
);
};
Milestone
Net-new, in two parts, in their own family so "timeline" keeps one meaning (the log).
Net-new componentOpen in Storybook