2
0

🚸 Auto scroll once picture choice images are fully loaded

This commit is contained in:
Baptiste Arnaud
2023-11-15 14:10:41 +01:00
parent 27e9c1adb9
commit 1f19eb8763
9 changed files with 52 additions and 7 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@typebot.io/js",
"version": "0.2.19",
"version": "0.2.20",
"description": "Javascript library to display typebots on your website",
"type": "module",
"main": "dist/index.js",

View File

@ -27,6 +27,7 @@ type Props = Pick<ContinueChatResponse, 'messages' | 'input'> & {
export const ChatChunk = (props: Props) => {
let inputRef: HTMLDivElement | undefined
const [displayedMessageIndex, setDisplayedMessageIndex] = createSignal(0)
const [lastBubbleOffsetTop, setLastBubbleOffsetTop] = createSignal<number>()
onMount(() => {
if (props.streamingMessageId) return
@ -48,6 +49,7 @@ export const ChatChunk = (props: Props) => {
)
props.onScrollToBottom(bubbleOffsetTop)
if (displayedMessageIndex() === props.messages.length) {
setLastBubbleOffsetTop(bubbleOffsetTop)
props.onAllBubblesDisplayed()
}
}
@ -111,6 +113,7 @@ export const ChatChunk = (props: Props) => {
defaultSettings.general.isInputPrefillEnabled
}
hasError={props.hasError}
onTransitionEnd={() => props.onScrollToBottom(lastBubbleOffsetTop())}
onSubmit={props.onSubmit}
onSkip={props.onSkip}
/>

View File

@ -46,6 +46,7 @@ type Props = {
context: BotContext
isInputPrefillEnabled: boolean
hasError: boolean
onTransitionEnd: () => void
onSubmit: (answer: string) => void
onSkip: () => void
}
@ -102,6 +103,7 @@ export const InputChatBlock = (props: Props) => {
block={props.block}
inputIndex={props.inputIndex}
isInputPrefillEnabled={props.isInputPrefillEnabled}
onTransitionEnd={props.onTransitionEnd}
onSubmit={handleSubmit}
onSkip={handleSkip}
/>
@ -116,6 +118,7 @@ const Input = (props: {
block: NonNullable<ContinueChatResponse['input']>
inputIndex: number
isInputPrefillEnabled: boolean
onTransitionEnd: () => void
onSubmit: (answer: InputSubmitContent) => void
onSkip: (label: string) => void
}) => {
@ -208,6 +211,7 @@ const Input = (props: {
defaultItems={block.items}
options={block.options}
onSubmit={onSubmit}
onTransitionEnd={props.onTransitionEnd}
/>
</Match>
<Match when={block.options?.isMultipleChoice}>
@ -215,6 +219,7 @@ const Input = (props: {
defaultItems={block.items}
options={block.options}
onSubmit={onSubmit}
onTransitionEnd={props.onTransitionEnd}
/>
</Match>
</Switch>
@ -245,6 +250,7 @@ const Input = (props: {
} as PaymentInputBlock['options'] & RuntimeOptions
}
onSuccess={submitPaymentSuccess}
onTransitionEnd={props.onTransitionEnd}
/>
</Match>
</Switch>

View File

@ -6,6 +6,7 @@ type Props = {
context: BotContext
options: PaymentInputBlock['options'] & RuntimeOptions
onSuccess: () => void
onTransitionEnd: () => void
}
export const PaymentForm = (props: Props) => (
@ -13,5 +14,6 @@ export const PaymentForm = (props: Props) => (
onSuccess={props.onSuccess}
options={props.options}
context={props.context}
onTransitionEnd={props.onTransitionEnd}
/>
)

View File

@ -14,6 +14,7 @@ type Props = {
context: BotContext
options: PaymentInputBlock['options'] & RuntimeOptions
onSuccess: () => void
onTransitionEnd: () => void
}
const slotName = 'stripe-payment-form'
@ -48,7 +49,10 @@ export const StripePaymentForm = (props: Props) => {
layout: 'tabs',
})
paymentElement.mount('#payment-element')
setTimeout(() => setIsMounted(true), 1000)
setTimeout(() => {
setIsMounted(true)
props.onTransitionEnd()
}, 1000)
})
const handleSubmit = async (event: Event & { submitter: HTMLElement }) => {

View File

@ -1,6 +1,6 @@
import { InputSubmitContent } from '@/types'
import { PictureChoiceBlock } from '@typebot.io/schemas/features/blocks/inputs/pictureChoice'
import { For, Show, createSignal, onMount } from 'solid-js'
import { For, Show, createEffect, createSignal, onMount } from 'solid-js'
import { Checkbox } from '../buttons/components/Checkbox'
import { SendButton } from '@/components'
import { isDefined, isEmpty, isSvgSrc } from '@typebot.io/lib'
@ -12,12 +12,14 @@ type Props = {
defaultItems: PictureChoiceBlock['items']
options: PictureChoiceBlock['options']
onSubmit: (value: InputSubmitContent) => void
onTransitionEnd: () => void
}
export const MultiplePictureChoice = (props: Props) => {
let inputRef: HTMLInputElement | undefined
const [filteredItems, setFilteredItems] = createSignal(props.defaultItems)
const [selectedItemIds, setSelectedItemIds] = createSignal<string[]>([])
const [totalLoadedImages, setTotalLoadedImages] = createSignal(0)
onMount(() => {
if (!isMobile() && inputRef) inputRef.focus()
@ -64,6 +66,18 @@ export const MultiplePictureChoice = (props: Props) => {
)
}
createEffect(() => {
if (
totalLoadedImages() ===
props.defaultItems.filter((item) => isDefined(item.pictureSrc)).length
)
props.onTransitionEnd()
})
const onImageLoad = () => {
setTotalLoadedImages((acc) => acc + 1)
}
return (
<form class="flex flex-col gap-2 w-full items-end" onSubmit={handleSubmit}>
<Show when={props.options?.isSearchable}>
@ -112,6 +126,7 @@ export const MultiplePictureChoice = (props: Props) => {
elementtiming={`Picture choice ${index() + 1}`}
fetchpriority={'high'}
class="m-auto"
onLoad={onImageLoad}
/>
<div
class={

View File

@ -1,19 +1,21 @@
import { SearchInput } from '@/components/inputs/SearchInput'
import { InputSubmitContent } from '@/types'
import { isMobile } from '@/utils/isMobileSignal'
import { isSvgSrc } from '@typebot.io/lib/utils'
import { isDefined, isSvgSrc } from '@typebot.io/lib/utils'
import { PictureChoiceBlock } from '@typebot.io/schemas/features/blocks/inputs/pictureChoice'
import { For, Show, createSignal, onMount } from 'solid-js'
import { For, Show, createEffect, createSignal, onMount } from 'solid-js'
type Props = {
defaultItems: PictureChoiceBlock['items']
options: PictureChoiceBlock['options']
onSubmit: (value: InputSubmitContent) => void
onTransitionEnd: () => void
}
export const SinglePictureChoice = (props: Props) => {
let inputRef: HTMLInputElement | undefined
const [filteredItems, setFilteredItems] = createSignal(props.defaultItems)
const [totalLoadedImages, setTotalLoadedImages] = createSignal(0)
onMount(() => {
if (!isMobile() && inputRef) inputRef.focus()
@ -41,6 +43,18 @@ export const SinglePictureChoice = (props: Props) => {
)
}
createEffect(() => {
if (
totalLoadedImages() ===
props.defaultItems.filter((item) => isDefined(item.pictureSrc)).length
)
props.onTransitionEnd()
})
const onImageLoad = () => {
setTotalLoadedImages((acc) => acc + 1)
}
return (
<div class="flex flex-col gap-2 w-full">
<Show when={props.options?.isSearchable}>
@ -77,6 +91,7 @@ export const SinglePictureChoice = (props: Props) => {
elementtiming={`Picture choice ${index() + 1}`}
fetchpriority={'high'}
class="m-auto"
onLoad={onImageLoad}
/>
<div
class={

View File

@ -1,6 +1,6 @@
{
"name": "@typebot.io/nextjs",
"version": "0.2.19",
"version": "0.2.20",
"description": "Convenient library to display typebots on your Next.js website",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View File

@ -1,6 +1,6 @@
{
"name": "@typebot.io/react",
"version": "0.2.19",
"version": "0.2.20",
"description": "Convenient library to display typebots on your React app",
"main": "dist/index.js",
"types": "dist/index.d.ts",