1import type { ReactNode } from "react";
2import { Pressable, View } from "react-native";
3import { AppText as Text } from "../../components/AppText";
4import { SymbolView } from "../../components/AppSymbol";
5import { cn } from "../../lib/cn";
6import type { ModelOption } from "../../lib/modelOptions";
7
8export type ModelRowProps = {
9 readonly option: ModelOption;
10 readonly selected: boolean;
11 readonly onPress: () => void;
12 readonly isFavorite: boolean;
13 readonly favoritesLoaded: boolean;
14 readonly onToggleFavorite: () => void;
15 readonly onToggleHidden?: () => void;
16 readonly hiddenUpdatePending?: boolean;
17 readonly isFirst: boolean;
18 readonly isLast: boolean;
19};
20
21export type ChoiceRowProps = {
22 readonly label: string;
23 readonly description?: string;
24 readonly selected: boolean;
25 readonly onPress: () => void;
26 readonly isLast: boolean;
27};
28
29type RowSelectionProps = {
30 readonly leadingSelection?: ReactNode;
31 readonly trailingSelection?: ReactNode;
32 readonly minimumHeight?: number;
33};
34
35export function ModelRowContent(
36 props: ModelRowProps &
37 RowSelectionProps & {
38 readonly labelNumberOfLines: number;
39 readonly selectedClassName?: string;
40 },
41) {
42 return (
43 <View
44 style={props.minimumHeight === undefined ? undefined : { minHeight: props.minimumHeight }}
45 className={cn(
46 "mx-4 min-h-11 flex-row items-center gap-2 bg-grouped-card px-4",
47 props.selectedClassName,
48 props.isFirst && "rounded-t-2xl",
49 props.isLast ? "rounded-b-2xl" : "border-b border-border-subtle",
50 )}
51 >
52 <Pressable
53 accessibilityLabel={[props.option.label, props.option.subtitle].filter(Boolean).join(", ")}
54 accessibilityRole="radio"
55 accessibilityState={{
56 checked: props.selected,
57 disabled: props.option.isUnavailable === true || props.option.isHidden === true,
58 }}
59 className="min-h-11 min-w-0 flex-1 flex-row items-center gap-2 active:opacity-70"
60 disabled={props.option.isUnavailable || props.option.isHidden}
61 onPress={props.onPress}
62 >
63 {props.leadingSelection}
64 <View className="min-w-0 flex-1">
65 <View className="flex-row items-center gap-2">
66 <Text
67 className="min-w-0 shrink text-base font-t3-medium text-foreground"
68 numberOfLines={props.labelNumberOfLines}
69 >
70 {props.option.label}
71 </Text>
72 {props.option.isDefault ? (
73 <View className="rounded-md bg-subtle-strong px-1.5 py-0.5">
74 <Text className="text-3xs font-t3-bold text-foreground-muted">Default</Text>
75 </View>
76 ) : null}
77 {props.option.isLegacy ? (
78 <View className="rounded-md bg-subtle px-1.5 py-0.5">
79 <Text className="text-3xs font-t3-bold text-foreground-muted">Legacy</Text>
80 </View>
81 ) : null}
82 {props.option.isHidden ? (
83 <Text className="text-xs text-foreground-muted">Hidden</Text>
84 ) : null}
85 {props.option.isUnavailable ? (
86 <Text className="text-xs text-foreground">Unavailable</Text>
87 ) : null}
88 </View>
89 {props.option.subtitle ? (
90 <Text
91 className="text-xs text-foreground-muted"
92 numberOfLines={props.labelNumberOfLines}
93 >
94 {props.option.subtitle}
95 </Text>
96 ) : null}
97 </View>
98 {props.trailingSelection}
99 </Pressable>
100 {props.onToggleHidden ? (
101 <Pressable
102 accessibilityLabel={`${props.option.isHidden ? "Show" : "Hide"} model: ${props.option.providerLabel}, ${props.option.label}`}
103 accessibilityRole="button"
104 accessibilityState={{ disabled: props.hiddenUpdatePending === true }}
105 className="min-h-11 min-w-11 items-center justify-center"
106 disabled={props.hiddenUpdatePending}
107 onPress={props.onToggleHidden}
108 >
109 <SymbolView
110 name={props.option.isHidden ? "eye.slash" : "eye"}
111 size={18}
112 tintColorClassName={props.option.isHidden ? "accent-icon-subtle" : "accent-icon"}
113 type="monochrome"
114 />
115 </Pressable>
116 ) : null}
117 <Pressable
118 accessibilityLabel={`${props.isFavorite ? "Remove from" : "Add to"} favorites: ${
119 props.option.providerLabel
120 }, ${props.option.label}`}
121 accessibilityRole="button"
122 accessibilityState={{ disabled: !props.favoritesLoaded, selected: props.isFavorite }}
123 className="min-h-11 min-w-11 items-center justify-center"
124 disabled={!props.favoritesLoaded}
125 onPress={props.onToggleFavorite}
126 >
127 <SymbolView
128 name={props.isFavorite ? "star.fill" : "star"}
129 size={18}
130 tintColorClassName={props.isFavorite ? "accent-icon" : "accent-icon-subtle"}
131 type="monochrome"
132 />
133 </Pressable>
134 </View>
135 );
136}
137
138/** Single option inside a submenu panel. */
139export function ChoiceRowContent(props: ChoiceRowProps & RowSelectionProps) {
140 return (
141 <Pressable
142 accessibilityLabel={props.description ? `${props.label}. ${props.description}` : props.label}
143 accessibilityRole="radio"
144 accessibilityState={{ checked: props.selected }}
145 onPress={props.onPress}
146 style={props.minimumHeight === undefined ? undefined : { minHeight: props.minimumHeight }}
147 className={cn(
148 "min-h-14 flex-row items-center gap-3 bg-grouped-card px-4 py-3 active:bg-subtle",
149 !props.isLast && "border-b border-border-subtle",
150 )}
151 >
152 {props.leadingSelection}
153 <View className="min-w-0 flex-1 gap-0.5">
154 <Text className="text-base font-t3-medium text-foreground">{props.label}</Text>
155 {props.description ? (
156 <Text className="text-sm leading-5 text-foreground-muted">{props.description}</Text>
157 ) : null}
158 </View>
159 {props.trailingSelection}
160 </Pressable>
161 );
162}