Skip to content

Commit

Permalink
feat: added slider to blog home page
Browse files Browse the repository at this point in the history
  • Loading branch information
burhanyilmaz committed Jan 23, 2024
1 parent 30ec2c0 commit 1389ef8
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 9 deletions.
20 changes: 20 additions & 0 deletions src/components/HomeSlider/SliderDots.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { View } from 'react-native';

const SliderDots = ({ count = 0, activeIndex = 0 }) => (
<View className="flex-row mb-2 self-center items-center">
{Array(count)
.fill('')
.map((_, index) => (
<View
key={`${index}-dot`}
className={
activeIndex === index
? 'border-zinc-600 border-b1 h-2.5 w-2.5 rounded-md mr-2'
: 'bg-zinc-600 h-2 w-2 rounded-md mr-2'
}
/>
))}
</View>
);

export default SliderDots;
49 changes: 49 additions & 0 deletions src/components/HomeSlider/SliderItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { text } from '@theme/text';
import { ImageBackground, Pressable, StyleSheet, Text, View } from 'react-native';

import { SLIDER_ITEM_HEIGHT, SLIDER_ITEM_WIDTH } from '.';

type Props = {
image: string;
title: string;
onPress: () => void;
categoryName: string;
};

const SliderItem = ({ image, categoryName, title, onPress }: Props) => (
<Pressable className="bg-white mb-4 pl-4" style={styles.sliderItemContainer} onPress={onPress}>
<View className="bg-zinc-100 mt-4 overflow-hidden rounded-lg self-center">
<ImageBackground
source={{ uri: image }}
className="rounded-lg justify-end overflow-hidden"
style={styles.sliderImageBg}>
<View className="rounded-lg absolute z-0 bg-black-o-40" style={styles.sliderImageBg} />
<View className="px-4 mb-4">
<View className="bg-zinc-50 rounded-full px-2 self-start mb-3">
<Text
numberOfLines={1}
className={text({
type: 'subtitle1',
class: 'text-zinc-800 my-1 min-w-[50px] text-center',
})}>
{categoryName}
</Text>
</View>
<Text
numberOfLines={2}
className={text({ type: 'title4', class: 'text-zinc-50 min-h-[36px]' })}>
{title}
</Text>
</View>
</ImageBackground>
</View>
</Pressable>
);

const styles = StyleSheet.create({
sliderContent: { paddingRight: 16 },
sliderItemContainer: { width: SLIDER_ITEM_WIDTH, height: SLIDER_ITEM_HEIGHT, overflow: 'hidden' },
sliderImageBg: { width: SLIDER_ITEM_WIDTH - 16, height: SLIDER_ITEM_HEIGHT - 16 },
});

export default SliderItem;
73 changes: 73 additions & 0 deletions src/components/HomeSlider/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import categoryStore from '@store/CategoryStore';
import { PostType } from '@store/PostStore';
import { width } from '@utils/helpers';
import { observer } from 'mobx-react-lite';
import { useState } from 'react';
import {
FlatList,
ListRenderItem,
NativeScrollEvent,
NativeSyntheticEvent,
StyleSheet,
View,
} from 'react-native';

import SliderDots from './SliderDots';
import SliderItem from './SliderItem';

export const SLIDER_ITEM_HEIGHT = width / 2;

export const SLIDER_PADDING_HORIZONTAL = 32;

export const SLIDER_ITEM_WIDTH = width - SLIDER_PADDING_HORIZONTAL;

type Props = {
posts: PostType[];
onPressPost: (post: PostType) => void;
};

const HomeSlider = ({ posts, onPressPost }: Props) => {
const [sliderIndex, setSliderIndex] = useState(0);
const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
const index = Math.round(event.nativeEvent.contentOffset.x / width);

if (index >= 0) {
setSliderIndex(index);
}
};

const RenderItem: ListRenderItem<PostType> = ({ item }) => (
<SliderItem
title={item.title}
image={item.headerImage || ''}
onPress={() => onPressPost(item)}
categoryName={item.category || categoryStore.categoryName(item.categories || [])}
/>
);

