build: create many optional props and general improve

This commit is contained in:
Jakub Grzywacz 2021-07-08 15:10:57 +02:00
parent d20174947f
commit 5cab329d57
No known key found for this signature in database
GPG Key ID: 5BBB685871FF63C4
10 changed files with 129 additions and 58 deletions

View File

@ -2,26 +2,70 @@
. .
## Installation
```sh
npm install react-native-emoji-keyboard
```
## Usage ## Usage
```js ```js
import EmojiKeyboard from "react-native-emoji-keyboard"; import EmojiPicker from '{package-name}';
// ... export default function App() {
const [isOpen, setIsOpen] = React.useState<boolean>(false);
const result = await EmojiKeyboard.multiply(3, 7); const handlePick = (emojiObject: EmojiType) => {
console.log(emojiObject);
/* example emojiObject = { {
"emoji": "❤️",
"name": "red heart",
"slug": "red_heart",
"skin_tone_support": false,
"unicode_version": "0.6",
"emoji_version": "0.6"
},
*/
};
return (
<EmojiPicker
onEmojiSelected={handleSelect}
open={isOpen}
onClose={() => setIsOpen(false)} />
)
}
``` ```
## Contributing ## Installation
```sh
yarn add {package-name}
```
See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow. or
```sh
npm install {package-name}
```
## Full Example
```js
TODO
```
## Accepted props (current implemented)
| Name | Type | Default Value | Required | Description |
|---|---|---|---|---|
| onEmojiSelected | function | undefined | yes | Callback on emoji selected |
| 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 |
| headerStyles | TextStyle | {} | no | Override category name styles |
| knobStyles | ViewStyle | {} | no | Override knob styles |
| containerStyles | ViewStyle | {} | no | Override container styles |
| hideHeader | boolean | false | no | Hide category names |
| expandable | boolean | true | no | Show knob and enable expand on swipe up |
| defaultHeight | number | 0.4 | no | Specify collapsed container height (1 is full screen height) |
| expandedHeight | number | 0.8 | no | Specify expanded container height (1 is full screen height) *only if expandable is true* |
| backdropColor | string | "#00000055" | no | Change backdrop color and alpha |
## License ## License
**MIT**
MIT <br /><br /><br />
## TODO
categories => Specify displayed categories
language => Use translation

View File

@ -21,7 +21,7 @@ export default function App() {
</TouchableOpacity> </TouchableOpacity>
<EmojiPicker <EmojiPicker
onEmojiSelected={handlePick} onEmojiSelected={handlePick}
isOpen={isModalOpen} open={isModalOpen}
onClose={() => setIsModalOpen(false)} onClose={() => setIsModalOpen(false)}
/> />
</SafeAreaView> </SafeAreaView>

View File

@ -62,7 +62,9 @@ export const EmojiKeyboard = () => {
}, [ctx, scrollNav]); }, [ctx, scrollNav]);
return ( return (
<View style={[styles.container, ctx.containerStyles]}> <View
style={[styles.container, styles.containerShadow, ctx.containerStyles]}
>
<Animated.FlatList <Animated.FlatList
data={CATEGORIES} data={CATEGORIES}
keyExtractor={(item: CategoryTypes) => item} keyExtractor={(item: CategoryTypes) => item}
@ -88,5 +90,14 @@ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
paddingTop: 16, paddingTop: 16,
borderRadius: 16,
backgroundColor: '#fff',
},
containerShadow: {
shadowColor: 'black',
shadowOpacity: 0.15,
shadowOffset: { width: 0, height: 0 },
shadowRadius: 5,
elevation: 10,
}, },
}); });

View File

