fix: scroll dropdown listing in hidden fields (#8738)
Fixes: #8716 [Screencast from 2024-11-25 22-06-24.webm](https://github.com/user-attachments/assets/35bd66cc-942f-4903-abda-0d67a75b6582) --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -15,6 +15,7 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
|||||||
import { onToggleColumnFilterComponentState } from '@/object-record/record-table/states/onToggleColumnFilterComponentState';
|
import { onToggleColumnFilterComponentState } from '@/object-record/record-table/states/onToggleColumnFilterComponentState';
|
||||||
import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState';
|
import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState';
|
||||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||||
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useTableColumns } from '../../hooks/useTableColumns';
|
import { useTableColumns } from '../../hooks/useTableColumns';
|
||||||
import { ColumnDefinition } from '../../types/ColumnDefinition';
|
import { ColumnDefinition } from '../../types/ColumnDefinition';
|
||||||
@ -91,43 +92,45 @@ export const RecordTableColumnHeadDropdownMenu = ({
|
|||||||
const canHide = column.isLabelIdentifier !== true;
|
const canHide = column.isLabelIdentifier !== true;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItemsContainer>
|
<ScrollWrapper contextProviderName="dropdownMenuItemsContainer">
|
||||||
{isFilterable && (
|
<DropdownMenuItemsContainer>
|
||||||
<MenuItem
|
{isFilterable && (
|
||||||
LeftIcon={IconFilter}
|
<MenuItem
|
||||||
onClick={handleFilterClick}
|
LeftIcon={IconFilter}
|
||||||
text="Filter"
|
onClick={handleFilterClick}
|
||||||
/>
|
text="Filter"
|
||||||
)}
|
/>
|
||||||
{isSortable && (
|
)}
|
||||||
<MenuItem
|
{isSortable && (
|
||||||
LeftIcon={IconSortDescending}
|
<MenuItem
|
||||||
onClick={handleSortClick}
|
LeftIcon={IconSortDescending}
|
||||||
text="Sort"
|
onClick={handleSortClick}
|
||||||
/>
|
text="Sort"
|
||||||
)}
|
/>
|
||||||
{showSeparator && <DropdownMenuSeparator />}
|
)}
|
||||||
{canMoveLeft && (
|
{showSeparator && <DropdownMenuSeparator />}
|
||||||
<MenuItem
|
{canMoveLeft && (
|
||||||
LeftIcon={IconArrowLeft}
|
<MenuItem
|
||||||
onClick={handleColumnMoveLeft}
|
LeftIcon={IconArrowLeft}
|
||||||
text="Move left"
|
onClick={handleColumnMoveLeft}
|
||||||
/>
|
text="Move left"
|
||||||
)}
|
/>
|
||||||
{canMoveRight && (
|
)}
|
||||||
<MenuItem
|
{canMoveRight && (
|
||||||
LeftIcon={IconArrowRight}
|
<MenuItem
|
||||||
onClick={handleColumnMoveRight}
|
LeftIcon={IconArrowRight}
|
||||||
text="Move right"
|
onClick={handleColumnMoveRight}
|
||||||
/>
|
text="Move right"
|
||||||
)}
|
/>
|
||||||
{canHide && (
|
)}
|
||||||
<MenuItem
|
{canHide && (
|
||||||
LeftIcon={IconEyeOff}
|
<MenuItem
|
||||||
onClick={handleColumnVisibility}
|
LeftIcon={IconEyeOff}
|
||||||
text="Hide"
|
onClick={handleColumnVisibility}
|
||||||
/>
|
text="Hide"
|
||||||
)}
|
/>
|
||||||
</DropdownMenuItemsContainer>
|
)}
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
</ScrollWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -65,6 +65,8 @@ const StyledColumnHeaderCell = styled.th<{
|
|||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
// TODO: refactor this, each component should own its CSS
|
||||||
div {
|
div {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
|
|||||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||||
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
export const RecordTableHeaderPlusButtonContent = () => {
|
export const RecordTableHeaderPlusButtonContent = () => {
|
||||||
@ -41,21 +42,19 @@ export const RecordTableHeaderPlusButtonContent = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{hiddenTableColumns.length > 0 && (
|
<ScrollWrapper contextProviderName="dropdownMenuItemsContainer">
|
||||||
<>
|
<DropdownMenuItemsContainer>
|
||||||
<DropdownMenuItemsContainer>
|
{hiddenTableColumns.map((column) => (
|
||||||
{hiddenTableColumns.map((column) => (
|
<MenuItem
|
||||||
<MenuItem
|
key={column.fieldMetadataId}
|
||||||
key={column.fieldMetadataId}
|
onClick={() => handleAddColumn(column)}
|
||||||
onClick={() => handleAddColumn(column)}
|
LeftIcon={getIcon(column.iconName)}
|
||||||
LeftIcon={getIcon(column.iconName)}
|
text={column.label}
|
||||||
text={column.label}
|
/>
|
||||||
/>
|
))}
|
||||||
))}
|
</DropdownMenuItemsContainer>
|
||||||
</DropdownMenuItemsContainer>
|
</ScrollWrapper>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer>
|
||||||
<UndecoratedLink
|
<UndecoratedLink
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|||||||
@ -1,3 +1,8 @@
|
|||||||
|
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||||
|
import { HotkeyEffect } from '@/ui/utilities/hotkey/components/HotkeyEffect';
|
||||||
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
|
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||||
|
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
||||||
import {
|
import {
|
||||||
autoUpdate,
|
autoUpdate,
|
||||||
flip,
|
flip,
|
||||||
@ -8,21 +13,19 @@ import {
|
|||||||
useFloating,
|
useFloating,
|
||||||
} from '@floating-ui/react';
|
} from '@floating-ui/react';
|
||||||
import { MouseEvent, ReactNode, useEffect, useRef } from 'react';
|
import { MouseEvent, ReactNode, useEffect, useRef } from 'react';
|
||||||
|
import { flushSync } from 'react-dom';
|
||||||
import { Keys } from 'react-hotkeys-hook';
|
import { Keys } from 'react-hotkeys-hook';
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
|
||||||
import { HotkeyEffect } from '@/ui/utilities/hotkey/components/HotkeyEffect';
|
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
|
||||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
|
||||||
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
import { useDropdown } from '../hooks/useDropdown';
|
import { useDropdown } from '../hooks/useDropdown';
|
||||||
import { useInternalHotkeyScopeManagement } from '../hooks/useInternalHotkeyScopeManagement';
|
import { useInternalHotkeyScopeManagement } from '../hooks/useInternalHotkeyScopeManagement';
|
||||||
|
|
||||||
import { DropdownUnmountEffect } from '@/ui/layout/dropdown/components/DropdownUnmountEffect';
|
import { DropdownUnmountEffect } from '@/ui/layout/dropdown/components/DropdownUnmountEffect';
|
||||||
|
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponeInstanceContext';
|
||||||
|
import { dropdownMaxHeightComponentStateV2 } from '@/ui/layout/dropdown/states/dropdownMaxHeightComponentStateV2';
|
||||||
import { useListenClickOutsideV2 } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2';
|
import { useListenClickOutsideV2 } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2';
|
||||||
|
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||||
import { DropdownMenu } from './DropdownMenu';
|
import { DropdownMenu } from './DropdownMenu';
|
||||||
import { DropdownOnToggleEffect } from './DropdownOnToggleEffect';
|
import { DropdownOnToggleEffect } from './DropdownOnToggleEffect';
|
||||||
|
|
||||||
@ -76,6 +79,11 @@ export const Dropdown = ({
|
|||||||
|
|
||||||
const offsetMiddlewares = [];
|
const offsetMiddlewares = [];
|
||||||
|
|
||||||
|
const [dropdownMaxHeight, setDropdownMaxHeight] = useRecoilComponentStateV2(
|
||||||
|
dropdownMaxHeightComponentStateV2,
|
||||||
|
dropdownId,
|
||||||
|
);
|
||||||
|
|
||||||
if (isDefined(dropdownOffset.x)) {
|
if (isDefined(dropdownOffset.x)) {
|
||||||
offsetMiddlewares.push(offset({ crossAxis: dropdownOffset.x }));
|
offsetMiddlewares.push(offset({ crossAxis: dropdownOffset.x }));
|
||||||
}
|
}
|
||||||
@ -90,13 +98,10 @@ export const Dropdown = ({
|
|||||||
flip(),
|
flip(),
|
||||||
size({
|
size({
|
||||||
padding: 32,
|
padding: 32,
|
||||||
apply: ({ availableHeight, elements }) => {
|
apply: ({ availableHeight }) => {
|
||||||
elements.floating.style.maxHeight =
|
flushSync(() => {
|
||||||
availableHeight >= elements.floating.scrollHeight
|
setDropdownMaxHeight(availableHeight);
|
||||||
? ''
|
});
|
||||||
: `${availableHeight}px`;
|
|
||||||
|
|
||||||
elements.floating.style.height = 'auto';
|
|
||||||
},
|
},
|
||||||
boundary: document.querySelector('#root') ?? undefined,
|
boundary: document.querySelector('#root') ?? undefined,
|
||||||
}),
|
}),
|
||||||
@ -149,8 +154,15 @@ export const Dropdown = ({
|
|||||||
[closeDropdown, isDropdownOpen],
|
[closeDropdown, isDropdownOpen],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const dropdownMenuStyles = {
|
||||||
|
...floatingStyles,
|
||||||
|
maxHeight: dropdownMaxHeight,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<DropdownComponentInstanceContext.Provider
|
||||||
|
value={{ instanceId: dropdownId }}
|
||||||
|
>
|
||||||
<DropdownScope dropdownScopeId={getScopeIdFromComponentId(dropdownId)}>
|
<DropdownScope dropdownScopeId={getScopeIdFromComponentId(dropdownId)}>
|
||||||
<div ref={containerRef} className={className}>
|
<div ref={containerRef} className={className}>
|
||||||
{clickableComponent && (
|
{clickableComponent && (
|
||||||
@ -175,7 +187,7 @@ export const Dropdown = ({
|
|||||||
width={dropdownMenuWidth ?? dropdownWidth}
|
width={dropdownMenuWidth ?? dropdownWidth}
|
||||||
data-select-disable
|
data-select-disable
|
||||||
ref={refs.setFloating}
|
ref={refs.setFloating}
|
||||||
style={floatingStyles}
|
style={dropdownMenuStyles}
|
||||||
>
|
>
|
||||||
{dropdownComponents}
|
{dropdownComponents}
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
@ -187,7 +199,7 @@ export const Dropdown = ({
|
|||||||
width={dropdownMenuWidth ?? dropdownWidth}
|
width={dropdownMenuWidth ?? dropdownWidth}
|
||||||
data-select-disable
|
data-select-disable
|
||||||
ref={refs.setFloating}
|
ref={refs.setFloating}
|
||||||
style={floatingStyles}
|
style={dropdownMenuStyles}
|
||||||
>
|
>
|
||||||
{dropdownComponents}
|
{dropdownComponents}
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
@ -199,6 +211,6 @@ export const Dropdown = ({
|
|||||||
</div>
|
</div>
|
||||||
</DropdownScope>
|
</DropdownScope>
|
||||||
<DropdownUnmountEffect dropdownId={dropdownId} />
|
<DropdownUnmountEffect dropdownId={dropdownId} />
|
||||||
</>
|
</DropdownComponentInstanceContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -26,6 +26,8 @@ const StyledDropdownMenu = styled.div<{
|
|||||||
z-index: 30;
|
z-index: 30;
|
||||||
width: ${({ width = 200 }) =>
|
width: ${({ width = 200 }) =>
|
||||||
typeof width === 'number' ? `${width}px` : width};
|
typeof width === 'number' ? `${width}px` : width};
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const DropdownMenu = StyledDropdownMenu;
|
export const DropdownMenu = StyledDropdownMenu;
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
|
||||||
|
|
||||||
const StyledDropdownMenuItemsExternalContainer = styled.div<{
|
const StyledDropdownMenuItemsExternalContainer = styled.div<{
|
||||||
hasMaxHeight?: boolean;
|
hasMaxHeight?: boolean;
|
||||||
}>`
|
}>`
|
||||||
@ -18,10 +16,6 @@ const StyledDropdownMenuItemsExternalContainer = styled.div<{
|
|||||||
width: calc(100% - 2 * var(--padding));
|
width: calc(100% - 2 * var(--padding));
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledScrollWrapper = styled(ScrollWrapper)`
|
|
||||||
width: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledDropdownMenuItemsInternalContainer = styled.div`
|
const StyledDropdownMenuItemsInternalContainer = styled.div`
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -48,17 +42,9 @@ export const DropdownMenuItemsContainer = ({
|
|||||||
hasMaxHeight={hasMaxHeight}
|
hasMaxHeight={hasMaxHeight}
|
||||||
className={className}
|
className={className}
|
||||||
>
|
>
|
||||||
{hasMaxHeight ? (
|
<StyledDropdownMenuItemsInternalContainer>
|
||||||
<StyledScrollWrapper contextProviderName="dropdownMenuItemsContainer">
|
{children}
|
||||||
<StyledDropdownMenuItemsInternalContainer>
|
</StyledDropdownMenuItemsInternalContainer>
|
||||||
{children}
|
|
||||||
</StyledDropdownMenuItemsInternalContainer>
|
|
||||||
</StyledScrollWrapper>
|
|
||||||
) : (
|
|
||||||
<StyledDropdownMenuItemsInternalContainer>
|
|
||||||
{children}
|
|
||||||
</StyledDropdownMenuItemsInternalContainer>
|
|
||||||
)}
|
|
||||||
</StyledDropdownMenuItemsExternalContainer>
|
</StyledDropdownMenuItemsExternalContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import styled from '@emotion/styled';
|
|||||||
|
|
||||||
const StyledDropdownMenuSeparator = styled.div`
|
const StyledDropdownMenuSeparator = styled.div`
|
||||||
background-color: ${({ theme }) => theme.border.color.light};
|
background-color: ${({ theme }) => theme.border.color.light};
|
||||||
height: 1px;
|
min-height: 1px;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -0,0 +1,4 @@
|
|||||||
|
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
|
||||||
|
|
||||||
|
export const DropdownComponentInstanceContext =
|
||||||
|
createComponentInstanceContext();
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponeInstanceContext';
|
||||||
|
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||||
|
|
||||||
|
export const dropdownMaxHeightComponentStateV2 = createComponentStateV2<
|
||||||
|
number | undefined
|
||||||
|
>({
|
||||||
|
key: 'dropdownMaxHeightComponentStateV2',
|
||||||
|
componentInstanceContext: DropdownComponentInstanceContext,
|
||||||
|
defaultValue: undefined,
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user