2
0

feat(inputs): Add buttons input

This commit is contained in:
Baptiste Arnaud
2022-01-12 09:10:59 +01:00
parent b20bcb1408
commit c02c61cd8b
47 changed files with 1109 additions and 243 deletions

View File

@ -9,7 +9,7 @@
"db": "*",
"fast-equals": "^2.0.4",
"models": "*",
"react-frame-component": "^5.2.1",
"react-frame-component": "5.2.2-alpha.0",
"react-phone-number-input": "^3.1.44",
"react-scroll": "^1.8.4",
"react-transition-group": "^4.4.2",

View File

@ -4,18 +4,21 @@ import { TransitionGroup, CSSTransition } from 'react-transition-group'
import { ChatStep } from './ChatStep'
import { AvatarSideContainer } from './AvatarSideContainer'
import { HostAvatarsContext } from '../../contexts/HostAvatarsContext'
import { Step, Table } from 'models'
import { ChoiceInputStep, Step } from 'models'
import { useTypebot } from '../../contexts/TypebotContext'
import { isChoiceInput } from 'utils'
type ChatBlockProps = {
steps: Table<Step>
stepIds: string[]
onBlockEnd: (nextBlockId?: string) => void
}
export const ChatBlock = ({ steps, onBlockEnd }: ChatBlockProps) => {
export const ChatBlock = ({ stepIds, onBlockEnd }: ChatBlockProps) => {
const { typebot } = useTypebot()
const [displayedSteps, setDisplayedSteps] = useState<Step[]>([])
useEffect(() => {
setDisplayedSteps([steps.byId[steps.allIds[0]]])
setDisplayedSteps([typebot.steps.byId[stepIds[0]]])
}, [])
useEffect(() => {
@ -29,17 +32,36 @@ export const ChatBlock = ({ steps, onBlockEnd }: ChatBlockProps) => {
})
}
const displayNextStep = () => {
const displayNextStep = (answerContent?: string) => {
const currentStep = [...displayedSteps].pop()
if (!currentStep) throw new Error('currentStep should exist')
const isSingleChoiceStep =
isChoiceInput(currentStep) && !currentStep.options.isMultipleChoice
if (isSingleChoiceStep)
return onBlockEnd(getSingleChoiceTargetId(currentStep, answerContent))
if (
currentStep?.target?.blockId ||
displayedSteps.length === steps.allIds.length
displayedSteps.length === stepIds.length
)
return onBlockEnd(currentStep?.target?.blockId)
const nextStep = steps.byId[displayedSteps.length]
const nextStep = typebot.steps.byId[stepIds[displayedSteps.length]]
if (nextStep) setDisplayedSteps([...displayedSteps, nextStep])
}
const getSingleChoiceTargetId = (
currentStep: ChoiceInputStep,
answerContent?: string
) => {
const itemId = currentStep.options.itemIds.find(
(itemId) => typebot.choiceItems.byId[itemId].content === answerContent
)
if (!itemId) throw new Error('itemId should exist')
const targetId =
typebot.choiceItems.byId[itemId].target?.blockId ??
currentStep.target?.blockId
return targetId
}
return (
<div className="flex">
<HostAvatarsContext>

View File

@ -7,19 +7,20 @@ import { HostMessageBubble } from './bubbles/HostMessageBubble'
import { TextForm } from './inputs/TextForm'
import { isInputStep, isTextBubbleStep } from 'utils'
import { DateForm } from './inputs/DateForm'
import { ChoiceForm } from './inputs/ChoiceForm'
export const ChatStep = ({
step,
onTransitionEnd,
}: {
step: Step
onTransitionEnd: () => void
onTransitionEnd: (answerContent?: string) => void
}) => {
const { addAnswer } = useAnswers()
const handleInputSubmit = (content: string) => {
addAnswer({ stepId: step.id, blockId: step.blockId, content })
onTransitionEnd()
onTransitionEnd(content)
}
if (isTextBubbleStep(step))
@ -60,5 +61,7 @@ const InputChatStep = ({
return <TextForm step={step} onSubmit={handleSubmit} />
case InputStepType.DATE:
return <DateForm options={step.options} onSubmit={handleSubmit} />
case InputStepType.CHOICE:
return <ChoiceForm options={step.options} onSubmit={handleSubmit} />
}
}

View File

@ -0,0 +1,64 @@
import { ChoiceInputOptions } from 'models'
import React, { useMemo, useState } from 'react'
import { filterTable } from 'utils'
import { useTypebot } from '../../../../contexts/TypebotContext'
import { SendButton } from './SendButton'
type ChoiceFormProps = {
options?: ChoiceInputOptions
onSubmit: (value: string) => void
}
export const ChoiceForm = ({ options, onSubmit }: ChoiceFormProps) => {
const { typebot } = useTypebot()
const items = useMemo(
() => filterTable(options?.itemIds ?? [], typebot.choiceItems),
[]
)
const [selectedIds, setSelectedIds] = useState<string[]>([])
const handleClick = (itemId: string) => (e: React.MouseEvent) => {
e.preventDefault()
if (options?.isMultipleChoice) toggleSelectedItemId(itemId)
else onSubmit(items.byId[itemId].content ?? '')
}
const toggleSelectedItemId = (itemId: string) => {
const existingIndex = selectedIds.indexOf(itemId)
if (existingIndex !== -1) {
selectedIds.splice(existingIndex, 1)
setSelectedIds([...selectedIds])
} else {
setSelectedIds([...selectedIds, itemId])
}
}
const handleSubmit = () =>
onSubmit(selectedIds.map((itemId) => items.byId[itemId].content).join(', '))
return (
<form className="flex flex-col" onSubmit={handleSubmit}>
<div className="flex flex-wrap">
{options?.itemIds.map((itemId) => (
<button
role={options?.isMultipleChoice ? 'checkbox' : 'button'}
onClick={handleClick(itemId)}
className={
'py-2 px-4 font-semibold rounded-md transition-all filter hover:brightness-90 active:brightness-75 duration-100 focus:outline-none mr-2 mb-2 typebot-button ' +
(selectedIds.includes(itemId) || !options?.isMultipleChoice
? 'active'
: '')
}
>
{items.byId[itemId].content}
</button>
))}
</div>
<div className="flex">
{selectedIds.length > 0 && (
<SendButton label={options?.buttonLabel ?? 'Send'} />
)}
</div>
</form>
)
}

View File

@ -62,6 +62,7 @@ export const DateForm = ({
<SendButton
label={labels?.button ?? 'Send'}
isDisabled={inputValues.to === '' && inputValues.from === ''}
className="my-2 ml-2"
/>
</form>
</div>

View File

@ -3,7 +3,7 @@ import { SendIcon } from '../../../../assets/icons'
type SendButtonProps = {
label: string
isDisabled: boolean
isDisabled?: boolean
} & React.ButtonHTMLAttributes<HTMLButtonElement>
export const SendButton = ({
@ -14,11 +14,12 @@ export const SendButton = ({
return (
<button
type="submit"
className={
'my-2 ml-2 py-2 px-4 font-semibold rounded-md text-white focus:outline-none flex items-center disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 transition-all filter hover:brightness-90 active:brightness-75 typebot-button active'
}
disabled={isDisabled}
{...props}
className={
'py-2 px-4 font-semibold rounded-md text-white focus:outline-none flex items-center disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 transition-all filter hover:brightness-90 active:brightness-75 typebot-button active ' +
props.className
}
>
<span className="hidden xs:flex">{label}</span>
<SendIcon className="send-icon flex xs:hidden" />

View File

@ -41,6 +41,7 @@ export const TextForm = ({ step, onSubmit }: TextFormProps) => {
<SendButton
label={step.options?.labels?.button ?? 'Send'}
isDisabled={inputValue === ''}
className="my-2 ml-2"
/>
</form>
</div>

View File

@ -61,7 +61,7 @@ export const ConversationContainer = ({
{displayedBlocks.map((block, idx) => (
<ChatBlock
key={block.id + idx}
steps={filterTable(block.stepIds, typebot.steps)}
stepIds={block.stepIds}
onBlockEnd={displayNextBlock}
/>
))}