ferns-ui 0.36.4 → 0.36.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/package.json +3 -4
  2. package/src/ActionSheet.tsx +1231 -0
  3. package/src/Avatar.tsx +317 -0
  4. package/src/Badge.tsx +65 -0
  5. package/src/Banner.tsx +124 -0
  6. package/src/BlurBox.native.tsx +40 -0
  7. package/src/BlurBox.tsx +31 -0
  8. package/src/Body.tsx +32 -0
  9. package/src/Box.tsx +308 -0
  10. package/src/Button.tsx +219 -0
  11. package/src/Card.tsx +23 -0
  12. package/src/CheckBox.tsx +118 -0
  13. package/src/Common.ts +2743 -0
  14. package/src/Constants.ts +53 -0
  15. package/src/CustomSelect.tsx +85 -0
  16. package/src/DateTimeActionSheet.tsx +409 -0
  17. package/src/DateTimeField.android.tsx +101 -0
  18. package/src/DateTimeField.ios.tsx +83 -0
  19. package/src/DateTimeField.tsx +69 -0
  20. package/src/DecimalRangeActionSheet.tsx +113 -0
  21. package/src/ErrorBoundary.tsx +37 -0
  22. package/src/ErrorPage.tsx +44 -0
  23. package/src/FernsProvider.tsx +21 -0
  24. package/src/Field.tsx +299 -0
  25. package/src/FieldWithLabels.tsx +36 -0
  26. package/src/FlatList.tsx +2 -0
  27. package/src/Form.tsx +182 -0
  28. package/src/HeaderButtons.tsx +107 -0
  29. package/src/Heading.tsx +53 -0
  30. package/src/HeightActionSheet.tsx +104 -0
  31. package/src/Hyperlink.tsx +181 -0
  32. package/src/Icon.tsx +24 -0
  33. package/src/IconButton.tsx +165 -0
  34. package/src/Image.tsx +50 -0
  35. package/src/ImageBackground.tsx +14 -0
  36. package/src/InfoTooltipButton.tsx +23 -0
  37. package/src/Layer.tsx +17 -0
  38. package/src/Link.tsx +17 -0
  39. package/src/Mask.tsx +21 -0
  40. package/src/MediaQuery.ts +46 -0
  41. package/src/Meta.tsx +9 -0
  42. package/src/Modal.tsx +248 -0
  43. package/src/ModalSheet.tsx +58 -0
  44. package/src/NumberPickerActionSheet.tsx +66 -0
  45. package/src/Page.tsx +133 -0
  46. package/src/Permissions.ts +44 -0
  47. package/src/PickerSelect.tsx +553 -0
  48. package/src/Pill.tsx +24 -0
  49. package/src/Pog.tsx +87 -0
  50. package/src/ProgressBar.tsx +55 -0
  51. package/src/ScrollView.tsx +2 -0
  52. package/src/SegmentedControl.tsx +102 -0
  53. package/src/SelectList.tsx +89 -0
  54. package/src/SideDrawer.tsx +62 -0
  55. package/src/Spinner.tsx +20 -0
  56. package/src/SplitPage.native.tsx +160 -0
  57. package/src/SplitPage.tsx +302 -0
  58. package/src/Switch.tsx +19 -0
  59. package/src/Table.tsx +87 -0
  60. package/src/TableHeader.tsx +36 -0
  61. package/src/TableHeaderCell.tsx +76 -0
  62. package/src/TableRow.tsx +87 -0
  63. package/src/TapToEdit.tsx +221 -0
  64. package/src/Text.tsx +131 -0
  65. package/src/TextArea.tsx +16 -0
  66. package/src/TextField.tsx +401 -0
  67. package/src/TextFieldNumberActionSheet.tsx +61 -0
  68. package/src/Toast.tsx +106 -0
  69. package/src/Tooltip.tsx +269 -0
  70. package/src/UnifiedScreens.ts +24 -0
  71. package/src/Unifier.ts +371 -0
  72. package/src/Utilities.tsx +159 -0
  73. package/src/WithLabel.tsx +57 -0
  74. package/src/dayjsExtended.ts +10 -0
  75. package/src/index.tsx +1346 -0
  76. package/src/polyfill.d.ts +11 -0
  77. package/src/tableContext.tsx +80 -0
