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:
David Pham
2023-10-21 17:51:53 +07:00
committed by GitHub
parent 9e9eca22a4
commit c90cf1eb8f
4 changed files with 80 additions and 105 deletions

View File

@ -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>

View File

@ -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>
);
};

View File

@ -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>
);
};