added new layout for canvas

This commit is contained in:
smfahim25 2025-01-25 16:46:53 +06:00
parent db1f5d8118
commit 976978aec4
11 changed files with 520 additions and 73 deletions

View file

@ -1,87 +1,107 @@
import { useEffect } from 'react';
import './App.css'
import Canvas from './components/Canvas'
import WebFont from 'webfontloader';
import Header from './components/Layouts/Header';
import SheetRightPanel from './components/Layouts/SheetRightPanel';
import SheetLeftPanel from './components/Layouts/SheetLeftPanel';
import { useEffect, useState } from "react";
import "./App.css";
// import Canvas from "./components/Canvas";
import WebFont from "webfontloader";
import Header from "./components/Layouts/Header";
import SheetRightPanel from "./components/Layouts/SheetRightPanel";
import SheetLeftPanel from "./components/Layouts/SheetLeftPanel";
import CanvasCapture from "./components/CanvasCapture";
import { Toaster } from './components/ui/toaster';
import { Toaster } from "./components/ui/toaster";
import { Sidebar } from "./components/Layouts/LeftSidebar";
import RightPanel from "./components/Layouts/RightPanel";
import { EditorPanel } from "./components/Panel/EditorPanel";
import { Canvas } from "./components/Panel/Canvas";
import TextPanel from "./components/Panel/TextPanel";
import { TopBar } from "./components/Panel/TopBar";
import { ActionButtons } from "./components/ActionButtons";
function App() {
useEffect(() => {
WebFont.load({
google: {
families: [
'Roboto:300,400,500,700',
'Open Sans:300,400,600,700',
'Lato:300,400,700',
'Montserrat:300,400,500,700',
'Raleway:300,400,500,700',
'Poppins:300,400,500,700',
'Merriweather:300,400,700',
'Playfair Display:400,500,700',
'Nunito:300,400,600,700',
'Oswald:300,400,500,600',
'Source Sans Pro:300,400,600,700',
'Ubuntu:300,400,500,700',
'Noto Sans:300,400,500,700',
'Work Sans:300,400,500,700',
'Bebas Neue',
'Arimo:300,400,500,700',
'PT Sans:300,400,700',
'PT Serif:300,400,700',
'Titillium Web:300,400,600,700',
'Fira Sans:300,400,500,700',
'Karla:300,400,600,700',
'Josefin Sans:300,400,500,700',
'Cairo:300,400,600,700',
'Rubik:300,400,500,700',
'Mulish:300,400,500,700',
'IBM Plex Sans:300,400,500,700',
'Quicksand:300,400,500,700',
'Cabin:300,400,500,700',
'Heebo:300,400,500,700',
'Exo 2:300,400,500,700',
'Manrope:300,400,500,700',
'Jost:300,400,500,700',
'Anton',
'Asap:300,400,600,700',
'Baloo 2:300,400,500,700',
'Barlow:300,400,500,700',
'Cantarell:300,400,700',
'Chivo:300,400,500,700',
'Inter:300,400,500,700',
'Dosis:300,400,500,700',
'Crimson Text:300,400,600,700',
'Amatic SC:300,400,700',
'ABeeZee',
'Raleway Dots',
'Pacifico',
'Orbitron:300,400,500,700',
'Varela Round',
'Acme',
'Teko:300,400,500,700',
"Roboto:300,400,500,700",
"Open Sans:300,400,600,700",
"Lato:300,400,700",
"Montserrat:300,400,500,700",
"Raleway:300,400,500,700",
"Poppins:300,400,500,700",
"Merriweather:300,400,700",
"Playfair Display:400,500,700",
"Nunito:300,400,600,700",
"Oswald:300,400,500,600",
"Source Sans Pro:300,400,600,700",
"Ubuntu:300,400,500,700",
"Noto Sans:300,400,500,700",
"Work Sans:300,400,500,700",
"Bebas Neue",
"Arimo:300,400,500,700",
"PT Sans:300,400,700",
"PT Serif:300,400,700",
"Titillium Web:300,400,600,700",
"Fira Sans:300,400,500,700",
"Karla:300,400,600,700",
"Josefin Sans:300,400,500,700",
"Cairo:300,400,600,700",
"Rubik:300,400,500,700",
"Mulish:300,400,500,700",
"IBM Plex Sans:300,400,500,700",
"Quicksand:300,400,500,700",
"Cabin:300,400,500,700",
"Heebo:300,400,500,700",
"Exo 2:300,400,500,700",
"Manrope:300,400,500,700",
"Jost:300,400,500,700",
"Anton",
"Asap:300,400,600,700",
"Baloo 2:300,400,500,700",
"Barlow:300,400,500,700",
"Cantarell:300,400,700",
"Chivo:300,400,500,700",
"Inter:300,400,500,700",
"Dosis:300,400,500,700",
"Crimson Text:300,400,600,700",
"Amatic SC:300,400,700",
"ABeeZee",
"Raleway Dots",
"Pacifico",
"Orbitron:300,400,500,700",
"Varela Round",
"Acme",
"Teko:300,400,500,700",
],
},
});
}, []);
return (
<div className="relative flex flex-col h-screen overflow-hidden">
<Toaster />
<SheetLeftPanel />
<Header />
<SheetRightPanel />
const [selectedItem, setSelectedItem] = useState("text");
const [hasSelectedObject, setHasSelectedObject] = useState(true);
<main className="flex-1 overflow-hidden mt-[60px]">
<div className="h-full overflow-auto">
<Canvas />
<CanvasCapture />
</div>
</main>
return (
// <div className="relative flex flex-col h-screen overflow-hidden">
// <Toaster />
// <SheetLeftPanel />
// <Header />
// <SheetRightPanel />
// <main className="flex-1 overflow-hidden mt-[60px]">
// <div className="h-full overflow-auto">
// <Canvas />
// <CanvasCapture />
// </div>
// </main>
// </div>
<div className="flex h-screen">
<Sidebar selectedItem={selectedItem} onItemSelect={setSelectedItem} />
{selectedItem === "text" && <TextPanel />}
<div className="flex-1 relative">
<TopBar isVisible={hasSelectedObject} />
<ActionButtons />
<Canvas />
</div>
</div>
)
);
}
export default App
export default App;

