build: create many optional props and general improve
This commit is contained in:
parent
d20174947f
commit
5cab329d57
68
README.md
68
README.md
|
@ -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
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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' },
|
|
||||||
});
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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' },
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
Loading…
Reference in New Issue