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

283 lines (241 loc) • 6.59 kB
import _pt from "prop-types"; import _ from 'lodash'; import React, { PureComponent } from 'react'; import { StyleSheet, Text } from 'react-native'; import { extractAccessibilityProps } from "../../commons/modifiers"; import { asBaseComponent } from "../../commons/new"; import { BorderRadiuses, Colors, Spacings, Typography } from "../../style"; import TouchableOpacity from "../touchableOpacity"; import Image from "../image"; import View from "../view"; const LABEL_FORMATTER_VALUES = [1, 2, 3, 4]; /** * @description: Round colored badge, typically used to show a number * @extends: View * @image: https://user-images.githubusercontent.com/33805983/34480753-df7a868a-efb6-11e7-9072-80f5c110a4f3.png * @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/BadgesScreen.tsx */ class Badge extends PureComponent { static propTypes = { /** * Text to show inside the badge. * Not passing a label (undefined) will present a pimple badge. */ label: _pt.string, /** * Color of the badge background */ backgroundColor: _pt.string, /** * the badge size */ size: _pt.number, /** * Press handler */ onPress: _pt.func, /** * width of border around the badge */ borderWidth: _pt.number, /** * radius of border around the badge */ borderRadius: _pt.number, /** * Additional styling to badge icon */ iconStyle: _pt.object, /** * Additional props passed to icon */ iconProps: _pt.object, /** * Custom element to render instead of an icon */ customElement: _pt.element }; static displayName = 'Badge'; constructor(props) { super(props); this.styles = createStyles(props); } getAccessibilityProps() { const { onPress, icon, label, accessibilityLabel } = this.props; return { accessibilityLabel: accessibilityLabel || label ? `${label} new items` : 'badge', ...extractAccessibilityProps(this.props), accessible: !_.isUndefined(label), accessibilityRole: onPress ? 'button' : icon ? 'image' : 'text' }; } get size() { return this.props.size || 20; } isSmallBadge() { return this.size <= 16; } getBadgeSizeStyle() { const { borderWidth, icon, customElement } = this.props; const label = this.getFormattedLabel(); const style = { paddingHorizontal: this.isSmallBadge() ? 4 : 6, height: this.size, minWidth: this.size }; if (icon && label) { style.paddingRight = 6; style.paddingLeft = 4; style.height = Spacings.s5; if (borderWidth) { style.height += borderWidth * 2; } return style; } if (customElement) { return style; } const isPimple = label === undefined; if (isPimple || icon) { style.paddingHorizontal = 0; style.minWidth = undefined; style.width = style.height; if (borderWidth) { style.height += borderWidth * 2; style.width += borderWidth * 2; } return style; } if (borderWidth) { style.height += borderWidth * 2; style.minWidth += borderWidth * 2; } return style; } getFormattedLabel() { const { labelFormatterLimit, label } = this.props; if (_.isNaN(label)) { return label; } if (LABEL_FORMATTER_VALUES.includes(labelFormatterLimit)) { const maxLabelNumber = 10 ** labelFormatterLimit - 1; let formattedLabel = label; if (formattedLabel > maxLabelNumber) { formattedLabel = `${maxLabelNumber}+`; } return formattedLabel; } else { return label; } } getBorderStyling() { const { borderWidth, borderColor, borderRadius } = this.props; const style = {}; if (borderWidth) { style.borderWidth = borderWidth; style.borderColor = borderColor; } if (borderRadius) { style.borderRadius = borderRadius; } return style; } renderLabel() { const { labelStyle, label } = this.props; if (label) { return <Text style={[this.styles.label, this.isSmallBadge() && this.styles.labelSmall, labelStyle]} allowFontScaling={false} numberOfLines={1} testID="badge"> {this.getFormattedLabel()} </Text>; } } renderCustomElement() { const { customElement } = this.props; return customElement; } renderIcon() { const { icon, iconStyle, iconProps, borderColor, label } = this.props; const flex = label ? 0 : 1; return icon && <Image source={icon} resizeMode="contain" //@ts-ignore borderColor={borderColor} {...iconProps} style={{ flex, ...iconStyle }} />; } render() { const { activeOpacity, backgroundColor, containerStyle, hitSlop, onPress, testID, ...others } = this.props; const backgroundStyle = backgroundColor && { backgroundColor }; const sizeStyle = this.getBadgeSizeStyle(); const borderStyle = this.getBorderStyling(); const Container = onPress ? TouchableOpacity : View; return (// The extra View wrapper is to break badge's flex-ness // @ts-ignore <View style={containerStyle} {...others} backgroundColor={undefined} // @ts-expect-error borderWidth={undefined} {...this.getAccessibilityProps()}> <Container testID={testID} pointerEvents={'none'} style={[sizeStyle, this.styles.badge, borderStyle, backgroundStyle]} onPress={onPress} activeOpacity={activeOpacity} hitSlop={hitSlop} row> {this.renderCustomElement()} {this.renderIcon()} {this.renderLabel()} </Container> </View> ); } } function createStyles(props) { const styles = StyleSheet.create({ badge: { alignSelf: 'flex-start', borderRadius: BorderRadiuses.br100, backgroundColor: !props.icon || props.customElement ? Colors.primary : undefined, alignItems: 'center', justifyContent: 'center', overflow: 'hidden' }, label: { ...Typography.text90, color: Colors.white, backgroundColor: 'transparent' }, labelSmall: { ...Typography.text100, lineHeight: undefined } }); return styles; } export { Badge }; // For tests export default asBaseComponent(Badge);