Improve clock styles and behavior

old
Javi Velasco 2015-08-28 10:53:29 +02:00
parent 14e1f85bff
commit 45a26ea682
8 changed files with 153 additions and 87 deletions

View File

@ -4,9 +4,9 @@ module.exports = React.createClass
# -- States & Properties
getDefaultProps: ->
className : ''
numbers : []
radius : 0
numbers : []
radius : 0
activeNumber : null
# -- Internal methods
_numberStyle: (radius, num) ->
@ -20,10 +20,11 @@ module.exports = React.createClass
# -- Render
render: ->
<div ref="root" className={"#{@props.className} #{css.face}"} style={@_faceStyle()}>
<div ref="root" className={css.face} style={@_faceStyle()}>
{ for i, k in @props.numbers
<span className={css.number}
key={i} style={@_numberStyle(@props.radius - @props.spacing, k + 1)}>
<span className={css.number + (if parseInt(i) == @props.activeNumber then ' active' else '')}
style={@_numberStyle(@props.radius - @props.spacing, k + 1)}
key={i} >
{i}
</span> }
</div>

View File

@ -5,25 +5,31 @@ module.exports = React.createClass
# -- States & Properties
propTypes:
initialAngle : React.PropTypes.number
className : React.PropTypes.string
onHandChange : React.PropTypes.func
onHandMoved : React.PropTypes.func
getDefaultProps: ->
angle : 0
className : ''
initialAngle : 0
length : 0
origin : {}
getInitialState: ->
angle : @props.angle
angle : @props.initialAngle
knobWidth : 0
radius : 0
# -- Lifecycle
componentDidMount: ->
@setState
knobWidth : @refs.knob.getDOMNode().offsetWidth
@setState knobWidth: @refs.knob.getDOMNode().offsetWidth
componentWillUpdate: (nextProps, nextState) ->
@props.onHandChange(nextState.angle)
if nextState.angle != @state.angle ||
nextProps.length != @props.length &&
@props.length != 0
@props.onHandChange(nextState.angle)
# -- Event handlers
_onKnobMouseDown: ->
@ -40,6 +46,9 @@ module.exports = React.createClass
newDegrees = if newDegrees == 360 then 0 else newDegrees
@setState(angle: newDegrees) if @state.angle != newDegrees
onMouseUp: ->
@_end(@_getMouseEventMap())
# -- Internal methods
_getPositionRadius: (position) ->
x = @props.origin.x - position.x
@ -52,10 +61,8 @@ module.exports = React.createClass
_positionToAngle: (position) ->
_angle360(@props.origin.x, @props.origin.y, position.x, position.y)
onMouseUp: ->
@_end(@_getMouseEventMap())
_end: (events) ->
@props.onHandMoved() if @props.onHandMoved
_removeEventsFromDocument(events)
# -- Render
@ -63,7 +70,7 @@ module.exports = React.createClass
style = prefixer.transform("rotate(#{@state.angle}deg)")
style.height = @props.length - @state.knobWidth/2
<div className={css.hand} style={style}>
<div className={css.hand + ' ' + @props.className} style={style}>
<div ref='knob' className={css.knob} onMouseDown={@_onKnobMouseDown}></div>
</div>

View File

@ -1,4 +1,3 @@
css = require './style'
Face = require './face'
Hand = require './hand'
@ -6,59 +5,73 @@ module.exports = React.createClass
# -- States & Properties
propTypes:
format : React.PropTypes.oneOf(['24hr', 'ampm'])
onChange : React.PropTypes.func
initialValue : React.PropTypes.number
format : React.PropTypes.oneOf(['24hr', 'ampm'])
onChange : React.PropTypes.func
onHandMoved : React.PropTypes.func
getDefaultProps: ->
format : '24hr'
onChange : null
initialValue : null
format : '24hr'
onChange : null
getInitialState: ->
am : false
inner : @props.format == '24hr' && 0 < @props.initialValue <= 12
value : @props.initialValue || if @props.format == '24hr' then 0 else 12
# -- Lifecycle
componentWillUpdate: (nextProps, nextState) ->
@props.onChange(nextState.value) if nextState.value != @state.value && @props.onChange
# -- Events
_onHandMouseMove: (radius) ->
if @props.format == '24hr'
currentAm = radius < @props.radius - @props.spacing * 2
@setState am: currentAm if @state.am != currentAm
currentInner = radius < @props.radius - @props.spacing * 2
@setState inner: currentInner if @state.inner != currentInner
_onHandChange: (value) ->
if @props.format == '24hr'
values = if @state.am then AM_HOURS else PM_HOURS
_onHandChange: (degrees) ->
newValue = @_valueFromDegrees(degrees)
@setState value: newValue if @state.value != newValue
# -- Internal Methods
_valueFromDegrees: (degrees) ->
if @props.format == 'ampm' || @props.format == '24hr' && @state.inner
parseInt(INNER_NUMBERS[degrees/STEP])
else
values = AM_HOURS
@props.onChange(parseInt(values[value/STEP])) if @props.onChange
parseInt(OUTER_NUMBERS[degrees/STEP])
# -- Render
render: ->
innerRadius = @props.radius - @props.spacing * 2
handRadius = if @props.format == '24hr' && @state.am then innerRadius else @props.radius
handRadius = if @state.inner then innerRadius else @props.radius
handLength = handRadius - @props.spacing
<div>
<Face
className={css.outerSphere}
numbers={if @props.format == '24hr' then PM_HOURS else AM_HOURS}
numbers={if @props.format == '24hr' then OUTER_NUMBERS else INNER_NUMBERS}
spacing={@props.spacing}
radius={@props.radius} />
radius={@props.radius}
activeNumber={@state.value} />
{
if @props.format == '24hr'
<Face
className={css.innerSphere}
numbers={AM_HOURS}
numbers={INNER_NUMBERS}
spacing={@props.spacing}
radius={innerRadius} />
radius={innerRadius}
activeNumber={@state.value} />
}
<Hand
degrees={0}
degrees={@state.degrees}
initialAngle={@props.initialValue * STEP}
length={handLength}
onHandMouseMove={@_onHandMouseMove}
onHandMoved={@props.onHandMoved}
onHandChange={@_onHandChange}
origin={@props.center}
step={STEP} />
</div>
# -- Private constants
AM_HOURS = [12].concat([1..11])
PM_HOURS = ['00'].concat([13..23])
STEP = 360/12
INNER_NUMBERS = [12].concat([1..11])
OUTER_NUMBERS = ['00'].concat([13..23])
STEP = 360/12

View File

