Feature / categories position (#12)

* feat: create category position prop

* docs: update props

* fix: fix spacing

* docs: add category position to docs
This commit is contained in:
Jakub Grzywacz 2021-07-28 14:53:35 +02:00 committed by GitHub
parent f23ed09c25
commit c0cbef73c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 195 additions and 16 deletions

View File

@ -60,6 +60,7 @@ export default function App() {
| onCategoryChangeFailed | function | warn(info) | no | Callback on category change failed (info: {index, highestMeasuredFrameIndex, averageItemLength}) | | onCategoryChangeFailed | function | warn(info) | no | Callback on category change failed (info: {index, highestMeasuredFrameIndex, averageItemLength}) |
| translation | CategoryTranslation | en | no | Translation object *see translation section* | | translation | CategoryTranslation | en | no | Translation object *see translation section* |
| disabledCategory | CategoryTypes[] | [] | no | Hide categories by passing their slugs | | disabledCategory | CategoryTypes[] | [] | no | Hide categories by passing their slugs |
| categoryPosition | CategoryPosition | categoryPosition | no | Specify category container position |
## 📊 Comparison ## 📊 Comparison
@ -112,12 +113,16 @@ You can clone the repo and run `yarn example ios` or `yarn example android` to p
![Preview](/example/assets/dark-preview.jpg) ![Preview](/example/assets/dark-preview.jpg)
### [Translated](/example/src/Translated/Translated.tsx) ### [Translated](/example/src/Translated/Translated.tsx)
![Preview](/example/assets/translated-preview.jpg) ![Preview](/example/assets/translated-preview.jpg)
### [DisabledCategories](/example/src/DisabledCategories/DisabledCategories.tsx) ### [Disabled Categories](/example/src/DisabledCategories/DisabledCategories.tsx)
![Preview](/example/assets/categories-preview.jpg) ![Preview](/example/assets/categories-preview.jpg)
### [StaticModal (without knob)](/example/src/StaticModal/StaticModal.tsx) ### [Static Modal (without knob)](/example/src/StaticModal/StaticModal.tsx)
![Preview](/example/assets/static-modal-preview.jpg) ![Preview](/example/assets/static-modal-preview.jpg)
### [Static](/example/src/Static/Static.tsx) ### [Static](/example/src/Static/Static.tsx)
![Preview](/example/assets/static-preview.jpg) ![Preview](/example/assets/static-preview.jpg)
### [Categories Top](/example/src/TopCategory/TopCategory.tsx)
![Preview](/example/assets/categories-top-preview.jpg)
### [Categories Bottom](/example/src/BottomCategory/BottomCategory.tsx)
![Preview](/example/assets/categories-bottom-preview.jpg)
## 📈 Future plans ## 📈 Future plans
* Skin tone palette selector. * Skin tone palette selector.
* Search bar. * Search bar.

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -9,6 +9,8 @@ import Translated from './Translated/Translated';
import DisabledCategories from './DisabledCategories/DisabledCategories'; import DisabledCategories from './DisabledCategories/DisabledCategories';
import StaticModal from './StaticModal/StaticModal'; import StaticModal from './StaticModal/StaticModal';
import Static from './Static/Static'; import Static from './Static/Static';
import TopCategory from './TopCategory/TopCategory';
import BottomCategory from './BottomCategory/BottomCategory';
const Stack = createStackNavigator(); const Stack = createStackNavigator();
export default () => { export default () => {
@ -25,6 +27,8 @@ export default () => {
/> />
<Stack.Screen name="StaticModal" component={StaticModal} /> <Stack.Screen name="StaticModal" component={StaticModal} />
<Stack.Screen name="Static" component={Static} /> <Stack.Screen name="Static" component={Static} />
<Stack.Screen name="TopCategory" component={TopCategory} />
<Stack.Screen name="BottomCategory" component={BottomCategory} />
</Stack.Navigator> </Stack.Navigator>
</NavigationContainer> </NavigationContainer>
); );

View File

@ -0,0 +1,43 @@
import * as React from 'react';
import { StyleSheet, Text, TouchableOpacity } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import EmojiPicker from 'rn-emoji-keyboard';
import type { EmojiType } from 'src/types';
const BottomCategory = () => {
const [result, setResult] = React.useState<string>();
const [isModalOpen, setIsModalOpen] = React.useState<boolean>(false);
const handlePick = (emoji: EmojiType) => {
setResult(emoji.emoji);
setIsModalOpen((prev) => !prev);
};
return (
<SafeAreaView style={styles.container}>
<Text style={styles.text}>Result: {result}</Text>
<TouchableOpacity onPress={() => setIsModalOpen(true)}>
<Text style={styles.text}>Open</Text>
</TouchableOpacity>
<EmojiPicker
onEmojiSelected={handlePick}
open={isModalOpen}
onClose={() => setIsModalOpen(false)}
categoryPosition="bottom"
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
text: {
textAlign: 'center',
margin: 64,
fontSize: 18,
},
});
export default BottomCategory;

View File

@ -11,6 +11,8 @@ type RootStackParamList = {
DisabledCategories: undefined; DisabledCategories: undefined;
StaticModal: undefined; StaticModal: undefined;
Static: undefined; Static: undefined;
TopCategory: undefined;
BottomCategory: undefined;
}; };
type Props = StackScreenProps<RootStackParamList, 'Examples'>; type Props = StackScreenProps<RootStackParamList, 'Examples'>;
@ -26,14 +28,25 @@ const Examples = ({ navigation }: Props) => {
onPress={() => navigation.navigate('Translated')} onPress={() => navigation.navigate('Translated')}
/> />
<Button <Button
title="DisabledCategories" title="Disabled Categories"
onPress={() => navigation.navigate('DisabledCategories')} onPress={() => navigation.navigate('DisabledCategories')}
/> />
<Button <Button
title="StaticModal" title="Static Modal (wihtout knob)"
onPress={() => navigation.navigate('StaticModal')} onPress={() => navigation.navigate('StaticModal')}
/> />
<Button title="Static" onPress={() => navigation.navigate('Static')} /> <Button
title="Static Component"
onPress={() => navigation.navigate('Static')}
/>
<Button
title="Category Top"
onPress={() => navigation.navigate('TopCategory')}
/>
<Button
title="Category Bottom"
onPress={() => navigation.navigate('BottomCategory')}
/>
</View> </View>
</SafeAreaView> </SafeAreaView>
); );

View File

@ -0,0 +1,43 @@
import * as React from 'react';
import { StyleSheet, Text, TouchableOpacity } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import EmojiPicker from 'rn-emoji-keyboard';
import type { EmojiType } from 'src/types';
const TopCategory = () => {
const [result, setResult] = React.useState<string>();
const [isModalOpen, setIsModalOpen] = React.useState<boolean>(false);
const handlePick = (emoji: EmojiType) => {
setResult(emoji.emoji);
setIsModalOpen((prev) => !prev);
};
return (
<SafeAreaView style={styles.container}>
<Text style={styles.text}>Result: {result}</Text>
<TouchableOpacity onPress={() => setIsModalOpen(true)}>
<Text style={styles.text}>Open</Text>
</TouchableOpacity>
<EmojiPicker
onEmojiSelected={handlePick}
open={isModalOpen}
onClose={() => setIsModalOpen(false)}
categoryPosition="top"
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
text: {
textAlign: 'center',
margin: 64,
fontSize: 18,
},
});
export default TopCategory;

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, ViewStyle } from 'react-native';
import { defaultKeyboardContext } from '../contexts/KeyboardProvider';
import { KeyboardContext } from '../contexts/KeyboardContext'; import { KeyboardContext } from '../contexts/KeyboardContext';
import { import {
CATEGORIES, CATEGORIES,
@ -21,6 +22,7 @@ export const Categories = ({ flatListRef, scrollNav }: CategoriesProps) => {
onCategoryChangeFailed, onCategoryChangeFailed,
disabledCategory, disabledCategory,
activeCategoryContainerColor, activeCategoryContainerColor,
categoryPosition,
} = React.useContext(KeyboardContext); } = React.useContext(KeyboardContext);
const handleScrollToCategory = React.useCallback( const handleScrollToCategory = React.useCallback(
@ -62,11 +64,35 @@ export const Categories = ({ flatListRef, scrollNav }: CategoriesProps) => {
[activeCategoryContainerColor, scrollNav] [activeCategoryContainerColor, scrollNav]
); );
const getStylesBasedOnPosition = () => {
const style: ViewStyle[] = [styles.navigation];
switch (categoryPosition) {
case 'floating':
style.push(styles.navigationFloating);
break;
case 'top':
style.push(styles.navigationTop);
break;
case 'bottom':
style.push(styles.navigationBottom);
break;
default:
break;
}
if (
categoryContainerColor !==
defaultKeyboardContext.categoryContainerColor ||
categoryPosition === 'floating'
)
style.push({
backgroundColor: categoryContainerColor,
});
return style;
};
return ( return (
<View style={styles.bottomBar}> <View style={[categoryPosition === 'floating' && styles.floating]}>
<View <View style={getStylesBasedOnPosition()}>
style={[styles.navigation, { backgroundColor: categoryContainerColor }]}
>
<FlatList <FlatList
data={CATEGORIES_NAVIGATION.filter( data={CATEGORIES_NAVIGATION.filter(
({ category }) => !disabledCategory.includes(category) ({ category }) => !disabledCategory.includes(category)
@ -87,7 +113,7 @@ export const Categories = ({ flatListRef, scrollNav }: CategoriesProps) => {
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
bottomBar: { floating: {
position: 'absolute', position: 'absolute',
bottom: 20, bottom: 20,
left: 20, left: 20,
@ -96,8 +122,25 @@ const styles = StyleSheet.create({
}, },
navigation: { navigation: {
padding: 3, padding: 3,
alignItems: 'center',
borderColor: '#00000011',
},
navigationFloating: {
borderRadius: 8, borderRadius: 8,
}, },
navigationBottom: {
paddingVertical: 6,
borderBottomLeftRadius: 8,
borderBottomRightRadius: 8,
borderTopWidth: 1,
},
navigationTop: {
paddingTop: 12,
paddingBottom: 6,
borderTopLeftRadius: 8,
borderTopRightRadius: 8,
borderBottomWidth: 1,
},
separator: { separator: {
width: 1, width: 1,
height: 28, height: 28,

View File

@ -26,6 +26,7 @@ export const EmojiCategory = ({
hideHeader, hideHeader,
headerStyles, headerStyles,
translation, translation,
categoryPosition,
} = React.useContext(KeyboardContext); } = React.useContext(KeyboardContext);
const { setKeyboardState } = useKeyboardStore(); const { setKeyboardState } = useKeyboardStore();
@ -89,7 +90,15 @@ export const EmojiCategory = ({
renderItem={renderItem} renderItem={renderItem}
removeClippedSubviews={true} removeClippedSubviews={true}
getItemLayout={getItemLayout} getItemLayout={getItemLayout}
ListFooterComponent={() => <View style={styles.footer} />} ListFooterComponent={() => (
<View
style={
categoryPosition === 'floating'
? styles.footerFloating
: styles.footer
}
/>
)}
windowSize={20} windowSize={20}
/> />
</View> </View>
@ -104,8 +113,10 @@ const styles = StyleSheet.create({
}, },
sectionTitle: { sectionTitle: {
opacity: 0.6, opacity: 0.6,
marginTop: 16,
marginBottom: 6, marginBottom: 6,
marginLeft: 12, marginLeft: 12,
}, },
footer: { height: 70 }, footer: { height: 8 },
footerFloating: { height: 70 },
}); });

View File

@ -20,6 +20,7 @@ export const EmojiStaticKeyboard = () => {
containerStyles, containerStyles,
onCategoryChangeFailed, onCategoryChangeFailed,
disabledCategory, disabledCategory,
categoryPosition,
} = React.useContext(KeyboardContext); } = React.useContext(KeyboardContext);
const flatListRef = React.useRef<FlatList>(null); const flatListRef = React.useRef<FlatList>(null);
@ -47,7 +48,14 @@ export const EmojiStaticKeyboard = () => {
}, [activeCategoryIndex, scrollNav]); }, [activeCategoryIndex, scrollNav]);
return ( return (
<View style={[styles.container, styles.containerShadow, containerStyles]}> <View
style={[
styles.container,
styles.containerShadow,
categoryPosition === 'top' && styles.containerReverse,
containerStyles,
]}
>
<Animated.FlatList <Animated.FlatList
data={emojisByGroup.filter((category) => { data={emojisByGroup.filter((category) => {
const title = category.title as CategoryTypes; const title = category.title as CategoryTypes;
@ -76,10 +84,10 @@ export const EmojiStaticKeyboard = () => {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
paddingTop: 16,
borderRadius: 16, borderRadius: 16,
backgroundColor: '#fff', backgroundColor: '#fff',
}, },
containerReverse: { flexDirection: 'column-reverse' },
containerShadow: { containerShadow: {
shadowColor: 'black', shadowColor: 'black',
shadowOpacity: 0.15, shadowOpacity: 0.15,

View File

@ -4,7 +4,12 @@ import {
defaultKeyboardContext, defaultKeyboardContext,
defaultKeyboardValues, defaultKeyboardValues,
} from './KeyboardProvider'; } from './KeyboardProvider';
import type { CategoryTranslation, EmojiType, CategoryTypes } from '../types'; import type {
CategoryTranslation,
EmojiType,
CategoryTypes,
CategoryPosition,
} from '../types';
export type OnEmojiSelected = (emoji: EmojiType) => void; export type OnEmojiSelected = (emoji: EmojiType) => void;
@ -32,6 +37,7 @@ export type KeyboardProps = {
}) => void; }) => void;
translation?: CategoryTranslation; translation?: CategoryTranslation;
disabledCategory?: CategoryTypes[]; disabledCategory?: CategoryTypes[];
categoryPosition?: CategoryPosition;
}; };
export type ContextValues = { export type ContextValues = {
activeCategoryIndex: number; activeCategoryIndex: number;

View File

@ -36,6 +36,7 @@ export const defaultKeyboardContext: Required<KeyboardProps> = {
}, },
translation: en, translation: en,
disabledCategory: [], disabledCategory: [],
categoryPosition: 'floating',
}; };
export const defaultKeyboardValues: ContextValues = { export const defaultKeyboardValues: ContextValues = {

View File

@ -21,6 +21,8 @@ export type CategoryTypes =
| 'symbols' | 'symbols'
| 'flags'; | 'flags';
export type CategoryPosition = 'floating' | 'top' | 'bottom';
export const CATEGORIES: CategoryTypes[] = [ export const CATEGORIES: CategoryTypes[] = [
'smileys_emotion', 'smileys_emotion',
'people_body', 'people_body',