1import React, {ReactNode} from 'react'; 2import { 3 StyleProp, 4 StyleSheet, 5 TextProps, 6 TextStyle, 7 TouchableHighlight, 8 TouchableOpacity, 9 View, 10 ViewStyle, 11} from 'react-native'; 12import rpx from '@/utils/rpx'; 13import useColors from '@/hooks/useColors'; 14import ThemeText from './themeText'; 15import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; 16import { 17 fontSizeConst, 18 fontWeightConst, 19 iconSizeConst, 20} from '@/constants/uiConst'; 21import FastImage from './fastImage'; 22import {ImageStyle} from 'react-native-fast-image'; 23 24interface IListItemProps { 25 // 是否有左右边距 26 withHorizonalPadding?: boolean; 27 // 左边距 28 leftPadding?: number; 29 // 右边距 30 rightPadding?: number; 31 // height: 32 style?: StyleProp<ViewStyle>; 33 // 高度类型 34 heightType?: 'big' | 'small' | 'normal' | 'none'; 35 children?: ReactNode; 36 onPress?: () => void; 37 onLongPress?: () => void; 38} 39 40const defaultPadding = rpx(24); 41const defaultActionWidth = rpx(80); 42 43const Size = { 44 big: rpx(120), 45 normal: rpx(108), 46 small: rpx(96), 47 none: undefined, 48}; 49 50function ListItem(props: IListItemProps) { 51 const { 52 withHorizonalPadding, 53 leftPadding = defaultPadding, 54 rightPadding = defaultPadding, 55 style, 56 heightType = 'normal', 57 children, 58 onPress, 59 onLongPress, 60 } = props; 61 62 const defaultStyle: StyleProp<ViewStyle> = { 63 paddingLeft: withHorizonalPadding ? leftPadding : 0, 64 paddingRight: withHorizonalPadding ? rightPadding : 0, 65 height: Size[heightType], 66 }; 67 68 const colors = useColors(); 69 70 return ( 71 <TouchableHighlight 72 style={styles.container} 73 underlayColor={colors.listActive} 74 onPress={onPress} 75 onLongPress={onLongPress}> 76 <View style={[styles.container, defaultStyle, style]}> 77 {children} 78 </View> 79 </TouchableHighlight> 80 ); 81} 82 83interface IListItemTextProps { 84 children?: number | string; 85 fontSize?: keyof typeof fontSizeConst; 86 fontWeight?: keyof typeof fontWeightConst; 87 width?: number; 88 position?: 'left' | 'right' | 'none'; 89 fixedWidth?: boolean; 90 containerStyle?: StyleProp<ViewStyle>; 91 contentStyle?: StyleProp<TextStyle>; 92 contentProps?: TextProps; 93} 94 95function ListItemText(props: IListItemTextProps) { 96 const { 97 children, 98 fontSize, 99 fontWeight, 100 position = 'left', 101 fixedWidth, 102 width, 103 containerStyle, 104 contentStyle, 105 contentProps = {}, 106 } = props; 107 108 const defaultStyle: StyleProp<ViewStyle> = { 109 marginRight: position === 'left' ? defaultPadding : 0, 110 marginLeft: position === 'right' ? defaultPadding : 0, 111 width: fixedWidth ? width ?? defaultActionWidth : undefined, 112 flexBasis: fixedWidth ? width ?? defaultActionWidth : undefined, 113 }; 114 115 return ( 116 <View style={[styles.actionBase, defaultStyle, containerStyle]}> 117 <ThemeText 118 fontSize={fontSize} 119 style={contentStyle} 120 fontWeight={fontWeight} 121 {...contentProps}> 122 {children} 123 </ThemeText> 124 </View> 125 ); 126} 127 128interface IListItemIconProps { 129 icon: string; 130 iconSize?: number; 131 width?: number; 132 position?: 'left' | 'right' | 'none'; 133 fixedWidth?: boolean; 134 containerStyle?: StyleProp<ViewStyle>; 135 contentStyle?: StyleProp<TextStyle>; 136 onPress?: () => void; 137} 138 139function ListItemIcon(props: IListItemIconProps) { 140 const { 141 icon, 142 iconSize = iconSizeConst.normal, 143 position = 'left', 144 fixedWidth, 145 width, 146 containerStyle, 147 contentStyle, 148 onPress, 149 } = props; 150 151 const defaultStyle: StyleProp<ViewStyle> = { 152 marginRight: position === 'left' ? defaultPadding : 0, 153 marginLeft: position === 'right' ? defaultPadding : 0, 154 width: fixedWidth ? width ?? defaultActionWidth : undefined, 155 flexBasis: fixedWidth ? width ?? defaultActionWidth : undefined, 156 }; 157 158 const innerContent = ( 159 <View style={[styles.actionBase, defaultStyle, containerStyle]}> 160 <Icon name={icon} size={iconSize} style={contentStyle} /> 161 </View> 162 ); 163 164 return onPress ? ( 165 <TouchableOpacity onPress={onPress}>{innerContent}</TouchableOpacity> 166 ) : ( 167 innerContent 168 ); 169} 170 171interface IListItemImageProps { 172 uri?: string; 173 fallbackImg?: number; 174 imageSize?: number; 175 width?: number; 176 position?: 'left' | 'right'; 177 fixedWidth?: boolean; 178 containerStyle?: StyleProp<ViewStyle>; 179 contentStyle?: StyleProp<ImageStyle>; 180} 181 182function ListItemImage(props: IListItemImageProps) { 183 const { 184 uri, 185 fallbackImg, 186 position = 'left', 187 fixedWidth, 188 width, 189 containerStyle, 190 contentStyle, 191 } = props; 192 193 const defaultStyle: StyleProp<ViewStyle> = { 194 marginRight: position === 'left' ? defaultPadding : 0, 195 marginLeft: position === 'right' ? defaultPadding : 0, 196 width: fixedWidth ? width ?? defaultActionWidth : undefined, 197 flexBasis: fixedWidth ? width ?? defaultActionWidth : undefined, 198 }; 199 200 return ( 201 <View style={[styles.actionBase, defaultStyle, containerStyle]}> 202 <FastImage 203 style={[styles.leftImage, contentStyle]} 204 uri={uri} 205 emptySrc={fallbackImg} 206 /> 207 </View> 208 ); 209} 210 211interface IContentProps { 212 title?: ReactNode; 213 description?: ReactNode; 214 containerStyle?: StyleProp<ViewStyle>; 215} 216 217function Content(props: IContentProps) { 218 const {title, description = null, containerStyle} = props; 219 220 let realTitle; 221 let realDescription; 222 223 if (typeof title === 'string' || typeof title === 'number') { 224 realTitle = <ThemeText numberOfLines={1}>{title}</ThemeText>; 225 } else { 226 realTitle = title; 227 } 228 229 if (typeof description === 'string' || typeof description === 'number') { 230 realDescription = ( 231 <ThemeText 232 numberOfLines={1} 233 fontSize="description" 234 fontColor="secondary" 235 style={styles.contentDesc}> 236 {description} 237 </ThemeText> 238 ); 239 } else { 240 realDescription = description; 241 } 242 243 return ( 244 <View style={[styles.itemContentContainer, containerStyle]}> 245 {realTitle} 246 {realDescription} 247 </View> 248 ); 249} 250 251const styles = StyleSheet.create({ 252 /** listitem */ 253 container: { 254 width: '100%', 255 flexDirection: 'row', 256 alignItems: 'center', 257 }, 258 /** left */ 259 actionBase: { 260 height: '100%', 261 flexShrink: 0, 262 flexGrow: 0, 263 flexBasis: 0, 264 flexDirection: 'row', 265 justifyContent: 'center', 266 alignItems: 'center', 267 }, 268 269 leftImage: { 270 width: rpx(80), 271 height: rpx(80), 272 borderRadius: rpx(16), 273 }, 274 itemContentContainer: { 275 flex: 1, 276 height: '100%', 277 justifyContent: 'center', 278 }, 279 contentDesc: { 280 marginTop: rpx(16), 281 }, 282}); 283 284ListItem.Size = Size; 285ListItem.ListItemIcon = ListItemIcon; 286ListItem.ListItemImage = ListItemImage; 287ListItem.ListItemText = ListItemText; 288ListItem.Content = Content; 289 290export default ListItem; 291