Sammy/t 392 aau i can drag and drop opportunities (#257)
* refactor: extract data from Board component * feature: display board on opportunities page * test: add strict mode in storybook * chore: replace dnd to make it work with React 18 and strict mode Atlassion has not fixed this issue in a year so we use the fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350 * refactor: move mocked-data in a file * chore: use real column names in mock data * feature: design columns * feature: add New button at bottum of columns * bugfix: move header out of dragable so the cards does not flicker on drop * lint: remove useless imports * refactor: rename board item key
This commit is contained in:
@ -1,12 +1,14 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import {
|
||||
DragDropContext,
|
||||
Draggable,
|
||||
Droppable,
|
||||
OnDragEndResponder,
|
||||
} from 'react-beautiful-dnd';
|
||||
import styled from '@emotion/styled';
|
||||
} 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';
|
||||
|
||||
@ -16,43 +18,27 @@ const StyledBoard = styled.div`
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
type ItemKey = `item-${number}`;
|
||||
interface Item {
|
||||
export type BoardItemKey = `item-${number}`;
|
||||
export interface Item {
|
||||
id: string;
|
||||
content: string;
|
||||
}
|
||||
interface Items {
|
||||
export interface Items {
|
||||
[key: string]: Item;
|
||||
}
|
||||
interface Column {
|
||||
export interface Column {
|
||||
id: string;
|
||||
title: string;
|
||||
itemKeys: ItemKey[];
|
||||
colorCode?: string;
|
||||
itemKeys: BoardItemKey[];
|
||||
}
|
||||
|
||||
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 }>;
|
||||
type BoardProps = {
|
||||
initialBoard: Column[];
|
||||
items: Items;
|
||||
};
|
||||
|
||||
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 = () => {
|
||||
export const Board = ({ initialBoard, items }: BoardProps) => {
|
||||
const [board, setBoard] = useState<Column[]>(initialBoard);
|
||||
|
||||
const onDragEnd: OnDragEndResponder = useCallback(
|
||||
@ -99,7 +85,11 @@ export const Board = () => {
|
||||
<Droppable key={column.id} droppableId={column.id}>
|
||||
{(provided) =>
|
||||
provided && (
|
||||
<BoardColumn title={column.title} droppableProvided={provided}>
|
||||
<BoardColumn
|
||||
title={column.title}
|
||||
colorCode={column.colorCode}
|
||||
droppableProvided={provided}
|
||||
>
|
||||
{column.itemKeys.map((itemKey, index) => (
|
||||
<Draggable
|
||||
key={itemKey}
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import { DraggableProvided } from 'react-beautiful-dnd';
|
||||
import styled from '@emotion/styled';
|
||||
import { DraggableProvided } from '@hello-pangea/dnd';
|
||||
|
||||
const StyledCard = styled.div`
|
||||
background-color: #ffffff;
|
||||
background-color: ${({ theme }) => theme.secondaryBackground};
|
||||
border: 1px solid ${({ theme }) => theme.quaternaryBackground};
|
||||
border-radius: 4px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
padding: 8px;
|
||||
margin-bottom: 8px;
|
||||
box-shadow: ${({ theme }) => theme.boxShadow};
|
||||
`;
|
||||
|
||||
type BoardCardProps = {
|
||||
|
||||
@ -1,35 +1,53 @@
|
||||
import { DroppableProvided } from 'react-beautiful-dnd';
|
||||
import styled from '@emotion/styled';
|
||||
import { DroppableProvided } from '@hello-pangea/dnd';
|
||||
|
||||
import { NewButton } from './BoardNewButton';
|
||||
|
||||
const StyledColumn = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 300px;
|
||||
margin-right: 16px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
padding: 16px;
|
||||
background-color: ${({ theme }) => theme.primaryBackground};
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledColumnTitle = styled.h3`
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: ${({ theme }) => theme.fontWeightBold};
|
||||
font-size: ${({ theme }) => theme.fontSizeMedium};
|
||||
line-height: ${({ theme }) => theme.lineHeight};
|
||||
color: ${({ color }) => color};
|
||||
margin: 0;
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const ItemContainer = styled.div``;
|
||||
|
||||
type BoardColumnProps = {
|
||||
title: string;
|
||||
colorCode?: string;
|
||||
children: any[];
|
||||
droppableProvided: DroppableProvided;
|
||||
};
|
||||
|
||||
export const BoardColumn = ({
|
||||
title,
|
||||
colorCode,
|
||||
children,
|
||||
droppableProvided,
|
||||
}: BoardColumnProps) => {
|
||||
return (
|
||||
<StyledColumn
|
||||
ref={droppableProvided.innerRef}
|
||||
{...droppableProvided.droppableProps}
|
||||
>
|
||||
<h3>{title}</h3>
|
||||
{children}
|
||||
{droppableProvided.placeholder}
|
||||
<StyledColumn>
|
||||
<StyledColumnTitle color={colorCode}>• {title}</StyledColumnTitle>
|
||||
<ItemContainer
|
||||
ref={droppableProvided.innerRef}
|
||||
{...droppableProvided.droppableProps}
|
||||
>
|
||||
{children}
|
||||
{droppableProvided.placeholder}
|
||||
<NewButton />
|
||||
</ItemContainer>
|
||||
</StyledColumn>
|
||||
);
|
||||
};
|
||||
|
||||
29
front/src/modules/ui/components/board/BoardNewButton.tsx
Normal file
29
front/src/modules/ui/components/board/BoardNewButton.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { IconPlus } from '@tabler/icons-react';
|
||||
|
||||
const StyledButton = styled.button`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: ${({ theme }) => theme.primaryBackground};
|
||||
color: ${({ theme }) => theme.text40};
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
align-self: baseline;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.secondaryBackground};
|
||||
}
|
||||
`;
|
||||
|
||||
export const NewButton = () => {
|
||||
return (
|
||||
<StyledButton>
|
||||
<IconPlus size={16} />
|
||||
New
|
||||
</StyledButton>
|
||||
);
|
||||
};
|
||||
@ -1,7 +1,10 @@
|
||||
import { StrictMode } from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Board } from '../Board';
|
||||
|
||||
import { initialBoard, items } from './mock-data';
|
||||
|
||||
const meta: Meta<typeof Board> = {
|
||||
title: 'Components/Board',
|
||||
component: Board,
|
||||
@ -11,5 +14,9 @@ export default meta;
|
||||
type Story = StoryObj<typeof Board>;
|
||||
|
||||
export const OneColumnBoard: Story = {
|
||||
render: () => <Board />,
|
||||
render: () => (
|
||||
<StrictMode>
|
||||
<Board initialBoard={initialBoard} items={items} />
|
||||
</StrictMode>
|
||||
),
|
||||
};
|
||||
|
||||
@ -0,0 +1,55 @@
|
||||
import { Column, Items } from '../Board';
|
||||
|
||||
export 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' },
|
||||
};
|
||||
for (let i = 7; i <= 20; i++) {
|
||||
const key = `item-${i}`;
|
||||
items[key] = { id: key, content: `Item ${i}` };
|
||||
}
|
||||
|
||||
export const initialBoard = [
|
||||
{
|
||||
id: 'column-1',
|
||||
title: 'New',
|
||||
colorCode: '#B76796',
|
||||
itemKeys: [
|
||||
'item-1',
|
||||
'item-2',
|
||||
'item-3',
|
||||
'item-4',
|
||||
'item-7',
|
||||
'item-8',
|
||||
'item-9',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'column-2',
|
||||
title: 'Screening',
|
||||
colorCode: '#CB912F',
|
||||
itemKeys: ['item-5', 'item-6'],
|
||||
},
|
||||
{
|
||||
id: 'column-3',
|
||||
colorCode: '#9065B0',
|
||||
title: 'Meeting',
|
||||
itemKeys: [],
|
||||
},
|
||||
{
|
||||
id: 'column-4',
|
||||
title: 'Proposal',
|
||||
colorCode: '#337EA9',
|
||||
itemKeys: [],
|
||||
},
|
||||
{
|
||||
id: 'column-5',
|
||||
colorCode: '#079039',
|
||||
title: 'Customer',
|
||||
itemKeys: [],
|
||||
},
|
||||
] satisfies Column[];
|
||||
Reference in New Issue
Block a user