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:
parent
f23ed09c25
commit
c0cbef73c2
|
@ -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 |
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
|
@ -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,
|
||||||
|
|
|
@ -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 },
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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',
|
||||||
|
|
Loading…
Reference in New Issue