278 refactor uiboard opportunitiesboard + put state in recoil (#280)

* refactor: move Board file to opportunities

* refactor: dropable props are move in ui component

* refactor: rename provided in droppableProvided

* refactor: rename provided in draggableProvided

* refactor: rename BoardCard in BoardItem

* refactor: rename BoardCard in BoardItem file

* refactor: BoardItem use children instead of content

* refactor: Extract StyledColumnContainer

* refactor: create method to get optimistic new board after update

* refactor: move getOptimisticNewBoard in board UI

* refactor: make provided nullable

* lint: remove unused import
This commit is contained in:
Sammy Teillet
2023-06-13 17:02:09 +02:00
committed by GitHub
parent 3a719001de
commit c20fd458ae
8 changed files with 139 additions and 131 deletions

View File

@@ -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<Column[]>(initialBoard);
const onDragEnd: OnDragEndResponder = useCallback(
(result) => {
const newBoard = getOptimisticlyUpdatedBoard(board, result);
if (!newBoard) return;
setBoard(newBoard);
// TODO implement update board mutation
},
[board],
);
return (
<DragDropContext onDragEnd={onDragEnd}>
<StyledBoard>
{board.map((column) => (
<Droppable key={column.id} droppableId={column.id}>
{(droppableProvided) => (
<StyledColumn>
<StyledColumnTitle color={column.colorCode}>
{column.title}
</StyledColumnTitle>
<ItemsContainer droppableProvided={droppableProvided}>
{column.itemKeys.map((itemKey, index) => (
<Draggable
key={itemKey}
draggableId={itemKey}
index={index}
>
{(draggableProvided) => (
<BoardItem draggableProvided={draggableProvided}>
<p>{items[itemKey].content}</p>
</BoardItem>
)}
</Draggable>
))}
</ItemsContainer>
<NewButton />
</StyledColumn>
)}
</Droppable>
))}
</StyledBoard>
</DragDropContext>
);
};

View File

