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 colors = useColors(); 152 153 const defaultStyle: StyleProp<ViewStyle> = { 154 marginRight: position === 'left' ? defaultPadding : 0, 155 marginLeft: position === 'right' ? defaultPadding : 0, 156 width: fixedWidth ? width ?? defaultActionWidth : undefined, 157 flexBasis: fixedWidth ? width ?? defaultActionWidth : undefined, 158 }; 159 160 const innerContent = ( 161 <View style={[styles.actionBase, defaultStyle, containerStyle]}> 162 <Icon 163 name={icon} 164 size={iconSize} 165 style={contentStyle} 166 color={colors.text} 167 /> 168 </View> 169 ); 170 171 return onPress ? ( 172 <TouchableOpacity onPress={onPress}>{innerContent}</TouchableOpacity> 173 ) : ( 174 innerContent 175 ); 176} 177 178interface IListItemImageProps { 179 uri?: string; 180 fallbackImg?: number; 181 imageSize?: number; 182 width?: number; 183 position?: 'left' | 'right'; 184 fixedWidth?: boolean; 185 containerStyle?: StyleProp<ViewStyle>; 186 contentStyle?: StyleProp<ImageStyle>; 187} 188 189function ListItemImage(props: IListItemImageProps) { 190 const { 191 uri, 192 fallbackImg, 193 position = 'left', 194 fixedWidth, 195 width, 196 containerStyle, 197 contentStyle, 198 } = props; 199 200 const defaultStyle: StyleProp<ViewStyle> = { 201 marginRight: position === 'left' ? defaultPadding : 0, 202 marginLeft: position === 'right' ? defaultPadding : 0, 203 width: fixedWidth ? width ?? defaultActionWidth : undefined, 204 flexBasis: fixedWidth ? width ?? defaultActionWidth : undefined, 205 }; 206 207 return ( 208 <View style={[styles.actionBase, defaultStyle, containerStyle]}> 209 <FastImage 210 style={[styles.leftImage, contentStyle]} 211 uri={uri} 212 emptySrc={fallbackImg} 213 /> 214 </View> 215 ); 216} 217 218interface IContentProps { 219 title?: ReactNode; 220 children?: ReactNode; 221 description?: ReactNode; 222 containerStyle?: StyleProp<ViewStyle>; 223} 224 225function Content(props: IContentProps) { 226 const { 227 children, 228 title = children, 229 description = null, 230 containerStyle, 231 } = props; 232 233 let realTitle; 234 let realDescription; 235 236 if (typeof title === 'string' || typeof title === 'number') { 237 realTitle = <ThemeText numberOfLines={1}>{title}</ThemeText>; 238 } else { 239 realTitle = title; 240 } 241 242 if (typeof description === 'string' || typeof description === 'number') { 243 realDescription = ( 244 <ThemeText 245 numberOfLines={1} 246 fontSize="description" 247 fontColor="secondary" 248 style={styles.contentDesc}> 249 {description} 250 </ThemeText> 251 ); 252 } else { 253 realDescription = description; 254 } 255 256 return ( 257 <View style={[styles.itemContentContainer, containerStyle]}> 258 {realTitle} 259 {realDescription} 260 </View> 261 ); 262} 263 264const styles = StyleSheet.create({ 265 /** listitem */ 266 container: { 267 width: '100%', 268 flexDirection: 'row', 269 alignItems: 'center', 270 }, 271 /** left */ 272 actionBase: { 273 height: '100%', 274 flexShrink: 0, 275 flexGrow: 0, 276 flexBasis: 0, 277 flexDirection: 'row', 278 justifyContent: 'center', 279 alignItems: 'center', 280 }, 281 282 leftImage: { 283 width: rpx(80), 284 height: rpx(80), 285 borderRadius: rpx(16), 286 }, 287 itemContentContainer: { 288 flex: 1, 289 height: '100%', 290 justifyContent: 'center', 291 }, 292 contentDesc: { 293 marginTop: rpx(16), 294 }, 295}); 296 297ListItem.Size = Size; 298ListItem.ListItemIcon = ListItemIcon; 299ListItem.ListItemImage = ListItemImage; 300ListItem.ListItemText = ListItemText; 301ListItem.Content = Content; 302 303export default ListItem; 304