2
0

feat(theme): Custom avatars

This commit is contained in:
Baptiste Arnaud
2022-02-16 15:08:50 +01:00
parent 1d3917f440
commit d2ac13ba5f
14 changed files with 294 additions and 81 deletions

View File

@ -1,13 +1,15 @@
import React, { useEffect, useState } from 'react'
import { useTypebot } from '../../contexts/TypebotContext'
import { HostAvatar } from '../avatars/HostAvatar'
import { Avatar } from '../avatars/Avatar'
import { useFrame } from 'react-frame-component'
import { CSSTransition, TransitionGroup } from 'react-transition-group'
import { useHostAvatars } from '../../contexts/HostAvatarsContext'
export const AvatarSideContainer = () => {
export const AvatarSideContainer = ({
hostAvatarSrc,
}: {
hostAvatarSrc: string
}) => {
const { lastBubblesTopOffset } = useHostAvatars()
const { typebot } = useTypebot()
const { window, document } = useFrame()
const [marginBottom, setMarginBottom] = useState(
window.innerWidth < 400 ? 38 : 48
@ -44,7 +46,7 @@ export const AvatarSideContainer = () => {
transition: 'top 350ms ease-out',
}}
>
<HostAvatar typebotName={typebot.name} />
<Avatar avatarSrc={hostAvatarSrc} />
</div>
</CSSTransition>
))}

View File

