← All proposals

Content Diff

A component, ContentDiff: the old value on a red line with a minus, the new value on a green line with a plus, in one bordered block, like a text diff.

Net-new componentOpen in Storybook

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

import { Icon } from '../Icon/Icon';

import './content-diff.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 ContentDiffProps extends ComponentPropsWithoutRef<'dl'> {
	/**
	 * The new value.
	 */
	after: ReactNode;
	/**
	 * Name of the new value for screen readers (for example "After"); not
	 * shown. Provide it translated.
	 */
	afterLabel: string;
	/**
	 * The current value.
	 */
	before: ReactNode;
	/**
	 * Name of the current value for screen readers (for example "Before"); not
	 * shown. Provide it translated.
	 */
	beforeLabel: string;
	/**
	 * Additional class names.
	 */
	className?: string;
}

/**
 * ContentDiff UI component. A before and after of a piece of content, stacked
 * like a text diff: the old value on a red line with a minus, the new value on
 * a green line with a plus.
 */
export const ContentDiff = ({
	after,
	afterLabel,
	before,
	beforeLabel,
	className,
	...props
}: ContentDiffProps) => {
	const baseClass = 'pds-content-diff';

	return (
		<dl className={mergeClasses([baseClass, className])} {...props}>
			<div className={`${baseClass}__line ${baseClass}__line--before`}>
				<dt className='visually-hidden'>{beforeLabel}</dt>
				<Icon
					aria-hidden='true'
					className={`${baseClass}__marker`}
					iconName='minus'
					size='s'
				/>
				<dd className={`${baseClass}__text`}>{before}</dd>
			</div>
			<div className={`${baseClass}__line ${baseClass}__line--after`}>
				<dt className='visually-hidden'>{afterLabel}</dt>
				<Icon
					aria-hidden='true'
					className={`${baseClass}__marker`}
					iconName='plus'
					size='s'
				/>
				<dd className={`${baseClass}__text`}>{after}</dd>
			</div>
		</dl>
	);
};