{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "alert",
  "title": "Alert",
  "description": "An in-page message with a title, description, action and dismiss, in neutral, success, warning and error tones and four layouts.",
  "dependencies": [
    "lucide-react",
    "motion",
    "radix-ui"
  ],
  "registryDependencies": [
    "utils",
    "https://miseui.junsu4621.workers.dev/r/motion.json"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/alert.tsx",
      "content": "import type * as React from \"react\"\nimport { useState } from \"react\"\nimport { AnimatePresence, motion, useIsPresent, useReducedMotion } from \"motion/react\"\nimport { SPRING_DEFAULT } from \"@/registry/miseui/lib/motion\"\nimport {\n  AlertAction,\n  DismissButton,\n  DISMISS_ROOM_CLASS,\n  ROOT_CLASS,\n  ToneChip,\n  ToneGlyph,\n  ToneRail,\n  TONE_CLASS,\n  type AlertActionProps,\n  type AlertTone,\n  type AlertVariant,\n} from \"@/registry/miseui/ui/alert-parts\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\ntype AlertEffect = \"materialize\"\n\nconst INSTANT = { duration: 0 } as const\nconst ENTER_SCALE = 0.96\nconst ENTER_BLUR = \"blur(8px)\"\n\ntype AlertProps = Omit<React.ComponentProps<\"div\">, \"title\" | \"children\"> & {\n  /** `soft` a tinted block, `outline` a card with the action beside it, `banner` a solid page-wide strip, `side` a rail. */\n  variant?: AlertVariant\n  /** `success` uses the theme accent. `warning` and `neutral` are told apart by their glyphs, as the theme has no warning colour. */\n  tone?: AlertTone\n  /** `materialize` arrives out of focus and springs its height on dismiss, so the page below closes up instead of jumping. */\n  effect?: AlertEffect\n  title?: React.ReactNode\n  description?: React.ReactNode\n  /** Usually an AlertAction or a small Button. */\n  action?: React.ReactNode\n  /** Replaces the tone's icon. */\n  icon?: React.ReactNode\n  /** Shows a close button. */\n  dismissible?: boolean\n  /** Name of the close button for assistive tech. */\n  dismissLabel?: string\n  /** Hands visibility to you. */\n  open?: boolean\n  defaultOpen?: boolean\n  onDismiss?: () => void\n}\n\ntype AlertSurfaceProps = Omit<AlertProps, \"open\" | \"defaultOpen\" | \"dismissible\" | \"onDismiss\"> & {\n  close?: () => void\n}\n\ntype AlertCopyProps = Pick<AlertProps, \"title\" | \"description\" | \"action\" | \"icon\"> & {\n  variant: AlertVariant\n  tone: AlertTone\n}\n\nfunction AlertLead({ variant, tone, icon }: Pick<AlertCopyProps, \"variant\" | \"tone\" | \"icon\">) {\n  if (variant === \"side\") return <ToneRail tone={tone} />\n  if (icon) return icon\n  if (variant === \"banner\") return <ToneGlyph tone={tone} />\n  return <ToneChip tone={tone} size={variant === \"soft\" ? \"sm\" : \"lg\"} />\n}\n\nfunction AlertCopy({ variant, tone, title, description, action, icon }: AlertCopyProps) {\n  if (variant === \"banner\") {\n    return (\n      <p data-part=\"line\" className=\"min-w-0 flex-1 basis-40 text-sm\">\n        {title ? <span className=\"font-medium\">{title} </span> : null}\n        {description ? <span className=\"opacity-85\">{description}</span> : null}\n      </p>\n    )\n  }\n  return (\n    <div data-part=\"text\" className={cn(\"grid min-w-0 flex-1 gap-1\", variant === \"outline\" && \"basis-32\")}>\n      {title ? (\n        <p className=\"flex items-start gap-2 text-sm font-medium [&>svg]:mt-0.5\">\n          {variant === \"side\" ? (icon ?? <ToneGlyph tone={tone} />) : null}\n          {title}\n        </p>\n      ) : null}\n      {description ? <div className=\"text-[13px] leading-5 text-muted-foreground\">{description}</div> : null}\n      {action && variant !== \"outline\" ? <div data-part=\"action\">{action}</div> : null}\n    </div>\n  )\n}\n\nfunction AlertSurface({\n  variant = \"soft\",\n  tone = \"neutral\",\n  effect,\n  title,\n  description,\n  action,\n  icon,\n  close,\n  dismissLabel = \"Dismiss\",\n  className,\n  ...props\n}: AlertSurfaceProps) {\n  // AnimatePresence keeps the old element alive during the exit, so its button would still fire a second onDismiss.\n  const isPresent = useIsPresent()\n  const actionInside = variant === \"soft\" || variant === \"side\"\n  return (\n    <div\n      data-slot=\"alert\"\n      data-variant={variant}\n      data-tone={tone}\n      data-effect={effect}\n      // Only errors interrupt a screen reader; everything else waits its turn.\n      role={tone === \"error\" ? \"alert\" : \"status\"}\n      inert={!isPresent}\n      className={cn(ROOT_CLASS[variant], TONE_CLASS[variant][tone], close && DISMISS_ROOM_CLASS[variant], className)}\n      {...props}\n    >\n      <AlertLead variant={variant} tone={tone} icon={icon} />\n      <AlertCopy {...{ variant, tone, title, description, action, icon }} />\n      {action && !actionInside ? (\n        <div data-part=\"action\" className=\"flex shrink-0 flex-wrap items-center gap-2\">\n          {action}\n        </div>\n      ) : null}\n      {close ? (\n        <DismissButton\n          label={dismissLabel}\n          variant={variant}\n          onClick={() => {\n            if (isPresent) close()\n          }}\n        />\n      ) : null}\n    </div>\n  )\n}\n\nfunction AlertPresence({ children }: { children: React.ReactNode }) {\n  const reduceMotion = useReducedMotion() ?? false\n  const hidden = reduceMotion ? { opacity: 0 } : { opacity: 0, scale: ENTER_SCALE, filter: ENTER_BLUR }\n  const shown = reduceMotion ? { opacity: 1 } : { opacity: 1, scale: 1, filter: \"blur(0px)\" }\n  const transition = reduceMotion ? INSTANT : SPRING_DEFAULT\n  return (\n    <motion.div\n      data-slot=\"alert-presence\"\n      className=\"overflow-hidden\"\n      initial={{ height: 0 }}\n      animate={{ height: \"auto\" }}\n      exit={{ height: 0 }}\n      transition={transition}\n    >\n      <motion.div initial={hidden} animate={shown} exit={hidden} transition={transition}>\n        {children}\n      </motion.div>\n    </motion.div>\n  )\n}\n\nfunction Alert({ open, defaultOpen = true, dismissible = false, onDismiss, ...props }: AlertProps) {\n  const [uncontrolled, setUncontrolled] = useState(defaultOpen)\n  const visible = open ?? uncontrolled\n  const close = () => {\n    setUncontrolled(false)\n    onDismiss?.()\n  }\n  const surface = <AlertSurface {...props} close={dismissible ? close : undefined} />\n  if (props.effect !== \"materialize\") return visible ? surface : null\n  return <AnimatePresence>{visible ? <AlertPresence key=\"alert\">{surface}</AlertPresence> : null}</AnimatePresence>\n}\n\nexport { Alert, AlertAction, type AlertProps, type AlertActionProps, type AlertVariant, type AlertTone, type AlertEffect }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/alert-parts.tsx",
      "content": "import type * as React from \"react\"\nimport { Slot } from \"radix-ui\"\nimport { Check, Info, TriangleAlert, X } from \"lucide-react\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\ntype AlertVariant = \"soft\" | \"outline\" | \"banner\" | \"side\"\ntype AlertTone = \"neutral\" | \"success\" | \"warning\" | \"error\"\n\nconst ROOT_CLASS: Record<AlertVariant, string> = {\n  soft: \"flex items-start gap-3 rounded-lg p-4\",\n  outline: \"flex flex-wrap items-center gap-x-4 gap-y-3 rounded-lg border bg-background p-4\",\n  banner: \"flex w-full flex-wrap items-center gap-x-3 gap-y-1 px-4 py-2\",\n  side: \"relative flex items-start gap-3 rounded-md bg-card py-3 pr-3 pl-6\",\n}\n\nconst TONE_CLASS: Record<AlertVariant, Record<AlertTone, string>> = {\n  soft: {\n    neutral: \"bg-muted\",\n    // Fixed hues, not the accent: with an amber accent a success in `mise` would read as a warning.\n    success: \"bg-emerald-500/10\",\n    warning: \"bg-amber-400/15\",\n    error: \"bg-destructive/10 dark:bg-destructive/15\",\n  },\n  outline: {\n    neutral: \"\",\n    success: \"border-emerald-500/50\",\n    warning: \"border-amber-500/60\",\n    error: \"border-destructive/50\",\n  },\n  banner: {\n    neutral: \"bg-secondary text-secondary-foreground\",\n    success: \"bg-emerald-600 text-white dark:bg-emerald-500 dark:text-emerald-950\",\n    warning: \"bg-amber-400 text-amber-950\",\n    // The dark theme lightens the error colour past what white text can sit on.\n    error: \"bg-destructive text-white dark:text-background\",\n  },\n  side: { neutral: \"\", success: \"\", warning: \"\", error: \"\" },\n}\n\nconst CHIP_TONE_CLASS: Record<AlertTone, string> = {\n  neutral: \"bg-foreground/10 text-foreground\",\n  success: \"bg-emerald-600 text-white dark:bg-emerald-500 dark:text-emerald-950\",\n  warning: \"bg-amber-400 text-amber-950\",\n  error: \"bg-destructive text-white dark:text-background\",\n}\n\nconst RAIL_TONE_CLASS: Record<AlertTone, string> = {\n  neutral: \"bg-foreground/20\",\n  success: \"bg-emerald-500\",\n  warning: \"bg-amber-400\",\n  error: \"bg-destructive\",\n}\n\n/** CircleAlert would draw a circle inside the chip, so the error glyph is only its exclamation mark. */\nfunction ExclamationMark(props: React.ComponentProps<\"svg\">) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" {...props}>\n      <path d=\"M12 6v8\" />\n      <path d=\"M12 18h.01\" />\n    </svg>\n  )\n}\n\nconst TONE_GLYPH: Record<AlertTone, React.ComponentType<{ className?: string }>> = {\n  neutral: Info,\n  success: Check,\n  warning: TriangleAlert,\n  error: ExclamationMark,\n}\n\nfunction ToneChip({ tone, size }: { tone: AlertTone; size: \"sm\" | \"lg\" }) {\n  const Glyph = TONE_GLYPH[tone]\n  return (\n    <span\n      aria-hidden=\"true\"\n      data-part=\"chip\"\n      className={cn(\n        \"inline-flex shrink-0 items-center justify-center rounded-full\",\n        size === \"sm\" ? \"size-5\" : \"size-8\",\n        CHIP_TONE_CLASS[tone],\n      )}\n    >\n      <Glyph className={size === \"sm\" ? \"size-3\" : \"size-4\"} />\n    </span>\n  )\n}\n\nfunction ToneGlyph({ tone }: { tone: AlertTone }) {\n  const Glyph = tone === \"error\" ? TriangleAlert : TONE_GLYPH[tone]\n  return <Glyph aria-hidden=\"true\" data-part=\"glyph\" className=\"size-4 shrink-0\" />\n}\n\nfunction ToneRail({ tone }: { tone: AlertTone }) {\n  return (\n    <span\n      aria-hidden=\"true\"\n      data-part=\"rail\"\n      className={cn(\"absolute inset-y-2 left-2 w-1 rounded-full\", RAIL_TONE_CLASS[tone])}\n    />\n  )\n}\n\n/** Pinned on the layouts that wrap, or a narrow screen would drop it to the next row, away from the corner. */\nconst DISMISS_PLACEMENT_CLASS: Record<AlertVariant, string> = {\n  soft: \"-m-1\",\n  outline: \"absolute top-2 right-2\",\n  banner: \"absolute top-0.5 right-2 pointer-coarse:top-0\",\n  side: \"-m-1\",\n}\n\n/** Keeps the text clear of a pinned close button. */\nconst DISMISS_ROOM_CLASS: Record<AlertVariant, string> = {\n  soft: \"\",\n  outline: \"relative pr-12\",\n  banner: \"relative pr-12\",\n  side: \"\",\n}\n\ninterface DismissButtonProps {\n  label: string\n  variant: AlertVariant\n  onClick: () => void\n}\n\nfunction DismissButton({ label, variant, onClick }: DismissButtonProps) {\n  return (\n    <button\n      type=\"button\"\n      aria-label={label}\n      data-slot=\"alert-dismiss\"\n      onClick={onClick}\n      className={cn(\n        \"inline-flex size-8 shrink-0 items-center justify-center rounded-sm outline-none pointer-coarse:size-10\",\n        \"transition-colors duration-(--duration-fast) ease-(--ease-mise) focus-visible:ring-[3px]\",\n        DISMISS_PLACEMENT_CLASS[variant],\n        variant === \"banner\"\n          ? \"text-current hover:bg-current/10 focus-visible:ring-current/50\"\n          : \"text-muted-foreground hover:bg-foreground/5 hover:text-foreground focus-visible:ring-ring/50\",\n      )}\n    >\n      <X aria-hidden=\"true\" className=\"size-4\" />\n    </button>\n  )\n}\n\ntype AlertActionProps = React.ComponentProps<\"button\"> & {\n  /** Renders your own link or router link instead of a button. */\n  asChild?: boolean\n}\n\n/** A text action that takes its colour from the alert, so it holds up on every surface including the solid banner. */\nfunction AlertAction({ asChild = false, className, ...props }: AlertActionProps) {\n  const Comp = asChild ? Slot.Root : \"button\"\n  return (\n    <Comp\n      data-slot=\"alert-action\"\n      type={asChild ? undefined : \"button\"}\n      className={cn(\n        \"inline-flex h-8 items-center rounded-sm text-sm font-medium underline decoration-current/40 underline-offset-4 outline-none pointer-coarse:h-10\",\n        \"transition-[text-decoration-color] duration-(--duration-fast) ease-(--ease-mise) hover:decoration-current\",\n        \"focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50\",\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  AlertAction,\n  DismissButton,\n  DISMISS_ROOM_CLASS,\n  ROOT_CLASS,\n  ToneChip,\n  ToneGlyph,\n  ToneRail,\n  TONE_CLASS,\n  type AlertActionProps,\n  type AlertTone,\n  type AlertVariant,\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "feedback"
  ],
  "type": "registry:ui"
}