{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dynamic-island",
  "type": "registry:ui",
  "description": "iOS-style dynamic island component with expandable content and smooth animations",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/default/ui/dynamic-island.tsx",
      "content": "\"use client\"\n\nimport React, {\n  createContext,\n  ReactNode,\n  useCallback,\n  useContext,\n  useEffect,\n  useReducer,\n  useRef,\n  useState,\n} from \"react\"\nimport { AnimatePresence, motion, useWillChange } from \"motion/react\"\n\nconst stiffness = 400\nconst damping = 30\nconst MIN_WIDTH = 691\nconst MAX_HEIGHT_MOBILE_ULTRA = 400\nconst MAX_HEIGHT_MOBILE_MASSIVE = 700\n\nconst min = (a: number, b: number) => (a < b ? a : b)\n\nexport type SizePresets =\n  | \"reset\"\n  | \"empty\"\n  | \"default\"\n  | \"compact\"\n  | \"compactLong\"\n  | \"large\"\n  | \"long\"\n  | \"minimalLeading\"\n  | \"minimalTrailing\"\n  | \"compactMedium\"\n  | \"medium\"\n  | \"tall\"\n  | \"ultra\"\n  | \"massive\"\n\nconst SIZE_PRESETS = {\n  RESET: \"reset\",\n  EMPTY: \"empty\",\n  DEFAULT: \"default\",\n  COMPACT: \"compact\",\n  COMPACT_LONG: \"compactLong\",\n  LARGE: \"large\",\n  LONG: \"long\",\n  MINIMAL_LEADING: \"minimalLeading\",\n  MINIMAL_TRAILING: \"minimalTrailing\",\n  COMPACT_MEDIUM: \"compactMedium\",\n  MEDIUM: \"medium\",\n  TALL: \"tall\",\n  ULTRA: \"ultra\",\n  MASSIVE: \"massive\",\n} as const\n\ntype Preset = {\n  width: number\n  height?: number\n  aspectRatio: number\n  borderRadius: number\n}\n\nconst DynamicIslandSizePresets: Record<SizePresets, Preset> = {\n  [SIZE_PRESETS.RESET]: {\n    width: 150,\n    aspectRatio: 1,\n    borderRadius: 20,\n  },\n  [SIZE_PRESETS.EMPTY]: {\n    width: 0,\n    aspectRatio: 0,\n    borderRadius: 0,\n  },\n  [SIZE_PRESETS.DEFAULT]: {\n    width: 150,\n    aspectRatio: 44 / 150,\n    borderRadius: 46,\n  },\n  [SIZE_PRESETS.MINIMAL_LEADING]: {\n    width: 52.33,\n    aspectRatio: 44 / 52.33,\n    borderRadius: 22,\n  },\n  [SIZE_PRESETS.MINIMAL_TRAILING]: {\n    width: 52.33,\n    aspectRatio: 44 / 52.33,\n    borderRadius: 22,\n  },\n  [SIZE_PRESETS.COMPACT]: {\n    width: 235,\n    aspectRatio: 44 / 235,\n    borderRadius: 46,\n  },\n  [SIZE_PRESETS.COMPACT_LONG]: {\n    width: 300,\n    aspectRatio: 44 / 235,\n    borderRadius: 46,\n  },\n  [SIZE_PRESETS.COMPACT_MEDIUM]: {\n    width: 351,\n    aspectRatio: 64 / 371,\n    borderRadius: 44,\n  },\n  [SIZE_PRESETS.LONG]: {\n    width: 371,\n    aspectRatio: 84 / 371,\n    borderRadius: 42,\n  },\n  [SIZE_PRESETS.MEDIUM]: {\n    width: 371,\n    aspectRatio: 210 / 371,\n    borderRadius: 22,\n  },\n  [SIZE_PRESETS.LARGE]: {\n    width: 371,\n    aspectRatio: 84 / 371,\n    borderRadius: 42,\n  },\n  [SIZE_PRESETS.TALL]: {\n    width: 371,\n    aspectRatio: 210 / 371,\n    borderRadius: 42,\n  },\n  [SIZE_PRESETS.ULTRA]: {\n    width: 630,\n    aspectRatio: 630 / 800,\n    borderRadius: 42,\n  },\n  [SIZE_PRESETS.MASSIVE]: {\n    width: 891,\n    height: 1900,\n    aspectRatio: 891 / 891,\n    borderRadius: 42,\n  },\n}\n\ntype BlobStateType = {\n  size: SizePresets\n  previousSize: SizePresets | undefined\n  animationQueue: Array<{ size: SizePresets; delay: number }>\n  isAnimating: boolean\n}\n\ntype BlobAction =\n  | { type: \"SET_SIZE\"; newSize: SizePresets }\n  | { type: \"INITIALIZE\"; firstState: SizePresets }\n  | {\n      type: \"SCHEDULE_ANIMATION\"\n      animationSteps: Array<{ size: SizePresets; delay: number }>\n    }\n  | { type: \"ANIMATION_END\" }\n\ntype BlobContextType = {\n  state: BlobStateType\n  dispatch: React.Dispatch<BlobAction>\n  setSize: (size: SizePresets) => void\n  scheduleAnimation: (\n    animationSteps: Array<{ size: SizePresets; delay: number }>\n  ) => void\n  presets: Record<SizePresets, Preset>\n}\n\nconst BlobContext = createContext<BlobContextType | undefined>(undefined)\n\nconst blobReducer = (\n  state: BlobStateType,\n  action: BlobAction\n): BlobStateType => {\n  switch (action.type) {\n    case \"SET_SIZE\":\n      return {\n        ...state,\n        size: action.newSize,\n        previousSize: state.size,\n        isAnimating: false, // Only set isAnimating to true if there are more steps\n      }\n    case \"SCHEDULE_ANIMATION\":\n      return {\n        ...state,\n        animationQueue: action.animationSteps,\n        isAnimating: action.animationSteps.length > 0,\n      }\n    case \"INITIALIZE\":\n      return {\n        ...state,\n        size: action.firstState,\n        previousSize: SIZE_PRESETS.EMPTY,\n        isAnimating: false,\n      }\n    case \"ANIMATION_END\":\n      return {\n        ...state,\n        isAnimating: false,\n      }\n    default:\n      return state\n  }\n}\n\ninterface DynamicIslandProviderProps {\n  children: React.ReactNode\n  initialSize?: SizePresets\n  initialAnimation?: Array<{ size: SizePresets; delay: number }>\n}\n\nconst DynamicIslandProvider: React.FC<DynamicIslandProviderProps> = ({\n  children,\n  initialSize = SIZE_PRESETS.DEFAULT,\n  initialAnimation = [],\n}) => {\n  const initialState: BlobStateType = {\n    size: initialSize,\n    previousSize: SIZE_PRESETS.EMPTY,\n    animationQueue: initialAnimation,\n    isAnimating: initialAnimation.length > 0,\n  }\n\n  const [state, dispatch] = useReducer(blobReducer, initialState)\n\n  useEffect(() => {\n    const processQueue = async () => {\n      for (const step of state.animationQueue) {\n        await new Promise((resolve) => setTimeout(resolve, step.delay))\n        dispatch({ type: \"SET_SIZE\", newSize: step.size })\n      }\n      dispatch({ type: \"ANIMATION_END\" })\n    }\n\n    if (state.animationQueue.length > 0) {\n      processQueue()\n    }\n  }, [state.animationQueue])\n\n  // biome-ignore lint/correctness/useExhaustiveDependencies: needed for dispatch\n  const setSize = useCallback(\n    (newSize: SizePresets) => {\n      if (state.previousSize !== newSize && newSize !== state.size) {\n        dispatch({ type: \"SET_SIZE\", newSize })\n      }\n    },\n    [state.previousSize, state.size, dispatch]\n  )\n\n  // biome-ignore lint/correctness/useExhaustiveDependencies: needed for dispatch\n  const scheduleAnimation = useCallback(\n    (animationSteps: Array<{ size: SizePresets; delay: number }>) => {\n      dispatch({ type: \"SCHEDULE_ANIMATION\", animationSteps })\n    },\n    [dispatch]\n  )\n\n  const contextValue = {\n    state,\n    dispatch,\n    setSize,\n    scheduleAnimation,\n    presets: DynamicIslandSizePresets,\n  }\n\n  return (\n    <BlobContext.Provider value={contextValue}>{children}</BlobContext.Provider>\n  )\n}\n\nconst useDynamicIslandSize = () => {\n  const context = useContext(BlobContext)\n  if (!context) {\n    throw new Error(\n      \"useDynamicIslandSize must be used within a DynamicIslandProvider\"\n    )\n  }\n  return context\n}\n\nconst useScheduledAnimations = (\n  animations: Array<{ size: SizePresets; delay: number }>\n) => {\n  const { scheduleAnimation } = useDynamicIslandSize()\n  const animationsRef = useRef(animations)\n\n  useEffect(() => {\n    scheduleAnimation(animationsRef.current)\n  }, [scheduleAnimation])\n}\n\nconst DynamicIslandContainer = ({ children }: { children: ReactNode }) => {\n  return (\n    <div className=\"z-10 flex h-full w-full items-end justify-center bg-transparent\">\n      {children}\n    </div>\n  )\n}\n\nconst DynamicIsland = ({\n  children,\n  id,\n  ...props\n}: {\n  children: ReactNode\n  id: string\n}) => {\n  const willChange = useWillChange()\n  const [screenSize, setScreenSize] = useState(\"desktop\")\n\n  useEffect(() => {\n    const handleResize = () => {\n      if (window.innerWidth <= 640) {\n        setScreenSize(\"mobile\")\n      } else if (window.innerWidth <= 1024) {\n        setScreenSize(\"tablet\")\n      } else {\n        setScreenSize(\"desktop\")\n      }\n    }\n\n    handleResize()\n    window.addEventListener(\"resize\", handleResize)\n    return () => window.removeEventListener(\"resize\", handleResize)\n  }, [])\n\n  return (\n    <DynamicIslandContainer>\n      <DynamicIslandContent\n        id={id}\n        willChange={willChange}\n        screenSize={screenSize}\n        {...props}\n      >\n        {children}\n      </DynamicIslandContent>\n    </DynamicIslandContainer>\n  )\n}\n\nconst calculateDimensions = (\n  size: SizePresets,\n  screenSize: string,\n  currentSize: Preset\n): { width: string; height: number } => {\n  const isMassiveOnMobile = size === \"massive\" && screenSize === \"mobile\"\n  const isUltraOnMobile = size === \"ultra\" && screenSize === \"mobile\"\n\n  if (isMassiveOnMobile) {\n    return { width: \"350px\", height: MAX_HEIGHT_MOBILE_MASSIVE }\n  }\n\n  if (isUltraOnMobile) {\n    return { width: \"350px\", height: MAX_HEIGHT_MOBILE_ULTRA }\n  }\n\n  const width = min(currentSize.width, MIN_WIDTH)\n  return { width: `${width}px`, height: currentSize.aspectRatio * width }\n}\n\nconst DynamicIslandContent = ({\n  children,\n  id,\n  willChange,\n  screenSize,\n  ...props\n}: {\n  children: React.ReactNode\n  id: string\n  willChange: any\n  screenSize: string\n  [key: string]: any\n}) => {\n  const { state, presets } = useDynamicIslandSize()\n  const currentSize = presets[state.size]\n\n  const dimensions = calculateDimensions(state.size, screenSize, currentSize)\n\n  return (\n    <motion.div\n      id={id}\n      className=\"mx-auto h-0 w-0 items-center justify-center border border-black/10 bg-black text-center text-black transition duration-300 ease-in-out focus-within:bg-neutral-900 hover:shadow-md dark:border dark:border-white/5 dark:focus-within:bg-black\"\n      animate={{\n        width: dimensions.width,\n        height: dimensions.height,\n        borderRadius: currentSize.borderRadius,\n        transition: {\n          type: \"spring\",\n          stiffness,\n          damping,\n        },\n        clipPath: `none`,\n        transitionEnd: {\n          clipPath: `url(#squircle-${state.size})`,\n        },\n      }}\n      style={{ willChange }}\n      {...props}\n    >\n      <AnimatePresence>{children}</AnimatePresence>\n    </motion.div>\n  )\n}\n\ntype DynamicContainerProps = {\n  className?: string\n  children?: React.ReactNode\n}\n\nconst DynamicContainer = ({ className, children }: DynamicContainerProps) => {\n  const willChange = useWillChange()\n  const { state } = useDynamicIslandSize()\n  const { size, previousSize } = state\n\n  const isSizeChanged = size !== previousSize\n\n  const initialState = {\n    opacity: size === previousSize ? 1 : 0,\n    scale: size === previousSize ? 1 : 0.9,\n    y: size === previousSize ? 0 : 5,\n  }\n\n  const animateState = {\n    opacity: 1,\n    scale: 1,\n    y: 0,\n  }\n\n  const transition = {\n    type: \"spring\" as const,\n    stiffness,\n    damping,\n    duration: isSizeChanged ? 0.5 : 0.8,\n  }\n\n  return (\n    <motion.div\n      initial={initialState}\n      animate={animateState}\n      transition={transition}\n      exit={{ opacity: 0, filter: \"blur(10px)\", scale: 0.95, y: 20 }}\n      style={{ willChange }}\n      className={className}\n    >\n      {children}\n    </motion.div>\n  )\n}\n\ntype DynamicChildrenProps = {\n  className?: string\n  children?: React.ReactNode\n}\n\nconst DynamicDiv = ({ className, children }: DynamicChildrenProps) => {\n  const { state } = useDynamicIslandSize()\n  const { size, previousSize } = state\n  const willChange = useWillChange()\n\n  return (\n    <motion.div\n      initial={{\n        opacity: size === previousSize ? 1 : 0,\n        scale: size === previousSize ? 1 : 0.9,\n      }}\n      animate={{\n        opacity: size === previousSize ? 0 : 1,\n        scale: size === previousSize ? 0.9 : 1,\n        transition: {\n          type: \"spring\",\n          stiffness,\n          damping,\n        },\n      }}\n      exit={{ opacity: 0, filter: \"blur(10px)\", scale: 0 }}\n      style={{ willChange }}\n      className={className}\n    >\n      {children}\n    </motion.div>\n  )\n}\n\ntype MotionProps = {\n  className: string\n  children: React.ReactNode\n}\n\nconst DynamicTitle = ({ className, children }: MotionProps) => {\n  const { state } = useDynamicIslandSize()\n  const { size, previousSize } = state\n  const willChange = useWillChange()\n\n  return (\n    <motion.h3\n      className={className}\n      initial={{ opacity: 0, scale: 0 }}\n      animate={{\n        opacity: size === previousSize ? 0 : 1,\n        scale: size === previousSize ? 0.9 : 1,\n        transition: { type: \"spring\", stiffness, damping },\n      }}\n      style={{ willChange }}\n    >\n      {children}\n    </motion.h3>\n  )\n}\n\nconst DynamicDescription = ({ className, children }: MotionProps) => {\n  const { state } = useDynamicIslandSize()\n  const { size, previousSize } = state\n  const willChange = useWillChange()\n\n  return (\n    <motion.p\n      className={className}\n      initial={{ opacity: 0, scale: 0 }}\n      animate={{\n        opacity: size === previousSize ? 0 : 1,\n        scale: size === previousSize ? 0.9 : 1,\n        transition: { type: \"spring\", stiffness, damping },\n      }}\n      style={{ willChange }}\n    >\n      {children}\n    </motion.p>\n  )\n}\n\nexport {\n  DynamicContainer,\n  DynamicTitle,\n  DynamicDescription,\n  DynamicIsland,\n  SIZE_PRESETS,\n  stiffness,\n  DynamicDiv,\n  damping,\n  DynamicIslandSizePresets,\n  BlobContext,\n  useDynamicIslandSize,\n  useScheduledAnimations,\n  DynamicIslandProvider,\n}\n\nexport default DynamicIsland\n",
      "type": "registry:ui"
    }
  ]
}