Skip to content
Source
Sign inSign up

SliderRow.android.tsx

79 lines3.0 KB

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