ferns-ui 0.36.4 → 0.37.0

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 (87) hide show
  1. package/dist/Banner.d.ts +6 -16
  2. package/dist/Banner.js +52 -43
  3. package/dist/Banner.js.map +1 -1
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +1 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/useStoredState.d.ts +1 -0
  8. package/dist/useStoredState.js +33 -0
  9. package/dist/useStoredState.js.map +1 -0
  10. package/package.json +55 -56
  11. package/src/ActionSheet.tsx +1231 -0
  12. package/src/Avatar.tsx +317 -0
  13. package/src/Badge.tsx +65 -0
  14. package/src/Banner.tsx +149 -0
  15. package/src/BlurBox.native.tsx +40 -0
  16. package/src/BlurBox.tsx +31 -0
  17. package/src/Body.tsx +32 -0
  18. package/src/Box.tsx +308 -0
  19. package/src/Button.tsx +219 -0
  20. package/src/Card.tsx +23 -0
  21. package/src/CheckBox.tsx +118 -0
  22. package/src/Common.ts +2743 -0
  23. package/src/Constants.ts +53 -0
  24. package/src/CustomSelect.tsx +85 -0
  25. package/src/DateTimeActionSheet.tsx +409 -0
  26. package/src/DateTimeField.android.tsx +101 -0
  27. package/src/DateTimeField.ios.tsx +83 -0
  28. package/src/DateTimeField.tsx +69 -0
  29. package/src/DecimalRangeActionSheet.tsx +113 -0
  30. package/src/ErrorBoundary.tsx +37 -0
  31. package/src/ErrorPage.tsx +44 -0
  32. package/src/FernsProvider.tsx +21 -0
  33. package/src/Field.tsx +299 -0
  34. package/src/FieldWithLabels.tsx +36 -0
  35. package/src/FlatList.tsx +2 -0
  36. package/src/Form.tsx +182 -0
  37. package/src/HeaderButtons.tsx +107 -0
  38. package/src/Heading.tsx +53 -0
  39. package/src/HeightActionSheet.tsx +104 -0
  40. package/src/Hyperlink.tsx +181 -0
  41. package/src/Icon.tsx +24 -0
  42. package/src/IconButton.tsx +165 -0
  43. package/src/Image.tsx +50 -0
  44. package/src/ImageBackground.tsx +14 -0
  45. package/src/InfoTooltipButton.tsx +23 -0
  46. package/src/Layer.tsx +17 -0
  47. package/src/Link.tsx +17 -0
  48. package/src/Mask.tsx +21 -0
  49. package/src/MediaQuery.ts +46 -0
  50. package/src/Meta.tsx +9 -0
  51. package/src/Modal.tsx +248 -0
  52. package/src/ModalSheet.tsx +58 -0
  53. package/src/NumberPickerActionSheet.tsx +66 -0
  54. package/src/Page.tsx +133 -0
  55. package/src/Permissions.ts +44 -0
  56. package/src/PickerSelect.tsx +553 -0
  57. package/src/Pill.tsx +24 -0
  58. package/src/Pog.tsx +87 -0
  59. package/src/ProgressBar.tsx +55 -0
  60. package/src/ScrollView.tsx +2 -0
  61. package/src/SegmentedControl.tsx +102 -0
  62. package/src/SelectList.tsx +89 -0
  63. package/src/SideDrawer.tsx +62 -0
  64. package/src/Spinner.tsx +20 -0
  65. package/src/SplitPage.native.tsx +160 -0
  66. package/src/SplitPage.tsx +302 -0
  67. package/src/Switch.tsx +19 -0
  68. package/src/Table.tsx +87 -0
  69. package/src/TableHeader.tsx +36 -0
  70. package/src/TableHeaderCell.tsx +76 -0
  71. package/src/TableRow.tsx +87 -0
  72. package/src/TapToEdit.tsx +221 -0
  73. package/src/Text.tsx +131 -0
  74. package/src/TextArea.tsx +16 -0
  75. package/src/TextField.tsx +401 -0
  76. package/src/TextFieldNumberActionSheet.tsx +61 -0
  77. package/src/Toast.tsx +106 -0
  78. package/src/Tooltip.tsx +269 -0
  79. package/src/UnifiedScreens.ts +24 -0
  80. package/src/Unifier.ts +371 -0
  81. package/src/Utilities.tsx +159 -0
  82. package/src/WithLabel.tsx +57 -0
  83. package/src/dayjsExtended.ts +10 -0
  84. package/src/index.tsx +1347 -0
  85. package/src/polyfill.d.ts +11 -0
  86. package/src/tableContext.tsx +80 -0
  87. package/src/useStoredState.ts +39 -0