@ -15,6 +15,7 @@ import {
import { executeLogic } from 'services/logic'
import { executeIntegration } from 'services/integration'
import { parseRetryStep, stepCanBeRetried } from 'services/inputs'
import { parseVariables } from 'index'
type ChatBlockProps = {
steps: PublicStep[]
@ -108,7 +109,13 @@ export const ChatBlock = ({
return (
<div className="flex">
<HostAvatarsContext>
<AvatarSideContainer />
{(typebot.theme.chat.hostAvatar?.isEnabled ?? true) && (
<AvatarSideContainer
hostAvatarSrc={parseVariables(typebot.variables)(
typebot.theme.chat.hostAvatar?.url
)}
/>
)}
<div className="flex flex-col w-full">
<TransitionGroup>
{displayedSteps

View File

@ -9,6 +9,8 @@ import { DateForm } from './inputs/DateForm'
import { ChoiceForm } from './inputs/ChoiceForm'
import { HostBubble } from './bubbles/HostBubble'
import { isInputValid } from 'services/inputs'
import { useTypebot } from 'contexts/TypebotContext'
import { parseVariables } from 'index'
export const ChatStep = ({
step,
@ -38,6 +40,7 @@ const InputChatStep = ({
step: InputStep
onSubmit: (value: string, isRetry: boolean) => void
}) => {
const { typebot } = useTypebot()
const { addNewAvatarOffset } = useHostAvatars()
const [answer, setAnswer] = useState<string>()
@ -52,7 +55,15 @@ const InputChatStep = ({
}
if (answer) {
return <GuestBubble message={answer} />
return (
<GuestBubble
message={answer}
showAvatar={typebot.theme.chat.guestAvatar?.isEnabled ?? false}
avatarSrc={parseVariables(typebot.variables)(
typebot.theme.chat.guestAvatar?.url
)}
/>
)
}
switch (step.type) {
case InputStepType.TEXT:

View File

@ -1,11 +1,18 @@
import { Avatar } from 'components/avatars/Avatar'
import React from 'react'
import { CSSTransition } from 'react-transition-group'
interface Props {
message: string
showAvatar: boolean
avatarSrc: string
}
export const GuestBubble = ({ message }: Props): JSX.Element => {
export const GuestBubble = ({
message,
showAvatar,
avatarSrc,
}: Props): JSX.Element => {
return (
<CSSTransition classNames="bubble" timeout={1000}>
<div className="flex justify-end mb-2 items-center">
@ -16,6 +23,7 @@ export const GuestBubble = ({ message }: Props): JSX.Element => {
>
{message}
</div>
{showAvatar && <Avatar avatarSrc={avatarSrc} />}
</div>
</div>
</CSSTransition>

View File

@ -0,0 +1,24 @@
import React from 'react'
import { DefaultAvatar } from './DefaultAvatar'
export const Avatar = ({ avatarSrc }: { avatarSrc: string }): JSX.Element => {
return (
<div className="w-full h-full rounded-full text-2xl md:text-4xl text-center xs:w-10 xs:h-10">
{avatarSrc !== '' ? (
<figure
className={
'flex justify-center items-center rounded-full text-white w-6 h-6 text-sm relative xs:w-full xs:h-full xs:text-xl'
}
>
<img
src={avatarSrc}
alt="Bot avatar"
className="rounded-full object-cover w-full h-full"
/>
</figure>
) : (
<DefaultAvatar />
)}
</div>
)
}

View File

@ -1,60 +1,46 @@
import React from 'react'
type DefaultAvatarProps = {
displayName?: string
size?: 'extra-small' | 'small' | 'medium' | 'large' | 'full'
className?: string
}
export const DefaultAvatar = ({
displayName,
}: DefaultAvatarProps): JSX.Element => {
export const DefaultAvatar = (): JSX.Element => {
return (
<figure
className={
'flex justify-center items-center rounded-full text-white w-6 h-6 text-sm relative xs:w-full xs:h-full xs:text-xl'
}
data-testid="default-avatar"
>
<Background
<svg
width="75"
height="75"
viewBox="0 0 75 75"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={
'absolute top-0 left-0 w-6 h-6 xs:w-full xs:h-full xs:text-xl'
}
/>
<p style={{ zIndex: 0 }}>{displayName && displayName[0].toUpperCase()}</p>
>
<mask id="mask0" x="0" y="0" mask-type="alpha">
<circle cx="37.5" cy="37.5" r="37.5" fill="#0042DA" />
</mask>
<g mask="url(#mask0)">
<rect x="-30" y="-43" width="131" height="154" fill="#0042DA" />
<rect
x="2.50413"
y="120.333"
width="81.5597"
height="86.4577"
rx="2.5"
transform="rotate(-52.6423 2.50413 120.333)"
stroke="#FED23D"
strokeWidth="5"
/>
<circle cx="76.5" cy="-1.5" r="29" stroke="#FF8E20" strokeWidth="5" />
<path
d="M-49.8224 22L-15.5 -40.7879L18.8224 22H-49.8224Z"
stroke="#F7F8FF"
strokeWidth="5"
/>
</g>
</svg>
</figure>
)
}
const Background = ({ className }: { className: string }) => (
<svg
width="75"
height="75"
viewBox="0 0 75 75"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<mask id="mask0" x="0" y="0" mask-type="alpha">
<circle cx="37.5" cy="37.5" r="37.5" fill="#0042DA" />
</mask>
<g mask="url(#mask0)">
<rect x="-30" y="-43" width="131" height="154" fill="#0042DA" />
<rect
x="2.50413"
y="120.333"
width="81.5597"
height="86.4577"
rx="2.5"
transform="rotate(-52.6423 2.50413 120.333)"
stroke="#FED23D"
strokeWidth="5"
/>
<circle cx="76.5" cy="-1.5" r="29" stroke="#FF8E20" strokeWidth="5" />
<path
d="M-49.8224 22L-15.5 -40.7879L18.8224 22H-49.8224Z"
stroke="#F7F8FF"
strokeWidth="5"
/>
</g>
</svg>
)

View File

@ -1,14 +0,0 @@
import React from 'react'
import { DefaultAvatar } from './DefaultAvatar'
export const HostAvatar = ({
typebotName,
}: {
typebotName: string
}): JSX.Element => {
return (
<div className="w-full h-full rounded-full text-2xl md:text-4xl text-center xs:w-10 xs:h-10">
<DefaultAvatar displayName={typebotName} />
</div>
)
}