Skip to content

Commit 51b41d2

Browse files
committed
feat: Add support for collapsible lanes
#79
1 parent e13a0c9 commit 51b41d2

File tree

8 files changed

+296
-31
lines changed

8 files changed

+296
-31
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ Pluggable components to add a trello like kanban board to your application
1212
* responsive and extensible
1313
* easily pluggable into existing application
1414
* supports pagination when scrolling individual lanes
15-
* drag-and-drop within and across lanes (compatible with touch devices)
15+
* drag-and-drop within and across lanes (compatible with touch devices)
1616
* event bus for triggering events externally (e.g.: adding or removing cards based on events coming from backend)
17+
* edit functionality to add/delete cards
1718

1819
## Getting Started
1920

@@ -73,6 +74,7 @@ This is the container component that encapsulates the lanes and cards
7374
| Name | Type | Description |
7475
| --------------------- | -------- | ---------------------------------------- |
7576
| draggable | boolean | Makes all cards in the lanes draggable. Default: false |
77+
| collapsibleLanes | boolean | Make the lanes with cards collapsible. Default: false |
7678
| editable | boolean | Makes the entire board editable. Allow cards to be added or deleted Default: false |
7779
| handleDragStart | function | Callback function triggered when card drag is started: `handleDragStart(cardId, laneId)` |
7880
| handleDragEnd | function | Callback function triggered when card drag ends: `handleDragEnd(cardId, sourceLaneId, targetLaneId, position)` |
@@ -201,7 +203,7 @@ Check out the [editable board story](https://rcdexta.github.io/react-trello/?sel
201203
* 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
202204
* 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
203205

204-
Check the Milestones for this project to track when the above features will be implemented.
206+
Check the Milestones for this project to track when the above features will be implemented.
205207

206208
## Development
207209

src/components/BoardContainer.js

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ class BoardContainer extends Component {
2424
case 'REFRESH_BOARD':
2525
return actions.loadBoard(event.data)
2626
case 'MOVE_CARD':
27-
return actions.moveCardAcrossLanes({fromLaneId: event.fromLaneId, toLaneId: event.toLaneId, cardId: event.cardId, index: event.index})
27+
return actions.moveCardAcrossLanes({
28+
fromLaneId: event.fromLaneId,
29+
toLaneId: event.toLaneId,
30+
cardId: event.cardId,
31+
index: event.index
32+
})
2833
}
2934
}
3035
}
@@ -51,21 +56,21 @@ class BoardContainer extends Component {
5156
}
5257
}
5358

