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";
99/** Shared geometry for Material navigation and selection lists. Group rows in one card. */
1010export function MaterialListRow({
1111 title,
12 titleClassName,
1213 subtitle,
1314 leading,
1415 trailing,
......@@ -16,6 +17,7 @@ export function MaterialListRow({
1617 ...props
1718}: Omit<ComponentProps<typeof Pressable>, "children"> & {
1819 readonly title: string;
20 readonly titleClassName?: string;
1921 readonly subtitle?: string | null;
2022 readonly leading?: ReactNode;
2123 readonly trailing?: ReactNode;
......@@ -38,7 +40,7 @@ export function MaterialListRow({
3840 >
3941 {leading ? <View className="size-6 items-center justify-center">{leading}</View> : null}
4042 <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}>
4244 {title}
4345 </AppText>
4446 {subtitle ? (
apps/mobile/src/features/diagnostics/SettingsDiagnosticsRouteScreen.tsx+6-16
......@@ -2,12 +2,13 @@ import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollVie
22import Constants from "expo-constants";
33import * as Updates from "expo-updates";
44import { useEffect, useState } from "react";
5import { ActivityIndicator, Platform, Pressable, View } from "react-native";
5import { ActivityIndicator, Platform, View } from "react-native";
66import { useSafeAreaInsets } from "react-native-safe-area-context";
77
88import { SymbolView } from "../../components/AppSymbol";
99import { AppText as Text } from "../../components/AppText";
1010import { tryCopyTextWithHaptic } from "../../lib/copyTextWithHaptic";
11import { SettingsActionRow } from "../settings/components/SettingsActionRow";
1112import { SettingsScreen } from "../settings/components/SettingsScreen";
1213import { SettingsSection } from "../settings/components/SettingsSection";
1314import {
......@@ -108,23 +109,12 @@ export function SettingsDiagnosticsRouteScreen() {
108109
109110 <View className="gap-3">
110111 <SettingsSection title="Actions">
111 <Pressable
112 accessibilityRole="button"
112 <SettingsActionRow
113 icon={copied ? "checkmark" : "doc.on.doc"}
114 label={copied ? "Copied" : "Copy crash report"}
113115 disabled={state.status !== "ready"}
114116 onPress={() => void copyReport()}
115 className="flex-row items-center gap-4 p-4 disabled:opacity-40"
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>
117 />
128118 </SettingsSection>
129119 <Text className="px-2 text-sm leading-normal text-foreground-muted">
130120 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 {
1616} from "../../state/client-cache-state";
1717import { useServerConfigs } from "../../state/entities";
1818import { useSavedRemoteConnections } from "../../state/use-remote-environment-registry";
19import { SettingsActionRow } from "./components/SettingsActionRow";
1920import { SettingsSection } from "./components/SettingsSection";
2021import { SettingsScreen } from "./components/SettingsScreen";
2122
......@@ -139,24 +140,14 @@ export function SettingsClientStorageRouteScreen() {
139140
140141 <View className="gap-3">
141142 <SettingsSection title="Actions">
142 <Pressable
143 accessibilityRole="button"
143 <SettingsActionRow
144 icon="trash"
145 label={summary ? `Clear ${formatBytes(summary.payloadBytes)}` : "Clear caches"}
146 tone="danger"
144147 disabled={isClearing || !summary || summary.recordCount === 0}
148 loading={isClearing}
145149 onPress={confirmClearAll}
146 className="flex-row items-center gap-4 p-4 disabled:opacity-40"
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>
150 />
160151 </SettingsSection>
161152 <Text className="px-2 text-sm leading-normal text-foreground-muted">
162153 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}