View file

@ -0,0 +1,82 @@
import { ChevronDown } from "lucide-react";
import { Button } from "./ui/button";
export function ActionButtons() {
return (
<div className="absolute top-4 right-0 z-50 flex items-center gap-2 bg-white rounded-l-[16px]">
<div className="px-2 py-2">
<Button variant="ghost" className="gap-2 h-9 px-2">
<svg
xmlns="http://www.w3.org/2000/svg"
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
>
<g clipPath="url(#clip0_71_2678)">
<path d="M1 3.5L11 3.5" stroke="black" strokeLinecap="round" />
<path
d="M1 8.5769L11 8.57691"
stroke="black"
strokeLinecap="round"
/>
<path
d="M9.0769 1L9.0769 11"
stroke="black"
strokeLinecap="round"
/>
<path
d="M3.4231 1L3.4231 11"
stroke="black"
strokeLinecap="round"
/>
</g>
<defs>
<clipPath id="clip0_71_2678">
<rect width="12" height="12" fill="white" />
</clipPath>
</defs>
</svg>
Resize <ChevronDown className="h-4 w-4" />
</Button>
</div>
<div className="mr-5">
<Button
variant="ghost"
size="icon"
className="rounded-[10px] border-[#6A47ED] border-[0.5px] bg-[#F5F2FF]"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="21"
height="21"
viewBox="0 0 21 21"
fill="none"
>
<path
d="M10.1288 13.041V1"
stroke="#6A47ED"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M13.0448 10.1143L10.1288 13.0423L7.21277 10.1143"
stroke="#6A47ED"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M14.62 5.50879C18.199 5.83879 19.5 7.17879 19.5 12.5088C19.5 19.6088 17.189 19.6088 10.25 19.6088C3.309 19.6088 1 19.6088 1 12.5088C1 7.17879 2.3 5.83879 5.88 5.50879"
stroke="#6A47ED"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</Button>
</div>
</div>
);
}

View file

@ -0,0 +1,35 @@
import { cn } from "@/lib/utils";
import { Text, Square, Upload, Shield, Image, Folder } from "lucide-react";
const sidebarItems = [
{ id: "design", icon: Text, label: "Design" },
{ id: "text", icon: Text, label: "Text" },
{ id: "shape", icon: Square, label: "Shape" },
{ id: "upload", icon: Upload, label: "Upload" },
{ id: "icon", icon: Shield, label: "Icon" },
{ id: "image", icon: Image, label: "Image" },
{ id: "project", icon: Folder, label: "Project" },
];
export function Sidebar({ selectedItem, onItemSelect }) {
return (
<div className="w-20 border-r h-screen bg-background flex flex-col items-center py-4 gap-6">
{sidebarItems.map((item) => {
const Icon = item.icon;
return (
<button
key={item.id}
onClick={() => onItemSelect(item.id)}
className={cn(
"flex flex-col items-center gap-1 p-2 rounded-lg w-16 hover:bg-accent",
selectedItem === item.id && "bg-accent"
)}
>
<Icon className="h-5 w-5" />
<span className="text-xs">{item.label}</span>
</button>
);
})}
</div>
);
}

