Skip to content

Commit

Permalink
2 merge types (christopher-caldwell#15)
Browse files Browse the repository at this point in the history
* fix: Consolidating types and adding a one board

* feat: Adding the unified board
  • Loading branch information
christopher-caldwell authored Nov 1, 2022
1 parent fda579d commit c8cd185
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 95 deletions.
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ yarn add @caldwell619/react-kanban

There are 2 main boards, `Controlled` and `Uncontrolled`.

This is a deviation from the original, as there was only one board exported. In the original, there's an in-library determination on whether or not the board is controlled. As of right now, that is not how this works, as you will know upfront whether or not your board is controlled.
This is a deviation from the original, as there was only one board exported. In the original, there's an in-library determination on whether or not the board is controlled. That is not quite how this works, as you will know upfront whether or not your board is controlled.

With that in mind, you can import each of the boards like this:

Expand Down Expand Up @@ -86,17 +86,23 @@ const board: KanbanBoard = {
<UncontrolledBoard initialBoard={board} />
```

## Uncontrolled
### Uncontrolled

With an uncontrolled board, you pass an `initialBoard` prop, which will be the basis of the internal state. When a user moves something, that is all controlled by internal state.

## Controlled
### Controlled

When you need a better control over the board, you should stick with the controlled board.
A controlled board means you need to deal with the board state yourself, you need to keep the state in your hands (component) and pass this state to the `<Board />`, we just reflect this state.

If you go with the controlled one, you need to pass your board through the `children` prop.

### Board

If you really want to use the one unified board, you can still do so with `Board`. Whether or not you provide `initialBoard` or `children` will determine which board you're using. If you provide both, `UncontrolledBoard` takes priority.

See an example, [here](./demo/src/features/one-board/index.tsx).

### Helpers to work with the controlled board

Helpers are exposed to help with the management of your board state when using the `ControlledBoard`. They are the same helpers used internally, so you can utilize them to assist in your controlled state.
Expand Down
8 changes: 7 additions & 1 deletion demo/src/features/controlled/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ export const ControlledBoardDemo: FC = () => {
title='Controlled'
url='https://github.com/christopher-caldwell/react-kanban/blob/main/demo/src/features/controlled/index.tsx'
/>
<ControlledBoard onCardDragEnd={handleCardMove} disableColumnDrag>
<ControlledBoard
onCardDragEnd={handleCardMove}
disableColumnDrag
onCardRemove={({ board, card, column }) => {
console.log({ board, card, column })
}}
>
{controlledBoard}
</ControlledBoard>
</>
Expand Down
52 changes: 52 additions & 0 deletions demo/src/features/one-board/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { FC, useState } from 'react'
import { Board, OnDragEndNotification, Card, moveCard, KanbanBoard } from '@caldwell619/react-kanban'

import { board } from '@/data'
import { Source } from '@/components'

// If you want to use the library like the old one, you can do so by determining which board you want to use
export const UncontrolledBoardDemo: FC = () => {
return (
<>
<Source
title='Uncontrolled'
url='https://github.com/christopher-caldwell/react-kanban/blob/main/demo/src/features/on-board/index.tsx'
/>
<Board
initialBoard={board}
onCardRemove={({ board, card, column }) => {
console.log({ board, card, column })
}}
/>
</>
)
}

export const ControlledBoardDemo: FC = () => {
// You need to control the state yourself.
const [controlledBoard, setBoard] = useState<KanbanBoard<Card>>({ ...board })

const handleCardMove: OnDragEndNotification<Card> = (_card, source, destination) => {
setBoard(currentBoard => {
return moveCard(currentBoard, source, destination)
})
}

return (
<>
<Source
title='Controlled'
url='https://github.com/christopher-caldwell/react-kanban/blob/main/demo/src/features/controlled/index.tsx'
/>
<Board
onCardDragEnd={handleCardMove}
disableColumnDrag
onCardRemove={({ board, card, column }) => {
console.log({ board, card, column })
}}
>
{controlledBoard}
</Board>
</>
)
}
9 changes: 6 additions & 3 deletions demo/src/features/uncontrolled/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ export const UncontrolledBoardDemo: FC = () => {
title='Uncontrolled'
url='https://github.com/christopher-caldwell/react-kanban/blob/main/demo/src/features/uncontrolled/index.tsx'
/>
<UncontrolledBoard initialBoard={board} />
<UncontrolledBoard
initialBoard={board}
onCardRemove={({ board, card, column }) => {
console.log({ board, card, column })
}}
/>
</>
)
}

// TODO: use form to control props
3 changes: 2 additions & 1 deletion src/features/board/components/Container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import { withDroppable } from '@/features/with-droppable'
import { RenderCard } from '@/features/column'
import { Card, Column as ColumnType, KanbanBoard } from '@/types'
import { SharedProps } from './shared'

const Columns = forwardRef<HTMLDivElement>((props, ref) => (
<div ref={ref} style={{ whiteSpace: 'nowrap' }} {...props} />
Expand Down Expand Up @@ -93,7 +94,7 @@ interface Props<TCard extends Card> {
renderCard: RenderCard<TCard>
disableColumnDrag: boolean
disableCardDrag: boolean
renderColumnHeader: (column: ColumnType<TCard>) => JSX.Element
renderColumnHeader: SharedProps<TCard>['renderColumnHeader']
renderColumnAdder: () => JSX.Element | null
allowRemoveColumn: boolean
onColumnRemove?: (column: ColumnType<TCard>) => void
Expand Down
47 changes: 12 additions & 35 deletions src/features/board/components/Controlled.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ColumnAdder } from '@/features/column-adder'
import { DefaultCard } from '@/features/card'
import { BoardContainer, OnDragEnd } from './Container'
import { Card, Column, KanbanBoard } from '@/types'
import { DefaultColumn } from '@/features/column'
import { BoardContainer, OnDragEnd } from './Container'
import { SharedProps } from './shared'

export const ControlledBoard = function <TCard extends Card>({
children: board,
Expand Down Expand Up @@ -49,9 +50,9 @@ export const ControlledBoard = function <TCard extends Card>({
: (column) => (
<DefaultColumn
allowRemoveColumn={allowRemoveColumn}
onColumnRemove={(updatedColumn) => onColumnRemove?.(board, updatedColumn)}
onColumnRemove={(updatedColumn) => onColumnRemove?.({ board, column: updatedColumn })}
allowRenameColumn={allowRenameColumn}
onColumnRename={(renamedColumn) => onColumnRename?.(board, renamedColumn)}
onColumnRename={(renamedColumn) => onColumnRename?.({ board, column: renamedColumn })}
>
{column}
</DefaultColumn>
Expand All @@ -60,15 +61,19 @@ export const ControlledBoard = function <TCard extends Card>({
renderCard={(_column, card, dragging) => {
if (renderCard) return renderCard(card, { dragging })
return (
<DefaultCard dragging={dragging} allowRemoveCard={!!allowRemoveCard} onCardRemove={onCardRemove}>
<DefaultCard
dragging={dragging}
allowRemoveCard={!!allowRemoveCard}
onCardRemove={(card) => onCardRemove?.({ board, column: _column, card })}
>
{card}
</DefaultCard>
)
}}
allowRemoveColumn={allowRemoveColumn}
onColumnRemove={(column) => onColumnRemove?.(board, column)}
onColumnRemove={(column) => onColumnRemove?.({ board, column })}
allowRenameColumn={allowRenameColumn}
onColumnRename={(column) => onColumnRename?.(board, column)}
onColumnRename={(column) => onColumnRename?.({ board, column })}
disableColumnDrag={disableColumnDrag}
disableCardDrag={disableCardDrag}
// TODO: Check these
Expand All @@ -82,39 +87,11 @@ export const ControlledBoard = function <TCard extends Card>({
)
}

interface CardBag {
dragging: boolean
}
export type OnDragEndNotification<TSubject> = (
subject: TSubject,
source: OnDragEnd<TSubject>['source'],
destination: OnDragEnd<TSubject>['destination']
) => void
export interface ControlledBoardProps<TCard extends Card> {
export interface ControlledBoardProps<TCard extends Card> extends SharedProps<TCard> {
children: KanbanBoard<TCard>
onCardDragEnd?: OnDragEndNotification<TCard>
onColumnDragEnd?: OnDragEndNotification<Column<TCard>>
/** Validation in which you provide the ID of the newly created column */
onNewColumnConfirm?: (column: Omit<Column<TCard>, 'id'>) => Column<TCard>
onColumnRemove?: (board: KanbanBoard<TCard>, column: Column<TCard>) => void
onColumnRename?: (board: KanbanBoard<TCard>, column: Column<TCard>) => void
onCardRemove?: () => void
/** If not provided , will render the default column adder */
renderColumnAdder?: () => JSX.Element
/** If not provided , will render the default column header */
renderColumnHeader?: (column: Column<TCard>) => JSX.Element
/** If not provided , will render the default card */
renderCard?: (card: TCard, cardBag: CardBag) => JSX.Element
/** @default true */
allowRemoveColumn?: boolean
/** @default true */
allowRenameColumn?: boolean
/** @default true */
allowRemoveCard?: boolean
/** @default true */
allowAddColumn?: boolean
/** @default false */
disableCardDrag?: boolean
/** @default false */
disableColumnDrag?: boolean
}
54 changes: 10 additions & 44 deletions src/features/board/components/Uncontrolled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { moveCard, moveColumn, addColumn, removeColumn, changeColumn, addCard, r
import { Card, Column, KanbanBoard } from '@/types'
import { BoardContainer } from './Container'
import { DefaultColumn } from '@/features/column'
import { SharedProps } from './shared'

export const UncontrolledBoard = function <TCard extends Card>({
initialBoard,
Expand Down Expand Up @@ -44,19 +45,19 @@ export const UncontrolledBoard = function <TCard extends Card>({
const column = renderColumnAdder ? newColumn : await onNewColumnConfirm?.(newColumn)
if (!column) throw new Error('Cant add falsy column')
const boardWithNewColumn = addColumn<TCard>(board, column)
onColumnNew?.(boardWithNewColumn, column as Column<TCard>)
onColumnNew?.({ board: boardWithNewColumn, column: column as Column<TCard> })
setBoard(boardWithNewColumn)
}

const handleColumnRemove = (column: Column<TCard>) => {
const filteredBoard = removeColumn(board, column)
onColumnRemove?.(filteredBoard, column)
onColumnRemove?.({ board: filteredBoard, column })
setBoard(filteredBoard)
}

const handleColumnRename = (column: Column<TCard>, title: string) => {
const boardWithRenamedColumn = changeColumn<TCard>(board, column, { title })
onColumnRename?.(boardWithRenamedColumn, { ...column, title })
onColumnRename?.({ board: boardWithRenamedColumn, column: { ...column, title } })
setBoard(boardWithRenamedColumn)
}

Expand All @@ -65,7 +66,7 @@ export const UncontrolledBoard = function <TCard extends Card>({
const targetColumn = boardWithNewCard.columns.find(({ id }) => id === column.id)
if (!targetColumn) throw new Error('Cannot find target column')

onCardNew?.(boardWithNewCard, targetColumn, card)
onCardNew?.({ board: boardWithNewCard, column: targetColumn, card })
setBoard(boardWithNewCard)
}

Expand All @@ -79,7 +80,7 @@ export const UncontrolledBoard = function <TCard extends Card>({
const boardWithoutCard = removeCard<TCard>(board, column, card)
const targetColumn = boardWithoutCard.columns.find(({ id }) => id === column.id)
if (!targetColumn) throw new Error('Cannot find target column')
onCardRemove?.(boardWithoutCard, targetColumn, card)
onCardRemove?.({ board: boardWithoutCard, column: targetColumn, card })
setBoard(boardWithoutCard)
}

Expand All @@ -103,6 +104,7 @@ export const UncontrolledBoard = function <TCard extends Card>({
// TODO: Check because og this could be falsy, also no idea what bound thing is
renderColumnHeader={(column) => {
if (renderColumnHeader) {
// TODO: Refactor this into a better signature
return renderColumnHeader(column, {
removeColumn: handleColumnRemove.bind(null, column),
renameColumn: handleColumnRename.bind(null, column),
Expand All @@ -112,9 +114,9 @@ export const UncontrolledBoard = function <TCard extends Card>({
return (
<DefaultColumn
allowRemoveColumn={allowRemoveColumn}
onColumnRemove={(updatedColumn) => onColumnRemove?.(board, updatedColumn)}
onColumnRemove={(updatedColumn) => onColumnRemove?.({ board, column: updatedColumn })}
allowRenameColumn={allowRenameColumn}
onColumnRename={(renamedColumn) => onColumnRename?.(board, renamedColumn)}
onColumnRename={(renamedColumn) => onColumnRename?.({ board, column: renamedColumn })}
>
{column}
</DefaultColumn>
Expand Down Expand Up @@ -147,42 +149,6 @@ export const UncontrolledBoard = function <TCard extends Card>({
)
}

type BoundFunction = any

export interface UncontrolledBoardProps<TCard extends Card> {
export interface UncontrolledBoardProps<TCard extends Card> extends SharedProps<TCard> {
initialBoard: KanbanBoard<TCard>
/** If not provided , will render the default column adder */
renderColumnAdder?: (options: { addColumn: (newColumn: Column<TCard>) => Promise<void> }) => JSX.Element
/** If not provided , will render the default column header */
renderColumnHeader?: (
column: Column<TCard>,
options: { removeColumn: BoundFunction; renameColumn: BoundFunction; addCard: BoundFunction }
) => JSX.Element
/** If not provided , will render the default card */
renderCard?: (card: TCard, options: { removeCard: BoundFunction; dragging: boolean }) => JSX.Element
onColumnRemove?: (filteredBoard: KanbanBoard<TCard>, column: Column<TCard>) => void
onColumnRename?: (board: KanbanBoard<TCard>, column: Column<TCard>) => void
onCardNew?: (board: KanbanBoard<TCard>, column: Column<TCard>, card: TCard) => void
onCardRemove?: (board: KanbanBoard<TCard>, column: Column<TCard>, card: TCard) => void
onColumnNew?: (board: KanbanBoard<TCard>, column: Column<TCard>) => void
/** Validation in which you provide the ID of the newly created card */
onNewCardConfirm?: (card: Omit<TCard, 'id'>) => Promise<TCard>
/** Validation in which you provide the ID of the newly created column */
onNewColumnConfirm?: (newColumn: Omit<Column<TCard>, 'id'>) => Promise<Column<TCard>>
onCardDragEnd?: () => void
onColumnDragEnd?: () => void
/** @default false */
disableCardDrag?: boolean
/** @default false */
disableColumnDrag?: boolean
/** @default true */
allowAddCard?: boolean | { on: 'top' | 'bottom' }
/** @default true */
allowRemoveCard?: boolean
/** @default true */
allowRemoveColumn?: boolean
/** @default true */
allowRenameColumn?: boolean
/** @default true */
allowAddColumn?: boolean
}
Loading

0 comments on commit c8cd185

Please sign in to comment.