Skip to content

Instantly share code, notes, and snippets.

@0m15
Last active August 29, 2015 13:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 0m15/9998859 to your computer and use it in GitHub Desktop.
Save 0m15/9998859 to your computer and use it in GitHub Desktop.
`/** @jsx React.DOM */`
# Component structure:
# - DropdownButton
# - DropdownMenu
# - DropdownMenuItem
# refs to some utils
classSet = React.addons.classSet
# the base dropdown mixin
DropdownStateMixin =
getInitialState: ->
open: false
style:
position: 'absolute'
componentDidMount: ->
@setPosition()
# handle window resize event
$(window).on 'resize', @setPosition
bindCloseHandler: ->
# handle click outside event
document.addEventListener 'click', @handleClickOutside
document.addEventListener 'keyup', @handleKeyup
unbindCloseHandler: ->
document.removeEventListener 'click', @handleClickOutside
document.removeEventListener 'keyup', @handleKeyup
handleClickOutside: (e) ->
@setState
open: false
@unbindCloseHandler()
handleKeyup: (e) ->
if e.keyCode is 27
@setState
open: false
@unbindCloseHandler()
handleToggle: (e) ->
e.preventDefault()
open = !@state.open
@setState
open: open
if open is true
@bindCloseHandler()
else
@unbindCloseHandler()
setPosition: ->
# ref to btn node
$btnEl = $(@refs.button.getDOMNode())
# props and states
direction = @props.direction
placement = @props.placement
style = @state.style
# measurements we need to calculate position
btnWidth = $btnEl[0].offsetWidth
if placement is 'right'
style.right = '100%'
style['margin-right'] = -1 * btnWidth + 'px'
else if placement is 'center'
style.left = 0
style['margin-left'] = -1 * btnWidth + 'px'
else
style.left = 0
if direction is 'up'
style.bottom = '100%'
else
style.top = '100%'
@setState
style: style
@adjustToViewport()
adjustToViewport: ->
$dropdownEl = $(@refs.menu.getDOMNode())
dropdownMarginRight = parseInt $dropdownEl.css('margin-right')
dropdownLeftOffset = $(@refs.button.getDOMNode()).offset().left
dropdownWidth = $dropdownEl.width()
style = @state.style
winWidth = $(window).width()
offsetDiff = 0
# check if we've gone outside of the viewport (left)
if dropdownLeftOffset < 0 || $dropdownEl.offset().left < 0
offsetDiff = dropdownMarginRight - (-dropdownLeftOffset)
if @props.placement is 'right'
style['margin-right'] = 0
style['left'] = 0
else
style['margin-left'] = 0
style['left'] = 0
# right
if dropdownLeftOffset + dropdownWidth > winWidth
offsetDiff = (dropdownLeftOffset + $dropdownEl.width()) - winWidth
style.left = -1 * offsetDiff + 'px'
@setState
style: style
DropdownButton = React.createClass
mixins: [DropdownStateMixin]
render: ->
classes = classSet
open: @state.open
dropdown: true
up: @props.direction == 'up'
down: @props.direction == 'down'
`<div className={classes}>
<a href="" onClick={this.handleToggle} className="btn btn-dropdown" ref="button">
{this.props.title} <i className="icon-caret"></i>
</a>
<DropdownMenu ref="menu" items={this.props.items} onSelect={this.props.onSelect} style={this.state.style} />
</div>
`
DropdownMenu = React.createClass
render: ->
`<ul className="dropdown-menu" style={this.props.style}>
{this.props.items.map(function(item, i) {
return (<DropdownMenuItem item={item} onSelect={this.props.onSelect} key={i} />)
}, this)}
</ul>
`
DropdownMenuItem = React.createClass
render: ->
`<li>
<a href="" onClick={this.handleClick}>{this.props.item.title}</a>
</li>`
handleClick: (e) ->
e.preventDefault()
@props.onSelect(@props.item)
DropdownExample = React.createClass
render: ->
@transferPropsTo `<DropdownButton
placement="left"
direction="down"
onSelect={this.handleSelect} />`
handleSelect: (item) ->
console.log 'selected', item
items = [
{ title: 'item 1' }
{ title: 'item 2' }
{ title: 'item 3' }
]
React.renderComponent( `<DropdownExample items={items} title="show menu" />`, document.getElementById('content'))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment