Merge pull request #12 from react-toolbox/datepicker

Datepicker
old
Javi Velasco 2015-09-04 22:18:11 +02:00
commit 4ba3e005a8
31 changed files with 1371 additions and 15 deletions

View File

@ -3,6 +3,7 @@ FontIcon = require '../font_icon'
Ripple = require '../ripple'
module.exports = React.createClass
displayName : 'Button'
# -- States & Properties
propTypes:

View File

@ -0,0 +1,29 @@
css = require './style'
dateUtils = require '../date_utils'
module.exports = React.createClass
displayName: 'Day',
propTypes:
day : React.PropTypes.number
onClick : React.PropTypes.func
selectedDate : React.PropTypes.object
viewDate : React.PropTypes.object
_dayStyle: ->
marginLeft: "#{dateUtils.firstWeekDay(@props.viewDate) * 100/7}%"
_isSelected: () ->
isSameYear = @props.viewDate.getFullYear() == @props.selectedDate.getFullYear()
isSameMonth = @props.viewDate.getMonth() == @props.selectedDate.getMonth()
isSameDay = @props.day == @props.selectedDate.getDate()
isSameYear && isSameMonth && isSameDay
render: ->
className = " #{css.day}"
className += " active" if @_isSelected()
dayStyle = @_dayStyle() if @props.day == 1
<div className={className} style={dayStyle}>
<span onClick={@props.onClick}>{ @props.day }</span>
</div>

View File

@ -0,0 +1,91 @@
CTG = React.addons.CSSTransitionGroup
css = require './style'
dateUtils = require '../date_utils'
FontIcon = require '../font_icon'
Month = require './month'
module.exports = React.createClass
displayName: 'Calendar',
# -- States & Properties
propTypes:
display : React.PropTypes.oneOf(['months', 'years'])
onChange : React.PropTypes.func
selectedDate : React.PropTypes.object
viewDate : React.PropTypes.object
getDefaultProps: ->
display : 'months'
selectedDate : new Date()
getInitialState: ->
selectedDate : @props.selectedDate
viewDate : @props.selectedDate
# -- Lifecycle
componentDidUpdate: (prevProps, prevState) ->
@_scrollToActive() if @refs.activeYear
if prevState.selectedDate.getTime() != @state.selectedDate.getTime() && @props.onChange
@props.onChange? @
# -- Events
onDayClick: (event) ->
@setState
selectedDate: dateUtils.setDay(@state.viewDate, parseInt(event.target.textContent))
onYearClick: (event) ->
newDate = dateUtils.setYear(@state.viewDate, parseInt(event.target.textContent))
@setState
selectedDate: newDate
viewDate: newDate
# -- Private methods
_scrollToActive: ->
@refs.years.getDOMNode().scrollTop =
@refs.activeYear.getDOMNode().offsetTop -
@refs.years.getDOMNode().offsetHeight/2 +
@refs.activeYear.getDOMNode().offsetHeight/2
# -- Public methods
getValue: ->
@state.selectedDate
incrementViewMonth: ->
@setState
direction: 'right'
viewDate: dateUtils.addMonths(@state.viewDate, 1)
decrementViewMonth: ->
@setState
direction: 'left'
viewDate: dateUtils.addMonths(@state.viewDate, -1)
# -- Render
renderYear: (year) ->
props =
className : if year == @state.viewDate.getFullYear() then 'active' else ''
key : "year-#{year}"
onClick : @onYearClick
props.ref = 'activeYear' if year == @state.viewDate.getFullYear()
return <li {...props}>{ year }</li>
render: ->
<div className={css.root}>
{ if @props.display == 'months'
<div className={@state.direction}>
<FontIcon className={css.prev} value='chevron_left' onClick={@decrementViewMonth} />
<FontIcon className={css.next} value='chevron_right' onClick={@incrementViewMonth} />
<CTG transitionName='slide-horizontal'>
<Month
key={@state.viewDate.getMonth()}
viewDate={@state.viewDate}
selectedDate={@state.selectedDate}
onDayClick={@onDayClick} />
</CTG>
</div>
else if @props.display == 'years'
<ul ref="years" className={css.years}>
{ @renderYear(i) for i in [1900..2100] }
</ul>
}
</div>

View File

@ -0,0 +1,29 @@
css = require './style'
Day = require './day'
util = require '../date_utils'
module.exports = React.createClass
displayName: 'Month',
propTypes:
onDayClick : React.PropTypes.func
selectedDate : React.PropTypes.object
viewDate : React.PropTypes.object
render: ->
<div>
<span className={css.title}>
{ util.monthInWords(@props.viewDate)} {@props.viewDate.getFullYear() }
</span>
<div className={css.week}>
{ <span key={"dw#{i}"}>{ util.weekDayInWords(i).charAt(0) }</span> for i in [0..6] }
</div>
<div className={css.days}>
{ for i in [1..util.daysInMonth(@props.viewDate)]
<Day key={"d#{i}"}
day={i}
onClick={@props.onDayClick}
selectedDate={@props.selectedDate}
viewDate={@props.viewDate} /> }
</div>
</div>

