184 lines
8.2 KiB
TypeScript
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>
|
|
);
|
|
};
|