Refactor Stacking Blocks interactive

Update Stacking Blocks interactive to break stacks on hover, display points in tooltips, and remove the modal.
Here are the files that were edited by the AI in that message:
-edited src/components/StackingBlocks.tsx
This commit is contained in:
gpt-engineer-app[bot] 2025-08-22 06:48:56 +00:00
parent d352192c9e
commit 8a6fed8078

View file

@ -1,11 +1,10 @@
import React, { useState, useCallback } from 'react';
import { Button } from '@/components/ui/button';
import { Slider } from '@/components/ui/slider';
import { Input } from '@/components/ui/input';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import { Label } from '@/components/ui/label';
import { Separator } from '@/components/ui/separator';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import SocialShare from './SocialShare';
import confetti from 'canvas-confetti';
@ -19,17 +18,16 @@ const StackingBlocks: React.FC<StackingBlocksProps> = ({ showSocialShare = false
const [score, setScore] = useState(0);
const [gameStarted, setGameStarted] = useState(false);
const [gameComplete, setGameComplete] = useState(false);
const [selectedStack, setSelectedStack] = useState<number | null>(null);
const [splitSize1, setSplitSize1] = useState('');
const [splitSize2, setSplitSize2] = useState('');
const [dialogOpen, setDialogOpen] = useState(false);
const [hoveredStack, setHoveredStack] = useState<number | null>(null);
const [hoveredSplitPoint, setHoveredSplitPoint] = useState<number | null>(null);
const startGame = useCallback(() => {
setStacks([n]);
setScore(0);
setGameStarted(true);
setGameComplete(false);
setSelectedStack(null);
setHoveredStack(null);
setHoveredSplitPoint(null);
}, [n]);
const resetGame = useCallback(() => {
@ -37,38 +35,23 @@ const StackingBlocks: React.FC<StackingBlocksProps> = ({ showSocialShare = false
setScore(0);
setGameStarted(false);
setGameComplete(false);
setSelectedStack(null);
setSplitSize1('');
setSplitSize2('');
setDialogOpen(false);
setHoveredStack(null);
setHoveredSplitPoint(null);
}, []);
const handleStackClick = useCallback((index: number, stackSize: number) => {
if (stackSize === 1) return; // Can't split stacks of size 1
setSelectedStack(index);
setSplitSize1('1');
setSplitSize2((stackSize - 1).toString());
setDialogOpen(true);
}, []);
const executeSplit = useCallback(() => {
if (selectedStack === null) return;
const handleStackClick = useCallback((stackIndex: number, splitPoint: number) => {
if (hoveredStack !== stackIndex || hoveredSplitPoint !== splitPoint) return;
const size1 = parseInt(splitSize1);
const size2 = parseInt(splitSize2);
const originalSize = stacks[selectedStack];
// Validate the split
if (size1 <= 0 || size2 <= 0 || size1 + size2 !== originalSize) {
return;
}
const stackSize = stacks[stackIndex];
const size1 = splitPoint;
const size2 = stackSize - splitPoint;
// Calculate points gained
const pointsGained = size1 * size2;
// Update stacks
const newStacks = [...stacks];
newStacks.splice(selectedStack, 1); // Remove the original stack
newStacks.splice(stackIndex, 1); // Remove the original stack
newStacks.push(size1, size2); // Add the two new stacks
newStacks.sort((a, b) => b - a); // Sort in descending order for better visualization
@ -85,20 +68,9 @@ const StackingBlocks: React.FC<StackingBlocksProps> = ({ showSocialShare = false
});
}
setDialogOpen(false);
setSelectedStack(null);
setSplitSize1('');
setSplitSize2('');
}, [selectedStack, splitSize1, splitSize2, stacks]);
const handleSplit1Change = (value: string) => {
setSplitSize1(value);
const size1 = parseInt(value);
if (!isNaN(size1) && selectedStack !== null) {
const originalSize = stacks[selectedStack];
setSplitSize2((originalSize - size1).toString());
}
};
setHoveredStack(null);
setHoveredSplitPoint(null);
}, [stacks, hoveredStack, hoveredSplitPoint]);
const getStackColor = (size: number) => {
if (size === 1) return 'bg-gray-200';
@ -108,25 +80,54 @@ const StackingBlocks: React.FC<StackingBlocksProps> = ({ showSocialShare = false
return 'bg-red-200';
};
const renderStack = (size: number, index: number) => {
const renderStack = (size: number, stackIndex: number) => {
const canSplit = size > 1;
return (
<div
key={index}
className={`flex flex-col items-center ${canSplit ? 'cursor-pointer' : ''}`}
onClick={() => canSplit && handleStackClick(index, size)}
>
<div className="text-sm font-medium mb-2">Stack {index + 1}</div>
<div key={stackIndex} className="flex flex-col items-center">
<div className="text-sm font-medium mb-2">Stack {stackIndex + 1}</div>
<div className="flex flex-col-reverse items-center">
{Array.from({ length: size }).map((_, blockIndex) => (
<div
key={blockIndex}
className={`w-12 h-6 border border-black ${getStackColor(size)} ${
canSplit ? 'hover:brightness-90' : ''
}`}
style={{ marginBottom: '-1px' }}
/>
))}
{Array.from({ length: size }).map((_, blockIndex) => {
const splitPoint = size - blockIndex; // How many blocks would be in the top stack if we split here
const bottomSize = blockIndex + 1; // How many blocks would be in the bottom stack
const pointsGained = canSplit && splitPoint > 0 && bottomSize > 0 ? splitPoint * bottomSize : 0;
const isHovered = hoveredStack === stackIndex && hoveredSplitPoint === splitPoint;
const showSplitLine = canSplit && splitPoint > 0 && bottomSize > 0 && isHovered;
return (
<TooltipProvider key={blockIndex}>
<Tooltip>
<TooltipTrigger asChild>
<div
className={`w-12 h-6 border border-black ${getStackColor(size)} ${
canSplit && splitPoint > 0 && bottomSize > 0 ? 'cursor-pointer hover:brightness-90' : ''
} ${showSplitLine ? 'border-t-4 border-t-red-500' : ''} relative`}
style={{ marginBottom: '-1px' }}
onMouseEnter={() => {
if (canSplit && splitPoint > 0 && bottomSize > 0) {
setHoveredStack(stackIndex);
setHoveredSplitPoint(splitPoint);
}
}}
onMouseLeave={() => {
setHoveredStack(null);
setHoveredSplitPoint(null);
}}
onClick={() => {
if (canSplit && splitPoint > 0 && bottomSize > 0) {
handleStackClick(stackIndex, splitPoint);
}
}}
/>
</TooltipTrigger>
{canSplit && pointsGained > 0 && (
<TooltipContent>
<p>Split into {splitPoint} + {bottomSize} = {pointsGained} points</p>
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
);
})}
</div>
<div className="text-xs text-muted-foreground mt-2">Size: {size}</div>
</div>
@ -189,7 +190,7 @@ const StackingBlocks: React.FC<StackingBlocksProps> = ({ showSocialShare = false
) : (
<>
<div className="text-center text-muted-foreground">
Click on any stack with more than 1 block to split it
Hover over a stack to see where it will split, then click to execute
</div>
<div className="flex flex-wrap justify-center gap-8 min-h-40">
@ -202,62 +203,6 @@ const StackingBlocks: React.FC<StackingBlocksProps> = ({ showSocialShare = false
</CardContent>
</Card>
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Split Stack</DialogTitle>
</DialogHeader>
<div className="space-y-4">
{selectedStack !== null && (
<>
<div className="text-sm text-muted-foreground">
Splitting stack of size {stacks[selectedStack]} into two stacks:
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="size1">First stack size:</Label>
<Input
id="size1"
type="number"
min="1"
max={selectedStack !== null ? stacks[selectedStack] - 1 : 1}
value={splitSize1}
onChange={(e) => handleSplit1Change(e.target.value)}
/>
</div>
<div>
<Label htmlFor="size2">Second stack size:</Label>
<Input
id="size2"
type="number"
value={splitSize2}
readOnly
className="bg-gray-50"
/>
</div>
</div>
{splitSize1 && splitSize2 && (
<div className="text-sm text-center p-2 bg-blue-50 rounded">
Points gained: {parseInt(splitSize1) * parseInt(splitSize2)}
</div>
)}
<div className="flex gap-2">
<Button onClick={executeSplit} className="flex-1">
Split Stack
</Button>
<Button
onClick={() => setDialogOpen(false)}
variant="outline"
className="flex-1"
>
Cancel
</Button>
</div>
</>
)}
</div>
</DialogContent>
</Dialog>
{showSocialShare && (
<SocialShare