Skip to content

Commit

Permalink
botonic-react: upgrade react-textarea-autosize (#2882)
Browse files Browse the repository at this point in the history
## Description

Upgrade version of react-textarea-autosize. This version is compatible
with react 16, 17 or 18
Create a component Textarea to extract logic in a new file
Create a component WebchatInputPanel that group all inputs: text input,
add emoji, add file, send button

## Context

More PRs merged in this PR:
- #2883
- #2886
  • Loading branch information
Iru89 committed Aug 27, 2024
1 parent 586553c commit ab0da2e
Show file tree
Hide file tree
Showing 15 changed files with 968 additions and 664 deletions.
961 changes: 559 additions & 402 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions packages/botonic-react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@botonic/react",
"version": "0.29.0",
"version": "0.29.1-alpha.0",
"license": "MIT",
"description": "Build Chatbots using React",
"main": "./lib/cjs",
Expand Down Expand Up @@ -30,7 +30,7 @@
"react-frame-component": "^4.1.3",
"react-json-tree": "^0.15.0",
"react-router-dom": "^5.3.4",
"react-textarea-autosize": "^7.1.2",
"react-textarea-autosize": "^8.5.3",
"simplebar-react": "^2.4.3",
"styled-components": "^5.3.0",
"ua-parser-js": "^0.8.1",
Expand Down
14 changes: 10 additions & 4 deletions packages/botonic-react/src/contexts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,22 +59,28 @@ export const WebchatContext = createContext<WebchatContextProps>({
setLastMessageVisible: () => {
return
},
sendAttachment: () => {
sendAttachment: async () => {
return
},
sendInput: () => {
sendInput: async () => {
return
},
sendPayload: () => {
sendPayload: async () => {
return
},
sendText: () => {
sendText: async () => {
return
},
theme: {},
toggleWebchat: () => {
return
},
toggleEmojiPicker: () => {
return
},
togglePersistentMenu: () => {
return
},
updateLatestInput: () => {
return
},
Expand Down
19 changes: 13 additions & 6 deletions packages/botonic-react/src/index-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ export enum SENDERS {
agent = 'agent',
}

export enum Typing {
On = 'typing_on',
Off = 'typing_off',
}

export interface WebchatMessage {
ack: 0 | 1
blob: boolean
Expand Down Expand Up @@ -177,7 +182,7 @@ export interface OnStateChangeArgs {
}

export interface MessageInfo {
data: any | 'typing_on'
data: any | Typing.On
id: string
type: 'update_webchat_settings' | 'sender_action'
}
Expand All @@ -191,17 +196,19 @@ export interface Event {
export interface WebchatContextProps {
addMessage: (message: WebchatMessage) => void
closeWebview: () => void
getThemeProperty: (property: string, defaultValue?: string | boolean) => any
getThemeProperty: (property: string, defaultValue?: any) => any
openWebview: (webviewComponent: Webview) => void
resetUnreadMessages: () => void
resolveCase: () => void
sendAttachment: (attachment: File) => void
sendInput: (input: CoreInput) => void
sendPayload: (payload: string) => void
sendText: (text: string, payload?: string) => void
sendAttachment: (attachment: File) => Promise<void>
sendInput: (input: CoreInput) => Promise<void>
sendPayload: (payload: string) => Promise<void>
sendText: (text: string, payload?: string) => Promise<void>
setLastMessageVisible: (isLastMessageVisible: boolean) => void
theme: ThemeProps
toggleWebchat: (toggle: boolean) => void
toggleEmojiPicker: (toggle: boolean) => void
togglePersistentMenu: (toggle: boolean) => void
updateLatestInput: (input: CoreInput) => void
updateMessage: (message: WebchatMessage) => void
updateReplies: (replies: boolean) => void
Expand Down
4 changes: 2 additions & 2 deletions packages/botonic-react/src/webchat-app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React, { createRef } from 'react'
import { render, unmountComponentAtNode } from 'react-dom'

import { WEBCHAT } from './constants'
import { SENDERS } from './index-types'
import { SENDERS, Typing } from './index-types'
import { msgToBotonic } from './msg-to-botonic'
import { isShadowDOMSupported, onDOMLoaded } from './util/dom'
import { Webchat } from './webchat/webchat'
Expand Down Expand Up @@ -156,7 +156,7 @@ export class WebchatApp {
} else if (event.message?.type === 'update_webchat_settings') {
this.updateWebchatSettings(event.message.data)
} else if (event.message?.type === 'sender_action') {
this.setTyping(event.message.data === 'typing_on')
this.setTyping(event.message.data === Typing.On)
} else {
this.onMessage &&
this.onMessage(this, { sentBy: SENDERS.bot, message: event.message })
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import React, { useContext } from 'react'
import styled from 'styled-components'

import LogoMenu from '../../assets/menuButton.svg'
import { Button } from '../../components/button'
import { ROLES, WEBCHAT } from '../../constants'
import { WebchatContext } from '../../contexts'
import { ConditionalAnimation } from '../components/conditional-animation'
import { useComponentVisible } from '../hooks'
import { Icon } from './common'

const ButtonsContainer = styled.div`
position: absolute;
Expand Down Expand Up @@ -60,30 +57,4 @@ export const OpenedPersistentMenu = ({ onClick, options, borderRadius }) => {
)
}

export const PersistentMenu = ({ onClick, persistentMenu }) => {
const { getThemeProperty } = useContext(WebchatContext)

const persistentMenuOptions = getThemeProperty(
WEBCHAT.CUSTOM_PROPERTIES.persistentMenu,
persistentMenu
)

const CustomMenuButton = getThemeProperty(
WEBCHAT.CUSTOM_PROPERTIES.customMenuButton,
undefined
)

return (
<>
{persistentMenuOptions ? (
<ConditionalAnimation>
<div role={ROLES.PERSISTENT_MENU_ICON} onClick={onClick}>
{CustomMenuButton ? <CustomMenuButton /> : <Icon src={LogoMenu} />}
</div>
</ConditionalAnimation>
) : null}
</>
)
}

export default OpenedPersistentMenu
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,23 @@ import React, { useContext, useRef } from 'react'
import AttachmentIcon from '../../assets/attachment-icon.svg'
import { ROLES, WEBCHAT } from '../../constants'
import { WebchatContext } from '../../contexts'
import { Icon } from '../components/common'
import { ConditionalAnimation } from '../components/conditional-animation'
import { Icon } from './common'

export const Attachment = ({ onChange, accept, enableAttachments }) => {
interface AttachmentProps {
accept: string
enableAttachments: boolean
onChange: (event: any) => void
}

export const Attachment = ({
accept,
enableAttachments,
onChange,
}: AttachmentProps) => {
const { getThemeProperty } = useContext(WebchatContext)

const fileInputRef = useRef(null)
const fileInputRef = useRef<HTMLInputElement | null>(null)

const CustomAttachments = getThemeProperty(
WEBCHAT.CUSTOM_PROPERTIES.customAttachments,
Expand All @@ -29,7 +39,9 @@ export const Attachment = ({ onChange, accept, enableAttachments }) => {

const handleOnChange = event => {
onChange(event)
fileInputRef.current.value = null
if (fileInputRef.current) {
fileInputRef.current.value = ''
}
}

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import Picker from 'emoji-picker-react'
import React, { useContext } from 'react'
import styled from 'styled-components'

import LogoEmoji from '../../assets/emojiButton.svg'
import { ROLES, WEBCHAT } from '../../constants'
import { WebchatContext } from '../../contexts'
import { Icon } from '../components/common'
import { ConditionalAnimation } from '../components/conditional-animation'
import { useComponentVisible } from '../hooks'
import { Icon } from './common'

export const EmojiPicker = ({ enableEmojiPicker, onClick }) => {
interface EmojiPickerProps {
enableEmojiPicker: boolean
onClick: () => void
}

export const EmojiPicker = ({
enableEmojiPicker,
onClick,
}: EmojiPickerProps) => {
const { getThemeProperty } = useContext(WebchatContext)

const CustomEmojiPicker = getThemeProperty(
Expand All @@ -28,7 +33,7 @@ export const EmojiPicker = ({ enableEmojiPicker, onClick }) => {
}
const emojiPickerEnabled = isEmojiPickerEnabled()

const handleClick = event => {
const handleClick = (event: any) => {
onClick()
event.stopPropagation()
}
Expand All @@ -49,30 +54,3 @@ export const EmojiPicker = ({ enableEmojiPicker, onClick }) => {
</>
)
}

const Container = styled.div`
display: flex;
justify-content: flex-end;
position: absolute;
right: 3px;
top: -324px;
`

export const OpenedEmojiPicker = props => {
const { ref, isComponentVisible } = useComponentVisible(true, props.onClick)
return (
<div ref={ref}>
{isComponentVisible && (
<Container role={ROLES.EMOJI_PICKER}>
<Picker
width='100%'
height='19rem'
previewConfig={{ showPreview: false }}
onEmojiClick={props.onEmojiClick}
disableAutoFocus={true}
/>
</Container>
)}
</div>
)
}
116 changes: 116 additions & 0 deletions packages/botonic-react/src/webchat/webchat-input-panel/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { INPUT } from '@botonic/core'
import React, { useContext } from 'react'
import { v4 as uuidv4 } from 'uuid'

import { WEBCHAT } from '../../constants'
import { WebchatContext } from '../../contexts'
import { getFullMimeWhitelist } from '../../message-utils'
import { DeviceAdapter } from '../devices/device-adapter'
import { Attachment } from './attachment'
import { EmojiPicker } from './emoji-picker'
import { OpenedEmojiPicker } from './opened-emoji-picker'
import { PersistentMenu } from './persistent-menu'
import { SendButton } from './send-button'
import { UserInputContainer } from './styles'
import { Textarea } from './textarea'

interface WebchatInputPanelProps {
persistentMenu: any
enableEmojiPicker: boolean
enableAttachments: boolean
handleAttachment: (event: any) => void
textareaRef: React.MutableRefObject<HTMLTextAreaElement>
deviceAdapter: DeviceAdapter
onUserInput?: (event: any) => Promise<void>
}

export const WebchatInputPanel = ({
persistentMenu,
enableEmojiPicker,
enableAttachments,
handleAttachment,
textareaRef,
deviceAdapter,
onUserInput,
}: WebchatInputPanelProps) => {
const {
getThemeProperty,
sendText,
togglePersistentMenu,
toggleEmojiPicker,
webchatState,
} = useContext(WebchatContext)

const handleSelectedEmoji = event => {
textareaRef.current.value += event.emoji
textareaRef.current.focus()
}

const handleEmojiClick = () => {
toggleEmojiPicker(!webchatState.isEmojiPickerOpen)
}

const handleMenu = () => {
togglePersistentMenu(!webchatState.isPersistentMenuOpen)
}

const sendTextAreaText = async () => {
await sendText(textareaRef.current.value)
textareaRef.current.value = ''
}

const sendChatEvent = async chatEvent => {
const chatEventInput = {
id: uuidv4(),
type: INPUT.CHAT_EVENT,
data: chatEvent,
}
if (onUserInput) {
onUserInput({
user: webchatState.session.user,
input: chatEventInput,
session: webchatState.session,
lastRoutePath: webchatState.lastRoutePath,
})
}
}

return (
<UserInputContainer
style={{
...getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.userInputStyle),
}}
className='user-input-container'
>
{webchatState.isEmojiPickerOpen && (
<OpenedEmojiPicker
onEmojiClick={handleSelectedEmoji}
onClick={handleEmojiClick}
/>
)}

<PersistentMenu onClick={handleMenu} persistentMenu={persistentMenu} />

<Textarea
deviceAdapter={deviceAdapter}
persistentMenu={persistentMenu}
textareaRef={textareaRef}
sendChatEvent={sendChatEvent}
sendTextAreaText={sendTextAreaText}
/>

<EmojiPicker
enableEmojiPicker={enableEmojiPicker}
onClick={handleEmojiClick}
/>

<Attachment
enableAttachments={enableAttachments}
onChange={handleAttachment}
accept={getFullMimeWhitelist().join(',')}
/>

<SendButton onClick={sendTextAreaText} />
</UserInputContainer>
)
}
Loading

0 comments on commit ab0da2e

Please sign in to comment.