Skip to content

Commit

Permalink
🚸 Improve chat auto scroll
Browse files Browse the repository at this point in the history
Instead of scrolling all the way to the bottom on each bubble, now we scroll to the top of the last bubble

Closes #486
  • Loading branch information
baptisteArno committed May 12, 2023
1 parent df8a406 commit a3fb098
Show file tree
Hide file tree
Showing 10 changed files with 33 additions and 33 deletions.
2 changes: 1 addition & 1 deletion packages/embeds/js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@typebot.io/js",
"version": "0.0.48",
"version": "0.0.49",
"description": "Javascript library to display typebots on your website",
"type": "module",
"main": "dist/index.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type Props = Pick<ChatReply, 'messages' | 'input'> & {
hasError: boolean
hideAvatar: boolean
onNewBubbleDisplayed: (blockId: string) => Promise<void>
onScrollToBottom: () => void
onScrollToBottom: (top?: number) => void
onSubmit: (input: string) => void
onSkip: () => void
onAllBubblesDisplayed: () => void
Expand All @@ -31,15 +31,15 @@ export const ChatChunk = (props: Props) => {
props.onScrollToBottom()
})

const displayNextMessage = async () => {
const displayNextMessage = async (bubbleOffsetTop?: number) => {
const lastBubbleBlockId = props.messages[displayedMessageIndex()].id
await props.onNewBubbleDisplayed(lastBubbleBlockId)
setDisplayedMessageIndex(
displayedMessageIndex() === props.messages.length
? displayedMessageIndex()
: displayedMessageIndex() + 1
)
props.onScrollToBottom()
props.onScrollToBottom(bubbleOffsetTop)
if (displayedMessageIndex() === props.messages.length) {
props.onAllBubblesDisplayed()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ type Props = {

export const ConversationContainer = (props: Props) => {
let chatContainer: HTMLDivElement | undefined
let bottomSpacer: HTMLDivElement | undefined
const [chatChunks, setChatChunks] = createSignal<
Pick<ChatReply, 'messages' | 'input' | 'clientSideActions'>[]
>([
Expand Down Expand Up @@ -153,10 +152,9 @@ export const ConversationContainer = (props: Props) => {
])
}

const autoScrollToBottom = () => {
if (!bottomSpacer) return
const autoScrollToBottom = (offsetTop?: number) => {
setTimeout(() => {
chatContainer?.scrollTo(0, chatContainer.scrollHeight)
chatContainer?.scrollTo(0, offsetTop ?? chatContainer.scrollHeight)
}, 50)
}

Expand Down Expand Up @@ -227,14 +225,11 @@ export const ConversationContainer = (props: Props) => {
</div>
)}
</Show>
<BottomSpacer ref={bottomSpacer} />
<BottomSpacer />
</div>
)
}

type BottomSpacerProps = {
ref: HTMLDivElement | undefined
}
const BottomSpacer = (props: BottomSpacerProps) => {
return <div ref={props.ref} class="w-full h-32 flex-shrink-0" />
const BottomSpacer = () => {
return <div class="w-full h-32 flex-shrink-0" />
}
6 changes: 3 additions & 3 deletions packages/embeds/js/src/components/bubbles/HostBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ import { Match, Switch } from 'solid-js'
type Props = {
message: ChatMessage
typingEmulation: TypingEmulation
onTransitionEnd: () => void
onTransitionEnd: (offsetTop?: number) => void
}

export const HostBubble = (props: Props) => {
const onTransitionEnd = () => {
props.onTransitionEnd()
const onTransitionEnd = (offsetTop?: number) => {
props.onTransitionEnd(offsetTop)
}

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createSignal, onCleanup, onMount } from 'solid-js'

type Props = {
url: AudioBubbleContent['url']
onTransitionEnd: () => void
onTransitionEnd: (offsetTop?: number) => void
}

const showAnimationDuration = 400
Expand All @@ -13,13 +13,14 @@ const typingDuration = 500
let typingTimeout: NodeJS.Timeout

export const AudioBubble = (props: Props) => {
let ref: HTMLDivElement | undefined
const [isTyping, setIsTyping] = createSignal(true)

onMount(() => {
typingTimeout = setTimeout(() => {
setIsTyping(false)
setTimeout(() => {
props.onTransitionEnd()
props.onTransitionEnd(ref?.offsetTop)
}, showAnimationDuration)
}, typingDuration)
})
Expand All @@ -29,7 +30,7 @@ export const AudioBubble = (props: Props) => {
})

return (
<div class="flex flex-col animate-fade-in">
<div class="flex flex-col animate-fade-in" ref={ref}>
<div class="flex w-full items-center">
<div class={'flex relative z-10 items-start typebot-host-bubble'}>
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@ import { createSignal, onCleanup, onMount } from 'solid-js'

type Props = {
content: EmbedBubbleContent
onTransitionEnd: () => void
onTransitionEnd: (offsetTop?: number) => void
}

let typingTimeout: NodeJS.Timeout

export const showAnimationDuration = 400

export const EmbedBubble = (props: Props) => {
let ref: HTMLDivElement | undefined
const [isTyping, setIsTyping] = createSignal(true)

onMount(() => {
typingTimeout = setTimeout(() => {
setIsTyping(false)
setTimeout(() => {
props.onTransitionEnd()
props.onTransitionEnd(ref?.offsetTop)
}, showAnimationDuration)
}, 2000)
})
Expand All @@ -28,7 +29,7 @@ export const EmbedBubble = (props: Props) => {
})

return (
<div class="flex flex-col w-full animate-fade-in">
<div class="flex flex-col w-full animate-fade-in" ref={ref}>
<div class="flex w-full items-center">
<div
class={'flex relative z-10 items-start typebot-host-bubble w-full'}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createSignal, onCleanup, onMount } from 'solid-js'

type Props = {
content: ImageBubbleContent
onTransitionEnd: () => void
onTransitionEnd: (offsetTop?: number) => void
}

export const showAnimationDuration = 400
Expand All @@ -14,14 +14,15 @@ export const mediaLoadingFallbackTimeout = 5000
let typingTimeout: NodeJS.Timeout

export const ImageBubble = (props: Props) => {
let ref: HTMLDivElement | undefined
let image: HTMLImageElement | undefined
const [isTyping, setIsTyping] = createSignal(true)

const onTypingEnd = () => {
if (!isTyping()) return
setIsTyping(false)
setTimeout(() => {
props.onTransitionEnd()
props.onTransitionEnd(ref?.offsetTop)
}, showAnimationDuration)
}

Expand Down Expand Up @@ -56,7 +57,7 @@ export const ImageBubble = (props: Props) => {
)

return (
<div class="flex flex-col animate-fade-in">
<div class="flex flex-col animate-fade-in" ref={ref}>
<div class="flex w-full items-center">
<div class={'flex relative z-10 items-start typebot-host-bubble'}>
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { computePlainText } from '../helpers/convertRichTextToPlainText'
type Props = {
content: TextBubbleContent
typingEmulation: TypingEmulation
onTransitionEnd: () => void
onTransitionEnd: (offsetTop?: number) => void
}

export const showAnimationDuration = 400
Expand All @@ -22,13 +22,14 @@ const defaultTypingEmulation = {
let typingTimeout: NodeJS.Timeout

export const TextBubble = (props: Props) => {
let ref: HTMLDivElement | undefined
const [isTyping, setIsTyping] = createSignal(true)

const onTypingEnd = () => {
if (!isTyping()) return
setIsTyping(false)
setTimeout(() => {
props.onTransitionEnd()
props.onTransitionEnd(ref?.offsetTop)
}, showAnimationDuration)
}

Expand All @@ -50,7 +51,7 @@ export const TextBubble = (props: Props) => {
})

return (
<div class="flex flex-col animate-fade-in">
<div class="flex flex-col animate-fade-in" ref={ref}>
<div class="flex w-full items-center">
<div class="flex relative items-start typebot-host-bubble">
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,22 @@ import { createSignal, Match, onCleanup, onMount, Switch } from 'solid-js'

type Props = {
content: VideoBubbleContent
onTransitionEnd: () => void
onTransitionEnd: (offsetTop?: number) => void
}

export const showAnimationDuration = 400

let typingTimeout: NodeJS.Timeout

export const VideoBubble = (props: Props) => {
let ref: HTMLDivElement | undefined
const [isTyping, setIsTyping] = createSignal(true)

const onTypingEnd = () => {
if (!isTyping()) return
setIsTyping(false)
setTimeout(() => {
props.onTransitionEnd()
props.onTransitionEnd(ref?.offsetTop)
}, showAnimationDuration)
}

Expand All @@ -32,7 +33,7 @@ export const VideoBubble = (props: Props) => {
})

return (
<div class="flex flex-col animate-fade-in">
<div class="flex flex-col animate-fade-in" ref={ref}>
<div class="flex w-full items-center">
<div class={'flex relative z-10 items-start typebot-host-bubble'}>
<div
Expand Down
2 changes: 1 addition & 1 deletion packages/embeds/react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@typebot.io/react",
"version": "0.0.48",
"version": "0.0.49",
"description": "React library to display typebots on your website",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down

0 comments on commit a3fb098

Please sign in to comment.