View File

@ -0,0 +1,118 @@
@import '../constants'
// -- Calendar sizes
ROW_HEIGHT = 40px
DAY_PADDING = 2px
TOTAL_HEIGHT = ROW_HEIGHT * 8 + DAY_PADDING * 12
:local(.root)
background : WHITE
height : TOTAL_HEIGHT
font-size : 14px
overflow : hidden
position : relative
line-height : ROW_HEIGHT
text-align : center
:local(.prev), :local(.next)
color : lighten(TEXT, 15%)
cursor : pointer
font-size : 26px
height : ROW_HEIGHT
line-height : ROW_HEIGHT
opacity : .7
position : absolute
text-align : center
top : 0
width : ROW_HEIGHT
z-index : Z_INDEX_HIGH
:local(.title)
font-weight : 500
:local(.prev)
left : 0
:local(.next)
right : 0
:local(.week)
display : flex
flex-wrap : wrap
font-size : 13px
height : ROW_HEIGHT
line-height : ROW_HEIGHT
opacity : .5
> span
flex : 0 0 (100/7)%
:local(.days)
display : flex
flex-wrap : wrap
font-size : 13px
:local(.day)
flex : 0 0 (100/7)%
padding : DAY_PADDING
> span
border-radius : 50%
cursor : pointer
display : inline-block
height : ROW_HEIGHT
line-height : ROW_HEIGHT
width : ROW_HEIGHT
&:hover:not(.active) > span
background : lighten(ACCENT, 65%)
color : WHITE
&.active > span
background : ACCENT
color : WHITE
// -- Transitions
:local(.root) .slide-horizontal-enter, :local(.root) .slide-horizontal-leave
transition : all .5s
:local(.root) .right
.slide-horizontal-enter
position : absolute
transform : translateX(100%)
opacity : 0
.slide-horizontal-leave, .slide-horizontal-enter-active
transform : translateX(0)
opacity : 1
.slide-horizontal-leave-active
transform : translateX(-100%)
opacity : 0
:local(.root) .left
.slide-horizontal-enter
position : absolute
transform : translateX(-100%)
opacity : 0
.slide-horizontal-leave, .slide-horizontal-enter-active
transform : translateX(0)
opacity : 1
.slide-horizontal-leave-active
transform : translateX(100%)
opacity : 0
:local(.years)
font-size : 18px
height : 100%
overflow : scroll
> li
cursor : pointer
line-height : 2.4
&.active
color : ACCENT
font-size : 24px

View File

@ -0,0 +1,35 @@
css = require './style'
module.exports = React.createClass
displayName : 'Face'
# -- States & Properties
getDefaultProps: ->
active : null
numbers : []
radius : 0
# -- Internal methods
_numberStyle: (radius, num) ->
position : 'absolute'
left : (radius + radius * Math.sin(360 * (Math.PI/180) / 12 * (num - 1)) + @props.spacing)
top : (radius - radius * Math.cos(360 * (Math.PI/180) / 12 * (num - 1)) + @props.spacing)
_faceStyle: ->
height : @props.radius * 2
width : @props.radius * 2
# -- Render
render: ->
<div ref="root"
className={css.face}
onTouchStart={@props.onTouchStart}
onMouseDown={@props.onMouseDown}
style={@_faceStyle()}>
{ for i, k in @props.numbers
<span className={css.number + (if parseInt(i) == @props.active then ' active' else '')}
style={@_numberStyle(@props.radius - @props.spacing, k + 1)}
key={i} >
{i}
</span> }
</div>

125
components/clock/hand.cjsx Normal file
View File

