Fix drag and drop in dropdown (#16622)
## Bug description Due to the dropdown using floating ui, there was an offset on the draggable item when the drag and drop was implemented inside a scrollable dropdown. ## Video QA ### Before https://github.com/user-attachments/assets/fe4b4c36-39ae-4d26-9e19-85d5ffd23b30 ### After https://github.com/user-attachments/assets/1f00b9b0-c231-49cb-a232-75ca42a98e8c
This commit is contained in:
+19
-14
@@ -10,6 +10,7 @@ import { RecordIndexContextProvider } from '@/object-record/record-index/context
|
||||
import { useRecordIndexFieldMetadataDerivedStates } from '@/object-record/record-index/hooks/useRecordIndexFieldMetadataDerivedStates';
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { useEffect } from 'react';
|
||||
@@ -105,21 +106,25 @@ const createStory = (contentId: ObjectOptionsContentId | null): Story => ({
|
||||
recordFieldByFieldMetadataItemId,
|
||||
}}
|
||||
>
|
||||
<ObjectOptionsDropdownContext.Provider
|
||||
value={{
|
||||
viewType: ViewType.Table,
|
||||
objectMetadataItem: companyObjectMetadataItem,
|
||||
recordIndexId: instanceId,
|
||||
currentContentId: contentId,
|
||||
onContentChange: () => {},
|
||||
resetContent: () => {},
|
||||
dropdownId: OBJECT_OPTIONS_DROPDOWN_ID,
|
||||
}}
|
||||
<DropdownComponentInstanceContext.Provider
|
||||
value={{ instanceId: OBJECT_OPTIONS_DROPDOWN_ID }}
|
||||
>
|
||||
<DropdownContent>
|
||||
<Story />
|
||||
</DropdownContent>
|
||||
</ObjectOptionsDropdownContext.Provider>
|
||||
<ObjectOptionsDropdownContext.Provider
|
||||
value={{
|
||||
viewType: ViewType.Table,
|
||||
objectMetadataItem: companyObjectMetadataItem,
|
||||
recordIndexId: instanceId,
|
||||
currentContentId: contentId,
|
||||
onContentChange: () => {},
|
||||
resetContent: () => {},
|
||||
dropdownId: OBJECT_OPTIONS_DROPDOWN_ID,
|
||||
}}
|
||||
>
|
||||
<DropdownContent>
|
||||
<Story />
|
||||
</DropdownContent>
|
||||
</ObjectOptionsDropdownContext.Provider>
|
||||
</DropdownComponentInstanceContext.Provider>
|
||||
</RecordIndexContextProvider>
|
||||
);
|
||||
},
|
||||
|
||||
+8
-1
@@ -1,6 +1,7 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { Draggable } from '@hello-pangea/dnd';
|
||||
import { isFunction } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type DraggableItemProps = {
|
||||
draggableId: string;
|
||||
@@ -12,6 +13,7 @@ type DraggableItemProps = {
|
||||
isInsideScrollableContainer?: boolean;
|
||||
draggableComponentStyles?: React.CSSProperties;
|
||||
disableDraggingBackground?: boolean;
|
||||
containerOffsetY?: number;
|
||||
};
|
||||
|
||||
export const DraggableItem = ({
|
||||
@@ -22,6 +24,7 @@ export const DraggableItem = ({
|
||||
isInsideScrollableContainer,
|
||||
draggableComponentStyles,
|
||||
disableDraggingBackground,
|
||||
containerOffsetY,
|
||||
}: DraggableItemProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -47,11 +50,15 @@ export const DraggableItem = ({
|
||||
...draggableComponentStyles,
|
||||
...draggableStyle,
|
||||
left: 'auto',
|
||||
...(isInsideScrollableContainer ? {} : { top: 'auto' }),
|
||||
transform: draggableStyle?.transform?.replace(
|
||||
/\(-?\d+px,/,
|
||||
'(0,',
|
||||
),
|
||||
...(isInsideScrollableContainer
|
||||
? {
|
||||
top: `${(isDefined(draggableStyle) && 'top' in draggableStyle ? draggableStyle.top : 0) - (containerOffsetY ?? 0)}px`,
|
||||
}
|
||||
: { top: 'auto' }),
|
||||
background:
|
||||
!disableDraggingBackground && isDragging
|
||||
? theme.background.transparent.light
|
||||
|
||||
@@ -9,6 +9,7 @@ import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/
|
||||
import { useToggleDropdown } from '@/ui/layout/dropdown/hooks/useToggleDropdown';
|
||||
import { dropdownMaxHeightComponentState } from '@/ui/layout/dropdown/states/internal/dropdownMaxHeightComponentState';
|
||||
import { dropdownMaxWidthComponentState } from '@/ui/layout/dropdown/states/internal/dropdownMaxWidthComponentState';
|
||||
import { dropdownYPositionComponentState } from '@/ui/layout/dropdown/states/internal/dropdownYPositionComponentState';
|
||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||
import { type DropdownOffset } from '@/ui/layout/dropdown/types/DropdownOffset';
|
||||
import { type GlobalHotkeysConfig } from '@/ui/utilities/hotkey/types/GlobalHotkeysConfig';
|
||||
@@ -117,6 +118,11 @@ export const Dropdown = ({
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
const setDropdownYPosition = useSetRecoilComponentState(
|
||||
dropdownYPositionComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
const bottomAutoresizePadding = isMobile
|
||||
? (middlewareBoundaryPadding.bottomMobile ??
|
||||
@@ -143,7 +149,7 @@ export const Dropdown = ({
|
||||
...boundaryOptions,
|
||||
}),
|
||||
size({
|
||||
apply: ({ availableHeight, availableWidth }) => {
|
||||
apply: ({ availableHeight, availableWidth, y: floatingY }) => {
|
||||
flushSync(() => {
|
||||
const maxHeightToApply =
|
||||
availableHeight < DROPDOWN_RESIZE_MIN_HEIGHT
|
||||
@@ -157,6 +163,7 @@ export const Dropdown = ({
|
||||
|
||||
setDropdownMaxHeight(maxHeightToApply);
|
||||
setDropdownMaxWidth(maxWidthToApply);
|
||||
setDropdownYPosition(floatingY);
|
||||
});
|
||||
},
|
||||
...boundaryOptions,
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
|
||||
export const dropdownYPositionComponentState = createComponentState<
|
||||
number | undefined
|
||||
>({
|
||||
key: 'dropdownYPositionComponentState',
|
||||
componentInstanceContext: DropdownComponentInstanceContext,
|
||||
defaultValue: undefined,
|
||||
});
|
||||
+7
@@ -10,6 +10,7 @@ import { visibleRecordFieldsComponentSelector } from '@/object-record/record-fie
|
||||
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
|
||||
import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableList';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { dropdownYPositionComponentState } from '@/ui/layout/dropdown/states/internal/dropdownYPositionComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { useContext } from 'react';
|
||||
@@ -76,6 +77,10 @@ export const ViewFieldsVisibleDropdownSection = () => {
|
||||
)
|
||||
.toSorted(sortByProperty('position'));
|
||||
|
||||
const dropdownYPosition = useRecoilComponentValue(
|
||||
dropdownYPositionComponentState,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuItemsContainer>
|
||||
@@ -107,6 +112,8 @@ export const ViewFieldsVisibleDropdownSection = () => {
|
||||
key={recordField.fieldMetadataItemId}
|
||||
draggableId={recordField.fieldMetadataItemId}
|
||||
index={fieldIndex + 1}
|
||||
isInsideScrollableContainer
|
||||
containerOffsetY={dropdownYPosition}
|
||||
itemComponent={
|
||||
<MenuItemDraggable
|
||||
key={recordField.fieldMetadataItemId}
|
||||
|
||||
Reference in New Issue
Block a user