Your Name a1b6496976
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat: upgrade detail section component renderer
2026-03-21 23:35:48 +08:00

131 lines
4.0 KiB
TypeScript

"use client";
import React, { useState, useRef, useEffect } from "react";
import { useCanvasStore } from "@/store/canvas";
import type { CanvasNode, TextNodeData } from "@/types/canvas";
import { cn } from "@/lib/utils";
interface TextNodeProps {
node: CanvasNode;
isSelected: boolean;
}
export function TextNode({ node, isSelected }: TextNodeProps) {
const data = node.data as TextNodeData;
const [isEditing, setIsEditing] = useState(false);
const [editContent, setEditContent] = useState(data.content);
const textareaRef = useRef<HTMLTextAreaElement>(null);
const updateNode = useCanvasStore((state) => state.updateNode);
// 进入编辑模式时聚焦
useEffect(() => {
if (isEditing && textareaRef.current) {
textareaRef.current.focus();
textareaRef.current.select();
}
}, [isEditing]);
// 保存编辑
const handleSave = () => {
updateNode(node.id, {
data: { ...data, content: editContent },
});
setIsEditing(false);
};
// 取消编辑
const handleCancel = () => {
setEditContent(data.content);
setIsEditing(false);
};
// 双击进入编辑
const handleDoubleClick = (e: React.MouseEvent) => {
e.stopPropagation();
setIsEditing(true);
};
const containerStyle = data.transparent
? undefined
: {
backgroundColor: data.backgroundColor || undefined,
borderColor: data.borderColor || undefined,
borderWidth:
typeof data.borderWidth === "number" ? `${data.borderWidth}px` : undefined,
borderStyle:
typeof data.borderWidth === "number" && data.borderWidth > 0 ? "solid" : undefined,
borderRadius:
typeof data.borderRadius === "number" ? `${data.borderRadius}px` : undefined,
padding: typeof data.padding === "number" ? `${data.padding}px` : undefined,
boxShadow: data.boxShadow || undefined,
backdropFilter:
typeof data.backdropBlur === "number" && data.backdropBlur > 0
? `blur(${data.backdropBlur}px)`
: undefined,
WebkitBackdropFilter:
typeof data.backdropBlur === "number" && data.backdropBlur > 0
? `blur(${data.backdropBlur}px)`
: undefined,
};
const textStyle = {
fontSize: data.fontSize || 14,
fontFamily: data.fontFamily || undefined,
color: data.color || "inherit",
fontWeight: data.fontWeight || "normal",
lineHeight: data.lineHeight || 1.35,
letterSpacing:
typeof data.letterSpacing === "number" ? `${data.letterSpacing}px` : undefined,
textAlign: data.textAlign || "left",
textTransform: data.textTransform || "none",
} as const;
return (
<div
className={cn(
"relative w-full h-full overflow-hidden p-3",
data.transparent
? "rounded-none border-0 bg-transparent p-0 shadow-none"
: "rounded-lg border bg-card shadow-sm",
isSelected && (data.transparent ? "ring-1 ring-primary/50" : "border-primary")
)}
onDoubleClick={handleDoubleClick}
style={containerStyle}
>
{isEditing ? (
<textarea
ref={textareaRef}
value={editContent}
onChange={(e) => setEditContent(e.target.value)}
onBlur={handleSave}
onKeyDown={(e) => {
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
handleSave();
}
if (e.key === 'Escape') {
handleCancel();
}
}}
className="w-full h-full resize-none bg-transparent outline-none text-sm"
style={textStyle}
/>
) : (
<div
className="w-full h-full overflow-auto text-sm whitespace-pre-wrap"
style={textStyle}
>
{data.content || '双击编辑文本'}
</div>
)}
{/* 编辑提示 */}
{!isEditing && isSelected && (
<div className="absolute bottom-1 right-1 text-[10px] text-muted-foreground">
双击编辑
</div>
)}
</div>
);
}