Skip to content
Source
Sign inSign up

NumberField.tsx

113 lines3.7 KB

1import { Button, HStack, Picker, Popover, Spacer, Text, VStack } from "@expo/ui/swift-ui";
2import {
3 accessibilityLabel,
4 buttonBorderShape,
5 buttonStyle,
6 disabled,
7 font,
8 foregroundStyle,
9 frame,
10 labelStyle,
11 padding,
12 pickerStyle,
13 presentationBackground,
14 tag,
15 tint,
16} from "@expo/ui/swift-ui/modifiers";
17import { useState } from "react";
18​
19import { NativeThemeHost } from "./NativeThemeHost";
20import { useControlTheme } from "./useControlTheme";
21import type { NumberFieldProps } from "./NumberField.types";
22​
23export function NumberField(props: NumberFieldProps) {
24 const { colors, fontSize } = useControlTheme();
25 const [open, setOpen] = useState(false);
26 const [draft, setDraft] = useState(props.value);
27 const values = Array.from({ length: props.max - props.min + 1 }, (_, index) => props.min + index);
28 if (!values.includes(draft)) values.push(draft);
29 values.sort((a, b) => a - b);
30 return (
31 <NativeThemeHost matchContents>
32 <Popover isPresented={open} onIsPresentedChange={setOpen}>
33 <Popover.Trigger>
34 <Button
35 onPress={() => {
36 if (props.disabled) return;
37 setDraft(props.value);
38 setOpen(true);
39 }}
40 modifiers={[
41 buttonStyle("bordered"),
42 disabled(props.disabled),
43 accessibilityLabel(
44 `${props.label}: ${props.formatValue?.(props.value) ?? props.value}`,
45 ),
46 frame({ minWidth: 64, minHeight: 44 }),
47 foregroundStyle(colors["--color-primary-text"]),
48 font({ size: fontSize }),
49 ]}
50 >
51 <Text>{String(props.value)}</Text>
52 </Button>
53 </Popover.Trigger>
54 <Popover.Content>
55 <VStack
56 modifiers={[
57 padding({ all: 12 }),
58 frame({ width: 240 }),
59 presentationBackground(colors["--color-sheet-solid"]),
60 ]}
61 >
62 <Picker
63 label={props.label}
64 selection={draft}
65 onSelectionChange={setDraft}
66 modifiers={[pickerStyle("wheel"), frame({ height: 180 })]}
67 >
68 {values.map((value) => (
69 <Text
70 key={value}
71 modifiers={[tag(value), foregroundStyle(colors["--color-foreground"])]}
72 >
73 {props.formatValue?.(value) ?? String(value)}
74 </Text>
75 ))}
76 </Picker>
77 <HStack>
78 <Button
79 label="Cancel"
80 systemImage="xmark"
81 onPress={() => setOpen(false)}
82 modifiers={[
83 labelStyle("iconOnly"),
84 buttonStyle("bordered"),
85 buttonBorderShape("circle"),
86 tint(colors["--color-foreground"]),
87 ]}
88 />
89 <Spacer />
90 <Button
91 label="Done"
92 systemImage="checkmark"
93 onPress={() => {
94 setOpen(false);
95 if (!props.disabled && draft !== props.value) props.onValueChange(draft);
96 }}
97 modifiers={[
98 labelStyle("iconOnly"),
99 buttonStyle("borderedProminent"),
100 buttonBorderShape("circle"),
101 tint(colors["--color-prominent-action"]),
102 foregroundStyle(colors["--color-prominent-action-foreground"]),
103 ]}
104 />
105 </HStack>
106 </VStack>
107 </Popover.Content>
108 </Popover>
109 </NativeThemeHost>
110 );
112​
113export type { NumberFieldProps } from "./NumberField.types";