{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "retro-toast",
  "title": "RetroToast",
  "description": "A Mac OS 9 styled custom toast system with mini-window appearance, striped title bar, beveled close box, and auto-dismiss via useRetroToast hook.",
  "registryDependencies": [
    "https://nostalgia-ui.com/r/nostalgia-theme.json"
  ],
  "files": [
    {
      "path": "registry/new-york/ui/retro-toast.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* ------------------------------------------------------------------ */\n/*  Toast Types                                                        */\n/* ------------------------------------------------------------------ */\n\ntype ToastEntry = {\n  id: string\n  title?: React.ReactNode\n  description?: React.ReactNode\n  duration?: number\n}\n\ntype ToastActions = {\n  toast: (opts: Omit<ToastEntry, \"id\">) => string\n  dismiss: (id: string) => void\n}\n\ntype ToastContextValue = ToastActions & {\n  /**\n   * Snapshot of the current toasts (read at access time). Reading it does not\n   * subscribe the component to updates — use `useRetroToasts()` for that.\n   */\n  readonly toasts: ToastEntry[]\n}\n\n/* ------------------------------------------------------------------ */\n/*  Context                                                            */\n/* ------------------------------------------------------------------ */\n\n// Actions are stable, so components that only fire toasts never re-render\n// when the toast list changes. The list lives in its own context.\nconst RetroToastContext = React.createContext<ToastContextValue | null>(null)\nconst RetroToastStateContext = React.createContext<ToastEntry[] | null>(null)\n\nfunction useRetroToast(): ToastContextValue {\n  const ctx = React.useContext(RetroToastContext)\n  if (!ctx) {\n    throw new Error(\"useRetroToast must be used within a <RetroToastProvider>\")\n  }\n  return ctx\n}\n\n/** Reactive list of the currently visible toasts. */\nfunction useRetroToasts(): ToastEntry[] {\n  const toasts = React.useContext(RetroToastStateContext)\n  if (!toasts) {\n    throw new Error(\"useRetroToasts must be used within a <RetroToastProvider>\")\n  }\n  return toasts\n}\n\n/* ------------------------------------------------------------------ */\n/*  Provider                                                           */\n/* ------------------------------------------------------------------ */\n\nlet counter = 0\n\n/** setTimeout overflows above this; treat larger durations as persistent. */\nconst MAX_TIMEOUT = 2_147_483_647\n\ntype ToastTimer = {\n  handle: ReturnType<typeof setTimeout> | null\n  remaining: number\n  startedAt: number\n  hovered: boolean\n  focused: boolean\n}\n\nfunction RetroToastProvider({ children }: { children: React.ReactNode }) {\n  const [toasts, setToasts] = React.useState<ToastEntry[]>([])\n  const toastsRef = React.useRef<ToastEntry[]>(toasts)\n  const timersRef = React.useRef(new Map<string, ToastTimer>())\n\n  React.useEffect(() => {\n    toastsRef.current = toasts\n  }, [toasts])\n\n  const clearTimer = React.useCallback((id: string) => {\n    const timer = timersRef.current.get(id)\n    if (timer?.handle) clearTimeout(timer.handle)\n    timersRef.current.delete(id)\n  }, [])\n\n  const dismiss = React.useCallback(\n    (id: string) => {\n      clearTimer(id)\n      setToasts((prev) => prev.filter((t) => t.id !== id))\n    },\n    [clearTimer]\n  )\n\n  const runTimer = React.useCallback(\n    (id: string) => {\n      const timer = timersRef.current.get(id)\n      if (!timer || timer.handle || timer.hovered || timer.focused) return\n      timer.startedAt = Date.now()\n      timer.handle = setTimeout(() => dismiss(id), timer.remaining)\n    },\n    [dismiss]\n  )\n\n  const pauseTimer = React.useCallback(\n    (id: string, reason: \"hovered\" | \"focused\") => {\n      const timer = timersRef.current.get(id)\n      if (!timer) return\n      timer[reason] = true\n      if (timer.handle) {\n        clearTimeout(timer.handle)\n        timer.handle = null\n        timer.remaining = Math.max(\n          0,\n          timer.remaining - (Date.now() - timer.startedAt)\n        )\n      }\n    },\n    []\n  )\n\n  const resumeTimer = React.useCallback(\n    (id: string, reason: \"hovered\" | \"focused\") => {\n      const timer = timersRef.current.get(id)\n      if (!timer) return\n      timer[reason] = false\n      runTimer(id)\n    },\n    [runTimer]\n  )\n\n  const toast = React.useCallback(\n    (opts: Omit<ToastEntry, \"id\">) => {\n      const id = `retro-toast-${++counter}`\n      const duration = opts.duration ?? 5000\n      setToasts((prev) => [...prev, { ...opts, id, duration }])\n      // Non-finite (e.g. Infinity) or huge durations = persistent toast\n      if (Number.isFinite(duration) && duration <= MAX_TIMEOUT) {\n        timersRef.current.set(id, {\n          handle: null,\n          remaining: Math.max(0, duration),\n          startedAt: 0,\n          hovered: false,\n          focused: false,\n        })\n        runTimer(id)\n      }\n      return id\n    },\n    [runTimer]\n  )\n\n  // Clear all pending timers on unmount\n  React.useEffect(() => {\n    const timers = timersRef.current\n    return () => {\n      timers.forEach((timer) => {\n        if (timer.handle) clearTimeout(timer.handle)\n      })\n      timers.clear()\n    }\n  }, [])\n\n  const actions = React.useMemo<ToastContextValue>(\n    () => ({\n      toast,\n      dismiss,\n      get toasts() {\n        return toastsRef.current\n      },\n    }),\n    [toast, dismiss]\n  )\n\n  return (\n    <RetroToastContext.Provider value={actions}>\n      <RetroToastStateContext.Provider value={toasts}>\n        {children}\n        <RetroToastViewport>\n          {toasts.map((t) => (\n            <RetroToast\n              key={t.id}\n              onMouseEnter={() => pauseTimer(t.id, \"hovered\")}\n              onMouseLeave={() => resumeTimer(t.id, \"hovered\")}\n              onFocus={() => pauseTimer(t.id, \"focused\")}\n              onBlur={(event) => {\n                // Ignore focus moving between elements inside the toast\n                if (event.currentTarget.contains(event.relatedTarget as Node | null)) {\n                  return\n                }\n                resumeTimer(t.id, \"focused\")\n              }}\n            >\n              {/* Mini title bar stripe */}\n              <div\n                className=\"os9-stripes mb-[4px] h-[3px] w-full\"\n                style={{\n                  boxShadow:\n                    \"inset 1px 0 0 #eee, inset -1px 0 0 #c5c5c5\",\n                }}\n                aria-hidden\n              />\n\n              <div className=\"flex items-start gap-[6px]\">\n                <div className=\"flex-1 min-w-0\">\n                  {t.title && <RetroToastTitle>{t.title}</RetroToastTitle>}\n                  {t.description && (\n                    <RetroToastDescription>{t.description}</RetroToastDescription>\n                  )}\n                </div>\n                <RetroToastClose onClick={() => dismiss(t.id)} />\n              </div>\n            </RetroToast>\n          ))}\n        </RetroToastViewport>\n      </RetroToastStateContext.Provider>\n    </RetroToastContext.Provider>\n  )\n}\nRetroToastProvider.displayName = \"RetroToastProvider\"\n\n/* ------------------------------------------------------------------ */\n/*  RetroToastViewport                                                 */\n/* ------------------------------------------------------------------ */\n\n/**\n * Persistent live region. It is always mounted so screen readers announce\n * toasts as they are appended to the list.\n */\nfunction RetroToastViewportInner(\n  { className, ...props }: React.HTMLAttributes<HTMLOListElement>,\n  ref: React.ForwardedRef<HTMLOListElement>\n) {\n  return (\n    <div\n      role=\"region\"\n      aria-label=\"Notifications\"\n      className={cn(\n        \"fixed bottom-0 right-0 z-50 p-[16px]\",\n        \"pointer-events-none\",\n        className\n      )}\n    >\n      <ol\n        ref={ref}\n        aria-live=\"polite\"\n        className=\"flex flex-col gap-2 list-none m-0 p-0\"\n        {...props}\n      />\n    </div>\n  )\n}\n\nconst RetroToastViewport = React.forwardRef(RetroToastViewportInner)\nRetroToastViewport.displayName = \"RetroToastViewport\"\n\n/* ------------------------------------------------------------------ */\n/*  RetroToast                                                         */\n/* ------------------------------------------------------------------ */\n\nfunction RetroToastInner(\n  { className, ...props }: React.HTMLAttributes<HTMLLIElement>,\n  ref: React.ForwardedRef<HTMLLIElement>\n) {\n  return (\n    <li\n      ref={ref}\n      className={cn(\n        \"pointer-events-auto w-[300px] p-[8px]\",\n        \"border border-os9-black bg-os9-gray-200\",\n        \"shadow-[2px_2px_0_var(--os9-black),inset_2px_2px_0_rgba(255,255,255,0.6),inset_-2px_-2px_0_rgba(38,38,38,0.4)]\",\n        \"animate-in slide-in-from-right-full fade-in-0 duration-200\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nconst RetroToast = React.forwardRef(RetroToastInner)\nRetroToast.displayName = \"RetroToast\"\n\n/* ------------------------------------------------------------------ */\n/*  RetroToastTitle                                                    */\n/* ------------------------------------------------------------------ */\n\nfunction RetroToastTitleInner(\n  { className, ...props }: React.HTMLAttributes<HTMLDivElement>,\n  ref: React.ForwardedRef<HTMLDivElement>\n) {\n  return (\n    <div\n      ref={ref}\n      className={cn(\n        \"font-[family-name:var(--os9-font-heading)] text-[12px] font-bold leading-[1.2] tracking-[0.42px] text-os9-black\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nconst RetroToastTitle = React.forwardRef(RetroToastTitleInner)\nRetroToastTitle.displayName = \"RetroToastTitle\"\n\n/* ------------------------------------------------------------------ */\n/*  RetroToastDescription                                              */\n/* ------------------------------------------------------------------ */\n\nfunction RetroToastDescriptionInner(\n  { className, ...props }: React.HTMLAttributes<HTMLDivElement>,\n  ref: React.ForwardedRef<HTMLDivElement>\n) {\n  return (\n    <div\n      ref={ref}\n      className={cn(\n        \"font-[family-name:var(--os9-font-sans)] text-[10px] leading-[1.4] mt-[2px] text-os9-black\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nconst RetroToastDescription = React.forwardRef(RetroToastDescriptionInner)\nRetroToastDescription.displayName = \"RetroToastDescription\"\n\n/* ------------------------------------------------------------------ */\n/*  RetroToastClose                                                    */\n/* ------------------------------------------------------------------ */\n\nfunction RetroToastCloseInner(\n  { className, ...props }: React.ButtonHTMLAttributes<HTMLButtonElement>,\n  ref: React.ForwardedRef<HTMLButtonElement>\n) {\n  return (\n    <button\n      ref={ref}\n      type=\"button\"\n      aria-label=\"Close\"\n      className={cn(\n        \"relative size-[13px] shrink-0 cursor-default\",\n        className\n      )}\n      {...props}\n    >\n      {/* Bevel frame */}\n      <span\n        className=\"absolute inset-[2px] border border-os9-black\"\n        style={{\n          backgroundImage:\n            \"linear-gradient(135deg, #9a9a9a 0%, #f1f1f1 100%)\",\n        }}\n      />\n      {/* Inner shadow */}\n      <span\n        className=\"absolute inset-[2px]\"\n        style={{\n          boxShadow:\n            \"inset 1px 1px 0 rgba(255,255,255,0.6), inset -1px -1px 0 rgba(0,0,0,0.15)\",\n        }}\n      />\n      {/* Outer pixel highlights */}\n      <span\n        className=\"absolute inset-0\"\n        style={{\n          boxShadow:\n            \"inset 1px 1px 0 rgba(255,255,255,0.5), inset -1px -1px 0 rgba(0,0,0,0.12)\",\n        }}\n      />\n    </button>\n  )\n}\n\nconst RetroToastClose = React.forwardRef(RetroToastCloseInner)\nRetroToastClose.displayName = \"RetroToastClose\"\n\n/* ------------------------------------------------------------------ */\n/*  Exports                                                            */\n/* ------------------------------------------------------------------ */\n\nexport {\n  RetroToastProvider,\n  RetroToast,\n  RetroToastTitle,\n  RetroToastDescription,\n  RetroToastClose,\n  RetroToastViewport,\n  useRetroToast,\n  useRetroToasts,\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}