- Set up Tailwind CSS configuration for styling - Create Button component with variants (primary, secondary, outline, ghost, danger) - Create Input component with label, error, and helper text support - Create Card component with composable subcomponents (Header, Title, Description, Content, Footer) - Create LoadingSpinner component with size and fullscreen options - Create ErrorMessage component with retry and dismiss actions - Add comprehensive test suite using Jest and React Testing Library - Configure ESLint and Jest for quality assurance All components follow consistent design patterns from mobile app and include proper TypeScript types and accessibility features. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
107 lines
2.2 KiB
TypeScript
107 lines
2.2 KiB
TypeScript
import React from 'react';
|
|
|
|
interface CardProps {
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
padding?: 'none' | 'sm' | 'md' | 'lg';
|
|
hover?: boolean;
|
|
onClick?: () => void;
|
|
}
|
|
|
|
export function Card({
|
|
children,
|
|
className = '',
|
|
padding = 'md',
|
|
hover = false,
|
|
onClick,
|
|
}: CardProps) {
|
|
const baseClasses = 'bg-white rounded-lg border border-gray-200 shadow-sm';
|
|
|
|
const paddingClasses = {
|
|
none: '',
|
|
sm: 'p-3',
|
|
md: 'p-4',
|
|
lg: 'p-6',
|
|
};
|
|
|
|
const hoverClasses = hover
|
|
? 'hover:shadow-md transition-shadow cursor-pointer'
|
|
: '';
|
|
|
|
const interactiveClasses = onClick ? 'cursor-pointer' : '';
|
|
|
|
const classes = `${baseClasses} ${paddingClasses[padding]} ${hoverClasses} ${interactiveClasses} ${className}`;
|
|
|
|
if (onClick) {
|
|
return (
|
|
<div className={classes} onClick={onClick} role="button" tabIndex={0}>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return <div className={classes}>{children}</div>;
|
|
}
|
|
|
|
interface CardHeaderProps {
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
}
|
|
|
|
export function CardHeader({ children, className = '' }: CardHeaderProps) {
|
|
return (
|
|
<div className={`border-b border-gray-200 pb-3 mb-3 ${className}`}>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface CardTitleProps {
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
}
|
|
|
|
export function CardTitle({ children, className = '' }: CardTitleProps) {
|
|
return (
|
|
<h3 className={`text-lg font-semibold text-textPrimary ${className}`}>
|
|
{children}
|
|
</h3>
|
|
);
|
|
}
|
|
|
|
interface CardDescriptionProps {
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
}
|
|
|
|
export function CardDescription({
|
|
children,
|
|
className = '',
|
|
}: CardDescriptionProps) {
|
|
return (
|
|
<p className={`text-sm text-textSecondary mt-1 ${className}`}>{children}</p>
|
|
);
|
|
}
|
|
|
|
interface CardContentProps {
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
}
|
|
|
|
export function CardContent({ children, className = '' }: CardContentProps) {
|
|
return <div className={className}>{children}</div>;
|
|
}
|
|
|
|
interface CardFooterProps {
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
}
|
|
|
|
export function CardFooter({ children, className = '' }: CardFooterProps) {
|
|
return (
|
|
<div className={`border-t border-gray-200 pt-3 mt-3 ${className}`}>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|