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:
127
front/src/modules/ui/components/board/Board.tsx
Normal file
127
front/src/modules/ui/components/board/Board.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
26
front/src/modules/ui/components/board/BoardCard.tsx
Normal file
26
front/src/modules/ui/components/board/BoardCard.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
35
front/src/modules/ui/components/board/BoardColumn.tsx
Normal file
35
front/src/modules/ui/components/board/BoardColumn.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@ -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 />,
|
||||
};
|
||||
Reference in New Issue
Block a user