@ -1,35 +1,34 @@
import * as React from 'react'; import * as React from 'react';
import { Animated, useWindowDimensions, StyleSheet } from 'react-native'; import { Animated, useWindowDimensions } from 'react-native';
import { EmojiKeyboard } from './EmojiKeyboard'; import { EmojiKeyboard } from './EmojiKeyboard';
import { Knob } from './components/Knob'; import { Knob } from './components/Knob';
import { KeyboardProvider } from './KeyboardProvider'; import { defaultKeyboardContext, KeyboardProvider } from './KeyboardProvider';
import type { KeyboardProps } from './KeyboardContext'; import type { KeyboardProps } from './KeyboardContext';
import type { EmojiType } from './types'; import type { EmojiType } from './types';
import { ModalWithBackdrop } from './components/ModalWithBackdrop'; import { ModalWithBackdrop } from './components/ModalWithBackdrop';
type EmojiPickerProps = {
isOpen: boolean;
onClose: () => void;
};
export const EmojiPicker = ({ export const EmojiPicker = ({
onEmojiSelected, onEmojiSelected,
isOpen, open,
onClose, onClose,
expandable = defaultKeyboardContext.expandable,
defaultHeight = defaultKeyboardContext.defaultHeight,
...props ...props
}: EmojiPickerProps & KeyboardProps) => { }: KeyboardProps) => {
const { height: screenHeight } = useWindowDimensions(); const { height: screenHeight } = useWindowDimensions();
const offsetY = React.useRef(new Animated.Value(0)).current; const offsetY = React.useRef(new Animated.Value(0)).current;
const height = React.useRef(new Animated.Value(screenHeight * 0.4)).current; const height = React.useRef(
new Animated.Value(screenHeight * defaultHeight)
).current;
const translateY = React.useRef(new Animated.Value(0)).current; const translateY = React.useRef(new Animated.Value(0)).current;
React.useEffect(() => { React.useEffect(() => {
Animated.timing(translateY, { Animated.timing(translateY, {
toValue: isOpen ? 0 : screenHeight, toValue: open ? 0 : screenHeight,
useNativeDriver: true, useNativeDriver: true,
duration: 500, duration: 500,
}).start(); }).start();
}, [isOpen, screenHeight, translateY]); }, [open, screenHeight, translateY]);
const close = () => { const close = () => {
height.setValue(screenHeight * 0.4); height.setValue(screenHeight * 0.4);
@ -43,18 +42,22 @@ export const EmojiPicker = ({
onEmojiSelected(emoji); onEmojiSelected(emoji);
close(); close();
}} }}
isOpen={isOpen} open={open}
onClose={close}
expandable={expandable}
defaultHeight={defaultHeight}
{...props} {...props}
> >
<ModalWithBackdrop isOpen={isOpen} backdropPress={close}> <ModalWithBackdrop isOpen={open} backdropPress={close}>
<> <>
<Knob height={height} offsetY={offsetY} onClose={onClose} /> {expandable && (
<Knob height={height} offsetY={offsetY} onClose={onClose} />
)}
<Animated.View <Animated.View
style={[ style={[
{ {
height: Animated.subtract(height, offsetY), height: Animated.subtract(height, offsetY),
}, },
styles.container,
]} ]}
> >
<EmojiKeyboard /> <EmojiKeyboard />
@ -64,16 +67,3 @@ export const EmojiPicker = ({
</KeyboardProvider> </KeyboardProvider>
); );
}; };
const styles = StyleSheet.create({
modalContainer: { flex: 1, justifyContent: 'flex-end' },
container: {
backgroundColor: '#fff',
borderRadius: 16,
shadowColor: 'black',
shadowOpacity: 0.15,
shadowOffset: { width: 0, height: 0 },
shadowRadius: 5,
},
backdrop: { backgroundColor: '#00000055' },
});

View File

