⚡ (js) Add placement option for bubble embed
Allows you to place the bubble embed on the bottom left corner of your site
This commit is contained in:
@@ -4,17 +4,24 @@ import {
|
|||||||
AccordionIcon,
|
AccordionIcon,
|
||||||
AccordionItem,
|
AccordionItem,
|
||||||
AccordionPanel,
|
AccordionPanel,
|
||||||
|
Button,
|
||||||
HStack,
|
HStack,
|
||||||
|
Menu,
|
||||||
|
MenuButton,
|
||||||
|
MenuItem,
|
||||||
|
MenuList,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { BubbleProps } from '@typebot.io/js'
|
import { BubbleProps } from '@typebot.io/js'
|
||||||
import {
|
import {
|
||||||
|
BubbleTheme,
|
||||||
ButtonTheme,
|
ButtonTheme,
|
||||||
PreviewMessageTheme,
|
PreviewMessageTheme,
|
||||||
} from '@typebot.io/js/dist/features/bubble/types'
|
} from '@typebot.io/js/dist/features/bubble/types'
|
||||||
import { ButtonThemeSettings } from './ButtonThemeSettings'
|
import { ButtonThemeSettings } from './ButtonThemeSettings'
|
||||||
import { PreviewMessageThemeSettings } from './PreviewMessageThemeSettings'
|
import { PreviewMessageThemeSettings } from './PreviewMessageThemeSettings'
|
||||||
|
import { ChevronDownIcon } from '@/components/icons'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isPreviewMessageEnabled: boolean
|
isPreviewMessageEnabled: boolean
|
||||||
@@ -41,6 +48,13 @@ export const ThemeSettings = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updatePlacement = (placement: BubbleTheme['placement']) => {
|
||||||
|
onChange({
|
||||||
|
...theme,
|
||||||
|
placement,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Accordion allowMultiple>
|
<Accordion allowMultiple>
|
||||||
<AccordionItem>
|
<AccordionItem>
|
||||||
@@ -51,6 +65,22 @@ export const ThemeSettings = ({
|
|||||||
<AccordionIcon />
|
<AccordionIcon />
|
||||||
</AccordionButton>
|
</AccordionButton>
|
||||||
<AccordionPanel as={Stack} pb={4} spacing={4} px="0">
|
<AccordionPanel as={Stack} pb={4} spacing={4} px="0">
|
||||||
|
<HStack justify="space-between">
|
||||||
|
<Text>Placement</Text>
|
||||||
|
<Menu>
|
||||||
|
<MenuButton as={Button} size="sm" rightIcon={<ChevronDownIcon />}>
|
||||||
|
{theme?.placement ?? 'right'}
|
||||||
|
</MenuButton>
|
||||||
|
<MenuList>
|
||||||
|
<MenuItem onClick={() => updatePlacement('right')}>
|
||||||
|
right
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={() => updatePlacement('left')}>
|
||||||
|
left
|
||||||
|
</MenuItem>
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
</HStack>
|
||||||
<ButtonThemeSettings
|
<ButtonThemeSettings
|
||||||
buttonTheme={theme?.button}
|
buttonTheme={theme?.button}
|
||||||
onChange={updateButtonTheme}
|
onChange={updateButtonTheme}
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ const parseBubbleTheme = (theme: BubbleProps['theme']): string => {
|
|||||||
const buttonThemeLine = parseButtonTheme(button)
|
const buttonThemeLine = parseButtonTheme(button)
|
||||||
const previewMessageThemeLine = parsePreviewMessageTheme(previewMessage)
|
const previewMessageThemeLine = parsePreviewMessageTheme(previewMessage)
|
||||||
const chatWindowThemeLine = parseChatWindowTheme(theme.chatWindow)
|
const chatWindowThemeLine = parseChatWindowTheme(theme.chatWindow)
|
||||||
const line = `theme: {${buttonThemeLine}${previewMessageThemeLine}${chatWindowThemeLine}},`
|
const placementLine = parseStringParam('placement', theme.placement, 'right')
|
||||||
|
const line = `theme: {${placementLine}${buttonThemeLine}${previewMessageThemeLine}${chatWindowThemeLine}},`
|
||||||
if (line === 'theme: {},') return ''
|
if (line === 'theme: {},') return ''
|
||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,15 @@ import { Typebot } from '@typebot.io/schemas'
|
|||||||
import { isCloudProdInstance } from '@/helpers/isCloudProdInstance'
|
import { isCloudProdInstance } from '@/helpers/isCloudProdInstance'
|
||||||
import packageJson from '../../../../../../../../packages/embeds/js/package.json'
|
import packageJson from '../../../../../../../../packages/embeds/js/package.json'
|
||||||
|
|
||||||
export const parseStringParam = (fieldName: string, fieldValue?: string) =>
|
export const parseStringParam = (
|
||||||
fieldValue ? `${fieldName}: "${fieldValue}",` : ``
|
fieldName: string,
|
||||||
|
fieldValue?: string,
|
||||||
|
defaultValue?: string
|
||||||
|
) => {
|
||||||
|
if (!fieldValue) return ''
|
||||||
|
if (isDefined(defaultValue) && fieldValue === defaultValue) return ''
|
||||||
|
return `${fieldName}: "${fieldValue}",`
|
||||||
|
}
|
||||||
|
|
||||||
export const parseNumberOrBoolParam = (
|
export const parseNumberOrBoolParam = (
|
||||||
fieldName: string,
|
fieldName: string,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/js",
|
"name": "@typebot.io/js",
|
||||||
"version": "0.0.59",
|
"version": "0.0.60",
|
||||||
"description": "Javascript library to display typebots on your website",
|
"description": "Javascript library to display typebots on your website",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ export const Bubble = (props: BubbleProps) => {
|
|||||||
<Show when={isPreviewMessageDisplayed()}>
|
<Show when={isPreviewMessageDisplayed()}>
|
||||||
<PreviewMessage
|
<PreviewMessage
|
||||||
{...previewMessage()}
|
{...previewMessage()}
|
||||||
|
placement={bubbleProps.theme?.placement}
|
||||||
previewMessageTheme={bubbleProps.theme?.previewMessage}
|
previewMessageTheme={bubbleProps.theme?.previewMessage}
|
||||||
buttonSize={bubbleProps.theme?.button?.size}
|
buttonSize={bubbleProps.theme?.button?.size}
|
||||||
onClick={handlePreviewMessageClick}
|
onClick={handlePreviewMessageClick}
|
||||||
@@ -137,6 +138,7 @@ export const Bubble = (props: BubbleProps) => {
|
|||||||
</Show>
|
</Show>
|
||||||
<BubbleButton
|
<BubbleButton
|
||||||
{...bubbleProps.theme?.button}
|
{...bubbleProps.theme?.button}
|
||||||
|
placement={bubbleProps.theme?.placement}
|
||||||
toggleBot={toggleBot}
|
toggleBot={toggleBot}
|
||||||
isBotOpened={isBotOpened()}
|
isBotOpened={isBotOpened()}
|
||||||
/>
|
/>
|
||||||
@@ -146,16 +148,20 @@ export const Bubble = (props: BubbleProps) => {
|
|||||||
height: 'calc(100% - 80px)',
|
height: 'calc(100% - 80px)',
|
||||||
transition:
|
transition:
|
||||||
'transform 200ms cubic-bezier(0, 1.2, 1, 1), opacity 150ms ease-out',
|
'transform 200ms cubic-bezier(0, 1.2, 1, 1), opacity 150ms ease-out',
|
||||||
'transform-origin': 'bottom right',
|
'transform-origin':
|
||||||
|
props.theme?.placement === 'left' ? 'bottom left' : 'bottom right',
|
||||||
transform: isBotOpened() ? 'scale3d(1, 1, 1)' : 'scale3d(0, 0, 1)',
|
transform: isBotOpened() ? 'scale3d(1, 1, 1)' : 'scale3d(0, 0, 1)',
|
||||||
'box-shadow': 'rgb(0 0 0 / 16%) 0px 5px 40px',
|
'box-shadow': 'rgb(0 0 0 / 16%) 0px 5px 40px',
|
||||||
'background-color': bubbleProps.theme?.chatWindow?.backgroundColor,
|
'background-color': bubbleProps.theme?.chatWindow?.backgroundColor,
|
||||||
'z-index': 42424242,
|
'z-index': 42424242,
|
||||||
}}
|
}}
|
||||||
class={
|
class={
|
||||||
'fixed sm:right-5 rounded-lg w-full sm:w-[400px] max-h-[704px]' +
|
'fixed rounded-lg w-full sm:w-[400px] max-h-[704px]' +
|
||||||
(isBotOpened() ? ' opacity-1' : ' opacity-0 pointer-events-none') +
|
(isBotOpened() ? ' opacity-1' : ' opacity-0 pointer-events-none') +
|
||||||
(props.theme?.button?.size === 'large' ? ' bottom-24' : ' bottom-20')
|
(props.theme?.button?.size === 'large'
|
||||||
|
? ' bottom-24'
|
||||||
|
: ' bottom-20') +
|
||||||
|
(props.theme?.placement === 'left' ? ' left-5' : ' right-5')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Show when={isBotStarted()}>
|
<Show when={isBotStarted()}>
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { Show } from 'solid-js'
|
import { Show } from 'solid-js'
|
||||||
import { isNotDefined, isSvgSrc } from '@typebot.io/lib'
|
import { isNotDefined, isSvgSrc } from '@typebot.io/lib'
|
||||||
import { ButtonTheme } from '../types'
|
import { BubbleTheme, ButtonTheme } from '../types'
|
||||||
import { isLight } from '@typebot.io/lib/hexToRgb'
|
import { isLight } from '@typebot.io/lib/hexToRgb'
|
||||||
|
|
||||||
type Props = ButtonTheme & {
|
type Props = Pick<BubbleTheme, 'placement'> &
|
||||||
|
ButtonTheme & {
|
||||||
isBotOpened: boolean
|
isBotOpened: boolean
|
||||||
toggleBot: () => void
|
toggleBot: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultButtonColor = '#0042DA'
|
const defaultButtonColor = '#0042DA'
|
||||||
const defaultDarkIconColor = '#27272A'
|
const defaultDarkIconColor = '#27272A'
|
||||||
@@ -20,8 +21,9 @@ export const BubbleButton = (props: Props) => (
|
|||||||
part="button"
|
part="button"
|
||||||
onClick={() => props.toggleBot()}
|
onClick={() => props.toggleBot()}
|
||||||
class={
|
class={
|
||||||
'fixed bottom-5 right-5 shadow-md rounded-full hover:scale-110 active:scale-95 transition-transform duration-200 flex justify-center items-center animate-fade-in' +
|
'fixed bottom-5 shadow-md rounded-full hover:scale-110 active:scale-95 transition-transform duration-200 flex justify-center items-center animate-fade-in' +
|
||||||
(props.size === 'large' ? ' w-16 h-16' : ' w-12 h-12')
|
(props.size === 'large' ? ' w-16 h-16' : ' w-12 h-12') +
|
||||||
|
(props.placement === 'left' ? ' left-5' : ' right-5')
|
||||||
}
|
}
|
||||||
style={{
|
style={{
|
||||||
'background-color': props.backgroundColor ?? defaultButtonColor,
|
'background-color': props.backgroundColor ?? defaultButtonColor,
|
||||||
@@ -49,6 +51,7 @@ export const BubbleButton = (props: Props) => (
|
|||||||
</Show>
|
</Show>
|
||||||
<Show when={props.customIconSrc && isImageSrc(props.customIconSrc)}>
|
<Show when={props.customIconSrc && isImageSrc(props.customIconSrc)}>
|
||||||
<img
|
<img
|
||||||
|
part="button-icon"
|
||||||
src={props.customIconSrc}
|
src={props.customIconSrc}
|
||||||
class={
|
class={
|
||||||
'duration-200 transition' +
|
'duration-200 transition' +
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import { createSignal, Show } from 'solid-js'
|
import { createSignal, Show } from 'solid-js'
|
||||||
import {
|
import {
|
||||||
|
BubbleTheme,
|
||||||
ButtonTheme,
|
ButtonTheme,
|
||||||
PreviewMessageParams,
|
PreviewMessageParams,
|
||||||
PreviewMessageTheme,
|
PreviewMessageTheme,
|
||||||
} from '../types'
|
} from '../types'
|
||||||
|
|
||||||
export type PreviewMessageProps = Pick<
|
export type PreviewMessageProps = Pick<BubbleTheme, 'placement'> &
|
||||||
PreviewMessageParams,
|
Pick<PreviewMessageParams, 'avatarUrl' | 'message'> & {
|
||||||
'avatarUrl' | 'message'
|
|
||||||
> & {
|
|
||||||
buttonSize: ButtonTheme['size']
|
buttonSize: ButtonTheme['size']
|
||||||
previewMessageTheme?: PreviewMessageTheme
|
previewMessageTheme?: PreviewMessageTheme
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
onCloseClick: () => void
|
onCloseClick: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultBackgroundColor = '#F7F8FF'
|
const defaultBackgroundColor = '#F7F8FF'
|
||||||
const defaultTextColor = '#303235'
|
const defaultTextColor = '#303235'
|
||||||
@@ -27,8 +26,9 @@ export const PreviewMessage = (props: PreviewMessageProps) => {
|
|||||||
part="preview-message"
|
part="preview-message"
|
||||||
onClick={() => props.onClick()}
|
onClick={() => props.onClick()}
|
||||||
class={
|
class={
|
||||||
'fixed right-5 max-w-[256px] rounded-md duration-200 flex items-center gap-4 shadow-md animate-fade-in cursor-pointer hover:shadow-lg p-4' +
|
'fixed max-w-[256px] rounded-md duration-200 flex items-center gap-4 shadow-md animate-fade-in cursor-pointer hover:shadow-lg p-4' +
|
||||||
(props.buttonSize === 'large' ? ' bottom-24' : ' bottom-20')
|
(props.buttonSize === 'large' ? ' bottom-24' : ' bottom-20') +
|
||||||
|
(props.placement === 'left' ? ' left-5' : ' right-5')
|
||||||
}
|
}
|
||||||
style={{
|
style={{
|
||||||
'background-color':
|
'background-color':
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export type BubbleTheme = {
|
|||||||
chatWindow?: ChatWindowTheme
|
chatWindow?: ChatWindowTheme
|
||||||
button?: ButtonTheme
|
button?: ButtonTheme
|
||||||
previewMessage?: PreviewMessageTheme
|
previewMessage?: PreviewMessageTheme
|
||||||
|
placement?: 'left' | 'right'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ChatWindowTheme = {
|
export type ChatWindowTheme = {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/react",
|
"name": "@typebot.io/react",
|
||||||
"version": "0.0.59",
|
"version": "0.0.60",
|
||||||
"description": "React library to display typebots on your website",
|
"description": "React library to display typebots on your website",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
Reference in New Issue
Block a user