xref: /MusicFree/src/components/base/listItem.tsx (revision 740e39476f71e0e17304d812ac0a4c4cdc183ed1)
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 {
16    fontSizeConst,
17    fontWeightConst,
18    iconSizeConst,
19} from '@/constants/uiConst';
20import FastImage from './fastImage';
21import {ImageStyle} from 'react-native-fast-image';
22import Icon, {IIconName} from '@/components/base/icon.tsx';
23
24interface IListItemProps {
25    // 是否有左右边距
26    withHorizontalPadding?: 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        withHorizontalPadding,
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: withHorizontalPadding ? leftPadding : 0,
65        paddingRight: withHorizontalPadding ? 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: IIconName;
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    color?: string;
139}
140
141function ListItemIcon(props: IListItemIconProps) {
142    const {
143        icon,
144        iconSize = iconSizeConst.normal,
145        position = 'left',
146        fixedWidth,
147        width,
148        containerStyle,
149        contentStyle,
150        onPress,
151        color,
152    } = props;
153
154    const colors = useColors();
155
156    const defaultStyle: StyleProp<ViewStyle> = {
157        marginRight: position === 'left' ? defaultPadding : 0,
158        marginLeft: position === 'right' ? defaultPadding : 0,
159        width: fixedWidth ? width ?? defaultActionWidth : undefined,
160        flexBasis: fixedWidth ? width ?? defaultActionWidth : undefined,
161    };
162
163    const innerContent = (
164        <View style={[styles.actionBase, defaultStyle, containerStyle]}>
165            <Icon
166                name={icon}
167                size={iconSize}
168                style={contentStyle}
169                color={color || colors.text}
170            />
171        </View>
172    );
173
174    return onPress ? (
175        <TouchableOpacity onPress={onPress}>{innerContent}</TouchableOpacity>
176    ) : (
177        innerContent
178    );
179}
180
181interface IListItemImageProps {
182    uri?: string;
183    fallbackImg?: number;
184    imageSize?: number;
185    width?: number;
186    position?: 'left' | 'right';
187    fixedWidth?: boolean;
188    containerStyle?: StyleProp<ViewStyle>;
189    contentStyle?: StyleProp<ImageStyle>;
190    maskIcon?: IIconName | null;
191}
192
193function ListItemImage(props: IListItemImageProps) {
194    const {
195        uri,
196        fallbackImg,
197        position = 'left',
198        fixedWidth,
199        width,
200        containerStyle,
201        contentStyle,
202        maskIcon,
203    } = props;
204
205    const defaultStyle: StyleProp<ViewStyle> = {
206        marginRight: position === 'left' ? defaultPadding : 0,
207        marginLeft: position === 'right' ? defaultPadding : 0,
208        width: fixedWidth ? width ?? defaultActionWidth : undefined,
209        flexBasis: fixedWidth ? width ?? defaultActionWidth : undefined,
210    };
211
212    return (
213        <View style={[styles.actionBase, defaultStyle, containerStyle]}>
214            <FastImage
215                style={[styles.leftImage, contentStyle]}
216                uri={uri}
217                emptySrc={fallbackImg}
218            />
219            {maskIcon ? (
220                <View style={[styles.leftImage, styles.imageMask]}>
221                    <Icon
222                        name={maskIcon}
223                        size={iconSizeConst.normal}
224                        color="red"
225                    />
226                </View>
227            ) : null}
228        </View>
229    );
230}
231
232interface IContentProps {
233    title?: ReactNode;
234    children?: ReactNode;
235    description?: ReactNode;
236    containerStyle?: StyleProp<ViewStyle>;
237}
238
239function Content(props: IContentProps) {
240    const {
241        children,
242        title = children,
243        description = null,
244        containerStyle,
245    } = props;
246
247    let realTitle;
248    let realDescription;
249
250    if (typeof title === 'string' || typeof title === 'number') {
251        realTitle = <ThemeText numberOfLines={1}>{title}</ThemeText>;
252    } else {
253        realTitle = title;
254    }
255
256    if (typeof description === 'string' || typeof description === 'number') {
257        realDescription = (
258            <ThemeText
259                numberOfLines={1}
260                fontSize="description"
261                fontColor="textSecondary"
262                style={styles.contentDesc}>
263                {description}
264            </ThemeText>
265        );
266    } else {
267        realDescription = description;
268    }
269
270    return (
271        <View style={[styles.itemContentContainer, containerStyle]}>
272            {realTitle}
273            {realDescription}
274        </View>
275    );
276}
277
278export function ListItemHeader(props: {children?: ReactNode}) {
279    const {children} = props;
280    return (
281        <ListItem
282            withHorizontalPadding
283            heightType="smallest"
284            style={styles.listItemHeader}>
285            {typeof children === 'string' ? (
286                <ThemeText
287                    fontSize="subTitle"
288                    fontColor="textSecondary"
289                    fontWeight="bold">
290                    {children}
291                </ThemeText>
292            ) : (
293                children
294            )}
295        </ListItem>
296    );
297}
298
299const styles = StyleSheet.create({
300    /** listitem */
301    container: {
302        width: '100%',
303        flexDirection: 'row',
304        alignItems: 'center',
305    },
306    /** left */
307    actionBase: {
308        height: '100%',
309        flexShrink: 0,
310        flexGrow: 0,
311        flexBasis: 0,
312        flexDirection: 'row',
313        justifyContent: 'center',
314        alignItems: 'center',
315    },
316
317    leftImage: {
318        width: rpx(80),
319        height: rpx(80),
320        borderRadius: rpx(16),
321    },
322    imageMask: {
323        position: 'absolute',
324        alignItems: 'center',
325        justifyContent: 'center',
326        backgroundColor: '#00000022',
327    },
328    itemContentContainer: {
329        flex: 1,
330        height: '100%',
331        justifyContent: 'center',
332    },
333    contentDesc: {
334        marginTop: rpx(16),
335    },
336
337    listItemHeader: {
338        marginTop: rpx(20),
339    },
340});
341
342ListItem.Size = Size;
343ListItem.ListItemIcon = ListItemIcon;
344ListItem.ListItemImage = ListItemImage;
345ListItem.ListItemText = ListItemText;
346ListItem.Content = Content;
347
348export default ListItem;
349