import { ComponentPropsWithoutRef } from 'react';
import {
CodeBlock,
FileDiff,
PanelList,
PanelListProps,
PanelRow,
} from '@pantheon-systems/pds-toolkit-react';
import { Icon } from '../Icon/Icon';
import './code-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(' ');
/**
* One changed file in a CodeDiff.
*/
export interface CodeDiffFile {
/**
* Number of added lines. Shows the PDS File Diff (counts and bar) at the end
* of the row when set together with `deletions`.
*/
additions?: number;
/**
* Number of removed lines. See `additions`.
*/
deletions?: number;
/**
* The diff text, in unified diff format.
*/
diff: string;
/**
* Aria-label for the expand button of this row, for example "Show changes
* to composer.json". Provide it translated.
*/
expandButtonLabel?: string;
/**
* Unique id for the file.
*/
id: string;
/**
* File name shown in the row.
*/
name: string;
}
/**
* Prop types for CodeDiff
*/
export interface CodeDiffProps extends ComponentPropsWithoutRef<'div'> {
/**
* Additional class names
*/
className?: string;
/**
* Aria-label and tooltip text for each diff's copy button.
*/
copyButtonLabel?: string;
/**
* The changed files, one expandable row each.
*/
files: CodeDiffFile[];
/**
* Whether the list has an outside border. Passed to the PDS Panel List.
*/
hasBorder?: PanelListProps['hasBorder'];
/**
* Show a copy button on each diff.
*/
hasCopyButton?: boolean;
/**
* Block padding of each row. Passed to the PDS Panel List's `paddingSize`.
* `2xs` (default) is the compact setting for this component; the Panel
* List's own default is `m`.
*/
paddingSize?: PanelListProps['paddingSize'];
}
/**
* CodeDiff UI component. A PDS Panel List of changed files. Each row expands to
* show its diff in a PDS Code Block.
*/
export const CodeDiff = ({
className,
copyButtonLabel,
files,
hasBorder = true,
hasCopyButton = false,
paddingSize = '2xs',
...props
}: CodeDiffProps) => {
// Set up classes.
const baseClass = 'pds-code-diff';
// Render the output
return (
<div className={mergeClasses([baseClass, className])} {...props}>
<PanelList hasBorder={hasBorder} paddingSize={paddingSize}>
{files.map((file) => {
const hasStats =
file.additions !== undefined && file.deletions !== undefined;
return (
<PanelRow
expandButtonAriaLabel={file.expandButtonLabel}
isExpandable
key={file.id}
>
<div className={`${baseClass}__file`} slot='items-left'>
<Icon iconName='fileDiff' size='s' />
<span className={`${baseClass}__name`}>{file.name}</span>
</div>
{hasStats ? (
<div className={`${baseClass}__stats`} slot='items-right'>
<FileDiff
additions={file.additions as number}
deletions={file.deletions as number}
/>
</div>
) : null}
<div slot='items-below'>
<CodeBlock
code={file.diff}
copyButtonLabel={copyButtonLabel}
hasCopyButton={hasCopyButton}
language='diff'
/>
</div>
</PanelRow>
);
})}
</PanelList>
</div>
);
};
Code Diff
A Panel List of changed files.
Net-new componentOpen in Storybook