added fucntionality for text

This commit is contained in:
smfahim25 2025-01-27 17:33:55 +06:00
parent 954ac950b0
commit 79ca662a08
18 changed files with 826 additions and 656 deletions

10
package-lock.json generated
View file

@ -31,6 +31,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-dropzone": "^14.3.5",
"react-icons": "^5.4.0",
"react-image-file-resizer": "^0.4.8",
"react-rnd": "^10.4.13",
"react-window": "^1.8.10",
@ -7128,6 +7129,15 @@
"react": ">= 16.8 || 18.0.0"
}
},
"node_modules/react-icons": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.4.0.tgz",
"integrity": "sha512-7eltJxgVt7X64oHh6wSWNwwbKTCtMfK35hcjvJS0yxEAhPM8oUKdS3+kqaW1vicIltw+kR2unHaa12S9pPALoQ==",
"license": "MIT",
"peerDependencies": {
"react": "*"
}
},
"node_modules/react-image-file-resizer": {
"version": "0.4.8",
"resolved": "https://registry.npmjs.org/react-image-file-resizer/-/react-image-file-resizer-0.4.8.tgz",

View file

@ -33,6 +33,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-dropzone": "^14.3.5",
"react-icons": "^5.4.0",
"react-image-file-resizer": "^0.4.8",
"react-rnd": "^10.4.13",
"react-window": "^1.8.10",

View file

@ -1,19 +1,20 @@
import { useContext, useEffect, useState } from "react";
import { useContext, 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 CanvasCapture from "./components/CanvasCapture";
import { Toaster } from "./components/ui/toaster";
// 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 { Sidebar } from "./components/Layouts/LeftSidebar";
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 { ActionButtons } from "./components/ActionButtons";
import EditorPanel from "./components/Panel/EditorPanel";
import CanvasContext from "./components/Context/canvasContext/CanvasContext";
import Canvas from "./components/Canvas/Canvas";
import ActiveObjectContext from "./components/Context/activeObject/ObjectContext";
function App() {
useEffect(() => {
@ -75,7 +76,7 @@ function App() {
}, []);
const { selectedPanel } = useContext(CanvasContext);
const [hasSelectedObject, setHasSelectedObject] = useState(true);
const { activeObject } = useContext(ActiveObjectContext);
return (
// <div className="relative flex flex-col h-screen overflow-hidden">
@ -96,7 +97,7 @@ function App() {
<Sidebar />
{selectedPanel !== "" && <EditorPanel />}
<div className="flex-1 relative">
<TopBar isVisible={hasSelectedObject} />
{activeObject && <TopBar />}
<ActionButtons />
<Canvas />
</div>

View file

@ -3,8 +3,9 @@ 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";
import ActiveObjectContext from "../Context/activeObject/ObjectContext";
export function Canvas() {
export default function Canvas() {
const {
setLeftPanelOpen,
setRightPanelOpen,
@ -24,6 +25,36 @@ export function Canvas() {
setScreenWidth,
} = useContext(CanvasContext);
const { activeObject, setActiveObject } = useContext(ActiveObjectContext);
useEffect(() => {
if (!canvas) return; // Ensure canvas is available
// Event handler for mouse down
const handleMouseDown = (event) => {
const target = event.target; // Get the clicked target
const activeObject = canvas.getActiveObject(); // Get the active object
if (target) {
if (target.type === "group") {
setActiveObject(activeObject);
} else {
setActiveObject(activeObject);
}
} else {
setActiveObject(activeObject);
}
};
// Attach the event listener
canvas.on("mouse:down", handleMouseDown);
// Cleanup function to remove the event listener
return () => {
canvas.off("mouse:down", handleMouseDown); // Remove the listener on unmount
};
}, [canvas, setActiveObject]);
useEffect(() => {
import("fabric").then((fabricModule) => {
window.fabric = fabricModule.fabric;
@ -127,7 +158,11 @@ export function Canvas() {
}, []);
return (
<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">
<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 ${
activeObject ? "mt-5" : "mt-20"
} mx-auto bg-white pl-5 pb-5 pt-5 border-0 shadow-none`}
>
<CardContent className="p-0 space-y-2">
<AspectRatio
ratio={getRatioValue(canvasRatio)}

View file

@ -1,11 +1,14 @@
import { useState } from 'react'
import ActiveObjectContext from './ObjectContext'
import { useState } from "react";
import ActiveObjectContext from "./ObjectContext";
export const ObjectProvider = ({ children }) => {
const ObjectProvider = ({ children }) => {
const [activeObject, setActiveObject] = useState(null);
return (
<ActiveObjectContext.Provider value={{ activeObject, setActiveObject }}>
{children}
</ActiveObjectContext.Provider>
)
}
);
};
export default ObjectProvider;

View file

@ -9,6 +9,7 @@ const CanvasContextProvider = ({ children }) => {
const [screenWidth, setScreenWidth] = useState(0);
const [canvasRatio, setCanvasRatio] = useState("4:3");
const [selectedPanel, setSelectedPanel] = useState("");
const [textColor, setTextColor] = useState(null);
const fabricCanvasRef = useRef(null);
return (
@ -21,6 +22,8 @@ const CanvasContextProvider = ({ children }) => {
canvasHeight,
canvasRatio,
setCanvasRatio,
textColor,
setTextColor,
selectedPanel,
setSelectedPanel,
setCanvasHeight,

View file

@ -1,16 +1,20 @@
import { useContext, useEffect, useState } from 'react'
import ActiveObjectContext from '../Context/activeObject/ObjectContext';
import CanvasContext from '../Context/canvasContext/CanvasContext';
import { fabric } from 'fabric';
import { debounce } from "lodash";
import { Separator } from '../ui/separator';
import { Label } from '../ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
import { Input } from '../ui/input';
import { Card } from '../ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
import { Button } from '../ui/button';
import CollapsibleComponent from './Customization/CollapsibleComponent';
import { useCallback, useContext, useEffect, useState } from "react";
import ActiveObjectContext from "../Context/activeObject/ObjectContext";
import CanvasContext from "../Context/canvasContext/CanvasContext";
import { fabric } from "fabric";
import { Separator } from "../ui/separator";
import { Label } from "../ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../ui/select";
import { Input } from "../ui/input";
import { Card } from "../ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
import CollapsibleComponent from "./Customization/CollapsibleComponent";
const ApplyColor = () => {
const [colorField, setColorField] = useState("fill");
@ -22,11 +26,12 @@ const ApplyColor = () => {
});
const [colorType, setColorType] = useState("color"); // 'color' or 'gradient'
const [gradientDirection, setGradientDirection] = useState("top-to-bottom");
// get values from context
const { activeObject } = useContext(ActiveObjectContext);
const { canvas } = useContext(CanvasContext);
// to get previous values from active object
// Get values from context
const { activeObject, setActiveObject } = useContext(ActiveObjectContext);
const { canvas, setTextColor } = useContext(CanvasContext);
// To get previous values from active object
useEffect(() => {
const handleObjectStyle = (object) => {
if (object.fill) {
@ -73,39 +78,18 @@ const ApplyColor = () => {
}
}, [activeObject, colorField]);
const handleSolidColorChange = debounce((newColor) => {
if (colorField === "fill") {
setFillColor(newColor)
}
else {
setBackgroundColor(newColor)
}
}, 100);
// Common debounce handler for updating colors
const debouncedSetGradientColors = debounce((key, value) => {
if (colorField === "fill") {
setGradientFillColors((prev) => ({
...prev,
[key]: value,
}))
}
}, 300); // Adjust debounce delay as needed
const handleColorChange = (key) => (e) => {
const newColor = e.target.value;
debouncedSetGradientColors(key, newColor);
};
// Apply color/gradient style to the selected object on the canvas
const applyColor = () => {
const applyColor = useCallback(() => {
const applyStyleToObject = (object, style) => {
if (colorType === "color") {
if (colorField === "fill" && object.fill !== style.fill) {
object.set("fill", style.fill);
}
if (colorField === "background" && object.backgroundColor !== style.backgroundColor) {
if (
colorField === "background" &&
object.backgroundColor !== style.backgroundColor
) {
object.set("backgroundColor", style.backgroundColor);
}
}
@ -151,11 +135,51 @@ const ApplyColor = () => {
backgroundColor: backgroundColor,
};
if (activeObject) {
applyStyleRecursively(activeObject, style);
// Trigger re-render for the canvas
setActiveObject(activeObject);
setTextColor(style);
canvas.renderAll();
}
}, [
activeObject,
fillColor,
backgroundColor,
gradientFillColors,
colorType,
gradientDirection,
setActiveObject,
canvas,
colorField,
setTextColor,
]);
// Watch for changes in color-related states and apply them instantly
useEffect(() => {
applyColor();
}, [
applyColor, // Now included in dependencies
fillColor,
backgroundColor,
gradientFillColors,
colorType,
gradientDirection,
]);
const handleSolidColorChange = (newColor) => {
if (colorField === "fill") {
setFillColor(newColor);
} else {
setBackgroundColor(newColor);
}
};
const handleGradientColorChange = (key, value) => {
setGradientFillColors((prev) => ({
...prev,
[key]: value,
}));
};
const content = () => {
return (
@ -164,7 +188,10 @@ const ApplyColor = () => {
<div className="space-y-2">
<div className="space-y-2">
<Label htmlFor="colorField">Color Field</Label>
<Select value={colorField} onValueChange={(value) => setColorField(value)}>
<Select
value={colorField}
onValueChange={(value) => setColorField(value)}
>
<SelectTrigger id="colorField">
<SelectValue placeholder="Select color field type" />
</SelectTrigger>
@ -175,10 +202,18 @@ const ApplyColor = () => {
</Select>
</div>
<Tabs value={colorType} onValueChange={(value) => setColorType(value)}>
<Tabs
value={colorType}
onValueChange={(value) => setColorType(value)}
>
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="color">Solid Color</TabsTrigger>
<TabsTrigger value="gradient" disabled={colorField === 'background'}>Gradient</TabsTrigger>
<TabsTrigger
value="gradient"
disabled={colorField === "background"}
>
Gradient
</TabsTrigger>
</TabsList>
<TabsContent value="color" className="space-y-4">
@ -188,36 +223,55 @@ const ApplyColor = () => {
<Input
id="solidColor"
type="color"
value={colorField === "fill" ? fillColor : backgroundColor}
value={
colorField === "fill" ? fillColor : backgroundColor
}
onChange={(e) => handleSolidColorChange(e.target.value)}
className="w-12 h-12 p-1 rounded-md"
/>
<Input
type="text"
value={colorField === "fill" ? fillColor : backgroundColor}
value={
colorField === "fill" ? fillColor : backgroundColor
}
onChange={(e) => handleSolidColorChange(e.target.value)}
className="flex-grow"
/>
</div>
</div>
<div className="h-24 rounded-md" style={{
backgroundColor: colorField === "fill" ? fillColor : backgroundColor,
}}></div>
<div
className="h-24 rounded-md"
style={{
backgroundColor:
colorField === "fill" ? fillColor : backgroundColor,
}}
></div>
</TabsContent>
<TabsContent value="gradient" className="space-y-4">
<div className="space-y-2">
<Label htmlFor="gradientDirection">Direction</Label>
<Select value={gradientDirection} onValueChange={setGradientDirection}>
<Select
value={gradientDirection}
onValueChange={setGradientDirection}
>
<SelectTrigger id="gradientDirection">
<SelectValue placeholder="Select direction" />
</SelectTrigger>
<SelectContent>
<SelectItem value="top-to-bottom">Top to Bottom</SelectItem>
<SelectItem value="bottom-to-top">Bottom to Top</SelectItem>
<SelectItem value="left-to-right">Left to Right</SelectItem>
<SelectItem value="right-to-left">Right to Left</SelectItem>
<SelectItem value="top-to-bottom">
Top to Bottom
</SelectItem>
<SelectItem value="bottom-to-top">
Bottom to Top
</SelectItem>
<SelectItem value="left-to-right">
Left to Right
</SelectItem>
<SelectItem value="right-to-left">
Right to Left
</SelectItem>
</SelectContent>
</Select>
</div>
@ -228,13 +282,17 @@ const ApplyColor = () => {
id="gradientColor1"
type="color"
value={gradientFillColors.color1}
onChange={handleColorChange('color1')}
onChange={(e) =>
handleGradientColorChange("color1", e.target.value)
}
className="w-12 h-12 p-1 rounded-md"
/>
<Input
type="text"
value={gradientFillColors.color1}
onChange={handleColorChange('color1')}
onChange={(e) =>
handleGradientColorChange("color1", e.target.value)
}
className="flex-grow"
/>
</div>
@ -246,44 +304,55 @@ const ApplyColor = () => {
id="gradientColor2"
type="color"
value={gradientFillColors.color2}
onChange={handleColorChange('color2')}
onChange={(e) =>
handleGradientColorChange("color2", e.target.value)
}
className="w-12 h-12 p-1 rounded-md"
/>
<Input
type="text"
value={gradientFillColors.color2}
onChange={handleColorChange('color2')}
onChange={(e) =>
handleGradientColorChange("color2", e.target.value)
}
className="flex-grow"
/>
</div>
</div>
<div className="h-24 rounded-md" style={{
background: `linear-gradient(${gradientDirection === 'top-to-bottom' ? 'to bottom' :
gradientDirection === 'bottom-to-top' ? 'to top' :
gradientDirection === 'left-to-right' ? 'to right' :
'to left'
}, ${gradientFillColors.color1}, ${gradientFillColors.color2})`,
}}></div>
<div
className="h-24 rounded-md"
style={{
background: `linear-gradient(${
gradientDirection === "top-to-bottom"
? "to bottom"
: gradientDirection === "bottom-to-top"
? "to top"
: gradientDirection === "left-to-right"
? "to right"
: "to left"
}, ${gradientFillColors.color1}, ${
gradientFillColors.color2
})`,
}}
></div>
</TabsContent>
</Tabs>
</div>
<Button className="my-2" onClick={() => { applyColor() }}>Apply</Button>
</div>
</div>
)
}
);
};
return (
<div>
<Card className="p-2">
<Card className="border-0 shadow-none">
<CollapsibleComponent text={"Color Control"}>
{content()}
</CollapsibleComponent>
</Card>
<Separator className="my-2" />
</div>
)
}
);
};
export default ApplyColor
export default ApplyColor;

View file

@ -18,7 +18,7 @@ const CollapsibleComponent = ({ children, text }) => {
}, [text]);
return (
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
<Collapsible open={isOpen}>
<CollapsibleTrigger asChild>
<div
className={`flex items-center justify-between cursor-pointer ${

View file

@ -4,7 +4,6 @@ import { Button } from "@/components/ui/button";
import { useToast } from "@/hooks/use-toast";
import { useContext, useEffect, useState } from "react";
import { Lock, Unlock } from "lucide-react";
import { Card } from "@/components/ui/card";
const LockObject = () => {
const { canvas } = useContext(CanvasContext);
@ -51,8 +50,7 @@ const LockObject = () => {
};
return (
<Card className="shadow-none border-0">
<h2 className="font-bold">{!isLocked ? "Lock" : "Unlock"} Object</h2>
<div className="shadow-none border-0">
<Button
onClick={toggleLock}
variant="outline"
@ -66,7 +64,7 @@ const LockObject = () => {
<Lock className="h-4 w-4" />
)}
</Button>
</Card>
</div>
);
};

View file

@ -1,46 +1,61 @@
import ActiveObjectContext from '@/components/Context/activeObject/ObjectContext';
import CanvasContext from '@/components/Context/canvasContext/CanvasContext';
import { Label } from '@/components/ui/label';
import { Slider } from '@/components/ui/slider';
import { useContext, useEffect, useState } from 'react'
import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext";
import CanvasContext from "@/components/Context/canvasContext/CanvasContext";
import { Label } from "@/components/ui/label";
import { Slider } from "@/components/ui/slider";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { useContext, useEffect, useState } from "react";
import { BsTransparency } from "react-icons/bs";
import { Button } from "@/components/ui/button";
const OpacityCustomization = () => {
const { activeObject } = useContext(ActiveObjectContext);
const { canvas } = useContext(CanvasContext);
const [opacity, setOpacity] = useState(0);
useEffect(() => {
if (activeObject) {
setOpacity(activeObject?.opacity);
}
}, [activeObject])
}, [activeObject]);
const adjustBackgroundOpacity = (value) => {
setOpacity(value);
const adjustBackgroundOpacity = (newOpacity) => {
setOpacity(newOpacity);
if (activeObject) {
activeObject.set("opacity", opacity); // Update the opacity
canvas.renderAll(); // Re-render the canvas
activeObject.set("opacity", newOpacity);
canvas.renderAll();
}
};
return (
<div className='grid gap-2 pt-1'>
<Label>
Adjust Opacity:
</Label>
<Popover>
<PopoverTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8">
<BsTransparency className="h-4 w-4" size={20} />
</Button>
</PopoverTrigger>
<PopoverContent className="w-64 mt-3">
<div className="grid gap-4">
<div className="flex items-center justify-between">
<Label className="text-sm font-medium">Opacity</Label>
<span className="text-sm text-gray-500">
{Math.round(opacity * 100)}%
</span>
</div>
<Slider
value={[opacity]}
min={0.0}
max={1.0}
step={0.01} // Step size for fine control
onValueChange={(value) => {
const newOpacity = value[0]; // Extract slider value
adjustBackgroundOpacity(newOpacity); // Adjust Fabric.js background opacity
}}
min={0}
max={1}
step={0.01}
onValueChange={(value) => adjustBackgroundOpacity(value[0])}
/>
</div>
)
}
</PopoverContent>
</Popover>
);
};
export default OpacityCustomization
export default OpacityCustomization;

View file

@ -47,7 +47,7 @@ const StrokeCustomization = () => {
}
}
if (object.strokeWidth) {
setStrokeWidth(object.strokeWidth || 0);
setStrokeWidth(0);
}
};

View file

@ -1,7 +1,7 @@
import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext";
import CanvasContext from "@/components/Context/canvasContext/CanvasContext";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
@ -12,9 +12,12 @@ import {
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 {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { useCallback, useContext, useEffect, useState } from "react";
import {
AlignLeft,
AlignCenter,
@ -23,8 +26,11 @@ import {
Italic,
Underline,
Strikethrough,
Minus,
Plus,
} from "lucide-react";
import CollapsibleComponent from "./CollapsibleComponent";
import { RiLineHeight } from "react-icons/ri";
import { Slider } from "@/components/ui/slider";
const fonts = [
"Roboto",
@ -79,9 +85,8 @@ const fonts = [
];
const TextCustomization = () => {
// get values from context
const { activeObject } = useContext(ActiveObjectContext);
const { canvas } = useContext(CanvasContext);
const { canvas, setSelectedPanel, textColor } = useContext(CanvasContext);
const [text, setText] = useState("");
const [fontFamily, setFontFamily] = useState("Arial");
@ -93,7 +98,7 @@ const TextCustomization = () => {
const [underline, setUnderline] = useState(false);
const [linethrough, setLinethrough] = useState(false);
const [textAlign, setTextAlign] = useState("left");
const [previewText, setPreviewText] = useState("");
const [fillColor, setFillColor] = useState("black");
useEffect(() => {
if (activeObject?.type === "i-text") {
@ -107,18 +112,21 @@ const TextCustomization = () => {
setUnderline(activeObject?.underline || false);
setLinethrough(activeObject?.linethrough || false);
setTextAlign(activeObject?.textAlign || "left");
setPreviewText(activeObject?.text || "");
setFillColor(textColor?.fill || "black");
}
}, [activeObject]);
}, [activeObject, textColor]);
const updateActiveObject = (properties) => {
const updateActiveObject = useCallback(
(properties) => {
if (activeObject?.type === "i-text") {
activeObject.set(properties);
canvas?.renderAll();
}
};
},
[activeObject, canvas]
); // Add dependencies
const applyChanges = () => {
const applyChanges = useCallback(() => {
updateActiveObject({
text,
fontFamily,
@ -131,12 +139,29 @@ const TextCustomization = () => {
linethrough,
textAlign,
});
};
}, [
text,
fontFamily,
fontSize,
fontStyle,
fontWeight,
lineHeight,
charSpacing,
underline,
linethrough,
textAlign,
updateActiveObject, // Add this dependency
]);
const handleTextChange = (newText) => {
setText(newText);
setPreviewText(newText);
};
// Automatically apply changes when state updates
useEffect(() => {
if (activeObject?.type === "i-text") {
applyChanges();
}
}, [
applyChanges, // Now included in dependencies
activeObject?.type, // Track active object type
]);
const handleFontFamilyChange = (newFontFamily) => {
setFontFamily(newFontFamily);
@ -151,80 +176,50 @@ const TextCustomization = () => {
};
const handleFontStyleChange = () => {
const newFontStyle = fontStyle === "normal" ? "italic" : "normal";
setFontStyle(newFontStyle);
setFontStyle(fontStyle === "normal" ? "italic" : "normal");
};
const handleFontWeightChange = () => {
const newFontWeight = fontWeight === "normal" ? "bold" : "normal";
setFontWeight(newFontWeight);
setFontWeight(fontWeight === "normal" ? "bold" : "normal");
};
const handleLineHeightChange = (newLineHeight) => {
setLineHeight(newLineHeight);
setLineHeight(parseFloat(newLineHeight));
};
const handleCharSpacingChange = (newCharSpacing) => {
setCharSpacing(newCharSpacing);
setCharSpacing(parseInt(newCharSpacing));
};
const handleUnderlineChange = () => {
const newUnderline = !underline;
setUnderline(newUnderline);
setUnderline(!underline);
};
const handleLinethroughChange = () => {
const newLinethrough = !linethrough;
setLinethrough(newLinethrough);
setLinethrough(!linethrough);
};
const content = () => {
if (!(activeObject?.type === "i-text")) {
return (
<div className="mt-2">
<Card>
<CardContent className="p-4">
<p className="text-center text-gray-500">
Select a text object to customize
</p>
</CardContent>
</Card>
</div>
);
} else {
return (
<CardContent className="p-2">
<Tabs defaultValue="text" className="w-full">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="text">Text</TabsTrigger>
<TabsTrigger value="style">Style</TabsTrigger>
</TabsList>
return <div></div>;
}
<TabsContent value="text" className="space-y-1">
<div className="space-y-1">
<Label htmlFor="text-content">Text Content</Label>
<Input
id="text-content"
value={text}
onChange={(e) => handleTextChange(e.target.value)}
/>
</div>
<div className="space-y-1">
<Label>Font Family</Label>
<Select
value={fontFamily}
onValueChange={handleFontFamilyChange}
>
<SelectTrigger>
return (
<div className="space-y-4">
{/* New Toolbar Design */}
<div className="flex w-full items-center space-x-2 rounded-lg p-1 bg-white">
{/* Font Family Select */}
<Select value={fontFamily} onValueChange={handleFontFamilyChange}>
<SelectTrigger className="min-w-[140px] h-8">
<SelectValue placeholder="Select a font" />
</SelectTrigger>
<SelectContent className="h-[250px]">
<SelectGroup>
{fonts.map((font) => (
<SelectItem
style={{ fontFamily: font }}
key={font}
value={font}
style={{ fontFamily: font }}
>
{font}
</SelectItem>
@ -232,91 +227,126 @@ const TextCustomization = () => {
</SelectGroup>
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<Label>Font Size</Label>
<div className="flex items-center space-x-2">
<Slider
value={[fontSize]}
onValueChange={([value]) => handleFontSizeChange(value)}
min={8}
max={72}
step={1}
className="flex-grow"
/>
<Input
type="number"
value={fontSize}
onChange={(e) =>
handleFontSizeChange(parseInt(e.target.value, 10) || 16)
}
className="w-16"
/>
</div>
</div>
</TabsContent>
<TabsContent value="style" className="space-y-4">
<div className="flex items-center justify-between">
<Label>Text Alignment</Label>
<div className="flex space-x-1">
{/* Font Size Controls */}
<div className="flex items-center border rounded-md">
<Button
variant={textAlign === "left" ? "default" : "outline"}
variant="ghost"
size="icon"
className="h-8 w-8 rounded-none"
onClick={() => handleFontSizeChange(Math.max(8, fontSize - 1))}
>
<Minus className="h-4 w-4" />
</Button>
<Input
type="text"
value={fontSize}
onChange={(e) => {
const numericValue = e.target.value.replace(/\D/g, "");
handleFontSizeChange(parseInt(numericValue) || 12);
}}
className="w-12 h-8 border-0 text-center"
/>
<Button
variant="ghost"
size="icon"
className="h-8 w-8 rounded-none"
onClick={() => handleFontSizeChange(Math.min(72, fontSize + 1))}
>
<Plus className="h-4 w-4" />
</Button>
</div>
{/* Vertical Separator */}
<div className="h-6 w-px bg-gray-200" />
{/* Text Formatting Controls */}
<div className="flex items-center space-x-1">
<Button
variant="ghost"
size="icon"
className="relative"
onClick={() => setSelectedPanel("color")}
>
<div className="relative">
<span className="text-lg font-semibold">A</span>
<div
className="absolute -bottom-0.5 -left-1 right-0 h-1 w-5 transition-all duration-200"
style={{ backgroundColor: fillColor }}
/>
</div>
</Button>
<Button
variant={textAlign === "left" ? "secondary" : "ghost"}
size="icon"
onClick={() => handleTextAlignChange("left")}
>
<AlignLeft className="h-4 w-4" />
</Button>
<Button
variant={textAlign === "center" ? "default" : "outline"}
variant={textAlign === "center" ? "secondary" : "ghost"}
size="icon"
onClick={() => handleTextAlignChange("center")}
>
<AlignCenter className="h-4 w-4" />
</Button>
<Button
variant={textAlign === "right" ? "default" : "outline"}
variant={textAlign === "right" ? "secondary" : "ghost"}
size="icon"
onClick={() => handleTextAlignChange("right")}
>
<AlignRight className="h-4 w-4" />
</Button>
</div>
</div>
<div className="flex items-center justify-between">
<Label>Text Style</Label>
<div className="flex space-x-1">
<Button
variant={fontWeight === "bold" ? "default" : "outline"}
variant={fontWeight === "bold" ? "secondary" : "ghost"}
size="icon"
className="h-8 w-8"
onClick={handleFontWeightChange}
>
<Bold className="h-4 w-4" />
</Button>
<Button
variant={fontStyle === "italic" ? "default" : "outline"}
size="icon"
onClick={handleFontStyleChange}
>
<Italic className="h-4 w-4" />
</Button>
<Button
variant={underline ? "default" : "outline"}
variant={underline ? "secondary" : "ghost"}
size="icon"
className="h-8 w-8"
onClick={handleUnderlineChange}
>
<Underline className="h-4 w-4" />
</Button>
<Button
variant={linethrough ? "default" : "outline"}
variant={fontStyle === "italic" ? "secondary" : "ghost"}
size="icon"
className="h-8 w-8"
onClick={handleFontStyleChange}
>
<Italic className="h-4 w-4" />
</Button>
<Button
variant={linethrough ? "secondary" : "ghost"}
size="icon"
className="h-8 w-8"
onClick={handleLinethroughChange}
>
<Strikethrough className="h-4 w-4" />
</Button>
</div>
</div>
{/* Vertical Separator */}
<div className="h-6 w-px bg-gray-200" />
{/* Spacing Controls */}
<Popover>
<PopoverTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8">
<RiLineHeight className="h-4 w-4" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-44 mt-3">
<div className="space-y-4">
<div className="space-y-2">
<Label>Line Height</Label>
<Label>Line Spacing</Label>
<div className="flex items-center space-x-2">
<Slider
value={[lineHeight]}
onValueChange={([value]) => handleLineHeightChange(value)}
@ -325,30 +355,38 @@ const TextCustomization = () => {
step={0.1}
/>
</div>
</div>
<div className="space-y-2">
<Label>Character Spacing</Label>
<Label>Letter Spacing</Label>
<div className="flex items-center space-x-2">
<Slider
value={[charSpacing]}
onValueChange={([value]) => handleCharSpacingChange(value)}
onValueChange={([value]) =>
handleCharSpacingChange(value)
}
min={-20}
max={100}
step={1}
/>
</div>
</TabsContent>
</Tabs>
</CardContent>
);
}
};
</div>
</div>
</PopoverContent>
</Popover>
</div>
return (
<Card className="p-2">
<CollapsibleComponent text={"Text Control"}>
{content()}
</CollapsibleComponent>
<div className="mt-4 space-y-4">
{previewText && (
{/* Text Input */}
{/* <div className="space-y-2">
<Label htmlFor="text-content">Text Content</Label>
<Input
id="text-content"
value={text}
onChange={(e) => handleTextChange(e.target.value)}
/>
</div> */}
{/* Preview */}
{/* {previewText && (
<div className="p-4 border rounded-md overflow-hidden">
<p className="font-bold mb-2">Preview:</p>
<p
@ -369,14 +407,17 @@ const TextCustomization = () => {
{previewText}
</p>
</div>
)}
)} */}
<Button onClick={applyChanges} className="w-full">
{/* Apply Changes Button */}
{/* <Button onClick={applyChanges} className="w-full">
Apply Changes
</Button>
</Button> */}
</div>
</Card>
);
};
return <div className="">{content()}</div>;
};
export default TextCustomization;

View file

@ -0,0 +1,29 @@
import { useContext } from "react";
import ApplyColor from "../EachComponent/ApplyColor";
import { Button } from "../ui/button";
import CanvasContext from "../Context/canvasContext/CanvasContext";
import { X } from "lucide-react";
import { ScrollArea } from "../ui/scroll-area";
const ColorPanel = () => {
const { setSelectedPanel } = useContext(CanvasContext);
return (
<div>
<div className="flex justify-between items-center p-4 border-b">
<h2 className="text-lg font-semibold">Color</h2>
<Button
variant="ghost"
size="icon"
onClick={() => setSelectedPanel("")}
>
<X className="h-4 w-4" />
</Button>
</div>
<ScrollArea className="lg:h-[calc(90vh-190px)] xl:h-[calc(100vh-190px)] px-4 py-4">
<ApplyColor />
</ScrollArea>
</div>
);
};
export default ColorPanel;

View file

@ -5,23 +5,31 @@ import StrokeCustomization from "../EachComponent/Customization/StrokeCustomizat
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";
import ApplyColor from "../EachComponent/ApplyColor";
const CommonPanel = () => {
const { canvas } = useContext(CanvasContext);
const activeObject = canvas?.getActiveObject();
const activeObjectType = activeObject?.type;
const hasClipPath = !!activeObject?.clipPath;
const customClipPath = activeObject?.isClipPath;
return (
<div>
<div className="space-y-5">
<SelectObjectFromGroup />
{/* Apply fill and background color */}
{activeObjectType !== "image" && !hasClipPath && !customClipPath && (
<ApplyColor />
)}
{/* Apply stroke and stroke color */}
{!customClipPath && (
<>
@ -37,9 +45,8 @@ const CommonPanel = () => {
{/* Controls for opacity, flip, and rotation */}
<Card className="shadow-none border-0">
<CollapsibleComponent text={"Opacity, Flip, Rotate Control"}>
<CollapsibleComponent text={"Flip, Rotate Control"}>
<div className="space-y-2">
<OpacityCustomization />
<FlipCustomization />
<RotateCustomization />
</div>

View file

@ -1,6 +1,7 @@
import { useContext } from "react";
import CanvasContext from "../Context/canvasContext/CanvasContext";
import TextPanel from "./TextPanel";
import ColorPanel from "./ColorPanel";
const EditorPanel = () => {
const { selectedPanel } = useContext(CanvasContext);
@ -9,6 +10,8 @@ const EditorPanel = () => {
switch (selectedPanel) {
case "text":
return <TextPanel />;
case "color":
return <ColorPanel />;
default:
return;
}
@ -17,7 +20,7 @@ const EditorPanel = () => {
return (
<>
{selectedPanel !== "" && (
<div className="w-80 h-[calc(100vh-32px)] bg-background rounded-xl shadow-lg mx-4 my-4">
<div className="w-80 lg:h-[calc(90vh-100px)] xl:h-[calc(100vh-100px)] bg-background rounded-xl shadow-lg mx-4 my-auto">
{renderPanel()}
</div>
)}

View file

@ -1,21 +1,25 @@
import { Button } from "../ui/Button";
import { X } from "lucide-react";
import { ScrollArea } from "../ui/scroll-area";
import { useContext } from "react";
import { useContext, useEffect, useState } 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() {
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 { canvas, setSelectedPanel } = useContext(CanvasContext);
const { activeObject, setActiveObject } = useContext(ActiveObjectContext);
const [open, setOpen] = useState(false);
useEffect(() => {
if (activeObject) {
setOpen(true);
} else {
setOpen(false);
}
}, [activeObject]);
const addText = () => {
if (canvas) {
const text = new fabric.IText("Editable Text", {
@ -37,12 +41,16 @@ export default function TextPanel() {
<div>
<div className="flex justify-between items-center p-4 border-b">
<h2 className="text-lg font-semibold">Text</h2>
<Button variant="ghost" size="icon">
<Button
variant="ghost"
size="icon"
onClick={() => setSelectedPanel("")}
>
<X className="h-4 w-4" />
</Button>
</div>
<ScrollArea className="h-[calc(100vh-115px)] px-4 py-4">
<ScrollArea className="lg:h-[calc(90vh-190px)] xl:h-[calc(100vh-190px)] 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={() => {
@ -77,15 +85,14 @@ export default function TextPanel() {
/>
</svg>
</Button>
{activeObject ? (
<div className="space-y-4">
<CommonPanel />
<TextCustomization />
</div>
) : (
{!open ? (
<p className="text-sm font-semibold text-center">
No active object found
</p>
) : (
<div className="space-y-4">
<CommonPanel />
</div>
)}
</ScrollArea>
</div>

View file

@ -1,13 +1,3 @@
import {
AlignCenter,
AlignLeft,
AlignRight,
Bold,
Italic,
Lock,
Underline,
} from "lucide-react";
import { Button } from "../ui/Button";
import {
Select,
SelectContent,
@ -15,70 +5,29 @@ import {
SelectTrigger,
SelectValue,
} from "../ui/Select";
import TextCustomization from "../EachComponent/Customization/TextCustomization";
import LockObject from "../EachComponent/Customization/LockObject";
import { ScrollArea, ScrollBar } from "../ui/scroll-area";
import OpacityCustomization from "../EachComponent/Customization/OpacityCustomization";
import CanvasContext from "../Context/canvasContext/CanvasContext";
import { useContext } from "react";
export function TopBar({ isVisible = false }) {
if (!isVisible) return null;
export function TopBar() {
const { selectedPanel } = useContext(CanvasContext);
return (
<div className="absolute top-4 left-[40%] -translate-x-1/2 z-40">
<div>
<ScrollArea
className={`absolute top-2 lg:left-[20%] ${
selectedPanel !== ""
? "lg:w-[600px] xl:w-[820px] xl:left-[40%]"
: "w-[70%] xl:left-[50%]"
} -translate-x-1/2 z-40 scrollbar-hide`}
>
<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>
<TextCustomization />
</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>
<OpacityCustomization />
<div className="h-4 w-px bg-border mx-2" />
<Select defaultValue="position">
@ -92,10 +41,10 @@ export function TopBar({ isVisible = false }) {
</SelectContent>
</Select>
<Button variant="ghost" size="icon" className="h-9 w-9">
<Lock className="h-4 w-4" />
</Button>
<LockObject />
</div>
<ScrollBar orientation="horizontal" />
</ScrollArea>
</div>
);
}

View file

@ -1,13 +1,12 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
import CanvasContextProvider from './components/Context/canvasContext/CanvasContextProvider'
import ColorContextProvider from './components/Context/colorContext/ColorContextProvider'
import { ObjectProvider } from './components/Context/activeObject/ObjectProvider'
import OpenContextProvider from './components/Context/openContext/OpenContextProvider'
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.jsx";
import CanvasContextProvider from "./components/Context/canvasContext/CanvasContextProvider";
import ColorContextProvider from "./components/Context/colorContext/ColorContextProvider";
import ObjectProvider from "./components/Context/activeObject/ObjectProvider";
import OpenContextProvider from "./components/Context/openContext/OpenContextProvider";
createRoot(document.getElementById('root')).render(
createRoot(document.getElementById("root")).render(
// <StrictMode>
<CanvasContextProvider>
<ColorContextProvider>
@ -19,4 +18,4 @@ createRoot(document.getElementById('root')).render(
</ColorContextProvider>
</CanvasContextProvider>
// </StrictMode>,
)
);