@ -0,0 +1,125 @@
css = require './style'
prefixer = require "../prefixer"
module.exports = React.createClass
displayName : 'Hand'
# -- States & Properties
propTypes:
className : React.PropTypes.string
initialAngle : React.PropTypes.number
onHandChange : React.PropTypes.func
onHandMoved : React.PropTypes.func
getDefaultProps: ->
className : ''
initialAngle : 0
length : 0
origin : {}
getInitialState: ->
angle : @props.initialAngle
knobWidth : 0
radius : 0
# -- Lifecycle
componentDidMount: ->
@setState knobWidth: @refs.knob.getDOMNode().offsetWidth
componentWillUpdate: (nextProps, nextState) ->
if nextState.angle != @state.angle ||
nextProps.length != @props.length &&
@props.length != 0
@props.onHandChange(nextState.angle)
# -- Event handlers
_getMouseEventMap: ->
mousemove : @onMouseMove
mouseup : @onMouseUp
_getTouchEventMap: ->
touchmove: @onTouchMove
touchend: @onTouchEnd
onMouseMove: (event) ->
@_move(_getMousePosition(event))
onTouchMove: (event) ->
@_move(_getTouchPosition(event))
onMouseUp: ->
@_end(@_getMouseEventMap())
onTouchEnd: ->
@_end(@_getTouchEventMap())
# -- Public API
mouseStart: (event) ->
_addEventsToDocument(@_getMouseEventMap())
@_move(_getMousePosition(event))
touchStart: (event) ->
_addEventsToDocument(@_getTouchEventMap())
@_move(_getTouchPosition(event))
_pauseEvent(event)
# -- Internal methods
_move: (position) ->
@props.onHandMouseMove(@_getPositionRadius(position)) if @props.onHandMouseMove
newDegrees = @_trimAngleToValue(@_positionToAngle(position))
newDegrees = if newDegrees == 360 then 0 else newDegrees
@setState(angle: newDegrees) if @state.angle != newDegrees
_getPositionRadius: (position) ->
x = @props.origin.x - position.x
y = @props.origin.y - position.y
Math.sqrt(x * x + y * y)
_trimAngleToValue: (angle) ->
@props.step * Math.round(angle/@props.step)
_positionToAngle: (position) ->
_angle360(@props.origin.x, @props.origin.y, position.x, position.y)
_end: (events) ->
@props.onHandMoved() if @props.onHandMoved
_removeEventsFromDocument(events)
# -- Render
render: ->
style = prefixer.transform("rotate(#{@state.angle}deg)")
style.height = @props.length - @state.knobWidth/2
<div className={css.hand + ' ' + @props.className} style={style}>
<div ref='knob' className={css.knob}></div>
</div>
# -- Static Helper functions
_addEventsToDocument = (events) ->
document.addEventListener(key, events[key], false) for key of events
_removeEventsFromDocument = (events) ->
document.removeEventListener(key, events[key], false) for key of events
_pauseEvent = (event) ->
event.stopPropagation()
event.preventDefault()
event.returnValue = false
event.cancelBubble = true
return null
_getMousePosition = (event) ->
x: event.pageX
y: event.pageY
_getTouchPosition = (event) ->
x: event.touches[0]['pageX']
y: event.touches[0]['pageY']
_angle360 = (cx, cy, ex, ey) ->
theta = _angle(cx, cy, ex, ey)
if (theta < 0) then 360 + theta else theta
_angle = (cx, cy, ex, ey) ->
theta = Math.atan2(ey - cy, ex - cx) + Math.PI/2
theta * 180 / Math.PI

View File

@ -0,0 +1,77 @@
Face = require './face'
Hand = require './hand'
module.exports = React.createClass
displayName : 'Hours'
# -- States & Properties
propTypes:
format : React.PropTypes.oneOf(['24hr', 'ampm'])
onChange : React.PropTypes.func
onHandMoved : React.PropTypes.func
selected : React.PropTypes.number
getInitialState: ->
innerNumber : @props.format == '24hr' && 0 < @props.selected <= 12
# -- Events
_onHandMouseMove: (radius) ->
if @props.format == '24hr'
currentInner = radius < @props.radius - @props.spacing * 2
@setState innerNumber: currentInner if @state.innerNumber != currentInner
_onHandChange: (degrees) ->
@props.onChange(@_valueFromDegrees(degrees))
_onMouseDown: (event)->
@refs.hand.mouseStart(event)
_onTouchStart: (event) ->
@refs.hand.touchStart(event)
# -- Internal Methods
_valueFromDegrees: (degrees) ->
if @props.format == 'ampm' || @props.format == '24hr' && @state.innerNumber
parseInt(INNER_NUMBERS[degrees/STEP])
else
parseInt(OUTER_NUMBERS[degrees/STEP])
# -- Render
render: ->
innerRadius = @props.radius - @props.spacing * 2
handRadius = if @state.innerNumber then innerRadius else @props.radius
handLength = handRadius - @props.spacing
ampmActive = if @props.format == '24hr' then @props.selected else @props.selected % 12 || 12
<div>
<Face
onTouchStart={@_onTouchStart}
onMouseDown={@_onMouseDown}
numbers={if @props.format == '24hr' then OUTER_NUMBERS else INNER_NUMBERS}
spacing={@props.spacing}
radius={@props.radius}
active={ampmActive} />
{
if @props.format == '24hr'
<Face
onMouseDown={@_onMouseDown}
numbers={INNER_NUMBERS}
spacing={@props.spacing}
radius={innerRadius}
active={@props.selected} />
}
<Hand ref='hand'
degrees={@state.degrees}
initialAngle={@props.selected * STEP}
length={handLength}
onHandMouseMove={@_onHandMouseMove}
onHandMoved={@props.onHandMoved}
onHandChange={@_onHandChange}
origin={@props.center}
step={STEP} />
</div>
# -- Private constants
INNER_NUMBERS = [12].concat([1..11])
OUTER_NUMBERS = ['00'].concat([13..23])
STEP = 360/12

100
components/clock/index.cjsx Normal file
View File

