authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-17 20:28:27-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-17 20:28:27-07:00
logc7fa1e96943f380a8893e89460d03a362285ab83
tree75fad321e97aab1f9a00b404999ffe2aab41bd72
parentfa2decfce7fa359551634b4d24a2aecd9cef88c9
signature Signed by PGP key B5690EEEBB952194

fix(mobile): share settings action row defaults (#12369)


4 files changed, 72 insertions(+), 33 deletions(-)

apps/mobile/src/components/MaterialListRow.tsx+3-1
...@@ -9,6 +9,7 @@ import { SymbolView } from "./AppSymbol";...@@ -9,6 +9,7 @@ import { SymbolView } from "./AppSymbol";
9/** Shared geometry for Material navigation and selection lists. Group rows in one card. */9/** Shared geometry for Material navigation and selection lists. Group rows in one card. */
10export function MaterialListRow({10export function MaterialListRow({
11 title,11 title,
12 titleClassName,
12 subtitle,13 subtitle,
13 leading,14 leading,
14 trailing,15 trailing,
...@@ -16,6 +17,7 @@ export function MaterialListRow({...@@ -16,6 +17,7 @@ export function MaterialListRow({
16 ...props17 ...props
17}: Omit<ComponentProps<typeof Pressable>, "children"> & {18}: Omit<ComponentProps<typeof Pressable>, "children"> & {
18 readonly title: string;19 readonly title: string;
20 readonly titleClassName?: string;
19 readonly subtitle?: string | null;21 readonly subtitle?: string | null;
20 readonly leading?: ReactNode;22 readonly leading?: ReactNode;
21 readonly trailing?: ReactNode;23 readonly trailing?: ReactNode;
...@@ -38,7 +40,7 @@ export function MaterialListRow({...@@ -38,7 +40,7 @@ export function MaterialListRow({
38 >40 >
39 {leading ? <View className="size-6 items-center justify-center">{leading}</View> : null}41 {leading ? <View className="size-6 items-center justify-center">{leading}</View> : null}
40 <View className="min-w-0 flex-1 gap-1">42 <View className="min-w-0 flex-1 gap-1">
41 <AppText className="text-base text-foreground" numberOfLines={2}>43 <AppText className={cn("text-base text-foreground", titleClassName)} numberOfLines={2}>
42 {title}44 {title}
43 </AppText>45 </AppText>
44 {subtitle ? (46 {subtitle ? (
apps/mobile/src/features/diagnostics/SettingsDiagnosticsRouteScreen.tsx+6-16
...@@ -2,12 +2,13 @@ import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollVie...@@ -2,12 +2,13 @@ import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollVie
2import Constants from "expo-constants";2import Constants from "expo-constants";
3import * as Updates from "expo-updates";3import * as Updates from "expo-updates";
4import { useEffect, useState } from "react";4import { useEffect, useState } from "react";
5import { ActivityIndicator, Platform, Pressable, View } from "react-native";5import { ActivityIndicator, Platform, View } from "react-native";
6import { useSafeAreaInsets } from "react-native-safe-area-context";6import { useSafeAreaInsets } from "react-native-safe-area-context";
77
8import { SymbolView } from "../../components/AppSymbol";8import { SymbolView } from "../../components/AppSymbol";
9import { AppText as Text } from "../../components/AppText";9import { AppText as Text } from "../../components/AppText";
10import { tryCopyTextWithHaptic } from "../../lib/copyTextWithHaptic";10import { tryCopyTextWithHaptic } from "../../lib/copyTextWithHaptic";
11import { SettingsActionRow } from "../settings/components/SettingsActionRow";
11import { SettingsScreen } from "../settings/components/SettingsScreen";12import { SettingsScreen } from "../settings/components/SettingsScreen";
12import { SettingsSection } from "../settings/components/SettingsSection";13import { SettingsSection } from "../settings/components/SettingsSection";
13import {14import {
...@@ -108,23 +109,12 @@ export function SettingsDiagnosticsRouteScreen() {...@@ -108,23 +109,12 @@ export function SettingsDiagnosticsRouteScreen() {
108109
109 <View className="gap-3">110 <View className="gap-3">
110 <SettingsSection title="Actions">111 <SettingsSection title="Actions">
111 <Pressable112 <SettingsActionRow
112 accessibilityRole="button"113 icon={copied ? "checkmark" : "doc.on.doc"}
114 label={copied ? "Copied" : "Copy crash report"}
113 disabled={state.status !== "ready"}115 disabled={state.status !== "ready"}
114 onPress={() => void copyReport()}116 onPress={() => void copyReport()}
115 className="flex-row items-center gap-4 p-4 disabled:opacity-40"117 />
116 >
117 <SymbolView
118 name={copied ? "checkmark" : "doc.on.doc"}
119 size={22}
120 tintColorClassName="accent-icon"
121 type="monochrome"
122 weight="regular"
123 />
124 <Text className="flex-1 text-lg text-foreground">
125 {copied ? "Copied" : "Copy crash report"}
126 </Text>
127 </Pressable>
128 </SettingsSection>118 </SettingsSection>
129 <Text className="px-2 text-sm leading-normal text-foreground-muted">119 <Text className="px-2 text-sm leading-normal text-foreground-muted">
130 Paste the report into a GitHub issue. It contains the app version, the JavaScript error120 Paste the report into a GitHub issue. It contains the app version, the JavaScript error
apps/mobile/src/features/settings/SettingsClientStorageRouteScreen.tsx+7-16
...@@ -16,6 +16,7 @@ import {...@@ -16,6 +16,7 @@ import {
16} from "../../state/client-cache-state";16} from "../../state/client-cache-state";
17import { useServerConfigs } from "../../state/entities";17import { useServerConfigs } from "../../state/entities";
18import { useSavedRemoteConnections } from "../../state/use-remote-environment-registry";18import { useSavedRemoteConnections } from "../../state/use-remote-environment-registry";
19import { SettingsActionRow } from "./components/SettingsActionRow";
19import { SettingsSection } from "./components/SettingsSection";20import { SettingsSection } from "./components/SettingsSection";
20import { SettingsScreen } from "./components/SettingsScreen";21import { SettingsScreen } from "./components/SettingsScreen";
2122
...@@ -139,24 +140,14 @@ export function SettingsClientStorageRouteScreen() {...@@ -139,24 +140,14 @@ export function SettingsClientStorageRouteScreen() {
139140
140 <View className="gap-3">141 <View className="gap-3">
141 <SettingsSection title="Actions">142 <SettingsSection title="Actions">
142 <Pressable143 <SettingsActionRow
143 accessibilityRole="button"144 icon="trash"
145 label={summary ? `Clear ${formatBytes(summary.payloadBytes)}` : "Clear caches"}
146 tone="danger"
144 disabled={isClearing || !summary || summary.recordCount === 0}147 disabled={isClearing || !summary || summary.recordCount === 0}
148 loading={isClearing}
145 onPress={confirmClearAll}149 onPress={confirmClearAll}
146 className="flex-row items-center gap-4 p-4 disabled:opacity-40"150 />
147 >
148 <SymbolView
149 name="trash"
150 size={22}
151 tintColorClassName="accent-danger-foreground"
152 type="monochrome"
153 weight="regular"
154 />
155 <Text className="flex-1 text-lg tabular-nums text-danger-foreground">
156 {summary ? `Clear ${formatBytes(summary.payloadBytes)}` : "Clear caches"}
157 </Text>
158 {isClearing ? <ActivityIndicator colorClassName="accent-danger-foreground" /> : null}
159 </Pressable>
160 </SettingsSection>151 </SettingsSection>
161 <Text className="px-2 text-sm leading-normal text-foreground-muted">152 <Text className="px-2 text-sm leading-normal text-foreground-muted">
162 Clearing caches never removes environment connections, credentials, account data, or153 Clearing caches never removes environment connections, credentials, account data, or
apps/mobile/src/features/settings/components/SettingsActionRow.tsx created+56
...@@ -0,0 +1,56 @@
1import type { ComponentProps } from "react";
2import { ActivityIndicator, Platform, Pressable } from "react-native";
3
4import { SymbolView } from "../../../components/AppSymbol";
5import { AppText as Text } from "../../../components/AppText";
6import { MaterialListRow } from "../../../components/MaterialListRow";
7import { cn } from "../../../lib/cn";
8
9export function SettingsActionRow(props: {
10 readonly icon: ComponentProps<typeof SymbolView>["name"];
11 readonly label: string;
12 readonly tone?: "default" | "danger";
13 readonly disabled?: boolean;
14 readonly loading?: boolean;
15 readonly onPress: () => void;
16}) {
17 const danger = props.tone === "danger";
18 const textClassName = danger ? "tabular-nums text-danger-foreground" : "text-foreground";
19 const iconColorClassName = danger ? "accent-danger-foreground" : "accent-icon";
20 const icon = (
21 <SymbolView
22 name={props.icon}
23 size={Platform.OS === "android" ? 24 : 22}
24 tintColorClassName={iconColorClassName}
25 type="monochrome"
26 weight="regular"
27 />
28 );
29 const spinner = props.loading ? <ActivityIndicator colorClassName={iconColorClassName} /> : null;
30
31 if (Platform.OS === "android") {
32 return (
33 <MaterialListRow
34 title={props.label}
35 titleClassName={textClassName}
36 leading={icon}
37 trailing={spinner}
38 disabled={props.disabled}
39 onPress={props.onPress}
40 />
41 );
42 }
43
44 return (
45 <Pressable
46 accessibilityRole="button"
47 disabled={props.disabled}
48 onPress={props.onPress}
49 className="flex-row items-center gap-4 p-4 disabled:opacity-40"
50 >
51 {icon}
52 <Text className={cn("flex-1 text-lg", textClassName)}>{props.label}</Text>
53 {spinner}
54 </Pressable>
55 );
56}