Skip to content

Commit f50608d

Browse files
committed
feat(json-crdt-peritext-ui): 🎸 position focus toolbar using entangled portal
1 parent 28a6901 commit f50608d

File tree

2 files changed

+39
-3
lines changed

2 files changed

+39
-3
lines changed

‎src/json-crdt-peritext-ui/plugins/toolbar/cursor/focus/RenderFocus.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import * as React from 'react';
22
import {CaretToolbar} from 'nice-ui/lib/4-card/Toolbar/ToolbarMenu/CaretToolbar';
3-
import {MoveToViewport} from 'nice-ui/lib/utils/popup/MoveToViewport';
43
import {useToolbarPlugin} from '../../context';
54
import {useSyncStore, useSyncStoreOpt, useTimeout} from '../../../../web/react/hooks';
65
import {CaretFrame} from '../util/CaretFrame';
76
import {FormattingsNewPane} from '../../formatting/FormattingsNewPane';
87
import {BottomPanePortal} from '../util/BottomPanePortal';
8+
import {TopPanePortal} from '../util/TopPanePortal';
99
import type {CaretViewProps} from '../../../../web/react/cursor/CaretView';
1010

1111
export interface RenderFocusProps extends CaretViewProps {
@@ -32,13 +32,13 @@ export const RenderFocus: React.FC<RenderFocusProps> = ({children, cursor}) => {
3232

3333
if (!formatting && showInlineToolbarValue && !isScrubbing && toolbar.txt.editor.mainCursor() === cursor)
3434
over = (
35-
<MoveToViewport>
35+
<TopPanePortal>
3636
<CaretToolbar
3737
disabled={!enableAfterCoolDown /* || (!focus && blurTimeout) */}
3838
menu={toolbar.getSelectionMenu()}
3939
onPopupClose={handleClose}
4040
/>
41-
</MoveToViewport>
41+
</TopPanePortal>
4242
);
4343

4444
if (!!formatting && showInlineToolbarValue && !isScrubbing && toolbar.txt.editor.mainCursor() === cursor) {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// biome-ignore lint: lint/style/useImportType
2+
import * as React from 'react';
3+
import {rule} from 'nano-theme';
4+
import {EntangledPortal, type EntangledPortalStateOpts} from '../../../../components/EntangledPortal';
5+
6+
const spanClass = rule({
7+
pe: 'none',
8+
});
9+
10+
const gap = 4;
11+
const position: EntangledPortalStateOpts['position'] = (base, dest) => {
12+
let x = base.x - (dest.width >> 1);
13+
let y = base.y - dest.height;
14+
if (x < gap) x = gap;
15+
else if (x + dest.width + gap > window.innerWidth) x = window.innerWidth - dest.width - gap;
16+
return [x, y];
17+
};
18+
19+
const span = {className: spanClass};
20+
21+
const entangledProps = {
22+
position,
23+
span,
24+
};
25+
26+
export interface TopPanePortalProps {
27+
children: React.ReactNode;
28+
}
29+
30+
export const TopPanePortal: React.FC<TopPanePortalProps> = ({children}) => {
31+
return (
32+
<EntangledPortal {...entangledProps}>
33+
{children}
34+
</EntangledPortal>
35+
);
36+
};

0 commit comments

Comments
 (0)