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.
@@ -0,0 +1,170 @@
1
+ import * as React from "react";
2
+ import { forwardRef } from "react";
3
+ import { Dimensions, Platform, Pressable, } from "react-native";
4
+ import { Portal } from "react-native-portalize";
5
+ import { Text } from "./Text";
6
+ import { Unifier } from "./Unifier";
7
+ const TOOLTIP_OFFSET = 8;
8
+ // How many pixels to leave between the tooltip and the edge of the screen
9
+ const TOOLTIP_OVERFLOW_PADDING = 20;
10
+ const overflowLeft = (x) => {
11
+ return x < TOOLTIP_OVERFLOW_PADDING;
12
+ };
13
+ const overflowRight = (x) => {
14
+ const { width: layoutWidth } = Dimensions.get("window");
15
+ return x + TOOLTIP_OVERFLOW_PADDING > layoutWidth;
16
+ };
17
+ const getTooltipPosition = ({ children, tooltip, measured, idealDirection, }) => {
18
+ if (!measured) {
19
+ console.debug("No measurements for child yet, cannot show tooltip.");
20
+ return {};
21
+ }
22
+ const { pageY: childrenY, height: childrenHeight, pageX: childrenX, width: childrenWidth, } = children;
23
+ const { width: tooltipWidth, height: tooltipHeight } = tooltip;
24
+ const horizontalCenter = childrenX + childrenWidth / 2;
25
+ const right = childrenX + childrenWidth + TOOLTIP_OFFSET;
26
+ const left = childrenX - tooltipWidth - TOOLTIP_OFFSET;
27
+ const top = childrenY - tooltipHeight - TOOLTIP_OFFSET;
28
+ const bottom = childrenY + childrenHeight + TOOLTIP_OFFSET;
29
+ const verticalCenter = top + childrenHeight + TOOLTIP_OFFSET;
30
+ // Top is overflowed if it would go off either side or the top of the screen.
31
+ const overflowTop = top < TOOLTIP_OVERFLOW_PADDING;
32
+ // Bottom is overflowed if it would go off either side or the bottom of the screen.
33
+ const overflowBottom = bottom + tooltipHeight + TOOLTIP_OVERFLOW_PADDING > Dimensions.get("window").height;
34
+ // If it would overflow to the right, try to put it above, if not, put it on the left.
35
+ // If it would overflow to the left, try to put it above, if not, put it to the right.
36
+ // Happy path:
37
+ if (idealDirection === "left" && !overflowLeft(left)) {
38
+ return { left, top: verticalCenter };
39
+ }
40
+ else if (idealDirection === "right" && !overflowRight(right + tooltipWidth)) {
41
+ return { left: right, top: verticalCenter };
42
+ }
43
+ else if (idealDirection === "bottom" &&
44
+ !overflowBottom &&
45
+ !overflowLeft(horizontalCenter - tooltipWidth) &&
46
+ !overflowRight(horizontalCenter + tooltipWidth)) {
47
+ return { left: horizontalCenter - tooltipWidth / 2, top: bottom };
48
+ }
49
+ else {
50
+ // At this point, we're either trying to place it above or below, and force it into the viewport.
51
+ let y = top;
52
+ if ((idealDirection === "bottom" && !overflowBottom) || overflowTop) {
53
+ y = bottom;
54
+ }
55
+ // If it fits in the viewport, center it above the child.
56
+ if (!overflowLeft(horizontalCenter - tooltipWidth) &&
57
+ !overflowRight(horizontalCenter + tooltipWidth)) {
58
+ return { left: horizontalCenter - tooltipWidth / 2, top: y };
59
+ }
60
+ // Failing that, if it fits on the left, put it there, otherwise to the right. We know it's smaller than the
61
+ // viewport.
62
+ else if (overflowLeft(horizontalCenter - tooltipWidth)) {
63
+ return { left: TOOLTIP_OVERFLOW_PADDING, top: y };
64
+ }
65
+ else {
66
+ return {
67
+ left: Dimensions.get("window").width - TOOLTIP_OVERFLOW_PADDING - tooltipWidth,
68
+ top: y,
69
+ };
70
+ }
71
+ }
72
+ };
73
+ // eslint-disable-next-line react/display-name
74
+ export const Tooltip = forwardRef((props, _ref) => {
75
+ const { text, children, bgColor, idealDirection } = props;
76
+ const hoverDelay = 500;
77
+ const hoverEndDelay = 1500;
78
+ const [visible, setVisible] = React.useState(false);
79
+ const [measurement, setMeasurement] = React.useState({
80
+ children: {},
81
+ tooltip: {},
82
+ measured: false,
83
+ });
84
+ const showTooltipTimer = React.useRef();
85
+ const hideTooltipTimer = React.useRef();
86
+ const childrenWrapperRef = React.useRef();
87
+ const touched = React.useRef(false);
88
+ const isWeb = Platform.OS === "web";
89
+ React.useEffect(() => {
90
+ return () => {
91
+ if (showTooltipTimer.current) {
92
+ clearTimeout(showTooltipTimer.current);
93
+ }
94
+ if (hideTooltipTimer.current) {
95
+ clearTimeout(hideTooltipTimer.current);
96
+ }
97
+ };
98
+ }, []);
99
+ const handleOnLayout = ({ nativeEvent: { layout } }) => {
100
+ var _a;
101
+ (_a = childrenWrapperRef === null || childrenWrapperRef === void 0 ? void 0 : childrenWrapperRef.current) === null || _a === void 0 ? void 0 : _a.measure((_x, _y, width, height, pageX, pageY) => {
102
+ setMeasurement({
103
+ children: { pageX, pageY, height, width },
104
+ tooltip: Object.assign({}, layout),
105
+ measured: true,
106
+ });
107
+ });
108
+ };
109
+ const handleTouchStart = () => {
110
+ if (hideTooltipTimer.current) {
111
+ clearTimeout(hideTooltipTimer.current);
112
+ }
113
+ showTooltipTimer.current = setTimeout(() => {
114
+ touched.current = true;
115
+ setVisible(true);
116
+ }, 100);
117
+ };
118
+ const handleHoverIn = () => {
119
+ if (hideTooltipTimer.current) {
120
+ clearTimeout(hideTooltipTimer.current);
121
+ }
122
+ showTooltipTimer.current = setTimeout(() => {
123
+ touched.current = true;
124
+ setVisible(true);
125
+ }, hoverDelay);
126
+ };
127
+ const handleHoverOut = () => {
128
+ touched.current = false;
129
+ if (showTooltipTimer.current) {
130
+ clearTimeout(showTooltipTimer.current);
131
+ }
132
+ hideTooltipTimer.current = setTimeout(() => {
133
+ setVisible(false);
134
+ setMeasurement({ children: {}, tooltip: {}, measured: false });
135
+ }, hoverEndDelay);
136
+ };
137
+ const mobilePressProps = {
138
+ onPress: React.useCallback(() => {
139
+ var _a, _b;
140
+ if (touched.current) {
141
+ return null;
142
+ }
143
+ else {
144
+ return (_b = (_a = children.props).onClick) === null || _b === void 0 ? void 0 : _b.call(_a);
145
+ }
146
+ }, [children.props]),
147
+ };
148
+ const webPressProps = {
149
+ onHoverIn: () => {
150
+ var _a, _b;
151
+ handleHoverIn();
152
+ (_b = (_a = children.props).onHoverIn) === null || _b === void 0 ? void 0 : _b.call(_a);
153
+ },
154
+ onHoverOut: () => {
155
+ var _a, _b;
156
+ handleHoverOut();
157
+ (_b = (_a = children.props).onHoverOut) === null || _b === void 0 ? void 0 : _b.call(_a);
158
+ },
159
+ };
160
+ return (React.createElement(React.Fragment, null,
161
+ visible && (React.createElement(Portal, null,
162
+ React.createElement(Pressable, { style: Object.assign(Object.assign({ alignSelf: "flex-start", justifyContent: "center", paddingHorizontal: 16, backgroundColor: Unifier.theme[bgColor !== null && bgColor !== void 0 ? bgColor : "darkGray"], borderRadius: 16, paddingVertical: 8, display: "flex", flexShrink: 1, maxWidth: Math.max(Dimensions.get("window").width - 32, 300) }, getTooltipPosition(Object.assign(Object.assign({}, measurement), { idealDirection }))), (measurement.measured ? { opacity: 1 } : { opacity: 0 })), testID: "tooltip-container", onLayout: handleOnLayout, onPress: () => {
163
+ setVisible(false);
164
+ } },
165
+ React.createElement(Text, { color: "white" }, text)))),
166
+ React.createElement(Pressable, Object.assign({ onTouchStart: handleTouchStart }, (isWeb ? webPressProps : mobilePressProps)), React.cloneElement(children, {
167
+ ref: childrenWrapperRef,
168
+ }))));
169
+ });
170
+ //# sourceMappingURL=Tooltip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tooltip.js","sourceRoot":"","sources":["../src/Tooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAC,UAAU,EAAC,MAAM,OAAO,CAAC;AACjC,OAAO,EACL,UAAU,EAGV,QAAQ,EACR,SAAS,GAEV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAC,MAAM,EAAC,MAAM,wBAAwB,CAAC;AAG9C,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AAElC,MAAM,cAAc,GAAG,CAAC,CAAC;AACzB,0EAA0E;AAC1E,MAAM,wBAAwB,GAAG,EAAE,CAAC;AAgBpC,MAAM,YAAY,GAAG,CAAC,CAAS,EAAW,EAAE;IAC1C,OAAO,CAAC,GAAG,wBAAwB,CAAC;AACtC,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,CAAC,CAAS,EAAW,EAAE;IAC3C,MAAM,EAAC,KAAK,EAAE,WAAW,EAAC,GAAG,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACtD,OAAO,CAAC,GAAG,wBAAwB,GAAG,WAAW,CAAC;AACpD,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,CAAC,EAC1B,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,cAAc,GACF,EAAoC,EAAE;IAClD,IAAI,CAAC,QAAQ,EAAE;QACb,OAAO,CAAC,KAAK,CAAC,qDAAqD,CAAC,CAAC;QACrE,OAAO,EAAE,CAAC;KACX;IAED,MAAM,EACJ,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,aAAa,GACrB,GAAwB,QAAQ,CAAC;IAClC,MAAM,EAAC,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAC,GAAG,OAAO,CAAC;IAC7D,MAAM,gBAAgB,GAAG,SAAS,GAAG,aAAa,GAAG,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,SAAS,GAAG,aAAa,GAAG,cAAc,CAAC;IACzD,MAAM,IAAI,GAAG,SAAS,GAAG,YAAY,GAAG,cAAc,CAAC;IAEvD,MAAM,GAAG,GAAG,SAAS,GAAG,aAAa,GAAG,cAAc,CAAC;IACvD,MAAM,MAAM,GAAG,SAAS,GAAG,cAAc,GAAG,cAAc,CAAC;IAC3D,MAAM,cAAc,GAAG,GAAG,GAAG,cAAc,GAAG,cAAc,CAAC;IAE7D,6EAA6E;IAC7E,MAAM,WAAW,GAAG,GAAG,GAAG,wBAAwB,CAAC;IAEnD,mFAAmF;IACnF,MAAM,cAAc,GAClB,MAAM,GAAG,aAAa,GAAG,wBAAwB,GAAG,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC;IAEtF,sFAAsF;IACtF,sFAAsF;IAEtF,cAAc;IACd,IAAI,cAAc,KAAK,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE;QACpD,OAAO,EAAC,IAAI,EAAE,GAAG,EAAE,cAAc,EAAC,CAAC;KACpC;SAAM,IAAI,cAAc,KAAK,OAAO,IAAI,CAAC,aAAa,CAAC,KAAK,GAAG,YAAY,CAAC,EAAE;QAC7E,OAAO,EAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,cAAc,EAAC,CAAC;KAC3C;SAAM,IACL,cAAc,KAAK,QAAQ;QAC3B,CAAC,cAAc;QACf,CAAC,YAAY,CAAC,gBAAgB,GAAG,YAAY,CAAC;QAC9C,CAAC,aAAa,CAAC,gBAAgB,GAAG,YAAY,CAAC,EAC/C;QACA,OAAO,EAAC,IAAI,EAAE,gBAAgB,GAAG,YAAY,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,EAAC,CAAC;KACjE;SAAM;QACL,iGAAiG;QAEjG,IAAI,CAAC,GAAG,GAAG,CAAC;QACZ,IAAI,CAAC,cAAc,KAAK,QAAQ,IAAI,CAAC,cAAc,CAAC,IAAI,WAAW,EAAE;YACnE,CAAC,GAAG,MAAM,CAAC;SACZ;QAED,yDAAyD;QACzD,IACE,CAAC,YAAY,CAAC,gBAAgB,GAAG,YAAY,CAAC;YAC9C,CAAC,aAAa,CAAC,gBAAgB,GAAG,YAAY,CAAC,EAC/C;YACA,OAAO,EAAC,IAAI,EAAE,gBAAgB,GAAG,YAAY,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,EAAC,CAAC;SAC5D;QACD,4GAA4G;QAC5G,YAAY;aACP,IAAI,YAAY,CAAC,gBAAgB,GAAG,YAAY,CAAC,EAAE;YACtD,OAAO,EAAC,IAAI,EAAE,wBAAwB,EAAE,GAAG,EAAE,CAAC,EAAC,CAAC;SACjD;aAAM;YACL,OAAO;gBACL,IAAI,EAAE,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,KAAK,GAAG,wBAAwB,GAAG,YAAY;gBAC9E,GAAG,EAAE,CAAC;aACP,CAAC;SACH;KACF;AACH,CAAC,CAAC;AASF,8CAA8C;AAC9C,MAAM,CAAC,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,KAAmB,EAAE,IAAS,EAAE,EAAE;IACnE,MAAM,EAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,EAAC,GAAG,KAAK,CAAC;IACxD,MAAM,UAAU,GAAG,GAAG,CAAC;IACvB,MAAM,aAAa,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAEpD,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC;QACnD,QAAQ,EAAE,EAAE;QACZ,OAAO,EAAE,EAAE;QACX,QAAQ,EAAE,KAAK;KAChB,CAAC,CAAC;IACH,MAAM,gBAAgB,GAAG,KAAK,CAAC,MAAM,EAAkB,CAAC;IACxD,MAAM,gBAAgB,GAAG,KAAK,CAAC,MAAM,EAAkB,CAAC;IACxD,MAAM,kBAAkB,GAAG,KAAK,CAAC,MAAM,EAAkC,CAAC;IAE1E,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAEpC,MAAM,KAAK,GAAG,QAAQ,CAAC,EAAE,KAAK,KAAK,CAAC;IAEpC,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,OAAO,GAAG,EAAE;YACV,IAAI,gBAAgB,CAAC,OAAO,EAAE;gBAC5B,YAAY,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;aACxC;YAED,IAAI,gBAAgB,CAAC,OAAO,EAAE;gBAC5B,YAAY,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;aACxC;QACH,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,cAAc,GAAG,CAAC,EAAC,WAAW,EAAE,EAAC,MAAM,EAAC,EAAoB,EAAE,EAAE;;QACpE,MAAA,kBAAkB,aAAlB,kBAAkB,uBAAlB,kBAAkB,CAAE,OAAO,0CAAE,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YAC3E,cAAc,CAAC;gBACb,QAAQ,EAAE,EAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAC;gBACvC,OAAO,oBAAM,MAAM,CAAC;gBACpB,QAAQ,EAAE,IAAI;aACf,CAAC,CAAC;QACL,CAAC,EAAE;IACL,CAAC,CAAC;IAEF,MAAM,gBAAgB,GAAG,GAAG,EAAE;QAC5B,IAAI,gBAAgB,CAAC,OAAO,EAAE;YAC5B,YAAY,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;SACxC;QAED,gBAAgB,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YACzC,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;YACvB,UAAU,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC,EAAE,GAAG,CAA8B,CAAC;IACvC,CAAC,CAAC;IAEF,MAAM,aAAa,GAAG,GAAG,EAAE;QACzB,IAAI,gBAAgB,CAAC,OAAO,EAAE;YAC5B,YAAY,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;SACxC;QAED,gBAAgB,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YACzC,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;YACvB,UAAU,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC,EAAE,UAAU,CAA8B,CAAC;IAC9C,CAAC,CAAC;IAEF,MAAM,cAAc,GAAG,GAAG,EAAE;QAC1B,OAAO,CAAC,OAAO,GAAG,KAAK,CAAC;QACxB,IAAI,gBAAgB,CAAC,OAAO,EAAE;YAC5B,YAAY,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;SACxC;QAED,gBAAgB,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YACzC,UAAU,CAAC,KAAK,CAAC,CAAC;YAClB,cAAc,CAAC,EAAC,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAC,CAAC,CAAC;QAC/D,CAAC,EAAE,aAAa,CAA8B,CAAC;IACjD,CAAC,CAAC;IAEF,MAAM,gBAAgB,GAAG;QACvB,OAAO,EAAE,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE;;YAC9B,IAAI,OAAO,CAAC,OAAO,EAAE;gBACnB,OAAO,IAAI,CAAC;aACb;iBAAM;gBACL,aAAO,MAAA,QAAQ,CAAC,KAAK,EAAC,OAAO,mDAAK;aACnC;QACH,CAAC,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;KACrB,CAAC;IAEF,MAAM,aAAa,GAAG;QACpB,SAAS,EAAE,GAAG,EAAE;;YACd,aAAa,EAAE,CAAC;YAChB,MAAA,MAAA,QAAQ,CAAC,KAAK,EAAC,SAAS,mDAAK;QAC/B,CAAC;QACD,UAAU,EAAE,GAAG,EAAE;;YACf,cAAc,EAAE,CAAC;YACjB,MAAA,MAAA,QAAQ,CAAC,KAAK,EAAC,UAAU,mDAAK;QAChC,CAAC;KACF,CAAC;IAEF,OAAO,CACL;QACG,OAAO,IAAI,CACV,oBAAC,MAAM;YACL,oBAAC,SAAS,IACR,KAAK,gCACH,SAAS,EAAE,YAAY,EACvB,cAAc,EAAE,QAAQ,EACxB,iBAAiB,EAAE,EAAE,EACrB,eAAe,EAAE,OAAO,CAAC,KAAK,CAAC,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,UAAU,CAAC,EACrD,YAAY,EAAE,EAAE,EAChB,eAAe,EAAE,CAAC,EAClB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,CAAC,EACb,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,KAAK,GAAG,EAAE,EAAE,GAAG,CAAC,IACzD,kBAAkB,iCAAM,WAA2B,KAAE,cAAc,IAAE,GACrE,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAC,OAAO,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC,EAAC,OAAO,EAAE,CAAC,EAAC,CAAC,GAEzD,MAAM,EAAC,mBAAmB,EAC1B,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,GAAG,EAAE;oBACZ,UAAU,CAAC,KAAK,CAAC,CAAC;gBACpB,CAAC;gBAED,oBAAC,IAAI,IAAC,KAAK,EAAC,OAAO,IAAE,IAAI,CAAQ,CACvB,CACL,CACV;QACD,oBAAC,SAAS,kBAAC,YAAY,EAAE,gBAAgB,IAAM,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,gBAAgB,CAAC,GACtF,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE;YAC5B,GAAG,EAAE,kBAAkB;SACxB,CAAC,CACQ,CACX,CACJ,CAAC;AACJ,CAAC,CAAC,CAAC"}
package/dist/index.d.ts 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 "./Link";
26
28
  export * from "./Mask";
