'use client' import React from 'react' import { cn } from '../../utils/cn' import { Copy01Icon } from '../icons-v2-generated/documents' import { CheckIcon } from '../icons-v2-generated' import { useCopyToClipboard } from '../../hooks' import { ProgressBar } from './progress-bar' export interface InfoCardFooterData { /** Leading icon next to the text, expected at 24x24 (e.g. ) */ icon?: React.ReactNode text: string /** Trailing icon/logo aligned to the right edge, expected at 24x24 */ logo?: React.ReactNode /** External resource link rendered below the text row */ link?: { href: string /** Defaults to href without the protocol */ label?: string } } export interface InfoCardData { title?: string subtitle?: string icon?: React.ReactNode items: Array<{ label?: string value: string | string[] copyable?: boolean /** Optional trailing icon/logo (e.g. an SVG) rendered next to the value */ icon?: React.ReactNode }> progress?: { value: number warningThreshold?: number criticalThreshold?: number inverted?: boolean // if true, high values are good (green), low values are bad (red) } /** Optional footer rendered below the content, separated by a divider line */ footer?: InfoCardFooterData } interface InfoCardProps { data: InfoCardData className?: string } export function InfoCard({ data, className = '' }: InfoCardProps) { return (
{/* Header (title + subtitle) and body (rows + progress) are two groups separated by `gap-l`; the title/subtitle stack tightly and the rows use `gap-xs` — matching the ODS "Info-card" design. With no title/subtitle, the body is the only group. */}
{(data.title || data.subtitle) && (
{data.title && (
{data.title} {data.icon}
)} {data.subtitle && (
{data.subtitle} {/* Icon lives with the title when present; otherwise it falls to the subtitle so subtitle-only cards still render it. */} {!data.title && data.icon}
)}
)} {(data.items.length > 0 || data.progress) && (
{/* Info items */} {data.items.map((item, index) => { const values = Array.isArray(item.value) ? item.value : [item.value] return ( {values.map((val, valIndex) => ( ))} ) })} {/* Progress bar */} {data.progress && ( )}
)}
{/* Footer */} {data.footer && }
) } function InfoCardFooter({ footer }: { footer: InfoCardFooterData }) { return (
{footer.icon} {footer.text}
{footer.logo}
{footer.link && ( {footer.link.label ?? footer.link.href.replace(/^https?:\/\//, '')} )}
) } interface InfoCardValueRowProps { label?: string value: string showLabel: boolean copyable?: boolean icon?: React.ReactNode copyAriaLabel: string } function InfoCardValueRow({ label, value, showLabel, copyable, icon, copyAriaLabel }: InfoCardValueRowProps) { const { copy, copied } = useCopyToClipboard() return (
{showLabel ? label : ''}
{value} {icon} {copyable && ( )}
) }