xref: /MusicFree/src/components/base/listItem.tsx (revision 410a159129b1f6a7a1f44fde7bfad9a46f91e161)
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' | 'smallest' | '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    smallest: rpx(72),
48    none: undefined,
49};
50
51function ListItem(props: IListItemProps) {
52    const {
53        withHorizonalPadding,
54        leftPadding = defaultPadding,
55        rightPadding = defaultPadding,
56        style,
57        heightType = 'normal',
58        children,
59        onPress,
60        onLongPress,
61    } = props;
62
63    const defaultStyle: StyleProp<ViewStyle> = {
64        paddingLeft: withHorizonalPadding ? leftPadding : 0,
65        paddingRight: withHorizonalPadding ? rightPadding : 0,
66        height: Size[heightType],
67    };
68
69    const colors = useColors();
70
71    return (
72        <TouchableHighlight
73            style={styles.container}
74            underlayColor={colors.listActive}
75            onPress={onPress}
76            onLongPress={onLongPress}>
77            <View style={[styles.container, defaultStyle, style]}>
78                {children}
79            </View>
80        </TouchableHighlight>
81    );
82}
83
84interface IListItemTextProps {
85    children?: number | string;
86    fontSize?: keyof typeof fontSizeConst;
87    fontWeight?: keyof typeof fontWeightConst;
88    width?: number;
89    position?: 'left' | 'right' | 'none';
90    fixedWidth?: boolean;
91    containerStyle?: StyleProp<ViewStyle>;
92    contentStyle?: StyleProp<TextStyle>;
93    contentProps?: TextProps;
94}
95
96function ListItemText(props: IListItemTextProps) {
97    const {
98        children,
99        fontSize,
100        fontWeight,
101        position = 'left',
102        fixedWidth,
103        width,
104        containerStyle,
105        contentStyle,
106        contentProps = {},
107    } = props;
108
109    const defaultStyle: StyleProp<ViewStyle> = {
110        marginRight: position === 'left' ? defaultPadding : 0,
111        marginLeft: position === 'right' ? defaultPadding : 0,
112        width: fixedWidth ? width ?? defaultActionWidth : undefined,
113        flexBasis: fixedWidth ? width ?? defaultActionWidth : undefined,
114    };
115
116    return (
117        <View style={[styles.actionBase, defaultStyle, containerStyle]}>
118            <ThemeText
119                fontSize={fontSize}
120                style={contentStyle}
121                fontWeight={fontWeight}
122                {...contentProps}>
123                {children}
124            </ThemeText>
125        </View>
126    );
127}
128
129interface IListItemIconProps {
130    icon: string;
131    iconSize?: number;
132    width?: number;
133    position?: 'left' | 'right' | 'none';
134    fixedWidth?: boolean;
135    containerStyle?: StyleProp<ViewStyle>;
136    contentStyle?: StyleProp<TextStyle>;
137    onPress?: () => void;
138}
139
140function ListItemIcon(props: IListItemIconProps) {
141    const {
142        icon,
143        iconSize = iconSizeConst.normal,
144        position = 'left',
145        fixedWidth,
146        width,
147        containerStyle,
148        contentStyle,
149        onPress,
150    } = props;
151
152    const colors = useColors();
153
154    const defaultStyle: StyleProp<ViewStyle> = {
155        marginRight: position === 'left' ? defaultPadding : 0,
156        marginLeft: position === 'right' ? defaultPadding : 0,
157        width: fixedWidth ? width ?? defaultActionWidth : undefined,
158        flexBasis: fixedWidth ? width ?? defaultActionWidth : undefined,
159    };
160
161    const innerContent = (
162        <View style={[styles.actionBase, defaultStyle, containerStyle]}>
163            <Icon
164                name={icon}
165                size={iconSize}
166                style={contentStyle}
167                color={colors.text}
168            />
169        </View>
170    );
171
172    return onPress ? (
173        <TouchableOpacity onPress={onPress}>{innerContent}</TouchableOpacity>
174    ) : (
175        innerContent
176    );
177}
178
179interface IListItemImageProps {
180    uri?: string;
181    fallbackImg?: number;
182    imageSize?: number;
183    width?: number;
184    position?: 'left' | 'right';
185    fixedWidth?: boolean;
186    containerStyle?: StyleProp<ViewStyle>;
187    contentStyle?: StyleProp<ImageStyle>;
188    maskIcon?: string | null;
189}
190
191function ListItemImage(props: IListItemImageProps) {
192    const {
193        uri,
194        fallbackImg,
195        position = 'left',
196        fixedWidth,
197        width,
198        containerStyle,
199        contentStyle,
200        maskIcon,
201    } = props;
202
203    const defaultStyle: StyleProp<ViewStyle> = {
204        marginRight: position === 'left' ? defaultPadding : 0,
205        marginLeft: position === 'right' ? defaultPadding : 0,
206        width: fixedWidth ? width ?? defaultActionWidth : undefined,
207        flexBasis: fixedWidth ? width ?? defaultActionWidth : undefined,
208    };
209
210    return (
211        <View style={[styles.actionBase, defaultStyle, containerStyle]}>
212            <FastImage
213                style={[styles.leftImage, contentStyle]}
214                uri={uri}
215                emptySrc={fallbackImg}
216            />
217            {maskIcon ? (
218                <View style={[styles.leftImage, styles.imageMask]}>
219                    <Icon
220                        name={maskIcon}
221                        size={iconSizeConst.normal}
222                        color="red"
223                    />
224                </View>
225            ) : null}
226        </View>
227    );
228}
229
230interface IContentProps {
231    title?: ReactNode;
232    children?: ReactNode;
233    description?: ReactNode;
234    containerStyle?: StyleProp<ViewStyle>;
235}
236
237function Content(props: IContentProps) {
238    const {
239        children,
240        title = children,
241        description = null,
242        containerStyle,
243    } = props;
244
245    let realTitle;
246    let realDescription;
247
248    if (typeof title === 'string' || typeof title === 'number') {
249        realTitle = <ThemeText numberOfLines={1}>{title}</ThemeText>;
250    } else {
251        realTitle = title;
252    }
253
254    if (typeof description === 'string' || typeof description === 'number') {
255        realDescription = (
256            <ThemeText
257                numberOfLines={1}
258                fontSize="description"
259                fontColor="textSecondary"
260                style={styles.contentDesc}>
261                {description}
262            </ThemeText>
263        );
264    } else {
265        realDescription = description;
266    }
267
268    return (
269        <View style={[styles.itemContentContainer, containerStyle]}>
270            {realTitle}
271            {realDescription}
272        </View>
273    );
274}
275
276export function ListItemHeader(props: {children?: ReactNode}) {
277    const {children} = props;
278    return (
279        <ListItem
280            withHorizonalPadding
281            heightType="smallest"
282            style={styles.listItemHeader}>
283            {typeof children === 'string' ? (
284                <ThemeText
285                    fontSize="subTitle"
286                    fontColor="textSecondary"
287                    fontWeight="bold">
288                    {children}
289                </ThemeText>
290            ) : (
291                children
292            )}
293        </ListItem>
294    );
295}
296
297const styles = StyleSheet.create({
298    /** listitem */
299    container: {
300        width: '100%',
301        flexDirection: 'row',
302        alignItems: 'center',
303    },
304    /** left */
305    actionBase: {
306        height: '100%',
307        flexShrink: 0,
308        flexGrow: 0,
309        flexBasis: 0,
310        flexDirection: 'row',
311        justifyContent: 'center',
312        alignItems: 'center',
313    },
314
315    leftImage: {
316        width: rpx(80),
317        height: rpx(80),
318        borderRadius: rpx(16),
319    },
320    imageMask: {
321        position: 'absolute',
322        alignItems: 'center',
323        justifyContent: 'center',
324        backgroundColor: '#00000022',
325    },
326    itemContentContainer: {
327        flex: 1,
328        height: '100%',
329        justifyContent: 'center',
330    },
331    contentDesc: {
332        marginTop: rpx(16),
333    },
334
335    listItemHeader: {
336        marginTop: rpx(20),
337    },
338});
339
340ListItem.Size = Size;
341ListItem.ListItemIcon = ListItemIcon;
342ListItem.ListItemImage = ListItemImage;
343ListItem.ListItemText = ListItemText;
344ListItem.Content = Content;
345
346export default ListItem;
347