Skip to content

Commit

Permalink
feat: Add support for collapsible lanes
Browse files Browse the repository at this point in the history
  • Loading branch information
rcdexta committed Apr 1, 2018
1 parent e13a0c9 commit 51b41d2
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 31 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ Pluggable components to add a trello like kanban board to your application
* responsive and extensible
* easily pluggable into existing application
* supports pagination when scrolling individual lanes
* drag-and-drop within and across lanes (compatible with touch devices)
* drag-and-drop within and across lanes (compatible with touch devices)
* event bus for triggering events externally (e.g.: adding or removing cards based on events coming from backend)
* edit functionality to add/delete cards

## Getting Started

Expand Down Expand Up @@ -73,6 +74,7 @@ This is the container component that encapsulates the lanes and cards
| Name | Type | Description |
| --------------------- | -------- | ---------------------------------------- |
| draggable | boolean | Makes all cards in the lanes draggable. Default: false |
| collapsibleLanes | boolean | Make the lanes with cards collapsible. Default: false |
| editable | boolean | Makes the entire board editable. Allow cards to be added or deleted Default: false |
| handleDragStart | function | Callback function triggered when card drag is started: `handleDragStart(cardId, laneId)` |
| handleDragEnd | function | Callback function triggered when card drag ends: `handleDragEnd(cardId, sourceLaneId, targetLaneId, position)` |
Expand Down Expand Up @@ -201,7 +203,7 @@ Check out the [editable board story](https://rcdexta.github.io/react-trello/?sel
* Rewrite the drag-n-drop functionality to support moving cards to a specific position within a lane or to a different lane. Ability to re-arrange lanes
* the prop `onDataChange` is a catch all callback that returns the entire board data when anything changes on the board. Micro-events like when a card is added or re-arranged should be possible too

Check the Milestones for this project to track when the above features will be implemented.
Check the Milestones for this project to track when the above features will be implemented.

## Development

Expand Down
36 changes: 22 additions & 14 deletions src/components/BoardContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ class BoardContainer extends Component {
case 'REFRESH_BOARD':
return actions.loadBoard(event.data)
case 'MOVE_CARD':
return actions.moveCardAcrossLanes({fromLaneId: event.fromLaneId, toLaneId: event.toLaneId, cardId: event.cardId, index: event.index})
return actions.moveCardAcrossLanes({
fromLaneId: event.fromLaneId,
toLaneId: event.toLaneId,
cardId: event.cardId,
index: event.index
})
}
}
}
Expand All @@ -51,21 +56,21 @@ class BoardContainer extends Component {
}
}

