{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "alert-motion",
  "title": "Motion alert",
  "description": "Styled alert: the Motion set. 5 decorative alert variations (SlideAlert, PopAlert, FadeAlert, ShakeAlert, RevealAlert) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2",
    "lucide-react@^1.23.0"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/alert-motion.tsx",
      "content": "\"use client\"\n\nimport type * as React from \"react\"\nimport { CircleAlert, CircleCheck, Info, TriangleAlert } from \"lucide-react\"\nimport { motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Motion alert family: 5 decorative alert surfaces. The surface is THE SAME in\n   every variant (a calm card + a thin tone-tinted border); the whole difference\n   is in the ENTRANCE animation - how the alert arrives on the page: sliding in\n   from the side, springing, settling, shaking, or unfolding from the left. The\n   animation runs only on MOUNT, never loops; every keyframe is written out\n   literally, so there is no source of time or randomness and every render\n   produces the same result (deterministic). Under reduced-motion there is NO\n   transform or clip, everything falls back to a plain fade. Color comes ONLY\n   from semantic tokens, via alpha color-mix or the tailwind opacity modifier.\n   Anatomy (Berkay's decision): the icon marks ONLY the title line - the icon and\n   the title share line 1, and the description starts BELOW the icon and takes\n   the full width. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nexport type StyledTone = \"info\" | \"success\" | \"warning\" | \"danger\"\n\nconst pad: Record<StyledSize, string> = {\n  sm: \"p-3 text-sm\",\n  md: \"p-4 text-sm\",\n  lg: \"p-4 text-base\",\n  xl: \"p-5 text-base\",\n}\n\n/* Grid: icon column plus content. The icon is a direct child, so the\n   [&>svg] / [&>i] pair (lucide <svg> and remixicon <i> compatibility) lives\n   here. */\nconst base =\n  \"relative grid w-full grid-cols-[calc(var(--spacing)*4)_1fr] items-start gap-x-2 gap-y-0.5 rounded-lg [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:translate-y-0.5 [&>i]:block [&>i]:size-4 [&>i]:shrink-0 [&>i]:translate-y-0.5 [&>i]:text-base [&>i]:leading-none\"\n\n/* Tone -> varsayilan lucide ikon. */\nconst toneIcon: Record<StyledTone, React.ReactNode> = {\n  info: <Info />,\n  success: <CircleCheck />,\n  warning: <TriangleAlert />,\n  danger: <CircleAlert />,\n}\n\n/* Icon colour comes from the root; the [&>i] pair is required for remixicon\n   compatibility. */\nconst iconTone: Record<StyledTone, string> = {\n  info: \"[&>svg]:text-info [&>i]:text-info\",\n  success: \"[&>svg]:text-success [&>i]:text-success\",\n  warning: \"[&>svg]:text-warning-soft-foreground [&>i]:text-warning-soft-foreground\",\n  danger: \"[&>svg]:text-danger [&>i]:text-danger\",\n}\n\n/* The surface is the same in every variant: a calm card plus a tone-tinted border. */\nconst surfaceTone: Record<StyledTone, string> = {\n  info: \"border border-info/30 bg-card text-card-foreground shadow-sm\",\n  success: \"border border-success/30 bg-card text-card-foreground shadow-sm\",\n  warning: \"border border-warning/40 bg-card text-card-foreground shadow-sm\",\n  danger: \"border border-danger/30 bg-card text-card-foreground shadow-sm\",\n}\n\ntype AlertProps = Omit<React.ComponentProps<\"div\">, \"title\"> & {\n  size?: StyledSize\n  tone?: StyledTone\n  title?: React.ReactNode\n  icon?: React.ReactNode\n}\n\n/* motion.div carries its own animation props that clash with React.ComponentProps<\"div\">, so we strip the clashing ones. */\ntype MotionSafeProps = Omit<\n  React.ComponentProps<\"div\">,\n  | \"title\"\n  | \"onAnimationStart\"\n  | \"onAnimationEnd\"\n  | \"onAnimationIteration\"\n  | \"onDrag\"\n  | \"onDragStart\"\n  | \"onDragEnd\"\n  | \"style\"\n>\n\n/* The spec types are derived from motion.div's own prop types - that way no extra type import is needed and they stay exactly in step with what framer expects. */\ntype MotionDivProps = React.ComponentProps<typeof motion.div>\n\ninterface MotionSpec {\n  initial: MotionDivProps[\"initial\"]\n  animate: MotionDivProps[\"animate\"]\n  transition: MotionDivProps[\"transition\"]\n}\n\n/* Shared shell: only the entry animation changes. When reduced=true the caller already sends the fade spec. */\nfunction Frame({\n  size,\n  tone,\n  title,\n  icon,\n  children,\n  className,\n  spec,\n  ...props\n}: MotionSafeProps & {\n  size: StyledSize\n  tone: StyledTone\n  title?: React.ReactNode\n  icon?: React.ReactNode\n  spec: MotionSpec\n}) {\n  return (\n    <motion.div\n      data-slot=\"styled-alert\"\n      data-tone={tone}\n      role=\"alert\"\n      className={cn(base, pad[size], iconTone[tone], surfaceTone[tone], className)}\n      initial={spec.initial}\n      animate={spec.animate}\n      transition={spec.transition}\n      {...props}\n    >\n      {icon ?? toneIcon[tone]}\n      <div\n        data-slot=\"styled-alert-title\"\n        className=\"col-start-2 min-h-4 font-medium tracking-tight\"\n      >\n        {title}\n      </div>\n      <div\n        data-slot=\"styled-alert-description\"\n        className=\"col-span-2 col-start-1 text-sm opacity-90 [&_p]:leading-relaxed\"\n      >\n        {children}\n      </div>\n    </motion.div>\n  )\n}\n\n/* The shared reduced-motion fallback: opacity only. */\nconst fadeSpec: MotionSpec = {\n  initial: { opacity: 0 },\n  animate: { opacity: 1 },\n  transition: { duration: 0.2, ease: \"easeOut\" },\n}\n\n/* Slide: soldan iceri kayarak girer. */\nexport function SlideAlert({\n  className,\n  size = \"md\",\n  tone = \"info\",\n  title = \"Heads up\",\n  children = \"The alert slides in from the left as it mounts.\",\n  icon,\n  ...props\n}: AlertProps) {\n  const reduce = useReducedMotion()\n  const spec: MotionSpec = reduce\n    ? fadeSpec\n    : {\n        initial: { opacity: 0, x: -28 },\n        animate: { opacity: 1, x: 0 },\n        transition: { duration: 0.35, ease: \"easeOut\" },\n      }\n  return (\n    <Frame size={size} tone={tone} title={title} icon={icon} spec={spec} className={className} {...props}>\n      {children}\n    </Frame>\n  )\n}\n\n/* Pop: yaylanarak buyur. Spring transition tipi sabit (\"spring\" as const). */\nexport function PopAlert({\n  className,\n  size = \"md\",\n  tone = \"info\",\n  title = \"Heads up\",\n  children = \"The alert springs up to full scale as it mounts.\",\n  icon,\n  ...props\n}: AlertProps) {\n  const reduce = useReducedMotion()\n  const spec: MotionSpec = reduce\n    ? fadeSpec\n    : {\n        initial: { opacity: 0, scale: 0.88 },\n        animate: { opacity: 1, scale: 1 },\n        transition: { type: \"spring\" as const, stiffness: 420, damping: 22 },\n      }\n  return (\n    <Frame size={size} tone={tone} title={title} icon={icon} spec={spec} className={className} {...props}>\n      {children}\n    </Frame>\n  )\n}\n\n/* Fade: en sakin giris - kucuk bir yukselme + sonumlenme. */\nexport function FadeAlert({\n  className,\n  size = \"md\",\n  tone = \"info\",\n  title = \"Heads up\",\n  children = \"The quietest entrance: a small rise and a fade.\",\n  icon,\n  ...props\n}: AlertProps) {\n  const reduce = useReducedMotion()\n  const spec: MotionSpec = reduce\n    ? fadeSpec\n    : {\n        initial: { opacity: 0, y: 10 },\n        animate: { opacity: 1, y: 0 },\n        transition: { duration: 0.4, ease: \"easeOut\" },\n      }\n  return (\n    <Frame size={size} tone={tone} title={title} icon={icon} spec={spec} className={className} {...props}>\n      {children}\n    </Frame>\n  )\n}\n\n/* Shake: a short shudder on entry. For error and attention states; the keyframe sequence is fixed, nothing random. */\nexport function ShakeAlert({\n  className,\n  size = \"md\",\n  tone = \"danger\",\n  title = \"Heads up\",\n  children = \"A short shake on entry for errors that need attention.\",\n  icon,\n  ...props\n}: AlertProps) {\n  const reduce = useReducedMotion()\n  const spec: MotionSpec = reduce\n    ? fadeSpec\n    : {\n        initial: { opacity: 0, x: 0 },\n        animate: { opacity: 1, x: [0, -9, 9, -6, 6, -3, 0] },\n        transition: { duration: 0.5, ease: \"easeOut\" },\n      }\n  return (\n    <Frame size={size} tone={tone} title={title} icon={icon} spec={spec} className={className} {...props}>\n      {children}\n    </Frame>\n  )\n}\n\n/* Reveal: it opens left to right with clip-path. The surface stays in place, only\n   the visible area grows. */\nexport function RevealAlert({\n  className,\n  size = \"md\",\n  tone = \"info\",\n  title = \"Heads up\",\n  children = \"A clip-path wipe opens the alert from left to right.\",\n  icon,\n  ...props\n}: AlertProps) {\n  const reduce = useReducedMotion()\n  const spec: MotionSpec = reduce\n    ? fadeSpec\n    : {\n        initial: { opacity: 1, clipPath: \"inset(0 100% 0 0)\" },\n        animate: { opacity: 1, clipPath: \"inset(0 0% 0 0)\" },\n        transition: { duration: 0.45, ease: \"easeOut\" },\n      }\n  return (\n    <Frame size={size} tone={tone} title={title} icon={icon} spec={spec} className={className} {...props}>\n      {children}\n    </Frame>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/alert-motion.tsx"
    }
  ],
  "categories": [
    "styled",
    "alert"
  ],
  "type": "registry:component"
}