can edit text + fix font weight - remove character space
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { type FC, useRef, useState, useEffect } from "react";
|
||||
import { type FC, useState, useEffect, useMemo } from "react";
|
||||
import Input from "@/components/Input";
|
||||
import Select from "@/components/Select";
|
||||
import ColorPicker from "@/components/ColorPicker";
|
||||
import Button from "@/components/Button";
|
||||
import { useEditorStore } from "@/pages/editor/store/editorStore";
|
||||
import { useTextDefaultsStore } from "@/pages/editor/store/textDefaultsStore";
|
||||
|
||||
@@ -15,6 +17,17 @@ const fontWeightOptions = [
|
||||
{ label: "Light", value: "300" },
|
||||
];
|
||||
|
||||
type TextFormState = {
|
||||
text: string;
|
||||
fontFamily: string;
|
||||
fontWeight: string;
|
||||
fontSize: number;
|
||||
fill: string;
|
||||
opacity: number;
|
||||
letterSpacing: number;
|
||||
wordSpacing: number;
|
||||
};
|
||||
|
||||
const TextInstruction: FC = () => {
|
||||
const { selectedObjectId, objects, updateObject } = useEditorStore();
|
||||
const { defaults, updateDefaults } = useTextDefaultsStore();
|
||||
@@ -24,39 +37,63 @@ const TextInstruction: FC = () => {
|
||||
: 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) => {
|
||||
const baseValues = useMemo(() => {
|
||||
if (isEditing && selectedObject) {
|
||||
updateObject(selectedObject.id, { [field]: value });
|
||||
return {
|
||||
text: selectedObject.text || "",
|
||||
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,
|
||||
};
|
||||
}
|
||||
return { text: "", ...defaults };
|
||||
}, [isEditing, selectedObject, defaults]);
|
||||
|
||||
const [formState, setFormState] = useState<TextFormState>(baseValues);
|
||||
|
||||
useEffect(() => {
|
||||
setFormState(baseValues);
|
||||
}, [baseValues]);
|
||||
|
||||
const handleChange = (field: keyof TextFormState, value: string | number) => {
|
||||
if (isEditing && selectedObject) {
|
||||
setFormState((prev) => ({ ...prev, [field]: value }));
|
||||
} else {
|
||||
updateDefaults({ [field]: value });
|
||||
// text فقط برای حالت ویرایش است و نباید در defaults ذخیره شود
|
||||
if (field !== "text") {
|
||||
updateDefaults({ [field]: value });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const hexColor = (currentValues.fill || "#000000").replace("#", "").toUpperCase();
|
||||
const opacityValue = currentValues.opacity;
|
||||
const colorInputRef = useRef<HTMLInputElement>(null);
|
||||
const handleSave = () => {
|
||||
if (isEditing && selectedObject) {
|
||||
updateObject(selectedObject.id, formState);
|
||||
}
|
||||
};
|
||||
|
||||
const isDirty = isEditing && selectedObject
|
||||
? formState.text !== baseValues.text ||
|
||||
formState.fontFamily !== baseValues.fontFamily ||
|
||||
formState.fontWeight !== baseValues.fontWeight ||
|
||||
formState.fontSize !== baseValues.fontSize ||
|
||||
formState.fill !== baseValues.fill ||
|
||||
formState.opacity !== baseValues.opacity ||
|
||||
formState.letterSpacing !== baseValues.letterSpacing ||
|
||||
formState.wordSpacing !== baseValues.wordSpacing
|
||||
: false;
|
||||
|
||||
const opacityValue = formState.opacity;
|
||||
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);
|
||||
@@ -66,20 +103,31 @@ const TextInstruction: FC = () => {
|
||||
const numValue = parseInt(opacityInput) || 0;
|
||||
const clampedValue = Math.min(100, Math.max(0, numValue));
|
||||
setOpacityInput(`${clampedValue}`);
|
||||
if (clampedValue !== opacityValue) {
|
||||
handleChange("opacity", clampedValue);
|
||||
}
|
||||
handleChange("opacity", clampedValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="font-bold">متن</h2>
|
||||
<div className="mt-8">
|
||||
|
||||
{isEditing && (
|
||||
<div className="mt-4">
|
||||
<label className="text-sm mb-1 block">متن</label>
|
||||
<textarea
|
||||
className="w-full min-h-[80px] rounded-xl border border-border px-3 py-2 text-sm outline-none focus:border-primary resize-y"
|
||||
value={formState.text}
|
||||
onChange={(e) => handleChange("text", e.target.value)}
|
||||
placeholder="متن خود را وارد کنید..."
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-4">
|
||||
<Select
|
||||
items={fontOptions}
|
||||
placeholder="انتخاب فونت"
|
||||
label="فونت"
|
||||
value={currentValues.fontFamily}
|
||||
value={formState.fontFamily}
|
||||
onChange={(e) => handleChange("fontFamily", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
@@ -88,12 +136,12 @@ const TextInstruction: FC = () => {
|
||||
<Select
|
||||
label="وزن"
|
||||
items={fontWeightOptions}
|
||||
value={currentValues.fontWeight}
|
||||
value={formState.fontWeight}
|
||||
onChange={(e) => handleChange("fontWeight", e.target.value)}
|
||||
/>
|
||||
|
||||
<Input
|
||||
value={`${currentValues.fontSize}px`}
|
||||
value={`${formState.fontSize}px`}
|
||||
label="سایز"
|
||||
onChange={(e) => {
|
||||
const value = e.target.value.replace("px", "").trim();
|
||||
@@ -103,48 +151,38 @@ const TextInstruction: FC = () => {
|
||||
/>
|
||||
</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="mt-4 flex flex-col gap-2">
|
||||
<div className="flex gap-3">
|
||||
<ColorPicker
|
||||
label="رنگ"
|
||||
className="flex-1"
|
||||
value={formState.fill}
|
||||
onChange={(value) => handleChange("fill", value)}
|
||||
/>
|
||||
<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 className="w-28">
|
||||
<label className="text-sm">شفافیت</label>
|
||||
<div className="mt-1 flex h-10 items-center rounded-xl border border-border px-2 text-sm">
|
||||
<input
|
||||
type="text"
|
||||
className="w-full border-none bg-transparent text-center text-sm outline-none"
|
||||
value={`${opacityInput}%`}
|
||||
onChange={handleOpacityChange}
|
||||
onBlur={handleOpacityBlur}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.currentTarget.blur();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex gap-2">
|
||||
{/* <div className="mt-4 flex gap-2">
|
||||
<Input
|
||||
label="فاصله کلمات"
|
||||
value={`${currentValues.wordSpacing}px`}
|
||||
value={`${formState.wordSpacing}px`}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value.replace("px", "").trim();
|
||||
const numValue = parseInt(value) || 0;
|
||||
@@ -154,14 +192,22 @@ const TextInstruction: FC = () => {
|
||||
|
||||
<Input
|
||||
label="فاصله حروف"
|
||||
value={`${currentValues.letterSpacing}px`}
|
||||
value={`${formState.letterSpacing}px`}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value.replace("px", "").trim();
|
||||
const numValue = parseInt(value) || 0;
|
||||
handleChange("letterSpacing", numValue);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
{isEditing && (
|
||||
<div className="mt-4">
|
||||
<Button type="button" onClick={handleSave} disabled={!isDirty} className="w-full">
|
||||
ذخیره تغییرات
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user