ferns-ui 0.22.4 → 0.23.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/Button.d.ts +2 -1
- package/dist/Button.js +2 -2
- package/dist/Button.js.map +1 -1
- package/dist/Common.d.ts +1 -14
- package/dist/Common.js.map +1 -1
- package/dist/FernsProvider.d.ts +4 -0
- package/dist/FernsProvider.js +7 -0
- package/dist/FernsProvider.js.map +1 -0
- package/dist/IconButton.d.ts +21 -3
- package/dist/IconButton.js +44 -31
- package/dist/IconButton.js.map +1 -1
- package/dist/InfoTooltipButton.d.ts +8 -0
- package/dist/InfoTooltipButton.js +6 -0
- package/dist/InfoTooltipButton.js.map +1 -0
- package/dist/Modal.js +17 -18
- package/dist/Modal.js.map +1 -1
- package/dist/SelectList.js +26 -4
- package/dist/SelectList.js.map +1 -1
- package/dist/TapToEdit.js +17 -0
- package/dist/TapToEdit.js.map +1 -1
- package/dist/Toast.d.ts +10 -0
- package/dist/Toast.js +40 -0
- package/dist/Toast.js.map +1 -0
- package/dist/Tooltip.d.ts +9 -0
- package/dist/Tooltip.js +170 -0
- package/dist/Tooltip.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/src/Button.tsx +3 -1
- package/src/Common.ts +2 -15
- package/src/FernsProvider.tsx +20 -0
- package/src/IconButton.tsx +131 -79
- package/src/InfoTooltipButton.tsx +23 -0
- package/src/Modal.tsx +34 -36
- package/src/SelectList.tsx +25 -3
- package/src/TapToEdit.tsx +21 -0
- package/src/Toast.tsx +87 -0
- package/src/Tooltip.tsx +259 -0
- package/src/index.tsx +4 -0
- package/README.md +0 -22
package/src/SelectList.tsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import {Platform} from "react-native";
|
|
2
3
|
|
|
3
4
|
import {FieldWithLabelsProps, StyleProp} from "./Common";
|
|
5
|
+
import {Icon} from "./Icon";
|
|
4
6
|
import RNPickerSelect from "./PickerSelect";
|
|
5
7
|
import {Unifier} from "./Unifier";
|
|
6
8
|
import {WithLabel} from "./WithLabel";
|
|
@@ -34,6 +36,12 @@ export function SelectList({
|
|
|
34
36
|
return (
|
|
35
37
|
<WithLabel {...withLabelProps}>
|
|
36
38
|
<RNPickerSelect
|
|
39
|
+
// Icon only needed for iOs, web and android use default icons
|
|
40
|
+
Icon={() => {
|
|
41
|
+
return Platform.OS === "ios" ? (
|
|
42
|
+
<Icon color="darkGray" name="angle-down" size="md" />
|
|
43
|
+
) : null;
|
|
44
|
+
}}
|
|
37
45
|
items={options}
|
|
38
46
|
placeholder={placeholder ? {label: placeholder, value: ""} : {}}
|
|
39
47
|
style={{
|
|
@@ -43,14 +51,28 @@ export function SelectList({
|
|
|
43
51
|
alignItems: style?.alignItems || "center",
|
|
44
52
|
minHeight: style?.minHeight || 50,
|
|
45
53
|
width: style?.width || "100%",
|
|
46
|
-
// Add padding so the border doesn't mess up layouts
|
|
47
|
-
paddingHorizontal: style?.paddingHorizontal || 6,
|
|
48
|
-
paddingVertical: style?.paddingVertical || 4,
|
|
49
54
|
borderColor: style?.borderColor || Unifier.theme.gray,
|
|
50
55
|
borderWidth: style?.borderWidth || 1,
|
|
51
56
|
borderRadius: style?.borderRadius || 5,
|
|
52
57
|
backgroundColor: style?.backgroundColor || Unifier.theme.white,
|
|
53
58
|
},
|
|
59
|
+
inputIOS: {
|
|
60
|
+
paddingVertical: 12,
|
|
61
|
+
paddingHorizontal: 10,
|
|
62
|
+
paddingRight: 30,
|
|
63
|
+
},
|
|
64
|
+
iconContainer: {
|
|
65
|
+
top: 13,
|
|
66
|
+
right: 10,
|
|
67
|
+
bottom: 12,
|
|
68
|
+
paddingLeft: 40,
|
|
69
|
+
},
|
|
70
|
+
inputWeb: {
|
|
71
|
+
// Add padding so the border doesn't mess up layouts
|
|
72
|
+
paddingHorizontal: style?.paddingHorizontal || 6,
|
|
73
|
+
paddingVertical: style?.paddingVertical || 4,
|
|
74
|
+
borderRadius: style?.borderRadius || 5,
|
|
75
|
+
},
|
|
54
76
|
}}
|
|
55
77
|
value={value}
|
|
56
78
|
onValueChange={onChange}
|
package/src/TapToEdit.tsx
CHANGED
|
@@ -110,6 +110,27 @@ export const TapToEdit = ({
|
|
|
110
110
|
} else if (fieldProps?.type === "multiselect") {
|
|
111
111
|
// ???
|
|
112
112
|
displayValue = value.join(", ");
|
|
113
|
+
} else if (fieldProps?.type === "address") {
|
|
114
|
+
let city = "";
|
|
115
|
+
if (value?.city) {
|
|
116
|
+
city = value?.state || value.zipcode ? `${value.city}, ` : `${value.city}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let state = "";
|
|
120
|
+
if (value?.state) {
|
|
121
|
+
state = value?.zipcode ? `${value.state} ` : `${value.state}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const zip = value?.zipcode;
|
|
125
|
+
|
|
126
|
+
const addressLineOne = value?.address1 ?? "";
|
|
127
|
+
const addressLineTwo = value?.address2 ?? "";
|
|
128
|
+
const addressLineThree = `${city}${state}${zip}`;
|
|
129
|
+
|
|
130
|
+
// Only add new lines if lines before and after are not empty to avoid awkward whitespace
|
|
131
|
+
displayValue = `${addressLineOne}${
|
|
132
|
+
addressLineOne && (addressLineTwo || addressLineThree) ? `\n` : ""
|
|
133
|
+
}${addressLineTwo}${addressLineTwo && addressLineThree ? `\n` : ""}${addressLineThree}`;
|
|
113
134
|
}
|
|
114
135
|
}
|
|
115
136
|
return (
|
package/src/Toast.tsx
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
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 {Text} from "./Text";
|
|
10
|
+
|
|
11
|
+
export function useToast(): any {
|
|
12
|
+
const toast = useRNToast();
|
|
13
|
+
return {
|
|
14
|
+
show: (
|
|
15
|
+
text: string,
|
|
16
|
+
options?: {
|
|
17
|
+
variant?: "default" | "warning" | "error";
|
|
18
|
+
buttonText?: string;
|
|
19
|
+
buttonOnClick: () => void | Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
): string => {
|
|
22
|
+
return toast.show(text, {
|
|
23
|
+
data: options,
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
hide: (id: string) => toast.hide(id),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function Toast({
|
|
31
|
+
message,
|
|
32
|
+
data,
|
|
33
|
+
}: {
|
|
34
|
+
message: string;
|
|
35
|
+
data: {
|
|
36
|
+
variant?: "default" | "warning" | "error";
|
|
37
|
+
buttonText?: string;
|
|
38
|
+
buttonOnClick?: () => void | Promise<void>;
|
|
39
|
+
};
|
|
40
|
+
}): React.ReactElement {
|
|
41
|
+
// margin 8 on either side, times the standard 4px we multiply by.
|
|
42
|
+
const width = Math.min(Dimensions.get("window").width - 16 * 4, 712);
|
|
43
|
+
const {variant, buttonText, buttonOnClick} = data ?? {};
|
|
44
|
+
let color: AllColors = "darkGray";
|
|
45
|
+
if (variant === "warning") {
|
|
46
|
+
color = "orange";
|
|
47
|
+
} else if (variant === "error") {
|
|
48
|
+
color = "red";
|
|
49
|
+
}
|
|
50
|
+
return (
|
|
51
|
+
<Box
|
|
52
|
+
alignItems="center"
|
|
53
|
+
color={color}
|
|
54
|
+
direction="row"
|
|
55
|
+
flex="shrink"
|
|
56
|
+
marginBottom={4}
|
|
57
|
+
marginLeft={8}
|
|
58
|
+
marginRight={8}
|
|
59
|
+
maxWidth={width}
|
|
60
|
+
padding={2}
|
|
61
|
+
paddingX={4}
|
|
62
|
+
paddingY={3}
|
|
63
|
+
rounding={4}
|
|
64
|
+
>
|
|
65
|
+
{Boolean(variant === "error") && (
|
|
66
|
+
<Box marginRight={4}>
|
|
67
|
+
<Icon color="white" name="exclamation-circle" size="lg" />
|
|
68
|
+
</Box>
|
|
69
|
+
)}
|
|
70
|
+
{Boolean(variant === "warning") && (
|
|
71
|
+
<Box marginRight={4}>
|
|
72
|
+
<Icon color="white" name="exclamation-triangle" size="lg" />
|
|
73
|
+
</Box>
|
|
74
|
+
)}
|
|
75
|
+
<Box alignItems="center" direction="column" flex="shrink" justifyContent="center">
|
|
76
|
+
<Text color="white" size="lg" weight="bold">
|
|
77
|
+
{message}
|
|
78
|
+
</Text>
|
|
79
|
+
</Box>
|
|
80
|
+
{Boolean(buttonOnClick && buttonText) && (
|
|
81
|
+
<Box alignItems="center" justifyContent="center" marginLeft={4}>
|
|
82
|
+
<Button color="lightGray" shape="pill" text={buttonText!} onClick={buttonOnClick} />
|
|
83
|
+
</Box>
|
|
84
|
+
)}
|
|
85
|
+
</Box>
|
|
86
|
+
);
|
|
87
|
+
}
|
package/src/Tooltip.tsx
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {forwardRef} from "react";
|
|
3
|
+
import {
|
|
4
|
+
Dimensions,
|
|
5
|
+
LayoutChangeEvent,
|
|
6
|
+
LayoutRectangle,
|
|
7
|
+
Platform,
|
|
8
|
+
Pressable,
|
|
9
|
+
View,
|
|
10
|
+
} from "react-native";
|
|
11
|
+
import {Portal} from "react-native-portalize";
|
|
12
|
+
|
|
13
|
+
import {TooltipDirection} from "./Common";
|
|
14
|
+
import {Text} from "./Text";
|
|
15
|
+
import {Unifier} from "./Unifier";
|
|
16
|
+
|
|
17
|
+
const TOOLTIP_OFFSET = 8;
|
|
18
|
+
// How many pixels to leave between the tooltip and the edge of the screen
|
|
19
|
+
const TOOLTIP_OVERFLOW_PADDING = 20;
|
|
20
|
+
|
|
21
|
+
type ChildrenMeasurement = {
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
pageX: number;
|
|
25
|
+
pageY: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type Measurement = {
|
|
29
|
+
children: ChildrenMeasurement;
|
|
30
|
+
tooltip: LayoutRectangle;
|
|
31
|
+
measured: boolean;
|
|
32
|
+
idealDirection?: TooltipDirection;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const overflowLeft = (x: number): boolean => {
|
|
36
|
+
return x < TOOLTIP_OVERFLOW_PADDING;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const overflowRight = (x: number): boolean => {
|
|
40
|
+
const {width: layoutWidth} = Dimensions.get("window");
|
|
41
|
+
return x + TOOLTIP_OVERFLOW_PADDING > layoutWidth;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const getTooltipPosition = ({
|
|
45
|
+
children,
|
|
46
|
+
tooltip,
|
|
47
|
+
measured,
|
|
48
|
+
idealDirection,
|
|
49
|
+
}: Measurement): {} | {left: number; top: number} => {
|
|
50
|
+
if (!measured) {
|
|
51
|
+
console.debug("No measurements for child yet, cannot show tooltip.");
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const {
|
|
56
|
+
pageY: childrenY,
|
|
57
|
+
height: childrenHeight,
|
|
58
|
+
pageX: childrenX,
|
|
59
|
+
width: childrenWidth,
|
|
60
|
+
}: ChildrenMeasurement = children;
|
|
61
|
+
const {width: tooltipWidth, height: tooltipHeight} = tooltip;
|
|
62
|
+
const horizontalCenter = childrenX + childrenWidth / 2;
|
|
63
|
+
const right = childrenX + childrenWidth + TOOLTIP_OFFSET;
|
|
64
|
+
const left = childrenX - tooltipWidth - TOOLTIP_OFFSET;
|
|
65
|
+
|
|
66
|
+
const top = childrenY - tooltipHeight - TOOLTIP_OFFSET;
|
|
67
|
+
const bottom = childrenY + childrenHeight + TOOLTIP_OFFSET;
|
|
68
|
+
const verticalCenter = top + childrenHeight + TOOLTIP_OFFSET;
|
|
69
|
+
|
|
70
|
+
// Top is overflowed if it would go off either side or the top of the screen.
|
|
71
|
+
const overflowTop = top < TOOLTIP_OVERFLOW_PADDING;
|
|
72
|
+
|
|
73
|
+
// Bottom is overflowed if it would go off either side or the bottom of the screen.
|
|
74
|
+
const overflowBottom =
|
|
75
|
+
bottom + tooltipHeight + TOOLTIP_OVERFLOW_PADDING > Dimensions.get("window").height;
|
|
76
|
+
|
|
77
|
+
// If it would overflow to the right, try to put it above, if not, put it on the left.
|
|
78
|
+
// If it would overflow to the left, try to put it above, if not, put it to the right.
|
|
79
|
+
|
|
80
|
+
// Happy path:
|
|
81
|
+
if (idealDirection === "left" && !overflowLeft(left)) {
|
|
82
|
+
return {left, top: verticalCenter};
|
|
83
|
+
} else if (idealDirection === "right" && !overflowRight(right + tooltipWidth)) {
|
|
84
|
+
return {left: right, top: verticalCenter};
|
|
85
|
+
} else if (
|
|
86
|
+
idealDirection === "bottom" &&
|
|
87
|
+
!overflowBottom &&
|
|
88
|
+
!overflowLeft(horizontalCenter - tooltipWidth) &&
|
|
89
|
+
!overflowRight(horizontalCenter + tooltipWidth)
|
|
90
|
+
) {
|
|
91
|
+
return {left: horizontalCenter - tooltipWidth / 2, top: bottom};
|
|
92
|
+
} else {
|
|
93
|
+
// At this point, we're either trying to place it above or below, and force it into the viewport.
|
|
94
|
+
|
|
95
|
+
let y = top;
|
|
96
|
+
if ((idealDirection === "bottom" && !overflowBottom) || overflowTop) {
|
|
97
|
+
y = bottom;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// If it fits in the viewport, center it above the child.
|
|
101
|
+
if (
|
|
102
|
+
!overflowLeft(horizontalCenter - tooltipWidth) &&
|
|
103
|
+
!overflowRight(horizontalCenter + tooltipWidth)
|
|
104
|
+
) {
|
|
105
|
+
return {left: horizontalCenter - tooltipWidth / 2, top: y};
|
|
106
|
+
}
|
|
107
|
+
// Failing that, if it fits on the left, put it there, otherwise to the right. We know it's smaller than the
|
|
108
|
+
// viewport.
|
|
109
|
+
else if (overflowLeft(horizontalCenter - tooltipWidth)) {
|
|
110
|
+
return {left: TOOLTIP_OVERFLOW_PADDING, top: y};
|
|
111
|
+
} else {
|
|
112
|
+
return {
|
|
113
|
+
left: Dimensions.get("window").width - TOOLTIP_OVERFLOW_PADDING - tooltipWidth,
|
|
114
|
+
top: y,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
interface TooltipProps {
|
|
121
|
+
children: React.ReactElement;
|
|
122
|
+
text: string;
|
|
123
|
+
idealDirection?: "top" | "bottom" | "left" | "right";
|
|
124
|
+
bgColor?: "white" | "lightGray" | "gray" | "darkGray";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// eslint-disable-next-line react/display-name
|
|
128
|
+
export const Tooltip = forwardRef((props: TooltipProps, _ref: any) => {
|
|
129
|
+
const {text, children, bgColor, idealDirection} = props;
|
|
130
|
+
const hoverDelay = 500;
|
|
131
|
+
const hoverEndDelay = 1500;
|
|
132
|
+
const [visible, setVisible] = React.useState(false);
|
|
133
|
+
|
|
134
|
+
const [measurement, setMeasurement] = React.useState({
|
|
135
|
+
children: {},
|
|
136
|
+
tooltip: {},
|
|
137
|
+
measured: false,
|
|
138
|
+
});
|
|
139
|
+
const showTooltipTimer = React.useRef<NodeJS.Timeout>();
|
|
140
|
+
const hideTooltipTimer = React.useRef<NodeJS.Timeout>();
|
|
141
|
+
const childrenWrapperRef = React.useRef() as React.MutableRefObject<View>;
|
|
142
|
+
|
|
143
|
+
const touched = React.useRef(false);
|
|
144
|
+
|
|
145
|
+
const isWeb = Platform.OS === "web";
|
|
146
|
+
|
|
147
|
+
React.useEffect(() => {
|
|
148
|
+
return () => {
|
|
149
|
+
if (showTooltipTimer.current) {
|
|
150
|
+
clearTimeout(showTooltipTimer.current);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (hideTooltipTimer.current) {
|
|
154
|
+
clearTimeout(hideTooltipTimer.current);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}, []);
|
|
158
|
+
|
|
159
|
+
const handleOnLayout = ({nativeEvent: {layout}}: LayoutChangeEvent) => {
|
|
160
|
+
childrenWrapperRef?.current?.measure((_x, _y, width, height, pageX, pageY) => {
|
|
161
|
+
setMeasurement({
|
|
162
|
+
children: {pageX, pageY, height, width},
|
|
163
|
+
tooltip: {...layout},
|
|
164
|
+
measured: true,
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const handleTouchStart = () => {
|
|
170
|
+
if (hideTooltipTimer.current) {
|
|
171
|
+
clearTimeout(hideTooltipTimer.current);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
showTooltipTimer.current = setTimeout(() => {
|
|
175
|
+
touched.current = true;
|
|
176
|
+
setVisible(true);
|
|
177
|
+
}, 100) as unknown as NodeJS.Timeout;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const handleHoverIn = () => {
|
|
181
|
+
if (hideTooltipTimer.current) {
|
|
182
|
+
clearTimeout(hideTooltipTimer.current);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
showTooltipTimer.current = setTimeout(() => {
|
|
186
|
+
touched.current = true;
|
|
187
|
+
setVisible(true);
|
|
188
|
+
}, hoverDelay) as unknown as NodeJS.Timeout;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const handleHoverOut = () => {
|
|
192
|
+
touched.current = false;
|
|
193
|
+
if (showTooltipTimer.current) {
|
|
194
|
+
clearTimeout(showTooltipTimer.current);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
hideTooltipTimer.current = setTimeout(() => {
|
|
198
|
+
setVisible(false);
|
|
199
|
+
setMeasurement({children: {}, tooltip: {}, measured: false});
|
|
200
|
+
}, hoverEndDelay) as unknown as NodeJS.Timeout;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const mobilePressProps = {
|
|
204
|
+
onPress: React.useCallback(() => {
|
|
205
|
+
if (touched.current) {
|
|
206
|
+
return null;
|
|
207
|
+
} else {
|
|
208
|
+
return children.props.onClick?.();
|
|
209
|
+
}
|
|
210
|
+
}, [children.props]),
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const webPressProps = {
|
|
214
|
+
onHoverIn: () => {
|
|
215
|
+
handleHoverIn();
|
|
216
|
+
children.props.onHoverIn?.();
|
|
217
|
+
},
|
|
218
|
+
onHoverOut: () => {
|
|
219
|
+
handleHoverOut();
|
|
220
|
+
children.props.onHoverOut?.();
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<>
|
|
226
|
+
{visible && (
|
|
227
|
+
<Portal>
|
|
228
|
+
<Pressable
|
|
229
|
+
style={{
|
|
230
|
+
alignSelf: "flex-start",
|
|
231
|
+
justifyContent: "center",
|
|
232
|
+
paddingHorizontal: 16,
|
|
233
|
+
backgroundColor: Unifier.theme[bgColor ?? "darkGray"],
|
|
234
|
+
borderRadius: 16,
|
|
235
|
+
paddingVertical: 8,
|
|
236
|
+
display: "flex",
|
|
237
|
+
flexShrink: 1,
|
|
238
|
+
maxWidth: Math.max(Dimensions.get("window").width - 32, 300),
|
|
239
|
+
...getTooltipPosition({...(measurement as Measurement), idealDirection}),
|
|
240
|
+
...(measurement.measured ? {opacity: 1} : {opacity: 0}),
|
|
241
|
+
}}
|
|
242
|
+
testID="tooltip-container"
|
|
243
|
+
onLayout={handleOnLayout}
|
|
244
|
+
onPress={() => {
|
|
245
|
+
setVisible(false);
|
|
246
|
+
}}
|
|
247
|
+
>
|
|
248
|
+
<Text color="white">{text}</Text>
|
|
249
|
+
</Pressable>
|
|
250
|
+
</Portal>
|
|
251
|
+
)}
|
|
252
|
+
<Pressable onTouchStart={handleTouchStart} {...(isWeb ? webPressProps : mobilePressProps)}>
|
|
253
|
+
{React.cloneElement(children, {
|
|
254
|
+
ref: childrenWrapperRef,
|
|
255
|
+
})}
|
|
256
|
+
</Pressable>
|
|
257
|
+
</>
|
|
258
|
+
);
|
|
259
|
+
});
|
package/src/index.tsx
CHANGED
|
@@ -13,6 +13,7 @@ export * from "./CheckBox";
|
|
|
13
13
|
export * from "./DateTimeActionSheet";
|
|
14
14
|
export * from "./ErrorBoundary";
|
|
15
15
|
export * from "./ErrorPage";
|
|
16
|
+
export * from "./FernsProvider";
|
|
16
17
|
export * from "./FlatList";
|
|
17
18
|
export * from "./Field";
|
|
18
19
|
export * from "./Form";
|
|
@@ -22,6 +23,7 @@ export * from "./Icon";
|
|
|
22
23
|
export * from "./IconButton";
|
|
23
24
|
export * from "./Image";
|
|
24
25
|
export * from "./ImageBackground";
|
|
26
|
+
export * from "./InfoTooltipButton";
|
|
25
27
|
// export * from "./Layout";
|
|
26
28
|
// export * from "./Drawer";
|
|
27
29
|
export * from "./Link";
|
|
@@ -41,6 +43,8 @@ export * from "./TapToEdit";
|
|
|
41
43
|
export * from "./Text";
|
|
42
44
|
export * from "./TextArea";
|
|
43
45
|
export * from "./TextField";
|
|
46
|
+
export * from "./Tooltip";
|
|
47
|
+
export * from "./Toast";
|
|
44
48
|
export * from "./UnifiedScreens";
|
|
45
49
|
export * from "./Unifier";
|
|
46
50
|
export * from "./WithLabel";
|
package/README.md
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# @ferns/ui
|
|
2
|
-
|
|
3
|
-
This package provides an abstraction for React (web) and React-Native code,
|
|
4
|
-
so the code and components you write work the same on both platforms. It does this
|
|
5
|
-
by creating a higher level abstraction over both and providing components
|
|
6
|
-
that work the same on all supported platforms. For example, instead of using
|
|
7
|
-
`<div>` and `<View>`, you use `<Box>` which provides a flexbox-first layout system,
|
|
8
|
-
translated to `<div>` and `<View>`.
|
|
9
|
-
|
|
10
|
-
`@ferns/ui` also provides an abstraction for many of the features of native and web,
|
|
11
|
-
such as Camera, Permissions, etc. This way you can write apps that work similarly
|
|
12
|
-
on all 3 platforms.
|
|
13
|
-
|
|
14
|
-
`@ferns/ui` is Typescript first.
|
|
15
|
-
|
|
16
|
-
You can easily add your own components by creating a `Foo.tsx` and
|
|
17
|
-
`Foo.native.tsx` component. Most of the `@ferns/ui` components are implemented
|
|
18
|
-
this way, with some only having a single `Component.tsx` if the component is a
|
|
19
|
-
higher level component that already works the same on all platforms.
|
|
20
|
-
|
|
21
|
-
`@ferns/ui` supports theming, allowing you to change the colors and fonts of the
|
|
22
|
-
components.
|