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
JavaScript
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);