@ -0,0 +1,100 @@
css = require './style'
dateUtils = require '../date_utils'
Hours = require './hours'
Minutes = require './minutes'
module.exports = React.createClass
displayName : 'Clock'
# -- States & Properties
propTypes:
className : React.PropTypes.string
display : React.PropTypes.oneOf(['hours', 'minutes'])
format : React.PropTypes.oneOf(['24hr', 'ampm'])
initialTime : React.PropTypes.object
onChange : React.PropTypes.func
getDefaultProps: ->
className : ''
display : 'hours'
format : '24hr'
initialTime : new Date()
getInitialState: ->
radius : 0
time : @props.initialTime
# -- Lifecycle
componentDidMount: ->
window.addEventListener('resize', @handleResize)
@setState radius: @_getRadius()
componentWillUpdate: (props, state) ->
center = @_getCenter()
if state.time.getTime() != @state.time.getTime() && @props.onChange
@props.onChange(state.time)
if @state.center?.x != center.x && @state.center?.y != center.y
@setState center: center
componentWillUnmount: ->
window.removeEventListener('resize', @handleResize)
# -- Events handlers
onHourChange: (hours) ->
@setState time: dateUtils.setHours(@state.time, @_adaptHourToFormat(hours))
onMinuteChange: (minutes) ->
@setState time: dateUtils.setMinutes(@state.time, minutes)
# -- Helper methods
_getRadius: ->
@refs.wrapper.getDOMNode().getBoundingClientRect().width/2
_adaptHourToFormat: (hour) ->
if @props.format == 'ampm'
if dateUtils.timeMode(@state.time) == 'pm'
if hour < 12 then hour + 12 else hour
else
if hour == 12 then 0 else hour
else
hour
handleResize: ->
@setState
center: @_getCenter()
radius: @_getRadius()
_getCenter: ->
bounds = @getDOMNode().getBoundingClientRect()
{
x: bounds.left + (bounds.right - bounds.left)/2
y: bounds.top + (bounds.bottom - bounds.top) /2
}
# -- Public methods
toggleTimeMode: ->
@setState time: dateUtils.toggleTimeMode(@state.time)
# -- Render
render: ->
<div className={css.root}>
<div ref="wrapper" className={css.wrapper} style={height: @state.radius * 2} >
{
if @props.display == 'minutes'
<Minutes
center={@state.center}
onChange={@onMinuteChange}
radius={@state.radius}
selected={@state.time.getMinutes()}
spacing={@state.radius * 0.16} />
else if @props.display == 'hours'
<Hours
center={@state.center}
format={@props.format}
onChange={@onHourChange}
radius={@state.radius}
selected={@state.time.getHours()}
spacing={@state.radius * 0.16} />
}
</div>
</div>

View File

@ -0,0 +1,49 @@
Face = require './face'
Hand = require './hand'
module.exports = React.createClass
displayName: 'Minutes'
# -- States & Properties
propTypes:
selected : React.PropTypes.number
onChange : React.PropTypes.func
getDefaultProps: ->
selected : 0
onChange : null
# -- Events
_onHandChange: (degrees) ->
@props.onChange(degrees/STEP)
_onMouseDown: (event)->
@refs.hand.mouseStart(event)
_onTouchStart: (event) ->
@refs.hand.touchStart(event)
# -- Render
render: ->
handClass = if MINUTES.indexOf(('0' + @props.selected).slice(-2)) == -1 then 'smallKnob' else ''
<div>
<Face
onTouchStart={@_onTouchStart}
onMouseDown={@_onMouseDown}
numbers={MINUTES}
spacing={@props.spacing}
radius={@props.radius}
active={@props.selected} />
<Hand ref='hand'
className={handClass}
initialAngle={@props.selected * STEP}
length={@props.radius - @props.spacing}
onHandChange={@_onHandChange}
origin={@props.center}
step={STEP} />
</div>
# -- Private constants
MINUTES = (('0' + i).slice(-2) for i in [0..55] by 5)
STEP = 360/60

View File

