Skip to content

Commit e8f7827

Browse files
committed
Support floating strategy and named portals
Allow ActionButtons to use fixed positioning via a floatingStrategy prop passed to useFloating. Support portal="aboveNavigationWidgets" to render action buttons in the high z-index portal used by hotspot tooltips. Turn linkPreviewFloatingStrategy into a general floatingStrategy prop on EditableLink so it applies to both the link tooltip and the action buttons.
1 parent f63d853 commit e8f7827

5 files changed

Lines changed: 66 additions & 8 deletions

File tree

entry_types/scrolled/package/spec/frontend/inlineEditing/ActionButtons-spec.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {ActionButtons} from 'frontend/inlineEditing/ActionButtons';
55
import {render} from '@testing-library/react';
66
import userEvent from '@testing-library/user-event';
77
import '@testing-library/jest-dom/extend-expect';
8+
import 'support/toHaveAncestorWithInlineStyle';
89

910
describe('ActionButtons', () => {
1011
it('renders icons and texts for each button', () => {
@@ -45,6 +46,34 @@ describe('ActionButtons', () => {
4546
expect(button).toHaveTextContent('');
4647
});
4748

49+
it('uses absolute position for floating element by default', () => {
50+
const {getByRole} = render(
51+
<ActionButtons buttons={[{icon: 'pencil', text: 'Edit'}]} />
52+
);
53+
54+
expect(getByRole('button', {name: 'Edit'})).toHaveAncestorWithInlineStyle('position: absolute');
55+
});
56+
57+
it('supports floatingStrategy prop', () => {
58+
const {getByRole} = render(
59+
<ActionButtons buttons={[{icon: 'pencil', text: 'Edit'}]}
60+
floatingStrategy="fixed" />
61+
);
62+
63+
expect(getByRole('button', {name: 'Edit'})).toHaveAncestorWithInlineStyle('position: fixed');
64+
});
65+
66+
it('renders in portal with given id when portal is a string', () => {
67+
render(
68+
<ActionButtons buttons={[{icon: 'pencil', text: 'Edit'}]}
69+
portal="aboveNavigationWidgets" />
70+
);
71+
72+
const portalContainer = document.getElementById('floating-ui-above-navigation-widgets');
73+
expect(portalContainer).toBeInTheDocument();
74+
expect(portalContainer.querySelector('button')).toBeInTheDocument();
75+
});
76+
4877
it('triggers individual click handlers', async () => {
4978
const onEdit = jest.fn();
5079
const onLink = jest.fn();

entry_types/scrolled/package/spec/frontend/inlineEditing/EditableLink-spec.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import {useFakeTranslations} from 'pageflow/testHelpers';
88
import {renderInContentElement} from 'pageflow-scrolled/testHelpers';
99
import {render, screen, waitFor} from '@testing-library/react';
1010
import userEvent from '@testing-library/user-event';
11-
import '@testing-library/jest-dom/extend-expect'
11+
import '@testing-library/jest-dom/extend-expect';
12+
import 'support/toHaveAncestorWithInlineStyle';
1213

1314
jest.mock('frontend/inlineEditing/useSelectLinkDestination');
1415

@@ -152,6 +153,17 @@ describe('EditableLink', () => {
152153
expect(onChange).toHaveBeenCalledWith(null);
153154
});
154155

156+
it('passes floatingStrategy to action buttons', () => {
157+
render(
158+
<EditableLink actionButtonVisible={true}
159+
floatingStrategy="fixed">
160+
Some link
161+
</EditableLink>
162+
);
163+
164+
expect(screen.getByRole('button', {name: 'Select link destination'})).toHaveAncestorWithInlineStyle('position: fixed');
165+
});
166+
155167
it('triggers onClick when tooltip is clicked', async () => {
156168
const onClick = jest.fn();
157169
const user = userEvent.setup();
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
expect.extend({
2+
toHaveAncestorWithInlineStyle(element, styleSubstring) {
3+
const ancestor = element.closest(`[style*="${styleSubstring}"]`);
4+
const pass = !!ancestor;
5+
6+
return {
7+
pass,
8+
message: () => pass
9+
? `expected element not to have ancestor with inline style '${styleSubstring}'`
10+
: `expected element to have ancestor with inline style '${styleSubstring}'`
11+
};
12+
}
13+
});

entry_types/scrolled/package/src/frontend/inlineEditing/ActionButtons.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ const icons = {
2121
unlink
2222
};
2323

24-
export function ActionButtons({buttons, position, portal, size = 'md'}) {
24+
export function ActionButtons({buttons, position, portal, floatingStrategy, size = 'md'}) {
2525
const iconSize = size === 'md' ? 15 : 20;
2626

2727
const {refs, floatingStyles, middlewareData} = useFloating({
28+
strategy: floatingStrategy,
2829
placement: position === 'center' ? 'bottom' :
2930
position === 'inside' ? 'top-end' :
3031
position === 'outsideLeft' ? 'bottom-start' :
@@ -41,7 +42,8 @@ export function ActionButtons({buttons, position, portal, size = 'md'}) {
4142
<span className={classNames(styles.reference,
4243
styles[`position-${position}`])}
4344
ref={refs.setReference}>
44-
<Portal enabled={portal}>
45+
<Portal enabled={!!portal}
46+
aboveNavigationWidgets={portal === 'aboveNavigationWidgets'}>
4547
<div ref={refs.setFloating}
4648
className={classNames(styles.floating, {[styles.escaped]: middlewareData.hide?.escaped})}
4749
style={floatingStyles}>
@@ -68,12 +70,13 @@ export function ActionButtons({buttons, position, portal, size = 'md'}) {
6870
);
6971
}
7072

71-
function Portal({enabled, children}) {
73+
function Portal({enabled, aboveNavigationWidgets, children}) {
7274
const floatingPortalRoot = useFloatingPortalRoot();
7375

7476
if (enabled) {
7577
return (
76-
<FloatingPortal root={floatingPortalRoot}>
78+
<FloatingPortal id={aboveNavigationWidgets ? 'floating-ui-above-navigation-widgets' : undefined}
79+
root={floatingPortalRoot}>
7780
{children}
7881
</FloatingPortal>
7982
);

entry_types/scrolled/package/src/frontend/inlineEditing/EditableLink.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export function EditableLink({
1616
linkPreviewDisabled,
1717
linkPreviewPosition = 'below',
1818
linkPreviewAlign = 'center',
19-
linkPreviewFloatingStrategy,
19+
floatingStrategy,
2020
actionButtonPosition = 'outside',
2121
actionButtonVisible = 'whenSelected',
2222
actionButtonPortal,
@@ -42,7 +42,7 @@ export function EditableLink({
4242
return (
4343
<div className={styles.wrapper}>
4444
<LinkTooltipProvider position={linkPreviewPosition}
45-
floatingStrategy={linkPreviewFloatingStrategy}
45+
floatingStrategy={floatingStrategy}
4646
align={linkPreviewAlign}
4747
onClick={onClick}
4848
gap={5}>
@@ -64,7 +64,8 @@ export function EditableLink({
6464
text: t('pageflow_scrolled.inline_editing.remove_link'),
6565
onClick: handleRemoveLink}] : [])]}
6666
position={actionButtonPosition}
67-
portal={actionButtonPortal} />}
67+
portal={actionButtonPortal}
68+
floatingStrategy={floatingStrategy} />}
6869
</div>
6970
);
7071
}

0 commit comments

Comments
 (0)