uitripled/components/live-editor.tsx
2025-11-17 04:03:42 +02:00

1346 lines
41 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState, useMemo } from "react";
import { Copy, Check, RotateCcw } from "lucide-react";
import { motion, AnimatePresence } from "framer-motion";
import { Button } from "./ui/button";
import {
SandpackProvider,
SandpackLayout,
SandpackCodeEditor,
SandpackPreview,
} from "@codesandbox/sandpack-react";
type LiveEditorProps = {
initialCode: string;
Component?: React.ComponentType<any>;
onRefresh?: () => void;
refreshKey?: number;
};
export function LiveEditor({ initialCode }: LiveEditorProps) {
const [code, setCode] = useState(initialCode);
const [copied, setCopied] = useState(false);
const handleCopy = async () => {
await navigator.clipboard.writeText(code);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
const handleReset = () => {
setCode(initialCode);
};
// Prepare code with replaced imports
const processedCode = useMemo(() => {
let processed = code
.replace(/@\/components\/ui\//g, "./components/ui/")
.replace(/@\/lib\//g, "./lib/");
// Ensure there's a default export
if (!processed.includes("export default")) {
// If there's a named export, convert it to default
const namedExportMatch = processed.match(
/export\s+(function|const)\s+(\w+)/
);
if (namedExportMatch) {
const componentName = namedExportMatch[2];
processed = processed.replace(/export\s+(function|const)\s+/, "$1 ");
processed += `\n\nexport default ${componentName}`;
}
}
return processed;
}, [code]);
// Prepare the files for Sandpack
const files = {
"/App.tsx": {
code: processedCode,
active: true,
},
"/lib/utils.ts": {
code: `import { clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: any[]) {
return twMerge(clsx(inputs))
}`,
},
"/components/ui/card.tsx": {
code: `import React from 'react'
import { cn } from '../../lib/utils'
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
'rounded-lg border shadow-sm',
className
)}
style={{
borderColor: 'rgb(var(--border))',
backgroundColor: 'rgb(var(--card-bg))',
color: 'rgb(var(--foreground))'
}}
{...props}
/>
)
)
Card.displayName = 'Card'
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
)
)
CardHeader.displayName = 'CardHeader'
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h3 ref={ref} className={cn('text-2xl font-semibold leading-none tracking-tight', className)} {...props} />
)
)
CardTitle.displayName = 'CardTitle'
const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => (
<p ref={ref} className={cn('text-sm', className)} style={{ color: 'rgb(var(--muted-foreground))' }} {...props} />
)
)
CardDescription.displayName = 'CardDescription'
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
)
)
CardContent.displayName = 'CardContent'
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} />
)
)
CardFooter.displayName = 'CardFooter'
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }`,
},
"/components/ui/button.tsx": {
code: `import React from 'react'
import { cn } from '../../lib/utils'
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'
size?: 'default' | 'sm' | 'lg' | 'icon'
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant = 'default', size = 'default', ...props }, ref) => {
const baseStyles = 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50'
const variants = {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-red-500 text-white hover:bg-red-600',
outline: 'border hover:bg-slate-100',
secondary: 'bg-slate-200 text-slate-900 hover:bg-slate-300',
ghost: 'hover:bg-slate-100',
link: 'text-slate-700 underline-offset-4 hover:underline',
}
const sizes = {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
}
return (
<button
className={cn(baseStyles, variants[variant], sizes[size], className)}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = 'Button'
export { Button }`,
},
"/components/ui/dropdown-menu.tsx": {
code: `'use client'
import * as React from 'react'
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
import { Check, ChevronRight, Circle } from 'lucide-react'
import { cn } from '../../lib/utils'
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',
inset && 'pl-8',
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName =
DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
'px-2 py-1.5 text-sm font-semibold',
inset && 'pl-8',
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn('-mx-1 my-1 h-px bg-muted', className)}
{...props}
/>
))
DropdownMenuSeparator.displayName =
DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn('ml-auto text-xs tracking-widest opacity-60', className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}`,
},
"/components/ui/input.tsx": {
code: `import React from 'react'
import { cn } from '../../lib/utils'
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
'flex h-10 w-full rounded-md border px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-slate-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-700 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className
)}
style={{
borderColor: 'rgb(var(--border))',
backgroundColor: 'rgb(var(--card-bg))',
color: 'rgb(var(--foreground))'
}}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = 'Input'
export { Input }`,
},
"/components/ui/badge.tsx": {
code: `import React from 'react'
import { cn } from '../../lib/utils'
interface BadgeProps extends React.HTMLAttributes<HTMLDivElement> {
variant?: 'default' | 'secondary' | 'destructive' | 'outline'
}
function Badge({ className, variant = 'default', ...props }: BadgeProps) {
const variants = {
default: 'border-transparent bg-primary text-primary-foreground hover:bg-primary/90',
secondary: 'border-transparent bg-slate-200 text-slate-900 hover:bg-slate-300',
destructive: 'border-transparent bg-red-500 text-white hover:bg-red-600',
outline: 'border-slate-300',
}
return (
<div
className={cn(
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
variants[variant],
className
)}
{...props}
/>
)
}
export { Badge }`,
},
"/components/ui/tabs.tsx": {
code: `import React from 'react'
import { cn } from '../../lib/utils'
interface TabsProps extends React.HTMLAttributes<HTMLDivElement> {
defaultValue?: string
value?: string
onValueChange?: (value: string) => void
}
const Tabs = React.forwardRef<HTMLDivElement, TabsProps>(
({ className, children, defaultValue, value, onValueChange, ...props }, ref) => {
const [activeValue, setActiveValue] = React.useState(value || defaultValue || '')
const handleValueChange = (newValue: string) => {
setActiveValue(newValue)
onValueChange?.(newValue)
}
return (
<div ref={ref} className={className} {...props}>
{React.Children.map(children, child => {
if (React.isValidElement(child)) {
return React.cloneElement(child as any, { activeValue, onValueChange: handleValueChange })
}
return child
})}
</div>
)
}
)
Tabs.displayName = 'Tabs'
const TabsList = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement> & { activeValue?: string }>(
({ className, children, activeValue, ...props }, ref) => (
<div
ref={ref}
className={cn(
'inline-flex h-10 items-center justify-center rounded-md bg-gray-100 p-1 text-gray-600 dark:bg-gray-800 dark:text-gray-400',
className
)}
{...props}
>
{React.Children.map(children, child => {
if (React.isValidElement(child)) {
return React.cloneElement(child as any, { activeValue, ...props })
}
return child
})}
</div>
)
)
TabsList.displayName = 'TabsList'
interface TabsTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
value: string
activeValue?: string
onValueChange?: (value: string) => void
}
const TabsTrigger = React.forwardRef<HTMLButtonElement, TabsTriggerProps>(
({ className, value, activeValue, onValueChange, ...props }, ref) => {
const isActive = activeValue === value
return (
<button
ref={ref}
className={cn(
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-900 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
isActive && 'bg-white text-gray-950 shadow-sm dark:bg-gray-950 dark:text-gray-50',
className
)}
onClick={() => onValueChange?.(value)}
{...props}
/>
)
}
)
TabsTrigger.displayName = 'TabsTrigger'
interface TabsContentProps extends React.HTMLAttributes<HTMLDivElement> {
value: string
activeValue?: string
}
const TabsContent = React.forwardRef<HTMLDivElement, TabsContentProps>(
({ className, value, activeValue, ...props }, ref) => {
if (activeValue !== value) return null
return (
<div
ref={ref}
className={cn(
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-900 focus-visible:ring-offset-2',
className
)}
{...props}
/>
)
}
)
TabsContent.displayName = 'TabsContent'
export { Tabs, TabsList, TabsTrigger, TabsContent }`,
},
"/components/ui/scroll-area.tsx": {
code: `import React from 'react'
import { cn } from '../../lib/utils'
const ScrollArea = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, children, ...props }, ref) => (
<div
ref={ref}
className={cn('relative overflow-hidden', className)}
{...props}
>
<div className="h-full w-full overflow-auto [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar]:h-2 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-muted [&::-webkit-scrollbar-track]:bg-transparent">
{children}
</div>
</div>
))
ScrollArea.displayName = 'ScrollArea'
const ScrollBar = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
'flex touch-none select-none transition-colors',
className
)}
{...props}
/>
))
ScrollBar.displayName = 'ScrollBar'
export { ScrollArea, ScrollBar }`,
},
"/components/ui/textarea.tsx": {
code: `import * as React from 'react'
import { cn } from '../../lib/utils'
export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className
)}
ref={ref}
{...props}
/>
)
}
)
Textarea.displayName = 'Textarea'
export { Textarea }`,
},
"/components/ui/label.tsx": {
code: `import * as React from 'react'
import { cn } from '../../lib/utils'
export interface LabelProps
extends React.LabelHTMLAttributes<HTMLLabelElement> {}
const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
({ className, ...props }, ref) => {
return (
<label
ref={ref}
className={cn(
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
className
)}
{...props}
/>
)
}
)
Label.displayName = 'Label'
export { Label }`,
},
"/components/ui/dialog.tsx": {
code: `import * as React from 'react'
import * as DialogPrimitive from '@radix-ui/react-dialog'
import { X } from 'lucide-react'
import { cn } from '../../lib/utils'
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className
)}
style={{
borderColor: 'rgb(var(--border))',
backgroundColor: 'rgb(var(--card-bg))',
color: 'rgb(var(--foreground))'
}}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col space-y-1.5 text-center sm:text-left',
className
)}
{...props}
/>
)
DialogHeader.displayName = 'DialogHeader'
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
className
)}
{...props}
/>
)
DialogFooter.displayName = 'DialogFooter'
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
'text-lg font-semibold leading-none tracking-tight',
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
style={{ color: 'rgb(var(--muted-foreground))' }}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}`,
},
"/components/ui/checkbox.tsx": {
code: `import * as React from 'react'
import { cn } from '../../lib/utils'
export interface CheckboxProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
({ className, ...props }, ref) => {
return (
<input
type="checkbox"
className={cn(
'peer h-4 w-4 shrink-0 rounded-sm border border-input ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
className
)}
ref={ref}
{...props}
/>
)
}
)
Checkbox.displayName = 'Checkbox'
export { Checkbox }`,
},
"/components/ui/switch.tsx": {
code: `import * as React from 'react'
import { cn } from '../../lib/utils'
export interface SwitchProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Switch = React.forwardRef<HTMLInputElement, SwitchProps>(
({ className, ...props }, ref) => {
return (
<input
type="checkbox"
className={cn(
'peer h-4 w-4 shrink-0 rounded-sm border border-input ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
className
)}
ref={ref}
{...props}
/>
)
}
)
Switch.displayName = 'Switch'
export { Switch }`,
},
"/components/ui/avatar.tsx": {
code: `import * as React from 'react'
import { cn } from '../../lib/utils'
export interface AvatarProps extends React.HTMLAttributes<HTMLDivElement> {}
const Avatar = React.forwardRef<HTMLDivElement, AvatarProps>(
({ className, ...props }, ref) => {
return (
<div
ref={ref}
className={cn('relative flex shrink-0 overflow-hidden rounded-full', className)}
{...props}
/>
)
}
)
Avatar.displayName = 'Avatar'
const AvatarFallback = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => {
return (
<div
ref={ref}
className={cn('flex h-full w-full items-center justify-center rounded-full bg-muted', className)}
{...props}
/>
)
}
)
AvatarFallback.displayName = 'AvatarFallback'
export { Avatar, AvatarFallback }`,
},
"/components/ui/separator.tsx": {
code: `"use client"
import * as React from 'react'
import * as SeparatorPrimitive from '@radix-ui/react-separator'
import { cn } from '../../lib/utils'
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = 'horizontal', decorative = true, ...props },
ref,
) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
'shrink-0 bg-border',
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
className,
)}
{...props}
/>
),
)
Separator.displayName = SeparatorPrimitive.Root.displayName
export { Separator }`,
},
"/styles.css": {
code: `@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--background: 0 0 0;
--foreground: 250 250 250;
--card: 23 23 23;
--card-bg: 23 23 23;
--card-foreground: 250 250 250;
--popover: 23 23 23;
--popover-foreground: 250 250 250;
--primary: 250 250 250;
--primary-foreground: 0 0 0;
--secondary: 48 48 48;
--secondary-foreground: 250 250 250;
--muted: 48 48 48;
--muted-foreground: 161 161 170;
--accent: 48 48 48;
--accent-foreground: 250 250 250;
--destructive: 239 68 68;
--destructive-foreground: 250 250 250;
--border: 48 48 48;
--input: 48 48 48;
--ring: 212 212 216;
--radius: 0.5rem;
--glass-bg: 0 0% 5% / 0.7;
--glass-border: 0 0% 100% / 0.1;
--gradient-subtle: linear-gradient(135deg, hsl(0 0% 8%), hsl(0 0% 5%));
--shadow-glass: 0 8px 32px 0 hsl(0 0% 0% / 0.5);
}
body {
margin: 0;
padding: 20px;
background: rgb(var(--background));
color: rgb(var(--foreground));
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
* {
border-color: rgb(var(--border));
}
/* Utility classes for text gradients and other effects */
.bg-clip-text {
-webkit-background-clip: text;
background-clip: text;
}
/* Custom gradient backgrounds using CSS variables */
.bg-gradient-to-r {
background-image: linear-gradient(to right, var(--tw-gradient-stops));
}
.bg-gradient-to-b {
background-image: linear-gradient(to bottom, var(--tw-gradient-stops));
}
.bg-gradient-to-br {
background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
}
.bg-gradient-to-t {
background-image: linear-gradient(to top, var(--tw-gradient-stops));
}
/* Primary color gradients */
.from-primary {
--tw-gradient-from: rgb(var(--primary));
--tw-gradient-to: rgb(var(--primary) / 0);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.to-primary {
--tw-gradient-to: rgb(var(--primary));
}
.via-primary\\/50 {
--tw-gradient-to: rgb(var(--primary) / 0);
--tw-gradient-stops: var(--tw-gradient-from), rgb(var(--primary) / 0.5), var(--tw-gradient-to);
}
.to-primary\\/5 {
--tw-gradient-to: rgb(var(--primary) / 0.05);
}
.to-primary\\/20 {
--tw-gradient-to: rgb(var(--primary) / 0.2);
}
.to-primary\\/60 {
--tw-gradient-to: rgb(var(--primary) / 0.6);
}
.from-primary\\/5 {
--tw-gradient-from: rgb(var(--primary) / 0.05);
--tw-gradient-to: rgb(var(--primary) / 0);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.via-primary\\/5 {
--tw-gradient-to: rgb(var(--primary) / 0);
--tw-gradient-stops: var(--tw-gradient-from), rgb(var(--primary) / 0.05), var(--tw-gradient-to);
}
.from-primary\\/20 {
--tw-gradient-from: rgb(var(--primary) / 0.2);
--tw-gradient-to: rgb(var(--primary) / 0);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.to-primary\\/5 {
--tw-gradient-to: rgb(var(--primary) / 0.05);
}
.from-primary\\/10 {
--tw-gradient-from: rgb(var(--primary) / 0.1);
--tw-gradient-to: rgb(var(--primary) / 0);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
/* Foreground gradients for text */
.from-foreground {
--tw-gradient-from: rgb(var(--foreground));
--tw-gradient-to: rgb(var(--foreground) / 0);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.to-foreground\\/70 {
--tw-gradient-to: rgb(var(--foreground) / 0.7);
}
.from-card {
--tw-gradient-from: rgb(var(--card));
--tw-gradient-to: rgb(var(--card) / 0);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.to-card\\/50 {
--tw-gradient-to: rgb(var(--card) / 0.5);
}
.via-card\\/60 {
--tw-gradient-to: rgb(var(--card) / 0);
--tw-gradient-stops: var(--tw-gradient-from), rgb(var(--card) / 0.6), var(--tw-gradient-to);
}
/* Purple color support for gradients */
.to-purple-500\\/10 {
--tw-gradient-to: rgb(168 85 247 / 0.1);
}
/* Background gradients */
.from-background {
--tw-gradient-from: rgb(var(--background));
--tw-gradient-to: rgb(var(--background) / 0);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.to-background {
--tw-gradient-to: rgb(var(--background));
}
.via-background {
--tw-gradient-to: rgb(var(--background) / 0);
--tw-gradient-stops: var(--tw-gradient-from), rgb(var(--background)), var(--tw-gradient-to);
}
/* Muted color support */
.from-muted\\/30 {
--tw-gradient-from: rgb(var(--muted) / 0.3);
--tw-gradient-to: rgb(var(--muted) / 0);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.to-muted\\/30 {
--tw-gradient-to: rgb(var(--muted) / 0.3);
}
.from-muted {
--tw-gradient-from: rgb(var(--muted));
--tw-gradient-to: rgb(var(--muted) / 0);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
/* Transparent gradient stops */
.to-transparent {
--tw-gradient-to: transparent;
}
/* Text color utilities */
.text-foreground {
color: rgb(var(--foreground));
}
.text-foreground\\/70 {
color: rgb(var(--foreground) / 0.7);
}
.text-foreground\\/60 {
color: rgb(var(--foreground) / 0.6);
}
.text-foreground\\/80 {
color: rgb(var(--foreground) / 0.8);
}
.text-foreground\\/40 {
color: rgb(var(--foreground) / 0.4);
}
.text-foreground\\/45 {
color: rgb(var(--foreground) / 0.45);
}
.text-muted-foreground {
color: rgb(var(--muted-foreground));
}
.text-primary-foreground {
color: rgb(var(--primary-foreground));
}
/* Background color utilities */
.bg-background {
background-color: rgb(var(--background));
}
.bg-background\\/45 {
background-color: rgb(var(--background) / 0.45);
}
.bg-background\\/55 {
background-color: rgb(var(--background) / 0.55);
}
.bg-background\\/60 {
background-color: rgb(var(--background) / 0.6);
}
.bg-background\\/70 {
background-color: rgb(var(--background) / 0.7);
}
.bg-background\\/80 {
background-color: rgb(var(--background) / 0.8);
}
.bg-foreground {
background-color: rgb(var(--foreground));
}
.bg-foreground\\/\\[0\\.035\\] {
background-color: rgb(var(--foreground) / 0.035);
}
.bg-foreground\\/\\[0\\.025\\] {
background-color: rgb(var(--foreground) / 0.025);
}
.bg-foreground\\/\\[0\\.05\\] {
background-color: rgb(var(--foreground) / 0.05);
}
.bg-foreground\\/\\[0\\.04\\] {
background-color: rgb(var(--foreground) / 0.04);
}
.bg-card {
background-color: rgb(var(--card));
}
.bg-primary {
background-color: rgb(var(--primary));
}
.bg-primary\\/20 {
background-color: rgb(var(--primary) / 0.2);
}
.bg-primary\\/15 {
background-color: rgb(var(--primary) / 0.15);
}
/* Border color utilities */
.border-border {
border-color: rgb(var(--border));
}
.border-border\\/40 {
border-color: rgb(var(--border) / 0.4);
}
.border-border\\/50 {
border-color: rgb(var(--border) / 0.5);
}
.border-border\\/60 {
border-color: rgb(var(--border) / 0.6);
}`,
},
};
return (
<div className="space-y-3 sm:space-y-4">
{/* Action Buttons */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 sm:gap-4 rounded-lg border bg-[var(--card-bg)] p-3 sm:p-4">
<div className="flex items-center gap-2 sm:gap-4">
<div className="flex flex-wrap items-center gap-2">
<motion.span
key={code}
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
className="rounded-full bg-green-500 px-2 sm:px-3 py-1 text-xs font-medium text-white"
>
Live Compiling
</motion.span>
<span className="text-xs sm:text-sm text-[var(--foreground)]/60">
{code.split("\n").length} lines {code.length} chars
</span>
</div>
</div>
<div className="flex items-center gap-2">
<Button
size="sm"
variant="ghost"
onClick={handleReset}
className="flex-1 sm:flex-initial"
>
<RotateCcw className="mr-1 sm:mr-2 h-4 w-4" />
<span className="hidden sm:inline">Reset</span>
</Button>
<Button
size="sm"
variant="default"
onClick={handleCopy}
className="flex-1 sm:flex-initial"
>
<AnimatePresence mode="wait">
{copied ? (
<motion.div
key="check"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
exit={{ scale: 0 }}
className="flex items-center gap-1 sm:gap-2"
>
<Check className="h-4 w-4" />
<span className="hidden sm:inline">Copied!</span>
</motion.div>
) : (
<motion.div
key="copy"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
exit={{ scale: 0 }}
className="flex items-center gap-1 sm:gap-2"
>
<Copy className="h-4 w-4" />
<span className="hidden sm:inline">Copy Code</span>
</motion.div>
)}
</AnimatePresence>
</Button>
</div>
</div>
{/* Sandpack Editor & Preview */}
<div className="overflow-hidden rounded-xl sm:rounded-2xl border shadow-xl">
<SandpackProvider
template="react-ts"
theme="dark"
files={files}
className="bg-card"
customSetup={{
dependencies: {
"framer-motion": "^11.0.0",
"lucide-react": "^0.263.1",
clsx: "^2.0.0",
recharts: "3.4.1",
"react-is": "^18.2.0",
"tailwind-merge": "^2.0.0",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.6",
"@radix-ui/react-popover": "^1.1.4",
"@radix-ui/react-toast": "^1.1.1",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-radio-group": "^1.1.2",
"@radix-ui/react-select": "^2.0.2",
"@radix-ui/react-separator": "^1.0.2",
"@radix-ui/react-slider": "^1.3.6",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.6",
},
}}
options={{
externalResources: ["https://cdn.tailwindcss.com"],
autorun: true,
autoReload: true,
recompileMode: "immediate",
recompileDelay: 300,
}}
>
<div className="sandpack-layout-wrapper">
<SandpackLayout className="bg-card">
<SandpackCodeEditor
style={{ height: "400px", minHeight: "400px" }}
showTabs={false}
showLineNumbers={true}
showInlineErrors={true}
wrapContent={true}
closableTabs={false}
className="bg-card"
/>
<SandpackPreview
style={{
height: "400px",
minHeight: "400px",
backgroundColor: "rgb(var(--card-bg)) !important",
}}
className="bg-card"
showOpenInCodeSandbox={false}
showRefreshButton={true}
actionsChildren={
<div className="flex items-center gap-2 px-2">
<span className="text-xs text-gray-600">
Live Preview
</span>
</div>
}
/>
</SandpackLayout>
</div>
</SandpackProvider>
</div>
{/* Info Message */}
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="rounded-lg border border-border p-3 sm:p-4"
>
<div className="space-y-2">
<p className="text-xs sm:text-sm text-muted-foreground">
<strong className="">Live Compilation:</strong> Your code is
compiled and executed in real-time in a sandboxed environment. All
UI components (Card, Button, Input, Badge, Tabs) are available.
Changes are reflected instantly!
</p>
</div>
</motion.div>
{/* Code Change Indicator */}
{code !== initialCode && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="rounded-lg border border-amber-500/20 bg-amber-500/10 p-3"
>
<p className="text-xs text-amber-600 dark:text-amber-400">
<strong>Code Modified:</strong> You&apos;ve made changes to the
original code. Click &quot;Copy Code&quot; to save your changes, or
&quot;Reset&quot; to restore the original.
</p>
</motion.div>
)}
</div>
);
}