ferns-ui 2.0.0-beta.2 → 2.0.0-beta.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/dist/Accordion.js +7 -2
- package/dist/Accordion.js.map +1 -1
- package/dist/ActionSheet.js +14 -11
- package/dist/ActionSheet.js.map +1 -1
- package/dist/AddressField.js +1 -1
- package/dist/AddressField.js.map +1 -1
- package/dist/Badge.js +1 -1
- package/dist/Badge.js.map +1 -1
- package/dist/Banner.js +1 -1
- package/dist/Banner.js.map +1 -1
- package/dist/Box.js +3 -3
- package/dist/Box.js.map +1 -1
- package/dist/Button.js +0 -1
- package/dist/Button.js.map +1 -1
- package/dist/CheckBox.js.map +1 -1
- package/dist/Common.d.ts +14 -9
- package/dist/Common.js.map +1 -1
- package/dist/DataTable.js +1 -2
- package/dist/DataTable.js.map +1 -1
- package/dist/DateTimeField.js +22 -22
- package/dist/DateTimeField.js.map +1 -1
- package/dist/EmailField.js +17 -37
- package/dist/EmailField.js.map +1 -1
- package/dist/EmojiSelector.d.ts +114 -0
- package/dist/EmojiSelector.js +332 -0
- package/dist/EmojiSelector.js.map +1 -0
- package/dist/FernsProvider.js +1 -1
- package/dist/FernsProvider.js.map +1 -1
- package/dist/Heading.js +3 -1
- package/dist/Heading.js.map +1 -1
- package/dist/Hyperlink.js +1 -1
- package/dist/Hyperlink.js.map +1 -1
- package/dist/IconButton.js +1 -1
- package/dist/IconButton.js.map +1 -1
- package/dist/Image.js.map +1 -1
- package/dist/MarkdownView.d.ts +5 -0
- package/dist/MarkdownView.js +44 -0
- package/dist/MarkdownView.js.map +1 -0
- package/dist/MobileAddressAutoComplete.js +1 -1
- package/dist/MobileAddressAutoComplete.js.map +1 -1
- package/dist/Modal.d.ts +1 -1
- package/dist/Modal.js +35 -15
- package/dist/Modal.js.map +1 -1
- package/dist/NumberField.js +10 -4
- package/dist/NumberField.js.map +1 -1
- package/dist/NumberPickerActionSheet.d.ts +1 -3
- package/dist/NumberPickerActionSheet.js +0 -3
- package/dist/NumberPickerActionSheet.js.map +1 -1
- package/dist/Page.js +1 -1
- package/dist/Page.js.map +1 -1
- package/dist/Pagination.js +2 -2
- package/dist/Pagination.js.map +1 -1
- package/dist/Permissions.d.ts +1 -1
- package/dist/Permissions.js +2 -2
- package/dist/Permissions.js.map +1 -1
- package/dist/PickerSelect.js +1 -1
- package/dist/PickerSelect.js.map +1 -1
- package/dist/SectionDivider.js +1 -1
- package/dist/SectionDivider.js.map +1 -1
- package/dist/SegmentedControl.js.map +1 -1
- package/dist/Signature.native.js +2 -2
- package/dist/Signature.native.js.map +1 -1
- package/dist/SignatureField.js +2 -2
- package/dist/SignatureField.js.map +1 -1
- package/dist/Slider.js +3 -3
- package/dist/Slider.js.map +1 -1
- package/dist/SplitPage.js +7 -7
- package/dist/SplitPage.js.map +1 -1
- package/dist/SplitPage.native.js +4 -6
- package/dist/SplitPage.native.js.map +1 -1
- package/dist/TapToEdit.js +3 -3
- package/dist/TapToEdit.js.map +1 -1
- package/dist/Text.js +1 -1
- package/dist/Text.js.map +1 -1
- package/dist/TextField.js +9 -2
- package/dist/TextField.js.map +1 -1
- package/dist/TextFieldNumberActionSheet.d.ts +2 -4
- package/dist/TextFieldNumberActionSheet.js +1 -4
- package/dist/TextFieldNumberActionSheet.js.map +1 -1
- package/dist/Tooltip.js +37 -19
- package/dist/Tooltip.js.map +1 -1
- package/dist/Unifier.d.ts +0 -1
- package/dist/Unifier.js.map +1 -1
- package/dist/Utilities.d.ts +1 -1
- package/dist/Utilities.js +2 -3
- package/dist/Utilities.js.map +1 -1
- package/dist/WebAddressAutocomplete.js +2 -1
- package/dist/WebAddressAutocomplete.js.map +1 -1
- package/dist/index.d.ts +11 -10
- package/dist/index.js +11 -9
- package/dist/index.js.map +1 -1
- package/dist/table/Table.js +14 -15
- package/dist/table/Table.js.map +1 -1
- package/dist/table/TableHeaderCell.js +2 -2
- package/dist/table/TableHeaderCell.js.map +1 -1
- package/dist/useStoredState.js +4 -2
- package/dist/useStoredState.js.map +1 -1
- package/package.json +7 -65
- package/src/Accordion.tsx +7 -1
- package/src/ActionSheet.tsx +26 -22
- package/src/AddressField.tsx +1 -1
- package/src/Badge.tsx +1 -1
- package/src/Banner.tsx +1 -1
- package/src/Box.test.tsx +71 -70
- package/src/Box.tsx +21 -9
- package/src/Button.tsx +0 -1
- package/src/CheckBox.tsx +7 -1
- package/src/Common.ts +15 -21
- package/src/DataTable.tsx +1 -2
- package/src/DateTimeField.tsx +22 -22
- package/src/EmailField.tsx +22 -42
- package/src/EmojiSelector.test.tsx +61 -0
- package/src/EmojiSelector.tsx +510 -0
- package/src/FernsProvider.tsx +1 -4
- package/src/Heading.tsx +3 -1
- package/src/Hyperlink.tsx +1 -1
- package/src/IconButton.tsx +2 -2
- package/src/Image.tsx +1 -0
- package/src/MarkdownView.tsx +67 -0
- package/src/MobileAddressAutoComplete.tsx +1 -1
- package/src/Modal.tsx +58 -21
- package/src/NumberField.tsx +10 -4
- package/src/NumberPickerActionSheet.tsx +1 -5
- package/src/Page.tsx +1 -1
- package/src/Pagination.tsx +2 -11
- package/src/Permissions.ts +2 -2
- package/src/PickerSelect.tsx +1 -1
- package/src/SectionDivider.tsx +1 -1
- package/src/SegmentedControl.tsx +3 -1
- package/src/Signature.native.tsx +2 -2
- package/src/SignatureField.tsx +2 -2
- package/src/Slider.tsx +10 -17
- package/src/SplitPage.native.tsx +2 -4
- package/src/SplitPage.tsx +4 -4
- package/src/TapToEdit.tsx +3 -7
- package/src/Text.tsx +1 -1
- package/src/TextArea.test.tsx +27 -43
- package/src/TextField.test.tsx +64 -5
- package/src/TextField.tsx +10 -1
- package/src/TextFieldNumberActionSheet.tsx +3 -7
- package/src/Tooltip.tsx +41 -19
- package/src/Unifier.ts +1 -3
- package/src/Utilities.tsx +3 -4
- package/src/WebAddressAutocomplete.tsx +1 -1
- package/src/__snapshots__/EmojiSelector.test.tsx.snap +604 -0
- package/src/index.tsx +12 -10
- package/src/table/Table.tsx +34 -36
- package/src/table/TableHeaderCell.tsx +2 -2
- package/src/useStoredState.ts +13 -11
package/src/DataTable.tsx
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {FontAwesome6} from "@expo/vector-icons";
|
|
2
2
|
import React, {FC, useCallback, useMemo, useRef, useState} from "react";
|
|
3
3
|
import {NativeScrollEvent, NativeSyntheticEvent, Pressable, ScrollView, View} from "react-native";
|
|
4
|
-
// @ts-ignore
|
|
5
4
|
import Markdown from "react-native-markdown-display";
|
|
6
5
|
|
|
7
6
|
import {Box} from "./Box";
|
|
@@ -18,9 +17,9 @@ import {Icon} from "./Icon";
|
|
|
18
17
|
import {InfoModalIcon} from "./InfoModalIcon";
|
|
19
18
|
import {Modal} from "./Modal";
|
|
20
19
|
import {Pagination} from "./Pagination";
|
|
21
|
-
import {TableTitle} from "./table/TableTitle";
|
|
22
20
|
import {Text} from "./Text";
|
|
23
21
|
import {useTheme} from "./Theme";
|
|
22
|
+
import {TableTitle} from "./table/TableTitle";
|
|
24
23
|
|
|
25
24
|
// TODO: Add permanent horizontal scroll bar so users with only a mouse can scroll left/right
|
|
26
25
|
// easily.
|
package/src/DateTimeField.tsx
CHANGED
|
@@ -262,20 +262,20 @@ export const DateTimeField: FC<DateTimeFieldProps> = ({
|
|
|
262
262
|
if (type === "date" || type === "datetime") {
|
|
263
263
|
if (fieldIndex === 0) {
|
|
264
264
|
// Month
|
|
265
|
-
const monthNum = parseInt(fieldValue);
|
|
266
|
-
if (isNaN(monthNum) || monthNum < 1 || monthNum > 12) {
|
|
265
|
+
const monthNum = parseInt(fieldValue, 10);
|
|
266
|
+
if (Number.isNaN(monthNum) || monthNum < 1 || monthNum > 12) {
|
|
267
267
|
return "Month must be between 1 and 12";
|
|
268
268
|
}
|
|
269
269
|
} else if (fieldIndex === 1) {
|
|
270
270
|
// Day
|
|
271
|
-
const dayNum = parseInt(fieldValue);
|
|
272
|
-
if (isNaN(dayNum) || dayNum < 1 || dayNum > 31) {
|
|
271
|
+
const dayNum = parseInt(fieldValue, 10);
|
|
272
|
+
if (Number.isNaN(dayNum) || dayNum < 1 || dayNum > 31) {
|
|
273
273
|
return "Day must be between 1 and 31";
|
|
274
274
|
}
|
|
275
275
|
} else if (fieldIndex === 2) {
|
|
276
276
|
// Year
|
|
277
|
-
const yearNum = parseInt(fieldValue);
|
|
278
|
-
if (isNaN(yearNum) || yearNum < 1900 || yearNum > 2100) {
|
|
277
|
+
const yearNum = parseInt(fieldValue, 10);
|
|
278
|
+
if (Number.isNaN(yearNum) || yearNum < 1900 || yearNum > 2100) {
|
|
279
279
|
return "Year must be between 1900 and 2100";
|
|
280
280
|
}
|
|
281
281
|
}
|
|
@@ -284,14 +284,14 @@ export const DateTimeField: FC<DateTimeFieldProps> = ({
|
|
|
284
284
|
if (type === "time" || type === "datetime") {
|
|
285
285
|
if (fieldIndex === (type === "time" ? 0 : 3)) {
|
|
286
286
|
// Hour
|
|
287
|
-
const hourNum = parseInt(fieldValue);
|
|
288
|
-
if (isNaN(hourNum) || hourNum < 1 || hourNum > 12) {
|
|
287
|
+
const hourNum = parseInt(fieldValue, 10);
|
|
288
|
+
if (Number.isNaN(hourNum) || hourNum < 1 || hourNum > 12) {
|
|
289
289
|
return "Hour must be between 1 and 12";
|
|
290
290
|
}
|
|
291
291
|
} else if (fieldIndex === (type === "time" ? 1 : 4)) {
|
|
292
292
|
// Minute
|
|
293
|
-
const minuteNum = parseInt(fieldValue);
|
|
294
|
-
if (isNaN(minuteNum) || minuteNum < 0 || minuteNum > 59) {
|
|
293
|
+
const minuteNum = parseInt(fieldValue, 10);
|
|
294
|
+
if (Number.isNaN(minuteNum) || minuteNum < 0 || minuteNum > 59) {
|
|
295
295
|
return "Minute must be between 0 and 59";
|
|
296
296
|
}
|
|
297
297
|
}
|
|
@@ -323,7 +323,7 @@ export const DateTimeField: FC<DateTimeFieldProps> = ({
|
|
|
323
323
|
if (!monthVal || !dayVal || !yearVal || !hour || !minuteVal) {
|
|
324
324
|
return undefined;
|
|
325
325
|
}
|
|
326
|
-
let hourNum = parseInt(hourVal);
|
|
326
|
+
let hourNum = parseInt(hourVal, 10);
|
|
327
327
|
if (ampPmVal === "pm" && hourNum !== 12) {
|
|
328
328
|
hourNum += 12;
|
|
329
329
|
} else if (ampPmVal === "am" && hourNum === 12) {
|
|
@@ -331,11 +331,11 @@ export const DateTimeField: FC<DateTimeFieldProps> = ({
|
|
|
331
331
|
}
|
|
332
332
|
date = DateTime.fromObject(
|
|
333
333
|
{
|
|
334
|
-
year: parseInt(yearVal),
|
|
335
|
-
month: parseInt(monthVal),
|
|
336
|
-
day: parseInt(dayVal),
|
|
334
|
+
year: parseInt(yearVal, 10),
|
|
335
|
+
month: parseInt(monthVal, 10),
|
|
336
|
+
day: parseInt(dayVal, 10),
|
|
337
337
|
hour: hourNum,
|
|
338
|
-
minute: parseInt(minuteVal),
|
|
338
|
+
minute: parseInt(minuteVal, 10),
|
|
339
339
|
second: 0,
|
|
340
340
|
millisecond: 0,
|
|
341
341
|
},
|
|
@@ -349,9 +349,9 @@ export const DateTimeField: FC<DateTimeFieldProps> = ({
|
|
|
349
349
|
}
|
|
350
350
|
date = DateTime.fromObject(
|
|
351
351
|
{
|
|
352
|
-
year: parseInt(yearVal),
|
|
353
|
-
month: parseInt(monthVal),
|
|
354
|
-
day: parseInt(dayVal),
|
|
352
|
+
year: parseInt(yearVal, 10),
|
|
353
|
+
month: parseInt(monthVal, 10),
|
|
354
|
+
day: parseInt(dayVal, 10),
|
|
355
355
|
hour: 0,
|
|
356
356
|
minute: 0,
|
|
357
357
|
second: 0,
|
|
@@ -365,7 +365,7 @@ export const DateTimeField: FC<DateTimeFieldProps> = ({
|
|
|
365
365
|
if (!hour || !minuteVal) {
|
|
366
366
|
return undefined;
|
|
367
367
|
}
|
|
368
|
-
let hourNum = parseInt(hour);
|
|
368
|
+
let hourNum = parseInt(hour, 10);
|
|
369
369
|
if (ampPmVal === "pm" && hourNum !== 12) {
|
|
370
370
|
hourNum += 12;
|
|
371
371
|
} else if (ampPmVal === "am" && hourNum === 12) {
|
|
@@ -374,7 +374,7 @@ export const DateTimeField: FC<DateTimeFieldProps> = ({
|
|
|
374
374
|
date = DateTime.fromObject(
|
|
375
375
|
{
|
|
376
376
|
hour: hourNum,
|
|
377
|
-
minute: parseInt(minuteVal),
|
|
377
|
+
minute: parseInt(minuteVal, 10),
|
|
378
378
|
second: 0,
|
|
379
379
|
millisecond: 0,
|
|
380
380
|
},
|
|
@@ -404,7 +404,7 @@ export const DateTimeField: FC<DateTimeFieldProps> = ({
|
|
|
404
404
|
// so it's always a valid time and easier to edit.
|
|
405
405
|
// This lets users freely edit or clear the minute field without breaking the time format.
|
|
406
406
|
const finalValue = numericValue === "" ? "00" : numericValue.slice(-2);
|
|
407
|
-
const minuteNum = parseInt(finalValue);
|
|
407
|
+
const minuteNum = parseInt(finalValue, 10);
|
|
408
408
|
|
|
409
409
|
// Update the minute state so the UI reflects the latest input,
|
|
410
410
|
// even if it's temporarily invalid
|
|
@@ -412,7 +412,7 @@ export const DateTimeField: FC<DateTimeFieldProps> = ({
|
|
|
412
412
|
setMinute(finalValue);
|
|
413
413
|
|
|
414
414
|
// Only update ref and result if it's a valid minute value
|
|
415
|
-
if (!isNaN(minuteNum) && minuteNum >= 0 && minuteNum <= 59) {
|
|
415
|
+
if (!Number.isNaN(minuteNum) && minuteNum >= 0 && minuteNum <= 59) {
|
|
416
416
|
pendingValueRef.current = {minute: finalValue};
|
|
417
417
|
setFieldErrors((prev) => ({...prev, [index]: undefined}));
|
|
418
418
|
|
package/src/EmailField.tsx
CHANGED
|
@@ -13,7 +13,11 @@ export const EmailField: FC<EmailFieldProps> = ({
|
|
|
13
13
|
...rest
|
|
14
14
|
}) => {
|
|
15
15
|
const [localValue, setLocalValue] = useState<string>(value || "");
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
// Sync local state with incoming prop values
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
setLocalValue(value || "");
|
|
20
|
+
}, [value]);
|
|
17
21
|
|
|
18
22
|
const validateEmail = useCallback((email: string): string | undefined => {
|
|
19
23
|
if (email.trim() === "") {
|
|
@@ -26,60 +30,36 @@ export const EmailField: FC<EmailFieldProps> = ({
|
|
|
26
30
|
return undefined;
|
|
27
31
|
}, []);
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const handleBlur = useCallback(
|
|
36
|
-
(email: string) => {
|
|
37
|
-
if (onBlur) {
|
|
38
|
-
onBlur(email);
|
|
39
|
-
}
|
|
40
|
-
const validationError = validateEmail(email);
|
|
41
|
-
if (validationError) {
|
|
42
|
-
setError(validationError);
|
|
43
|
-
} else {
|
|
44
|
-
setError(undefined);
|
|
33
|
+
const localOnChange = useCallback(
|
|
34
|
+
(e: string) => {
|
|
35
|
+
setLocalValue(e);
|
|
36
|
+
const err = validateEmail(e);
|
|
37
|
+
if (!err && onChange) {
|
|
38
|
+
onChange(e);
|
|
45
39
|
}
|
|
46
40
|
},
|
|
47
|
-
[
|
|
41
|
+
[onChange, validateEmail, setLocalValue]
|
|
48
42
|
);
|
|
49
43
|
|
|
50
|
-
const
|
|
51
|
-
(
|
|
52
|
-
setLocalValue(
|
|
53
|
-
const
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
if (!validationError) {
|
|
58
|
-
onChange(email);
|
|
44
|
+
const localOnBlur = useCallback(
|
|
45
|
+
(e: string) => {
|
|
46
|
+
setLocalValue(e);
|
|
47
|
+
const err = validateEmail(e);
|
|
48
|
+
if (!err && onBlur) {
|
|
49
|
+
onBlur(e);
|
|
59
50
|
}
|
|
60
51
|
},
|
|
61
|
-
[
|
|
52
|
+
[onBlur, validateEmail]
|
|
62
53
|
);
|
|
63
|
-
|
|
64
54
|
return (
|
|
65
55
|
<TextField
|
|
66
|
-
errorText={
|
|
56
|
+
errorText={errorText || validateEmail(localValue)}
|
|
67
57
|
iconName={iconName}
|
|
68
58
|
placeholder={placeholder}
|
|
69
59
|
type="email"
|
|
70
60
|
value={localValue}
|
|
71
|
-
onBlur={
|
|
72
|
-
|
|
73
|
-
if (onBlur) {
|
|
74
|
-
onBlur(value || "");
|
|
75
|
-
}
|
|
76
|
-
}}
|
|
77
|
-
onChange={(e) => {
|
|
78
|
-
handleChange(e);
|
|
79
|
-
if (onChange) {
|
|
80
|
-
onChange;
|
|
81
|
-
}
|
|
82
|
-
}}
|
|
61
|
+
onBlur={localOnBlur}
|
|
62
|
+
onChange={localOnChange}
|
|
83
63
|
{...rest}
|
|
84
64
|
/>
|
|
85
65
|
);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import EmojiSelector, {Categories} from "./EmojiSelector";
|
|
4
|
+
import {renderWithTheme} from "./test-utils";
|
|
5
|
+
|
|
6
|
+
describe("EmojiSelector", () => {
|
|
7
|
+
it("renders search bar when showSearchBar is true", () => {
|
|
8
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
9
|
+
<EmojiSelector
|
|
10
|
+
category={Categories.all}
|
|
11
|
+
columns={6}
|
|
12
|
+
onEmojiSelected={jest.fn()}
|
|
13
|
+
placeholder="Search emojis"
|
|
14
|
+
showHistory={false}
|
|
15
|
+
showSearchBar
|
|
16
|
+
showSectionTitles
|
|
17
|
+
showTabs
|
|
18
|
+
theme="#007AFF"
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
expect(getByPlaceholderText("Search emojis")).toBeTruthy();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("renders tab bar when showTabs is true", () => {
|
|
26
|
+
const {getByText} = renderWithTheme(
|
|
27
|
+
<EmojiSelector
|
|
28
|
+
category={Categories.people}
|
|
29
|
+
columns={6}
|
|
30
|
+
onEmojiSelected={jest.fn()}
|
|
31
|
+
placeholder="Search emojis"
|
|
32
|
+
showHistory={false}
|
|
33
|
+
showSearchBar
|
|
34
|
+
showSectionTitles
|
|
35
|
+
showTabs
|
|
36
|
+
theme="#007AFF"
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// One of the known category symbols
|
|
41
|
+
expect(getByText("😀")).toBeTruthy();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("matches snapshot", () => {
|
|
45
|
+
const tree = renderWithTheme(
|
|
46
|
+
<EmojiSelector
|
|
47
|
+
category={Categories.people}
|
|
48
|
+
columns={6}
|
|
49
|
+
onEmojiSelected={jest.fn()}
|
|
50
|
+
placeholder="Search emojis"
|
|
51
|
+
showHistory={false}
|
|
52
|
+
showSearchBar
|
|
53
|
+
showSectionTitles
|
|
54
|
+
showTabs
|
|
55
|
+
theme="#007AFF"
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
expect(tree.toJSON()).toMatchSnapshot();
|
|
60
|
+
});
|
|
61
|
+
});
|