mirror of
https://github.com/lingble/twenty.git
synced 2025-11-21 00:15:07 +00:00
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:
76
front/src/modules/opportunities/components/Board.tsx
Normal file
76
front/src/modules/opportunities/components/Board.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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' },
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
Reference in New Issue
Block a user