From 69e322af28720f453a7d61343823ad31de9376b7 Mon Sep 17 00:00:00 2001 From: A Charlwood Date: Fri, 13 Feb 2026 17:16:06 +0000 Subject: [PATCH] Task 8: Build reusable Card component with CardHeader - Create Card component with base styling (white bg, 8px radius, shadow-sm) - Hover state: deepens shadow to shadow-md, strengthens border - Full-width variant spans both grid columns - CardHeader sub-component with colored dot, title, optional right text - Dot colors: teal, amber, green, alert, purple - Header styling: 12px uppercase title, 10px mono right text - All styles use CSS custom properties from design tokens --- src/components/Card.tsx | 91 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/components/Card.tsx diff --git a/src/components/Card.tsx b/src/components/Card.tsx new file mode 100644 index 0000000..7b04a4c --- /dev/null +++ b/src/components/Card.tsx @@ -0,0 +1,91 @@ +import React from 'react' + +interface CardProps { + children: React.ReactNode + full?: boolean // spans both grid columns + className?: string +} + +export function Card({ children, full, className }: CardProps) { + const [isHovered, setIsHovered] = React.useState(false) + + const baseStyles: React.CSSProperties = { + background: 'var(--surface)', + border: isHovered + ? '1px solid var(--border)' + : '1px solid var(--border-light)', + borderRadius: 'var(--radius)', + padding: '20px', + boxShadow: isHovered ? 'var(--shadow-md)' : 'var(--shadow-sm)', + transition: 'box-shadow 0.2s, border-color 0.2s', + gridColumn: full ? '1 / -1' : undefined, + } + + return ( +
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + {children} +
+ ) +} + +interface CardHeaderProps { + dotColor: 'teal' | 'amber' | 'green' | 'alert' | 'purple' + title: string + rightText?: string +} + +const dotColorMap: Record = { + teal: '#0D6E6E', + amber: '#D97706', + green: '#059669', + alert: '#DC2626', + purple: '#7C3AED', +} + +export function CardHeader({ dotColor, title, rightText }: CardHeaderProps) { + const headerStyles: React.CSSProperties = { + display: 'flex', + alignItems: 'center', + gap: '8px', + marginBottom: '16px', + } + + const dotStyles: React.CSSProperties = { + width: '8px', + height: '8px', + borderRadius: '50%', + backgroundColor: dotColorMap[dotColor], + flexShrink: 0, + } + + const titleStyles: React.CSSProperties = { + fontSize: '12px', + fontWeight: 600, + textTransform: 'uppercase', + letterSpacing: '0.06em', + color: 'var(--text-secondary)', + } + + const rightTextStyles: React.CSSProperties = { + fontSize: '10px', + fontWeight: 400, + textTransform: 'none', + letterSpacing: 'normal', + color: 'var(--text-tertiary)', + fontFamily: "'Geist Mono', monospace", + marginLeft: 'auto', + } + + return ( +
+
+ {title} + {rightText && {rightText}} +
+ ) +}