xref: /MusicFree/src/components/base/listItem.tsx (revision e0caf3427c927f4cf2efda9969f9a6f4fb0ef565)
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}
189
190function ListItemImage(props: IListItemImageProps) {
191    const {
192        uri,
193        fallbackImg,
194        position = 'left',
195        fixedWidth,
196        width,
197        containerStyle,
198        contentStyle,
199    } = props;
200
201    const defaultStyle: StyleProp<ViewStyle> = {
202        marginRight: position === 'left' ? defaultPadding : 0,
203        marginLeft: position === 'right' ? defaultPadding : 0,
204        width: fixedWidth ? width ?? defaultActionWidth : undefined,
205        flexBasis: fixedWidth ? width ?? defaultActionWidth : undefined,
206    };
207
208    return (
209        <View style={[styles.actionBase, defaultStyle, containerStyle]}>
210            <FastImage
211                style={[styles.leftImage, contentStyle]}
212                uri={uri}
213                emptySrc={fallbackImg}
214            />
215        </View>
216    );
217}
218
219interface IContentProps {
220    title?: ReactNode;
221    children?: ReactNode;
222    description?: ReactNode;
223    containerStyle?: StyleProp<ViewStyle>;
224}
225
226function Content(props: IContentProps) {
227    const {
228        children,
229        title = children,
230        description = null,
231        containerStyle,
232    } = props;
233
234    let realTitle;
235    let realDescription;
236
237    if (typeof title === 'string' || typeof title === 'number') {
238        realTitle = <ThemeText numberOfLines={1}>{title}</ThemeText>;
239    } else {
240        realTitle = title;
241    }
242
243    if (typeof description === 'string' || typeof description === 'number') {
244        realDescription = (
245            <ThemeText
246                numberOfLines={1}
247                fontSize="description"
248                fontColor="textSecondary"
249                style={styles.contentDesc}>
250                {description}
251            </ThemeText>
252        );
253    } else {
254        realDescription = description;
255    }
256
257    return (
258        <View style={[styles.itemContentContainer, containerStyle]}>
259            {realTitle}
260            {realDescription}
261        </View>
262    );
263}
264
265export function ListItemHeader(props: {children?: ReactNode}) {
266    const {children} = props;
267    return (
268        <ListItem
269            withHorizonalPadding
270            heightType="smallest"
271            style={styles.listItemHeader}>
272            {typeof children === 'string' ? (
273                <ThemeText
274                    fontSize="subTitle"
275                    fontColor="textSecondary"
276                    fontWeight="bold">
277                    {children}
278                </ThemeText>
279            ) : (
280                children
281            )}
282        </ListItem>
283    );
284}
285
286const styles = StyleSheet.create({
287    /** listitem */
288    container: {
289        width: '100%',
290        flexDirection: 'row',
291        alignItems: 'center',
292    },
293    /** left */
294    actionBase: {
295        height: '100%',
296        flexShrink: 0,
297        flexGrow: 0,
298        flexBasis: 0,
299        flexDirection: 'row',
300        justifyContent: 'center',
301        alignItems: 'center',
302    },
303
304    leftImage: {
305        width: rpx(80),
306        height: rpx(80),
307        borderRadius: rpx(16),
308    },
309    itemContentContainer: {
310        flex: 1,
311        height: '100%',
312        justifyContent: 'center',
313    },
314    contentDesc: {
315        marginTop: rpx(16),
316    },
317
318    listItemHeader: {
319        marginTop: rpx(20),
320    },
321});
322
323ListItem.Size = Size;
324ListItem.ListItemIcon = ListItemIcon;
325ListItem.ListItemImage = ListItemImage;
326ListItem.ListItemText = ListItemText;
327ListItem.Content = Content;
328
329export default ListItem;
330