diff --git a/front/src/modules/opportunities/components/Board.tsx b/front/src/modules/opportunities/components/Board.tsx new file mode 100644 index 000000000..aeab00250 --- /dev/null +++ b/front/src/modules/opportunities/components/Board.tsx @@ -0,0 +1,76 @@ +import { useCallback, useState } from 'react'; +import { + DragDropContext, + Draggable, + Droppable, + OnDragEndResponder, +} from '@hello-pangea/dnd'; + +import { + Column, + getOptimisticlyUpdatedBoard, + Items, + StyledBoard, +} from '../../ui/components/board/Board'; +import { + ItemsContainer, + StyledColumn, + StyledColumnTitle, +} from '../../ui/components/board/BoardColumn'; +// Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd +// https://github.com/atlassian/react-beautiful-dnd/issues/2350 +import { BoardItem } from '../../ui/components/board/BoardItem'; +import { NewButton } from '../../ui/components/board/BoardNewButton'; + +type BoardProps = { + initialBoard: Column[]; + items: Items; +}; + +export const Board = ({ initialBoard, items }: BoardProps) => { + const [board, setBoard] = useState(initialBoard); + + const onDragEnd: OnDragEndResponder = useCallback( + (result) => { + const newBoard = getOptimisticlyUpdatedBoard(board, result); + if (!newBoard) return; + setBoard(newBoard); + // TODO implement update board mutation + }, + [board], + ); + + return ( + + + {board.map((column) => ( + + {(droppableProvided) => ( + + + • {column.title} + + + {column.itemKeys.map((itemKey, index) => ( + + {(draggableProvided) => ( + +

{items[itemKey].content}

