Skip to content

Commit

Permalink
feat(Table): fixed drag context and styles for prevent tattered behav…
Browse files Browse the repository at this point in the history
…iour
  • Loading branch information
tapo4ek committed Sep 26, 2024
1 parent e60e268 commit 12fc6c8
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 74 deletions.
3 changes: 3 additions & 0 deletions src/components/TableSettings/TableSettings.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ $block: '.#{variables.$ns}table-settings';
#{$block} {
&__popover-content {
padding: var(--g-spacing-1);
margin-block: -10px;
}

&__popover-actions {
position: relative;
padding: var(--g-spacing-2);
background-color: var(--g-color-base-background);
}
}
48 changes: 24 additions & 24 deletions src/components/TableSettings/TableSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,26 @@ export const TableSettings = <TData extends unknown>({
);
const [orderState, setOrderState] = React.useState(() => getInitialOrderItems(filteredColumns));

const applyNewSettings = () => {
const columnOrder = orderStateToColumnOrder(orderState);

if (onSettingsApply) onSettingsApply({visibilityState, columnOrder});

if (filterable) table.setColumnVisibility(visibilityState);
if (sortable) table.setColumnOrder(columnOrder);

setOpen(false);
};

const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor));

const {orderedItems, activeDepth, handleDragEnd, handleDragStart, handleDragCancel} =
useOrderedItems(filteredColumns, orderState, setOrderState);

