1import type { ComponentProps, ReactNode } from "react";
2import { Platform, Pressable, View } from "react-native";
3
4import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider";
5import { cn } from "../lib/cn";
6import { AppText } from "./AppText";
7import { SymbolView } from "./AppSymbol";
8import { useAndroidControlSizing } from "./useAndroidControlSizing";
9
10/** Shared geometry for Material navigation and selection lists. Group rows in one card. */
11export function MaterialListRow({
12 title,
13 titleClassName,
14 subtitle,
15 subtitleNumberOfLines,
16 subtitleEllipsizeMode,
17 leading,
18 trailing,
19 className,
20 ...props
21}: Omit<ComponentProps<typeof Pressable>, "children"> & {
22 readonly title: string;
23 readonly titleClassName?: string;
24 readonly subtitle?: string | null;
25 readonly subtitleNumberOfLines?: ComponentProps<typeof AppText>["numberOfLines"];
26 readonly subtitleEllipsizeMode?: ComponentProps<typeof AppText>["ellipsizeMode"];
27 readonly leading?: ReactNode;
28 readonly trailing?: ReactNode;
29}) {
30 const { themeVariables } = useAppearancePreferences();
31 const { smallIconSize } = useAndroidControlSizing();
32 return (
33 <Pressable
34 accessibilityRole="button"
35 accessibilityLabel={[title, subtitle].filter(Boolean).join(", ")}
36 android_ripple={{ color: themeVariables["--color-subtle-strong"] }}
37 // Give the scroll view time to claim drags before starting the ripple.
38 unstable_pressDelay={Platform.OS === "android" ? 50 : undefined}
39 {...props}
40 className={cn(
41 "flex-row items-center gap-4 overflow-hidden bg-card px-4 py-3",
42 subtitle ? "min-h-18" : "min-h-14",
43 props.disabled && "opacity-45",
44 className,
45 )}
46 >
47 {leading ? <View className="size-6 items-center justify-center">{leading}</View> : null}
48 <View className="min-w-0 flex-1 gap-1">
49 <AppText className={cn("text-base text-foreground", titleClassName)} numberOfLines={2}>
50 {title}
51 </AppText>
52 {subtitle ? (
53 <AppText
54 className="text-sm text-foreground-muted"
55 ellipsizeMode={subtitleEllipsizeMode}
56 numberOfLines={subtitleNumberOfLines ?? 2}
57 >
58 {subtitle}
59 </AppText>
60 ) : null}
61 </View>
62 {trailing !== undefined ? (
63 trailing
64 ) : !props.disabled ? (
65 <SymbolView name="chevron.right" size={smallIconSize} tintColorClassName="accent-chevron" />
66 ) : null}
67 </Pressable>
68 );
69}