Generated by Spark: Make atomic component library until done. If done, just wire them into other components.

This commit is contained in:
2026-01-17 16:18:25 +00:00
committed by GitHub
parent b240fb0b9b
commit ba78f094ff
14 changed files with 684 additions and 36 deletions

View File

@@ -40,6 +40,14 @@ import {
IconButton,
ActionButton,
StatusBadge,
NumberInput,
TextGradient,
Pulse,
QuickActionButton,
PanelHeader,
LiveIndicator,
Sparkle,
GlowCard,
} from '@/components/atoms'
import {
Heart,
@@ -54,6 +62,8 @@ import {
Info,
WarningCircle,
XCircle,
Rocket,
Code as CodeIcon,
} from '@phosphor-icons/react'
export function AtomicLibraryShowcase() {
@@ -62,6 +72,7 @@ export function AtomicLibraryShowcase() {
const [rangeValue, setRangeValue] = useState<[number, number]>([20, 80])
const [filterValue, setFilterValue] = useState('')
const [rating, setRating] = useState(3)
const [numberValue, setNumberValue] = useState(10)
return (
<Container size="xl" className="py-8">
@@ -481,15 +492,132 @@ export function AtomicLibraryShowcase() {
</Stack>
</Section>
<Section spacing="lg">
<Heading level={2}>Enhanced Components</Heading>
<Separator />
<Stack direction="vertical" spacing="lg">
<div>
<PanelHeader
title="Panel Header Component"
subtitle="A reusable header with optional icon and actions"
icon={<Rocket size={24} weight="duotone" />}
actions={
<Button size="sm" variant="outline">Action</Button>
}
/>
</div>
<div>
<Text variant="muted" className="mb-2">Number Input</Text>
<NumberInput
label="Quantity"
value={numberValue}
onChange={setNumberValue}
min={0}
max={100}
step={5}
/>
</div>
<div>
<Text variant="muted" className="mb-2">Text Gradient</Text>
<Heading level={2}>
<TextGradient from="from-primary" to="to-accent">
Beautiful Gradient Text
</TextGradient>
</Heading>
</div>
<div>
<Text variant="muted" className="mb-2">Status Indicators</Text>
<Flex gap="lg" align="center">
<Flex gap="sm" align="center">
<Pulse variant="success" />
<Text variant="small">Active</Text>
</Flex>
<LiveIndicator />
<Flex gap="sm" align="center">
<Sparkle variant="gold" />
<Text variant="small">Featured</Text>
</Flex>
</Flex>
</div>
<div>
<Text variant="muted" className="mb-2">Quick Action Buttons</Text>
<ResponsiveGrid columns={4} gap="md">
<QuickActionButton
icon={<CodeIcon size={32} weight="duotone" />}
label="Code"
description="Edit files"
variant="primary"
onClick={() => alert('Code clicked')}
/>
<QuickActionButton
icon={<Rocket size={32} weight="duotone" />}
label="Deploy"
description="Launch app"
variant="accent"
onClick={() => alert('Deploy clicked')}
/>
<QuickActionButton
icon={<Star size={32} weight="duotone" />}
label="Favorite"
description="Save for later"
variant="default"
onClick={() => alert('Favorite clicked')}
/>
<QuickActionButton
icon={<ShoppingCart size={32} weight="duotone" />}
label="Shop"
description="Browse items"
variant="muted"
onClick={() => alert('Shop clicked')}
/>
</ResponsiveGrid>
</div>
<div>
<Text variant="muted" className="mb-2">Glow Cards</Text>
<ResponsiveGrid columns={3} gap="md">
<GlowCard glowColor="primary" intensity="medium">
<div className="p-4">
<Heading level={4}>Primary Glow</Heading>
<Text variant="muted" className="mt-2">
Card with primary glow effect
</Text>
</div>
</GlowCard>
<GlowCard glowColor="accent" intensity="high">
<div className="p-4">
<Heading level={4}>Accent Glow</Heading>
<Text variant="muted" className="mt-2">
Card with accent glow effect
</Text>
</div>
</GlowCard>
<GlowCard glowColor="success" intensity="medium">
<div className="p-4">
<Heading level={4}>Success Glow</Heading>
<Text variant="muted" className="mt-2">
Card with success glow effect
</Text>
</div>
</GlowCard>
</ResponsiveGrid>
</div>
</Stack>
</Section>
<Section spacing="lg" className="pb-12">
<Heading level={2}>Summary</Heading>
<Separator />
<InfoPanel variant="success" icon={<CheckCircle />}>
<Heading level={5} className="mb-2">Atomic Component Library Complete!</Heading>
<Text>
The atomic component library includes 50+ production-ready components covering buttons,
badges, typography, forms, progress indicators, feedback, avatars, cards, and layout utilities.
All components are fully typed, accessible, and follow the design system.
The atomic component library includes 60+ production-ready components covering buttons,
badges, typography, forms, progress indicators, feedback, avatars, cards, enhanced inputs,
gradient effects, and layout utilities. All components are fully typed, accessible, and follow the design system.
</Text>
</InfoPanel>
</Section>

View File

@@ -8,9 +8,20 @@ import {
Play,
Cube,
FileText,
Rocket,
GitBranch,
Package,
} from '@phosphor-icons/react'
import { ProjectFile, PrismaModel, ComponentNode, ThemeConfig, PlaywrightTest, StorybookStory, UnitTest, FlaskConfig, NextJsConfig } from '@/types/project'
import { SeedDataStatus, DetailRow, CompletionCard, TipsCard, StatCard } from '@/components/atoms'
import {
SeedDataStatus,
DetailRow,
CompletionCard,
TipsCard,
StatCard,
QuickActionButton,
PanelHeader,
} from '@/components/atoms'
import { GitHubBuildStatus } from '@/components/molecules/GitHubBuildStatus'
import { useDashboardMetrics } from '@/hooks/ui/use-dashboard-metrics'
import { useDashboardTips } from '@/hooks/ui/use-dashboard-tips'
@@ -25,6 +36,7 @@ interface ProjectDashboardProps {
unitTests: UnitTest[]
flaskConfig: FlaskConfig
nextjsConfig: NextJsConfig
onNavigate?: (page: string) => void
}
export function ProjectDashboard(props: ProjectDashboardProps) {
@@ -38,6 +50,7 @@ export function ProjectDashboard(props: ProjectDashboardProps) {
unitTests,
flaskConfig,
nextjsConfig,
onNavigate,
} = props
const metrics = useDashboardMetrics({
@@ -133,9 +146,50 @@ export function ProjectDashboard(props: ProjectDashboardProps) {
/>
)}
<div>
<PanelHeader
title="Quick Actions"
subtitle="Jump to commonly used tools"
icon={<Rocket size={24} weight="duotone" />}
/>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4 mt-4">
<QuickActionButton
icon={<Code size={32} weight="duotone" />}
label="Code Editor"
description="Edit files"
variant="primary"
onClick={() => onNavigate?.('code')}
/>
<QuickActionButton
icon={<Database size={32} weight="duotone" />}
label="Models"
description="Design schema"
variant="primary"
onClick={() => onNavigate?.('models')}
/>
<QuickActionButton
icon={<Tree size={32} weight="duotone" />}
label="Components"
description="Build UI"
variant="accent"
onClick={() => onNavigate?.('components')}
/>
<QuickActionButton
icon={<Package size={32} weight="duotone" />}
label="Deploy"
description="Export project"
variant="accent"
onClick={() => onNavigate?.('export')}
/>
</div>
</div>
<Card>
<CardHeader>
<CardTitle>Project Details</CardTitle>
<CardTitle className="flex items-center gap-2">
<GitBranch size={20} />
Project Details
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<DetailRow

View File

@@ -0,0 +1,62 @@
import { Card } from '@/components/ui/card'
import { cn } from '@/lib/utils'
import { ReactNode } from 'react'
interface GlowCardProps {
children: ReactNode
glowColor?: 'primary' | 'accent' | 'success' | 'warning' | 'error'
intensity?: 'low' | 'medium' | 'high'
className?: string
onClick?: () => void
}
export function GlowCard({
children,
glowColor = 'primary',
intensity = 'medium',
className,
onClick,
}: GlowCardProps) {
const glowClasses = {
primary: {
low: 'shadow-primary/10',
medium: 'shadow-primary/20 hover:shadow-primary/30',
high: 'shadow-primary/30 hover:shadow-primary/50',
},
accent: {
low: 'shadow-accent/10',
medium: 'shadow-accent/20 hover:shadow-accent/30',
high: 'shadow-accent/30 hover:shadow-accent/50',
},
success: {
low: 'shadow-green-500/10',
medium: 'shadow-green-500/20 hover:shadow-green-500/30',
high: 'shadow-green-500/30 hover:shadow-green-500/50',
},
warning: {
low: 'shadow-yellow-500/10',
medium: 'shadow-yellow-500/20 hover:shadow-yellow-500/30',
high: 'shadow-yellow-500/30 hover:shadow-yellow-500/50',
},
error: {
low: 'shadow-red-500/10',
medium: 'shadow-red-500/20 hover:shadow-red-500/30',
high: 'shadow-red-500/30 hover:shadow-red-500/50',
},
}
return (
<Card
onClick={onClick}
className={cn(
'transition-all duration-300',
'shadow-lg',
glowClasses[glowColor][intensity],
onClick && 'cursor-pointer hover:scale-[1.02]',
className
)}
>
{children}
</Card>
)
}

View File

@@ -0,0 +1,49 @@
import { cn } from '@/lib/utils'
interface LiveIndicatorProps {
label?: string
showLabel?: boolean
size?: 'sm' | 'md' | 'lg'
className?: string
}
export function LiveIndicator({
label = 'LIVE',
showLabel = true,
size = 'md',
className,
}: LiveIndicatorProps) {
const sizeClasses = {
sm: 'text-xs gap-1.5',
md: 'text-sm gap-2',
lg: 'text-base gap-2.5',
}
const dotSizeClasses = {
sm: 'w-2 h-2',
md: 'w-2.5 h-2.5',
lg: 'w-3 h-3',
}
return (
<div className={cn('inline-flex items-center font-medium', sizeClasses[size], className)}>
<span className="relative flex">
<span
className={cn(
'absolute inline-flex rounded-full bg-red-500 opacity-75 animate-ping',
dotSizeClasses[size]
)}
/>
<span
className={cn(
'relative inline-flex rounded-full bg-red-500',
dotSizeClasses[size]
)}
/>
</span>
{showLabel && (
<span className="text-red-500 font-bold tracking-wider">{label}</span>
)}
</div>
)
}

View File

@@ -0,0 +1,89 @@
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import { Minus, Plus } from '@phosphor-icons/react'
import { cn } from '@/lib/utils'
interface NumberInputProps {
value: number
onChange: (value: number) => void
min?: number
max?: number
step?: number
label?: string
disabled?: boolean
className?: string
}
export function NumberInput({
value,
onChange,
min,
max,
step = 1,
label,
disabled,
className,
}: NumberInputProps) {
const handleIncrement = () => {
const newValue = value + step
if (max === undefined || newValue <= max) {
onChange(newValue)
}
}
const handleDecrement = () => {
const newValue = value - step
if (min === undefined || newValue >= min) {
onChange(newValue)
}
}
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = parseFloat(e.target.value)
if (!isNaN(newValue)) {
if ((min === undefined || newValue >= min) && (max === undefined || newValue <= max)) {
onChange(newValue)
}
}
}
return (
<div className={cn('flex flex-col gap-2', className)}>
{label && (
<label className="text-sm font-medium text-foreground">{label}</label>
)}
<div className="flex items-center gap-2">
<Button
type="button"
variant="outline"
size="icon"
onClick={handleDecrement}
disabled={disabled || (min !== undefined && value <= min)}
className="h-9 w-9 shrink-0"
>
<Minus />
</Button>
<Input
type="number"
value={value}
onChange={handleInputChange}
min={min}
max={max}
step={step}
disabled={disabled}
className="text-center"
/>
<Button
type="button"
variant="outline"
size="icon"
onClick={handleIncrement}
disabled={disabled || (max !== undefined && value >= max)}
className="h-9 w-9 shrink-0"
>
<Plus />
</Button>
</div>
</div>
)
}

View File

@@ -0,0 +1,57 @@
import { ReactNode } from 'react'
import { cn } from '@/lib/utils'
import { Separator } from '@/components/ui/separator'
interface PanelHeaderProps {
title: string
subtitle?: string | ReactNode
icon?: ReactNode
actions?: ReactNode
className?: string
showSeparator?: boolean
}
export function PanelHeader({
title,
subtitle,
icon,
actions,
className,
showSeparator = true,
}: PanelHeaderProps) {
return (
<div className={cn('space-y-3', className)}>
<div className="flex items-start justify-between gap-4">
<div className="flex items-start gap-3 flex-1 min-w-0">
{icon && (
<div className="text-primary mt-0.5 shrink-0">
{icon}
</div>
)}
<div className="flex-1 min-w-0">
<h2 className="text-lg font-semibold text-foreground truncate">
{title}
</h2>
{subtitle && (
typeof subtitle === 'string' ? (
<p className="text-sm text-muted-foreground mt-1">
{subtitle}
</p>
) : (
<div className="mt-1">
{subtitle}
</div>
)
)}
</div>
</div>
{actions && (
<div className="flex items-center gap-2 shrink-0">
{actions}
</div>
)}
</div>
{showSeparator && <Separator />}
</div>
)
}

View File

@@ -0,0 +1,56 @@
import { cn } from '@/lib/utils'
interface PulseProps {
variant?: 'primary' | 'accent' | 'success' | 'warning' | 'error'
size?: 'sm' | 'md' | 'lg'
speed?: 'slow' | 'normal' | 'fast'
className?: string
}
export function Pulse({
variant = 'primary',
size = 'md',
speed = 'normal',
className,
}: PulseProps) {
const sizeClasses = {
sm: 'w-2 h-2',
md: 'w-3 h-3',
lg: 'w-4 h-4',
}
const variantClasses = {
primary: 'bg-primary',
accent: 'bg-accent',
success: 'bg-green-500',
warning: 'bg-yellow-500',
error: 'bg-red-500',
}
const speedClasses = {
slow: 'animate-pulse [animation-duration:3s]',
normal: 'animate-pulse',
fast: 'animate-pulse [animation-duration:0.5s]',
}
return (
<div className={cn('relative inline-flex', className)}>
<span
className={cn(
'inline-flex rounded-full opacity-75',
sizeClasses[size],
variantClasses[variant],
speedClasses[speed]
)}
/>
<span
className={cn(
'absolute inline-flex rounded-full opacity-75',
sizeClasses[size],
variantClasses[variant],
speedClasses[speed]
)}
/>
</div>
)
}

View File

@@ -0,0 +1,61 @@
import { Card } from '@/components/ui/card'
import { cn } from '@/lib/utils'
import { ReactNode } from 'react'
interface QuickActionButtonProps {
icon: ReactNode
label: string
description?: string
onClick: () => void
variant?: 'default' | 'primary' | 'accent' | 'muted'
disabled?: boolean
className?: string
}
export function QuickActionButton({
icon,
label,
description,
onClick,
variant = 'default',
disabled,
className,
}: QuickActionButtonProps) {
const variantClasses = {
default: 'hover:bg-muted/50 hover:border-border',
primary: 'hover:bg-primary/10 hover:border-primary/50',
accent: 'hover:bg-accent/10 hover:border-accent/50',
muted: 'bg-muted hover:bg-muted/70',
}
const iconColorClasses = {
default: 'text-foreground',
primary: 'text-primary',
accent: 'text-accent',
muted: 'text-muted-foreground',
}
return (
<Card
onClick={disabled ? undefined : onClick}
className={cn(
'p-6 cursor-pointer transition-all duration-200',
'flex flex-col items-center justify-center gap-3 text-center',
'hover:scale-105 active:scale-95',
variantClasses[variant],
disabled && 'opacity-50 cursor-not-allowed hover:scale-100',
className
)}
>
<div className={cn('text-4xl', iconColorClasses[variant])}>
{icon}
</div>
<div className="space-y-1">
<h3 className="font-semibold text-foreground">{label}</h3>
{description && (
<p className="text-sm text-muted-foreground">{description}</p>
)}
</div>
</Card>
)
}

View File

@@ -0,0 +1,35 @@
import { cn } from '@/lib/utils'
import { Sparkle as SparkleIcon } from '@phosphor-icons/react'
interface SparkleProps {
variant?: 'default' | 'primary' | 'accent' | 'gold'
size?: number
animate?: boolean
className?: string
}
export function Sparkle({
variant = 'default',
size = 16,
animate = true,
className,
}: SparkleProps) {
const variantClasses = {
default: 'text-foreground',
primary: 'text-primary',
accent: 'text-accent',
gold: 'text-yellow-500',
}
return (
<SparkleIcon
size={size}
weight="fill"
className={cn(
variantClasses[variant],
animate && 'animate-pulse',
className
)}
/>
)
}

View File

@@ -0,0 +1,38 @@
import { cn } from '@/lib/utils'
import { ReactNode } from 'react'
interface TextGradientProps {
children: ReactNode
from?: string
to?: string
via?: string
direction?: 'to-r' | 'to-l' | 'to-b' | 'to-t' | 'to-br' | 'to-bl' | 'to-tr' | 'to-tl'
className?: string
animate?: boolean
}
export function TextGradient({
children,
from = 'from-primary',
to = 'to-accent',
via,
direction = 'to-r',
className,
animate = false,
}: TextGradientProps) {
const gradientClasses = cn(
'bg-gradient-to-r',
from,
via,
to,
direction !== 'to-r' && `bg-gradient-${direction}`,
'bg-clip-text text-transparent',
animate && 'animate-gradient-x'
)
return (
<span className={cn(gradientClasses, className)}>
{children}
</span>
)
}

View File

@@ -101,3 +101,11 @@ export { ResponsiveGrid } from './ResponsiveGrid'
export { Flex } from './Flex'
export { CircularProgress } from './CircularProgress'
export { AvatarGroup } from './AvatarGroup'
export { NumberInput } from './NumberInput'
export { TextGradient } from './TextGradient'
export { Pulse } from './Pulse'
export { QuickActionButton } from './QuickActionButton'
export { PanelHeader } from './PanelHeader'
export { LiveIndicator } from './LiveIndicator'
export { Sparkle } from './Sparkle'
export { GlowCard } from './GlowCard'

View File

@@ -1,7 +1,9 @@
import { ComponentDefinition, getCategoryComponents } from '@/lib/component-definitions'
import { ComponentPaletteItem } from '@/components/atoms/ComponentPaletteItem'
import { PanelHeader } from '@/components/atoms'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Package } from '@phosphor-icons/react'
interface ComponentPaletteProps {
onDragStart: (component: ComponentDefinition, e: React.DragEvent) => void
@@ -17,14 +19,16 @@ export function ComponentPalette({ onDragStart }: ComponentPaletteProps) {
return (
<div className="h-full flex flex-col">
<div className="p-4 border-b border-border">
<h2 className="text-lg font-semibold">Components</h2>
<p className="text-xs text-muted-foreground mt-1">
Drag components to the canvas
</p>
<div className="p-4">
<PanelHeader
title="Components"
subtitle="Drag components to the canvas"
icon={<Package size={20} weight="duotone" />}
showSeparator={false}
/>
</div>
<Tabs defaultValue="layout" className="flex-1 flex flex-col">
<Tabs defaultValue="layout" className="flex-1 flex flex-col border-t border-border">
<TabsList className="w-full justify-start px-4 pt-2">
{categories.map((cat) => (
<TabsTrigger key={cat.id} value={cat.id} className="text-xs">

View File

@@ -1,5 +1,6 @@
import { UIComponent } from '@/types/json-ui'
import { ComponentTreeNode } from '@/components/atoms/ComponentTreeNode'
import { PanelHeader } from '@/components/atoms'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Button } from '@/components/ui/button'
import { Tree, Plus } from '@phosphor-icons/react'
@@ -60,11 +61,12 @@ export function ComponentTree({
return (
<div className="h-full flex flex-col">
<div className="p-4 border-b border-border flex items-center justify-between">
<div className="flex items-center gap-2">
<Tree className="w-5 h-5 text-primary" weight="duotone" />
<h2 className="text-lg font-semibold">Component Tree</h2>
</div>
<div className="p-4">
<PanelHeader
title="Component Tree"
subtitle={`${components.length} component${components.length !== 1 ? 's' : ''}`}
icon={<Tree size={20} weight="duotone" />}
/>
</div>
<ScrollArea className="flex-1">

View File

@@ -1,8 +1,10 @@
import { UIComponent } from '@/types/json-ui'
import { PropertyEditorField } from '@/components/atoms/PropertyEditorField'
import { PanelHeader } from '@/components/atoms'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Button } from '@/components/ui/button'
import { Separator } from '@/components/ui/separator'
import { Badge } from '@/components/ui/badge'
import { Sliders, Trash, Code } from '@phosphor-icons/react'
import { getComponentDef } from '@/lib/component-definitions'
@@ -100,26 +102,29 @@ export function PropertyEditor({ component, onUpdate, onDelete }: PropertyEditor
return (
<div className="h-full flex flex-col">
<div className="p-4 border-b border-border">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<Sliders className="w-5 h-5 text-primary" weight="duotone" />
<h2 className="text-lg font-semibold">Properties</h2>
</div>
<Button
variant="ghost"
size="sm"
onClick={onDelete}
className="text-destructive hover:text-destructive hover:bg-destructive/10"
>
<Trash className="w-4 h-4" />
</Button>
</div>
<div className="flex items-center gap-2">
<Code className="w-4 h-4 text-muted-foreground" />
<span className="text-sm font-medium">{def?.label || component.type}</span>
<span className="text-xs text-muted-foreground">#{component.id}</span>
</div>
<div className="p-4">
<PanelHeader
title="Properties"
subtitle={
<div className="flex items-center gap-2 mt-1">
<Badge variant="outline" className="text-xs font-mono">
{def?.label || component.type}
</Badge>
<span className="text-xs text-muted-foreground">#{component.id}</span>
</div>
}
icon={<Sliders size={20} weight="duotone" />}
actions={
<Button
variant="ghost"
size="sm"
onClick={onDelete}
className="text-destructive hover:text-destructive hover:bg-destructive/10"
>
<Trash className="w-4 h-4" />
</Button>
}
/>
</div>
<ScrollArea className="flex-1 p-4">