Skip to content

Commit e581ecf

Browse files
authored
Issue 1500: allows useOverlayPosition to take a prop maxHeight (#2673)
1 parent e2b459a commit e581ecf

File tree

4 files changed

+55
-17
lines changed

4 files changed

+55
-17
lines changed

packages/@react-aria/overlays/src/calculatePosition.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ interface PositionOpts {
5858
shouldFlip: boolean,
5959
boundaryElement: HTMLElement,
6060
offset: number,
61-
crossOffset: number
61+
crossOffset: number,
62+
maxHeight?: number
6263
}
6364

6465
export interface PositionResult {
@@ -284,7 +285,8 @@ export function calculatePositionInternal(
284285
containerOffsetWithBoundary: Offset,
285286
offset: number,
286287
crossOffset: number,
287-
isContainerPositioned: boolean
288+
isContainerPositioned: boolean,
289+
userSetMaxHeight?: number
288290
): PositionResult {
289291
let placementInfo = parsePlacement(placementInput);
290292
let {size, crossAxis, crossSize, placement, crossPlacement} = placementInfo;
@@ -332,6 +334,10 @@ export function calculatePositionInternal(
332334
padding
333335
);
334336

337+
if (userSetMaxHeight && userSetMaxHeight < maxHeight) {
338+
maxHeight = userSetMaxHeight;
339+
}
340+
335341
overlaySize.height = Math.min(overlaySize.height, maxHeight);
336342

337343
position = computePosition(childOffset, boundaryDimensions, overlaySize, placementInfo, normalizedOffset, crossOffset, containerOffsetWithBoundary, isContainerPositioned);
@@ -363,7 +369,8 @@ export function calculatePosition(opts: PositionOpts): PositionResult {
363369
shouldFlip,
364370
boundaryElement,
365371
offset,
366-
crossOffset
372+
crossOffset,
373+
maxHeight
367374
} = opts;
368375

369376
let container = overlayNode.offsetParent || document.body;
@@ -398,6 +405,7 @@ export function calculatePosition(opts: PositionOpts): PositionResult {
398405
containerOffsetWithBoundary,
399406
offset,
400407
crossOffset,
401-
isContainerPositioned
408+
isContainerPositioned,
409+
maxHeight
402410
);
403411
}

packages/@react-aria/overlays/src/useOverlayPosition.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,12 @@ interface AriaPositionProps extends PositionProps {
4242
*/
4343
shouldUpdatePosition?: boolean,
4444
/** Handler that is called when the overlay should close. */
45-
onClose?: () => void
45+
onClose?: () => void,
46+
/**
47+
* The maxHeight specified for the overlay element.
48+
* By default, it will take all space up to the current viewport height.
49+
*/
50+
maxHeight?: number
4651
}
4752

4853
interface PositionAria {
@@ -77,7 +82,8 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
7782
crossOffset = 0,
7883
shouldUpdatePosition = true,
7984
isOpen = true,
80-
onClose
85+
onClose,
86+
maxHeight
8187
} = props;
8288
let [position, setPosition] = useState<PositionResult>({
8389
position: {},
@@ -99,7 +105,8 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
99105
offset,
100106
crossOffset,
101107
isOpen,
102-
direction
108+
direction,
109+
maxHeight
103110
];
104111

105112
let updatePosition = useCallback(() => {
@@ -117,7 +124,8 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
117124
shouldFlip,
118125
boundaryElement,
119126
offset,
120-
crossOffset
127+
crossOffset,
128+
maxHeight
121129
})
122130
);
123131
}, deps);

packages/@react-aria/overlays/stories/UseOverlayPosition.stories.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ import {useOverlayTriggerState} from '@react-stately/overlays';
2020

2121
function Trigger(props: {
2222
withPortal: boolean,
23-
placement: Placement
23+
placement: Placement,
24+
maxHeight?: number
2425
}) {
25-
const {withPortal, placement} = props;
26+
const {withPortal, placement, maxHeight} = props;
2627
const targetRef = React.useRef<HTMLButtonElement>(null);
2728
const overlayRef = React.useRef<HTMLDivElement>(null);
2829
const state = useOverlayTriggerState({
@@ -37,7 +38,8 @@ function Trigger(props: {
3738
shouldFlip: false,
3839
isOpen: state.isOpen,
3940
offset: 10,
40-
placement
41+
placement,
42+
maxHeight
4143
});
4244

4345
let overlay = (
@@ -56,11 +58,10 @@ function Trigger(props: {
5658
margin: 0,
5759
listStyleType: 'none'
5860
}}>
59-
<li>Hello</li>
60-
<li>Hello</li>
61-
<li>Hello</li>
62-
<li>Hello</li>
63-
<li>Hello</li>
61+
{maxHeight
62+
? [...Array(20)].map((_, i) => <li>Hello {i}</li>)
63+
: [...Array(5)].map((_, i) => <li>Hello {i}</li>)
64+
}
6465
</ul>
6566
</div>
6667
);
@@ -80,4 +81,6 @@ storiesOf('UseOverlayPosition', module)
8081
.add('document.body container bottom', () => <Trigger withPortal placement="bottom" />)
8182
.add('document.body container top', () => <Trigger withPortal placement="top" />)
8283
.add('positioned container bottom', () => <Trigger withPortal={false} placement="bottom" />)
83-
.add('positioned container top', () => <Trigger withPortal={false} placement="top" />);
84+
.add('positioned container top', () => <Trigger withPortal={false} placement="top" />)
85+
.add('maxHeight=200 container bottom', () => <Trigger withPortal maxHeight={200} placement="bottom" />)
86+
.add('maxHeight=200 container top', () => <Trigger withPortal maxHeight={200} placement="top" />);

packages/@react-aria/overlays/test/useOverlayPosition.test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,25 @@ describe('useOverlayPosition', function () {
129129
`);
130130
});
131131

132+
it('should update the overlay\'s maxHeight by the given one if it\'s smaller than available viewport height.', function () {
133+
let res = render(<Example maxHeight={450} />);
134+
let overlay = res.getByTestId('overlay');
135+
136+
expect(overlay).toHaveStyle(`
137+
left: 12px;
138+
top: 350px;
139+
max-height: 406px;
140+
`);
141+
142+
res.rerender(<Example maxHeight={150} />);
143+
144+
expect(overlay).toHaveStyle(`
145+
left: 12px;
146+
top: 350px;
147+
max-height: 150px;
148+
`);
149+
});
150+
132151
it('should close the overlay when the trigger scrolls', function () {
133152
let onClose = jest.fn();
134153
let res = render(

0 commit comments

Comments
 (0)