Skip to content

Commit ee26b26

Browse files
authored
[AP-5625] Add optional popover and tooltip adjustment to viewport size (#751)
* add popover adjustment to viewport * fix knip * add functionality to tooltip * add description to properties * attach listener only when option enabled * some refactoring * pass props in navigation bar
1 parent 0102600 commit ee26b26

9 files changed

Lines changed: 131 additions & 4 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ When you create a PR against the `master` branch, Amplify creates a new deployme
105105
Below versions of Node & npm are required:
106106

107107
```bash
108-
"node": ">=18.12.0",
109-
"npm": ">=8.19.2"
108+
"node": ">=22.14.0",
109+
"npm": ">=10.9.2"
110110
```
111111

112112
Run `nvm use` to use supported version.

src/components/extendedPopover/ExtendedPopover.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from "react";
22
import * as ReactDOM from "react-dom";
33
import styled from "styled-components";
4-
import { useDisclosure, useEvent } from "../../hooks";
4+
import { useDisclosure, useEvent, useViewportDimensions } from "../../hooks";
55
import theme from "../../theme";
66
import {
77
Placement,
@@ -24,6 +24,7 @@ type Props = {
2424
clickOutsideToClose?: boolean;
2525
triggerOn?: ExtendedPopoverAction;
2626
closeOn?: ExtendedPopoverAction;
27+
adjustPositionToViewportSize?: boolean;
2728
};
2829

2930
export function ExtendedPopover({
@@ -39,6 +40,7 @@ export function ExtendedPopover({
3940
margin = DEFAULT_MARGIN,
4041
triggerOn = "click",
4142
closeOn = "click",
43+
adjustPositionToViewportSize = false,
4244
}: Props) {
4345
const triggerRef = React.useRef<HTMLDivElement>(null);
4446
const contentRef = React.useRef<HTMLDivElement>(null);
@@ -49,6 +51,10 @@ export function ExtendedPopover({
4951
const [triggerDimensions, setTriggerDimensions] =
5052
React.useState<DOMRect | null>(null);
5153

54+
const viewportDimensions = useViewportDimensions(
55+
adjustPositionToViewportSize
56+
);
57+
5258
React.useEffect(() => {
5359
if (
5460
isOpen &&
@@ -107,8 +113,10 @@ export function ExtendedPopover({
107113
const style = getStyle({
108114
wrapperDimensions: triggerDimensions,
109115
tooltipDimensions: contentDimensions,
116+
viewportDimensions,
110117
placement,
111118
position,
119+
adjustPositionToViewportSize,
112120
});
113121

114122
return (

src/components/extendedPopover/Props.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
> - `position`: `start | end | center` {optional} relative shift of popover placement compared to parent element, defaults to `start`
55
> - `margin`: `{ top?: number, bottom?: number, left?: number, right?: number }` {optional} margin around popover, defaults to `5px` for all properties
66
> - `clickOutsideToClose`: `boolean` {optional} should popover be closed when clicked outside, defaults to `true`
7+
> - `adjustPositionToViewportSize`: `boolean` {optional} should popover position be adjusted to viewport size if calculated position is outside of viewport dimensions, defaults to `false`

src/components/extendedTooltip/ExtendedTooltip.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from "react";
22
import * as ReactDOM from "react-dom";
33
import styled, { css } from "styled-components";
4-
import { useEvent } from "../../hooks";
4+
import { useEvent, useViewportDimensions } from "../../hooks";
55
import theme from "../../theme";
66
import {
77
DEFAULT_MARGIN,
@@ -29,6 +29,7 @@ export type ExtendedTooltipProps = Timeout & {
2929
status?: Status;
3030
zIndex?: number;
3131
display?: Display;
32+
adjustPositionToViewportSize?: boolean;
3233
};
3334

3435
export function ExtendedTooltip({
@@ -42,10 +43,15 @@ export function ExtendedTooltip({
4243
margin = DEFAULT_MARGIN,
4344
zIndex = theme.zindex.sticky,
4445
display,
46+
adjustPositionToViewportSize = false,
4547
}: ExtendedTooltipProps) {
4648
const wrapperRef = React.useRef<HTMLDivElement>(null);
4749
const tooltipRef = React.useRef<HTMLDivElement>(null);
4850

51+
const viewportDimensions = useViewportDimensions(
52+
adjustPositionToViewportSize
53+
);
54+
4955
const { isHovered, updateIsHovered } = useTooltipHover();
5056

5157
const [tooltipDimensions, setTooltipDimensions] =
@@ -78,9 +84,11 @@ export function ExtendedTooltip({
7884
...getStyle({
7985
wrapperDimensions,
8086
tooltipDimensions,
87+
viewportDimensions,
8188
placement,
8289
position,
8390
margin,
91+
adjustPositionToViewportSize,
8492
}),
8593
zIndex,
8694
};

src/components/extendedTooltip/Props.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
> - `showTimeout`: `number` {optional} delay time in miliseconds for opening of tooltip
77
> - `hideTimeout`: `number` {optional} delay time in miliseconds for closing of tooltip
88
> - `zIndex`: `number` {optional} z-index of the tooltip (default is whatever theme.zindex.sticky is set to)
9+
> - `adjustPositionToViewportSize`: `boolean` {optional} should popover position be adjusted to viewport size if calculated position is outside of viewport dimensions, defaults to `false`

src/components/navigationBar/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export type PopoverConfig = {
2828
triggerOn: ExtendedPopoverAction;
2929
closeOn: ExtendedPopoverAction;
3030
position: Position;
31+
adjustPositionToViewportSize?: boolean;
3132
};
3233

3334
export type ExpandableConfig = {

src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from "./useClickOutside";
22
export * from "./useEvent";
33
export * from "./useDisclosure";
4+
export * from "./useViewportDimensions";

src/hooks/useViewportDimensions.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { useState, useEffect } from "react";
2+
import { getViewportDimensions } from "../utils/position";
3+
4+
export function useViewportDimensions(addResizeListener: boolean) {
5+
const [viewportDimensions, setViewportDimensions] = useState(
6+
getViewportDimensions
7+
);
8+
9+
useEffect(() => {
10+
function handleResize() {
11+
setViewportDimensions(getViewportDimensions());
12+
}
13+
if (addResizeListener) {
14+
window.addEventListener("resize", handleResize);
15+
return () => window.removeEventListener("resize", handleResize);
16+
}
17+
18+
return () => {};
19+
}, [addResizeListener]);
20+
return viewportDimensions;
21+
}

src/utils/position.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,19 @@ export type Margin = {
1111
right?: number;
1212
};
1313

14+
type ViewportDimensions = {
15+
width: number;
16+
height: number;
17+
};
18+
1419
type StyleProps = {
1520
wrapperDimensions: DOMRect | null;
1621
tooltipDimensions: DOMRect | null;
22+
viewportDimensions: ViewportDimensions;
1723
placement: Placement;
1824
position: Position;
1925
margin?: Margin;
26+
adjustPositionToViewportSize?: boolean;
2027
};
2128

2229
type ReqiuredStyleProps = StyleProps & {
@@ -27,26 +34,43 @@ type ReqiuredStyleProps = StyleProps & {
2734
export function getStyle({
2835
wrapperDimensions,
2936
tooltipDimensions,
37+
viewportDimensions,
3038
placement,
3139
position,
3240
margin,
41+
adjustPositionToViewportSize,
3342
}: StyleProps) {
3443
if (wrapperDimensions != null && tooltipDimensions != null) {
3544
const top = getTop({
3645
wrapperDimensions,
3746
tooltipDimensions,
47+
viewportDimensions,
3848
placement,
3949
position,
4050
margin,
4151
});
4252
const left = getLeft({
4353
wrapperDimensions,
4454
tooltipDimensions,
55+
viewportDimensions,
4556
placement,
4657
position,
4758
margin,
4859
});
4960

61+
if (adjustPositionToViewportSize) {
62+
const adjustToViewport = adjustPositionToViewportDimensions(
63+
{ top, left },
64+
tooltipDimensions,
65+
viewportDimensions
66+
);
67+
68+
const adjustedTop = top + adjustToViewport.top;
69+
const adjustedLeft = left + adjustToViewport.left;
70+
71+
return { top: adjustedTop, left: adjustedLeft };
72+
}
73+
5074
return { top, left };
5175
}
5276

@@ -155,6 +179,68 @@ function getVerticalAlignmentToEnd(
155179
);
156180
}
157181

182+
function adjustPositionToViewportDimensions(
183+
style: { top: number; left: number },
184+
tooltipDimensions: DOMRect,
185+
viewportDimensions: ViewportDimensions
186+
) {
187+
const adjustTop = adjustTopToViewportDimensions(
188+
style,
189+
tooltipDimensions,
190+
viewportDimensions
191+
);
192+
const adjustLeft = adjustLeftToViewportDimensions(
193+
style,
194+
tooltipDimensions,
195+
viewportDimensions
196+
);
197+
198+
return { top: adjustTop, left: adjustLeft };
199+
}
200+
201+
function adjustTopToViewportDimensions(
202+
style: { top: number; left: number },
203+
tooltipDimensions: DOMRect,
204+
viewportDimensions: ViewportDimensions
205+
) {
206+
if (style.top < 0) {
207+
return 0 - style.top;
208+
}
209+
210+
const tooltipBottom = style.top + tooltipDimensions.height;
211+
212+
if (tooltipBottom > viewportDimensions.height) {
213+
return viewportDimensions.height - tooltipBottom;
214+
}
215+
216+
return 0;
217+
}
218+
219+
function adjustLeftToViewportDimensions(
220+
style: { top: number; left: number },
221+
tooltipDimensions: DOMRect,
222+
viewportDimensions: ViewportDimensions
223+
) {
224+
if (style.left < 0) {
225+
return 0 - style.left;
226+
}
227+
228+
const tooltipRight = style.left + tooltipDimensions.width;
229+
230+
if (tooltipRight > viewportDimensions.width) {
231+
return viewportDimensions.width - tooltipRight;
232+
}
233+
234+
return 0;
235+
}
236+
237+
export function getViewportDimensions() {
238+
return {
239+
width: document.documentElement.clientWidth,
240+
height: document.documentElement.clientHeight,
241+
};
242+
}
243+
158244
export const DEFAULT_MARGIN = {
159245
top: 5,
160246
bottom: 5,

0 commit comments

Comments
 (0)