From 4d4993dc006dfeaebd4456f96ba30b257fcc4b60 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Tue, 13 Oct 2026 18:20:15 +0300 Subject: [PATCH] Optimize tabs using Activity --- app/components/elements/Tabs.tsx | 18 ++++++++++++++++-- app/components/match-page/MatchTabs.tsx | 1 + .../team/routes/t.$customUrl.index.tsx | 2 +- .../routes/org.$slug.tsx | 4 ++-- app/features/trophies/routes/trophies.new.tsx | 2 +- e2e/pages/trophies/new-trophy-page.ts | 2 ++ 6 files changed, 23 insertions(+), 6 deletions(-) diff --git a/app/components/elements/Tabs.tsx b/app/components/elements/Tabs.tsx index 2ad70ee8c..c8f871dd3 100644 --- a/app/components/elements/Tabs.tsx +++ b/app/components/elements/Tabs.tsx @@ -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 ``: 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 = (
); + + if (!tabs.keepPanelsMounted) return panel; + + return ( + + {panel} + + ); } diff --git a/app/components/match-page/MatchTabs.tsx b/app/components/match-page/MatchTabs.tsx index 7a8ff53c3..04fe51bf3 100644 --- a/app/components/match-page/MatchTabs.tsx +++ b/app/components/match-page/MatchTabs.tsx @@ -73,6 +73,7 @@ export function MatchTabs({ onSelectionChange={(key) => setTab(key as MatchTabsKey)} disappearing={false} padded={false} + keepPanelsMounted > {tabs.map((tab) => ( diff --git a/app/features/team/routes/t.$customUrl.index.tsx b/app/features/team/routes/t.$customUrl.index.tsx index 9027313cd..89de55635 100644 --- a/app/features/team/routes/t.$customUrl.index.tsx +++ b/app/features/team/routes/t.$customUrl.index.tsx @@ -85,7 +85,7 @@ export default function TeamIndexPage() {
{layoutData.team.bio}
) : null} {showSections ? ( - + - + }> {t("org:edit.form.socialLinks.title")} @@ -408,7 +408,7 @@ function SeriesView({
- + {t("org:events.tabs.events")} diff --git a/app/features/trophies/routes/trophies.new.tsx b/app/features/trophies/routes/trophies.new.tsx index bec03af2c..00bece531 100644 --- a/app/features/trophies/routes/trophies.new.tsx +++ b/app/features/trophies/routes/trophies.new.tsx @@ -93,7 +93,7 @@ export default function NewTrophyPage() { return (
- + {t("trophies:new.tabs.upload")}