Baktiyar Ablaikhan

Resizable panels

Source
After bencho.dev ↗
Author
Interaction after bencho.dev
Licence
Original code — interaction after bencho.dev

Loading…

Drag the divider to resize the panels; it snaps shut near the edges.

Code

"use client";

import { animate, motion, useMotionValue, useMotionValueEvent, useTransform } from "motion/react";
import { useRef, useState } from "react";
import { Surface } from "../ui";

export default function ResizablePanels() {
  const box = useRef<HTMLDivElement>(null);
  const split = useMotionValue(0.42);
  const [drag, setDrag] = useState(false);
  const [pct, setPct] = useState(42);
  const left = useTransform(split, (v) => `${v * 100}%`);
  useMotionValueEvent(split, "change", (v) => setPct(Math.round(v * 100)));

  const at = (x: number) => {
    const r = box.current!.getBoundingClientRect();
    return (x - r.left) / r.width;
  };

  return (
    <Surface className="p-[1.4em]">
      <div ref={box} className="relative flex h-full w-full overflow-hidden rounded-[1em] bg-white shadow-[0_12px_32px_-18px_rgba(0,0,0,0.35),0_0_0_1px_rgba(0,0,0,0.04)]">
        <motion.div style={{ width: left }} className="relative overflow-hidden p-[1em]">
          <p className="font-mono text-[0.65em] uppercase tracking-[0.15em] text-[#8a8984]">Files</p>
          {["index.tsx", "panel.tsx", "styles.css", "README.md"].map((f, i) => (
            <div key={f} className={`mt-[0.5em] truncate rounded-[0.4em] px-[0.5em] py-[0.25em] text-[0.8em] ${i === 1 ? "bg-[#f1f0ec]" : ""}`}>
              {f}
            </div>
          ))}
        </motion.div>
        <div
          className="group relative z-10 w-[1.2em] shrink-0 cursor-col-resize touch-none"
          onPointerDown={(e) => {
            e.currentTarget.setPointerCapture(e.pointerId);
            setDrag(true);
          }}
          onPointerMove={(e) => {
            if (!drag) return;
            const t = at(e.clientX);
            // resist near the edges, then snap shut
            split.set(Math.min(0.85, Math.max(0.06, t)));
          }}
          onPointerUp={() => {
            setDrag(false);
            const v = split.get();
            if (v < 0.16) animate(split, 0.06, { type: "spring", stiffness: 400, damping: 30 });
            else if (v > 0.78) animate(split, 0.85, { type: "spring", stiffness: 400, damping: 30 });
          }}
          onDoubleClick={() => animate(split, 0.42, { type: "spring", stiffness: 300, damping: 26 })}
        >
          <div className="absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-[#ecebe6]" />
          <motion.div
            animate={{ height: drag ? "3.6em" : "2em", backgroundColor: drag ? "#2B46B8" : "#cfcec8" }}
            className="absolute left-1/2 top-1/2 w-[0.3em] -translate-x-1/2 -translate-y-1/2 rounded-full group-hover:bg-[#151515]"
          />
          <motion.span
            animate={{ opacity: drag ? 1 : 0, y: drag ? 0 : 4 }}
            className="pointer-events-none absolute left-1/2 top-[calc(50%+2.4em)] -translate-x-1/2 rounded-full bg-[#151515] px-[0.5em] py-[0.1em] font-mono text-[0.6em] text-white"
          >
            {pct}%
          </motion.span>
        </div>
        <div className="min-w-0 flex-1 bg-[#fbfaf7] p-[1em]">
          <p className="font-mono text-[0.65em] uppercase tracking-[0.15em] text-[#8a8984]">panel.tsx</p>
          <div className="mt-[0.7em] space-y-[0.5em]">
            {[70, 88, 52, 94, 40, 76, 60].map((w, i) => (
              <div key={i} className="flex items-center gap-[0.6em]">
                <span className="w-[1.2em] text-right font-mono text-[0.6em] text-[#c9c8c3]">{i + 1}</span>
                <div className="h-[0.45em] rounded-full" style={{ width: `${w}%`, background: i % 3 === 1 ? "#c9d1f3" : "#e4e3de" }} />
              </div>
            ))}
          </div>
        </div>
      </div>
    </Surface>
  );
}
Prompt

Build "Resizable panels", a layout animation. What it does: Drag the divider to resize the panels; it snaps shut near the edges. Write it as a single self-contained React component in TypeScript that fills its parent (w-full h-full), styled with Tailwind CSS, using motion. Technique: an HTML canvas, the Motion library, pointer tracking. It sits on a light background, in a 16:10 frame. Respect prefers-reduced-motion with a still state, pause work when the element is off screen, and keep it accessible. Original: https://bencho.dev (Interaction after bencho.dev, Original code — interaction after bencho.dev).