import React, {
useCallback,
useEffect,
useLayoutEffect,
useRef,
useState
} from "https://esm.sh/react@19.0.0";
import ReactDOM from "https://esm.sh/react-dom@19.0.0/client";
import gsap from "https://esm.sh/gsap";
import { Flip } from "https://esm.sh/gsap/Flip";
import { useGSAP } from "https://esm.sh/@gsap/react?deps=react@19.0.0";
gsap.registerPlugin(useGSAP);
let count = 0;
gsap.registerPlugin(Flip);
const wrapColor = gsap.utils.wrap(["blue", "green", "red", "orange"]);
function createItem() {
return { id: ++count, color: wrapColor(count), status: "entered" };
}
function App() {
const el = useRef();
const q = gsap.utils.selector(el);
const removeItems = useCallback(
(exitingItems) => {
if (!exitingItems.length) return;
setLayout((prev) => ({
state: Flip.getState(q(".box, .button")),
items: prev.items.filter((item) => !exitingItems.includes(item))
}));
},
[q]
);
const [layout, setLayout] = useState(() => ({
items: [createItem(), createItem(), createItem(), createItem()].reverse()
}));
useGSAP(
() => {
if (!layout.state) return;
const exiting = layout.items.filter((item) => item.status === "exiting");
const timeline = Flip.from(layout.state, {
absolute: true,
ease: "power1.inOut",
targets: q(".box, .button"),
scale: true,
simple: true,
onEnter: (elements) => {
return gsap.fromTo(
elements,
{
opacity: 0,
scale: 0
},
{
opacity: 1,
scale: 1,
delay: 0.2,
duration: 0.3
}
);
},
onLeave: (elements) => {
return gsap.to(elements, {
opacity: 0,
scale: 0
});
},
onComplete() {
let boxes = document.querySelector(".boxes"),
lastChild = boxes.lastChild;
boxes.appendChild(lastChild);
}
});
timeline.add(() => removeItems(exiting));
},
{
dependencies: [layout, q, removeItems]
}
);
const addItem = () => {
setLayout({
state: Flip.getState(q(".box, .button")),
items: [createItem(), ...layout.items]
});
};
const shuffle = () => {
setLayout({
state: Flip.getState(q(".box, .button")),
items: [...gsap.utils.shuffle(layout.items)]
});
};
const remove = (item) => {
item.status = "exiting";
setLayout({
...layout,
state: Flip.getState(q(".box, .button"))
});
};
return (
<div className="app text-center" ref={el}>
<div>
<button className="button" onClick={addItem}>
Add Box
</button>
<button className="button" onClick={shuffle}>
Shuffle
</button>
</div>
<div className="boxes">
{layout.items.map((item) => (
<div
id={`box-${item.id}`}
key={item.id}
className={`box gradient-${item.color} ${item.status}`}
onClick={() => remove(item)}
>
Click Me
</div>
))}
</div>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
View Compiled