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
```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
**MIT**
MIT
<br /><br /><br />
## TODO
categories => Specify displayed categories
language => Use translation

View File

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

View File

@ -62,7 +62,9 @@ export const EmojiKeyboard = () => {
}, [ctx, scrollNav]);
return (
<View style={[styles.container, ctx.containerStyles]}>
<View
style={[styles.container, styles.containerShadow, ctx.containerStyles]}
>
<Animated.FlatList
data={CATEGORIES}
keyExtractor={(item: CategoryTypes) => item}
@ -88,5 +90,14 @@ const styles = StyleSheet.create({
container: {
flex: 1,
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 { Animated, useWindowDimensions, StyleSheet } from 'react-native';
import { Animated, useWindowDimensions } from 'react-native';
import { EmojiKeyboard } from './EmojiKeyboard';
import { Knob } from './components/Knob';
import { KeyboardProvider } from './KeyboardProvider';
import { defaultKeyboardContext, KeyboardProvider } from './KeyboardProvider';
import type { KeyboardProps } from './KeyboardContext';
import type { EmojiType } from './types';
import { ModalWithBackdrop } from './components/ModalWithBackdrop';
type EmojiPickerProps = {
isOpen: boolean;
onClose: () => void;
};
export const EmojiPicker = ({
onEmojiSelected,
isOpen,
open,
onClose,
expandable = defaultKeyboardContext.expandable,
defaultHeight = defaultKeyboardContext.defaultHeight,
...props
}: EmojiPickerProps & KeyboardProps) => {
}: KeyboardProps) => {
const { height: screenHeight } = useWindowDimensions();
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;
React.useEffect(() => {
Animated.timing(translateY, {
toValue: isOpen ? 0 : screenHeight,
toValue: open ? 0 : screenHeight,
useNativeDriver: true,
duration: 500,
}).start();
}, [isOpen, screenHeight, translateY]);
}, [open, screenHeight, translateY]);
const close = () => {
height.setValue(screenHeight * 0.4);
@ -43,18 +42,22 @@ export const EmojiPicker = ({
onEmojiSelected(emoji);
close();
}}
isOpen={isOpen}
open={open}
onClose={close}
expandable={expandable}
defaultHeight={defaultHeight}
{...props}
>
<ModalWithBackdrop isOpen={isOpen} backdropPress={close}>
<ModalWithBackdrop isOpen={open} backdropPress={close}>
<>
{expandable && (
<Knob height={height} offsetY={offsetY} onClose={onClose} />
)}
<Animated.View
style={[
{
height: Animated.subtract(height, offsetY),
},
styles.container,
]}
>
<EmojiKeyboard />
@ -64,16 +67,3 @@ export const EmojiPicker = ({
</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 type { ViewStyle } from 'react-native';
import type { TextStyle, ViewStyle } from 'react-native';
import {
defaultKeyboardContext,
defaultKeyboardValues,
@ -7,10 +7,18 @@ import {
import type { EmojiType } from './types';
export type KeyboardProps = {
open: boolean;
onClose: () => void;
onEmojiSelected: (emoji: EmojiType) => void;
emojiSize?: number;
containerStyles?: ViewStyle;
isOpen?: boolean;
knobStyles?: ViewStyle;
headerStyles?: TextStyle;
expandable?: boolean;
hideHeader?: boolean;
defaultHeight?: number;
expandedHeight?: number;
backdropColor?: string;
};
export type ContextValues = {
activeCategoryIndex: number;

View File

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

View File

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

View File

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

View File

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

View File

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