import React from 'react'; import { createPortal } from 'react-dom'; import { Icon } from '@/components/icon/Icon'; import { Button } from '@/components/ui/button'; import { OpenChamberLogo } from '@/components/ui/OpenChamberLogo'; import { useI18n } from '@/lib/i18n'; export const MobileQrScannerOverlay: React.FC<{ onCancel: () => void }> = ({ onCancel }) => { const { t } = useI18n(); const overlayRef = React.useRef(null); React.useLayoutEffect(() => { const htmlBackground = { value: document.documentElement.style.getPropertyValue('background-color'), priority: document.documentElement.style.getPropertyPriority('background-color'), }; const bodyBackground = { value: document.body.style.getPropertyValue('background-color'), priority: document.body.style.getPropertyPriority('background-color'), }; // The app's reduced-transparency theme deliberately uses an !important // background. Use an inline important color while CameraX is behind the // WebView; the CSS minifier collapses `background: transparent` in a way // that does not reset that important background color on Android WebView. document.documentElement.style.setProperty('background-color', 'rgba(0, 0, 0, 0)', 'important'); document.body.style.setProperty('background-color', 'rgba(0, 0, 0, 0)', 'important'); // startScan() places CameraX behind the WebView. OpenChamber has several // independent portal roots, so hiding only #root (or relying on inherited // visibility) can leave a sheet/sidebar painted over the preview. Opacity on // each top-level sibling is composited for its whole subtree and cannot be // overridden by descendants. const hidden = new Map(); const hideBodySibling = (node: Node) => { if (!(node instanceof HTMLElement) || node === overlayRef.current || hidden.has(node)) return; hidden.set(node, { opacity: node.style.opacity, pointerEvents: node.style.pointerEvents }); node.style.setProperty('opacity', '0'); node.style.setProperty('pointer-events', 'none'); }; Array.from(document.body.children).forEach(hideBodySibling); const observer = new MutationObserver((records) => { records.forEach((record) => record.addedNodes.forEach(hideBodySibling)); }); observer.observe(document.body, { childList: true }); return () => { observer.disconnect(); hidden.forEach((previous, element) => { element.style.opacity = previous.opacity; element.style.pointerEvents = previous.pointerEvents; }); if (htmlBackground.value) { document.documentElement.style.setProperty('background-color', htmlBackground.value, htmlBackground.priority); } else { document.documentElement.style.removeProperty('background-color'); } if (bodyBackground.value) { document.body.style.setProperty('background-color', bodyBackground.value, bodyBackground.priority); } else { document.body.style.removeProperty('background-color'); } }; }, []); React.useEffect(() => { const handleVisibilityChange = () => { if (document.visibilityState === 'hidden') onCancel(); }; document.addEventListener('visibilitychange', handleVisibilityChange); return () => document.removeEventListener('visibilitychange', handleVisibilityChange); }, [onCancel]); return createPortal(

{t('mobile.connect.welcome.scanHint')}

, document.body, ); }; export const MobileQrConnectionLoading: React.FC = () => { const { t } = useI18n(); return createPortal(
{t('mobile.connect.connecting')}
, document.body, ); };