Skip to content

Commit 65d8dae

Browse files
committed
fix: Add ability to add a new lane to the board
1 parent 1e93e93 commit 65d8dae

File tree

8 files changed

+200
-52
lines changed

8 files changed

+200
-52
lines changed

src/actions/BoardActions.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
import {createAction} from 'redux-actions'
22

33
export const loadBoard = createAction('LOAD_BOARD')
4+
export const addLane = createAction('ADD_LANE')

src/components/BoardContainer.js

Lines changed: 105 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,26 @@
11
import React, {Component} from 'react'
2-
import PropTypes from 'prop-types'
3-
import pick from 'lodash/pick'
4-
import isEqual from 'lodash/isEqual'
5-
import {BoardDiv} from '../styles/Base'
62
import {bindActionCreators} from 'redux'
73
import {connect} from 'react-redux'
8-
import Lane from './Lane'
94
import Container from '../dnd/Container'
105
import Draggable from '../dnd/Draggable'
6+
import PropTypes from 'prop-types'
7+
import pick from 'lodash/pick'
8+
import isEqual from 'lodash/isEqual'
9+
import {BoardDiv, LaneSection} from '../styles/Base'
10+
import {NewLaneButton} from '../styles/Elements'
11+
import Lane from './Lane'
12+
import NewLane from './NewLane'
13+
1114
import * as boardActions from '../actions/BoardActions'
1215
import * as laneActions from '../actions/LaneActions'
1316

1417
class BoardContainer extends Component {
15-
wireEventBus = () => {
16-
const {actions, eventBusHandle} = this.props
17-
let eventBus = {
18-
publish: event => {
19-
switch (event.type) {
20-
case 'ADD_CARD':
21-
return actions.addCard({laneId: event.laneId, card: event.card})
22-
case 'REMOVE_CARD':
23-
return actions.removeCard({laneId: event.laneId, cardId: event.cardId})
24-
case 'REFRESH_BOARD':
25-
return actions.loadBoard(event.data)
26-
case 'MOVE_CARD':
27-
return actions.moveCardAcrossLanes({
28-
fromLaneId: event.fromLaneId,
29-
toLaneId: event.toLaneId,
30-
cardId: event.cardId,
31-
index: event.index
32-
})
33-
case 'UPDATE_LANES':
34-
return actions.updateLanes(event.lanes)
35-
}
36-
}
37-
}
38-
eventBusHandle(eventBus)
18+
//+add 2018.08.23
19+
state = {
20+
addLaneMode: false
3921
}
4022

23+
//+end
4124
componentWillMount() {
4225
const {actions, eventBusHandle} = this.props
4326
actions.loadBoard(this.props.data)
@@ -58,27 +41,81 @@ class BoardContainer extends Component {
5841
}
5942
}
6043

61-
getCardDetails = (laneId, cardIndex) => {
62-
return this.props.reducerData.lanes.find(lane => lane.id === laneId).cards[cardIndex]
63-
}
64-
6544
onDragStart = ({payload}) => {
6645
const {handleLaneDragStart} = this.props
6746
handleLaneDragStart(payload.id)
6847
}
6948

7049
onLaneDrop = ({removedIndex, addedIndex, payload}) => {
7150
const {actions, handleLaneDragEnd} = this.props
72-
actions.moveLane({oldIndex: removedIndex, newIndex: addedIndex})
73-
handleLaneDragEnd(payload.id, addedIndex)
51+
if (removedIndex !== addedIndex) {// 2018.08.22
52+
// actions.moveLane({oldIndex: removedIndex, newIndex: addedIndex});
53+
handleLaneDragEnd(removedIndex, addedIndex, payload)
54+
}
55+
}
56+
getCardDetails = (laneId, cardIndex) => {
57+
return this.props.reducerData.lanes.find(lane => lane.id === laneId).cards[cardIndex]
7458
}
75-
7659
getLaneDetails = index => {
7760
return this.props.reducerData.lanes[index]
7861
}
7962

63+
wireEventBus = () => {
64+
const {actions, eventBusHandle} = this.props
65+
let eventBus = {
66+
publish: event => {
67+
switch (event.type) {
68+
case 'ADD_CARD':
69+
return actions.addCard({laneId: event.laneId, card: event.card})
70+
case 'REMOVE_CARD':
71+
return actions.removeCard({laneId: event.laneId, cardId: event.cardId})
72+
case 'REFRESH_BOARD':
73+
return actions.loadBoard(event.data)
74+
case 'MOVE_CARD':
75+
return actions.moveCardAcrossLanes({
76+
fromLaneId: event.fromLaneId,
77+
toLaneId: event.toLaneId,
78+
cardId: event.cardId,
79+
index: event.index
80+
});
81+
case 'UPDATE_LANES':
82+
return actions.updateLanes(event.lanes)
83+
}
84+
},
85+
};
86+
eventBusHandle(eventBus)
87+
}
88+
89+
// + add
90+
hideEditableLane = () => {
91+
this.setState({addLaneMode: false})
92+
}
93+
94+
showEditableLane = () => {
95+
this.setState({addLaneMode: true})
96+
}
97+
98+
addNewLane = params => {
99+
this.hideEditableLane()
100+
this.props.actions.addLane(params)
101+
}
102+
103+
renderNewLane = () => {
104+
const {newLaneTemplate} = this.props
105+
if (newLaneTemplate) {
106+
const newCardWithProps = React.cloneElement(newLaneTemplate, {
107+
onCancel: this.hideEditableLane,
108+
onAdd: this.addNewLane
109+
})
110+
return <span>{newCardWithProps}</span>
111+
} else {
112+
return <NewLane onCancel={this.hideEditableLane} onAdd={this.addNewLane}/>
113+
}
114+
}
115+
80116
render() {
81-
const {id, reducerData, draggable, laneDraggable, laneDragClass, style, ...otherProps} = this.props
117+
const {id, reducerData, draggable, laneDraggable, laneDragClass, style, addLaneTitle, editable, ...otherProps} = this.props
118+
const {addLaneMode} = this.state
82119
// Stick to whitelisting attributes to segregate board and lane props
83120
const passthroughProps = pick(this.props, [
84121
'onLaneScroll',
@@ -94,14 +131,17 @@ class BoardContainer extends Component {
94131
'editable',
95132
'hideCardDeleteIcon',
96133
'customCardLayout',
97-
'newCardTemplate',
98134
'customLaneHeader',
99135
'tagStyle',
100136
'handleDragStart',
101137
'handleDragEnd',
102138
'cardDragClass',
103-
'children'
104-
])
139+
'children',
140+
'addLaneTitle',
141+
'addCardTitle',
142+
'newLaneTemplate',
143+
'newCardTemplate'
144+
]);
105145

106146
return (
107147
<BoardDiv style={style} {...otherProps} draggable={false}>
@@ -110,9 +150,10 @@ class BoardContainer extends Component {
110150
onDragStart={this.onDragStart}
111151
dragClass={laneDragClass}
112152
onDrop={this.onLaneDrop}
113-
lockAxis={'x'}
153+
lockAxis="x"
114154
getChildPayload={index => this.getLaneDetails(index)}
115-
groupName={`TrelloBoard${id}`}>
155+
groupName={`TrelloBoard${id}`}
156+
>
116157
{reducerData.lanes.map((lane, index) => {
117158
const {id, droppable, ...otherProps} = lane
118159
const laneToRender = (
@@ -125,16 +166,25 @@ class BoardContainer extends Component {
125166
{...otherProps}
126167
{...passthroughProps}
127168
/>
128-
)
169+
);
129170
return draggable && laneDraggable ? (
130171
<Draggable key={lane.id}>{laneToRender}</Draggable>
131172
) : (
132173
<span key={lane.id}>{laneToRender}</span>
133-
)
174+
);
134175
})}
135176
</Container>
177+
<Container
178+
orientation="horizontal"
179+
>
180+
{editable && !addLaneMode ? (
181+
<LaneSection style={{width: 200}}>
182+
<NewLaneButton onClick={this.showEditableLane}>{addLaneTitle}</NewLaneButton>
183+
</LaneSection>
184+
) : (addLaneMode && this.renderNewLane())}
185+
</Container>
136186
</BoardDiv>
137-
)
187+
);
138188
}
139189
}
140190

