Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add fullscreen mode for documentation pages #3365

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions components/FullScreenToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { ReactNode } from 'react';
import React, { useState } from 'react';

interface FullscreenProps {
children: ReactNode;
className?: string;
}

const FullscreenToggle: React.FC<FullscreenProps> = ({ children, className = '' }) => {
const [isFullscreen, setIsFullscreen] = useState(false);
Comment on lines +9 to +10
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Sync component state with browser's fullscreen state

The initial state might be incorrect if fullscreen is toggled outside the component. Consider adding a useEffect to sync with the browser's fullscreen state.

 const FullscreenToggle: React.FC<FullscreenProps> = ({ children, className = '' }) => {
   const [isFullscreen, setIsFullscreen] = useState(false);
+
+  useEffect(() => {
+    const updateFullscreenState = () => {
+      setIsFullscreen(!!document.fullscreenElement);
+    };
+
+    document.addEventListener('fullscreenchange', updateFullscreenState);
+    return () => document.removeEventListener('fullscreenchange', updateFullscreenState);
+  }, []);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const FullscreenToggle: React.FC<FullscreenProps> = ({ children, className = '' }) => {
const [isFullscreen, setIsFullscreen] = useState(false);
const FullscreenToggle: React.FC<FullscreenProps> = ({ children, className = '' }) => {
const [isFullscreen, setIsFullscreen] = useState(false);
useEffect(() => {
const updateFullscreenState = () => {
setIsFullscreen(!!document.fullscreenElement);
};
document.addEventListener('fullscreenchange', updateFullscreenState);
return () => document.removeEventListener('fullscreenchange', updateFullscreenState);
}, []);


const toggleFullscreen = () => {
if (document.fullscreenElement) {
document
.exitFullscreen()
.then(() => setIsFullscreen(false))
.catch((err) => console.error('Error attempting to exit fullscreen:', err));
} else {
document.documentElement
.requestFullscreen()
.then(() => setIsFullscreen(true))
.catch((err) => console.error('Error attempting to enable fullscreen:', err));
}
};
Comment on lines +12 to +24
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance error handling and browser compatibility

The current implementation has two potential issues:

  1. Error handling only logs to console, which isn't visible to users
  2. No check for fullscreen API support across different browsers
+  const isFullscreenSupported = () => {
+    return document.fullscreenEnabled ||
+      document.webkitFullscreenEnabled ||
+      document.mozFullScreenEnabled ||
+      document.msFullscreenEnabled;
+  };
+
   const toggleFullscreen = () => {
+    if (!isFullscreenSupported()) {
+      alert('Fullscreen mode is not supported in your browser');
+      return;
+    }
+
     if (document.fullscreenElement) {
       document
         .exitFullscreen()
         .then(() => setIsFullscreen(false))
-        .catch((err) => console.error('Error attempting to exit fullscreen:', err));
+        .catch((err) => {
+          console.error('Error attempting to exit fullscreen:', err);
+          alert('Failed to exit fullscreen mode. Please try using Escape key.');
+        });
     } else {
       document.documentElement
         .requestFullscreen()
         .then(() => setIsFullscreen(true))
-        .catch((err) => console.error('Error attempting to enable fullscreen:', err));
+        .catch((err) => {
+          console.error('Error attempting to enable fullscreen:', err);
+          alert('Failed to enter fullscreen mode. This might be due to browser restrictions.');
+        });
     }
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const toggleFullscreen = () => {
if (document.fullscreenElement) {
document
.exitFullscreen()
.then(() => setIsFullscreen(false))
.catch((err) => console.error('Error attempting to exit fullscreen:', err));
} else {
document.documentElement
.requestFullscreen()
.then(() => setIsFullscreen(true))
.catch((err) => console.error('Error attempting to enable fullscreen:', err));
}
};
const isFullscreenSupported = () => {
return document.fullscreenEnabled ||
document.webkitFullscreenEnabled ||
document.mozFullScreenEnabled ||
document.msFullscreenEnabled;
};
const toggleFullscreen = () => {
if (!isFullscreenSupported()) {
alert('Fullscreen mode is not supported in your browser');
return;
}
if (document.fullscreenElement) {
document
.exitFullscreen()
.then(() => setIsFullscreen(false))
.catch((err) => {
console.error('Error attempting to exit fullscreen:', err);
alert('Failed to exit fullscreen mode. Please try using Escape key.');
});
} else {
document.documentElement
.requestFullscreen()
.then(() => setIsFullscreen(true))
.catch((err) => {
console.error('Error attempting to enable fullscreen:', err);
alert('Failed to enter fullscreen mode. This might be due to browser restrictions.');
});
}
};


return (
<div className={`relative ${className}`}>
<button
onClick={toggleFullscreen}
className='fixed right-4 top-4 z-50 rounded-lg border border-gray-200 bg-white
p-2 shadow-sm transition-colors duration-200 hover:bg-gray-50'
aria-label={isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}
>
{isFullscreen ? (
<svg className='size-5 text-gray-700' fill='none' stroke='currentColor' viewBox='0 0 24 24'>
<path strokeLinecap='round' strokeLinejoin='round' strokeWidth={2} d='M6 18L18 6M6 6l12 12' />
</svg>
) : (
<svg className='size-5 text-gray-700' fill='none' stroke='currentColor' viewBox='0 0 24 24'>
<path
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth={2}
d='M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5v-4m0 4h-4m4 0l-5-5'
/>
</svg>
)}
</button>
{children}
</div>
);
};

export default FullscreenToggle;
167 changes: 85 additions & 82 deletions components/layout/DocsLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { getAllPosts } from '../../utils/api';
import Button from '../buttons/Button';
import DocsButton from '../buttons/DocsButton';
import Feedback from '../Feedback';
import FullscreenToggle from '../FullScreenToggle';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix inconsistent casing in import path

The import statement uses 'FullScreenToggle' while the component is named 'FullscreenToggle'. This inconsistency could cause issues on case-sensitive filesystems.

-import FullscreenToggle from '../FullScreenToggle';
+import FullscreenToggle from '../FullscreenToggle';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import FullscreenToggle from '../FullScreenToggle';
import FullscreenToggle from '../FullscreenToggle';

import Head from '../Head';
import ArrowRight from '../icons/ArrowRight';
import IconMenuCenter from '../icons/CenterMenu';
Expand Down Expand Up @@ -113,103 +114,105 @@ export default function DocsLayout({ post, navItems = {}, children }: IDocsLayou
}

return (
<DocsContext.Provider value={{ post, navItems }}>
<div className='w-full bg-white px-4 sm:px-6 lg:px-8 xl:mx-auto xl:max-w-7xl'>
{showMenu && <DocsMobileMenu onClickClose={() => setShowMenu(false)} post={post} navigation={navigation} />}
<div className='flex flex-row' id='main-content'>
{/* <!-- Static sidebar for desktop --> */}
{sidebar}
<div className='flex w-0 max-w-full flex-1 flex-col lg:max-w-(screen-16)'>
<main className='relative z-0 pb-6 pt-2 focus:outline-none md:py-6' tabIndex={0}>
{!showMenu && (
<div className='lg:hidden'>
<button
onClick={() => setShowMenu(true)}
className='flex px-4 text-gray-500 hover:text-gray-900 focus:outline-none sm:px-6 md:px-8'
aria-label='Open sidebar'
>
<span>{post.sectionTitle}</span>
<ArrowRight className='size-5 rotate-90 pl-1' />
</button>
</div>
)}

{/* @TODO Will uncomment the component once it is in use */}
{/* <AnnouncementHero className='ml-6' hideVideo={true} /> */}

<div className={`xl:flex ${post.toc && post.toc.length ? 'xl:flex-row-reverse' : ''}`}>
<TOC
toc={post.toc}
depth={3}
className='sticky top-20 mt-4 max-h-screen overflow-y-auto bg-blue-100 p-4 xl:mt-0 xl:w-72 xl:bg-transparent xl:pb-8'
/>
<div className='px-4 sm:px-6 xl:max-w-184 xl:flex-1 xl:px-8'>
<Heading level={HeadingLevel.h1} typeStyle={HeadingTypeStyle.lg}>
{post.title}
</Heading>
<div>
<p className='font-normal font-sans text-sm text-gray-600 antialiased'>
Found an error? Have a suggestion?
{generateEditLink(post)}
</p>
<FullscreenToggle>
<DocsContext.Provider value={{ post, navItems }}>
<div className='w-full bg-white px-4 sm:px-6 lg:px-8 xl:mx-auto xl:max-w-7xl'>
{showMenu && <DocsMobileMenu onClickClose={() => setShowMenu(false)} post={post} navigation={navigation} />}
<div className='flex flex-row' id='main-content'>
{/* <!-- Static sidebar for desktop --> */}
{sidebar}
<div className='flex w-0 max-w-full flex-1 flex-col lg:max-w-(screen-16)'>
<main className='relative z-0 pb-6 pt-2 focus:outline-none md:py-6' tabIndex={0}>
{!showMenu && (
<div className='lg:hidden'>
<button
onClick={() => setShowMenu(true)}
className='flex px-4 text-gray-500 hover:text-gray-900 focus:outline-none sm:px-6 md:px-8'
aria-label='Open sidebar'
>
<span>{post.sectionTitle}</span>
<ArrowRight className='size-5 rotate-90 pl-1' />
</button>
</div>
{post.releaseNoteLink && (
// show only when it is related to specification (/docs/reference/specification) AND is not a pre-release
// for example, if the post's title is "3.0.0 (Pre-release)", which will not have RN, so do not render this section.
<div className='mt-5 w-full rounded-lg bg-secondary-100 px-2 py-3 text-center'>
<div>
<span className='font-sans text-sm text-gray-800 antialiased'>
{`What is new in v${post.title}? Have a look at the `}
</span>
<Link
href={post.releaseNoteLink}
target='_blank'
rel='noopener noreferrer'
className={
'cursor-pointer font-body text-sm font-medium leading-6 text-secondary-500 underline transition duration-150 ease-in-out hover:text-secondary-600 focus:text-gray-900 focus:outline-none'
}
>
release notes
</Link>
.
</div>
<div>
<span className='font-sans text-sm text-gray-800 antialiased'>
Interested in release notes of other versions of the specification?&nbsp;
</span>
<span className='font-sans text-sm text-gray-800 antialiased'>
Check&nbsp;
)}

{/* @TODO Will uncomment the component once it is in use */}
{/* <AnnouncementHero className='ml-6' hideVideo={true} /> */}

<div className={`xl:flex ${post.toc && post.toc.length ? 'xl:flex-row-reverse' : ''}`}>
<TOC
toc={post.toc}
depth={3}
className='sticky top-20 mt-4 max-h-screen overflow-y-auto bg-blue-100 p-4 xl:mt-0 xl:w-72 xl:bg-transparent xl:pb-8'
/>
<div className='px-4 sm:px-6 xl:max-w-184 xl:flex-1 xl:px-8'>
<Heading level={HeadingLevel.h1} typeStyle={HeadingTypeStyle.lg}>
{post.title}
</Heading>
<div>
<p className='font-normal font-sans text-sm text-gray-600 antialiased'>
Found an error? Have a suggestion?
{generateEditLink(post)}
</p>
</div>
{post.releaseNoteLink && (
// show only when it is related to specification (/docs/reference/specification) AND is not a pre-release
// for example, if the post's title is "3.0.0 (Pre-release)", which will not have RN, so do not render this section.
<div className='mt-5 w-full rounded-lg bg-secondary-100 px-2 py-3 text-center'>
<div>
<span className='font-sans text-sm text-gray-800 antialiased'>
{`What is new in v${post.title}? Have a look at the `}
</span>
<Link
href='https://www.asyncapi.com/blog?tags=Release+Notes'
href={post.releaseNoteLink}
target='_blank'
rel='noopener noreferrer'
className={
'cursor-pointer font-body text-sm font-medium leading-6 text-secondary-500 underline transition duration-150 ease-in-out hover:text-secondary-600 focus:text-gray-900 focus:outline-none'
}
>
list of release notes
release notes
</Link>
.
</span>
</div>
<div>
<span className='font-sans text-sm text-gray-800 antialiased'>
Interested in release notes of other versions of the specification?&nbsp;
</span>
<span className='font-sans text-sm text-gray-800 antialiased'>
Check&nbsp;
<Link
href='https://www.asyncapi.com/blog?tags=Release+Notes'
target='_blank'
rel='noopener noreferrer'
className={
'cursor-pointer font-body text-sm font-medium leading-6 text-secondary-500 underline transition duration-150 ease-in-out hover:text-secondary-600 focus:text-gray-900 focus:outline-none'
}
>
list of release notes
</Link>
.
</span>
</div>
</div>
)}
<article className='my-12'>
<Head title={post.title} description={post.excerpt} image={post.cover} />
{children}
</article>
<div>
<DocsButton post={post} />
</div>
<div className=''>
<Feedback />
</div>
)}
<article className='my-12'>
<Head title={post.title} description={post.excerpt} image={post.cover} />
{children}
</article>
<div>
<DocsButton post={post} />
</div>
<div className=''>
<Feedback />
</div>
</div>
</div>
</main>
</main>
</div>
</div>
</div>
</div>
</DocsContext.Provider>
</DocsContext.Provider>
</FullscreenToggle>
);
}
Loading