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:
parent
d352192c9e
commit
8a6fed8078
1 changed files with 62 additions and 117 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue