fix(website): resolve Safari hydration error

This commit is contained in:
Darius Cepulis
2025-10-21 16:44:12 -07:00
parent 2d926b4ed7
commit 2dd7768d3a
6 changed files with 70 additions and 75 deletions
+5
View File
@@ -5,6 +5,11 @@ import html from 'shiki/langs/html.mjs';
import tsx from 'shiki/langs/tsx.mjs';
import gruvboxLightHard from 'shiki/themes/gruvbox-light-hard.mjs';
// If you try importing more than one island with ClientCode in it,
// Safari MAY throw a hydration error because of this top-level await.
// https://github.com/withastro/astro/issues/10055
// consolidate the ClientCodes into a single island to work around... for now :(
// eslint-disable-next-line antfu/no-top-level-await
const highlighter: Highlighter = await createHighlighter({
themes: [gruvboxLightHard],
@@ -1,7 +1,7 @@
import type { Media, Skin } from '@/stores/homePageDemos';
import { useStore } from '@nanostores/react';
import { framework, media, skin } from '@/stores/homePageDemos';
import ClientCode from './ClientCode';
import ClientCode from '../ClientCode';
function generateHTMLCode(skin: Skin, media: Media): string {
const skinTag = `${skin}-skin`;
@@ -38,7 +38,7 @@ export const VideoPlayer = () => {
interface Props {
className?: string;
}
export default function HomePageBaseDemo({ className }: Props) {
export default function BaseDemo({ className }: Props) {
const $framework = useStore(framework);
const $skin = useStore(skin);
const $media = useStore(media);
@@ -0,0 +1,33 @@
import type { Media, Skin } from '@/stores/homePageDemos';
import { useStore } from '@nanostores/react';
import { framework, media, skin } from '@/stores/homePageDemos';
import ClientCode from '../ClientCode';
// eslint-disable-next-line unused-imports/no-unused-vars
function generateHTMLCode(skin: Skin, media: Media): string {
return `Coming soon`;
}
// eslint-disable-next-line unused-imports/no-unused-vars
function generateReactCode(skin: Skin, media: Media): string {
return `Coming soon`;
}
interface Props {
className?: string;
}
export default function EjectDemo({ className }: Props) {
const $framework = useStore(framework);
const $skin = useStore(skin);
const $media = useStore(media);
const code = $framework === 'html'
? generateHTMLCode($skin, $media)
: generateReactCode($skin, $media);
const lang = $framework === 'html' ? 'html' : 'tsx';
return (
<ClientCode code={code} lang={lang} className={className} />
);
}
@@ -0,0 +1,28 @@
import clsx from 'clsx';
import BaseDemo from './Base';
import EjectDemo from './Eject';
interface Props {
className?: string;
}
export default function HomePageDemo({ className }: Props) {
return (
<section className={clsx('grid gap-x-9 gap-y-6 lg:grid-cols-2', className)}>
<section className="grid grid-rows-subgrid row-span-2 mb-6">
<header className="max-w-3xl">
<h2 className="text-h5 font-semibold lg:text-h4 mb-1">Assemble your player</h2>
<p>Feel at home with your framework, skin, and media source</p>
</header>
<BaseDemo className="lg:h-89" />
</section>
<section className="grid grid-rows-subgrid row-span-2 mb-6">
<header className="max-w-3xl">
<h2 className="text-h5 font-semibold lg:text-h4 mb-1">Take full control</h2>
<p>Make your player truly your own with fully-editable components</p>
</header>
<EjectDemo className="lg:h-89" />
</section>
</section>
);
}
@@ -1,55 +0,0 @@
import type { Media, Skin } from '@/stores/homePageDemos';
import { useStore } from '@nanostores/react';
import { framework, media, skin } from '@/stores/homePageDemos';
import ClientCode from './ClientCode';
function generateHTMLCode(skin: Skin, media: Media): string {
const skinTag = `${skin}-skin`;
const mediaTag = media;
const videoExtension = media === 'hls-video' ? 'm3u8' : 'mp4';
return `<video-provider>
<${skinTag}>
<${mediaTag} src="https://example.com/video.${videoExtension}"></${mediaTag}>
</${skinTag}>
</video-provider>`;
}
function generateReactCode(skin: Skin, media: Media): string {
const skinComponent = skin === 'frosted' ? 'FrostedSkin' : 'MinimalSkin';
const mediaComponent = media === 'video' ? 'Video' : 'HLSVideo';
const skinImport = skin === 'frosted' ? 'frosted' : 'minimal';
const videoExtension = media === 'hls-video' ? 'm3u8' : 'mp4';
return `import { VideoProvider, ${mediaComponent} } from '@vjs-10/react';
import { ${skinComponent} } from '@vjs-10/react/skins/${skinImport}';
export const VideoPlayer = () => {
return (
<VideoProvider>
<${skinComponent}>
<${mediaComponent} src="https://example.com/video.${videoExtension}" />
</${skinComponent}>
</VideoProvider>
);
};`;
}
interface Props {
className?: string;
}
export default function HomePageEjectDemo({ className }: Props) {
const $framework = useStore(framework);
const $skin = useStore(skin);
const $media = useStore(media);
const code = $framework === 'html'
? generateHTMLCode($skin, $media)
: generateReactCode($skin, $media);
const lang = $framework === 'html' ? 'html' : 'tsx';
return (
<ClientCode code={code} lang={lang} className={className} />
);
}
+2 -18
View File
@@ -6,8 +6,7 @@ import NavBar from '@/components/NavBar/NavBar.astro';
import Footer from '@/components/Footer.astro';
import { SITE_DESCRIPTION, SITE_TITLE } from '@/consts';
import Base from '@/layouts/Base.astro';
import HomePageBaseDemo from '@/components/HomePageBaseDemo';
import HomePageEjectDemo from '@/components/HomePageEjectDemo';
import HomePageDemo from '@/components/HomePageDemo';
import HomePageControls from '@/components/home/HomePageControls';
import { PLAYBACK_ID } from '@/components/home/config';
import { CircleCheck, Clock } from 'lucide-react';
@@ -44,22 +43,7 @@ const poster = `https://image.mux.com/${PLAYBACK_ID}/thumbnail.webp?time=0`;
<!-- padding corresponds to negative margin on player -->
<main class="@container pt-11 pb-36 md:pt-21 lg:pt-32 px-6 md:px-10 w-full max-w-7xl mx-auto min-h-64">
<HomePageControls client:idle className="col-span-full my-10 sm:mb-20" />
<section class="grid gap-x-9 gap-y-6 lg:grid-cols-2 mb-6 lg:mb-30">
<section class="grid grid-rows-subgrid row-span-2 mb-6">
<header class="max-w-3xl">
<h2 class="text-h5 font-semibold lg:text-h4 mb-1">Assemble your player</h2>
<p>Feel at home with your framework, skin, and media source</p>
</header>
<HomePageBaseDemo client:idle className="lg:h-89" />
</section>
<section class="grid grid-rows-subgrid row-span-2 mb-6">
<header class="max-w-3xl">
<h2 class="text-h5 font-semibold lg:text-h4 mb-1">Take full control</h2>
<p>Make your player truly your own with fully-editable components</p>
</header>
<HomePageEjectDemo client:idle className="lg:h-89" />
</section>
</section>
<HomePageDemo client:idle className="mb-6 lg:mb-30" />
<section class="rounded-3xl">
<header class="max-w-3xl mb-3">
<h2 class="text-h5 font-semibold lg:text-h4">Under construction</h2>