mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-24 13:44:54 +00:00
Generated by Spark: Make atomic component library until done. If done, just wire them into other components.
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
62
src/components/atoms/GlowCard.tsx
Normal file
62
src/components/atoms/GlowCard.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
49
src/components/atoms/LiveIndicator.tsx
Normal file
49
src/components/atoms/LiveIndicator.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
89
src/components/atoms/NumberInput.tsx
Normal file
89
src/components/atoms/NumberInput.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
57
src/components/atoms/PanelHeader.tsx
Normal file
57
src/components/atoms/PanelHeader.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
56
src/components/atoms/Pulse.tsx
Normal file
56
src/components/atoms/Pulse.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
61
src/components/atoms/QuickActionButton.tsx
Normal file
61
src/components/atoms/QuickActionButton.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
35
src/components/atoms/Sparkle.tsx
Normal file
35
src/components/atoms/Sparkle.tsx
Normal 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
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
38
src/components/atoms/TextGradient.tsx
Normal file
38
src/components/atoms/TextGradient.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user