xref: /MusicFree/src/components/base/listItem.tsx (revision 277c528005b29b919b3eda695ee03717976a5a83)
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="textSecondary"
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