expo-popcore-app/components/blocks/home/TemplateCard.tsx

130 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { memo } from 'react'
import { Pressable, StyleSheet, Text, View, ViewStyle } from 'react-native'
import { Image } from 'expo-image'
import { LinearGradient } from 'expo-linear-gradient'
export interface TemplateCardProps {
id?: string
title: string
previewUrl?: string
webpPreviewUrl?: string
coverImageUrl?: string
aspectRatio?: string
cardWidth: number
onPress: (id: string) => void
testID?: string
}
/**
* 解析 aspectRatio 字符串为数字
* 例如: "128:128" -> 1, "16:9" -> 1.777..., "1.5" -> 1.5
*/
export function parseAspectRatio(aspectRatioString?: string): number | undefined {
if (!aspectRatioString) return undefined
if (aspectRatioString.includes(':')) {
const [w, h] = aspectRatioString.split(':').map(Number)
return w / h
}
return Number(aspectRatioString)
}
/**
* 获取图片源 URI按优先级: webpPreviewUrl > previewUrl > coverImageUrl
*/
export function getImageUri(
webpPreviewUrl?: string,
previewUrl?: string,
coverImageUrl?: string
): string | undefined {
return webpPreviewUrl || previewUrl || coverImageUrl
}
const TemplateCardComponent: React.FC<TemplateCardProps> = ({
id,
title,
previewUrl,
webpPreviewUrl,
coverImageUrl,
aspectRatio: aspectRatioString,
cardWidth,
onPress,
testID,
}) => {
const aspectRatio = parseAspectRatio(aspectRatioString)
const imageUri = getImageUri(webpPreviewUrl, previewUrl, coverImageUrl)
// 如果没有 id则不渲染卡片
if (!id) {
return null
}
return (
<Pressable
style={[styles.card, { width: cardWidth }]}
onPress={() => onPress(id)}
testID={testID}
>
<View
style={[
styles.cardImageContainer,
aspectRatio ? { aspectRatio } : undefined,
].filter(Boolean) as ViewStyle[]}
>
<Image
source={{ uri: imageUri }}
style={[
styles.cardImage,
aspectRatio ? { aspectRatio } : undefined,
].filter(Boolean) as ViewStyle[]}
contentFit="cover"
/>
<LinearGradient
colors={['rgba(17, 17, 17, 0)', 'rgba(17, 17, 17, 0.9)']}
start={{ x: 0, y: 0 }}
end={{ x: 0, y: 1 }}
style={styles.cardImageGradient}
/>
<Text style={styles.cardTitle} numberOfLines={1}>
{title}
</Text>
</View>
</Pressable>
)
}
export const TemplateCard = memo(TemplateCardComponent)
const styles = StyleSheet.create({
card: {
marginBottom: 5,
},
cardImageContainer: {
width: '100%',
borderRadius: 8,
overflow: 'hidden',
position: 'relative',
},
cardImage: {
width: '100%',
borderRadius: 8,
},
cardImageGradient: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
height: '50%',
},
cardTitle: {
position: 'absolute',
bottom: 8,
left: 8,
right: 8,
color: '#F5F5F5',
fontSize: 12,
fontWeight: '500',
},
})