Generated by Spark: Expand atomic component library until done.

This commit is contained in:
2026-01-17 15:58:27 +00:00
committed by GitHub
parent 6a17b4ea45
commit b6325aa3fc
24 changed files with 1758 additions and 0 deletions

View File

@@ -0,0 +1,453 @@
import {
ActionButton,
IconButton,
Text,
Heading,
Link,
Divider,
Avatar,
Chip,
Code,
Kbd,
ProgressBar,
Skeleton,
Tooltip,
Alert,
Spinner,
Dot,
Image,
Label,
HelperText,
Container,
Section,
Stack,
Spacer,
Timestamp,
ScrollArea,
StatusBadge,
LoadingSpinner,
EmptyState,
} from '@/components/atoms'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import {
Plus,
Download,
Trash,
Heart,
Star,
GitBranch,
} from '@phosphor-icons/react'
export function AtomicComponentShowcase() {
return (
<ScrollArea maxHeight="100vh">
<Container size="xl" className="py-8">
<Stack spacing="xl">
<div>
<Heading level={1}>Atomic Component Library</Heading>
<Text variant="muted">
Comprehensive collection of reusable atomic components for CodeForge
</Text>
</div>
<Divider />
<Section>
<Stack spacing="lg">
<Card>
<CardHeader>
<CardTitle>Typography</CardTitle>
<CardDescription>Text elements and headings</CardDescription>
</CardHeader>
<CardContent>
<Stack spacing="md">
<div>
<Heading level={1}>Heading Level 1</Heading>
<Heading level={2}>Heading Level 2</Heading>
<Heading level={3}>Heading Level 3</Heading>
<Heading level={4}>Heading Level 4</Heading>
<Heading level={5}>Heading Level 5</Heading>
<Heading level={6}>Heading Level 6</Heading>
</div>
<Divider />
<div>
<Text variant="body">Body text - Default paragraph text</Text>
<Text variant="muted">Muted text - For less important information</Text>
<Text variant="caption">Caption text - Small descriptive text</Text>
<Text variant="small">Small text - Compact information</Text>
</div>
</Stack>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Buttons</CardTitle>
<CardDescription>Action buttons and icon buttons</CardDescription>
</CardHeader>
<CardContent>
<Stack direction="horizontal" spacing="md" wrap>
<ActionButton label="Primary" onClick={() => {}} variant="default" />
<ActionButton label="Outline" onClick={() => {}} variant="outline" />
<ActionButton label="Ghost" onClick={() => {}} variant="ghost" />
<ActionButton label="Destructive" onClick={() => {}} variant="destructive" />
<ActionButton
icon={<Plus />}
label="With Icon"
onClick={() => {}}
/>
<ActionButton
icon={<Download />}
label="Tooltip"
onClick={() => {}}
tooltip="Download file"
/>
</Stack>
<Spacer size="md" />
<Stack direction="horizontal" spacing="md">
<IconButton icon={<Plus />} />
<IconButton icon={<Download />} variant="default" />
<IconButton icon={<Trash />} variant="destructive" />
<IconButton icon={<Heart />} variant="outline" />
</Stack>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Links</CardTitle>
<CardDescription>Text links with variants</CardDescription>
</CardHeader>
<CardContent>
<Stack spacing="sm">
<Link href="#" variant="default">Default Link</Link>
<Link href="#" variant="muted">Muted Link</Link>
<Link href="#" variant="accent">Accent Link</Link>
<Link href="#" variant="destructive">Destructive Link</Link>
<Link href="https://example.com" variant="default" external>
External Link
</Link>
</Stack>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Badges & Chips</CardTitle>
<CardDescription>Status indicators and removable tags</CardDescription>
</CardHeader>
<CardContent>
<Stack spacing="md">
<Stack direction="horizontal" spacing="sm" wrap>
<StatusBadge status="active" />
<StatusBadge status="inactive" />
<StatusBadge status="pending" />
<StatusBadge status="error" />
<StatusBadge status="success" />
<StatusBadge status="warning" />
</Stack>
<Stack direction="horizontal" spacing="sm" wrap>
<Chip variant="default">Default</Chip>
<Chip variant="primary">Primary</Chip>
<Chip variant="accent">Accent</Chip>
<Chip variant="muted">Muted</Chip>
<Chip variant="default" size="sm">Small</Chip>
<Chip variant="primary" onRemove={() => {}}>Removable</Chip>
</Stack>
</Stack>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Dots & Indicators</CardTitle>
<CardDescription>Status dots with pulse animation</CardDescription>
</CardHeader>
<CardContent>
<Stack direction="horizontal" spacing="lg" align="center">
<Stack direction="horizontal" spacing="sm" align="center">
<Dot variant="default" />
<Text variant="caption">Default</Text>
</Stack>
<Stack direction="horizontal" spacing="sm" align="center">
<Dot variant="primary" />
<Text variant="caption">Primary</Text>
</Stack>
<Stack direction="horizontal" spacing="sm" align="center">
<Dot variant="success" pulse />
<Text variant="caption">Success (pulse)</Text>
</Stack>
<Stack direction="horizontal" spacing="sm" align="center">
<Dot variant="warning" pulse />
<Text variant="caption">Warning (pulse)</Text>
</Stack>
<Stack direction="horizontal" spacing="sm" align="center">
<Dot variant="error" pulse />
<Text variant="caption">Error (pulse)</Text>
</Stack>
</Stack>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Avatar</CardTitle>
<CardDescription>User avatars with fallbacks</CardDescription>
</CardHeader>
<CardContent>
<Stack direction="horizontal" spacing="md" align="center">
<Avatar size="xs" fallback="XS" />
<Avatar size="sm" fallback="SM" />
<Avatar size="md" fallback="MD" />
<Avatar size="lg" fallback="LG" />
<Avatar size="xl" fallback="XL" />
</Stack>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Code & Keyboard</CardTitle>
<CardDescription>Code snippets and keyboard shortcuts</CardDescription>
</CardHeader>
<CardContent>
<Stack spacing="md">
<div>
<Text variant="body">
Use <Code>npm install</Code> to install dependencies.
</Text>
</div>
<Code inline={false}>
{`function hello() {
console.log("Hello, world!");
}`}
</Code>
<Stack direction="horizontal" spacing="sm" align="center">
<Text variant="body">Press</Text>
<Kbd>Ctrl</Kbd>
<Text variant="body">+</Text>
<Kbd>K</Kbd>
<Text variant="body">to search</Text>
</Stack>
</Stack>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Progress Bars</CardTitle>
<CardDescription>Progress indicators with variants</CardDescription>
</CardHeader>
<CardContent>
<Stack spacing="md">
<div>
<Label>Default Progress</Label>
<ProgressBar value={65} showLabel />
</div>
<div>
<Label>Accent Progress</Label>
<ProgressBar value={80} variant="accent" showLabel />
</div>
<div>
<Label>Error Progress</Label>
<ProgressBar value={25} variant="destructive" showLabel />
</div>
<div>
<Label>Small Size</Label>
<ProgressBar value={50} size="sm" />
</div>
<div>
<Label>Large Size</Label>
<ProgressBar value={90} size="lg" />
</div>
</Stack>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Loading States</CardTitle>
<CardDescription>Spinners and skeletons</CardDescription>
</CardHeader>
<CardContent>
<Stack spacing="md">
<Stack direction="horizontal" spacing="lg" align="center">
<LoadingSpinner size="sm" />
<LoadingSpinner size="md" />
<LoadingSpinner size="lg" />
<Spinner size={16} />
<Spinner size={24} />
<Spinner size={32} />
</Stack>
<Divider />
<Stack spacing="sm">
<Skeleton variant="text" width="100%" />
<Skeleton variant="text" width="80%" />
<Skeleton variant="rounded" width="100%" height={100} />
<Stack direction="horizontal" spacing="sm">
<Skeleton variant="circular" width={40} height={40} />
<Stack spacing="xs" className="flex-1">
<Skeleton variant="text" width="60%" />
<Skeleton variant="text" width="40%" />
</Stack>
</Stack>
</Stack>
</Stack>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Alerts</CardTitle>
<CardDescription>Contextual feedback messages</CardDescription>
</CardHeader>
<CardContent>
<Stack spacing="md">
<Alert variant="info" title="Information">
This is an informational message with useful context.
</Alert>
<Alert variant="success" title="Success">
Your changes have been saved successfully.
</Alert>
<Alert variant="warning" title="Warning">
Please review your input before continuing.
</Alert>
<Alert variant="error" title="Error">
An error occurred while processing your request.
</Alert>
</Stack>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Form Elements</CardTitle>
<CardDescription>Labels and helper text</CardDescription>
</CardHeader>
<CardContent>
<Stack spacing="md">
<div>
<Label htmlFor="example-input">Email Address</Label>
<HelperText variant="default">
We'll never share your email with anyone else.
</HelperText>
</div>
<div>
<Label htmlFor="required-input" required>
Required Field
</Label>
<HelperText variant="error">
This field is required.
</HelperText>
</div>
<div>
<Label htmlFor="success-input">Username</Label>
<HelperText variant="success">
This username is available!
</HelperText>
</div>
</Stack>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Timestamps</CardTitle>
<CardDescription>Date and time formatting</CardDescription>
</CardHeader>
<CardContent>
<Stack spacing="sm">
<Stack direction="horizontal" spacing="sm">
<Text variant="body">Absolute:</Text>
<Timestamp date={new Date()} />
</Stack>
<Stack direction="horizontal" spacing="sm">
<Text variant="body">Relative:</Text>
<Timestamp date={new Date(Date.now() - 1000 * 60 * 30)} relative />
</Stack>
<Stack direction="horizontal" spacing="sm">
<Text variant="body">Custom format:</Text>
<Timestamp date={new Date()} formatString="EEEE, MMMM do, yyyy" />
</Stack>
</Stack>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Tooltips</CardTitle>
<CardDescription>Hover to see tooltips</CardDescription>
</CardHeader>
<CardContent>
<Stack direction="horizontal" spacing="md">
<Tooltip content="This is a tooltip" side="top">
<ActionButton label="Top" onClick={() => {}} />
</Tooltip>
<Tooltip content="This is a tooltip" side="right">
<ActionButton label="Right" onClick={() => {}} />
</Tooltip>
<Tooltip content="This is a tooltip" side="bottom">
<ActionButton label="Bottom" onClick={() => {}} />
</Tooltip>
<Tooltip content="This is a tooltip" side="left">
<ActionButton label="Left" onClick={() => {}} />
</Tooltip>
</Stack>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Layout Components</CardTitle>
<CardDescription>Stack and spacing utilities</CardDescription>
</CardHeader>
<CardContent>
<Stack spacing="md">
<div>
<Label>Horizontal Stack</Label>
<Stack direction="horizontal" spacing="sm">
<div className="p-4 bg-primary text-primary-foreground rounded">1</div>
<div className="p-4 bg-primary text-primary-foreground rounded">2</div>
<div className="p-4 bg-primary text-primary-foreground rounded">3</div>
</Stack>
</div>
<div>
<Label>Vertical Stack with Large Spacing</Label>
<Stack direction="vertical" spacing="lg">
<div className="p-4 bg-accent text-accent-foreground rounded">A</div>
<div className="p-4 bg-accent text-accent-foreground rounded">B</div>
<div className="p-4 bg-accent text-accent-foreground rounded">C</div>
</Stack>
</div>
<div>
<Label>Justified Between</Label>
<Stack direction="horizontal" justify="between" align="center">
<IconButton icon={<Star />} />
<Text variant="body">Center Content</Text>
<IconButton icon={<GitBranch />} />
</Stack>
</div>
</Stack>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Empty States</CardTitle>
<CardDescription>Placeholder for empty content</CardDescription>
</CardHeader>
<CardContent>
<EmptyState
icon={<GitBranch size={48} />}
title="No items found"
description="Get started by creating your first item"
/>
</CardContent>
</Card>
</Stack>
</Section>
</Stack>
</Container>
</ScrollArea>
)
}

