Optimize tabs using Activity

This commit is contained in:
Kalle
2026-10-13 18:20:15 +03:00
parent ca12b7cff1
commit 4d4993dc00
6 changed files with 23 additions and 6 deletions

View File

@@ -13,6 +13,7 @@ type TabsOrientation = "horizontal" | "vertical";
interface TabsContextValue {
selectedKey: string | null;
orientation: TabsOrientation;
keepPanelsMounted: boolean;
select: (key: string) => void;
registerTab: (key: string, element: HTMLElement) => () => void;
moveFocus: (
@@ -40,6 +41,8 @@ interface SendouTabsProps {
disappearing?: boolean;
/** Vertical orientation switches to horizontal below this main content width (px). */
horizontalBelow?: number;
/** Hidden panels stay mounted via `<Activity>`: pre-rendered in the background once the page is idle and state kept across switches, making tab switches instant. Default false. */
keepPanelsMounted?: boolean;
className?: string;
children: React.ReactNode;
}
@@ -68,6 +71,7 @@ export function SendouTabs({
padded = true,
disappearing = true,
horizontalBelow,
keepPanelsMounted = false,
className,
orientation = "horizontal",
children,
@@ -112,6 +116,7 @@ export function SendouTabs({
const context: TabsContextValue = {
selectedKey: currentKey,
orientation: effectiveOrientation,
keepPanelsMounted,
select,
registerTab: (key, element) => {
tabsRef.current.set(key, element);
@@ -326,9 +331,10 @@ export function SendouTabPanel({
}: SendouTabPanelProps) {
const tabs = useTabsContext();
if (tabs.selectedKey !== id) return null;
const selected = tabs.selectedKey === id;
if (!selected && !tabs.keepPanelsMounted) return null;
return (
const panel = (
<div
className={clsx(className, styles.tabPanel)}
role="tabpanel"
@@ -338,4 +344,12 @@ export function SendouTabPanel({
{children}
</div>
);
if (!tabs.keepPanelsMounted) return panel;
return (
<React.Activity mode={selected ? "visible" : "hidden"}>
{panel}
</React.Activity>
);
}

View File

@@ -73,6 +73,7 @@ export function MatchTabs({
onSelectionChange={(key) => setTab(key as MatchTabsKey)}
disappearing={false}
padded={false}
keepPanelsMounted
>
<SendouTabList>
{tabs.map((tab) => (

View File

@@ -85,7 +85,7 @@ export default function TeamIndexPage() {
<article data-testid="team-bio">{layoutData.team.bio}</article>
) : null}
{showSections ? (
<SendouTabs>
<SendouTabs keepPanelsMounted>
<SendouTabList>
<SendouTab
id="players"

View File

@@ -237,7 +237,7 @@ function InfoTabs() {
return (
<div>
<SendouTabs>
<SendouTabs keepPanelsMounted>
<SendouTabList>
<SendouTab id="socials" isDisabled={!hasSocials} icon={<LinkIcon />}>
{t("org:edit.form.socialLinks.title")}
@@ -408,7 +408,7 @@ function SeriesView({
<div className="stack md">
<SeriesHeader series={series} />
<div>
<SendouTabs>
<SendouTabs keepPanelsMounted>
<SendouTabList>
<SendouTab id="events" number={series.eventsCount}>
{t("org:events.tabs.events")}

View File

@@ -93,7 +93,7 @@ export default function NewTrophyPage() {
return (
<Main halfWidth>
<SendouTabs>
<SendouTabs keepPanelsMounted>
<SendouTabList>
<SendouTab id="upload">{t("trophies:new.tabs.upload")}</SendouTab>
<SendouTab

View File

@@ -87,7 +87,9 @@ class PendingTrophyList {
}
row(trophyName: string) {
// the other list stays mounted in a hidden tab panel
return this.page
.getByRole("tabpanel")
.getByTestId("pending-trophy")
.filter({ hasText: trophyName });
}