Sammy/t 394 aadev i have a storybook with drag and drop features (#255)

* feature: add Board component

* feature: add BoardColumn

* feature: add BoardCard

* chore: install react beautiful dnd

* refactor: use children instead of item parameter

* feature: wrap board in DragDropContext

* feature: wrap columns in dropable

* feature: wrap boardCard in draggable

* feature: add a second column

* refactor: rename columns to initialBoard

* refactor: use itemKeys instead if item directly

* feature: add key for react to render

* refactor: type the onDragEnd callback

* feature: drag and drop elements between columns
This commit is contained in:
Sammy Teillet
2023-06-08 15:09:40 +02:00
committed by GitHub
parent ce4ba10f7b
commit b827716d1b
6 changed files with 322 additions and 0 deletions

View File

@ -0,0 +1,127 @@
import { useCallback, useState } from 'react';
import {
DragDropContext,
Draggable,
Droppable,
OnDragEndResponder,
} from 'react-beautiful-dnd';
import styled from '@emotion/styled';
import { BoardCard } from './BoardCard';
import { BoardColumn } from './BoardColumn';
const StyledBoard = styled.div`
display: flex;
flex-direction: row;
height: 100%;
`;
type ItemKey = `item-${number}`;
interface Item {
id: string;
content: string;
}
interface Items {
[key: string]: Item;
}
interface Column {
id: string;
title: string;
itemKeys: ItemKey[];
}
const items: Items = {
'item-1': { id: 'item-1', content: 'Item 1' },
'item-2': { id: 'item-2', content: 'Item 2' },
'item-3': { id: 'item-3', content: 'Item 3' },
'item-4': { id: 'item-4', content: 'Item 4' },
'item-5': { id: 'item-5', content: 'Item 5' },
'item-6': { id: 'item-6', content: 'Item 6' },
} satisfies Record<ItemKey, { id: ItemKey; content: string }>;
const initialBoard = [
{
id: 'column-1',
title: 'Column 1',
itemKeys: ['item-1', 'item-2', 'item-3', 'item-4'],
},
{
id: 'column-2',
title: 'Column 2',
itemKeys: ['item-5', 'item-6'],
},
] satisfies Column[];
export const Board = () => {
const [board, setBoard] = useState<Column[]>(initialBoard);
const onDragEnd: OnDragEndResponder = useCallback(
(result) => {
const { destination, source } = result;
if (!destination) return;
const sourceColumnIndex = board.findIndex(
(column) => column.id === source.droppableId,
);
const sourceColumn = board[sourceColumnIndex];
const destinationColumnIndex = board.findIndex(
(column) => column.id === destination.droppableId,
);
const destinationColumn = board[destinationColumnIndex];
if (!destinationColumn || !sourceColumn) return;
const sourceItems = sourceColumn.itemKeys;
const destinationItems = destinationColumn.itemKeys;
const [removed] = sourceItems.splice(source.index, 1);
destinationItems.splice(destination.index, 0, removed);
const newSourceColumn = {
...sourceColumn,
itemKeys: sourceItems,
};
const newDestinationColumn = {
...destinationColumn,
itemKeys: destinationItems,
};
const newBoard = [...board];
newBoard.splice(sourceColumnIndex, 1, newSourceColumn);
newBoard.splice(destinationColumnIndex, 1, newDestinationColumn);
setBoard(newBoard);
},
[board],
);
return (
<DragDropContext onDragEnd={onDragEnd}>
<StyledBoard>
{board.map((column) => (
<Droppable key={column.id} droppableId={column.id}>
{(provided) =>
provided && (
<BoardColumn title={column.title} droppableProvided={provided}>
{column.itemKeys.map((itemKey, index) => (
<Draggable
key={itemKey}
draggableId={itemKey}
index={index}
>
{(provided) =>
provided && (
<BoardCard
content={items[itemKey].content}
draggableProvided={provided}
/>
)
}
</Draggable>
))}
</BoardColumn>
)
}
</Droppable>
))}
</StyledBoard>
</DragDropContext>
);
};

View File

@ -0,0 +1,26 @@
import { DraggableProvided } from 'react-beautiful-dnd';
import styled from '@emotion/styled';
const StyledCard = styled.div`
background-color: #ffffff;
border-radius: 4px;
padding: 16px;
margin-bottom: 16px;
`;
type BoardCardProps = {
content: string;
draggableProvided: DraggableProvided;
};
export const BoardCard = ({ content, draggableProvided }: BoardCardProps) => {
return (
<StyledCard
ref={draggableProvided?.innerRef}
{...draggableProvided.dragHandleProps}
{...draggableProvided.draggableProps}
>
{content}
</StyledCard>
);
};

View File

@ -0,0 +1,35 @@
import { DroppableProvided } from 'react-beautiful-dnd';
import styled from '@emotion/styled';
const StyledColumn = styled.div`
display: flex;
flex-direction: column;
width: 300px;
margin-right: 16px;
background-color: #f5f5f5;
border-radius: 4px;
padding: 16px;
`;
type BoardColumnProps = {
title: string;
children: any[];
droppableProvided: DroppableProvided;
};
export const BoardColumn = ({
title,
children,
droppableProvided,
}: BoardColumnProps) => {
return (
<StyledColumn
ref={droppableProvided.innerRef}
{...droppableProvided.droppableProps}
>
<h3>{title}</h3>
{children}
{droppableProvided.placeholder}
</StyledColumn>
);
};

View File

@ -0,0 +1,15 @@
import { Meta, StoryObj } from '@storybook/react';
import { Board } from '../Board';
const meta: Meta<typeof Board> = {
title: 'Components/Board',
component: Board,
};
export default meta;
type Story = StoryObj<typeof Board>;
export const OneColumnBoard: Story = {
render: () => <Board />,
};