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}) |
| translation | CategoryTranslation | en | no | Translation object *see translation section* |
| disabledCategory | CategoryTypes[] | [] | no | Hide categories by passing their slugs |
| categoryPosition | CategoryPosition | categoryPosition | no | Specify category container position |
## 📊 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)
### [Translated](/example/src/Translated/Translated.tsx)
![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)
### [StaticModal (without knob)](/example/src/StaticModal/StaticModal.tsx)
### [Static Modal (without knob)](/example/src/StaticModal/StaticModal.tsx)
![Preview](/example/assets/static-modal-preview.jpg)
### [Static](/example/src/Static/Static.tsx)
![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
* Skin tone palette selector.
* 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 StaticModal from './StaticModal/StaticModal';
import Static from './Static/Static';
import TopCategory from './TopCategory/TopCategory';
import BottomCategory from './BottomCategory/BottomCategory';
const Stack = createStackNavigator();
export default () => {
@ -25,6 +27,8 @@ export default () => {
/>
<Stack.Screen name="StaticModal" component={StaticModal} />
<Stack.Screen name="Static" component={Static} />
<Stack.Screen name="TopCategory" component={TopCategory} />
<Stack.Screen name="BottomCategory" component={BottomCategory} />
</Stack.Navigator>
</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;
StaticModal: undefined;
Static: undefined;
TopCategory: undefined;
BottomCategory: undefined;
};
type Props = StackScreenProps<RootStackParamList, 'Examples'>;
@ -26,14 +28,25 @@ const Examples = ({ navigation }: Props) => {
onPress={() => navigation.navigate('Translated')}
/>
<Button
title="DisabledCategories"
title="Disabled Categories"
onPress={() => navigation.navigate('DisabledCategories')}
/>
<Button
title="StaticModal"
title="Static Modal (wihtout knob)"
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>
</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 { 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 {
CATEGORIES,
@ -21,6 +22,7 @@ export const Categories = ({ flatListRef, scrollNav }: CategoriesProps) => {
onCategoryChangeFailed,
disabledCategory,
activeCategoryContainerColor,
categoryPosition,
} = React.useContext(KeyboardContext);
const handleScrollToCategory = React.useCallback(
@ -62,11 +64,35 @@ export const Categories = ({ flatListRef, scrollNav }: CategoriesProps) => {
[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 (
<View style={styles.bottomBar}>
<View
style={[styles.navigation, { backgroundColor: categoryContainerColor }]}
>
<View style={[categoryPosition === 'floating' && styles.floating]}>
<View style={getStylesBasedOnPosition()}>
<FlatList
data={CATEGORIES_NAVIGATION.filter(
({ category }) => !disabledCategory.includes(category)
@ -87,7 +113,7 @@ export const Categories = ({ flatListRef, scrollNav }: CategoriesProps) => {
};
const styles = StyleSheet.create({
bottomBar: {
floating: {
position: 'absolute',
bottom: 20,
left: 20,
@ -96,8 +122,25 @@ const styles = StyleSheet.create({
},
navigation: {
padding: 3,
alignItems: 'center',
borderColor: '#00000011',
},
navigationFloating: {
borderRadius: 8,
},
navigationBottom: {
paddingVertical: 6,
borderBottomLeftRadius: 8,
borderBottomRightRadius: 8,
borderTopWidth: 1,
},
navigationTop: {
paddingTop: 12,
paddingBottom: 6,
borderTopLeftRadius: 8,
borderTopRightRadius: 8,
borderBottomWidth: 1,
},
separator: {
width: 1,
height: 28,

View File

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

View File

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

View File

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

View File

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

View File

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