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 |
| onClose | function | undefined | yes | Request close modal *runs when onEmojiSelected or backdrop pressed* |
| 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 |
| knobStyles | ViewStyle | {} | no | Override knob 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}) |
| 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 |
| enableSearchBar | boolean | false | no | Enable search bar |
| closeSearchColor | string | "#00000055" | no | Change button (cross) color for close/cancel search |
## 📊 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)
### [Static](/example/src/Static/Static.tsx)
![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)
![Preview](/example/assets/categories-top-preview.jpg)
### [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 StaticModal from './StaticModal/StaticModal';
import Static from './Static/Static';
import EnableRecently from './EnableRecently/EnableRecently';
import TopCategory from './TopCategory/TopCategory';
import BottomCategory from './BottomCategory/BottomCategory';
import SearchBar from './SearchBar/SearchBar';
@ -20,7 +21,11 @@ export default () => {
<Stack.Navigator>
<Stack.Screen name="Examples" component={Examples} />
<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="TopCategory" component={TopCategory} />
<Stack.Screen name="BottomCategory" component={BottomCategory} />
<Stack.Screen name="Translated" component={Translated} />
<Stack.Screen
name="DisabledCategories"
@ -28,9 +33,6 @@ 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.Screen name="SearchBar" component={SearchBar} />
</Stack.Navigator>
</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;
StaticModal: undefined;
Static: undefined;
EnableRecently: undefined;
TopCategory: undefined;
BottomCategory: undefined;
SearchBar: undefined;
@ -40,6 +41,10 @@ const Examples = ({ navigation }: Props) => {
title="Static Component"
onPress={() => navigation.navigate('Static')}
/>
<Button
title="Enable recently used"
onPress={() => navigation.navigate('EnableRecently')}
/>
<Button
title="Category Top"
onPress={() => navigation.navigate('TopCategory')}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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