View File

@@ -0,0 +1,51 @@
import { ReactNode } from 'react'
import { Info, Warning, CheckCircle, XCircle } from '@phosphor-icons/react'
import { cn } from '@/lib/utils'
interface AlertProps {
variant?: 'info' | 'warning' | 'success' | 'error'
title?: string
children: ReactNode
className?: string
}
const variantConfig = {
info: {
icon: Info,
classes: 'bg-blue-50 border-blue-200 text-blue-900',
},
warning: {
icon: Warning,
classes: 'bg-yellow-50 border-yellow-200 text-yellow-900',
},
success: {
icon: CheckCircle,
classes: 'bg-green-50 border-green-200 text-green-900',
},
error: {
icon: XCircle,
classes: 'bg-red-50 border-red-200 text-red-900',
},
}
export function Alert({ variant = 'info', title, children, className }: AlertProps) {
const config = variantConfig[variant]
const Icon = config.icon
return (
<div
className={cn(
'flex gap-3 p-4 rounded-lg border',
config.classes,
className
)}
role="alert"
>
<Icon size={20} weight="bold" className="flex-shrink-0 mt-0.5" />
<div className="flex-1">
{title && <div className="font-semibold mb-1">{title}</div>}
<div className="text-sm">{children}</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,37 @@
import { cn } from '@/lib/utils'
interface AvatarProps {
src?: string
alt?: string
fallback?: string
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
className?: string
}
const sizeClasses = {
xs: 'w-6 h-6 text-xs',
sm: 'w-8 h-8 text-sm',
md: 'w-10 h-10 text-base',
lg: 'w-12 h-12 text-lg',
xl: 'w-16 h-16 text-xl',
}
export function Avatar({ src, alt, fallback, size = 'md', className }: AvatarProps) {
const initials = fallback || alt?.slice(0, 2).toUpperCase() || '?'
return (
<div
className={cn(
'relative inline-flex items-center justify-center rounded-full bg-muted overflow-hidden',
sizeClasses[size],
className
)}
>
{src ? (
<img src={src} alt={alt} className="w-full h-full object-cover" />
) : (
<span className="font-medium text-muted-foreground">{initials}</span>
)}
</div>
)
}

View File

@@ -0,0 +1,54 @@
import { ReactNode } from 'react'
import { X } from '@phosphor-icons/react'
import { cn } from '@/lib/utils'
interface ChipProps {
children: ReactNode
variant?: 'default' | 'primary' | 'accent' | 'muted'
size?: 'sm' | 'md'
onRemove?: () => void
className?: string
}
const variantClasses = {
default: 'bg-secondary text-secondary-foreground',
primary: 'bg-primary text-primary-foreground',
accent: 'bg-accent text-accent-foreground',
muted: 'bg-muted text-muted-foreground',
}
const sizeClasses = {
sm: 'px-2 py-0.5 text-xs',
md: 'px-3 py-1 text-sm',
}
export function Chip({
children,
variant = 'default',
size = 'md',
onRemove,
className
}: ChipProps) {
return (
<span
className={cn(
'inline-flex items-center gap-1 rounded-full font-medium',
variantClasses[variant],
sizeClasses[size],
className
)}
>
{children}
{onRemove && (
<button
type="button"
onClick={onRemove}
className="inline-flex items-center justify-center hover:bg-black/10 rounded-full transition-colors"
aria-label="Remove"
>
<X size={size === 'sm' ? 12 : 14} weight="bold" />
</button>
)}
</span>
)
}

View File

@@ -0,0 +1,34 @@
import { ReactNode } from 'react'
import { cn } from '@/lib/utils'
interface CodeProps {
children: ReactNode
inline?: boolean
className?: string
}
export function Code({ children, inline = true, className }: CodeProps) {
if (inline) {
return (
<code
className={cn(
'px-1.5 py-0.5 rounded bg-muted text-foreground font-mono text-sm',
className
)}
>
{children}
</code>
)
}
return (
<pre
className={cn(
'p-4 rounded-lg bg-muted text-foreground font-mono text-sm overflow-x-auto',
className
)}
>
<code>{children}</code>
</pre>
)
}

View File

@@ -0,0 +1,24 @@
import { ReactNode } from 'react'
import { cn } from '@/lib/utils'
interface ContainerProps {
children: ReactNode
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full'
className?: string
}
const sizeClasses = {
sm: 'max-w-screen-sm',
md: 'max-w-screen-md',
lg: 'max-w-screen-lg',
xl: 'max-w-screen-xl',
full: 'max-w-full',
}
export function Container({ children, size = 'xl', className }: ContainerProps) {
return (
<div className={cn('mx-auto px-4 sm:px-6 lg:px-8', sizeClasses[size], className)}>
{children}
</div>
)
}

View File

@@ -0,0 +1,25 @@
import { cn } from '@/lib/utils'
interface DividerProps {
orientation?: 'horizontal' | 'vertical'
className?: string
decorative?: boolean
}
export function Divider({
orientation = 'horizontal',
className,
decorative = true
}: DividerProps) {
return (
<div
role={decorative ? 'presentation' : 'separator'}
aria-orientation={orientation}
className={cn(
'bg-border',
orientation === 'horizontal' ? 'h-[1px] w-full' : 'w-[1px] h-full',
className
)}
/>
)
}

View File

@@ -0,0 +1,53 @@
import { cn } from '@/lib/utils'
interface DotProps {
variant?: 'default' | 'primary' | 'accent' | 'success' | 'warning' | 'error'
size?: 'xs' | 'sm' | 'md' | 'lg'
pulse?: boolean
className?: string
}
const variantClasses = {
default: 'bg-muted-foreground',
primary: 'bg-primary',
accent: 'bg-accent',
success: 'bg-green-500',
warning: 'bg-yellow-500',
error: 'bg-destructive',
}
const sizeClasses = {
xs: 'w-1.5 h-1.5',
sm: 'w-2 h-2',
md: 'w-3 h-3',
lg: 'w-4 h-4',
}
export function Dot({
variant = 'default',
size = 'sm',
pulse = false,
className
}: DotProps) {
return (
<span className="relative inline-flex">
<span
className={cn(
'inline-block rounded-full',
variantClasses[variant],
sizeClasses[size],
className
)}
/>
{pulse && (
<span
className={cn(
'absolute inline-flex rounded-full opacity-75 animate-ping',
variantClasses[variant],
sizeClasses[size]
)}
/>
)}
</span>
)
}

View File

@@ -0,0 +1,22 @@
import { ReactNode } from 'react'
import { cn } from '@/lib/utils'
interface HelperTextProps {
children: ReactNode
variant?: 'default' | 'error' | 'success'
className?: string
}
const variantClasses = {
default: 'text-muted-foreground',
error: 'text-destructive',
success: 'text-green-600',
}
export function HelperText({ children, variant = 'default', className }: HelperTextProps) {
return (
<p className={cn('text-xs mt-1', variantClasses[variant], className)}>
{children}
</p>
)
}

View File

@@ -0,0 +1,67 @@
import { useState } from 'react'
import { cn } from '@/lib/utils'
interface ImageProps {
src: string
alt: string
width?: number | string
height?: number | string
fit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'
fallback?: string
className?: string
onLoad?: () => void
onError?: () => void
}
export function Image({
src,
alt,
width,
height,
fit = 'cover',
fallback,
className,
onLoad,
onError
}: ImageProps) {
const [error, setError] = useState(false)
const [loading, setLoading] = useState(true)
const handleLoad = () => {
setLoading(false)
onLoad?.()
}
const handleError = () => {
setError(true)
setLoading(false)
onError?.()
}
const imgSrc = error && fallback ? fallback : src
return (
<div
className={cn('relative overflow-hidden', className)}
style={{
width: typeof width === 'number' ? `${width}px` : width,
height: typeof height === 'number' ? `${height}px` : height,
}}
>
{loading && (
<div className="absolute inset-0 bg-muted animate-pulse" />
)}
<img
src={imgSrc}
alt={alt}
onLoad={handleLoad}
onError={handleError}
className={cn(
'w-full h-full transition-opacity',
loading ? 'opacity-0' : 'opacity-100',
`object-${fit}`
)}
/>
</div>
)
}

View File

@@ -0,0 +1,21 @@
import { ReactNode } from 'react'
import { cn } from '@/lib/utils'
interface KbdProps {
children: ReactNode
className?: string
}
export function Kbd({ children, className }: KbdProps) {
return (
<kbd
className={cn(
'inline-flex items-center justify-center px-2 py-1 text-xs font-mono font-semibold',
'bg-muted text-foreground border border-border rounded shadow-sm',
className
)}
>
{children}
</kbd>
)
}

View File

@@ -0,0 +1,24 @@
import { ReactNode } from 'react'
import { cn } from '@/lib/utils'
interface LabelProps {
children: ReactNode
htmlFor?: string
required?: boolean
className?: string
}
export function Label({ children, htmlFor, required, className }: LabelProps) {
return (
<label
htmlFor={htmlFor}
className={cn(
'text-sm font-medium text-foreground leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
className
)}
>
{children}
{required && <span className="text-destructive ml-1">*</span>}
</label>
)
}

View File

@@ -0,0 +1,40 @@
import { ReactNode } from 'react'
import { cn } from '@/lib/utils'
interface LinkProps {
href: string
children: ReactNode
variant?: 'default' | 'muted' | 'accent' | 'destructive'
external?: boolean
className?: string
onClick?: (e: React.MouseEvent) => void
}
const variantClasses = {
default: 'text-foreground hover:text-primary underline-offset-4 hover:underline',
muted: 'text-muted-foreground hover:text-foreground underline-offset-4 hover:underline',
accent: 'text-accent hover:text-accent/80 underline-offset-4 hover:underline',
destructive: 'text-destructive hover:text-destructive/80 underline-offset-4 hover:underline',
}
export function Link({
href,
children,
variant = 'default',
external = false,
className,
onClick
}: LinkProps) {
const externalProps = external ? { target: '_blank', rel: 'noopener noreferrer' } : {}
return (
<a
href={href}
className={cn('transition-colors duration-150', variantClasses[variant], className)}
onClick={onClick}
{...externalProps}
>
{children}
</a>
)
}

View File

@@ -0,0 +1,62 @@
import { cn } from '@/lib/utils'
interface ProgressBarProps {
value: number
max?: number
size?: 'sm' | 'md' | 'lg'
variant?: 'default' | 'accent' | 'destructive'
showLabel?: boolean
className?: string
}
const sizeClasses = {
sm: 'h-1',
md: 'h-2',
lg: 'h-3',
}
const variantClasses = {
default: 'bg-primary',
accent: 'bg-accent',
destructive: 'bg-destructive',
}
export function ProgressBar({
value,
max = 100,
size = 'md',
variant = 'default',
showLabel = false,
className
}: ProgressBarProps) {
const percentage = Math.min(Math.max((value / max) * 100, 0), 100)
return (
<div className="w-full">
<div
className={cn(
'relative w-full bg-secondary rounded-full overflow-hidden',
sizeClasses[size],
className
)}
role="progressbar"
aria-valuenow={value}
aria-valuemin={0}
aria-valuemax={max}
>
<div
className={cn(
'h-full transition-all duration-300 ease-out',
variantClasses[variant]
)}
style={{ width: `${percentage}%` }}
/>
</div>
{showLabel && (
<span className="text-xs text-muted-foreground mt-1 block">
{Math.round(percentage)}%
</span>
)}
</div>
)
}

View File

@@ -0,0 +1,496 @@
# Atomic Component Library
A comprehensive collection of reusable atomic components for the CodeForge low-code development platform. These components follow atomic design principles and provide consistent, accessible UI elements across the application.
## Component Categories
### Typography
#### Heading
Semantic heading elements with consistent styling.
```tsx
<Heading level={1}>Main Title</Heading>
<Heading level={2}>Section Title</Heading>
<Heading level={3} className="text-accent">Custom Styled</Heading>
```
**Props:**
- `level`: 1-6 (default: 1)
- `className`: string
- `children`: ReactNode
#### Text
Paragraph text with semantic variants.
```tsx
<Text variant="body">Default body text</Text>
<Text variant="muted">Less important text</Text>
<Text variant="caption">Small descriptive text</Text>
<Text variant="small">Compact information</Text>
```
**Props:**
- `variant`: 'body' | 'caption' | 'muted' | 'small'
- `className`: string
- `children`: ReactNode
#### Link
Styled anchor elements with variant support.
```tsx
<Link href="/path" variant="default">Internal Link</Link>
<Link href="https://example.com" external>External Link</Link>
```
**Props:**
- `href`: string
- `variant`: 'default' | 'muted' | 'accent' | 'destructive'
- `external`: boolean
- `onClick`: (e: MouseEvent) => void
- `className`: string
---
### Buttons & Actions
#### ActionButton
Full-featured button with icon and tooltip support.
```tsx
<ActionButton
label="Save"
icon={<FloppyDisk />}
onClick={handleSave}
variant="default"
tooltip="Save changes"
/>
```
**Props:**
- `label`: string
- `onClick`: () => void
- `icon`: ReactNode
- `variant`: 'default' | 'outline' | 'ghost' | 'destructive'
- `size`: 'default' | 'sm' | 'lg' | 'icon'
- `tooltip`: string
- `disabled`: boolean
- `className`: string
#### IconButton
Compact icon-only button.
```tsx
<IconButton icon={<Plus />} onClick={handleAdd} />
<IconButton icon={<Trash />} variant="destructive" />
```
**Props:**
- `icon`: ReactNode
- `onClick`: () => void
- `variant`: 'default' | 'secondary' | 'outline' | 'ghost' | 'destructive'
- `size`: 'default' | 'sm' | 'lg' | 'icon'
- `disabled`: boolean
- `title`: string
- `className`: string
---
### Indicators & Badges
#### StatusBadge
Status indicator with predefined states.
```tsx
<StatusBadge status="active" />
<StatusBadge status="error" label="Failed" />
```
**Props:**
- `status`: 'active' | 'inactive' | 'pending' | 'error' | 'success' | 'warning'
- `label`: string (optional)
#### Chip
Tag component with optional remove functionality.
```tsx
<Chip variant="primary">React</Chip>
<Chip variant="accent" onRemove={handleRemove}>Removable</Chip>
```
**Props:**
- `variant`: 'default' | 'primary' | 'accent' | 'muted'
- `size`: 'sm' | 'md'
- `onRemove`: () => void
- `className`: string
#### Dot
Small status indicator dot with pulse animation.
```tsx
<Dot variant="success" />
<Dot variant="warning" pulse />
```
**Props:**
- `variant`: 'default' | 'primary' | 'accent' | 'success' | 'warning' | 'error'
- `size`: 'xs' | 'sm' | 'md' | 'lg'
- `pulse`: boolean
- `className`: string
---
### Display Components
#### Avatar
User avatar with fallback to initials.
```tsx
<Avatar src="/avatar.jpg" alt="John Doe" size="md" />
<Avatar fallback="JD" size="lg" />
```
**Props:**
- `src`: string
- `alt`: string
- `fallback`: string
- `size`: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
- `className`: string
#### Image
Enhanced image component with loading and error states.
```tsx
<Image
src="/photo.jpg"
alt="Description"
width={300}
height={200}
fit="cover"
fallback="/placeholder.jpg"
/>
```
**Props:**
- `src`: string
- `alt`: string
- `width`: number | string
- `height`: number | string
- `fit`: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'
- `fallback`: string
- `onLoad`: () => void
- `onError`: () => void
- `className`: string
#### Code
Inline or block code display.
```tsx
<Code inline>npm install</Code>
<Code inline={false}>
{`function hello() {
console.log("Hello");
}`}
</Code>
```
**Props:**
- `inline`: boolean (default: true)
- `className`: string
#### Kbd
Keyboard shortcut display.
```tsx
<Kbd>Ctrl</Kbd> + <Kbd>K</Kbd>
```
**Props:**
- `className`: string
---
### Feedback Components
#### Alert
Contextual alert messages.
```tsx
<Alert variant="info" title="Note">
This is important information.
</Alert>
<Alert variant="error" title="Error">
Something went wrong.
</Alert>
```
**Props:**
- `variant`: 'info' | 'warning' | 'success' | 'error'
- `title`: string
- `className`: string
#### Spinner / LoadingSpinner
Loading indicators.
```tsx
<Spinner size={24} />
<LoadingSpinner size="md" />
```
**Props (Spinner):**
- `size`: number
- `className`: string
**Props (LoadingSpinner):**
- `size`: 'sm' | 'md' | 'lg'
- `className`: string
#### ProgressBar
Progress indicator with percentage.
```tsx
<ProgressBar value={65} max={100} showLabel />
<ProgressBar value={80} variant="accent" />
```
**Props:**
- `value`: number
- `max`: number (default: 100)
- `size`: 'sm' | 'md' | 'lg'
- `variant`: 'default' | 'accent' | 'destructive'
- `showLabel`: boolean
- `className`: string
#### Skeleton
Loading placeholder.
```tsx
<Skeleton variant="text" width="100%" />
<Skeleton variant="circular" width={40} height={40} />
<Skeleton variant="rounded" width="100%" height={100} />
```
**Props:**
- `variant`: 'text' | 'rectangular' | 'circular' | 'rounded'
- `width`: string | number
- `height`: string | number
- `className`: string
#### Tooltip
Hover tooltip.
```tsx
<Tooltip content="More information" side="top">
<Button>Hover me</Button>
</Tooltip>
```
**Props:**
- `content`: ReactNode
- `side`: 'top' | 'right' | 'bottom' | 'left'
- `delayDuration`: number (default: 200)
- `className`: string
---
### Form Components
#### Label
Form field label with required indicator.
```tsx
<Label htmlFor="email">Email Address</Label>
<Label htmlFor="password" required>Password</Label>
```
**Props:**
- `htmlFor`: string
- `required`: boolean
- `className`: string
#### HelperText
Form field helper or error text.
```tsx
<HelperText variant="default">
Enter your email address
</HelperText>
<HelperText variant="error">
This field is required
</HelperText>
```
**Props:**
- `variant`: 'default' | 'error' | 'success'
- `className`: string
---
### Layout Components
#### Container
Max-width content container with responsive padding.
```tsx
<Container size="lg">
<h1>Content</h1>
</Container>
```
**Props:**
- `size`: 'sm' | 'md' | 'lg' | 'xl' | 'full'
- `className`: string
#### Section
Semantic section with vertical spacing.
```tsx
<Section spacing="lg">
<h2>Section Title</h2>
<p>Content</p>
</Section>
```
**Props:**
- `spacing`: 'none' | 'sm' | 'md' | 'lg' | 'xl'
- `className`: string
#### Stack
Flexbox layout with consistent spacing.
```tsx
<Stack direction="vertical" spacing="md">
<div>Item 1</div>
<div>Item 2</div>
</Stack>
<Stack direction="horizontal" justify="between" align="center">
<Button>Left</Button>
<Button>Right</Button>
</Stack>
```
**Props:**
- `direction`: 'horizontal' | 'vertical'
- `spacing`: 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'
- `align`: 'start' | 'center' | 'end' | 'stretch'
- `justify`: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly'
- `wrap`: boolean
- `className`: string
#### Spacer
Blank space for layout spacing.
```tsx
<Spacer size="md" axis="vertical" />
<Spacer size="lg" axis="horizontal" />
```
**Props:**
- `size`: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'
- `axis`: 'horizontal' | 'vertical' | 'both'
- `className`: string
#### Divider
Visual separator line.
```tsx
<Divider orientation="horizontal" />
<Divider orientation="vertical" className="h-full" />
```
**Props:**
- `orientation`: 'horizontal' | 'vertical'
- `decorative`: boolean (default: true)
- `className`: string
#### ScrollArea
Custom styled scrollable area.
```tsx
<ScrollArea maxHeight={400}>
<div>Long content...</div>
</ScrollArea>
```
**Props:**
- `maxHeight`: string | number
- `className`: string
---
### Utility Components
#### Timestamp
Formatted date/time display.
```tsx
<Timestamp date={new Date()} />
<Timestamp date={dateValue} relative />
<Timestamp date={dateValue} formatString="MMM d, yyyy" />
```
**Props:**
- `date`: Date | number | string
- `relative`: boolean
- `formatString`: string (default: 'MMM d, yyyy h:mm a')
- `className`: string
---
## Design Principles
1. **Consistency**: All components use the same design tokens and styling patterns
2. **Accessibility**: ARIA attributes and semantic HTML throughout
3. **Flexibility**: Comprehensive prop APIs for customization
4. **Performance**: Lightweight implementations with minimal dependencies
5. **Type Safety**: Full TypeScript support with proper prop types
## Usage Guidelines
### Import Pattern
```tsx
import { Heading, Text, Button, Stack } from '@/components/atoms'
```
### Composition
Atomic components are designed to be composed together:
```tsx
<Card>
<Stack spacing="md">
<Stack direction="horizontal" justify="between" align="center">
<Stack direction="horizontal" spacing="sm" align="center">
<Avatar fallback="JD" size="sm" />
<Stack spacing="xs">
<Heading level={4}>John Doe</Heading>
<Text variant="muted">2 hours ago</Text>
</Stack>
</Stack>
<StatusBadge status="active" />
</Stack>
<Divider />
<Text variant="body">
Check out this <Code>implementation</Code> detail.
</Text>
<Stack direction="horizontal" spacing="sm">
<ActionButton icon={<Heart />} label="Like" onClick={handleLike} />
<ActionButton icon={<Share />} label="Share" onClick={handleShare} variant="outline" />
</Stack>
</Stack>
</Card>
```
## Theme Integration
All components respect the design system defined in `index.css`:
- Color variables: `--primary`, `--accent`, `--background`, etc.
- Typography: JetBrains Mono for code, IBM Plex Sans for UI
- Spacing scale: Consistent gap/padding values
- Border radius: `--radius` variable
## Browser Support
- Modern browsers (Chrome, Firefox, Safari, Edge)
- CSS Grid and Flexbox support required
- ES2020+ JavaScript features

View File

@@ -0,0 +1,35 @@
import { ReactNode } from 'react'
import { cn } from '@/lib/utils'
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'
interface ScrollAreaProps {
children: ReactNode
className?: string
maxHeight?: string | number
}
export function ScrollArea({ children, className, maxHeight }: ScrollAreaProps) {
return (
<ScrollAreaPrimitive.Root
className={cn('relative overflow-hidden', className)}
style={{ maxHeight: typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight }}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollAreaPrimitive.Scrollbar
className="flex touch-none select-none transition-colors p-0.5 bg-transparent hover:bg-muted"
orientation="vertical"
>
<ScrollAreaPrimitive.Thumb className="flex-1 bg-border rounded-full relative" />
</ScrollAreaPrimitive.Scrollbar>
<ScrollAreaPrimitive.Scrollbar
className="flex touch-none select-none transition-colors p-0.5 bg-transparent hover:bg-muted"
orientation="horizontal"
>
<ScrollAreaPrimitive.Thumb className="flex-1 bg-border rounded-full relative" />
</ScrollAreaPrimitive.Scrollbar>
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
)
}

View File

@@ -0,0 +1,24 @@
import { ReactNode } from 'react'
import { cn } from '@/lib/utils'
interface SectionProps {
children: ReactNode
spacing?: 'none' | 'sm' | 'md' | 'lg' | 'xl'
className?: string
}
const spacingClasses = {
none: '',
sm: 'py-4',
md: 'py-8',
lg: 'py-12',
xl: 'py-16',
}
export function Section({ children, spacing = 'md', className }: SectionProps) {
return (
<section className={cn(spacingClasses[spacing], className)}>
{children}
</section>
)
}

View File

@@ -0,0 +1,36 @@
import { cn } from '@/lib/utils'
interface SkeletonProps {
variant?: 'text' | 'rectangular' | 'circular' | 'rounded'
width?: string | number
height?: string | number
className?: string
}
const variantClasses = {
text: 'rounded h-4',
rectangular: 'rounded-none',
circular: 'rounded-full',
rounded: 'rounded-lg',
}
export function Skeleton({
variant = 'rectangular',
width,
height,
className
}: SkeletonProps) {
return (
<div
className={cn(
'bg-muted animate-pulse',
variantClasses[variant],
className
)}
style={{
width: typeof width === 'number' ? `${width}px` : width,
height: typeof height === 'number' ? `${height}px` : height,
}}
/>
)
}

View File

@@ -0,0 +1,31 @@
import { cn } from '@/lib/utils'
interface SpacerProps {
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'
axis?: 'horizontal' | 'vertical' | 'both'
className?: string
}
const sizeClasses = {
xs: 1,
sm: 2,
md: 4,
lg: 8,
xl: 16,
'2xl': 24,
}
export function Spacer({ size = 'md', axis = 'vertical', className }: SpacerProps) {
const spacing = sizeClasses[size]
return (
<div
className={cn(className)}
style={{
width: axis === 'horizontal' || axis === 'both' ? `${spacing * 4}px` : undefined,
height: axis === 'vertical' || axis === 'both' ? `${spacing * 4}px` : undefined,
}}
aria-hidden="true"
/>
)
}

View File

@@ -0,0 +1,17 @@
import { CircleNotch } from '@phosphor-icons/react'
import { cn } from '@/lib/utils'
interface SpinnerProps {
size?: number
className?: string
}
export function Spinner({ size = 24, className }: SpinnerProps) {
return (
<CircleNotch
size={size}
weight="bold"
className={cn('animate-spin text-primary', className)}
/>
)
}

View File

@@ -0,0 +1,63 @@
import { ReactNode } from 'react'
import { cn } from '@/lib/utils'
interface StackProps {
children: ReactNode
direction?: 'horizontal' | 'vertical'
spacing?: 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'
align?: 'start' | 'center' | 'end' | 'stretch'
justify?: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly'
wrap?: boolean
className?: string
}
const spacingClasses = {
none: 'gap-0',
xs: 'gap-1',
sm: 'gap-2',
md: 'gap-4',
lg: 'gap-6',
xl: 'gap-8',
}
const alignClasses = {
start: 'items-start',
center: 'items-center',
end: 'items-end',
stretch: 'items-stretch',
}
const justifyClasses = {
start: 'justify-start',
center: 'justify-center',
end: 'justify-end',
between: 'justify-between',
around: 'justify-around',
evenly: 'justify-evenly',
}
export function Stack({
children,
direction = 'vertical',
spacing = 'md',
align = 'stretch',
justify = 'start',
wrap = false,
className
}: StackProps) {
return (
<div
className={cn(
'flex',
direction === 'horizontal' ? 'flex-row' : 'flex-col',
spacingClasses[spacing],
alignClasses[align],
justifyClasses[justify],
wrap && 'flex-wrap',
className
)}
>
{children}
</div>
)
}

View File

@@ -0,0 +1,31 @@
import { format, formatDistanceToNow } from 'date-fns'
import { cn } from '@/lib/utils'
interface TimestampProps {
date: Date | number | string
relative?: boolean
formatString?: string
className?: string
}
export function Timestamp({
date,
relative = false,
formatString = 'MMM d, yyyy h:mm a',
className
}: TimestampProps) {
const dateObj = typeof date === 'string' || typeof date === 'number' ? new Date(date) : date
const displayText = relative
? formatDistanceToNow(dateObj, { addSuffix: true })
: format(dateObj, formatString)
return (
<time
dateTime={dateObj.toISOString()}
className={cn('text-sm text-muted-foreground', className)}
>
{displayText}
</time>
)
}

View File

@@ -0,0 +1,36 @@
import { ReactNode } from 'react'
import {
Tooltip as TooltipPrimitive,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip'
interface TooltipProps {
content: ReactNode
children: ReactNode
side?: 'top' | 'right' | 'bottom' | 'left'
delayDuration?: number
className?: string
}
export function Tooltip({
content,
children,
side = 'top',
delayDuration = 200,
className
}: TooltipProps) {
return (
<TooltipProvider delayDuration={delayDuration}>
<TooltipPrimitive>
<TooltipTrigger asChild>
{children}
</TooltipTrigger>
<TooltipContent side={side} className={className}>
{content}
</TooltipContent>
</TooltipPrimitive>
</TooltipProvider>
)
}

View File

@@ -25,3 +25,25 @@ export { EmptyState } from './EmptyState'
export { DetailRow } from './DetailRow'
export { CompletionCard } from './CompletionCard'
export { TipsCard } from './TipsCard'
export { Link } from './Link'
export { Divider } from './Divider'
export { Avatar } from './Avatar'
export { Chip } from './Chip'
export { Code } from './Code'
export { Kbd } from './Kbd'
export { ProgressBar } from './ProgressBar'
export { Skeleton } from './Skeleton'
export { Tooltip } from './Tooltip'
export { Alert } from './Alert'
export { Spinner } from './Spinner'
export { Dot } from './Dot'
export { Image } from './Image'
export { Label } from './Label'
export { HelperText } from './HelperText'
export { Container } from './Container'
export { Section } from './Section'
export { Stack } from './Stack'
export { Spacer } from './Spacer'
export { Timestamp } from './Timestamp'
export { ScrollArea } from './ScrollArea'