Files
2019-01-25 17:01:52 +08:00

536 lines
14 KiB
JavaScript

'use strict'
var React = require('react')
var assign = require('object-assign')
var normalize = require('react-style-normalizer')
function emptyFn(){}
function toUpperFirst(s){
return s?
s.charAt(0).toUpperCase() + s.substring(1):
''
}
var ALIGN = (function(){
var MAP = {
left : 'flex-start',
start : 'flex-start',
center: 'center',
right : 'flex-end',
end : 'flex-end'
}
return function(value){
return MAP[value] || value
}
})()
var PropTypes = React.PropTypes
var DISPLAY_NAME = 'ReactButton'
var THEME = {
'default': {
//default type
style: {
border : '1px solid rgb(46, 153, 235)',
color : 'rgb(84, 84, 84)',
},
overStyle: {
background: 'linear-gradient(to bottom, rgb(125, 191, 242) 0%, rgb(110, 184, 241) 50%, rgb(117, 188, 242) 100%)',
color: 'white'
},
activeStyle: {
//-6 lightness from overStyle
background: ' linear-gradient(to bottom, rgb(106,182,240) 0%,rgb(91,175,239) 50%,rgb(96,178,240) 100%)',
color: 'white'
},
//disabled
disabledStyle: {
//theme properties
background: 'rgb(221, 221, 221)',
border: '1px solid rgb(147, 147, 147)',
color: 'rgb(128, 128, 128)'
},
//pressed
pressedStyle: {
background: 'linear-gradient(to bottom, rgb(22,135,222) 0%,rgb(20,129,212) 50%,rgb(20,132,218) 100%)',
color: 'white'
},
overPressedStyle: {
// +14 lightness from pressed style
background: 'linear-gradient(to bottom, rgb(48,153,234) 0%,rgb(36,148,234) 50%,rgb(41,151,235) 100%)',
},
activePressedStyle: {
background: 'linear-gradient(to bottom, rgb(58,159,236) 0%,rgb(45,153,235) 50%,rgb(50,155,236) 100%)'
},
//focused
focusedStyle: {}
//---NONE ----
},
'primary': {
style: {
background: 'linear-gradient(to bottom, #4ea9ee 0%,#41a2ed 50%,#46a5ee 100%)',
color: 'white'
},
overStyle: {
// + 10 lightness from primary
background: 'linear-gradient(to bottom, rgb(96,178,240) 0%,rgb(83,171,239) 50%,rgb(88,174,240) 100%)'
},
activeStyle: {
// -5 lightness from primary
background: 'linear-gradient(to bottom, rgb(64,162,236) 0%,rgb(50,155,236) 50%,rgb(55,158,237) 100%)'
},
//disabled
disabledStyle: {
//theme properties
background: 'rgb(116, 144, 166)',
color: 'rgb(190, 190, 190)'
}
//pressed
//---NONE---
//focused
//---NONE---
}
}
var ReactButton = React.createClass({
displayName: DISPLAY_NAME,
propTypes: {
fn: PropTypes.func,
onClick: PropTypes.func,
primary: PropTypes.bool,
disabled: PropTypes.bool,
pressed: PropTypes.bool,
defaultPressed: PropTypes.bool,
href: PropTypes.string,
align: PropTypes.string,
style: PropTypes.object,
className : PropTypes.string,
activeClassName : PropTypes.string,
overClassName : PropTypes.string,
focusedClassName: PropTypes.string,
disabledClassName: PropTypes.string
},
getDefaultProps: function() {
return {
isReactButton: true,
applyDefaultTheme: true,
buttonStates: ['focused', 'pressed'],
'data-display-name': DISPLAY_NAME,
align: 'center',
defaultStyle: {
boxSizing : 'border-box',
display : 'inline-flex',
alignItems : 'center',
justifyContent: 'center',
userSelect : 'none',
textDecoration: 'none',
cursor : 'pointer',
overflow : 'hidden',
//theme properties
//fontFamily: 'Arial',
// fontSize : '0.9em',
whiteSpace: 'nowrap',
padding : 5,
margin : 2
},
defaultDisabledStyle: {
cursor: 'default',
},
defaultLabelStyle: {
display: 'inline-block'
},
ellipsisLabelStyle: {
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap'
},
ellipsis: true,
href: ''
}
},
getInitialState: function() {
return {
mouseOver: false,
active: false,
defaultPressed: this.props.defaultPressed
}
},
isFocused: function() {
return this.state.focused
},
isActive: function() {
return !!this.state.active
},
render: function(){
var props = this.prepareProps(this.props, this.state)
return (props.factory || React.DOM.a)(props)
},
prepareProps: function(thisProps, state) {
var props = {}
assign(props, thisProps)
props.theme = this.prepareTheme(props)
var pressed = props.pressed != null? props.pressed: state.defaultPressed
if (pressed != null){
props.pressed = pressed
}
props.active = props.activeState == null? !!state.active: props.activeState
props.over = props.overState == null? !!state.mouseOver: props.overState
props.focused = props.focusedState == null? !!state.focused: props.focusedState
props['data-active'] = props.active
props['data-over'] = props.over
props['data-focused'] = props.focused
props['data-pressed'] = props.pressed
props['data-disabled'] = props.disabled
props.style = this.prepareStyle(props, state)
props.className = this.prepareClassName(props, state)
props.children = this.prepareChildren(props)
var handleClick = this.handleClick.bind(this, props)
props.onClick = typeof props.interceptClick == 'function'?
props.interceptClick.bind(this, handleClick):
handleClick
props.onFocus = this.handleFocus.bind(this, props)
props.onBlur = this.handleBlur.bind(this, props)
props.onMouseEnter = this.handleMouseEnter.bind(this, props)
props.onMouseLeave = this.handleMouseLeave.bind(this, props)
props.onMouseDown = this.handleMouseDown.bind(this, props)
props.onMouseUp = this.handleMouseUp.bind(this, props)
return props
},
handleFocus: function(props, event) {
if (props.disabled){
return
}
this.setState({
focused: true
})
;(this.props.onFocus || emptyFn)(event)
},
handleBlur: function(props, event) {
if (props.disabled){
return
}
this.setState({
focused: false
})
;(this.props.onBlur || emptyFn)(event)
},
handleClick: function(props, event) {
if (!props.href || props.disabled){
event.preventDefault()
}
if (props.disabled){
return
}
if (props.pressed != null){
var newPressed = !props.pressed
if (this.props.pressed == null){
this.setState({
defaultPressed: newPressed
})
}
;(this.props.onToggle || emptyFn)(newPressed, event)
}
;(this.props.onClick || emptyFn)(event)
;(this.props.fn || emptyFn)(props, event)
},
handleMouseEnter: function(props, event) {
if (props.disabled){
return
}
this.setState({
mouseOver: true
})
;(this.props.onMouseEnter || emptyFn)(event)
},
handleMouseLeave: function(props, event) {
if (props.disabled){
return
}
this.setState({
mouseOver: false
})
;(this.props.onMouseLeave || emptyFn)(event)
},
handleMouseUp: function(props, event) {
if (props.disabled){
return
}
this.setState({
active: false
})
window.removeEventListener('mouseup', this.handleMouseUp)
;(this.props.onMouseUp || emptyFn)(event)
;(this.props.onDeactivate || emptyFn)(event)
},
handleMouseDown: function(props, event) {
if (props.disabled){
return
}
this.setState({
active: true
})
window.addEventListener('mouseup', this.handleMouseUp)
;(this.props.onMouseDown || emptyFn)(event)
;(this.props.onActivate || emptyFn)(event)
},
prepareTheme: function(props){
var theme = props.theme
var THEMES = props.themes = props.themes || this.constructor.theme || THEME
if (typeof theme == 'string'){
theme = THEMES[theme]?
THEMES[theme]:
null
}
return theme == null?
THEMES.default:
theme
},
prepareChildren: function(props) {
var children = props.children
if (props.label){
var labelProps = assign({}, props.defaultLabelProps, props.labelProps)
var defaultLabelStyle = assign({}, props.defaultLabelStyle)
if (props.ellipsis){
assign(defaultLabelStyle, props.ellipsisLabelStyle)
}
var labelStyle = assign({}, defaultLabelStyle, labelProps.style, props.labelStyle)
labelProps.style = labelStyle
children = React.createElement("span", React.__spread({}, labelProps),
props.label
)
}
if (typeof this.props.renderChildren === 'function'){
return this.props.renderChildren(children)
}
return children
},
prepareClassName: function(props) {
var className = props.className || ''
if (props.disabled){
if (props.disabledClassName){
className += ' ' + props.disabledClassName
}
} else {
if (props.active && props.activeClassName){
className += ' ' + props.activeClassName
}
if (props.pressed && props.pressedClassName){
className += ' ' + props.pressedClassName
}
if (props.over && props.overClassName){
className += ' ' + props.overClassName
}
if (props.focused && props.focusedClassName){
className += ' ' + props.focusedClassName
}
}
return className
},
prepareComputedStyleNames: function(props){
if (typeof props.computeStyleNames == 'function'){
return props.computeStyleNames(props)
}
var names = ['style']
if (props.disabled){
names.push('disabledStyle')
return names
}
if (props.focused){
names.push('focusedStyle')
}
if (props.pressed){
names.push('pressedStyle')
}
if (typeof props.addStateStyle == 'function'){
props.addStateStyle(names)
}
if (props.focused && props.pressed){
names.push('focusedPressedStyle')
}
if (typeof props.addCombinedStateStyle == 'function'){
props.addCombinedStateStyle(names)
}
//names is something like ['style','focusedStyle','pressedStyle', 'focusedPressedStyle']
//
//now we add over and active styles
var overNames
if (props.over){
overNames = names.map(function(name){
return 'over' + toUpperFirst(name)
})
}
var activeNames
if (props.active){
activeNames = names.map(function(name){
return 'active' + toUpperFirst(name)
})
}
overNames && names.push.apply(names, overNames)
activeNames && names.push.apply(names, activeNames)
return names
},
prepareStyle: function(props) {
var style = assign({}, this.prepareDefaultStyle(props))
var styleNames = this.prepareComputedStyleNames(props)
var theme = props.theme
var THEMES = props.themes
if (theme){
//apply default theme first
if (props.applyDefaultTheme && THEMES.default && theme != THEMES.default){
styleNames.forEach(function(styleName){
assign(style, THEMES.default[styleName])
})
}
//then apply theme
styleNames.forEach(function(styleName){
assign(style, theme[styleName])
})
}
;(props.onThemeStyleReady || emptyFn)(style, props)
//TODO apply default non-theme first to typed buttons
//then non-theme
styleNames.forEach(function(styleName){
assign(style, props[styleName])
})
;(props.onStyleReady || emptyFn)(style, props)
return normalize(style)
},
prepareDefaultStyle: function(props){
var defaultStyle = assign({}, props.defaultStyle)
if (props.block){
defaultStyle.display = 'flex'
}
defaultStyle.justifyContent = ALIGN(props.align)
if (props.disabled){
assign(defaultStyle, props.defaultDisabledStyle)
}
return defaultStyle
}
})
ReactButton.themes = THEME
module.exports = ReactButton