import { Button } from '@fluid-design/fluid-ui'; import { SunIcon } from '@heroicons/react/24/solid'; import * as path from 'path'; import React, { useEffect, useRef, useState } from 'react'; import ReactDOM, { createPortal } from 'react-dom'; import { IoIosContrast } from 'react-icons/io'; import { MdDarkMode, MdFormatTextdirectionLToR, MdFormatTextdirectionRToL, } from 'react-icons/md'; import clsxm from '@/lib/clsxm'; import { useTheme } from '@/store/useTheme'; import windowExists from '@/helpers/window-exists'; export const FunctionalIFrameComponent = ({ children, title, preferences, ...props }: { children: React.ReactNode; title: string; preferences: PreferencesProps[]; }) => { const iframeRef = useRef(null); const [isIframeLoaded, setIsIframeLoaded] = useState(false); const defaultBodyClasses = [ 'bg-grid-gray-300/20', 'dark:bg-grid-gray-700/20', '[background-position:top_left]', '[background-attachment:fixed]', ]; const updateHtmlClasses = () => { if (typeof window !== 'undefined') { const html = iframeRef.current?.contentDocument?.documentElement; const body = iframeRef.current?.contentDocument?.body; if (html && body) { const activePreferenceClasses = preferences .map((p) => (p.isActive ? p.name : '')) .filter((p) => p !== 'rtl'); html.className = clsxm(activePreferenceClasses); body.className = clsxm(defaultBodyClasses, activePreferenceClasses); // update rtl if isActive const rtlPreference = preferences.find((p) => p.name === 'rtl'); if (rtlPreference?.isActive) { html.dir = 'rtl'; } else { html.dir = 'ltr'; } } } }; const setIframeHeight = ({ iframe, iframeDoc }) => { iframe.height = iframeDoc.body.scrollHeight + 'px'; }; useEffect(() => { if (!iframeRef.current) return; const iframe = iframeRef.current; const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; const cssLink = iframeDoc.createElement('link'); cssLink.rel = 'stylesheet'; cssLink.href = path.join(process.cwd(), '/iframe/tw-iframe.css'); // check for css already loaded, check if it contains 'tw-iframe.css' if (iframeDoc.head.querySelector('link[href*="tw-iframe.css"]')) { return; } else { iframeDoc.head.appendChild(cssLink); } updateHtmlClasses(); if ( iframeDoc.readyState === 'complete' || iframeDoc.readyState === 'interactive' ) { setIsIframeLoaded(true); setTimeout(() => { setIframeHeight({ iframe, iframeDoc }); }, 1000); } }, [iframeRef]); useEffect(() => { if (iframeRef.current) { setIsIframeLoaded(true); updateHtmlClasses(); } }, [preferences, iframeRef]); // Recalculate height on window resize const resizeListener = () => { const iframe = iframeRef.current; const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; setIframeHeight({ iframe, iframeDoc }); }; useEffect(() => { if (typeof window !== 'undefined') window.addEventListener('resize', resizeListener); return () => { if (typeof window !== 'undefined') window.removeEventListener('resize', resizeListener); }; }, [iframeRef]); if (!windowExists) return null; return ( ); }; interface PreferencesProps { name: string; icon: { active: React.FC; inactive: React.FC; }; iconClassName: { active: string; inactive: string; }; isActive: boolean; className: string; } export const CodeFrame = ({ title = 'Example', children = null, ...props }) => { const prefs = [ { name: 'dark', icon: { inactive: MdDarkMode, active: SunIcon }, iconClassName: { active: '', inactive: '', }, isActive: false, className: 'dark', }, { name: 'contrast', icon: { inactive: IoIosContrast, active: IoIosContrast }, iconClassName: { active: 'rotate-180', inactive: '', }, isActive: false, className: 'contrast', }, { name: 'rtl', icon: { inactive: MdFormatTextdirectionRToL, active: MdFormatTextdirectionLToR, }, iconClassName: { active: '', inactive: '', }, isActive: false, className: 'rtl', }, ]; const [preferences, setPreferences] = useState(prefs); const { mode } = useTheme(); const touchStyle = 'pointer-touch:opacity-100 pointer-touch:pointer-events-auto opacity-0 pointer-events-none code-block-touch'; const handlePreferences = (value: PreferencesProps['name']) => { // Add class to previewRef based on Active preference const newPreferences = preferences.map((pref) => { if (pref.name === value) { pref.isActive = !pref.isActive; } return pref; }); setPreferences(newPreferences); }; const isPrefDark = preferences.find((p) => p.name === 'dark')?.isActive; useEffect(() => { if (!windowExists()) return; const newPreferences = preferences.map((pref) => { if (pref.name === 'dark') { pref.isActive = mode === 'dark'; } return pref; }); setPreferences(newPreferences); }, [mode]); return ( <>
{title}
{preferences.map( ( { name, icon: { active: ActiveIcon, inactive: InactiveIcon }, iconClassName: { active: activeIconClassName, inactive: inactiveIconClassName, }, isActive, className, }, i ) => ( {i !== 0 && (
)} ) )}
{children}
); };