Dynamic Island
beui/dynamic-islandFreeBlock
iOS-style island pill that morphs between live activity views with bouncy shell resize and blur crossfades.
Install it
npx shadcn@latest add https://beui.dev/r/dynamic-island.jsonSource
1"use client";2// beui.dev/components/blocks/dynamic-island34import { AnimatePresence, motion, useReducedMotion } from "motion/react";5import {6 createContext,7 useContext,8 useEffect,9 useLayoutEffect,10 useMemo,11 useRef,12 useState,13 type ReactNode,14} from "react";15import { EASE_OUT } from "@/lib/ease";16import { cn } from "@/lib/utils";1718type IslandContextValue = {19 view: string | null;20};2122const IslandContext = createContext<IslandContextValue | null>(null);2324// Shell physics in Apple's duration/bounce form one long perceptual glide with barely-there bounce, identical in25// both directions. The shell animates real width/height (not transforms), so26// slots are never scale-distorted.27const SHELL_SPRING = {28 type: "spring",29 duration: 0.8,30 bounce: 0.2,31} as const;3233// Content gets a touch more life than the shell.34const CONTENT_SPRING = {35 type: "spring",36 duration: 0.8,37 bounce: 0.35,38} as const;3940// Constant radius — never animated. The browser clamps it to half the shell41// height, so the pill-to-rounded-rect morph falls out of the resize for free42// with zero chance of corner glitches.43const RADIUS = 32;4445// iPhone pill dimensions. Also the shell's pre-measure animate target: if the46// first commit already has a view active (e.g. a click replayed after47// hydration), the shell blooms from the pill instead of rendering expanded48// with no animation. Lives in `animate`, not `initial`, so server and client49// markup agree.50const PILL_WIDTH = 126;51const PILL_HEIGHT = 37;5253/** Tracks the natural size of the content so the shell can spring to it. */54function useContentSize() {55 const ref = useRef<HTMLDivElement | null>(null);56 const [size, setSize] = useState<{ width: number; height: number } | null>(57 null,58 );5960 // Synchronous mount measure: the shell must own explicit dimensions before61 // the first interaction. ResizeObserver fires async after mount — a quick62 // first press could beat it, leaving the shell auto-sized so the view63 // snapped open instead of springing.64 useLayoutEffect(() => {65 const el = ref.current;66 if (!el) return;67 setSize({ width: el.offsetWidth, height: el.offsetHeight });68 }, []);6970 useEffect(() => {71 const el = ref.current;72 if (!el || typeof ResizeObserver === "undefined") return;73 const observer = new ResizeObserver(() => {74 setSize({ width: el.offsetWidth, height: el.offsetHeight });75 });76 observer.observe(el);77 return () => observer.disconnect();78 }, []);7980// … truncated
What it pulls in
npm packages
Files it writes
More from beui
All 104 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| File Upload Attachment Uploadattachment-upload | beui | Blocks | Free | 4 deps · 5 files | |
| Availability Scheduleravailability-scheduler | beui | Blocks | Free | 4 deps · 15 files | |
| Bloom Menubloom-menu | beui | Blocks | Free | 4 deps · 3 files | |
| Command Palettecommand-palette | beui | Blocks | Free | 4 deps · 3 files | |
| Expandable Action Barexpandable-action-bar | beui | Blocks | Free | 3 deps · 2 files | |
| Expandable Tabsexpandable-tabs | beui | Blocks | Free | 3 deps · 3 files | |
| Feedback Widgetfeedback-widget | beui | Blocks | Free | 4 deps · 9 files | |
| File Upload Upload Queuefile-upload | beui | Blocks | Free | 4 deps · 3 files |
