Switch.jsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. /*
  2. Copyright (C) 2017 Cloudbase Solutions SRL
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. // @flow
  15. import React from 'react'
  16. import { observer } from 'mobx-react'
  17. import styled from 'styled-components'
  18. import Palette from '../../styleUtils/Palette'
  19. import StyleProps from '../../styleUtils/StyleProps'
  20. const Wrapper = styled.div`
  21. display: flex;
  22. height: ${StyleProps.inputSizes.regular.height}px;
  23. align-items: center;
  24. ${props => props.disabled ? 'opacity: 0.5;' : ''}
  25. ${props => props.justifyContent ? `justify-content: ${props.justifyContent};` : ''}
  26. ${props => props.width ? `width: ${props.width};` : ''}
  27. `
  28. const InputWrapper = styled.div`
  29. position: relative;
  30. width: ${props => props.height * 2}px;
  31. height: ${props => props.height}px;
  32. ${props => !props.disabled ? 'cursor: pointer;' : ''};
  33. `
  34. const inputBackground = props => {
  35. if (props.big) {
  36. if (props.checked) {
  37. return Palette.alert
  38. }
  39. return Palette.primary
  40. }
  41. if (props.secondary && props.checked) {
  42. return Palette.grayscale[5]
  43. }
  44. if (props.checked) {
  45. return Palette.primary
  46. }
  47. return 'white'
  48. }
  49. const getInputBorderColor = props => {
  50. if (props.big && props.checked) {
  51. return Palette.alert
  52. }
  53. if (props.secondary) {
  54. return Palette.grayscale[5]
  55. }
  56. if (props.triState && props.checked == null) {
  57. return Palette.grayscale[2]
  58. }
  59. return Palette.primary
  60. }
  61. const InputBackground = styled.div`
  62. position: absolute;
  63. top: 0;
  64. left: 0;
  65. right: 0;
  66. bottom: 0;
  67. transition: all ${StyleProps.animations.swift};
  68. background: ${props => inputBackground(props)};
  69. border-radius: 50px;
  70. border: 1px solid ${props => getInputBorderColor(props)};
  71. `
  72. const getThumbLeft = props => {
  73. if (props.checked) {
  74. return props.height - 1
  75. }
  76. if (props.triState && props.checked == null) {
  77. return (props.height / 2) - 1
  78. }
  79. return -1
  80. }
  81. const InputThumb = styled.div`
  82. position: absolute;
  83. width: ${props => props.height - 2}px;
  84. height: ${props => props.height - 2}px;
  85. transition: all ${StyleProps.animations.swift};
  86. top: -1px;
  87. left: ${props => getThumbLeft(props)}px;
  88. background: white;
  89. border: 1px solid ${props => getInputBorderColor(props)};
  90. border-radius: 50%;
  91. `
  92. const Label = styled.div`
  93. margin-left: 16px;
  94. ${props => props.secondary ? `color: ${Palette.grayscale[4]};` : ''}
  95. white-space: nowrap;
  96. `
  97. const LeftLabel = styled.div`
  98. margin-right: 16px;
  99. color: ${Palette.grayscale[4]};
  100. white-space: nowrap;
  101. `
  102. type Props = {
  103. onChange: (checked: ?boolean) => void,
  104. checked: boolean,
  105. disabled: boolean,
  106. triState: boolean,
  107. leftLabel: boolean,
  108. secondary: boolean,
  109. noLabel: boolean,
  110. height: number,
  111. width: string,
  112. justifyContent: string,
  113. big: boolean,
  114. checkedLabel: string,
  115. uncheckedLabel: string,
  116. 'data-test-id'?: string,
  117. style: { [string]: mixed },
  118. }
  119. type State = {
  120. lastChecked: ?boolean,
  121. }
  122. @observer
  123. class Switch extends React.Component<Props, State> {
  124. static defaultProps: $Shape<Props> = {
  125. checkedLabel: 'Yes',
  126. uncheckedLabel: 'No',
  127. height: 24,
  128. }
  129. state = {
  130. lastChecked: null,
  131. }
  132. getLabel() {
  133. let label = this.props.checked ? this.props.checkedLabel : this.props.uncheckedLabel
  134. if (this.props.triState && this.props.checked == null) {
  135. label = 'Not Set'
  136. }
  137. return label
  138. }
  139. handleInputChange() {
  140. if (this.props.disabled) {
  141. return
  142. }
  143. if (this.props.triState) {
  144. if (this.props.checked === true || this.props.checked === false) {
  145. this.setState({ lastChecked: this.props.checked })
  146. this.props.onChange(null)
  147. } else {
  148. this.props.onChange(!this.state.lastChecked)
  149. }
  150. } else {
  151. this.props.onChange(!this.props.checked)
  152. }
  153. }
  154. renderInput() {
  155. return (
  156. <InputWrapper
  157. triState={this.props.triState}
  158. big={this.props.big}
  159. height={this.props.height}
  160. secondary={this.props.secondary}
  161. disabled={this.props.disabled}
  162. onClick={() => { this.handleInputChange() }}
  163. data-test-id={this.props['data-test-id'] || 'switch-input'}
  164. >
  165. <InputBackground
  166. triState={this.props.triState}
  167. big={this.props.big}
  168. checked={this.props.checked}
  169. height={this.props.height}
  170. secondary={this.props.secondary}
  171. >
  172. <InputThumb
  173. triState={this.props.triState}
  174. big={this.props.big}
  175. checked={this.props.checked}
  176. height={this.props.height}
  177. secondary={this.props.secondary}
  178. />
  179. </InputBackground>
  180. </InputWrapper>
  181. )
  182. }
  183. renderLeftLabel() {
  184. if (!this.props.leftLabel || this.props.noLabel) {
  185. return null
  186. }
  187. return (
  188. <LeftLabel>{this.getLabel()}</LeftLabel>
  189. )
  190. }
  191. renderRightLabel() {
  192. if (this.props.big || this.props.leftLabel || this.props.noLabel) {
  193. return null
  194. }
  195. return (
  196. <Label secondary={this.props.secondary}>{this.getLabel()}</Label>
  197. )
  198. }
  199. render() {
  200. return (
  201. <Wrapper
  202. disabled={this.props.disabled}
  203. style={this.props.style}
  204. width={this.props.width}
  205. justifyContent={this.props.justifyContent}
  206. >
  207. {this.renderLeftLabel()}
  208. {this.renderInput()}
  209. {this.renderRightLabel()}
  210. </Wrapper>
  211. )
  212. }
  213. }
  214. export default Switch