{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "glass-alert",
  "title": "Glass Alert",
  "description": "A short message set in tinted glass, with four tones and an optional dismiss.",
  "dependencies": [
    "clsx"
  ],
  "registryDependencies": [
    "@glassyui/glass-provider"
  ],
  "files": [
    {
      "path": "registry/glassyui/glass-alert.tsx",
      "content": "'use client';\n\nimport clsx from 'clsx';\nimport { forwardRef, useCallback, useEffect, useRef, useState } from 'react';\nimport type { HTMLAttributes, ReactNode, TransitionEvent } from 'react';\nimport { useOptionalPreferences } from './glass-provider';\n\nexport type GlassAlertTone = 'info' | 'success' | 'warning' | 'danger';\n\nexport type GlassAlertProps = Omit<HTMLAttributes<HTMLDivElement>, 'title'> & {\n  /** Names the close button. Defaults to \"Dismiss\". */\n  dismissLabel?: string;\n  /** Replaces the tone's icon. Pass `false` for none. */\n  icon?: ReactNode | false;\n  /**\n   * Shows a close button. The alert folds away, then this runs: remove the alert here, and give\n   * each alert a stable `key` so a later one never inherits the fold.\n   */\n  onDismiss?: () => void;\n  title?: ReactNode;\n  tone?: GlassAlertTone;\n};\n\nconst ICON_PATHS: Record<GlassAlertTone, string[]> = {\n  info: ['M10 17a7 7 0 1 0 0-14 7 7 0 0 0 0 14Z', 'M10 9.2v4.1', 'M10 6.7h.01'],\n  success: ['M10 17a7 7 0 1 0 0-14 7 7 0 0 0 0 14Z', 'M6.9 10.2l2.2 2.2 4-4.4'],\n  warning: ['M10 3.6 17 16H3L10 3.6Z', 'M10 8.4v3.4', 'M10 14h.01'],\n  danger: ['M10 17a7 7 0 1 0 0-14 7 7 0 0 0 0 14Z', 'M7.6 7.6l4.8 4.8', 'M12.4 7.6l-4.8 4.8'],\n};\n\nfunction ToneIcon({ tone }: { tone: GlassAlertTone }) {\n  return (\n    <svg aria-hidden viewBox=\"0 0 20 20\">\n      {ICON_PATHS[tone].map((path) => (\n        <path\n          d={path}\n          fill=\"none\"\n          key={path}\n          stroke=\"currentColor\"\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n          strokeWidth=\"1.6\"\n        />\n      ))}\n    </svg>\n  );\n}\n\n// How long an alert takes to fold away. The stylesheet's transition matches it.\nconst FOLD_MS = 240;\n\n/**\n * A short message set in tinted glass. Warnings and errors interrupt screen readers; the other\n * tones wait their turn. Override with `role`.\n */\nexport const GlassAlert = forwardRef<HTMLDivElement, GlassAlertProps>(\n  (\n    {\n      children,\n      className,\n      dismissLabel = 'Dismiss',\n      icon,\n      onDismiss,\n      role,\n      title,\n      tone = 'info',\n      ...props\n    },\n    forwardedRef,\n  ) => {\n    const { preferences } = useOptionalPreferences();\n    const [folding, setFolding] = useState(false);\n\n    // The fold ends in the handler the caller has now, not the one they had at the click, and it\n    // ends once.\n    const dismissed = useRef(onDismiss);\n    const finished = useRef(false);\n\n    useEffect(() => {\n      dismissed.current = onDismiss;\n    }, [onDismiss]);\n\n    const finish = useCallback(() => {\n      if (finished.current) return;\n\n      finished.current = true;\n      dismissed.current?.();\n    }, []);\n\n    // A hidden tab runs no transitions, and the alert must still go. Unmounting cancels it, so a\n    // caller who removed the alert first is not told about it afterwards.\n    useEffect(() => {\n      if (!folding) return;\n\n      const timer = window.setTimeout(finish, FOLD_MS + 120);\n\n      return () => window.clearTimeout(timer);\n    }, [finish, folding]);\n\n    const dismiss = () => {\n      if (folding) return;\n\n      if (\n        preferences.reduceMotion ||\n        window.matchMedia?.('(prefers-reduced-motion: reduce)').matches\n      ) {\n        onDismiss?.();\n\n        return;\n      }\n\n      setFolding(true);\n    };\n\n    // The alert sits in a row that shrinks to nothing, so what is below it slides up instead of\n    // jumping, and its own padding and margin go with it.\n    const folded = (event: TransitionEvent<HTMLDivElement>) => {\n      if (event.target === event.currentTarget && event.propertyName === 'grid-template-rows') {\n        finish();\n      }\n    };\n\n    return (\n      <div\n        className=\"lg-alert-fold\"\n        data-state={folding ? 'folding' : undefined}\n        onTransitionEnd={folded}\n      >\n        <div className=\"lg-alert-fold-inner\">\n          <div\n            {...props}\n            ref={forwardedRef}\n            className={clsx('lg-alert', className)}\n            data-tone={tone}\n            role={role ?? (tone === 'warning' || tone === 'danger' ? 'alert' : 'status')}\n          >\n            {icon === false ? null : (\n              <span aria-hidden className=\"lg-alert-icon\">\n                {icon ?? <ToneIcon tone={tone} />}\n              </span>\n            )}\n            <div className=\"lg-alert-body\">\n              {title ? <p className=\"lg-alert-title\">{title}</p> : null}\n              {children ? <div className=\"lg-alert-description\">{children}</div> : null}\n            </div>\n            {onDismiss ? (\n              <button\n                aria-label={dismissLabel}\n                className=\"lg-alert-close\"\n                onClick={dismiss}\n                type=\"button\"\n              >\n                <svg aria-hidden viewBox=\"0 0 20 20\">\n                  <path\n                    d=\"M5.5 5.5l9 9m0-9l-9 9\"\n                    fill=\"none\"\n                    stroke=\"currentColor\"\n                    strokeLinecap=\"round\"\n                    strokeWidth=\"1.6\"\n                  />\n                </svg>\n              </button>\n            ) : null}\n          </div>\n        </div>\n      </div>\n    );\n  },\n);\n\nGlassAlert.displayName = 'GlassAlert';\n",
      "type": "registry:ui",
      "target": "components/glassyui/glass-alert.tsx"
    }
  ],
  "meta": {
    "sourceModules": [
      "alert"
    ]
  },
  "type": "registry:ui"
}