54-
onDragStart = card => {
59+
onDragStart = card => {
5560
const {handleDragStart} = this.props
56-
handleDragStart(card.draggableId, card.source.droppableId)
61+
handleDragStart(card.draggableId, card.source.droppableId)
5762
}
5863

5964
onDragEnd = result => {
6065
const {handleDragEnd} = this.props
6166
const {source, destination, draggableId} = result
6267
if (destination) {
63-
this.props.actions.moveCardAcrossLanes({
64-
fromLaneId: source.droppableId,
65-
toLaneId: destination.droppableId,
66-
cardId: draggableId,
67-
index: destination.index
68-
})
68+
this.props.actions.moveCardAcrossLanes({
69+
fromLaneId: source.droppableId,
70+
toLaneId: destination.droppableId,
71+
cardId: draggableId,
72+
index: destination.index
73+
})
6974
handleDragEnd(draggableId, source.droppableId, destination.droppableId, destination.index)
7075
}
7176
}
@@ -82,6 +87,7 @@ class BoardContainer extends Component {
8287
'addCardLink',
8388
'laneSortFunction',
8489
'draggable',
90+
'collapsibleLanes',
8591
'editable',
8692
'hideCardDeleteIcon',
8793
'customCardLayout',
@@ -127,8 +133,9 @@ BoardContainer.propTypes = {
127133
onLaneClick: PropTypes.func,
128134
laneSortFunction: PropTypes.func,
129135
draggable: PropTypes.bool,
136+
collapsibleLanes: PropTypes.bool,
130137
editable: PropTypes.bool,
131-
hideCardDeleteIcon: PropTypes.bool,
138+
hideCardDeleteIcon: PropTypes.bool,
132139
handleDragStart: PropTypes.func,
133140
handleDragEnd: PropTypes.func,
134141
customCardLayout: PropTypes.bool,
@@ -140,11 +147,12 @@ BoardContainer.propTypes = {
140147

141148
BoardContainer.defaultProps = {
142149
onDataChange: () => {},
143-
handleDragStart: () => {},
144-
handleDragEnd: () => {},
150+
handleDragStart: () => {},
151+
handleDragEnd: () => {},
145152
editable: false,
146-
hideCardDeleteIcon: false,
147-
draggable: false
153+
hideCardDeleteIcon: false,
154+
draggable: false,
155+
collapsibleLanes: false
148156
}
149157

150158
const mapStateToProps = state => {

src/components/Lane.js

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,32 @@ import React, {Component} from 'react'
22
import PropTypes from 'prop-types'
33
import {bindActionCreators} from 'redux'
44
import {connect} from 'react-redux'
5-
import update from 'immutability-helper'
65
import isEqual from 'lodash/isEqual'
76
import {Droppable} from 'react-beautiful-dnd'
87
import uuidv1 from 'uuid/v1'
98

109
import Loader from './Loader'
1110
import Card from './Card'
1211
import NewCard from './NewCard'
13-
import {Section, Title, RightContent, DraggableList, AddCardLink, ScrollableLane, LaneHeader} from '../styles/Base'
12+
import {
13+
AddCardLink,
14+
LaneFooter,
15+
LaneHeader,
16+
RightContent,
17+
ScrollableLane,
18+
Section,
19+
Title
20+
} from '../styles/Base'
1421

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

1725
class Lane extends Component {
1826
state = {
1927
loading: false,
2028
currentPage: this.props.currentPage,
21-
addCardMode: false
29+
addCardMode: false,
30+
collapsed: false
2231
}
2332

2433
handleScroll = evt => {
@@ -120,11 +129,13 @@ class Lane extends Component {
120129
}
121130
}
122131

123-
renderDragContainer = (isDraggingOver) => {
132+
renderDragContainer = isDraggingOver => {
124133
const {laneSortFunction, editable, hideCardDeleteIcon, tagStyle, cardStyle, draggable, cards} = this.props
125-
const {addCardMode} = this.state
134+
const {addCardMode, collapsed} = this.state
126135

127-
const cardList = this.sortCards(cards, laneSortFunction).map((card, idx) => (
136+
const showableCards = collapsed ? [] : cards
137+
138+
const cardList = this.sortCards(showableCards, laneSortFunction).map((card, idx) => (
128139
<Card
129140
key={card.id}
130141
index={idx}
@@ -137,7 +148,7 @@ class Lane extends Component {
137148
onDelete={this.props.onCardDelete}
138149
draggable={draggable}
139150
editable={editable}
140-
hideCardDeleteIcon={hideCardDeleteIcon}
151+
hideCardDeleteIcon={hideCardDeleteIcon}
141152
{...card}
142153
/>
143154
))
@@ -152,14 +163,17 @@ class Lane extends Component {
152163
}
153164

154165
renderHeader = () => {
155-
if (this.props.customLaneHeader) {
156-
const customLaneElement = React.cloneElement(this.props.customLaneHeader, {...this.props})
166+
const {customLaneHeader} = this.props
167+
if (customLaneHeader) {
168+
const customLaneElement = React.cloneElement(customLaneHeader, {...this.props})
157169
return <span>{customLaneElement}</span>
158170
} else {
159171
const {title, label, titleStyle, labelStyle} = this.props
160172
return (
161-
<LaneHeader>
162-
<Title style={titleStyle}>{title}</Title>
173+
<LaneHeader onDoubleClick={this.toggleLaneCollapsed}>
174+
<Title style={titleStyle}>
175+
{title}
176+
</Title>
163177
{label && (
164178
<RightContent>
165179
<span style={labelStyle}>{label}</span>
@@ -170,12 +184,31 @@ class Lane extends Component {
170184
}
171185
}
172186

187+
renderFooter = () => {
188+
const {collapsibleLanes, cards} = this.props
189+
const {collapsed} = this.state
190+
if (collapsibleLanes && cards.length > 0) {
191+
return <LaneFooter onClick={this.toggleLaneCollapsed}>
192+
{collapsed ? <ExpandBtn/> : <CollapseBtn/>}
193+
</LaneFooter>
194+
}
195+
}
196+
197+
toggleLaneCollapsed = () => {
198+
this.props.collapsibleLanes && this.setState(state => ({collapsed: !state.collapsed}))
199+
}
200+
173201
render() {
174202
const {loading} = this.state
175203
const {id, onLaneClick, index, droppable, ...otherProps} = this.props
176204
const isDropDisabled = !droppable
177205
return (
178-
<Droppable droppableId={id} type="card" index={index} isDropDisabled={isDropDisabled} ignoreContainerClipping={false}>
206+
<Droppable
207+
droppableId={id}
208+
type="card"
209+
index={index}
210+
isDropDisabled={isDropDisabled}
211+
ignoreContainerClipping={false}>
179212
{(dropProvided, dropSnapshot) => {
180213
const isDraggingOver = dropSnapshot.isDraggingOver
181214
return (
@@ -189,6 +222,7 @@ class Lane extends Component {
189222
{this.renderHeader()}
190223
{this.renderDragContainer(isDraggingOver)}
191224
{loading && <Loader />}
225+
{this.renderFooter()}
192226
</Section>
193227
)
194228
}}
@@ -215,6 +249,7 @@ Lane.propTypes = {
215249
label: PropTypes.string,
216250
currentPage: PropTypes.number,
217251
draggable: PropTypes.bool,
252+
collapsibleLanes: PropTypes.bool,
218253
droppable: PropTypes.bool,
219254
onLaneScroll: PropTypes.func,
220255
onCardClick: PropTypes.func,

src/styles/Base.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ export const LaneHeader = styled(Header)`
5050
margin-bottom: 0px;
5151
`
5252

53+
export const LaneFooter = styled.div`
54+
display: flex;
55+
justify-content: center;
56+
align-items: center;
57+
width: 100%;
58+
position: relative;
59+
height: 10px;
60+
`
61+
5362
export const ScrollableLane = styled.div`
5463
flex: 1;
5564
overflow-y: auto;
@@ -74,11 +83,9 @@ export const Title = styled.span`
7483
export const RightContent = styled.span`
7584
width: 30%;
7685
text-align: right;
77-
padding-right: 5px;
86+
padding-right: 10px;
7887
font-size: 13px;
7988
`
80-
81-
8289
export const CardWrapper = styled.article`
8390
border-radius: 3px;
8491
border-bottom: 1px solid #ccc;

src/styles/Elements.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,59 @@ export const DeleteIcon = styled.span`
5656
}
5757
`
5858

59+
export const ExpandCollapseBase = styled.span`
60+
width: 36px;
61+
margin: 0 auto;
62+
font-size: 14px;
63+
position: relative;
64+
cursor: pointer;
65+
66+
`
67+
68+
export const CollapseBtn = styled(ExpandCollapseBase)`
69+
&:before {
70+
content: '';
71+
position: absolute;
72+
top: 0;
73+
left: 0;
74+
border-bottom: 7px solid #444;
75+
border-left: 7px solid transparent;
76+
border-right: 7px solid transparent;
77+
border-radius: 6px;
78+
}
79+
&:after {
80+
content: '';
81+
position: absolute;
82+
left: 4px;
83+
top: 4px;
84+
border-bottom: 3px solid #e3e3e3;
85+
border-left: 3px solid transparent;
86+
border-right: 3px solid transparent;
87+
}
88+
`
89+
90+
export const ExpandBtn = styled(ExpandCollapseBase)`
91+
&:before {
92+
content: '';
93+
position: absolute;
94+
top: 0;
95+
left: 0;
96+
border-top: 7px solid #444;
97+
border-left: 7px solid transparent;
98+
border-right: 7px solid transparent;
99+
border-radius: 6px;
100+
}
101+
&:after {
102+
content: '';
103+
position: absolute;
104+
left: 4px;
105+
top: 0px;
106+
border-top: 3px solid #e3e3e3;
107+
border-left: 3px solid transparent;
108+
border-right: 3px solid transparent;
109+
}
110+
`
111+
59112
export const AddButton = styled.button`
60113
background: #5aac44;
61114
color: #fff;

stories/CollapsibleLanes.story.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React from 'react'
2+
import {withInfo} from '@storybook/addon-info'
3+
import {storiesOf} from '@storybook/react'
4+
5+
import Board from '../src'
6+
7+
const data = require('./data/collapsible.json')
8+
9+
storiesOf('Advanced Features', module).add(
10+
'Collapsible Lanes',
11+
withInfo('Collapse lanes when double clicking on the lanes')(() => {
12+
const shouldReceiveNewData = nextData => {
13+
console.log('data has changed')
14+
console.log(nextData)
15+
}
16+
17+
return (
18+
<Board
19+
data={data}
20+
draggable
21+
collapsibleLanes
22+
onDataChange={shouldReceiveNewData}
23+
/>
24+
)
25+
})
26+
)

0 commit comments

Comments
 (0)