From c89c1630680c1e59ad668b92de269bfccc0420d0 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Tue, 24 Mar 2026 12:14:08 +0100 Subject: [PATCH] feat(Card): add optional title prop with uppercase monospace header When a title string is provided, renders an uppercase monospace h3 header with a subtle border separator above the card body. Children are wrapped in a padded body div when title is present; without title, children render directly as before (no breaking change). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../primitives/Card/Card.module.css | 19 +++++++ .../primitives/Card/Card.test.tsx | 49 +++++++++++++++++++ src/design-system/primitives/Card/Card.tsx | 14 +++++- 3 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 src/design-system/primitives/Card/Card.test.tsx diff --git a/src/design-system/primitives/Card/Card.module.css b/src/design-system/primitives/Card/Card.module.css index aa9085b..6e88525 100644 --- a/src/design-system/primitives/Card/Card.module.css +++ b/src/design-system/primitives/Card/Card.module.css @@ -11,3 +11,22 @@ .accent-warning { border-top: 3px solid var(--warning); } .accent-error { border-top: 3px solid var(--error); } .accent-running { border-top: 3px solid var(--running); } + +.titleHeader { + padding: 12px 16px; + border-bottom: 1px solid var(--border-subtle); +} + +.titleText { + font-size: 11px; + text-transform: uppercase; + font-family: var(--font-mono); + font-weight: 600; + color: var(--text-secondary); + letter-spacing: 0.5px; + margin: 0; +} + +.body { + padding: 16px; +} diff --git a/src/design-system/primitives/Card/Card.test.tsx b/src/design-system/primitives/Card/Card.test.tsx new file mode 100644 index 0000000..bff36e4 --- /dev/null +++ b/src/design-system/primitives/Card/Card.test.tsx @@ -0,0 +1,49 @@ +import { describe, it, expect } from 'vitest' +import { render, screen } from '@testing-library/react' +import { Card } from './Card' + +describe('Card', () => { + it('renders children', () => { + render(Hello world) + expect(screen.getByText('Hello world')).toBeInTheDocument() + }) + + it('renders title when provided', () => { + render(Content) + expect(screen.getByText('Status')).toBeInTheDocument() + }) + + it('does not render title header when title is omitted', () => { + const { container } = render(Content) + expect(container.querySelector('h3')).toBeNull() + }) + + it('wraps children in body div when title is provided', () => { + render(Content) + const content = screen.getByText('Content') + expect(content.parentElement).toHaveClass('body') + }) + + it('renders with accent and title together', () => { + const { container } = render( + Content, + ) + const card = container.firstChild as HTMLElement + expect(card).toHaveClass('accent-success') + expect(screen.getByText('Health')).toBeInTheDocument() + expect(screen.getByText('Content')).toBeInTheDocument() + }) + + it('accepts className prop', () => { + const { container } = render(Content) + const card = container.firstChild as HTMLElement + expect(card).toHaveClass('custom') + }) + + it('renders children directly when no title (no wrapper div)', () => { + const { container } = render(Direct child) + const card = container.firstChild as HTMLElement + const span = screen.getByText('Direct child') + expect(span.parentElement).toBe(card) + }) +}) diff --git a/src/design-system/primitives/Card/Card.tsx b/src/design-system/primitives/Card/Card.tsx index e6656d4..1595e0e 100644 --- a/src/design-system/primitives/Card/Card.tsx +++ b/src/design-system/primitives/Card/Card.tsx @@ -4,15 +4,25 @@ import type { ReactNode } from 'react' interface CardProps { children: ReactNode accent?: 'amber' | 'success' | 'warning' | 'error' | 'running' | 'none' + title?: string className?: string } -export function Card({ children, accent = 'none', className }: CardProps) { +export function Card({ children, accent = 'none', title, className }: CardProps) { const classes = [ styles.card, accent !== 'none' ? styles[`accent-${accent}`] : '', className ?? '', ].filter(Boolean).join(' ') - return
{children}
+ return ( +
+ {title && ( +
+

{title}

+
+ )} + {title ?
{children}
: children} +
+ ) }