canvas-backend/src/components/EachComponent/Customization/TextCustomization.jsx
2025-02-11 17:55:34 +06:00

463 lines
14 KiB
JavaScript

import ActiveObjectContext from "@/components/Context/activeObject/ObjectContext";
import CanvasContext from "@/components/Context/canvasContext/CanvasContext";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { useCallback, useContext, useEffect, useRef, useState } from "react";
import {
AlignLeft,
AlignCenter,
AlignRight,
Bold,
Italic,
Underline,
Strikethrough,
Minus,
Plus,
} from "lucide-react";
import { RiLineHeight } from "react-icons/ri";
import { Slider } from "@/components/ui/slider";
import { Tooltip } from "react-tooltip";
const fonts = [
"Roboto",
"Open Sans",
"Lato",
"Montserrat",
"Raleway",
"Poppins",
"Merriweather",
"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 { activeObject } = useContext(ActiveObjectContext);
const { canvas, setSelectedPanel, textColor } = useContext(CanvasContext);
const activeObjectType = activeObject?.type;
const hasClipPath = !!activeObject?.clipPath;
const customClipPath = activeObject?.isClipPath;
const prevTextRef = useRef("");
const [text, setText] = useState("");
const [fontFamily, setFontFamily] = useState("Arial");
const [fontSize, setFontSize] = useState(20);
const [fontStyle, setFontStyle] = useState("normal");
const [fontWeight, setFontWeight] = useState("normal");
const [lineHeight, setLineHeight] = useState(1.16);
const [charSpacing, setCharSpacing] = useState(0);
const [underline, setUnderline] = useState(false);
const [linethrough, setLinethrough] = useState(false);
const [textAlign, setTextAlign] = useState("left");
const [fillColor, setFillColor] = useState("black");
useEffect(() => {
if (activeObject?.type === "i-text") {
if (
activeObject?.text !== undefined &&
activeObject.text !== prevTextRef.current
) {
setText(activeObject.text);
prevTextRef.current = activeObject.text;
}
setText(activeObject?.text || "");
setFontFamily(activeObject?.fontFamily || "Arial");
setFontSize(activeObject?.fontSize || 20);
setFontStyle(activeObject?.fontStyle || "normal");
setFontWeight(activeObject?.fontWeight || "normal");
setLineHeight(activeObject?.lineHeight || 1.16);
setCharSpacing(activeObject?.charSpacing || 0);
setUnderline(activeObject?.underline || false);
setLinethrough(activeObject?.linethrough || false);
setTextAlign(activeObject?.textAlign || "left");
setFillColor(textColor?.fill || "black");
}
}, [activeObject, textColor]);
const updateActiveObject = useCallback(
(properties) => {
if (activeObject?.type === "i-text") {
// Preserve the text value when updating other properties
const updatedProperties = {
...properties,
text: text || prevTextRef.current || properties.text,
};
activeObject.set(updatedProperties);
canvas?.renderAll();
}
},
[activeObject, canvas, text]
); // Add dependencies
const applyChanges = useCallback(() => {
updateActiveObject({
text,
fontFamily,
fontSize,
fontStyle,
fontWeight,
lineHeight,
charSpacing,
underline,
linethrough,
textAlign,
});
}, [
text,
fontFamily,
fontSize,
fontStyle,
fontWeight,
lineHeight,
charSpacing,
underline,
linethrough,
textAlign,
updateActiveObject, // Add this dependency
]);
const handleColorPanelClick = () => {
// Store current text value before switching panels
prevTextRef.current = text;
setSelectedPanel("color");
};
// 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);
};
const handleFontSizeChange = (newFontSize) => {
setFontSize(newFontSize);
};
const handleTextAlignChange = (newTextAlign) => {
setTextAlign(newTextAlign);
};
const handleFontStyleChange = () => {
setFontStyle(fontStyle === "normal" ? "italic" : "normal");
};
const handleFontWeightChange = () => {
setFontWeight(fontWeight === "normal" ? "bold" : "normal");
};
const handleLineHeightChange = (newLineHeight) => {
setLineHeight(parseFloat(newLineHeight));
};
const handleCharSpacingChange = (newCharSpacing) => {
setCharSpacing(parseInt(newCharSpacing));
};
const handleUnderlineChange = () => {
setUnderline(!underline);
};
const handleLinethroughChange = () => {
setLinethrough(!linethrough);
};
const content = () => {
if (!(activeObject?.type === "i-text")) {
return <div></div>;
}
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 */}
<a data-tooltip-id="fonts">
<Select
value={fontFamily}
onValueChange={handleFontFamilyChange}
title="Font Family"
>
<SelectTrigger className="min-w-[140px] h-8">
<SelectValue placeholder="Select a font" />
</SelectTrigger>
<SelectContent className="h-[250px] z-[9999]">
<SelectGroup>
{fonts.map((font) => (
<SelectItem
key={font}
value={font}
style={{ fontFamily: font }}
>
{font}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</a>
<Tooltip id="fonts" content="Font Family" place="bottom" />
{/* Font Size Controls */}
<div className="flex items-center border rounded-md">
<a data-tooltip-id="font-dec">
<Button
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>
</a>
<Tooltip
id="font-dec"
content="Decrease font size"
place="bottom"
/>
<a data-tooltip-id="font-size">
<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"
/>
</a>
<Tooltip id="font-size" content="Font size" place="bottom" />
<a data-tooltip-id="font-inc">
<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>
</a>
<Tooltip
id="font-inc"
content="Increase font size"
place="bottom"
/>
</div>
{/* Vertical Separator */}
<div className="h-6 w-px bg-gray-200" />
{/* Text Formatting Controls */}
<div className="flex items-center space-x-1">
{activeObjectType !== "image" &&
!hasClipPath &&
!customClipPath && (
<a data-tooltip-id="text-color">
<Button
variant="ghost"
size="icon"
className="relative"
onClick={handleColorPanelClick} // Updated onClick handler
>
<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>
</a>
)}
<Tooltip id="text-color" content="Text color" place="bottom" />
<a data-tooltip-id="text-left">
<Button
variant={textAlign === "left" ? "secondary" : "ghost"}
size="icon"
onClick={() => handleTextAlignChange("left")}
>
<AlignLeft className="h-4 w-4" />
</Button>
</a>
<a data-tooltip-id="text-center">
<Button
variant={textAlign === "center" ? "secondary" : "ghost"}
size="icon"
onClick={() => handleTextAlignChange("center")}
>
<AlignCenter className="h-4 w-4" />
</Button>
</a>
<a data-tooltip-id="text-right">
<Button
variant={textAlign === "right" ? "secondary" : "ghost"}
size="icon"
onClick={() => handleTextAlignChange("right")}
>
<AlignRight className="h-4 w-4" />
</Button>
</a>
<a data-tooltip-id="text-bold">
<Button
variant={fontWeight === "bold" ? "secondary" : "ghost"}
size="icon"
className="h-8 w-8"
onClick={handleFontWeightChange}
>
<Bold className="h-4 w-4" />
</Button>
</a>
<a data-tooltip-id="text-underline">
<Button
variant={underline ? "secondary" : "ghost"}
size="icon"
className="h-8 w-8"
onClick={handleUnderlineChange}
>
<Underline className="h-4 w-4" />
</Button>
</a>
<a data-tooltip-id="text-italic">
<Button
variant={fontStyle === "italic" ? "secondary" : "ghost"}
size="icon"
className="h-8 w-8"
onClick={handleFontStyleChange}
>
<Italic className="h-4 w-4" />
</Button>
</a>
<a data-tooltip-id="line-through">
<Button
variant={linethrough ? "secondary" : "ghost"}
size="icon"
className="h-8 w-8"
onClick={handleLinethroughChange}
>
<Strikethrough className="h-4 w-4" />
</Button>
</a>
<Tooltip id="text-left" content="Left align" place="bottom" />
<Tooltip id="text-center" content="Center align" place="bottom" />
<Tooltip id="text-right" content="Right align" place="bottom" />
<Tooltip id="text-bold" content="Bold" place="bottom" />
<Tooltip id="text-underline" content="Underline" place="bottom" />
<Tooltip id="text-italic" content="Italics" place="bottom" />
<Tooltip id="line-through" content="StrikeThrough" place="bottom" />
</div>
{/* Vertical Separator */}
<div className="h-6 w-px bg-gray-200" />
{/* Spacing Controls */}
<Popover>
<PopoverTrigger asChild>
<a data-tooltip-id="spacing">
<Button variant="ghost" size="icon" className="h-8 w-8">
<RiLineHeight className="h-4 w-4" />
</Button>
</a>
</PopoverTrigger>
<PopoverContent className="w-44 mt-3">
<div className="space-y-4">
<div className="space-y-2">
<Label>Line Spacing</Label>
<div className="flex items-center space-x-2">
<Slider
value={[lineHeight]}
onValueChange={([value]) => handleLineHeightChange(value)}
min={0.5}
max={3}
step={0.1}
/>
</div>
</div>
<div className="space-y-2">
<Label>Letter Spacing</Label>
<div className="flex items-center space-x-2">
<Slider
value={[charSpacing]}
onValueChange={([value]) =>
handleCharSpacingChange(value)
}
min={-20}
max={100}
step={1}
/>
</div>
</div>
</div>
</PopoverContent>
</Popover>
<Tooltip id="spacing" content="Spacing" place="bottom" />
</div>
</div>
);
};
return <div className="">{content()}</div>;
};
export default TextCustomization;