Docs
3D Card

3D Card

A 3D card component that can be used to display multiple items with animation.

Note: The dynamic island is shown below the navbar of the page. Scroll down to see the effect.
components/snippet/dynamic-island-nav.tsx
'use client';
 
import Image from 'next/image';
import { Aperture, ChevronDown, ChevronUp } from 'lucide-react';
import { motion, useScroll, useMotionValueEvent } from 'framer-motion';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import Link from 'next/link';
 
export default function DynamicIslandNav() {
  const [showNav, setShowNav] = useState<boolean>(false);
  const [hidden, setHidden] = useState<boolean>(false);
 
  const { scrollY } = useScroll();
 
  useMotionValueEvent(scrollY, 'change', (latest) => {
    const previous = scrollY.getPrevious();
 
    if (latest > previous && latest > 150) {
      setHidden(true);
      setShowNav(false);
    } else {
      setHidden(false);
    }
  });
 
  return (
    <motion.nav
      className={`fixed inset-0 top-24 w-[95%] sm:w-[90%] mx-auto bg-zinc-950 font-medium text-slate-50 flex  max-sm:justify-between gap-4 px-3 max-w-7xl items-center rounded-full font-mono h-14 p-5 overflow-hidden `}
      variants={{
        long: { maxWidth: 950 },
        short: { maxWidth: 280 },
        hideNav: {
          height: 56,
          borderRadius: 50,
 
          alignItems: 'center',
          transition: { delay: 0, duration: 0.3 },
        },
        showNav: {
          height: 200,
          borderRadius: 22,
          alignItems: 'start',
          transition: { delay: 0 },
        },
      }}
      initial={'short'}
      animate={[hidden ? 'short' : 'long', showNav ? 'showNav' : 'hideNav']}
      transition={{
        duration: 0.6,
        type: 'spring',
        stiffness: 80,
        damping: 14,
      }}
    >
      <div className="min-w-[40px] min-h-[40px] rounded-full gap-2  flex items-center justify-center">
        <Aperture size={40} />
      </div>
      <motion.ul
        className={`w-full ${showNav
          ? '[--display-from:none] [--display-to:flex]'
          : 'max-sm:[--display-from:none] sm:[--display-to:flex]'
          }  [--opacity-from:0.1] [--opacity-to:1] flex-col sm:flex-row items-center justify-center gap-10 max-sm:gap-5 max-sm:pt-10`}
        variants={{
          hidden: {
            display: 'var(--display-from, none)',
            opacity: 'var(--opacity-from, 1)',
            transition: { duration: 0.1, delay: 0 },
          },
          visible: {
            display: 'var(--display-to, none)',
            opacity: 'var(--opacity-to, 1)',
            transition: { duration: 0.6, delay: 0.2 },
          },
        }}
        initial={'hidden'}
        animate={[
          hidden && !showNav ? 'hidden' : 'visible',
          showNav ? 'visible' : '',
        ]}
      >
        <li>
          <Link
            href="/home"
            aria-label="scroll to contact section"
            role="button"
          >
            Home
          </Link>
        </li>
        <li>
          <Link
            href="/about"
            aria-label="scroll to contact section"
            role="button"
          >
            About
          </Link>
        </li>
        <li>
          <Link
            href="contact"
            aria-label="scroll to contact section"
            role="button"
          >
            Contact
          </Link>
        </li>
      </motion.ul>
 
      <motion.div
        className="w-full [--display-from:none][--display-to:inline-block] "
        variants={{
          hidden: {
            display: 'var(--display-from, none)',
            // opacity: 'var(--opacity-from)',
            transition: { delay: 0, duration: 0.3 },
          },
          visible: {
            display: 'var(--display-to)',
            // opacity: 'var(--opacity-to)',
            transition: { delay: 0.2, duration: 0.3 },
          },
        }}
        initial="hidden"
        animate={hidden ? 'visible' : 'hidden'}
      >
        <Button variant="ghost" className="w-full" asChild>
          <Link
            href="contact"
            aria-label="scroll to contact section"
            role="button"
          >
            Contact
          </Link>
        </Button>
      </motion.div>
 
      <Button
        size={'icon'}
        variant={'ghost'}
        className="rounded-full min-w-[40px] sm:hidden"
        onClick={() => {
          setHidden(false);
          setShowNav((prev) => !prev);
        }}
      >
        {showNav ? <ChevronUp /> : <ChevronDown />}
      </Button>
    </motion.nav>
  );
}