feat(steps): ✨ Add Embed bubble
This commit is contained in:
@ -197,9 +197,12 @@ const ChatChunks = ({
|
||||
const avatarSideContainerRef = useRef<any>()
|
||||
|
||||
useEffect(() => {
|
||||
avatarSideContainerRef.current?.refreshTopOffset()
|
||||
refreshTopOffset()
|
||||
})
|
||||
|
||||
const refreshTopOffset = () =>
|
||||
avatarSideContainerRef.current?.refreshTopOffset()
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex">
|
||||
@ -209,18 +212,26 @@ const ChatChunks = ({
|
||||
hostAvatarSrc={hostAvatar.src}
|
||||
/>
|
||||
)}
|
||||
<TransitionGroup>
|
||||
{bubbles.map((step) => (
|
||||
<CSSTransition
|
||||
key={step.id}
|
||||
classNames="bubble"
|
||||
timeout={500}
|
||||
unmountOnExit
|
||||
>
|
||||
<HostBubble step={step} onTransitionEnd={onDisplayNextStep} />
|
||||
</CSSTransition>
|
||||
))}
|
||||
</TransitionGroup>
|
||||
<div className="flex-1">
|
||||
<TransitionGroup>
|
||||
{bubbles.map((step) => (
|
||||
<CSSTransition
|
||||
key={step.id}
|
||||
classNames="bubble"
|
||||
timeout={500}
|
||||
unmountOnExit
|
||||
>
|
||||
<HostBubble
|
||||
step={step}
|
||||
onTransitionEnd={() => {
|
||||
onDisplayNextStep()
|
||||
refreshTopOffset()
|
||||
}}
|
||||
/>
|
||||
</CSSTransition>
|
||||
))}
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
<CSSTransition
|
||||
classNames="bubble"
|
||||
|
@ -0,0 +1,68 @@
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { EmbedBubbleStep } from 'models'
|
||||
import { TypingContent } from './TypingContent'
|
||||
|
||||
type Props = {
|
||||
step: EmbedBubbleStep
|
||||
onTransitionEnd: () => void
|
||||
}
|
||||
|
||||
export const showAnimationDuration = 400
|
||||
|
||||
export const EmbedBubble = ({ step, onTransitionEnd }: Props) => {
|
||||
const messageContainer = useRef<HTMLDivElement | null>(null)
|
||||
const [isTyping, setIsTyping] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
showContentAfterMediaLoad()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const showContentAfterMediaLoad = () => {
|
||||
setTimeout(() => {
|
||||
setIsTyping(false)
|
||||
onTypingEnd()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const onTypingEnd = () => {
|
||||
setIsTyping(false)
|
||||
setTimeout(() => {
|
||||
onTransitionEnd()
|
||||
}, showAnimationDuration)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full" ref={messageContainer}>
|
||||
<div className="flex mb-2 w-full lg:w-11/12 items-center">
|
||||
<div
|
||||
className={
|
||||
'flex relative z-10 items-start typebot-host-bubble w-full'
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="flex items-center absolute px-4 py-2 rounded-lg bubble-typing z-10 "
|
||||
style={{
|
||||
width: isTyping ? '4rem' : '100%',
|
||||
height: isTyping ? '2rem' : '100%',
|
||||
}}
|
||||
>
|
||||
{isTyping ? <TypingContent /> : <></>}
|
||||
</div>
|
||||
<iframe
|
||||
id="embed-bubble-content"
|
||||
src={step.content.url}
|
||||
className={
|
||||
'w-full z-20 p-4 content-opacity ' +
|
||||
(isTyping ? 'opacity-0' : 'opacity-100')
|
||||
}
|
||||
style={{
|
||||
height: isTyping ? '2rem' : step.content.height,
|
||||
borderRadius: '15px',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import { BubbleStep, BubbleStepType } from 'models'
|
||||
import React from 'react'
|
||||
import { EmbedBubble } from './EmbedBubble'
|
||||
import { ImageBubble } from './ImageBubble'
|
||||
import { TextBubble } from './TextBubble'
|
||||
import { VideoBubble } from './VideoBubble'
|
||||
@ -17,5 +18,7 @@ export const HostBubble = ({ step, onTransitionEnd }: Props) => {
|
||||
return <ImageBubble step={step} onTransitionEnd={onTransitionEnd} />
|
||||
case BubbleStepType.VIDEO:
|
||||
return <VideoBubble step={step} onTransitionEnd={onTransitionEnd} />
|
||||
case BubbleStepType.EMBED:
|
||||
return <EmbedBubble step={step} onTransitionEnd={onTransitionEnd} />
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,23 @@
|
||||
import { StepBase } from '.'
|
||||
|
||||
export type BubbleStep = TextBubbleStep | ImageBubbleStep | VideoBubbleStep
|
||||
export type BubbleStep =
|
||||
| TextBubbleStep
|
||||
| ImageBubbleStep
|
||||
| VideoBubbleStep
|
||||
| EmbedBubbleStep
|
||||
|
||||
export enum BubbleStepType {
|
||||
TEXT = 'text',
|
||||
IMAGE = 'image',
|
||||
VIDEO = 'video',
|
||||
EMBED = 'embed',
|
||||
}
|
||||
|
||||
export type BubbleStepContent =
|
||||
| TextBubbleContent
|
||||
| ImageBubbleContent
|
||||
| VideoBubbleContent
|
||||
| EmbedBubbleContent
|
||||
|
||||
export type TextBubbleStep = StepBase & {
|
||||
type: BubbleStepType.TEXT
|
||||
@ -28,6 +34,11 @@ export type VideoBubbleStep = StepBase & {
|
||||
content: VideoBubbleContent
|
||||
}
|
||||
|
||||
export type EmbedBubbleStep = StepBase & {
|
||||
type: BubbleStepType.EMBED
|
||||
content: EmbedBubbleContent
|
||||
}
|
||||
|
||||
export type TextBubbleContent = {
|
||||
html: string
|
||||
richText: unknown[]
|
||||
@ -38,6 +49,11 @@ export type ImageBubbleContent = {
|
||||
url?: string
|
||||
}
|
||||
|
||||
export type EmbedBubbleContent = {
|
||||
url?: string
|
||||
height: number
|
||||
}
|
||||
|
||||
export enum VideoBubbleContentType {
|
||||
URL = 'url',
|
||||
YOUTUBE = 'youtube',
|
||||
@ -59,3 +75,5 @@ export const defaultTextBubbleContent: TextBubbleContent = {
|
||||
export const defaultImageBubbleContent: ImageBubbleContent = {}
|
||||
|
||||
export const defaultVideoBubbleContent: VideoBubbleContent = {}
|
||||
|
||||
export const defaultEmbedBubbleContent: EmbedBubbleContent = { height: 400 }
|
||||
|
@ -149,3 +149,11 @@ export const omit: Omit = (obj, ...keys) => {
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
export const sanitizeUrl = (url: string): string =>
|
||||
url.startsWith('http') ||
|
||||
url.startsWith('mailto:') ||
|
||||
url.startsWith('tel:') ||
|
||||
url.startsWith('sms:')
|
||||
? url
|
||||
: `https://${url}`
|
||||
|
Reference in New Issue
Block a user