UNPKG

react-native-ui-lib

Version:

<p align="center"> <img src="https://user-images.githubusercontent.com/1780255/105469025-56759000-5ca0-11eb-993d-3568c1fd54f4.png" height="250px" style="display:block"/> </p> <p align="center">UI Toolset & Components Library for React Native</p> <p a

464 lines (402 loc) • 11.7 kB
import _ from 'lodash'; import React, { PureComponent } from 'react'; import { Platform, StyleSheet, LayoutAnimation } from 'react-native'; import { asBaseComponent, forwardRef, Constants } from "../../commons/new"; import { Colors, Typography, BorderRadiuses } from "../../style"; // @ts-ignore need to migrate to commonsNew import { modifiers } from "../../commons"; import TouchableOpacity from "../touchableOpacity"; import Text from "../text"; import Image from "../image"; import { ButtonSize, ButtonAnimationDirection, ButtonProps, DEFAULT_PROPS } from "./ButtonTypes"; export { ButtonSize, ButtonAnimationDirection, ButtonProps }; import { PADDINGS, HORIZONTAL_PADDINGS, MIN_WIDTH, DEFAULT_SIZE } from "./ButtonConstants"; const { extractColorValue, extractTypographyValue } = modifiers; class Button extends PureComponent { static displayName = 'Button'; static defaultProps = DEFAULT_PROPS; static sizes = ButtonSize; static animationDirection = ButtonAnimationDirection; // This redundant constructor for some reason fix tests :/ // eslint-disable-next-line constructor(props) { super(props); } state = { size: undefined }; styles = createStyles(); componentDidUpdate(prevProps) { if (this.props.animateLayout && !_.isEqual(prevProps, this.props)) { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); } } // This method will be called more than once in case of layout change! onLayout = event => { const height = event.nativeEvent.layout.height; if (this.props.round) { const width = event.nativeEvent.layout.width; const size = height >= width ? height : width; this.setState({ size }); } if (Constants.isAndroid && Platform.Version <= 17) { this.setState({ borderRadius: height / 2 }); } }; get isOutline() { const { outline, outlineColor } = this.props; return Boolean(outline || outlineColor); } get isLink() { const { link, hyperlink } = this.props; return link || hyperlink; } get isFilled() { return !this.isOutline && !this.isLink; } get isIconButton() { const { iconSource, label } = this.props; return iconSource && !label; } getBackgroundColor() { const { backgroundColor: themeBackgroundColor, modifiers } = this.props; const { disabled, outline, disabledBackgroundColor, backgroundColor: propsBackgroundColor } = this.props; const { backgroundColor: stateBackgroundColor } = modifiers; if (!outline && !this.isLink) { if (disabled) { return disabledBackgroundColor || Colors.backgroundDisabled; } return propsBackgroundColor || stateBackgroundColor || themeBackgroundColor || Colors.backgroundPrimaryHeavy; } return 'transparent'; } getActiveBackgroundColor() { const { getActiveBackgroundColor } = this.props; if (getActiveBackgroundColor) { return getActiveBackgroundColor(this.getBackgroundColor(), this.props); } } getLabelColor() { const { linkColor, outline, outlineColor, disabled, color: propsColor } = this.props; const isLink = this.isLink; let color = Colors.textDefaultLight; if (isLink) { color = linkColor || Colors.textPrimary; } else if (outline) { color = outlineColor || Colors.textPrimary; } else if (this.isIconButton) { color = undefined; // Colors.grey10; } if (disabled && (isLink || outline)) { return Colors.textDisabled; } color = propsColor || extractColorValue(this.props) || color; return color; } getLabelSizeStyle() { const size = this.props.size || DEFAULT_SIZE; const LABEL_STYLE_BY_SIZE = {}; LABEL_STYLE_BY_SIZE[Button.sizes.xSmall] = { ...Typography.text80 }; LABEL_STYLE_BY_SIZE[Button.sizes.small] = { ...Typography.text80 }; LABEL_STYLE_BY_SIZE[Button.sizes.medium] = { ...Typography.text80 }; LABEL_STYLE_BY_SIZE[Button.sizes.large] = {}; const labelSizeStyle = LABEL_STYLE_BY_SIZE[size]; return labelSizeStyle; } getContainerSizeStyle() { const { outline, avoidMinWidth, avoidInnerPadding, round } = this.props; const size = this.props.size || DEFAULT_SIZE; const outlineWidth = this.props.outlineWidth || 1; const CONTAINER_STYLE_BY_SIZE = {}; CONTAINER_STYLE_BY_SIZE[Button.sizes.xSmall] = round ? { height: this.state.size, width: this.state.size, padding: PADDINGS.XSMALL } : { paddingVertical: PADDINGS.XSMALL, paddingHorizontal: HORIZONTAL_PADDINGS.XSMALL, minWidth: MIN_WIDTH.XSMALL }; CONTAINER_STYLE_BY_SIZE[Button.sizes.small] = round ? { height: this.state.size, width: this.state.size, padding: PADDINGS.SMALL } : { paddingVertical: PADDINGS.SMALL, paddingHorizontal: HORIZONTAL_PADDINGS.SMALL, minWidth: MIN_WIDTH.SMALL }; CONTAINER_STYLE_BY_SIZE[Button.sizes.medium] = round ? { height: this.state.size, width: this.state.size, padding: PADDINGS.MEDIUM } : { paddingVertical: PADDINGS.MEDIUM, paddingHorizontal: HORIZONTAL_PADDINGS.MEDIUM, minWidth: MIN_WIDTH.MEDIUM }; CONTAINER_STYLE_BY_SIZE[Button.sizes.large] = round ? { height: this.state.size, width: this.state.size, padding: PADDINGS.LARGE } : { paddingVertical: PADDINGS.LARGE, paddingHorizontal: HORIZONTAL_PADDINGS.LARGE, minWidth: MIN_WIDTH.LARGE }; if (outline) { _.forEach(CONTAINER_STYLE_BY_SIZE, style => { if (round) { style.padding -= outlineWidth; // eslint-disable-line } else { style.paddingVertical -= outlineWidth; // eslint-disable-line style.paddingHorizontal -= outlineWidth; // eslint-disable-line } }); } const containerSizeStyle = CONTAINER_STYLE_BY_SIZE[size]; if (this.isLink || this.isIconButton && !round) { containerSizeStyle.paddingVertical = undefined; containerSizeStyle.paddingHorizontal = undefined; containerSizeStyle.minWidth = undefined; } if (avoidMinWidth) { containerSizeStyle.minWidth = undefined; } if (avoidInnerPadding) { containerSizeStyle.paddingVertical = undefined; containerSizeStyle.paddingHorizontal = undefined; } return containerSizeStyle; } getOutlineStyle() { const { outline, outlineColor, outlineWidth, disabled } = this.props; let outlineStyle; if ((outline || outlineColor) && !this.isLink) { outlineStyle = { borderWidth: outlineWidth || 1, borderColor: outlineColor || Colors.outlinePrimary }; if (disabled) { outlineStyle.borderColor = Colors.outlineDisabled; } } return outlineStyle; } getBorderRadiusStyle() { const { fullWidth, borderRadius: borderRadiusFromProps, modifiers } = this.props; if (this.isLink || fullWidth || borderRadiusFromProps === 0) { return { borderRadius: 0 }; } const { borderRadius: borderRadiusFromState } = modifiers; const borderRadius = borderRadiusFromProps || borderRadiusFromState || BorderRadiuses.br100; return { borderRadius }; } getShadowStyle() { const backgroundColor = this.getBackgroundColor(); const { enableShadow } = this.props; if (enableShadow) { return [this.styles.shadowStyle, { shadowColor: backgroundColor }]; } } getIconStyle() { const { disabled, iconStyle: propsIconStyle, iconOnRight } = this.props; const size = this.props.size || DEFAULT_SIZE; const iconStyle = { tintColor: this.getLabelColor() }; const marginSide = [Button.sizes.large, Button.sizes.medium].includes(size) ? 8 : 4; if (!this.isIconButton) { if (iconOnRight) { iconStyle.marginLeft = marginSide; } else { iconStyle.marginRight = marginSide; } } if (disabled && !this.isFilled) { iconStyle.tintColor = Colors.iconDisabled; } return [iconStyle, propsIconStyle]; } getAnimationDirectionStyle() { const { animateTo } = this.props; let style; switch (animateTo) { case 'left': style = { alignSelf: 'flex-start' }; break; case 'right': style = { alignSelf: 'flex-end' }; break; default: // 'center' is the default break; } return style; } renderIcon() { const { iconSource, supportRTL, testID } = this.props; if (iconSource) { const iconStyle = this.getIconStyle(); if (typeof iconSource === 'function') { return iconSource(iconStyle); } else { return <Image source={iconSource} supportRTL={supportRTL} style={iconStyle} testID={`${testID}.icon`} />; } } return null; } renderLabel() { const { label, labelStyle, labelProps, hyperlink, testID } = this.props; const typography = extractTypographyValue(this.props); const color = this.getLabelColor(); const labelSizeStyle = this.getLabelSizeStyle(); if (label) { return <Text style={[this.styles.text, !!color && { color }, labelSizeStyle, { ...typography }, labelStyle]} underline={hyperlink} numberOfLines={1} testID={`${testID}.label`} {...labelProps}> {label} </Text>; } return null; } render() { const { onPress, disabled, style, testID, animateLayout, modifiers, forwardedRef, ...others } = this.props; const shadowStyle = this.getShadowStyle(); const { margins } = modifiers; const backgroundColor = this.getBackgroundColor(); const outlineStyle = this.getOutlineStyle(); const containerSizeStyle = this.getContainerSizeStyle(); const borderRadiusStyle = this.getBorderRadiusStyle(); return <TouchableOpacity row centerV style={[this.styles.container, animateLayout && this.getAnimationDirectionStyle(), containerSizeStyle, this.isLink && this.styles.innerContainerLink, shadowStyle, margins, { backgroundColor }, borderRadiusStyle, outlineStyle, style]} activeOpacity={0.6} activeBackgroundColor={this.getActiveBackgroundColor()} onLayout={this.onLayout} onPress={onPress} disabled={disabled} testID={testID} {...others} ref={forwardedRef}> {this.props.children} {this.props.iconOnRight ? this.renderLabel() : this.renderIcon()} {this.props.iconOnRight ? this.renderIcon() : this.renderLabel()} </TouchableOpacity>; } } function createStyles() { return StyleSheet.create({ container: { backgroundColor: 'transparent', justifyContent: 'center', alignItems: 'center' }, innerContainerLink: { minWidth: undefined, paddingHorizontal: undefined, paddingVertical: undefined, borderRadius: BorderRadiuses.br0, backgroundColor: undefined }, shadowStyle: { shadowOffset: { height: 5, width: 0 }, shadowOpacity: 0.35, shadowRadius: 9.5, elevation: 2 }, text: { backgroundColor: 'transparent', flex: 0, flexDirection: 'row', ...Typography.text70 } }); } export { Button }; // For tests export default asBaseComponent(forwardRef(Button));