added text section design layout

This commit is contained in:
smfahim25 2025-01-26 17:26:25 +06:00
parent 976978aec4
commit 954ac950b0
21 changed files with 2156 additions and 1822 deletions

View file

@ -1,4 +1,4 @@
import { useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import "./App.css"; import "./App.css";
// import Canvas from "./components/Canvas"; // import Canvas from "./components/Canvas";
import WebFont from "webfontloader"; import WebFont from "webfontloader";
@ -8,12 +8,12 @@ import SheetLeftPanel from "./components/Layouts/SheetLeftPanel";
import CanvasCapture from "./components/CanvasCapture"; import CanvasCapture from "./components/CanvasCapture";
import { Toaster } from "./components/ui/toaster"; import { Toaster } from "./components/ui/toaster";
import { Sidebar } from "./components/Layouts/LeftSidebar"; 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 { Canvas } from "./components/Panel/Canvas";
import TextPanel from "./components/Panel/TextPanel"; import TextPanel from "./components/Panel/TextPanel";
import { TopBar } from "./components/Panel/TopBar"; import { TopBar } from "./components/Panel/TopBar";
import { ActionButtons } from "./components/ActionButtons"; import { ActionButtons } from "./components/ActionButtons";
import EditorPanel from "./components/Panel/EditorPanel";
import CanvasContext from "./components/Context/canvasContext/CanvasContext";
function App() { function App() {
useEffect(() => { useEffect(() => {
@ -74,7 +74,7 @@ function App() {
}); });
}, []); }, []);
const [selectedItem, setSelectedItem] = useState("text"); const { selectedPanel } = useContext(CanvasContext);
const [hasSelectedObject, setHasSelectedObject] = useState(true); const [hasSelectedObject, setHasSelectedObject] = useState(true);
return ( return (
@ -93,8 +93,8 @@ function App() {
// </div> // </div>
<div className="flex h-screen"> <div className="flex h-screen">
<Sidebar selectedItem={selectedItem} onItemSelect={setSelectedItem} /> <Sidebar />
{selectedItem === "text" && <TextPanel />} {selectedPanel !== "" && <EditorPanel />}
<div className="flex-1 relative"> <div className="flex-1 relative">
<TopBar isVisible={hasSelectedObject} /> <TopBar isVisible={hasSelectedObject} />
<ActionButtons /> <ActionButtons />

View file

@ -1,44 +1,58 @@
import { ChevronDown } from "lucide-react"; import CanvasContext from "./Context/canvasContext/CanvasContext";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "./ui/select";
import { useContext } from "react";
const aspectRatios = [
{ value: "1:1", label: "Square (1:1)" },
{ value: "4:3", label: "Standard (4:3)" },
{ value: "3:2", label: "Classic (3:2)" },
{ value: "16:9", label: "Widescreen (16:9)" },
{ value: "9:16", label: "Portrait (9:16)" },
{ value: "21:9", label: "Ultrawide (21:9)" },
{ value: "32:9", label: "Super Ultrawide (32:9)" },
{ value: "1.85:1", label: "Cinema Standard (1.85:1)" },
{ value: "2.39:1", label: "Anamorphic Widescreen (2.39:1)" },
{ value: "2.76:1", label: "Ultra Panavision 70 (2.76:1)" },
{ value: "5:4", label: "Large Format (5:4)" },
{ value: "7:5", label: "Artistic Format (7:5)" },
{ value: "11:8.5", label: "Letter Size (11:8.5)" },
{ value: "3:4", label: "Portrait (3:4)" },
{ value: "1.91:1", label: "Facebook Ads (1.91:1)" },
];
export function ActionButtons() { export function ActionButtons() {
const { setCanvasRatio, canvasRatio } = useContext(CanvasContext);
const handleRatioChange = (newRatio) => {
setCanvasRatio(newRatio);
};
return ( return (
<div className="absolute top-4 right-0 z-50 flex items-center gap-2 bg-white rounded-l-[16px]"> <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"> <div className="px-2 py-2">
<Button variant="ghost" className="gap-2 h-9 px-2"> <div className="w-full">
<svg <Select onValueChange={handleRatioChange} value={canvasRatio}>
xmlns="http://www.w3.org/2000/svg" <SelectTrigger className="w-full text-xs font-bold">
width="12" <SelectValue placeholder="Select aspect ratio" />
height="12" </SelectTrigger>
viewBox="0 0 12 12" <SelectContent>
fill="none" {aspectRatios.map((ratio) => (
<SelectItem
key={ratio.value}
value={ratio.value}
className="text-xs font-bold"
> >
<g clipPath="url(#clip0_71_2678)"> {ratio.label}
<path d="M1 3.5L11 3.5" stroke="black" strokeLinecap="round" /> </SelectItem>
<path ))}
d="M1 8.5769L11 8.57691" </SelectContent>
stroke="black" </Select>
strokeLinecap="round" </div>
/>
<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>
<div className="mr-5"> <div className="mr-5">
<Button <Button

View file

@ -1,5 +1,5 @@
import { useRef, useState } from 'react' import { useRef, useState } from "react";
import CanvasContext from './CanvasContext'; import CanvasContext from "./CanvasContext";
const CanvasContextProvider = ({ children }) => { const CanvasContextProvider = ({ children }) => {
const canvasRef = useRef(null); const canvasRef = useRef(null);
@ -7,13 +7,32 @@ const CanvasContextProvider = ({ children }) => {
const [canvasHeight, setCanvasHeight] = useState(0); const [canvasHeight, setCanvasHeight] = useState(0);
const [canvasWidth, setCanvasWidth] = useState(0); const [canvasWidth, setCanvasWidth] = useState(0);
const [screenWidth, setScreenWidth] = useState(0); const [screenWidth, setScreenWidth] = useState(0);
const [canvasRatio, setCanvasRatio] = useState("4:3");
const [selectedPanel, setSelectedPanel] = useState("");
const fabricCanvasRef = useRef(null); const fabricCanvasRef = useRef(null);
return ( return (
<CanvasContext.Provider value={{ canvasRef, canvas, setCanvas, fabricCanvasRef, canvasHeight, setCanvasHeight, canvasWidth, setCanvasWidth, screenWidth, setScreenWidth }}> <CanvasContext.Provider
value={{
canvasRef,
canvas,
setCanvas,
fabricCanvasRef,
canvasHeight,
canvasRatio,
setCanvasRatio,
selectedPanel,
setSelectedPanel,
setCanvasHeight,
canvasWidth,
setCanvasWidth,
screenWidth,
setScreenWidth,
}}
>
{children} {children}
</CanvasContext.Provider> </CanvasContext.Provider>
) );
} };
export default CanvasContextProvider export default CanvasContextProvider;

View file

@ -1,25 +1,60 @@
import ActiveObjectContext from '@/components/Context/activeObject/ObjectContext'; import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext";
import CanvasContext from '@/components/Context/canvasContext/CanvasContext'; import CanvasContext from "@/components/Context/canvasContext/CanvasContext";
import { useContext, useRef, useState } from 'react'; import { useContext, useRef, useState } from "react";
import { Button } from '@/components/ui/button'; import { Button } from "@/components/ui/button";
import { fabric } from 'fabric'; import { fabric } from "fabric";
import Resizer from "react-image-file-resizer"; import Resizer from "react-image-file-resizer";
import { Input } from '@/components/ui/input'; import { Input } from "@/components/ui/input";
import { Label } from '@/components/ui/label'; import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import {
import { Slider } from '@/components/ui/slider'; Select,
import { HardDriveUpload, ImagePlus, Upload, Crop, RotateCw, FileType, ChevronUp, ChevronDown } from 'lucide-react'; SelectContent,
import { Card, CardContent } from '@/components/ui/card'; SelectItem,
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; SelectTrigger,
import CollapsibleComponent from './CollapsibleComponent'; SelectValue,
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; } from "@/components/ui/select";
import { Slider } from "@/components/ui/slider";
import {
HardDriveUpload,
ImagePlus,
Upload,
Crop,
RotateCw,
FileType,
ChevronUp,
ChevronDown,
} from "lucide-react";
import { Card, CardContent } from "@/components/ui/card";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import CollapsibleComponent from "./CollapsibleComponent";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
const features = [ const features = [
{ icon: Upload, title: 'Auto-Resize', description: 'Images larger than 1080px are automatically resized' }, {
{ icon: Crop, title: 'Custom Dimensions', description: 'Set your preferred resize dimensions' }, icon: Upload,
{ icon: RotateCw, title: 'Quality & Rotation', description: 'Adjust image quality and rotation as needed' }, title: "Auto-Resize",
{ icon: FileType, title: 'Format Conversion', description: 'Convert between various image formats' }, description: "Images larger than 1080px are automatically resized",
] },
{
icon: Crop,
title: "Custom Dimensions",
description: "Set your preferred resize dimensions",
},
{
icon: RotateCw,
title: "Quality & Rotation",
description: "Adjust image quality and rotation as needed",
},
{
icon: FileType,
title: "Format Conversion",
description: "Convert between various image formats",
},
];
const AddImageIntoShape = () => { const AddImageIntoShape = () => {
const { canvas } = useContext(CanvasContext); const { canvas } = useContext(CanvasContext);
@ -29,7 +64,7 @@ const AddImageIntoShape = () => {
const [height, setHeight] = useState(1080); const [height, setHeight] = useState(1080);
const [quality, setQuality] = useState(100); const [quality, setQuality] = useState(100);
const [rotation, setRotation] = useState("0"); const [rotation, setRotation] = useState("0");
const [format, setFormat] = useState('JPEG'); const [format, setFormat] = useState("JPEG");
const fileInputRef = useRef(null); const fileInputRef = useRef(null);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
@ -44,7 +79,7 @@ const AddImageIntoShape = () => {
(resizedFile) => { (resizedFile) => {
callback(resizedFile); // Pass the resized file to the callback callback(resizedFile); // Pass the resized file to the callback
}, },
'file' // Output type "file" // Output type
); );
}; };
@ -155,14 +190,14 @@ const AddImageIntoShape = () => {
top: activeObject.top, top: activeObject.top,
clipPath: activeObject, // Apply clipPath to the image clipPath: activeObject, // Apply clipPath to the image
originX: activeObject.originX, // Match origin point originX: activeObject.originX, // Match origin point
originY: activeObject.originY // Match origin point originY: activeObject.originY, // Match origin point
}); });
// Adjust position based on the clipPath's transformations // Adjust position based on the clipPath's transformations
fabricImage.set({ fabricImage.set({
left: activeObject.left, left: activeObject.left,
top: activeObject.top, top: activeObject.top,
angle: activeObject.angle // Match rotation if any angle: activeObject.angle, // Match rotation if any
}); });
canvas.add(fabricImage); canvas.add(fabricImage);
@ -176,7 +211,6 @@ const AddImageIntoShape = () => {
const content = () => { const content = () => {
return ( return (
<div> <div>
{/* <Card className="my-2"> {/* <Card className="my-2">
<CardContent className="p-0"> <CardContent className="p-0">
<Alert> <Alert>
@ -213,13 +247,21 @@ const AddImageIntoShape = () => {
</AlertTitle> </AlertTitle>
<AlertDescription className="mt-1"> <AlertDescription className="mt-1">
<p className="mb-1"> <p className="mb-1">
Insert and customize images within shapes. Adjust image position and clipping after insertion. Insert and customize images within shapes. Adjust image
position and clipping after insertion.
</p> </p>
<Collapsible open={isOpen} onOpenChange={setIsOpen}> <Collapsible open={isOpen} onOpenChange={setIsOpen}>
<CollapsibleTrigger asChild> <CollapsibleTrigger asChild>
<Button variant="outline" className="w-full justify-between mt-2"> <Button
variant="outline"
className="w-full justify-between mt-2"
>
<span>Key Features</span> <span>Key Features</span>
{isOpen ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />} {isOpen ? (
<ChevronUp className="h-4 w-4" />
) : (
<ChevronDown className="h-4 w-4" />
)}
</Button> </Button>
</CollapsibleTrigger> </CollapsibleTrigger>
<CollapsibleContent> <CollapsibleContent>
@ -231,8 +273,12 @@ const AddImageIntoShape = () => {
> >
<feature.icon className="h-6 w-6 mr-3 flex-shrink-0 text-primary" /> <feature.icon className="h-6 w-6 mr-3 flex-shrink-0 text-primary" />
<div> <div>
<h3 className="font-semibold mb-1">{feature.title}</h3> <h3 className="font-semibold mb-1">
<p className="text-sm text-gray-600">{feature.description}</p> {feature.title}
</h3>
<p className="text-sm text-gray-600">
{feature.description}
</p>
</div> </div>
</div> </div>
))} ))}
@ -247,7 +293,7 @@ const AddImageIntoShape = () => {
{errorMessage && ( {errorMessage && (
<p className="text-red-600 text-sm mt-2">{errorMessage}</p> <p className="text-red-600 text-sm mt-2">{errorMessage}</p>
)} )}
<div className='flex flex-col w-[100%]'> <div className="flex flex-col w-[100%]">
<div className="space-y-1"> <div className="space-y-1">
{/* Width Slider */} {/* Width Slider */}
<div className="space-y-1"> <div className="space-y-1">
@ -274,8 +320,7 @@ const AddImageIntoShape = () => {
</div> </div>
{/* Quality Slider */} {/* Quality Slider */}
{ {format === "JPEG" && (
format === "JPEG" &&
<div className="space-y-1"> <div className="space-y-1">
<Label className="text-xs">Quality: {quality}%</Label> <Label className="text-xs">Quality: {quality}%</Label>
<Slider <Slider
@ -285,14 +330,17 @@ const AddImageIntoShape = () => {
onValueChange={(value) => setQuality(value[0])} onValueChange={(value) => setQuality(value[0])}
/> />
</div> </div>
} )}
<div className='grid grid-cols-2 gap-1 items-center'> <div className="grid grid-cols-2 gap-1 items-center">
{/* Rotation */} {/* Rotation */}
<div> <div>
<Label className="text-xs">Rotation: {rotation}°</Label> <Label className="text-xs">Rotation: {rotation}°</Label>
<Select value={rotation} onValueChange={(value) => setRotation(value)}> <Select
value={rotation}
onValueChange={(value) => setRotation(value)}
>
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
<SelectValue placeholder="Select rotation in degree" /> <SelectValue placeholder="Select rotation in degree" />
</SelectTrigger> </SelectTrigger>
@ -309,7 +357,10 @@ const AddImageIntoShape = () => {
{/* Format Dropdown */} {/* Format Dropdown */}
<div> <div>
<Label className="text-xs">Format</Label> <Label className="text-xs">Format</Label>
<Select value={format} onValueChange={(value) => setFormat(value)}> <Select
value={format}
onValueChange={(value) => setFormat(value)}
>
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
<SelectValue placeholder="Select format" /> <SelectValue placeholder="Select format" />
</SelectTrigger> </SelectTrigger>
@ -331,15 +382,16 @@ const AddImageIntoShape = () => {
type="file" type="file"
accept="image/*" accept="image/*"
ref={fileInputRef} ref={fileInputRef}
onChange={fileHandler} /> onChange={fileHandler}
/>
</Button> </Button>
</div> </div>
</div> </div>
) );
} };
return ( return (
<Card className="flex flex-col p-2"> <Card className="flex flex-col shadow-none border-0">
<CollapsibleComponent text={"Insert Image"}> <CollapsibleComponent text={"Insert Image"}>
{content()} {content()}
</CollapsibleComponent> </CollapsibleComponent>
@ -348,6 +400,3 @@ const AddImageIntoShape = () => {
}; };
export default AddImageIntoShape; export default AddImageIntoShape;

View file

@ -1,6 +1,8 @@
import { Button } from "@/components/ui/button"; import {
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; Collapsible,
import { ChevronUp, ChevronDown } from "lucide-react"; CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
const CollapsibleComponent = ({ children, text }) => { const CollapsibleComponent = ({ children, text }) => {
@ -10,27 +12,25 @@ const CollapsibleComponent = ({ children, text }) => {
// Check if the text prop is "Canvas Setting" and set isOpen to false // Check if the text prop is "Canvas Setting" and set isOpen to false
if (text === "Canvas Setting") { if (text === "Canvas Setting") {
setIsOpen(false); setIsOpen(false);
} else {
setIsOpen(true);
} }
else { }, [text]);
setIsOpen(true)
}
}, [text])
return ( return (
<Collapsible open={isOpen} onOpenChange={setIsOpen}> <Collapsible open={isOpen} onOpenChange={setIsOpen}>
<CollapsibleTrigger asChild> <CollapsibleTrigger asChild>
<div className={`flex items-center justify-between cursor-pointer ${!isOpen ? "my-2" : ""}`}> <div
<h2 className='font-bold'>{text}</h2> className={`flex items-center justify-between cursor-pointer ${
<Button variant={"outline"}> !isOpen ? "my-2" : ""
{isOpen ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />} }`}
</Button> >
<h2 className="font-bold mb-2">{text}</h2>
</div> </div>
</CollapsibleTrigger> </CollapsibleTrigger>
<CollapsibleContent> <CollapsibleContent>{children}</CollapsibleContent>
{children}
</CollapsibleContent>
</Collapsible> </Collapsible>
) );
} };
export default CollapsibleComponent export default CollapsibleComponent;

View file

@ -1,10 +1,10 @@
import ActiveObjectContext from '@/components/Context/activeObject/ObjectContext'; import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext";
import CanvasContext from '@/components/Context/canvasContext/CanvasContext'; import CanvasContext from "@/components/Context/canvasContext/CanvasContext";
import { Button } from '@/components/ui/button'; import { Button } from "@/components/ui/button";
import { useToast } from '@/hooks/use-toast'; import { useToast } from "@/hooks/use-toast";
import { useContext, useEffect, useState } from 'react'; import { useContext, useEffect, useState } from "react";
import { Lock, Unlock } from "lucide-react"; import { Lock, Unlock } from "lucide-react";
import { Card } from '@/components/ui/card'; import { Card } from "@/components/ui/card";
const LockObject = () => { const LockObject = () => {
const { canvas } = useContext(CanvasContext); const { canvas } = useContext(CanvasContext);
@ -15,11 +15,10 @@ const LockObject = () => {
useEffect(() => { useEffect(() => {
if (activeObject?.lockMovementX === false) { if (activeObject?.lockMovementX === false) {
setIsLocked(false); setIsLocked(false);
} } else {
else {
setIsLocked(true); setIsLocked(true);
} }
}, [activeObject]) }, [activeObject]);
const toggleLock = () => { const toggleLock = () => {
if (!canvas || !activeObject) { if (!canvas || !activeObject) {
@ -27,7 +26,7 @@ const LockObject = () => {
title: "No object selected", title: "No object selected",
description: "Please select an object to lock or unlock.", description: "Please select an object to lock or unlock.",
variant: "destructive", variant: "destructive",
}) });
return; return;
} }
@ -45,13 +44,15 @@ const LockObject = () => {
toast({ toast({
title: newLockState ? "Object locked" : "Object unlocked", title: newLockState ? "Object locked" : "Object unlocked",
description: newLockState ? "The object is now locked in place." : "The object can now be moved and resized.", description: newLockState
}) ? "The object is now locked in place."
} : "The object can now be moved and resized.",
});
};
return ( return (
<Card className="p-2"> <Card className="shadow-none border-0">
<h2 className='font-bold'>{!isLocked ? "Lock" : "Unlock"} Object</h2> <h2 className="font-bold">{!isLocked ? "Lock" : "Unlock"} Object</h2>
<Button <Button
onClick={toggleLock} onClick={toggleLock}
variant="outline" variant="outline"
@ -59,10 +60,14 @@ const LockObject = () => {
disabled={!activeObject} disabled={!activeObject}
title={isLocked ? "Unlock object" : "Lock object"} title={isLocked ? "Unlock object" : "Lock object"}
> >
{isLocked ? <Unlock className="h-4 w-4" /> : <Lock className="h-4 w-4" />} {isLocked ? (
<Unlock className="h-4 w-4" />
) : (
<Lock className="h-4 w-4" />
)}
</Button> </Button>
</Card> </Card>
) );
} };
export default LockObject export default LockObject;

View file

@ -1,57 +1,54 @@
import ActiveObjectContext from '@/components/Context/activeObject/ObjectContext'; import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext";
import CanvasContext from '@/components/Context/canvasContext/CanvasContext'; import CanvasContext from "@/components/Context/canvasContext/CanvasContext";
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from "@/components/ui/card";
import { Input } from '@/components/ui/input'; import { Input } from "@/components/ui/input";
import { Label } from '@/components/ui/label'; import { Label } from "@/components/ui/label";
import { Slider } from '@/components/ui/slider'; import { Slider } from "@/components/ui/slider";
import { useContext, useEffect, useState } from 'react'; import { useContext, useEffect, useState } from "react";
import { ArrowUp, ArrowDown, ArrowLeft, ArrowRight, ChevronUp, ChevronDown } from 'lucide-react'; import { ArrowUp, ArrowDown, ArrowLeft, ArrowRight } from "lucide-react";
import { Button } from '@/components/ui/button'; import { Button } from "@/components/ui/button";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import CollapsibleComponent from "./CollapsibleComponent";
import CollapsibleComponent from './CollapsibleComponent';
const PositionCustomization = () => { const PositionCustomization = () => {
const { canvas } = useContext(CanvasContext) const { canvas } = useContext(CanvasContext);
const { activeObject } = useContext(ActiveObjectContext) const { activeObject } = useContext(ActiveObjectContext);
const [objectPosition, setObjectPosition] = useState({ left: 50, top: 50 }) const [objectPosition, setObjectPosition] = useState({ left: 50, top: 50 });
const [isOpen, setIsOpen] = useState(true);
useEffect(() => { useEffect(() => {
if (activeObject) { if (activeObject) {
setObjectPosition({ setObjectPosition({
left: Math.round(activeObject.left || 0), left: Math.round(activeObject.left || 0),
top: Math.round(activeObject.top || 0), top: Math.round(activeObject.top || 0),
}) });
} }
}, [activeObject]) }, [activeObject]);
const updateObjectPosition = (key, value) => { const updateObjectPosition = (key, value) => {
const updatedPosition = { ...objectPosition, [key]: value } const updatedPosition = { ...objectPosition, [key]: value };
setObjectPosition(updatedPosition) setObjectPosition(updatedPosition);
if (canvas && activeObject) { if (canvas && activeObject) {
activeObject.set(updatedPosition) activeObject.set(updatedPosition);
canvas.renderAll() canvas.renderAll();
}
} }
};
const handleInputChange = (key, value) => { const handleInputChange = (key, value) => {
const numValue = parseInt(value, 10) const numValue = parseInt(value, 10);
if (!isNaN(numValue)) { if (!isNaN(numValue)) {
updateObjectPosition(key, numValue) updateObjectPosition(key, numValue);
}
} }
};
const handleSliderChange = (key, value) => { const handleSliderChange = (key, value) => {
updateObjectPosition(key, value[0]) updateObjectPosition(key, value[0]);
} };
const adjustPosition = (key, delta) => { const adjustPosition = (key, delta) => {
const newValue = objectPosition[key] + delta const newValue = objectPosition[key] + delta;
updateObjectPosition(key, newValue) updateObjectPosition(key, newValue);
} };
const content = () => { const content = () => {
return ( return (
@ -64,7 +61,7 @@ const PositionCustomization = () => {
id="position-left" id="position-left"
type="number" type="number"
value={objectPosition.left} value={objectPosition.left}
onChange={(e) => handleInputChange('left', e.target.value)} onChange={(e) => handleInputChange("left", e.target.value)}
className="w-20" className="w-20"
/> />
<Slider <Slider
@ -72,7 +69,7 @@ const PositionCustomization = () => {
max={canvas ? canvas.width : 1000} max={canvas ? canvas.width : 1000}
step={1} step={1}
value={[objectPosition.left]} value={[objectPosition.left]}
onValueChange={(value) => handleSliderChange('left', value)} onValueChange={(value) => handleSliderChange("left", value)}
className="flex-grow" className="flex-grow"
/> />
</div> </div>
@ -84,7 +81,7 @@ const PositionCustomization = () => {
id="position-top" id="position-top"
type="number" type="number"
value={objectPosition.top} value={objectPosition.top}
onChange={(e) => handleInputChange('top', e.target.value)} onChange={(e) => handleInputChange("top", e.target.value)}
className="w-20" className="w-20"
/> />
<Slider <Slider
@ -92,7 +89,7 @@ const PositionCustomization = () => {
max={canvas ? canvas.height : 1000} max={canvas ? canvas.height : 1000}
step={1} step={1}
value={[objectPosition.top]} value={[objectPosition.top]}
onValueChange={(value) => handleSliderChange('top', value)} onValueChange={(value) => handleSliderChange("top", value)}
className="flex-grow" className="flex-grow"
/> />
</div> </div>
@ -102,7 +99,7 @@ const PositionCustomization = () => {
<div className="w-32 h-32 grid grid-cols-3 gap-1 p-2 mx-auto"> <div className="w-32 h-32 grid grid-cols-3 gap-1 p-2 mx-auto">
<div className="col-start-2"> <div className="col-start-2">
<Button <Button
onClick={() => adjustPosition('top', -1)} onClick={() => adjustPosition("top", -1)}
aria-label="Move up" aria-label="Move up"
variant="outline" variant="outline"
size="icon" size="icon"
@ -113,7 +110,7 @@ const PositionCustomization = () => {
</div> </div>
<div className="col-start-1 row-start-2"> <div className="col-start-1 row-start-2">
<Button <Button
onClick={() => adjustPosition('left', -1)} onClick={() => adjustPosition("left", -1)}
aria-label="Move left" aria-label="Move left"
variant="outline" variant="outline"
size="icon" size="icon"
@ -124,7 +121,7 @@ const PositionCustomization = () => {
</div> </div>
<div className="col-start-3 row-start-2"> <div className="col-start-3 row-start-2">
<Button <Button
onClick={() => adjustPosition('left', 1)} onClick={() => adjustPosition("left", 1)}
aria-label="Move right" aria-label="Move right"
variant="outline" variant="outline"
size="icon" size="icon"
@ -135,7 +132,7 @@ const PositionCustomization = () => {
</div> </div>
<div className="col-start-2 row-start-3"> <div className="col-start-2 row-start-3">
<Button <Button
onClick={() => adjustPosition('top', 1)} onClick={() => adjustPosition("top", 1)}
aria-label="Move down" aria-label="Move down"
variant="outline" variant="outline"
size="icon" size="icon"
@ -145,18 +142,17 @@ const PositionCustomization = () => {
</Button> </Button>
</div> </div>
</div> </div>
</CardContent> </CardContent>
) );
} };
return ( return (
<Card className="p-2"> <Card className="shadow-none border-0">
<CollapsibleComponent text={"Position Control"}> <CollapsibleComponent text={"Position Control"}>
{content()} {content()}
</CollapsibleComponent> </CollapsibleComponent>
</Card> </Card>
) );
} };
export default PositionCustomization export default PositionCustomization;

View file

@ -16,7 +16,7 @@ const ScaleObjects = () => {
setScaleX(activeObject?.scaleX); setScaleX(activeObject?.scaleX);
setScaleY(activeObject?.scaleY); setScaleY(activeObject?.scaleY);
} }
}, [activeObject]) }, [activeObject]);
// Handle scaleX changes // Handle scaleX changes
const handleScaleXChange = (value) => { const handleScaleXChange = (value) => {
@ -79,11 +79,11 @@ const ScaleObjects = () => {
/> />
</div> </div>
</div> </div>
) );
} };
return ( return (
<Card className="grid items-center gap-2 p-2"> <Card className="grid items-center gap-2 shadow-none border-0">
<CollapsibleComponent text={"Scale Control"}> <CollapsibleComponent text={"Scale Control"}>
{content()} {content()}
</CollapsibleComponent> </CollapsibleComponent>

View file

@ -1,29 +1,35 @@
import ActiveObjectContext from '@/components/Context/activeObject/ObjectContext'; import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import {
import { useContext, useEffect, useRef, useState } from 'react' Select,
import { Card, } from '@/components/ui/card'; SelectContent,
import CanvasContext from '@/components/Context/canvasContext/CanvasContext'; SelectItem,
import { Separator } from '@/components/ui/separator'; SelectTrigger,
import { fabric } from 'fabric'; SelectValue,
} from "@/components/ui/select";
import { useContext, useEffect, useRef, useState } from "react";
import { Card } from "@/components/ui/card";
import CanvasContext from "@/components/Context/canvasContext/CanvasContext";
import { Separator } from "@/components/ui/separator";
import { fabric } from "fabric";
const SelectObjectFromGroup = () => { const SelectObjectFromGroup = () => {
const [groupObjects, setGroupObjects] = useState([]) const [groupObjects, setGroupObjects] = useState([]);
const [selectedObject, setSelectedObject] = useState(null) const [selectedObject, setSelectedObject] = useState(null);
const { setActiveObject } = useContext(ActiveObjectContext) const { setActiveObject } = useContext(ActiveObjectContext);
const { canvas } = useContext(CanvasContext) const { canvas } = useContext(CanvasContext);
const previewCanvasRef = useRef(null) const previewCanvasRef = useRef(null);
const activeObject = canvas?.getActiveObject() const activeObject = canvas?.getActiveObject();
useEffect(() => { useEffect(() => {
if (activeObject?.type === 'group') { if (activeObject?.type === "group") {
setGroupObjects(activeObject._objects || []) setGroupObjects(activeObject._objects || []);
setSelectedObject(null) setSelectedObject(null);
} else { } else {
setGroupObjects([]) setGroupObjects([]);
setSelectedObject(null) setSelectedObject(null);
} }
}, [activeObject]) }, [activeObject]);
// useEffect(() => { // useEffect(() => {
// if (selectedObject && previewCanvasRef.current) { // if (selectedObject && previewCanvasRef.current) {
@ -61,7 +67,6 @@ const SelectObjectFromGroup = () => {
// } // }
// }, [selectedObject]) // }, [selectedObject])
useEffect(() => { useEffect(() => {
if (selectedObject && previewCanvasRef.current) { if (selectedObject && previewCanvasRef.current) {
// Create a new static canvas // Create a new static canvas
@ -69,7 +74,7 @@ const SelectObjectFromGroup = () => {
previewCanvas.setDimensions({ previewCanvas.setDimensions({
width: 100, width: 100,
height: 100 height: 100,
}); });
// Clear previous objects (if any) // Clear previous objects (if any)
@ -88,8 +93,8 @@ const SelectObjectFromGroup = () => {
clonedObject.set({ clonedObject.set({
left: 50, left: 50,
top: 50, top: 50,
originX: 'center', originX: "center",
originY: 'center', originY: "center",
}); });
// Add the cloned object to preview canvas // Add the cloned object to preview canvas
@ -105,25 +110,28 @@ const SelectObjectFromGroup = () => {
} }
}, [selectedObject, previewCanvasRef]); }, [selectedObject, previewCanvasRef]);
const handleSelectObject = (value) => { const handleSelectObject = (value) => {
const selected = groupObjects[parseInt(value)] const selected = groupObjects[parseInt(value)];
setSelectedObject(selected) setSelectedObject(selected);
setActiveObject(selected) setActiveObject(selected);
} };
if (!activeObject || activeObject.type !== 'group') { if (!activeObject || activeObject.type !== "group") {
return null return null;
} }
return ( return (
<div> <div>
<Card className="p-4"> <Card className="p-4 shadow-none border-0">
<h2 className='font-bold mb-4'>Group Objects</h2> <h2 className="font-bold mb-4">Group Objects</h2>
<div className='flex flex-col items-center justify-center space-y-4'> <div className="flex flex-col items-center justify-center space-y-4">
<Select <Select
onValueChange={handleSelectObject} onValueChange={handleSelectObject}
value={selectedObject ? groupObjects.indexOf(selectedObject).toString() : undefined} value={
selectedObject
? groupObjects.indexOf(selectedObject).toString()
: undefined
}
> >
<SelectTrigger className="w-full max-w-xs"> <SelectTrigger className="w-full max-w-xs">
<SelectValue placeholder="Select object" /> <SelectValue placeholder="Select object" />
@ -138,7 +146,7 @@ const SelectObjectFromGroup = () => {
</Select> </Select>
{selectedObject && ( {selectedObject && (
<div className='w-[100px] h-[100px] bg-muted rounded-md overflow-hidden border border-gray-300'> <div className="w-[100px] h-[100px] bg-muted rounded-md overflow-hidden border border-gray-300">
<canvas ref={previewCanvasRef} width={100} height={100} /> <canvas ref={previewCanvasRef} width={100} height={100} />
</div> </div>
)} )}
@ -146,6 +154,6 @@ const SelectObjectFromGroup = () => {
</Card> </Card>
<Separator className="my-4" /> <Separator className="my-4" />
</div> </div>
) );
} };
export default SelectObjectFromGroup; export default SelectObjectFromGroup;

View file

@ -28,7 +28,7 @@ const ShadowCustomization = () => {
setBlur(activeObject?.shadow?.blur || 0.5); setBlur(activeObject?.shadow?.blur || 0.5);
setOpacity(activeObject?.shadow?.opacity || 0.5); setOpacity(activeObject?.shadow?.opacity || 0.5);
} }
}, [activeObject]) }, [activeObject]);
const handleApplyShadow = () => { const handleApplyShadow = () => {
if (activeObject && canvas) { if (activeObject && canvas) {
@ -44,14 +44,14 @@ const ShadowCustomization = () => {
activeObject.dirty = true; // Mark the object as dirty for re-render activeObject.dirty = true; // Mark the object as dirty for re-render
canvas.renderAll(); // Trigger canvas re-render canvas.renderAll(); // Trigger canvas re-render
} }
} };
const handleDisableShadow = () => { const handleDisableShadow = () => {
if (activeObject && canvas) { if (activeObject && canvas) {
activeObject.set("shadow", null) activeObject.set("shadow", null);
canvas.renderAll() canvas.renderAll();
}
} }
};
const content = () => { const content = () => {
return ( return (
@ -116,22 +116,31 @@ const ShadowCustomization = () => {
</div> </div>
</div> </div>
<div className="w-32 h-32 bg-white rounded-md flex items-center justify-center mx-auto border-2 my-4" style={{ <div
boxShadow: `${offsetX}px ${offsetY}px ${blur}px ${shadowColor}${Math.round(opacity * 255).toString(16).padStart(2, '0')}` className="w-32 h-32 bg-white rounded-md flex items-center justify-center mx-auto border-2 my-4"
}}> style={{
boxShadow: `${offsetX}px ${offsetY}px ${blur}px ${shadowColor}${Math.round(
opacity * 255
)
.toString(16)
.padStart(2, "0")}`,
}}
>
<span className="text-4xl">A</span> <span className="text-4xl">A</span>
</div> </div>
<div className="grid gap-2"> <div className="grid gap-2">
<Button onClick={handleApplyShadow}>Apply Shadow</Button> <Button onClick={handleApplyShadow}>Apply Shadow</Button>
<Button variant="outline" onClick={handleDisableShadow}>Disable Shadow</Button> <Button variant="outline" onClick={handleDisableShadow}>
Disable Shadow
</Button>
</div> </div>
</div> </div>
) );
} };
return ( return (
<Card className='p-2'> <Card className="shadow-none border-0">
<CollapsibleComponent text={"Shadow Control"}> <CollapsibleComponent text={"Shadow Control"}>
{content()} {content()}
</CollapsibleComponent> </CollapsibleComponent>
@ -140,4 +149,3 @@ const ShadowCustomization = () => {
}; };
export default ShadowCustomization; export default ShadowCustomization;

View file

@ -1,10 +1,10 @@
import ActiveObjectContext from '@/components/Context/activeObject/ObjectContext'; import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext";
import CanvasContext from '@/components/Context/canvasContext/CanvasContext'; import CanvasContext from "@/components/Context/canvasContext/CanvasContext";
import { Card } from '@/components/ui/card'; import { Card } from "@/components/ui/card";
import { Label } from '@/components/ui/label'; import { Label } from "@/components/ui/label";
import { Slider } from '@/components/ui/slider'; import { Slider } from "@/components/ui/slider";
import { useContext, useEffect, useState } from 'react' import { useContext, useEffect, useState } from "react";
import CollapsibleComponent from './CollapsibleComponent'; import CollapsibleComponent from "./CollapsibleComponent";
const SkewCustomization = () => { const SkewCustomization = () => {
const { canvas } = useContext(CanvasContext); const { canvas } = useContext(CanvasContext);
@ -17,7 +17,7 @@ const SkewCustomization = () => {
setSkewX(activeObject?.skewX); setSkewX(activeObject?.skewX);
setSkewY(activeObject?.skewY); setSkewY(activeObject?.skewY);
} }
}, [activeObject]) }, [activeObject]);
// Update skewX directly // Update skewX directly
const handleSkewXChange = (value) => { const handleSkewXChange = (value) => {
@ -52,7 +52,7 @@ const SkewCustomization = () => {
step={1} step={1}
value={[skewX]} value={[skewX]}
onValueChange={(value) => { onValueChange={(value) => {
handleSkewXChange(value) handleSkewXChange(value);
}} }}
/> />
</div> </div>
@ -65,21 +65,21 @@ const SkewCustomization = () => {
step={1} step={1}
value={[skewY]} value={[skewY]}
onValueChange={(value) => { onValueChange={(value) => {
handleSkewYChange(value) handleSkewYChange(value);
}} }}
/> />
</div> </div>
</div> </div>
) );
} };
return ( return (
<Card className="p-2"> <Card className="shadow-none border-0">
<CollapsibleComponent text={"Skew Control"}> <CollapsibleComponent text={"Skew Control"}>
{content()} {content()}
</CollapsibleComponent> </CollapsibleComponent>
</Card> </Card>
) );
} };
export default SkewCustomization export default SkewCustomization;

View file

@ -1,16 +1,22 @@
import { useContext, useState, useRef, useEffect } from 'react' import { useContext, useState, useRef, useEffect } from "react";
import { fabric } from 'fabric' import { fabric } from "fabric";
import ActiveObjectContext from '@/components/Context/activeObject/ObjectContext' import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext";
import CanvasContext from '@/components/Context/canvasContext/CanvasContext' import CanvasContext from "@/components/Context/canvasContext/CanvasContext";
import { Card, CardContent } from '@/components/ui/card' import { Card, CardContent } from "@/components/ui/card";
import { Label } from '@/components/ui/label' import { Label } from "@/components/ui/label";
import { Input } from '@/components/ui/input' import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import {
import { Slider } from '@/components/ui/slider' Select,
import { Button } from '@/components/ui/button' SelectContent,
import { Paintbrush, ContrastIcon as Gradient } from 'lucide-react' SelectItem,
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' SelectTrigger,
import CollapsibleComponent from './CollapsibleComponent' SelectValue,
} from "@/components/ui/select";
import { Slider } from "@/components/ui/slider";
import { Button } from "@/components/ui/button";
import { Paintbrush } from "lucide-react";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import CollapsibleComponent from "./CollapsibleComponent";
const StrokeCustomization = () => { const StrokeCustomization = () => {
const { activeObject } = useContext(ActiveObjectContext); const { activeObject } = useContext(ActiveObjectContext);
@ -26,10 +32,8 @@ const StrokeCustomization = () => {
const [colorType, setColorType] = useState("color"); const [colorType, setColorType] = useState("color");
const [gradientDirection, setGradientDirection] = useState("to bottom"); const [gradientDirection, setGradientDirection] = useState("to bottom");
// Utility function to handle styles of objects // Utility function to handle styles of objects
const handleObjectStyle = (object) => { const handleObjectStyle = (object) => {
// Determine fill type (solid or gradient)
if (object.stroke) { if (object.stroke) {
if (typeof object.stroke === "string") { if (typeof object.stroke === "string") {
setColorType("color"); setColorType("color");
@ -42,7 +46,6 @@ const StrokeCustomization = () => {
}); });
} }
} }
// Handle stroke width
if (object.strokeWidth) { if (object.strokeWidth) {
setStrokeWidth(object.strokeWidth || 0); setStrokeWidth(object.strokeWidth || 0);
} }
@ -74,10 +77,10 @@ const StrokeCustomization = () => {
if (!previewRef.current) return; if (!previewRef.current) return;
const previewStyle = { const previewStyle = {
width: '80px', width: "80px",
height: '80px', height: "80px",
border: `${strokeWidth}px solid`, border: `${strokeWidth}px solid`,
borderRadius: '4px', borderRadius: "4px",
}; };
if (colorType === "color") { if (colorType === "color") {
@ -91,7 +94,13 @@ const StrokeCustomization = () => {
useEffect(() => { useEffect(() => {
updatePreview(); updatePreview();
}, [strokeWidth, strokeColor, gradientStrokeColors, colorType, gradientDirection]); }, [
strokeWidth,
strokeColor,
gradientStrokeColors,
colorType,
gradientDirection,
]);
const handleStrokeWidthChange = (value) => { const handleStrokeWidthChange = (value) => {
setStrokeWidth(value); setStrokeWidth(value);
@ -106,7 +115,7 @@ const StrokeCustomization = () => {
}; };
const handleGradientColorChange = (key, e) => { const handleGradientColorChange = (key, e) => {
setGradientStrokeColors(prev => ({ ...prev, [key]: e.target.value })); setGradientStrokeColors((prev) => ({ ...prev, [key]: e.target.value }));
}; };
const applyStrokeStyle = () => { const applyStrokeStyle = () => {
@ -161,6 +170,17 @@ const StrokeCustomization = () => {
canvas.renderAll(); canvas.renderAll();
}; };
// Automatically apply stroke styles on state change
useEffect(() => {
applyStrokeStyle();
}, [
strokeWidth,
strokeColor,
gradientStrokeColors,
colorType,
gradientDirection,
]);
const revertStroke = () => { const revertStroke = () => {
if (!activeObject) return; if (!activeObject) return;
@ -209,21 +229,26 @@ const StrokeCustomization = () => {
min={0} min={0}
max={50} max={50}
value={strokeWidth} value={strokeWidth}
onChange={(e) => handleStrokeWidthChange(Number(e.target.value))} onChange={(e) =>
handleStrokeWidthChange(Number(e.target.value))
}
className="w-16" className="w-16"
/> />
</div> </div>
</div> </div>
<div> <div>
<Tabs value={colorType} onValueChange={(value) => handleColorTypeChange(value)}> <Tabs
value={colorType}
onValueChange={(value) => handleColorTypeChange(value)}
>
<TabsList className="grid w-full grid-cols-2"> <TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="color"> <TabsTrigger value="color">
<Paintbrush className="w-4 h-4 mr-2" /> <Paintbrush className="w-4 h-4 mr-2" />
Solid Solid
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="gradient"> <TabsTrigger value="gradient" className="flex gap-2">
<Gradient className="w-4 h-4 mr-2" /> <div className="h-4 w-4 rounded bg-gradient-to-r from-purple-500 to-pink-500" />
Gradient Gradient
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
@ -241,7 +266,10 @@ const StrokeCustomization = () => {
/> />
<div <div
className="absolute inset-0 pointer-events-none" className="absolute inset-0 pointer-events-none"
style={{ backgroundColor: strokeColor, borderRadius: '0.375rem' }} style={{
backgroundColor: strokeColor,
borderRadius: "0.375rem",
}}
></div> ></div>
</div> </div>
<Input <Input
@ -256,7 +284,10 @@ const StrokeCustomization = () => {
<TabsContent value="gradient" className="space-y-4"> <TabsContent value="gradient" className="space-y-4">
<div className="space-y-1"> <div className="space-y-1">
<Label htmlFor="gradient-direction">Gradient Direction</Label> <Label htmlFor="gradient-direction">Gradient Direction</Label>
<Select value={gradientDirection} onValueChange={setGradientDirection}> <Select
value={gradientDirection}
onValueChange={setGradientDirection}
>
<SelectTrigger id="gradient-direction"> <SelectTrigger id="gradient-direction">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
@ -277,18 +308,21 @@ const StrokeCustomization = () => {
id="gradient-color-1" id="gradient-color-1"
type="color" type="color"
value={gradientStrokeColors.color1} value={gradientStrokeColors.color1}
onChange={(e) => handleGradientColorChange('color1', e)} onChange={(e) => handleGradientColorChange("color1", e)}
className="w-10 h-10 p-1 rounded-md cursor-pointer" className="w-10 h-10 p-1 rounded-md cursor-pointer"
/> />
<div <div
className="absolute inset-0 pointer-events-none" className="absolute inset-0 pointer-events-none"
style={{ backgroundColor: gradientStrokeColors.color1, borderRadius: '0.375rem' }} style={{
backgroundColor: gradientStrokeColors.color1,
borderRadius: "0.375rem",
}}
></div> ></div>
</div> </div>
<Input <Input
type="text" type="text"
value={gradientStrokeColors.color1} value={gradientStrokeColors.color1}
onChange={(e) => handleGradientColorChange('color1', e)} onChange={(e) => handleGradientColorChange("color1", e)}
className="flex-grow" className="flex-grow"
/> />
</div> </div>
@ -302,18 +336,21 @@ const StrokeCustomization = () => {
id="gradient-color-2" id="gradient-color-2"
type="color" type="color"
value={gradientStrokeColors.color2} value={gradientStrokeColors.color2}
onChange={(e) => handleGradientColorChange('color2', e)} onChange={(e) => handleGradientColorChange("color2", e)}
className="w-10 h-10 p-1 rounded-md cursor-pointer" className="w-10 h-10 p-1 rounded-md cursor-pointer"
/> />
<div <div
className="absolute inset-0 pointer-events-none" className="absolute inset-0 pointer-events-none"
style={{ backgroundColor: gradientStrokeColors.color2, borderRadius: '0.375rem' }} style={{
backgroundColor: gradientStrokeColors.color2,
borderRadius: "0.375rem",
}}
></div> ></div>
</div> </div>
<Input <Input
type="text" type="text"
value={gradientStrokeColors.color2} value={gradientStrokeColors.color2}
onChange={(e) => handleGradientColorChange('color2', e)} onChange={(e) => handleGradientColorChange("color2", e)}
className="flex-grow" className="flex-grow"
/> />
</div> </div>
@ -324,13 +361,19 @@ const StrokeCustomization = () => {
<div className="space-y-1"> <div className="space-y-1">
<Label>Preview</Label> <Label>Preview</Label>
<div className="border rounded-md p-2 flex items-center justify-center" style={{ height: '120px' }}> <div
<div ref={previewRef} style={{ width: '80px', height: '80px' }}></div> className="border rounded-md p-2 flex items-center justify-center"
style={{ height: "120px" }}
>
<div
ref={previewRef}
style={{ width: "80px", height: "80px" }}
></div>
</div> </div>
</div> </div>
<div className="grid gap-1"> <div className="grid gap-1">
<Button onClick={applyStrokeStyle}> <Button onClick={applyStrokeStyle} className="bg-[#FF2B85]">
Apply Stroke Apply Stroke
</Button> </Button>
<Button onClick={revertStroke} variant="outline"> <Button onClick={revertStroke} variant="outline">
@ -339,11 +382,11 @@ const StrokeCustomization = () => {
</div> </div>
</div> </div>
</CardContent> </CardContent>
) );
} };
return ( return (
<Card className="p-2"> <Card className=" shadow-none border-0">
<CollapsibleComponent text={"Stroke Control"}> <CollapsibleComponent text={"Stroke Control"}>
{content()} {content()}
</CollapsibleComponent> </CollapsibleComponent>
@ -352,16 +395,3 @@ const StrokeCustomization = () => {
}; };
export default StrokeCustomization; export default StrokeCustomization;

View file

@ -1,25 +1,81 @@
import ActiveObjectContext from '@/components/Context/activeObject/ObjectContext'; import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext";
import CanvasContext from '@/components/Context/canvasContext/CanvasContext'; import CanvasContext from "@/components/Context/canvasContext/CanvasContext";
import { Button } from '@/components/ui/button'; import { Button } from "@/components/ui/button";
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from "@/components/ui/card";
import { Input } from '@/components/ui/input'; import { Input } from "@/components/ui/input";
import { Label } from '@/components/ui/label'; import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import {
import { Slider } from '@/components/ui/slider'; Select,
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; SelectContent,
import { useContext, useEffect, useState } from 'react' SelectGroup,
import { AlignLeft, AlignCenter, AlignRight, Bold, Italic, Underline, Strikethrough } from 'lucide-react'; SelectItem,
import CollapsibleComponent from './CollapsibleComponent'; SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Slider } from "@/components/ui/slider";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useContext, useEffect, useState } from "react";
import {
AlignLeft,
AlignCenter,
AlignRight,
Bold,
Italic,
Underline,
Strikethrough,
} from "lucide-react";
import CollapsibleComponent from "./CollapsibleComponent";
const fonts = [ const fonts = [
'Roboto', 'Open Sans', 'Lato', 'Montserrat', 'Raleway', 'Poppins', 'Merriweather', "Roboto",
'Playfair Display', 'Nunito', 'Oswald', 'Source Sans Pro', 'Ubuntu', 'Noto Sans', "Open Sans",
'Work Sans', 'Bebas Neue', 'Arimo', 'PT Sans', 'PT Serif', 'Titillium Web', "Lato",
'Fira Sans', 'Karla', 'Josefin Sans', 'Cairo', 'Rubik', 'Mulish', 'IBM Plex Sans', "Montserrat",
'Quicksand', 'Cabin', 'Heebo', 'Exo 2', 'Manrope', 'Jost', 'Anton', 'Asap', "Raleway",
'Baloo 2', 'Barlow', 'Cantarell', 'Chivo', 'Inter', 'Dosis', 'Crimson Text', "Poppins",
'Amatic SC', 'ABeeZee', 'Raleway Dots', 'Pacifico', 'Orbitron', 'Varela Round', "Merriweather",
'Acme', 'Teko', "Playfair Display",
"Nunito",
"Oswald",
"Source Sans Pro",
"Ubuntu",
"Noto Sans",
"Work Sans",
"Bebas Neue",
"Arimo",
"PT Sans",
"PT Serif",
"Titillium Web",
"Fira Sans",
"Karla",
"Josefin Sans",
"Cairo",
"Rubik",
"Mulish",
"IBM Plex Sans",
"Quicksand",
"Cabin",
"Heebo",
"Exo 2",
"Manrope",
"Jost",
"Anton",
"Asap",
"Baloo 2",
"Barlow",
"Cantarell",
"Chivo",
"Inter",
"Dosis",
"Crimson Text",
"Amatic SC",
"ABeeZee",
"Raleway Dots",
"Pacifico",
"Orbitron",
"Varela Round",
"Acme",
"Teko",
]; ];
const TextCustomization = () => { const TextCustomization = () => {
@ -27,40 +83,40 @@ const TextCustomization = () => {
const { activeObject } = useContext(ActiveObjectContext); const { activeObject } = useContext(ActiveObjectContext);
const { canvas } = useContext(CanvasContext); const { canvas } = useContext(CanvasContext);
const [text, setText] = useState(''); const [text, setText] = useState("");
const [fontFamily, setFontFamily] = useState('Arial'); const [fontFamily, setFontFamily] = useState("Arial");
const [fontSize, setFontSize] = useState(20); const [fontSize, setFontSize] = useState(20);
const [fontStyle, setFontStyle] = useState('normal'); const [fontStyle, setFontStyle] = useState("normal");
const [fontWeight, setFontWeight] = useState('normal'); const [fontWeight, setFontWeight] = useState("normal");
const [lineHeight, setLineHeight] = useState(1.16); const [lineHeight, setLineHeight] = useState(1.16);
const [charSpacing, setCharSpacing] = useState(0); const [charSpacing, setCharSpacing] = useState(0);
const [underline, setUnderline] = useState(false); const [underline, setUnderline] = useState(false);
const [linethrough, setLinethrough] = useState(false); const [linethrough, setLinethrough] = useState(false);
const [textAlign, setTextAlign] = useState('left'); const [textAlign, setTextAlign] = useState("left");
const [previewText, setPreviewText] = useState(''); const [previewText, setPreviewText] = useState("");
useEffect(() => { useEffect(() => {
if (activeObject?.type === "i-text") { if (activeObject?.type === "i-text") {
setText(activeObject?.text || ''); setText(activeObject?.text || "");
setFontFamily(activeObject?.fontFamily || 'Arial'); setFontFamily(activeObject?.fontFamily || "Arial");
setFontSize(activeObject?.fontSize || 20); setFontSize(activeObject?.fontSize || 20);
setFontStyle(activeObject?.fontStyle || 'normal'); setFontStyle(activeObject?.fontStyle || "normal");
setFontWeight(activeObject?.fontWeight || 'normal'); setFontWeight(activeObject?.fontWeight || "normal");
setLineHeight(activeObject?.lineHeight || 1.16); setLineHeight(activeObject?.lineHeight || 1.16);
setCharSpacing(activeObject?.charSpacing || 0); setCharSpacing(activeObject?.charSpacing || 0);
setUnderline(activeObject?.underline || false); setUnderline(activeObject?.underline || false);
setLinethrough(activeObject?.linethrough || false); setLinethrough(activeObject?.linethrough || false);
setTextAlign(activeObject?.textAlign || 'left'); setTextAlign(activeObject?.textAlign || "left");
setPreviewText(activeObject?.text || ''); setPreviewText(activeObject?.text || "");
} }
}, [activeObject]) }, [activeObject]);
const updateActiveObject = (properties) => { const updateActiveObject = (properties) => {
if (activeObject?.type === "i-text") { if (activeObject?.type === "i-text") {
activeObject.set(properties) activeObject.set(properties);
canvas?.renderAll() canvas?.renderAll();
}
} }
};
const applyChanges = () => { const applyChanges = () => {
updateActiveObject({ updateActiveObject({
@ -73,69 +129,69 @@ const TextCustomization = () => {
charSpacing, charSpacing,
underline, underline,
linethrough, linethrough,
textAlign textAlign,
}); });
} };
const handleTextChange = (newText) => { const handleTextChange = (newText) => {
setText(newText); setText(newText);
setPreviewText(newText); setPreviewText(newText);
} };
const handleFontFamilyChange = (newFontFamily) => { const handleFontFamilyChange = (newFontFamily) => {
setFontFamily(newFontFamily); setFontFamily(newFontFamily);
} };
const handleFontSizeChange = (newFontSize) => { const handleFontSizeChange = (newFontSize) => {
setFontSize(newFontSize) setFontSize(newFontSize);
} };
const handleTextAlignChange = (newTextAlign) => { const handleTextAlignChange = (newTextAlign) => {
setTextAlign(newTextAlign) setTextAlign(newTextAlign);
} };
const handleFontStyleChange = () => { const handleFontStyleChange = () => {
const newFontStyle = fontStyle === 'normal' ? 'italic' : 'normal' const newFontStyle = fontStyle === "normal" ? "italic" : "normal";
setFontStyle(newFontStyle) setFontStyle(newFontStyle);
} };
const handleFontWeightChange = () => { const handleFontWeightChange = () => {
const newFontWeight = fontWeight === 'normal' ? 'bold' : 'normal' const newFontWeight = fontWeight === "normal" ? "bold" : "normal";
setFontWeight(newFontWeight) setFontWeight(newFontWeight);
} };
const handleLineHeightChange = (newLineHeight) => { const handleLineHeightChange = (newLineHeight) => {
setLineHeight(newLineHeight) setLineHeight(newLineHeight);
} };
const handleCharSpacingChange = (newCharSpacing) => { const handleCharSpacingChange = (newCharSpacing) => {
setCharSpacing(newCharSpacing) setCharSpacing(newCharSpacing);
} };
const handleUnderlineChange = () => { const handleUnderlineChange = () => {
const newUnderline = !underline const newUnderline = !underline;
setUnderline(newUnderline) setUnderline(newUnderline);
} };
const handleLinethroughChange = () => { const handleLinethroughChange = () => {
const newLinethrough = !linethrough const newLinethrough = !linethrough;
setLinethrough(newLinethrough) setLinethrough(newLinethrough);
} };
const content = () => { const content = () => {
if (!(activeObject?.type === "i-text")) { if (!(activeObject?.type === "i-text")) {
return ( return (
<div className='mt-2'> <div className="mt-2">
<Card> <Card>
<CardContent className="p-4"> <CardContent className="p-4">
<p className="text-center text-gray-500">Select a text object to customize</p> <p className="text-center text-gray-500">
Select a text object to customize
</p>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
) );
} } else {
else {
return ( return (
<CardContent className="p-2"> <CardContent className="p-2">
<Tabs defaultValue="text" className="w-full"> <Tabs defaultValue="text" className="w-full">
@ -155,14 +211,21 @@ const TextCustomization = () => {
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
<Label>Font Family</Label> <Label>Font Family</Label>
<Select value={fontFamily} onValueChange={handleFontFamilyChange}> <Select
value={fontFamily}
onValueChange={handleFontFamilyChange}
>
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select a font" /> <SelectValue placeholder="Select a font" />
</SelectTrigger> </SelectTrigger>
<SelectContent className="h-[250px]"> <SelectContent className="h-[250px]">
<SelectGroup> <SelectGroup>
{fonts.map((font) => ( {fonts.map((font) => (
<SelectItem style={{ fontFamily: font }} key={font} value={font}> <SelectItem
style={{ fontFamily: font }}
key={font}
value={font}
>
{font} {font}
</SelectItem> </SelectItem>
))} ))}
@ -184,7 +247,9 @@ const TextCustomization = () => {
<Input <Input
type="number" type="number"
value={fontSize} value={fontSize}
onChange={(e) => handleFontSizeChange(parseInt(e.target.value, 10) || 16)} onChange={(e) =>
handleFontSizeChange(parseInt(e.target.value, 10) || 16)
}
className="w-16" className="w-16"
/> />
</div> </div>
@ -195,23 +260,23 @@ const TextCustomization = () => {
<Label>Text Alignment</Label> <Label>Text Alignment</Label>
<div className="flex space-x-1"> <div className="flex space-x-1">
<Button <Button
variant={textAlign === 'left' ? 'default' : 'outline'} variant={textAlign === "left" ? "default" : "outline"}
size="icon" size="icon"
onClick={() => handleTextAlignChange('left')} onClick={() => handleTextAlignChange("left")}
> >
<AlignLeft className="h-4 w-4" /> <AlignLeft className="h-4 w-4" />
</Button> </Button>
<Button <Button
variant={textAlign === 'center' ? 'default' : 'outline'} variant={textAlign === "center" ? "default" : "outline"}
size="icon" size="icon"
onClick={() => handleTextAlignChange('center')} onClick={() => handleTextAlignChange("center")}
> >
<AlignCenter className="h-4 w-4" /> <AlignCenter className="h-4 w-4" />
</Button> </Button>
<Button <Button
variant={textAlign === 'right' ? 'default' : 'outline'} variant={textAlign === "right" ? "default" : "outline"}
size="icon" size="icon"
onClick={() => handleTextAlignChange('right')} onClick={() => handleTextAlignChange("right")}
> >
<AlignRight className="h-4 w-4" /> <AlignRight className="h-4 w-4" />
</Button> </Button>
@ -221,28 +286,28 @@ const TextCustomization = () => {
<Label>Text Style</Label> <Label>Text Style</Label>
<div className="flex space-x-1"> <div className="flex space-x-1">
<Button <Button
variant={fontWeight === 'bold' ? 'default' : 'outline'} variant={fontWeight === "bold" ? "default" : "outline"}
size="icon" size="icon"
onClick={handleFontWeightChange} onClick={handleFontWeightChange}
> >
<Bold className="h-4 w-4" /> <Bold className="h-4 w-4" />
</Button> </Button>
<Button <Button
variant={fontStyle === 'italic' ? 'default' : 'outline'} variant={fontStyle === "italic" ? "default" : "outline"}
size="icon" size="icon"
onClick={handleFontStyleChange} onClick={handleFontStyleChange}
> >
<Italic className="h-4 w-4" /> <Italic className="h-4 w-4" />
</Button> </Button>
<Button <Button
variant={underline ? 'default' : 'outline'} variant={underline ? "default" : "outline"}
size="icon" size="icon"
onClick={handleUnderlineChange} onClick={handleUnderlineChange}
> >
<Underline className="h-4 w-4" /> <Underline className="h-4 w-4" />
</Button> </Button>
<Button <Button
variant={linethrough ? 'default' : 'outline'} variant={linethrough ? "default" : "outline"}
size="icon" size="icon"
onClick={handleLinethroughChange} onClick={handleLinethroughChange}
> >
@ -273,9 +338,9 @@ const TextCustomization = () => {
</TabsContent> </TabsContent>
</Tabs> </Tabs>
</CardContent> </CardContent>
) );
}
} }
};
return ( return (
<Card className="p-2"> <Card className="p-2">
@ -283,32 +348,35 @@ const TextCustomization = () => {
{content()} {content()}
</CollapsibleComponent> </CollapsibleComponent>
<div className="mt-4 space-y-4"> <div className="mt-4 space-y-4">
{ {previewText && (
previewText &&
<div className="p-4 border rounded-md overflow-hidden"> <div className="p-4 border rounded-md overflow-hidden">
<p className="font-bold mb-2">Preview:</p> <p className="font-bold mb-2">Preview:</p>
<p style={{ <p
style={{
fontFamily, fontFamily,
fontSize: `${fontSize}px`, fontSize: `${fontSize}px`,
fontStyle, fontStyle,
fontWeight, fontWeight,
lineHeight, lineHeight,
letterSpacing: `${charSpacing}px`, letterSpacing: `${charSpacing}px`,
textDecoration: `${underline ? 'underline' : ''} ${linethrough ? 'line-through' : ''}`, textDecoration: `${underline ? "underline" : ""} ${
textAlign linethrough ? "line-through" : ""
}} className='truncate'> }`,
textAlign,
}}
className="truncate"
>
{previewText} {previewText}
</p> </p>
</div> </div>
} )}
<Button onClick={applyChanges} className="w-full"> <Button onClick={applyChanges} className="w-full">
Apply Changes Apply Changes
</Button> </Button>
</div> </div>
</Card> </Card>
) );
} };
export default TextCustomization
export default TextCustomization;

View file

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

View file

@ -1,28 +0,0 @@
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

@ -1,10 +1,147 @@
import { useEffect, useContext } from "react";
import { AspectRatio } from "@/components/ui/aspect-ratio";
import OpenContext from "../Context/openContext/OpenContext";
import CanvasContext from "../Context/canvasContext/CanvasContext";
import { Card, CardContent } from "../ui/card";
export function Canvas() { export function Canvas() {
const {
setLeftPanelOpen,
setRightPanelOpen,
setOpenSetting,
setOpenObjectPanel,
rightPanelOpen,
} = useContext(OpenContext);
const {
canvasRef,
canvas,
setCanvas,
fabricCanvasRef,
canvasRatio,
setCanvasHeight,
setCanvasWidth,
setScreenWidth,
} = useContext(CanvasContext);
useEffect(() => {
import("fabric").then((fabricModule) => {
window.fabric = fabricModule.fabric;
});
}, []);
const getRatioValue = (ratio) => {
const [width, height] = ratio.split(":").map(Number);
return width / height;
};
useEffect(() => {
const updateCanvasSize = () => {
if (canvasRef.current && canvas) {
// Update canvas dimensions
const newWidth = canvasRef?.current?.offsetWidth;
const newHeight = canvasRef?.current?.offsetHeight;
canvas.setWidth(newWidth);
canvas.setHeight(newHeight);
setCanvasWidth(newWidth);
setCanvasHeight(newHeight);
// Adjust the background image to fit the updated canvas size
const bgImage = canvas.backgroundImage;
if (bgImage) {
// Calculate scaling factors for width and height
const scaleX = newWidth / bgImage.width;
const scaleY = newHeight / bgImage.height;
// Use the larger scale to cover the entire canvas
const scale = Math.max(scaleX, scaleY);
// Apply scale and position the image
bgImage.scaleX = scale;
bgImage.scaleY = scale;
bgImage.left = 0; // Align left
bgImage.top = 0; // Align top
// Update the background image
canvas.setBackgroundImage(bgImage, canvas.renderAll.bind(canvas));
} else {
// Render the canvas if no background image
canvas.renderAll();
}
}
setScreenWidth(document.getElementById("root").offsetWidth);
// Handle responsive behavior for panels
if (document.getElementById("root").offsetWidth <= 640) {
setLeftPanelOpen(false);
setRightPanelOpen(false);
}
if (document.getElementById("root").offsetWidth > 640) {
setOpenObjectPanel(false);
setOpenSetting(false);
}
};
// Initial setup
updateCanvasSize();
// Listen for window resize
window.addEventListener("resize", updateCanvasSize);
// Cleanup listener on unmount
return () => window.removeEventListener("resize", updateCanvasSize);
}, [
setCanvasWidth,
setCanvasHeight,
canvasRatio,
canvasRef,
canvas,
setLeftPanelOpen,
setOpenObjectPanel,
setRightPanelOpen,
rightPanelOpen,
setScreenWidth,
setOpenSetting,
]);
useEffect(() => {
if (window.fabric) {
if (fabricCanvasRef?.current) {
fabricCanvasRef?.current.dispose();
}
// Set styles directly on the canvas element
const canvasElement = document.getElementById("fabric-canvas");
if (canvasElement) {
canvasElement.classList.add("fabric-canvas-container"); // Add the CSS class
}
fabricCanvasRef.current = new window.fabric.Canvas("fabric-canvas", {
width: canvasRef?.current?.offsetWidth,
height: canvasRef?.current?.offsetWidth,
backgroundColor: "#ffffff",
});
setCanvas(fabricCanvasRef?.current);
}
}, []);
return ( return (
<div className="flex-1 h-[88vh] bg-[#F5F0FF] p-8 flex flex-col mt-20"> <Card className="w-full max-w-3xl p-2 my-4 overflow-y-scroll scrollbar-thin scrollbar-thumb-secondary scrollbar-track-background rounded-none flex-1 flex flex-col mt-24 mx-auto bg-white pl-5 pb-5 pt-5 border-0 shadow-none">
{/* Main Canvas Area */} <CardContent className="p-0 space-y-2">
<div className="flex-1 bg-white rounded-3xl shadow-sm mb-4 flex items-center justify-center"> <AspectRatio
<div className="w-32 h-32 bg-muted rounded-lg" /> ratio={getRatioValue(canvasRatio)}
</div> className="overflow-y-scroll shadow-red-200 overflow-x-hidden shadow-lg rounded-lg border-2 border-primary/10 transition-all duration-300 ease-in-out hover:shadow-xl scrollbar-hide"
>
<div
ref={canvasRef}
className="w-full h-full flex items-center justify-center bg-white rounded-md shadow-lg"
id="canvas-ref"
>
<canvas id="fabric-canvas" />
</div> </div>
</AspectRatio>
</CardContent>
</Card>
); );
} }

View file

@ -0,0 +1,65 @@
import { useContext } from "react";
import CanvasContext from "../Context/canvasContext/CanvasContext";
import SelectObjectFromGroup from "../EachComponent/Customization/SelectObjectFromGroup";
import StrokeCustomization from "../EachComponent/Customization/StrokeCustomization";
import PositionCustomization from "../EachComponent/Customization/PositionCustomization";
import { Card } from "../ui/card";
import CollapsibleComponent from "../EachComponent/Customization/CollapsibleComponent";
import OpacityCustomization from "../EachComponent/Customization/OpacityCustomization";
import FlipCustomization from "../EachComponent/Customization/FlipCustomization";
import RotateCustomization from "../EachComponent/Customization/RotateCustomization";
import SkewCustomization from "../EachComponent/Customization/SkewCustomization";
import ScaleObjects from "../EachComponent/Customization/ScaleObjects";
import ShadowCustomization from "../EachComponent/Customization/ShadowCustomization";
import AddImageIntoShape from "../EachComponent/Customization/AddImageIntoShape";
const CommonPanel = () => {
const { canvas } = useContext(CanvasContext);
const activeObject = canvas?.getActiveObject();
const customClipPath = activeObject?.isClipPath;
return (
<div>
<div className="space-y-5">
<SelectObjectFromGroup />
{/* Apply stroke and stroke color */}
{!customClipPath && (
<>
<StrokeCustomization />
</>
)}
{activeObject?.type !== "group" && (
<>
<PositionCustomization />
</>
)}
{/* Controls for opacity, flip, and rotation */}
<Card className="shadow-none border-0">
<CollapsibleComponent text={"Opacity, Flip, Rotate Control"}>
<div className="space-y-2">
<OpacityCustomization />
<FlipCustomization />
<RotateCustomization />
</div>
</CollapsibleComponent>
</Card>
{/* Skew Customization */}
<SkewCustomization />
{/* Scale Objects */}
<ScaleObjects />
{/* Shadow Customization */}
<ShadowCustomization />
{/* Add image into shape */}
<AddImageIntoShape />
</div>
</div>
);
};
export default CommonPanel;

View file

@ -1,38 +0,0 @@
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

@ -1,56 +1,28 @@
import { Plus, Search, Upload } from "lucide-react"; import { useContext } from "react";
import { Button } from "../ui/button"; import CanvasContext from "../Context/canvasContext/CanvasContext";
import { Input } from "../ui/input"; import TextPanel from "./TextPanel";
export function EditorPanel({ type }) { const EditorPanel = () => {
if (!type) return null; const { selectedPanel } = useContext(CanvasContext);
const panelContent = { const renderPanel = () => {
text: ( switch (selectedPanel) {
<> case "text":
<h2 className="text-lg font-semibold mb-4">Text</h2> return <TextPanel />;
<Button className="w-full"> default:
<Plus className="mr-2 h-4 w-4" /> Add Text return;
</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 ( return (
<div className="w-80 bg-background border-r border-border p-4 overflow-y-auto"> <>
{panelContent[type]} {selectedPanel !== "" && (
<div className="w-80 h-[calc(100vh-32px)] bg-background rounded-xl shadow-lg mx-4 my-4">
{renderPanel()}
</div> </div>
)}
</>
); );
} };
export default EditorPanel;

View file

@ -1,20 +0,0 @@
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

@ -1,56 +1,92 @@
import { Button } from "../ui/Button"; import { Button } from "../ui/Button";
import { Input } from "../ui/Input";
import { X } from "lucide-react"; import { X } from "lucide-react";
import { ScrollArea } from "../ui/scroll-area"; import { ScrollArea } from "../ui/scroll-area";
import { useContext } from "react";
import CanvasContext from "../Context/canvasContext/CanvasContext";
import ActiveObjectContext from "../Context/activeObject/ObjectContext";
import { fabric } from "fabric";
import CommonPanel from "./CommonPanel";
import TextCustomization from "../EachComponent/Customization/TextCustomization";
export default function TextPanel() { export default function TextPanel() {
const { canvas } = useContext(CanvasContext);
const { setActiveObject } = useContext(ActiveObjectContext);
const activeObject = canvas?.getActiveObject();
const activeObjectType = activeObject?.type;
const hasClipPath = !!activeObject?.clipPath;
const customClipPath = activeObject?.isClipPath;
const addText = () => {
if (canvas) {
const text = new fabric.IText("Editable Text", {
left: 100,
top: 100,
fontFamily: "Poppins",
fontSize: 16,
});
// Add the text to the canvas and re-render
canvas.add(text);
// canvas.clipPath = text;
canvas.setActiveObject(text);
setActiveObject(text);
canvas.renderAll();
}
};
return ( return (
<div className="w-80 h-[calc(100vh-32px)] bg-background rounded-3xl shadow-lg mx-4 my-4"> <div>
<ScrollArea className="h-[calc(100vh-32px)] px-4 py-4"> <div className="flex justify-between items-center p-4 border-b">
<div className="flex justify-between items-center mb-4">
<h2 className="text-lg font-semibold">Text</h2> <h2 className="text-lg font-semibold">Text</h2>
<Button variant="ghost" size="icon"> <Button variant="ghost" size="icon">
<X className="h-4 w-4" /> <X className="h-4 w-4" />
</Button> </Button>
</div> </div>
<Button className="w-full bg-[#FF4D8D] hover:bg-[#FF3D7D] text-white rounded-2xl mb-6 h-12 text-base font-medium"> <ScrollArea className="h-[calc(100vh-115px)] px-4 py-4">
<Button
className="w-full bg-[#FF2B85] hover:bg-[#FF2B85] text-white rounded-[10px] mb-6 h-12 font-medium text-xl"
onClick={() => {
addText();
}}
>
Add Text Add Text
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
>
<path
d="M7.5 18.3333H12.5C16.6667 18.3333 18.3333 16.6666 18.3333 12.5V7.49996C18.3333 3.33329 16.6667 1.66663 12.5 1.66663H7.5C3.33333 1.66663 1.66667 3.33329 1.66667 7.49996V12.5C1.66667 16.6666 3.33333 18.3333 7.5 18.3333Z"
stroke="white"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M5.83333 7.40837C8.45833 6.10004 11.5417 6.10004 14.1667 7.40837"
stroke="white"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M10 13.5833V6.60828"
stroke="white"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</Button> </Button>
{activeObject ? (
<div className="space-y-4"> <div className="space-y-4">
<div> <CommonPanel />
<p className="text-sm text-muted-foreground mb-2"> <TextCustomization />
Default text style </div>
) : (
<p className="text-sm font-semibold text-center">
No active object found
</p> </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> </ScrollArea>
</div> </div>
); );