authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-10-11 22:31:14-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-11 22:31:14-07:00
logabc80c80a73fdda78600184276da9e36cca89703
tree81bbd28c12b0ff8bfc81e8e8ed7a2355380bb3a0
parent9a2b9a2d4040c7e78c03a95d59b76b92e6bf2957
signature Signed by PGP key B5690EEEBB952194

refactor(mobile): share themed choice rows in settings (#18516)


3 files changed, 64 insertions(+), 43 deletions(-)

apps/mobile/src/components/ListChoiceRow.tsx created+60
...@@ -0,0 +1,60 @@
1import { Platform, View } from "react-native";
2import { useResolveClassNames } from "uniwind";
3
4import { cn } from "../lib/cn";
5import { SymbolView } from "./AppSymbol";
6import { ListRowLayout } from "./ListRowLayout";
7import { MaterialRadioIndicator } from "./MaterialRadioIndicator";
8import { RowPressable } from "./RowPressable";
9
10export function ListChoiceRow(props: {
11 readonly label: string;
12 readonly description?: string;
13 readonly selected: boolean;
14 readonly separated?: boolean;
15 readonly disabled?: boolean;
16 readonly onPress: () => void;
17}) {
18 const rippleStyle = useResolveClassNames("bg-subtle-strong");
19 return (
20 <RowPressable
21 accessibilityRole="radio"
22 accessibilityLabel={props.label}
23 accessibilityHint={props.description}
24 accessibilityState={{ checked: props.selected, disabled: Boolean(props.disabled) }}
25 disabled={props.disabled}
26 onPress={props.onPress}
27 android_ripple={{ color: rippleStyle.backgroundColor }}
28 unstable_pressDelay={Platform.OS === "android" ? 50 : undefined}
29 className={cn(
30 props.selected ? "bg-subtle" : "bg-grouped-card",
31 props.separated && "border-t border-border-subtle",
32 )}
33 >
34 <ListRowLayout
35 label={props.label}
36 description={props.description}
37 disabled={props.disabled}
38 accessory={
39 <View
40 accessibilityElementsHidden
41 importantForAccessibility="no-hide-descendants"
42 className="size-6 items-center justify-center"
43 >
44 {Platform.OS === "android" ? (
45 <MaterialRadioIndicator selected={props.selected} />
46 ) : props.selected ? (
47 <SymbolView
48 name="checkmark"
49 size={18}
50 tintColorClassName="accent-primary-text"
51 type="monochrome"
52 weight="semibold"
53 />
54 ) : null}
55 </View>
56 }
57 />
58 </RowPressable>
59 );
60}
apps/mobile/src/components/MaterialRadioIndicator.android.tsx+3-2
...@@ -22,7 +22,8 @@ export function MaterialRadioIndicator({ selected }: { readonly selected: boolea...@@ -22,7 +22,8 @@ export function MaterialRadioIndicator({ selected }: { readonly selected: boolea
22 height: 20,22 height: 20,
23 borderRadius: 10,23 borderRadius: 10,
24 borderWidth: 2,24 borderWidth: 2,
25 borderColor: themeVariables[selected ? "--color-primary" : "--color-foreground-muted"],25 borderColor:
26 themeVariables[selected ? "--color-primary-text" : "--color-foreground-muted"],
26 alignItems: "center",27 alignItems: "center",
27 justifyContent: "center",28 justifyContent: "center",
28 }}29 }}
...@@ -33,7 +34,7 @@ export function MaterialRadioIndicator({ selected }: { readonly selected: boolea...@@ -33,7 +34,7 @@ export function MaterialRadioIndicator({ selected }: { readonly selected: boolea
33 width: 10,34 width: 10,
34 height: 10,35 height: 10,
35 borderRadius: 5,36 borderRadius: 5,
36 backgroundColor: themeVariables["--color-primary"],37 backgroundColor: themeVariables["--color-primary-text"],
37 }}38 }}
38 />39 />
39 ) : null}40 ) : null}
apps/mobile/src/features/settings/components/SettingsChoiceRow.tsx+1-41
...@@ -1,41 +1 @@...@@ -1,41 +1 @@
1import { Pressable, View } from "react-native";1export { ListChoiceRow as SettingsChoiceRow } from "../../../components/ListChoiceRow";
2
3import { AppText as Text } from "../../../components/AppText";
4import { SymbolView } from "../../../components/AppSymbol";
5
6export function SettingsChoiceRow(props: {
7 readonly label: string;
8 readonly description: string;
9 readonly selected: boolean;
10 readonly separated: boolean;
11 readonly disabled: boolean;
12 readonly onPress: () => void;
13}) {
14 return (
15 <Pressable
16 accessibilityRole="radio"
17 accessibilityState={{ checked: props.selected, disabled: props.disabled }}
18 className={
19 props.separated
20 ? "flex-row items-center gap-4 border-t border-border-subtle p-4 active:opacity-70"
21 : "flex-row items-center gap-4 p-4 active:opacity-70"
22 }
23 disabled={props.disabled}
24 onPress={props.onPress}
25 >
26 <View className="min-w-0 flex-1 gap-1">
27 <Text className="text-lg text-foreground android:text-base">{props.label}</Text>
28 <Text className="text-sm leading-normal text-foreground-muted">{props.description}</Text>
29 </View>
30 {props.selected ? (
31 <SymbolView
32 name="checkmark"
33 size={18}
34 tintColorClassName="accent-icon"
35 type="monochrome"
36 weight="semibold"
37 />
38 ) : null}
39 </Pressable>
40 );
41}