return (
<View>
<FlatList
horizontal
pagingEnabled
key="homeSlider"
data={posts || []}
onScroll={onScroll}
removeClippedSubviews
renderItem={RenderItem}
decelerationRate="fast"
snapToInterval={SLIDER_ITEM_WIDTH}
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.sliderContent}
/>
<SliderDots count={posts?.length} activeIndex={sliderIndex} />
</View>
);
};

const styles = StyleSheet.create({
sliderContent: { paddingRight: 16 },
sliderItemContainer: { width: SLIDER_ITEM_WIDTH, height: SLIDER_ITEM_HEIGHT },
});

export default observer(HomeSlider);
34 changes: 27 additions & 7 deletions src/screens/Blog/HomeScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,65 @@
import HomeSlider from '@components/HomeSlider';
import Post from '@containers/Post';
import { BlogStackNavigatorParamList } from '@navigators/BlogNavigator';
import { NavigationProp, useNavigation } from '@react-navigation/native';
import postStore, { PostType } from '@store/PostStore';
import savedStore from '@store/SavedStore';
import searchStore from '@store/SearchStore';
import colors from '@theme/colors';
import { text } from '@theme/text';
import { observer } from 'mobx-react-lite';
import { useEffect } from 'react';
import { ActivityIndicator, FlatList, ListRenderItem, View } from 'react-native';
import { ActivityIndicator, FlatList, ListRenderItem, Text, View } from 'react-native';

const BlogHomeScreen = () => {
const { navigate } = useNavigation<NavigationProp<BlogStackNavigatorParamList>>();
const posts = Array.from(postStore.posts.values());

useEffect(
() => () => {
postStore.clearAll();
savedStore.clearAll();
searchStore.clearAll();
},
[],
);

const RenderItem: ListRenderItem<PostType> = ({ item: post }) => (
<Post post={post} onPressPost={() => navigate('PostDetail', { post })} />
<View className="px-5">
<Post post={post} onPressPost={() => navigate('PostDetail', { post })} />
</View>
);

const RenderListHeader = () => (
<View>
<HomeSlider
posts={postStore.sliderPosts}
onPressPost={post => navigate('PostDetail', { post })}
/>
<Text className={text({ type: 'subtitle', class: 'text-left opacity-50 mx-4 mb-2' })}>
All Posts
</Text>
</View>
);

return (
<View className="bg-white flex-1">
{postStore.loading && posts.length > 0 && (
<View className="bg-white flex-1">
{postStore.loading && postStore.listingPosts.length > 0 && (
<ActivityIndicator
size="large"
color={colors.zinc[600]}
className="absolute z-50 right-0 left-0 bottom-4"
/>
)}
<FlatList
data={posts}
windowSize={6}
className="px-5 pt-4"
key="blogHome"
removeClippedSubviews
initialNumToRender={6}
maxToRenderPerBatch={6}
renderItem={RenderItem}
data={postStore.listingPosts}
onEndReached={postStore.increasePage}
ListHeaderComponent={RenderListHeader}
ListEmptyComponent={<ActivityIndicator size="large" color={colors.zinc[600]} />}
/>
</View>
Expand Down
7 changes: 5 additions & 2 deletions src/store/PostStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,15 @@ const PostStore = t
loading: t.optional(t.boolean, false),
})
.views(self => ({
get fetchPosts() {
return Array.from(self.posts.values());
get listingPosts() {
return Array.from(self.posts.values()).slice(4, self.posts.size);
},
getSelectedPost(id: string) {
return self.posts.get(id);
},
get sliderPosts() {
return Array.from(self.posts.values()).slice(0, 4);
},
}))
.actions(self => ({
setUrl: (url: string) => {
Expand Down
4 changes: 4 additions & 0 deletions src/theme/colors.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
module.exports = {
white: '#ffffff',
black: '#000000',
'black-o': {
40: 'rgba(0,0,0,0.4)',
50: 'rgba(0,0,0,0.5)',
},
zinc: {
50: '#fafafa',
100: '#f4f4f5',
Expand Down

0 comments on commit 1389ef8

Please sign in to comment.