refactor: use better context

This commit is contained in:
Jakub Grzywacz 2021-07-08 10:32:16 +02:00
parent d9635bb135
commit e165a829b7
No known key found for this signature in database
GPG Key ID: 5BBB685871FF63C4
10 changed files with 188 additions and 110 deletions

View File

@ -4,36 +4,37 @@ import {
StyleSheet, StyleSheet,
View, View,
FlatList, FlatList,
ViewStyle,
useWindowDimensions, useWindowDimensions,
Animated, Animated,
NativeSyntheticEvent, NativeSyntheticEvent,
NativeScrollEvent, NativeScrollEvent,
ViewToken,
} from 'react-native'; } from 'react-native';
import { CATEGORIES, CategoryTypes, EmojiType } from './types'; import { CATEGORIES, CategoryTypes } from './types';
import { EmojiCategory } from './components/EmojiCategory'; import { EmojiCategory } from './components/EmojiCategory';
import { KeyboardContext } from './KeyboardContext'; import { KeyboardContext } from './KeyboardContext';
import { Categories } from './components/Categories'; import { Categories } from './components/Categories';
type EmojiKeyboardProps = { export const EmojiKeyboard = () => {
onEmojiSelected: (emoji: EmojiType) => void;
containerStyles?: ViewStyle;
numberOfColumns?: number;
emojiSize?: number;
};
export const EmojiKeyboard = ({
onEmojiSelected,
containerStyles,
numberOfColumns = 7,
emojiSize = 28,
}: EmojiKeyboardProps) => {
const { width } = useWindowDimensions(); const { width } = useWindowDimensions();
const ctx = React.useContext(KeyboardContext);
const flatListRef = React.useRef<FlatList>(null); const flatListRef = React.useRef<FlatList>(null);
const scrollX = React.useRef(new Animated.Value(0)).current; const scrollX = React.useRef(new Animated.Value(0)).current;
const scrollNav = React.useRef(new Animated.Value(0)).current; const scrollNav = React.useRef(new Animated.Value(0)).current;
const onViewableItemsChanged = React.useRef(
({ viewableItems }: { viewableItems: Array<ViewToken> }) => {
if (viewableItems.length > 0 && viewableItems[0].index !== null) {
ctx?.setActiveCategoryIndex(viewableItems[0].index);
}
}
);
const viewabilityConfig = React.useRef({
viewAreaCoveragePercentThreshold: 60,
});
const getItemLayout = ( const getItemLayout = (
_: CategoryTypes[] | null | undefined, _: CategoryTypes[] | null | undefined,
index: number index: number
@ -63,15 +64,9 @@ export const EmojiKeyboard = ({
useNativeDriver: false, useNativeDriver: false,
} }
); );
return ( return (
<KeyboardContext.Provider <View style={[styles.container, ctx?.containerStyles]}>
value={{
onEmojiSelected,
numberOfColumns,
emojiSize,
}}
>
<View style={[styles.container, containerStyles]}>
<FlatList <FlatList
data={CATEGORIES} data={CATEGORIES}
keyExtractor={(item) => item} keyExtractor={(item) => item}
@ -86,10 +81,11 @@ export const EmojiKeyboard = ({
decelerationRate="fast" decelerationRate="fast"
getItemLayout={getItemLayout} getItemLayout={getItemLayout}
onScroll={onScroll} onScroll={onScroll}
onViewableItemsChanged={onViewableItemsChanged.current}
viewabilityConfig={viewabilityConfig.current}
/> />
<Categories flatListRef={flatListRef} scrollNav={scrollNav} /> <Categories flatListRef={flatListRef} scrollNav={scrollNav} />
</View> </View>
</KeyboardContext.Provider>
); );
}; };

View File

@ -8,9 +8,11 @@ import {
TouchableOpacity, TouchableOpacity,
View, View,
} from 'react-native'; } from 'react-native';
import type { EmojiKeyboardProps } from './types';
import { EmojiKeyboard } from './EmojiKeyboard'; import { EmojiKeyboard } from './EmojiKeyboard';
import { Knob } from './components/Knob'; import { Knob } from './components/Knob';
import { KeyboardProvider } from './KeyboardProvider';
import type { KeyboardProps } from './KeyboardContext';
import type { EmojiType } from './types';
type EmojiPickerProps = { type EmojiPickerProps = {
isOpen: boolean; isOpen: boolean;
@ -21,12 +23,22 @@ export const EmojiPicker = ({
onEmojiSelected, onEmojiSelected,
isOpen, isOpen,
onClose, onClose,
}: EmojiPickerProps & EmojiKeyboardProps) => { ...props
}: EmojiPickerProps & KeyboardProps) => {
const { height: screenHeight } = useWindowDimensions(); const { height: screenHeight } = useWindowDimensions();
const offsetY = React.useRef(new Animated.Value(0)).current; const offsetY = React.useRef(new Animated.Value(0)).current;
const height = React.useRef(new Animated.Value(screenHeight * 0.4)).current; const height = React.useRef(new Animated.Value(screenHeight * 0.4)).current;
return ( return (
<KeyboardProvider
onEmojiSelected={(emoji: EmojiType) => {
height.setValue(screenHeight * 0.4);
offsetY.setValue(0);
onEmojiSelected(emoji);
onClose();
}}
{...props}
>
<Modal visible={isOpen} animationType="slide" transparent={true}> <Modal visible={isOpen} animationType="slide" transparent={true}>
<TouchableOpacity <TouchableOpacity
style={styles.modalContainer} style={styles.modalContainer}
@ -39,17 +51,12 @@ export const EmojiPicker = ({
<> <>
<Knob height={height} offsetY={offsetY} onClose={onClose} /> <Knob height={height} offsetY={offsetY} onClose={onClose} />
<Animated.View <Animated.View
style={{ height: Animated.subtract(height, offsetY) }} style={[
{ height: Animated.subtract(height, offsetY) },
styles.container,
]}
> >
<EmojiKeyboard <EmojiKeyboard />
onEmojiSelected={(emoji) => {
height.setValue(screenHeight * 0.4);
offsetY.setValue(0);
onEmojiSelected(emoji);
onClose();
}}
containerStyles={styles.container}
/>
</Animated.View> </Animated.View>
</> </>
</TouchableOpacity> </TouchableOpacity>
@ -57,6 +64,7 @@ export const EmojiPicker = ({
</View> </View>
</TouchableOpacity> </TouchableOpacity>
</Modal> </Modal>
</KeyboardProvider>
); );
}; };

View File

@ -1,10 +1,18 @@
import * as React from 'react'; import * as React from 'react';
import type { ViewStyle } from 'react-native';
import type { EmojiType } from './types'; import type { EmojiType } from './types';
export const defaultKeyboardContext = { export type KeyboardProps = {
onEmojiSelected: (_emoji: EmojiType) => {}, onEmojiSelected: (emoji: EmojiType) => void;
numberOfColumns: 7, numberOfColumns?: number;
emojiSize: 24, emojiSize?: number;
containerStyles?: ViewStyle;
};
export type ContextValues = {
activeCategoryIndex: number;
setActiveCategoryIndex: (index: number) => void;
}; };
export const KeyboardContext = React.createContext(defaultKeyboardContext); export const KeyboardContext = React.createContext<
(KeyboardProps & ContextValues) | null
>(null);

40
src/KeyboardProvider.tsx Normal file
View File

@ -0,0 +1,40 @@
import * as React from 'react';
import {
KeyboardProps,
ContextValues,
KeyboardContext,
} from './KeyboardContext';
import type { EmojiType } from './types';
type ProviderProps = KeyboardProps & {
children: React.ReactNode;
};
export const defaultKeyboardContext: KeyboardProps = {
onEmojiSelected: (_emoji: EmojiType) => {},
numberOfColumns: 7,
emojiSize: 24,
};
export const defaultKeyboardValues: Partial<ContextValues> = {
// activeCategoryIndex: 0,
};
export const KeyboardProvider: React.FC<ProviderProps> = React.memo((props) => {
const [activeCategoryIndex, setActiveCategoryIndex] = React.useState(0);
const value: KeyboardProps & ContextValues = {
...defaultKeyboardContext,
...defaultKeyboardValues,
...props,
activeCategoryIndex,
setActiveCategoryIndex,
};
return (
<KeyboardContext.Provider value={value}>
{props.children}
</KeyboardContext.Provider>
);
});
KeyboardProvider.displayName = 'KeyboardProvider';

View File

@ -1,5 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { View, Animated, StyleSheet, FlatList } from 'react-native'; import { View, Animated, StyleSheet, FlatList } from 'react-native';
import { KeyboardContext } from '../KeyboardContext';
import { CATEGORIES, CATEGORIES_NAVIGATION, CategoryTypes } from '../types'; import { CATEGORIES, CATEGORIES_NAVIGATION, CategoryTypes } from '../types';
import { CategoryItem } from './CategoryItem'; import { CategoryItem } from './CategoryItem';
@ -9,6 +10,7 @@ type CategoriesProps = {
}; };
export const Categories = ({ flatListRef, scrollNav }: CategoriesProps) => { export const Categories = ({ flatListRef, scrollNav }: CategoriesProps) => {
const ctx = React.useContext(KeyboardContext);
const handleScrollToCategory = (category: CategoryTypes) => { const handleScrollToCategory = (category: CategoryTypes) => {
flatListRef?.current?.scrollToIndex({ flatListRef?.current?.scrollToIndex({
index: CATEGORIES.indexOf(category), index: CATEGORIES.indexOf(category),
@ -20,9 +22,10 @@ export const Categories = ({ flatListRef, scrollNav }: CategoriesProps) => {
<FlatList <FlatList
data={CATEGORIES_NAVIGATION} data={CATEGORIES_NAVIGATION}
keyExtractor={(item) => item.category} keyExtractor={(item) => item.category}
renderItem={({ item }) => ( renderItem={({ item, index }) => (
<CategoryItem <CategoryItem
item={item} item={item}
index={index}
handleScrollToCategory={handleScrollToCategory} handleScrollToCategory={handleScrollToCategory}
/> />
)} )}
@ -37,6 +40,7 @@ export const Categories = ({ flatListRef, scrollNav }: CategoriesProps) => {
/> />
); );
}} }}
extraData={ctx?.activeCategoryIndex}
/> />
</View> </View>
</View> </View>

View File

@ -1,22 +1,38 @@
import * as React from 'react'; import * as React from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; import { View, StyleSheet, TouchableOpacity } from 'react-native';
import { KeyboardContext } from '../KeyboardContext';
import type { CategoryTypes } from '../types'; import type { CategoryTypes } from '../types';
import { Icon } from './Icon';
type CategoryItemProps = { type CategoryItemProps = {
item: any; item: any;
index: number;
handleScrollToCategory: (category: CategoryTypes) => void; handleScrollToCategory: (category: CategoryTypes) => void;
}; };
export const CategoryItem = ({ export const CategoryItem = ({
item, item,
index,
handleScrollToCategory, handleScrollToCategory,
}: CategoryItemProps) => ( }: CategoryItemProps) => {
<TouchableOpacity onPress={() => handleScrollToCategory(item.category)}> const ctx = React.useContext(KeyboardContext);
return (
<TouchableOpacity
onPress={() => {
handleScrollToCategory(item.category);
ctx?.setActiveCategoryIndex(index);
}}
>
<View style={styles.container}> <View style={styles.container}>
<Text style={styles.icon}>{item.icon}</Text> <Icon
iconName={item.icon}
isActive={ctx?.activeCategoryIndex === index}
/>
</View> </View>
</TouchableOpacity> </TouchableOpacity>
); );
};
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {

View File

@ -16,7 +16,7 @@ export const EmojiCategory = ({ item }: { item: CategoryTypes }) => {
const { width } = useWindowDimensions(); const { width } = useWindowDimensions();
const ctx = React.useContext(KeyboardContext); const ctx = React.useContext(KeyboardContext);
const numberOfColumns = React.useRef<number>( const numberOfColumns = React.useRef<number>(
Math.floor(width / (ctx.emojiSize + 16)) Math.floor(width / (ctx?.emojiSize ? ctx?.emojiSize : 0 + 16))
); );
const [data, setData] = React.useState<EmojiType[]>([]); const [data, setData] = React.useState<EmojiType[]>([]);
// console.log(width); // console.log(width);
@ -31,8 +31,10 @@ export const EmojiCategory = ({ item }: { item: CategoryTypes }) => {
}, [item]); }, [item]);
const getItemLayout = (_: EmojiType[] | null | undefined, index: number) => ({ const getItemLayout = (_: EmojiType[] | null | undefined, index: number) => ({
length: ctx.emojiSize, length: ctx?.emojiSize ? ctx?.emojiSize : 0,
offset: ctx.emojiSize * Math.ceil(index / ctx.numberOfColumns), offset:
(ctx?.emojiSize ? ctx?.emojiSize : 0) *
Math.ceil(index / (ctx?.numberOfColumns ? ctx?.numberOfColumns : 1)),
index, index,
}); });

View File

@ -7,11 +7,11 @@ export const SingleEmoji = ({ item }: { item: EmojiType }) => {
const ctx = React.useContext(KeyboardContext); const ctx = React.useContext(KeyboardContext);
return ( return (
<TouchableOpacity <TouchableOpacity
onPress={() => ctx.onEmojiSelected(item)} onPress={() => ctx?.onEmojiSelected(item)}
style={styles.container} style={styles.container}
> >
<View> <View>
<Text style={{ fontSize: ctx.emojiSize }}>{item.emoji}</Text> <Text style={{ fontSize: ctx?.emojiSize }}>{item.emoji}</Text>
</View> </View>
</TouchableOpacity> </TouchableOpacity>
); );

View File

@ -1,6 +1,7 @@
import { EmojiKeyboard } from './EmojiKeyboard'; import { EmojiKeyboard } from './EmojiKeyboard';
import { EmojiPicker } from './EmojiPicker'; import { EmojiPicker } from './EmojiPicker';
import { KeyboardProvider } from './KeyboardProvider';
export { EmojiKeyboard }; export { EmojiKeyboard, KeyboardProvider };
export default EmojiPicker; export default EmojiPicker;

View File

@ -1,5 +1,3 @@
import type { ViewStyle } from 'react-native';
export type EmojiType = { export type EmojiType = {
emoji: string; emoji: string;
emoji_version: string; emoji_version: string;
@ -38,20 +36,25 @@ type CategoryNavigationItem = {
}; };
export const CATEGORIES_NAVIGATION: CategoryNavigationItem[] = [ export const CATEGORIES_NAVIGATION: CategoryNavigationItem[] = [
{ icon: '😀', category: 'Smileys & Emotion' }, { icon: 'Smile', category: 'Smileys & Emotion' },
{ icon: '👋', category: 'People & Body' }, { icon: 'Users', category: 'People & Body' },
{ icon: '🐵', category: 'Animals & Nature' }, { icon: 'Trees', category: 'Animals & Nature' },
{ icon: '🍇', category: 'Food & Drink' }, { icon: 'Pizza', category: 'Food & Drink' },
{ icon: '🌍', category: 'Travel & Places' }, { icon: 'Plane', category: 'Travel & Places' },
{ icon: '🎃', category: 'Activities' }, { icon: 'Football', category: 'Activities' },
{ icon: '👓', category: 'Objects' }, { icon: 'Lightbulb', category: 'Objects' },
{ icon: '🏧', category: 'Symbols' }, { icon: 'Ban', category: 'Symbols' },
{ icon: '🏁', category: 'Flags' }, { icon: 'Flag', category: 'Flags' },
]; ];
export type EmojiKeyboardProps = { // export const CATEGORIES_NAVIGATION: CategoryNavigationItem[] = [
onEmojiSelected: (emoji: EmojiType) => void; // { icon: '😀', category: 'Smileys & Emotion' },
containerStyles?: ViewStyle; // { icon: '👋', category: 'People & Body' },
numberOfColumns?: number; // { icon: '🐵', category: 'Animals & Nature' },
emojiSize?: number; // { icon: '🍇', category: 'Food & Drink' },
}; // { icon: '🌍', category: 'Travel & Places' },
// { icon: '🎃', category: 'Activities' },
// { icon: '👓', category: 'Objects' },
// { icon: '🏧', category: 'Symbols' },
// { icon: '🏁', category: 'Flags' },
// ];