2
0

fix(engine): 🐛 Fix scrolling behavior and avatar position

This commit is contained in:
Baptiste Arnaud
2022-02-07 08:05:53 +01:00
parent 1f320c5d99
commit f4336b83cc
7 changed files with 31 additions and 17 deletions

View File

@ -154,6 +154,13 @@ textarea {
.typebot-button { .typebot-button {
color: var(--typebot-button-color); color: var(--typebot-button-color);
background-color: var(--typebot-button-bg-color); background-color: var(--typebot-button-bg-color);
border: 1px solid var(--typebot-button-bg-color);
}
.typebot-button.selectable {
color: var(--typebot-host-bubble-color);
background-color: var(--typebot-host-bubble-bg-color);
border: 1px solid var(--typebot-button-bg-color);
} }
.typebot-host-bubble { .typebot-host-bubble {

View File

@ -38,10 +38,10 @@ export const AvatarSideContainer = () => {
unmountOnExit unmountOnExit
> >
<div <div
className="fixed w-6 h-6 xs:w-10 xs:h-10 mb-4 xs:mb-2 flex items-center top-0" className="absolute w-6 h-6 xs:w-10 xs:h-10 mb-4 xs:mb-2 flex items-center top-0"
style={{ style={{
top: `calc(${topOffset}px - ${marginBottom}px)`, top: `calc(${topOffset}px - ${marginBottom}px)`,
transition: 'top 500ms ease-out', transition: 'top 350ms ease-out',
}} }}
> >
<HostAvatar typebotName={typebot.name} /> <HostAvatar typebotName={typebot.name} />

View File

@ -1,5 +1,4 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { animateScroll as scroll } from 'react-scroll'
import { TransitionGroup, CSSTransition } from 'react-transition-group' import { TransitionGroup, CSSTransition } from 'react-transition-group'
import { ChatStep } from './ChatStep' import { ChatStep } from './ChatStep'
import { AvatarSideContainer } from './AvatarSideContainer' import { AvatarSideContainer } from './AvatarSideContainer'
@ -20,6 +19,7 @@ type ChatBlockProps = {
steps: PublicStep[] steps: PublicStep[]
startStepIndex: number startStepIndex: number
blockIndex: number blockIndex: number
onScroll: () => void
onBlockEnd: (edgeId?: string) => void onBlockEnd: (edgeId?: string) => void
} }
@ -27,6 +27,7 @@ export const ChatBlock = ({
steps, steps,
startStepIndex, startStepIndex,
blockIndex, blockIndex,
onScroll,
onBlockEnd, onBlockEnd,
}: ChatBlockProps) => { }: ChatBlockProps) => {
const { typebot, updateVariableValue } = useTypebot() const { typebot, updateVariableValue } = useTypebot()
@ -41,7 +42,7 @@ export const ChatBlock = ({
}, []) }, [])
useEffect(() => { useEffect(() => {
autoScrollToBottom() onScroll()
onNewStepDisplayed() onNewStepDisplayed()
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [displayedSteps]) }, [displayedSteps])
@ -69,13 +70,6 @@ export const ChatBlock = ({
} }
} }
const autoScrollToBottom = () => {
scroll.scrollToBottom({
duration: 500,
containerId: 'scrollable-container',
})
}
const displayNextStep = (answerContent?: string) => { const displayNextStep = (answerContent?: string) => {
const currentStep = [...displayedSteps].pop() const currentStep = [...displayedSteps].pop()
if (currentStep) { if (currentStep) {

View File

@ -55,7 +55,9 @@ export const TextBubble = ({ step, onTransitionEnd }: Props) => {
const sendAvatarOffset = () => { const sendAvatarOffset = () => {
if (!messageContainer.current) return if (!messageContainer.current) return
const containerDimensions = messageContainer.current.getBoundingClientRect() const containerDimensions = messageContainer.current.getBoundingClientRect()
updateLastAvatarOffset(containerDimensions.top + containerDimensions.height) updateLastAvatarOffset(
messageContainer.current.offsetTop + containerDimensions.height
)
} }
return ( return (

View File

@ -44,8 +44,8 @@ export const ChoiceForm = ({ step, onSubmit }: ChoiceFormProps) => {
className={ 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 ' + '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 ' +
(selectedIndices.includes(idx) || !step.options?.isMultipleChoice (selectedIndices.includes(idx) || !step.options?.isMultipleChoice
? 'active' ? ''
: '') : 'selectable')
} }
data-testid="button" data-testid="button"
> >

View File

@ -17,7 +17,7 @@ export const SendButton = ({
disabled={isDisabled} disabled={isDisabled}
{...props} {...props}
className={ 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 ' + '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 ' +
props.className props.className
} }
> >

View File

@ -7,6 +7,7 @@ import { useAnswers } from '../contexts/AnswersContext'
import { deepEqual } from 'fast-equals' import { deepEqual } from 'fast-equals'
import { Answer, Edge, PublicBlock, PublicTypebot } from 'models' import { Answer, Edge, PublicBlock, PublicTypebot } from 'models'
import { byId } from 'utils' import { byId } from 'utils'
import { animateScroll as scroll } from 'react-scroll'
type Props = { type Props = {
typebot: PublicTypebot typebot: PublicTypebot
@ -27,6 +28,7 @@ export const ConversationContainer = ({
const [localAnswer, setLocalAnswer] = useState<Answer | undefined>() const [localAnswer, setLocalAnswer] = useState<Answer | undefined>()
const { answers } = useAnswers() const { answers } = useAnswers()
const bottomAnchor = useRef<HTMLDivElement | null>(null) const bottomAnchor = useRef<HTMLDivElement | null>(null)
const scrollableContainer = useRef<HTMLDivElement | null>(null)
const displayNextBlock = (edgeId?: string) => { const displayNextBlock = (edgeId?: string) => {
const nextEdge = typebot.edges.find(byId(edgeId)) const nextEdge = typebot.edges.find(byId(edgeId))
@ -60,10 +62,18 @@ export const ConversationContainer = ({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [answers]) }, [answers])
const autoScrollToBottom = () => {
if (!scrollableContainer.current) return
scroll.scrollToBottom({
duration: 500,
container: scrollableContainer.current,
})
}
return ( return (
<div <div
ref={scrollableContainer}
className="overflow-y-scroll w-full lg:w-3/4 min-h-full rounded lg:px-5 px-3 pt-10 relative scrollable-container typebot-chat-view" className="overflow-y-scroll w-full lg:w-3/4 min-h-full rounded lg:px-5 px-3 pt-10 relative scrollable-container typebot-chat-view"
id="scrollable-container"
> >
{displayedBlocks.map((displayedBlock, idx) => ( {displayedBlocks.map((displayedBlock, idx) => (
<ChatBlock <ChatBlock
@ -71,11 +81,12 @@ export const ConversationContainer = ({
steps={displayedBlock.block.steps} steps={displayedBlock.block.steps}
startStepIndex={displayedBlock.startStepIndex} startStepIndex={displayedBlock.startStepIndex}
blockIndex={idx} blockIndex={idx}
onScroll={autoScrollToBottom}
onBlockEnd={displayNextBlock} onBlockEnd={displayNextBlock}
/> />
))} ))}
{/* We use a block to simulate padding because it makes iOS scroll flicker */} {/* We use a block to simulate padding because it makes iOS scroll flicker */}
<div className="w-full" ref={bottomAnchor} /> <div className="w-full h-32" ref={bottomAnchor} />
</div> </div>
) )
} }