Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adding custom buttons #172

Merged
merged 5 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/assets/img/custom-button-preview-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/img/custom-button-preview-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/img/custom-button-preview-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions docs/docs/api/modal.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,18 @@ Reveal the search bar, used to find specific emoji.

<ApiTable typeVal='boolean' defaultVal='false'/>

### `hideSearchBarClearIcon`

Hide the search bar clear icon inside the search input.

<ApiTable typeVal='boolean' defaultVal='false'/>

### `customButtons`

Inject custom buttons into the component.

<ApiTable typeVal='React.ReactNode' defaultVal='null'/>

### `enableCategoryChangeAnimation`

Allow to turn off FlatList scrolling animation when category is changed.
Expand Down
63 changes: 63 additions & 0 deletions docs/docs/documentation/Examples/custom-buttons.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
sidebar_position: 13
title: Custom Buttons
---

import preview1 from '../../../assets/img/custom-button-preview-1.png'
import preview2 from '../../../assets/img/custom-button-preview-2.png'
import preview3 from '../../../assets/img/custom-button-preview-3.png'

:::info
To preview app with this example, clone [**github repo**](https://github.com/TheWidlarzGroup/rn-emoji-keyboard.git) and run `yarn example ios` or `yarn example android`.
:::

### Usage

We've introduced a custom button feature to provide you with the flexibility to add your unique functionality to our interface.

The `customButtons` prop allows you to inject custom buttons into the `EmojiPicker` component, enabling additional functionalities or actions within the emoji picker interface. This flexible prop accepts an array of React elements, allowing for multiple custom buttons to be specified.

To use the `customButtons` prop, pass an array of React components that you wish to render as buttons within the emoji picker. Each component must be assigned a unique key prop to help React identify which items have changed, are being added, or are removed.

If search bar is enabled, custom buttons shows next to it.

The `DeleteButton` is a pre-designed component that can be used within the `EmojiPicker` as part of the customButtons prop array. You can add `onPress` prop with a function that will be called when the DeleteButton is pressed. This allows you to define the specific action that should occur on press, such as deleting an emoji from the input field.
. You can add any pressableProps you need to this custom component.

```jsx
import EmojiPicker from 'rn-emoji-keyboard'

const ExampleComponent = () => {
// ...

return (
<EmojiPicker
onEmojiSelected={handlePick}
open={isModalOpen}
onClose={() => setIsModalOpen(false)}
enableSearchBar
customButtons={[
<DeleteButton
key="deleteButton"
onPress={deleteLastEmoji}
style={({ pressed }) => ({
backgroundColor: pressed ? '#000' : '#e1e1e1',
padding: 10,
borderRadius: 100,
})}
iconNormalColor="#000"
iconActiveColor="#fff"
/>,
]}
allowMultipleSelections
categoryPosition="top"
/>
)
}
```

<div className="gallery">
<img src={preview1} alt="First Image" />
<img src={preview2} alt="Second Image" />
<img src={preview3} alt="Third Image" />
</div>
37 changes: 36 additions & 1 deletion docs/docs/documentation/Examples/dark.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ title: Dark Mode
We do not provide a prop that will directly enable dark mode, but with the ability to adjust theme, you can easily achieve it
using theme prop.

```tsx
```jsx
<EmojiPicker
onEmojiSelected={handlePick}
open={isModalOpen}
Expand All @@ -29,3 +29,38 @@ using theme prop.

The effect of the above code.
![The effect of the above code](../../../assets/img/dark-mode-preview.jpg)

Above, the `theme` property is displayed with the styles required for the basic version of the EmojiPicker. Below, you'll find the `theme` property showcasing all available styles.

```jsx
<EmojiPicker
onEmojiSelected={handlePick}
open={isModalOpen}
onClose={() => setIsModalOpen(false)}
theme={{
backdrop: '#16161888',
knob: '#766dfc',
container: '#282829',
header: '#fff',
skinTonesContainer: '#252427',
category: {
icon: '#766dfc',
iconActive: '#fff',
container: '#252427',
containerActive: '#766dfc',
},
customButton: {
icon: '#766dfc',
iconPressed: '#fff',
background: '#252427',
backgroundPressed: '#766dfc',
},
search: {
text: '#fff',
placeholder: '#ffffff2c',
icon: '#fff',
background: '#00000011',
},
}}
/>
```
2 changes: 2 additions & 0 deletions docs/docs/documentation/Examples/search.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ To preview app with this example, clone [**github repo**](https://github.com/The

If you want to reveal the search bar, used to find specific emoji, just pass `enableSearchBar` to `EmojiPicker`.

You can also choose whether to display the search bar clear icon (which appears when you type something in search input). To hide this icon, pass the `hideSearchBarClearIcon` prop to `EmojiPicker`.

```jsx
import EmojiPicker from 'rn-emoji-keyboard'

Expand Down
31 changes: 31 additions & 0 deletions docs/src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,34 @@
font-weight: 700;
src: local('JetBrainsMono'), url(../../assets/fonts/JetBrainsMono-Bold.woff2) format('woff2');
}

.gallery {
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: row;
}

.gallery img {
width: 32%;
margin-right: 2%;
}

.gallery img:last-child {
margin-right: 0;
}

@media (max-width: 768px) {
.gallery {
flex-direction: column;
}
.gallery img {
width: 100%;
margin-right: 0px;
margin-bottom: 20px;
}

.gallery img:last-child {
margin-right: 0;
}
}
53 changes: 53 additions & 0 deletions example/app/(examples)/custom-buttons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Button } from 'example/src/components/Button'
import React from 'react'
import { Results } from 'example/src/components/Results'
import EmojiPicker, { type EmojiType } from 'rn-emoji-keyboard'
import { DeleteButton } from '../../../src/components/DeleteButton'

export default function () {
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)
}

const deleteLastEmoji = () => {
if (result) {
let arrayFromString = Array.from(result)
arrayFromString.pop()
setResult(arrayFromString.join(''))
}
}

return (
<>
<Results label={result} />
<Button onPress={() => setIsModalOpen(true)} label="Open" />

<EmojiPicker
onEmojiSelected={handlePick}
open={isModalOpen}
onClose={() => setIsModalOpen(false)}
enableSearchBar
customButtons={[
<DeleteButton
key="deleteButton"
onPress={deleteLastEmoji}
style={({ pressed }) => ({
backgroundColor: pressed ? '#000' : '#e1e1e1',
padding: 10,
borderRadius: 100,
})}
iconNormalColor="#000"
iconActiveColor="#fff"
/>,
]}
allowMultipleSelections
categoryPosition="top"
/>
</>
)
}
2 changes: 1 addition & 1 deletion example/app/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as React from 'react'

import { StyleSheet, View } from 'react-native'
import { Link } from 'example/src/components/Link'
import { Stack } from 'expo-router'
Expand All @@ -18,6 +17,7 @@ export const screens = {
'/search': 'Search Bar',
'/selected-emojis': 'Selected Emojis',
'/translated': 'Translated',
'/custom-buttons': 'Custom Button',
} as const

export default function App() {
Expand Down
Binary file added src/assets/icons/backspace.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 66 additions & 0 deletions src/components/DeleteButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react'
import {
View,
Pressable,
StyleSheet,
type StyleProp,
type ViewStyle,
type PressableProps,
} from 'react-native'
import { KeyboardContext } from '../contexts/KeyboardContext'
import { Icon } from './Icon'

type CustomButtonType = {
containerStyle?: StyleProp<ViewStyle>
iconNormalColor?: string
iconActiveColor?: string
} & PressableProps

export const DeleteButton = ({
containerStyle,
iconNormalColor,
iconActiveColor,
...pressableProps
}: CustomButtonType) => {
const { theme } = React.useContext(KeyboardContext)
return (
<View style={[styles.buttonContainer, containerStyle]}>
<Pressable
style={({ pressed }) => [
{
backgroundColor: pressed
? theme.customButton.backgroundPressed
: theme.customButton.background,
padding: 8,
borderRadius: 100,
},
styles.button,
]}
{...pressableProps}
>
{({ pressed }) => (
<Icon
iconName="Backspace"
isActive={pressed}
normalColor={iconNormalColor || theme.customButton.icon}
activeColor={iconActiveColor || theme.customButton.iconPressed}
/>
)}
</Pressable>
</View>
)
}

const styles = StyleSheet.create({
buttonContainer: {
marginTop: 16,
marginLeft: 8,
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'center',
},
button: {
justifyContent: 'center',
alignItems: 'center',
},
})
22 changes: 18 additions & 4 deletions src/components/EmojiStaticKeyboard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as React from 'react'

import {
StyleSheet,
View,
Expand Down Expand Up @@ -30,6 +29,7 @@
enableCategoryChangeGesture,
categoryPosition,
enableSearchBar,
customButtons,
searchPhrase,
renderList,
disableSafeArea,
Expand Down Expand Up @@ -130,7 +130,7 @@
>
<ConditionalContainer
condition={!disableSafeArea}
container={(children) => (

Check warning on line 133 in src/components/EmojiStaticKeyboard.tsx

View workflow job for this annotation

GitHub Actions / eslint

Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component and pass data as props. If you want to allow component creation in props, set allowAsProps option to true
<SafeAreaView
style={[styles.flex, categoryPosition === 'top' && styles.containerReverse]}
>
Expand All @@ -139,9 +139,18 @@
)}
>
<>
{enableSearchBar && (
<SearchBar scrollEmojiCategoryListToIndex={scrollEmojiCategoryListToIndex} />
)}
<View
style={
categoryPosition === 'top'
? [styles.searchContainer, { marginBottom: 16 }]
: styles.searchContainer
}
>
{enableSearchBar && (
<SearchBar scrollEmojiCategoryListToIndex={scrollEmojiCategoryListToIndex} />
)}
{customButtons}
</View>
<Animated.FlatList<EmojisByCategory>
extraData={[keyboardState.recentlyUsed.length, searchPhrase]}
data={renderList}
Expand Down Expand Up @@ -181,6 +190,11 @@
flex: 1,
borderRadius: 16,
},
searchContainer: {
paddingHorizontal: 16,
flexDirection: 'row',
justifyContent: 'flex-end',
},
containerReverse: { flexDirection: 'column-reverse' },
containerShadow: {
shadowColor: 'black',
Expand Down
4 changes: 3 additions & 1 deletion src/components/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const Icon = ({
normalColor,
activeColor,
}: {
iconName: IconNames | 'Close' | 'QuestionMark'
iconName: IconNames | 'Close' | 'QuestionMark' | 'Backspace'
isActive: boolean
normalColor: string
activeColor: string
Expand Down Expand Up @@ -43,6 +43,8 @@ export const Icon = ({
return <PngIcon fill={color} source={require('../assets/icons/clock.png')} />
case 'QuestionMark':
return <PngIcon fill={color} source={require('../assets/icons/questionMark.png')} />
case 'Backspace':
return <PngIcon fill={color} source={require('../assets/icons/backspace.png')} />
default:
exhaustiveTypeCheck(iconName)
return null
Expand Down
4 changes: 3 additions & 1 deletion src/components/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const SearchBar = ({ scrollEmojiCategoryListToIndex }: SearchBarProps) =>
setShouldAnimateScroll(enableSearchAnimation)
}
}

const clearPhrase = () => {
setSearchPhrase('')
clearEmojiTonesData()
Expand Down Expand Up @@ -83,12 +84,13 @@ export const SearchBar = ({ scrollEmojiCategoryListToIndex }: SearchBarProps) =>
const styles = StyleSheet.create({
container: {
marginTop: 16,
marginHorizontal: 16,
marginRight: 8,
borderRadius: 100,
borderWidth: 1,
borderColor: '#00000011',
flexDirection: 'row',
alignItems: 'center',
flex: 1,
},
input: {
paddingVertical: 8,
Expand Down
Loading