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

botonic-react: upgrade react-textarea-autosize #2882

Merged
merged 11 commits into from
Aug 27, 2024
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
Loading