Skip to content

Commit

Permalink
Kanban react implementation sample
Browse files Browse the repository at this point in the history
  • Loading branch information
jcastano-xk committed Feb 24, 2017
1 parent 44d5e77 commit 4d45e79
Show file tree
Hide file tree
Showing 23 changed files with 1,673 additions and 11 deletions.
120 changes: 120 additions & 0 deletions app/Card.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import React, {Component, PropTypes} from 'react'
import marked from 'marked'
import ReactCSSTransitionGroup from 'react-addons-css-transition-group'
import {DragSource, DropTarget} from 'react-dnd'
import CheckList from './CheckList'
import constants from './constants'

let titlePropType = (props, propName, componentName) => {
if(props[propName]) {
let value = props[propName]
if(typeof value !== 'string' || value.length > 80) {
return new Error(`${propName} in ${componentName} is longer than 80 characters`)
}
}
}

const cardDragSpec = {
beginDrag(props) {
return {
id: props.id
}
},
endDrag(props) {
props.cardCallbacks.persistCardDrag(props.id, props.status)
}
}

const cardDropSpec = {
hover(props, monitor) {
const draggedId = monitor.getItem().id
props.cardCallbacks.updatePosition(draggedId, props.id)
}
}

let collectDrag = (connect, monitor) => {
return {
connectDragSource: connect.dragSource()
}
}

let collectDrop = (connect, monitor) => {
return {
connectDropTarget: connect.dropTarget()
}
}

class Card extends Component {
constructor() {
super(...arguments);
this.state = {
showDetails: false
};
}

toggleDetails() {
this.setState({
showDetails: !this.state.showDetails
})
}

render() {
const {connectDragSource, connectDropTarget} = this.props

let cardDetails;
let sideColor = {
position: 'absolute',
zIndex: -1,
top: 0,
bottom: 0,
left: 0,
width: 7,
backgroundColor: this.props.color
};

if(this.state.showDetails) {
cardDetails = (
<div className="Card__details">
<span dangerouslySetInnerHTML={{__html:marked(this.props.description)}} />
<CheckList
cardId={this.props.id}
tasks={this.props.tasks}
tasksCallbacks={this.props.tasksCallbacks} />
</div>
)
}
return connectDropTarget(connectDragSource(
<div className="Card">
<div style={sideColor} />
<div className={this.state.showDetails? "Card__title Card__title--is-open" : "Card__title"} onClick={this.toggleDetails.bind(this)}>
{this.props.title}
</div>
<ReactCSSTransitionGroup
transitionName="toggle"
transitionEnterTimeout={250}
transitionLeaveTimeout={250}
>
{cardDetails}
</ReactCSSTransitionGroup>
</div>
));
}
}


Card.propTypes = {
id: PropTypes.number,
title: titlePropType,
description: PropTypes.string,
color: PropTypes.string,
tasks: PropTypes.arrayOf(PropTypes.object),
tasksCallbacks: PropTypes.object.isRequired,
cardCallbacks: PropTypes.object.isRequired,
connectDragSource: PropTypes.func.isRequired,
connectDropTarget: PropTypes.func.isRequired
}

const dragHigherOrderCard = DragSource(constants.CARD, cardDragSpec, collectDrag)(Card)
const dragDropHigherOrderCard = DropTarget(constants.CARD, cardDropSpec, collectDrop)(dragHigherOrderCard)

export default dragDropHigherOrderCard
40 changes: 40 additions & 0 deletions app/CheckList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, {Component, PropTypes} from 'react'

export default class CheckList extends Component {
checkInputKeyPress(evt) {
if(evt.key === 'Enter') {
this.props.tasksCallbacks.add(this.props.cardId, evt.target.value)
evt.target.value = ''
}
}

render() {
console.log(this.props.tasksCallbacks);
var tasks = this.props.tasks.map((task, taskindex) => {
return <li key={task.id} className="CheckList__task">
<input type="checkbox" defaultChecked={task.done} onChange={this.props.tasksCallbacks.toggle.bind(null, this.props.cardId, task.id, taskindex)}/>
{task.name}
<a
href="#"
className="CheckList__task--remove"
onClick={this.props.tasksCallbacks.delete.bind(null, this.props.cardId, task.id, taskindex)}
>
</a>
</li>
});

return (
<div className="CheckList">
<ul> {tasks} </ul>
<input type="text" className="CheckList--add-task" placeholder="Type then hit Enter to add a task" onKeyPress={this.checkInputKeyPress.bind(this)} />
</div>
);
}
}


CheckList.propTypes = {
tasks: PropTypes.arrayOf(PropTypes.object),
cardId: PropTypes.number,
tasksCallbacks: PropTypes.object.isRequired
}
51 changes: 51 additions & 0 deletions app/KanbanBoard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, {Component, PropTypes} from 'react';
import ReactDOM from 'react-dom';
import {DragDropContext} from 'react-dnd'
import HTML5Backend from 'react-dnd-html5-backend'
import List from './List';

// Recoinciliation, React phase before touch the DOM
// React has a few steps to decide how to udpate the DOM
// depending of the change that happened
// -> React uses event delegation on the ROOT component
// and attach the events and unbind them on umount phase
// Controlled componentes: components that are based on the state
// Uncontrolled componentes, the ones that can be mutated on DOM side
// and they way to get them is via refs or onSubmit
class KanbanBoard extends Component {
render() {

return (
<div className="App">
<List
tasksCallbacks={this.props.tasksCallbacks}
cardCallbacks={this.props.cardCallbacks}
id="todo"
title="To Do"
cards={ this.props.cards.filter((card) => card.status === "todo")}>
</List>
<List
tasksCallbacks={this.props.tasksCallbacks}
cardCallbacks={this.props.cardCallbacks}
id="in-progress"
title="In Progress"
cards={ this.props.cards.filter((card) => card.status === "in-progress")}>
</List>
<List
tasksCallbacks={this.props.tasksCallbacks}
cardCallbacks={this.props.cardCallbacks}
id="done"
title="Done"
cards={ this.props.cards.filter((card) => card.status === "done")}>
</List>
</div>
);
}
}
KanbanBoard.propTypes = {
cards: PropTypes.arrayOf(PropTypes.object),
tasksCallbacks: PropTypes.object.isRequired,
cardCallbacks: PropTypes.object.isRequired
}

export default DragDropContext(HTML5Backend)(KanbanBoard)
Loading

0 comments on commit 4d45e79

Please sign in to comment.