@@ -0,0 +1,83 @@
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
+ errorMessageColor,
16
+ dateFormat,
17
+ pickerType = "inline",
18
+ label,
19
+ }: DateTimeFieldProps): ReactElement => {
20
+ const [showPicker, setShowPicker] = useState(false);
21
+
22
+ const defaultFormat = useMemo(() => {
23
+ if (dateFormat) {
24
+ return dateFormat;
25
+ } else {
26
+ if (mode === "date") {
27
+ return "MMMM Do YYYY";
28
+ } else if (mode === "time") {
29
+ return "h:mm a";
30
+ } else {
31
+ return "MMMM Do YYYY, h:mm a";
32
+ }
33
+ }
34
+ }, [mode, dateFormat]);
35
+
36
+ return (
37
+ <WithLabel label={label} labelSize="lg">
38
+ <WithLabel
39
+ label={errorMessage}
40
+ labelColor={errorMessageColor || "red"}
41
+ labelPlacement="after"
42
+ labelSize="sm"
43
+ >
44
+ <TextInput
45
+ inputMode="none"
46
+ style={{
47
+ flex: 1,
48
+ paddingTop: 10,
49
+ paddingRight: 10,
50
+ paddingBottom: 10,
51
+ paddingLeft: 10,
52
+ height: 40,
53
+ width: "100%",
54
+ color: Unifier.theme.darkGray,
55
+ fontFamily: Unifier.theme.primaryFont,
56
+ borderWidth: 1,
57
+ }}
58
+ value={dayjs(value).format(defaultFormat)}
59
+ onPressIn={() => {
60
+ setShowPicker(!showPicker);
61
+ }}
62
+ />
63
+
64
+ {showPicker && (
65
+ <DateTimePicker
66
+ accentColor={Unifier.theme.primary}
67
+ display={pickerType}
68
+ mode={mode}
69
+ style={{alignSelf: "flex-start"}}
70
+ testID="dateTimePicker"
71
+ value={dayjs(value).toDate()}
72
+ onChange={(event: any, date: any) => {
73
+ if (!date) {
74
+ return;
75
+ }
76
+ onChange(date);
77
+ }}
78
+ />
79
+ )}
80
+ </WithLabel>
81
+ </WithLabel>
82
+ );
83
+ };
@@ -0,0 +1,69 @@
1
+ import isArray from "lodash/isArray";
2
+ import React, {ReactElement} from "react";
3
+ import DatePicker from "react-date-picker";
4
+ import DateTimePickerWeb from "react-datetime-picker";
5
+ import TimePicker from "react-time-picker";
6
+
7
+ import {Box} from "./Box";
8
+ import {DateTimeFieldProps, WithChildren} from "./Common";
9
+ import {WithLabel} from "./WithLabel";
10
+
11
+ export const DateTimeField = ({
12
+ mode,
13
+ value,
14
+ onChange,
15
+ errorMessage,
16
+ errorMessageColor,
17
+ }: WithChildren<DateTimeFieldProps>): ReactElement => {
18
+ return (
19
+ <WithLabel
20
+ label={errorMessage}
21
+ labelColor={errorMessageColor || "red"}
22
+ labelPlacement="after"
23
+ labelSize="sm"
24
+ >
25
+ <Box flex="grow" maxWidth={300} zIndex="auto">
26
+ {mode === "datetime" && (
27
+ <DateTimePickerWeb
28
+ disableClock
29
+ value={value}
30
+ onChange={(newVal) => {
31
+ if (isArray(newVal) || !newVal) {
32
+ console.warn("DateTimePicker returned an array", newVal);
33
+ return;
34
+ }
35
+ onChange(newVal);
36
+ }}
37
+ />
38
+ )}
39
+ {mode === "date" && (
40
+ <DatePicker
41
+ value={value}
42
+ onChange={(newVal) => {
43
+ if (isArray(newVal) || !newVal) {
44
+ console.warn("DatePicker returned an array", newVal);
45
+ return;
46
+ }
47
+ onChange(newVal);
48
+ }}
49
+ />
50
+ )}
51
+ {mode === "time" && (
52
+ <TimePicker
53
+ disableClock
54
+ value={value}
55
+ onChange={(newVal) => {
56
+ if (isArray(newVal) || !newVal) {
57
+ console.warn("TimePicker returned an array", newVal);
58
+ return;
59
+ }
60
+ // TimePicker returns a string or Date, so we need to make sure it's a Date
61
+ const newDate = new Date(newVal);
62
+ onChange(newDate);
63
+ }}
64
+ />
65
+ )}
66
+ </Box>
67
+ </WithLabel>
68
+ );
69
+ };
@@ -0,0 +1,113 @@
1
+ import {Picker} from "@react-native-picker/picker";
2
+ import range from "lodash/range";
3
+ import React from "react";
4
+
5
+ import {ActionSheet} from "./ActionSheet";
6
+ import {Box} from "./Box";
7
+ import {Button} from "./Button";
8
+ import {OnChangeCallback} from "./Common";
9
+
10
+ const PICKER_HEIGHT = 104;
11
+
12
+ interface DecimalRangeActionSheetProps {
13
+ value: string;
14
+ min: number;
15
+ max: number;
16
+ onChange: OnChangeCallback;
17
+ actionSheetRef: React.RefObject<any>;
18
+ }
19
+
20
+ interface DecimalRangeActionSheetState {
21
+ whole: string;
22
+ decimal: string;
23
+ }
24
+
25
+ export class DecimalRangeActionSheet extends React.Component<
26
+ DecimalRangeActionSheetProps,
27
+ DecimalRangeActionSheetState
28
+ > {
29
+ constructor(props: DecimalRangeActionSheetProps) {
30
+ super(props);
31
+ this.state = {
32
+ whole: String(Math.floor(Number(props.value))),
33
+ decimal: String((Number(props.value) * 10) % 10),
34
+ };
35
+ }
36
+
37
+ render() {
38
+ return (
39
+ <ActionSheet ref={this.props.actionSheetRef} bounceOnOpen gestureEnabled>
40
+ <Box marginBottom={8} paddingX={4} width="100%">
41
+ <Box alignItems="end" display="flex" width="100%">
42
+ <Box width="33%">
43
+ <Button
44
+ color="blue"
45
+ size="lg"
46
+ text="Close"
47
+ type="ghost"
48
+ onClick={() => {
49
+ this.props.actionSheetRef?.current?.setModalVisible(false);
50
+ }}
51
+ />
52
+ </Box>
53
+ </Box>
54
+ <Box direction="row" width="100%">
55
+ <Box width="50%">
56
+ <Picker
57
+ itemStyle={{
58
+ height: PICKER_HEIGHT,
59
+ }}
60
+ selectedValue={this.state.whole}
61
+ style={{
62
+ height: PICKER_HEIGHT,
63
+ backgroundColor: "#FFFFFF",
64
+ }}
65
+ onValueChange={(whole) => {
66
+ this.setState({whole: String(whole)});
67
+ this.props.onChange({
68
+ value: String(Number(whole) + Number(this.state.decimal) * 0.1),
69
+ });
70
+ }}
71
+ >
72
+ {range(this.props.min, this.props.max + 1).map((n) => {
73
+ // console.log("FIRST", String(n));
74
+ return <Picker.Item key={String(n)} label={String(n)} value={String(n)} />;
75
+ })}
76
+ </Picker>
77
+ </Box>
78
+ <Box width="50%">
79
+ <Picker
80
+ itemStyle={{
81
+ height: PICKER_HEIGHT,
82
+ }}
83
+ selectedValue={this.state.decimal}
84
+ style={{
85
+ height: PICKER_HEIGHT,
86
+ backgroundColor: "#FFFFFF",
87
+ }}
88
+ onValueChange={(decimal) => {
89
+ // console.log(
90
+ // "DECIMAL",
91
+ // decimal,
92
+ // this.state.whole,
93
+ // Number(decimal.toString()) * 0.1,
94
+ // String(this.state.whole + Number(decimal) * 0.1)
95
+ // );
96
+ this.setState({decimal: String(decimal)});
97
+ this.props.onChange({
98
+ value: String(Number(this.state.whole) + Number(decimal) * 0.1),
99
+ });
100
+ }}
101
+ >
102
+ {range(0, 10).map((n) => {
103
+ // console.log("N", n);
104
+ return <Picker.Item key={String(n)} label={`.${String(n)}`} value={String(n)} />;
105
+ })}
106
+ </Picker>
107
+ </Box>
108
+ </Box>
109
+ </Box>
110
+ </ActionSheet>
111
+ );
112
+ }
113
+ }
@@ -0,0 +1,37 @@
1
+ import React from "react";
2
+
3
+ import {ErrorBoundaryProps} from "./Common";
4
+ import {ErrorPage} from "./ErrorPage";
5
+
6
+ interface State {
7
+ error?: Error;
8
+ }
9
+
10
+ export class ErrorBoundary extends React.Component<ErrorBoundaryProps, State> {
11
+ state = {error: undefined};
12
+
13
+ static getDerivedStateFromError(error: Error) {
14
+ console.warn("[ErrorBoundary] Derived error", error);
15
+ return {error};
16
+ }
17
+
18
+ componentDidCatch(error: Error, info: {componentStack: string}) {
19
+ console.warn("[ErrorBoundary] Caught error", error);
20
+
21
+ if (this.props.onError) {
22
+ this.props.onError(error, info.componentStack);
23
+ }
24
+ }
25
+
26
+ resetError = () => {
27
+ this.setState({error: undefined});
28
+ };
29
+
30
+ render() {
31
+ const error = this.state.error;
32
+ if (error) {
33
+ return <ErrorPage error={error} resetError={this.resetError} />;
34
+ }
35
+ return this.props.children;
36
+ }
37
+ }
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+
3
+ import {Box} from "./Box";
4
+ import {Button} from "./Button";
5
+ import {Text} from "./Text";
6
+ interface ErrorPageProps {
7
+ error: Error;
8
+ resetError: () => void;
9
+ }
10
+
11
+ export class ErrorPage extends React.Component<ErrorPageProps, {}> {
12
+ constructor(props: ErrorPageProps) {
13
+ super(props);
14
+ this.state = {};
15
+ }
16
+
17
+ render() {
18
+ return (
19
+ <Box
20
+ alignItems="center"
21
+ direction="column"
22
+ display="flex"
23
+ height="100%"
24
+ justifyContent="center"
25
+ padding={6}
26
+ width="100%"
27
+ >
28
+ <Text align="center" color="red" size="lg" weight="bold">
29
+ Oops!
30
+ </Text>
31
+ <Box paddingY={3}>
32
+ <Text align="center">
33
+ There&apos;s an error. Sorry! Our team just got a notification about the error so they
34
+ can fix it as soon as possible!
35
+ </Text>
36
+ </Box>
37
+ <Box paddingY={3}>
38
+ <Text>{this.props.error.toString()}</Text>
39
+ </Box>
40
+ <Button color="blue" text="Try again" onClick={this.props.resetError} />
41
+ </Box>
42
+ );
43
+ }
44
+ }
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ import {Host} from "react-native-portalize";
3
+ import {ToastProvider} from "react-native-toast-notifications";
4
+
5
+ import {Toast} from "./Toast";
6
+
7
+ export function FernsProvider({children}: {children: React.ReactNode}): React.ReactElement {
8
+ return (
9
+ <ToastProvider
10
+ animationDuration={250}
11
+ animationType="slide-in"
12
+ duration={50000}
13
+ offset={50}
14
+ placement="bottom"
15
+ renderToast={(toastOptions) => <Toast {...(toastOptions as any)} />}
16
+ swipeEnabled
17
+ >
18
+ <Host>{children}</Host>
19
+ </ToastProvider>
20
+ );
21
+ }
package/src/Field.tsx ADDED
@@ -0,0 +1,299 @@
1
+ import React from "react";
2
+
3
+ import {Box} from "./Box";
4
+ import {CheckBox} from "./CheckBox";
5
+ import {AddressInterface, FieldWithLabelsProps, ReactChildren, TextFieldType} from "./Common";
6
+ import {USSTATESLIST} from "./Constants";
7
+ import {CustomSelect} from "./CustomSelect";
8
+ import {FieldWithLabels} from "./FieldWithLabels";
9
+ import {SelectList, SelectListOptions} from "./SelectList";
10
+ import {Switch} from "./Switch";
11
+ import {Text} from "./Text";
12
+ import {TextArea} from "./TextArea";
13
+ import {TextField} from "./TextField";
14
+
15
+ export interface FieldProps extends FieldWithLabelsProps {
16
+ name?: string;
17
+ label?: string;
18
+ height?: number;
19
+ type?:
20
+ | "address"
21
+ | "boolean"
22
+ | "currency"
23
+ | "customSelect"
24
+ | "date"
25
+ | "datetime"
26
+ | "email"
27
+ | "multiselect"
28
+ | "number"
29
+ | "password"
30
+ | "percent"
31
+ | "phoneNumber"
32
+ | "select"
33
+ | "text"
34
+ | "textarea"
35
+ | "time"
36
+ | "url";
37
+ rows?: number;
38
+ value?: any;
39
+ onChange?: any;
40
+ options?: SelectListOptions;
41
+ placeholder?: string;
42
+ disabled?: boolean;
43
+ useCheckbox?: boolean;
44
+ }
45
+
46
+ export const Field = ({
47
+ name,
48
+ label,
49
+ labelColor,
50
+ height,
51
+ type,
52
+ rows,
53
+ value,
54
+ onChange,
55
+ options,
56
+ placeholder,
57
+ disabled,
58
+ errorMessage,
59
+ errorMessageColor,
60
+ helperText,
61
+ helperTextColor,
62
+ }: FieldProps) => {
63
+ const handleAddressChange = (field: string, newValue: string) => {
64
+ onChange({...value, [field]: newValue});
65
+ };
66
+
67
+ const handleSwitchChange = (switchValue: boolean) => {
68
+ onChange(switchValue);
69
+ };
70
+
71
+ const renderField = (): ReactChildren => {
72
+ if (type === "select") {
73
+ if (!options) {
74
+ console.error("Field with type=select require options");
75
+ return undefined;
76
+ }
77
+ return (
78
+ <SelectList
79
+ disabled={disabled}
80
+ id={name}
81
+ options={options}
82
+ placeholder={placeholder}
83
+ value={value}
84
+ onChange={onChange}
85
+ />
86
+ );
87
+ } else if (type === "multiselect") {
88
+ if (options === undefined) {
89
+ console.error("Field with type=multiselect require options");
90
+ return undefined;
91
+ }
92
+ return (
93
+ <Box width="100%">
94
+ {options.map((o) => (
95
+ <Box
96
+ key={o.label + o.value}
97
+ alignItems="center"
98
+ direction="row"
99
+ justifyContent="between"
100
+ width="100%"
101
+ >
102
+ <Box flex="shrink" marginRight={2}>
103
+ <Text weight="bold">{o.label}</Text>
104
+ </Box>
105
+ <Box>
106
+ <CheckBox
107
+ key={o.label + o.value}
108
+ checked={(value ?? []).includes(o.value)}
109
+ disabled={disabled}
110
+ name={name}
111
+ size="sm"
112
+ onChange={(result) => {
113
+ let newValue;
114
+ if (result.value) {
115
+ if (value.includes(o.value)) {
116
+ console.warn(`Tried to add value that already exists: ${o.value}`);
117
+ return;
118
+ }
119
+ newValue = [...value, o.value];
120
+ } else {
121
+ newValue = value.filter((v: string) => v !== o.value);
122
+ }
123
+ onChange(newValue);
124
+ }}
125
+ />
126
+ </Box>
127
+ </Box>
128
+ ))}
129
+ </Box>
130
+ );
131
+ } else if (type === "textarea") {
132
+ return (
133
+ <TextArea
134
+ disabled={disabled}
135
+ height={height ?? 100}
136
+ id={name}
137
+ placeholder={Boolean(value) ? "" : placeholder}
138
+ rows={rows}
139
+ value={String(value)}
140
+ onChange={(result) => onChange(result.value)}
141
+ />
142
+ );
143
+ } else if (type === "boolean") {
144
+ return (
145
+ <Switch
146
+ disabled={disabled}
147
+ id={name}
148
+ name={name}
149
+ switched={Boolean(value)}
150
+ onChange={(result) => handleSwitchChange(result)}
151
+ />
152
+ );
153
+ } else if (type && ["date", "time", "datetime"].includes(type)) {
154
+ return (
155
+ <TextField
156
+ disabled={disabled}
157
+ id={name}
158
+ placeholder={placeholder}
159
+ type={type as "date" | "time" | "datetime"}
160
+ value={value}
161
+ onChange={(result) => onChange(result.value)}
162
+ />
163
+ );
164
+ } else if (type === "address") {
165
+ const addressValue = value ? value : {};
166
+ const {
167
+ address1 = "",
168
+ address2 = "",
169
+ city = "",
170
+ state = "",
171
+ zipcode = "",
172
+ }: AddressInterface = addressValue;
173
+ return (
174
+ <>
175
+ <TextField
176
+ disabled={disabled}
177
+ id="address1"
178
+ label="Street Address"
179
+ type="text"
180
+ value={address1}
181
+ onChange={(result) => handleAddressChange("address1", result.value)}
182
+ />
183
+ <TextField
184
+ disabled={disabled}
185
+ id="address2"
186
+ label="Apt, suite, etc"
187
+ type="text"
188
+ value={address2}
189
+ onChange={(result) => handleAddressChange("address2", result.value)}
190
+ />
191
+ <TextField
192
+ disabled={disabled}
193
+ id="city"
194
+ label="City"
195
+ type="text"
196
+ value={city}
197
+ onChange={(result) => handleAddressChange("city", result.value)}
198
+ />
199
+ <SelectList
200
+ disabled={disabled}
201
+ id="state"
202
+ label="State"
203
+ options={USSTATESLIST}
204
+ placeholder="Select state"
205
+ style={{borderRadius: 16}}
206
+ value={state}
207
+ onChange={(result) => handleAddressChange("state", result)}
208
+ />
209
+ <TextField
210
+ disabled={disabled}
211
+ id="zipcode"
212
+ label="Zipcode"
213
+ type="text"
214
+ value={zipcode}
215
+ onChange={(result) => handleAddressChange("zipcode", result.value)}
216
+ />
217
+ </>
218
+ );
219
+ } else if (type === "customSelect") {
220
+ if (!options) {
221
+ console.error("Field with type=customSelect require options");
222
+ return null;
223
+ }
224
+ return (
225
+ <CustomSelect
226
+ disabled={disabled}
227
+ options={options}
228
+ placeholder={placeholder}
229
+ value={value}
230
+ onChange={onChange}
231
+ />
232
+ );
233
+ } else {
234
+ let tfType: TextFieldType = "text";
235
+ let tfValue: string = value;
236
+ // Number is supported differently because we need fractional numbers and they don't work
237
+ // well on iOS.
238
+ if (
239
+ type &&
240
+ ["date", "time", "datetime", "email", "phoneNumber", "password", "url"].includes(type)
241
+ ) {
242
+ tfType = type as TextFieldType;
243
+ } else if (type === "percent" || type === "currency") {
244
+ tfType = "text";
245
+ }
246
+ let autoComplete: "on" | "current-password" | "username" = "on";
247
+ if (tfType === "password") {
248
+ autoComplete = "current-password";
249
+ } else if (tfType === "email") {
250
+ autoComplete = "username";
251
+ }
252
+ if (type === "percent") {
253
+ tfValue = `${Number(value).toFixed(0)}%`;
254
+ } else if (type === "currency") {
255
+ tfValue = `$${Number(value).toFixed(2)}`;
256
+ }
257
+ return (
258
+ <TextField
259
+ autoComplete={autoComplete}
260
+ disabled={disabled}
261
+ id={name}
262
+ placeholder={placeholder}
263
+ type={
264
+ tfType as
265
+ | "date"
266
+ | "datetime"
267
+ | "email"
268
+ | "number"
269
+ | "password"
270
+ | "phoneNumber"
271
+ | "text"
272
+ | "time"
273
+ | "url"
274
+ }
275
+ value={tfValue}
276
+ onChange={(result) => onChange(result.value)}
277
+ />
278
+ );
279
+ }
280
+ };
281
+
282
+ const children = renderField();
283
+ return (
284
+ <Box marginBottom={5}>
285
+ <FieldWithLabels
286
+ {...{
287
+ errorMessage,
288
+ errorMessageColor,
289
+ helperText,
290
+ helperTextColor,
291
+ label,
292
+ labelColor,
293
+ }}
294
+ >
295
+ {children}
296
+ </FieldWithLabels>
297
+ </Box>
298
+ );
299
+ };
@@ -0,0 +1,36 @@
1
+ import React from "react";
2
+
3
+ import {FieldWithLabelsProps} from "./Common";
4
+ import {WithLabel} from "./WithLabel";
5
+
6
+ export const FieldWithLabels = ({
7
+ children,
8
+ errorMessage,
9
+ errorMessageColor = "red",
10
+ helperText,
11
+ helperTextColor = "darkGray",
12
+ label,
13
+ labelColor = "darkGray",
14
+ }: FieldWithLabelsProps) => {
15
+ return (
16
+ <WithLabel
17
+ label={helperText}
18
+ labelColor={helperTextColor}
19
+ labelPlacement="after"
20
+ labelSize="sm"
21
+ show={Boolean(helperText)}
22
+ >
23
+ <WithLabel
24
+ label={errorMessage}
25
+ labelColor={errorMessageColor}
26
+ labelPlacement="after"
27
+ labelSize="md"
28
+ show={Boolean(errorMessage)}
29
+ >
30
+ <WithLabel label={label} labelColor={labelColor} show={Boolean(label)}>
31
+ {children}
32
+ </WithLabel>
33
+ </WithLabel>
34
+ </WithLabel>
35
+ );
36
+ };