{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "alert",
  "type": "registry:ui",
  "registryDependencies": [],
  "dependencies": ["motion", "class-variance-authority"],
  "devDependencies": [],
  "files": [
    {
      "path": "alert.tsx",
      "content": "\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n  AnimatePresence,\n  motion,\n  useReducedMotion,\n  type Variants,\n} from \"motion/react\";\nimport {\n  Children,\n  type ComponentPropsWithoutRef,\n  type CSSProperties,\n  createContext,\n  type FocusEvent,\n  forwardRef,\n  isValidElement,\n  type ReactNode,\n  type Ref,\n  useCallback,\n  useContext,\n  useEffect,\n  useId,\n  useRef,\n  useState,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst alertCornerClassName =\n  \"rounded-lg supports-[corner-shape:squircle]:corner-squircle supports-[corner-shape:squircle]:rounded-[11px]\";\n\nconst alertVariants = cva(\n  cn(\n    alertCornerClassName,\n    \"relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 border border-foreground/8 px-3.5 py-2.5 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current\"\n  ),\n  {\n    variants: {\n      appearance: {\n        default: \"bg-card text-card-foreground\",\n        success:\n          \"border-emerald-200 bg-emerald-50 text-emerald-950 *:data-[slot=alert-description]:text-emerald-800/80 dark:border-emerald-800/70 dark:bg-emerald-950/40 dark:text-emerald-50 dark:*:data-[slot=alert-description]:text-emerald-100/75 [&>svg]:text-emerald-700 dark:[&>svg]:text-emerald-300\",\n        info: \"border-sky-200 bg-sky-50 text-sky-950 *:data-[slot=alert-description]:text-sky-800/80 dark:border-sky-800/70 dark:bg-sky-950/40 dark:text-sky-50 dark:*:data-[slot=alert-description]:text-sky-100/75 [&>svg]:text-sky-700 dark:[&>svg]:text-sky-300\",\n        destructive:\n          \"bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current\",\n        warning:\n          \"border-amber-200 bg-amber-50 text-amber-950 *:data-[slot=alert-description]:text-stone-600 dark:border-amber-800/70 dark:bg-amber-950/40 dark:text-amber-50 dark:*:data-[slot=alert-description]:text-amber-100/70 [&>svg]:text-amber-900 dark:[&>svg]:text-amber-200\",\n      },\n    },\n    defaultVariants: {\n      appearance: \"default\",\n    },\n  }\n);\n\nconst componentThemeClassName =\n  \"[--ic-background:#ffffff] [--ic-foreground:#111111] [--ic-primary:#111111] [--ic-secondary:#646b75] [--ic-surface-border:#e9edf2] [--ic-border:#e3e7ec] [--ic-card:#ffffff] [--ic-card-foreground:#111111] [--ic-muted:#f5f7fa] [--ic-muted-foreground:#6d7480] [--ic-accent:#f3f5f8] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--ic-accent-foreground:#111111] [--ic-input:#e3e7ec] [--ic-ring:rgba(17,17,17,0.16)] [--ic-destructive:#dc2626] [--ic-paper:#fcfcfd] [--ic-popover-foreground:#111111] [--ic-brand:#0ea5e9] [--ic-brand-soft:#bae6fd] [--ic-shadow-soft:0_18px_38px_-24px_rgba(15,23,42,0.35)] [--ic-chart-1:oklch(0.52_0.19_254)] [--ic-chart-2:oklch(0.74_0.11_232)] [--ic-chart-3:oklch(0.42_0.16_262)] [--ic-chart-4:oklch(0.84_0.07_228)] [--ic-chart-5:oklch(0.62_0.14_240)] [--color-background:var(--ic-background)] [--color-foreground:var(--ic-foreground)] [--color-primary:var(--ic-primary)] [--color-secondary:var(--ic-secondary)] [--color-border:var(--ic-border)] [--color-card:var(--ic-card)] [--color-card-foreground:var(--ic-card-foreground)] [--color-muted:var(--ic-muted)] [--color-muted-foreground:var(--ic-muted-foreground)] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--color-input:var(--ic-input)] [--color-ring:var(--ic-ring)] [--color-destructive:var(--ic-destructive)] [--color-paper:var(--ic-paper)] [--color-popover-foreground:var(--ic-popover-foreground)] [--color-brand:var(--ic-brand)] [--color-brand-soft:var(--ic-brand-soft)] [--color-chart-1:var(--ic-chart-1)] [--color-chart-2:var(--ic-chart-2)] [--color-chart-3:var(--ic-chart-3)] [--color-chart-4:var(--ic-chart-4)] [--color-chart-5:var(--ic-chart-5)] dark:[--ic-background:#111111] dark:[--ic-foreground:#f6f3ec] dark:[--ic-primary:#f6f3ec] dark:[--ic-secondary:#cbc6bb] dark:[--ic-surface-border:#2a2a25] dark:[--ic-border:#2b2a25] dark:[--ic-card:#111111] dark:[--ic-card-foreground:#f6f3ec] dark:[--ic-muted:#171716] dark:[--ic-muted-foreground:#9a958a] dark:[--ic-accent:#1a1a18] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] dark:[--ic-accent-foreground:#f6f3ec] dark:[--ic-input:#2b2a25] dark:[--ic-ring:rgba(246,243,236,0.18)] dark:[--ic-destructive:#f87171] dark:[--ic-paper:#171716] dark:[--ic-popover-foreground:#f6f3ec] dark:[--ic-brand:#38bdf8] dark:[--ic-brand-soft:#0c4a6e] dark:[--ic-shadow-soft:0_20px_44px_-28px_rgba(0,0,0,0.6)] dark:[--ic-chart-1:oklch(0.68_0.17_250)] dark:[--ic-chart-2:oklch(0.82_0.09_225)] dark:[--ic-chart-3:oklch(0.58_0.15_260)] dark:[--ic-chart-4:oklch(0.75_0.12_235)] dark:[--ic-chart-5:oklch(0.88_0.06_220)]\";\n\nexport type AlertPosition =\n  | \"top-left\"\n  | \"top-center\"\n  | \"top-right\"\n  | \"bottom-left\"\n  | \"bottom-center\"\n  | \"bottom-right\";\n\nexport type AlertVariant = \"inline\" | \"toast\";\n\nexport type AlertSize = \"sm\" | \"md\" | \"lg\" | \"xl\";\n\nexport const DEFAULT_ALERT_SIZE: AlertSize = \"md\";\n\nexport type AlertAppearance = NonNullable<\n  VariantProps<typeof alertVariants>[\"appearance\"]\n>;\n\nconst ALERT_SIZE_CLASS: Record<AlertSize, string> = {\n  sm: \"max-w-[320px]\",\n  md: \"max-w-[400px]\",\n  lg: \"max-w-[480px]\",\n  xl: \"max-w-[560px]\",\n};\n\nconst ALERT_TOAST_SIZE_CLASS: Record<AlertSize, string> = {\n  sm: \"max-w-full sm:max-w-[320px]\",\n  md: \"max-w-full sm:max-w-[400px]\",\n  lg: \"max-w-full sm:max-w-[480px]\",\n  xl: \"max-w-full sm:max-w-[560px]\",\n};\n\nconst ALERT_TOAST_STACK_GAP = 12;\nconst ALERT_TOAST_STACK_HEIGHT = 72;\n\ntype ToastRegistration = {\n  id: string;\n  position: AlertPosition;\n};\n\nlet alertToastRegistry: ToastRegistration[] = [];\nconst alertToastListeners = new Set<() => void>();\n\nfunction notifyAlertToastListeners() {\n  for (const listener of alertToastListeners) {\n    listener();\n  }\n}\n\nfunction registerAlertToast(id: string, position: AlertPosition) {\n  alertToastRegistry = [...alertToastRegistry, { id, position }];\n  notifyAlertToastListeners();\n}\n\nfunction unregisterAlertToast(id: string) {\n  alertToastRegistry = alertToastRegistry.filter((entry) => entry.id !== id);\n  notifyAlertToastListeners();\n}\n\nfunction subscribeAlertToasts(listener: () => void) {\n  alertToastListeners.add(listener);\n\n  return () => {\n    alertToastListeners.delete(listener);\n  };\n}\n\nexport function getAlertToastStackIndex(\n  id: string,\n  position: AlertPosition,\n  registry: ToastRegistration[] = alertToastRegistry\n): number {\n  const sameCorner = registry.filter((entry) => entry.position === position);\n  const index = sameCorner.findIndex((entry) => entry.id === id);\n\n  return index < 0 ? 0 : index;\n}\n\nexport function resolveAlertToastStackOffset({\n  position,\n  stackIndex,\n}: {\n  position: AlertPosition;\n  stackIndex: number;\n}): CSSProperties {\n  if (stackIndex <= 0) {\n    return {};\n  }\n\n  const offset =\n    stackIndex * (ALERT_TOAST_STACK_HEIGHT + ALERT_TOAST_STACK_GAP);\n\n  if (position.startsWith(\"top\")) {\n    return { top: `calc(1rem + ${offset}px)` };\n  }\n\n  return { bottom: `calc(1rem + ${offset}px)` };\n}\n\nexport function resolveAlertWidth({\n  size = DEFAULT_ALERT_SIZE,\n  width,\n  variant,\n}: {\n  size?: AlertSize;\n  width?: string | number;\n  variant: AlertVariant;\n}): { className?: string; style?: CSSProperties } {\n  if (width !== undefined && width !== \"\") {\n    const maxWidth =\n      typeof width === \"number\"\n        ? width > 0\n          ? `${width}px`\n          : undefined\n        : width.trim();\n\n    if (!maxWidth) {\n      return {\n        className:\n          variant === \"toast\"\n            ? ALERT_TOAST_SIZE_CLASS[size]\n            : ALERT_SIZE_CLASS[size],\n      };\n    }\n\n    return {\n      className: \"max-w-full\",\n      style: { maxWidth },\n    };\n  }\n\n  return {\n    className:\n      variant === \"toast\"\n        ? ALERT_TOAST_SIZE_CLASS[size]\n        : ALERT_SIZE_CLASS[size],\n  };\n}\n\ntype AlertContextValue = {\n  descriptionId: string;\n  hasIcon: boolean;\n  reduceMotion: boolean;\n  titleId: string;\n  titleLines: AlertTitleLines;\n};\n\nexport type AlertTitleLines = 1 | 2 | 3 | \"none\";\n\nconst AlertContext = createContext<AlertContextValue | null>(null);\n\nfunction getComponentDisplayName(type: unknown) {\n  if (typeof type === \"function\" || typeof type === \"object\") {\n    return (type as { displayName?: string }).displayName;\n  }\n\n  return undefined;\n}\n\ntype MotionDivProps = ComponentPropsWithoutRef<typeof motion.div>;\n\nexport interface AlertProps\n  extends Omit<MotionDivProps, \"title\">,\n    VariantProps<typeof alertVariants> {\n  children?: ReactNode;\n  /** Leading graphic (e.g. a Lucide icon). You control markup and sizing. */\n  icon?: ReactNode;\n  title?: ReactNode;\n  message?: ReactNode;\n  /** Optional action row rendered below the message. */\n  action?: ReactNode;\n  dismissible?: boolean;\n  /** Preset max width. Defaults to md (400px). */\n  size?: AlertSize;\n  /** Custom max width. Pass a CSS length such as \"28rem\" or a pixel number. Overrides size when set. */\n  width?: string | number;\n  /** Explicitly choose inline flow or viewport toast behavior. */\n  variant?: AlertVariant;\n  position?: AlertPosition;\n  /** Auto-dismiss after this many milliseconds. Defaults to 5 000. Pass 0 to disable. */\n  timeout?: number;\n  /** Controlled visibility. Pair with onOpenChange for parent-managed alerts. */\n  open?: boolean;\n  /** Initial visibility when uncontrolled. Defaults to true. */\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  /** Max title lines before truncation. Defaults to 1. Pass \"none\" to allow wrapping. */\n  titleLines?: AlertTitleLines;\n  onDismiss?: () => void;\n}\n\nexport interface AlertTitleProps extends MotionDivProps {\n  children?: ReactNode;\n}\n\nexport interface AlertDescriptionProps extends MotionDivProps {\n  children?: ReactNode;\n}\n\nconst DEFAULT_TOAST_POSITION: AlertPosition = \"top-right\";\n\n/**\n * Mobile-first: every positioned alert sits at the top of the viewport,\n * spanning the full width minus safe margins (inset-x-4). At the sm\n * breakpoint the requested corner takes over.\n */\nconst positionClasses: Record<AlertPosition, string> = {\n  \"top-left\": \"fixed top-4 inset-x-4 sm:inset-x-auto sm:left-4 sm:right-auto\",\n  \"top-center\":\n    \"fixed top-4 inset-x-4 sm:inset-x-auto sm:left-1/2 sm:-translate-x-1/2\",\n  \"top-right\": \"fixed top-4 inset-x-4 sm:inset-x-auto sm:left-auto sm:right-4\",\n  \"bottom-left\":\n    \"fixed top-4 inset-x-4 sm:inset-x-auto sm:top-auto sm:bottom-4 sm:left-4 sm:right-auto\",\n  \"bottom-center\":\n    \"fixed top-4 inset-x-4 sm:inset-x-auto sm:top-auto sm:bottom-4 sm:left-1/2 sm:-translate-x-1/2\",\n  \"bottom-right\":\n    \"fixed top-4 inset-x-4 sm:inset-x-auto sm:top-auto sm:bottom-4 sm:left-auto sm:right-4\",\n};\n\nexport interface AlertActionProps extends MotionDivProps {\n  children?: ReactNode;\n}\n\nfunction getAlertTitleLineClampClass(titleLines: AlertTitleLines) {\n  switch (titleLines) {\n    case \"none\":\n      return undefined;\n    case 2:\n      return \"line-clamp-2\";\n    case 3:\n      return \"line-clamp-3\";\n    default:\n      return \"line-clamp-1\";\n  }\n}\n\n/** Vertical travel only — kept small so motion reads as a drift, not a snap. */\nfunction getAlertMotionOffset(position?: AlertPosition): { y: number } {\n  if (!position) {\n    return { y: 6 };\n  }\n\n  return position.startsWith(\"top\") ? { y: -8 } : { y: 8 };\n}\n\nconst FLUID_EASE: [number, number, number, number] = [0.22, 1, 0.36, 1];\nconst FLUID_EXIT_EASE: [number, number, number, number] = [0.4, 0, 0.2, 1];\n\nfunction getReducedMotionContainerVariants(): Variants {\n  return {\n    hidden: { opacity: 0 },\n    visible: {\n      opacity: 1,\n      transition: { duration: 0.15 },\n    },\n    exit: {\n      opacity: 0,\n      transition: { duration: 0.1 },\n    },\n  };\n}\n\nfunction getContainerVariants(\n  position?: AlertPosition,\n  reduceMotion = false\n): Variants {\n  if (reduceMotion) {\n    return getReducedMotionContainerVariants();\n  }\n\n  const { y: dy } = getAlertMotionOffset(position);\n\n  return {\n    hidden: {\n      opacity: 0,\n      y: dy,\n      scale: 0.982,\n      filter: \"blur(3px)\",\n    },\n    visible: {\n      opacity: 1,\n      y: 0,\n      scale: 1,\n      filter: \"blur(0px)\",\n      transition: {\n        opacity: { duration: 0.42, ease: FLUID_EASE },\n        y: { duration: 0.48, ease: FLUID_EASE },\n        scale: { duration: 0.48, ease: FLUID_EASE },\n        filter: { duration: 0.52, ease: FLUID_EASE },\n        staggerChildren: 0.055,\n        delayChildren: 0.12,\n      },\n    },\n    exit: {\n      opacity: 0,\n      y: dy * 0.55,\n      scale: 0.986,\n      filter: \"blur(2px)\",\n      transition: {\n        opacity: { duration: 0.3, ease: FLUID_EXIT_EASE },\n        y: { duration: 0.34, ease: FLUID_EXIT_EASE },\n        scale: { duration: 0.32, ease: FLUID_EXIT_EASE },\n        filter: { duration: 0.28, ease: FLUID_EXIT_EASE },\n      },\n    },\n  };\n}\n\n/** Text drifts in softly while the container handles exit motion. */\nconst childVariants: Variants = {\n  hidden: { opacity: 0, y: 4 },\n  visible: {\n    opacity: 1,\n    y: 0,\n    transition: { duration: 0.38, ease: FLUID_EASE },\n  },\n};\n\nconst reducedMotionChildVariants: Variants = {\n  hidden: { opacity: 0 },\n  visible: { opacity: 1, transition: { duration: 0.15 } },\n};\n\n/** Icon eases in with the same fluid curve as the container. */\nconst iconVariants: Variants = {\n  hidden: { opacity: 0, scale: 0.92 },\n  visible: {\n    opacity: 1,\n    scale: 1,\n    transition: { duration: 0.44, ease: FLUID_EASE, delay: 0.1 },\n  },\n};\n\nconst reducedMotionIconVariants: Variants = {\n  hidden: { opacity: 0 },\n  visible: { opacity: 1, transition: { duration: 0.15 } },\n};\n\nfunction getChildVariants(reduceMotion: boolean) {\n  return reduceMotion ? reducedMotionChildVariants : childVariants;\n}\n\nfunction getIconVariants(reduceMotion: boolean) {\n  return reduceMotion ? reducedMotionIconVariants : iconVariants;\n}\n\nconst AlertTitle = forwardRef<HTMLDivElement, AlertTitleProps>(\n  ({ children, className, id, ...props }, ref) => {\n    const context = useContext(AlertContext);\n    const titleLineClampClass = getAlertTitleLineClampClass(\n      context?.titleLines ?? 1\n    );\n\n    return (\n      <motion.div\n        className={cn(\n          \"min-h-4 font-medium tracking-tight\",\n          titleLineClampClass,\n          context?.hasIcon ? \"col-start-2\" : \"col-start-1\",\n          className\n        )}\n        data-slot=\"alert-title\"\n        id={id ?? context?.titleId}\n        ref={ref}\n        variants={getChildVariants(context?.reduceMotion ?? false)}\n        {...props}\n      >\n        {children}\n      </motion.div>\n    );\n  }\n);\n\nAlertTitle.displayName = \"AlertTitle\";\n\nconst AlertDescription = forwardRef<HTMLDivElement, AlertDescriptionProps>(\n  ({ children, className, id, ...props }, ref) => {\n    const context = useContext(AlertContext);\n\n    return (\n      <motion.div\n        className={cn(\n          \"grid justify-items-start gap-1 text-muted-foreground text-sm [&_p]:leading-relaxed\",\n          context?.hasIcon ? \"col-start-2\" : \"col-start-1\",\n          className\n        )}\n        data-slot=\"alert-description\"\n        id={id ?? context?.descriptionId}\n        ref={ref}\n        variants={getChildVariants(context?.reduceMotion ?? false)}\n        {...props}\n      >\n        {children}\n      </motion.div>\n    );\n  }\n);\n\nAlertDescription.displayName = \"AlertDescription\";\n\nconst AlertAction = forwardRef<HTMLDivElement, AlertActionProps>(\n  ({ children, className, ...props }, ref) => {\n    const context = useContext(AlertContext);\n\n    return (\n      <motion.div\n        className={cn(\n          \"mt-2 flex flex-wrap items-center gap-2\",\n          context?.hasIcon ? \"col-start-2\" : \"col-start-1\",\n          className\n        )}\n        data-slot=\"alert-action\"\n        ref={ref}\n        variants={getChildVariants(context?.reduceMotion ?? false)}\n        {...props}\n      >\n        {children}\n      </motion.div>\n    );\n  }\n);\n\nAlertAction.displayName = \"AlertAction\";\n\nfunction isAlertTitleChild(child: ReactNode) {\n  if (!isValidElement(child)) {\n    return false;\n  }\n\n  return (\n    child.type === AlertTitle ||\n    getComponentDisplayName(child.type) === AlertTitle.displayName\n  );\n}\n\nfunction isAlertDescriptionChild(child: ReactNode) {\n  if (!isValidElement(child)) {\n    return false;\n  }\n\n  return (\n    child.type === AlertDescription ||\n    getComponentDisplayName(child.type) === AlertDescription.displayName\n  );\n}\n\nfunction isAlertActionChild(child: ReactNode) {\n  if (!isValidElement(child)) {\n    return false;\n  }\n\n  return (\n    child.type === AlertAction ||\n    getComponentDisplayName(child.type) === AlertAction.displayName\n  );\n}\n\nfunction splitAlertChildren(children: ReactNode) {\n  const childArray = Children.toArray(children);\n  const [firstChild, ...remainingChildren] = childArray;\n\n  let leadingIcon: ReactNode = null;\n  let contentChildren = childArray;\n\n  if (\n    firstChild &&\n    !isAlertTitleChild(firstChild) &&\n    !isAlertDescriptionChild(firstChild) &&\n    !isAlertActionChild(firstChild)\n  ) {\n    leadingIcon = firstChild;\n    contentChildren = remainingChildren;\n  }\n\n  const actionChildren = contentChildren.filter(isAlertActionChild);\n  const textChildren = contentChildren.filter(\n    (child) => !isAlertActionChild(child)\n  );\n\n  return {\n    actionChildren,\n    contentChildren: textChildren,\n    leadingIcon,\n  };\n}\n\nfunction useAlertLifecycle({\n  defaultOpen = true,\n  onDismiss,\n  onOpenChange,\n  open,\n  showDismiss,\n  timeout,\n}: {\n  defaultOpen?: boolean;\n  onDismiss?: () => void;\n  onOpenChange?: (open: boolean) => void;\n  open?: boolean;\n  showDismiss: boolean;\n  timeout: number;\n}) {\n  const isControlled = open !== undefined;\n  const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);\n  const visible = isControlled ? open : uncontrolledOpen;\n  const [mounted, setMounted] = useState(false);\n  const [isHovered, setIsHovered] = useState(false);\n  const [isFocusWithin, setIsFocusWithin] = useState(false);\n  const [isDocumentHidden, setIsDocumentHidden] = useState(false);\n  const timeoutIdRef = useRef<number | null>(null);\n  const remainingTimeRef = useRef(timeout);\n  const timerStartedAtRef = useRef<number | null>(null);\n  const dismissalRequestedRef = useRef(false);\n  const previousTimeoutRef = useRef(timeout);\n\n  const setVisible = useCallback(\n    (nextOpen: boolean) => {\n      if (!isControlled) {\n        setUncontrolledOpen(nextOpen);\n      }\n\n      onOpenChange?.(nextOpen);\n    },\n    [isControlled, onOpenChange]\n  );\n\n  useEffect(() => setMounted(true), []);\n\n  useEffect(() => {\n    if (open === true) {\n      dismissalRequestedRef.current = false;\n    }\n  }, [open]);\n\n  useEffect(() => {\n    const handleVisibilityChange = () => {\n      setIsDocumentHidden(document.hidden);\n    };\n\n    handleVisibilityChange();\n    document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n\n    return () => {\n      document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n    };\n  }, []);\n\n  const clearTimer = useCallback(() => {\n    if (timeoutIdRef.current !== null) {\n      window.clearTimeout(timeoutIdRef.current);\n      timeoutIdRef.current = null;\n    }\n\n    timerStartedAtRef.current = null;\n  }, []);\n\n  const requestDismiss = useCallback(() => {\n    if (dismissalRequestedRef.current) {\n      return;\n    }\n\n    dismissalRequestedRef.current = true;\n    clearTimer();\n    setVisible(false);\n  }, [clearTimer, setVisible]);\n\n  const pauseTimer = useCallback(() => {\n    if (timeoutIdRef.current === null || timerStartedAtRef.current === null) {\n      return;\n    }\n\n    const elapsed = Date.now() - timerStartedAtRef.current;\n    remainingTimeRef.current = Math.max(0, remainingTimeRef.current - elapsed);\n    clearTimer();\n  }, [clearTimer]);\n\n  const startTimer = useCallback(() => {\n    if (remainingTimeRef.current <= 0) {\n      if (timeoutIdRef.current === null) {\n        requestDismiss();\n      }\n\n      return;\n    }\n\n    if (timeoutIdRef.current !== null) {\n      return;\n    }\n\n    timerStartedAtRef.current = Date.now();\n    timeoutIdRef.current = window.setTimeout(() => {\n      clearTimer();\n      requestDismiss();\n    }, remainingTimeRef.current);\n  }, [clearTimer, requestDismiss]);\n\n  useEffect(() => {\n    if (previousTimeoutRef.current === timeout) {\n      return;\n    }\n\n    previousTimeoutRef.current = timeout;\n    clearTimer();\n    remainingTimeRef.current = timeout;\n\n    if (\n      visible &&\n      timeout > 0 &&\n      !(isHovered || isFocusWithin || isDocumentHidden)\n    ) {\n      startTimer();\n    }\n  }, [\n    clearTimer,\n    isDocumentHidden,\n    isFocusWithin,\n    isHovered,\n    startTimer,\n    timeout,\n    visible,\n  ]);\n\n  useEffect(() => {\n    if (!visible || timeout <= 0) {\n      clearTimer();\n      return;\n    }\n\n    if (isHovered || isFocusWithin || isDocumentHidden) {\n      pauseTimer();\n      return;\n    }\n\n    startTimer();\n  }, [\n    clearTimer,\n    isDocumentHidden,\n    isFocusWithin,\n    isHovered,\n    pauseTimer,\n    startTimer,\n    timeout,\n    visible,\n  ]);\n\n  useEffect(() => clearTimer, [clearTimer]);\n\n  useEffect(() => {\n    if (!(visible && showDismiss)) {\n      return;\n    }\n\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (event.key !== \"Escape\") {\n        return;\n      }\n\n      event.preventDefault();\n      requestDismiss();\n    };\n\n    document.addEventListener(\"keydown\", handleKeyDown);\n\n    return () => {\n      document.removeEventListener(\"keydown\", handleKeyDown);\n    };\n  }, [requestDismiss, showDismiss, visible]);\n\n  const handleBlurCapture = (event: FocusEvent<HTMLDivElement>) => {\n    const nextFocusedNode = event.relatedTarget;\n\n    if (\n      nextFocusedNode instanceof Node &&\n      event.currentTarget.contains(nextFocusedNode)\n    ) {\n      return;\n    }\n\n    setIsFocusWithin(false);\n  };\n\n  const handleExitComplete = useCallback(() => {\n    if (!dismissalRequestedRef.current) {\n      return;\n    }\n\n    onDismiss?.();\n  }, [onDismiss]);\n\n  return {\n    handleBlurCapture,\n    handleExitComplete,\n    mounted,\n    requestDismiss,\n    setIsFocusWithin,\n    setIsHovered,\n    visible,\n  };\n}\n\nfunction AlertIcon({ children }: { children: ReactNode }) {\n  const context = useContext(AlertContext);\n\n  if (!children) {\n    return null;\n  }\n\n  return (\n    <motion.div\n      className=\"col-start-1 row-start-1 shrink-0 [&_svg]:size-4 [&_svg]:translate-y-0.5 [&_svg]:text-current\"\n      variants={getIconVariants(context?.reduceMotion ?? false)}\n    >\n      {children}\n    </motion.div>\n  );\n}\n\nfunction getDismissButtonClasses(\n  appearance?: VariantProps<typeof alertVariants>[\"appearance\"]\n) {\n  switch (appearance) {\n    case \"warning\":\n      return \"text-amber-900/40 hover:text-amber-900 dark:text-amber-100/45 dark:hover:text-amber-100\";\n    case \"success\":\n      return \"text-emerald-800/40 hover:text-emerald-900 dark:text-emerald-100/45 dark:hover:text-emerald-100\";\n    case \"info\":\n      return \"text-sky-800/40 hover:text-sky-900 dark:text-sky-100/45 dark:hover:text-sky-100\";\n    case \"destructive\":\n      return \"text-destructive/40 hover:text-destructive\";\n    default:\n      return \"text-foreground/35 hover:text-foreground/70\";\n  }\n}\n\nfunction AlertDismissButton({\n  appearance,\n  className,\n  onDismiss,\n  show,\n}: {\n  appearance?: VariantProps<typeof alertVariants>[\"appearance\"];\n  className?: string;\n  onDismiss: () => void;\n  show: boolean;\n}) {\n  const context = useContext(AlertContext);\n\n  if (!show) {\n    return null;\n  }\n\n  return (\n    <motion.button\n      aria-label=\"Dismiss alert\"\n      className={cn(\n        \"supports-[corner-shape:squircle]:corner-squircle relative -my-2 -mr-2 inline-flex size-10 shrink-0 items-center justify-center self-start rounded-lg transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring supports-[corner-shape:squircle]:rounded-[11px]\",\n        getDismissButtonClasses(appearance),\n        className\n      )}\n      data-slot=\"alert-dismiss\"\n      onClick={onDismiss}\n      type=\"button\"\n      variants={getChildVariants(context?.reduceMotion ?? false)}\n    >\n      <svg fill=\"none\" height=\"14\" viewBox=\"0 0 14 14\" width=\"14\">\n        <path\n          d=\"M3.5 3.5L10.5 10.5M10.5 3.5L3.5 10.5\"\n          stroke=\"currentColor\"\n          strokeLinecap=\"round\"\n          strokeWidth=\"1.2\"\n        />\n      </svg>\n    </motion.button>\n  );\n}\n\nfunction getAlertGridClasses({\n  hasIcon,\n  showDismiss,\n}: {\n  hasIcon: boolean;\n  showDismiss: boolean;\n}) {\n  if (hasIcon) {\n    return showDismiss\n      ? \"grid-cols-[calc(var(--spacing)*4)_1fr_auto] gap-x-3\"\n      : \"grid-cols-[calc(var(--spacing)*4)_1fr] gap-x-3\";\n  }\n\n  return showDismiss ? \"grid-cols-[1fr_auto]\" : undefined;\n}\n\nfunction AlertCard({\n  appearance,\n  className,\n  content,\n  descriptionId,\n  hasDescription,\n  hasIcon,\n  hasTitle,\n  icon,\n  motionProps,\n  onBlurCapture,\n  onDismiss,\n  onExitComplete,\n  onFocusCapture,\n  onPointerEnter,\n  onPointerLeave,\n  position,\n  reduceMotion,\n  ref,\n  showDismiss,\n  size,\n  stackOffsetStyle,\n  titleId,\n  titleLines,\n  variants,\n  variant,\n  visible,\n  width,\n}: {\n  appearance?: VariantProps<typeof alertVariants>[\"appearance\"];\n  className?: MotionDivProps[\"className\"];\n  content: ReactNode;\n  descriptionId: string;\n  hasDescription: boolean;\n  hasIcon: boolean;\n  hasTitle: boolean;\n  icon: ReactNode;\n  motionProps: MotionDivProps;\n  onBlurCapture: (event: FocusEvent<HTMLDivElement>) => void;\n  onDismiss: () => void;\n  onExitComplete: () => void;\n  onFocusCapture: () => void;\n  onPointerEnter: () => void;\n  onPointerLeave: () => void;\n  position?: AlertPosition;\n  reduceMotion: boolean;\n  ref?: Ref<HTMLDivElement>;\n  showDismiss: boolean;\n  size?: AlertSize;\n  stackOffsetStyle?: CSSProperties;\n  titleId: string;\n  titleLines: AlertTitleLines;\n  variants: Variants;\n  variant: AlertVariant;\n  visible: boolean;\n  width?: string | number;\n}) {\n  const dismissColumnClass = hasIcon\n    ? \"col-start-3 row-start-1\"\n    : \"col-start-2 row-start-1\";\n  const alertWidth = resolveAlertWidth({\n    size: size ?? DEFAULT_ALERT_SIZE,\n    width,\n    variant,\n  });\n\n  return (\n    <AnimatePresence onExitComplete={onExitComplete}>\n      {visible ? (\n        <AlertContext.Provider\n          value={{\n            descriptionId,\n            hasIcon,\n            reduceMotion,\n            titleId,\n            titleLines,\n          }}\n        >\n          <motion.div\n            {...motionProps}\n            animate=\"visible\"\n            aria-atomic={true}\n            aria-describedby={hasDescription ? descriptionId : undefined}\n            aria-labelledby={hasTitle ? titleId : undefined}\n            aria-live={variant === \"toast\" ? \"polite\" : undefined}\n            className={cn(\n              componentThemeClassName,\n              alertVariants({ appearance }),\n              getAlertGridClasses({ hasIcon, showDismiss }),\n              reduceMotion\n                ? \"will-change-[opacity]\"\n                : \"transform-gpu will-change-[transform,opacity,filter]\",\n              alertWidth.className,\n              position ? positionClasses[position] : undefined,\n              position && \"z-[300] shadow-[var(--ic-shadow-soft)]\",\n              className\n            )}\n            data-slot=\"alert\"\n            exit=\"exit\"\n            initial=\"hidden\"\n            onBlurCapture={onBlurCapture}\n            onFocusCapture={onFocusCapture}\n            onPointerEnter={onPointerEnter}\n            onPointerLeave={onPointerLeave}\n            ref={ref}\n            role={variant === \"toast\" ? \"status\" : \"alert\"}\n            style={{\n              ...motionProps.style,\n              ...alertWidth.style,\n              ...stackOffsetStyle,\n            }}\n            variants={variants}\n          >\n            {icon ? <AlertIcon>{icon}</AlertIcon> : null}\n            {content}\n            <AlertDismissButton\n              appearance={appearance}\n              className={dismissColumnClass}\n              onDismiss={onDismiss}\n              show={showDismiss}\n            />\n          </motion.div>\n        </AlertContext.Provider>\n      ) : null}\n    </AnimatePresence>\n  );\n}\n\nexport function getAlertConfig({\n  dismissible,\n  hasCompoundChildren,\n  position,\n  timeout,\n  variant,\n}: {\n  dismissible?: boolean;\n  hasCompoundChildren: boolean;\n  position?: AlertPosition;\n  timeout?: number;\n  variant?: AlertVariant;\n}) {\n  const resolvedVariant: AlertVariant = position\n    ? \"toast\"\n    : (variant ?? \"inline\");\n\n  return {\n    resolvedDismissible:\n      dismissible ?? (hasCompoundChildren ? resolvedVariant === \"toast\" : true),\n    resolvedPosition:\n      resolvedVariant === \"toast\"\n        ? (position ?? DEFAULT_TOAST_POSITION)\n        : undefined,\n    resolvedTimeout:\n      timeout ??\n      (hasCompoundChildren && resolvedVariant === \"inline\" ? 0 : 5000),\n    resolvedVariant,\n  };\n}\n\nfunction getLegacyAlertContent({\n  action,\n  message,\n  title,\n}: {\n  action?: ReactNode;\n  message?: ReactNode;\n  title?: ReactNode;\n}) {\n  return (\n    <>\n      {title ? <AlertTitle>{title}</AlertTitle> : null}\n      {message ? <AlertDescription>{message}</AlertDescription> : null}\n      {action ? <AlertAction>{action}</AlertAction> : null}\n    </>\n  );\n}\n\nfunction getAlertContent({\n  action,\n  children,\n  hasCompoundChildren,\n  icon,\n  message,\n  title,\n}: {\n  action?: ReactNode;\n  children?: ReactNode;\n  hasCompoundChildren: boolean;\n  icon?: ReactNode;\n  message?: ReactNode;\n  title?: ReactNode;\n}) {\n  if (hasCompoundChildren) {\n    const { actionChildren, contentChildren, leadingIcon } =\n      splitAlertChildren(children);\n    const renderedIcon = icon ?? leadingIcon;\n\n    return {\n      hasDescription: contentChildren.some(isAlertDescriptionChild),\n      hasIcon: Boolean(renderedIcon),\n      hasTitle: contentChildren.some(isAlertTitleChild),\n      renderedContent: (\n        <>\n          {contentChildren}\n          {actionChildren}\n          {action ? <AlertAction>{action}</AlertAction> : null}\n        </>\n      ),\n      renderedIcon,\n    };\n  }\n\n  const renderedIcon = icon;\n\n  return {\n    hasDescription: Boolean(message),\n    hasIcon: Boolean(renderedIcon),\n    hasTitle: Boolean(title),\n    renderedContent: getLegacyAlertContent({\n      action,\n      message,\n      title,\n    }),\n    renderedIcon,\n  };\n}\n\nexport const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(\n  {\n    appearance,\n    children,\n    className,\n    icon,\n    title,\n    message,\n    action,\n    dismissible,\n    size = DEFAULT_ALERT_SIZE,\n    width,\n    variant,\n    position,\n    timeout,\n    open,\n    defaultOpen = true,\n    onOpenChange,\n    titleLines = 1,\n    onDismiss,\n    ...props\n  },\n  ref\n) {\n  const hasCompoundChildren = Children.count(children) > 0;\n  const reduceMotion = useReducedMotion() ?? false;\n  const toastId = useId();\n  const [toastStackIndex, setToastStackIndex] = useState(0);\n  const {\n    resolvedDismissible,\n    resolvedPosition,\n    resolvedTimeout,\n    resolvedVariant,\n  } = getAlertConfig({\n    dismissible,\n    hasCompoundChildren,\n    position,\n    timeout,\n    variant,\n  });\n  const {\n    handleBlurCapture,\n    handleExitComplete,\n    mounted,\n    requestDismiss,\n    setIsFocusWithin,\n    setIsHovered,\n    visible,\n  } = useAlertLifecycle({\n    defaultOpen,\n    onDismiss,\n    onOpenChange,\n    open,\n    showDismiss: resolvedDismissible,\n    timeout: resolvedTimeout,\n  });\n\n  useEffect(() => {\n    if (resolvedVariant !== \"toast\" || !resolvedPosition || !visible) {\n      return;\n    }\n\n    registerAlertToast(toastId, resolvedPosition);\n\n    const updateStackIndex = () => {\n      setToastStackIndex(getAlertToastStackIndex(toastId, resolvedPosition));\n    };\n\n    const unsubscribe = subscribeAlertToasts(updateStackIndex);\n    updateStackIndex();\n\n    return () => {\n      unsubscribe();\n    };\n  }, [resolvedPosition, resolvedVariant, toastId, visible]);\n\n  const handleToastExitComplete = useCallback(() => {\n    if (resolvedVariant === \"toast\" && resolvedPosition) {\n      unregisterAlertToast(toastId);\n    }\n\n    handleExitComplete();\n  }, [handleExitComplete, resolvedPosition, resolvedVariant, toastId]);\n\n  useEffect(\n    () => () => {\n      unregisterAlertToast(toastId);\n    },\n    [toastId]\n  );\n\n  const titleId = useId();\n  const messageId = useId();\n\n  const containerVariants = getContainerVariants(\n    resolvedPosition,\n    reduceMotion\n  );\n  const { hasDescription, hasTitle, renderedContent, renderedIcon } =\n    getAlertContent({\n      action,\n      children,\n      hasCompoundChildren,\n      icon,\n      message,\n      title,\n    });\n\n  const stackOffsetStyle =\n    resolvedVariant === \"toast\" && resolvedPosition\n      ? resolveAlertToastStackOffset({\n          position: resolvedPosition,\n          stackIndex: toastStackIndex,\n        })\n      : undefined;\n\n  const card = (\n    <AlertCard\n      appearance={appearance}\n      className={className}\n      content={renderedContent}\n      descriptionId={messageId}\n      hasDescription={hasDescription}\n      hasIcon={Boolean(renderedIcon)}\n      hasTitle={hasTitle}\n      icon={renderedIcon}\n      motionProps={props}\n      onBlurCapture={handleBlurCapture}\n      onDismiss={requestDismiss}\n      onExitComplete={handleToastExitComplete}\n      onFocusCapture={() => setIsFocusWithin(true)}\n      onPointerEnter={() => setIsHovered(true)}\n      onPointerLeave={() => setIsHovered(false)}\n      position={resolvedPosition}\n      reduceMotion={reduceMotion}\n      ref={ref}\n      showDismiss={resolvedDismissible}\n      size={size}\n      stackOffsetStyle={stackOffsetStyle}\n      titleId={titleId}\n      titleLines={titleLines}\n      variant={resolvedVariant}\n      variants={containerVariants}\n      visible={visible}\n      width={width}\n    />\n  );\n\n  /**\n   * When a position is given, portal the alert to document.body so it\n   * escapes any ancestor CSS transform (Motion scale/y), which\n   * would otherwise make `position: fixed` relative to the transformed\n   * element instead of the viewport.\n   */\n  if (resolvedVariant === \"toast\") {\n    if (!mounted || typeof document === \"undefined\") {\n      return null;\n    }\n\n    return createPortal(card, document.body);\n  }\n\n  return card;\n});\n\nAlert.displayName = \"Alert\";\n\nexport default Alert;\nexport { AlertAction, AlertDescription, AlertTitle };\n",
      "type": "registry:ui"
    }
  ],
  "title": "Alert",
  "description": "Compound Alert, AlertTitle, and AlertDescription parts with optional icons, legacy prop support, polite live announcements, and inline or toast behavior."
}