27
29
  export * from "./Meta";
@@ -38,6 +40,8 @@ export * from "./TapToEdit";
38
40
  export * from "./Text";
39
41
  export * from "./TextArea";
40
42
  export * from "./TextField";
43
+ export * from "./Tooltip";
44
+ export * from "./Toast";
41
45
  export * from "./UnifiedScreens";
42
46
  export * from "./Unifier";
43
47
  export * from "./WithLabel";
package/dist/index.js 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";
@@ -40,6 +42,8 @@ export * from "./TapToEdit";
40
42
  export * from "./Text";
41
43
  export * from "./TextArea";
42
44
  export * from "./TextField";
45
+ export * from "./Tooltip";
46
+ export * from "./Toast";
43
47
  export * from "./UnifiedScreens";
44
48
  export * from "./Unifier";
45
49
  export * from "./WithLabel";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,OAAO,CAAC;AACtB,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC;AACxB,cAAc,mBAAmB,CAAC;AAClC,4BAA4B;AAC5B,4BAA4B;AAC5B,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AAExB,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,2BAA2B;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,OAAO,CAAC;AACtB,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC;AACxB,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,4BAA4B;AAC5B,4BAA4B;AAC5B,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AAExB,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,2BAA2B;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ferns-ui",
3
- "version": "0.22.4",
3
+ "version": "0.23.0",
4
4
  "main": "dist/index.js",
