Skip to content

Commit 40e4d76

Browse files
committed
fix(YfmTable): fixed positioning of floating controls when scrolling horizontally in table
1 parent 7bdb5c3 commit 40e4d76

File tree

11 files changed

+475
-186
lines changed

11 files changed

+475
-186
lines changed
Lines changed: 88 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {useState} from 'react';
1+
import {forwardRef, useState} from 'react';
22

33
import {Ellipsis as DotsColumn, GripHorizontal as GripColumn} from '@gravity-ui/icons';
44
import {
@@ -12,7 +12,7 @@ import {
1212

1313
import {useBooleanState} from 'src/react-utils';
1414

15-
import {FloatingPopup, type FloatingPopupProps} from '../FloatingPopup';
15+
import {FloatingPopup, type FloatingPopupProps, type FloatingPopupRef} from '../FloatingPopup';
1616

1717
const popupOffset: FloatingPopupProps['offset'] = {
1818
mainAxis: -9.5,
@@ -21,88 +21,104 @@ const popupOffset: FloatingPopupProps['offset'] = {
2121
export type FloatingMenuProps = {
2222
dirtype: 'row' | 'column';
2323
canDrag: boolean;
24-
anchorElement: Element;
24+
anchorElement: NonNullable<FloatingPopupProps['anchorElement']>;
2525
dropdownItems: DropdownMenuProps<unknown>['items'];
2626
switcherMouseProps?: Pick<
2727
ButtonButtonProps,
2828
'onMouseDown' | 'onMouseMove' | 'onMouseUp' | 'onMouseLeave'
2929
>;
3030
onOpenToggle: NonNullable<DropdownMenuProps<unknown>['onOpenToggle']>;
31+
floatingStyles?: FloatingPopupProps['floatingStyles'];
3132
};
3233

33-
export const FloatingMenu: React.FC<FloatingMenuProps> = function YfmTableFloatingMenu(props) {
34-
const {dirtype, canDrag, anchorElement, dropdownItems, switcherMouseProps, onOpenToggle} =
35-
props;
34+
export const FloatingMenu = forwardRef<FloatingPopupRef, FloatingMenuProps>(
35+
function YfmTableFloatingMenu(props, ref) {
36+
const {
37+
dirtype,
38+
canDrag,
39+
anchorElement,
40+
dropdownItems,
41+
switcherMouseProps,
42+
floatingStyles,
43+
onOpenToggle,
44+
} = props;
3645

37-
const [isMenuOpened, setMenuOpened] = useState(false);
38-
const [isHovered, setHovered, unsetHovered] = useBooleanState(false);
46+
const [isMenuOpened, setMenuOpened] = useState(false);
47+
const [isHovered, setHovered, unsetHovered] = useBooleanState(false);
3948

40-
const showActionView = isMenuOpened || isHovered;
41-
const isRowType = dirtype === 'row';
49+
const showActionView = isMenuOpened || isHovered;
50+
const isRowType = dirtype === 'row';
4251

43-
return (
44-
<FloatingPopup
45-
open
46-
offset={popupOffset}
47-
anchorElement={anchorElement}
48-
placement={isRowType ? 'left' : 'top'}
49-
floatingStyles={{
50-
lineHeight: 'initial',
51-
}}
52-
style={{
53-
backgroundColor: 'transparent',
54-
}}
55-
>
56-
<DropdownMenu
57-
onOpenToggle={(...args) => {
58-
setMenuOpened(...args);
59-
onOpenToggle(...args);
52+
return (
53+
<FloatingPopup
54+
open
55+
ref={ref}
56+
offset={popupOffset}
57+
anchorElement={anchorElement}
58+
placement={isRowType ? 'left' : 'top'}
59+
floatingStyles={{
60+
lineHeight: 'initial',
61+
...floatingStyles,
6062
}}
61-
renderSwitcher={(switcherProps) => (
62-
<Flex
63-
centerContent
64-
width={20} // xs button
65-
height={20} // xs button
66-
style={{
67-
borderRadius: 'var(--g-border-radius-xs)',
68-
backgroundColor: showActionView
69-
? 'var(--g-color-base-background)'
70-
: undefined,
71-
}}
72-
onMouseEnter={setHovered}
73-
onMouseLeave={unsetHovered}
74-
>
75-
<Button
63+
style={{
64+
backgroundColor: 'transparent',
65+
}}
66+
>
67+
<DropdownMenu
68+
onOpenToggle={(...args) => {
69+
setMenuOpened(...args);
70+
onOpenToggle(...args);
71+
}}
72+
renderSwitcher={(switcherProps) => (
73+
<Flex
74+
centerContent
75+
width={20} // xs button
76+
height={20} // xs button
7677
style={{
77-
cursor: canDrag ? 'grab' : undefined,
78-
transform: isRowType ? 'rotate(90deg)' : undefined,
79-
'--g-button-height': showActionView ? undefined : '5px',
80-
'--_--background-color': showActionView
81-
? undefined
82-
: 'var(--g-color-base-background)',
78+
borderRadius: 'var(--g-border-radius-xs)',
79+
backgroundColor: showActionView
80+
? 'var(--g-color-base-background)'
81+
: undefined,
8382
}}
84-
view={isMenuOpened ? 'outlined-action' : 'outlined'}
85-
pin={showActionView ? 'round-round' : 'circle-circle'}
86-
size="xs"
87-
qa={isRowType ? 'g-md-yfm-table-row-btn' : 'g-md-yfm-table-column-btn'}
88-
{...switcherProps}
89-
{...switcherMouseProps}
83+
onMouseEnter={setHovered}
84+
onMouseLeave={unsetHovered}
9085
>
91-
{showActionView ? (
92-
<Icon data={canDrag ? GripColumn : DotsColumn} />
93-
) : (
94-
String.fromCharCode(8194) // en space
95-
)}
96-
</Button>
97-
</Flex>
98-
)}
99-
popupProps={{
100-
zIndex: 1010,
101-
placement: isRowType ? 'right-start' : 'bottom-start',
102-
}}
103-
menuProps={{qa: `g-md-yfm-table-${dirtype}-menu`}}
104-
items={dropdownItems}
105-
/>
106-
</FloatingPopup>
107-
);
108-
};
86+
<Button
87+
style={{
88+
cursor: canDrag ? 'grab' : undefined,
89+
transform: isRowType ? 'rotate(90deg)' : undefined,
90+
'--g-button-height': showActionView ? undefined : '5px',
91+
'--_--background-color': showActionView
92+
? undefined
93+
: 'var(--g-color-base-background)',
94+
}}
95+
view={isMenuOpened ? 'outlined-action' : 'outlined'}
96+
pin={showActionView ? 'round-round' : 'circle-circle'}
97+
size="xs"
98+
qa={
99+
isRowType
100+
? 'g-md-yfm-table-row-btn'
101+
: 'g-md-yfm-table-column-btn'
102+
}
103+
{...switcherProps}
104+
{...switcherMouseProps}
105+
>
106+
{showActionView ? (
107+
<Icon data={canDrag ? GripColumn : DotsColumn} />
108+
) : (
109+
String.fromCharCode(8194) // en space
110+
)}
111+
</Button>
112+
</Flex>
113+
)}
114+
popupProps={{
115+
zIndex: 1010,
116+
placement: isRowType ? 'right-start' : 'bottom-start',
117+
}}
118+
menuProps={{qa: `g-md-yfm-table-${dirtype}-menu`}}
119+
items={dropdownItems}
120+
/>
121+
</FloatingPopup>
122+
);
123+
},
124+
);

src/extensions/yfm/YfmTable/plugins/YfmTableControls/components/FloatingMenuControl/FloatingMenuControl.tsx

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import {useMemo} from 'react';
1+
import {useMemo, useRef} from 'react';
22

3+
// eslint-disable-next-line import/no-extraneous-dependencies
4+
import type {ReferenceElement} from '@floating-ui/dom';
35
import {
46
ArrowDown,
57
ArrowLeft,
@@ -15,11 +17,16 @@ import {i18n} from 'src/i18n/yfm-table';
1517

1618
import type {DnDControlHandler} from '../../dnd/dnd';
1719
import {FloatingMenu, type FloatingMenuProps} from '../FloatingMenu/FloatingMenu';
20+
import type {FloatingPopupRef} from '../FloatingPopup';
21+
// import {useRafThrottle} from '../hooks/use-raf-throttle';
22+
23+
type ControlType = FloatingMenuProps['dirtype'];
1824

1925
export type FloatingMenuControlProps = {
20-
acnhorElement: Element;
26+
tableElement: Element;
27+
anchorElement: ReferenceElement;
2128
multiple: boolean;
22-
type: FloatingMenuProps['dirtype'];
29+
type: ControlType;
2330
dndHandler?: DnDControlHandler;
2431
onMenuOpenToggle: FloatingMenuProps['onOpenToggle'];
2532
onClearCellsClick: () => void;
@@ -34,7 +41,8 @@ export const FloatingMenuControl: React.FC<FloatingMenuControlProps> =
3441
type,
3542
multiple,
3643
dndHandler,
37-
acnhorElement,
44+
// tableElement,
45+
anchorElement,
3846
onMenuOpenToggle,
3947
onClearCellsClick,
4048
onInsertBeforeClick,
@@ -94,12 +102,49 @@ export const FloatingMenuControl: React.FC<FloatingMenuControlProps> =
94102
],
95103
);
96104

105+
const floatingRef = useRef<FloatingPopupRef>(null);
106+
// const [visible, setVisible] = useState(true);
107+
108+
// const updateVisibility = () => {
109+
// const newVisible = shouldBeVisible(
110+
// type,
111+
// tableElement,
112+
// floatingRef.current?.floating.current,
113+
// );
114+
// console.log('updateVisibility', {
115+
// newVisible,
116+
// floating: floatingRef.current?.floating.current,
117+
// tableElement,
118+
// });
119+
// if (visible !== newVisible) setVisible(newVisible);
120+
// };
121+
122+
// const onChange = useRafThrottle(() => {
123+
// floatingRef.current?.forceUpdate();
124+
// updateVisibility();
125+
// });
126+
127+
// useEffect(() => {
128+
// if (type !== 'column') return undefined;
129+
130+
// const observer = new ResizeObserver(onChange);
131+
// observer.observe(tableElement);
132+
// tableElement.addEventListener('scroll', onChange);
133+
134+
// return () => {
135+
// observer.unobserve(tableElement);
136+
// tableElement.removeEventListener('scroll', onChange);
137+
// };
138+
// }, [tableElement, onChange, type]);
139+
97140
return (
98141
<FloatingMenu
99142
dirtype={type}
143+
ref={floatingRef}
144+
// floatingStyles={visible ? undefined : {visibility: 'hidden'}}
100145
canDrag={dndHandler ? dndHandler.canDrag() : false}
101146
onOpenToggle={onMenuOpenToggle}
102-
anchorElement={acnhorElement}
147+
anchorElement={anchorElement}
103148
switcherMouseProps={
104149
dndHandler
105150
? {
@@ -114,3 +159,18 @@ export const FloatingMenuControl: React.FC<FloatingMenuControlProps> =
114159
/>
115160
);
116161
};
162+
163+
// function shouldBeVisible(
164+
// type: ControlType,
165+
// tableElem: Element,
166+
// floatingElem: Element | null | undefined,
167+
// ): boolean {
168+
// if (type !== 'column' || !floatingElem) return true;
169+
170+
// const floatingRect = floatingElem.getBoundingClientRect();
171+
// const tableRect = tableElem.getBoundingClientRect();
172+
173+
// const floatingCenter = floatingRect.x + floatingRect.width / 2;
174+
175+
// return floatingCenter > tableRect.left && floatingCenter < tableRect.right;
176+
// }

src/extensions/yfm/YfmTable/plugins/YfmTableControls/components/FloatingPlusButton/FloatingPlusButton.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import {useState} from 'react';
1+
import {forwardRef, useState} from 'react';
22

3-
import {FloatingPopup, type FloatingPopupProps} from '../FloatingPopup';
3+
import {FloatingPopup, type FloatingPopupProps, type FloatingPopupRef} from '../FloatingPopup';
44

55
import {InsertCursor, type InsertCursorProps} from './InsertCursor';
66
import {PlusButton, type PlusButtonProps} from './PlusButton';
@@ -18,18 +18,23 @@ const offsetByType: Record<InsertCursorProps['type'], FloatingPopupProps['offset
1818
column: {alignmentAxis: -10},
1919
};
2020

21-
export type FloatingPlusButtonProps = Pick<PlusButtonProps, 'onClick'> &
21+
export type FloatingPlusButtonRef = FloatingPopupRef & {};
22+
23+
export type FloatingPlusButtonProps = Pick<FloatingPopupProps, 'floatingStyles'> &
24+
Pick<PlusButtonProps, 'onClick'> &
2225
Pick<InsertCursorProps, 'type' | 'anchor'>;
2326

24-
export const FloatingPlusButton: React.FC<FloatingPlusButtonProps> =
25-
function YfmTableFloatingPlusButton({anchor, type, ...btnProps}) {
27+
export const FloatingPlusButton = forwardRef<FloatingPlusButtonRef, FloatingPlusButtonProps>(
28+
function YfmTableFloatingPlusButton({anchor, type, floatingStyles, ...btnProps}, ref) {
2629
const [hovered, setHovered] = useState(false);
2730

2831
return (
2932
<>
3033
<FloatingPopup
3134
open
35+
ref={ref}
3236
anchorElement={anchor}
37+
floatingStyles={floatingStyles}
3338
placement={placementByType[type]}
3439
offset={offsetByType[type]}
3540
style={styles}
@@ -43,4 +48,5 @@ export const FloatingPlusButton: React.FC<FloatingPlusButtonProps> =
4348
{hovered && <InsertCursor anchor={anchor} type={type} />}
4449
</>
4550
);
46-
};
51+
},
52+
);

0 commit comments

Comments
 (0)