From a21785972d13380e885ed81b8193c4d4e2b5b20d Mon Sep 17 00:00:00 2001 From: Chandini Date: Wed, 10 Sep 2025 14:22:52 +0530 Subject: [PATCH] frontend ai mockup changes --- package-lock.json | 83 +++++ package.json | 6 + src/components/canvas.tsx | 143 ++++++++ src/components/component-palette.tsx | 213 ++++++++++++ src/components/component-renderer.tsx | 253 ++++++++++++++ src/components/dual-canvas-editor.tsx | 234 +++++++++++++ src/components/main-dashboard.tsx | 30 +- src/components/prompt-side-panel.tsx | 2 +- src/components/properties-panel.tsx | 323 ++++++++++++++++++ src/components/ui/color-picker.tsx | 30 ++ src/components/ui/resizable.tsx | 59 ++++ src/components/ui/separator.tsx | 31 ++ src/components/ui/slider.tsx | 28 ++ src/components/wireframe-canvas.tsx | 28 +- src/components/wireframe-renderer.tsx | 180 ++++++++++ src/config/backend.ts | 4 +- src/lib/store.ts | 156 +++++++++ src/lib/wireframe-converter.tsx | 474 ++++++++++++++++++++++++++ src/lib/wireframe-integration.tsx | 154 +++++++++ 19 files changed, 2403 insertions(+), 28 deletions(-) create mode 100644 src/components/canvas.tsx create mode 100644 src/components/component-palette.tsx create mode 100644 src/components/component-renderer.tsx create mode 100644 src/components/dual-canvas-editor.tsx create mode 100644 src/components/properties-panel.tsx create mode 100644 src/components/ui/color-picker.tsx create mode 100644 src/components/ui/resizable.tsx create mode 100644 src/components/ui/separator.tsx create mode 100644 src/components/ui/slider.tsx create mode 100644 src/components/wireframe-renderer.tsx create mode 100644 src/lib/store.ts create mode 100644 src/lib/wireframe-converter.tsx create mode 100644 src/lib/wireframe-integration.tsx diff --git a/package-lock.json b/package-lock.json index 06a324b..15b928d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,9 @@ "version": "0.1.0", "dependencies": { "@anthropic-ai/sdk": "^0.57.0", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", "@next/font": "^14.2.15", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.2", @@ -17,7 +20,9 @@ "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-progress": "^1.1.7", "@radix-ui/react-radio-group": "^1.3.7", + "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.2.5", + "@radix-ui/react-slider": "^1.3.6", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tabs": "^1.1.12", @@ -29,6 +34,7 @@ "next": "15.4.6", "react": "19.1.0", "react-dom": "19.1.0", + "react-resizable-panels": "^3.0.5", "socket.io-client": "^4.8.1", "svg-path-parser": "^1.1.0", "tailwind-merge": "^3.3.1", @@ -84,6 +90,73 @@ "anthropic-ai-sdk": "bin/cli" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/modifiers": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-9.0.0.tgz", + "integrity": "sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@emnapi/core": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", @@ -8001,6 +8074,16 @@ } } }, + "node_modules/react-resizable-panels": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-3.0.5.tgz", + "integrity": "sha512-3z1yN25DMTXLg2wfyFrW32r5k4WEcUa3F7cJ2EgtNK07lnOs4mpM8yWLGunCpkhcQRwJX4fqoLcIh/pHPxzlmQ==", + "license": "MIT", + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/react-style-singleton": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", diff --git a/package.json b/package.json index e0eaa46..61fe4cf 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,9 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.57.0", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", "@next/font": "^14.2.15", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.2", @@ -18,7 +21,9 @@ "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-progress": "^1.1.7", "@radix-ui/react-radio-group": "^1.3.7", + "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.2.5", + "@radix-ui/react-slider": "^1.3.6", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tabs": "^1.1.12", @@ -30,6 +35,7 @@ "next": "15.4.6", "react": "19.1.0", "react-dom": "19.1.0", + "react-resizable-panels": "^3.0.5", "socket.io-client": "^4.8.1", "svg-path-parser": "^1.1.0", "tailwind-merge": "^3.3.1", diff --git a/src/components/canvas.tsx b/src/components/canvas.tsx new file mode 100644 index 0000000..04b1962 --- /dev/null +++ b/src/components/canvas.tsx @@ -0,0 +1,143 @@ +"use client" + +import { useRef, useState } from "react" +import { useEditorStore } from "@/lib/store" +import { ComponentRenderer } from "./component-renderer" +import { cn } from "@/lib/utils" + +export function Canvas() { + const canvasRef = useRef(null) + const [isDragging, setIsDragging] = useState(false) + const [dragStart, setDragStart] = useState<{ x: number; y: number } | null>(null) + const [selectedComponents, setSelectedComponents] = useState>(new Set()) + + const { + components, + selectedComponent, + selectComponent, + moveComponent, + updateComponent + } = useEditorStore() + + const handleMouseDown = (e: React.MouseEvent) => { + if (e.target === canvasRef.current) { + // Clicked on empty canvas, deselect all + selectComponent(null) + setSelectedComponents(new Set()) + } + } + + const handleComponentClick = (componentId: string, e: React.MouseEvent) => { + e.stopPropagation() + + const component = components.find(c => c.id === componentId) + if (component) { + selectComponent(component) + setSelectedComponents(new Set([componentId])) + } + } + + const handleComponentMouseDown = (componentId: string, e: React.MouseEvent) => { + e.stopPropagation() + + const component = components.find(c => c.id === componentId) + if (component) { + selectComponent(component) + setSelectedComponents(new Set([componentId])) + + // Start dragging + setIsDragging(true) + setDragStart({ x: e.clientX, y: e.clientY }) + } + } + + const handleMouseMove = (e: React.MouseEvent) => { + if (isDragging && dragStart && selectedComponent) { + const deltaX = e.clientX - dragStart.x + const deltaY = e.clientY - dragStart.y + + const newPosition = { + x: Math.max(0, selectedComponent.position.x + deltaX), + y: Math.max(0, selectedComponent.position.y + deltaY) + } + + moveComponent(selectedComponent.id, newPosition) + setDragStart({ x: e.clientX, y: e.clientY }) + } + } + + const handleMouseUp = () => { + setIsDragging(false) + setDragStart(null) + } + + return ( +
+ {/* Canvas Grid Background */} +
+
+
+ + {/* Components */} + {components.map((component) => ( +
handleComponentClick(component.id, e)} + onMouseDown={(e) => handleComponentMouseDown(component.id, e)} + > + +
+ ))} + + {/* Drop Zone Indicator */} + {isDragging && ( +
+
+
+ )} + + {/* Empty State */} + {components.length === 0 && ( +
+
+
🎨
+

No components yet

+

Drag components from the palette to get started

+
+
+ )} +
+ ) +} diff --git a/src/components/component-palette.tsx b/src/components/component-palette.tsx new file mode 100644 index 0000000..6cde264 --- /dev/null +++ b/src/components/component-palette.tsx @@ -0,0 +1,213 @@ +"use client" + +import { useState } from "react" +import { Button } from "./ui/button" +import { Input } from "./ui/input" +import { Textarea } from "./ui/textarea" +import { Card, CardContent, CardHeader, CardTitle } from "./ui/card" +import { Checkbox } from "./ui/checkbox" +import { Switch } from "./ui/switch" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select" +import { RadioGroup, RadioGroupItem } from "./ui/radio-group" +import { Progress } from "./ui/progress" +import { Avatar, AvatarFallback } from "./ui/avatar" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./ui/table" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs" +import { Badge } from "./ui/badge" +import { cn } from "@/lib/utils" +import { + MousePointer, + Square, + Circle, + Type, + Image, + Layout, + BarChart3, + Settings, + User, + Calendar, + Mail, + Phone, + Globe, + Search, + Filter, + Download, + Upload, + Edit, + Trash2, + Plus, + Minus, + Check, + X, + ArrowRight, + ArrowLeft, + ArrowUp, + ArrowDown, + ChevronDown, + ChevronUp, + ChevronLeft, + ChevronRight, + Menu, + MoreHorizontal, + Star, + Heart, + Share, + Bookmark, + Flag, + AlertCircle, + Info, + CheckCircle, + XCircle, + AlertTriangle, + HelpCircle +} from "lucide-react" + +const componentCategories = [ + { + name: "Basic", + icon: Square, + components: [ + { type: "button", name: "Button", icon: MousePointer }, + { type: "input", name: "Input", icon: Type }, + { type: "textarea", name: "Textarea", icon: Type }, + { type: "card", name: "Card", icon: Square }, + ] + }, + { + name: "Form", + icon: Settings, + components: [ + { type: "checkbox", name: "Checkbox", icon: Check }, + { type: "switch", name: "Switch", icon: Settings }, + { type: "select", name: "Select", icon: ChevronDown }, + { type: "radiogroup", name: "Radio Group", icon: Circle }, + ] + }, + { + name: "Data", + icon: BarChart3, + components: [ + { type: "table", name: "Table", icon: Layout }, + { type: "progress", name: "Progress", icon: BarChart3 }, + { type: "tabs", name: "Tabs", icon: Layout }, + ] + }, + { + name: "Media", + icon: Image, + components: [ + { type: "avatar", name: "Avatar", icon: User }, + ] + } +] + +export function ComponentPalette() { + const [selectedCategory, setSelectedCategory] = useState("Basic") + const [searchQuery, setSearchQuery] = useState("") + + const filteredComponents = componentCategories + .find(cat => cat.name === selectedCategory) + ?.components.filter(comp => + comp.name.toLowerCase().includes(searchQuery.toLowerCase()) + ) || [] + + return ( +
+ {/* Header */} +
+