View file

@ -0,0 +1,28 @@
import { X } from "lucide-react";
import { Button } from "../ui/button";
import DesignPanel from "../Panel/DesignPanel";
// import TextPanel from "../Panel/TextPanel";
import ShapePanel from "../Panel/ShapePanel";
const panels = {
design: DesignPanel,
// text: TextPanel,
shape: ShapePanel,
// Add other panels as needed
};
export default function RightPanel({ activePanel }) {
const PanelComponent = panels[activePanel] || (() => null);
return (
<div className="fixed right-0 top-0 h-screen w-72 border-l bg-background p-4">
<div className="flex items-center justify-between border-b pb-4">
<h2 className="text-lg font-semibold capitalize">{activePanel}</h2>
<Button variant="ghost" size="icon">
<X className="h-4 w-4" />
</Button>
</div>
<PanelComponent />
</div>
);
}

View file

@ -0,0 +1,10 @@
export function Canvas() {
return (
<div className="flex-1 h-[88vh] bg-[#F5F0FF] p-8 flex flex-col mt-20">
{/* Main Canvas Area */}
<div className="flex-1 bg-white rounded-3xl shadow-sm mb-4 flex items-center justify-center">
<div className="w-32 h-32 bg-muted rounded-lg" />
</div>
</div>
);
}

View file

