@fto-consult/expo-ui
Version:
Bibliothèque de composants UI Expo,react-native
350 lines (336 loc) • 10.3 kB
JavaScript
import React from '$react';
import View from "$ecomponents/View";
import {
Animated,
Platform,
StyleSheet,
Pressable,
} from 'react-native';
import Icon from "$ecomponents/Icon";
import {Text,TouchableRipple } from 'react-native-paper';
import theme,{ black,Colors, white,StyleProps} from '$theme';
import PropTypes from "prop-types";
import {flatMode } from '$ecomponents/TextField/utils';
import {defaultStr} from "$cutils";
import Surface from "$ecomponents/Surface";
import {isObj,defaultObj} from "$cutils";
const ChipComponent = React.forwardRef(({
mode = flatMode,
children,
icon,
avatar,
selected = false,
disabled = false,
closeIconAccessibilityLabel = 'Close',
onPress,
onLongPress,
onClose,
closeIcon,
labelStyle,
labelProps,
style,
testID,
selectedColor,
ellipsizeMode,
...rest
},ref) => {
labelProps = Object.assign({},labelProps);
testID =defaultStr(testID,"RN_ChipComponent");
const { current: elevation } = React.useRef(
new Animated.Value(0)
);
const handlePressIn = () => {
const { scale } = theme.animation;
Animated.timing(elevation, {
toValue: 4,
duration: 200 * scale,
useNativeDriver: true,
}).start();
};
const handlePressOut = () => {
const { scale } = theme.animation;
Animated.timing(elevation, {
toValue: 0,
duration: 150 * scale,
useNativeDriver: true,
}).start();
};
const dark = theme.isDark();
const defaultBackgroundColor =
mode === 'outlined' ? theme.colors.surface : dark ? '#383838' : '#ebebeb';
style = Object.assign({},StyleSheet.flatten(style));
const { backgroundColor = defaultBackgroundColor, borderRadius = 16,color:customColor} = style;
const borderColor =
mode === 'outlined'? (selectedColor !== undefined ? selectedColor : Colors.setAlpha(dark ? white : black,0.29)) : backgroundColor;
const textColor = Colors.isValid(customColor)?customColor
: Colors.setAlpha(selectedColor !== undefined ? selectedColor : theme.colors.text,0.87);
const iconColor = Colors.setAlpha(textColor,theme.ALPHA);
const backgroundColorString = typeof backgroundColor === 'string' ? backgroundColor : defaultBackgroundColor;
const selectedBackgroundColor = ( dark ? Colors.lighten(backgroundColorString,mode === 'outlined' ? 0.2 : 0.4)
: Colors.darken(backgroundColorString,mode === 'outlined' ? 0.08 : 0.2)
)
const underlayColor = selectedColor ? Colors.setAlpha(selectedColor,theme.ALPHA) : selectedBackgroundColor;
const accessibilityTraits = ['button'];
const accessibilityState = {
selected,
disabled,
};
if (selected) {
accessibilityTraits.push('selected');
}
if (disabled) {
accessibilityTraits.push('disabled');
}
const onChipClose = (e)=>{
React.stopEventPropagation(e);
if(onClose){
onClose(e);
}
}
return (
<Surface
ref = {ref}
elevation= {typeof elevation =='number'? elevation : 0}
style={
[
styles.container,
{
backgroundColor: selected
? selectedBackgroundColor
: backgroundColor,
borderColor,
borderRadius,
},
style,
]
}
{...rest}
testID = {testID}
>
<TouchableRipple
borderless
delayPressIn={0}
style={[{ borderRadius }, styles.touchable]}
onPress={onPress}
onLongPress={onLongPress}
onPressIn={handlePressIn}
onPressOut={handlePressOut}
underlayColor={underlayColor}
disabled={disabled}
// @ts-expect-error We keep old a11y props for backwards compat with old RN versions
accessibilityTraits={accessibilityTraits}
accessibilityComponentType="button"
//role="button"
accessibilityState={accessibilityState}
testID={testID+"_Container"}
>
<View testID={testID+"_ContentContainer"} style={[styles.content, { paddingRight: onClose ? 32 : 4 }]}>
{avatar && !icon ? (
<View testID={testID+"_AvatarContainer"} style={[styles.avatarWrapper, disabled && { opacity: 0.26 }]}>
{React.isValidElement(avatar)
? React.cloneElement(avatar, {
style: [styles.avatar, avatar.props.style],
})
: avatar}
</View>
) : null}
{icon || selected ? (
<View
style={[
styles.icon,
avatar ? [styles.avatar, styles.avatarSelected] : null,
]}
testID={testID+"_IconContainer"}
>
{icon ? (
<Icon
source={icon}
color={avatar ? white : iconColor}
size={18}
testID={testID+"_CloseIcon"}
/>
) : (
<Icon
name="check"
color={avatar ? white : iconColor}
size={18}
testID={testID+"_Icon"}
direction="ltr"
/>
)}
</View>
) : null}
<Text
numberOfLines={1}
testID={testID+"_Text"}
{...labelProps}
style={StyleSheet.flatten([
{userSelect:"none"},
styles.text,
{
...(defaultObj(theme.fonts?.regular,theme.fonts?.default)),
color: textColor,
marginHorizontal : 4,
marginLeft: avatar || icon || selected ? 4 : 8,
},
labelProps.style,
labelStyle,
])}
ellipsizeMode={ellipsizeMode}
>
{children}
</Text>
</View>
</TouchableRipple>
{onClose ? (
<View style={styles.closeButtonStyle} testID={testID+"_CloseButtonContainer"}>
<Pressable
onPress={onPress}
// @ts-expect-error We keep old a11y props for backwards compat with old RN versions
accessibilityTraits="button"
accessibilityComponentType="button"
//role="button"
aria-label={closeIconAccessibilityLabel}
testID={testID+"_CloseButtonRipple"}
>
<View testID={testID+"_ContentContainer"} style={[styles.icon, styles.closeIcon]}>
{closeIcon ? (
<Icon testID={testID+"_CloseButtonCloseIcon"} source={closeIcon} onPress = {onChipClose} color={iconColor} size={16} />
) : (
<Icon
name="close-circle"
size={16}
color={iconColor}
onPress = {onChipClose}
testID={testID+"_CloseButtonChip"}
/>
)}
</View>
</Pressable>
</View>
) : null}
</Surface>
);
});
const styles = StyleSheet.create({
container: {
borderWidth: StyleSheet.hairlineWidth,
borderStyle: 'solid',
flexDirection: Platform.select({ default: 'column', web: 'row' }),
},
content: {
flexDirection: 'row',
alignItems: 'center',
paddingLeft: 4,
position: 'relative',
flexGrow: 1,
},
icon: {
padding: 4,
alignSelf: 'center',
},
closeIcon: {
marginRight: 4,
},
text: {
minHeight: 24,
lineHeight: 24,
verticalAlign: 'middle',
marginVertical: 4,
},
avatar: {
width: 24,
height: 24,
borderRadius: 12,
},
avatarWrapper: {
marginRight: 4,
},
avatarSelected: {
position: 'absolute',
top: 4,
left: 4,
backgroundColor: 'rgba(0, 0, 0, .29)',
},
closeButtonStyle: {
position: 'absolute',
right: 0,
height: '100%',
justifyContent: 'center',
alignItems: 'center',
},
touchable: {
flexGrow: 1,
},
});
export default ChipComponent;
ChipComponent.propTypes = {
/**
* Mode of the chip.
* - `flat` - flat chip without outline.
* - `outlined` - chip with an outline.
*/
mode : PropTypes.oneOf(['flat','outlined']),
/**
* Text content of the `Chip`.
*/
children: PropTypes.node,
/**
* Icon to display for the `Chip`. Both icon and avatar cannot be specified.
*/
icon: PropTypes.shape({...Icon.propTypes}),
/**
* Avatar to display for the `Chip`. Both icon and avatar cannot be specified.
*/
avatar: PropTypes.node,
/**
* Icon to display as the close button for the `Chip`. The icon appears only when the onClose prop is specified.
*/
closeIcon: PropTypes.shape({...Icon.propTypes}),
/**
* Whether chip is selected.
*/
selected: PropTypes.bool,
/**
* Whether to style the chip color as selected.
*/
selectedColor: PropTypes.string,
/**
* Whether the chip is disabled. A disabled chip is greyed out and `onPress` is not called on touch.
*/
disabled: PropTypes.bool,
/**
* Accessibility label for the close icon. This is read by the screen reader when the user taps the close icon.
*/
closeIconAccessibilityLabel: PropTypes.string,
/**
* Function to execute on press.
*/
onPress: PropTypes.func,
/**
* Function to execute on long press.
*/
onLongPress: PropTypes.func,
/**
* Function to execute on close button press. The close button appears only when this prop is specified.
*/
onClose: PropTypes.func,
/**
* Style of chip's text
*/
labelStyle: StyleProps,
style: StyleProps,
/**
* @optional
*/
theme: PropTypes.object,
/**
* Pass down testID from chip props to touchable for Detox tests.
*/
testID: PropTypes.string,
/**
* Ellipsize Mode for the children text
*/
ellipsizeMode: PropTypes.object,
labelProps : PropTypes.object,
};