mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-25 06:04:54 +00:00
Generated by Spark: Expand atomic component library until done.
This commit is contained in:
453
src/components/AtomicComponentShowcase.tsx
Normal file
453
src/components/AtomicComponentShowcase.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
51
src/components/atoms/Alert.tsx
Normal file
51
src/components/atoms/Alert.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
37
src/components/atoms/Avatar.tsx
Normal file
37
src/components/atoms/Avatar.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
54
src/components/atoms/Chip.tsx
Normal file
54
src/components/atoms/Chip.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
34
src/components/atoms/Code.tsx
Normal file
34
src/components/atoms/Code.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
24
src/components/atoms/Container.tsx
Normal file
24
src/components/atoms/Container.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
25
src/components/atoms/Divider.tsx
Normal file
25
src/components/atoms/Divider.tsx
Normal 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
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
53
src/components/atoms/Dot.tsx
Normal file
53
src/components/atoms/Dot.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
22
src/components/atoms/HelperText.tsx
Normal file
22
src/components/atoms/HelperText.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
67
src/components/atoms/Image.tsx
Normal file
67
src/components/atoms/Image.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
21
src/components/atoms/Kbd.tsx
Normal file
21
src/components/atoms/Kbd.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
24
src/components/atoms/Label.tsx
Normal file
24
src/components/atoms/Label.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
40
src/components/atoms/Link.tsx
Normal file
40
src/components/atoms/Link.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
62
src/components/atoms/ProgressBar.tsx
Normal file
62
src/components/atoms/ProgressBar.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
496
src/components/atoms/README.md
Normal file
496
src/components/atoms/README.md
Normal 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
|
||||
35
src/components/atoms/ScrollArea.tsx
Normal file
35
src/components/atoms/ScrollArea.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
24
src/components/atoms/Section.tsx
Normal file
24
src/components/atoms/Section.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
36
src/components/atoms/Skeleton.tsx
Normal file
36
src/components/atoms/Skeleton.tsx
Normal 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,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
31
src/components/atoms/Spacer.tsx
Normal file
31
src/components/atoms/Spacer.tsx
Normal 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"
|
||||
/>
|
||||
)
|
||||
}
|
||||
17
src/components/atoms/Spinner.tsx
Normal file
17
src/components/atoms/Spinner.tsx
Normal 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)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
63
src/components/atoms/Stack.tsx
Normal file
63
src/components/atoms/Stack.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
31
src/components/atoms/Timestamp.tsx
Normal file
31
src/components/atoms/Timestamp.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
36
src/components/atoms/Tooltip.tsx
Normal file
36
src/components/atoms/Tooltip.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user