Skip to content
This repository was archived by the owner on Jul 28, 2025. It is now read-only.

Commit ead1dad

Browse files
committed
Merge branch 'refactor-header-menu'
2 parents b331dbd + 0e238b7 commit ead1dad

20 files changed

+138
-126
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@
6464
"react-csv": "2.2.2",
6565
"react-datepicker": "4.8.0",
6666
"react-dom": "18.2.0",
67-
"react-popper": "2.3.0",
6867
"react-select": "5.6.1",
6968
"react-window": "1.8.8",
7069
"zustand": "4.1.4"

src/cdm/HeaderActionModel.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ export type HeaderActionResponse = {
44
buttons: JSX.Element[]
55
headerMenuProps: HeaderMenuProps
66
hooks: {
7-
setExpanded: (expanded: boolean) => void,
7+
setMenuEl: (expanded: null | HTMLElement) => void,
8+
setTypesEl: (expanded: null | HTMLElement) => void,
89
setKeyState: (key: string) => void,
910
keyState: string,
1011
[key: string]: any | ((a: any) => void)

src/cdm/HeaderModel.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ import { DatabaseHeaderProps, RowDataType, TableColumn } from "cdm/FolderModel";
44
export type HeaderMenuProps = {
55
headerProps: DatabaseHeaderProps;
66
propertyIcon: JSX.Element;
7-
expanded: boolean;
8-
setExpanded: (expanded: boolean) => void;
9-
created: boolean;
7+
menuEl: null | HTMLElement;
8+
setMenuEl: (menuEl: null | HTMLElement) => void;
109
referenceElement: HTMLDivElement;
1110
labelState: string;
1211
setLabelState: (label: string) => void;

src/components/DefaultHeader.tsx

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState } from "react";
1+
import React, { MouseEventHandler, useState } from "react";
22
import TextIcon from "components/img/Text";
33
import MultiIcon from "components/img/Multi";
44
import HashIcon from "components/img/Hash";
@@ -31,8 +31,6 @@ import { AddColumnModalProps } from "cdm/ModalsModel";
3131
*/
3232
export default function DefaultHeader(headerProps: DatabaseHeaderProps) {
3333
LOGGER.debug(`=>Header ${headerProps.column.columnDef}`);
34-
// TODO : add a tooltip to the header
35-
const created: boolean = false;
3634
/** Properties of header */
3735
const { header, table } = headerProps;
3836
const { tableState } = table.options.meta;
@@ -47,7 +45,7 @@ export default function DefaultHeader(headerProps: DatabaseHeaderProps) {
4745
/** Column values */
4846
const { id, input, label, config } = header.column.columnDef as TableColumn;
4947
/** reducer asociated to database */
50-
const [expanded, setExpanded] = useState(created || false);
48+
const [menuEl, setMenuEl] = useState<null | HTMLElement>(null);
5149
const [referenceElement, setReferenceElement] = useState(null);
5250
const [labelState, setLabelState] = useState(label);
5351

@@ -110,12 +108,16 @@ export default function DefaultHeader(headerProps: DatabaseHeaderProps) {
110108
new AddColumnModal(table.options.meta.view, addColumnProps).open();
111109
}
112110

111+
const openMenuHandler: MouseEventHandler<HTMLDivElement> = (event) => {
112+
setMenuEl(menuEl ? null : event.currentTarget);
113+
};
114+
113115
LOGGER.debug(`<=Header ${label}`);
114116
return id !== MetadataColumns.ADD_COLUMN ? (
115117
<>
116118
<div
117119
className={`${c("th-content")}`}
118-
onClick={() => setExpanded(true)}
120+
onClick={openMenuHandler}
119121
ref={setReferenceElement}
120122
>
121123
<span className="svg-icon svg-gray icon-margin">{propertyIcon}</span>
@@ -139,19 +141,15 @@ export default function DefaultHeader(headerProps: DatabaseHeaderProps) {
139141
</span>
140142
)}
141143
</div>
142-
{ReactDOM.createPortal(
143-
<HeaderMenu
144-
headerProps={headerProps}
145-
propertyIcon={propertyIcon}
146-
expanded={expanded}
147-
setExpanded={setExpanded}
148-
created={created}
149-
referenceElement={referenceElement}
150-
labelState={labelState}
151-
setLabelState={setLabelState}
152-
/>,
153-
activeDocument.body
154-
)}
144+
<HeaderMenu
145+
headerProps={headerProps}
146+
propertyIcon={propertyIcon}
147+
menuEl={menuEl}
148+
setMenuEl={setMenuEl}
149+
referenceElement={referenceElement}
150+
labelState={labelState}
151+
setLabelState={setLabelState}
152+
/>
155153
</>
156154
) : (
157155
<div

src/components/HeaderMenu.tsx

Lines changed: 64 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import { InputType, StyleVariables } from "helpers/Constants";
22
import { dbTrim, c, getLabelHeader } from "helpers/StylesHelper";
33
import AdjustmentsIcon from "components/img/AdjustmentsIcon";
4-
import React, { FocusEventHandler, useEffect, useState } from "react";
5-
import { usePopper } from "react-popper";
4+
import React, { FocusEventHandler, useState } from "react";
5+
import Popper from "@mui/material/Popper";
66
import header_action_button_section from "components/headerActions/HeaderActionButtonSection";
77
import header_action_types_section from "components/headerActions/HeaderActiontypesSection";
88
import { ColumnSettingsModal } from "components/modals/columnSettings/ColumnSettingsModal";
9+
import { PopperTypesStyleModifiers } from "components/styles/PopperStyles";
910
import { TableColumn } from "cdm/FolderModel";
1011
import { HeaderActionResponse } from "cdm/HeaderActionModel";
1112
import { HeaderMenuProps } from "cdm/HeaderModel";
13+
import Box from "@mui/material/Box";
14+
import ClickAwayListener from "@mui/material/ClickAwayListener";
1215

1316
const HeaderMenu = (headerMenuProps: HeaderMenuProps) => {
1417
const { table, column } = headerMenuProps.headerProps;
18+
1519
const [columnsInfo, columnActions] = table.options.meta.tableState.columns(
1620
(state) => [state.info, state.actions]
1721
);
@@ -23,69 +27,51 @@ const HeaderMenu = (headerMenuProps: HeaderMenuProps) => {
2327
);
2428

2529
/** Header props */
26-
const {
27-
propertyIcon,
28-
expanded,
29-
setExpanded,
30-
created,
31-
referenceElement,
32-
labelState,
33-
setLabelState,
34-
} = headerMenuProps;
30+
const { propertyIcon, menuEl, setMenuEl, labelState, setLabelState } =
31+
headerMenuProps;
3532

3633
const { key, isMetadata, input } = column.columnDef as TableColumn;
3734
/** Column values */
3835
const [keyState, setkeyState] = useState(dbTrim(key));
39-
const [popperElement, setPopperElement] = useState(null);
4036
const [inputRef, setInputRef] = useState(null);
41-
const { styles, attributes } = usePopper(referenceElement, popperElement, {
42-
placement: "bottom",
43-
strategy: "absolute",
44-
});
45-
// Manage type of data
46-
const [typeReferenceElement, setTypeReferenceElement] = useState(null);
47-
const [typePopperElement, setTypePopperElement] = useState(null);
48-
const [showType, setShowType] = useState(false);
37+
38+
// Manage menu Popper
39+
const openMenu = Boolean(menuEl);
40+
const idMenu = openMenu ? `header-menu-popper` : undefined;
41+
42+
// Manage type Popper
43+
const [typesEl, setTypesEl] = useState<null | HTMLElement>(null);
44+
const [typesTimeout, setTypesTimeout] = useState(null);
45+
46+
const isTypesShown = Boolean(typesEl);
47+
const idTypes = isTypesShown ? `types-menu-popper` : undefined;
4948

5049
// Manage errors
5150
const [labelStateInvalid, setLabelStateInvalid] = useState(false);
5251

53-
/** Event driven actions */
54-
useEffect(() => {
55-
// Throw event if created changed to expand or collapse the menu
56-
if (created) {
57-
setExpanded(true);
58-
}
59-
}, [created]);
60-
6152
/**
6253
* Array of action buttons asociated to the header
6354
*/
6455
let headerActionResponse: HeaderActionResponse = {
6556
buttons: [],
6657
headerMenuProps: headerMenuProps,
6758
hooks: {
68-
setExpanded: setExpanded,
59+
setMenuEl: setMenuEl,
60+
setTypesEl: setTypesEl,
6961
keyState: keyState,
7062
setKeyState: setkeyState,
71-
setShowType: setShowType,
7263
},
7364
};
7465
const headerButtons =
7566
header_action_button_section.run(headerActionResponse).buttons;
7667

77-
/**
78-
* Array of type headers available to change the data type of the column
79-
*/
68+
// /**
69+
// * Array of type headers available to change the data type of the column
70+
// */
8071
headerActionResponse.buttons = [];
8172
const typesButtons =
8273
header_action_types_section.run(headerActionResponse).buttons;
8374

84-
const typePopper = usePopper(typeReferenceElement, typePopperElement, {
85-
placement: "right",
86-
strategy: "fixed",
87-
});
88-
8975
function persistLabelChange() {
9076
// Update state of altered column
9177
columnActions.alterColumnLabel(column.columnDef as TableColumn, labelState);
@@ -119,22 +105,10 @@ const HeaderMenu = (headerMenuProps: HeaderMenuProps) => {
119105
};
120106

121107
return (
122-
<div>
123-
{expanded && (
124-
<div className="overlay" onClick={() => setExpanded(false)} />
125-
)}
126-
{expanded && (
127-
<div
128-
ref={setPopperElement}
129-
style={{ ...styles.popper, zIndex: 3 }}
130-
{...attributes.popper}
131-
>
132-
<div
133-
className={`menu ${c("popper")}`}
134-
style={{
135-
width: 240,
136-
}}
137-
>
108+
<Popper id={idMenu} open={openMenu} anchorEl={menuEl} key={idMenu}>
109+
<ClickAwayListener onClickAway={() => setMenuEl(null)}>
110+
<Box>
111+
<div className={`menu ${c("popper")}`}>
138112
{/** Edit header label section */}
139113
{!isMetadata && (
140114
<>
@@ -177,9 +151,17 @@ const HeaderMenu = (headerMenuProps: HeaderMenuProps) => {
177151
<div style={{ padding: "4px 0px" }}>
178152
<div
179153
className="menu-item sort-button"
180-
onMouseEnter={() => setShowType(true)}
181-
onMouseLeave={() => setShowType(false)}
182-
ref={setTypeReferenceElement}
154+
onMouseOver={async (event) => {
155+
setTypesEl(event.currentTarget);
156+
}}
157+
onMouseLeave={() => {
158+
const timeoutId = setTimeout(() => {
159+
setTypesEl(null);
160+
setTypesTimeout(null);
161+
// timeout until event is triggered after user has stopped typing
162+
}, 250);
163+
setTypesTimeout(timeoutId);
164+
}}
183165
>
184166
<span className="svg-icon svg-text icon-margin">
185167
{propertyIcon}
@@ -188,24 +170,29 @@ const HeaderMenu = (headerMenuProps: HeaderMenuProps) => {
188170
{getLabelHeader(input)}
189171
</span>
190172
</div>
191-
{showType && (
192-
<div
193-
className={`menu ${c("popper")}`}
194-
ref={setTypePopperElement}
195-
onMouseEnter={() => setShowType(true)}
196-
onMouseLeave={() => setShowType(false)}
197-
{...typePopper.attributes.popper}
198-
style={{
199-
...typePopper.styles.popper,
200-
width: 200,
201-
zIndex: 4,
202-
padding: "4px 0px",
203-
}}
204-
>
173+
<Popper
174+
id={idTypes}
175+
open={isTypesShown}
176+
anchorEl={typesEl}
177+
placement="right"
178+
disablePortal={false}
179+
key={idTypes}
180+
modifiers={PopperTypesStyleModifiers()}
181+
onMouseOver={() => {
182+
if (typesTimeout) {
183+
clearTimeout(typesTimeout);
184+
setTypesTimeout(null);
185+
}
186+
}}
187+
onMouseLeave={async () => {
188+
setTypesEl(null);
189+
}}
190+
>
191+
<Box className={`menu ${c("popper")}`}>
205192
{/** Childs of typesButtons */}
206193
{typesButtons}
207-
</div>
208-
)}
194+
</Box>
195+
</Popper>
209196
</div>
210197
</>
211198
)}
@@ -241,7 +228,7 @@ const HeaderMenu = (headerMenuProps: HeaderMenuProps) => {
241228
view: table.options.meta.view,
242229
headerMenuProps: headerMenuProps,
243230
}).open();
244-
setExpanded(false);
231+
setMenuEl(null);
245232
}}
246233
>
247234
<span className="svg-icon svg-text icon-margin">
@@ -253,9 +240,9 @@ const HeaderMenu = (headerMenuProps: HeaderMenuProps) => {
253240
</div>
254241
)}
255242
</div>
256-
</div>
257-
)}
258-
</div>
243+
</Box>
244+
</ClickAwayListener>
245+
</Popper>
259246
);
260247
};
261248

src/components/headerActions/handlers/buttons/AddColumnHandlerAction.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ function addColumnToRightButton(headerActionResponse: HeaderActionResponse) {
3737

3838
const addColumnToRightOnClick = async () => {
3939
columnActions.addToRight(column);
40-
hooks.setExpanded(false);
40+
hooks.setMenuEl(null);
4141
};
4242
return headerButtonComponent({
4343
onClick: addColumnToRightOnClick,
@@ -57,7 +57,7 @@ function addColumnToLeftButton(headerActionResponse: HeaderActionResponse) {
5757

5858
const addColumnToLeftOnClick = async () => {
5959
columnActions.addToLeft(column);
60-
hooks.setExpanded(false);
60+
hooks.setMenuEl(null);
6161
};
6262

6363
return headerButtonComponent({

src/components/headerActions/handlers/buttons/HideColumnHandlerAction.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ function hideButton(headerActionResponse: HeaderActionResponse) {
3333
const currentCol = column.columnDef as TableColumn;
3434
column.getToggleVisibilityHandler()({ target: { checked: false } });
3535
columnActions.alterIsHidden(currentCol, true);
36-
hooks.setExpanded(false);
36+
hooks.setMenuEl(null);
3737
};
3838

3939
return headerButtonComponent({

src/components/headerActions/handlers/buttons/RemoveColumnHandlerAction.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ function removeButton(headerActionResponse: HeaderActionResponse) {
6464
}
6565
dataActions.removeDataOfColumn(column.columnDef as TableColumn);
6666
columnActions.remove(column.columnDef as TableColumn);
67-
hooks.setExpanded(false);
67+
hooks.setMenuEl(null);
6868
// Remove column from group_folder_column
6969
const groupFolderColumn = ddbbConfig.group_folder_column.split(",");
7070
if (groupFolderColumn.includes(column.columnDef.id)) {

src/components/headerActions/handlers/buttons/SortHandlerAction.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ function sortingUpButton(headerActionResponse: HeaderActionResponse) {
5353
tablecolumn.isSorted =
5454
tablecolumn.isSorted && !tablecolumn.isSortedDesc ? false : true;
5555
tablecolumn.isSortedDesc = false;
56-
hooks.setExpanded(false);
56+
hooks.setMenuEl(null);
5757
// Save on memory
5858
let currentSorting = [...table.options.state.sorting];
5959
if (tablecolumn.isSorted) {
@@ -100,7 +100,7 @@ function sortingDownButton(headerActionResponse: HeaderActionResponse) {
100100
tablecolumn.isSorted && tablecolumn.isSortedDesc ? false : true;
101101
tablecolumn.isSortedDesc = true;
102102

103-
hooks.setExpanded(false);
103+
hooks.setMenuEl(null);
104104
// Update on memory
105105
let currentSorting = [...table.options.state.sorting];
106106
if (tablecolumn.isSorted) {

src/components/headerActions/handlers/types/CheckboxTypeHeaderAction.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ function checkboxTypeComponent(headerActionResponse: HeaderActionResponse) {
3434
);
3535

3636
const checkBoxTypeOnClick = async () => {
37-
hooks.setShowType(false);
38-
hooks.setExpanded(false);
37+
hooks.setTypesEl(null);
38+
hooks.setMenuEl(null);
3939
dataActions.parseDataOfColumn(
4040
column.columnDef as TableColumn,
4141
InputType.CHECKBOX,

0 commit comments

Comments
 (0)