Skip to content

Commit

Permalink
feat: add DndTreeSelect component
Browse files Browse the repository at this point in the history
  • Loading branch information
GermanVor committed Feb 7, 2024
1 parent 2c1471c commit 1c1e9d2
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 111 deletions.
104 changes: 104 additions & 0 deletions src/components/TreeSelect/DndTreeSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React from 'react';

import {Grip} from '@gravity-ui/icons';
import {DragDropContext, Draggable, Droppable} from 'react-beautiful-dnd';
import type {OnDragEndResponder} from 'react-beautiful-dnd';

import {Icon} from '../Icon';
import {ListContainerView} from '../useList';
import type {ListItemType} from '../useList';
import {reorderArray} from '../useList/__stories__/utils/reorderArray';

import {TreeSelect} from './TreeSelect';
import {TreeSelectItem} from './TreeSelectItem';
import type {RenderContainerProps, RenderItem, TreeSelectProps} from './types';

const renderDndItem: RenderItem<string> = (item, state) => {
const commonProps = {
...state,
title: item,
endSlot: <Icon data={Grip} size={16} />,
};

return (
<Draggable draggableId={state.id} index={Number(state.id)} key={`item-key-${state.id}`}>
{(provided, snapshot) => {
// not expected offset appears, one way to fix - remove this offsets explicitly
if (snapshot.isDragging && provided.draggableProps.style) {
const style = provided.draggableProps.style as React.CSSProperties;

style.left = undefined;
style.top = undefined;
}

return (
<TreeSelectItem
ref={provided.innerRef}
{...commonProps}
{...provided.draggableProps}
{...provided.dragHandleProps}
active={snapshot.isDragging}
/>
);
}}
</Draggable>
);
};

export type DndTreeSelectProps<T> = Omit<
TreeSelectProps<T>,
'items' | 'renderContainer' | 'renderItem'
> & {
items: ListItemType<T>[];
setItems: (_: ListItemType<T>[]) => void;
};
export function DndTreeSelect({
items,
setItems,
...treeSelectNativeProps
}: DndTreeSelectProps<string>) {
const renderContainer = React.useCallback(
({renderItem, visibleFlattenIds, containerRef, id}: RenderContainerProps<string>) => {
const handleDrugEnd: OnDragEndResponder = ({destination, source}) => {
if (destination?.index && destination?.index !== source.index) {
const newItemsOrder = reorderArray(items, source.index, destination.index);
setItems(newItemsOrder);
}
};

const visibleFlattenItemList = visibleFlattenIds.map((visibleFlattenId) =>
renderItem(visibleFlattenId),
);

return (
<ListContainerView ref={containerRef} id={id}>
<DragDropContext onDragEnd={handleDrugEnd}>
<Droppable droppableId="droppable">
{(droppableProvided) => {
return (
<div
{...droppableProvided.droppableProps}
ref={droppableProvided.innerRef}
>
{visibleFlattenItemList}
{droppableProvided.placeholder}
</div>
);
}}
</Droppable>
</DragDropContext>
</ListContainerView>
);
},
[items, setItems],
);

return (
<TreeSelect
{...treeSelectNativeProps}
items={items}
renderContainer={renderContainer}
renderItem={renderDndItem}
/>
);
}
Original file line number Diff line number Diff line change
@@ -1,130 +1,21 @@
import React from 'react';

import {Grip} from '@gravity-ui/icons';
import {DragDropContext, Draggable, Droppable} from 'react-beautiful-dnd';
import type {
DraggableProvided,
DraggableRubric,
DraggableStateSnapshot,
DroppableProvided,
OnDragEndResponder,
} from 'react-beautiful-dnd';

import {Icon} from '../../../Icon';
import {Flex} from '../../../layout';
import {ListContainerView} from '../../../useList';
import {createRandomizedData} from '../../../useList/__stories__/utils/makeData';
import {reorderArray} from '../../../useList/__stories__/utils/reorderArray';
import {TreeSelect} from '../../TreeSelect';
import {TreeSelectItem} from '../../TreeSelectItem';
import type {TreeSelectItemProps} from '../../TreeSelectItem';
import {DndTreeSelect} from '../../DndTreeSelect';
import type {TreeSelectProps} from '../../types';

const DraggableListItem = ({
provided,
...props
}: {provided?: DraggableProvided} & TreeSelectItemProps) => {
return (
<TreeSelectItem
{...provided?.dragHandleProps}
{...provided?.draggableProps}
ref={provided?.innerRef}
{...props}
/>
);
};

export interface WithDndListExampleProps
extends Omit<TreeSelectProps<string>, 'value' | 'onUpdate' | 'items' | 'getItemContent'> {}

export const WithDndListExample = (props: WithDndListExampleProps) => {
const [items, setItems] = React.useState(() =>
createRandomizedData({num: 10, depth: 0, getData: (title) => title}),
);
const [value, setValue] = React.useState<string[]>([]);

const handleDrugEnd: OnDragEndResponder = ({destination, source}) => {
if (destination?.index && destination?.index !== source.index) {
setItems((items) => reorderArray(items, source.index, destination.index));
}
};

return (
<Flex>
<TreeSelect
{...props}
value={value}
items={items}
onItemClick={(_, {id, isGroup, disabled}) => {
if (!isGroup && !disabled) {
setValue([id]);
}
}}
renderContainer={({renderItem, visibleFlattenIds, containerRef, id}) => {
return (
<DragDropContext onDragEnd={handleDrugEnd}>
<Droppable
droppableId="droppable"
renderClone={(
provided: DraggableProvided,
snapshot: DraggableStateSnapshot,
rubric: DraggableRubric,
) => {
return renderItem(visibleFlattenIds[rubric.source.index], {
provided,
active: snapshot.isDragging,
});
}}
>
{(droppableProvided: DroppableProvided) => (
<ListContainerView ref={containerRef} id={id}>
<div
{...droppableProvided.droppableProps}
ref={droppableProvided.innerRef}
>
{visibleFlattenIds.map((id) => renderItem(id))}
{droppableProvided.placeholder}
</div>
</ListContainerView>
)}
</Droppable>
</DragDropContext>
);
}}
renderItem={(item, state, _listContext, renderContextProps) => {
const commonProps = {
...state,
title: item,
endSlot: <Icon data={Grip} size={16} />,
};

// here passed props from `renderContainer` method.
if (renderContextProps) {
return (
<DraggableListItem
key={`item-key-${state.id}`}
{...commonProps}
{...renderContextProps}
/>
);
}
return (
<Draggable
draggableId={state.id}
index={Number(state.id)}
key={`item-key-${state.id}`}
>
{(provided: DraggableProvided, snapshot: DraggableStateSnapshot) => (
<DraggableListItem
provided={provided}
{...commonProps}
active={snapshot.isDragging}
/>
)}
</Draggable>
);
}}
/>
<DndTreeSelect {...props} items={items} setItems={setItems} />
</Flex>
);
};

0 comments on commit 1c1e9d2

Please sign in to comment.