NRVM

Back to Discover
MotionAdvanced

Dynamic Island

An iOS-style expanding pill.

Live Preview
12:43

Click the island to expand

"use client";

import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { Phone, PhoneOff, Mic } from "lucide-react";

export function DynamicIsland() {
  const [expanded, setExpanded] = useState(false);

  return (
    <div className="flex items-center justify-center w-full h-80 bg-[#0a0a0a] rounded-xl border border-border relative overflow-hidden">
      <div className="absolute top-4 left-1/2 -translate-x-1/2 z-50">
        <motion.div
          layout
          onClick={() => setExpanded(!expanded)}
          className="bg-black rounded-[32px] cursor-pointer overflow-hidden shadow-xl border border-white/10"
          initial={{ width: 120, height: 32 }}
          animate={{
            width: expanded ? 340 : 120,
            height: expanded ? 80 : 32,
            borderRadius: expanded ? 32 : 32
          }}
          transition={{ type: "spring", stiffness: 400, damping: 30 }}
        >
          <AnimatePresence mode="popLayout">
            {!expanded ? (
              <motion.div
                key="collapsed"
                initial={{ opacity: 0 }}
                animate={{ opacity: 1 }}
                exit={{ opacity: 0 }}
                transition={{ duration: 0.15 }}
                className="w-full h-full flex items-center justify-between px-3"
              >
                <div className="flex items-center gap-1.5">
                  <div className="w-4 h-4 rounded-full bg-emerald-500/20 flex items-center justify-center">
                    <Phone className="w-2.5 h-2.5 text-emerald-500 fill-emerald-500" />
                  </div>
                  <span className="text-[10px] font-medium text-emerald-500">12:43</span>
                </div>
                <div className="flex items-center gap-1">
                  <div className="w-1 h-1 rounded-full bg-emerald-500 animate-pulse" />
                  <div className="w-1 h-1 rounded-full bg-emerald-500 animate-pulse delay-75" />
                  <div className="w-1 h-1 rounded-full bg-emerald-500 animate-pulse delay-150" />
                </div>
              </motion.div>
            ) : (
              <motion.div
                key="expanded"
                initial={{ opacity: 0, scale: 0.9 }}
                animate={{ opacity: 1, scale: 1 }}
                exit={{ opacity: 0, scale: 0.9 }}
                transition={{ duration: 0.2 }}
                className="w-full h-full flex items-center justify-between px-6"
              >
                <div className="flex items-center gap-4">
                  <div className="w-12 h-12 rounded-full overflow-hidden bg-zinc-800">
                    <img src="https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&w=128&q=80" alt="Caller" className="w-full h-full object-cover" />
                  </div>
                  <div className="flex flex-col">
                    <span className="text-sm font-medium text-white">Mom</span>
                    <span className="text-xs text-zinc-400">00:12</span>
                  </div>
                </div>
                <div className="flex items-center gap-3">
                  <button className="w-10 h-10 rounded-full bg-rose-500 flex items-center justify-center hover:bg-rose-600 transition-colors">
                    <PhoneOff className="w-5 h-5 text-white" />
                  </button>
                  <button className="w-10 h-10 rounded-full bg-emerald-500 flex items-center justify-center hover:bg-emerald-600 transition-colors">
                    <Mic className="w-5 h-5 text-white" />
                  </button>
                </div>
              </motion.div>
            )}
          </AnimatePresence>
        </motion.div>
      </div>
      <p className="text-sm text-zinc-500 absolute bottom-4">Click the island to expand</p>
    </div>
  );
}