authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-04 19:53:45-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-04 19:53:45-07:00
logac90950f1ac315bcef5f92b53c5e0d6579daeefd
treee83434442658027a52ea2d69e1ede6f49fd32c2d
parent84b99f3fb7bbc759fcbb589d031920e76edf4746
signature Signed by PGP key B5690EEEBB952194

fix(mobile): keep usage tabs below the header when switching (#9876)


1 files changed, 5 insertions(+), 11 deletions(-)

apps/mobile/src/features/usage/UsageRouteScreen.tsx+5-11
...@@ -11,7 +11,7 @@ import {...@@ -11,7 +11,7 @@ import {
11 formatUsd,11 formatUsd,
12 makeWindow,12 makeWindow,
13} from "@t3tools/shared/usageFormat";13} from "@t3tools/shared/usageFormat";
14import { useMemo, useRef, useState } from "react";14import { useMemo, useState } from "react";
15import { Platform, Pressable, RefreshControl, ScrollView, View } from "react-native";15import { Platform, Pressable, RefreshControl, ScrollView, View } from "react-native";
16import { useSafeAreaInsets } from "react-native-safe-area-context";16import { useSafeAreaInsets } from "react-native-safe-area-context";
1717
...@@ -96,14 +96,6 @@ export function UsageRouteScreen() {...@@ -96,14 +96,6 @@ export function UsageRouteScreen() {
96 // environment stays pending forever — neither may pin the spinner on.96 // environment stays pending forever — neither may pin the spinner on.
97 const refreshingUsage = environments.some((entry) => entry.isPending && entry.summary !== null);97 const refreshingUsage = environments.some((entry) => entry.isPending && entry.summary !== null);
98 const showingLimits = tab === "limits";98 const showingLimits = tab === "limits";
99 // One ScrollView serves both tabs, so the offset would otherwise carry over
100 // and a short Limits list could open scrolled past its own content.
101 const scrollRef = useRef<ScrollView>(null);
102 const selectTab = (next: UsageTab) => {
103 if (next === tab) return;
104 setTab(next);
105 scrollRef.current?.scrollTo({ y: 0, animated: false });
106 };
107 const selectWindow = (days: number) => {99 const selectWindow = (days: number) => {
108 setWindowSelection({100 setWindowSelection({
109 days,101 days,
...@@ -133,7 +125,9 @@ export function UsageRouteScreen() {...@@ -133,7 +125,9 @@ export function UsageRouteScreen() {
133 </>125 </>
134 ) : null}126 ) : null}
135 <ScrollView127 <ScrollView
136 ref={scrollRef}128 // Remount at each tab's native top. Scrolling to y: 0 ignores iOS's
129 // automatic header inset and hides the tab bar under the header.
130 key={tab}
137 contentInsetAdjustmentBehavior="automatic"131 contentInsetAdjustmentBehavior="automatic"
138 showsVerticalScrollIndicator={false}132 showsVerticalScrollIndicator={false}
139 className="flex-1"133 className="flex-1"
...@@ -146,7 +140,7 @@ export function UsageRouteScreen() {...@@ -146,7 +140,7 @@ export function UsageRouteScreen() {
146 />140 />
147 }141 }
148 >142 >
149 <SegmentedControl options={TAB_OPTIONS} selected={tab} onSelect={selectTab} role="tab" />143 <SegmentedControl options={TAB_OPTIONS} selected={tab} onSelect={setTab} role="tab" />
150144
151 {showingLimits ? (145 {showingLimits ? (
152 <UsageLimitsSection now={limits.now} failedLabels={limits.failedLabels} />146 <UsageLimitsSection now={limits.now} failedLabels={limits.failedLabels} />