ferns-ui 1.3.0 → 1.5.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.
- package/dist/Common.d.ts +7 -0
- package/dist/Common.js.map +1 -1
- package/dist/DateTimeActionSheet.js +4 -4
- package/dist/DateTimeActionSheet.js.map +1 -1
- package/dist/DateTimeField.d.ts +1 -1
- package/dist/DateTimeField.js +329 -135
- package/dist/DateTimeField.js.map +1 -1
- package/dist/DateUtilities.d.ts +4 -0
- package/dist/DateUtilities.js +32 -0
- package/dist/DateUtilities.js.map +1 -1
- package/dist/Field.js.map +1 -1
- package/dist/SegmentedControl.d.ts +1 -1
- package/dist/SegmentedControl.js +61 -26
- package/dist/SegmentedControl.js.map +1 -1
- package/dist/TimezonePicker.d.ts +10 -2
- package/dist/TimezonePicker.js +22 -19
- package/dist/TimezonePicker.js.map +1 -1
- package/package.json +1 -1
- package/src/Common.ts +8 -0
- package/src/DateTimeActionSheet.tsx +5 -9
- package/src/DateTimeField.tsx +487 -148
- package/src/DateUtilities.tsx +35 -0
- package/src/Field.tsx +1 -1
- package/src/SegmentedControl.tsx +95 -29
- package/src/TimezonePicker.tsx +36 -27
package/src/DateUtilities.tsx
CHANGED
|
@@ -333,3 +333,38 @@ export function getIsoDate(date: string | undefined): string | undefined {
|
|
|
333
333
|
}
|
|
334
334
|
return convertNullToUndefined(DateTime.fromISO(date).toUTC().toISO());
|
|
335
335
|
}
|
|
336
|
+
|
|
337
|
+
const usTimezoneOptions = [
|
|
338
|
+
{label: "Eastern", value: "America/New_York"},
|
|
339
|
+
{label: "Central", value: "America/Chicago"},
|
|
340
|
+
{label: "Mountain", value: "America/Denver"},
|
|
341
|
+
{label: "Pacific", value: "America/Los_Angeles"},
|
|
342
|
+
{label: "Alaska", value: "America/Anchorage"},
|
|
343
|
+
{label: "Hawaii", value: "Pacific/Honolulu"},
|
|
344
|
+
{label: "Arizona", value: "America/Phoenix"},
|
|
345
|
+
];
|
|
346
|
+
|
|
347
|
+
export function getTimezoneOptions(location: "USA" | "Worldwide", shortTimezone = false) {
|
|
348
|
+
let timezones: [string, string][];
|
|
349
|
+
if (location === "USA") {
|
|
350
|
+
timezones = usTimezoneOptions.map((tz) => [tz.label, tz.value]);
|
|
351
|
+
} else {
|
|
352
|
+
timezones = (Intl as any).supportedValuesOf("timeZone").map((tz: any) => {
|
|
353
|
+
return [tz, tz];
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
return timezones.map(([name, tz]) => {
|
|
357
|
+
const dateTime = DateTime.now().setZone(tz);
|
|
358
|
+
let tzAbbr = dateTime.toFormat("ZZZZ"); // Gets timezone abbreviation like "EST", "CST", etc.
|
|
359
|
+
|
|
360
|
+
// Special case for Arizona which returns MST during standard time
|
|
361
|
+
if (tz === "America/Phoenix") {
|
|
362
|
+
tzAbbr = "AZ";
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
label: shortTimezone ? tzAbbr : name,
|
|
367
|
+
value: tz,
|
|
368
|
+
};
|
|
369
|
+
});
|
|
370
|
+
}
|
package/src/Field.tsx
CHANGED
|
@@ -50,7 +50,7 @@ export const Field: FC<FieldProps> = ({type, ...rest}) => {
|
|
|
50
50
|
} else if (type && ["date", "time", "datetime"].includes(type)) {
|
|
51
51
|
return (
|
|
52
52
|
<DateTimeField
|
|
53
|
-
{...(rest as DateTimeFieldProps)}
|
|
53
|
+
{...(rest as DateTimeFieldProps as any)}
|
|
54
54
|
type={type as "date" | "time" | "datetime"}
|
|
55
55
|
/>
|
|
56
56
|
);
|
package/src/SegmentedControl.tsx
CHANGED
|
@@ -1,58 +1,124 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, {useCallback, useState} from "react";
|
|
2
2
|
import {Pressable, View} from "react-native";
|
|
3
3
|
|
|
4
|
+
import {Badge} from "./Badge";
|
|
4
5
|
import {SegmentedControlProps} from "./Common";
|
|
5
6
|
import {Heading} from "./Heading";
|
|
7
|
+
import {Icon} from "./Icon";
|
|
6
8
|
import {useTheme} from "./Theme";
|
|
7
9
|
|
|
8
|
-
export const SegmentedControl = ({
|
|
10
|
+
export const SegmentedControl: React.FC<SegmentedControlProps> = ({
|
|
9
11
|
items,
|
|
10
12
|
onChange = () => {},
|
|
11
13
|
size = "md",
|
|
12
14
|
selectedIndex,
|
|
13
|
-
|
|
15
|
+
maxItems,
|
|
16
|
+
badges = [],
|
|
17
|
+
}) => {
|
|
14
18
|
const height = size === "md" ? 36 : 44;
|
|
15
19
|
const {theme} = useTheme();
|
|
20
|
+
const [startIndex, setStartIndex] = useState(0);
|
|
21
|
+
|
|
22
|
+
const handlePrevious = useCallback(() => {
|
|
23
|
+
setStartIndex((prev) => Math.max(0, prev - (maxItems ?? 4)));
|
|
24
|
+
}, []);
|
|
25
|
+
|
|
26
|
+
const handleNext = useCallback(() => {
|
|
27
|
+
setStartIndex((prev) => Math.min(items.length - (maxItems ?? items.length), prev + (maxItems ?? 4)));
|
|
28
|
+
}, [items.length, maxItems]);
|
|
29
|
+
|
|
30
|
+
const visibleItems = maxItems ? items.slice(startIndex, startIndex + maxItems) : items;
|
|
31
|
+
const visibleBadges = maxItems ? badges.slice(startIndex, startIndex + maxItems) : badges;
|
|
32
|
+
const canScrollLeft = startIndex > 0;
|
|
33
|
+
const canScrollRight = maxItems ? startIndex + maxItems < items.length : false;
|
|
34
|
+
const shouldShowScrollButtons = maxItems ? maxItems < items.length : false;
|
|
35
|
+
|
|
16
36
|
return (
|
|
17
37
|
<View
|
|
18
38
|
style={{
|
|
19
39
|
display: "flex",
|
|
20
|
-
flexGrow: 1,
|
|
21
40
|
flexDirection: "row",
|
|
22
|
-
flexShrink: 1,
|
|
23
41
|
alignItems: "center",
|
|
24
|
-
gap:
|
|
25
|
-
height,
|
|
26
|
-
maxHeight: height,
|
|
27
|
-
borderRadius: theme.primitives.radius3xl,
|
|
28
|
-
borderColor: theme.primitives.neutral300,
|
|
29
|
-
borderWidth: 3,
|
|
30
|
-
backgroundColor: theme.primitives.neutral300,
|
|
42
|
+
gap: 8,
|
|
31
43
|
}}
|
|
32
44
|
>
|
|
33
|
-
{
|
|
34
|
-
<Pressable
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
{Boolean(shouldShowScrollButtons) && (
|
|
46
|
+
<Pressable disabled={!canScrollLeft} onPress={handlePrevious}>
|
|
47
|
+
<Icon
|
|
48
|
+
color={canScrollLeft ? "linkLight" : "extraLight"}
|
|
49
|
+
iconName="chevron-left"
|
|
50
|
+
size="lg"
|
|
51
|
+
/>
|
|
52
|
+
</Pressable>
|
|
53
|
+
)}
|
|
54
|
+
<View
|
|
55
|
+
style={{
|
|
56
|
+
display: "flex",
|
|
57
|
+
flexGrow: 1,
|
|
58
|
+
flexDirection: "row",
|
|
59
|
+
flexShrink: 1,
|
|
60
|
+
alignItems: "center",
|
|
61
|
+
height,
|
|
62
|
+
maxHeight: height,
|
|
63
|
+
backgroundColor: theme.primitives.neutral300,
|
|
64
|
+
overflow: "hidden",
|
|
65
|
+
borderRadius: theme.primitives.radius3xl,
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
<View
|
|
37
69
|
style={{
|
|
38
70
|
display: "flex",
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
alignItems: "center",
|
|
42
|
-
height: "100%",
|
|
43
|
-
flexBasis: 0,
|
|
44
|
-
gap: 12,
|
|
71
|
+
flexDirection: "row",
|
|
72
|
+
gap: 4,
|
|
45
73
|
flexGrow: 1,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
backgroundColor: index === selectedIndex ? theme.surface.base : undefined,
|
|
49
|
-
overflow: "hidden",
|
|
74
|
+
paddingHorizontal: 4,
|
|
75
|
+
height: height - 4,
|
|
50
76
|
}}
|
|
51
|
-
onPress={() => onChange(index)}
|
|
52
77
|
>
|
|
53
|
-
|
|
78
|
+
{visibleItems.map((item, index) => {
|
|
79
|
+
const actualIndex = startIndex + index;
|
|
80
|
+
return (
|
|
81
|
+
<Pressable
|
|
82
|
+
key={actualIndex}
|
|
83
|
+
aria-role="button"
|
|
84
|
+
style={{
|
|
85
|
+
display: "flex",
|
|
86
|
+
paddingHorizontal: 2,
|
|
87
|
+
justifyContent: "center",
|
|
88
|
+
alignItems: "center",
|
|
89
|
+
height: "100%",
|
|
90
|
+
flexDirection: "row",
|
|
91
|
+
gap: 8,
|
|
92
|
+
flexGrow: 1,
|
|
93
|
+
flexBasis: 0,
|
|
94
|
+
borderRadius: theme.primitives.radius3xl,
|
|
95
|
+
backgroundColor: actualIndex === selectedIndex ? theme.surface.base : undefined,
|
|
96
|
+
overflow: "hidden",
|
|
97
|
+
}}
|
|
98
|
+
onPress={() => onChange(actualIndex)}
|
|
99
|
+
>
|
|
100
|
+
<Heading size="sm">{item}</Heading>
|
|
101
|
+
{visibleBadges[index] && (
|
|
102
|
+
<Badge
|
|
103
|
+
status={visibleBadges[index].status ?? "info"}
|
|
104
|
+
value={visibleBadges[index].count}
|
|
105
|
+
variant="numberOnly"
|
|
106
|
+
/>
|
|
107
|
+
)}
|
|
108
|
+
</Pressable>
|
|
109
|
+
);
|
|
110
|
+
})}
|
|
111
|
+
</View>
|
|
112
|
+
</View>
|
|
113
|
+
{Boolean(shouldShowScrollButtons) && (
|
|
114
|
+
<Pressable disabled={!canScrollRight} onPress={handleNext}>
|
|
115
|
+
<Icon
|
|
116
|
+
color={canScrollRight ? "linkLight" : "extraLight"}
|
|
117
|
+
iconName="chevron-right"
|
|
118
|
+
size="lg"
|
|
119
|
+
/>
|
|
54
120
|
</Pressable>
|
|
55
|
-
)
|
|
121
|
+
)}
|
|
56
122
|
</View>
|
|
57
123
|
);
|
|
58
124
|
};
|
package/src/TimezonePicker.tsx
CHANGED
|
@@ -1,37 +1,46 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import {SelectFieldPropsBase} from "./Common";
|
|
4
|
+
import {getTimezoneOptions} from "./DateUtilities";
|
|
5
5
|
import {SelectField} from "./SelectField";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
{label: "Hawaii", value: "Pacific/Honolulu"},
|
|
15
|
-
{label: "Arizona", value: "America/Phoenix"},
|
|
16
|
-
];
|
|
7
|
+
interface TimezonePickerProps extends Omit<SelectFieldPropsBase, "options"> {
|
|
8
|
+
timezone?: string;
|
|
9
|
+
onChange: (value: string) => void;
|
|
10
|
+
location?: "USA" | "Worldwide";
|
|
11
|
+
hideTitle?: boolean;
|
|
12
|
+
shortTimezone?: boolean;
|
|
13
|
+
}
|
|
17
14
|
|
|
18
|
-
export const TimezonePicker = ({
|
|
15
|
+
export const TimezonePicker: React.FC<TimezonePickerProps> = ({
|
|
19
16
|
timezone,
|
|
20
17
|
onChange,
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
location = "USA",
|
|
19
|
+
hideTitle = false,
|
|
20
|
+
shortTimezone = false,
|
|
21
|
+
...fieldProps
|
|
23
22
|
}: TimezonePickerProps): React.ReactElement => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
</Box>
|
|
35
|
-
);
|
|
23
|
+
// eslint-disable-next-line react/display-name
|
|
24
|
+
const tzOptions = React.useMemo(
|
|
25
|
+
() => getTimezoneOptions(location, shortTimezone),
|
|
26
|
+
[location, shortTimezone]
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// Check that value is in the list of options
|
|
30
|
+
const valueIndex = tzOptions.findIndex((t) => t.value === timezone);
|
|
31
|
+
if (valueIndex === -1) {
|
|
32
|
+
console.warn(`${timezone} is not a valid timezone`);
|
|
36
33
|
}
|
|
34
|
+
|
|
35
|
+
const title = hideTitle ? undefined : "Timezone";
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<SelectField
|
|
39
|
+
title={title}
|
|
40
|
+
{...fieldProps}
|
|
41
|
+
options={tzOptions}
|
|
42
|
+
value={timezone}
|
|
43
|
+
onChange={onChange}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
37
46
|
};
|