Fix issue 2126: DataTable '+' button dropdown positioning glitch (#2150)
* Fix issue 2126: DataTable '+' button dropdown positioning glitch * Simplify code * Fix lint --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -4,6 +4,8 @@ import { useRecoilCallback, useRecoilState } from 'recoil';
|
||||
|
||||
import { IconPlus } from '@/ui/display/icon';
|
||||
import { IconButton } from '@/ui/input/button/components/IconButton';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||
import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
|
||||
@ -16,7 +18,7 @@ import { visibleTableColumnsScopedSelector } from '../states/selectors/visibleTa
|
||||
import { tableColumnsScopedState } from '../states/tableColumnsScopedState';
|
||||
|
||||
import { ColumnHeadWithDropdown } from './ColumnHeadWithDropdown';
|
||||
import { DataTableHeaderPlusButton } from './DataTableHeaderPlusButton';
|
||||
import { DataTableHeaderPlusButtonContent } from './DataTableHeaderPlusButtonContent';
|
||||
import { SelectAllCheckbox } from './SelectAllCheckbox';
|
||||
|
||||
const COLUMN_MIN_WIDTH = 104;
|
||||
@ -58,18 +60,6 @@ const StyledResizeHandler = styled.div`
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
const StyledAddIconButtonWrapper = styled.div`
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const StyledDataTableColumnMenu = styled(DataTableHeaderPlusButton)`
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 100%;
|
||||
z-index: ${({ theme }) => theme.lastLayerZIndex};
|
||||
`;
|
||||
|
||||
const StyledTableHead = styled.thead`
|
||||
cursor: pointer;
|
||||
`;
|
||||
@ -79,6 +69,12 @@ const StyledColumnHeadContainer = styled.div`
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
const HIDDEN_TABLE_COLUMN_DROPDOWN_SCOPE_ID =
|
||||
'hidden-table-columns-dropdown-scope-id';
|
||||
|
||||
const HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID =
|
||||
'hidden-table-columns-dropdown-hotkey-scope-id';
|
||||
|
||||
export const DataTableHeader = () => {
|
||||
const [resizeFieldOffset, setResizeFieldOffset] = useRecoilState(
|
||||
resizeFieldOffsetState,
|
||||
@ -104,7 +100,6 @@ export const DataTableHeader = () => {
|
||||
number | null
|
||||
>(null);
|
||||
const [resizedFieldKey, setResizedFieldKey] = useState<string | null>(null);
|
||||
const [isColumnMenuOpen, setIsColumnMenuOpen] = useState(false);
|
||||
|
||||
const { handleColumnsChange } = useTableColumns();
|
||||
|
||||
@ -157,10 +152,6 @@ export const DataTableHeader = () => {
|
||||
onMouseUp: handleResizeHandlerEnd,
|
||||
});
|
||||
|
||||
const toggleColumnMenu = useCallback(() => {
|
||||
setIsColumnMenuOpen((previousValue) => !previousValue);
|
||||
}, []);
|
||||
|
||||
const primaryColumn = visibleTableColumns[0];
|
||||
|
||||
return (
|
||||
@ -202,24 +193,29 @@ export const DataTableHeader = () => {
|
||||
/>
|
||||
</StyledColumnHeaderCell>
|
||||
))}
|
||||
|
||||
<th>
|
||||
{hiddenTableColumns.length > 0 && (
|
||||
<StyledAddIconButtonWrapper>
|
||||
<IconButton
|
||||
size="medium"
|
||||
variant="tertiary"
|
||||
Icon={IconPlus}
|
||||
onClick={toggleColumnMenu}
|
||||
position="middle"
|
||||
/>
|
||||
{isColumnMenuOpen && (
|
||||
<StyledDataTableColumnMenu
|
||||
onAddColumn={toggleColumnMenu}
|
||||
onClickOutside={toggleColumnMenu}
|
||||
<StyledColumnHeadContainer>
|
||||
<DropdownScope
|
||||
dropdownScopeId={HIDDEN_TABLE_COLUMN_DROPDOWN_SCOPE_ID}
|
||||
>
|
||||
<Dropdown
|
||||
clickableComponent={
|
||||
<IconButton
|
||||
size="medium"
|
||||
variant="tertiary"
|
||||
Icon={IconPlus}
|
||||
position="middle"
|
||||
/>
|
||||
}
|
||||
dropdownComponents={<DataTableHeaderPlusButtonContent />}
|
||||
dropdownPlacement="bottom-start"
|
||||
dropdownHotkeyScope={{
|
||||
scope: HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</StyledAddIconButtonWrapper>
|
||||
</DropdownScope>
|
||||
</StyledColumnHeadContainer>
|
||||
)}
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
import { ComponentProps, useCallback, useRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { FieldMetadata } from '@/ui/data/field/types/FieldMetadata';
|
||||
import { IconPlus } from '@/ui/display/icon';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
|
||||
import { useTableColumns } from '../hooks/useTableColumns';
|
||||
import { TableRecoilScopeContext } from '../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import { hiddenTableColumnsScopedSelector } from '../states/selectors/hiddenTableColumnsScopedSelector';
|
||||
import { ColumnDefinition } from '../types/ColumnDefinition';
|
||||
|
||||
const StyledHeaderPlusButton = styled(DropdownMenu)`
|
||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||
`;
|
||||
|
||||
type DataTableHeaderPlusButtonProps = {
|
||||
onAddColumn?: () => void;
|
||||
onClickOutside?: () => void;
|
||||
} & ComponentProps<'div'>;
|
||||
|
||||
export const DataTableHeaderPlusButton = ({
|
||||
onAddColumn,
|
||||
onClickOutside = () => undefined,
|
||||
}: DataTableHeaderPlusButtonProps) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
const hiddenTableColumns = useRecoilScopedValue(
|
||||
hiddenTableColumnsScopedSelector,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
|
||||
useListenClickOutside({
|
||||
refs: [ref],
|
||||
callback: onClickOutside,
|
||||
});
|
||||
|
||||
const { handleColumnVisibilityChange } = useTableColumns();
|
||||
|
||||
const handleAddColumn = useCallback(
|
||||
(column: ColumnDefinition<FieldMetadata>) => {
|
||||
onAddColumn?.();
|
||||
handleColumnVisibilityChange(column);
|
||||
},
|
||||
[handleColumnVisibilityChange, onAddColumn],
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledHeaderPlusButton ref={ref}>
|
||||
<DropdownMenuItemsContainer>
|
||||
{hiddenTableColumns.map((column) => (
|
||||
<MenuItem
|
||||
key={column.key}
|
||||
iconButtons={[
|
||||
{
|
||||
Icon: IconPlus,
|
||||
onClick: () => handleAddColumn(column),
|
||||
},
|
||||
]}
|
||||
LeftIcon={column.Icon}
|
||||
text={column.name}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</StyledHeaderPlusButton>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,50 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { FieldMetadata } from '@/ui/data/field/types/FieldMetadata';
|
||||
import { IconPlus } from '@/ui/display/icon';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
|
||||
import { useTableColumns } from '../hooks/useTableColumns';
|
||||
import { TableRecoilScopeContext } from '../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import { hiddenTableColumnsScopedSelector } from '../states/selectors/hiddenTableColumnsScopedSelector';
|
||||
import { ColumnDefinition } from '../types/ColumnDefinition';
|
||||
|
||||
export const DataTableHeaderPlusButtonContent = () => {
|
||||
const { closeDropdown } = useDropdown();
|
||||
|
||||
const hiddenTableColumns = useRecoilScopedValue(
|
||||
hiddenTableColumnsScopedSelector,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
|
||||
const { handleColumnVisibilityChange } = useTableColumns();
|
||||
|
||||
const handleAddColumn = useCallback(
|
||||
(column: ColumnDefinition<FieldMetadata>) => {
|
||||
closeDropdown();
|
||||
handleColumnVisibilityChange(column);
|
||||
},
|
||||
[handleColumnVisibilityChange, closeDropdown],
|
||||
);
|
||||
|
||||
return (
|
||||
<DropdownMenuItemsContainer>
|
||||
{hiddenTableColumns.map((column) => (
|
||||
<MenuItem
|
||||
key={column.key}
|
||||
iconButtons={[
|
||||
{
|
||||
Icon: IconPlus,
|
||||
onClick: () => handleAddColumn(column),
|
||||
},
|
||||
]}
|
||||
LeftIcon={column.Icon}
|
||||
text={column.name}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user