@@ -1,4 +1,4 @@
import { Column, Items } from '../Board'; import { Column, Items } from '../../../ui/components/board/Board';
export const items: Items = { export const items: Items = {
'item-1': { id: 'item-1', content: 'Item 1' }, 'item-1': { id: 'item-1', content: 'Item 1' },

View File

@@ -1,18 +1,7 @@
import { useCallback, useState } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { import { DropResult } from '@hello-pangea/dnd';
DragDropContext,
Draggable,
Droppable,
OnDragEndResponder,
} from '@hello-pangea/dnd';
// Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd export const StyledBoard = styled.div`
// https://github.com/atlassian/react-beautiful-dnd/issues/2350
import { BoardCard } from './BoardCard';
import { BoardColumn } from './BoardColumn';
const StyledBoard = styled.div`
display: flex; display: flex;
flex-direction: row; flex-direction: row;
height: 100%; height: 100%;
@@ -33,16 +22,10 @@ export interface Column {
itemKeys: BoardItemKey[]; itemKeys: BoardItemKey[];
} }
type BoardProps = { export const getOptimisticlyUpdatedBoard = (
initialBoard: Column[]; board: Column[],
items: Items; result: DropResult,
}; ) => {
export const Board = ({ initialBoard, items }: BoardProps) => {
const [board, setBoard] = useState<Column[]>(initialBoard);
const onDragEnd: OnDragEndResponder = useCallback(
(result) => {
const { destination, source } = result; const { destination, source } = result;
if (!destination) return; if (!destination) return;
const sourceColumnIndex = board.findIndex( const sourceColumnIndex = board.findIndex(
@@ -73,45 +56,5 @@ export const Board = ({ initialBoard, items }: BoardProps) => {
const newBoard = [...board]; const newBoard = [...board];
newBoard.splice(sourceColumnIndex, 1, newSourceColumn); newBoard.splice(sourceColumnIndex, 1, newSourceColumn);
newBoard.splice(destinationColumnIndex, 1, newDestinationColumn); newBoard.splice(destinationColumnIndex, 1, newDestinationColumn);
setBoard(newBoard); return newBoard;
},
[board],
);
return (
<DragDropContext onDragEnd={onDragEnd}>
<StyledBoard>
{board.map((column) => (
<Droppable key={column.id} droppableId={column.id}>
{(provided) =>
provided && (
<BoardColumn
title={column.title}
colorCode={column.colorCode}
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

@@ -1,9 +1,8 @@
import React from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { DroppableProvided } from '@hello-pangea/dnd'; import { DroppableProvided } from '@hello-pangea/dnd';
import { NewButton } from './BoardNewButton'; export const StyledColumn = styled.div`
const StyledColumn = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 300px; width: 300px;
@@ -11,7 +10,7 @@ const StyledColumn = styled.div`
padding: ${({ theme }) => theme.spacing(2)}; padding: ${({ theme }) => theme.spacing(2)};
`; `;
const StyledColumnTitle = styled.h3` export const StyledColumnTitle = styled.h3`
font-family: 'Inter'; font-family: 'Inter';
font-style: normal; font-style: normal;
font-weight: ${({ theme }) => theme.fontWeightBold}; font-weight: ${({ theme }) => theme.fontWeightBold};
@@ -22,32 +21,22 @@ const StyledColumnTitle = styled.h3`
margin-bottom: ${({ theme }) => theme.spacing(2)}; margin-bottom: ${({ theme }) => theme.spacing(2)};
`; `;
const ItemContainer = styled.div``; export const StyledItemContainer = styled.div``;
type BoardColumnProps = { export const ItemsContainer = ({
title: string;
colorCode?: string;
children: any[];
droppableProvided: DroppableProvided;
};
export const BoardColumn = ({
title,
colorCode,
children, children,
droppableProvided, droppableProvided,
}: BoardColumnProps) => { }: {
children: React.ReactNode;
droppableProvided: DroppableProvided;
}) => {
return ( return (
<StyledColumn> <StyledItemContainer
<StyledColumnTitle color={colorCode}> {title}</StyledColumnTitle> ref={droppableProvided?.innerRef}
<ItemContainer {...droppableProvided?.droppableProps}
ref={droppableProvided.innerRef}
{...droppableProvided.droppableProps}
> >
{children} {children}
{droppableProvided.placeholder} {droppableProvided?.placeholder}
<NewButton /> </StyledItemContainer>
</ItemContainer>
</StyledColumn>
); );
}; };

View File

@@ -4,25 +4,25 @@ import { DraggableProvided } from '@hello-pangea/dnd';
const StyledCard = styled.div` const StyledCard = styled.div`
background-color: ${({ theme }) => theme.secondaryBackground}; background-color: ${({ theme }) => theme.secondaryBackground};
border: 1px solid ${({ theme }) => theme.quaternaryBackground}; border: 1px solid ${({ theme }) => theme.quaternaryBackground};
border-radius: 4px; border-radius: ${({ theme }) => theme.borderRadius};
padding: 8px; padding: ${({ theme }) => theme.spacing(2)};
margin-bottom: 8px; margin-bottom: ${({ theme }) => theme.spacing(2)};
box-shadow: ${({ theme }) => theme.boxShadow}; box-shadow: ${({ theme }) => theme.boxShadow};
`; `;
type BoardCardProps = { type BoardCardProps = {
content: string; children: React.ReactNode;
draggableProvided: DraggableProvided; draggableProvided: DraggableProvided;
}; };
export const BoardCard = ({ content, draggableProvided }: BoardCardProps) => { export const BoardItem = ({ children, draggableProvided }: BoardCardProps) => {
return ( return (
<StyledCard <StyledCard
ref={draggableProvided?.innerRef} ref={draggableProvided?.innerRef}
{...draggableProvided.dragHandleProps} {...draggableProvided?.dragHandleProps}
{...draggableProvided.draggableProps} {...draggableProvided?.draggableProps}
> >
{content} {children}
</StyledCard> </StyledCard>
); );
}; };

View File

@@ -8,7 +8,7 @@ const StyledButton = styled.button`
background-color: ${({ theme }) => theme.primaryBackground}; background-color: ${({ theme }) => theme.primaryBackground};
color: ${({ theme }) => theme.text40}; color: ${({ theme }) => theme.text40};
border: none; border: none;
border-radius: 4px; border-radius: ${({ theme }) => theme.borderRadius};
cursor: pointer; cursor: pointer;
transition: background-color 0.2s ease-in-out; transition: background-color 0.2s ease-in-out;
align-self: baseline; align-self: baseline;

View File

@@ -5,8 +5,8 @@ import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer'
import { import {
initialBoard, initialBoard,
items, items,
} from '../../modules/ui/components/board/__stories__/mock-data'; } from '../../modules/opportunities/components/__stories__/mock-data';
import { Board } from '../../modules/ui/components/board/Board'; import { Board } from '../../modules/opportunities/components/Board';
export function Opportunities() { export function Opportunities() {
return ( return (