text component

This commit is contained in:
hamid zarghami
2025-11-20 08:35:50 +03:30
parent 3b47e29ba4
commit 3454129dca
7 changed files with 321 additions and 13 deletions
@@ -1,7 +1,6 @@
import { Trash } from "iconsax-react";
import type { EditorObject } from "../../store/editorStore";
import {
TextSettings,
ShapeSettings,
LineSettings,
SizeSettings,
@@ -10,6 +9,7 @@ import {
TransformSettings,
GridSettings,
} from "./settings";
import TextInstruction from "./instructions/TextInstruction";
type ObjectSettingsProps = {
selectedObject: EditorObject;
@@ -30,7 +30,7 @@ const ObjectSettings = ({ selectedObject, onUpdate, onDelete }: ObjectSettingsPr
</button>
</div>
{selectedObject.type === "text" && <TextSettings selectedObject={selectedObject} onUpdate={onUpdate} />}
{selectedObject.type === "text" && <TextInstruction />}
{(selectedObject.type === "rectangle" || selectedObject.type === "circle") && (
<ShapeSettings selectedObject={selectedObject} onUpdate={onUpdate} />
@@ -1,8 +1,170 @@
const TextInstruction = () => (
<div className="text-sm text-gray-600">
<p>برای افزودن متن، روی کانوس کلیک کنید</p>
</div>
);
import { type FC, useRef, useState, useEffect } from "react";
import Input from "@/components/Input";
import Select from "@/components/Select";
import { useEditorStore } from "@/pages/editor/store/editorStore";
import { useTextDefaultsStore } from "@/pages/editor/store/textDefaultsStore";
const fontOptions = [
{ label: "ایرانسل", value: "1" },
{ label: "فونت 2", value: "2" },
];
const fontWeightOptions = [
{ label: "Bold", value: "bold" },
{ label: "Normal", value: "normal" },
{ label: "Light", value: "300" },
];
const TextInstruction: FC = () => {
const { selectedObjectId, objects, updateObject } = useEditorStore();
const { defaults, updateDefaults } = useTextDefaultsStore();
const selectedObject = selectedObjectId
? objects.find((obj) => obj.id === selectedObjectId && obj.type === "text")
: null;
const isEditing = !!selectedObject;
const currentValues = isEditing
? {
fontFamily: selectedObject.fontFamily || defaults.fontFamily,
fontWeight: selectedObject.fontWeight || defaults.fontWeight,
fontSize: selectedObject.fontSize || defaults.fontSize,
fill: selectedObject.fill || defaults.fill,
opacity: selectedObject.opacity ?? defaults.opacity,
letterSpacing: selectedObject.letterSpacing ?? defaults.letterSpacing,
wordSpacing: selectedObject.wordSpacing ?? defaults.wordSpacing,
}
: defaults;
const handleChange = (field: string, value: string | number) => {
if (isEditing && selectedObject) {
updateObject(selectedObject.id, { [field]: value });
} else {
updateDefaults({ [field]: value });
}
};
const hexColor = (currentValues.fill || "#000000").replace("#", "").toUpperCase();
const opacityValue = currentValues.opacity;
const colorInputRef = useRef<HTMLInputElement>(null);
const [opacityInput, setOpacityInput] = useState<string>(`${opacityValue}`);
useEffect(() => {
setOpacityInput(`${opacityValue}`);
}, [opacityValue]);
const handleColorClick = () => {
colorInputRef.current?.click();
};
const handleOpacityChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value.replace("%", "").trim();
setOpacityInput(value);
};
const handleOpacityBlur = () => {
const numValue = parseInt(opacityInput) || 0;
const clampedValue = Math.min(100, Math.max(0, numValue));
setOpacityInput(`${clampedValue}`);
if (clampedValue !== opacityValue) {
handleChange("opacity", clampedValue);
}
};
return (
<div>
<h2 className="font-bold">متن</h2>
<div className="mt-8">
<Select
items={fontOptions}
placeholder="انتخاب فونت"
label="فونت"
value={currentValues.fontFamily}
onChange={(e) => handleChange("fontFamily", e.target.value)}
/>
</div>
<div className="flex gap-2 mt-4">
<Select
label="وزن"
items={fontWeightOptions}
value={currentValues.fontWeight}
onChange={(e) => handleChange("fontWeight", e.target.value)}
/>
<Input
value={`${currentValues.fontSize}px`}
label="سایز"
onChange={(e) => {
const value = e.target.value.replace("px", "").trim();
const numValue = parseInt(value) || 0;
handleChange("fontSize", numValue);
}}
/>
</div>
<div className="mt-4">
<div className="text-sm">رنگ</div>
<div className="mt-2 border border-border rounded-xl p-2 flex items-center">
<button
type="button"
onClick={handleColorClick}
className="flex-1 flex items-center gap-1.5 cursor-pointer hover:opacity-80 transition-opacity"
>
<div
className="size-6 rounded"
style={{ backgroundColor: currentValues.fill }}
/>
<div className="text-sm">{hexColor}</div>
</button>
<input
ref={colorInputRef}
type="color"
value={currentValues.fill}
onChange={(e) => handleChange("fill", e.target.value)}
className="hidden"
/>
<div className="text-sm flex px-2 mr-3 border-r border-border">
<input
type="text"
className="text-center w-14 border-none outline-none bg-transparent text-sm"
value={`${opacityInput}%`}
onChange={handleOpacityChange}
onBlur={handleOpacityBlur}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.currentTarget.blur();
}
}}
/>
</div>
</div>
</div>
<div className="mt-4 flex gap-2">
<Input
label="فاصله کلمات"
value={`${currentValues.wordSpacing}px`}
onChange={(e) => {
const value = e.target.value.replace("px", "").trim();
const numValue = parseInt(value) || 0;
handleChange("wordSpacing", numValue);
}}
/>
<Input
label="فاصله حروف"
value={`${currentValues.letterSpacing}px`}
onChange={(e) => {
const value = e.target.value.replace("px", "").trim();
const numValue = parseInt(value) || 0;
handleChange("letterSpacing", numValue);
}}
/>
</div>
</div>
);
};
export default TextInstruction;