| author | |
| committer | |
| log | 9a2b9a2d4040c7e78c03a95d59b76b92e6bf2957 |
| tree | 4151df94449e4e56bb398509f4a7ac96d267a18b |
| parent | a39fd7be954379dfedab0f1d1e2fd8c02d748e9b |
| signature | Signed by PGP key B5690EEEBB952194 |
3 files changed, 53 insertions(+), 77 deletions(-)
apps/mobile/src/components/ListToggleRow.tsx created+45| ... | @@ -0,0 +1,45 @@ | ||
| 1 | import { Pressable } from "react-native"; | ||
| 2 | |||
| 3 | import { AppText } from "./AppText"; | ||
| 4 | import { ListRowLayout, type ListRowContentProps } from "./ListRowLayout"; | ||
| 5 | import { ThemedSwitch } from "./ThemedSwitch"; | ||
| 6 | |||
| 7 | /** A null value means the selected environments disagree; activation sets them all on. */ | ||
| 8 | export function ListToggleRow( | ||
| 9 | props: Omit<ListRowContentProps, "detail"> & { | ||
| 10 | readonly value: boolean | null; | ||
| 11 | readonly onValueChange: (value: boolean) => void; | ||
| 12 | }, | ||
| 13 | ) { | ||
| 14 | return ( | ||
| 15 | <ListRowLayout | ||
| 16 | icon={props.icon} | ||
| 17 | label={props.label} | ||
| 18 | description={props.description} | ||
| 19 | disabled={props.disabled} | ||
| 20 | accessory={ | ||
| 21 | props.value === null ? ( | ||
| 22 | <Pressable | ||
| 23 | accessibilityLabel={`Set ${props.label} on for selected environments`} | ||
| 24 | accessibilityRole="button" | ||
| 25 | accessibilityHint={props.description} | ||
| 26 | accessibilityState={{ disabled: Boolean(props.disabled) }} | ||
| 27 | disabled={props.disabled} | ||
| 28 | className="rounded-full bg-subtle px-3 py-2 active:opacity-70" | ||
| 29 | onPress={() => props.onValueChange(true)} | ||
| 30 | > | ||
| 31 | <AppText className="text-sm font-t3-medium text-foreground">Mixed · Set on</AppText> | ||
| 32 | </Pressable> | ||
| 33 | ) : ( | ||
| 34 | <ThemedSwitch | ||
| 35 | accessibilityLabel={props.label} | ||
| 36 | accessibilityHint={props.description} | ||
| 37 | disabled={props.disabled} | ||
| 38 | onValueChange={props.onValueChange} | ||
| 39 | value={props.value} | ||
| 40 | /> | ||
| 41 | ) | ||
| 42 | } | ||
| 43 | /> | ||
| 44 | ); | ||
| 45 | } | ||
apps/mobile/src/features/settings/components/SettingsControlRow.tsx deleted-39| ... | @@ -1,39 +0,0 @@ | ||
| 1 | import type { ComponentProps, ReactNode } from "react"; | ||
| 2 | import { Platform, View } from "react-native"; | ||
| 3 | |||
| 4 | import { SymbolView } from "../../../components/AppSymbol"; | ||
| 5 | import { AppText as Text } from "../../../components/AppText"; | ||
| 6 | import { cn } from "../../../lib/cn"; | ||
| 7 | |||
| 8 | export function SettingsControlRow(props: { | ||
| 9 | readonly disabled?: boolean; | ||
| 10 | readonly icon: ComponentProps<typeof SymbolView>["name"]; | ||
| 11 | readonly label: string; | ||
| 12 | readonly subtitle?: string; | ||
| 13 | readonly children: ReactNode; | ||
| 14 | }) { | ||
| 15 | return ( | ||
| 16 | <View | ||
| 17 | className={cn( | ||
| 18 | "flex-row items-center gap-4 p-4 android:min-h-14 android:py-3", | ||
| 19 | props.subtitle && "android:min-h-18", | ||
| 20 | props.disabled && "opacity-[0.45]", | ||
| 21 | )} | ||
| 22 | > | ||
| 23 | <SymbolView | ||
| 24 | name={props.icon} | ||
| 25 | size={Platform.OS === "android" ? 24 : 22} | ||
| 26 | tintColorClassName="accent-icon" | ||
| 27 | type="monochrome" | ||
| 28 | weight="regular" | ||
| 29 | /> | ||
| 30 | <View className="min-w-0 flex-1 android:gap-1"> | ||
| 31 | <Text className="text-lg text-foreground android:text-base">{props.label}</Text> | ||
| 32 | {props.subtitle ? ( | ||
| 33 | <Text className="text-sm text-foreground-muted">{props.subtitle}</Text> | ||
| 34 | ) : null} | ||
| 35 | </View> | ||
| 36 | {props.children} | ||
| 37 | </View> | ||
| 38 | ); | ||
| 39 | } | ||
apps/mobile/src/features/settings/components/SettingsSwitchRow.tsx+8-38| ... | @@ -1,42 +1,12 @@ | ... | @@ -1,42 +1,12 @@ |
| 1 | import type { ComponentProps } from "react"; | 1 | import type { ComponentProps } from "react"; |
| 2 | import { Pressable } from "react-native"; | ||
| 3 | 2 | ||
| 4 | import { AppText as Text } from "../../../components/AppText"; | 3 | import { ListToggleRow } from "../../../components/ListToggleRow"; |
| 5 | import { ThemedSwitch } from "../../../components/ThemedSwitch"; | ||
| 6 | import { SettingsControlRow } from "./SettingsControlRow"; | ||
| 7 | 4 | ||
| 8 | export function SettingsSwitchRow( | 5 | export function SettingsSwitchRow({ |
| 9 | props: Omit<ComponentProps<typeof SettingsControlRow>, "children"> & { | 6 | subtitle, |
| 10 | readonly value: boolean | null; | 7 | ...props |
| 11 | readonly onValueChange: (value: boolean) => void; | 8 | }: Omit<ComponentProps<typeof ListToggleRow>, "description"> & { |
| 12 | }, | 9 | readonly subtitle?: string; |
| 13 | ) { | 10 | }) { |
| 14 | return ( | 11 | return <ListToggleRow {...props} description={subtitle} />; |
| 15 | <SettingsControlRow | ||
| 16 | disabled={props.disabled} | ||
| 17 | icon={props.icon} | ||
| 18 | label={props.label} | ||
| 19 | subtitle={props.subtitle} | ||
| 20 | > | ||
| 21 | {props.value === null ? ( | ||
| 22 | <Pressable | ||
| 23 | accessibilityLabel={`Set ${props.label} on for selected environments`} | ||
| 24 | accessibilityRole="button" | ||
| 25 | disabled={props.disabled} | ||
| 26 | className="rounded-full bg-subtle px-3 py-2 active:opacity-70" | ||
| 27 | onPress={() => props.onValueChange(true)} | ||
| 28 | > | ||
| 29 | <Text className="text-sm font-t3-medium text-foreground">Mixed · Set on</Text> | ||
| 30 | </Pressable> | ||
| 31 | ) : ( | ||
| 32 | <ThemedSwitch | ||
| 33 | style={{ alignSelf: "center" }} | ||
| 34 | accessibilityLabel={props.label} | ||
| 35 | disabled={props.disabled} | ||
| 36 | onValueChange={props.onValueChange} | ||
| 37 | value={props.value} | ||
| 38 | /> | ||
| 39 | )} | ||
| 40 | </SettingsControlRow> | ||
| 41 | ); | ||
| 42 | } | 12 | } |