'use client' import * as React from 'react' import Link from '../../embed-shims/next-link' import { cn } from '../../utils/cn' import { AssigneeDropdown, type AssigneeDropdownProps } from './assignee-dropdown' import { SquareAvatar } from './square-avatar' import { TicketStatusTag, type TicketStatusTagProps } from './ticket-status-tag' /** A plain-text value, optionally with a trailing image (avatar), leading icon, or click action. */ export interface InfoSectionTextValue { type?: 'text' text: string /** Image shown after the text (e.g. a user or organization avatar). */ imageSrc?: string /** Initials shown when the image is missing or fails to load. Defaults to `text`. */ imageFallback?: string /** Avatar shape. Defaults to `round`. */ imageVariant?: 'square' | 'round' /** Icon shown before the text. */ icon?: React.ReactNode /** Renders the text as a button; on hover it takes the accent color. */ onClick?: () => void /** * Renders the text as a link (Next.js `Link`). Takes precedence over `onClick`. * A regular click navigates in the same tab; the browser's native new-tab * affordances (cmd/ctrl-click, "open in new tab") work as usual. */ href?: string /** Force the link to always open in a new tab (sets `target="_blank"`). */ openInNewTab?: boolean } /** An inline assignee picker (autocomplete with search). Defaults to the `compact` variant. */ export type InfoSectionAssigneeValue = { type: 'assignee' } & AssigneeDropdownProps /** An inline status changer (dropdown). Pass `options` + `onSelect` to make it interactive. */ export type InfoSectionStatusValue = { type: 'status' } & TicketStatusTagProps /** Arbitrary value content. */ export interface InfoSectionCustomValue { type: 'custom' content: React.ReactNode } export type InfoSectionValue = | InfoSectionTextValue | InfoSectionAssigneeValue | InfoSectionStatusValue | InfoSectionCustomValue export interface InfoSectionRow { /** Stable key for the row. */ id: string /** Left-side label. */ label: string /** Right-side value: text (with optional image), assignee picker, status dropdown, or custom node. */ value: InfoSectionValue } export interface InfoSectionProps { /** Optional caption rendered above the card (e.g. "Ticket Details"). */ title?: string rows: InfoSectionRow[] className?: string } function TextValue({ value }: { value: InfoSectionTextValue }) { const { text, imageSrc, imageFallback, imageVariant = 'round', icon, onClick, href, openInNewTab } = value const hasImage = imageSrc !== undefined || imageFallback !== undefined const interactiveClass = 'truncate text-left text-h4 text-ods-text-primary transition-colors hover:text-ods-accent' return ( <> {icon && ( {icon} )} {href ? ( {text} ) : onClick ? ( ) : ( {text} )} {hasImage && ( )} ) } function RowValue({ value }: { value: InfoSectionValue }) { switch (value.type) { case 'assignee': { const { type: _type, ...props } = value return } case 'status': { const { type: _type, ...props } = value return } case 'custom': return <>{value.content} default: return } } /** * A labelled info card: a vertical stack of `label —— value` rows inside a * bordered card, with an optional uppercase caption above it. Each row's value * is configurable — plain text (with an optional trailing image), an inline * assignee picker (autocomplete with search), an inline status dropdown, or an * arbitrary node. */ export function InfoSection({ title, rows, className }: InfoSectionProps) { return (
{title &&

{title}

}
{rows.map(row => (
{row.label}
))}
) }