Fix YouTube embed overlaps side nav in new design

Closes #3066
This commit is contained in:
Kalle
2026-06-18 14:33:39 +03:00
parent 3b03bc2550
commit 4eee45bf67
2 changed files with 63 additions and 15 deletions

View File

@@ -6,20 +6,22 @@
margin-block: var(--s-2);
}
@media (min-width: 1425px) {
.embedContainer {
position: fixed;
left: max(24px, calc((100vw - 800px) / 2 - 320px - 48px));
top: 120px;
width: 320px;
margin-block: 0;
}
}
/* When the form's left margin can fit the embed (measured in JS so it accounts
for the side nav being collapsed and the chat sidebar being open), float it
just left of the form. The rail is anchored to the form's own left edge, so
the side nav offset cancels out and it never overlaps the nav. Its width is
set inline by the same JS. The rail spans the full form height while the
embed sticks within it, so it stays in view while scrolling the long form. */
.embedRail.floating {
position: absolute;
top: 0;
bottom: 0;
right: calc(100% + var(--s-6));
@media (min-width: 1700px) {
.embedContainer {
left: max(24px, calc((100vw - 800px) / 2 - 400px - 48px));
width: 400px;
& .embedContainer {
position: sticky;
top: calc(var(--layout-nav-height) + var(--s-4));
margin-block: 0;
}
}

View File

@@ -1,3 +1,4 @@
import clsx from "clsx";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useLoaderData } from "react-router";
@@ -13,6 +14,7 @@ import { FormFieldWrapper } from "~/form/fields/FormFieldWrapper";
import type { WeaponPoolItem } from "~/form/fields/WeaponPoolFormField";
import type { FormRenderProps } from "~/form/SendouForm";
import { SendouForm, useFormFieldContext } from "~/form/SendouForm";
import { useIsomorphicLayoutEffect } from "~/hooks/useIsomorphicLayoutEffect";
import { useRecentlyReportedWeapons } from "~/hooks/useRecentlyReportedWeapons";
import type { MainWeaponId, StageId } from "~/modules/in-game-lists/types";
import { useHasRole } from "~/modules/permissions/hooks";
@@ -127,6 +129,7 @@ function YouTubeEmbedWrapper({
onPlayerReady: (player: YT.Player) => void;
}) {
const { values } = useFormFieldContext();
const floatWidth = useFloatingEmbedWidth();
const youtubeUrl = values.youtubeUrl as string | undefined;
if (!youtubeUrl) return null;
@@ -135,12 +138,55 @@ function YouTubeEmbedWrapper({
if (!videoId) return null;
return (
<div className={styles.embedContainer}>
<YouTubeEmbed id={videoId} enableApi onPlayerReady={onPlayerReady} />
<div
className={clsx(styles.embedRail, { [styles.floating]: floatWidth })}
style={floatWidth ? { width: floatWidth } : undefined}
>
<div className={styles.embedContainer}>
<YouTubeEmbed id={videoId} enableApi onPlayerReady={onPlayerReady} />
</div>
</div>
);
}
const EMBED_RAIL_GAP = 24; // mirrors var(--s-6)
const EMBED_FLOAT_WIDTHS = [400, 320] as const;
/**
* Returns the width to float the embed at when the form's left margin can fit
* it (widest that fits), or `null` to leave it in flow above the fields.
* Measures the form's actual left margin so it accounts for the side nav being
* collapsed and the chat sidebar being open, neither of which a media query can
* see.
*/
function useFloatingEmbedWidth(): number | null {
const [leftMargin, setLeftMargin] = useState(0);
useIsomorphicLayoutEffect(() => {
const main = document.querySelector("main");
const container = main?.parentElement;
if (!main || !container) return;
const measure = () => {
setLeftMargin(
main.getBoundingClientRect().left -
container.getBoundingClientRect().left,
);
};
measure();
const observer = new ResizeObserver(measure);
observer.observe(container);
return () => observer.disconnect();
}, []);
return (
EMBED_FLOAT_WIDTHS.find((width) => leftMargin >= width + EMBED_RAIL_GAP) ??
null
);
}
type VodFormFieldComponent = FormRenderProps<
typeof vodFormBaseSchema.shape
>["FormField"];