@@ -0,0 +1,53 @@
1
+ export const USSTATESLIST = [
2
+ {label: "AL", value: "Alabama"},
3
+ {label: "AK", value: "Alaska"},
4
+ {label: "AZ", value: "Arizona"},
5
+ {label: "AR", value: "Arkansas"},
6
+ {label: "CA", value: "California"},
7
+ {label: "CO", value: "Colorado"},
8
+ {label: "CT", value: "Connecticut"},
9
+ {label: "DE", value: "Delaware"},
10
+ {label: "DC", value: "District Of Columbia"},
11
+ {label: "FL", value: "Florida"},
12
+ {label: "GA", value: "Georgia"},
13
+ {label: "HI", value: "Hawaii"},
14
+ {label: "ID", value: "Idaho"},
15
+ {label: "IL", value: "Illinois"},
16
+ {label: "IN", value: "Indiana"},
17
+ {label: "IA", value: "Iowa"},
18
+ {label: "KS", value: "Kansas"},
19
+ {label: "KY", value: "Kentucky"},
20
+ {label: "LA", value: "Louisiana"},
21
+ {label: "ME", value: "Maine"},
22
+ {label: "MD", value: "Maryland"},
23
+ {label: "MA", value: "Massachusetts"},
24
+ {label: "MI", value: "Michigan"},
25
+ {label: "MN", value: "Minnesota"},
26
+ {label: "MS", value: "Mississippi"},
27
+ {label: "MO", value: "Missouri"},
28
+ {label: "MT", value: "Montana"},
29
+ {label: "NE", value: "Nebraska"},
30
+ {label: "NV", value: "Nevada"},
31
+ {label: "NH", value: "New Hampshire"},
32
+ {label: "NJ", value: "New Jersey"},
33
+ {label: "NM", value: "New Mexico"},
34
+ {label: "NY", value: "New York"},
35
+ {label: "NC", value: "North Carolina"},
36
+ {label: "ND", value: "North Dakota"},
37
+ {label: "OH", value: "Ohio"},
38
+ {label: "OK", value: "Oklahoma"},
39
+ {label: "OR", value: "Oregon"},
40
+ {label: "PA", value: "Pennsylvania"},
41
+ {label: "RI", value: "Rhode Island"},
42
+ {label: "SC", value: "South Carolina"},
43
+ {label: "SD", value: "South Dakota"},
44
+ {label: "TN", value: "Tennessee"},
45
+ {label: "TX", value: "Texas"},
46
+ {label: "UT", value: "Utah"},
47
+ {label: "VT", value: "Vermont"},
48
+ {label: "VA", value: "Virginia"},
49
+ {label: "WA", value: "Washington"},
50
+ {label: "WV", value: "West Virginia"},
51
+ {label: "WI", value: "Wisconsin"},
52
+ {label: "WY", value: "Wyoming"},
53
+ ];
@@ -0,0 +1,85 @@
1
+ import React, {ReactElement, useEffect, useMemo, useState} from "react";
2
+
3
+ import {Box} from "./Box";
4
+ import {SelectList} from "./SelectList";
5
+ import {TextField} from "./TextField";
6
+
7
+ export interface CustomSelectProps {
8
+ value: string;
9
+ onChange: (value: string) => void;
10
+ options: Array<{label: string; value: string}>;
11
+ placeholder?: string;
12
+ disabled?: boolean;
13
+ label?: string;
14
+ labelColor?: string;
15
+ }
16
+
17
+ export const CustomSelect = ({
18
+ value,
19
+ onChange,
20
+ placeholder,
21
+ disabled,
22
+ options,
23
+ }: CustomSelectProps): ReactElement | null => {
24
+ const [customValue, setCustomValue] = useState(value);
25
+ const [showCustomInput, setShowCustomInput] = useState(false);
26
+
27
+ // Boolean that checks if customValue is a value from the
28
+ // options prop or if it is a true custom value
29
+ const isValueCustom: boolean = useMemo((): boolean => {
30
+ // We add an empty value to protect against an empty string custom value or if the placeholder value is selected
31
+ return ![...options, {value: ""}].map((i) => i.value).includes(customValue);
32
+ }, [options, customValue]);
33
+
34
+ useEffect(() => {
35
+ setShowCustomInput(isValueCustom);
36
+ if (!showCustomInput) {
37
+ setCustomValue(value);
38
+ }
39
+ }, [showCustomInput, value, isValueCustom]);
40
+
41
+ // Custom select has 3 values - the overall field value, the value of the select menu, and the value of the custom input
42
+ const handleCustomSelectListChange = (newValue: string) => {
43
+ // If "custom" is selected from the dropdown, toggle the custom input open and clear the previous value
44
+ if (newValue === "custom") {
45
+ setShowCustomInput(true);
46
+ setCustomValue(isValueCustom ? "custom" : newValue);
47
+ onChange("");
48
+ }
49
+
50
+ // If any non-custom value is selected
51
+ else {
52
+ // Close the custom input if open and clear the value
53
+ if (showCustomInput) {
54
+ setShowCustomInput(false);
55
+ }
56
+
57
+ // Update the field value and select value
58
+ onChange(newValue);
59
+ }
60
+ };
61
+
62
+ return (
63
+ <>
64
+ <SelectList
65
+ id="providedOptions"
66
+ options={[...options, {label: "Custom", value: "custom"}]}
67
+ placeholder={placeholder}
68
+ value={isValueCustom ? "custom" : customValue}
69
+ onChange={handleCustomSelectListChange}
70
+ />
71
+ {Boolean(showCustomInput) && (
72
+ <Box paddingY={2}>
73
+ <TextField
74
+ disabled={disabled}
75
+ id="customOptions"
76
+ placeholder={placeholder}
77
+ type="text"
78
+ value={value}
79
+ onChange={(result) => onChange(result.value)}
80
+ />
81
+ </Box>
82
+ )}
83
+ </>
84
+ );
85
+ };
@@ -0,0 +1,409 @@
1
+ import {Picker} from "@react-native-picker/picker";
2
+ import range from "lodash/range";
3
+ import React, {useEffect, useState} from "react";
4
+ import {Platform, StyleProp, TextInput, TextStyle, View} from "react-native";
5
+ import {Calendar} from "react-native-calendars";
6
+
7
+ import {Box} from "./Box";
8
+ import {OnChangeCallback} from "./Common";
9
+ import dayjs from "./dayjsExtended";
10
+ import {Heading} from "./Heading";
11
+ import {IconButton} from "./IconButton";
12
+ import {isMobileDevice} from "./MediaQuery";
13
+ import {Modal} from "./Modal";
14
+ import {SelectList} from "./SelectList";
15
+ import {Unifier} from "./Unifier";
16
+
17
+ const TIME_PICKER_HEIGHT = 104;
18
+ const INPUT_HEIGHT = 40;
19
+
20
+ const hours = range(1, 13).map((n) => String(n));
21
+ // TODO: support limited picker minutes, e.g. 5 or 15 minute increments.
22
+ const minutes = range(0, 60).map((n) => String(n).padStart(2, "0"));
23
+ const minutesOptions = [...minutes, "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
24
+
25
+ function TimeInput({
26
+ type,
27
+ value,
28
+ onChange,
29
+ }: {
30
+ type: "hour" | "minute";
31
+ value: number;
32
+ onChange: (value: number) => void;
33
+ }): React.ReactElement {
34
+ const defaultText = type === "minute" ? String(value).padStart(2, "0") : String(value);
35
+ const [text, setText] = useState(defaultText);
36
+ const [focused, setFocused] = useState(false);
37
+ let error = false;
38
+ if (type === "hour") {
39
+ error = !hours.includes(String(Number(text)));
40
+ } else if (type === "minute") {
41
+ error = !minutesOptions.includes(String(Number(text)));
42
+ }
43
+
44
+ // Broken out because types don't think "outline" is a valid style.
45
+ const textInputStyle: StyleProp<TextStyle> = {
46
+ flex: 1,
47
+ paddingTop: 4,
48
+ paddingRight: 4,
49
+ paddingBottom: 4,
50
+ paddingLeft: 0,
51
+ height: INPUT_HEIGHT,
52
+ width: "100%",
53
+ color: Unifier.theme.darkGray,
54
+ fontFamily: Unifier.theme.primaryFont,
55
+ };
56
+
57
+ return (
58
+ <View
59
+ style={{
60
+ flexDirection: "row",
61
+ justifyContent: "center",
62
+ alignItems: "center",
63
+ height: INPUT_HEIGHT,
64
+ width: "100%",
65
+ // Add padding so the border doesn't mess up layouts
66
+ paddingHorizontal: focused ? 10 : 14,
67
+ paddingVertical: focused ? 0 : 4,
68
+ borderColor: error ? Unifier.theme.red : Unifier.theme.blue,
69
+ borderWidth: focused ? 5 : 1,
70
+ borderRadius: 5,
71
+ backgroundColor: Unifier.theme.white,
72
+ }}
73
+ >
74
+ <TextInput
75
+ keyboardType="number-pad"
76
+ returnKeyType="done"
77
+ style={
78
+ {
79
+ ...textInputStyle,
80
+ outline: Platform.select({web: "none"}),
81
+ } as any
82
+ }
83
+ textContentType="none"
84
+ underlineColorAndroid="transparent"
85
+ value={text}
86
+ onBlur={() => {
87
+ setFocused(false);
88
+ }}
89
+ onChangeText={(t) => {
90
+ setText(t);
91
+ onChange(Number(t));
92
+ }}
93
+ onFocus={() => {
94
+ setFocused(true);
95
+ }}
96
+ />
97
+ </View>
98
+ );
99
+ }
100
+
101
+ function CalendarHeader({
102
+ addMonth,
103
+ month,
104
+ }: {
105
+ addMonth: (num: number) => void;
106
+ month: Date[];
107
+ }): React.ReactElement {
108
+ const displayDate = dayjs(month[0]).format("MMM YYYY");
109
+ return (
110
+ <Box alignItems="center" direction="row" height={40} justifyContent="between" width="100%">
111
+ <IconButton
112
+ accessibilityLabel="arrow"
113
+ bgColor="white"
114
+ icon="angle-double-left"
115
+ iconColor="primary"
116
+ size="md"
117
+ onClick={() => {
118
+ addMonth(-12);
119
+ }}
120
+ />
121
+ <IconButton
122
+ accessibilityLabel="arrow"
123
+ bgColor="white"
124
+ icon="angle-left"
125
+ iconColor="primary"
126
+ size="md"
127
+ onClick={() => {
128
+ addMonth(-1);
129
+ }}
130
+ />
131
+ <Heading size="sm">{displayDate}</Heading>
132
+ <IconButton
133
+ accessibilityLabel="arrow"
134
+ bgColor="white"
135
+ icon="angle-right"
136
+ iconColor="primary"
137
+ size="md"
138
+ onClick={() => {
139
+ addMonth(1);
140
+ }}
141
+ />
142
+ <IconButton
143
+ accessibilityLabel="arrow"
144
+ bgColor="white"
145
+ icon="angle-double-right"
146
+ iconColor="primary"
147
+ size="md"
148
+ onClick={() => {
149
+ addMonth(12);
150
+ }}
151
+ />
152
+ </Box>
153
+ );
154
+ }
155
+
156
+ interface DateTimeActionSheetProps {
157
+ value?: string;
158
+ mode?: "date" | "time" | "datetime";
159
+ // Returns an ISO 8601 string. If mode is "time", the date portion is today.
160
+ onChange: OnChangeCallback;
161
+ actionSheetRef: React.RefObject<any>;
162
+ visible: boolean;
163
+ onDismiss: () => void;
164
+ }
165
+
166
+ // For mobile, renders all components in an action sheet.
167
+ // For web, renders all components in a modal.
168
+ // For mobile:
169
+ // If mode is "time", renders a spinner picker for time picker on both platforms.
170
+ // If mode is "date", renders our custom calendar on both platforms.
171
+ // If mode is "datetime", renders a spinner picker for time picker and our custom calendar on both platforms.
172
+ // For web, renders a simplistic text box for time picker and a calendar for date picker in a modal
173
+ // In the future, web time picker should be a typeahead dropdown like Google calendar.
174
+ export function DateTimeActionSheet({
175
+ // actionSheetRef,
176
+ mode,
177
+ value,
178
+ onChange,
179
+ visible,
180
+ onDismiss,
181
+ }: DateTimeActionSheetProps) {
182
+ // Accept ISO 8601, HH:mm, or hh:mm A formats. We may want only HH:mm or hh:mm A for mode=time
183
+ let m;
184
+ if (value) {
185
+ m = dayjs(value, ["YYYY", "YYYY-MM-DD", "HH:mm", "hh:mm A"]);
186
+ } else {
187
+ m = dayjs();
188
+ }
189
+
190
+ if (!m.isValid()) {
191
+ throw new Error(`Invalid date/time value ${value}`);
192
+ }
193
+
194
+ let hr = dayjs(m).hour() % 12;
195
+ if (hr === 0) {
196
+ hr = 12;
197
+ }
198
+
199
+ const [hour, setHour] = useState<number>(hr);
200
+ const [minute, setMinute] = useState<number>(dayjs(m).minute());
201
+ const [amPm, setAmPm] = useState<"am" | "pm">(dayjs(m).format("a") === "am" ? "am" : "pm");
202
+ const [date, setDate] = useState<string>(dayjs(m).toISOString());
203
+
204
+ useEffect(() => {
205
+ let datetime;
206
+ if (value) {
207
+ datetime = dayjs(value, ["YYYY", "YYYY-MM-DD", "HH:mm", "hh:mm A"]);
208
+ } else {
209
+ datetime = dayjs();
210
+ }
211
+ let h = dayjs(datetime).hour() % 12;
212
+ if (h === 0) {
213
+ h = 12;
214
+ }
215
+ setHour(h);
216
+ setMinute(dayjs(datetime).minute());
217
+ setAmPm(dayjs(datetime).format("a") === "am" ? "am" : "pm");
218
+ setDate(dayjs(datetime).toISOString());
219
+ }, [value]);
220
+
221
+ // TODO Support 24 hour time for time picker.
222
+ const renderMobileTime = () => {
223
+ return (
224
+ <Box direction="row" width="100%">
225
+ <Box paddingY={2} width="35%">
226
+ <Picker
227
+ itemStyle={{
228
+ height: TIME_PICKER_HEIGHT,
229
+ }}
230
+ selectedValue={hour}
231
+ style={{
232
+ height: TIME_PICKER_HEIGHT,
233
+ backgroundColor: "#FFFFFF",
234
+ }}
235
+ onValueChange={(itemValue) => setHour(itemValue)}
236
+ >
237
+ {hours.map((n) => (
238
+ <Picker.Item key={String(n)} label={String(n)} value={String(n)} />
239
+ ))}
240
+ </Picker>
241
+ </Box>
242
+ <Box paddingY={2} width="35%">
243
+ <Picker
244
+ itemStyle={{
245
+ height: TIME_PICKER_HEIGHT,
246
+ }}
247
+ selectedValue={minute}
248
+ style={{
249
+ height: TIME_PICKER_HEIGHT,
250
+ backgroundColor: "#FFFFFF",
251
+ }}
252
+ onValueChange={(itemValue) => setMinute(itemValue)}
253
+ >
254
+ {minutes.map((n) => (
255
+ <Picker.Item key={String(n)} label={String(n)} value={String(n)} />
256
+ ))}
257
+ </Picker>
258
+ </Box>
259
+ <Box paddingY={2} width="30%">
260
+ <Picker
261
+ itemStyle={{
262
+ height: TIME_PICKER_HEIGHT,
263
+ }}
264
+ selectedValue={amPm}
265
+ style={{
266
+ height: TIME_PICKER_HEIGHT,
267
+ backgroundColor: "#FFFFFF",
268
+ }}
269
+ onValueChange={(itemValue) => setAmPm(itemValue)}
270
+ >
271
+ <Picker.Item key="am" label="am" value="am" />
272
+ <Picker.Item key="pm" label="pm" value="pm" />
273
+ </Picker>
274
+ </Box>
275
+ </Box>
276
+ );
277
+ };
278
+
279
+ // TODO: Support a typeahead dropdown for time picker, similar to Google Calendar on the web.
280
+ const renderWebTime = () => {
281
+ return (
282
+ <Box direction="row" justifyContent="center" width="100%">
283
+ <Box width={60}>
284
+ <TimeInput type="hour" value={hour} onChange={(v) => setHour(v)} />
285
+ </Box>
286
+ <Box
287
+ alignItems="center"
288
+ height={INPUT_HEIGHT}
289
+ justifyContent="center"
290
+ marginLeft={2}
291
+ marginRight={2}
292
+ >
293
+ <Heading size="md">:</Heading>
294
+ </Box>
295
+ <Box marginRight={2} width={60}>
296
+ <TimeInput type="minute" value={minute} onChange={(v) => setMinute(v)} />
297
+ </Box>
298
+
299
+ <Box width={60}>
300
+ <SelectList
301
+ options={[
302
+ {label: "am", value: "am"},
303
+ {label: "pm", value: "pm"},
304
+ ]}
305
+ style={{minHeight: INPUT_HEIGHT}}
306
+ value={amPm}
307
+ onChange={(result) => {
308
+ setAmPm(result as "am" | "pm");
309
+ }}
310
+ />
311
+ </Box>
312
+ </Box>
313
+ );
314
+ };
315
+
316
+ const renderDateTime = (): React.ReactElement => {
317
+ return (
318
+ <Box>
319
+ <Box marginBottom={2}>{renderDateCalendar()}</Box>
320
+ {isMobileDevice() ? renderMobileTime() : renderWebTime()}
321
+ </Box>
322
+ );
323
+ };
324
+
325
+ // Note: do not call this if waiting on a state change.
326
+ const sendOnChange = () => {
327
+ const hourChange = amPm === "pm" && hour !== 12 ? Number(hour) + 12 : Number(hour);
328
+ if (mode === "date") {
329
+ onChange({value: date});
330
+ } else if (mode === "time") {
331
+ onChange({
332
+ value: dayjs().hour(hourChange).minute(Number(minute)).toISOString(),
333
+ });
334
+ } else if (mode === "datetime") {
335
+ onChange({
336
+ value: dayjs(date).hour(hourChange).minute(Number(minute)).toISOString(),
337
+ });
338
+ }
339
+ onDismiss();
340
+ };
341
+
342
+ const sendClear = () => {
343
+ onChange({
344
+ value: "",
345
+ });
346
+ onDismiss();
347
+ };
348
+
349
+ // Renders our custom calendar component on mobile or web.
350
+ const renderDateCalendar = () => {
351
+ const markedDates: {[id: string]: {selected: boolean; selectedColor: string}} = {};
352
+ if (date) {
353
+ markedDates[dayjs(date).format("YYYY-MM-DD")] = {
354
+ selected: true,
355
+ selectedColor: Unifier.theme.primary,
356
+ };
357
+ }
358
+ return (
359
+ <Calendar
360
+ customHeader={CalendarHeader}
361
+ initialDate={dayjs(date).format("YYYY-MM-DD")}
362
+ markedDates={markedDates}
363
+ onDayPress={(day) => {
364
+ setDate(day.dateString);
365
+ // If mode is just date, we can shortcut and close right away. time and datetime need to wait for the
366
+ // primary button.
367
+ if (mode === "date") {
368
+ onChange({value: day.dateString});
369
+ onDismiss();
370
+ }
371
+ }}
372
+ />
373
+ );
374
+ };
375
+
376
+ const renderContent = (): React.ReactElement => {
377
+ if (isMobileDevice()) {
378
+ if (mode === "date") {
379
+ return renderDateCalendar();
380
+ } else if (mode === "time") {
381
+ return renderMobileTime();
382
+ } else {
383
+ return renderDateTime();
384
+ }
385
+ } else {
386
+ if (mode === "date") {
387
+ return renderDateCalendar();
388
+ } else if (mode === "time") {
389
+ return renderWebTime();
390
+ } else {
391
+ return renderDateTime();
392
+ }
393
+ }
394
+ };
395
+
396
+ return (
397
+ <Modal
398
+ primaryButtonOnClick={sendOnChange}
399
+ primaryButtonText="Save"
400
+ secondaryButtonOnClick={sendClear}
401
+ secondaryButtonText="Clear"
402
+ showClose
403
+ visible={visible}
404
+ onDismiss={onDismiss}
405
+ >
406
+ {renderContent()}
407
+ </Modal>
408
+ );
409
+ }
@@ -0,0 +1,101 @@
1
+ import DateTimePicker from "@react-native-community/datetimepicker";
2
+ import React, {ReactElement, useMemo, useState} from "react";
3
+ import {TextInput} from "react-native";
4
+
5
+ import {DateTimeFieldProps} from "./Common";
6
+ import dayjs from "./dayjsExtended";
7
+ import {Unifier} from "./Unifier";
8
+ import {WithLabel} from "./WithLabel";
9
+
10
+ export const DateTimeField = ({
11
+ mode,
12
+ value,
13
+ onChange,
14
+ errorMessage,
15
+ pickerType = "default",
16
+ dateFormat,
17
+ errorMessageColor,
18
+ }: DateTimeFieldProps): ReactElement => {
19
+ // const [showCalendar, setShowCalendar] = useState(false);
20
+ // const [showClock, setShowClock] = useState(false);
21
+ // const [tempDate, setTempDate] = useState<Date>();
22
+ const [pickerMode, setPickerMode] = useState(mode);
23
+ const [showPicker, setShowPicker] = useState(false);
24
+
25
+ const showCalendarFirst = mode === "datetime" || mode === "date";
26
+
27
+ const defaultFormat = useMemo(() => {
28
+ if (dateFormat) {
29
+ return dateFormat;
30
+ } else {
31
+ if (mode === "date") {
32
+ return "MMMM Do YYYY";
33
+ } else if (mode === "time") {
34
+ return "h:mm a";
35
+ } else {
36
+ return "MMMM Do YYYY, h:mm a";
37
+ }
38
+ }
39
+ }, [mode, dateFormat]);
40
+
41
+ const showMode = (currentMode: "date" | "time") => {
42
+ setShowPicker(true);
43
+ setPickerMode(currentMode);
44
+ };
45
+
46
+ const showDatePicker = () => {
47
+ showMode("date");
48
+ };
49
+
50
+ const showTimePicker = () => {
51
+ showMode("time");
52
+ };
53
+
54
+ return (
55
+ <WithLabel
56
+ label={errorMessage}
57
+ labelColor={errorMessageColor || "red"}
58
+ labelPlacement="after"
59
+ labelSize="sm"
60
+ >
61
+ <WithLabel>
62
+ <TextInput
63
+ inputMode="none"
64
+ style={{
65
+ flex: 1,
66
+ paddingTop: 10,
67
+ paddingRight: 10,
68
+ paddingBottom: 10,
69
+ paddingLeft: 10,
70
+ height: 40,
71
+ width: "100%",
72
+ color: Unifier.theme.darkGray,
73
+ fontFamily: Unifier.theme.primaryFont,
74
+ borderWidth: 1,
75
+ }}
76
+ value={dayjs(value).format(defaultFormat)}
77
+ onPressIn={() => {
78
+ showCalendarFirst ? showDatePicker() : showTimePicker();
79
+ }}
80
+ />
81
+ {showPicker && (
82
+ <DateTimePicker
83
+ display={pickerType}
84
+ mode={pickerMode}
85
+ testID="dateTimePicker"
86
+ value={value}
87
+ onChange={(event, date) => {
88
+ if (date) {
89
+ onChange(date);
90
+ if (pickerMode === "date" && mode === "datetime") {
91
+ showTimePicker();
92
+ }
93
+ }
94
+ setShowPicker(false);
95
+ }}
96
+ />
97
+ )}
98
+ </WithLabel>
99
+ </WithLabel>
100
+ );
101
+ };