const renderColumns = (renderedColumns: Column<TData>[]) => {
return renderedColumns.map((innerColumn, index) => {
return renderedColumns.map((innerColumn) => {
const children = renderColumns(innerColumn.columns);
const header = headersById[innerColumn.id];
const rootNode = innerColumn.depth === 0;
const lastInGroup = index === renderedColumns.length - 1;

return (
<TableSettingsColumn
Expand All @@ -81,34 +95,15 @@ export const TableSettings = <TData extends unknown>({
visibilityState={visibilityState}
sortable={sortable}
filterable={filterable}
activeDepth={activeDepth}
onVisibilityToggle={setVisibilityState}
showDivider={rootNode && !lastInGroup}
>
{children}
</TableSettingsColumn>
);
});
};

const applyNewSettings = () => {
const columnOrder = orderStateToColumnOrder(orderState);

if (onSettingsApply) onSettingsApply({visibilityState, columnOrder});

if (filterable) table.setColumnVisibility(visibilityState);
if (sortable) table.setColumnOrder(columnOrder);

setOpen(false);
};

const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor));

const {orderedItems, handleDragEnd} = useOrderedItems(
filteredColumns,
orderState,
setOrderState,
);

return (
<React.Fragment>
<Popup
Expand All @@ -118,7 +113,12 @@ export const TableSettings = <TData extends unknown>({
placement={POPUP_PLACEMENT}
>
<div className={b('popover-content')}>
<DndContext onDragEnd={handleDragEnd} sensors={sensors}>
<DndContext
onDragEnd={handleDragEnd}
onDragStart={handleDragStart}
onDragCancel={handleDragCancel}
sensors={sensors}
>
<SortableContext
items={orderedItems.map(({id}) => id)}
strategy={verticalListSortingStrategy}
Expand Down
31 changes: 27 additions & 4 deletions src/components/TableSettings/TableSettings.utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';

import type {DragEndEvent, UniqueIdentifier} from '@dnd-kit/core';
import type {DragEndEvent, DragStartEvent, UniqueIdentifier} from '@dnd-kit/core';
import {arrayMove} from '@dnd-kit/sortable';
import type {Column} from '@tanstack/react-table';

Expand Down Expand Up @@ -32,6 +32,22 @@ export const useOrderedItems = <TData extends unknown>(
orderState: Record<string, string[]>,
setOrderState: React.Dispatch<React.SetStateAction<Record<string, string[]>>>,
) => {
const [activeDepth, setActiveDepth] = React.useState<number | undefined>();

const dephMap = React.useMemo(() => {
const stack = [...items];
const result: Record<string, number> = {};

while (stack.length) {
const item = stack.pop();
if (!item) continue;
result[item.id] = item.depth;
if (item.columns) stack.push(...item.columns);
}

return result;
}, [items]);

const orderedItems = React.useMemo(() => {
return (
orderState['root']
Expand All @@ -42,13 +58,12 @@ export const useOrderedItems = <TData extends unknown>(
}, [orderState, items]);

const handleDragEnd = ({active, over}: DragEndEvent) => {
setActiveDepth(undefined);
const activeContainer = findContainer(orderState, active.id);
const overContainer = findContainer(orderState, over?.id);
if (!activeContainer || !overContainer || activeContainer !== overContainer) return;

const activeIndex = orderState[activeContainer].indexOf(active.id.toString());
const overIndex = over ? orderState[overContainer].indexOf(over?.id.toString()) : -1;

if (activeIndex !== overIndex) {
setOrderState((prevState) => ({
...prevState,
Expand All @@ -57,7 +72,15 @@ export const useOrderedItems = <TData extends unknown>(
}
};

return {orderedItems, handleDragEnd};
const handleDragStart = ({active}: DragStartEvent) => {
setActiveDepth(dephMap[active.id]);
};

const handleDragCancel = () => {
setActiveDepth(undefined);
};

return {orderedItems, activeDepth, handleDragEnd, handleDragStart, handleDragCancel};
};

export const orderStateToColumnOrder = (state: Record<string, string[]>) => {
Expand Down
28 changes: 20 additions & 8 deletions src/components/TableSettingsColumn/TableSettingsColumn.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
$block: '.#{variables.$ns}table-settings-column';

#{$block} {
border-radius: var(--g-spacing-1);
overflow: hidden;
position: relative;

&:hover:not(:has(#{$block}:hover)) {
background-color: var(--g-color-base-simple-hover);
&__layout {
border-radius: var(--g-spacing-1);

&:hover:not(:has(#{$block}__layout:hover)) {
overflow: hidden;
background-color: var(--g-color-base-simple-hover);
}
}

&__drag-handle {
Expand Down Expand Up @@ -36,10 +40,6 @@ $block: '.#{variables.$ns}table-settings-column';

&__divider {
padding: var(--g-spacing-1) var(--g-spacing-2);

&_hidden {
height: 1px;
}
}

&__name {
Expand All @@ -48,3 +48,15 @@ $block: '.#{variables.$ns}table-settings-column';
}
}
}

#{$block}_dragging {
z-index: 2;

> #{$block}__background {
background-color: var(--g-color-base-background);
}
}

#{$block}_root#{$block}_dragging {
padding-bottom: 9px;
}
87 changes: 49 additions & 38 deletions src/components/TableSettingsColumn/TableSettingsColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface Props<TData> extends TableSettingsOptions {
column: Column<TData>;
header: Header<TData, unknown>;
visibilityState: VisibilityState;
showDivider: boolean;
activeDepth?: number;
onVisibilityToggle: (updater: Updater<VisibilityState>) => void;
}

Expand All @@ -26,9 +26,9 @@ export const TableSettingsColumn = <TData extends unknown>({
header,
children,
visibilityState,
showDivider,
filterable,
sortable,
activeDepth,
onVisibilityToggle,
}: React.PropsWithChildren<Props<TData>>) => {
const innerColumns = column.getLeafColumns();
Expand Down Expand Up @@ -73,8 +73,16 @@ export const TableSettingsColumn = <TData extends unknown>({
return spacers;
};

const isDisabledContext =
typeof activeDepth === 'number' ? activeDepth !== column.depth : false;
const isRoot = column.depth === 0;

const {attributes, listeners, setNodeRef, transform, transition, isDragging} = useSortable({
id: column.id,
disabled: {
draggable: isDisabledContext,
droppable: isDisabledContext,
},
});

if (transform) transform.scaleY = 1;
Expand All @@ -84,44 +92,47 @@ export const TableSettingsColumn = <TData extends unknown>({
};

return (
<div style={style} ref={setNodeRef}>
<div
key={column.id}
className={b({'with-divider': showDivider && !isDragging})}
{...attributes}
data-role="drag-handle"
>
<div className={b('content')}>
{sortable ? (
<span className={b('drag-handle', {dragging: isDragging})} {...listeners}>
<Icon data={Grip} size={16} />
</span>
) : null}
{filterable ? (
<Checkbox
checked={isVisible}
disabled={!isEnabledHidding(column)}
onChange={toggle}
indeterminate={isIndeterminate}
/>
) : null}
{renderSpacers()}
<Text variant="body-1" className={b('name', {parent: isParent})}>
{columnHeader}
</Text>
</div>

<SortableContext
id={column.id}
items={column.columns?.map(({id}) => id)}
strategy={verticalListSortingStrategy}
<div style={style} ref={setNodeRef} className={b({dragging: isDragging, root: isRoot})}>
<div className={b('background')}>
<div
{...attributes}
className={b('layout', {'hide-divider': isRoot && !isDragging})}
>
{children}
</SortableContext>
<div className={b('content')}>
{sortable ? (
<span
className={b('drag-handle', {dragging: isDragging})}
{...listeners}
>
<Icon data={Grip} size={16} />
</span>
) : null}
{filterable ? (
<Checkbox
checked={isVisible}
disabled={!isEnabledHidding(column)}
onChange={toggle}
indeterminate={isIndeterminate}
/>
) : null}
{renderSpacers()}
<Text variant="body-1" className={b('name', {parent: isParent})}>
{columnHeader}
</Text>
</div>

<SortableContext
id={column.id}
items={column.columns?.map(({id}) => id)}
strategy={verticalListSortingStrategy}
>
{children}
</SortableContext>
</div>
</div>
{showDivider ? (
<div className={b('divider', {hidden: isDragging})}>
{isDragging ? null : <Divider />}
{isRoot && !isDragging ? (
<div className={b('divider')}>
<Divider />
</div>
) : null}
</div>
Expand Down

0 comments on commit 12fc6c8

Please sign in to comment.