| // src/components/data-hub/ProtocolStepCard.tsx | |
| import { useState } from "react"; | |
| import type { ProtocolStep } from "@/types/data-hub"; | |
| import { Check, Clock, Lightbulb, ChevronDown, ChevronUp, Edit, X, Save } from "lucide-react"; | |
| interface ProtocolStepCardProps { | |
| step: ProtocolStep; | |
| index: number; | |
| runMode: boolean; | |
| editMode: boolean; | |
| onToggleComplete: () => void; | |
| onUpdate: (data: Partial<ProtocolStep>) => void; | |
| } | |
| export function ProtocolStepCard({ step, index, runMode, editMode, onToggleComplete, onUpdate }: ProtocolStepCardProps) { | |
| const [expanded, setExpanded] = useState(false); | |
| const [editingField, setEditingField] = useState<string | null>(null); | |
| const [tempValue, setTempValue] = useState(""); | |
| return ( | |
| <div className={`rounded-xl border p-4 transition ${ | |
| step.completed ? "border-green-300 dark:border-green-700 bg-green-50/20 dark:bg-green-900/10" : "border-white/20 dark:border-white/10" | |
| }`}> | |
| <div className="flex items-start gap-3"> | |
| {/* Checkbox (always visible in run mode) */} | |
| <button onClick={onToggleComplete} | |
| className={`w-6 h-6 rounded-full border-2 flex items-center justify-center shrink-0 mt-0.5 transition ${ | |
| step.completed ? "bg-green-500 border-green-500 text-white" : "border-gray-300 dark:border-gray-600" | |
| } ${runMode ? "cursor-pointer hover:border-green-400" : "cursor-default"}`}> | |
| {step.completed && <Check className="w-4 h-4"/>} | |
| </button> | |
| <div className="flex-1"> | |
| <div className="flex items-center gap-2"> | |
| <span className="text-xs font-mono text-gray-400">{index + 1}.</span> | |
| <h4 className="font-semibold text-sm">{step.title}</h4> | |
| {step.duration && <span className="flex items-center gap-1 text-[10px] text-gray-400"><Clock className="w-3 h-3"/>{step.duration}</span>} | |
| </div> | |
| <p className="text-sm text-gray-600 dark:text-gray-400 mt-1">{step.description}</p> | |
| {step.tips && ( | |
| <button onClick={() => setExpanded(!expanded)} className="text-xs text-amber-600 flex items-center gap-1 mt-1"> | |
| <Lightbulb className="w-3 h-3"/> Tips {expanded ? <ChevronUp className="w-3 h-3"/> : <ChevronDown className="w-3 h-3"/>} | |
| </button> | |
| )} | |
| {expanded && step.tips && <p className="text-xs text-gray-500 mt-1 bg-amber-50/30 dark:bg-amber-900/10 p-2 rounded">{step.tips}</p>} | |
| </div> | |
| {editMode && ( | |
| <div className="flex gap-1"> | |
| <button onClick={() => { setEditingField("title"); setTempValue(step.title); }} className="p-1 text-gray-400 hover:text-primary"><Edit className="w-3.5 h-3.5"/></button> | |
| </div> | |
| )} | |
| </div> | |
| {/* Inline edit (shown only for title here; can be extended) */} | |
| {editMode && editingField && ( | |
| <div className="flex gap-2 mt-2 ml-9"> | |
| <input type="text" value={tempValue} onChange={e => setTempValue(e.target.value)} className="flex-1 text-sm px-3 py-1 bg-white/10 rounded" autoFocus /> | |
| <button onClick={() => { onUpdate({ [editingField]: tempValue }); setEditingField(null); }} className="btn btn-primary btn-xs"><Save className="w-3 h-3"/></button> | |
| <button onClick={() => setEditingField(null)} className="btn btn-ghost btn-xs"><X className="w-3 h-3"/></button> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } |
Xet Storage Details
- Size:
- 3.4 kB
- Xet hash:
- 10ef4edf032a9b278186fe6ab792b1c4417bfdee4354bbfbda5ea1b03cb3662f
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.