onDragStart = card => {
onDragStart = card => {
const {handleDragStart} = this.props
handleDragStart(card.draggableId, card.source.droppableId)
handleDragStart(card.draggableId, card.source.droppableId)
}

onDragEnd = result => {
const {handleDragEnd} = this.props
const {source, destination, draggableId} = result
if (destination) {
this.props.actions.moveCardAcrossLanes({
fromLaneId: source.droppableId,
toLaneId: destination.droppableId,
cardId: draggableId,
index: destination.index
})
this.props.actions.moveCardAcrossLanes({
fromLaneId: source.droppableId,
toLaneId: destination.droppableId,
cardId: draggableId,
index: destination.index
})
handleDragEnd(draggableId, source.droppableId, destination.droppableId, destination.index)
}
}
Expand All @@ -82,6 +87,7 @@ class BoardContainer extends Component {
'addCardLink',
'laneSortFunction',
'draggable',
'collapsibleLanes',
'editable',
'hideCardDeleteIcon',
'customCardLayout',
Expand Down Expand Up @@ -127,8 +133,9 @@ BoardContainer.propTypes = {
onLaneClick: PropTypes.func,
laneSortFunction: PropTypes.func,
draggable: PropTypes.bool,
collapsibleLanes: PropTypes.bool,
editable: PropTypes.bool,
hideCardDeleteIcon: PropTypes.bool,
hideCardDeleteIcon: PropTypes.bool,
handleDragStart: PropTypes.func,
handleDragEnd: PropTypes.func,
customCardLayout: PropTypes.bool,
Expand All @@ -140,11 +147,12 @@ BoardContainer.propTypes = {

BoardContainer.defaultProps = {
onDataChange: () => {},
handleDragStart: () => {},
handleDragEnd: () => {},
handleDragStart: () => {},
handleDragEnd: () => {},
editable: false,
hideCardDeleteIcon: false,
draggable: false
hideCardDeleteIcon: false,
draggable: false,
collapsibleLanes: false
}

const mapStateToProps = state => {
Expand Down
59 changes: 47 additions & 12 deletions src/components/Lane.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,32 @@ import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {bindActionCreators} from 'redux'
import {connect} from 'react-redux'
import update from 'immutability-helper'
import isEqual from 'lodash/isEqual'
import {Droppable} from 'react-beautiful-dnd'
import uuidv1 from 'uuid/v1'

import Loader from './Loader'
import Card from './Card'
import NewCard from './NewCard'
import {Section, Title, RightContent, DraggableList, AddCardLink, ScrollableLane, LaneHeader} from '../styles/Base'
import {
AddCardLink,
LaneFooter,
LaneHeader,
RightContent,
ScrollableLane,
Section,
Title
} from '../styles/Base'

import * as laneActions from '../actions/LaneActions'
import {CollapseBtn, ExpandBtn} from '../styles/Elements'

class Lane extends Component {
state = {
loading: false,
currentPage: this.props.currentPage,
addCardMode: false
addCardMode: false,
collapsed: false
}

handleScroll = evt => {
Expand Down Expand Up @@ -120,11 +129,13 @@ class Lane extends Component {
}
}

renderDragContainer = (isDraggingOver) => {
renderDragContainer = isDraggingOver => {
const {laneSortFunction, editable, hideCardDeleteIcon, tagStyle, cardStyle, draggable, cards} = this.props
const {addCardMode} = this.state
const {addCardMode, collapsed} = this.state

const cardList = this.sortCards(cards, laneSortFunction).map((card, idx) => (
const showableCards = collapsed ? [] : cards

const cardList = this.sortCards(showableCards, laneSortFunction).map((card, idx) => (
<Card
key={card.id}
index={idx}
Expand All @@ -137,7 +148,7 @@ class Lane extends Component {
onDelete={this.props.onCardDelete}
draggable={draggable}
editable={editable}
hideCardDeleteIcon={hideCardDeleteIcon}
hideCardDeleteIcon={hideCardDeleteIcon}
{...card}
/>
))
Expand All @@ -152,14 +163,17 @@ class Lane extends Component {
}

renderHeader = () => {
if (this.props.customLaneHeader) {
const customLaneElement = React.cloneElement(this.props.customLaneHeader, {...this.props})
const {customLaneHeader} = this.props
if (customLaneHeader) {
const customLaneElement = React.cloneElement(customLaneHeader, {...this.props})
return <span>{customLaneElement}</span>
} else {
const {title, label, titleStyle, labelStyle} = this.props
return (
<LaneHeader>
<Title style={titleStyle}>{title}</Title>
<LaneHeader onDoubleClick={this.toggleLaneCollapsed}>
<Title style={titleStyle}>
{title}
</Title>
{label && (
<RightContent>
<span style={labelStyle}>{label}</span>
Expand All @@ -170,12 +184,31 @@ class Lane extends Component {
}
}

renderFooter = () => {
const {collapsibleLanes, cards} = this.props
const {collapsed} = this.state
if (collapsibleLanes && cards.length > 0) {
return <LaneFooter onClick={this.toggleLaneCollapsed}>
{collapsed ? <ExpandBtn/> : <CollapseBtn/>}
</LaneFooter>
}
}

toggleLaneCollapsed = () => {
this.props.collapsibleLanes && this.setState(state => ({collapsed: !state.collapsed}))
}

render() {
const {loading} = this.state
const {id, onLaneClick, index, droppable, ...otherProps} = this.props
const isDropDisabled = !droppable
return (
<Droppable droppableId={id} type="card" index={index} isDropDisabled={isDropDisabled} ignoreContainerClipping={false}>
<Droppable
droppableId={id}
type="card"
index={index}
isDropDisabled={isDropDisabled}
ignoreContainerClipping={false}>
{(dropProvided, dropSnapshot) => {
const isDraggingOver = dropSnapshot.isDraggingOver
return (
Expand All @@ -189,6 +222,7 @@ class Lane extends Component {
{this.renderHeader()}
{this.renderDragContainer(isDraggingOver)}
{loading && <Loader />}
{this.renderFooter()}
</Section>
)
}}
Expand All @@ -215,6 +249,7 @@ Lane.propTypes = {
label: PropTypes.string,
currentPage: PropTypes.number,
draggable: PropTypes.bool,
collapsibleLanes: PropTypes.bool,
droppable: PropTypes.bool,
onLaneScroll: PropTypes.func,
onCardClick: PropTypes.func,
Expand Down
13 changes: 10 additions & 3 deletions src/styles/Base.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ export const LaneHeader = styled(Header)`
margin-bottom: 0px;
`

export const LaneFooter = styled.div`
display: flex;
justify-content: center;
align-items: center;
width: 100%;
position: relative;
height: 10px;
`

export const ScrollableLane = styled.div`
flex: 1;
overflow-y: auto;
Expand All @@ -74,11 +83,9 @@ export const Title = styled.span`
export const RightContent = styled.span`
width: 30%;
text-align: right;
padding-right: 5px;
padding-right: 10px;
font-size: 13px;
`


export const CardWrapper = styled.article`
border-radius: 3px;
border-bottom: 1px solid #ccc;
Expand Down
53 changes: 53 additions & 0 deletions src/styles/Elements.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,59 @@ export const DeleteIcon = styled.span`
}
`

export const ExpandCollapseBase = styled.span`
width: 36px;
margin: 0 auto;
font-size: 14px;
position: relative;
cursor: pointer;
`

export const CollapseBtn = styled(ExpandCollapseBase)`
&:before {
content: '';
position: absolute;
top: 0;
left: 0;
border-bottom: 7px solid #444;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-radius: 6px;
}
&:after {
content: '';
position: absolute;
left: 4px;
top: 4px;
border-bottom: 3px solid #e3e3e3;
border-left: 3px solid transparent;
border-right: 3px solid transparent;
}
`

export const ExpandBtn = styled(ExpandCollapseBase)`
&:before {
content: '';
position: absolute;
top: 0;
left: 0;
border-top: 7px solid #444;
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-radius: 6px;
}
&:after {
content: '';
position: absolute;
left: 4px;
top: 0px;
border-top: 3px solid #e3e3e3;
border-left: 3px solid transparent;
border-right: 3px solid transparent;
}
`

export const AddButton = styled.button`
background: #5aac44;
color: #fff;
Expand Down
26 changes: 26 additions & 0 deletions stories/CollapsibleLanes.story.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react'
import {withInfo} from '@storybook/addon-info'
import {storiesOf} from '@storybook/react'

import Board from '../src'

const data = require('./data/collapsible.json')

storiesOf('Advanced Features', module).add(
'Collapsible Lanes',
withInfo('Collapse lanes when double clicking on the lanes')(() => {
const shouldReceiveNewData = nextData => {
console.log('data has changed')
console.log(nextData)
}

return (
<Board
data={data}
draggable
collapsibleLanes
onDataChange={shouldReceiveNewData}
/>
)
})
)
Loading

0 comments on commit 51b41d2

Please sign in to comment.