hydropd / website /src /components /Card.tsx
rikardsaqe's picture
User fixes + full Impeccable critique pass
4548710 verified
Raw
History Blame Contribute Delete
1.8 kB
import { useState, type ReactNode } from 'react'
interface CardProps {
title?: ReactNode
subtitle?: ReactNode
actions?: ReactNode
children: ReactNode
className?: string
/** Render the header as a show/hide toggle for the body. */
collapsible?: boolean
/** Initial open state when collapsible (default: closed). */
defaultOpen?: boolean
}
export default function Card({
title,
subtitle,
actions,
children,
className,
collapsible = false,
defaultOpen = false,
}: CardProps) {
const [open, setOpen] = useState(defaultOpen)
const isOpen = !collapsible || open
const titleNode = collapsible ? (
<button
type="button"
className="card-collapse-toggle"
aria-expanded={isOpen}
onClick={() => setOpen((v) => !v)}
style={{
background: 'none',
border: 'none',
padding: 0,
cursor: 'pointer',
font: 'inherit',
color: 'inherit',
display: 'flex',
alignItems: 'center',
gap: 8,
textAlign: 'left',
}}
>
<span aria-hidden className={'caret' + (isOpen ? ' open' : '')}>
</span>
{title && <h3 style={{ margin: 0 }}>{title}</h3>}
</button>
) : (
title && <h3>{title}</h3>
)
return (
<section className={'card' + (className ? ' ' + className : '')}>
{(title || (actions && isOpen)) && (
<div className="toolbar" style={{ marginBottom: isOpen && subtitle ? 4 : 14 }}>
<div className="card-title">{titleNode}</div>
{actions && isOpen && <div style={{ display: 'flex', gap: 8 }}>{actions}</div>}
</div>
)}
{isOpen && (
<>
{subtitle && <p className="card-subtitle">{subtitle}</p>}
{children}
</>
)}
</section>
)
}