{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "field-list", "type": "registry:component", "title": "Field list with copy", "description": "The receive surface: bare values, per-field copy naming the field, amber Required pill, '(not required)' variants instead of vanishing rows.", "registryDependencies": [ "@venlyfinance/venly-tokens" ], "files": [ { "path": "registry/components/field-list.tsx", "type": "registry:component", "target": "~/components/venly/components/field-list.tsx", "content": "import type { CSSProperties, ReactElement } from \"react\";\n\n/**\n * Field list with copy – the receive surface.\n *\n * Design contract encoded by this component:\n * - Values are bare text on the card, not boxed.\n * - Per-field copy affordance sits at the row's far right.\n * - A required field carries an amber \"Required ⚠\" pill inline at the\n * row's right end, immediately left of the copy control – the pill,\n * never a colour change on the value.\n * - A conditionally-absent row is ambiguous: render the \"(not required)\"\n * variant rather than omitting the row.\n * - Copy confirmation names the field (\"You copied your payment\n * reference\") – the onCopy callback receives the label for exactly that.\n */\nexport interface FieldRow {\n label: string;\n /** undefined/null renders the \"(not required)\" variant. */\n value?: string | null;\n /** The payer MUST include this field for funds to match. */\n required?: boolean;\n /** Show the copy control. Default true when a value exists. */\n copyable?: boolean;\n /** Render alphanumeric tokens (IBANs, references) in tabular figures. */\n mono?: boolean;\n}\n\nexport interface FieldListProps {\n fields: FieldRow[];\n /** Called with the field label and value after a copy click. */\n onCopy?: (label: string, value: string) => void;\n style?: CSSProperties;\n className?: string;\n}\n\nfunction RequiredPill(): ReactElement {\n return (\n \n Required\n \n \n );\n}\n\nexport function FieldList({ fields, onCopy, style, className }: FieldListProps): ReactElement {\n return (\n \n {fields.map((field) => {\n const hasValue = field.value !== undefined && field.value !== null && field.value !== \"\";\n const copyable = hasValue && (field.copyable ?? true);\n return (\n \n \n {field.label}\n \n \n {/* A required field can never read \"(not required)\": a missing\n mandatory value is a state (\"not assigned yet\"), not an\n exemption. */}\n {hasValue ? field.value : field.required ? \"Not assigned yet\" : \"(not required)\"}\n \n {field.required ? : null}\n {copyable ? (\n onCopy(field.label, field.value as string) : undefined}\n style={{\n border: \"none\",\n background: \"none\",\n cursor: \"pointer\",\n color: \"var(--text-secondary)\",\n fontSize: \"var(--font-size-body)\",\n lineHeight: 1,\n padding: \"var(--space-3xs)\",\n flex: \"none\",\n }}\n >\n ⧉\n \n ) : null}\n \n );\n })}\n \n );\n}\n" } ] }