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 <img width="1400" height="1980" alt="drag-feedback-before-after" src="https://github.com/user-attachments/assets/ee1fe81f-a762-4343-8db6-c51cf63dbac8" />
This commit is contained in:
committed by
GitHub
parent
0f35b5895a
commit
8a856a4bce
@@ -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;
|
||||
|
||||
+1
@@ -41,6 +41,7 @@ export const RecordCalendarCardDraggableContainer = ({
|
||||
type={RECORD_CALENDAR_CARD_DND_TYPE}
|
||||
accept={RECORD_CALENDAR_CARD_DND_TYPE}
|
||||
disabled={dragIsDisabled}
|
||||
fadeSourceWhileDragging
|
||||
>
|
||||
<StyledDraggableContainer
|
||||
id={`record-calendar-card-${recordId}-${calendarDay}`}
|
||||
|
||||
+5
@@ -13,6 +13,7 @@ import { getRecordCalendarWeekTimedEventHeight } from '@/object-record/record-ca
|
||||
import { RecordCard } from '@/object-record/record-card/components/RecordCard';
|
||||
import { useOpenRecordFromIndexView } from '@/object-record/record-index/hooks/useOpenRecordFromIndexView';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { DRAG_SOURCE_OPACITY } from '@/ui/utilities/drag-and-drop/constants/DragSourceOpacity';
|
||||
import { useAtomComponentFamilyState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyState';
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
import { useDraggable } from '@dnd-kit/react';
|
||||
@@ -30,6 +31,7 @@ const RECORD_CALENDAR_WEEK_EVENT_EXPANDED_MIN_HEIGHT =
|
||||
RECORD_CALENDAR_WEEK_EVENT_TIME_ROW_HEIGHT;
|
||||
|
||||
const StyledEventPositioner = styled.div<{
|
||||
$isDragSourceFaded: boolean;
|
||||
columnCount: number;
|
||||
columnIndex: number;
|
||||
heightInPixels: number;
|
||||
@@ -47,6 +49,8 @@ const StyledEventPositioner = styled.div<{
|
||||
columnIndex,
|
||||
}).left};
|
||||
min-width: 0;
|
||||
opacity: ${({ $isDragSourceFaded }) =>
|
||||
$isDragSourceFaded ? DRAG_SOURCE_OPACITY : 1};
|
||||
overflow: hidden;
|
||||
position: ${({ isAllDay }) => (isAllDay ? 'relative' : 'absolute')};
|
||||
right: auto;
|
||||
@@ -257,6 +261,7 @@ export const RecordCalendarWeekEvent = ({
|
||||
return (
|
||||
<StyledEventPositioner
|
||||
ref={draggableRef}
|
||||
$isDragSourceFaded={isDragSource}
|
||||
columnCount={columnCount}
|
||||
columnIndex={columnIndex}
|
||||
heightInPixels={heightInPixels}
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
import { DRAG_SOURCE_OPACITY } from '@/ui/utilities/drag-and-drop/constants/DragSourceOpacity';
|
||||
import { styled } from '@linaria/react';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
@@ -11,7 +12,7 @@ const StyledBoardCard = styled.div<{
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
cursor: pointer;
|
||||
opacity: ${({ isDragging }) => (isDragging ? '0.3' : '1')};
|
||||
opacity: ${({ isDragging }) => (isDragging ? DRAG_SOURCE_OPACITY : 1)};
|
||||
|
||||
width: 100%;
|
||||
|
||||
|
||||
+3
@@ -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 (
|
||||
<RecordTableCellStyleWrapper
|
||||
isSelected={isSelected}
|
||||
isDragging={isDragging}
|
||||
hasRightBorder={false}
|
||||
widthClassName={RECORD_TABLE_COLUMN_CHECKBOX_WIDTH_CLASS_NAME}
|
||||
>
|
||||
|
||||
+1
@@ -41,6 +41,7 @@ export const RecordTableCellDragAndDrop = () => {
|
||||
return (
|
||||
<RecordTableCellStyleWrapper
|
||||
data-select-disable
|
||||
isDragging={isDragging}
|
||||
hasRightBorder={false}
|
||||
hasBottomBorder={false}
|
||||
widthClassName={RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH_CLASS_NAME}
|
||||
|
||||
+17
-23
@@ -2,7 +2,6 @@ import { useSortable } from '@dnd-kit/react/sortable';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type ReactNode, useContext, useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { ThemeContext } from 'twenty-ui/theme-constants';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { RecordGroupContext } from '@/object-record/record-group/states/context/RecordGroupContext';
|
||||
@@ -16,6 +15,7 @@ import { useIsTableRowSecondaryDragged } from '@/object-record/record-table/reco
|
||||
import { type RecordTableRowDragData } from '@/object-record/record-table/types/RecordTableRowDragData';
|
||||
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';
|
||||
|
||||
// The grip, checkbox and first field cells are sticky at
|
||||
@@ -44,8 +44,6 @@ export const RecordTableDraggableTr = ({
|
||||
onClick,
|
||||
children,
|
||||
}: RecordTableDraggableTrProps) => {
|
||||
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 (
|
||||
<RecordTableTr
|
||||
@@ -89,19 +86,16 @@ export const RecordTableDraggableTr = ({
|
||||
ref={ref}
|
||||
className={className}
|
||||
style={{
|
||||
background: isDragging ? theme.background.transparent.light : undefined,
|
||||
borderColor: isDragging
|
||||
? `${theme.border.color.medium}`
|
||||
: 'transparent',
|
||||
opacity: isSecondaryDragged ? 0.3 : undefined,
|
||||
opacity:
|
||||
isDragSource || isSecondaryDragged ? DRAG_SOURCE_OPACITY : undefined,
|
||||
}}
|
||||
isDragging={isDragging}
|
||||
isDragging={false}
|
||||
data-testid={`row-id-${recordId}`}
|
||||
data-selectable-id={recordId}
|
||||
onClick={onClick}
|
||||
>
|
||||
<DragDropItemSortableHandleRefContext.Provider value={handleRef}>
|
||||
<RecordTableRowDraggableContextProvider value={{ isDragging }}>
|
||||
<RecordTableRowDraggableContextProvider value={{ isDragging: false }}>
|
||||
{children}
|
||||
<RecordTableRowMultiDragPreview />
|
||||
</RecordTableRowDraggableContextProvider>
|
||||
|
||||
+2
@@ -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'};
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+7
@@ -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<string, unknown>;
|
||||
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}
|
||||
>
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const DRAG_SOURCE_OPACITY = 'var(--dnd-drag-source-opacity)';
|
||||
Reference in New Issue
Block a user