| author | |
| committer | |
| log | abc80c80a73fdda78600184276da9e36cca89703 |
| tree | 81bbd28c12b0ff8bfc81e8e8ed7a2355380bb3a0 |
| parent | 9a2b9a2d4040c7e78c03a95d59b76b92e6bf2957 |
| signature | Signed by PGP key B5690EEEBB952194 |
3 files changed, 64 insertions(+), 43 deletions(-)
apps/mobile/src/components/ListChoiceRow.tsx created+60| ... | @@ -0,0 +1,60 @@ | ||
| 1 | import { Platform, View } from "react-native"; | ||
| 2 | import { useResolveClassNames } from "uniwind"; | ||
| 3 | |||
| 4 | import { cn } from "../lib/cn"; | ||
| 5 | import { SymbolView } from "./AppSymbol"; | ||
| 6 | import { ListRowLayout } from "./ListRowLayout"; | ||
| 7 | import { MaterialRadioIndicator } from "./MaterialRadioIndicator"; | ||
| 8 | import { RowPressable } from "./RowPressable"; | ||
| 9 | |||
| 10 | export 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 @@ |
| 1 | import { Pressable, View } from "react-native"; | 1 | export { ListChoiceRow as SettingsChoiceRow } from "../../../components/ListChoiceRow"; |
| 2 | |||
| 3 | import { AppText as Text } from "../../../components/AppText"; | ||
| 4 | import { SymbolView } from "../../../components/AppSymbol"; | ||
| 5 | |||
| 6 | export 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 | } |