1import DateTimePicker, {
2  DateTimePickerEvent,
3  IOSNativeProps,
4} from '@react-native-community/datetimepicker';
5import SegmentedControl from '@react-native-segmented-control/segmented-control';
6import moment from 'moment';
7import React, { useRef, useState } from 'react';
8import {
9  SafeAreaView,
10  ScrollView,
11  StyleSheet,
12  View,
13  Text,
14  StatusBar,
15  Platform,
16  TextInput,
17  useColorScheme,
18  Switch,
19  TextProps,
20  TextInputProps,
21  Button,
22} from 'react-native';
23import { Colors } from 'react-native/Libraries/NewAppScreen';
24
25export const DAY_OF_WEEK = Object.freeze({
26  Sunday: 0,
27  Monday: 1,
28  Tuesday: 2,
29  Wednesday: 3,
30  Thursday: 4,
31  Friday: 5,
32  Saturday: 6,
33});
34
35const ThemedText = (props: TextProps) => {
36  const isDarkMode = useColorScheme() === 'dark';
37
38  const textColorByMode = { color: isDarkMode ? Colors.white : Colors.black };
39
40  const TextElement = React.createElement(Text, props);
41  return React.cloneElement(TextElement, {
42    style: [props.style, textColorByMode],
43  });
44};
45const ThemedTextInput = (props: TextInputProps) => {
46  const isDarkMode = useColorScheme() === 'dark';
47
48  const textColorByMode = { color: isDarkMode ? Colors.white : Colors.black };
49
50  const TextElement = React.createElement(TextInput, props);
51  return React.cloneElement(TextElement, {
52    style: [props.style, styles.textInput, textColorByMode],
53    placeholderTextColor: isDarkMode ? Colors.white : Colors.black,
54  });
55};
56
57type Mode = NonNullable<IOSNativeProps['mode']>;
58const MODE_VALUES = Platform.select({
59  ios: ['date', 'time', 'datetime', 'countdown'],
60  android: ['date', 'time'],
61}) as Mode[];
62const DISPLAY_VALUES = Platform.select({
63  ios: ['default', 'spinner', 'compact', 'inline'],
64  android: ['default', 'spinner'],
65})! as ['default', 'spinner'];
66
67type MinuteInterval = NonNullable<IOSNativeProps['minuteInterval']>;
68const MINUTE_INTERVALS: MinuteInterval[] = [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30];
69
70// This example is a refactored copy from https://github.com/react-native-community/react-native-datetimepicker/tree/master/example
71// Please try to keep it up to date when updating @react-native-community/datetimepicker package :)
72
73const DateTimePickerScreen = () => {
74  // Sat, 13 Nov 2021 10:00:00 GMT (local: Saturday, November 13, 2021 11:00:00 AM GMT+01:00)
75  const sourceMoment = moment.unix(1636797600);
76  const sourceDate = sourceMoment.local().toDate();
77  const [show, setShow] = useState(false);
78  const [date, setDate] = useState<Date>(sourceDate);
79  const [mode, setMode] = useState<Mode>(MODE_VALUES[0]);
80  const [textColor, setTextColor] = useState<string | undefined>();
81  const [accentColor, setAccentColor] = useState<string | undefined>();
82  const [display, setDisplay] = useState<'default' | 'spinner'>(DISPLAY_VALUES[0]);
83  const [interval, setMinInterval] = useState<MinuteInterval>(1);
84  const [neutralButtonLabel, setNeutralButtonLabel] = useState<string | undefined>();
85  const [disabled, setDisabled] = useState(false);
86  const [neutralButtonPressed, setNeutralButtonPressed] = useState<boolean>(false);
87
88  const scrollRef = useRef<ScrollView>(null);
89
90  const onChange = (event: DateTimePickerEvent, selectedDate?: Date | undefined) => {
91    if (Platform.OS === 'android') {
92      setShow(false);
93    }
94    const currentDate = selectedDate || date;
95    if (event.type === 'neutralButtonPressed') {
96      setNeutralButtonPressed(true);
97      setDate(new Date());
98    } else {
99      setNeutralButtonPressed(false);
100      setDate(currentDate);
101    }
102  };
103
104  const isDarkMode = useColorScheme() === 'dark';
105
106  const backgroundStyle = {
107    backgroundColor: isDarkMode ? Colors.dark : Colors.lighter,
108  };
109
110  return (
111    <SafeAreaView style={[backgroundStyle, { flex: 1 }]}>
112      <StatusBar barStyle="dark-content" />
113      <ScrollView
114        testID="DateTimePickerScrollView"
115        ref={scrollRef}
116        onContentSizeChange={() => {
117          if (Platform.OS === 'ios') {
118            scrollRef.current?.scrollToEnd({ animated: true });
119          }
120        }}>
121        {/* @ts-expect-error */}
122        {global.HermesInternal != null && (
123          <View style={styles.engine}>
124            <Text testID="hermesIndicator" style={styles.footer}>
125              Engine: Hermes
126            </Text>
127          </View>
128        )}
129        <View
130          testID="appRootView"
131          style={{ backgroundColor: isDarkMode ? Colors.black : Colors.white }}>
132          <ThemedText>mode prop:</ThemedText>
133          <SegmentedControl
134            values={MODE_VALUES}
135            selectedIndex={MODE_VALUES.indexOf(mode)}
136            onChange={(event) => {
137              setMode(MODE_VALUES[event.nativeEvent.selectedSegmentIndex]);
138            }}
139          />
140          <ThemedText>display prop:</ThemedText>
141          <SegmentedControl
142            values={DISPLAY_VALUES}
143            selectedIndex={DISPLAY_VALUES.indexOf(display)}
144            onChange={(event) => {
145              setDisplay(DISPLAY_VALUES[event.nativeEvent.selectedSegmentIndex]);
146            }}
147          />
148          <ThemedText>minute interval prop:</ThemedText>
149          <SegmentedControl
150            values={MINUTE_INTERVALS.map(String)}
151            selectedIndex={MINUTE_INTERVALS.indexOf(interval)}
152            onChange={(event) => {
153              setMinInterval(MINUTE_INTERVALS[event.nativeEvent.selectedSegmentIndex]);
154            }}
155          />
156          {Platform.OS === 'ios' && (
157            <>
158              <View style={styles.header}>
159                <ThemedText style={styles.textLabel}>text color (iOS only)</ThemedText>
160                <ThemedTextInput
161                  value={textColor}
162                  onChangeText={(text) => {
163                    setTextColor(text.toLowerCase());
164                  }}
165                  placeholder="textColor"
166                />
167              </View>
168              <View style={styles.header}>
169                <ThemedText style={styles.textLabel}>accent color (iOS only)</ThemedText>
170                <ThemedTextInput
171                  value={accentColor}
172                  onChangeText={(text) => {
173                    setAccentColor(text.toLowerCase());
174                  }}
175                  placeholder="accentColor"
176                />
177              </View>
178              <View style={styles.header}>
179                <ThemedText style={styles.textLabel}>disabled (iOS only)</ThemedText>
180                <Switch value={disabled} onValueChange={setDisabled} />
181              </View>
182            </>
183          )}
184          {Platform.OS === 'android' && (
185            <View style={styles.header}>
186              <ThemedText style={styles.textLabel}>neutralButtonLabel (android only)</ThemedText>
187              <ThemedTextInput
188                value={neutralButtonLabel}
189                onChangeText={setNeutralButtonLabel}
190                placeholder="neutralButtonLabel"
191                testID="neutralButtonLabelTextInput"
192              />
193            </View>
194          )}
195          <View style={[styles.button, { flexDirection: 'row', justifyContent: 'space-around' }]}>
196            <Button
197              testID="showPickerButton"
198              onPress={() => {
199                setShow(true);
200              }}
201              title="Show picker!"
202            />
203            <Button testID="hidePicker" onPress={() => setShow(false)} title="Hide picker!" />
204          </View>
205          <View
206            style={[
207              styles.header,
208              {
209                flexDirection: 'column',
210                justifyContent: 'space-around',
211                alignContent: 'space-around',
212              },
213            ]}>
214            <ThemedText testID="dateText" style={styles.dateTimeText}>
215              {moment(date).format('MM/DD/YYYY  HH:mm')}
216            </ThemedText>
217            {neutralButtonPressed && (
218              <ThemedText testID="neutralButtonPressed" style={styles.dateTimeText}>
219                Neutral button was pressed, date changed to now.
220              </ThemedText>
221            )}
222          </View>
223          {show && (
224            <DateTimePicker
225              testID="dateTimePicker"
226              minuteInterval={interval}
227              value={date}
228              mode={mode}
229              display={display}
230              onChange={onChange}
231              style={styles.iOsPicker}
232              textColor={textColor || undefined}
233              accentColor={accentColor || undefined}
234              disabled={disabled}
235              {...(Platform.OS !== 'android' && {
236                is24Hour: true,
237                neutralButton: neutralButtonLabel
238                  ? { label: neutralButtonLabel, textColor: 'grey' }
239                  : undefined,
240              })}
241            />
242          )}
243        </View>
244      </ScrollView>
245    </SafeAreaView>
246  );
247};
248
249const styles = StyleSheet.create({
250  scrollView: {
251    backgroundColor: Colors.lighter,
252  },
253  engine: {
254    position: 'absolute',
255    right: 0,
256  },
257  body: {
258    backgroundColor: Colors.white,
259  },
260  footer: {
261    color: Colors.dark,
262    fontSize: 12,
263    fontWeight: '600',
264    padding: 4,
265    paddingRight: 12,
266    textAlign: 'right',
267  },
268  container: {
269    marginTop: 32,
270    flex: 1,
271    justifyContent: 'center',
272    backgroundColor: '#F5FCFF',
273  },
274  containerWindows: {
275    marginTop: 32,
276    flex: 1,
277    justifyContent: 'center',
278    alignItems: 'center',
279    backgroundColor: '#F5FCFF',
280  },
281  header: {
282    justifyContent: 'center',
283    alignItems: 'center',
284    flexDirection: 'row',
285  },
286  textLabel: {
287    margin: 10,
288    flex: 1,
289  },
290  textInput: {
291    height: 60,
292    flex: 1,
293  },
294  button: {
295    alignItems: 'center',
296    marginBottom: 10,
297  },
298  resetButton: {
299    width: 150,
300  },
301  text: {
302    fontSize: 20,
303    fontWeight: 'bold',
304  },
305  dateTimeText: {
306    fontSize: 16,
307    fontWeight: 'normal',
308  },
309  iOsPicker: {
310    flex: 1,
311    marginTop: 30,
312  },
313  windowsPicker: {
314    flex: 1,
315    paddingTop: 10,
316    width: 350,
317  },
318});
319
320DateTimePickerScreen.navigationOptions = {
321  title: 'DateTimePicker',
322};
323
324export default DateTimePickerScreen;
325