mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-25 06:04:54 +00:00
feat: Delete 38 duplicate atom TSX files with JSON equivalents
Deleted the following JSON-compatible atoms that now route through json-components: - ActionButton, ActionCard, ActionIcon, Alert, AppLogo, Avatar, AvatarGroup - Badge, BindingIndicator, Breadcrumb, Button, ButtonGroup, Calendar, Card - Checkbox, Chip, CircularProgress, Code, CommandPalette, CompletionCard - ComponentPaletteItem, ConfirmButton, ContextMenu, DataSourceBadge, DataTable - Drawer, Form, Heading, HoverCard, PageHeader, SearchInput, Separator - Skeleton, Slider, Spinner, StatusIcon, StepIndicator, Stepper, Switch, Table These atoms are all marked with deleteOldTSX: true and jsonCompatible: true in the registry, and are now fully exported from src/lib/json-ui/json-components. Build status: ✅ PASSING - 38 atom files deleted - 0 TypeScript errors - All 9,408 modules transformed successfully Atoms remaining as TSX files (73): - 8 non-JSON-compatible atoms (e.g., ColorSwatch, Container, DataList) - 65 layout/utility atoms (Flex, Grid, IconButton, Stack, Text, etc.) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,51 +0,0 @@
|
||||
import { ReactNode } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from '@/components/ui/tooltip'
|
||||
|
||||
export interface ActionButtonProps {
|
||||
icon?: ReactNode
|
||||
label: string
|
||||
onClick: () => void
|
||||
variant?: 'default' | 'outline' | 'ghost' | 'destructive'
|
||||
size?: 'default' | 'sm' | 'lg' | 'icon'
|
||||
tooltip?: string
|
||||
disabled?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function ActionButton({
|
||||
icon,
|
||||
label,
|
||||
onClick,
|
||||
variant = 'default',
|
||||
size = 'default',
|
||||
tooltip,
|
||||
disabled,
|
||||
className,
|
||||
}: ActionButtonProps) {
|
||||
const button = (
|
||||
<Button
|
||||
variant={variant}
|
||||
size={size}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={className}
|
||||
>
|
||||
{icon && <span className="mr-2">{icon}</span>}
|
||||
{label}
|
||||
</Button>
|
||||
)
|
||||
|
||||
if (tooltip) {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>{button}</TooltipTrigger>
|
||||
<TooltipContent>{tooltip}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
|
||||
return button
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { CaretRight } from '@phosphor-icons/react'
|
||||
|
||||
interface ActionCardProps {
|
||||
icon?: React.ReactNode
|
||||
title: string
|
||||
description?: string
|
||||
onClick?: () => void
|
||||
className?: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export function ActionCard({ icon, title, description, onClick, className, disabled }: ActionCardProps) {
|
||||
return (
|
||||
<Card
|
||||
className={cn(
|
||||
'cursor-pointer transition-all hover:shadow-md hover:border-primary/50',
|
||||
disabled && 'opacity-50 cursor-not-allowed',
|
||||
className
|
||||
)}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
{icon && (
|
||||
<div className="flex-shrink-0 p-2 rounded-lg bg-primary/10 text-primary">
|
||||
{icon}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-semibold text-sm mb-1">{title}</div>
|
||||
{description && (
|
||||
<div className="text-xs text-muted-foreground line-clamp-2">{description}</div>
|
||||
)}
|
||||
</div>
|
||||
<CaretRight size={16} className="flex-shrink-0 text-muted-foreground" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Plus, Pencil, Trash, Copy, Download, Upload } from '@phosphor-icons/react'
|
||||
|
||||
interface ActionIconProps {
|
||||
action: 'add' | 'edit' | 'delete' | 'copy' | 'download' | 'upload'
|
||||
size?: number
|
||||
weight?: 'thin' | 'light' | 'regular' | 'bold' | 'fill' | 'duotone'
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function ActionIcon({ action, size = 16, weight = 'regular', className = '' }: ActionIconProps) {
|
||||
const iconMap = {
|
||||
add: Plus,
|
||||
edit: Pencil,
|
||||
delete: Trash,
|
||||
copy: Copy,
|
||||
download: Download,
|
||||
upload: Upload,
|
||||
}
|
||||
|
||||
const IconComponent = iconMap[action]
|
||||
return <IconComponent size={size} weight={weight} className={className} />
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { Code } from '@phosphor-icons/react'
|
||||
|
||||
export function AppLogo() {
|
||||
return (
|
||||
<div className="w-8 h-8 sm:w-10 sm:h-10 rounded-lg bg-gradient-to-br from-primary to-accent flex items-center justify-center shrink-0">
|
||||
<Code size={20} weight="duotone" className="text-white sm:w-6 sm:h-6" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface AvatarGroupProps {
|
||||
avatars: {
|
||||
src?: string
|
||||
alt: string
|
||||
fallback: string
|
||||
}[]
|
||||
max?: number
|
||||
size?: 'xs' | 'sm' | 'md' | 'lg'
|
||||
className?: string
|
||||
}
|
||||
|
||||
const sizeClasses = {
|
||||
xs: 'h-6 w-6 text-xs',
|
||||
sm: 'h-8 w-8 text-xs',
|
||||
md: 'h-10 w-10 text-sm',
|
||||
lg: 'h-12 w-12 text-base',
|
||||
}
|
||||
|
||||
export function AvatarGroup({
|
||||
avatars,
|
||||
max = 5,
|
||||
size = 'md',
|
||||
className,
|
||||
}: AvatarGroupProps) {
|
||||
const displayAvatars = avatars.slice(0, max)
|
||||
const remainingCount = Math.max(avatars.length - max, 0)
|
||||
|
||||
return (
|
||||
<div className={cn('flex -space-x-2', className)}>
|
||||
{displayAvatars.map((avatar, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={cn(
|
||||
'relative inline-flex items-center justify-center rounded-full border-2 border-background bg-muted overflow-hidden',
|
||||
sizeClasses[size]
|
||||
)}
|
||||
title={avatar.alt}
|
||||
>
|
||||
{avatar.src ? (
|
||||
<img src={avatar.src} alt={avatar.alt} className="h-full w-full object-cover" />
|
||||
) : (
|
||||
<span className="font-medium text-foreground">{avatar.fallback}</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{remainingCount > 0 && (
|
||||
<div
|
||||
className={cn(
|
||||
'relative inline-flex items-center justify-center rounded-full border-2 border-background bg-muted',
|
||||
sizeClasses[size]
|
||||
)}
|
||||
>
|
||||
<span className="font-medium text-foreground">+{remainingCount}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { Badge as ShadcnBadge } from '@/components/ui/badge'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
interface BadgeProps {
|
||||
children: ReactNode
|
||||
variant?: 'default' | 'secondary' | 'destructive' | 'outline'
|
||||
size?: 'sm' | 'md' | 'lg'
|
||||
icon?: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'text-xs px-2 py-0.5',
|
||||
md: 'text-sm px-2.5 py-0.5',
|
||||
lg: 'text-base px-3 py-1',
|
||||
}
|
||||
|
||||
export function Badge({
|
||||
children,
|
||||
variant = 'default',
|
||||
size = 'md',
|
||||
icon,
|
||||
className,
|
||||
}: BadgeProps) {
|
||||
return (
|
||||
<ShadcnBadge
|
||||
variant={variant}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-1.5',
|
||||
sizeClasses[size],
|
||||
className
|
||||
)}
|
||||
>
|
||||
{icon && <span className="flex-shrink-0">{icon}</span>}
|
||||
{children}
|
||||
</ShadcnBadge>
|
||||
)
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { Link } from '@phosphor-icons/react'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
|
||||
interface BindingIndicatorProps {
|
||||
sourceId: string
|
||||
path?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function BindingIndicator({ sourceId, path, className = '' }: BindingIndicatorProps) {
|
||||
const bindingText = path ? `${sourceId}.${path}` : sourceId
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className={`inline-flex items-center gap-1 px-2 py-1 rounded text-xs bg-accent/10 text-accent border border-accent/30 ${className}`}>
|
||||
<Link weight="bold" className="w-3 h-3" />
|
||||
<span className="font-mono">{bindingText}</span>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p className="text-xs">Bound to: {bindingText}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { CaretRight } from '@phosphor-icons/react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface BreadcrumbItem {
|
||||
label: string
|
||||
href?: string
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
interface BreadcrumbNavProps {
|
||||
items?: BreadcrumbItem[]
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function BreadcrumbNav({ items = [], className }: BreadcrumbNavProps) {
|
||||
return (
|
||||
<nav aria-label="Breadcrumb" className={cn('flex items-center gap-2', className)}>
|
||||
{items.map((item, index) => {
|
||||
const isLast = index === items.length - 1
|
||||
const linkClassName = cn(
|
||||
'text-sm transition-colors',
|
||||
isLast ? 'text-foreground font-medium' : 'text-muted-foreground hover:text-foreground'
|
||||
)
|
||||
|
||||
return (
|
||||
<div key={index} className="flex items-center gap-2">
|
||||
{item.href ? (
|
||||
<a href={item.href} onClick={item.onClick} className={linkClassName}>
|
||||
{item.label}
|
||||
</a>
|
||||
) : item.onClick ? (
|
||||
<button onClick={item.onClick} className={linkClassName}>
|
||||
{item.label}
|
||||
</button>
|
||||
) : (
|
||||
<span
|
||||
className={cn(
|
||||
'text-sm',
|
||||
isLast ? 'text-foreground font-medium' : 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
)}
|
||||
{!isLast && <CaretRight className="w-4 h-4 text-muted-foreground" />}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
export const Breadcrumb = BreadcrumbNav
|
||||
@@ -1,43 +0,0 @@
|
||||
import { Button as ShadcnButton, ButtonProps as ShadcnButtonProps } from '@/components/ui/button'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
export interface ButtonProps extends ShadcnButtonProps {
|
||||
children: ReactNode
|
||||
leftIcon?: ReactNode
|
||||
rightIcon?: ReactNode
|
||||
loading?: boolean
|
||||
fullWidth?: boolean
|
||||
}
|
||||
|
||||
export function Button({
|
||||
children,
|
||||
leftIcon,
|
||||
rightIcon,
|
||||
loading,
|
||||
fullWidth,
|
||||
disabled,
|
||||
className,
|
||||
...props
|
||||
}: ButtonProps) {
|
||||
return (
|
||||
<ShadcnButton
|
||||
disabled={disabled || loading}
|
||||
className={cn(fullWidth && 'w-full', className)}
|
||||
{...props}
|
||||
>
|
||||
{loading ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-4 w-4 border-2 border-current border-t-transparent rounded-full animate-spin" />
|
||||
<span>{children}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
{leftIcon && <span className="flex-shrink-0">{leftIcon}</span>}
|
||||
<span>{children}</span>
|
||||
{rightIcon && <span className="flex-shrink-0">{rightIcon}</span>}
|
||||
</div>
|
||||
)}
|
||||
</ShadcnButton>
|
||||
)
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
interface ButtonGroupProps {
|
||||
children: ReactNode
|
||||
orientation?: 'horizontal' | 'vertical'
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function ButtonGroup({
|
||||
children,
|
||||
orientation = 'horizontal',
|
||||
className,
|
||||
}: ButtonGroupProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'inline-flex',
|
||||
orientation === 'horizontal' ? 'flex-row' : 'flex-col',
|
||||
'[&>button]:rounded-none',
|
||||
'[&>button:first-child]:rounded-l-md',
|
||||
'[&>button:last-child]:rounded-r-md',
|
||||
orientation === 'vertical' && '[&>button:first-child]:rounded-t-md [&>button:first-child]:rounded-l-none',
|
||||
orientation === 'vertical' && '[&>button:last-child]:rounded-b-md [&>button:last-child]:rounded-r-none',
|
||||
'[&>button:not(:last-child)]:border-r-0',
|
||||
orientation === 'vertical' && '[&>button:not(:last-child)]:border-b-0 [&>button:not(:last-child)]:border-r',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { Calendar as ShadcnCalendar } from '@/components/ui/calendar'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface CalendarProps {
|
||||
selected?: Date
|
||||
onSelect?: (date: Date | undefined) => void
|
||||
mode?: 'single' | 'multiple' | 'range'
|
||||
disabled?: Date | ((date: Date) => boolean)
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function Calendar({
|
||||
selected,
|
||||
onSelect,
|
||||
mode = 'single',
|
||||
disabled,
|
||||
className,
|
||||
}: CalendarProps) {
|
||||
return (
|
||||
<ShadcnCalendar
|
||||
mode={mode as any}
|
||||
selected={selected}
|
||||
onSelect={onSelect as any}
|
||||
disabled={disabled}
|
||||
className={cn('rounded-md border', className)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface CardProps {
|
||||
children: React.ReactNode
|
||||
variant?: 'default' | 'bordered' | 'elevated' | 'flat'
|
||||
padding?: 'none' | 'sm' | 'md' | 'lg'
|
||||
hover?: boolean
|
||||
className?: string
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
export function Card({
|
||||
children,
|
||||
variant = 'default',
|
||||
padding = 'md',
|
||||
hover = false,
|
||||
className,
|
||||
onClick
|
||||
}: CardProps) {
|
||||
const variantStyles = {
|
||||
default: 'bg-card border border-border',
|
||||
bordered: 'bg-background border-2 border-border',
|
||||
elevated: 'bg-card shadow-lg border border-border',
|
||||
flat: 'bg-muted',
|
||||
}
|
||||
|
||||
const paddingStyles = {
|
||||
none: 'p-0',
|
||||
sm: 'p-3',
|
||||
md: 'p-6',
|
||||
lg: 'p-8',
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
'rounded-lg transition-all',
|
||||
variantStyles[variant],
|
||||
paddingStyles[padding],
|
||||
hover && 'hover:shadow-md hover:scale-[1.01] cursor-pointer',
|
||||
onClick && 'cursor-pointer',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import { Check, Minus } from '@phosphor-icons/react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface CheckboxProps {
|
||||
checked: boolean
|
||||
onChange: (checked: boolean) => void
|
||||
label?: string
|
||||
indeterminate?: boolean
|
||||
disabled?: boolean
|
||||
size?: 'sm' | 'md' | 'lg'
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function Checkbox({
|
||||
checked,
|
||||
onChange,
|
||||
label,
|
||||
indeterminate = false,
|
||||
disabled = false,
|
||||
size = 'md',
|
||||
className
|
||||
}: CheckboxProps) {
|
||||
const sizeStyles = {
|
||||
sm: 'w-4 h-4',
|
||||
md: 'w-5 h-5',
|
||||
lg: 'w-6 h-6',
|
||||
}
|
||||
|
||||
const iconSize = {
|
||||
sm: 12,
|
||||
md: 16,
|
||||
lg: 20,
|
||||
}
|
||||
|
||||
return (
|
||||
<label className={cn('flex items-center gap-2 cursor-pointer', disabled && 'opacity-50 cursor-not-allowed', className)}>
|
||||
<button
|
||||
type="button"
|
||||
role="checkbox"
|
||||
aria-checked={indeterminate ? 'mixed' : checked}
|
||||
disabled={disabled}
|
||||
onClick={() => !disabled && onChange(!checked)}
|
||||
className={cn(
|
||||
'flex items-center justify-center rounded border-2 transition-colors',
|
||||
sizeStyles[size],
|
||||
checked || indeterminate
|
||||
? 'bg-primary border-primary text-primary-foreground'
|
||||
: 'bg-background border-input hover:border-ring'
|
||||
)}
|
||||
>
|
||||
{indeterminate ? (
|
||||
<Minus size={iconSize[size]} weight="bold" />
|
||||
) : checked ? (
|
||||
<Check size={iconSize[size]} weight="bold" />
|
||||
) : null}
|
||||
</button>
|
||||
{label && <span className="text-sm font-medium select-none">{label}</span>}
|
||||
</label>
|
||||
)
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface CircularProgressProps {
|
||||
value: number
|
||||
max?: number
|
||||
size?: 'sm' | 'md' | 'lg' | 'xl'
|
||||
showLabel?: boolean
|
||||
strokeWidth?: number
|
||||
className?: string
|
||||
}
|
||||
|
||||
const sizeClasses = {
|
||||
sm: { dimension: 48, stroke: 4, fontSize: 'text-xs' },
|
||||
md: { dimension: 64, stroke: 5, fontSize: 'text-sm' },
|
||||
lg: { dimension: 96, stroke: 6, fontSize: 'text-base' },
|
||||
xl: { dimension: 128, stroke: 8, fontSize: 'text-lg' },
|
||||
}
|
||||
|
||||
export function CircularProgress({
|
||||
value,
|
||||
max = 100,
|
||||
size = 'md',
|
||||
showLabel = true,
|
||||
strokeWidth,
|
||||
className,
|
||||
}: CircularProgressProps) {
|
||||
const { dimension, stroke, fontSize } = sizeClasses[size]
|
||||
const actualStroke = strokeWidth || stroke
|
||||
const percentage = Math.min((value / max) * 100, 100)
|
||||
const radius = (dimension - actualStroke) / 2
|
||||
const circumference = radius * 2 * Math.PI
|
||||
const offset = circumference - (percentage / 100) * circumference
|
||||
|
||||
return (
|
||||
<div className={cn('relative inline-flex items-center justify-center', className)}>
|
||||
<svg width={dimension} height={dimension} className="transform -rotate-90">
|
||||
<circle
|
||||
cx={dimension / 2}
|
||||
cy={dimension / 2}
|
||||
r={radius}
|
||||
stroke="currentColor"
|
||||
strokeWidth={actualStroke}
|
||||
fill="none"
|
||||
className="text-muted opacity-20"
|
||||
/>
|
||||
<circle
|
||||
cx={dimension / 2}
|
||||
cy={dimension / 2}
|
||||
r={radius}
|
||||
stroke="currentColor"
|
||||
strokeWidth={actualStroke}
|
||||
fill="none"
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={offset}
|
||||
strokeLinecap="round"
|
||||
className="text-primary transition-all duration-300"
|
||||
/>
|
||||
</svg>
|
||||
{showLabel && (
|
||||
<span className={cn('absolute font-semibold', fontSize)}>
|
||||
{Math.round(percentage)}%
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import {
|
||||
Command,
|
||||
CommandDialog,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from '@/components/ui/command'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
interface CommandOption {
|
||||
value: string
|
||||
label: string
|
||||
icon?: ReactNode
|
||||
onSelect?: () => void
|
||||
}
|
||||
|
||||
interface CommandPaletteProps {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
placeholder?: string
|
||||
emptyMessage?: string
|
||||
groups: {
|
||||
heading?: string
|
||||
items: CommandOption[]
|
||||
}[]
|
||||
}
|
||||
|
||||
export function CommandPalette({
|
||||
open,
|
||||
onOpenChange,
|
||||
placeholder = 'Type a command or search...',
|
||||
emptyMessage = 'No results found.',
|
||||
groups,
|
||||
}: CommandPaletteProps) {
|
||||
return (
|
||||
<CommandDialog open={open} onOpenChange={onOpenChange}>
|
||||
<CommandInput placeholder={placeholder} />
|
||||
<CommandList>
|
||||
<CommandEmpty>{emptyMessage}</CommandEmpty>
|
||||
{groups.map((group, groupIndex) => (
|
||||
<CommandGroup key={groupIndex} heading={group.heading}>
|
||||
{group.items.map((item) => (
|
||||
<CommandItem
|
||||
key={item.value}
|
||||
value={item.value}
|
||||
onSelect={() => {
|
||||
item.onSelect?.()
|
||||
onOpenChange(false)
|
||||
}}
|
||||
>
|
||||
{item.icon && <span className="mr-2">{item.icon}</span>}
|
||||
<span>{item.label}</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
))}
|
||||
</CommandList>
|
||||
</CommandDialog>
|
||||
)
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { CheckCircle } from '@phosphor-icons/react'
|
||||
|
||||
interface CompletionCardProps {
|
||||
completionScore: number
|
||||
completionMessage: string
|
||||
isReadyToExport: boolean
|
||||
}
|
||||
|
||||
export function CompletionCard({
|
||||
completionScore,
|
||||
completionMessage,
|
||||
isReadyToExport
|
||||
}: CompletionCardProps) {
|
||||
return (
|
||||
<Card className="bg-gradient-to-br from-primary/10 to-accent/10 border-primary/20">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<CheckCircle size={24} weight="duotone" className="text-primary" />
|
||||
Project Completeness
|
||||
</CardTitle>
|
||||
<CardDescription>Overall progress of your application</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-4xl font-bold">{completionScore}%</span>
|
||||
<Badge variant={isReadyToExport ? 'default' : 'secondary'} className="text-sm">
|
||||
{isReadyToExport ? 'Ready to Export' : 'In Progress'}
|
||||
</Badge>
|
||||
</div>
|
||||
<Progress value={completionScore} className="h-3" />
|
||||
<p className="text-sm text-muted-foreground">{completionMessage}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { ComponentDefinition } from '@/lib/component-definition-types'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import * as Icons from '@phosphor-icons/react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface ComponentPaletteItemProps {
|
||||
component: ComponentDefinition
|
||||
onDragStart: (component: ComponentDefinition, e: React.DragEvent) => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function ComponentPaletteItem({ component, onDragStart, className }: ComponentPaletteItemProps) {
|
||||
const IconComponent = (Icons as any)[component.icon] || Icons.Cube
|
||||
|
||||
return (
|
||||
<Card
|
||||
draggable
|
||||
onDragStart={(e) => onDragStart(component, e)}
|
||||
className={cn(
|
||||
'p-3 cursor-move hover:bg-accent/50 hover:border-accent transition-all',
|
||||
'flex flex-col items-center gap-2 text-center',
|
||||
'hover:scale-105 active:scale-95',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<IconComponent className="w-6 h-6 text-primary" weight="duotone" />
|
||||
<span className="text-xs font-medium text-foreground">{component.label}</span>
|
||||
<span className="text-[10px] text-muted-foreground">{component.type}</span>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Button, ButtonProps } from '@/components/ui/button'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface ConfirmButtonProps extends Omit<ButtonProps, 'onClick'> {
|
||||
onConfirm: () => void | Promise<void>
|
||||
confirmText?: string
|
||||
isLoading?: boolean
|
||||
}
|
||||
|
||||
export function ConfirmButton({
|
||||
onConfirm,
|
||||
confirmText = 'Are you sure?',
|
||||
isLoading,
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: ConfirmButtonProps) {
|
||||
const handleClick = async () => {
|
||||
if (window.confirm(confirmText)) {
|
||||
await onConfirm()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
disabled={isLoading}
|
||||
className={cn(className)}
|
||||
{...props}
|
||||
>
|
||||
{isLoading ? 'Loading...' : children}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import {
|
||||
ContextMenu as ShadcnContextMenu,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuTrigger,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuSub,
|
||||
ContextMenuSubContent,
|
||||
ContextMenuSubTrigger,
|
||||
} from '@/components/ui/context-menu'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
export interface ContextMenuItemType {
|
||||
label: string
|
||||
icon?: ReactNode
|
||||
shortcut?: string
|
||||
onSelect?: () => void
|
||||
disabled?: boolean
|
||||
separator?: boolean
|
||||
submenu?: ContextMenuItemType[]
|
||||
}
|
||||
|
||||
interface ContextMenuProps {
|
||||
trigger: ReactNode
|
||||
items: ContextMenuItemType[]
|
||||
}
|
||||
|
||||
export function ContextMenu({ trigger, items }: ContextMenuProps) {
|
||||
const renderItems = (menuItems: ContextMenuItemType[]) => {
|
||||
return menuItems.map((item, index) => {
|
||||
if (item.separator) {
|
||||
return <ContextMenuSeparator key={`separator-${index}`} />
|
||||
}
|
||||
|
||||
if (item.submenu && item.submenu.length > 0) {
|
||||
return (
|
||||
<ContextMenuSub key={index}>
|
||||
<ContextMenuSubTrigger>
|
||||
{item.icon && <span className="mr-2">{item.icon}</span>}
|
||||
{item.label}
|
||||
</ContextMenuSubTrigger>
|
||||
<ContextMenuSubContent>
|
||||
{renderItems(item.submenu)}
|
||||
</ContextMenuSubContent>
|
||||
</ContextMenuSub>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ContextMenuItem
|
||||
key={index}
|
||||
onSelect={item.onSelect}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
{item.icon && <span className="mr-2">{item.icon}</span>}
|
||||
<span className="flex-1">{item.label}</span>
|
||||
{item.shortcut && (
|
||||
<span className="ml-auto text-xs text-muted-foreground">
|
||||
{item.shortcut}
|
||||
</span>
|
||||
)}
|
||||
</ContextMenuItem>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<ShadcnContextMenu>
|
||||
<ContextMenuTrigger asChild>{trigger}</ContextMenuTrigger>
|
||||
<ContextMenuContent>{renderItems(items)}</ContextMenuContent>
|
||||
</ShadcnContextMenu>
|
||||
)
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { DataSourceType } from '@/types/json-ui'
|
||||
import { Database, File } from '@phosphor-icons/react'
|
||||
|
||||
interface DataSourceBadgeProps {
|
||||
type: DataSourceType
|
||||
className?: string
|
||||
}
|
||||
|
||||
const dataSourceConfig = {
|
||||
kv: {
|
||||
icon: Database,
|
||||
label: 'KV Storage',
|
||||
className: 'bg-accent/20 text-accent border-accent/30'
|
||||
},
|
||||
static: {
|
||||
icon: File,
|
||||
label: 'Static',
|
||||
className: 'bg-muted text-muted-foreground border-border'
|
||||
}
|
||||
}
|
||||
|
||||
export function DataSourceBadge({ type, className = '' }: DataSourceBadgeProps) {
|
||||
const config = dataSourceConfig[type]
|
||||
const Icon = config.icon
|
||||
|
||||
return (
|
||||
<Badge className={`flex items-center gap-1 ${config.className} ${className}`} variant="outline">
|
||||
<Icon className="w-3 h-3" weight="bold" />
|
||||
<span>{config.label}</span>
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
export interface Column<T> {
|
||||
key: string
|
||||
header: string | ReactNode
|
||||
cell?: (item: T) => ReactNode
|
||||
sortable?: boolean
|
||||
width?: string
|
||||
}
|
||||
|
||||
interface DataTableProps<T> {
|
||||
data: T[]
|
||||
columns: Column<T>[]
|
||||
onRowClick?: (item: T) => void
|
||||
emptyMessage?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function DataTable<T extends Record<string, any>>({
|
||||
data,
|
||||
columns,
|
||||
onRowClick,
|
||||
emptyMessage = 'No data available',
|
||||
className,
|
||||
}: DataTableProps<T>) {
|
||||
return (
|
||||
<div className={cn('rounded-md border', className)}>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
{columns.map((column) => (
|
||||
<TableHead
|
||||
key={column.key}
|
||||
style={{ width: column.width }}
|
||||
className={cn(column.sortable && 'cursor-pointer select-none')}
|
||||
>
|
||||
{column.header}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="text-center py-8 text-muted-foreground">
|
||||
{emptyMessage}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
data.map((item, rowIndex) => (
|
||||
<TableRow
|
||||
key={rowIndex}
|
||||
onClick={() => onRowClick?.(item)}
|
||||
className={cn(onRowClick && 'cursor-pointer')}
|
||||
>
|
||||
{columns.map((column) => (
|
||||
<TableCell key={column.key}>
|
||||
{column.cell ? column.cell(item) : item[column.key]}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import { X } from '@phosphor-icons/react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface DrawerProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
title?: string
|
||||
children: React.ReactNode
|
||||
position?: 'left' | 'right' | 'top' | 'bottom'
|
||||
size?: 'sm' | 'md' | 'lg'
|
||||
showCloseButton?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function Drawer({
|
||||
isOpen,
|
||||
onClose,
|
||||
title,
|
||||
children,
|
||||
position = 'right',
|
||||
size = 'md',
|
||||
showCloseButton = true,
|
||||
className,
|
||||
}: DrawerProps) {
|
||||
if (!isOpen) return null
|
||||
|
||||
const positionStyles = {
|
||||
left: 'left-0 top-0 h-full',
|
||||
right: 'right-0 top-0 h-full',
|
||||
top: 'top-0 left-0 w-full',
|
||||
bottom: 'bottom-0 left-0 w-full',
|
||||
}
|
||||
|
||||
const sizeStyles = {
|
||||
sm: position === 'left' || position === 'right' ? 'w-64' : 'h-64',
|
||||
md: position === 'left' || position === 'right' ? 'w-96' : 'h-96',
|
||||
lg: position === 'left' || position === 'right' ? 'w-[600px]' : 'h-[600px]',
|
||||
}
|
||||
|
||||
const slideAnimation = {
|
||||
left: 'animate-in slide-in-from-left',
|
||||
right: 'animate-in slide-in-from-right',
|
||||
top: 'animate-in slide-in-from-top',
|
||||
bottom: 'animate-in slide-in-from-bottom',
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm animate-in fade-in-0"
|
||||
onClick={onClose}
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
'fixed z-50 bg-card border border-border shadow-lg',
|
||||
positionStyles[position],
|
||||
sizeStyles[size],
|
||||
slideAnimation[position],
|
||||
className
|
||||
)}
|
||||
>
|
||||
{(title || showCloseButton) && (
|
||||
<div className="flex items-center justify-between p-6 border-b border-border">
|
||||
{title && <h2 className="text-lg font-semibold">{title}</h2>}
|
||||
{showCloseButton && (
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="ml-auto p-1 rounded-md hover:bg-accent transition-colors"
|
||||
aria-label="Close drawer"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="p-6 overflow-auto h-full">{children}</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import {
|
||||
Form as ShadcnForm,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import { UseFormReturn } from 'react-hook-form'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
interface FormProps {
|
||||
form: UseFormReturn<any>
|
||||
onSubmit: (values: any) => void | Promise<void>
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function Form({ form, onSubmit, children, className }: FormProps) {
|
||||
return (
|
||||
<ShadcnForm {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className={className}>
|
||||
{children}
|
||||
</form>
|
||||
</ShadcnForm>
|
||||
)
|
||||
}
|
||||
|
||||
export { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage }
|
||||
@@ -1,24 +0,0 @@
|
||||
import { ReactNode, createElement } from 'react'
|
||||
|
||||
interface HeadingProps {
|
||||
children: ReactNode
|
||||
level?: 1 | 2 | 3 | 4 | 5 | 6
|
||||
className?: string
|
||||
}
|
||||
|
||||
const levelClasses = {
|
||||
1: 'text-4xl font-bold tracking-tight',
|
||||
2: 'text-3xl font-semibold tracking-tight',
|
||||
3: 'text-2xl font-semibold tracking-tight',
|
||||
4: 'text-xl font-semibold',
|
||||
5: 'text-lg font-medium',
|
||||
6: 'text-base font-medium',
|
||||
}
|
||||
|
||||
export function Heading({ children, level = 1, className = '' }: HeadingProps) {
|
||||
return createElement(
|
||||
`h${level}`,
|
||||
{ className: `${levelClasses[level]} ${className}` },
|
||||
children
|
||||
)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import {
|
||||
HoverCard as ShadcnHoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from '@/components/ui/hover-card'
|
||||
import { ReactNode } from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface HoverCardProps {
|
||||
trigger: ReactNode
|
||||
children: ReactNode
|
||||
side?: 'top' | 'right' | 'bottom' | 'left'
|
||||
align?: 'start' | 'center' | 'end'
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function HoverCard({
|
||||
trigger,
|
||||
children,
|
||||
side = 'bottom',
|
||||
align = 'center',
|
||||
className,
|
||||
}: HoverCardProps) {
|
||||
return (
|
||||
<ShadcnHoverCard>
|
||||
<HoverCardTrigger asChild>{trigger}</HoverCardTrigger>
|
||||
<HoverCardContent side={side} align={align} className={cn(className)}>
|
||||
{children}
|
||||
</HoverCardContent>
|
||||
</ShadcnHoverCard>
|
||||
)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Separator as ShadcnSeparator } from '@/components/ui/separator'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface SeparatorProps {
|
||||
orientation?: 'horizontal' | 'vertical'
|
||||
decorative?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function Separator({
|
||||
orientation = 'horizontal',
|
||||
decorative = true,
|
||||
className,
|
||||
}: SeparatorProps) {
|
||||
return (
|
||||
<ShadcnSeparator
|
||||
orientation={orientation}
|
||||
decorative={decorative}
|
||||
className={className}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
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,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface SliderProps {
|
||||
value: number
|
||||
onChange: (value: number) => void
|
||||
min?: number
|
||||
max?: number
|
||||
step?: number
|
||||
label?: string
|
||||
showValue?: boolean
|
||||
disabled?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function Slider({
|
||||
value,
|
||||
onChange,
|
||||
min = 0,
|
||||
max = 100,
|
||||
step = 1,
|
||||
label,
|
||||
showValue = false,
|
||||
disabled = false,
|
||||
className
|
||||
}: SliderProps) {
|
||||
const percentage = ((value - min) / (max - min)) * 100
|
||||
|
||||
return (
|
||||
<div className={cn('w-full', className)}>
|
||||
{(label || showValue) && (
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
{label && <span className="text-sm font-medium">{label}</span>}
|
||||
{showValue && <span className="text-sm text-muted-foreground">{value}</span>}
|
||||
</div>
|
||||
)}
|
||||
<div className="relative">
|
||||
<input
|
||||
type="range"
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
value={value}
|
||||
onChange={(e) => onChange(Number(e.target.value))}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
'w-full h-2 bg-secondary rounded-lg appearance-none cursor-pointer',
|
||||
'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
||||
disabled && 'opacity-50 cursor-not-allowed',
|
||||
'[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:h-5',
|
||||
'[&::-webkit-slider-thumb]:bg-primary [&::-webkit-slider-thumb]:rounded-full',
|
||||
'[&::-webkit-slider-thumb]:cursor-pointer [&::-webkit-slider-thumb]:transition-transform',
|
||||
'[&::-webkit-slider-thumb]:hover:scale-110',
|
||||
'[&::-moz-range-thumb]:w-5 [&::-moz-range-thumb]:h-5 [&::-moz-range-thumb]:bg-primary',
|
||||
'[&::-moz-range-thumb]:border-0 [&::-moz-range-thumb]:rounded-full',
|
||||
'[&::-moz-range-thumb]:cursor-pointer [&::-moz-range-thumb]:transition-transform',
|
||||
'[&::-moz-range-thumb]:hover:scale-110'
|
||||
)}
|
||||
style={{
|
||||
background: `linear-gradient(to right, hsl(var(--primary)) 0%, hsl(var(--primary)) ${percentage}%, hsl(var(--secondary)) ${percentage}%, hsl(var(--secondary)) 100%)`
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
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)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { CheckCircle, CloudCheck } from '@phosphor-icons/react'
|
||||
|
||||
interface StatusIconProps {
|
||||
type: 'saved' | 'synced'
|
||||
size?: number
|
||||
animate?: boolean
|
||||
}
|
||||
|
||||
export function StatusIcon({ type, size = 14, animate = false }: StatusIconProps) {
|
||||
const baseClassName = type === 'saved' ? 'text-accent' : ''
|
||||
const animateClassName = animate ? 'animate-in zoom-in duration-200' : ''
|
||||
const className = [baseClassName, animateClassName].filter(Boolean).join(' ')
|
||||
|
||||
if (type === 'saved') {
|
||||
return (
|
||||
<CheckCircle
|
||||
size={size}
|
||||
weight="fill"
|
||||
className={className}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return <CloudCheck size={size} weight="duotone" />
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Check } from '@phosphor-icons/react'
|
||||
|
||||
interface StepIndicatorProps {
|
||||
steps: Array<{
|
||||
id: string
|
||||
label: string
|
||||
}>
|
||||
currentStep: string
|
||||
completedSteps?: string[]
|
||||
onStepClick?: (stepId: string) => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function StepIndicator({
|
||||
steps,
|
||||
currentStep,
|
||||
completedSteps = [],
|
||||
onStepClick,
|
||||
className
|
||||
}: StepIndicatorProps) {
|
||||
return (
|
||||
<div className={cn('flex items-center gap-2', className)}>
|
||||
{steps.map((step, index) => {
|
||||
const isCompleted = completedSteps.includes(step.id)
|
||||
const isCurrent = step.id === currentStep
|
||||
const isClickable = !!onStepClick
|
||||
|
||||
return (
|
||||
<div key={step.id} className="flex items-center gap-2">
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center gap-2',
|
||||
isClickable && 'cursor-pointer'
|
||||
)}
|
||||
onClick={() => isClickable && onStepClick(step.id)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-center w-8 h-8 rounded-full text-sm font-medium transition-colors',
|
||||
isCompleted && 'bg-accent text-accent-foreground',
|
||||
isCurrent && !isCompleted && 'bg-primary text-primary-foreground',
|
||||
!isCurrent && !isCompleted && 'bg-muted text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{isCompleted ? <Check size={16} weight="bold" /> : index + 1}
|
||||
</div>
|
||||
<span className={cn(
|
||||
'text-sm font-medium',
|
||||
isCurrent && 'text-foreground',
|
||||
!isCurrent && 'text-muted-foreground'
|
||||
)}>
|
||||
{step.label}
|
||||
</span>
|
||||
</div>
|
||||
{index < steps.length - 1 && (
|
||||
<div className={cn(
|
||||
'w-8 h-0.5',
|
||||
completedSteps.includes(steps[index + 1].id) ? 'bg-accent' : 'bg-border'
|
||||
)} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import { Check } from '@phosphor-icons/react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface Step {
|
||||
label: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
interface StepperProps {
|
||||
steps: Step[]
|
||||
currentStep: number
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function Stepper({ steps, currentStep, className }: StepperProps) {
|
||||
return (
|
||||
<div className={cn('w-full', className)}>
|
||||
<div className="flex items-center justify-between">
|
||||
{steps.map((step, index) => {
|
||||
const isCompleted = index < currentStep
|
||||
const isCurrent = index === currentStep
|
||||
const isLast = index === steps.length - 1
|
||||
|
||||
return (
|
||||
<div key={index} className="flex items-center flex-1">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<div
|
||||
className={cn(
|
||||
'w-10 h-10 rounded-full flex items-center justify-center font-bold text-sm transition-colors',
|
||||
isCompleted && 'bg-primary text-primary-foreground',
|
||||
isCurrent && 'bg-primary text-primary-foreground ring-4 ring-primary/20',
|
||||
!isCompleted && !isCurrent && 'bg-muted text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{isCompleted ? <Check weight="bold" /> : index + 1}
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div
|
||||
className={cn(
|
||||
'text-sm font-medium',
|
||||
(isCompleted || isCurrent) ? 'text-foreground' : 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{step.label}
|
||||
</div>
|
||||
{step.description && (
|
||||
<div className="text-xs text-muted-foreground mt-0.5">
|
||||
{step.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!isLast && (
|
||||
<div
|
||||
className={cn(
|
||||
'flex-1 h-0.5 mx-4 transition-colors',
|
||||
isCompleted ? 'bg-primary' : 'bg-muted'
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import { Switch as ShadcnSwitch } from '@/components/ui/switch'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface SwitchProps {
|
||||
checked: boolean
|
||||
onCheckedChange: (checked: boolean) => void
|
||||
label?: string
|
||||
description?: string
|
||||
disabled?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function Switch({
|
||||
checked,
|
||||
onCheckedChange,
|
||||
label,
|
||||
description,
|
||||
disabled,
|
||||
className,
|
||||
}: SwitchProps) {
|
||||
if (!label) {
|
||||
return (
|
||||
<ShadcnSwitch
|
||||
checked={checked}
|
||||
onCheckedChange={onCheckedChange}
|
||||
disabled={disabled}
|
||||
className={className}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('flex items-center justify-between gap-4', className)}>
|
||||
<div className="flex-1 space-y-1">
|
||||
<Label className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
||||
{label}
|
||||
</Label>
|
||||
{description && (
|
||||
<p className="text-sm text-muted-foreground">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
<ShadcnSwitch
|
||||
checked={checked}
|
||||
onCheckedChange={onCheckedChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface TableColumn<T> {
|
||||
key: keyof T | string
|
||||
header: string
|
||||
render?: (item: T) => React.ReactNode
|
||||
width?: string
|
||||
}
|
||||
|
||||
interface TableProps<T> {
|
||||
data: T[]
|
||||
columns: TableColumn<T>[]
|
||||
onRowClick?: (item: T) => void
|
||||
striped?: boolean
|
||||
hoverable?: boolean
|
||||
compact?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function Table<T extends Record<string, any>>({
|
||||
data,
|
||||
columns,
|
||||
onRowClick,
|
||||
striped = false,
|
||||
hoverable = true,
|
||||
compact = false,
|
||||
className,
|
||||
}: TableProps<T>) {
|
||||
return (
|
||||
<div className={cn('w-full overflow-auto', className)}>
|
||||
<table className="w-full border-collapse">
|
||||
<thead>
|
||||
<tr className="border-b border-border bg-muted/50">
|
||||
{columns.map((column, index) => (
|
||||
<th
|
||||
key={index}
|
||||
className={cn(
|
||||
'text-left font-medium text-sm text-muted-foreground',
|
||||
compact ? 'px-3 py-2' : 'px-4 py-3',
|
||||
column.width && `w-[${column.width}]`
|
||||
)}
|
||||
>
|
||||
{column.header}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.map((item, rowIndex) => (
|
||||
<tr
|
||||
key={rowIndex}
|
||||
onClick={() => onRowClick?.(item)}
|
||||
className={cn(
|
||||
'border-b border-border transition-colors',
|
||||
striped && rowIndex % 2 === 1 && 'bg-muted/30',
|
||||
hoverable && 'hover:bg-muted/50',
|
||||
onRowClick && 'cursor-pointer'
|
||||
)}
|
||||
>
|
||||
{columns.map((column, colIndex) => (
|
||||
<td
|
||||
key={colIndex}
|
||||
className={cn(
|
||||
'text-sm',
|
||||
compact ? 'px-3 py-2' : 'px-4 py-3'
|
||||
)}
|
||||
>
|
||||
{column.render
|
||||
? column.render(item)
|
||||
: item[column.key as keyof T]}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{data.length === 0 && (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
No data available
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ComponentDefinition } from '@/lib/component-definition-types'
|
||||
import { getCategoryComponents } from '@/lib/component-definition-utils'
|
||||
import { ComponentPaletteItem } from '@/components/atoms/ComponentPaletteItem'
|
||||
import { ComponentPaletteItem } from '@/components/atoms'
|
||||
import { PanelHeader, Stack } from '@/components/atoms'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Card, IconButton, Stack, Flex, Text } from '@/components/atoms'
|
||||
import { DataSourceBadge } from '@/components/atoms/DataSourceBadge'
|
||||
import { DataSourceBadge } from '@/components/atoms'
|
||||
import { DataSource } from '@/types/json-ui'
|
||||
import { Pencil, Trash } from '@phosphor-icons/react'
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { DataSource } from '@/types/json-ui'
|
||||
import { DataSourceBadge } from '@/components/atoms/DataSourceBadge'
|
||||
import { DataSourceBadge } from '@/components/atoms'
|
||||
import { DataSourceIdField } from '@/components/molecules/data-source-editor/DataSourceIdField'
|
||||
import { KvSourceFields } from '@/components/molecules/data-source-editor/KvSourceFields'
|
||||
import { StaticSourceFields } from '@/components/molecules/data-source-editor/StaticSourceFields'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UIComponent } from '@/types/json-ui'
|
||||
import { ComponentTreeNode } from '@/components/atoms/ComponentTreeNode'
|
||||
import { ComponentTreeNode } from '@/components/atoms'
|
||||
|
||||
interface ComponentTreeNodesProps {
|
||||
components: UIComponent[]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PropertyEditorField } from '@/components/atoms/PropertyEditorField'
|
||||
import { PropertyEditorField } from '@/components/atoms'
|
||||
import { Stack, Text } from '@/components/atoms'
|
||||
import { PropertyEditorFieldDefinition } from '@/components/molecules/property-editor/propertyEditorConfig'
|
||||
import { UIComponent } from '@/types/json-ui'
|
||||
|
||||
Reference in New Issue
Block a user