components/explorer/FeedCardCollection.tsx
Annotation non disponible
Lance npm run annotate (nécessite ANTHROPIC_API_KEY dans .env.local) pour générer une annotation française par Claude Haiku 4.5.
Concepts détectés — comprends la théorie
UI React Native
15 occurrencesComposants UI React Native (View, Text, etc.). Différents de l'HTML web — on rend du natif.
Voir l'article général
Hooks React
3 occurrencesCe fichier utilise des hooks React. Les hooks sont la façon moderne de gérer l'état et les effets dans React. Voir l'architecture mobile pour le pattern complet.
Voir l'article général
Routing Expo (mobile)
1 occurrenceCe fichier utilise le routing Expo (file-based). Convention : `_layout.tsx` pour les layouts, `[param].tsx` pour les routes dynamiques.
Voir l'article général
1 export
FeedCardCollection
Code source· tsx
import React, { useMemo } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, ImageBackground } from 'react-native';
import { router } from 'expo-router';
import { useTheme } from '@/hooks/useTheme';
import { FONTS, SPACING, RADIUS } from '@/lib/constants';
import type { CollectionSummary } from '@/types/api';
import { haptics } from '@/lib/haptics';
import { ArrowRight } from '@/lib/icons';
// Sprint Explorer Feed Algorithmique — 2026-05-18
// Hero card collection éditoriale : bannière + emoji + nom + CTA Explorer.
type Props = { collection: CollectionSummary };
export function FeedCardCollection({ collection }: Props) {
const { colors } = useTheme();
const styles = useMemo(() => makeStyles(colors), [colors]);
const onPress = () => {
haptics.selection();
router.push((`/explorer/collection/${collection.slug}`) as any);
};
const subtitle =
collection.nbItems > 0
? `${collection.nbItems} item${collection.nbItems > 1 ? 's' : ''}`
: 'Collection éditoriale';
return (
<TouchableOpacity
style={styles.card}
activeOpacity={0.85}
onPress={onPress}
accessibilityRole="button"
accessibilityLabel={`Collection ${collection.nom}, ${subtitle}`}
accessibilityHint="Appuie pour explorer la collection"
>
{collection.imageUrl ? (
<ImageBackground
source={{ uri: collection.imageUrl }}
style={styles.bg}
imageStyle={styles.bgImage}
>
<View style={styles.overlay}>
<Inner collection={collection} subtitle={subtitle} colors={colors} />
</View>
</ImageBackground>
) : (
<View style={[styles.bg, { backgroundColor: colors.bgElevated }]}>
<Inner collection={collection} subtitle={subtitle} colors={colors} />
</View>
)}
</TouchableOpacity>
);
}
function Inner({
collection,
subtitle,
colors,
}: {
collection: CollectionSummary;
subtitle: string;
colors: ReturnType<typeof useTheme>['colors'];
}) {
return (
<View style={{ padding: SPACING.lg, flex: 1, justifyContent: 'space-between' }}>
<View style={{ flexDirection: 'row', alignItems: 'center', gap: SPACING.sm }}>
{collection.emoji ? (
<Text style={{ fontSize: 36 }}>{collection.emoji}</Text>
) : null}
<View style={{ flex: 1 }}>
<Text
style={{
color: '#fff',
fontSize: FONTS.lg,
fontWeight: FONTS.bold,
textShadowColor: 'rgba(0,0,0,0.6)',
textShadowRadius: 4,
}}
numberOfLines={2}
>
{collection.nom}
</Text>
<Text
style={{
color: '#fff',
fontSize: FONTS.xs,
opacity: 0.9,
marginTop: 2,
textShadowColor: 'rgba(0,0,0,0.6)',
textShadowRadius: 4,
}}
>
{subtitle}
</Text>
</View>
</View>
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
{collection.description ? (
<Text
style={{
color: '#fff',
fontSize: FONTS.sm,
opacity: 0.92,
flex: 1,
marginRight: SPACING.sm,
textShadowColor: 'rgba(0,0,0,0.6)',
textShadowRadius: 4,
}}
numberOfLines={2}
>
{collection.description}
</Text>
) : <View style={{ flex: 1 }} />}
<View
style={{
flexDirection: 'row',
alignItems: 'center',
gap: 4,
paddingHorizontal: SPACING.md,
paddingVertical: SPACING.sm,
backgroundColor: colors.primary,
borderRadius: RADIUS.full,
}}
>
<Text style={{ color: '#fff', fontSize: FONTS.sm, fontWeight: FONTS.bold }}>
Explorer
</Text>
<ArrowRight size={14} color="#fff" weight="bold" />
</View>
</View>
</View>
);
}
function makeStyles(colors: ReturnType<typeof useTheme>['colors']) {
return StyleSheet.create({
card: {
width: '100%',
height: 160,
borderRadius: RADIUS.lg,
overflow: 'hidden',
backgroundColor: colors.bgCard,
marginBottom: SPACING.md,
},
bg: { flex: 1 },
bgImage: { borderRadius: RADIUS.lg },
overlay: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.42)',
},
});
}
import React, { useMemo } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, ImageBackground } from 'react-native';
import { router } from 'expo-router';
import { useTheme } from '@/hooks/useTheme';
import { FONTS, SPACING, RADIUS } from '@/lib/constants';
import type { CollectionSummary } from '@/types/api';
import { haptics } from '@/lib/haptics';
import { ArrowRight } from '@/lib/icons';
// Sprint Explorer Feed Algorithmique — 2026-05-18
// Hero card collection éditoriale : bannière + emoji + nom + CTA Explorer.
type Props = { collection: CollectionSummary };
export function FeedCardCollection({ collection }: Props) {
const { colors } = useTheme();
const styles = useMemo(() => makeStyles(colors), [colors]);
const onPress = () => {
haptics.selection();
router.push((`/explorer/collection/${collection.slug}`) as any);
};
const subtitle =
collection.nbItems > 0
? `${collection.nbItems} item${collection.nbItems > 1 ? 's' : ''}`
: 'Collection éditoriale';
return (
<TouchableOpacity
style={styles.card}
activeOpacity={0.85}
onPress={onPress}
accessibilityRole="button"
accessibilityLabel={`Collection ${collection.nom}, ${subtitle}`}
accessibilityHint="Appuie pour explorer la collection"
>
{collection.imageUrl ? (
<ImageBackground
source={{ uri: collection.imageUrl }}
style={styles.bg}
imageStyle={styles.bgImage}
>
<View style={styles.overlay}>
<Inner collection={collection} subtitle={subtitle} colors={colors} />
</View>
</ImageBackground>
) : (
<View style={[styles.bg, { backgroundColor: colors.bgElevated }]}>
<Inner collection={collection} subtitle={subtitle} colors={colors} />
</View>
)}
</TouchableOpacity>
);
}
function Inner({
collection,
subtitle,
colors,
}: {
collection: CollectionSummary;
subtitle: string;
colors: ReturnType<typeof useTheme>['colors'];
}) {
return (
<View style={{ padding: SPACING.lg, flex: 1, justifyContent: 'space-between' }}>
<View style={{ flexDirection: 'row', alignItems: 'center', gap: SPACING.sm }}>
{collection.emoji ? (
<Text style={{ fontSize: 36 }}>{collection.emoji}</Text>
) : null}
<View style={{ flex: 1 }}>
<Text
style={{
color: '#fff',
fontSize: FONTS.lg,
fontWeight: FONTS.bold,
textShadowColor: 'rgba(0,0,0,0.6)',
textShadowRadius: 4,
}}
numberOfLines={2}
>
{collection.nom}
</Text>
<Text
style={{
color: '#fff',
fontSize: FONTS.xs,
opacity: 0.9,
marginTop: 2,
textShadowColor: 'rgba(0,0,0,0.6)',
textShadowRadius: 4,
}}
>
{subtitle}
</Text>
</View>
</View>
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
{collection.description ? (
<Text
style={{
color: '#fff',
fontSize: FONTS.sm,
opacity: 0.92,
flex: 1,
marginRight: SPACING.sm,
textShadowColor: 'rgba(0,0,0,0.6)',
textShadowRadius: 4,
}}
numberOfLines={2}
>
{collection.description}
</Text>
) : <View style={{ flex: 1 }} />}
<View
style={{
flexDirection: 'row',
alignItems: 'center',
gap: 4,
paddingHorizontal: SPACING.md,
paddingVertical: SPACING.sm,
backgroundColor: colors.primary,
borderRadius: RADIUS.full,
}}
>
<Text style={{ color: '#fff', fontSize: FONTS.sm, fontWeight: FONTS.bold }}>
Explorer
</Text>
<ArrowRight size={14} color="#fff" weight="bold" />
</View>
</View>
</View>
);
}
function makeStyles(colors: ReturnType<typeof useTheme>['colors']) {
return StyleSheet.create({
card: {
width: '100%',
height: 160,
borderRadius: RADIUS.lg,
overflow: 'hidden',
backgroundColor: colors.bgCard,
marginBottom: SPACING.md,
},
bg: { flex: 1 },
bgImage: { borderRadius: RADIUS.lg },
overlay: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.42)',
},
});
}