@ -0,0 +1,98 @@
@import '../constants'
NUMBER_SIZE = 20px
HAND_WIDTH = 4px
HAND_DOT_SIZE = 10px
KNOB_SIZE = 34px
SMALL_KNOB_SIZE = 12px
:local(.root)
padding : 15px
:local(.wrapper)
background-color : DIVIDER
border-radius : 50%
position : relative
:local(.face)
border-radius : 50%
cursor : pointer
position : relative
z-index : Z_INDEX_HIGH
:local(.number)
height : NUMBER_SIZE
margin-left : -(NUMBER_SIZE/2)
margin-top : -(NUMBER_SIZE/2)
pointer-events : none
position : relative
text-align : center
user-select : none
width : NUMBER_SIZE
&.active
color : WHITE
:local(.face)
position : absolute
top : 50%
left : 50%
transform : translateX(-50%) translateY(-50%)
:local(.hand)
background-color : ACCENT
bottom : 50%
display : block
left : 50%
margin-left : -(HAND_WIDTH/2)
position : absolute
transform-origin : 50% 100%
width : HAND_WIDTH
&:before
background-color : ACCENT
border-radius : 50%
bottom : 0
content : ''
height : HAND_DOT_SIZE
left : 50%
margin-bottom : -(HAND_DOT_SIZE/2)
margin-left : -(HAND_DOT_SIZE/2)
position : absolute
width : HAND_DOT_SIZE
&.smallKnob :local(.knob)
background-color: lighten(ACCENT, 70%)
&:after
background : ACCENT
border-radius : 50%
content : ''
height : SMALL_KNOB_SIZE
left : 50%
margin-left : -(SMALL_KNOB_SIZE/2)
margin-top : -(SMALL_KNOB_SIZE/2)
position : absolute
top : 50%
width : SMALL_KNOB_SIZE
&:before
background : ACCENT
bottom : 0
content : ''
height : KNOB_SIZE - SMALL_KNOB_SIZE
left : 50%
margin-left : -(HAND_WIDTH/2)
position : absolute
width : HAND_WIDTH
:local(.knob)
background-color : ACCENT
border-radius : 50%
cursor : pointer
height : KNOB_SIZE
left : 50%
margin-left : -(KNOB_SIZE/2)
position : absolute
top : -(KNOB_SIZE)
width : KNOB_SIZE

View File

@ -1,4 +1,4 @@
@import url('http://fonts.googleapis.com/css?family=Roboto:300,400,700')
@import url('http://fonts.googleapis.com/css?family=Roboto:300,400,500,700')
@import './vendor.styl'
@import './normalize.styl'

View File

