welcome-sylvi/components/GitWorkflowDiagram.tsx
2026-01-14 14:59:00 -07:00

184 lines
8.2 KiB
TypeScript

import React, { useState, useRef, useEffect } from 'react';
import gsap from 'gsap';
import { FileText, ArrowRight, Database, Layers, HardDrive, RefreshCw } from 'lucide-react';
export const GitWorkflowDiagram: React.FC = () => {
const [state, setState] = useState<'modified' | 'staged' | 'committed'>('modified');
// Refs for animation targets
const fileRef = useRef<HTMLDivElement>(null);
const workingDirRef = useRef<HTMLDivElement>(null);
const stagingRef = useRef<HTMLDivElement>(null);
const repoRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
// Initial entrance animation
gsap.fromTo(containerRef.current,
{ opacity: 0, scale: 0.95 },
{ opacity: 1, scale: 1, duration: 0.8, ease: "power2.out" }
);
}, []);
useEffect(() => {
const ctx = gsap.context(() => {
// Reset file position when state changes, then animate to new spot
// Note: In a real complex app we might calculate exact coordinates using getBoundingClientRect
// For this demo, we'll rely on the React re-render to place it in the DOM, then animate "from" the previous position if possible,
// or just animate "in" to emphasize the move.
// Simple scale/pop effect on change
if (fileRef.current) {
gsap.fromTo(fileRef.current,
{ scale: 0.5, opacity: 0, y: 20 },
{ scale: 1, opacity: 1, y: 0, duration: 0.5, ease: "back.out(1.7)" }
);
}
}, containerRef);
return () => ctx.revert();
}, [state]);
const handleReset = () => {
setState('modified');
};
return (
<div ref={containerRef} className="my-10 p-4 sm:p-6 bg-card rounded-xl border border-border shadow-sm overflow-hidden">
<div className="flex justify-between items-center mb-6 border-b border-border pb-4">
<h3 className="font-bold text-lg text-primary">Interactive Workflow</h3>
<button
onClick={handleReset}
className="flex items-center gap-2 text-xs text-muted-foreground hover:text-primary transition-colors"
>
<RefreshCw size={14} />
Reset
</button>
</div>
{/* Zones Container */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 relative min-h-[300px]">
{/* Working Directory Zone */}
<div ref={workingDirRef} className={`relative p-4 rounded-lg border-2 transition-colors duration-300 ${state === 'modified' ? 'border-primary bg-primary/5' : 'border-dashed border-muted'}`}>
<div className="flex items-center gap-2 mb-4 text-sm font-semibold text-foreground/80">
<HardDrive size={18} />
<span>Working Directory</span>
</div>
<p className="text-xs text-muted-foreground mb-8">Where you edit files.</p>
{state === 'modified' && (
<div ref={fileRef} className="bg-white p-3 rounded shadow-md border border-gray-200 flex items-center gap-3">
<FileText className="text-orange-500" />
<div>
<div className="text-sm font-bold">script.js</div>
<div className="text-xs text-orange-600">Modified</div>
</div>
</div>
)}
<div className="absolute bottom-4 left-0 w-full flex justify-center">
<button
disabled={state !== 'modified'}
onClick={() => setState('staged')}
className={`
px-4 py-2 rounded-full text-xs font-bold transition-all
${state === 'modified'
? 'bg-primary text-primary-foreground hover:bg-primary/90 hover:scale-105 shadow-md cursor-pointer'
: 'bg-muted text-muted-foreground cursor-not-allowed opacity-50'
}
`}
>
git add
</button>
</div>
</div>
{/* Staging Area Zone */}
<div ref={stagingRef} className={`relative p-4 rounded-lg border-2 transition-colors duration-300 ${state === 'staged' ? 'border-primary bg-primary/5' : 'border-dashed border-muted'}`}>
<div className="flex items-center gap-2 mb-4 text-sm font-semibold text-foreground/80">
<Layers size={18} />
<span>Staging Area</span>
</div>
<p className="text-xs text-muted-foreground mb-8">Files ready to commit.</p>
{state === 'staged' && (
<div ref={fileRef} className="bg-white p-3 rounded shadow-md border border-gray-200 flex items-center gap-3">
<FileText className="text-green-500" />
<div>
<div className="text-sm font-bold">script.js</div>
<div className="text-xs text-green-600">Staged</div>
</div>
</div>
)}
<div className="absolute bottom-4 left-0 w-full flex justify-center">
<button
disabled={state !== 'staged'}
onClick={() => setState('committed')}
className={`
px-4 py-2 rounded-full text-xs font-bold transition-all
${state === 'staged'
? 'bg-primary text-primary-foreground hover:bg-primary/90 hover:scale-105 shadow-md cursor-pointer'
: 'bg-muted text-muted-foreground cursor-not-allowed opacity-50'
}
`}
>
git commit
</button>
</div>
</div>
{/* Repository Zone */}
<div ref={repoRef} className={`relative p-4 rounded-lg border-2 transition-colors duration-300 ${state === 'committed' ? 'border-primary bg-primary/5' : 'border-dashed border-muted'}`}>
<div className="flex items-center gap-2 mb-4 text-sm font-semibold text-foreground/80">
<Database size={18} />
<span>Repository</span>
</div>
<p className="text-xs text-muted-foreground mb-8">Saved history.</p>
{state === 'committed' && (
<div ref={fileRef} className="bg-white p-3 rounded shadow-md border border-gray-200 flex items-center gap-3">
<FileText className="text-blue-500" />
<div>
<div className="text-sm font-bold">script.js</div>
<div className="text-xs text-blue-600">Committed</div>
</div>
</div>
)}
<div className="absolute bottom-4 left-0 w-full flex justify-center">
<button
disabled={state !== 'committed'}
className={`
px-4 py-2 rounded-full text-xs font-bold transition-all
${state === 'committed'
? 'bg-secondary text-secondary-foreground cursor-default'
: 'bg-muted text-muted-foreground cursor-not-allowed opacity-50'
}
`}
>
Saved!
</button>
</div>
</div>
{/* Connecting Arrows (Visual only, hidden on mobile for cleaner look) */}
<div className="hidden md:block absolute top-1/2 left-[33%] -translate-y-1/2 -translate-x-1/2 z-10 text-muted-foreground/30">
<ArrowRight size={24} />
</div>
<div className="hidden md:block absolute top-1/2 left-[66%] -translate-y-1/2 -translate-x-1/2 z-10 text-muted-foreground/30">
<ArrowRight size={24} />
</div>
</div>
<div className="mt-4 p-3 bg-muted/30 rounded text-xs text-muted-foreground text-center">
{state === 'modified' && "You have changed a file. It's in your Working Directory."}
{state === 'staged' && "You've added the file to the Staging Area. It's ready to be part of the next snapshot."}
{state === 'committed' && "Success! The file is safely stored in the Repository history."}
</div>
</div>
);
};