UNPKG

react-native-ui-lib

Version:

[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg)](https://stand-with-ukraine.pp.ua)

218 lines (217 loc) 6.04 kB
import _isNaN from "lodash/isNaN"; import _isUndefined from "lodash/isUndefined"; import React, { PureComponent } from 'react'; import { StyleSheet } 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"; import Text from "../text"; const LABEL_FORMATTER_VALUES = [1, 2, 3, 4]; const DEFAULT_PIMPLE_SIZE = 10; const DEFAULT_BADGE_SIZE = 20; /** * @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 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() { const { size, label } = this.props; if (size !== undefined) { return size; } return label === undefined ? DEFAULT_PIMPLE_SIZE : DEFAULT_BADGE_SIZE; } 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" recorderTag={'unmask'}> {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" tintColor={Colors.$iconDefaultLight} //@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.$backgroundGeneralHeavy : undefined, alignItems: 'center', justifyContent: 'center', overflow: 'hidden' }, label: { ...Typography.text90, color: Colors.$textDefaultLight, backgroundColor: 'transparent' }, labelSmall: { ...Typography.text100, lineHeight: undefined } }); return styles; } export { Badge }; // For tests export default asBaseComponent(Badge);