index.tsx 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. import {
  2. type FC,
  3. useEffect,
  4. useRef
  5. } from "react";
  6. import {
  7. TouchableOpacity,
  8. Animated,
  9. Platform,
  10. Easing,
  11. View
  12. } from "react-native";
  13. import type ISwitchProps from "./type";
  14. import stylesheet, {
  15. getSwitchType,
  16. useStyles
  17. } from "./stylesheet";
  18. import {
  19. NCoreUIKitLocalize,
  20. NCoreUIKitTheme
  21. } from "../../core/hooks";
  22. import type ITextProps from "../text/type";
  23. import Loading from "../loading";
  24. import Text from "../text";
  25. const Switch: FC<ISwitchProps> = ({
  26. displayBehaviourWhileLoading = "disabled",
  27. spreadBehaviour = "baseline",
  28. animationDuration = 100,
  29. isOptional = false,
  30. isDisabled = false,
  31. type = "neutral",
  32. isFlip = false,
  33. customLocalize,
  34. subTitleStyle,
  35. optionalText,
  36. customTheme,
  37. titleStyle,
  38. isLoading,
  39. isActive,
  40. subTitle,
  41. onPress,
  42. title,
  43. style,
  44. ...props
  45. }) => {
  46. const {
  47. inlineSpaces,
  48. colors,
  49. spaces
  50. } = NCoreUIKitTheme.useContext(customTheme);
  51. const {
  52. localize
  53. } = NCoreUIKitLocalize.useContext(customLocalize);
  54. const indicatorAnim = useRef(new Animated.Value(0)).current;
  55. const currentType = getSwitchType({
  56. type
  57. });
  58. const SWITCH_INDICATOR_SIZE = 20;
  59. const {
  60. indicatorContainer: indicatorContainerDynamicStyle,
  61. contentContainer: contentContainerDynamicStyle,
  62. titleContainer: titleContainerDynamicStyle,
  63. optionalText: optionalTextDynamicStyle,
  64. container: containerDynamicStyle,
  65. indicator: indicatorDynamicStyle,
  66. subTitle: subTitleDynamicStyle,
  67. loading: loadingDynamicStyle,
  68. overlay: overlayDynamicStyle,
  69. title: titleDynamicStyle
  70. } = useStyles({
  71. displayBehaviourWhileLoading,
  72. SWITCH_INDICATOR_SIZE,
  73. spreadBehaviour,
  74. inlineSpaces,
  75. currentType,
  76. isDisabled,
  77. isLoading,
  78. isActive,
  79. isFlip,
  80. colors,
  81. spaces,
  82. type
  83. });
  84. useEffect(() => {
  85. if(isActive) {
  86. Animated.timing(indicatorAnim, {
  87. toValue: (SWITCH_INDICATOR_SIZE / 2) + spaces.spacingSm,
  88. duration: animationDuration,
  89. useNativeDriver: true,
  90. easing: Easing.linear
  91. }).start();
  92. } else {
  93. Animated.timing(indicatorAnim, {
  94. duration: animationDuration,
  95. useNativeDriver: true,
  96. easing: Easing.linear,
  97. toValue: 0
  98. }).start();
  99. }
  100. }, [isActive]);
  101. const titleProps: ITextProps = {
  102. color: currentType.titleColor,
  103. };
  104. const subTitleProps: ITextProps = {
  105. color: currentType.subTitleColor,
  106. };
  107. const indicatorIconProps: {
  108. color: keyof NCoreUIKit.IconContentColors;
  109. customColor?: string;
  110. } = {
  111. color: currentType.indicatorColor
  112. };
  113. if (isDisabled || isLoading) {
  114. const stateType = type === "danger" ? "error" : type;
  115. subTitleProps.customColor = colors.system.state.content.disabled[stateType];
  116. titleProps.customColor = colors.system.state.content.disabled[stateType];
  117. if(isActive && stateType === "neutral") {
  118. indicatorIconProps.customColor = colors.system.state.border.disabled.primary;
  119. } else {
  120. indicatorIconProps.customColor = colors.system.state.border.disabled[stateType];
  121. }
  122. }
  123. const renderOptionalText = () => {
  124. if(!isOptional && !optionalText) {
  125. return null;
  126. }
  127. return <Text
  128. variant="labelLargeSize"
  129. color={titleProps.color}
  130. style={[
  131. optionalTextDynamicStyle
  132. ]}
  133. {...titleProps}
  134. >
  135. ( {isOptional ? localize("is-optional") : optionalText} )
  136. </Text>;
  137. };
  138. const renderTitle = () => {
  139. if (!title) {
  140. return null;
  141. }
  142. return <View
  143. style={[
  144. stylesheet.titleContainer,
  145. titleContainerDynamicStyle
  146. ]}
  147. >
  148. <Text
  149. variant="bodyMediumSize"
  150. style={[
  151. titleStyle,
  152. stylesheet.title,
  153. titleDynamicStyle
  154. ]}
  155. {...titleProps}
  156. >
  157. {title}
  158. </Text>
  159. {renderOptionalText()}
  160. </View>;
  161. };
  162. const renderIndicator = () => {
  163. return <Animated.View
  164. style={[
  165. stylesheet.indicator,
  166. indicatorDynamicStyle,
  167. {
  168. transform: [{
  169. translateX: indicatorAnim
  170. }]
  171. }
  172. ]}
  173. />;
  174. };
  175. const renderIndicatorContainer = () => {
  176. if (isLoading) {
  177. return <Loading
  178. style={{
  179. ...loadingDynamicStyle
  180. }}
  181. />;
  182. }
  183. return <View
  184. style={[
  185. stylesheet.indicatorContainer,
  186. indicatorContainerDynamicStyle
  187. ]}
  188. >
  189. {renderIndicator()}
  190. {renderOverlay()}
  191. </View>;
  192. };
  193. const renderOverlay = () => {
  194. return <View
  195. style={[
  196. stylesheet.overlay,
  197. overlayDynamicStyle
  198. ]}
  199. />;
  200. };
  201. const renderSubtitle = () => {
  202. if(!subTitle) {
  203. return null;
  204. }
  205. return <Text
  206. {...subTitleProps}
  207. style={[
  208. subTitleStyle,
  209. stylesheet.subTitle,
  210. subTitleDynamicStyle
  211. ]}
  212. >
  213. {subTitle}
  214. </Text>;
  215. };
  216. const renderContent = () => {
  217. if(!title) {
  218. return null;
  219. }
  220. return <View
  221. style={[
  222. stylesheet.contentContainer,
  223. contentContainerDynamicStyle
  224. ]}
  225. >
  226. {renderTitle()}
  227. {renderSubtitle()}
  228. </View>;
  229. };
  230. return (
  231. <TouchableOpacity
  232. {...props}
  233. onPress={isDisabled || isLoading ? () => null : onPress ? onPress : undefined}
  234. disabled={isDisabled || isLoading || !onPress}
  235. style={[
  236. style,
  237. stylesheet.container,
  238. containerDynamicStyle
  239. ]}
  240. {...Platform.select({
  241. web: {
  242. dataSet: {
  243. disablePressableDownEffect: "true"
  244. }
  245. },
  246. default: {}
  247. })}
  248. >
  249. {isFlip ? null : renderIndicatorContainer()}
  250. {renderContent()}
  251. {isFlip ? renderIndicatorContainer() : null}
  252. </TouchableOpacity>
  253. );
  254. };
  255. export default Switch;