@@ -161,15 +211,17 @@ BoardContainer.propTypes = {
161211
handleLaneDragStart: PropTypes.func,
162212
handleLaneDragEnd: PropTypes.func,
163213
customCardLayout: PropTypes.bool,
164-
newCardTemplate: PropTypes.node,
165214
customLaneHeader: PropTypes.element,
166215
style: PropTypes.object,
167216
tagStyle: PropTypes.object,
168217
laneDraggable: PropTypes.bool,
169218
cardDraggable: PropTypes.bool,
170219
cardDragClass: PropTypes.string,
171-
laneDragClass: PropTypes.string
172-
}
220+
laneDragClass: PropTypes.string,
221+
addLaneTitle: PropTypes.string,
222+
addCardTitle: PropTypes.string,
223+
newLaneTemplate: PropTypes.node
224+
};
173225

174226
BoardContainer.defaultProps = {
175227
onDataChange: () => {},
@@ -184,12 +236,14 @@ BoardContainer.defaultProps = {
184236
laneDraggable: true,
185237
cardDraggable: true,
186238
cardDragClass: 'react_trello_dragClass',
187-
laneDragClass: 'react_trello_dragLaneClass'
188-
}
239+
laneDragClass: 'react_trello_dragLaneClass',
240+
addLaneTitle: '+ Add another lane',
241+
addCardTitle: 'Add Card'
242+
};
189243

190244
const mapStateToProps = state => {
191245
return state.lanes ? {reducerData: state} : {}
192-
}
246+
};
193247