5
5
  "license": "Apache-2.0",
6
6
  "scripts": {
@@ -167,6 +167,7 @@
167
167
  "react-native-picker-select": "^8.0.0",
168
168
  "react-native-portalize": "^1.0.7",
169
169
  "react-native-svg": "^13.0.0",
170
+ "react-native-toast-notifications": "^3.3.1",
170
171
  "react-router": "^6.3.0",
171
172
  "react-router-dom": "^6.3.0",
172
173
  "react-time-picker": "^5.0.0",
@@ -198,6 +199,7 @@
198
199
  "react-native-picker-select": "^8.0.0",
199
200
  "react-native-portalize": "^1.0.7",
200
201
  "react-native-svg": "^13.0.0",
202
+ "react-native-toast-notifications": "^3.3.1",
201
203
  "react-router": "^6.3.0",
202
204
  "react-router-dom": "^6.3.0",
203
205
  "react-time-picker": "^5.0.0"
package/src/Button.tsx CHANGED
@@ -29,6 +29,7 @@ export interface ButtonProps {
29
29
  withConfirmation?: boolean;
30
30
  confirmationText?: string;
31
31
  confirmationHeading?: string;
32
+ shape?: "rounded" | "pill";
32
33
  }
33
34
 
34
35
  const buttonTextColor: {[buttonColor: string]: "white" | "darkGray"} = {
@@ -67,6 +68,7 @@ export function Button({
67
68
  withConfirmation = false,
68
69
  confirmationText = "Are you sure you want to continue?",
69
70
  confirmationHeading = "Confirm",
71
+ shape = "rounded",
70
72
  }: ButtonProps) {
71
73
  const [loading, setLoading] = useState(propsLoading);
72
74
  const [showConfirmation, setShowConfirmation] = useState(false);
@@ -136,7 +138,7 @@ export function Button({
136
138
  // flexGrow: inline ? 0 : 1,
137
139
  alignItems: "center",
138
140
  justifyContent: "center",
139
- borderRadius: 5,
141
+ borderRadius: shape === "pill" ? 999 : 5,
140
142
  borderColor: getBorderColor(color),
141
143
  borderWidth: type === "outline" ? 2 : 0,
142
144
  opacity: disabled ? 0.4 : 1,
package/src/Common.ts CHANGED
@@ -2004,25 +2004,12 @@ export interface IconProps {
2004
2004
  containerStyle?: any;
2005
2005
  }
2006
2006
 
2007
- export interface IconButtonProps {
2008
- prefix?: IconPrefix;
2009
- icon: IconName;
2010
- accessibilityLabel: string;
2011
- iconColor: "darkGray" | ButtonColor | ThemeColor | Color;
2012
- onClick: () => void;
2013
- size?: IconSize;
2014
- bgColor?: "transparent" | "transparentDarkGray" | "gray" | "lightGray" | "white"; // default transparent
2015
- disabled?: boolean;
2016
- selected?: boolean;
2017
- withConfirmation?: boolean;
2018
- confirmationText?: string;
2019
- confirmationHeading?: string;
2020
- }
2021
-
2022
2007
  export interface NavigatorProps {
2023
2008
  config?: any;
2024
2009
  }
2025
2010
 
2011
+ export type TooltipDirection = "top" | "bottom" | "left" | "right";
2012
+
2026
2013
  export interface PillProps {
2027
2014
  text: string;
2028
2015
  color: AllColors;
@@ -0,0 +1,20 @@
1
+ import React from "react";
2
+ import {ToastProvider} from "react-native-toast-notifications";
3
+
4
+ import {Toast} from "./Toast";
5
+
6
+ export function FernsProvider({children}: {children: React.ReactNode}): React.ReactElement {
7
+ return (
8
+ <ToastProvider
9
+ animationDuration={250}
10
+ animationType="slide-in"
11
+ duration={50000}
12
+ offset={50}
13
+ placement="bottom"
14
+ renderToast={(toastOptions) => <Toast {...(toastOptions as any)} />}
15
+ swipeEnabled
16
+ >
17
+ {children}
18
+ </ToastProvider>
19
+ );
20
+ }
@@ -1,89 +1,141 @@
1
- import React, {useState} from "react";
2
- import {TouchableOpacity} from "react-native";
1
+ import React, {forwardRef, useState} from "react";
2
+ import {Platform, TouchableOpacity, View} from "react-native";
3
3
 
4
- import {IconButtonProps, iconSizeToNumber} from "./Common";
4
+ import {
5
+ ButtonColor,
6
+ Color,
7
+ IconName,
8
+ IconPrefix,
9
+ IconSize,
10
+ iconSizeToNumber,
11
+ ThemeColor,
12
+ TooltipDirection,
13
+ } from "./Common";
5
14
  import {Icon} from "./Icon";
6
15
  import {Modal} from "./Modal";
7
16
  import {Text} from "./Text";
17
+ import {Tooltip} from "./Tooltip";
8
18
  import {Unifier} from "./Unifier";
9
19
 
10
- export function IconButton({
11
- prefix,
12
- icon,
13
- iconColor,
14
- onClick,
15
- size,
16
- bgColor = "transparent",
17
- withConfirmation = false,
18
- confirmationText = "Are you sure you want to continue?",
19
- confirmationHeading = "Confirm",
20
- }: IconButtonProps) {
21
- const [showConfirmation, setShowConfirmation] = useState(false);
20
+ export interface IconButtonProps {
21
+ prefix?: IconPrefix;
22
+ icon: IconName;
23
+ accessibilityLabel: string;
24
+ iconColor: "darkGray" | ButtonColor | ThemeColor | Color;
25
+ onClick: () => void;
26
+ size?: IconSize;
27
+ bgColor?: "transparent" | "transparentDarkGray" | "gray" | "lightGray" | "white"; // default transparent
28
+ disabled?: boolean;
29
+ selected?: boolean;
30
+ withConfirmation?: boolean;
31
+ confirmationText?: string;
32
+ confirmationHeading?: string;
33
+ tooltip?: {
34
+ text: string;
35
+ idealDirection?: TooltipDirection;
36
+ };
37
+ }
22
38
 
23
- let opacity = 1;
24
- let color;
25
- if (bgColor === "transparentDarkGray") {
26
- opacity = 0.8;
27
- color = Unifier.theme.darkGray;
28
- } else if (bgColor === "transparent" || !bgColor) {
29
- opacity = 0.0;
30
- color = Unifier.theme.white;
31
- } else {
32
- color = Unifier.theme[bgColor];
33
- }
39
+ // eslint-disable-next-line react/display-name
40
+ export const IconButton = forwardRef(
41
+ (
42
+ {
43
+ prefix,
44
+ icon,
45
+ iconColor,
46
+ onClick,
47
+ size,
48
+ bgColor = "transparent",
49
+ withConfirmation = false,
50
+ confirmationText = "Are you sure you want to continue?",
51
+ confirmationHeading = "Confirm",
52
+ tooltip,
53
+ }: IconButtonProps,
54
+ ref
55
+ ) => {
56
+ const [showConfirmation, setShowConfirmation] = useState(false);
34
57
 
35
- const renderConfirmation = () => {
36
- return (
37
- <Modal
38
- heading={confirmationHeading}
39
- primaryButtonOnClick={() => {
40
- onClick();
41
- setShowConfirmation(false);
42
- }}
43
- primaryButtonText="Confirm"
44
- secondaryButtonOnClick={(): void => setShowConfirmation(false)}
45
- secondaryButtonText="Cancel"
46
- size="sm"
47
- visible={showConfirmation}
48
- onDismiss={(): void => {
49
- setShowConfirmation(false);
50
- }}
51
- >
52
- <Text>{confirmationText}</Text>
53
- </Modal>
54
- );
55
- };
58
+ let opacity = 1;
59
+ let color: string;
60
+ if (bgColor === "transparentDarkGray") {
61
+ opacity = 0.8;
62
+ color = Unifier.theme.darkGray;
63
+ } else if (bgColor === "transparent" || !bgColor) {
64
+ opacity = 1.0;
65
+ color = Unifier.theme.white;
66
+ } else {
67
+ color = Unifier.theme[bgColor];
68
+ }
56
69
 
57
- return (
58
- <>
59
- <TouchableOpacity
60
- hitSlop={{top: 10, left: 10, bottom: 10, right: 10}}
61
- style={{
62
- opacity,
63
- backgroundColor: color,
64
- borderRadius: 100,
65
- // paddingBottom: iconSizeToNumber(size) / 4,
66
- // paddingTop: iconSizeToNumber(size) / 4,
67
- // paddingLeft: iconSizeToNumber(size) / 2,
68
- // paddingRight: iconSizeToNumber(size) / 2,
69
- width: iconSizeToNumber(size) * 2.5,
70
- height: iconSizeToNumber(size) * 2.5,
71
- display: "flex",
72
- justifyContent: "center",
73
- alignItems: "center",
74
- }}
75
- onPress={() => {
76
- Unifier.utils.haptic();
77
- if (withConfirmation && !showConfirmation) {
78
- setShowConfirmation(true);
79
- } else if (onClick) {
70
+ const renderConfirmation = () => {
71
+ return (
72
+ <Modal
73
+ heading={confirmationHeading}
74
+ primaryButtonOnClick={() => {
80
75
  onClick();
81
- }
82
- }}
83
- >
84
- <Icon color={iconColor} name={icon} prefix={prefix || "fas"} size={size} />
85
- </TouchableOpacity>
86
- {Boolean(withConfirmation) && renderConfirmation()}
87
- </>
88
- );
89
- }
76
+ setShowConfirmation(false);
77
+ }}
78
+ primaryButtonText="Confirm"
79
+ secondaryButtonOnClick={(): void => setShowConfirmation(false)}
80
+ secondaryButtonText="Cancel"
81
+ size="sm"
82
+ visible={showConfirmation}
83
+ onDismiss={(): void => {
84
+ setShowConfirmation(false);
85
+ }}
86
+ >
87
+ <Text>{confirmationText}</Text>
88
+ </Modal>
89
+ );
90
+ };
91
+
92
+ function renderIconButton(): React.ReactElement {
93
+ return (
94
+ <View>
95
+ <TouchableOpacity
96
+ ref={ref as any}
97
+ hitSlop={{top: 10, left: 10, bottom: 10, right: 10}}
98
+ style={{
99
+ opacity,
100
+ backgroundColor: color,
101
+ borderRadius: 100,
102
+ // paddingBottom: iconSizeToNumber(size) / 4,
103
+ // paddingTop: iconSizeToNumber(size) / 4,
104
+ // paddingLeft: iconSizeToNumber(size) / 2,
105
+ // paddingRight: iconSizeToNumber(size) / 2,
106
+ width: iconSizeToNumber(size) * 2.5,
107
+ height: iconSizeToNumber(size) * 2.5,
108
+ display: "flex",
109
+ justifyContent: "center",
110
+ alignItems: "center",
111
+ }}
112
+ onPress={() => {
113
+ Unifier.utils.haptic();
114
+ if (withConfirmation && !showConfirmation) {
115
+ setShowConfirmation(true);
116
+ } else if (onClick) {
117
+ onClick();
118
+ }
119
+ }}
120
+ >
121
+ <Icon color={iconColor} name={icon} prefix={prefix || "fas"} size={size} />
122
+ </TouchableOpacity>
123
+ {Boolean(withConfirmation) && renderConfirmation()}
124
+ </View>
125
+ );
126
+ }
127
+
128
+ // Only add for web. This doesn't make much sense for mobile, since the action would be performed for the button
129
+ // as well as the tooltip appearing.
130
+ // TODO: Add tooltip info button next to the icon button on mobile.
131
+ if (tooltip && Platform.OS === "web") {
132
+ return (
133
+ <Tooltip idealDirection={tooltip.idealDirection} text={tooltip.text}>
134
+ {renderIconButton()}
135
+ </Tooltip>
136
+ );
137
+ } else {
138
+ return renderIconButton();
139
+ }
140
+ }
141
+ );
@@ -0,0 +1,23 @@
1
+ import React from "react";
2
+
3
+ import {IconSize} from "./Common";
4
+ import {IconButton} from "./IconButton";
5
+
6
+ interface InfoTooltipButtonProps {
7
+ text: string;
8
+ size?: IconSize;
9
+ }
10
+
11
+ export function InfoTooltipButton({text, size}: InfoTooltipButtonProps): React.ReactElement {
12
+ return (
13
+ <IconButton
14
+ accessibilityLabel="info"
15
+ bgColor="transparent"
16
+ icon="exclamation"
17
+ iconColor="blue"
18
+ size={size}
19
+ tooltip={{text}}
20
+ onClick={() => {}}
21
+ />
22
+ );
23
+ }
package/src/Modal.tsx CHANGED
@@ -108,43 +108,41 @@ export const Modal = ({
108
108
  }
109
109
 
110
110
  return (
111
- <Box alignItems="center" flex="grow" height="100%" justifyContent="center" width="100%">
112
- <RNModal animationType="slide" transparent visible={visible} onRequestClose={onDismiss}>
113
- <Box
114
- alignItems="center"
115
- alignSelf="center"
116
- color="white"
117
- dangerouslySetInlineStyle={{
118
- __style: {
119
- zIndex: 1,
120
- shadowColor: "#999",
121
- shadowOffset: {
122
- width: 4,
123
- height: 6,
124
- },
125
- shadowRadius: 4,
126
- shadowOpacity: 1.0,
127
- elevation: 8,
111
+ <RNModal animationType="slide" transparent visible={visible} onRequestClose={onDismiss}>
112
+ <Box
113
+ alignItems="center"
114
+ alignSelf="center"
115
+ color="white"
116
+ dangerouslySetInlineStyle={{
117
+ __style: {
118
+ zIndex: 1,
119
+ shadowColor: "#999",
120
+ shadowOffset: {
121
+ width: 4,
122
+ height: 6,
128
123
  },
129
- }}
130
- direction="column"
131
- justifyContent="center"
132
- marginTop={12}
133
- maxWidth={sizePx}
134
- minWidth={300}
135
- paddingX={8}
136
- paddingY={2}
137
- rounding={6}
138
- shadow
139
- width={sizePx}
140
- >
141
- <Box marginBottom={6} width="100%">
142
- {renderHeader()}
143
- <Box paddingY={4}>{children}</Box>
144
- <Box paddingY={4}>{renderFooter()}</Box>
145
- </Box>
124
+ shadowRadius: 4,
125
+ shadowOpacity: 1.0,
126
+ elevation: 8,
127
+ },
128
+ }}
129
+ direction="column"
130
+ justifyContent="center"
131
+ marginTop={12}
132
+ maxWidth={sizePx}
133
+ minWidth={300}
134
+ paddingX={8}
135
+ paddingY={2}
136
+ rounding={6}
137
+ shadow
138
+ width={sizePx}
139
+ >
140
+ <Box marginBottom={6} width="100%">
141
+ {renderHeader()}
142
+ <Box paddingY={4}>{children}</Box>
143
+ <Box paddingY={4}>{renderFooter()}</Box>
146
144
  </Box>
147
- </RNModal>
148
- </Box>
145
+ </Box>
146
+ </RNModal>
149
147
  );
150
148
  };