Merge remote-tracking branch 'origin/master' into feature/searchbar

This commit is contained in:
Jakub Grzywacz 2021-07-30 13:20:38 +02:00
commit 96746d3e6d
No known key found for this signature in database
GPG Key ID: 5BBB685871FF63C4
16 changed files with 104 additions and 27 deletions

View File

@ -45,6 +45,9 @@ export default function App() {
| open | boolean | false | yes | Opens modal picker | | open | boolean | false | yes | Opens modal picker |
| onClose | function | undefined | yes | Request close modal *runs when onEmojiSelected or backdrop pressed* | | onClose | function | undefined | yes | Request close modal *runs when onEmojiSelected or backdrop pressed* |
| emojiSize | number | 28 | no | Custom emoji size | | emojiSize | number | 28 | no | Custom emoji size |
| enableRecentlyUsed | boolean | false | no | Enable recently used emojis in categories |
| categoryPosition | 'floating' \| 'top' \| 'bottom' | 'floating' | no | Specify category container position || enableSearchBar | boolean | false | no | Enable search bar |
| closeSearchColor | string | "#00000055" | no | Change button (cross) color for close/cancel search |
| headerStyles | TextStyle | {} | no | Override category name styles | | headerStyles | TextStyle | {} | no | Override category name styles |
| knobStyles | ViewStyle | {} | no | Override knob styles | | knobStyles | ViewStyle | {} | no | Override knob styles |
| containerStyles | ViewStyle | {} | no | Override container styles | | containerStyles | ViewStyle | {} | no | Override container styles |
@ -60,9 +63,6 @@ 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 |
| enableSearchBar | boolean | false | no | Enable search bar |
| closeSearchColor | string | "#00000055" | no | Change button (cross) color for close/cancel search |
## 📊 Comparison ## 📊 Comparison
@ -121,6 +121,8 @@ You can clone the repo and run `yarn example ios` or `yarn example android` to p
![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)
### [Recently used](/example/src/EnableRecently/EnableRecently.tsx)
![Preview](/example/assets/enable-recently-used-preview.jpg)
### [Categories Top](/example/src/TopCategory/TopCategory.tsx) ### [Categories Top](/example/src/TopCategory/TopCategory.tsx)
![Preview](/example/assets/categories-top-preview.jpg) ![Preview](/example/assets/categories-top-preview.jpg)
### [Categories Bottom](/example/src/BottomCategory/BottomCategory.tsx) ### [Categories Bottom](/example/src/BottomCategory/BottomCategory.tsx)

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -9,6 +9,7 @@ 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 EnableRecently from './EnableRecently/EnableRecently';
import TopCategory from './TopCategory/TopCategory'; import TopCategory from './TopCategory/TopCategory';
import BottomCategory from './BottomCategory/BottomCategory'; import BottomCategory from './BottomCategory/BottomCategory';
import SearchBar from './SearchBar/SearchBar'; import SearchBar from './SearchBar/SearchBar';
@ -20,7 +21,11 @@ export default () => {
<Stack.Navigator> <Stack.Navigator>
<Stack.Screen name="Examples" component={Examples} /> <Stack.Screen name="Examples" component={Examples} />
<Stack.Screen name="Basic" component={Basic} /> <Stack.Screen name="Basic" component={Basic} />
<Stack.Screen name="EnableRecently" component={EnableRecently} />
<Stack.Screen name="SearchBar" component={SearchBar} />
<Stack.Screen name="Dark" component={Dark} /> <Stack.Screen name="Dark" component={Dark} />
<Stack.Screen name="TopCategory" component={TopCategory} />
<Stack.Screen name="BottomCategory" component={BottomCategory} />
<Stack.Screen name="Translated" component={Translated} /> <Stack.Screen name="Translated" component={Translated} />
<Stack.Screen <Stack.Screen
name="DisabledCategories" name="DisabledCategories"
@ -28,9 +33,6 @@ 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.Screen name="SearchBar" component={SearchBar} />
</Stack.Navigator> </Stack.Navigator>
</NavigationContainer> </NavigationContainer>
); );

View File

@ -0,0 +1,45 @@
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 EnableRecently = () => {
const [result, setResult] = React.useState<string>();
const [isModalOpen, setIsModalOpen] = React.useState<boolean>(false);
const handlePick = (emoji: EmojiType) => {
console.log(emoji);
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)}
enableRecentlyUsed
enableSearchBar
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
text: {
textAlign: 'center',
margin: 64,
fontSize: 18,
},
});
export default EnableRecently;

View File