Components

+

Drag components to canvas

+
+ + {/* Search */} +
+ setSearchQuery(e.target.value)} + className="w-full" + /> +
+ + {/* Categories */} +
+
+ {componentCategories.map((category) => ( + + ))} +
+
+ + {/* Components List */} +
+
+ {filteredComponents.map((component) => ( + + ))} +
+ + {filteredComponents.length === 0 && ( +
+ +

No components found

+
+ )} +
+
+ ) +} + +interface ComponentPreviewProps { + type: string + name: string + icon: React.ComponentType<{ className?: string }> +} + +function ComponentPreview({ type, name, icon: Icon }: ComponentPreviewProps) { + const [isDragging, setIsDragging] = useState(false) + + const handleDragStart = (e: React.DragEvent) => { + setIsDragging(true) + e.dataTransfer.setData('application/json', JSON.stringify({ type })) + e.dataTransfer.effectAllowed = 'copy' + } + + const handleDragEnd = () => { + setIsDragging(false) + } + + return ( +
+
+
+ +
+ + {name} + +
+
+ ) +} diff --git a/src/components/component-renderer.tsx b/src/components/component-renderer.tsx new file mode 100644 index 0000000..b0206b3 --- /dev/null +++ b/src/components/component-renderer.tsx @@ -0,0 +1,253 @@ +"use client" + +import { ComponentInstance } from "@/lib/store" +import { WireframeRenderer } from "./wireframe-renderer" +import { Button } from "./ui/button" +import { Input } from "./ui/input" +import { Textarea } from "./ui/textarea" +import { Card, CardContent, CardHeader, CardTitle } from "./ui/card" +import { Checkbox } from "./ui/checkbox" +import { Switch } from "./ui/switch" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select" +import { RadioGroup, RadioGroupItem } from "./ui/radio-group" +import { Progress } from "./ui/progress" +import { Avatar, AvatarFallback } from "./ui/avatar" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./ui/table" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs" +import { cn } from "@/lib/utils" + +interface ComponentRendererProps { + component: ComponentInstance + isSelected?: boolean + onClick?: () => void + onMouseDown?: (e: React.MouseEvent) => void +} + +export function ComponentRenderer({ + component, + isSelected = false, + onClick, + onMouseDown +}: ComponentRendererProps) { + const { type, props } = component + + // Handle wireframe components + if (type.startsWith('wireframe-')) { + return ( + + ) + } + + // Handle regular UI components + const renderComponent = () => { + switch (type) { + case 'button': + return ( + + ) + + case 'input': + return ( + + ) + + case 'textarea': + return ( +