@ -50,3 +50,10 @@ ZDEPTH_SHADOW_5 = 0 19px 60px rgba(0,0,0,0.30), 0 15px 20px rgba(0,0
ANIMATION_DURATION = 450ms
ANIMATION_EASE = cubic-bezier(.55,0,.1,1)
ANIMATION_DELAY = (ANIMATION_DURATION / 5)
// -- Z Indexes
Z_INDEX_HIGHER = 200
Z_INDEX_HIGH = 100
Z_INDEX_NORMAL = 1
Z_INDEX_LOW = -100
Z_INDEX_LOWER = -200

View File

@ -0,0 +1,25 @@
# DatePicker
```javascript
var DatePicker = require('react-toolbox/components/date_picker');
var date = new Date(1995,11,17);
// Initialized date picker
<DatePicker value={date} />
```
## Properties
| Name | Type | Default | Description|
| ------------- |:-------:|:--------------- |:---------- |
| **value** | Date | `new Date()` | Date object with currrent date by default |
## Methods
#### getValue
Returns the value of the picker.
```
input_instance.getValue();
```

View File

@ -0,0 +1,70 @@
css = require './style'
dateUtils = require '../date_utils'
Dialog = require '../dialog'
Calendar = require '../calendar'
module.exports = React.createClass
displayName: 'CalendarDialog'
# -- States & Properties
propTypes:
initialDate : React.PropTypes.object
onDateSelected : React.PropTypes.func
getDefaultProps: ->
initialDate : new Date()
getInitialState: ->
date : @props.initialDate
display : 'months'
actions : [
{ caption: "Cancel", type: "flat accent", onClick: @onDateCancel },
{ caption: "Ok", type: "flat accent", onClick: @onDateSelected }
]
# -- Events
onCalendarChange: (calendar) ->
@setState
date: dateUtils.cloneDatetime(calendar.getValue())
display: 'months'
onDateCancel: (ref, method) ->
@refs.dialog.hide()
onDateSelected: ->
@props.onDateSelected(@state.date) if @props.onDateSelected
@refs.dialog.hide()
# -- Public methods
show: ->
@refs.dialog.show()
displayMonths: ->
@setState display: 'months'
displayYears: ->
@setState display: 'years'
# -- Render
render: ->
className = "display-#{@state.display}"
<Dialog ref="dialog" type={css.dialog} className={className} actions={@state.actions}>
<header className={css.header}>
<span className={css.headerWeekday}>{dateUtils.weekDayInWords(@state.date.getDay())}</span>
<div onClick={@displayMonths}>
<span className={css.headerMonth}>{dateUtils.monthInShortWords(@state.date)}</span>
<span className={css.headerDay}>{@state.date.getDate()}</span>
</div>
<span className={css.headerYear} onClick={@displayYears}>
{@state.date.getFullYear()}
</span>
</header>
<div className={css.calendarWrapper}>
<Calendar
ref="calendar"
display={@state.display}
onChange={@onCalendarChange}
selectedDate={@state.date} />
</div>
</Dialog>

View File

@ -0,0 +1,52 @@
css = require './style'
dateUtils = require '../date_utils'
Input = require '../input'
CalendarDialog = require './dialog'
module.exports = React.createClass
displayName : 'DatePicker'
propTypes:
className : React.PropTypes.string
value : React.PropTypes.object
getDefaultProps: ->
className : ''
getInitialState: ->
value : @props.value
# -- Events
openCalendarDialog: ->
@refs.dialog.show()
onDateSelected: (value) ->
@refs.input.setValue(@formatDate(value))
@setState value: value
# -- Private methods
formatDate: (date) ->
day = date.getDate()
month = dateUtils.monthInWords(date)
year = date.getFullYear()
"#{day} #{month} #{year}"
# -- Public methods
getValue: ->
@state.value
# -- Render
render: ->
<div>
<Input
ref="input"
type="text"
disabled={true}
onClick={@openCalendarDialog}
placeholder="Pick up date"
value={@formatDate(@state.value) if @state.value} />
<CalendarDialog
ref="dialog"
initialDate={@state.value}
onDateSelected={@onDateSelected} />
</div>

View File

@ -0,0 +1,53 @@
@import '../constants'
TOTAL_WIDTH = 330px
:local(.dialog)
padding : 0
text-align : center
width : TOTAL_WIDTH
> nav
margin-top : 0
padding-bottom : 10px
:local(.header)
background-color : ACCENT
color : white
:local(.headerWeekday)
background-color : darken(ACCENT, 20%)
display : block
font-size : 14px
line-height : 1.8
width : 100%
:local(.headerMonth)
cursor : pointer
display : block
font-size : 18px
padding : 10px
text-transform : uppercase
:local(.headerDay)
cursor : pointer
display : block
font-size : 50px
line-height : 40px
:local(.headerYear)
cursor : pointer
display : block
font-size : 18px
padding : 10px
:local(.calendarWrapper)
padding : 10px 5px
:local(.dialog)
&.display-years
:local(.headerDay), :local(.headerMonth)
opacity : .7
&.display-months :local(.headerYear)
opacity : .7

View File

@ -0,0 +1,103 @@
module.exports =
daysInMonth: (date) ->
(new Date(date.getFullYear(), date.getMonth() + 1, 0)).getDate()
firstWeekDay: (date) ->
(new Date(date.getFullYear(), date.getMonth(), 1)).getDay()
monthInWords: (date) ->
switch (date.getMonth())
when 0 then 'January'
when 1 then 'February'
when 2 then 'March'
when 3 then 'April'
when 4 then 'May'
when 5 then 'June'
when 6 then 'July'
when 7 then 'August'
when 8 then 'September'
when 9 then 'October'
when 10 then 'November'
when 11 then 'December'
monthInShortWords: (date) ->
switch (date.getMonth())
when 0 then 'Jan'
when 1 then 'Feb'
when 2 then 'Mar'
when 3 then 'Apr'
when 4 then 'May'
when 5 then 'Jun'
when 6 then 'Jul'
when 7 then 'Aug'
when 8 then 'Sep'
when 9 then 'Oct'
when 10 then 'Nov'
when 11 then 'Dec'
weekDayInWords: (day) ->
switch (day)
when 0 then 'Sunday'
when 1 then 'Monday'
when 2 then 'Tuesday'
when 3 then 'Wednesday'
when 4 then 'Thursday'
when 5 then 'Friday'
when 6 then 'Saturday'
weekDayInShortWords: (day) ->
switch (day)
when 0 then 'Sun'
when 1 then 'Mon'
when 2 then 'Tue'
when 3 then 'Wed'
when 4 then 'Thu'
when 5 then 'Fri'
when 6 then 'Sat'
addDays: (date, days) ->
newDate = @cloneDatetime(date)
newDate.setDate(date.getDate() + days)
newDate
addMonths: (date, months) ->
newDate = @cloneDatetime(date)
newDate.setMonth(date.getMonth() + months)
newDate
addYears: (date, years) ->
newDate = @cloneDatetime(date)
newDate.setFullYear(date.getFullYear() + years)
newDate
setDay: (date, day) ->
newDate = @cloneDatetime(date)
newDate.setDate(day)
newDate
setYear: (date, year) ->
newDate = @cloneDatetime(date)
newDate.setFullYear(year)
newDate
cloneDatetime: (date) ->
new Date(date.getTime())
timeMode: (datetime) ->
if datetime.getHours() >= 12 then 'pm' else 'am'
toggleTimeMode: (datetime) ->
newDatetime = @cloneDatetime(datetime)
hours = datetime.getHours()
if hours > 12 then newDatetime.setHours(hours - 12) else newDatetime.setHours(hours + 12)
newDatetime
setHours: (datetime, hours) ->
newDatetime = @cloneDatetime(datetime)
newDatetime.setHours(hours)
newDatetime
setMinutes: (datetime, minutes) ->
newDatetime = @cloneDatetime(datetime)
newDatetime.setMinutes(minutes)
newDatetime

View File

@ -3,6 +3,7 @@ Button = require '../button'
Navigation = require '../navigation'
module.exports = React.createClass
displayName : 'Dialog'
# -- States & Properties
propTypes:
@ -21,12 +22,14 @@ module.exports = React.createClass
# -- Render
render: ->
className = "#{localCSS.root} #{@props.className}"
className += " #{@props.type}" if @props.type
className += ' active' if @state.active
<div data-react-toolbox='dialog' data-flex='vertical center' className={className}>
<div className={localCSS.container}>
{<h1>{@props.title}</h1>}
rootClass = localCSS.root
rootClass += ' active' if @state.active
containerClass = "#{localCSS.container} #{@props.className}"
containerClass += " #{@props.type}" if @props.type
<div data-react-toolbox='dialog' data-flex='vertical center' className={rootClass}>
<div className={containerClass}>
{<h1>{@props.title}</h1> if @props.title}
{@props.children}
{<Navigation actions={@props.actions}/> if @props.actions.length > 0}
</div>

View File

@ -29,13 +29,7 @@
transition-property : opacity
transition-duration : (ANIMATION_DURATION / 2)
transition-timing-function : ANIMATION_EASE
// -- Overrides
&.small > :local(.container)
width : 33vw
&.normal > :local(.container)
width : 50vw
&.large > :local(.container)
width : 96vw
&:not(.active)
pointer-events : none
opacity : 0
@ -48,3 +42,11 @@
> :local(.container)
opacity : 1
transform : translateY(0%)
// -- Overrides
:local(.container).small
width : 33vw
:local(.container).normal
width : 50vw
:local(.container).large
width : 96vw

View File

@ -1,6 +1,7 @@
localCSS = require './style'
module.exports = React.createClass
displayName: 'FontIcon',
# -- States & Properties
propTypes:
@ -10,7 +11,10 @@ module.exports = React.createClass
getDefaultProps: ->
className : ''
onClick: (event) ->
@props.onClick? @props.onClick(event)
# -- Render
render: ->
className = "#{localCSS.root} #{@props.className} #{@props.value}"
<span data-react-toolbox='icon' className={className} />
<span data-react-toolbox='icon' className={className} onClick={@onClick} />

View File

@ -1,6 +1,7 @@
localCSS = require './style'
module.exports = React.createClass
displayName : 'Input'
# -- States & Properties
propTypes:

View File

@ -3,6 +3,7 @@ Button = require '../button'
Link = require '../link'
module.exports = React.createClass
displayName : 'Navigation'
# -- States & Properties
propTypes:

View File

@ -0,0 +1,92 @@
css = require './style'
dateUtils = require '../date_utils'
Button = require '../button'
Clock = require '../clock'
Dialog = require '../dialog'
module.exports = React.createClass
displayName : 'TimePickerDialog'
# -- States & Properties
propTypes:
className : React.PropTypes.string
initialTime : React.PropTypes.object
format : React.PropTypes.oneOf(['24hr', 'ampm'])
onTimeSelected : React.PropTypes.func
getDefaultProps: ->
className : ''
initialTime : new Date()
format : '24hr'
getInitialState: ->
display : 'hours'
time : @props.initialTime
actions: [
{ caption: "Cancel", type: "flat accent", onClick: @onTimeCancel },
{ caption: "Ok", type: "flat accent", onClick: @onTimeSelected }
]
# -- Events
onClockChange: (time) ->
@setState time: time
onTimeCancel: (ref, method) ->
@refs.dialog.hide()
onTimeSelected: ->
@props.onTimeSelected(@state.time) if @props.onTimeSelected
@refs.dialog.hide()
# -- Public methods
displayMinutes: ->
@setState display: 'minutes'
displayHours: ->
@setState display: 'hours'
toggleTimeMode: ->
@refs.clock.toggleTimeMode()
show: ->
@refs.dialog.show()
setTimeout @refs.clock.handleResize, 500
# -- Private helpers
_formatHours: ->
if @props.format == 'ampm' then @state.time.getHours() % 12 || 12 else @state.time.getHours()
# -- Render
render: ->
className = " "
className += " display-#{@state.display}"
className += " format-#{dateUtils.timeMode(@state.time)}"
<Dialog ref="dialog" type={css.dialog} className={className} actions={@state.actions}>
<header className={css.header}>
<span className={css.hours} onClick={@displayHours} >
{_twoDigits(@_formatHours())}
</span>
<span className={css.separator}>:</span>
<span className={css.minutes} onClick={@displayMinutes}>
{_twoDigits(@state.time.getMinutes())}
</span>
{
if @props.format == 'ampm'
<div className={css.ampm}>
<span className={css.am} onClick={@toggleTimeMode}>AM</span>
<span className={css.pm} onClick={@toggleTimeMode}>PM</span>
</div>
}
</header>
<Clock ref="clock"
display={@state.display}
format={@props.format}
initialTime={@props.initialTime}
onChange={@onClockChange} />
</Dialog>
# -- Private helpers
_twoDigits = (number) ->
('0' + number).slice(-2)

View File

@ -0,0 +1,64 @@
css = require './style'
Input = require '../input'
TimeDialog = require './dialog'
module.exports = React.createClass
displayName : 'TimePicker'
# -- States & Properties
propTypes:
format : React.PropTypes.oneOf(['24hr', 'ampm'])
value : React.PropTypes.object
getDefaultProps: ->
format : '24hr'
getInitialState: ->
value : @props.value
# -- Events
onTimeSelected: (time) ->
@refs.input.setValue(@formatTime(time))
@setState value: time
openTimeDialog: ->
@refs.dialog.show()
# -- Private methods
formatTime: (date) ->
hours = date.getHours()
mins = date.getMinutes().toString()
if (@props.format == "ampm")
isAM = hours < 12
hours = hours % 12
additional = if isAM then " am" else " pm"
hours = (hours || 12).toString()
mins = "0" + mins if (mins.length < 2 )
return hours + (if mins == "00" then "" else ":" + mins) + additional
hours = hours.toString()
hours = "0" + hours if (hours.length < 2)
mins = "0" + mins if (mins.length < 2)
return hours + ":" + mins
# -- Public methods
getValue: ->
@state.value
# -- Render
render: ->
<div>
<Input
ref="input"
type="text"
disabled={true}
onClick={@openTimeDialog}
placeholder="Pick up time"
value={@formatTime(@state.value) if @state.value} />
<TimeDialog
ref="dialog"
initialTime={@state.value}
format={@props.format}
onTimeSelected={@onTimeSelected} />
</div>

View File

@ -0,0 +1,60 @@
@import '../constants'
AMPM_HEIGHT = 22px
AMPM_WIDTH = 40px
PICKER_WIDTH = 330px
// Picker dialog
:local(.dialog)
padding : 0
width : PICKER_WIDTH
> nav
margin-top : 0
padding-bottom : 10px
:local(.header)
background : ACCENT
color : WHITE
font-size : 52px
padding : 10px
text-align : center
position : relative
width : 100%
:local(.hours), :local(.minutes)
cursor : pointer
display : inline-block
opacity : .6
:local(.separator)
margin : 0 5px
opacity : .6
:local(.ampm)
font-size : 16px
height : AMPM_HEIGHT * 2
line-height : AMPM_HEIGHT
margin-top : - AMPM_HEIGHT
position : absolute
right : 20px
text-align : center
top : 50%
width : AMPM_WIDTH
:local(.am), :local(.pm)
cursor : pointer
display : block
opacity : .6
// Modifiers
:local(.dialog).display-hours :local(.hours)
opacity : 1
:local(.dialog).display-minutes :local(.minutes)
opacity : 1
:local(.dialog).format-am :local(.am)
opacity : 1
:local(.dialog).format-pm :local(.pm)
opacity : 1

View File

@ -0,0 +1,28 @@
# TimePicker
```javascript
var TimePicker = require('react-toolbox/components/time_picker');
var time = new Date();
time.setHours(17);
time.setMinutes(28);
// Initialized time picker with AM-PM format
<TimePicker format="ampm" value={time} />
```
## Properties
| Name | Type | Default | Description|
| ------------- |:-------:|:--------------- |:---------- |
| **format** | String | `24hr` | Format to display the clock. It can be *24hr* or *ampm*.|
| **value** | Date | `new Date()` | Datetime object with currrent time by default |
## Methods
#### getValue
Returns the value of the picker.
```
input_instance.getValue();
```

View File

@ -0,0 +1,7 @@
Calendar = require '../../components/calendar'
module.exports = React.createClass
displayName: 'TestCalendar',
render: ->
<Calendar />

View File

@ -0,0 +1,7 @@
Clock = require '../../components/clock'
module.exports = React.createClass
render: ->
<div>
<Clock display="hours" />
</div>

View File

@ -0,0 +1,19 @@
DatePicker = require '../../components/date_picker'
TimePicker = require '../../components/time_picker'
module.exports = React.createClass
displayName: 'PickersTest'
render: ->
datetime = new Date(1995,11,17)
datetime.setHours(17)
datetime.setMinutes(28)
<section>
<h2>Pickers</h2>
<DatePicker />
<DatePicker value={datetime} />
<TimePicker value={datetime} />
<TimePicker format="ampm" />
</section>

View File

@ -5,6 +5,7 @@ Aside = require './components/aside'
Autocomplete = require './components/autocomplete'
Button = require './components/button'
Card = require './components/card'
Calendar = require '../components/calendar'
Dialog = require './components/dialog'
Dropdown = require './components/dropdown'
FontIcon = require './components/font_icon'
@ -12,9 +13,13 @@ Form = require './components/form'
Progress = require './components/progress'
Slider = require './components/slider'
Switch = require './components/switch'
Calendar = require './components/calendar'
Pickers = require './components/pickers'
Clock = require './components/clock'
Tabs = require './components/tabs'
Test = React.createClass
displayName: 'App'
# -- Render
render: ->
@ -33,6 +38,7 @@ Test = React.createClass
<Slider />
<Switch />
<Tabs />
<Pickers />
</app>
React.render <Test/>, document.body