1import { Host, Slider } from "@expo/ui/jetpack-compose";
2import { fillMaxWidth } from "@expo/ui/jetpack-compose/modifiers";
3import * as Haptics from "expo-haptics";
4import { useRef, type ComponentProps } from "react";
5import { View } from "react-native";
6
7import { AppText as Text } from "../../../../components/AppText";
8import { SymbolView } from "../../../../components/AppSymbol";
9import { useAppearancePreferences } from "../AppearancePreferencesProvider";
10import type { FontSizeSliderRow as SharedFontSizeSliderRow } from "./FontSizeSliderRow.shared";
11
12export function FontSizeSliderRow(props: ComponentProps<typeof SharedFontSizeSliderRow>) {
13 const {
14 themeAppearance,
15 systemColorsActive,
16 themeVariables: colors,
17 } = useAppearancePreferences();
18 const draft = useRef(props.value);
19 const commit = (value: number) => {
20 if (props.disabled) return;
21 const next = Math.min(
22 props.max,
23 Math.max(props.min, props.min + Math.round((value - props.min) / props.step) * props.step),
24 );
25 if (next === props.value) return;
26 void Haptics.selectionAsync().catch(() => undefined);
27 props.onChange(next);
28 };
29 return (
30 <View className={props.disabled ? "gap-2 p-4 opacity-45" : "gap-2 p-4"}>
31 <View className="flex-row items-center gap-4">
32 <SymbolView name={props.icon} size={24} tintColorClassName="accent-icon" />
33 <Text className="min-w-0 flex-1 text-base">{props.label}</Text>
34 <Text className="text-sm text-foreground-muted">{props.valueLabel}</Text>
35 </View>
36 <View
37 accessible
38 accessibilityLabel={props.label}
39 accessibilityRole="adjustable"
40 accessibilityState={{ disabled: Boolean(props.disabled) }}
41 accessibilityValue={{
42 min: props.min,
43 max: props.max,
44 now: props.value,
45 text: props.valueLabel,
46 }}
47 accessibilityActions={[{ name: "increment" }, { name: "decrement" }]}
48 onAccessibilityAction={({ nativeEvent }) => {
49 if (nativeEvent.actionName === "increment") commit(props.value + props.step);
50 else if (nativeEvent.actionName === "decrement") commit(props.value - props.step);
51 }}
52 >
53 <View importantForAccessibility="no-hide-descendants">
54 <Host
55 colorScheme={themeAppearance}
56 seedColor={systemColorsActive ? undefined : colors["--color-primary"]}
57 style={{ height: 48, width: "100%" }}
58 >
59 <Slider
60 min={props.min}
61 max={props.max}
62 value={props.value}
63 steps={Math.max(0, Math.round((props.max - props.min) / props.step) - 1)}
64 enabled={!props.disabled}
65 modifiers={[fillMaxWidth()]}
66 colors={{
67 thumbColor: colors["--color-primary"],
68 activeTrackColor: colors["--color-primary"],
69 inactiveTrackColor: colors["--color-secondary"],
70 }}
71 onValueChange={(value) => {
72 draft.current = value;
73 }}
74 onValueChangeFinished={() => commit(draft.current)}
75 />
76 </Host>
77 </View>
78 </View>
79 </View>
80 );
81}