+
+ )} +
+ ))} +
+ +
+ )} +
+ ))} +
+
+ ); +}; diff --git a/front/src/modules/ui/components/board/__stories__/Board.stories.tsx b/front/src/modules/opportunities/components/__stories__/Board.stories.tsx similarity index 100% rename from front/src/modules/ui/components/board/__stories__/Board.stories.tsx rename to front/src/modules/opportunities/components/__stories__/Board.stories.tsx diff --git a/front/src/modules/ui/components/board/__stories__/mock-data.ts b/front/src/modules/opportunities/components/__stories__/mock-data.ts similarity index 94% rename from front/src/modules/ui/components/board/__stories__/mock-data.ts rename to front/src/modules/opportunities/components/__stories__/mock-data.ts index 769477a26..a382080d3 100644 --- a/front/src/modules/ui/components/board/__stories__/mock-data.ts +++ b/front/src/modules/opportunities/components/__stories__/mock-data.ts @@ -1,4 +1,4 @@ -import { Column, Items } from '../Board'; +import { Column, Items } from '../../../ui/components/board/Board'; export const items: Items = { 'item-1': { id: 'item-1', content: 'Item 1' }, diff --git a/front/src/modules/ui/components/board/Board.tsx b/front/src/modules/ui/components/board/Board.tsx index d1ce8d9fb..3ae911eb7 100644 --- a/front/src/modules/ui/components/board/Board.tsx +++ b/front/src/modules/ui/components/board/Board.tsx @@ -1,18 +1,7 @@ -import { useCallback, useState } from 'react'; import styled from '@emotion/styled'; -import { - DragDropContext, - Draggable, - Droppable, - OnDragEndResponder, -} from '@hello-pangea/dnd'; +import { DropResult } from '@hello-pangea/dnd'; -// Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd -// https://github.com/atlassian/react-beautiful-dnd/issues/2350 -import { BoardCard } from './BoardCard'; -import { BoardColumn } from './BoardColumn'; - -const StyledBoard = styled.div` +export const StyledBoard = styled.div` display: flex; flex-direction: row; height: 100%; @@ -33,85 +22,39 @@ export interface Column { itemKeys: BoardItemKey[]; } -type BoardProps = { - initialBoard: Column[]; - items: Items; -}; - -export const Board = ({ initialBoard, items }: BoardProps) => { - const [board, setBoard] = useState(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], +export const getOptimisticlyUpdatedBoard = ( + board: Column[], + result: DropResult, +) => { + const { destination, source } = result; + if (!destination) return; + const sourceColumnIndex = board.findIndex( + (column) => column.id === source.droppableId, ); - - return ( - - - {board.map((column) => ( - - {(provided) => - provided && ( - - {column.itemKeys.map((itemKey, index) => ( - - {(provided) => - provided && ( - - ) - } - - ))} - - ) - } - - ))} - - + 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); + return newBoard; }; diff --git a/front/src/modules/ui/components/board/BoardColumn.tsx b/front/src/modules/ui/components/board/BoardColumn.tsx index a1525edd2..cf7655e3c 100644 --- a/front/src/modules/ui/components/board/BoardColumn.tsx +++ b/front/src/modules/ui/components/board/BoardColumn.tsx @@ -1,9 +1,8 @@ +import React from 'react'; import styled from '@emotion/styled'; import { DroppableProvided } from '@hello-pangea/dnd'; -import { NewButton } from './BoardNewButton'; - -const StyledColumn = styled.div` +export const StyledColumn = styled.div` display: flex; flex-direction: column; width: 300px; @@ -11,7 +10,7 @@ const StyledColumn = styled.div` padding: ${({ theme }) => theme.spacing(2)}; `; -const StyledColumnTitle = styled.h3` +export const StyledColumnTitle = styled.h3` font-family: 'Inter'; font-style: normal; font-weight: ${({ theme }) => theme.fontWeightBold}; @@ -22,32 +21,22 @@ const StyledColumnTitle = styled.h3` margin-bottom: ${({ theme }) => theme.spacing(2)}; `; -const ItemContainer = styled.div``; +export const StyledItemContainer = styled.div``; -type BoardColumnProps = { - title: string; - colorCode?: string; - children: any[]; - droppableProvided: DroppableProvided; -}; - -export const BoardColumn = ({ - title, - colorCode, +export const ItemsContainer = ({ children, droppableProvided, -}: BoardColumnProps) => { +}: { + children: React.ReactNode; + droppableProvided: DroppableProvided; +}) => { return ( - - • {title} - - {children} - {droppableProvided.placeholder} - - - + + {children} + {droppableProvided?.placeholder} + ); }; diff --git a/front/src/modules/ui/components/board/BoardCard.tsx b/front/src/modules/ui/components/board/BoardItem.tsx similarity index 56% rename from front/src/modules/ui/components/board/BoardCard.tsx rename to front/src/modules/ui/components/board/BoardItem.tsx index 9a120386b..3311e6e21 100644 --- a/front/src/modules/ui/components/board/BoardCard.tsx +++ b/front/src/modules/ui/components/board/BoardItem.tsx @@ -4,25 +4,25 @@ import { DraggableProvided } from '@hello-pangea/dnd'; const StyledCard = styled.div` background-color: ${({ theme }) => theme.secondaryBackground}; border: 1px solid ${({ theme }) => theme.quaternaryBackground}; - border-radius: 4px; - padding: 8px; - margin-bottom: 8px; + border-radius: ${({ theme }) => theme.borderRadius}; + padding: ${({ theme }) => theme.spacing(2)}; + margin-bottom: ${({ theme }) => theme.spacing(2)}; box-shadow: ${({ theme }) => theme.boxShadow}; `; type BoardCardProps = { - content: string; + children: React.ReactNode; draggableProvided: DraggableProvided; }; -export const BoardCard = ({ content, draggableProvided }: BoardCardProps) => { +export const BoardItem = ({ children, draggableProvided }: BoardCardProps) => { return ( - {content} + {children} ); }; diff --git a/front/src/modules/ui/components/board/BoardNewButton.tsx b/front/src/modules/ui/components/board/BoardNewButton.tsx index 756101d17..645432611 100644 --- a/front/src/modules/ui/components/board/BoardNewButton.tsx +++ b/front/src/modules/ui/components/board/BoardNewButton.tsx @@ -8,7 +8,7 @@ const StyledButton = styled.button` background-color: ${({ theme }) => theme.primaryBackground}; color: ${({ theme }) => theme.text40}; border: none; - border-radius: 4px; + border-radius: ${({ theme }) => theme.borderRadius}; cursor: pointer; transition: background-color 0.2s ease-in-out; align-self: baseline; diff --git a/front/src/pages/opportunities/Opportunities.tsx b/front/src/pages/opportunities/Opportunities.tsx index e026e2294..8d6a016b8 100644 --- a/front/src/pages/opportunities/Opportunities.tsx +++ b/front/src/pages/opportunities/Opportunities.tsx @@ -5,8 +5,8 @@ import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer' import { initialBoard, items, -} from '../../modules/ui/components/board/__stories__/mock-data'; -import { Board } from '../../modules/ui/components/board/Board'; +} from '../../modules/opportunities/components/__stories__/mock-data'; +import { Board } from '../../modules/opportunities/components/Board'; export function Opportunities() { return (