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.
- package/package.json +3 -4
- package/src/ActionSheet.tsx +1231 -0
- package/src/Avatar.tsx +317 -0
- package/src/Badge.tsx +65 -0
- package/src/Banner.tsx +124 -0
- package/src/BlurBox.native.tsx +40 -0
- package/src/BlurBox.tsx +31 -0
- package/src/Body.tsx +32 -0
- package/src/Box.tsx +308 -0
- package/src/Button.tsx +219 -0
- package/src/Card.tsx +23 -0
- package/src/CheckBox.tsx +118 -0
- package/src/Common.ts +2743 -0
- package/src/Constants.ts +53 -0
- package/src/CustomSelect.tsx +85 -0
- package/src/DateTimeActionSheet.tsx +409 -0
- package/src/DateTimeField.android.tsx +101 -0
- package/src/DateTimeField.ios.tsx +83 -0
- package/src/DateTimeField.tsx +69 -0
- package/src/DecimalRangeActionSheet.tsx +113 -0
- package/src/ErrorBoundary.tsx +37 -0
- package/src/ErrorPage.tsx +44 -0
- package/src/FernsProvider.tsx +21 -0
- package/src/Field.tsx +299 -0
- package/src/FieldWithLabels.tsx +36 -0
- package/src/FlatList.tsx +2 -0
- package/src/Form.tsx +182 -0
- package/src/HeaderButtons.tsx +107 -0
- package/src/Heading.tsx +53 -0
- package/src/HeightActionSheet.tsx +104 -0
- package/src/Hyperlink.tsx +181 -0
- package/src/Icon.tsx +24 -0
- package/src/IconButton.tsx +165 -0
- package/src/Image.tsx +50 -0
- package/src/ImageBackground.tsx +14 -0
- package/src/InfoTooltipButton.tsx +23 -0
- package/src/Layer.tsx +17 -0
- package/src/Link.tsx +17 -0
- package/src/Mask.tsx +21 -0
- package/src/MediaQuery.ts +46 -0
- package/src/Meta.tsx +9 -0
- package/src/Modal.tsx +248 -0
- package/src/ModalSheet.tsx +58 -0
- package/src/NumberPickerActionSheet.tsx +66 -0
- package/src/Page.tsx +133 -0
- package/src/Permissions.ts +44 -0
- package/src/PickerSelect.tsx +553 -0
- package/src/Pill.tsx +24 -0
- package/src/Pog.tsx +87 -0
- package/src/ProgressBar.tsx +55 -0
- package/src/ScrollView.tsx +2 -0
- package/src/SegmentedControl.tsx +102 -0
- package/src/SelectList.tsx +89 -0
- package/src/SideDrawer.tsx +62 -0
- package/src/Spinner.tsx +20 -0
- package/src/SplitPage.native.tsx +160 -0
- package/src/SplitPage.tsx +302 -0
- package/src/Switch.tsx +19 -0
- package/src/Table.tsx +87 -0
- package/src/TableHeader.tsx +36 -0
- package/src/TableHeaderCell.tsx +76 -0
- package/src/TableRow.tsx +87 -0
- package/src/TapToEdit.tsx +221 -0
- package/src/Text.tsx +131 -0
- package/src/TextArea.tsx +16 -0
- package/src/TextField.tsx +401 -0
- package/src/TextFieldNumberActionSheet.tsx +61 -0
- package/src/Toast.tsx +106 -0
- package/src/Tooltip.tsx +269 -0
- package/src/UnifiedScreens.ts +24 -0
- package/src/Unifier.ts +371 -0
- package/src/Utilities.tsx +159 -0
- package/src/WithLabel.tsx +57 -0
- package/src/dayjsExtended.ts +10 -0
- package/src/index.tsx +1346 -0
- package/src/polyfill.d.ts +11 -0
- package/src/tableContext.tsx +80 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import {AsYouType} from "libphonenumber-js";
|
|
2
|
+
import React, {ReactElement, useCallback, useMemo, useState} from "react";
|
|
3
|
+
import {
|
|
4
|
+
ActivityIndicator,
|
|
5
|
+
KeyboardTypeOptions,
|
|
6
|
+
Platform,
|
|
7
|
+
Pressable,
|
|
8
|
+
TextInput,
|
|
9
|
+
View,
|
|
10
|
+
} from "react-native";
|
|
11
|
+
|
|
12
|
+
import {Box} from "./Box";
|
|
13
|
+
import {TextFieldProps} from "./Common";
|
|
14
|
+
import {DateTimeActionSheet} from "./DateTimeActionSheet";
|
|
15
|
+
import dayjs from "./dayjsExtended";
|
|
16
|
+
import {DecimalRangeActionSheet} from "./DecimalRangeActionSheet";
|
|
17
|
+
import {HeightActionSheet} from "./HeightActionSheet";
|
|
18
|
+
import {Icon} from "./Icon";
|
|
19
|
+
import {NumberPickerActionSheet} from "./NumberPickerActionSheet";
|
|
20
|
+
import {Unifier} from "./Unifier";
|
|
21
|
+
import {WithLabel} from "./WithLabel";
|
|
22
|
+
|
|
23
|
+
const keyboardMap: {[id: string]: string | undefined} = {
|
|
24
|
+
date: "default",
|
|
25
|
+
email: "email-address",
|
|
26
|
+
number: "number-pad",
|
|
27
|
+
numberRange: "number-pad",
|
|
28
|
+
decimalRange: "decimal-pad",
|
|
29
|
+
decimal: "decimal-pad",
|
|
30
|
+
height: "default",
|
|
31
|
+
password: "default",
|
|
32
|
+
phoneNumber: "number-pad",
|
|
33
|
+
search: "default",
|
|
34
|
+
text: "default",
|
|
35
|
+
url: Platform.select({
|
|
36
|
+
ios: "url",
|
|
37
|
+
android: "default",
|
|
38
|
+
}),
|
|
39
|
+
username: "default",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Not an exhaustive list of all the textContent types, but the ones we use.
|
|
43
|
+
const textContentMap: {
|
|
44
|
+
[id: string]: "none" | "emailAddress" | "password" | "username" | "URL" | undefined;
|
|
45
|
+
} = {
|
|
46
|
+
date: "none",
|
|
47
|
+
email: "emailAddress",
|
|
48
|
+
number: "none",
|
|
49
|
+
decimal: "none",
|
|
50
|
+
decimalRange: "none",
|
|
51
|
+
height: "none",
|
|
52
|
+
password: "password",
|
|
53
|
+
search: "none",
|
|
54
|
+
text: "none",
|
|
55
|
+
url: Platform.select({
|
|
56
|
+
ios: "URL",
|
|
57
|
+
android: "none",
|
|
58
|
+
}),
|
|
59
|
+
username: "username",
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export function TextField({
|
|
63
|
+
blurOnSubmit = true,
|
|
64
|
+
value,
|
|
65
|
+
height: propsHeight,
|
|
66
|
+
onChange,
|
|
67
|
+
paddingX,
|
|
68
|
+
paddingY,
|
|
69
|
+
min,
|
|
70
|
+
max,
|
|
71
|
+
type = "text",
|
|
72
|
+
searching,
|
|
73
|
+
autoComplete,
|
|
74
|
+
autoFocus,
|
|
75
|
+
disabled,
|
|
76
|
+
errorMessage,
|
|
77
|
+
errorMessageColor,
|
|
78
|
+
inputRef,
|
|
79
|
+
multiline,
|
|
80
|
+
rows,
|
|
81
|
+
placeholder,
|
|
82
|
+
grow,
|
|
83
|
+
label,
|
|
84
|
+
labelColor,
|
|
85
|
+
returnKeyType,
|
|
86
|
+
onBlur,
|
|
87
|
+
style,
|
|
88
|
+
onEnter,
|
|
89
|
+
onSubmitEditing,
|
|
90
|
+
}: TextFieldProps): ReactElement {
|
|
91
|
+
const dateActionSheetRef: React.RefObject<any> = React.createRef();
|
|
92
|
+
const numberRangeActionSheetRef: React.RefObject<any> = React.createRef();
|
|
93
|
+
const decimalRangeActionSheetRef: React.RefObject<any> = React.createRef();
|
|
94
|
+
const weightActionSheetRef: React.RefObject<any> = React.createRef();
|
|
95
|
+
|
|
96
|
+
const [focused, setFocused] = useState(false);
|
|
97
|
+
const [height, setHeight] = useState(propsHeight || 40);
|
|
98
|
+
const [showDate, setShowDate] = useState(false);
|
|
99
|
+
|
|
100
|
+
const renderIcon = () => {
|
|
101
|
+
if (type !== "search") {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
if (searching) {
|
|
105
|
+
return (
|
|
106
|
+
<Box marginRight={4}>
|
|
107
|
+
<ActivityIndicator color={Unifier.theme.primary} size="small" />
|
|
108
|
+
</Box>
|
|
109
|
+
);
|
|
110
|
+
} else {
|
|
111
|
+
return (
|
|
112
|
+
<Box marginRight={2}>
|
|
113
|
+
<Icon name="search" prefix="far" size="md" />
|
|
114
|
+
</Box>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
let borderColor;
|
|
120
|
+
if (errorMessage) {
|
|
121
|
+
borderColor = Unifier.theme.red;
|
|
122
|
+
} else if (focused) {
|
|
123
|
+
borderColor = Unifier.theme.blue;
|
|
124
|
+
} else {
|
|
125
|
+
borderColor = Unifier.theme.gray;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const getHeight = useCallback(() => {
|
|
129
|
+
if (grow) {
|
|
130
|
+
return Math.max(40, height);
|
|
131
|
+
} else if (multiline) {
|
|
132
|
+
return height || "100%";
|
|
133
|
+
} else {
|
|
134
|
+
return 40;
|
|
135
|
+
}
|
|
136
|
+
}, [grow, height, multiline]);
|
|
137
|
+
|
|
138
|
+
const defaultTextInputStyles = useMemo(() => {
|
|
139
|
+
const defaultStyles = {
|
|
140
|
+
flex: 1,
|
|
141
|
+
paddingTop: 4,
|
|
142
|
+
paddingRight: 4,
|
|
143
|
+
paddingBottom: 4,
|
|
144
|
+
paddingLeft: 0,
|
|
145
|
+
height: getHeight(),
|
|
146
|
+
width: "100%",
|
|
147
|
+
color: Unifier.theme.darkGray,
|
|
148
|
+
fontFamily: Unifier.theme.primaryFont,
|
|
149
|
+
...style,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
if (Platform.OS === "web") {
|
|
153
|
+
defaultStyles.outline = 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return defaultStyles;
|
|
157
|
+
}, [getHeight, style]);
|
|
158
|
+
|
|
159
|
+
const isHandledByModal = [
|
|
160
|
+
"date",
|
|
161
|
+
"datetime",
|
|
162
|
+
"time",
|
|
163
|
+
"numberRange",
|
|
164
|
+
"decimalRange",
|
|
165
|
+
"height",
|
|
166
|
+
].includes(type);
|
|
167
|
+
|
|
168
|
+
const isEditable = !disabled && !isHandledByModal;
|
|
169
|
+
|
|
170
|
+
const shouldAutocorrect = type === "text" && (!autoComplete || autoComplete === "on");
|
|
171
|
+
|
|
172
|
+
const keyboardType = keyboardMap[type];
|
|
173
|
+
const textContentType = textContentMap[type || "text"];
|
|
174
|
+
|
|
175
|
+
const withLabelProps = {
|
|
176
|
+
label,
|
|
177
|
+
labelColor,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const onTap = useCallback((): void => {
|
|
181
|
+
if (disabled) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (["date", "datetime", "time"].includes(type)) {
|
|
185
|
+
setShowDate(true);
|
|
186
|
+
} else if (type === "numberRange") {
|
|
187
|
+
numberRangeActionSheetRef?.current?.show();
|
|
188
|
+
} else if (type === "decimalRange") {
|
|
189
|
+
decimalRangeActionSheetRef?.current?.show();
|
|
190
|
+
} else if (type === "height") {
|
|
191
|
+
weightActionSheetRef?.current?.show();
|
|
192
|
+
}
|
|
193
|
+
}, [decimalRangeActionSheetRef, disabled, numberRangeActionSheetRef, type, weightActionSheetRef]);
|
|
194
|
+
|
|
195
|
+
let displayValue = value;
|
|
196
|
+
if (displayValue) {
|
|
197
|
+
if (type === "date") {
|
|
198
|
+
// We get off by one errors because UTC midnight might be yesterday. So we add the timezone offset.
|
|
199
|
+
if (
|
|
200
|
+
dayjs.utc(value).hour() === 0 &&
|
|
201
|
+
dayjs.utc(value).minute() === 0 &&
|
|
202
|
+
dayjs.utc(value).second() === 0
|
|
203
|
+
) {
|
|
204
|
+
const timezoneOffset = new Date().getTimezoneOffset();
|
|
205
|
+
displayValue = dayjs.utc(value).add(timezoneOffset, "minutes").format("MM/DD/YYYY");
|
|
206
|
+
} else {
|
|
207
|
+
displayValue = dayjs(value).format("MM/DD/YYYY");
|
|
208
|
+
}
|
|
209
|
+
} else if (type === "time") {
|
|
210
|
+
displayValue = dayjs(value).format("h:mm A");
|
|
211
|
+
} else if (type === "datetime") {
|
|
212
|
+
displayValue = dayjs(value).format("MM/DD/YYYY h:mm A");
|
|
213
|
+
} else if (type === "height") {
|
|
214
|
+
displayValue = `${Math.floor(Number(value) / 12)} ft, ${Number(value) % 12} in`;
|
|
215
|
+
} else if (type === "phoneNumber") {
|
|
216
|
+
// By default, if a value is something like `"(123)"`
|
|
217
|
+
// then Backspace would only erase the rightmost brace
|
|
218
|
+
// becoming something like `"(123"`
|
|
219
|
+
// which would give the same `"123"` value
|
|
220
|
+
// which would then be formatted back to `"(123)"`
|
|
221
|
+
// and so a user wouldn't be able to erase the phone number.
|
|
222
|
+
// This is the workaround for that.
|
|
223
|
+
const formattedPhoneNumber = new AsYouType("US").input(displayValue);
|
|
224
|
+
if (displayValue !== formattedPhoneNumber && displayValue.length !== 4) {
|
|
225
|
+
displayValue = formattedPhoneNumber;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
// Set some default values for modal-edited fields so we don't go from uncontrolled to controlled when setting
|
|
230
|
+
// the date.
|
|
231
|
+
if (["date", "datetime", "time"].includes(type)) {
|
|
232
|
+
displayValue = "";
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const Wrapper = isHandledByModal ? Pressable : View;
|
|
237
|
+
|
|
238
|
+
return (
|
|
239
|
+
<>
|
|
240
|
+
<WithLabel
|
|
241
|
+
label={errorMessage}
|
|
242
|
+
labelColor={errorMessageColor || "red"}
|
|
243
|
+
labelPlacement="after"
|
|
244
|
+
labelSize="sm"
|
|
245
|
+
>
|
|
246
|
+
<WithLabel {...withLabelProps}>
|
|
247
|
+
<Wrapper
|
|
248
|
+
style={{
|
|
249
|
+
flexDirection: "row",
|
|
250
|
+
justifyContent: "center",
|
|
251
|
+
alignItems: "center",
|
|
252
|
+
// height: multiline || grow ? undefined : 40,
|
|
253
|
+
minHeight: getHeight(),
|
|
254
|
+
width: "100%",
|
|
255
|
+
// Add padding so the border doesn't mess up layouts
|
|
256
|
+
paddingHorizontal: paddingX || focused ? 10 : 14,
|
|
257
|
+
paddingVertical: paddingY || focused ? 0 : 4,
|
|
258
|
+
borderColor,
|
|
259
|
+
borderWidth: focused ? 5 : 1,
|
|
260
|
+
borderRadius: 16,
|
|
261
|
+
backgroundColor: disabled ? Unifier.theme.gray : Unifier.theme.white,
|
|
262
|
+
overflow: "hidden",
|
|
263
|
+
}}
|
|
264
|
+
onPress={() => {
|
|
265
|
+
// This runs on web
|
|
266
|
+
onTap();
|
|
267
|
+
}}
|
|
268
|
+
onTouchStart={() => {
|
|
269
|
+
// This runs on mobile
|
|
270
|
+
onTap();
|
|
271
|
+
}}
|
|
272
|
+
>
|
|
273
|
+
{renderIcon()}
|
|
274
|
+
<TextInput
|
|
275
|
+
ref={(ref) => {
|
|
276
|
+
if (inputRef) {
|
|
277
|
+
inputRef(ref);
|
|
278
|
+
}
|
|
279
|
+
}}
|
|
280
|
+
autoCapitalize={type === "text" ? "sentences" : "none"}
|
|
281
|
+
autoCorrect={shouldAutocorrect}
|
|
282
|
+
autoFocus={autoFocus}
|
|
283
|
+
blurOnSubmit={blurOnSubmit}
|
|
284
|
+
editable={isEditable}
|
|
285
|
+
keyboardType={keyboardType as KeyboardTypeOptions}
|
|
286
|
+
multiline={multiline}
|
|
287
|
+
numberOfLines={rows || 4}
|
|
288
|
+
placeholder={placeholder}
|
|
289
|
+
placeholderTextColor={Unifier.theme.gray}
|
|
290
|
+
returnKeyType={type === "number" || type === "decimal" ? "done" : returnKeyType}
|
|
291
|
+
secureTextEntry={type === "password"}
|
|
292
|
+
style={defaultTextInputStyles}
|
|
293
|
+
textContentType={textContentType}
|
|
294
|
+
underlineColorAndroid="transparent"
|
|
295
|
+
value={displayValue}
|
|
296
|
+
onBlur={() => {
|
|
297
|
+
if (!isHandledByModal) {
|
|
298
|
+
setFocused(false);
|
|
299
|
+
}
|
|
300
|
+
if (onBlur) {
|
|
301
|
+
onBlur({value: value ?? ""});
|
|
302
|
+
}
|
|
303
|
+
// if (type === "date") {
|
|
304
|
+
// actionSheetRef?.current?.hide();
|
|
305
|
+
// }
|
|
306
|
+
}}
|
|
307
|
+
onChangeText={(text) => {
|
|
308
|
+
if (onChange) {
|
|
309
|
+
if (type === "phoneNumber") {
|
|
310
|
+
const formattedPhoneNumber = new AsYouType("US").input(text);
|
|
311
|
+
// another workaround for the same issue as above with backspacing phone numbers
|
|
312
|
+
if (formattedPhoneNumber === value) {
|
|
313
|
+
onChange({value: text});
|
|
314
|
+
} else {
|
|
315
|
+
onChange({value: formattedPhoneNumber});
|
|
316
|
+
}
|
|
317
|
+
} else {
|
|
318
|
+
onChange({value: text});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}}
|
|
322
|
+
onContentSizeChange={(event) => {
|
|
323
|
+
if (!grow) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
setHeight(event.nativeEvent.contentSize.height);
|
|
327
|
+
}}
|
|
328
|
+
onFocus={() => {
|
|
329
|
+
if (!isHandledByModal) {
|
|
330
|
+
setFocused(true);
|
|
331
|
+
}
|
|
332
|
+
}}
|
|
333
|
+
onSubmitEditing={() => {
|
|
334
|
+
if (onEnter) {
|
|
335
|
+
onEnter();
|
|
336
|
+
}
|
|
337
|
+
if (onSubmitEditing) {
|
|
338
|
+
onSubmitEditing();
|
|
339
|
+
}
|
|
340
|
+
}}
|
|
341
|
+
/>
|
|
342
|
+
</Wrapper>
|
|
343
|
+
</WithLabel>
|
|
344
|
+
</WithLabel>
|
|
345
|
+
{(type === "date" || type === "time" || type === "datetime") && (
|
|
346
|
+
<DateTimeActionSheet
|
|
347
|
+
actionSheetRef={dateActionSheetRef}
|
|
348
|
+
mode={type}
|
|
349
|
+
value={value}
|
|
350
|
+
visible={showDate}
|
|
351
|
+
onChange={(result) => {
|
|
352
|
+
onChange(result);
|
|
353
|
+
setShowDate(false);
|
|
354
|
+
setFocused(false);
|
|
355
|
+
}}
|
|
356
|
+
onDismiss={() => setShowDate(false)}
|
|
357
|
+
/>
|
|
358
|
+
)}
|
|
359
|
+
{/* {type === "date" && showDate && ( */}
|
|
360
|
+
{/* <Box maxWidth={300}> */}
|
|
361
|
+
{/* /!* TODO: Calendar should disappear when you click away from it. *!/ */}
|
|
362
|
+
{/* <Calendar */}
|
|
363
|
+
{/* customHeader={CalendarHeader} */}
|
|
364
|
+
{/* initialDate={value} */}
|
|
365
|
+
{/* onDayPress={(day: any) => { */}
|
|
366
|
+
{/* onChange({value: day.dateString}); */}
|
|
367
|
+
{/* setShowDate(false); */}
|
|
368
|
+
{/* }} */}
|
|
369
|
+
{/* /> */}
|
|
370
|
+
{/* </Box> */}
|
|
371
|
+
{/* )} */}
|
|
372
|
+
{type === "numberRange" && value && (
|
|
373
|
+
<NumberPickerActionSheet
|
|
374
|
+
actionSheetRef={numberRangeActionSheetRef}
|
|
375
|
+
max={max || (min || 0) + 100}
|
|
376
|
+
min={min || 0}
|
|
377
|
+
value={value}
|
|
378
|
+
onChange={(result) => onChange(result)}
|
|
379
|
+
/>
|
|
380
|
+
)}
|
|
381
|
+
{type === "decimalRange" && value && (
|
|
382
|
+
<DecimalRangeActionSheet
|
|
383
|
+
actionSheetRef={decimalRangeActionSheetRef}
|
|
384
|
+
max={max || (min || 0) + 100}
|
|
385
|
+
min={min || 0}
|
|
386
|
+
value={value}
|
|
387
|
+
onChange={(result) => onChange(result)}
|
|
388
|
+
/>
|
|
389
|
+
)}
|
|
390
|
+
{type === "height" && (
|
|
391
|
+
<HeightActionSheet
|
|
392
|
+
actionSheetRef={weightActionSheetRef}
|
|
393
|
+
value={value}
|
|
394
|
+
onChange={(result) => {
|
|
395
|
+
onChange(result);
|
|
396
|
+
}}
|
|
397
|
+
/>
|
|
398
|
+
)}
|
|
399
|
+
</>
|
|
400
|
+
);
|
|
401
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import DateTimePicker from "@react-native-community/datetimepicker";
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
import {ActionSheet} from "./ActionSheet";
|
|
5
|
+
import {Box} from "./Box";
|
|
6
|
+
import {Button} from "./Button";
|
|
7
|
+
import {OnChangeCallback} from "./Common";
|
|
8
|
+
import dayjs from "./dayjsExtended";
|
|
9
|
+
|
|
10
|
+
interface NumberPickerActionSheetProps {
|
|
11
|
+
value?: string;
|
|
12
|
+
mode?: "date" | "time";
|
|
13
|
+
onChange: OnChangeCallback;
|
|
14
|
+
actionSheetRef: React.RefObject<any>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface NumberPickerActionSheetState {}
|
|
18
|
+
|
|
19
|
+
export class NumberPickerActionSheet extends React.Component<
|
|
20
|
+
NumberPickerActionSheetProps,
|
|
21
|
+
NumberPickerActionSheetState
|
|
22
|
+
> {
|
|
23
|
+
constructor(props: NumberPickerActionSheetProps) {
|
|
24
|
+
super(props);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
render() {
|
|
28
|
+
return (
|
|
29
|
+
<ActionSheet ref={this.props.actionSheetRef} bounceOnOpen gestureEnabled>
|
|
30
|
+
<Box marginBottom={8} paddingX={4} width="100%">
|
|
31
|
+
<Box alignItems="end" display="flex" width="100%">
|
|
32
|
+
<Box width="33%">
|
|
33
|
+
<Button
|
|
34
|
+
color="blue"
|
|
35
|
+
size="lg"
|
|
36
|
+
text="Save"
|
|
37
|
+
type="ghost"
|
|
38
|
+
onClick={() => {
|
|
39
|
+
this.props.actionSheetRef?.current?.setModalVisible(false);
|
|
40
|
+
}}
|
|
41
|
+
/>
|
|
42
|
+
</Box>
|
|
43
|
+
</Box>
|
|
44
|
+
<DateTimePicker
|
|
45
|
+
display="spinner"
|
|
46
|
+
is24Hour
|
|
47
|
+
mode={this.props.mode}
|
|
48
|
+
testID="dateTimePicker"
|
|
49
|
+
value={dayjs(this.props.value).toDate()}
|
|
50
|
+
onChange={(event: any, date?: Date) => {
|
|
51
|
+
if (!date) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
this.props.onChange({event, value: date.toString()});
|
|
55
|
+
}}
|
|
56
|
+
/>
|
|
57
|
+
</Box>
|
|
58
|
+
</ActionSheet>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/Toast.tsx
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {Dimensions} from "react-native";
|
|
3
|
+
import {useToast as useRNToast} from "react-native-toast-notifications";
|
|
4
|
+
|
|
5
|
+
import {Box} from "./Box";
|
|
6
|
+
import {Button} from "./Button";
|
|
7
|
+
import {AllColors} from "./Common";
|
|
8
|
+
import {Icon} from "./Icon";
|
|
9
|
+
import {IconButton} from "./IconButton";
|
|
10
|
+
import {Text} from "./Text";
|
|
11
|
+
|
|
12
|
+
const TOAST_DURATION_MS = 3 * 1000;
|
|
13
|
+
|
|
14
|
+
export function useToast(): any {
|
|
15
|
+
const toast = useRNToast();
|
|
16
|
+
return {
|
|
17
|
+
show: (
|
|
18
|
+
text: string,
|
|
19
|
+
options?: {
|
|
20
|
+
variant?: "default" | "warning" | "error";
|
|
21
|
+
buttonText?: string;
|
|
22
|
+
buttonOnClick: () => void | Promise<void>;
|
|
23
|
+
persistent?: boolean;
|
|
24
|
+
onDismiss?: () => void | Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
): string => {
|
|
27
|
+
return toast.show(text, {
|
|
28
|
+
data: options,
|
|
29
|
+
// a duration of 0 keeps the toast up infinitely until hidden
|
|
30
|
+
duration: options?.persistent ? 0 : TOAST_DURATION_MS,
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
hide: (id: string) => toast.hide(id),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function Toast({
|
|
38
|
+
message,
|
|
39
|
+
data,
|
|
40
|
+
}: {
|
|
41
|
+
message: string;
|
|
42
|
+
data: {
|
|
43
|
+
variant?: "default" | "warning" | "error";
|
|
44
|
+
buttonText?: string;
|
|
45
|
+
buttonOnClick?: () => void | Promise<void>;
|
|
46
|
+
persistent?: boolean;
|
|
47
|
+
onDismiss?: () => void;
|
|
48
|
+
};
|
|
49
|
+
}): React.ReactElement {
|
|
50
|
+
// margin 8 on either side, times the standard 4px we multiply by.
|
|
51
|
+
const width = Math.min(Dimensions.get("window").width - 16 * 4, 712);
|
|
52
|
+
const {variant, buttonText, buttonOnClick, persistent, onDismiss} = data ?? {};
|
|
53
|
+
let color: AllColors = "darkGray";
|
|
54
|
+
if (variant === "warning") {
|
|
55
|
+
color = "orange";
|
|
56
|
+
} else if (variant === "error") {
|
|
57
|
+
color = "red";
|
|
58
|
+
}
|
|
59
|
+
return (
|
|
60
|
+
<Box
|
|
61
|
+
alignItems="center"
|
|
62
|
+
color={color}
|
|
63
|
+
direction="row"
|
|
64
|
+
flex="shrink"
|
|
65
|
+
marginBottom={4}
|
|
66
|
+
marginLeft={8}
|
|
67
|
+
marginRight={8}
|
|
68
|
+
maxWidth={width}
|
|
69
|
+
padding={2}
|
|
70
|
+
paddingX={4}
|
|
71
|
+
paddingY={3}
|
|
72
|
+
rounding={4}
|
|
73
|
+
>
|
|
74
|
+
{Boolean(variant === "error") && (
|
|
75
|
+
<Box marginRight={4}>
|
|
76
|
+
<Icon color="white" name="exclamation-circle" size="lg" />
|
|
77
|
+
</Box>
|
|
78
|
+
)}
|
|
79
|
+
{Boolean(variant === "warning") && (
|
|
80
|
+
<Box marginRight={4}>
|
|
81
|
+
<Icon color="white" name="exclamation-triangle" size="lg" />
|
|
82
|
+
</Box>
|
|
83
|
+
)}
|
|
84
|
+
<Box alignItems="center" direction="column" flex="shrink" justifyContent="center">
|
|
85
|
+
<Text color="white" size="lg" weight="bold">
|
|
86
|
+
{message}
|
|
87
|
+
</Text>
|
|
88
|
+
</Box>
|
|
89
|
+
{Boolean(buttonOnClick && buttonText) && (
|
|
90
|
+
<Box alignItems="center" justifyContent="center" marginLeft={4}>
|
|
91
|
+
<Button color="lightGray" shape="pill" text={buttonText!} onClick={buttonOnClick} />
|
|
92
|
+
</Box>
|
|
93
|
+
)}
|
|
94
|
+
{Boolean(onDismiss && persistent) && (
|
|
95
|
+
<Box alignItems="center" justifyContent="center" marginLeft={4}>
|
|
96
|
+
<IconButton
|
|
97
|
+
accessibilityLabel="Dismiss notification"
|
|
98
|
+
icon="times"
|
|
99
|
+
iconColor="white"
|
|
100
|
+
onClick={onDismiss!}
|
|
101
|
+
/>
|
|
102
|
+
</Box>
|
|
103
|
+
)}
|
|
104
|
+
</Box>
|
|
105
|
+
);
|
|
106
|
+
}
|