Skip to content

Commit 47859b3

Browse files
committed
feat(mobile/mosques): swipeable hero pager for multi-photo mosques
FlatList pagingEnabled replaces the hero-plus-strip layout so the whole gallery lives in one swipeable surface; active page indicator pill overlays at the bottom.
1 parent c2b2d0b commit 47859b3

2 files changed

Lines changed: 70 additions & 38 deletions

File tree

apps/mobile/features/mosques/components/mosque-detail-content.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { MosqueDetailTabs, type MosqueTab } from "./mosque-detail-tabs";
99
import { MosqueEventsTab } from "./mosque-events-tab";
1010
import { MosqueMetaRow } from "./mosque-meta-row";
1111
import { MosqueOverviewTab } from "./mosque-overview-tab";
12-
import { MosquePhotoHero, MosquePhotoStrip } from "./mosque-photo-gallery";
12+
import { MosquePhotoHero } from "./mosque-photo-gallery";
1313
import { MosqueReviewsTab } from "./mosque-reviews-tab";
1414

1515
export function MosqueDetailContent({ data }: { data: MosqueDetail }) {
@@ -51,12 +51,6 @@ export function MosqueDetailContent({ data }: { data: MosqueDetail }) {
5151
</View>
5252
</View>
5353

54-
{mosque.photos.length > 1 ? (
55-
<View className="mt-s-5">
56-
<MosquePhotoStrip photos={mosque.photos} />
57-
</View>
58-
) : null}
59-
6054
<View className="mt-s-5">
6155
<MosqueDetailTabs active={tab} onChange={setTab} />
6256
</View>
Lines changed: 69 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,91 @@
11
import { Image } from "expo-image";
2-
import { FlatList, View } from "react-native";
2+
import { useState } from "react";
3+
import {
4+
Dimensions,
5+
FlatList,
6+
type NativeScrollEvent,
7+
type NativeSyntheticEvent,
8+
View,
9+
} from "react-native";
310
import { MosqueMark } from "@/components/ui/mosque-mark";
11+
import { useThemeColors } from "@/lib/theme";
412

513
type Props = {
614
photos: string[];
715
};
816

917
export function MosquePhotoHero({ photos }: Props) {
10-
const uri = photos[0];
18+
const width = Dimensions.get("window").width;
19+
const [index, setIndex] = useState(0);
20+
const colors = useThemeColors();
1121

12-
if (!uri) {
22+
if (photos.length === 0) {
1323
return (
1424
<View className="aspect-[4/3] items-center justify-center bg-green-tint">
1525
<MosqueMark size="xl" />
1626
</View>
1727
);
1828
}
1929

20-
return (
21-
<View className="aspect-[4/3] bg-line">
22-
<Image
23-
source={{ uri }}
24-
contentFit="cover"
25-
style={{ width: "100%", height: "100%" }}
26-
transition={200}
27-
/>
28-
</View>
29-
);
30-
}
31-
32-
export function MosquePhotoStrip({ photos }: Props) {
33-
const rest = photos.slice(1);
34-
if (rest.length === 0) return null;
35-
36-
return (
37-
<FlatList
38-
data={rest}
39-
keyExtractor={(uri) => uri}
40-
horizontal
41-
showsHorizontalScrollIndicator={false}
42-
contentContainerStyle={{ paddingHorizontal: 24, gap: 8 }}
43-
renderItem={({ item }) => (
30+
if (photos.length === 1) {
31+
return (
32+
<View className="aspect-[4/3] bg-line">
4433
<Image
45-
source={{ uri: item }}
34+
source={{ uri: photos[0] }}
4635
contentFit="cover"
47-
style={{ width: 120, height: 88, borderRadius: 8 }}
36+
style={{ width: "100%", height: "100%" }}
4837
transition={200}
4938
/>
50-
)}
51-
/>
39+
</View>
40+
);
41+
}
42+
43+
const onScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
44+
const next = Math.round(e.nativeEvent.contentOffset.x / width);
45+
if (next !== index) setIndex(next);
46+
};
47+
48+
return (
49+
<View className="aspect-[4/3] bg-line">
50+
<FlatList
51+
data={photos}
52+
keyExtractor={(uri) => uri}
53+
horizontal
54+
pagingEnabled
55+
showsHorizontalScrollIndicator={false}
56+
onScroll={onScroll}
57+
scrollEventThrottle={16}
58+
renderItem={({ item }) => (
59+
<View style={{ width, aspectRatio: 4 / 3 }}>
60+
<Image
61+
source={{ uri: item }}
62+
contentFit="cover"
63+
style={{ width: "100%", height: "100%" }}
64+
transition={200}
65+
/>
66+
</View>
67+
)}
68+
/>
69+
70+
<View
71+
pointerEvents="none"
72+
className="absolute inset-x-0 bottom-s-3 flex-row justify-center gap-s-1"
73+
>
74+
{photos.map((uri, i) => {
75+
const active = i === index;
76+
return (
77+
<View
78+
key={uri}
79+
style={{
80+
width: active ? 18 : 6,
81+
height: 6,
82+
borderRadius: 3,
83+
backgroundColor: active ? colors.white : `${colors.white}80`,
84+
}}
85+
/>
86+
);
87+
})}
88+
</View>
89+
</View>
5290
);
5391
}

0 commit comments

Comments
 (0)