@pagopa/io-app-design-system 3.0.0 → 3.1.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/lib/commonjs/components/codeInput/CodeInput.js +41 -21
- package/lib/commonjs/components/codeInput/CodeInput.js.map +1 -1
- package/lib/commonjs/components/index.js +11 -0
- package/lib/commonjs/components/index.js.map +1 -1
- package/lib/commonjs/components/layout/HeaderSecondLevel.js +2 -2
- package/lib/commonjs/components/layout/HeaderSecondLevel.js.map +1 -1
- package/lib/commonjs/components/tooltip/Arrows.js +49 -0
- package/lib/commonjs/components/tooltip/Arrows.js.map +1 -0
- package/lib/commonjs/components/tooltip/Tooltip.js +174 -0
- package/lib/commonjs/components/tooltip/Tooltip.js.map +1 -0
- package/lib/commonjs/components/tooltip/index.js +17 -0
- package/lib/commonjs/components/tooltip/index.js.map +1 -0
- package/lib/commonjs/components/tooltip/styles.js +50 -0
- package/lib/commonjs/components/tooltip/styles.js.map +1 -0
- package/lib/commonjs/components/tooltip/utils/index.js +180 -0
- package/lib/commonjs/components/tooltip/utils/index.js.map +1 -0
- package/lib/commonjs/components/tooltip/utils/types.js +2 -0
- package/lib/commonjs/components/tooltip/utils/types.js.map +1 -0
- package/lib/module/components/codeInput/CodeInput.js +43 -23
- package/lib/module/components/codeInput/CodeInput.js.map +1 -1
- package/lib/module/components/index.js +1 -0
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/components/layout/HeaderSecondLevel.js +2 -2
- package/lib/module/components/layout/HeaderSecondLevel.js.map +1 -1
- package/lib/module/components/tooltip/Arrows.js +36 -0
- package/lib/module/components/tooltip/Arrows.js.map +1 -0
- package/lib/module/components/tooltip/Tooltip.js +165 -0
- package/lib/module/components/tooltip/Tooltip.js.map +1 -0
- package/lib/module/components/tooltip/index.js +2 -0
- package/lib/module/components/tooltip/index.js.map +1 -0
- package/lib/module/components/tooltip/styles.js +43 -0
- package/lib/module/components/tooltip/styles.js.map +1 -0
- package/lib/module/components/tooltip/utils/index.js +163 -0
- package/lib/module/components/tooltip/utils/index.js.map +1 -0
- package/lib/module/components/tooltip/utils/types.js +2 -0
- package/lib/module/components/tooltip/utils/types.js.map +1 -0
- package/lib/typescript/components/codeInput/CodeInput.d.ts.map +1 -1
- package/lib/typescript/components/index.d.ts +1 -0
- package/lib/typescript/components/index.d.ts.map +1 -1
- package/lib/typescript/components/layout/HeaderSecondLevel.d.ts.map +1 -1
- package/lib/typescript/components/tooltip/Arrows.d.ts +14 -0
- package/lib/typescript/components/tooltip/Arrows.d.ts.map +1 -0
- package/lib/typescript/components/tooltip/Tooltip.d.ts +64 -0
- package/lib/typescript/components/tooltip/Tooltip.d.ts.map +1 -0
- package/lib/typescript/components/tooltip/index.d.ts +2 -0
- package/lib/typescript/components/tooltip/index.d.ts.map +1 -0
- package/lib/typescript/components/tooltip/styles.d.ts +41 -0
- package/lib/typescript/components/tooltip/styles.d.ts.map +1 -0
- package/lib/typescript/components/tooltip/utils/index.d.ts +89 -0
- package/lib/typescript/components/tooltip/utils/index.d.ts.map +1 -0
- package/lib/typescript/components/tooltip/utils/types.d.ts +10 -0
- package/lib/typescript/components/tooltip/utils/types.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/codeInput/CodeInput.tsx +53 -30
- package/src/components/index.tsx +1 -0
- package/src/components/layout/HeaderSecondLevel.tsx +6 -9
- package/src/components/tooltip/Arrows.tsx +36 -0
- package/src/components/tooltip/Tooltip.tsx +313 -0
- package/src/components/tooltip/index.ts +1 -0
- package/src/components/tooltip/styles.ts +44 -0
- package/src/components/tooltip/utils/index.ts +179 -0
- package/src/components/tooltip/utils/types.ts +9 -0
|
@@ -178,22 +178,19 @@ export const HeaderSecondLevel = ({
|
|
|
178
178
|
/* Visual attributes when there are transitions between states */
|
|
179
179
|
const HEADER_DEFAULT_BG_COLOR: IOColors = theme["appBackground-primary"];
|
|
180
180
|
|
|
181
|
-
const headerBgColorTransparentState = backgroundColor
|
|
182
|
-
? hexToRgba(backgroundColor, 0)
|
|
183
|
-
: transparent
|
|
184
|
-
? hexToRgba(IOColors[HEADER_DEFAULT_BG_COLOR], 0)
|
|
185
|
-
: IOColors[HEADER_DEFAULT_BG_COLOR];
|
|
186
|
-
|
|
187
181
|
const headerBgColorSolidState =
|
|
188
182
|
backgroundColor ?? IOColors[HEADER_DEFAULT_BG_COLOR];
|
|
189
183
|
|
|
184
|
+
const headerBgColorTransparentState = transparent
|
|
185
|
+
? hexToRgba(headerBgColorSolidState, 0)
|
|
186
|
+
: headerBgColorSolidState;
|
|
187
|
+
|
|
190
188
|
const borderColorDefault = IOColors[theme["divider-default"]];
|
|
191
189
|
|
|
192
|
-
const borderColorTransparentState = backgroundColor
|
|
193
|
-
? hexToRgba(backgroundColor, 0)
|
|
194
|
-
: hexToRgba(borderColorDefault, 0);
|
|
195
190
|
const borderColorSolidState = backgroundColor ?? borderColorDefault;
|
|
196
191
|
|
|
192
|
+
const borderColorTransparentState = hexToRgba(borderColorSolidState, 0);
|
|
193
|
+
|
|
197
194
|
useLayoutEffect(() => {
|
|
198
195
|
if (isTitleAccessible) {
|
|
199
196
|
const reactNode = findNodeHandle(titleRef.current);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import Svg, { Path } from 'react-native-svg';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { IOColors } from '../../core';
|
|
4
|
+
|
|
5
|
+
export const LeftArrow = ({ color = IOColors.white }: { color?: string }) => (
|
|
6
|
+
<Svg fill="none" >
|
|
7
|
+
<Path
|
|
8
|
+
d="M12.6955 15.2C14.8289 13.6 14.8289 10.4 12.6955 8.8L0.962204 0V24L12.6955 15.2Z"
|
|
9
|
+
fill={color}
|
|
10
|
+
/>
|
|
11
|
+
</Svg>
|
|
12
|
+
);
|
|
13
|
+
export const RightArrow = ({ color = IOColors.white }: { color?: string }) => (
|
|
14
|
+
<Svg fill={color}>
|
|
15
|
+
<Path
|
|
16
|
+
d="M2.30448 15.2031C0.171145 13.6031 0.171145 10.4031 2.30448 8.80314L14.0378 0.00314331V24.0031L2.30448 15.2031Z"
|
|
17
|
+
fill={color}
|
|
18
|
+
/>
|
|
19
|
+
</Svg>
|
|
20
|
+
);
|
|
21
|
+
export const BottomArrow = ({ color = IOColors.white }: { color?: string }) => (
|
|
22
|
+
<Svg fill={color}>
|
|
23
|
+
<Path
|
|
24
|
+
d="M15.2 2.26667C13.6 0.133334 10.4 0.133333 8.8 2.26667L0 14L24 14L15.2 2.26667Z"
|
|
25
|
+
fill={color}
|
|
26
|
+
/>
|
|
27
|
+
</Svg>
|
|
28
|
+
);
|
|
29
|
+
export const TopArrow = ({ color = IOColors.white }: { color?: string }) => (
|
|
30
|
+
<Svg fill={color}>
|
|
31
|
+
<Path
|
|
32
|
+
d="M15.2 11.7333C13.6 13.8667 10.4 13.8667 8.8 11.7333L0 0L24 0L15.2 11.7333Z"
|
|
33
|
+
fill={color}
|
|
34
|
+
/>
|
|
35
|
+
</Svg>
|
|
36
|
+
);
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useState,
|
|
3
|
+
useRef,
|
|
4
|
+
PropsWithChildren,
|
|
5
|
+
useEffect,
|
|
6
|
+
useCallback,
|
|
7
|
+
JSXElementConstructor,
|
|
8
|
+
useMemo,
|
|
9
|
+
ReactElement
|
|
10
|
+
} from "react";
|
|
11
|
+
import {
|
|
12
|
+
View,
|
|
13
|
+
Modal,
|
|
14
|
+
Dimensions,
|
|
15
|
+
LayoutChangeEvent,
|
|
16
|
+
TouchableWithoutFeedback
|
|
17
|
+
} from "react-native";
|
|
18
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
19
|
+
import { every, some } from "lodash";
|
|
20
|
+
import { IOColors } from "../../core";
|
|
21
|
+
import { Body, H6 } from "../typography";
|
|
22
|
+
import { IconButton } from "../buttons";
|
|
23
|
+
import { BottomArrow, LeftArrow, RightArrow, TopArrow } from "./Arrows";
|
|
24
|
+
import {
|
|
25
|
+
ARROW_HEIGHT,
|
|
26
|
+
EMPTY_SPACE,
|
|
27
|
+
getArrowBoxByPlacement,
|
|
28
|
+
getArrowCoords,
|
|
29
|
+
getArrowVerticalAlignment,
|
|
30
|
+
getDisplayInsets,
|
|
31
|
+
getTooltipCoords,
|
|
32
|
+
getTooltipVerticalAlignment,
|
|
33
|
+
isDefined,
|
|
34
|
+
isNotZero
|
|
35
|
+
} from "./utils";
|
|
36
|
+
import { getChildrenPosition, tooltipStyles } from "./styles";
|
|
37
|
+
import {
|
|
38
|
+
ChildrenCoords,
|
|
39
|
+
DisplayInsets,
|
|
40
|
+
Placement,
|
|
41
|
+
TooltipLayout
|
|
42
|
+
} from "./utils/types";
|
|
43
|
+
|
|
44
|
+
const screenDimensions = Dimensions.get("window");
|
|
45
|
+
const INITIAL_COORDS: ChildrenCoords = {
|
|
46
|
+
x: 0,
|
|
47
|
+
y: 0,
|
|
48
|
+
width: 0,
|
|
49
|
+
height: 0
|
|
50
|
+
};
|
|
51
|
+
const ARROWS_BY_PLACEMENT: Record<
|
|
52
|
+
Placement,
|
|
53
|
+
JSXElementConstructor<{ color: string }>
|
|
54
|
+
> = {
|
|
55
|
+
top: TopArrow,
|
|
56
|
+
bottom: BottomArrow,
|
|
57
|
+
left: LeftArrow,
|
|
58
|
+
right: RightArrow
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
type CommonProps = {
|
|
62
|
+
/**
|
|
63
|
+
* The title text displayed at the top of the tooltip.
|
|
64
|
+
*/
|
|
65
|
+
title: string;
|
|
66
|
+
/**
|
|
67
|
+
* The tooltip text content.
|
|
68
|
+
*/
|
|
69
|
+
content: string;
|
|
70
|
+
/**
|
|
71
|
+
* Controls the visibility of the tooltip.
|
|
72
|
+
*/
|
|
73
|
+
isVisible: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Initial tooltip position; can be 'top', 'bottom', 'left', or 'right'.
|
|
76
|
+
* @default top
|
|
77
|
+
*/
|
|
78
|
+
placement?: Placement;
|
|
79
|
+
/**
|
|
80
|
+
* Insets for adjusting tooltip position within screen boundaries.
|
|
81
|
+
* @default {}
|
|
82
|
+
*/
|
|
83
|
+
displayInsets?: Partial<DisplayInsets>;
|
|
84
|
+
/**
|
|
85
|
+
* Accessibility label for the close icon button.
|
|
86
|
+
*/
|
|
87
|
+
closeIconAccessibilityLabel: string;
|
|
88
|
+
/**
|
|
89
|
+
* Determines whether interactions with the tooltip's children are allowed when `isVisible` is set to true.
|
|
90
|
+
* @default false
|
|
91
|
+
*/
|
|
92
|
+
childrenInteractionsEnabled?: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Callback function triggered when the tooltip is closed.
|
|
95
|
+
*/
|
|
96
|
+
onClose: () => void;
|
|
97
|
+
};
|
|
98
|
+
type CloseWithTapOnBackground = {
|
|
99
|
+
/**
|
|
100
|
+
* Allows closing the tooltip by tapping outside of it.
|
|
101
|
+
*/
|
|
102
|
+
allowCloseOnBackgroundTap: true;
|
|
103
|
+
/**
|
|
104
|
+
* Accessibility label for the tooltip background mask.
|
|
105
|
+
*/
|
|
106
|
+
backgroundAccessibilityLabel: string;
|
|
107
|
+
};
|
|
108
|
+
type CloseWithBackgroundTapDisabled = {
|
|
109
|
+
allowCloseOnBackgroundTap?: false;
|
|
110
|
+
};
|
|
111
|
+
type Props = CommonProps & (CloseWithTapOnBackground | CloseWithBackgroundTapDisabled);
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Tooltip component that displays a contextual tooltip around its children.
|
|
115
|
+
* The tooltip position is controlled by the `placement` prop and can adjust
|
|
116
|
+
* dynamically if there is insufficient space.
|
|
117
|
+
* @param {Props} props - The component props
|
|
118
|
+
*
|
|
119
|
+
* @returns {ReactElement} A tooltip component rendered around the specified children.
|
|
120
|
+
*/
|
|
121
|
+
export const Tooltip = ({
|
|
122
|
+
children,
|
|
123
|
+
title,
|
|
124
|
+
content,
|
|
125
|
+
placement: initialPlacement = "top",
|
|
126
|
+
closeIconAccessibilityLabel,
|
|
127
|
+
isVisible,
|
|
128
|
+
displayInsets = {},
|
|
129
|
+
allowCloseOnBackgroundTap,
|
|
130
|
+
childrenInteractionsEnabled = false,
|
|
131
|
+
onClose
|
|
132
|
+
}: PropsWithChildren<Props>): ReactElement => {
|
|
133
|
+
const insets = useSafeAreaInsets();
|
|
134
|
+
const [currentPlacement, setCurrentPlacement] =
|
|
135
|
+
useState<Placement>(initialPlacement);
|
|
136
|
+
const [childrenCoords, setChildrenCoords] = useState<ChildrenCoords>(INITIAL_COORDS);
|
|
137
|
+
const [tooltipLayout, setTooltipLayout] = useState<TooltipLayout>();
|
|
138
|
+
const childRef = useRef<View>(null);
|
|
139
|
+
const titleRef = useRef<View>(null);
|
|
140
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
|
141
|
+
|
|
142
|
+
const Arrow = useMemo(
|
|
143
|
+
() => ARROWS_BY_PLACEMENT[currentPlacement],
|
|
144
|
+
[currentPlacement]
|
|
145
|
+
);
|
|
146
|
+
const isChildrenMeasurementFinished =
|
|
147
|
+
every(childrenCoords, isDefined)
|
|
148
|
+
&& some(childrenCoords, isNotZero);
|
|
149
|
+
const isTooltipMeasurementCompleted = isDefined(tooltipLayout);
|
|
150
|
+
const tooltipVisibility = { opacity: isTooltipMeasurementCompleted ? 1 : 0 };
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* This function sets the `Tooltip` children coordinates
|
|
154
|
+
*/
|
|
155
|
+
const measureChildrenCoords = useCallback(() => {
|
|
156
|
+
if (childRef.current && typeof childRef.current.measure === "function") {
|
|
157
|
+
childRef.current.measure((_, __, width, height, px, py) => {
|
|
158
|
+
const coords = {
|
|
159
|
+
x: px,
|
|
160
|
+
y: py,
|
|
161
|
+
width,
|
|
162
|
+
height
|
|
163
|
+
};
|
|
164
|
+
if (every(coords, isDefined)) {
|
|
165
|
+
setChildrenCoords(coords);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}, []);
|
|
170
|
+
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
if (isVisible) {
|
|
173
|
+
// A new measure is executed every time the `Tooltip` is visible
|
|
174
|
+
// This is required for use within ScrollView components.
|
|
175
|
+
// eslint-disable-next-line functional/immutable-data
|
|
176
|
+
timeoutRef.current = setTimeout(measureChildrenCoords, 100);
|
|
177
|
+
} else {
|
|
178
|
+
setChildrenCoords(INITIAL_COORDS);
|
|
179
|
+
setCurrentPlacement(initialPlacement);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return () => {
|
|
183
|
+
if (isVisible) {
|
|
184
|
+
clearTimeout(timeoutRef.current);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}, [isVisible, initialPlacement, measureChildrenCoords]);
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* This function works with `top` and `bottom` placement and sets the current placement to their opposite value
|
|
191
|
+
* if in the selected one there is no space to prompt the tooltip
|
|
192
|
+
*/
|
|
193
|
+
const invertPlacementIfNeeded = useCallback(
|
|
194
|
+
(nativeEvent: LayoutChangeEvent["nativeEvent"]) => {
|
|
195
|
+
if (initialPlacement === "top") {
|
|
196
|
+
const hasSpace = nativeEvent.layout.y >= insets.top;
|
|
197
|
+
|
|
198
|
+
if (!hasSpace) {
|
|
199
|
+
setCurrentPlacement("bottom");
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (initialPlacement === "bottom") {
|
|
203
|
+
const remainingSpace =
|
|
204
|
+
screenDimensions.height - nativeEvent.layout.y - insets.bottom;
|
|
205
|
+
const tooltipMinHeight =
|
|
206
|
+
nativeEvent.layout.height + ARROW_HEIGHT + EMPTY_SPACE;
|
|
207
|
+
const hasSpace = remainingSpace >= tooltipMinHeight;
|
|
208
|
+
|
|
209
|
+
if (!hasSpace) {
|
|
210
|
+
setCurrentPlacement("top");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
[insets.bottom, insets.top, initialPlacement]
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const handleTooltipOnLayout = useCallback(
|
|
218
|
+
({ nativeEvent }: LayoutChangeEvent) => {
|
|
219
|
+
invertPlacementIfNeeded(nativeEvent);
|
|
220
|
+
setTooltipLayout(nativeEvent.layout);
|
|
221
|
+
},
|
|
222
|
+
[invertPlacementIfNeeded]
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const handleTapOnBackground = useCallback(() => {
|
|
226
|
+
if (allowCloseOnBackgroundTap) {
|
|
227
|
+
onClose();
|
|
228
|
+
}
|
|
229
|
+
}, [allowCloseOnBackgroundTap, onClose]);
|
|
230
|
+
|
|
231
|
+
return (
|
|
232
|
+
<>
|
|
233
|
+
<View
|
|
234
|
+
// This prop is necessary for .measure to work correctly, as explained here: https://github.com/facebook/react-native/issues/29712
|
|
235
|
+
collapsable={false}
|
|
236
|
+
ref={childRef}
|
|
237
|
+
>
|
|
238
|
+
{children}
|
|
239
|
+
</View>
|
|
240
|
+
<Modal transparent visible={isVisible && isChildrenMeasurementFinished}>
|
|
241
|
+
<View
|
|
242
|
+
accessibilityElementsHidden={!childrenInteractionsEnabled}
|
|
243
|
+
importantForAccessibility={childrenInteractionsEnabled ? 'auto' : 'no-hide-descendants'}
|
|
244
|
+
pointerEvents={childrenInteractionsEnabled ? "auto" : "box-only"}
|
|
245
|
+
style={[
|
|
246
|
+
tooltipStyles.childrenContainer,
|
|
247
|
+
getChildrenPosition(childrenCoords)
|
|
248
|
+
]}
|
|
249
|
+
>
|
|
250
|
+
{children}
|
|
251
|
+
</View>
|
|
252
|
+
<TouchableWithoutFeedback
|
|
253
|
+
accessible={allowCloseOnBackgroundTap}
|
|
254
|
+
accessibilityRole={allowCloseOnBackgroundTap ? "button" : "none"}
|
|
255
|
+
importantForAccessibility={allowCloseOnBackgroundTap ? 'yes' : 'no'}
|
|
256
|
+
accessibilityElementsHidden={!allowCloseOnBackgroundTap}
|
|
257
|
+
onPress={handleTapOnBackground}
|
|
258
|
+
>
|
|
259
|
+
<View
|
|
260
|
+
style={[
|
|
261
|
+
tooltipStyles.overlay,
|
|
262
|
+
{ height: screenDimensions.height }
|
|
263
|
+
]}
|
|
264
|
+
/>
|
|
265
|
+
</TouchableWithoutFeedback>
|
|
266
|
+
<View
|
|
267
|
+
onLayout={handleTooltipOnLayout}
|
|
268
|
+
style={[
|
|
269
|
+
tooltipStyles.tooltipContainer,
|
|
270
|
+
getTooltipCoords(
|
|
271
|
+
currentPlacement,
|
|
272
|
+
childrenCoords,
|
|
273
|
+
getDisplayInsets(displayInsets),
|
|
274
|
+
screenDimensions
|
|
275
|
+
),
|
|
276
|
+
getTooltipVerticalAlignment(
|
|
277
|
+
currentPlacement,
|
|
278
|
+
childrenCoords.height,
|
|
279
|
+
tooltipLayout?.height
|
|
280
|
+
),
|
|
281
|
+
tooltipVisibility
|
|
282
|
+
]}
|
|
283
|
+
>
|
|
284
|
+
<H6 ref={titleRef}>{title}</H6>
|
|
285
|
+
<View style={tooltipStyles.closeIcon}>
|
|
286
|
+
<IconButton
|
|
287
|
+
color="neutral"
|
|
288
|
+
icon="closeSmall"
|
|
289
|
+
accessibilityLabel={closeIconAccessibilityLabel}
|
|
290
|
+
onPress={onClose}
|
|
291
|
+
/>
|
|
292
|
+
</View>
|
|
293
|
+
<Body>{content}</Body>
|
|
294
|
+
</View>
|
|
295
|
+
<View
|
|
296
|
+
style={[
|
|
297
|
+
tooltipStyles.arrowContainer,
|
|
298
|
+
getArrowBoxByPlacement(currentPlacement),
|
|
299
|
+
getArrowCoords(
|
|
300
|
+
currentPlacement,
|
|
301
|
+
childrenCoords,
|
|
302
|
+
screenDimensions
|
|
303
|
+
),
|
|
304
|
+
getArrowVerticalAlignment(currentPlacement, childrenCoords.height),
|
|
305
|
+
tooltipVisibility
|
|
306
|
+
]}
|
|
307
|
+
>
|
|
308
|
+
<Arrow color={IOColors.white} />
|
|
309
|
+
</View>
|
|
310
|
+
</Modal>
|
|
311
|
+
</>
|
|
312
|
+
);
|
|
313
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Tooltip";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
import { IOColors } from '../../core';
|
|
3
|
+
import { ChildrenCoords } from './utils/types';
|
|
4
|
+
|
|
5
|
+
export const tooltipStyles = StyleSheet.create({
|
|
6
|
+
overlay: {
|
|
7
|
+
position: "absolute",
|
|
8
|
+
width: "100%",
|
|
9
|
+
height: "100%",
|
|
10
|
+
backgroundColor: IOColors["grey-850"],
|
|
11
|
+
opacity: 0.6,
|
|
12
|
+
zIndex: 997
|
|
13
|
+
},
|
|
14
|
+
childrenContainer: {
|
|
15
|
+
position: "absolute",
|
|
16
|
+
zIndex: 1000
|
|
17
|
+
},
|
|
18
|
+
tooltipContainer: {
|
|
19
|
+
position: "absolute",
|
|
20
|
+
paddingHorizontal: 16,
|
|
21
|
+
paddingVertical: 16,
|
|
22
|
+
backgroundColor: IOColors.white,
|
|
23
|
+
borderRadius: 8,
|
|
24
|
+
zIndex: 2000,
|
|
25
|
+
overflow: "visible"
|
|
26
|
+
},
|
|
27
|
+
arrowContainer: {
|
|
28
|
+
position: "absolute",
|
|
29
|
+
display: 'flex',
|
|
30
|
+
zIndex: 3000
|
|
31
|
+
},
|
|
32
|
+
closeIcon: {
|
|
33
|
+
position: 'absolute',
|
|
34
|
+
right: 8,
|
|
35
|
+
top: 9 // It's been used `9` instead of `8` to fix accessibility focus order. In this way title is read before close icon.
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const getChildrenPosition = (childrenCoords: ChildrenCoords) => ({
|
|
40
|
+
top: childrenCoords.y,
|
|
41
|
+
left: childrenCoords.x,
|
|
42
|
+
width: childrenCoords.width,
|
|
43
|
+
height: childrenCoords.height
|
|
44
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { ScaledSize } from 'react-native';
|
|
2
|
+
import { IOVisualCostants } from '../../../core';
|
|
3
|
+
import { ChildrenCoords, DisplayInsets, Placement } from './types';
|
|
4
|
+
|
|
5
|
+
export const ARROW_WIDTH = 24;
|
|
6
|
+
export const ARROW_HEIGHT = 14;
|
|
7
|
+
export const EMPTY_SPACE = 8;
|
|
8
|
+
const DEFAULT_INSETS: DisplayInsets = {
|
|
9
|
+
top: 0,
|
|
10
|
+
bottom: 0,
|
|
11
|
+
left: IOVisualCostants.appMarginDefault,
|
|
12
|
+
right: IOVisualCostants.appMarginDefault,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param displayInsets custom display insets
|
|
17
|
+
* @returns An `object` based on `DEFAULT_INSETS` and `displayInsets`
|
|
18
|
+
*/
|
|
19
|
+
export const getDisplayInsets = (
|
|
20
|
+
displayInsets: Partial<DisplayInsets>
|
|
21
|
+
): DisplayInsets => ({ ...DEFAULT_INSETS, ...displayInsets });
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
*
|
|
25
|
+
* @param placement The `Tooltip` placement
|
|
26
|
+
* @returns The `Arrow` box `width` and `height` based on `placement` value
|
|
27
|
+
*/
|
|
28
|
+
export const getArrowBoxByPlacement = (placement: Placement) => {
|
|
29
|
+
switch (placement) {
|
|
30
|
+
case 'left':
|
|
31
|
+
case 'right':
|
|
32
|
+
return {
|
|
33
|
+
width: ARROW_HEIGHT,
|
|
34
|
+
height: ARROW_WIDTH,
|
|
35
|
+
};
|
|
36
|
+
default:
|
|
37
|
+
return {
|
|
38
|
+
height: ARROW_HEIGHT,
|
|
39
|
+
width: ARROW_WIDTH,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A utility function to calculate the `Tooltip` coordinates and dimensions
|
|
46
|
+
* @param placement The `Tooltip` placement in relation of its children
|
|
47
|
+
* @param childrenCoords The measures in screen of the `Tooltip` children
|
|
48
|
+
* @param displayInsets The active display insets
|
|
49
|
+
* @param screenDimensions The dimensions of the device screen
|
|
50
|
+
* @returns The `Tooltip` coordinates
|
|
51
|
+
*/
|
|
52
|
+
export const getTooltipCoords = (
|
|
53
|
+
placement: Placement,
|
|
54
|
+
childrenCoords: ChildrenCoords,
|
|
55
|
+
displayInsets: DisplayInsets,
|
|
56
|
+
screenDimensions: ScaledSize
|
|
57
|
+
) => {
|
|
58
|
+
const { width: screenWidth, height: screenHeight } = screenDimensions;
|
|
59
|
+
|
|
60
|
+
switch (placement) {
|
|
61
|
+
case "top":
|
|
62
|
+
return {
|
|
63
|
+
bottom: screenHeight - childrenCoords.y + ARROW_HEIGHT + EMPTY_SPACE,
|
|
64
|
+
left: displayInsets.left,
|
|
65
|
+
width: screenWidth - displayInsets.left - displayInsets.right
|
|
66
|
+
};
|
|
67
|
+
case "bottom":
|
|
68
|
+
return {
|
|
69
|
+
top: childrenCoords.y + childrenCoords.height + ARROW_HEIGHT + EMPTY_SPACE,
|
|
70
|
+
left: displayInsets.left,
|
|
71
|
+
width: screenWidth - displayInsets.left - displayInsets.right
|
|
72
|
+
};
|
|
73
|
+
case "left":
|
|
74
|
+
return {
|
|
75
|
+
top: childrenCoords.y,
|
|
76
|
+
left: displayInsets.left,
|
|
77
|
+
width:
|
|
78
|
+
screenWidth - (screenWidth - childrenCoords.x) - ARROW_HEIGHT - displayInsets.left - EMPTY_SPACE
|
|
79
|
+
};
|
|
80
|
+
case "right":
|
|
81
|
+
const elementSize = childrenCoords.width + childrenCoords.x + ARROW_HEIGHT + EMPTY_SPACE;
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
top: childrenCoords.y,
|
|
85
|
+
left: elementSize,
|
|
86
|
+
width:
|
|
87
|
+
screenWidth -
|
|
88
|
+
(elementSize + displayInsets.right)
|
|
89
|
+
};
|
|
90
|
+
// TODO: provide a default center position in case of Tooltip without children
|
|
91
|
+
default:
|
|
92
|
+
return {};
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A utility function to calculate the `Tooltip`'s `Arrow` coordinates
|
|
98
|
+
* @param placement The `Arrow` placement in relation of the `Tooltip` children
|
|
99
|
+
* @param childrenCoords The measures in screen of the `Tooltip` children
|
|
100
|
+
* @param screenDimensions The active display insets
|
|
101
|
+
* @returns The `Tooltip`'s Arrow coordinates
|
|
102
|
+
*/
|
|
103
|
+
export const getArrowCoords = (
|
|
104
|
+
placement: Placement,
|
|
105
|
+
childrenCoords: ChildrenCoords,
|
|
106
|
+
screenDimensions: ScaledSize
|
|
107
|
+
) => {
|
|
108
|
+
const { width: screenWidth, height: screenHeight } = screenDimensions;
|
|
109
|
+
|
|
110
|
+
switch (placement) {
|
|
111
|
+
case "top":
|
|
112
|
+
return {
|
|
113
|
+
bottom: screenHeight - childrenCoords.y + EMPTY_SPACE,
|
|
114
|
+
left: childrenCoords.x + childrenCoords.width / 2 - ARROW_WIDTH / 2
|
|
115
|
+
};
|
|
116
|
+
case "bottom":
|
|
117
|
+
return {
|
|
118
|
+
top: childrenCoords.y + childrenCoords.height + EMPTY_SPACE,
|
|
119
|
+
left: childrenCoords.x + childrenCoords.width / 2 - ARROW_WIDTH / 2
|
|
120
|
+
};
|
|
121
|
+
case "left":
|
|
122
|
+
return {
|
|
123
|
+
top: childrenCoords.y,
|
|
124
|
+
left: screenWidth - (screenWidth - childrenCoords.x) - ARROW_HEIGHT - EMPTY_SPACE - 1, // FIXME -> This `-1` is necessary because of the Svg size doesn't match the box size
|
|
125
|
+
};
|
|
126
|
+
case "right":
|
|
127
|
+
return {
|
|
128
|
+
top: childrenCoords.y,
|
|
129
|
+
left: childrenCoords.width + childrenCoords.x + EMPTY_SPACE
|
|
130
|
+
};
|
|
131
|
+
default:
|
|
132
|
+
// TODO: provide a default center position in case of Tooltip without children
|
|
133
|
+
return {};
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* A utility function to calculate the `Tooltip` vertical alignment
|
|
139
|
+
* @param placement The `Tooltip` placement in relation of its children
|
|
140
|
+
* @param childrenHeight The `Tooltip`'s children height
|
|
141
|
+
* @param tooltipHeight The `Tooltip`'s height
|
|
142
|
+
* @returns If placement is `left` or `right` it returns the vertical tranlsation to align the `Tooltip` center with its `children` center,
|
|
143
|
+
* otherwise `null` is returned
|
|
144
|
+
*/
|
|
145
|
+
export const getTooltipVerticalAlignment = (placement: Placement, childrenHeight: number, tooltipHeight?: number) => {
|
|
146
|
+
if ((placement === "left" || placement === "right") && tooltipHeight) {
|
|
147
|
+
return {
|
|
148
|
+
transform: [
|
|
149
|
+
{
|
|
150
|
+
translateY:
|
|
151
|
+
-tooltipHeight / 2 + childrenHeight / 2
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* A utility function to calculate the `Arrow` vertical alignment
|
|
161
|
+
* @param placement The `Tooltip` placement in relation of its children
|
|
162
|
+
* @param childrenHeight The `Tooltip`'s children height
|
|
163
|
+
*/
|
|
164
|
+
export const getArrowVerticalAlignment = (placement: Placement, childrenHeight: number) => {
|
|
165
|
+
if (placement === "left" || placement === "right") {
|
|
166
|
+
return {
|
|
167
|
+
transform: [
|
|
168
|
+
{
|
|
169
|
+
translateY:
|
|
170
|
+
-ARROW_WIDTH / 2 + childrenHeight / 2
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export const isDefined = <T>(v: T) => v !== undefined;
|
|
179
|
+
export const isNotZero = (v: number) => v !== 0;
|