From 8a856a4bcee49e5034c78eaab3b7992340b84b14 Mon Sep 17 00:00:00 2001 From: Thomas des Francs Date: Wed, 5 Aug 2026 09:35:46 +0200 Subject: [PATCH] Standardize dragged element feedback (#23772) ## Summary - make dragged table rows use a consistent background across sticky cells - apply one shared opacity treatment to table rows, Kanban cards, and other dnd-kit feedback ## Before/After drag-feedback-before-after --- packages/twenty-front/src/index.css | 11 +++++ .../RecordCalendarCardDraggableContainer.tsx | 1 + .../components/RecordCalendarWeekEvent.tsx | 5 +++ .../record-card/components/RecordCard.tsx | 3 +- .../components/RecordTableCellCheckbox.tsx | 3 ++ .../components/RecordTableCellDragAndDrop.tsx | 1 + .../components/RecordTableDraggableTr.tsx | 40 ++++++++----------- .../components/RecordTableRowDiv.tsx | 2 + .../RecordTableRowDragOverlayContent.tsx | 2 +- .../components/DragDropItemSortableCell.tsx | 7 ++++ .../constants/DragSourceOpacity.ts | 1 + 11 files changed, 51 insertions(+), 25 deletions(-) create mode 100644 packages/twenty-front/src/modules/ui/utilities/drag-and-drop/constants/DragSourceOpacity.ts diff --git a/packages/twenty-front/src/index.css b/packages/twenty-front/src/index.css index cf9ab76a4d..fbdde142cb 100644 --- a/packages/twenty-front/src/index.css +++ b/packages/twenty-front/src/index.css @@ -21,6 +21,17 @@ form { display: contents; } +:root { + --dnd-drag-feedback-opacity: 0.6; + --dnd-drag-source-opacity: 0.3; +} + +[data-dnd-dragging='true'] { + /* dnd-kit can reuse the source node as its moving feedback. In that case, + feedback opacity must win over the source fade applied by the component. */ + opacity: var(--dnd-drag-feedback-opacity) !important; +} + /* https://stackoverflow.com/questions/44543157/how-to-hide-the-google-invisible-recaptcha-badge */ .grecaptcha-badge { visibility: hidden !important; diff --git a/packages/twenty-front/src/modules/object-record/record-calendar/record-calendar-card/components/RecordCalendarCardDraggableContainer.tsx b/packages/twenty-front/src/modules/object-record/record-calendar/record-calendar-card/components/RecordCalendarCardDraggableContainer.tsx index e0a8959788..de2e7cde46 100644 --- a/packages/twenty-front/src/modules/object-record/record-calendar/record-calendar-card/components/RecordCalendarCardDraggableContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-calendar/record-calendar-card/components/RecordCalendarCardDraggableContainer.tsx @@ -41,6 +41,7 @@ export const RecordCalendarCardDraggableContainer = ({ type={RECORD_CALENDAR_CARD_DND_TYPE} accept={RECORD_CALENDAR_CARD_DND_TYPE} disabled={dragIsDisabled} + fadeSourceWhileDragging > + $isDragSourceFaded ? DRAG_SOURCE_OPACITY : 1}; overflow: hidden; position: ${({ isAllDay }) => (isAllDay ? 'relative' : 'absolute')}; right: auto; @@ -257,6 +261,7 @@ export const RecordCalendarWeekEvent = ({ return ( (isDragging ? '0.3' : '1')}; + opacity: ${({ isDragging }) => (isDragging ? DRAG_SOURCE_OPACITY : 1)}; width: 100%; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox.tsx index 131a4d9f42..053cbc8366 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox.tsx @@ -7,6 +7,7 @@ import { RECORD_TABLE_COLUMN_CHECKBOX_WIDTH } from '@/object-record/record-table import { RECORD_TABLE_COLUMN_CHECKBOX_WIDTH_CLASS_NAME } from '@/object-record/record-table/constants/RecordTableColumnCheckboxWidthClassName'; import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight'; import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext'; +import { useRecordTableRowDraggableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowDraggableContext'; import { RecordTableCellStyleWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellStyleWrapper'; import { useSetCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useSetCurrentRowSelected'; import { isDefined } from 'twenty-shared/utils'; @@ -27,6 +28,7 @@ export const RecordTableCellCheckbox = () => { const { t } = useLingui(); const { isSelected } = useRecordTableRowContextOrThrow(); + const { isDragging } = useRecordTableRowDraggableContextOrThrow(); const { setCurrentRowSelected } = useSetCurrentRowSelected(); @@ -43,6 +45,7 @@ export const RecordTableCellCheckbox = () => { return ( diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellDragAndDrop.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellDragAndDrop.tsx index 13e9e8f840..2daa897181 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellDragAndDrop.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellDragAndDrop.tsx @@ -41,6 +41,7 @@ export const RecordTableCellDragAndDrop = () => { return ( { - const { theme } = useContext(ThemeContext); - const { isSecondaryDragged } = useIsTableRowSecondaryDragged(recordId); const { recordGroupId } = useContext(RecordGroupContext); @@ -68,19 +66,18 @@ export const RecordTableDraggableTr = ({ // per-instance id avoids the collision; recordId travels in the drag data. const [sortableId] = useState(() => v4()); - const { handleRef, ref, isDragging, isDragSource, isDropTarget } = - useSortable({ - id: sortableId, - index: draggableIndex, - group: droppableId, - type: RECORD_TABLE_ROW_DND_TYPE, - accept: RECORD_TABLE_ROW_DND_TYPE, - data: rowDragData, - disabled: isDragDisabled, - transition: null, - plugins: DND_KIT_PLUGINS_WITHOUT_OPTIMISTIC, - feedback: 'clone', - }); + const { handleRef, ref, isDragSource, isDropTarget } = useSortable({ + id: sortableId, + index: draggableIndex, + group: droppableId, + type: RECORD_TABLE_ROW_DND_TYPE, + accept: RECORD_TABLE_ROW_DND_TYPE, + data: rowDragData, + disabled: isDragDisabled, + transition: null, + plugins: DND_KIT_PLUGINS_WITHOUT_OPTIMISTIC, + feedback: 'clone', + }); return ( - + {children} diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowDiv.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowDiv.tsx index 909ccac0ed..44cc1e03ce 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowDiv.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowDiv.tsx @@ -4,6 +4,8 @@ import { themeCssVariables } from 'twenty-ui/theme-constants'; const StyledTr = styled.div<{ isDragging: boolean; }>` + border-bottom: ${({ isDragging }) => + isDragging ? `1px solid ${themeCssVariables.border.color.medium}` : 'none'}; border-top: ${({ isDragging }) => isDragging ? `1px solid ${themeCssVariables.border.color.medium}` : 'none'}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowDragOverlayContent.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowDragOverlayContent.tsx index f71d2f9517..3ba9df24f0 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowDragOverlayContent.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowDragOverlayContent.tsx @@ -177,7 +177,7 @@ export const RecordTableRowDragOverlayContent = ({ recordId={recordId} focusIndex={sourceData.focusIndex} style={{ - background: themeCssVariables.background.transparent.light, + background: themeCssVariables.background.secondary, borderColor: themeCssVariables.border.color.medium, }} isDragging diff --git a/packages/twenty-front/src/modules/ui/utilities/drag-and-drop/components/DragDropItemSortableCell.tsx b/packages/twenty-front/src/modules/ui/utilities/drag-and-drop/components/DragDropItemSortableCell.tsx index a6e98c767f..5ebfb20bf2 100644 --- a/packages/twenty-front/src/modules/ui/utilities/drag-and-drop/components/DragDropItemSortableCell.tsx +++ b/packages/twenty-front/src/modules/ui/utilities/drag-and-drop/components/DragDropItemSortableCell.tsx @@ -9,6 +9,7 @@ import { themeCssVariables } from 'twenty-ui/theme-constants'; import { DragDropItemDropLine } from '@/ui/utilities/drag-and-drop/components/DragDropItemDropLine'; import { DND_KIT_PLUGINS_WITHOUT_OPTIMISTIC } from '@/ui/utilities/drag-and-drop/constants/DndKitPluginsWithoutOptimistic'; +import { DRAG_SOURCE_OPACITY } from '@/ui/utilities/drag-and-drop/constants/DragSourceOpacity'; import { DragDropItemSortableHandleRefContext } from '@/ui/utilities/drag-and-drop/context/DragDropItemSortableHandleRefContext'; import { preventNativeDragStart } from '@/ui/utilities/drag-and-drop/utils/preventNativeDragStart'; @@ -23,6 +24,7 @@ const SORTABLE_TRANSITION = { const StyledSortableRoot = styled.div<{ $disabled?: boolean; $fill?: boolean; + $isDragSourceFaded?: boolean; $isDraggingHighlighted?: boolean; }>` background: ${({ $isDraggingHighlighted }) => @@ -37,6 +39,8 @@ const StyledSortableRoot = styled.div<{ height: ${({ $fill }) => ($fill ? '100%' : 'auto')}; min-height: 0; min-width: ${({ $fill }) => ($fill ? '0' : 'auto')}; + opacity: ${({ $isDragSourceFaded }) => + $isDragSourceFaded ? DRAG_SOURCE_OPACITY : 1}; outline: none; position: relative; transition: background 0.1s ease; @@ -54,6 +58,7 @@ type DragDropItemSortableCellProps = { children: ReactNode; data?: Record; disabled?: boolean; + fadeSourceWhileDragging?: boolean; fill?: boolean; group: string; hasTransition?: boolean; @@ -70,6 +75,7 @@ export const DragDropItemSortableCell = ({ children, data, disabled = false, + fadeSourceWhileDragging = false, fill = false, group, hasTransition = true, @@ -116,6 +122,7 @@ export const DragDropItemSortableCell = ({ ref={ref} $disabled={disabled} $fill={fill} + $isDragSourceFaded={fadeSourceWhileDragging && isDragSource} $isDraggingHighlighted={highlightWhileDragging && isDragging} onDragStart={preventNativeDragStart} > diff --git a/packages/twenty-front/src/modules/ui/utilities/drag-and-drop/constants/DragSourceOpacity.ts b/packages/twenty-front/src/modules/ui/utilities/drag-and-drop/constants/DragSourceOpacity.ts new file mode 100644 index 0000000000..1480ef7161 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/utilities/drag-and-drop/constants/DragSourceOpacity.ts @@ -0,0 +1 @@ +export const DRAG_SOURCE_OPACITY = 'var(--dnd-drag-source-opacity)';