{"$schema":"https:\/\/ui.particle.academy\/schema\/registry-item.json","name":"portal","type":"registry:ui","title":"Portal","description":"Portal from react-fancy","package":"react-fancy","dependencies":[],"registryDependencies":[],"files":[{"path":"components\/fancy\/portal\/Portal.tsx","content":"import { createPortal } from \"react-dom\";\r\nimport { useEffect, useRef, useState, type ReactNode } from \"react\";\r\n\r\nexport interface PortalProps {\r\n  children: ReactNode;\r\n  container?: HTMLElement;\r\n}\r\n\r\nexport function Portal({ children, container }: PortalProps) {\r\n  \/\/ Render nothing on the server AND on the client's first (hydration) render,\r\n  \/\/ then portal after mount. Without the `mounted` gate the client would render\r\n  \/\/ the portal immediately while the server rendered null \u2014 a hydration\r\n  \/\/ mismatch (the portal wrapper appears client-only). Deferring to after mount\r\n  \/\/ keeps the first client render identical to the server. SSR-safe for every\r\n  \/\/ Portal consumer (Toast, Modal, Dropdown, Tooltip, Popover, \u2026).\r\n  const [mounted, setMounted] = useState(false);\r\n  useEffect(() => {\r\n    setMounted(true);\r\n  }, []);\r\n\r\n  if (!mounted || typeof document === \"undefined\") return null;\r\n\r\n  const target = container ?? document.body;\r\n\r\n  return createPortal(\r\n    <PortalDarkWrapper>{children}<\/PortalDarkWrapper>,\r\n    target,\r\n  );\r\n}\r\n\r\n\/**\r\n * Wrapper that propagates the `dark` class from <html> into the portal subtree.\r\n * This ensures Tailwind `dark:` utilities work even though Portal renders\r\n * outside the React component tree.\r\n *\/\r\nfunction PortalDarkWrapper({ children }: { children: ReactNode }) {\r\n  const ref = useRef<HTMLDivElement>(null);\r\n\r\n  useEffect(() => {\r\n    const root = document.documentElement;\r\n    const wrapper = ref.current;\r\n    if (!wrapper) return;\r\n\r\n    const sync = () => {\r\n      const isDark =\r\n        root.classList.contains(\"dark\") ||\r\n        root.getAttribute(\"data-theme\") === \"dark\";\r\n      wrapper.classList.toggle(\"dark\", isDark);\r\n    };\r\n\r\n    sync();\r\n\r\n    const observer = new MutationObserver(sync);\r\n    observer.observe(root, {\r\n      attributes: true,\r\n      attributeFilter: [\"class\", \"data-theme\"],\r\n    });\r\n\r\n    return () => observer.disconnect();\r\n  }, []);\r\n\r\n  return (\r\n    <div ref={ref} data-react-fancy-portal=\"\" style={{ display: \"contents\" }}>\r\n      {children}\r\n    <\/div>\r\n  );\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/portal\/Portal.tsx"},{"path":"components\/fancy\/portal\/index.ts","content":"export { Portal } from \".\/Portal\";\r\nexport type { PortalProps } from \".\/Portal\";\r\n","type":"registry:ui","target":"components\/fancy\/portal\/index.ts"}]}