@ -11,6 +11,7 @@ type RootStackParamList = {
DisabledCategories: undefined; DisabledCategories: undefined;
StaticModal: undefined; StaticModal: undefined;
Static: undefined; Static: undefined;
EnableRecently: undefined;
TopCategory: undefined; TopCategory: undefined;
BottomCategory: undefined; BottomCategory: undefined;
SearchBar: undefined; SearchBar: undefined;
@ -40,6 +41,10 @@ const Examples = ({ navigation }: Props) => {
title="Static Component" title="Static Component"
onPress={() => navigation.navigate('Static')} onPress={() => navigation.navigate('Static')}
/> />
<Button
title="Enable recently used"
onPress={() => navigation.navigate('EnableRecently')}
/>
<Button <Button
title="Category Top" title="Category Top"
onPress={() => navigation.navigate('TopCategory')} onPress={() => navigation.navigate('TopCategory')}

View File

@ -1,5 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { View, Animated, StyleSheet, FlatList, ViewStyle } from 'react-native'; import { View, Animated, StyleSheet, FlatList, ViewStyle } from 'react-native';
import { useKeyboardStore } from '../store/useKeyboardStore';
import { defaultKeyboardContext } from '../contexts/KeyboardProvider'; import { defaultKeyboardContext } from '../contexts/KeyboardProvider';
import { KeyboardContext } from '../contexts/KeyboardContext'; import { KeyboardContext } from '../contexts/KeyboardContext';
import { import {
@ -22,10 +23,11 @@ export const Categories = ({ flatListRef, scrollNav }: CategoriesProps) => {
onCategoryChangeFailed, onCategoryChangeFailed,
disabledCategory, disabledCategory,
activeCategoryContainerColor, activeCategoryContainerColor,
enableRecentlyUsed,
categoryPosition, categoryPosition,
searchPhrase, searchPhrase,
} = React.useContext(KeyboardContext); } = React.useContext(KeyboardContext);
const { keyboardState } = useKeyboardStore();
const handleScrollToCategory = React.useCallback( const handleScrollToCategory = React.useCallback(
(category: CategoryTypes) => { (category: CategoryTypes) => {
flatListRef?.current?.scrollToIndex({ flatListRef?.current?.scrollToIndex({
@ -64,6 +66,9 @@ export const Categories = ({ flatListRef, scrollNav }: CategoriesProps) => {
), ),
[activeCategoryContainerColor, scrollNav] [activeCategoryContainerColor, scrollNav]
); );
const isRecentlyUsedHidden = (category: CategoryTypes) =>
category === 'recently_used' &&
(keyboardState.recentlyUsed.length === 0 || !enableRecentlyUsed);
const getStylesBasedOnPosition = () => { const getStylesBasedOnPosition = () => {
const style: ViewStyle[] = [styles.navigation]; const style: ViewStyle[] = [styles.navigation];
@ -97,6 +102,7 @@ export const Categories = ({ flatListRef, scrollNav }: CategoriesProps) => {
<FlatList <FlatList
data={CATEGORIES_NAVIGATION.filter(({ category }) => { data={CATEGORIES_NAVIGATION.filter(({ category }) => {
if (searchPhrase === '' && category === 'search') return false; if (searchPhrase === '' && category === 'search') return false;
if (isRecentlyUsedHidden(category)) return false;
return !disabledCategory.includes(category); return !disabledCategory.includes(category);
})} })}
keyExtractor={(item) => item.category} keyExtractor={(item) => item.category}

View File

@ -60,7 +60,7 @@ export const EmojiCategory = ({
if (emoji.name === 'blank emoji') return; if (emoji.name === 'blank emoji') return;
const parsedEmoji = parseEmoji(emoji); const parsedEmoji = parseEmoji(emoji);
onEmojiSelected(parsedEmoji); onEmojiSelected(parsedEmoji);
setKeyboardState({ type: 'RECENT_EMOJI_ADD', payload: parsedEmoji }); setKeyboardState({ type: 'RECENT_EMOJI_ADD', payload: emoji });
}, },
[onEmojiSelected, setKeyboardState] [onEmojiSelected, setKeyboardState]
); );

View File

@ -13,6 +13,7 @@ import { KeyboardContext } from '../contexts/KeyboardContext';
import { Categories } from './Categories'; import { Categories } from './Categories';
import emojisByGroup from '../assets/emojis.json'; import emojisByGroup from '../assets/emojis.json';
import { SearchBar } from './SearchBar'; import { SearchBar } from './SearchBar';
import { useKeyboardStore } from '../store/useKeyboardStore';
export const EmojiStaticKeyboard = () => { export const EmojiStaticKeyboard = () => {
const { width } = useWindowDimensions(); const { width } = useWindowDimensions();
@ -26,9 +27,8 @@ export const EmojiStaticKeyboard = () => {
searchPhrase, searchPhrase,
setActiveCategoryIndex, setActiveCategoryIndex,
} = React.useContext(KeyboardContext); } = React.useContext(KeyboardContext);
const { keyboardState } = useKeyboardStore();
const flatListRef = React.useRef<FlatList>(null); const flatListRef = React.useRef<FlatList>(null);
const scrollNav = React.useRef(new Animated.Value(0)).current; const scrollNav = React.useRef(new Animated.Value(0)).current;
const getItemLayout = ( const getItemLayout = (
@ -51,12 +51,18 @@ export const EmojiStaticKeyboard = () => {
}).start(); }).start();
}, [activeCategoryIndex, scrollNav]); }, [activeCategoryIndex, scrollNav]);
const getData = React.useCallback(() => { const renderList = React.useMemo(() => {
const enabledCategories = emojisByGroup.filter((category) => { const data = emojisByGroup.filter((category) => {
const title = category.title as CategoryTypes; const title = category.title as CategoryTypes;
return !disabledCategory.includes(title); return !disabledCategory.includes(title);
}); });
enabledCategories.push({ if (keyboardState.recentlyUsed.length) {
data.push({
title: 'recently_used',
data: keyboardState.recentlyUsed,
});
}
data.push({
title: 'search', title: 'search',
data: emojisByGroup data: emojisByGroup
.map((group) => group.data) .map((group) => group.data)
@ -66,15 +72,16 @@ export const EmojiStaticKeyboard = () => {
return emoji.name.toLowerCase().includes(searchPhrase.toLowerCase()); return emoji.name.toLowerCase().includes(searchPhrase.toLowerCase());
}), }),
}); });
return enabledCategories; return data;
}, [disabledCategory, searchPhrase]); }, [disabledCategory, keyboardState.recentlyUsed, searchPhrase]);
React.useEffect(() => { React.useEffect(() => {
if (searchPhrase !== '') { if (searchPhrase !== '') {
flatListRef.current?.scrollToEnd(); flatListRef.current?.scrollToEnd();
setActiveCategoryIndex(getData().length - 1); setActiveCategoryIndex(renderList.length - 1);
} }
}, [getData, searchPhrase, setActiveCategoryIndex]); }, [renderList, searchPhrase, setActiveCategoryIndex]);
return ( return (
<View <View
style={[ style={[
@ -86,8 +93,8 @@ export const EmojiStaticKeyboard = () => {
> >
{enableSearchBar && <SearchBar flatListRef={flatListRef} />} {enableSearchBar && <SearchBar flatListRef={flatListRef} />}
<Animated.FlatList <Animated.FlatList
data={getData()} extraData={[keyboardState.recentlyUsed.length, searchPhrase]}
extraData={searchPhrase} data={renderList}
keyExtractor={(item: EmojisByCategory) => item.title} keyExtractor={(item: EmojisByCategory) => item.title}
renderItem={renderItem} renderItem={renderItem}
removeClippedSubviews={true} removeClippedSubviews={true}

View File

@ -10,6 +10,7 @@ import Ban from '../assets/Ban';
import Users from '../assets/Users'; import Users from '../assets/Users';
import Search from '../assets/Search'; import Search from '../assets/Search';
import Close from '../assets/Close'; import Close from '../assets/Close';
import Clock from '../assets/Clock';
export const Icon = ({ export const Icon = ({
iconName, iconName,
@ -46,6 +47,8 @@ export const Icon = ({
return <Search fill={color()} />; return <Search fill={color()} />;
case 'Close': case 'Close':
return <Close fill={color()} />; return <Close fill={color()} />;
case 'Clock':
return <Clock fill={color()} />;
default: default:
return null; return null;
} }

View File

@ -13,7 +13,7 @@ export class SingleEmoji extends React.Component<{
render() { render() {
const { item, emojiSize, onPress } = this.props; const { item, emojiSize, onPress } = this.props;
return ( return (
<TouchableOpacity onPress={() => onPress()} style={styles.container}> <TouchableOpacity onPress={onPress} style={styles.container}>
<View style={styles.iconContainer}> <View style={styles.iconContainer}>
<Text style={{ fontSize: emojiSize }}>{item.emoji}</Text> <Text style={{ fontSize: emojiSize }}>{item.emoji}</Text>
</View> </View>

View File

@ -37,6 +37,7 @@ export type KeyboardProps = {
}) => void; }) => void;
translation?: CategoryTranslation; translation?: CategoryTranslation;
disabledCategory?: CategoryTypes[]; disabledCategory?: CategoryTypes[];
enableRecentlyUsed?: boolean;
categoryPosition?: CategoryPosition; categoryPosition?: CategoryPosition;
enableSearchBar?: boolean; enableSearchBar?: boolean;
closeSearchColor?: string; closeSearchColor?: string;

View File

@ -36,6 +36,7 @@ export const defaultKeyboardContext: Required<KeyboardProps> = {
}, },
translation: en, translation: en,
disabledCategory: [], disabledCategory: [],
enableRecentlyUsed: false,
categoryPosition: 'floating', categoryPosition: 'floating',
enableSearchBar: false, enableSearchBar: false,
closeSearchColor: '#00000055', closeSearchColor: '#00000055',

View File

@ -1,12 +1,12 @@
import type { EmojiType } from 'src/types'; import type { JsonEmoji } from 'src/types';
export type RecentEmojiState = { export type RecentEmojiState = {
recentlyUsed: EmojiType[]; recentlyUsed: JsonEmoji[];
}; };
export type RecentEmojiAction = export type RecentEmojiAction =
| { type: 'RECENT_EMOJI_ADD'; payload: EmojiType } | { type: 'RECENT_EMOJI_ADD'; payload: JsonEmoji }
| { type: 'RECENT_EMOJI_REMOVE'; payload: EmojiType } | { type: 'RECENT_EMOJI_REMOVE'; payload: JsonEmoji }
| { type: 'RECENT_EMOJI_CLEAR' }; | { type: 'RECENT_EMOJI_CLEAR' };
export default function recentEmojiReducer( export default function recentEmojiReducer(
@ -17,7 +17,7 @@ export default function recentEmojiReducer(
case 'RECENT_EMOJI_ADD': case 'RECENT_EMOJI_ADD':
return { return {
...state, ...state,
recentlyUsed: [...filterEmoji(state, action.payload), action.payload], recentlyUsed: [action.payload, ...filterEmoji(state, action.payload)],
}; };
case 'RECENT_EMOJI_REMOVE': case 'RECENT_EMOJI_REMOVE':
return { return {
@ -31,5 +31,5 @@ export default function recentEmojiReducer(
} }
} }
const filterEmoji = (state: RecentEmojiState, emoji: EmojiType) => const filterEmoji = (state: RecentEmojiState, emoji: JsonEmoji) =>
state.recentlyUsed.filter((usedEmoji) => usedEmoji.emoji !== emoji.emoji); state.recentlyUsed.filter((usedEmoji) => usedEmoji.emoji !== emoji.emoji);

View File

@ -1,6 +1,7 @@
import type { CategoryTranslation } from '../types'; import type { CategoryTranslation } from '../types';
export const en: CategoryTranslation = { export const en: CategoryTranslation = {
recently_used: 'Recently used',
smileys_emotion: 'Smileys & Emotion', smileys_emotion: 'Smileys & Emotion',
people_body: 'People & Body', people_body: 'People & Body',
animals_nature: 'Animals & Nature', animals_nature: 'Animals & Nature',

View File

@ -1,7 +1,8 @@
import type { CategoryTranslation } from '../types'; import type { CategoryTranslation } from '../types';
const pl: CategoryTranslation = { const pl: CategoryTranslation = {
smileys_emotion: 'Buźki i Emocje', recently_used: 'Ostatnio używane',
smileys_emotion: 'Buźki i emocje',
people_body: 'Ludzie', people_body: 'Ludzie',
animals_nature: 'Zwierzęta i przyroda', animals_nature: 'Zwierzęta i przyroda',
food_drink: 'Jedzenie i napoje', food_drink: 'Jedzenie i napoje',

View File

@ -20,6 +20,7 @@ export type CategoryTypes =
| 'objects' | 'objects'
| 'symbols' | 'symbols'
| 'flags' | 'flags'
| 'recently_used'
| 'search'; | 'search';
export type CategoryPosition = 'floating' | 'top' | 'bottom'; export type CategoryPosition = 'floating' | 'top' | 'bottom';
@ -34,6 +35,7 @@ export const CATEGORIES: CategoryTypes[] = [
'objects', 'objects',
'symbols', 'symbols',
'flags', 'flags',
'recently_used',
'search', 'search',
]; ];
@ -56,6 +58,7 @@ export const CATEGORIES_NAVIGATION: CategoryNavigationItem[] = [
{ icon: 'Lightbulb', category: 'objects' }, { icon: 'Lightbulb', category: 'objects' },
{ icon: 'Ban', category: 'symbols' }, { icon: 'Ban', category: 'symbols' },
{ icon: 'Flag', category: 'flags' }, { icon: 'Flag', category: 'flags' },
{ icon: 'Clock', category: 'recently_used' },
{ icon: 'Search', category: 'search' }, { icon: 'Search', category: 'search' },
]; ];