import React from "react"; import UI from "#components/ui"; import type { CardProps, CardTitleProps, CardContentProps, CardFooterProps, CardComponent, } from "./card.types"; import { cn, CARD_CLASSES, handleCardKeyDown, warnInteractiveUsage, } from "./card.utils"; /** * Card.Title - Title sub-component for Card * * Renders a heading element for the card title. Defaults to h3 for proper * document outline, but can be customized via the `as` prop. * * ## Accessibility * - Use appropriate heading level based on document structure * - Combine with `aria-labelledby` on parent Card for accessible names * * @example * ```tsx * * Featured Product * * ``` * * @example * ```tsx * // Custom heading level * Section Title * ``` */ export const Title = ({ children, className, style, as = "h3", ...props }: CardTitleProps) => { return ( {children} ); }; Title.displayName = "Card.Title"; /** * Card.Content - Content sub-component for Card * * Renders the main content area of the card. Defaults to `
` for * standalone content, but can be changed to `div` or `section` via the `as` prop. * * ## Semantic HTML Guidelines * - Use `article` (default) for self-contained, syndicate-able content * - Use `div` for generic content containers * - Use `section` for thematic groupings with a heading * * @example * ```tsx * * Article Title * *

This is standalone content...

*
*
* ``` * * @example * ```tsx * // Generic container (not standalone content) * *

Generic content...

*
* ``` */ export const Content = ({ children, className, style, as = "article", ...props }: CardContentProps) => { return ( {children} ); }; Content.displayName = "Card.Content"; /** * Card.Footer - Footer sub-component for Card * * Renders a footer section for the card. Use for actions, metadata, or * supplementary information. * * @example * ```tsx * * Product * Description... * * * $29.99 * * * ``` * * @example * ```tsx * // Semantic footer element * *

Last updated: 2024-01-15

*
* ``` */ export const Footer = ({ children, className, style, as = "div", ...props }: CardFooterProps) => { return ( {children} ); }; Footer.displayName = "Card.Footer"; /** * Card - A flexible, accessible card component with compound component pattern * * The Card component provides a container for grouping related content and actions. * It follows the compound component pattern, exposing `Card.Title`, `Card.Content`, * and `Card.Footer` sub-components for structured layouts. * * ## Features * - **Polymorphic rendering**: Render as any HTML element via `as` prop * - **Compound components**: Structured sub-components for consistent layouts * - **Interactive variant**: Built-in keyboard navigation and ARIA support * - **Fully accessible**: WCAG 2.1 AA compliant with proper semantic HTML * * ## Accessibility * * ### Non-Interactive Cards * - Use semantic HTML: `article` for standalone content, `section` for groupings * - Provide accessible names with `aria-labelledby` referencing the title * * ### Interactive Cards (Clickable) * - Set `interactive={true}` to enable keyboard navigation (Enter/Space) * - Provide accessible name via `aria-label` or `aria-labelledby` * - Ensure adequate focus indicators (handled by CSS) * * @example * // Basic card with compound components * ```tsx * * Product Name * *

Product description goes here...

*
* * * *
* ``` * * @example * // Accessible interactive card * ```tsx * navigate('/product/123')} * > * Product Name * Click anywhere to view details * * ``` * * @example * // Semantic article card with accessible name * ```tsx * * Article Headline * Article body... * * ``` * * @example * // Card as a landmark region * ```tsx * * Featured * ... * * ``` */ const CardRoot = ({ as = "div", styles, children, classes = "shadow-sm", id, interactive = false, onClick, tabIndex, role, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, ...props }: CardProps) => { // Development warnings for common accessibility issues React.useEffect(() => { warnInteractiveUsage(!!onClick, interactive); }, [onClick, interactive]); // Interactive card handling const handleKeyDown = (event: React.KeyboardEvent) => { if (interactive || onClick) { handleCardKeyDown(event, onClick); } }; const interactiveProps = interactive ? { role: role || "button", tabIndex: tabIndex ?? 0, onClick, onKeyDown: handleKeyDown, } : { role, onClick, }; return ( {children} ); }; // Create compound component with proper TypeScript typing export const Card = CardRoot as CardComponent; Card.displayName = "Card"; Card.Title = Title; Card.Content = Content; Card.Footer = Footer; export default Card; // Export types for external consumption export type { CardProps, CardTitleProps, CardContentProps, CardFooterProps, CardComponent, } from "./card.types";