@ -1,5 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import type { ViewStyle } from 'react-native'; import type { TextStyle, ViewStyle } from 'react-native';
import { import {
defaultKeyboardContext, defaultKeyboardContext,
defaultKeyboardValues, defaultKeyboardValues,
@ -7,10 +7,18 @@ import {
import type { EmojiType } from './types'; import type { EmojiType } from './types';
export type KeyboardProps = { export type KeyboardProps = {
open: boolean;
onClose: () => void;
onEmojiSelected: (emoji: EmojiType) => void; onEmojiSelected: (emoji: EmojiType) => void;
emojiSize?: number; emojiSize?: number;
containerStyles?: ViewStyle; containerStyles?: ViewStyle;
isOpen?: boolean; knobStyles?: ViewStyle;
headerStyles?: TextStyle;
expandable?: boolean;
hideHeader?: boolean;
defaultHeight?: number;
expandedHeight?: number;
backdropColor?: string;
}; };
export type ContextValues = { export type ContextValues = {
activeCategoryIndex: number; activeCategoryIndex: number;

View File

@ -11,10 +11,18 @@ type ProviderProps = KeyboardProps & {
}; };
export const defaultKeyboardContext: Required<KeyboardProps> = { export const defaultKeyboardContext: Required<KeyboardProps> = {
open: false,
onClose: () => {},
onEmojiSelected: (_emoji: EmojiType) => {}, onEmojiSelected: (_emoji: EmojiType) => {},
emojiSize: 24, emojiSize: 28,
containerStyles: {}, containerStyles: {},
isOpen: false, knobStyles: {},
headerStyles: {},
expandable: true,
hideHeader: false,
defaultHeight: 0.4,
expandedHeight: 0.8,
backdropColor: '#00000055',
}; };
export const defaultKeyboardValues: ContextValues = { export const defaultKeyboardValues: ContextValues = {
@ -27,8 +35,8 @@ export const KeyboardProvider: React.FC<ProviderProps> = React.memo((props) => {
const [activeCategoryIndex, setActiveCategoryIndex] = React.useState(0); const [activeCategoryIndex, setActiveCategoryIndex] = React.useState(0);
React.useEffect(() => { React.useEffect(() => {
if (props.isOpen) setActiveCategoryIndex(0); if (props.open) setActiveCategoryIndex(0);
}, [props.isOpen]); }, [props.open]);
const value: Required<KeyboardProps> & ContextValues = { const value: Required<KeyboardProps> & ContextValues = {
...defaultKeyboardContext, ...defaultKeyboardContext,

View File

@ -42,7 +42,9 @@ export const EmojiCategory = ({ item }: { item: CategoryTypes }) => {
return ( return (
<View style={[styles.container, { width: width }]}> <View style={[styles.container, { width: width }]}>
<Text style={styles.sectionTitle}>{item}</Text> {!ctx.hideHeader && (
<Text style={[styles.sectionTitle, ctx.headerStyles]}>{item}</Text>
)}
<FlatList <FlatList
data={data} data={data}
keyExtractor={(emoji) => emoji.name} keyExtractor={(emoji) => emoji.name}

View File

@ -5,6 +5,7 @@ import {
PanGestureHandlerGestureEvent, PanGestureHandlerGestureEvent,
State, State,
} from 'react-native-gesture-handler'; } from 'react-native-gesture-handler';
import { KeyboardContext } from '../KeyboardContext';
type KnobProps = { type KnobProps = {
offsetY: Animated.Value; offsetY: Animated.Value;
@ -14,6 +15,7 @@ type KnobProps = {
export const Knob = ({ offsetY, height, onClose }: KnobProps) => { export const Knob = ({ offsetY, height, onClose }: KnobProps) => {
const { height: screenHeight } = useWindowDimensions(); const { height: screenHeight } = useWindowDimensions();
const ctx = React.useContext(KeyboardContext);
const handleGesture = ({ const handleGesture = ({
nativeEvent: { translationY, state }, nativeEvent: { translationY, state },
@ -31,16 +33,16 @@ export const Knob = ({ offsetY, height, onClose }: KnobProps) => {
if (translationY < -30) { if (translationY < -30) {
Animated.spring(height, { Animated.spring(height, {
useNativeDriver: false, useNativeDriver: false,
toValue: screenHeight * 0.8, toValue: screenHeight * ctx.expandedHeight,
}).start(); }).start();
} else if (translationY > 150) { } else if (translationY > 150) {
height.setValue(screenHeight * 0.4); height.setValue(screenHeight * ctx.defaultHeight);
offsetY.setValue(0); offsetY.setValue(0);
onClose(); onClose();
} else { } else {
Animated.spring(height, { Animated.spring(height, {
useNativeDriver: false, useNativeDriver: false,
toValue: screenHeight * 0.4, toValue: screenHeight * ctx.defaultHeight,
}).start(); }).start();
} }
} }
@ -51,7 +53,7 @@ export const Knob = ({ offsetY, height, onClose }: KnobProps) => {
onGestureEvent={handleGesture} onGestureEvent={handleGesture}
onHandlerStateChange={handleGesture} onHandlerStateChange={handleGesture}
> >
<Animated.View style={styles.knob} /> <Animated.View style={[styles.knob, ctx.knobStyles]} />
</PanGestureHandler> </PanGestureHandler>
); );
}; };

View File

@ -8,6 +8,7 @@ import {
TouchableOpacity, TouchableOpacity,
View, View,
} from 'react-native'; } from 'react-native';
import { KeyboardContext } from '../KeyboardContext';
type ModalWithBackdropProps = { type ModalWithBackdropProps = {
isOpen: boolean; isOpen: boolean;
@ -22,6 +23,7 @@ export const ModalWithBackdrop = ({
}: ModalWithBackdropProps) => { }: ModalWithBackdropProps) => {
const { height: screenHeight } = useWindowDimensions(); const { height: screenHeight } = useWindowDimensions();
const translateY = React.useRef(new Animated.Value(0)).current; const translateY = React.useRef(new Animated.Value(0)).current;
const ctx = React.useContext(KeyboardContext);
React.useEffect(() => { React.useEffect(() => {
Animated.timing(translateY, { Animated.timing(translateY, {
@ -38,7 +40,12 @@ export const ModalWithBackdrop = ({
activeOpacity={1} activeOpacity={1}
onPress={backdropPress} onPress={backdropPress}
> >
<View style={[styles.modalContainer, styles.backdrop]}> <View
style={[
styles.modalContainer,
{ backgroundColor: ctx.backdropColor },
]}
>
<SafeAreaView style={styles.modalContainer}> <SafeAreaView style={styles.modalContainer}>
<TouchableOpacity activeOpacity={1}> <TouchableOpacity activeOpacity={1}>
<Animated.View <Animated.View
@ -66,5 +73,4 @@ const styles = StyleSheet.create({
shadowOffset: { width: 0, height: 0 }, shadowOffset: { width: 0, height: 0 },
shadowRadius: 5, shadowRadius: 5,
}, },
backdrop: { backgroundColor: '#00000055' },
}); });

View File

@ -1,10 +1,10 @@
export type EmojiType = { export type EmojiType = {
emoji: string; emoji: string;
emoji_version: string;
name: string; name: string;
skin_tone_support: boolean;
slug: string; slug: string;
skin_tone_support: boolean;
unicode_version: string; unicode_version: string;
emoji_version: string;
}; };
export type CategoryTypes = export type CategoryTypes =