返回 presentation-ai
editable-header.tsx
1 import { X } from "lucide-react";
2 import type React from "react";
3 import { useEffect, useState } from "react";
4
5 import { ChartTypePicker } from "./chart-type-picker";
6 import { type SeriesChartType } from "./types";
7
8 interface EditableHeaderProps {
9 value: string;
10 onRename: (newName: string) => void;
11 onRemove: () => void;
12 canRemove: boolean;
13 colorIndex?: number;
14 chartType?: SeriesChartType;
15 onChartTypeChange?: (type: SeriesChartType) => void;
16 showChartTypePicker?: boolean;
17 }
18
19 export function EditableHeader({
20 value,
21 onRename,
22 onRemove,
23 canRemove,
24 colorIndex = 0,
25 chartType = "bar",
26 onChartTypeChange,
27 showChartTypePicker = false,
28 }: EditableHeaderProps) {
29 const [localValue, setLocalValue] = useState(value);
30 const [isEditing, setIsEditing] = useState(false);
31
32 useEffect(() => {
33 setLocalValue(value);
34 }, [value]);
35
36 const commitHeaderRename = () => {
37 setIsEditing(false);
38 if (localValue.trim() && localValue !== value) {
39 onRename(localValue);
40 } else if (!localValue.trim()) {
41 setLocalValue(value);
42 }
43 };
44
45 const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
46 if (e.key === "Enter") {
47 e.currentTarget.blur();
48 }
49 if (e.key === "Escape") {
50 setLocalValue(value);
51 e.currentTarget.blur();
52 }
53 };
54
55 return (
56 <div className="group flex items-center gap-1">
57 <div
58 className="h-4 w-2 shrink-0 rounded-sm"
59 style={{ backgroundColor: `hsl(var(--chart-${(colorIndex % 5) + 1}))` }}
60 />
61 {showChartTypePicker && onChartTypeChange && (
62 <ChartTypePicker value={chartType} onChange={onChartTypeChange} />
63 )}
64 <input
65 aria-label="editable header control"
66 value={localValue}
67 onChange={(e) => setLocalValue(e.target.value)}
68 onBlur={commitHeaderRename}
69 onFocus={() => setIsEditing(true)}
70 onKeyDown={handleKeyDown}
71 className={`h-6 min-w-0 flex-1 border-0 bg-transparent px-1 text-xs font-semibold outline-none ${isEditing ? "bg-background ring ring-primary" : ""} truncate focus:bg-background focus:ring focus:ring-primary`}
72 placeholder="Series"
73 />
74 {canRemove && (
75 <button
76 type="button"
77 onClick={onRemove}
78 className="p-0.5 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100 hover:text-destructive"
79 >
80 <X className="size-3" />
81 </button>
82 )}
83 </div>
84 );
85 }
86
86 lines Plain Text