Switch.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @flow strict-local
  8. * @format
  9. * @generate-docs
  10. */
  11. 'use strict';
  12. import Platform from '../../Utilities/Platform';
  13. import * as React from 'react';
  14. import StyleSheet from '../../StyleSheet/StyleSheet';
  15. import AndroidSwitchNativeComponent, {
  16. Commands as AndroidSwitchCommands,
  17. } from './AndroidSwitchNativeComponent';
  18. import SwitchNativeComponent, {
  19. Commands as SwitchCommands,
  20. } from './SwitchNativeComponent';
  21. import type {ColorValue} from '../../StyleSheet/StyleSheetTypes';
  22. import type {SyntheticEvent} from '../../Types/CoreEventTypes';
  23. import type {ViewProps} from '../View/ViewPropTypes';
  24. type SwitchChangeEvent = SyntheticEvent<
  25. $ReadOnly<{|
  26. value: boolean,
  27. |}>,
  28. >;
  29. export type Props = $ReadOnly<{|
  30. ...ViewProps,
  31. /**
  32. * Whether the switch is disabled. Defaults to false.
  33. */
  34. disabled?: ?boolean,
  35. /**
  36. * Boolean value of the switch. Defaults to false.
  37. */
  38. value?: ?boolean,
  39. /**
  40. * Custom color for the switch thumb.
  41. */
  42. thumbColor?: ?ColorValue,
  43. /**
  44. * Custom colors for the switch track.
  45. *
  46. * NOTE: On iOS when the switch value is false, the track shrinks into the
  47. * border. If you want to change the color of the background exposed by the
  48. * shrunken track, use `ios_backgroundColor`.
  49. */
  50. trackColor?: ?$ReadOnly<{|
  51. false?: ?ColorValue,
  52. true?: ?ColorValue,
  53. |}>,
  54. /**
  55. * On iOS, custom color for the background. This background color can be seen
  56. * either when the switch value is false or when the switch is disabled (and
  57. * the switch is translucent).
  58. */
  59. ios_backgroundColor?: ?ColorValue,
  60. /**
  61. * Called when the user tries to change the value of the switch.
  62. *
  63. * Receives the change event as an argument. If you want to only receive the
  64. * new value, use `onValueChange` instead.
  65. */
  66. onChange?: ?(event: SwitchChangeEvent) => Promise<void> | void,
  67. /**
  68. * Called when the user tries to change the value of the switch.
  69. *
  70. * Receives the new value as an argument. If you want to instead receive an
  71. * event, use `onChange`.
  72. */
  73. onValueChange?: ?(value: boolean) => Promise<void> | void,
  74. |}>;
  75. /**
  76. * A visual toggle between two mutually exclusive states.
  77. *
  78. * This is a controlled component that requires an `onValueChange` callback that
  79. * updates the `value` prop in order for the component to reflect user actions.
  80. * If the `value` prop is not updated, the component will continue to render the
  81. * supplied `value` prop instead of the expected result of any user actions.
  82. */
  83. class Switch extends React.Component<Props> {
  84. _nativeSwitchRef: ?React.ElementRef<
  85. typeof SwitchNativeComponent | typeof AndroidSwitchNativeComponent,
  86. >;
  87. _lastNativeValue: ?boolean;
  88. render(): React.Node {
  89. const {
  90. disabled,
  91. ios_backgroundColor,
  92. onChange,
  93. onValueChange,
  94. style,
  95. thumbColor,
  96. trackColor,
  97. value,
  98. ...props
  99. } = this.props;
  100. const trackColorForFalse = trackColor?.false;
  101. const trackColorForTrue = trackColor?.true;
  102. if (Platform.OS === 'android') {
  103. const platformProps = {
  104. enabled: disabled !== true,
  105. on: value === true,
  106. style,
  107. thumbTintColor: thumbColor,
  108. trackColorForFalse: trackColorForFalse,
  109. trackColorForTrue: trackColorForTrue,
  110. trackTintColor: value === true ? trackColorForTrue : trackColorForFalse,
  111. };
  112. return (
  113. <AndroidSwitchNativeComponent
  114. {...props}
  115. {...platformProps}
  116. accessibilityRole={props.accessibilityRole ?? 'switch'}
  117. onChange={this._handleChange}
  118. onResponderTerminationRequest={returnsFalse}
  119. onStartShouldSetResponder={returnsTrue}
  120. ref={this._handleSwitchNativeComponentRef}
  121. />
  122. );
  123. }
  124. const platformProps = {
  125. disabled,
  126. onTintColor: trackColorForTrue,
  127. style: StyleSheet.compose(
  128. {height: 31, width: 51},
  129. StyleSheet.compose(
  130. style,
  131. ios_backgroundColor == null
  132. ? null
  133. : {
  134. backgroundColor: ios_backgroundColor,
  135. borderRadius: 16,
  136. },
  137. ),
  138. ),
  139. thumbTintColor: thumbColor,
  140. tintColor: trackColorForFalse,
  141. value: value === true,
  142. };
  143. return (
  144. <SwitchNativeComponent
  145. {...props}
  146. {...platformProps}
  147. accessibilityRole={props.accessibilityRole ?? 'switch'}
  148. onChange={this._handleChange}
  149. onResponderTerminationRequest={returnsFalse}
  150. onStartShouldSetResponder={returnsTrue}
  151. ref={this._handleSwitchNativeComponentRef}
  152. />
  153. );
  154. }
  155. componentDidUpdate() {
  156. // This is necessary in case native updates the switch and JS decides
  157. // that the update should be ignored and we should stick with the value
  158. // that we have in JS.
  159. const nativeProps = {};
  160. const value = this.props.value === true;
  161. if (this._lastNativeValue !== value) {
  162. nativeProps.value = value;
  163. }
  164. if (
  165. Object.keys(nativeProps).length > 0 &&
  166. this._nativeSwitchRef &&
  167. this._nativeSwitchRef.setNativeProps
  168. ) {
  169. if (Platform.OS === 'android') {
  170. AndroidSwitchCommands.setNativeValue(
  171. this._nativeSwitchRef,
  172. nativeProps.value,
  173. );
  174. } else {
  175. SwitchCommands.setValue(this._nativeSwitchRef, nativeProps.value);
  176. }
  177. }
  178. }
  179. _handleChange = (event: SwitchChangeEvent) => {
  180. if (this.props.onChange != null) {
  181. this.props.onChange(event);
  182. }
  183. if (this.props.onValueChange != null) {
  184. this.props.onValueChange(event.nativeEvent.value);
  185. }
  186. this._lastNativeValue = event.nativeEvent.value;
  187. this.forceUpdate();
  188. };
  189. _handleSwitchNativeComponentRef = (
  190. ref: ?React.ElementRef<
  191. typeof SwitchNativeComponent | typeof AndroidSwitchNativeComponent,
  192. >,
  193. ) => {
  194. this._nativeSwitchRef = ref;
  195. };
  196. }
  197. const returnsFalse = () => false;
  198. const returnsTrue = () => true;
  199. module.exports = Switch;