ferns-ui 0.26.0 → 0.26.2
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/ActionSheet.d.ts +1 -1
- package/dist/ActionSheet.js +1 -2
- package/dist/ActionSheet.js.map +1 -1
- package/dist/CheckBox.d.ts +1 -4
- package/dist/CheckBox.js +33 -20
- package/dist/CheckBox.js.map +1 -1
- package/dist/Common.d.ts +44 -10
- package/dist/Common.js.map +1 -1
- package/dist/DateTimeField.android.d.ts +1 -1
- package/dist/DateTimeField.android.js +58 -16
- package/dist/DateTimeField.android.js.map +1 -1
- package/dist/DateTimeField.d.ts +2 -2
- package/dist/DateTimeField.ios.d.ts +1 -1
- package/dist/DateTimeField.ios.js +43 -12
- package/dist/DateTimeField.ios.js.map +1 -1
- package/dist/DateTimeField.js.map +1 -1
- package/dist/Field.js +2 -2
- package/dist/Field.js.map +1 -1
- package/dist/Mask.d.ts +2 -3
- package/dist/MediaQuery.d.ts +1 -0
- package/dist/MediaQuery.js +16 -0
- package/dist/MediaQuery.js.map +1 -1
- package/dist/Page.js.map +1 -1
- package/dist/SegmentedControl.js +6 -2
- package/dist/SegmentedControl.js.map +1 -1
- package/dist/SelectList.d.ts +1 -1
- package/dist/SelectList.js +9 -8
- package/dist/SelectList.js.map +1 -1
- package/dist/SplitPage.d.ts +3 -21
- package/dist/SplitPage.js +78 -23
- package/dist/SplitPage.js.map +1 -1
- package/dist/SplitPage.native.d.ts +3 -0
- package/dist/SplitPage.native.js +75 -0
- package/dist/SplitPage.native.js.map +1 -0
- package/dist/TapToEdit.d.ts +1 -0
- package/dist/TapToEdit.js +27 -17
- package/dist/TapToEdit.js.map +1 -1
- package/dist/TextField.js +11 -6
- package/dist/TextField.js.map +1 -1
- package/dist/Utilities.js +1 -2
- package/dist/Utilities.js.map +1 -1
- package/dist/WithLabel.d.ts +3 -3
- package/dist/WithLabel.js +3 -0
- package/dist/WithLabel.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/ActionSheet.tsx +2 -3
- package/src/CheckBox.tsx +85 -60
- package/src/Common.ts +49 -10
- package/src/DateTimeField.android.tsx +71 -26
- package/src/DateTimeField.ios.tsx +55 -13
- package/src/DateTimeField.tsx +2 -2
- package/src/Field.tsx +4 -4
- package/src/Mask.tsx +2 -2
- package/src/MediaQuery.ts +14 -0
- package/src/Page.tsx +0 -1
- package/src/SegmentedControl.tsx +3 -3
- package/src/SelectList.tsx +9 -2
- package/src/SplitPage.native.tsx +156 -0
- package/src/SplitPage.tsx +136 -47
- package/src/TapToEdit.tsx +31 -22
- package/src/TextField.tsx +26 -17
- package/src/Utilities.tsx +1 -3
- package/src/WithLabel.tsx +6 -3
- package/src/index.tsx +6 -5
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import DateTimePicker from "@react-native-community/datetimepicker";
|
|
2
2
|
import moment from "moment-timezone";
|
|
3
|
-
import React, {ReactElement} from "react";
|
|
3
|
+
import React, {ReactElement, useMemo, useState} from "react";
|
|
4
|
+
import {TextInput} from "react-native";
|
|
4
5
|
|
|
5
|
-
import {Box} from "./Box";
|
|
6
6
|
import {DateTimeFieldProps} from "./Common";
|
|
7
|
+
import {Unifier} from "./Unifier";
|
|
7
8
|
import {WithLabel} from "./WithLabel";
|
|
8
9
|
|
|
9
10
|
export const DateTimeField = ({
|
|
@@ -12,29 +13,70 @@ export const DateTimeField = ({
|
|
|
12
13
|
onChange,
|
|
13
14
|
errorMessage,
|
|
14
15
|
errorMessageColor,
|
|
16
|
+
dateFormat,
|
|
17
|
+
pickerType = "inline",
|
|
18
|
+
label,
|
|
15
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
|
+
|
|
16
36
|
return (
|
|
17
|
-
<WithLabel
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
<
|
|
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={moment(value).format(defaultFormat)}
|
|
59
|
+
onPressIn={() => {
|
|
60
|
+
setShowPicker(!showPicker);
|
|
61
|
+
}}
|
|
62
|
+
/>
|
|
63
|
+
|
|
64
|
+
{showPicker && (
|
|
25
65
|
<DateTimePicker
|
|
26
|
-
|
|
66
|
+
accentColor={Unifier.theme.primary}
|
|
67
|
+
display={pickerType}
|
|
27
68
|
mode={mode}
|
|
69
|
+
style={{alignSelf: "flex-start"}}
|
|
28
70
|
testID="dateTimePicker"
|
|
29
71
|
value={moment(value).toDate()}
|
|
30
72
|
onChange={(event: any, date: any) => {
|
|
31
73
|
if (!date) {
|
|
32
74
|
return;
|
|
33
75
|
}
|
|
34
|
-
onChange(
|
|
76
|
+
onChange(date);
|
|
35
77
|
}}
|
|
36
78
|
/>
|
|
37
|
-
|
|
79
|
+
)}
|
|
38
80
|
</WithLabel>
|
|
39
81
|
</WithLabel>
|
|
40
82
|
);
|
package/src/DateTimeField.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import DateTimePickerWeb from "react-datetime-picker";
|
|
|
4
4
|
import TimePicker from "react-time-picker";
|
|
5
5
|
|
|
6
6
|
import {Box} from "./Box";
|
|
7
|
-
import {DateTimeFieldProps} from "./Common";
|
|
7
|
+
import {DateTimeFieldProps, WithChildren} from "./Common";
|
|
8
8
|
import {WithLabel} from "./WithLabel";
|
|
9
9
|
|
|
10
10
|
export const DateTimeField = ({
|
|
@@ -13,7 +13,7 @@ export const DateTimeField = ({
|
|
|
13
13
|
onChange,
|
|
14
14
|
errorMessage,
|
|
15
15
|
errorMessageColor,
|
|
16
|
-
}: DateTimeFieldProps): ReactElement => {
|
|
16
|
+
}: WithChildren<DateTimeFieldProps>): ReactElement => {
|
|
17
17
|
return (
|
|
18
18
|
<WithLabel
|
|
19
19
|
label={errorMessage}
|
package/src/Field.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import React from "react";
|
|
|
2
2
|
|
|
3
3
|
import {Box} from "./Box";
|
|
4
4
|
import {CheckBox} from "./CheckBox";
|
|
5
|
-
import {AddressInterface, FieldWithLabelsProps, TextFieldType} from "./Common";
|
|
5
|
+
import {AddressInterface, FieldWithLabelsProps, ReactChildren, TextFieldType} from "./Common";
|
|
6
6
|
import {USSTATESLIST} from "./Constants";
|
|
7
7
|
import {CustomSelect} from "./CustomSelect";
|
|
8
8
|
import {FieldWithLabels} from "./FieldWithLabels";
|
|
@@ -66,11 +66,11 @@ export const Field = ({
|
|
|
66
66
|
onChange(switchValue);
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
-
const renderField = () => {
|
|
69
|
+
const renderField = (): ReactChildren => {
|
|
70
70
|
if (type === "select") {
|
|
71
71
|
if (!options) {
|
|
72
72
|
console.error("Field with type=select require options");
|
|
73
|
-
return
|
|
73
|
+
return undefined;
|
|
74
74
|
}
|
|
75
75
|
return (
|
|
76
76
|
<SelectList
|
|
@@ -85,7 +85,7 @@ export const Field = ({
|
|
|
85
85
|
} else if (type === "multiselect") {
|
|
86
86
|
if (!options) {
|
|
87
87
|
console.error("Field with type=multiselect require options");
|
|
88
|
-
return
|
|
88
|
+
return undefined;
|
|
89
89
|
}
|
|
90
90
|
return (
|
|
91
91
|
<Box width="100%">
|
package/src/Mask.tsx
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import {View} from "react-native";
|
|
3
3
|
|
|
4
|
-
import {MaskProps} from "./Common";
|
|
4
|
+
import {MaskProps, ReactChildren} from "./Common";
|
|
5
5
|
|
|
6
|
-
export function Mask(props: MaskProps):
|
|
6
|
+
export function Mask(props: MaskProps): ReactChildren {
|
|
7
7
|
if (props.shape === "rounded") {
|
|
8
8
|
return <View style={{overflow: "hidden", borderRadius: 12}}>{props.children}</View>;
|
|
9
9
|
} else if (props.shape === "circle") {
|
package/src/MediaQuery.ts
CHANGED
|
@@ -26,3 +26,17 @@ export function mediaQueryLargerThan(size: "xs" | "sm" | "md" | "lg"): boolean {
|
|
|
26
26
|
}
|
|
27
27
|
return false;
|
|
28
28
|
}
|
|
29
|
+
|
|
30
|
+
export function mediaQuerySmallerThan(size: "xs" | "sm" | "md" | "lg"): boolean {
|
|
31
|
+
const media = mediaQuery();
|
|
32
|
+
if (size === "lg") {
|
|
33
|
+
return true;
|
|
34
|
+
} else if (size === "md") {
|
|
35
|
+
return ["xs", "sm", "md"].includes(media);
|
|
36
|
+
} else if (size === "sm") {
|
|
37
|
+
return ["xs", "sm"].includes(media);
|
|
38
|
+
} else if (size === "xs") {
|
|
39
|
+
return ["xs"].includes(media);
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
package/src/Page.tsx
CHANGED
|
@@ -97,7 +97,6 @@ export class Page extends React.Component<PageProps, {}> {
|
|
|
97
97
|
padding={this.props.padding !== undefined ? this.props.padding : 2}
|
|
98
98
|
scroll={this.props.scroll === undefined ? true : this.props.scroll}
|
|
99
99
|
width="100%"
|
|
100
|
-
// color="ligh"
|
|
101
100
|
>
|
|
102
101
|
{this.renderHeader()}
|
|
103
102
|
{this.props.loading === true && (
|
package/src/SegmentedControl.tsx
CHANGED
|
@@ -27,7 +27,7 @@ export const SegmentedControl = ({
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
if (!multiselect && selectedItemIndexes?.length && selectedItemIndexes?.length > 1) {
|
|
30
|
-
console.warn("
|
|
30
|
+
console.warn("Multiple selections not allowed without multiselect flag");
|
|
31
31
|
return null;
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -49,7 +49,7 @@ export const SegmentedControl = ({
|
|
|
49
49
|
display="flex"
|
|
50
50
|
height={40}
|
|
51
51
|
justifyContent="between"
|
|
52
|
-
padding={1}
|
|
52
|
+
// padding={1}
|
|
53
53
|
rounding={3}
|
|
54
54
|
width="100%"
|
|
55
55
|
>
|
|
@@ -58,7 +58,7 @@ export const SegmentedControl = ({
|
|
|
58
58
|
key={index}
|
|
59
59
|
color={isTabActive(index)}
|
|
60
60
|
height="100%"
|
|
61
|
-
paddingX={2}
|
|
61
|
+
// paddingX={2}
|
|
62
62
|
rounding={3}
|
|
63
63
|
width={`${100 / items.length}%`}
|
|
64
64
|
>
|
package/src/SelectList.tsx
CHANGED
|
@@ -30,18 +30,25 @@ export function SelectList({
|
|
|
30
30
|
labelColor,
|
|
31
31
|
style,
|
|
32
32
|
placeholder,
|
|
33
|
+
disabled,
|
|
33
34
|
}: SelectListProps) {
|
|
34
35
|
const withLabelProps = {label, labelColor};
|
|
35
36
|
|
|
37
|
+
let backgroundColor = style?.backgroundColor || Unifier.theme.white;
|
|
38
|
+
if (disabled) {
|
|
39
|
+
backgroundColor = Unifier.theme.lightGray;
|
|
40
|
+
}
|
|
41
|
+
|
|
36
42
|
return (
|
|
37
43
|
<WithLabel {...withLabelProps}>
|
|
38
44
|
<RNPickerSelect
|
|
39
|
-
// Icon only needed for iOs, web and android use default icons
|
|
40
45
|
Icon={() => {
|
|
46
|
+
// Icon only needed for iOS, web and android use default icons
|
|
41
47
|
return Platform.OS === "ios" ? (
|
|
42
48
|
<Icon color="darkGray" name="angle-down" size="md" />
|
|
43
49
|
) : null;
|
|
44
50
|
}}
|
|
51
|
+
disabled={disabled}
|
|
45
52
|
items={options}
|
|
46
53
|
placeholder={placeholder ? {label: placeholder, value: ""} : {}}
|
|
47
54
|
style={{
|
|
@@ -54,7 +61,7 @@ export function SelectList({
|
|
|
54
61
|
borderColor: style?.borderColor || Unifier.theme.gray,
|
|
55
62
|
borderWidth: style?.borderWidth || 1,
|
|
56
63
|
borderRadius: style?.borderRadius || 5,
|
|
57
|
-
backgroundColor
|
|
64
|
+
backgroundColor,
|
|
58
65
|
},
|
|
59
66
|
inputIOS: {
|
|
60
67
|
paddingVertical: 12,
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// TODO: Update SplitPage native to have desktop UX for tablet sized screens
|
|
2
|
+
import React, {Children, useCallback, useEffect, useState} from "react";
|
|
3
|
+
import {Dimensions, ListRenderItemInfo, View} from "react-native";
|
|
4
|
+
import {SwiperFlatList} from "react-native-swiper-flatlist";
|
|
5
|
+
|
|
6
|
+
import {Box} from "./Box";
|
|
7
|
+
import {SplitPageProps} from "./Common";
|
|
8
|
+
import {FlatList} from "./FlatList";
|
|
9
|
+
import {IconButton} from "./IconButton";
|
|
10
|
+
import {Spinner} from "./Spinner";
|
|
11
|
+
import {Unifier} from "./Unifier";
|
|
12
|
+
|
|
13
|
+
export const SplitPage = ({
|
|
14
|
+
children,
|
|
15
|
+
loading = false,
|
|
16
|
+
color,
|
|
17
|
+
keyboardOffset,
|
|
18
|
+
renderListViewItem,
|
|
19
|
+
renderListViewHeader,
|
|
20
|
+
renderContent,
|
|
21
|
+
onSelectionChange = () => {},
|
|
22
|
+
listViewData,
|
|
23
|
+
listViewExtraData,
|
|
24
|
+
bottomNavBarHeight,
|
|
25
|
+
showItemList,
|
|
26
|
+
}: SplitPageProps) => {
|
|
27
|
+
const [selectedId, setSelectedId] = useState<number | undefined>(undefined);
|
|
28
|
+
|
|
29
|
+
const elementArray = Children.toArray(children);
|
|
30
|
+
const {width} = Dimensions.get("window");
|
|
31
|
+
|
|
32
|
+
const onItemSelect = useCallback(
|
|
33
|
+
(item: ListRenderItemInfo<any>) => {
|
|
34
|
+
setSelectedId(item.index);
|
|
35
|
+
onSelectionChange(item);
|
|
36
|
+
},
|
|
37
|
+
[onSelectionChange]
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const onItemDeselect = useCallback(() => {
|
|
41
|
+
setSelectedId(undefined);
|
|
42
|
+
onSelectionChange(undefined);
|
|
43
|
+
}, [onSelectionChange]);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (showItemList) {
|
|
47
|
+
onItemDeselect();
|
|
48
|
+
}
|
|
49
|
+
}, [showItemList, onItemDeselect]);
|
|
50
|
+
|
|
51
|
+
if (!children && !renderContent) {
|
|
52
|
+
console.warn("A child node is required");
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const renderItem = (itemInfo: ListRenderItemInfo<any>) => {
|
|
57
|
+
return (
|
|
58
|
+
<Box
|
|
59
|
+
onClick={() => {
|
|
60
|
+
Unifier.utils.haptic();
|
|
61
|
+
onItemSelect(itemInfo);
|
|
62
|
+
}}
|
|
63
|
+
>
|
|
64
|
+
{renderListViewItem(itemInfo)}
|
|
65
|
+
</Box>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const renderList = () => {
|
|
70
|
+
if (selectedId !== undefined) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<View
|
|
76
|
+
style={{
|
|
77
|
+
width: "100%",
|
|
78
|
+
maxWidth: "100%",
|
|
79
|
+
flexGrow: 1,
|
|
80
|
+
flexShrink: 0,
|
|
81
|
+
display: "flex",
|
|
82
|
+
flexDirection: "column",
|
|
83
|
+
paddingBottom: bottomNavBarHeight,
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
{renderListViewHeader && renderListViewHeader()}
|
|
87
|
+
<FlatList
|
|
88
|
+
data={listViewData}
|
|
89
|
+
extraData={listViewExtraData}
|
|
90
|
+
keyExtractor={(item) => item.id}
|
|
91
|
+
renderItem={renderItem}
|
|
92
|
+
/>
|
|
93
|
+
</View>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const renderListContent = () => {
|
|
98
|
+
if (selectedId === undefined) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
return (
|
|
102
|
+
<Box flex="grow" padding={2}>
|
|
103
|
+
<Box width="100%">
|
|
104
|
+
<IconButton
|
|
105
|
+
accessibilityLabel="close"
|
|
106
|
+
icon="times"
|
|
107
|
+
iconColor="darkGray"
|
|
108
|
+
onClick={() => onItemDeselect()}
|
|
109
|
+
/>
|
|
110
|
+
</Box>
|
|
111
|
+
{renderContent && renderContent(selectedId)}
|
|
112
|
+
</Box>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const renderChildrenContent = () => {
|
|
117
|
+
if (selectedId === undefined) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
return (
|
|
121
|
+
<SwiperFlatList
|
|
122
|
+
nestedScrollEnabled
|
|
123
|
+
renderAll
|
|
124
|
+
showPagination={elementArray.length > 1}
|
|
125
|
+
style={{width: "100%"}}
|
|
126
|
+
>
|
|
127
|
+
{elementArray.map((element, i) => {
|
|
128
|
+
return (
|
|
129
|
+
<View
|
|
130
|
+
key={i}
|
|
131
|
+
style={{width, height: elementArray.length > 1 ? "95%" : "100%", padding: 4}}
|
|
132
|
+
>
|
|
133
|
+
{element}
|
|
134
|
+
</View>
|
|
135
|
+
);
|
|
136
|
+
})}
|
|
137
|
+
</SwiperFlatList>
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const renderMainContent = renderContent ? renderListContent() : renderChildrenContent();
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<Box
|
|
145
|
+
avoidKeyboard
|
|
146
|
+
color={color || "lightGray"}
|
|
147
|
+
flex="grow"
|
|
148
|
+
height="100%"
|
|
149
|
+
keyboardOffset={keyboardOffset}
|
|
150
|
+
width="100%"
|
|
151
|
+
>
|
|
152
|
+
{loading === true && <Spinner color={Unifier.theme.darkGray as any} size="md" />}
|
|
153
|
+
{selectedId === undefined ? renderList() : renderMainContent}
|
|
154
|
+
</Box>
|
|
155
|
+
);
|
|
156
|
+
};
|
package/src/SplitPage.tsx
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import React, {Children,
|
|
2
|
-
import {ListRenderItemInfo, ScrollView, View} from "react-native";
|
|
1
|
+
import React, {Children, useCallback, useEffect, useState} from "react";
|
|
2
|
+
import {Dimensions, ListRenderItemInfo, ScrollView, View} from "react-native";
|
|
3
|
+
import {SwiperFlatList} from "react-native-swiper-flatlist";
|
|
3
4
|
|
|
4
5
|
import {Box} from "./Box";
|
|
5
|
-
import {
|
|
6
|
+
import {SplitPageProps} from "./Common";
|
|
6
7
|
import {FlatList} from "./FlatList";
|
|
7
8
|
import {IconButton} from "./IconButton";
|
|
8
9
|
import {mediaQueryLargerThan} from "./MediaQuery";
|
|
@@ -10,24 +11,6 @@ import {SegmentedControl} from "./SegmentedControl";
|
|
|
10
11
|
import {Spinner} from "./Spinner";
|
|
11
12
|
import {Unifier} from "./Unifier";
|
|
12
13
|
|
|
13
|
-
interface SplitPageProps {
|
|
14
|
-
children?: ReactChild | ReactChild[] | null;
|
|
15
|
-
tabs?: string[];
|
|
16
|
-
// TODO: figure out navigation
|
|
17
|
-
navigation?: any;
|
|
18
|
-
loading?: boolean;
|
|
19
|
-
color?: Color;
|
|
20
|
-
keyboardOffset?: number;
|
|
21
|
-
renderListViewItem: (itemInfo: ListRenderItemInfo<any>) => ReactElement | null;
|
|
22
|
-
renderListViewHeader?: () => ReactElement | null;
|
|
23
|
-
renderContent?: (index?: number) => ReactElement | ReactElement[] | null;
|
|
24
|
-
listViewData: any[];
|
|
25
|
-
listViewExtraData?: any;
|
|
26
|
-
listViewWidth?: number;
|
|
27
|
-
renderChild?: () => ReactChild;
|
|
28
|
-
selectLimit?: number;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
14
|
// A component for rendering a list on one side and a details view on the right for large screens,
|
|
32
15
|
// and a scrollable list where clicking an item takes you the details view.
|
|
33
16
|
export const SplitPage = ({
|
|
@@ -39,16 +22,41 @@ export const SplitPage = ({
|
|
|
39
22
|
renderListViewItem,
|
|
40
23
|
renderListViewHeader,
|
|
41
24
|
renderContent,
|
|
25
|
+
onSelectionChange = () => {},
|
|
42
26
|
listViewData,
|
|
43
27
|
listViewExtraData,
|
|
44
28
|
listViewWidth,
|
|
29
|
+
bottomNavBarHeight,
|
|
30
|
+
showItemList,
|
|
45
31
|
selectLimit,
|
|
46
32
|
}: SplitPageProps) => {
|
|
47
33
|
const [selectedId, setSelectedId] = useState<number | undefined>(undefined);
|
|
48
34
|
const [activeTabs, setActiveTabs] = useState<number[]>(tabs.length > 2 ? [0, 1] : []);
|
|
35
|
+
const {width} = Dimensions.get("window");
|
|
36
|
+
|
|
37
|
+
const isMobileDevice = !mediaQueryLargerThan("sm");
|
|
49
38
|
|
|
50
39
|
const elementArray = Children.toArray(children);
|
|
51
40
|
|
|
41
|
+
const onItemSelect = useCallback(
|
|
42
|
+
(item: ListRenderItemInfo<any>) => {
|
|
43
|
+
setSelectedId(item.index);
|
|
44
|
+
onSelectionChange(item);
|
|
45
|
+
},
|
|
46
|
+
[onSelectionChange]
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const onItemDeselect = useCallback(() => {
|
|
50
|
+
setSelectedId(undefined);
|
|
51
|
+
onSelectionChange(undefined);
|
|
52
|
+
}, [onSelectionChange]);
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (showItemList) {
|
|
56
|
+
onItemDeselect();
|
|
57
|
+
}
|
|
58
|
+
}, [showItemList, onItemDeselect]);
|
|
59
|
+
|
|
52
60
|
if (!children && !renderContent) {
|
|
53
61
|
console.warn("A child node is required");
|
|
54
62
|
return null;
|
|
@@ -63,7 +71,7 @@ export const SplitPage = ({
|
|
|
63
71
|
return (
|
|
64
72
|
<Box
|
|
65
73
|
onClick={() => {
|
|
66
|
-
|
|
74
|
+
onItemSelect(itemInfo);
|
|
67
75
|
}}
|
|
68
76
|
>
|
|
69
77
|
{renderListViewItem(itemInfo)}
|
|
@@ -72,19 +80,14 @@ export const SplitPage = ({
|
|
|
72
80
|
};
|
|
73
81
|
|
|
74
82
|
const renderList = () => {
|
|
75
|
-
if (!mediaQueryLargerThan("sm") && selectedId) {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
83
|
return (
|
|
79
84
|
<View
|
|
80
85
|
style={{
|
|
81
|
-
width:
|
|
82
|
-
maxWidth:
|
|
86
|
+
width: listViewWidth ?? 300,
|
|
87
|
+
maxWidth: listViewWidth ?? 300,
|
|
83
88
|
flexGrow: 1,
|
|
84
89
|
flexShrink: 0,
|
|
85
90
|
display: "flex",
|
|
86
|
-
paddingTop: "12px",
|
|
87
|
-
paddingBottom: "12px",
|
|
88
91
|
flexDirection: "column",
|
|
89
92
|
}}
|
|
90
93
|
>
|
|
@@ -102,16 +105,6 @@ export const SplitPage = ({
|
|
|
102
105
|
const renderListContent = () => {
|
|
103
106
|
return (
|
|
104
107
|
<Box flex="grow" padding={2}>
|
|
105
|
-
{!mediaQueryLargerThan("sm") && (
|
|
106
|
-
<Box width="100%">
|
|
107
|
-
<IconButton
|
|
108
|
-
accessibilityLabel="close"
|
|
109
|
-
icon="times"
|
|
110
|
-
iconColor="darkGray"
|
|
111
|
-
onClick={() => setSelectedId(undefined)}
|
|
112
|
-
/>
|
|
113
|
-
</Box>
|
|
114
|
-
)}
|
|
115
108
|
{renderContent && renderContent(selectedId)}
|
|
116
109
|
</Box>
|
|
117
110
|
);
|
|
@@ -128,7 +121,7 @@ export const SplitPage = ({
|
|
|
128
121
|
alignItems: "center",
|
|
129
122
|
}}
|
|
130
123
|
>
|
|
131
|
-
<Box
|
|
124
|
+
<Box marginBottom={4} paddingX={4} width="100%">
|
|
132
125
|
<SegmentedControl
|
|
133
126
|
items={tabs}
|
|
134
127
|
multiselect
|
|
@@ -143,10 +136,10 @@ export const SplitPage = ({
|
|
|
143
136
|
direction="row"
|
|
144
137
|
flex="grow"
|
|
145
138
|
height="100%"
|
|
146
|
-
paddingX={
|
|
139
|
+
paddingX={4}
|
|
147
140
|
width={activeTabs.length > 1 ? "100%" : "60%"}
|
|
148
141
|
>
|
|
149
|
-
{activeTabs.map((tabIndex) => {
|
|
142
|
+
{activeTabs.map((tabIndex, i) => {
|
|
150
143
|
return (
|
|
151
144
|
<ScrollView
|
|
152
145
|
key={tabIndex}
|
|
@@ -156,8 +149,9 @@ export const SplitPage = ({
|
|
|
156
149
|
style={{
|
|
157
150
|
flex: 1,
|
|
158
151
|
width: "60%",
|
|
159
|
-
padding: 3 * SPACING,
|
|
160
152
|
height: "100%",
|
|
153
|
+
paddingRight: i ? 0 : 16,
|
|
154
|
+
paddingLeft: i ? 16 : 0,
|
|
161
155
|
}}
|
|
162
156
|
>
|
|
163
157
|
{elementArray[tabIndex]}
|
|
@@ -180,7 +174,6 @@ export const SplitPage = ({
|
|
|
180
174
|
style={{
|
|
181
175
|
flex: 1,
|
|
182
176
|
width: "60%",
|
|
183
|
-
padding: 3 * SPACING,
|
|
184
177
|
height: "100%",
|
|
185
178
|
}}
|
|
186
179
|
>
|
|
@@ -193,21 +186,117 @@ export const SplitPage = ({
|
|
|
193
186
|
}
|
|
194
187
|
};
|
|
195
188
|
|
|
189
|
+
const renderMobileList = () => {
|
|
190
|
+
if (isMobileDevice && selectedId !== undefined) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<View
|
|
196
|
+
style={{
|
|
197
|
+
width: "100%",
|
|
198
|
+
maxWidth: "100%",
|
|
199
|
+
height: "100%",
|
|
200
|
+
flexGrow: 1,
|
|
201
|
+
flexShrink: 0,
|
|
202
|
+
display: "flex",
|
|
203
|
+
flexDirection: "column",
|
|
204
|
+
}}
|
|
205
|
+
>
|
|
206
|
+
{renderListViewHeader && renderListViewHeader()}
|
|
207
|
+
<FlatList
|
|
208
|
+
data={listViewData}
|
|
209
|
+
extraData={listViewExtraData}
|
|
210
|
+
keyExtractor={(item) => item.id}
|
|
211
|
+
nestedScrollEnabled
|
|
212
|
+
renderItem={renderItem}
|
|
213
|
+
/>
|
|
214
|
+
</View>
|
|
215
|
+
);
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const renderMobileListContent = () => {
|
|
219
|
+
if (isMobileDevice && selectedId === undefined) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<Box flex="grow" padding={2}>
|
|
225
|
+
{isMobileDevice && (
|
|
226
|
+
<Box width="100%">
|
|
227
|
+
<IconButton
|
|
228
|
+
accessibilityLabel="close"
|
|
229
|
+
icon="times"
|
|
230
|
+
iconColor="darkGray"
|
|
231
|
+
onClick={() => onItemDeselect()}
|
|
232
|
+
/>
|
|
233
|
+
</Box>
|
|
234
|
+
)}
|
|
235
|
+
{renderContent && renderContent(selectedId)}
|
|
236
|
+
</Box>
|
|
237
|
+
);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const renderMobileChildrenContent = () => {
|
|
241
|
+
if (selectedId === undefined) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
return (
|
|
245
|
+
<SwiperFlatList
|
|
246
|
+
nestedScrollEnabled
|
|
247
|
+
paginationStyle={{justifyContent: "center", width: "95%"}}
|
|
248
|
+
renderAll
|
|
249
|
+
showPagination
|
|
250
|
+
style={{width: "100%"}}
|
|
251
|
+
>
|
|
252
|
+
{elementArray.map((element, i) => {
|
|
253
|
+
return (
|
|
254
|
+
<View
|
|
255
|
+
key={i}
|
|
256
|
+
style={{
|
|
257
|
+
width: width - 8,
|
|
258
|
+
padding: 4,
|
|
259
|
+
height: elementArray.length > 1 ? "90vh" : "100vh",
|
|
260
|
+
paddingBottom: bottomNavBarHeight,
|
|
261
|
+
}}
|
|
262
|
+
>
|
|
263
|
+
{element}
|
|
264
|
+
</View>
|
|
265
|
+
);
|
|
266
|
+
})}
|
|
267
|
+
</SwiperFlatList>
|
|
268
|
+
);
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const renderSplitPage = () => {
|
|
272
|
+
return (
|
|
273
|
+
<>
|
|
274
|
+
{renderList()}
|
|
275
|
+
{renderContent ? renderListContent() : renderChildrenContent()}
|
|
276
|
+
</>
|
|
277
|
+
);
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const renderMobileSplitPage = () => {
|
|
281
|
+
const renderMainContent = renderContent
|
|
282
|
+
? renderMobileListContent()
|
|
283
|
+
: renderMobileChildrenContent();
|
|
284
|
+
return selectedId === undefined ? renderMobileList() : renderMainContent;
|
|
285
|
+
};
|
|
286
|
+
|
|
196
287
|
return (
|
|
197
288
|
<Box
|
|
198
289
|
avoidKeyboard
|
|
199
290
|
color={color || "lightGray"}
|
|
200
291
|
direction="row"
|
|
201
292
|
display="flex"
|
|
202
|
-
flex="grow"
|
|
203
293
|
height="100%"
|
|
204
294
|
keyboardOffset={keyboardOffset}
|
|
205
295
|
padding={2}
|
|
206
296
|
width="100%"
|
|
207
297
|
>
|
|
208
298
|
{loading === true && <Spinner color={Unifier.theme.darkGray as any} size="md" />}
|
|
209
|
-
{
|
|
210
|
-
{renderContent ? renderListContent() : renderChildrenContent()}
|
|
299
|
+
{Boolean(isMobileDevice) ? renderMobileSplitPage() : renderSplitPage()}
|
|
211
300
|
</Box>
|
|
212
301
|
);
|
|
213
302
|
};
|