@ -7,13 +7,14 @@ module.exports = React.createClass
# -- States & Properties
propTypes:
className : React.PropTypes.string
mode : React.PropTypes.oneOf(['hours', 'minutes'])
startMode : React.PropTypes.oneOf(['hours', 'minutes'])
getDefaultProps: ->
className : ''
mode : 'hours'
startMode : 'hours'
getInitialState: ->
mode : @props.startMode
radius : 0
# -- Lifecycle
@ -57,14 +58,16 @@ module.exports = React.createClass
<div className={css.root}>
<div ref="wrapper" className={css.wrapper} style={height: @state.radius * 2} >
{
if @props.mode == 'minutes'
if @state.mode == 'minutes'
<Minutes
center={@state.center}
onChange={@onMinuteChange}
radius={@state.radius}
spacing={@state.radius * 0.16} />
else if @props.mode == 'hours'
else if @state.mode == 'hours'
<Hours
format={'24hr'}
initialValue={16}
center={@state.center}
onChange={@onHourChange}
radius={@state.radius}

View File

@ -6,11 +6,23 @@ module.exports = React.createClass
# -- States & Properties
propTypes:
onChange : React.PropTypes.func
initialValue : React.PropTypes.number
onChange : React.PropTypes.func
getDefaultProps: ->
initialValue : 0
onChange : null
getInitialState: ->
value : @props.initialValue
# -- Events
_onHandChange: (value) ->
@props.onChange(parseInt(value/STEP)) if @props.onChange
_onHandChange: (degrees) ->
@setState value: parseInt(degrees/STEP)
# -- Internal methods
_valueIsExactMinute: ->
MINUTES.indexOf(('0' + @state.value).slice(-2)) != -1
# -- Render
render: ->
@ -19,9 +31,12 @@ module.exports = React.createClass
className={css.outerSphere}
numbers={MINUTES}
spacing={@props.spacing}
radius={@props.radius} />
radius={@props.radius}
activeNumber={@state.value} />
<Hand
degrees={0}
className={'small-knob' unless @_valueIsExactMinute()}
initialAngle={@props.initialValue * STEP}
length={@props.radius - @props.spacing}
onHandChange={@_onHandChange}
origin={@props.center}

View File

@ -1,68 +1,88 @@
handWidth = 4px
handDotSize = 6px
knobSize = 34px
@import '../constants'
NUMBER_SIZE = 20px
HAND_WIDTH = 4px
HAND_DOT_SIZE = 6px
KNOB_SIZE = 34px
SMALL_KNOB_SIZE = 14px
:local(.root)
border: 1px solid pink
padding: 20px
padding : 20px
:local(.wrapper)
position: relative
background-color : DIVIDER
border-radius : 50%
position : relative
:local(.face)
border-radius: 50%
position: relative
border-radius : 50%
pointer-events : none
position : relative
z-index : Z_INDEX_HIGH
:local(.number)
width: 20px
text-align: center
pointer-events: none
height: 20px
margin-left: -10px
margin-top: -10px
height : NUMBER_SIZE
margin-left : -(NUMBER_SIZE/2)
margin-top : -(NUMBER_SIZE/2)
position : relative
text-align : center
width : NUMBER_SIZE
:local(.outerSphere), :local(.innerSphere)
position: absolute
top: 50%
left: 50%
transform: translateX(-50%) translateY(-50%)
&.active
color : WHITE
:local(.outerSphere)
background: gray
:local(.innerSphere)
background: yellow
:local(.face)
position : absolute
top : 50%
left : 50%
transform : translateX(-50%) translateY(-50%)
:local(.hand)
background-color : magenta
background-color : ACCENT
bottom : 50%
display : block
left : 50%
margin-left : -(handWidth/2)
opacity : .6
margin-left : -(HAND_WIDTH/2)
position : absolute
transform-origin : 50% 100%
width : handWidth
width : HAND_WIDTH
&:before
background-color : magenta
background-color : ACCENT
border-radius : 50%
bottom : 0
content : ''
height : handDotSize
height : HAND_DOT_SIZE
left : 50%
margin-bottom : -(handDotSize/2)
margin-left : -(handDotSize/2)
margin-bottom : -(HAND_DOT_SIZE/2)
margin-left : -(HAND_DOT_SIZE/2)
position : absolute
width : handDotSize
width : HAND_DOT_SIZE
&.small-knob :local(.knob)
top : -(KNOB_SIZE/2)
margin-top : -(SMALL_KNOB_SIZE/2)
margin-left : -(SMALL_KNOB_SIZE/2)
height : SMALL_KNOB_SIZE
width : SMALL_KNOB_SIZE
&:before
background : ACCENT
bottom : -((KNOB_SIZE - SMALL_KNOB_SIZE)/2)
content : ''
height : ((KNOB_SIZE - SMALL_KNOB_SIZE)/2)
left : 50%
margin-left : -(HAND_WIDTH/2)
position : absolute
width : HAND_WIDTH
:local(.knob)
background-color : magenta
background-color : ACCENT
border-radius : 50%
cursor : pointer
height : knobSize
height : KNOB_SIZE
left : 50%
margin-left : -(knobSize/2)
margin-left : -(KNOB_SIZE/2)
position : absolute
top : -(knobSize)
width : knobSize
top : -(KNOB_SIZE)
width : KNOB_SIZE

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

@ -3,6 +3,6 @@ Clock = require '../../components/clock'
module.exports = React.createClass
render: ->
<div>
<Clock mode="hours" />
<Clock mode="minutes" />
<Clock startMode="hours" />
<Clock startMode="minutes" />
</div>