194248
const mapDispatchToProps = dispatch => ({actions: bindActionCreators({...boardActions, ...laneActions}, dispatch)})
195249

src/components/NewLane.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React, {Component} from 'react'
2+
import PropTypes from 'prop-types'
3+
import {LaneTitle, NewLaneButtons, Section} from '../styles/Base'
4+
import EditableLabel from './widgets/EditableLabel'
5+
import {AddButton, CancelButton} from '../styles/Elements'
6+
7+
class NewLane extends Component {
8+
updateField = (field, value) => {
9+
this.setState({[field]: value})
10+
}
11+
12+
handleAdd = () => {
13+
this.props.onAdd(this.state)
14+
}
15+
16+
render() {
17+
const {onCancel} = this.props
18+
return (
19+
<Section>
20+
<LaneTitle>
21+
<EditableLabel placeholder="title" onChange={val => this.updateField('title', val)} autoFocus/>
22+
</LaneTitle>
23+
<NewLaneButtons>
24+
<AddButton onClick={this.handleAdd}>Add</AddButton>
25+
<CancelButton onClick={onCancel}>Cancel</CancelButton>
26+
</NewLaneButtons>
27+
</Section>
28+
)
29+
}
30+
}
31+
32+
NewLane.propTypes = {
33+
onCancel: PropTypes.func.isRequired,
34+
onAdd: PropTypes.func.isRequired
35+
}
36+
NewLane.defaultProps = {}
37+
38+
export default NewLane

src/helpers/LaneHelper.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import update from 'immutability-helper'
2+
import uuidv1 from 'uuid/v1'
23

34
const LaneHelper = {
45
initialiseLanes: (state, {lanes}) => {
@@ -40,6 +41,11 @@ const LaneHelper = {
4041
return update(state, {lanes: {$set: newLanes}})
4142
},
4243

44+
addLane: (state, lane) => {
45+
const newLane = {...lane, id: uuidv1(), cards: []}
46+
return update(state, {lanes: {$push: [newLane]}})
47+
},
48+
4349
removeCardFromLane: (state, {laneId, cardId}) => {
4450
const lanes = state.lanes.map(lane => {
4551
if (lane.id === laneId) {

src/reducers/BoardReducer.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ const boardReducer = (state = {lanes: []}, action) => {
1919
return Lh.paginateLane(state, payload)
2020
case 'MOVE_LANE':
2121
return Lh.moveLane(state, payload)
22+
case 'ADD_LANE':
23+
return Lh.addLane(state, payload)
2224
default:
2325
return state
2426
}

src/styles/Base.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ injectGlobal`
2222
`
2323

2424
export const BoardDiv = styled.div`
25-
background-color: #23719f;
25+
background-color: #3179BA;
2626
overflow-y: hidden;
2727
padding: 5px;
2828
color: #393939;
@@ -163,3 +163,28 @@ export const AddCardLink = styled.a`
163163
text-decoration: underline;
164164
}
165165
`
166+
167+
export const LaneTitle = styled.div`
168+
font-size: 15px;
169+
width:268px;
170+
height: auto;
171+
`
172+
173+
export const LaneSection = styled.section`
174+
background-color: #2B6AA3;
175+
border-radius: 3px;
176+
margin: 5px ;
177+
position: relative;
178+
padding: 5px;
179+
display: inline-flex;
180+
height: auto;
181+
flex-direction: column;
182+
`
183+
184+
export const NewLaneSection = styled(LaneSection)`
185+
background-color: #E0E3E6;
186+
`
187+
188+
export const NewLaneButtons = styled.div`
189+
margin-top: 10px;
190+
`

src/styles/Elements.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,18 @@ export const CancelButton = styled.button`
139139
cursor: pointer;
140140
margin-bottom: 0;
141141
`
142+
export const NewLaneButton = styled.button`
143+
background: #2B6AA3;
144+
border: none;
145+
color: #fff;
146+
transition: background 0.3s ease;
147+
min-height: 32px;
148+
padding: 4px 16px;
149+
vertical-align: top;
150+
margin-top: 0;
151+
margin-right: 0px;
152+
border-radius: 4px;
153+
font-size: 13px;
154+
cursor: pointer;
155+
margin-bottom: 0;
156+
`

stories/EditableBoard.story.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {storiesOf} from '@storybook/react'
55
import Board from '../src'
66

77
const data = require('./data/base.json')
8+
const smallData = require('./data/data-sort')
89

910
class NewCard extends Component {
1011
updateField = (field, evt) => {
@@ -80,3 +81,9 @@ storiesOf('Editable Board', module)
8081
return <Board data={data} editable newCardTemplate={<NewCard />} />
8182
})
8283
)
84+
.add(
85+
'Add New Lane',
86+
withInfo('Allow adding new lane')(() => {
87+
return <Board data={smallData} editable />
88+
})
89+
)

0 commit comments

Comments
 (0)