@ -0,0 +1,38 @@
import { Label } from "../ui/label";
import { Slider } from "../ui/slider";
export default function DesignPanel() {
return (
<div className="space-y-4 pt-4">
<div className="space-y-2">
<Label>Shadow Style</Label>
<div className="grid grid-cols-4 gap-2">
{Array.from({ length: 4 }).map((_, i) => (
<div
key={i}
className="aspect-square rounded-lg bg-muted hover:bg-muted/80"
/>
))}
</div>
</div>
<div className="space-y-4">
<div className="space-y-2">
<Label>Offset X</Label>
<Slider defaultValue={[0]} max={100} step={1} />
</div>
<div className="space-y-2">
<Label>Offset Y</Label>
<Slider defaultValue={[0]} max={100} step={1} />
</div>
<div className="space-y-2">
<Label>Blur</Label>
<Slider defaultValue={[0]} max={100} step={1} />
</div>
<div className="space-y-2">
<Label>Opacity</Label>
<Slider defaultValue={[100]} max={100} step={1} />
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,56 @@
import { Plus, Search, Upload } from "lucide-react";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
export function EditorPanel({ type }) {
if (!type) return null;
const panelContent = {
text: (
<>
<h2 className="text-lg font-semibold mb-4">Text</h2>
<Button className="w-full">
<Plus className="mr-2 h-4 w-4" /> Add Text
</Button>
<div className="mt-4 space-y-2">
<Input placeholder="Add a Heading" />
<Input placeholder="Add a Subheading" />
<Input placeholder="Add body text" />
</div>
</>
),
shape: (
<>
<h2 className="text-lg font-semibold mb-4">Shape</h2>
<div className="relative mb-4">
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<Input placeholder="Search shapes" className="pl-8" />
</div>
<div className="grid grid-cols-3 gap-2">
{[...Array(6)].map((_, i) => (
<div key={i} className="aspect-square bg-muted rounded-md" />
))}
</div>
</>
),
image: (
<>
<h2 className="text-lg font-semibold mb-4">Image</h2>
<Button className="w-full">
<Upload className="mr-2 h-4 w-4" /> Upload Image
</Button>
<div className="mt-4 grid grid-cols-2 gap-2">
{[...Array(4)].map((_, i) => (
<div key={i} className="aspect-square bg-muted rounded-md" />
))}
</div>
</>
),
};
return (
<div className="w-80 bg-background border-r border-border p-4 overflow-y-auto">
{panelContent[type]}
</div>
);
}

View file

@ -0,0 +1,20 @@
import { Input } from "../ui/input";
import { ScrollArea } from "../ui/scroll-area";
export default function ShapePanel() {
return (
<div className="space-y-4 pt-4">
<Input placeholder="Search with project name" />
<ScrollArea className="h-[calc(100vh-8rem)]">
<div className="grid grid-cols-3 gap-2">
{Array.from({ length: 9 }).map((_, i) => (
<div
key={i}
className="aspect-square rounded-md bg-muted hover:bg-muted/80"
/>
))}
</div>
</ScrollArea>
</div>
);
}

View file

@ -0,0 +1,57 @@
import { Button } from "../ui/Button";
import { Input } from "../ui/Input";
import { X } from "lucide-react";
import { ScrollArea } from "../ui/scroll-area";
export default function TextPanel() {
return (
<div className="w-80 h-[calc(100vh-32px)] bg-background rounded-3xl shadow-lg mx-4 my-4">
<ScrollArea className="h-[calc(100vh-32px)] px-4 py-4">
<div className="flex justify-between items-center mb-4">
<h2 className="text-lg font-semibold">Text</h2>
<Button variant="ghost" size="icon">
<X className="h-4 w-4" />
</Button>
</div>
<Button className="w-full bg-[#FF4D8D] hover:bg-[#FF3D7D] text-white rounded-2xl mb-6 h-12 text-base font-medium">
Add Text
</Button>
<div className="space-y-4">
<div>
<p className="text-sm text-muted-foreground mb-2">
Default text style
</p>
<Input
placeholder="Add a Heading H1"
className="mb-2 rounded-2xl h-12"
/>
<Input
placeholder="Add a Subheading H2"
className="mb-2 rounded-2xl h-12"
/>
<Input
placeholder="Add Some body text"
className="rounded-2xl h-12"
/>
</div>
<div>
<div className="flex justify-between items-center mb-2">
<p className="text-sm text-muted-foreground">Text Design</p>
<Button variant="link" size="sm">
See all
</Button>
</div>
<div className="grid grid-cols-2 gap-2">
{[...Array(6)].map((_, i) => (
<div key={i} className="aspect-square bg-muted rounded-lg" />
))}
</div>
</div>
</div>
</ScrollArea>
</div>
);
}

View file

@ -0,0 +1,101 @@
import {
AlignCenter,
AlignLeft,
AlignRight,
Bold,
Italic,
Lock,
Underline,
} from "lucide-react";
import { Button } from "../ui/Button";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../ui/Select";
export function TopBar({ isVisible = false }) {
if (!isVisible) return null;
return (
<div className="absolute top-4 left-[40%] -translate-x-1/2 z-40">
<div className="bg-white rounded-[16px] shadow-sm px-4 py-2 flex items-center gap-2">
<Select defaultValue="Albert Sans">
<SelectTrigger className="w-[140px] h-9">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="Albert Sans">Albert Sans</SelectItem>
<SelectItem value="Arial">Arial</SelectItem>
<SelectItem value="Helvetica">Helvetica</SelectItem>
</SelectContent>
</Select>
<div className="flex items-center border rounded-lg h-9">
<Button
variant="ghost"
size="icon"
className="h-9 w-9 rounded-none rounded-l-lg"
>
-
</Button>
<div className="px-3 py-1">12</div>
<Button
variant="ghost"
size="icon"
className="h-9 w-9 rounded-none rounded-r-lg"
>
+
</Button>
</div>
<div className="h-4 w-px bg-border mx-2" />
<div className="flex items-center gap-0.5">
<Button variant="ghost" size="icon" className="h-9 w-9">
<Bold className="h-4 w-4" />
</Button>
<Button variant="ghost" size="icon" className="h-9 w-9">
<Italic className="h-4 w-4" />
</Button>
<Button variant="ghost" size="icon" className="h-9 w-9">
<Underline className="h-4 w-4" />
</Button>
</div>
<div className="h-4 w-px bg-border mx-2" />
<div className="flex items-center gap-0.5">
<Button variant="ghost" size="icon" className="h-9 w-9">
<AlignLeft className="h-4 w-4" />
</Button>
<Button variant="ghost" size="icon" className="h-9 w-9">
<AlignCenter className="h-4 w-4" />
</Button>
<Button variant="ghost" size="icon" className="h-9 w-9">
<AlignRight className="h-4 w-4" />
</Button>
</div>
<div className="h-4 w-px bg-border mx-2" />
<Select defaultValue="position">
<SelectTrigger className="w-[100px] h-9">
<SelectValue placeholder="Position" />
</SelectTrigger>
<SelectContent>
<SelectItem value="position">Position</SelectItem>
<SelectItem value="front">Bring to Front</SelectItem>
<SelectItem value="back">Send to Back</SelectItem>
</SelectContent>
</Select>
<Button variant="ghost" size="icon" className="h-9 w-9">
<Lock className="h-4 w-4" />
</Button>
</div>
</div>
);
}

View file

@ -150,6 +150,6 @@
}
body {
@apply bg-background text-foreground;
@apply bg-[#f5f0ff] text-foreground;
}
}
}