✨ (theme) Add container theme options: border, shadow, filter (#1436)
Closes #1332
This commit is contained in:
@ -1,4 +1,3 @@
|
|||||||
# Make sure to change this to your own random string of 32 characters (https://docs.typebot.io/self-hosting/docker#2-add-the-required-configuration)
|
|
||||||
ENCRYPTION_SECRET=H+KbL/OFrqbEuDy/1zX8bsPG+spXri3S
|
ENCRYPTION_SECRET=H+KbL/OFrqbEuDy/1zX8bsPG+spXri3S
|
||||||
|
|
||||||
DATABASE_URL=postgresql://postgres:typebot@localhost:5432/typebot
|
DATABASE_URL=postgresql://postgres:typebot@localhost:5432/typebot
|
||||||
|
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@ -12,9 +12,10 @@
|
|||||||
"editor.tabSize": 2,
|
"editor.tabSize": 2,
|
||||||
"typescript.updateImportsOnFileMove.enabled": "always",
|
"typescript.updateImportsOnFileMove.enabled": "always",
|
||||||
"playwright.env": {
|
"playwright.env": {
|
||||||
"DATABASE_URL": "postgresql://postgres:typebot@localhost:5432/typebot",
|
"DATABASE_URL": "postgresql://postgres:typebot@127.0.0.1:5432/typebot",
|
||||||
"NEXT_PUBLIC_VIEWER_URL": "http://localhost:3001",
|
"NEXT_PUBLIC_VIEWER_URL": "http://localhost:3001",
|
||||||
"NEXTAUTH_URL": "http://localhost:3000"
|
"NEXTAUTH_URL": "http://localhost:3000",
|
||||||
|
"ENCRYPTION_SECRET": "H+KbL/OFrqbEuDy/1zX8bsPG+spXri3S"
|
||||||
},
|
},
|
||||||
"[prisma]": {
|
"[prisma]": {
|
||||||
"editor.defaultFormatter": "Prisma.prisma"
|
"editor.defaultFormatter": "Prisma.prisma"
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"format:check": "prettier --check ./src --ignore-path ../../.prettierignore"
|
"format:check": "prettier --check ./src --ignore-path ../../.prettierignore"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@typebot.io/theme": "workspace:*",
|
||||||
"@braintree/sanitize-url": "7.0.1",
|
"@braintree/sanitize-url": "7.0.1",
|
||||||
"@chakra-ui/anatomy": "2.1.1",
|
"@chakra-ui/anatomy": "2.1.1",
|
||||||
"@chakra-ui/react": "2.7.1",
|
"@chakra-ui/react": "2.7.1",
|
||||||
|
@ -35,10 +35,16 @@ const colorsSelection: `#${string}`[] = [
|
|||||||
type Props = {
|
type Props = {
|
||||||
value?: string
|
value?: string
|
||||||
defaultValue?: string
|
defaultValue?: string
|
||||||
|
isDisabled?: boolean
|
||||||
onColorChange: (color: string) => void
|
onColorChange: (color: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ColorPicker = ({ value, defaultValue, onColorChange }: Props) => {
|
export const ColorPicker = ({
|
||||||
|
value,
|
||||||
|
defaultValue,
|
||||||
|
isDisabled,
|
||||||
|
onColorChange,
|
||||||
|
}: Props) => {
|
||||||
const { t } = useTranslate()
|
const { t } = useTranslate()
|
||||||
const [color, setColor] = useState(defaultValue ?? '')
|
const [color, setColor] = useState(defaultValue ?? '')
|
||||||
const displayedValue = value ?? color
|
const displayedValue = value ?? color
|
||||||
@ -63,6 +69,7 @@ export const ColorPicker = ({ value, defaultValue, onColorChange }: Props) => {
|
|||||||
padding={0}
|
padding={0}
|
||||||
borderRadius={3}
|
borderRadius={3}
|
||||||
borderWidth={1}
|
borderWidth={1}
|
||||||
|
isDisabled={isDisabled}
|
||||||
>
|
>
|
||||||
<Box rounded="full" boxSize="14px" bgColor={displayedValue} />
|
<Box rounded="full" boxSize="14px" bgColor={displayedValue} />
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -141,6 +141,8 @@ export const UnsplashPicker = ({ imageSize, onImageSelect }: Props) => {
|
|||||||
fetchNewImages(query, 0)
|
fetchNewImages(query, 0)
|
||||||
}}
|
}}
|
||||||
withVariableButton={false}
|
withVariableButton={false}
|
||||||
|
debounceTimeout={500}
|
||||||
|
forceDebounce
|
||||||
/>
|
/>
|
||||||
<Link
|
<Link
|
||||||
isExternal
|
isExternal
|
||||||
|
@ -24,6 +24,7 @@ import { env } from '@typebot.io/env'
|
|||||||
import { MoreInfoTooltip } from '../MoreInfoTooltip'
|
import { MoreInfoTooltip } from '../MoreInfoTooltip'
|
||||||
|
|
||||||
export type TextInputProps = {
|
export type TextInputProps = {
|
||||||
|
forceDebounce?: boolean
|
||||||
defaultValue?: string
|
defaultValue?: string
|
||||||
onChange?: (value: string) => void
|
onChange?: (value: string) => void
|
||||||
debounceTimeout?: number
|
debounceTimeout?: number
|
||||||
@ -62,6 +63,7 @@ export const TextInput = forwardRef(function TextInput(
|
|||||||
autoComplete,
|
autoComplete,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
autoFocus,
|
autoFocus,
|
||||||
|
forceDebounce,
|
||||||
onChange: _onChange,
|
onChange: _onChange,
|
||||||
onFocus,
|
onFocus,
|
||||||
onKeyUp,
|
onKeyUp,
|
||||||
@ -83,7 +85,7 @@ export const TextInput = forwardRef(function TextInput(
|
|||||||
const onChange = useDebouncedCallback(
|
const onChange = useDebouncedCallback(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
_onChange ?? (() => {}),
|
_onChange ?? (() => {}),
|
||||||
env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
|
env.NEXT_PUBLIC_E2E_TEST && !forceDebounce ? 0 : debounceTimeout
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -73,7 +73,6 @@ export const PreviewDrawer = () => {
|
|||||||
right="0"
|
right="0"
|
||||||
top={`0`}
|
top={`0`}
|
||||||
h={`100%`}
|
h={`100%`}
|
||||||
w={`${width}px`}
|
|
||||||
bgColor={useColorModeValue('white', 'gray.900')}
|
bgColor={useColorModeValue('white', 'gray.900')}
|
||||||
borderLeftWidth={'1px'}
|
borderLeftWidth={'1px'}
|
||||||
shadow="lg"
|
shadow="lg"
|
||||||
@ -82,6 +81,7 @@ export const PreviewDrawer = () => {
|
|||||||
onMouseLeave={() => setIsResizeHandleVisible(false)}
|
onMouseLeave={() => setIsResizeHandleVisible(false)}
|
||||||
p="6"
|
p="6"
|
||||||
zIndex={10}
|
zIndex={10}
|
||||||
|
style={{ width: `${width}px` }}
|
||||||
>
|
>
|
||||||
<Fade in={isResizeHandleVisible}>
|
<Fade in={isResizeHandleVisible}>
|
||||||
<ResizeHandle
|
<ResizeHandle
|
||||||
|
@ -5,13 +5,13 @@ import { Typebot } from '@typebot.io/schemas'
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { BubbleSettings } from '../../../settings/BubbleSettings/BubbleSettings'
|
import { BubbleSettings } from '../../../settings/BubbleSettings/BubbleSettings'
|
||||||
import { JavascriptBubbleSnippet } from '../JavascriptBubbleSnippet'
|
import { JavascriptBubbleSnippet } from '../JavascriptBubbleSnippet'
|
||||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
import { defaultButtonsBackgroundColor } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
|
|
||||||
export const parseDefaultBubbleTheme = (typebot?: Typebot) => ({
|
export const parseDefaultBubbleTheme = (typebot?: Typebot) => ({
|
||||||
button: {
|
button: {
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
typebot?.theme.chat?.buttons?.backgroundColor ??
|
typebot?.theme.chat?.buttons?.backgroundColor ??
|
||||||
defaultTheme.chat.buttons.backgroundColor,
|
defaultButtonsBackgroundColor,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ export const DefaultAvatar = (props: IconProps) => {
|
|||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
boxSize="40px"
|
boxSize="40px"
|
||||||
|
borderRadius="full"
|
||||||
data-testid="default-avatar"
|
data-testid="default-avatar"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
|
@ -35,7 +35,7 @@ export const ThemeSideMenu = () => {
|
|||||||
typebot &&
|
typebot &&
|
||||||
updateTypebot({ updates: { theme: { ...typebot.theme, customCss } } })
|
updateTypebot({ updates: { theme: { ...typebot.theme, customCss } } })
|
||||||
|
|
||||||
const selectedTemplate = (
|
const selectTemplate = (
|
||||||
selectedTemplate: Partial<Pick<ThemeTemplate, 'id' | 'theme'>>
|
selectedTemplate: Partial<Pick<ThemeTemplate, 'id' | 'theme'>>
|
||||||
) => {
|
) => {
|
||||||
if (!typebot) return
|
if (!typebot) return
|
||||||
@ -56,6 +56,8 @@ export const ThemeSideMenu = () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const templateId = typebot?.selectedThemeTemplateId ?? undefined
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
flex="1"
|
flex="1"
|
||||||
@ -84,12 +86,10 @@ export const ThemeSideMenu = () => {
|
|||||||
<AccordionPanel pb={12}>
|
<AccordionPanel pb={12}>
|
||||||
{typebot && (
|
{typebot && (
|
||||||
<ThemeTemplates
|
<ThemeTemplates
|
||||||
selectedTemplateId={
|
selectedTemplateId={templateId}
|
||||||
typebot.selectedThemeTemplateId ?? undefined
|
|
||||||
}
|
|
||||||
currentTheme={typebot.theme}
|
currentTheme={typebot.theme}
|
||||||
workspaceId={typebot.workspaceId}
|
workspaceId={typebot.workspaceId}
|
||||||
onTemplateSelect={selectedTemplate}
|
onTemplateSelect={selectTemplate}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
@ -106,6 +106,7 @@ export const ThemeSideMenu = () => {
|
|||||||
<AccordionPanel pb={4}>
|
<AccordionPanel pb={4}>
|
||||||
{typebot && (
|
{typebot && (
|
||||||
<GeneralSettings
|
<GeneralSettings
|
||||||
|
key={templateId}
|
||||||
isBrandingEnabled={
|
isBrandingEnabled={
|
||||||
typebot.settings.general?.isBrandingEnabled ??
|
typebot.settings.general?.isBrandingEnabled ??
|
||||||
defaultSettings.general.isBrandingEnabled
|
defaultSettings.general.isBrandingEnabled
|
||||||
@ -128,9 +129,11 @@ export const ThemeSideMenu = () => {
|
|||||||
<AccordionPanel pb={4}>
|
<AccordionPanel pb={4}>
|
||||||
{typebot && (
|
{typebot && (
|
||||||
<ChatThemeSettings
|
<ChatThemeSettings
|
||||||
|
key={templateId}
|
||||||
workspaceId={typebot.workspaceId}
|
workspaceId={typebot.workspaceId}
|
||||||
typebotId={typebot.id}
|
typebotId={typebot.id}
|
||||||
chatTheme={typebot.theme.chat}
|
chatTheme={typebot.theme.chat}
|
||||||
|
generalBackground={typebot.theme.general?.background}
|
||||||
onChatThemeChange={updateChatTheme}
|
onChatThemeChange={updateChatTheme}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -147,6 +150,7 @@ export const ThemeSideMenu = () => {
|
|||||||
<AccordionPanel pb={4}>
|
<AccordionPanel pb={4}>
|
||||||
{typebot && (
|
{typebot && (
|
||||||
<CustomCssSettings
|
<CustomCssSettings
|
||||||
|
key={templateId}
|
||||||
customCss={typebot.theme.customCss}
|
customCss={typebot.theme.customCss}
|
||||||
onCustomCssChange={updateCustomCss}
|
onCustomCssChange={updateCustomCss}
|
||||||
/>
|
/>
|
||||||
|
@ -19,8 +19,13 @@ import { Theme, ThemeTemplate } from '@typebot.io/schemas'
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { DefaultAvatar } from './DefaultAvatar'
|
import { DefaultAvatar } from './DefaultAvatar'
|
||||||
import {
|
import {
|
||||||
|
defaultButtonsBackgroundColor,
|
||||||
BackgroundType,
|
BackgroundType,
|
||||||
defaultTheme,
|
defaultGuestAvatarIsEnabled,
|
||||||
|
defaultGuestBubblesBackgroundColor,
|
||||||
|
defaultHostAvatarIsEnabled,
|
||||||
|
defaultBackgroundColor,
|
||||||
|
defaultHostBubblesBackgroundColor,
|
||||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
import { useTranslate } from '@tolgee/react'
|
import { useTranslate } from '@tolgee/react'
|
||||||
|
|
||||||
@ -71,28 +76,28 @@ export const ThemeTemplateCard = ({
|
|||||||
const hostAvatar = {
|
const hostAvatar = {
|
||||||
isEnabled:
|
isEnabled:
|
||||||
themeTemplate.theme.chat?.hostAvatar?.isEnabled ??
|
themeTemplate.theme.chat?.hostAvatar?.isEnabled ??
|
||||||
defaultTheme.chat.hostAvatar.isEnabled,
|
defaultHostAvatarIsEnabled,
|
||||||
url: themeTemplate.theme.chat?.hostAvatar?.url,
|
url: themeTemplate.theme.chat?.hostAvatar?.url,
|
||||||
}
|
}
|
||||||
|
|
||||||
const hostBubbleBgColor =
|
const hostBubbleBgColor =
|
||||||
themeTemplate.theme.chat?.hostBubbles?.backgroundColor ??
|
themeTemplate.theme.chat?.hostBubbles?.backgroundColor ??
|
||||||
defaultTheme.chat.hostBubbles.backgroundColor
|
defaultHostBubblesBackgroundColor
|
||||||
|
|
||||||
const guestAvatar = {
|
const guestAvatar = {
|
||||||
isEnabled:
|
isEnabled:
|
||||||
themeTemplate.theme.chat?.guestAvatar?.isEnabled ??
|
themeTemplate.theme.chat?.guestAvatar?.isEnabled ??
|
||||||
defaultTheme.chat.guestAvatar.isEnabled,
|
defaultGuestAvatarIsEnabled,
|
||||||
url: themeTemplate.theme.chat?.guestAvatar?.url,
|
url: themeTemplate.theme.chat?.guestAvatar?.url,
|
||||||
}
|
}
|
||||||
|
|
||||||
const guestBubbleBgColor =
|
const guestBubbleBgColor =
|
||||||
themeTemplate.theme.chat?.guestBubbles?.backgroundColor ??
|
themeTemplate.theme.chat?.guestBubbles?.backgroundColor ??
|
||||||
defaultTheme.chat.guestBubbles.backgroundColor
|
defaultGuestBubblesBackgroundColor
|
||||||
|
|
||||||
const buttonBgColor =
|
const buttonBgColor =
|
||||||
themeTemplate.theme.chat?.buttons?.backgroundColor ??
|
themeTemplate.theme.chat?.buttons?.backgroundColor ??
|
||||||
defaultTheme.chat.buttons.backgroundColor
|
defaultButtonsBackgroundColor
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
@ -197,8 +202,7 @@ const parseBackground = (
|
|||||||
case undefined:
|
case undefined:
|
||||||
case BackgroundType.COLOR:
|
case BackgroundType.COLOR:
|
||||||
return {
|
return {
|
||||||
backgroundColor:
|
backgroundColor: background?.content ?? defaultBackgroundColor,
|
||||||
background?.content ?? defaultTheme.general.background.content,
|
|
||||||
}
|
}
|
||||||
case BackgroundType.IMAGE:
|
case BackgroundType.IMAGE:
|
||||||
return { backgroundImage: `url(${background.content})` }
|
return { backgroundImage: `url(${background.content})` }
|
||||||
|
@ -0,0 +1,134 @@
|
|||||||
|
import { FormLabel, HStack, Stack } from '@chakra-ui/react'
|
||||||
|
import { ChatTheme, GeneralTheme } from '@typebot.io/schemas'
|
||||||
|
import React from 'react'
|
||||||
|
import {
|
||||||
|
defaultBlur,
|
||||||
|
defaultContainerBackgroundColor,
|
||||||
|
defaultContainerMaxHeight,
|
||||||
|
defaultContainerMaxWidth,
|
||||||
|
defaultDarkTextColor,
|
||||||
|
defaultLightTextColor,
|
||||||
|
defaultOpacity,
|
||||||
|
defaultRoundness,
|
||||||
|
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
|
import { ContainerThemeForm } from './ContainerThemeForm'
|
||||||
|
import { NumberInput } from '@/components/inputs'
|
||||||
|
import { DropdownList } from '@/components/DropdownList'
|
||||||
|
import { isChatContainerLight } from '@typebot.io/theme/isChatContainerLight'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
generalBackground: GeneralTheme['background']
|
||||||
|
container: ChatTheme['container']
|
||||||
|
onContainerChange: (container: ChatTheme['container'] | undefined) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ChatContainerForm = ({
|
||||||
|
generalBackground,
|
||||||
|
container,
|
||||||
|
onContainerChange,
|
||||||
|
}: Props) => {
|
||||||
|
const updateMaxWidth = (maxWidth?: number) =>
|
||||||
|
updateDimension('maxWidth', maxWidth, maxWidthUnit)
|
||||||
|
|
||||||
|
const updateMaxWidthUnit = (unit: string) =>
|
||||||
|
updateDimension('maxWidth', maxWidth, unit)
|
||||||
|
|
||||||
|
const updateMaxHeight = (maxHeight?: number) =>
|
||||||
|
updateDimension('maxHeight', maxHeight, maxHeightUnit)
|
||||||
|
|
||||||
|
const updateMaxHeightUnit = (unit: string) =>
|
||||||
|
updateDimension('maxHeight', maxHeight, unit)
|
||||||
|
|
||||||
|
const updateDimension = (
|
||||||
|
dimension: 'maxWidth' | 'maxHeight',
|
||||||
|
value: number | undefined,
|
||||||
|
unit: string
|
||||||
|
) =>
|
||||||
|
onContainerChange({
|
||||||
|
...container,
|
||||||
|
[dimension]: `${value}${unit}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { value: maxWidth, unit: maxWidthUnit } = parseValueAndUnit(
|
||||||
|
container?.maxWidth ?? defaultContainerMaxWidth
|
||||||
|
)
|
||||||
|
|
||||||
|
const { value: maxHeight, unit: maxHeightUnit } = parseValueAndUnit(
|
||||||
|
container?.maxHeight ?? defaultContainerMaxHeight
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<HStack justifyContent="space-between">
|
||||||
|
<FormLabel mb="0" mr="0">
|
||||||
|
Max width:
|
||||||
|
</FormLabel>
|
||||||
|
<HStack>
|
||||||
|
<NumberInput
|
||||||
|
size="sm"
|
||||||
|
width="100px"
|
||||||
|
defaultValue={maxWidth}
|
||||||
|
min={0}
|
||||||
|
step={10}
|
||||||
|
withVariableButton={false}
|
||||||
|
onValueChange={updateMaxWidth}
|
||||||
|
/>
|
||||||
|
<DropdownList
|
||||||
|
size="sm"
|
||||||
|
items={['px', '%', 'vh', 'vw']}
|
||||||
|
currentItem={maxWidthUnit}
|
||||||
|
onItemSelect={updateMaxWidthUnit}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
<HStack justifyContent="space-between">
|
||||||
|
<FormLabel mb="0" mr="0">
|
||||||
|
Max height:
|
||||||
|
</FormLabel>
|
||||||
|
<HStack>
|
||||||
|
<NumberInput
|
||||||
|
size="sm"
|
||||||
|
width="100px"
|
||||||
|
defaultValue={maxHeight}
|
||||||
|
min={0}
|
||||||
|
step={10}
|
||||||
|
onValueChange={updateMaxHeight}
|
||||||
|
withVariableButton={false}
|
||||||
|
/>
|
||||||
|
<DropdownList
|
||||||
|
size="sm"
|
||||||
|
items={['px', '%', 'vh', 'vw']}
|
||||||
|
currentItem={maxHeightUnit}
|
||||||
|
onItemSelect={updateMaxHeightUnit}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
<ContainerThemeForm
|
||||||
|
theme={container}
|
||||||
|
defaultTheme={{
|
||||||
|
backgroundColor: defaultContainerBackgroundColor,
|
||||||
|
border: {
|
||||||
|
roundeness: defaultRoundness,
|
||||||
|
},
|
||||||
|
blur: defaultBlur,
|
||||||
|
opacity: defaultOpacity,
|
||||||
|
color: isChatContainerLight({
|
||||||
|
chatContainer: container,
|
||||||
|
generalBackground,
|
||||||
|
})
|
||||||
|
? defaultLightTextColor
|
||||||
|
: defaultDarkTextColor,
|
||||||
|
}}
|
||||||
|
onThemeChange={onContainerChange}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseValueAndUnit = (valueWithUnit: string) => {
|
||||||
|
const value = parseFloat(valueWithUnit)
|
||||||
|
const unit = valueWithUnit.replace(value.toString(), '')
|
||||||
|
return { value, unit }
|
||||||
|
}
|
@ -1,23 +1,36 @@
|
|||||||
import {
|
|
||||||
LargeRadiusIcon,
|
|
||||||
MediumRadiusIcon,
|
|
||||||
NoRadiusIcon,
|
|
||||||
} from '@/components/icons'
|
|
||||||
import { RadioButtons } from '@/components/inputs/RadioButtons'
|
|
||||||
import { Heading, Stack } from '@chakra-ui/react'
|
import { Heading, Stack } from '@chakra-ui/react'
|
||||||
import { AvatarProps, ChatTheme, Theme } from '@typebot.io/schemas'
|
import {
|
||||||
|
AvatarProps,
|
||||||
|
ChatTheme,
|
||||||
|
GeneralTheme,
|
||||||
|
Theme,
|
||||||
|
} from '@typebot.io/schemas'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { AvatarForm } from './AvatarForm'
|
import { AvatarForm } from './AvatarForm'
|
||||||
import { ButtonsTheme } from './ButtonsTheme'
|
|
||||||
import { GuestBubbles } from './GuestBubbles'
|
|
||||||
import { HostBubbles } from './HostBubbles'
|
|
||||||
import { InputsTheme } from './InputsTheme'
|
|
||||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
|
||||||
import { useTranslate } from '@tolgee/react'
|
import { useTranslate } from '@tolgee/react'
|
||||||
|
import { ChatContainerForm } from './ChatContainerForm'
|
||||||
|
import { ContainerThemeForm } from './ContainerThemeForm'
|
||||||
|
import {
|
||||||
|
defaultButtonsBackgroundColor,
|
||||||
|
defaultButtonsColor,
|
||||||
|
defaultButtonsBorderThickness,
|
||||||
|
defaultGuestBubblesBackgroundColor,
|
||||||
|
defaultGuestBubblesColor,
|
||||||
|
defaultHostBubblesBackgroundColor,
|
||||||
|
defaultHostBubblesColor,
|
||||||
|
defaultInputsBackgroundColor,
|
||||||
|
defaultInputsColor,
|
||||||
|
defaultInputsPlaceholderColor,
|
||||||
|
defaultInputsShadow,
|
||||||
|
defaultOpacity,
|
||||||
|
defaultBlur,
|
||||||
|
defaultRoundness,
|
||||||
|
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workspaceId: string
|
workspaceId: string
|
||||||
typebotId: string
|
typebotId: string
|
||||||
|
generalBackground: GeneralTheme['background']
|
||||||
chatTheme: Theme['chat']
|
chatTheme: Theme['chat']
|
||||||
onChatThemeChange: (chatTheme: ChatTheme) => void
|
onChatThemeChange: (chatTheme: ChatTheme) => void
|
||||||
}
|
}
|
||||||
@ -26,6 +39,7 @@ export const ChatThemeSettings = ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
typebotId,
|
typebotId,
|
||||||
chatTheme,
|
chatTheme,
|
||||||
|
generalBackground,
|
||||||
onChatThemeChange,
|
onChatThemeChange,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { t } = useTranslate()
|
const { t } = useTranslate()
|
||||||
@ -44,6 +58,16 @@ export const ChatThemeSettings = ({
|
|||||||
const updateInputs = (inputs: NonNullable<Theme['chat']>['inputs']) =>
|
const updateInputs = (inputs: NonNullable<Theme['chat']>['inputs']) =>
|
||||||
onChatThemeChange({ ...chatTheme, inputs })
|
onChatThemeChange({ ...chatTheme, inputs })
|
||||||
|
|
||||||
|
const updateChatContainer = (
|
||||||
|
container: NonNullable<Theme['chat']>['container']
|
||||||
|
) => onChatThemeChange({ ...chatTheme, container })
|
||||||
|
|
||||||
|
const updateInputsPlaceholderColor = (placeholderColor: string) =>
|
||||||
|
onChatThemeChange({
|
||||||
|
...chatTheme,
|
||||||
|
inputs: { ...chatTheme?.inputs, placeholderColor },
|
||||||
|
})
|
||||||
|
|
||||||
const updateHostAvatar = (hostAvatar: AvatarProps) =>
|
const updateHostAvatar = (hostAvatar: AvatarProps) =>
|
||||||
onChatThemeChange({ ...chatTheme, hostAvatar })
|
onChatThemeChange({ ...chatTheme, hostAvatar })
|
||||||
|
|
||||||
@ -52,6 +76,14 @@ export const ChatThemeSettings = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={6}>
|
<Stack spacing={6}>
|
||||||
|
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
||||||
|
<Heading fontSize="lg">Container</Heading>
|
||||||
|
<ChatContainerForm
|
||||||
|
generalBackground={generalBackground}
|
||||||
|
container={chatTheme?.container}
|
||||||
|
onContainerChange={updateChatContainer}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
<AvatarForm
|
<AvatarForm
|
||||||
uploadFileProps={{
|
uploadFileProps={{
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@ -59,7 +91,7 @@ export const ChatThemeSettings = ({
|
|||||||
fileName: 'hostAvatar',
|
fileName: 'hostAvatar',
|
||||||
}}
|
}}
|
||||||
title={t('theme.sideMenu.chat.botAvatar')}
|
title={t('theme.sideMenu.chat.botAvatar')}
|
||||||
avatarProps={chatTheme?.hostAvatar ?? defaultTheme.chat.hostAvatar}
|
avatarProps={chatTheme?.hostAvatar}
|
||||||
isDefaultCheck
|
isDefaultCheck
|
||||||
onAvatarChange={updateHostAvatar}
|
onAvatarChange={updateHostAvatar}
|
||||||
/>
|
/>
|
||||||
@ -70,63 +102,83 @@ export const ChatThemeSettings = ({
|
|||||||
fileName: 'guestAvatar',
|
fileName: 'guestAvatar',
|
||||||
}}
|
}}
|
||||||
title={t('theme.sideMenu.chat.userAvatar')}
|
title={t('theme.sideMenu.chat.userAvatar')}
|
||||||
avatarProps={chatTheme?.guestAvatar ?? defaultTheme.chat.guestAvatar}
|
avatarProps={chatTheme?.guestAvatar}
|
||||||
onAvatarChange={updateGuestAvatar}
|
onAvatarChange={updateGuestAvatar}
|
||||||
/>
|
/>
|
||||||
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
||||||
<Heading fontSize="lg">{t('theme.sideMenu.chat.botBubbles')}</Heading>
|
<Heading fontSize="lg">{t('theme.sideMenu.chat.botBubbles')}</Heading>
|
||||||
<HostBubbles
|
<ContainerThemeForm
|
||||||
hostBubbles={chatTheme?.hostBubbles ?? defaultTheme.chat.hostBubbles}
|
testId="hostBubblesTheme"
|
||||||
onHostBubblesChange={updateHostBubbles}
|
theme={chatTheme?.hostBubbles}
|
||||||
|
onThemeChange={updateHostBubbles}
|
||||||
|
defaultTheme={{
|
||||||
|
backgroundColor: defaultHostBubblesBackgroundColor,
|
||||||
|
color: defaultHostBubblesColor,
|
||||||
|
opacity: defaultOpacity,
|
||||||
|
blur: defaultBlur,
|
||||||
|
border: {
|
||||||
|
roundeness: defaultRoundness,
|
||||||
|
},
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
||||||
<Heading fontSize="lg">{t('theme.sideMenu.chat.userBubbles')}</Heading>
|
<Heading fontSize="lg">{t('theme.sideMenu.chat.userBubbles')}</Heading>
|
||||||
<GuestBubbles
|
<ContainerThemeForm
|
||||||
guestBubbles={
|
testId="guestBubblesTheme"
|
||||||
chatTheme?.guestBubbles ?? defaultTheme.chat.guestBubbles
|
theme={chatTheme?.guestBubbles}
|
||||||
}
|
onThemeChange={updateGuestBubbles}
|
||||||
onGuestBubblesChange={updateGuestBubbles}
|
defaultTheme={{
|
||||||
|
backgroundColor: defaultGuestBubblesBackgroundColor,
|
||||||
|
color: defaultGuestBubblesColor,
|
||||||
|
opacity: defaultOpacity,
|
||||||
|
blur: defaultBlur,
|
||||||
|
border: {
|
||||||
|
roundeness: defaultRoundness,
|
||||||
|
},
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
||||||
<Heading fontSize="lg">{t('theme.sideMenu.chat.buttons')}</Heading>
|
<Heading fontSize="lg">{t('theme.sideMenu.chat.buttons')}</Heading>
|
||||||
<ButtonsTheme
|
<ContainerThemeForm
|
||||||
buttons={chatTheme?.buttons ?? defaultTheme.chat.buttons}
|
testId="buttonsTheme"
|
||||||
onButtonsChange={updateButtons}
|
theme={chatTheme?.buttons}
|
||||||
|
onThemeChange={updateButtons}
|
||||||
|
defaultTheme={{
|
||||||
|
backgroundColor: defaultButtonsBackgroundColor,
|
||||||
|
color: defaultButtonsColor,
|
||||||
|
opacity: defaultOpacity,
|
||||||
|
blur: defaultBlur,
|
||||||
|
border: {
|
||||||
|
roundeness: defaultRoundness,
|
||||||
|
thickness: defaultButtonsBorderThickness,
|
||||||
|
color:
|
||||||
|
chatTheme?.buttons?.backgroundColor ??
|
||||||
|
defaultButtonsBackgroundColor,
|
||||||
|
},
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
||||||
<Heading fontSize="lg">{t('theme.sideMenu.chat.inputs')}</Heading>
|
<Heading fontSize="lg">{t('theme.sideMenu.chat.inputs')}</Heading>
|
||||||
<InputsTheme
|
<ContainerThemeForm
|
||||||
inputs={chatTheme?.inputs ?? defaultTheme.chat.inputs}
|
testId="inputsTheme"
|
||||||
onInputsChange={updateInputs}
|
theme={chatTheme?.inputs}
|
||||||
/>
|
onThemeChange={updateInputs}
|
||||||
</Stack>
|
onPlaceholderColorChange={updateInputsPlaceholderColor}
|
||||||
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
defaultTheme={{
|
||||||
<Heading fontSize="lg">
|
backgroundColor: defaultInputsBackgroundColor,
|
||||||
{t('theme.sideMenu.chat.cornersRoundness')}
|
color: defaultInputsColor,
|
||||||
</Heading>
|
placeholderColor: defaultInputsPlaceholderColor,
|
||||||
<RadioButtons
|
shadow: defaultInputsShadow,
|
||||||
options={[
|
opacity: defaultOpacity,
|
||||||
{
|
blur: defaultBlur,
|
||||||
label: <NoRadiusIcon />,
|
border: {
|
||||||
value: 'none',
|
roundeness: defaultRoundness,
|
||||||
},
|
},
|
||||||
{
|
}}
|
||||||
label: <MediumRadiusIcon />,
|
|
||||||
value: 'medium',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: <LargeRadiusIcon />,
|
|
||||||
value: 'large',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
value={chatTheme?.roundness ?? defaultTheme.chat.roundness}
|
|
||||||
onSelect={(roundness) =>
|
|
||||||
onChatThemeChange({ ...chatTheme, roundness })
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -0,0 +1,297 @@
|
|||||||
|
import {
|
||||||
|
Stack,
|
||||||
|
FormLabel,
|
||||||
|
HStack,
|
||||||
|
Switch,
|
||||||
|
Accordion,
|
||||||
|
AccordionButton,
|
||||||
|
AccordionIcon,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionPanel,
|
||||||
|
} from '@chakra-ui/react'
|
||||||
|
import {
|
||||||
|
ContainerTheme,
|
||||||
|
ContainerBorderTheme,
|
||||||
|
InputTheme,
|
||||||
|
} from '@typebot.io/schemas'
|
||||||
|
import React from 'react'
|
||||||
|
import { ColorPicker } from '../../../../components/ColorPicker'
|
||||||
|
import { useTranslate } from '@tolgee/react'
|
||||||
|
import { NumberInput } from '@/components/inputs'
|
||||||
|
import { DropdownList } from '@/components/DropdownList'
|
||||||
|
import {
|
||||||
|
borderRoundness,
|
||||||
|
defaultOpacity,
|
||||||
|
shadows,
|
||||||
|
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
|
|
||||||
|
type Props<T extends ((placeholder: string) => void) | undefined> = {
|
||||||
|
theme: (T extends undefined ? ContainerTheme : InputTheme) | undefined
|
||||||
|
defaultTheme: T extends undefined ? ContainerTheme : InputTheme
|
||||||
|
placeholderColor?: T extends undefined ? never : string
|
||||||
|
testId?: string
|
||||||
|
onThemeChange: (
|
||||||
|
theme: T extends undefined ? ContainerTheme : InputTheme
|
||||||
|
) => void
|
||||||
|
onPlaceholderColorChange?: T
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ContainerThemeForm = <
|
||||||
|
T extends ((placeholder: string) => void) | undefined
|
||||||
|
>({
|
||||||
|
theme,
|
||||||
|
testId,
|
||||||
|
defaultTheme,
|
||||||
|
onPlaceholderColorChange,
|
||||||
|
onThemeChange,
|
||||||
|
}: Props<T>) => {
|
||||||
|
const { t } = useTranslate()
|
||||||
|
|
||||||
|
const updateBackgroundColor = (backgroundColor: string) =>
|
||||||
|
onThemeChange({ ...theme, backgroundColor })
|
||||||
|
|
||||||
|
const toggleBackgroundColor = () =>
|
||||||
|
onThemeChange({
|
||||||
|
...theme,
|
||||||
|
backgroundColor:
|
||||||
|
backgroundColor === 'transparent' ? '#ffffff' : 'transparent',
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateTextColor = (color: string) => onThemeChange({ ...theme, color })
|
||||||
|
|
||||||
|
const updateShadow = (shadow?: ContainerTheme['shadow']) =>
|
||||||
|
onThemeChange({ ...theme, shadow })
|
||||||
|
|
||||||
|
const updateBlur = (blur?: number) => onThemeChange({ ...theme, blur })
|
||||||
|
|
||||||
|
const updateOpacity = (opacity?: number) =>
|
||||||
|
onThemeChange({ ...theme, opacity })
|
||||||
|
|
||||||
|
const updateBorder = (border: ContainerBorderTheme) =>
|
||||||
|
onThemeChange({ ...theme, border })
|
||||||
|
|
||||||
|
const updatePlaceholderColor = (color: string) =>
|
||||||
|
onThemeChange({ ...theme, placeholderColor: color } as InputTheme)
|
||||||
|
|
||||||
|
const backgroundColor =
|
||||||
|
theme?.backgroundColor ?? defaultTheme?.backgroundColor
|
||||||
|
|
||||||
|
const shadow = theme?.shadow ?? defaultTheme?.shadow ?? 'none'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack spacing={4} data-testid={testId}>
|
||||||
|
<HStack justify="space-between">
|
||||||
|
<FormLabel mb="0" mr="0">
|
||||||
|
{t('theme.sideMenu.chat.theme.background')}
|
||||||
|
</FormLabel>
|
||||||
|
<HStack>
|
||||||
|
<Switch
|
||||||
|
defaultChecked={backgroundColor !== 'transparent'}
|
||||||
|
onChange={toggleBackgroundColor}
|
||||||
|
/>
|
||||||
|
<ColorPicker
|
||||||
|
isDisabled={backgroundColor === 'transparent'}
|
||||||
|
value={backgroundColor}
|
||||||
|
onColorChange={updateBackgroundColor}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
<HStack justify="space-between">
|
||||||
|
<FormLabel mb="0" mr="0">
|
||||||
|
{t('theme.sideMenu.chat.theme.text')}
|
||||||
|
</FormLabel>
|
||||||
|
<ColorPicker
|
||||||
|
value={theme?.color ?? defaultTheme?.color}
|
||||||
|
onColorChange={updateTextColor}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
{onPlaceholderColorChange && (
|
||||||
|
<HStack justify="space-between">
|
||||||
|
<FormLabel mb="0" mr="0">
|
||||||
|
{t('theme.sideMenu.chat.theme.placeholder')}
|
||||||
|
</FormLabel>
|
||||||
|
<ColorPicker
|
||||||
|
value={
|
||||||
|
theme && 'placeholderColor' in theme
|
||||||
|
? theme.placeholderColor
|
||||||
|
: defaultTheme && 'placeholderColor' in defaultTheme
|
||||||
|
? defaultTheme.placeholderColor
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
onColorChange={updatePlaceholderColor}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Accordion allowToggle>
|
||||||
|
<AccordionItem>
|
||||||
|
<AccordionButton justifyContent="space-between">
|
||||||
|
Border
|
||||||
|
<AccordionIcon />
|
||||||
|
</AccordionButton>
|
||||||
|
<AccordionPanel>
|
||||||
|
<BorderThemeForm
|
||||||
|
border={theme?.border}
|
||||||
|
defaultBorder={defaultTheme.border}
|
||||||
|
onBorderChange={updateBorder}
|
||||||
|
/>
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
|
<AccordionItem>
|
||||||
|
<AccordionButton justifyContent="space-between">
|
||||||
|
Advanced
|
||||||
|
<AccordionIcon />
|
||||||
|
</AccordionButton>
|
||||||
|
<AccordionPanel as={Stack}>
|
||||||
|
{backgroundColor !== 'transparent' && (
|
||||||
|
<>
|
||||||
|
<NumberInput
|
||||||
|
size="sm"
|
||||||
|
direction="row"
|
||||||
|
label="Opacity:"
|
||||||
|
width="100px"
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
step={0.1}
|
||||||
|
defaultValue={theme?.opacity ?? defaultTheme?.opacity}
|
||||||
|
onValueChange={updateOpacity}
|
||||||
|
withVariableButton={false}
|
||||||
|
/>
|
||||||
|
{(theme?.opacity ?? defaultTheme?.opacity) !== 1 && (
|
||||||
|
<NumberInput
|
||||||
|
size="sm"
|
||||||
|
direction="row"
|
||||||
|
label="Blur:"
|
||||||
|
suffix="px"
|
||||||
|
width="100px"
|
||||||
|
min={0}
|
||||||
|
defaultValue={theme?.blur ?? defaultTheme?.blur}
|
||||||
|
onValueChange={updateBlur}
|
||||||
|
withVariableButton={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<HStack justify="space-between">
|
||||||
|
<FormLabel mb="0" mr="0">
|
||||||
|
Shadow:
|
||||||
|
</FormLabel>
|
||||||
|
<HStack>
|
||||||
|
<DropdownList
|
||||||
|
currentItem={shadow}
|
||||||
|
onItemSelect={updateShadow}
|
||||||
|
items={shadows}
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const BorderThemeForm = ({
|
||||||
|
border,
|
||||||
|
defaultBorder,
|
||||||
|
onBorderChange,
|
||||||
|
}: {
|
||||||
|
border: ContainerBorderTheme | undefined
|
||||||
|
defaultBorder: ContainerBorderTheme | undefined
|
||||||
|
onBorderChange: (border: ContainerBorderTheme) => void
|
||||||
|
}) => {
|
||||||
|
const updateRoundness = (roundeness: (typeof borderRoundness)[number]) => {
|
||||||
|
onBorderChange({ ...border, roundeness })
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCustomRoundeness = (customRoundeness: number | undefined) => {
|
||||||
|
onBorderChange({ ...border, customRoundeness })
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateThickness = (thickness: number | undefined) => {
|
||||||
|
onBorderChange({ ...border, thickness })
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateColor = (color: string | undefined) => {
|
||||||
|
onBorderChange({ ...border, color })
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateOpacity = (opacity: number | undefined) => {
|
||||||
|
onBorderChange({ ...border, opacity })
|
||||||
|
}
|
||||||
|
|
||||||
|
const thickness = border?.thickness ?? defaultBorder?.thickness ?? 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<HStack justifyContent="space-between">
|
||||||
|
<FormLabel mb="0" mr="0">
|
||||||
|
Roundness:
|
||||||
|
</FormLabel>
|
||||||
|
<HStack>
|
||||||
|
<DropdownList
|
||||||
|
currentItem={border?.roundeness ?? defaultBorder?.roundeness}
|
||||||
|
onItemSelect={updateRoundness}
|
||||||
|
items={borderRoundness}
|
||||||
|
placeholder="md"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
{(border?.roundeness ?? defaultBorder?.roundeness) === 'custom' && (
|
||||||
|
<NumberInput
|
||||||
|
size="sm"
|
||||||
|
suffix="px"
|
||||||
|
width="60px"
|
||||||
|
min={0}
|
||||||
|
defaultValue={border?.customRoundeness}
|
||||||
|
onValueChange={updateCustomRoundeness}
|
||||||
|
withVariableButton={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
<HStack justifyContent="space-between">
|
||||||
|
<FormLabel mb="0" mr="0">
|
||||||
|
Thickness:
|
||||||
|
</FormLabel>
|
||||||
|
<NumberInput
|
||||||
|
size="sm"
|
||||||
|
suffix="px"
|
||||||
|
width="60px"
|
||||||
|
min={0}
|
||||||
|
defaultValue={thickness}
|
||||||
|
onValueChange={updateThickness}
|
||||||
|
withVariableButton={false}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{thickness > 0 && (
|
||||||
|
<>
|
||||||
|
<HStack justifyContent="space-between">
|
||||||
|
<FormLabel mb="0" mr="0">
|
||||||
|
Color:
|
||||||
|
</FormLabel>
|
||||||
|
<ColorPicker
|
||||||
|
value={border?.color ?? defaultBorder?.color}
|
||||||
|
onColorChange={updateColor}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
<NumberInput
|
||||||
|
size="sm"
|
||||||
|
direction="row"
|
||||||
|
label="Opacity:"
|
||||||
|
width="100px"
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
step={0.1}
|
||||||
|
defaultValue={border?.opacity ?? defaultOpacity}
|
||||||
|
onValueChange={updateOpacity}
|
||||||
|
withVariableButton={false}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
@ -1,13 +1,16 @@
|
|||||||
import { Stack, Flex, Text } from '@chakra-ui/react'
|
import { Stack, Flex, Text } from '@chakra-ui/react'
|
||||||
import { ContainerColors } from '@typebot.io/schemas'
|
import { ContainerTheme } from '@typebot.io/schemas'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ColorPicker } from '../../../../components/ColorPicker'
|
import { ColorPicker } from '../../../../components/ColorPicker'
|
||||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
|
||||||
import { useTranslate } from '@tolgee/react'
|
import { useTranslate } from '@tolgee/react'
|
||||||
|
import {
|
||||||
|
defaultGuestBubblesBackgroundColor,
|
||||||
|
defaultGuestBubblesColor,
|
||||||
|
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
guestBubbles: ContainerColors | undefined
|
guestBubbles: ContainerTheme | undefined
|
||||||
onGuestBubblesChange: (hostBubbles: ContainerColors | undefined) => void
|
onGuestBubblesChange: (hostBubbles: ContainerTheme | undefined) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GuestBubbles = ({ guestBubbles, onGuestBubblesChange }: Props) => {
|
export const GuestBubbles = ({ guestBubbles, onGuestBubblesChange }: Props) => {
|
||||||
@ -25,8 +28,7 @@ export const GuestBubbles = ({ guestBubbles, onGuestBubblesChange }: Props) => {
|
|||||||
<Text>{t('theme.sideMenu.chat.theme.background')}</Text>
|
<Text>{t('theme.sideMenu.chat.theme.background')}</Text>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
value={
|
value={
|
||||||
guestBubbles?.backgroundColor ??
|
guestBubbles?.backgroundColor ?? defaultGuestBubblesBackgroundColor
|
||||||
defaultTheme.chat.guestBubbles.backgroundColor
|
|
||||||
}
|
}
|
||||||
onColorChange={updateBackground}
|
onColorChange={updateBackground}
|
||||||
/>
|
/>
|
||||||
@ -34,7 +36,7 @@ export const GuestBubbles = ({ guestBubbles, onGuestBubblesChange }: Props) => {
|
|||||||
<Flex justify="space-between" align="center">
|
<Flex justify="space-between" align="center">
|
||||||
<Text>{t('theme.sideMenu.chat.theme.text')}</Text>
|
<Text>{t('theme.sideMenu.chat.theme.text')}</Text>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
value={guestBubbles?.color ?? defaultTheme.chat.guestBubbles.color}
|
value={guestBubbles?.color ?? defaultGuestBubblesColor}
|
||||||
onColorChange={updateText}
|
onColorChange={updateText}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import { Stack, Flex, Text } from '@chakra-ui/react'
|
import { Stack, Flex, Text } from '@chakra-ui/react'
|
||||||
import { ContainerColors } from '@typebot.io/schemas'
|
import { ContainerTheme } from '@typebot.io/schemas'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ColorPicker } from '../../../../components/ColorPicker'
|
import { ColorPicker } from '../../../../components/ColorPicker'
|
||||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
|
||||||
import { useTranslate } from '@tolgee/react'
|
import { useTranslate } from '@tolgee/react'
|
||||||
|
import {
|
||||||
|
defaultHostBubblesBackgroundColor,
|
||||||
|
defaultHostBubblesColor,
|
||||||
|
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
hostBubbles: ContainerColors | undefined
|
hostBubbles: ContainerTheme | undefined
|
||||||
onHostBubblesChange: (hostBubbles: ContainerColors | undefined) => void
|
onHostBubblesChange: (hostBubbles: ContainerTheme | undefined) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HostBubbles = ({ hostBubbles, onHostBubblesChange }: Props) => {
|
export const HostBubbles = ({ hostBubbles, onHostBubblesChange }: Props) => {
|
||||||
@ -24,8 +27,7 @@ export const HostBubbles = ({ hostBubbles, onHostBubblesChange }: Props) => {
|
|||||||
<Text>{t('theme.sideMenu.chat.theme.background')}</Text>
|
<Text>{t('theme.sideMenu.chat.theme.background')}</Text>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
value={
|
value={
|
||||||
hostBubbles?.backgroundColor ??
|
hostBubbles?.backgroundColor ?? defaultHostBubblesBackgroundColor
|
||||||
defaultTheme.chat.hostBubbles.backgroundColor
|
|
||||||
}
|
}
|
||||||
onColorChange={handleBackgroundChange}
|
onColorChange={handleBackgroundChange}
|
||||||
/>
|
/>
|
||||||
@ -33,7 +35,7 @@ export const HostBubbles = ({ hostBubbles, onHostBubblesChange }: Props) => {
|
|||||||
<Flex justify="space-between" align="center">
|
<Flex justify="space-between" align="center">
|
||||||
<Text>{t('theme.sideMenu.chat.theme.text')}</Text>
|
<Text>{t('theme.sideMenu.chat.theme.text')}</Text>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
value={hostBubbles?.color ?? defaultTheme.chat.hostBubbles.color}
|
value={hostBubbles?.color ?? defaultHostBubblesColor}
|
||||||
onColorChange={handleTextChange}
|
onColorChange={handleTextChange}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
import { Stack, Flex, Text } from '@chakra-ui/react'
|
|
||||||
import { InputColors, Theme } from '@typebot.io/schemas'
|
|
||||||
import React from 'react'
|
|
||||||
import { ColorPicker } from '../../../../components/ColorPicker'
|
|
||||||
import { useTranslate } from '@tolgee/react'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
inputs: NonNullable<Theme['chat']>['inputs']
|
|
||||||
onInputsChange: (buttons: InputColors) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const InputsTheme = ({ inputs, onInputsChange }: Props) => {
|
|
||||||
const { t } = useTranslate()
|
|
||||||
|
|
||||||
const handleBackgroundChange = (backgroundColor: string) =>
|
|
||||||
onInputsChange({ ...inputs, backgroundColor })
|
|
||||||
const handleTextChange = (color: string) =>
|
|
||||||
onInputsChange({ ...inputs, color })
|
|
||||||
const handlePlaceholderChange = (placeholderColor: string) =>
|
|
||||||
onInputsChange({ ...inputs, placeholderColor })
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack data-testid="inputs-theme">
|
|
||||||
<Flex justify="space-between" align="center">
|
|
||||||
<Text>{t('theme.sideMenu.chat.theme.background')}</Text>
|
|
||||||
<ColorPicker
|
|
||||||
value={inputs?.backgroundColor}
|
|
||||||
onColorChange={handleBackgroundChange}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
<Flex justify="space-between" align="center">
|
|
||||||
<Text>{t('theme.sideMenu.chat.theme.text')}</Text>
|
|
||||||
<ColorPicker value={inputs?.color} onColorChange={handleTextChange} />
|
|
||||||
</Flex>
|
|
||||||
<Flex justify="space-between" align="center">
|
|
||||||
<Text>{t('theme.sideMenu.chat.theme.placeholder')}</Text>
|
|
||||||
<ColorPicker
|
|
||||||
value={inputs?.placeholderColor}
|
|
||||||
onColorChange={handlePlaceholderChange}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
@ -16,7 +16,8 @@ import React from 'react'
|
|||||||
import { ColorPicker } from '../../../../components/ColorPicker'
|
import { ColorPicker } from '../../../../components/ColorPicker'
|
||||||
import {
|
import {
|
||||||
BackgroundType,
|
BackgroundType,
|
||||||
defaultTheme,
|
defaultBackgroundColor,
|
||||||
|
defaultBackgroundType,
|
||||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
import { useTranslate } from '@tolgee/react'
|
import { useTranslate } from '@tolgee/react'
|
||||||
|
|
||||||
@ -34,10 +35,7 @@ export const BackgroundContent = ({
|
|||||||
const handleContentChange = (content: string) =>
|
const handleContentChange = (content: string) =>
|
||||||
onBackgroundContentChange(content)
|
onBackgroundContentChange(content)
|
||||||
|
|
||||||
if (
|
if ((background?.type ?? defaultBackgroundType) === BackgroundType.IMAGE) {
|
||||||
(background?.type ?? defaultTheme.general.background.type) ===
|
|
||||||
BackgroundType.IMAGE
|
|
||||||
) {
|
|
||||||
if (!typebot) return null
|
if (!typebot) return null
|
||||||
return (
|
return (
|
||||||
<Popover isLazy placement="top">
|
<Popover isLazy placement="top">
|
||||||
@ -76,15 +74,12 @@ export const BackgroundContent = ({
|
|||||||
</Popover>
|
</Popover>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (
|
if ((background?.type ?? defaultBackgroundType) === BackgroundType.COLOR) {
|
||||||
(background?.type ?? defaultTheme.general.background.type) ===
|
|
||||||
BackgroundType.COLOR
|
|
||||||
) {
|
|
||||||
return (
|
return (
|
||||||
<Flex justify="space-between" align="center">
|
<Flex justify="space-between" align="center">
|
||||||
<Text>{t('theme.sideMenu.global.background.color')}</Text>
|
<Text>{t('theme.sideMenu.global.background.color')}</Text>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
value={background?.content ?? defaultTheme.general.background.content}
|
value={background?.content ?? defaultBackgroundColor}
|
||||||
onColorChange={handleContentChange}
|
onColorChange={handleContentChange}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { RadioButtons } from '@/components/inputs/RadioButtons'
|
import { RadioButtons } from '@/components/inputs/RadioButtons'
|
||||||
import { Stack, Text } from '@chakra-ui/react'
|
import { Stack } from '@chakra-ui/react'
|
||||||
import { Background } from '@typebot.io/schemas'
|
import { Background } from '@typebot.io/schemas'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { BackgroundContent } from './BackgroundContent'
|
import { BackgroundContent } from './BackgroundContent'
|
||||||
import {
|
import {
|
||||||
BackgroundType,
|
BackgroundType,
|
||||||
defaultTheme,
|
defaultBackgroundType,
|
||||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
import { useTranslate } from '@tolgee/react'
|
import { useTranslate } from '@tolgee/react'
|
||||||
|
|
||||||
@ -28,7 +28,6 @@ export const BackgroundSelector = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={4}>
|
<Stack spacing={4}>
|
||||||
<Text>{t('theme.sideMenu.global.background')}</Text>
|
|
||||||
<RadioButtons
|
<RadioButtons
|
||||||
options={[
|
options={[
|
||||||
{
|
{
|
||||||
@ -44,7 +43,7 @@ export const BackgroundSelector = ({
|
|||||||
value: BackgroundType.NONE,
|
value: BackgroundType.NONE,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
value={background?.type ?? defaultTheme.general.background.type}
|
value={background?.type ?? defaultBackgroundType}
|
||||||
onSelect={handleBackgroundTypeChange}
|
onSelect={handleBackgroundTypeChange}
|
||||||
/>
|
/>
|
||||||
<BackgroundContent
|
<BackgroundContent
|
||||||
|
@ -4,7 +4,11 @@ import {
|
|||||||
Stack,
|
Stack,
|
||||||
Switch,
|
Switch,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
Text,
|
Accordion,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionButton,
|
||||||
|
AccordionPanel,
|
||||||
|
AccordionIcon,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { Background, Font, ProgressBar, Theme } from '@typebot.io/schemas'
|
import { Background, Font, ProgressBar, Theme } from '@typebot.io/schemas'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
@ -16,7 +20,7 @@ import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
|||||||
import { ChangePlanModal } from '@/features/billing/components/ChangePlanModal'
|
import { ChangePlanModal } from '@/features/billing/components/ChangePlanModal'
|
||||||
import { useTranslate } from '@tolgee/react'
|
import { useTranslate } from '@tolgee/react'
|
||||||
import {
|
import {
|
||||||
defaultTheme,
|
defaultFontType,
|
||||||
fontTypes,
|
fontTypes,
|
||||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
import { trpc } from '@/lib/trpc'
|
import { trpc } from '@/lib/trpc'
|
||||||
@ -91,7 +95,7 @@ export const GeneralSettings = ({
|
|||||||
const fontType =
|
const fontType =
|
||||||
(typeof generalTheme?.font === 'string'
|
(typeof generalTheme?.font === 'string'
|
||||||
? 'Google'
|
? 'Google'
|
||||||
: generalTheme?.font?.type) ?? defaultTheme.general.font.type
|
: generalTheme?.font?.type) ?? defaultFontType
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={6}>
|
<Stack spacing={6}>
|
||||||
@ -115,23 +119,46 @@ export const GeneralSettings = ({
|
|||||||
onChange={updateBranding}
|
onChange={updateBranding}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
<ProgressBarForm
|
<Accordion allowToggle>
|
||||||
progressBar={generalTheme?.progressBar}
|
<AccordionItem>
|
||||||
onProgressBarChange={updateProgressBar}
|
<AccordionButton justifyContent="space-between">
|
||||||
/>
|
Progress Bar
|
||||||
<Stack>
|
<AccordionIcon />
|
||||||
<Text>{t('theme.sideMenu.global.font')}</Text>
|
</AccordionButton>
|
||||||
<RadioButtons
|
<AccordionPanel>
|
||||||
options={fontTypes}
|
<ProgressBarForm
|
||||||
defaultValue={fontType}
|
progressBar={generalTheme?.progressBar}
|
||||||
onSelect={updateFontType}
|
onProgressBarChange={updateProgressBar}
|
||||||
/>
|
/>
|
||||||
<FontForm font={generalTheme?.font} onFontChange={updateFont} />
|
</AccordionPanel>
|
||||||
</Stack>
|
</AccordionItem>
|
||||||
<BackgroundSelector
|
<AccordionItem>
|
||||||
background={generalTheme?.background}
|
<AccordionButton justifyContent="space-between">
|
||||||
onBackgroundChange={handleBackgroundChange}
|
{t('theme.sideMenu.global.font')}
|
||||||
/>
|
<AccordionIcon />
|
||||||
|
</AccordionButton>
|
||||||
|
<AccordionPanel as={Stack}>
|
||||||
|
<RadioButtons
|
||||||
|
options={fontTypes}
|
||||||
|
defaultValue={fontType}
|
||||||
|
onSelect={updateFontType}
|
||||||
|
/>
|
||||||
|
<FontForm font={generalTheme?.font} onFontChange={updateFont} />
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
|
<AccordionItem>
|
||||||
|
<AccordionButton justifyContent="space-between">
|
||||||
|
{t('theme.sideMenu.global.background')}
|
||||||
|
<AccordionIcon />
|
||||||
|
</AccordionButton>
|
||||||
|
<AccordionPanel>
|
||||||
|
<BackgroundSelector
|
||||||
|
background={generalTheme?.background}
|
||||||
|
onBackgroundChange={handleBackgroundChange}
|
||||||
|
/>
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Select } from '@/components/inputs/Select'
|
import { Select } from '@/components/inputs/Select'
|
||||||
import { env } from '@typebot.io/env'
|
import { env } from '@typebot.io/env'
|
||||||
import { GoogleFont } from '@typebot.io/schemas'
|
import { GoogleFont } from '@typebot.io/schemas'
|
||||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
import { defaultFontFamily } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -11,8 +11,7 @@ type Props = {
|
|||||||
|
|
||||||
export const GoogleFontForm = ({ font, onFontChange }: Props) => {
|
export const GoogleFontForm = ({ font, onFontChange }: Props) => {
|
||||||
const [currentFont, setCurrentFont] = useState(
|
const [currentFont, setCurrentFont] = useState(
|
||||||
(typeof font === 'string' ? font : font?.family) ??
|
(typeof font === 'string' ? font : font?.family) ?? defaultFontFamily
|
||||||
defaultTheme.general.font.family
|
|
||||||
)
|
)
|
||||||
const [googleFonts, setGoogleFonts] = useState<string[]>([])
|
const [googleFonts, setGoogleFonts] = useState<string[]>([])
|
||||||
|
|
||||||
|
@ -5,7 +5,11 @@ import { NumberInput } from '@/components/inputs'
|
|||||||
import { FormLabel, HStack } from '@chakra-ui/react'
|
import { FormLabel, HStack } from '@chakra-ui/react'
|
||||||
import { ProgressBar } from '@typebot.io/schemas'
|
import { ProgressBar } from '@typebot.io/schemas'
|
||||||
import {
|
import {
|
||||||
defaultTheme,
|
defaultProgressBarColor,
|
||||||
|
defaultProgressBarIsEnabled,
|
||||||
|
defaultProgressBarPlacement,
|
||||||
|
defaultProgressBarPosition,
|
||||||
|
defaultProgressBarThickness,
|
||||||
progressBarPlacements,
|
progressBarPlacements,
|
||||||
progressBarPositions,
|
progressBarPositions,
|
||||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
@ -37,18 +41,14 @@ export const ProgressBarForm = ({
|
|||||||
return (
|
return (
|
||||||
<SwitchWithRelatedSettings
|
<SwitchWithRelatedSettings
|
||||||
label={'Enable progress bar?'}
|
label={'Enable progress bar?'}
|
||||||
initialValue={
|
initialValue={progressBar?.isEnabled ?? defaultProgressBarIsEnabled}
|
||||||
progressBar?.isEnabled ?? defaultTheme.general.progressBar.isEnabled
|
|
||||||
}
|
|
||||||
onCheckChange={updateEnabled}
|
onCheckChange={updateEnabled}
|
||||||
>
|
>
|
||||||
<DropdownList
|
<DropdownList
|
||||||
size="sm"
|
size="sm"
|
||||||
direction="row"
|
direction="row"
|
||||||
label="Placement:"
|
label="Placement:"
|
||||||
currentItem={
|
currentItem={progressBar?.placement ?? defaultProgressBarPlacement}
|
||||||
progressBar?.placement ?? defaultTheme.general.progressBar.placement
|
|
||||||
}
|
|
||||||
onItemSelect={updatePlacement}
|
onItemSelect={updatePlacement}
|
||||||
items={progressBarPlacements}
|
items={progressBarPlacements}
|
||||||
/>
|
/>
|
||||||
@ -58,9 +58,7 @@ export const ProgressBarForm = ({
|
|||||||
Color:
|
Color:
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
defaultValue={
|
defaultValue={progressBar?.color ?? defaultProgressBarColor}
|
||||||
progressBar?.color ?? defaultTheme.general.progressBar.color
|
|
||||||
}
|
|
||||||
onColorChange={updateColor}
|
onColorChange={updateColor}
|
||||||
/>
|
/>
|
||||||
</HStack>
|
</HStack>
|
||||||
@ -69,9 +67,7 @@ export const ProgressBarForm = ({
|
|||||||
direction="row"
|
direction="row"
|
||||||
withVariableButton={false}
|
withVariableButton={false}
|
||||||
maxW="100px"
|
maxW="100px"
|
||||||
defaultValue={
|
defaultValue={progressBar?.thickness ?? defaultProgressBarThickness}
|
||||||
progressBar?.thickness ?? defaultTheme.general.progressBar.thickness
|
|
||||||
}
|
|
||||||
onValueChange={updateThickness}
|
onValueChange={updateThickness}
|
||||||
size="sm"
|
size="sm"
|
||||||
/>
|
/>
|
||||||
@ -80,9 +76,7 @@ export const ProgressBarForm = ({
|
|||||||
direction="row"
|
direction="row"
|
||||||
label="Position when embedded:"
|
label="Position when embedded:"
|
||||||
moreInfoTooltip='Select "fixed" to always position the progress bar at the top of the window even though your bot is embedded. Select "absolute" to position the progress bar at the top of the chat container.'
|
moreInfoTooltip='Select "fixed" to always position the progress bar at the top of the window even though your bot is embedded. Select "absolute" to position the progress bar at the top of the chat container.'
|
||||||
currentItem={
|
currentItem={progressBar?.position ?? defaultProgressBarPosition}
|
||||||
progressBar?.position ?? defaultTheme.general.progressBar.position
|
|
||||||
}
|
|
||||||
onItemSelect={updatePosition}
|
onItemSelect={updatePosition}
|
||||||
items={progressBarPositions}
|
items={progressBarPositions}
|
||||||
/>
|
/>
|
||||||
|
@ -3,6 +3,10 @@ import test, { expect } from '@playwright/test'
|
|||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { importTypebotInDatabase } from '@typebot.io/playwright/databaseActions'
|
import { importTypebotInDatabase } from '@typebot.io/playwright/databaseActions'
|
||||||
import { freeWorkspaceId } from '@typebot.io/playwright/databaseSetup'
|
import { freeWorkspaceId } from '@typebot.io/playwright/databaseSetup'
|
||||||
|
import {
|
||||||
|
defaultContainerMaxHeight,
|
||||||
|
defaultContainerMaxWidth,
|
||||||
|
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
|
|
||||||
const hostAvatarUrl =
|
const hostAvatarUrl =
|
||||||
'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1760&q=80'
|
'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1760&q=80'
|
||||||
@ -30,6 +34,7 @@ test.describe.parallel('Theme page', () => {
|
|||||||
await expect(page.locator('a:has-text("Made with Typebot")')).toBeHidden()
|
await expect(page.locator('a:has-text("Made with Typebot")')).toBeHidden()
|
||||||
|
|
||||||
// Font
|
// Font
|
||||||
|
await page.getByRole('button', { name: 'Font' }).click()
|
||||||
await page.getByRole('textbox').fill('Roboto Slab')
|
await page.getByRole('textbox').fill('Roboto Slab')
|
||||||
await page.getByRole('menuitem', { name: 'Roboto Slab' }).click()
|
await page.getByRole('menuitem', { name: 'Roboto Slab' }).click()
|
||||||
await expect(page.locator('.typebot-container')).toHaveCSS(
|
await expect(page.locator('.typebot-container')).toHaveCSS(
|
||||||
@ -42,6 +47,7 @@ test.describe.parallel('Theme page', () => {
|
|||||||
'background-color',
|
'background-color',
|
||||||
'rgba(0, 0, 0, 0)'
|
'rgba(0, 0, 0, 0)'
|
||||||
)
|
)
|
||||||
|
await page.getByRole('button', { name: 'Background' }).click()
|
||||||
await page.click('text=Color')
|
await page.click('text=Color')
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.getByRole('button', { name: 'Pick a color' }).click()
|
await page.getByRole('button', { name: 'Pick a color' }).click()
|
||||||
@ -82,6 +88,38 @@ test.describe.parallel('Theme page', () => {
|
|||||||
await expect(page.getByRole('button', { name: 'Go' })).toBeVisible()
|
await expect(page.getByRole('button', { name: 'Go' })).toBeVisible()
|
||||||
await page.click('button:has-text("Chat")')
|
await page.click('button:has-text("Chat")')
|
||||||
|
|
||||||
|
// Container
|
||||||
|
await expect(page.locator('.typebot-chat-view')).toHaveCSS(
|
||||||
|
'max-width',
|
||||||
|
defaultContainerMaxWidth
|
||||||
|
)
|
||||||
|
await page
|
||||||
|
.locator('div')
|
||||||
|
.filter({ hasText: /^Max width:px$/ })
|
||||||
|
.getByRole('spinbutton')
|
||||||
|
.fill('600')
|
||||||
|
await expect(page.locator('.typebot-chat-view')).toHaveCSS(
|
||||||
|
'max-width',
|
||||||
|
'600px'
|
||||||
|
)
|
||||||
|
await expect(page.locator('.typebot-chat-view')).toHaveCSS(
|
||||||
|
'max-height',
|
||||||
|
defaultContainerMaxHeight
|
||||||
|
)
|
||||||
|
await page
|
||||||
|
.locator('div')
|
||||||
|
.filter({ hasText: /^Max height:%$/ })
|
||||||
|
.getByRole('spinbutton')
|
||||||
|
.fill('80')
|
||||||
|
await expect(page.locator('.typebot-chat-view')).toHaveCSS(
|
||||||
|
'max-height',
|
||||||
|
'80%'
|
||||||
|
)
|
||||||
|
await expect(page.locator('.typebot-chat-view')).toHaveCSS(
|
||||||
|
'color',
|
||||||
|
'rgb(48, 50, 53)'
|
||||||
|
)
|
||||||
|
|
||||||
// Host avatar
|
// Host avatar
|
||||||
await expect(
|
await expect(
|
||||||
page.locator('[data-testid="default-avatar"]').nth(1)
|
page.locator('[data-testid="default-avatar"]').nth(1)
|
||||||
@ -102,39 +140,13 @@ test.describe.parallel('Theme page', () => {
|
|||||||
|
|
||||||
await expect(page.locator('.typebot-container img')).toBeHidden()
|
await expect(page.locator('.typebot-container img')).toBeHidden()
|
||||||
|
|
||||||
// Roundness
|
|
||||||
await expect(page.getByRole('button', { name: 'Go' })).toHaveCSS(
|
|
||||||
'border-radius',
|
|
||||||
'6px'
|
|
||||||
)
|
|
||||||
await page
|
|
||||||
.getByRole('region', { name: 'Chat' })
|
|
||||||
.getByRole('radiogroup')
|
|
||||||
.locator('div')
|
|
||||||
.first()
|
|
||||||
.click()
|
|
||||||
await expect(page.getByRole('button', { name: 'Go' })).toHaveCSS(
|
|
||||||
'border-radius',
|
|
||||||
'0px'
|
|
||||||
)
|
|
||||||
await page
|
|
||||||
.getByRole('region', { name: 'Chat' })
|
|
||||||
.getByRole('radiogroup')
|
|
||||||
.locator('div')
|
|
||||||
.nth(2)
|
|
||||||
.click()
|
|
||||||
await expect(page.getByRole('button', { name: 'Go' })).toHaveCSS(
|
|
||||||
'border-radius',
|
|
||||||
'20px'
|
|
||||||
)
|
|
||||||
|
|
||||||
// Host bubbles
|
// Host bubbles
|
||||||
await page.click(
|
await page.click(
|
||||||
'[data-testid="host-bubbles-theme"] >> [aria-label="Pick a color"] >> nth=0'
|
'[data-testid="hostBubblesTheme"] >> [aria-label="Pick a color"] >> nth=0'
|
||||||
)
|
)
|
||||||
await page.fill('input[value="#F7F8FF"]', '#2a9d8f')
|
await page.fill('input[value="#F7F8FF"]', '#2a9d8f')
|
||||||
await page.click(
|
await page.click(
|
||||||
'[data-testid="host-bubbles-theme"] >> [aria-label="Pick a color"] >> nth=1'
|
'[data-testid="hostBubblesTheme"] >> [aria-label="Pick a color"] >> nth=1'
|
||||||
)
|
)
|
||||||
await page.fill('input[value="#303235"]', '#ffffff')
|
await page.fill('input[value="#303235"]', '#ffffff')
|
||||||
const hostBubble = page.locator('[data-testid="host-bubble"] >> nth=-1')
|
const hostBubble = page.locator('[data-testid="host-bubble"] >> nth=-1')
|
||||||
@ -146,11 +158,11 @@ test.describe.parallel('Theme page', () => {
|
|||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
await page.click(
|
await page.click(
|
||||||
'[data-testid="buttons-theme"] >> [aria-label="Pick a color"] >> nth=0'
|
'[data-testid="buttonsTheme"] >> [aria-label="Pick a color"] >> nth=0'
|
||||||
)
|
)
|
||||||
await page.fill('input[value="#0042DA"]', '#7209b7')
|
await page.fill('input[value="#0042DA"]', '#7209b7')
|
||||||
await page.click(
|
await page.click(
|
||||||
'[data-testid="buttons-theme"] >> [aria-label="Pick a color"] >> nth=1'
|
'[data-testid="buttonsTheme"] >> [aria-label="Pick a color"] >> nth=1'
|
||||||
)
|
)
|
||||||
await page.fill('input[value="#FFFFFF"]', '#e9c46a')
|
await page.fill('input[value="#FFFFFF"]', '#e9c46a')
|
||||||
const button = page.getByRole('button', { name: 'Go' })
|
const button = page.getByRole('button', { name: 'Go' })
|
||||||
@ -159,11 +171,11 @@ test.describe.parallel('Theme page', () => {
|
|||||||
|
|
||||||
// Guest bubbles
|
// Guest bubbles
|
||||||
await page.click(
|
await page.click(
|
||||||
'[data-testid="guest-bubbles-theme"] >> [aria-label="Pick a color"] >> nth=0'
|
'[data-testid="guestBubblesTheme"] >> [aria-label="Pick a color"] >> nth=0'
|
||||||
)
|
)
|
||||||
await page.fill('input[value="#FF8E21"]', '#d8f3dc')
|
await page.fill('input[value="#FF8E21"]', '#d8f3dc')
|
||||||
await page.click(
|
await page.click(
|
||||||
'[data-testid="guest-bubbles-theme"] >> [aria-label="Pick a color"] >> nth=1'
|
'[data-testid="guestBubblesTheme"] >> [aria-label="Pick a color"] >> nth=1'
|
||||||
)
|
)
|
||||||
await page.fill('input[value="#FFFFFF"]', '#264653')
|
await page.fill('input[value="#FFFFFF"]', '#264653')
|
||||||
await page.getByRole('button', { name: 'Go' }).click()
|
await page.getByRole('button', { name: 'Go' }).click()
|
||||||
@ -192,11 +204,11 @@ test.describe.parallel('Theme page', () => {
|
|||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
// Input
|
// Input
|
||||||
await page.click(
|
await page.click(
|
||||||
'[data-testid="inputs-theme"] >> [aria-label="Pick a color"] >> nth=0'
|
'[data-testid="inputsTheme"] >> [aria-label="Pick a color"] >> nth=0'
|
||||||
)
|
)
|
||||||
await page.fill('input[value="#FFFFFF"]', '#ffe8d6')
|
await page.fill('input[value="#FFFFFF"]', '#ffe8d6')
|
||||||
await page.click(
|
await page.click(
|
||||||
'[data-testid="inputs-theme"] >> [aria-label="Pick a color"] >> nth=1'
|
'[data-testid="inputsTheme"] >> [aria-label="Pick a color"] >> nth=1'
|
||||||
)
|
)
|
||||||
await page.fill('input[value="#303235"]', '#023e8a')
|
await page.fill('input[value="#303235"]', '#023e8a')
|
||||||
const input = page.locator('.typebot-input')
|
const input = page.locator('.typebot-input')
|
||||||
|
@ -21342,6 +21342,70 @@
|
|||||||
"chat": {
|
"chat": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"container": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"maxWidth": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"maxHeight": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"backgroundColor": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"blur": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1
|
||||||
|
},
|
||||||
|
"shadow": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"sm",
|
||||||
|
"md",
|
||||||
|
"lg",
|
||||||
|
"xl",
|
||||||
|
"2xl"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"border": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"thickness": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"roundeness": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"medium",
|
||||||
|
"large",
|
||||||
|
"custom"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"customRoundeness": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"hostAvatar": {
|
"hostAvatar": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -21372,6 +21436,53 @@
|
|||||||
},
|
},
|
||||||
"color": {
|
"color": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"blur": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1
|
||||||
|
},
|
||||||
|
"shadow": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"sm",
|
||||||
|
"md",
|
||||||
|
"lg",
|
||||||
|
"xl",
|
||||||
|
"2xl"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"border": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"thickness": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"roundeness": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"medium",
|
||||||
|
"large",
|
||||||
|
"custom"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"customRoundeness": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -21383,6 +21494,53 @@
|
|||||||
},
|
},
|
||||||
"color": {
|
"color": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"blur": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1
|
||||||
|
},
|
||||||
|
"shadow": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"sm",
|
||||||
|
"md",
|
||||||
|
"lg",
|
||||||
|
"xl",
|
||||||
|
"2xl"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"border": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"thickness": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"roundeness": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"medium",
|
||||||
|
"large",
|
||||||
|
"custom"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"customRoundeness": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -21394,6 +21552,53 @@
|
|||||||
},
|
},
|
||||||
"color": {
|
"color": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"blur": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1
|
||||||
|
},
|
||||||
|
"shadow": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"sm",
|
||||||
|
"md",
|
||||||
|
"lg",
|
||||||
|
"xl",
|
||||||
|
"2xl"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"border": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"thickness": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"roundeness": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"medium",
|
||||||
|
"large",
|
||||||
|
"custom"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"customRoundeness": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -21406,6 +21611,53 @@
|
|||||||
"color": {
|
"color": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"blur": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1
|
||||||
|
},
|
||||||
|
"shadow": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"sm",
|
||||||
|
"md",
|
||||||
|
"lg",
|
||||||
|
"xl",
|
||||||
|
"2xl"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"border": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"thickness": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"roundeness": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"medium",
|
||||||
|
"large",
|
||||||
|
"custom"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"customRoundeness": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"placeholderColor": {
|
"placeholderColor": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@ -21417,7 +21669,8 @@
|
|||||||
"none",
|
"none",
|
||||||
"medium",
|
"medium",
|
||||||
"large"
|
"large"
|
||||||
]
|
],
|
||||||
|
"description": "Deprecated, use `container.border.roundeness` instead"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -6821,6 +6821,70 @@
|
|||||||
"chat": {
|
"chat": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"container": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"maxWidth": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"maxHeight": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"backgroundColor": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"blur": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1
|
||||||
|
},
|
||||||
|
"shadow": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"sm",
|
||||||
|
"md",
|
||||||
|
"lg",
|
||||||
|
"xl",
|
||||||
|
"2xl"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"border": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"thickness": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"roundeness": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"medium",
|
||||||
|
"large",
|
||||||
|
"custom"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"customRoundeness": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"hostAvatar": {
|
"hostAvatar": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -6851,6 +6915,53 @@
|
|||||||
},
|
},
|
||||||
"color": {
|
"color": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"blur": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1
|
||||||
|
},
|
||||||
|
"shadow": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"sm",
|
||||||
|
"md",
|
||||||
|
"lg",
|
||||||
|
"xl",
|
||||||
|
"2xl"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"border": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"thickness": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"roundeness": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"medium",
|
||||||
|
"large",
|
||||||
|
"custom"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"customRoundeness": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -6862,6 +6973,53 @@
|
|||||||
},
|
},
|
||||||
"color": {
|
"color": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"blur": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1
|
||||||
|
},
|
||||||
|
"shadow": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"sm",
|
||||||
|
"md",
|
||||||
|
"lg",
|
||||||
|
"xl",
|
||||||
|
"2xl"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"border": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"thickness": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"roundeness": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"medium",
|
||||||
|
"large",
|
||||||
|
"custom"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"customRoundeness": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -6873,6 +7031,53 @@
|
|||||||
},
|
},
|
||||||
"color": {
|
"color": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"blur": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1
|
||||||
|
},
|
||||||
|
"shadow": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"sm",
|
||||||
|
"md",
|
||||||
|
"lg",
|
||||||
|
"xl",
|
||||||
|
"2xl"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"border": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"thickness": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"roundeness": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"medium",
|
||||||
|
"large",
|
||||||
|
"custom"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"customRoundeness": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -6885,6 +7090,53 @@
|
|||||||
"color": {
|
"color": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"blur": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1
|
||||||
|
},
|
||||||
|
"shadow": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"sm",
|
||||||
|
"md",
|
||||||
|
"lg",
|
||||||
|
"xl",
|
||||||
|
"2xl"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"border": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"thickness": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"roundeness": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"medium",
|
||||||
|
"large",
|
||||||
|
"custom"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"customRoundeness": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"placeholderColor": {
|
"placeholderColor": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@ -6896,7 +7148,8 @@
|
|||||||
"none",
|
"none",
|
||||||
"medium",
|
"medium",
|
||||||
"large"
|
"large"
|
||||||
]
|
],
|
||||||
|
"description": "Deprecated, use `container.border.roundeness` instead"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -7,8 +7,11 @@ import { TypebotPageProps, TypebotPageV2 } from '@/components/TypebotPageV2'
|
|||||||
import { TypebotPageV3, TypebotV3PageProps } from '@/components/TypebotPageV3'
|
import { TypebotPageV3, TypebotV3PageProps } from '@/components/TypebotPageV3'
|
||||||
import { env } from '@typebot.io/env'
|
import { env } from '@typebot.io/env'
|
||||||
import prisma from '@typebot.io/lib/prisma'
|
import prisma from '@typebot.io/lib/prisma'
|
||||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
|
||||||
import { defaultSettings } from '@typebot.io/schemas/features/typebot/settings/constants'
|
import { defaultSettings } from '@typebot.io/schemas/features/typebot/settings/constants'
|
||||||
|
import {
|
||||||
|
defaultBackgroundColor,
|
||||||
|
defaultBackgroundType,
|
||||||
|
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
|
|
||||||
// Browsers that doesn't support ES modules and/or web components
|
// Browsers that doesn't support ES modules and/or web components
|
||||||
const incompatibleBrowsers = [
|
const incompatibleBrowsers = [
|
||||||
@ -109,9 +112,10 @@ const getTypebotFromPublicId = async (publicId?: string) => {
|
|||||||
? ({
|
? ({
|
||||||
name: publishedTypebot.typebot.name,
|
name: publishedTypebot.typebot.name,
|
||||||
publicId: publishedTypebot.typebot.publicId ?? null,
|
publicId: publishedTypebot.typebot.publicId ?? null,
|
||||||
background:
|
background: publishedTypebot.theme.general?.background ?? {
|
||||||
publishedTypebot.theme.general?.background ??
|
type: defaultBackgroundType,
|
||||||
defaultTheme.general.background,
|
content: defaultBackgroundColor,
|
||||||
|
},
|
||||||
isHideQueryParamsEnabled:
|
isHideQueryParamsEnabled:
|
||||||
publishedTypebot.settings.general?.isHideQueryParamsEnabled ??
|
publishedTypebot.settings.general?.isHideQueryParamsEnabled ??
|
||||||
defaultSettings.general.isHideQueryParamsEnabled,
|
defaultSettings.general.isHideQueryParamsEnabled,
|
||||||
@ -156,9 +160,10 @@ const getTypebotFromCustomDomain = async (customDomain: string) => {
|
|||||||
? ({
|
? ({
|
||||||
name: publishedTypebot.typebot.name,
|
name: publishedTypebot.typebot.name,
|
||||||
publicId: publishedTypebot.typebot.publicId ?? null,
|
publicId: publishedTypebot.typebot.publicId ?? null,
|
||||||
background:
|
background: publishedTypebot.theme.general?.background ?? {
|
||||||
publishedTypebot.theme.general?.background ??
|
type: defaultBackgroundType,
|
||||||
defaultTheme.general.background,
|
content: defaultBackgroundColor,
|
||||||
|
},
|
||||||
isHideQueryParamsEnabled:
|
isHideQueryParamsEnabled:
|
||||||
publishedTypebot.settings.general?.isHideQueryParamsEnabled ??
|
publishedTypebot.settings.general?.isHideQueryParamsEnabled ??
|
||||||
defaultSettings.general.isHideQueryParamsEnabled,
|
defaultSettings.general.isHideQueryParamsEnabled,
|
||||||
@ -233,7 +238,10 @@ const App = ({
|
|||||||
defaultSettings.general.isHideQueryParamsEnabled
|
defaultSettings.general.isHideQueryParamsEnabled
|
||||||
}
|
}
|
||||||
background={
|
background={
|
||||||
publishedTypebot.background ?? defaultTheme.general.background
|
publishedTypebot.background ?? {
|
||||||
|
type: defaultBackgroundType,
|
||||||
|
content: defaultBackgroundColor,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
metadata={publishedTypebot.metadata ?? {}}
|
metadata={publishedTypebot.metadata ?? {}}
|
||||||
font={publishedTypebot.font}
|
font={publishedTypebot.font}
|
||||||
|
@ -34,11 +34,14 @@ import { continueBotFlow } from './continueBotFlow'
|
|||||||
import { parseVariables } from '@typebot.io/variables/parseVariables'
|
import { parseVariables } from '@typebot.io/variables/parseVariables'
|
||||||
import { defaultSettings } from '@typebot.io/schemas/features/typebot/settings/constants'
|
import { defaultSettings } from '@typebot.io/schemas/features/typebot/settings/constants'
|
||||||
import { IntegrationBlockType } from '@typebot.io/schemas/features/blocks/integrations/constants'
|
import { IntegrationBlockType } from '@typebot.io/schemas/features/blocks/integrations/constants'
|
||||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
|
||||||
import { VisitedEdge } from '@typebot.io/prisma'
|
import { VisitedEdge } from '@typebot.io/prisma'
|
||||||
import { env } from '@typebot.io/env'
|
import { env } from '@typebot.io/env'
|
||||||
import { getFirstEdgeId } from './getFirstEdgeId'
|
import { getFirstEdgeId } from './getFirstEdgeId'
|
||||||
import { Reply } from './types'
|
import { Reply } from './types'
|
||||||
|
import {
|
||||||
|
defaultGuestAvatarIsEnabled,
|
||||||
|
defaultHostAvatarIsEnabled,
|
||||||
|
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
|
|
||||||
type StartParams =
|
type StartParams =
|
||||||
| ({
|
| ({
|
||||||
@ -385,12 +388,11 @@ const getResult = async ({
|
|||||||
|
|
||||||
const parseDynamicThemeInState = (theme: Theme) => {
|
const parseDynamicThemeInState = (theme: Theme) => {
|
||||||
const hostAvatarUrl =
|
const hostAvatarUrl =
|
||||||
theme.chat?.hostAvatar?.isEnabled ?? defaultTheme.chat.hostAvatar.isEnabled
|
theme.chat?.hostAvatar?.isEnabled ?? defaultHostAvatarIsEnabled
|
||||||
? theme.chat?.hostAvatar?.url
|
? theme.chat?.hostAvatar?.url
|
||||||
: undefined
|
: undefined
|
||||||
const guestAvatarUrl =
|
const guestAvatarUrl =
|
||||||
theme.chat?.guestAvatar?.isEnabled ??
|
theme.chat?.guestAvatar?.isEnabled ?? defaultGuestAvatarIsEnabled
|
||||||
defaultTheme.chat.guestAvatar.isEnabled
|
|
||||||
? theme.chat?.guestAvatar?.url
|
? theme.chat?.guestAvatar?.url
|
||||||
: undefined
|
: undefined
|
||||||
if (!hostAvatarUrl?.startsWith('{{') && !guestAvatarUrl?.startsWith('{{'))
|
if (!hostAvatarUrl?.startsWith('{{') && !guestAvatarUrl?.startsWith('{{'))
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
Background,
|
Background,
|
||||||
ChatTheme,
|
ChatTheme,
|
||||||
ContainerColors,
|
ContainerTheme,
|
||||||
GeneralTheme,
|
GeneralTheme,
|
||||||
InputColors,
|
InputTheme,
|
||||||
Theme,
|
Theme,
|
||||||
} from '@typebot.io/schemas'
|
} from '@typebot.io/schemas'
|
||||||
import { BackgroundType } from '@typebot.io/schemas/features/typebot/theme/constants'
|
import { BackgroundType } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
@ -66,7 +66,7 @@ const setChatTheme = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const setHostBubbles = (
|
const setHostBubbles = (
|
||||||
hostBubbles: ContainerColors,
|
hostBubbles: ContainerTheme,
|
||||||
documentStyle: CSSStyleDeclaration
|
documentStyle: CSSStyleDeclaration
|
||||||
) => {
|
) => {
|
||||||
if (hostBubbles.backgroundColor)
|
if (hostBubbles.backgroundColor)
|
||||||
@ -82,7 +82,7 @@ const setHostBubbles = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const setGuestBubbles = (
|
const setGuestBubbles = (
|
||||||
guestBubbles: ContainerColors,
|
guestBubbles: any,
|
||||||
documentStyle: CSSStyleDeclaration
|
documentStyle: CSSStyleDeclaration
|
||||||
) => {
|
) => {
|
||||||
if (guestBubbles.backgroundColor)
|
if (guestBubbles.backgroundColor)
|
||||||
@ -98,7 +98,7 @@ const setGuestBubbles = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const setButtons = (
|
const setButtons = (
|
||||||
buttons: ContainerColors,
|
buttons: ContainerTheme,
|
||||||
documentStyle: CSSStyleDeclaration
|
documentStyle: CSSStyleDeclaration
|
||||||
) => {
|
) => {
|
||||||
if (buttons.backgroundColor)
|
if (buttons.backgroundColor)
|
||||||
@ -113,7 +113,7 @@ const setButtons = (
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const setInputs = (inputs: InputColors, documentStyle: CSSStyleDeclaration) => {
|
const setInputs = (inputs: InputTheme, documentStyle: CSSStyleDeclaration) => {
|
||||||
if (inputs.backgroundColor)
|
if (inputs.backgroundColor)
|
||||||
documentStyle.setProperty(
|
documentStyle.setProperty(
|
||||||
cssVariableNames.chat.inputs.bgColor,
|
cssVariableNames.chat.inputs.bgColor,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/js",
|
"name": "@typebot.io/js",
|
||||||
"version": "0.2.64",
|
"version": "0.2.65",
|
||||||
"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",
|
||||||
@ -31,6 +31,7 @@
|
|||||||
"@typebot.io/env": "workspace:*",
|
"@typebot.io/env": "workspace:*",
|
||||||
"@typebot.io/lib": "workspace:*",
|
"@typebot.io/lib": "workspace:*",
|
||||||
"@typebot.io/schemas": "workspace:*",
|
"@typebot.io/schemas": "workspace:*",
|
||||||
|
"@typebot.io/theme": "workspace:*",
|
||||||
"@typebot.io/tsconfig": "workspace:*",
|
"@typebot.io/tsconfig": "workspace:*",
|
||||||
"@types/dompurify": "3.0.3",
|
"@types/dompurify": "3.0.3",
|
||||||
"autoprefixer": "10.4.14",
|
"autoprefixer": "10.4.14",
|
||||||
|
@ -2,47 +2,6 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
:host {
|
|
||||||
--typebot-container-bg-image: none;
|
|
||||||
--typebot-container-bg-color: transparent;
|
|
||||||
--typebot-container-font-family: 'Open Sans';
|
|
||||||
--typebot-container-color: #303235;
|
|
||||||
|
|
||||||
--typebot-button-bg-color: #0042da;
|
|
||||||
--typebot-button-bg-color-rgb: 0, 66, 218;
|
|
||||||
--typebot-button-color: #ffffff;
|
|
||||||
|
|
||||||
--typebot-checkbox-bg-color: #ffffff;
|
|
||||||
|
|
||||||
--typebot-host-bubble-bg-color: #f7f8ff;
|
|
||||||
--typebot-host-bubble-color: #303235;
|
|
||||||
|
|
||||||
--typebot-guest-bubble-bg-color: #ff8e21;
|
|
||||||
--typebot-guest-bubble-color: #ffffff;
|
|
||||||
|
|
||||||
--typebot-input-bg-color: #ffffff;
|
|
||||||
--typebot-input-color: #303235;
|
|
||||||
--typebot-input-placeholder-color: #9095a0;
|
|
||||||
|
|
||||||
--typebot-header-bg-color: #ffffff;
|
|
||||||
--typebot-header-color: #303235;
|
|
||||||
|
|
||||||
--selectable-base-alpha: 0;
|
|
||||||
|
|
||||||
--typebot-border-radius: 6px;
|
|
||||||
|
|
||||||
--typebot-progress-bar-position: fixed;
|
|
||||||
--typebot-progress-bar-bg-color: #f7f8ff;
|
|
||||||
--typebot-progress-bar-color: #0042da;
|
|
||||||
--typebot-progress-bar-height: 6px;
|
|
||||||
--typebot-progress-bar-top: 0;
|
|
||||||
--typebot-progress-bar-bottom: auto;
|
|
||||||
|
|
||||||
/* Phone input */
|
|
||||||
--PhoneInputCountryFlag-borderColor: transparent;
|
|
||||||
--PhoneInput-color--focus: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||||
.scrollable-container::-webkit-scrollbar {
|
.scrollable-container::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
@ -161,67 +120,100 @@ pre {
|
|||||||
.typebot-container {
|
.typebot-container {
|
||||||
background-image: var(--typebot-container-bg-image);
|
background-image: var(--typebot-container-bg-image);
|
||||||
background-color: var(--typebot-container-bg-color);
|
background-color: var(--typebot-container-bg-color);
|
||||||
|
background-position: center;
|
||||||
|
background-size: cover;
|
||||||
font-family: var(--typebot-container-font-family), -apple-system,
|
font-family: var(--typebot-container-font-family), -apple-system,
|
||||||
BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,
|
BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,
|
||||||
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||||
|
container-type: inline-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typebot-chat-view {
|
||||||
|
max-width: var(--typebot-chat-container-max-width);
|
||||||
|
background-color: rgba(
|
||||||
|
var(--typebot-chat-container-bg-rgb),
|
||||||
|
var(--typebot-chat-container-opacity)
|
||||||
|
);
|
||||||
|
color: rgb(var(--typebot-chat-container-color));
|
||||||
|
min-height: 100%;
|
||||||
|
backdrop-filter: blur(var(--typebot-chat-container-blur));
|
||||||
|
border-width: var(--typebot-chat-container-border-width);
|
||||||
|
border-color: rgba(
|
||||||
|
var(--typebot-chat-container-border-rgb),
|
||||||
|
var(--typebot-chat-container-border-opacity)
|
||||||
|
);
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
box-shadow: var(--typebot-chat-container-box-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (min-width: 480px) {
|
||||||
|
.typebot-chat-view {
|
||||||
|
min-height: var(--typebot-chat-container-max-height);
|
||||||
|
max-height: var(--typebot-chat-container-max-height);
|
||||||
|
border-radius: var(--typebot-chat-container-border-radius);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-button {
|
.typebot-button {
|
||||||
color: var(--typebot-button-color);
|
color: var(--typebot-button-color);
|
||||||
background-color: var(--typebot-button-bg-color);
|
background-color: rgba(
|
||||||
border: 1px solid var(--typebot-button-bg-color);
|
var(--typebot-button-bg-rgb),
|
||||||
border-radius: var(--typebot-border-radius);
|
var(--typebot-button-opacity)
|
||||||
|
);
|
||||||
|
border-width: var(--typebot-button-border-width);
|
||||||
|
border-color: rgba(
|
||||||
|
var(--typebot-button-border-rgb),
|
||||||
|
var(--typebot-button-border-opacity)
|
||||||
|
);
|
||||||
|
border-radius: var(--typebot-button-border-radius);
|
||||||
|
box-shadow: var(--typebot-button-box-shadow);
|
||||||
|
backdrop-filter: blur(var(--typebot-button-blur));
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-button.selectable {
|
|
||||||
color: var(--typebot-host-bubble-color);
|
|
||||||
background-color: var(--typebot-host-bubble-bg-color);
|
|
||||||
border: 1px solid var(--typebot-button-bg-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.typebot-selectable {
|
.typebot-selectable {
|
||||||
border: 1px solid
|
border-width: var(--typebot-button-border-width);
|
||||||
rgba(
|
border-color: rgba(
|
||||||
var(--typebot-button-bg-color-rgb),
|
var(--typebot-button-border-rgb),
|
||||||
calc(var(--selectable-base-alpha) + 0.25)
|
calc(var(--selectable-alpha-ratio) * 0.25)
|
||||||
);
|
);
|
||||||
border-radius: var(--typebot-border-radius);
|
border-radius: var(--typebot-button-border-radius);
|
||||||
color: var(--typebot-container-color);
|
color: rgb(var(--typebot-chat-container-color));
|
||||||
background-color: rgba(
|
background-color: rgba(
|
||||||
var(--typebot-button-bg-color-rgb),
|
var(--typebot-button-bg-rgb),
|
||||||
calc(var(--selectable-base-alpha) + 0.08)
|
calc(var(--selectable-alpha-ratio) * 0.08)
|
||||||
);
|
);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
backdrop-filter: blur(2px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-selectable:hover {
|
.typebot-selectable:hover {
|
||||||
background-color: rgba(
|
background-color: rgba(
|
||||||
var(--typebot-button-bg-color-rgb),
|
var(--typebot-button-bg-rgb),
|
||||||
calc(var(--selectable-base-alpha) + 0.12)
|
calc(var(--selectable-alpha-ratio) * 0.12)
|
||||||
);
|
);
|
||||||
border-color: rgba(
|
border-color: rgba(
|
||||||
var(--typebot-button-bg-color-rgb),
|
var(--typebot-button-border-rgb),
|
||||||
calc(var(--selectable-base-alpha) + 0.3)
|
calc(var(--selectable-alpha-ratio) * 0.3)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-selectable.selected {
|
.typebot-selectable.selected {
|
||||||
background-color: rgba(
|
background-color: rgba(
|
||||||
var(--typebot-button-bg-color-rgb),
|
var(--typebot-button-bg-rgb),
|
||||||
calc(var(--selectable-base-alpha) + 0.18)
|
calc(var(--selectable-alpha-ratio) * 0.18)
|
||||||
);
|
);
|
||||||
border-color: rgba(
|
border-color: rgba(
|
||||||
var(--typebot-button-bg-color-rgb),
|
var(--typebot-button-border-rgb),
|
||||||
calc(var(--selectable-base-alpha) + 0.35)
|
calc(var(--selectable-alpha-ratio) * 0.35)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-checkbox {
|
.typebot-checkbox {
|
||||||
border: 1px solid var(--typebot-button-bg-color);
|
border: 1px solid
|
||||||
border-radius: var(--typebot-border-radius);
|
rgba(var(--typebot-button-bg-rgb), var(--typebot-button-opacity));
|
||||||
background-color: var(--typebot-checkbox-bg-color);
|
border-radius: var(--typebot-button-border-radius);
|
||||||
|
background-color: rgba(var(--typebot-checkbox-bg-rgb));
|
||||||
color: var(--typebot-button-color);
|
color: var(--typebot-button-color);
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
@ -229,7 +221,7 @@ pre {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.typebot-checkbox.checked {
|
.typebot-checkbox.checked {
|
||||||
background-color: var(--typebot-button-bg-color);
|
background-color: rgb(var(--typebot-button-bg-rgb));
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-host-bubble {
|
.typebot-host-bubble {
|
||||||
@ -237,22 +229,56 @@ pre {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.typebot-host-bubble > .bubble-typing {
|
.typebot-host-bubble > .bubble-typing {
|
||||||
background-color: var(--typebot-host-bubble-bg-color);
|
background-color: rgba(
|
||||||
border: var(--typebot-host-bubble-border);
|
var(--typebot-host-bubble-bg-rgb),
|
||||||
|
var(--typebot-host-bubble-opacity)
|
||||||
|
);
|
||||||
|
border-width: var(--typebot-host-bubble-border-width);
|
||||||
|
border-color: rgba(
|
||||||
|
var(--typebot-host-bubble-border-rgb),
|
||||||
|
var(--typebot-host-bubble-border-opacity)
|
||||||
|
);
|
||||||
|
border-radius: var(--typebot-host-bubble-border-radius);
|
||||||
|
box-shadow: var(--typebot-host-bubble-box-shadow);
|
||||||
|
backdrop-filter: blur(var(--typebot-host-bubble-blur));
|
||||||
|
}
|
||||||
|
|
||||||
|
.typebot-host-bubble img,
|
||||||
|
.typebot-host-bubble video,
|
||||||
|
.typebot-host-bubble iframe {
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-guest-bubble {
|
.typebot-guest-bubble {
|
||||||
color: var(--typebot-guest-bubble-color);
|
color: var(--typebot-guest-bubble-color);
|
||||||
background-color: var(--typebot-guest-bubble-bg-color);
|
background-color: rgba(
|
||||||
border-radius: 6px;
|
var(--typebot-guest-bubble-bg-rgb),
|
||||||
|
var(--typebot-guest-bubble-opacity)
|
||||||
|
);
|
||||||
|
border-width: var(--typebot-guest-bubble-border-width);
|
||||||
|
border-color: rgba(
|
||||||
|
var(--typebot-guest-bubble-border-rgb),
|
||||||
|
var(--typebot-guest-bubble-border-opacity)
|
||||||
|
);
|
||||||
|
border-radius: var(--typebot-guest-bubble-border-radius);
|
||||||
|
box-shadow: var(--typebot-guest-bubble-box-shadow);
|
||||||
|
backdrop-filter: blur(var(--typebot-guest-bubble-blur));
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-input {
|
.typebot-input {
|
||||||
color: var(--typebot-input-color);
|
color: var(--typebot-input-color);
|
||||||
background-color: var(--typebot-input-bg-color);
|
background-color: rgba(
|
||||||
box-shadow: 0 2px 6px -1px rgba(0, 0, 0, 0.1);
|
var(--typebot-input-bg-rgb),
|
||||||
border-radius: var(--typebot-border-radius);
|
var(--typebot-input-opacity)
|
||||||
|
);
|
||||||
|
border-width: var(--typebot-input-border-width);
|
||||||
|
border-color: rgba(
|
||||||
|
var(--typebot-input-border-rgb),
|
||||||
|
var(--typebot-input-border-opacity)
|
||||||
|
);
|
||||||
|
border-radius: var(--typebot-input-border-radius);
|
||||||
|
box-shadow: var(--typebot-input-box-shadow);
|
||||||
|
backdrop-filter: blur(var(--typebot-input-blur));
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-input-error-message {
|
.typebot-input-error-message {
|
||||||
@ -263,24 +289,20 @@ pre {
|
|||||||
fill: var(--typebot-button-color);
|
fill: var(--typebot-button-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-chat-view {
|
|
||||||
max-width: 900px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ping span {
|
.ping span {
|
||||||
background-color: var(--typebot-button-bg-color);
|
background-color: rgb(var(--typebot-button-bg-rgb));
|
||||||
}
|
}
|
||||||
|
|
||||||
.rating-icon-container svg {
|
.rating-icon-container svg {
|
||||||
width: 42px;
|
width: 42px;
|
||||||
height: 42px;
|
height: 42px;
|
||||||
stroke: var(--typebot-button-bg-color);
|
stroke: rgb(var(--typebot-button-bg-rgb));
|
||||||
fill: var(--typebot-host-bubble-bg-color);
|
fill: var(--typebot-host-bubble-bg-color);
|
||||||
transition: fill 100ms ease-out;
|
transition: fill 100ms ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rating-icon-container.selected svg {
|
.rating-icon-container.selected svg {
|
||||||
fill: var(--typebot-button-bg-color);
|
fill: rgb(var(--typebot-button-bg-rgb));
|
||||||
}
|
}
|
||||||
|
|
||||||
.rating-icon-container:hover svg {
|
.rating-icon-container:hover svg {
|
||||||
@ -292,59 +314,60 @@ pre {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.upload-progress-bar {
|
.upload-progress-bar {
|
||||||
background-color: var(--typebot-button-bg-color);
|
background-color: rgb(var(--typebot-button-bg-rgb));
|
||||||
border-radius: var(--typebot-border-radius);
|
border-radius: var(--typebot-input-border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.total-files-indicator {
|
.total-files-indicator {
|
||||||
background-color: var(--typebot-button-bg-color);
|
background-color: rgb(var(--typebot-button-bg-rgb));
|
||||||
color: var(--typebot-button-color);
|
color: var(--typebot-button-color);
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-upload-input {
|
.typebot-upload-input {
|
||||||
transition: border-color 100ms ease-out;
|
transition: border-color 100ms ease-out;
|
||||||
border-radius: var(--typebot-border-radius);
|
border-radius: var(--typebot-input-border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-upload-input.dragging-over {
|
.typebot-upload-input.dragging-over {
|
||||||
border-color: var(--typebot-button-bg-color);
|
border-color: rgb(var(--typebot-button-bg-rgb));
|
||||||
}
|
}
|
||||||
|
|
||||||
.secondary-button {
|
.secondary-button {
|
||||||
background-color: var(--typebot-host-bubble-bg-color);
|
background-color: var(--typebot-host-bubble-bg-color);
|
||||||
color: var(--typebot-host-bubble-color);
|
color: var(--typebot-host-bubble-color);
|
||||||
border-radius: var(--typebot-border-radius);
|
border-radius: var(--typebot-button-border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-country-select {
|
.typebot-country-select {
|
||||||
color: var(--typebot-input-color);
|
color: var(--typebot-input-color);
|
||||||
background-color: var(--typebot-input-bg-color);
|
background-color: var(--typebot-input-bg-color);
|
||||||
border-radius: var(--typebot-border-radius);
|
border-radius: var(--typebot-button-border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-date-input {
|
.typebot-date-input {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
color: var(--typebot-input-color);
|
color: var(--typebot-input-color);
|
||||||
background-color: var(--typebot-input-bg-color);
|
background-color: var(--typebot-input-bg-color);
|
||||||
border-radius: var(--typebot-border-radius);
|
border-radius: var(--typebot-input-border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-popup-blocked-toast {
|
.typebot-popup-blocked-toast {
|
||||||
border-radius: var(--typebot-border-radius);
|
border-radius: var(--typebot-input-border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-picture-button {
|
.typebot-picture-button {
|
||||||
color: var(--typebot-button-color);
|
color: var(--typebot-button-color);
|
||||||
background-color: var(--typebot-button-bg-color);
|
background-color: rgb(var(--typebot-button-bg-rgb));
|
||||||
border-radius: var(--typebot-border-radius);
|
border-radius: var(--typebot-button-border-radius);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
width: 236px;
|
width: 236px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-picture-button > img,
|
.typebot-picture-button > img,
|
||||||
.typebot-selectable-picture > img {
|
.typebot-selectable-picture > img {
|
||||||
border-radius: var(--typebot-border-radius) var(--typebot-border-radius) 0 0;
|
border-radius: var(--typebot-button-border-radius)
|
||||||
|
var(--typebot-button-border-radius) 0 0;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
@ -362,14 +385,14 @@ pre {
|
|||||||
.typebot-selectable-picture {
|
.typebot-selectable-picture {
|
||||||
border: 1px solid
|
border: 1px solid
|
||||||
rgba(
|
rgba(
|
||||||
var(--typebot-button-bg-color-rgb),
|
var(--typebot-button-bg-rgb),
|
||||||
calc(var(--selectable-base-alpha) + 0.25)
|
calc(var(--selectable-alpha-ratio) * 0.25)
|
||||||
);
|
);
|
||||||
border-radius: var(--typebot-border-radius);
|
border-radius: var(--typebot-button-border-radius);
|
||||||
color: var(--typebot-container-color);
|
color: rgb(var(--typebot-chat-container-color));
|
||||||
background-color: rgba(
|
background-color: rgba(
|
||||||
var(--typebot-button-bg-color-rgb),
|
var(--typebot-button-bg-rgb),
|
||||||
calc(var(--selectable-base-alpha) + 0.08)
|
calc(var(--selectable-alpha-ratio) * 0.08)
|
||||||
);
|
);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
width: 236px;
|
width: 236px;
|
||||||
@ -377,23 +400,23 @@ pre {
|
|||||||
|
|
||||||
.typebot-selectable-picture:hover {
|
.typebot-selectable-picture:hover {
|
||||||
background-color: rgba(
|
background-color: rgba(
|
||||||
var(--typebot-button-bg-color-rgb),
|
var(--typebot-button-bg-rgb),
|
||||||
calc(var(--selectable-base-alpha) + 0.12)
|
calc(var(--selectable-alpha-ratio) * 0.12)
|
||||||
);
|
);
|
||||||
border-color: rgba(
|
border-color: rgba(
|
||||||
var(--typebot-button-bg-color-rgb),
|
var(--typebot-button-bg-rgb),
|
||||||
calc(var(--selectable-base-alpha) + 0.3)
|
calc(var(--selectable-alpha-ratio) * 0.3)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-selectable-picture.selected {
|
.typebot-selectable-picture.selected {
|
||||||
background-color: rgba(
|
background-color: rgba(
|
||||||
var(--typebot-button-bg-color-rgb),
|
var(--typebot-button-bg-rgb),
|
||||||
calc(var(--selectable-base-alpha) + 0.18)
|
calc(var(--selectable-alpha-ratio) * 0.18)
|
||||||
);
|
);
|
||||||
border-color: rgba(
|
border-color: rgba(
|
||||||
var(--typebot-button-bg-color-rgb),
|
var(--typebot-button-bg-rgb),
|
||||||
calc(var(--selectable-base-alpha) + 0.35)
|
calc(var(--selectable-alpha-ratio) * 0.35)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,8 +427,8 @@ select option {
|
|||||||
|
|
||||||
.typebot-progress-bar-container {
|
.typebot-progress-bar-container {
|
||||||
background-color: rgba(
|
background-color: rgba(
|
||||||
var(--typebot-button-bg-color-rgb),
|
var(--typebot-button-bg-rgb),
|
||||||
calc(var(--selectable-base-alpha) + 0.12)
|
calc(var(--selectable-alpha-ratio) * 0.12)
|
||||||
);
|
);
|
||||||
|
|
||||||
height: var(--typebot-progress-bar-height);
|
height: var(--typebot-progress-bar-height);
|
||||||
|
@ -16,7 +16,6 @@ import {
|
|||||||
import { setCssVariablesValue } from '@/utils/setCssVariablesValue'
|
import { setCssVariablesValue } from '@/utils/setCssVariablesValue'
|
||||||
import immutableCss from '../assets/immutable.css'
|
import immutableCss from '../assets/immutable.css'
|
||||||
import { Font, InputBlock, StartFrom } from '@typebot.io/schemas'
|
import { Font, InputBlock, StartFrom } from '@typebot.io/schemas'
|
||||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { HTTPError } from 'ky'
|
import { HTTPError } from 'ky'
|
||||||
import { injectFont } from '@/utils/injectFont'
|
import { injectFont } from '@/utils/injectFont'
|
||||||
@ -25,6 +24,11 @@ import { Portal } from 'solid-js/web'
|
|||||||
import { defaultSettings } from '@typebot.io/schemas/features/typebot/settings/constants'
|
import { defaultSettings } from '@typebot.io/schemas/features/typebot/settings/constants'
|
||||||
import { persist } from '@/utils/persist'
|
import { persist } from '@/utils/persist'
|
||||||
import { setBotContainerHeight } from '@/utils/botContainerHeightSignal'
|
import { setBotContainerHeight } from '@/utils/botContainerHeightSignal'
|
||||||
|
import {
|
||||||
|
defaultFontFamily,
|
||||||
|
defaultFontType,
|
||||||
|
defaultProgressBarPosition,
|
||||||
|
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
|
|
||||||
export type BotProps = {
|
export type BotProps = {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@ -262,8 +266,10 @@ const BotContent = (props: BotContentProps) => {
|
|||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
injectFont(
|
injectFont(
|
||||||
props.initialChatReply.typebot.theme.general?.font ??
|
props.initialChatReply.typebot.theme.general?.font ?? {
|
||||||
defaultTheme.general.font
|
type: defaultFontType,
|
||||||
|
family: defaultFontFamily,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
if (!botContainer) return
|
if (!botContainer) return
|
||||||
setCssVariablesValue(
|
setCssVariablesValue(
|
||||||
@ -282,7 +288,7 @@ const BotContent = (props: BotContentProps) => {
|
|||||||
<div
|
<div
|
||||||
ref={botContainer}
|
ref={botContainer}
|
||||||
class={clsx(
|
class={clsx(
|
||||||
'relative flex w-full h-full text-base overflow-hidden bg-cover bg-center flex-col items-center typebot-container @container',
|
'relative flex w-full h-full text-base overflow-hidden flex-col justify-center items-center typebot-container',
|
||||||
props.class
|
props.class
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@ -296,8 +302,7 @@ const BotContent = (props: BotContentProps) => {
|
|||||||
when={
|
when={
|
||||||
props.progressBarRef &&
|
props.progressBarRef &&
|
||||||
(props.initialChatReply.typebot.theme.general?.progressBar
|
(props.initialChatReply.typebot.theme.general?.progressBar
|
||||||
?.position ?? defaultTheme.general.progressBar.position) ===
|
?.position ?? defaultProgressBarPosition) === 'fixed'
|
||||||
'fixed'
|
|
||||||
}
|
}
|
||||||
fallback={<ProgressBar value={progressValue() as number} />}
|
fallback={<ProgressBar value={progressValue() as number} />}
|
||||||
>
|
>
|
||||||
@ -306,7 +311,7 @@ const BotContent = (props: BotContentProps) => {
|
|||||||
</Portal>
|
</Portal>
|
||||||
</Show>
|
</Show>
|
||||||
</Show>
|
</Show>
|
||||||
<div class="flex w-full h-full justify-center">
|
<div class="flex w-full h-full justify-center items-center">
|
||||||
<ConversationContainer
|
<ConversationContainer
|
||||||
context={props.context}
|
context={props.context}
|
||||||
initialChatReply={props.initialChatReply}
|
initialChatReply={props.initialChatReply}
|
||||||
|
@ -6,8 +6,11 @@ import { HostBubble } from '../bubbles/HostBubble'
|
|||||||
import { InputChatBlock } from '../InputChatBlock'
|
import { InputChatBlock } from '../InputChatBlock'
|
||||||
import { AvatarSideContainer } from './AvatarSideContainer'
|
import { AvatarSideContainer } from './AvatarSideContainer'
|
||||||
import { StreamingBubble } from '../bubbles/StreamingBubble'
|
import { StreamingBubble } from '../bubbles/StreamingBubble'
|
||||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
|
||||||
import { defaultSettings } from '@typebot.io/schemas/features/typebot/settings/constants'
|
import { defaultSettings } from '@typebot.io/schemas/features/typebot/settings/constants'
|
||||||
|
import {
|
||||||
|
defaultGuestAvatarIsEnabled,
|
||||||
|
defaultHostAvatarIsEnabled,
|
||||||
|
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
|
|
||||||
type Props = Pick<ContinueChatResponse, 'messages' | 'input'> & {
|
type Props = Pick<ContinueChatResponse, 'messages' | 'input'> & {
|
||||||
theme: Theme
|
theme: Theme
|
||||||
@ -77,7 +80,7 @@ export const ChatChunk = (props: Props) => {
|
|||||||
<Show
|
<Show
|
||||||
when={
|
when={
|
||||||
(props.theme.chat?.hostAvatar?.isEnabled ??
|
(props.theme.chat?.hostAvatar?.isEnabled ??
|
||||||
defaultTheme.chat.hostAvatar.isEnabled) &&
|
defaultHostAvatarIsEnabled) &&
|
||||||
props.messages.length > 0
|
props.messages.length > 0
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -93,7 +96,7 @@ export const ChatChunk = (props: Props) => {
|
|||||||
style={{
|
style={{
|
||||||
'max-width':
|
'max-width':
|
||||||
props.theme.chat?.guestAvatar?.isEnabled ??
|
props.theme.chat?.guestAvatar?.isEnabled ??
|
||||||
defaultTheme.chat.guestAvatar.isEnabled
|
defaultGuestAvatarIsEnabled
|
||||||
? isMobile()
|
? isMobile()
|
||||||
? 'calc(100% - 32px - 32px)'
|
? 'calc(100% - 32px - 32px)'
|
||||||
: 'calc(100% - 48px - 48px)'
|
: 'calc(100% - 48px - 48px)'
|
||||||
@ -131,7 +134,7 @@ export const ChatChunk = (props: Props) => {
|
|||||||
chunkIndex={props.index}
|
chunkIndex={props.index}
|
||||||
hasHostAvatar={
|
hasHostAvatar={
|
||||||
props.theme.chat?.hostAvatar?.isEnabled ??
|
props.theme.chat?.hostAvatar?.isEnabled ??
|
||||||
defaultTheme.chat.hostAvatar.isEnabled
|
defaultHostAvatarIsEnabled
|
||||||
}
|
}
|
||||||
guestAvatar={props.theme.chat?.guestAvatar}
|
guestAvatar={props.theme.chat?.guestAvatar}
|
||||||
context={props.context}
|
context={props.context}
|
||||||
@ -151,7 +154,7 @@ export const ChatChunk = (props: Props) => {
|
|||||||
<Show
|
<Show
|
||||||
when={
|
when={
|
||||||
props.theme.chat?.hostAvatar?.isEnabled ??
|
props.theme.chat?.hostAvatar?.isEnabled ??
|
||||||
defaultTheme.chat.hostAvatar.isEnabled
|
defaultHostAvatarIsEnabled
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<AvatarSideContainer
|
<AvatarSideContainer
|
||||||
@ -165,7 +168,7 @@ export const ChatChunk = (props: Props) => {
|
|||||||
style={{
|
style={{
|
||||||
'max-width':
|
'max-width':
|
||||||
props.theme.chat?.hostAvatar?.isEnabled ??
|
props.theme.chat?.hostAvatar?.isEnabled ??
|
||||||
defaultTheme.chat.hostAvatar.isEnabled
|
defaultHostAvatarIsEnabled
|
||||||
? isMobile()
|
? isMobile()
|
||||||
? 'calc(100% - 32px - 32px)'
|
? 'calc(100% - 32px - 32px)'
|
||||||
: 'calc(100% - 48px - 48px)'
|
: 'calc(100% - 48px - 48px)'
|
||||||
|
@ -284,7 +284,7 @@ export const ConversationContainer = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={chatContainer}
|
ref={chatContainer}
|
||||||
class="flex flex-col overflow-y-auto w-full min-h-full px-3 pt-10 relative scrollable-container typebot-chat-view scroll-smooth gap-2"
|
class="flex flex-col overflow-y-auto w-full px-3 pt-10 relative scrollable-container typebot-chat-view scroll-smooth gap-2"
|
||||||
>
|
>
|
||||||
<For each={chatChunks()}>
|
<For each={chatChunks()}>
|
||||||
{(chatChunk, index) => (
|
{(chatChunk, index) => (
|
||||||
|
@ -2,7 +2,7 @@ import { Theme } from '@typebot.io/schemas'
|
|||||||
import { Show } from 'solid-js'
|
import { Show } from 'solid-js'
|
||||||
import { LoadingBubble } from '../bubbles/LoadingBubble'
|
import { LoadingBubble } from '../bubbles/LoadingBubble'
|
||||||
import { AvatarSideContainer } from './AvatarSideContainer'
|
import { AvatarSideContainer } from './AvatarSideContainer'
|
||||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
import { defaultHostAvatarIsEnabled } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
theme: Theme
|
theme: Theme
|
||||||
@ -15,7 +15,7 @@ export const LoadingChunk = (props: Props) => (
|
|||||||
<Show
|
<Show
|
||||||
when={
|
when={
|
||||||
props.theme.chat?.hostAvatar?.isEnabled ??
|
props.theme.chat?.hostAvatar?.isEnabled ??
|
||||||
defaultTheme.chat.hostAvatar.isEnabled
|
defaultHostAvatarIsEnabled
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<AvatarSideContainer
|
<AvatarSideContainer
|
||||||
|
@ -35,8 +35,8 @@ import { MultiplePictureChoice } from '@/features/blocks/inputs/pictureChoice/Mu
|
|||||||
import { formattedMessages } from '@/utils/formattedMessagesSignal'
|
import { formattedMessages } from '@/utils/formattedMessagesSignal'
|
||||||
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
|
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
|
||||||
import { defaultPaymentInputOptions } from '@typebot.io/schemas/features/blocks/inputs/payment/constants'
|
import { defaultPaymentInputOptions } from '@typebot.io/schemas/features/blocks/inputs/payment/constants'
|
||||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
|
||||||
import { persist } from '@/utils/persist'
|
import { persist } from '@/utils/persist'
|
||||||
|
import { defaultGuestAvatarIsEnabled } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
ref: HTMLDivElement | undefined
|
ref: HTMLDivElement | undefined
|
||||||
@ -82,8 +82,7 @@ export const InputChatBlock = (props: Props) => {
|
|||||||
<GuestBubble
|
<GuestBubble
|
||||||
message={formattedMessage() ?? (answer() as string)}
|
message={formattedMessage() ?? (answer() as string)}
|
||||||
showAvatar={
|
showAvatar={
|
||||||
props.guestAvatar?.isEnabled ??
|
props.guestAvatar?.isEnabled ?? defaultGuestAvatarIsEnabled
|
||||||
defaultTheme.chat.guestAvatar.isEnabled
|
|
||||||
}
|
}
|
||||||
avatarSrc={props.guestAvatar?.url && props.guestAvatar.url}
|
avatarSrc={props.guestAvatar?.url && props.guestAvatar.url}
|
||||||
/>
|
/>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { SendButton } from '@/components/SendButton'
|
import { SendButton } from '@/components/SendButton'
|
||||||
import { InputSubmitContent } from '@/types'
|
import { InputSubmitContent } from '@/types'
|
||||||
import type { RatingInputBlock } from '@typebot.io/schemas'
|
import type { RatingInputBlock } from '@typebot.io/schemas'
|
||||||
import { createSignal, For, Match, Switch } from 'solid-js'
|
import { createSignal, For, Match, Switch, Show } from 'solid-js'
|
||||||
import { isDefined, isEmpty, isNotDefined } from '@typebot.io/lib'
|
import { isDefined, isEmpty, isNotDefined } from '@typebot.io/lib'
|
||||||
import { Button } from '@/components/Button'
|
import { Button } from '@/components/Button'
|
||||||
import { defaultRatingInputOptions } from '@typebot.io/schemas/features/blocks/inputs/rating/constants'
|
import { defaultRatingInputOptions } from '@typebot.io/schemas/features/blocks/inputs/rating/constants'
|
||||||
@ -107,17 +107,24 @@ const RatingButton = (props: RatingButtonProps) => {
|
|||||||
'Numbers'
|
'Numbers'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Button
|
<Show when={props.isOneClickSubmitEnabled}>
|
||||||
on:click={handleClick}
|
<Button on:click={handleClick}>{props.idx}</Button>
|
||||||
class={
|
</Show>
|
||||||
props.isOneClickSubmitEnabled ||
|
<Show when={!props.isOneClickSubmitEnabled}>
|
||||||
(isDefined(props.rating) && props.idx <= props.rating)
|
<div
|
||||||
? ''
|
role="checkbox"
|
||||||
: 'selectable'
|
aria-checked={isDefined(props.rating) && props.idx <= props.rating}
|
||||||
}
|
on:click={handleClick}
|
||||||
>
|
class={
|
||||||
{props.idx}
|
'py-2 px-4 font-semibold focus:outline-none cursor-pointer select-none typebot-selectable' +
|
||||||
</Button>
|
(isDefined(props.rating) && props.idx <= props.rating
|
||||||
|
? ' selected'
|
||||||
|
: '')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{props.idx}
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
</Match>
|
</Match>
|
||||||
<Match
|
<Match
|
||||||
when={
|
when={
|
||||||
|
@ -6,6 +6,7 @@ import { isMobile } from '@/utils/isMobileSignal'
|
|||||||
import type { TextInputBlock } from '@typebot.io/schemas'
|
import type { TextInputBlock } from '@typebot.io/schemas'
|
||||||
import { createSignal, onCleanup, onMount } from 'solid-js'
|
import { createSignal, onCleanup, onMount } from 'solid-js'
|
||||||
import { defaultTextInputOptions } from '@typebot.io/schemas/features/blocks/inputs/text/constants'
|
import { defaultTextInputOptions } from '@typebot.io/schemas/features/blocks/inputs/text/constants'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
block: TextInputBlock
|
block: TextInputBlock
|
||||||
@ -55,7 +56,10 @@ export const TextInput = (props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={'flex items-end justify-between pr-2 typebot-input w-full'}
|
class={clsx(
|
||||||
|
'flex justify-between pr-2 typebot-input w-full',
|
||||||
|
props.block.options?.isLong ? 'items-end' : 'items-center'
|
||||||
|
)}
|
||||||
data-testid="input"
|
data-testid="input"
|
||||||
style={{
|
style={{
|
||||||
'max-width': props.block.options?.isLong ? undefined : '350px',
|
'max-width': props.block.options?.isLong ? undefined : '350px',
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { isNotEmpty } from '@typebot.io/lib'
|
import { isNotEmpty } from '@typebot.io/lib'
|
||||||
import { Font } from '@typebot.io/schemas'
|
import { Font } from '@typebot.io/schemas'
|
||||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
import { defaultFontFamily } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
|
|
||||||
const googleFontCdnBaseUrl = 'https://fonts.bunny.net/css2'
|
const googleFontCdnBaseUrl = 'https://fonts.bunny.net/css2'
|
||||||
const elementId = 'typebot-font'
|
const elementId = 'typebot-font'
|
||||||
@ -10,8 +10,7 @@ export const injectFont = (font: Font) => {
|
|||||||
|
|
||||||
if (typeof font === 'string' || font.type === 'Google') {
|
if (typeof font === 'string' || font.type === 'Google') {
|
||||||
const fontFamily =
|
const fontFamily =
|
||||||
(typeof font === 'string' ? font : font.family) ??
|
(typeof font === 'string' ? font : font.family) ?? defaultFontFamily
|
||||||
defaultTheme.general.font.family
|
|
||||||
if (existingFont?.getAttribute('href')?.includes(fontFamily)) return
|
if (existingFont?.getAttribute('href')?.includes(fontFamily)) return
|
||||||
existingFont?.remove()
|
existingFont?.remove()
|
||||||
const fontElement = document.createElement('link')
|
const fontElement = document.createElement('link')
|
||||||
|
@ -1,24 +1,51 @@
|
|||||||
import {
|
import {
|
||||||
Background,
|
Background,
|
||||||
ChatTheme,
|
ChatTheme,
|
||||||
ContainerColors,
|
ContainerBorderTheme,
|
||||||
|
ContainerTheme,
|
||||||
GeneralTheme,
|
GeneralTheme,
|
||||||
InputColors,
|
InputTheme,
|
||||||
Theme,
|
Theme,
|
||||||
} from '@typebot.io/schemas'
|
} from '@typebot.io/schemas'
|
||||||
import { isLight, hexToRgb } from '@typebot.io/lib/hexToRgb'
|
import { isLight, hexToRgb } from '@typebot.io/lib/hexToRgb'
|
||||||
import { isNotEmpty } from '@typebot.io/lib'
|
import { isDefined, isEmpty } from '@typebot.io/lib'
|
||||||
import {
|
import {
|
||||||
BackgroundType,
|
BackgroundType,
|
||||||
defaultTheme,
|
defaultBackgroundColor,
|
||||||
|
defaultBackgroundType,
|
||||||
|
defaultButtonsBackgroundColor,
|
||||||
|
defaultButtonsColor,
|
||||||
|
defaultButtonsBorderThickness,
|
||||||
|
defaultContainerBackgroundColor,
|
||||||
|
defaultContainerMaxHeight,
|
||||||
|
defaultContainerMaxWidth,
|
||||||
|
defaultDarkTextColor,
|
||||||
|
defaultFontFamily,
|
||||||
|
defaultGuestBubblesBackgroundColor,
|
||||||
|
defaultGuestBubblesColor,
|
||||||
|
defaultHostBubblesBackgroundColor,
|
||||||
|
defaultHostBubblesColor,
|
||||||
|
defaultInputsBackgroundColor,
|
||||||
|
defaultInputsColor,
|
||||||
|
defaultInputsPlaceholderColor,
|
||||||
|
defaultLightTextColor,
|
||||||
|
defaultProgressBarBackgroundColor,
|
||||||
|
defaultProgressBarColor,
|
||||||
|
defaultProgressBarPlacement,
|
||||||
|
defaultProgressBarPosition,
|
||||||
|
defaultProgressBarThickness,
|
||||||
|
defaultInputsShadow,
|
||||||
|
defaultOpacity,
|
||||||
|
defaultBlur,
|
||||||
|
defaultRoundness,
|
||||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
|
import { isChatContainerLight } from '@typebot.io/theme/isChatContainerLight'
|
||||||
|
|
||||||
const cssVariableNames = {
|
const cssVariableNames = {
|
||||||
general: {
|
general: {
|
||||||
bgImage: '--typebot-container-bg-image',
|
bgImage: '--typebot-container-bg-image',
|
||||||
bgColor: '--typebot-container-bg-color',
|
bgColor: '--typebot-container-bg-color',
|
||||||
fontFamily: '--typebot-container-font-family',
|
fontFamily: '--typebot-container-font-family',
|
||||||
color: '--typebot-container-color',
|
|
||||||
progressBar: {
|
progressBar: {
|
||||||
position: '--typebot-progress-bar-position',
|
position: '--typebot-progress-bar-position',
|
||||||
color: '--typebot-progress-bar-color',
|
color: '--typebot-progress-bar-color',
|
||||||
@ -29,28 +56,67 @@ const cssVariableNames = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
chat: {
|
chat: {
|
||||||
|
container: {
|
||||||
|
maxWidth: '--typebot-chat-container-max-width',
|
||||||
|
maxHeight: '--typebot-chat-container-max-height',
|
||||||
|
bgColor: '--typebot-chat-container-bg-rgb',
|
||||||
|
color: '--typebot-chat-container-color',
|
||||||
|
borderRadius: '--typebot-chat-container-border-radius',
|
||||||
|
borderWidth: '--typebot-chat-container-border-width',
|
||||||
|
borderColor: '--typebot-chat-container-border-rgb',
|
||||||
|
borderOpacity: '--typebot-chat-container-border-opacity',
|
||||||
|
opacity: '--typebot-chat-container-opacity',
|
||||||
|
blur: '--typebot-chat-container-blur',
|
||||||
|
boxShadow: '--typebot-chat-container-box-shadow',
|
||||||
|
},
|
||||||
hostBubbles: {
|
hostBubbles: {
|
||||||
bgColor: '--typebot-host-bubble-bg-color',
|
bgColor: '--typebot-host-bubble-bg-rgb',
|
||||||
color: '--typebot-host-bubble-color',
|
color: '--typebot-host-bubble-color',
|
||||||
|
borderRadius: '--typebot-host-bubble-border-radius',
|
||||||
|
borderWidth: '--typebot-host-bubble-border-width',
|
||||||
|
borderColor: '--typebot-host-bubble-border-rgb',
|
||||||
|
borderOpacity: '--typebot-host-bubble-border-opacity',
|
||||||
|
opacity: '--typebot-host-bubble-opacity',
|
||||||
|
blur: '--typebot-host-bubble-blur',
|
||||||
|
boxShadow: '--typebot-host-bubble-box-shadow',
|
||||||
},
|
},
|
||||||
guestBubbles: {
|
guestBubbles: {
|
||||||
bgColor: '--typebot-guest-bubble-bg-color',
|
bgColor: '--typebot-guest-bubble-bg-rgb',
|
||||||
color: '--typebot-guest-bubble-color',
|
color: '--typebot-guest-bubble-color',
|
||||||
|
borderRadius: '--typebot-guest-bubble-border-radius',
|
||||||
|
borderWidth: '--typebot-guest-bubble-border-width',
|
||||||
|
borderColor: '--typebot-guest-bubble-border-rgb',
|
||||||
|
borderOpacity: '--typebot-guest-bubble-border-opacity',
|
||||||
|
opacity: '--typebot-guest-bubble-opacity',
|
||||||
|
blur: '--typebot-guest-bubble-blur',
|
||||||
|
boxShadow: '--typebot-guest-bubble-box-shadow',
|
||||||
},
|
},
|
||||||
inputs: {
|
inputs: {
|
||||||
bgColor: '--typebot-input-bg-color',
|
bgColor: '--typebot-input-bg-rgb',
|
||||||
color: '--typebot-input-color',
|
color: '--typebot-input-color',
|
||||||
placeholderColor: '--typebot-input-placeholder-color',
|
placeholderColor: '--typebot-input-placeholder-color',
|
||||||
|
borderRadius: '--typebot-input-border-radius',
|
||||||
|
borderWidth: '--typebot-input-border-width',
|
||||||
|
borderColor: '--typebot-input-border-rgb',
|
||||||
|
borderOpacity: '--typebot-input-border-opacity',
|
||||||
|
opacity: '--typebot-input-opacity',
|
||||||
|
blur: '--typebot-input-blur',
|
||||||
|
boxShadow: '--typebot-input-box-shadow',
|
||||||
},
|
},
|
||||||
buttons: {
|
buttons: {
|
||||||
bgColor: '--typebot-button-bg-color',
|
bgRgb: '--typebot-button-bg-rgb',
|
||||||
bgColorRgb: '--typebot-button-bg-color-rgb',
|
|
||||||
color: '--typebot-button-color',
|
color: '--typebot-button-color',
|
||||||
|
borderRadius: '--typebot-button-border-radius',
|
||||||
|
borderWidth: '--typebot-button-border-width',
|
||||||
|
borderColor: '--typebot-button-border-rgb',
|
||||||
|
borderOpacity: '--typebot-button-border-opacity',
|
||||||
|
opacity: '--typebot-button-opacity',
|
||||||
|
blur: '--typebot-button-blur',
|
||||||
|
boxShadow: '--typebot-button-box-shadow',
|
||||||
},
|
},
|
||||||
checkbox: {
|
checkbox: {
|
||||||
bgColor: '--typebot-checkbox-bg-color',
|
bgRgb: '--typebot-checkbox-bg-rgb',
|
||||||
color: '--typebot-checkbox-color',
|
alphaRatio: '--selectable-alpha-ratio',
|
||||||
baseAlpha: '--selectable-base-alpha',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const
|
} as const
|
||||||
@ -63,43 +129,31 @@ export const setCssVariablesValue = (
|
|||||||
if (!theme) return
|
if (!theme) return
|
||||||
const documentStyle = container?.style
|
const documentStyle = container?.style
|
||||||
if (!documentStyle) return
|
if (!documentStyle) return
|
||||||
setGeneralTheme(
|
setGeneralTheme(theme.general, documentStyle, isPreview)
|
||||||
theme.general ?? defaultTheme.general,
|
setChatTheme(theme.chat, theme.general?.background, documentStyle)
|
||||||
documentStyle,
|
|
||||||
isPreview
|
|
||||||
)
|
|
||||||
setChatTheme(theme.chat ?? defaultTheme.chat, documentStyle)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const setGeneralTheme = (
|
const setGeneralTheme = (
|
||||||
generalTheme: GeneralTheme,
|
generalTheme: GeneralTheme | undefined,
|
||||||
documentStyle: CSSStyleDeclaration,
|
documentStyle: CSSStyleDeclaration,
|
||||||
isPreview?: boolean
|
isPreview?: boolean
|
||||||
) => {
|
) => {
|
||||||
setTypebotBackground(
|
setGeneralBackground(generalTheme?.background, documentStyle)
|
||||||
generalTheme.background ?? defaultTheme.general.background,
|
|
||||||
documentStyle
|
|
||||||
)
|
|
||||||
documentStyle.setProperty(
|
documentStyle.setProperty(
|
||||||
cssVariableNames.general.fontFamily,
|
cssVariableNames.general.fontFamily,
|
||||||
(typeof generalTheme.font === 'string'
|
(typeof generalTheme?.font === 'string'
|
||||||
? generalTheme.font
|
? generalTheme.font
|
||||||
: generalTheme.font?.family) ?? defaultTheme.general.font.family
|
: generalTheme?.font?.family) ?? defaultFontFamily
|
||||||
)
|
|
||||||
setProgressBar(
|
|
||||||
generalTheme.progressBar ?? defaultTheme.general.progressBar,
|
|
||||||
documentStyle,
|
|
||||||
isPreview
|
|
||||||
)
|
)
|
||||||
|
setProgressBar(generalTheme?.progressBar, documentStyle, isPreview)
|
||||||
}
|
}
|
||||||
|
|
||||||
const setProgressBar = (
|
const setProgressBar = (
|
||||||
progressBar: NonNullable<GeneralTheme['progressBar']>,
|
progressBar: GeneralTheme['progressBar'],
|
||||||
documentStyle: CSSStyleDeclaration,
|
documentStyle: CSSStyleDeclaration,
|
||||||
isPreview?: boolean
|
isPreview?: boolean
|
||||||
) => {
|
) => {
|
||||||
const position =
|
const position = progressBar?.position ?? defaultProgressBarPosition
|
||||||
progressBar.position ?? defaultTheme.general.progressBar.position
|
|
||||||
|
|
||||||
documentStyle.setProperty(
|
documentStyle.setProperty(
|
||||||
cssVariableNames.general.progressBar.position,
|
cssVariableNames.general.progressBar.position,
|
||||||
@ -107,22 +161,20 @@ const setProgressBar = (
|
|||||||
)
|
)
|
||||||
documentStyle.setProperty(
|
documentStyle.setProperty(
|
||||||
cssVariableNames.general.progressBar.color,
|
cssVariableNames.general.progressBar.color,
|
||||||
progressBar.color ?? defaultTheme.general.progressBar.color
|
progressBar?.color ?? defaultProgressBarColor
|
||||||
)
|
)
|
||||||
documentStyle.setProperty(
|
documentStyle.setProperty(
|
||||||
cssVariableNames.general.progressBar.colorRgb,
|
cssVariableNames.general.progressBar.colorRgb,
|
||||||
hexToRgb(
|
hexToRgb(
|
||||||
progressBar.backgroundColor ??
|
progressBar?.backgroundColor ?? defaultProgressBarBackgroundColor
|
||||||
defaultTheme.general.progressBar.backgroundColor
|
|
||||||
).join(', ')
|
).join(', ')
|
||||||
)
|
)
|
||||||
documentStyle.setProperty(
|
documentStyle.setProperty(
|
||||||
cssVariableNames.general.progressBar.height,
|
cssVariableNames.general.progressBar.height,
|
||||||
`${progressBar.thickness ?? defaultTheme.general.progressBar.thickness}px`
|
`${progressBar?.thickness ?? defaultProgressBarThickness}px`
|
||||||
)
|
)
|
||||||
|
|
||||||
const placement =
|
const placement = progressBar?.placement ?? defaultProgressBarPlacement
|
||||||
progressBar.placement ?? defaultTheme.general.progressBar.placement
|
|
||||||
|
|
||||||
documentStyle.setProperty(
|
documentStyle.setProperty(
|
||||||
cssVariableNames.general.progressBar.top,
|
cssVariableNames.general.progressBar.top,
|
||||||
@ -136,123 +188,428 @@ const setProgressBar = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const setChatTheme = (
|
const setChatTheme = (
|
||||||
chatTheme: ChatTheme,
|
chatTheme: ChatTheme | undefined,
|
||||||
|
generalBackground: GeneralTheme['background'],
|
||||||
documentStyle: CSSStyleDeclaration
|
documentStyle: CSSStyleDeclaration
|
||||||
) => {
|
) => {
|
||||||
setHostBubbles(
|
setChatContainer(
|
||||||
chatTheme.hostBubbles ?? defaultTheme.chat.hostBubbles,
|
chatTheme?.container,
|
||||||
documentStyle
|
generalBackground,
|
||||||
|
documentStyle,
|
||||||
|
chatTheme?.roundness
|
||||||
)
|
)
|
||||||
setGuestBubbles(
|
setHostBubbles(chatTheme?.hostBubbles, documentStyle, chatTheme?.roundness)
|
||||||
chatTheme.guestBubbles ?? defaultTheme.chat.guestBubbles,
|
setGuestBubbles(chatTheme?.guestBubbles, documentStyle, chatTheme?.roundness)
|
||||||
documentStyle
|
setButtons(chatTheme?.buttons, documentStyle, chatTheme?.roundness)
|
||||||
|
setInputs(chatTheme?.inputs, documentStyle, chatTheme?.roundness)
|
||||||
|
setCheckbox(chatTheme?.container, generalBackground, documentStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
const setChatContainer = (
|
||||||
|
container: ChatTheme['container'],
|
||||||
|
generalBackground: GeneralTheme['background'],
|
||||||
|
documentStyle: CSSStyleDeclaration,
|
||||||
|
legacyRoundness?: ChatTheme['roundness']
|
||||||
|
) => {
|
||||||
|
const chatContainerBgColor =
|
||||||
|
container?.backgroundColor ?? defaultContainerBackgroundColor
|
||||||
|
const isBgDisabled =
|
||||||
|
chatContainerBgColor === 'transparent' || isEmpty(chatContainerBgColor)
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.container.bgColor,
|
||||||
|
isBgDisabled ? '0, 0, 0' : hexToRgb(chatContainerBgColor).join(', ')
|
||||||
)
|
)
|
||||||
setButtons(chatTheme.buttons ?? defaultTheme.chat.buttons, documentStyle)
|
|
||||||
setInputs(chatTheme.inputs ?? defaultTheme.chat.inputs, documentStyle)
|
documentStyle.setProperty(
|
||||||
setRoundness(
|
cssVariableNames.chat.container.color,
|
||||||
chatTheme.roundness ?? defaultTheme.chat.roundness,
|
hexToRgb(
|
||||||
documentStyle
|
container?.color ??
|
||||||
|
(isChatContainerLight({
|
||||||
|
chatContainer: container,
|
||||||
|
generalBackground,
|
||||||
|
})
|
||||||
|
? defaultLightTextColor
|
||||||
|
: defaultDarkTextColor)
|
||||||
|
).join(', ')
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.container.maxWidth,
|
||||||
|
container?.maxWidth ?? defaultContainerMaxWidth
|
||||||
|
)
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.container.maxHeight,
|
||||||
|
container?.maxHeight ?? defaultContainerMaxHeight
|
||||||
|
)
|
||||||
|
const opacity = isBgDisabled
|
||||||
|
? '1'
|
||||||
|
: (container?.opacity ?? defaultOpacity).toString()
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.container.opacity,
|
||||||
|
isBgDisabled ? '0' : (container?.opacity ?? defaultOpacity).toString()
|
||||||
|
)
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.container.blur,
|
||||||
|
opacity === '1' || isBgDisabled
|
||||||
|
? '0xp'
|
||||||
|
: `${container?.blur ?? defaultBlur}px`
|
||||||
|
)
|
||||||
|
setShadow(
|
||||||
|
container?.shadow,
|
||||||
|
documentStyle,
|
||||||
|
cssVariableNames.chat.container.boxShadow
|
||||||
|
)
|
||||||
|
|
||||||
|
setBorderRadius(
|
||||||
|
container?.border ?? {
|
||||||
|
roundeness: legacyRoundness ?? defaultRoundness,
|
||||||
|
},
|
||||||
|
documentStyle,
|
||||||
|
cssVariableNames.chat.container.borderRadius
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.container.borderWidth,
|
||||||
|
isDefined(container?.border?.thickness)
|
||||||
|
? `${container?.border?.thickness}px`
|
||||||
|
: '0'
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.container.borderOpacity,
|
||||||
|
isDefined(container?.border?.opacity)
|
||||||
|
? container.border.opacity.toString()
|
||||||
|
: defaultOpacity.toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.container.borderColor,
|
||||||
|
hexToRgb(container?.border?.color ?? '').join(', ')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const setHostBubbles = (
|
const setHostBubbles = (
|
||||||
hostBubbles: ContainerColors,
|
hostBubbles: ContainerTheme | undefined,
|
||||||
documentStyle: CSSStyleDeclaration
|
documentStyle: CSSStyleDeclaration,
|
||||||
|
legacyRoundness?: ChatTheme['roundness']
|
||||||
) => {
|
) => {
|
||||||
documentStyle.setProperty(
|
documentStyle.setProperty(
|
||||||
cssVariableNames.chat.hostBubbles.bgColor,
|
cssVariableNames.chat.hostBubbles.bgColor,
|
||||||
hostBubbles.backgroundColor ?? defaultTheme.chat.hostBubbles.backgroundColor
|
hexToRgb(
|
||||||
|
hostBubbles?.backgroundColor ?? defaultHostBubblesBackgroundColor
|
||||||
|
).join(', ')
|
||||||
)
|
)
|
||||||
documentStyle.setProperty(
|
documentStyle.setProperty(
|
||||||
cssVariableNames.chat.hostBubbles.color,
|
cssVariableNames.chat.hostBubbles.color,
|
||||||
hostBubbles.color ?? defaultTheme.chat.hostBubbles.color
|
hostBubbles?.color ?? defaultHostBubblesColor
|
||||||
|
)
|
||||||
|
setBorderRadius(
|
||||||
|
hostBubbles?.border ?? {
|
||||||
|
roundeness: legacyRoundness ?? defaultRoundness,
|
||||||
|
},
|
||||||
|
documentStyle,
|
||||||
|
cssVariableNames.chat.hostBubbles.borderRadius
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.hostBubbles.borderWidth,
|
||||||
|
isDefined(hostBubbles?.border?.thickness)
|
||||||
|
? `${hostBubbles?.border?.thickness}px`
|
||||||
|
: '0'
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.hostBubbles.borderColor,
|
||||||
|
hexToRgb(hostBubbles?.border?.color ?? '').join(', ')
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.hostBubbles.opacity,
|
||||||
|
isDefined(hostBubbles?.opacity)
|
||||||
|
? hostBubbles.opacity.toString()
|
||||||
|
: defaultOpacity.toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.hostBubbles.borderOpacity,
|
||||||
|
isDefined(hostBubbles?.border?.opacity)
|
||||||
|
? hostBubbles.border.opacity.toString()
|
||||||
|
: defaultOpacity.toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.hostBubbles.blur,
|
||||||
|
isDefined(hostBubbles?.blur)
|
||||||
|
? `${hostBubbles.blur ?? 0}px`
|
||||||
|
: defaultBlur.toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
setShadow(
|
||||||
|
hostBubbles?.shadow,
|
||||||
|
documentStyle,
|
||||||
|
cssVariableNames.chat.hostBubbles.boxShadow
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const setGuestBubbles = (
|
const setGuestBubbles = (
|
||||||
guestBubbles: ContainerColors,
|
guestBubbles: ContainerTheme | undefined,
|
||||||
documentStyle: CSSStyleDeclaration
|
documentStyle: CSSStyleDeclaration,
|
||||||
|
legacyRoundness?: ChatTheme['roundness']
|
||||||
) => {
|
) => {
|
||||||
documentStyle.setProperty(
|
documentStyle.setProperty(
|
||||||
cssVariableNames.chat.guestBubbles.bgColor,
|
cssVariableNames.chat.guestBubbles.bgColor,
|
||||||
guestBubbles.backgroundColor ??
|
hexToRgb(
|
||||||
defaultTheme.chat.guestBubbles.backgroundColor
|
guestBubbles?.backgroundColor ?? defaultGuestBubblesBackgroundColor
|
||||||
|
).join(', ')
|
||||||
)
|
)
|
||||||
|
|
||||||
documentStyle.setProperty(
|
documentStyle.setProperty(
|
||||||
cssVariableNames.chat.guestBubbles.color,
|
cssVariableNames.chat.guestBubbles.color,
|
||||||
guestBubbles.color ?? defaultTheme.chat.guestBubbles.color
|
guestBubbles?.color ?? defaultGuestBubblesColor
|
||||||
|
)
|
||||||
|
|
||||||
|
setBorderRadius(
|
||||||
|
guestBubbles?.border ?? {
|
||||||
|
roundeness: legacyRoundness ?? defaultRoundness,
|
||||||
|
},
|
||||||
|
documentStyle,
|
||||||
|
cssVariableNames.chat.guestBubbles.borderRadius
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.guestBubbles.borderWidth,
|
||||||
|
isDefined(guestBubbles?.border?.thickness)
|
||||||
|
? `${guestBubbles?.border?.thickness}px`
|
||||||
|
: '0'
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.guestBubbles.borderColor,
|
||||||
|
hexToRgb(guestBubbles?.border?.color ?? '').join(', ')
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.guestBubbles.borderOpacity,
|
||||||
|
isDefined(guestBubbles?.border?.opacity)
|
||||||
|
? guestBubbles.border.opacity.toString()
|
||||||
|
: defaultOpacity.toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.guestBubbles.opacity,
|
||||||
|
isDefined(guestBubbles?.opacity)
|
||||||
|
? guestBubbles.opacity.toString()
|
||||||
|
: defaultOpacity.toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.guestBubbles.blur,
|
||||||
|
isDefined(guestBubbles?.blur)
|
||||||
|
? `${guestBubbles.blur ?? 0}px`
|
||||||
|
: defaultBlur.toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
setShadow(
|
||||||
|
guestBubbles?.shadow,
|
||||||
|
documentStyle,
|
||||||
|
cssVariableNames.chat.guestBubbles.boxShadow
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const setButtons = (
|
const setButtons = (
|
||||||
buttons: ContainerColors,
|
buttons: ContainerTheme | undefined,
|
||||||
documentStyle: CSSStyleDeclaration
|
documentStyle: CSSStyleDeclaration,
|
||||||
|
legacyRoundness?: ChatTheme['roundness']
|
||||||
) => {
|
) => {
|
||||||
const bgColor =
|
const bgColor = buttons?.backgroundColor ?? defaultButtonsBackgroundColor
|
||||||
buttons.backgroundColor ?? defaultTheme.chat.buttons.backgroundColor
|
|
||||||
documentStyle.setProperty(cssVariableNames.chat.buttons.bgColor, bgColor)
|
|
||||||
documentStyle.setProperty(
|
documentStyle.setProperty(
|
||||||
cssVariableNames.chat.buttons.bgColorRgb,
|
cssVariableNames.chat.buttons.bgRgb,
|
||||||
|
hexToRgb(bgColor).join(', ')
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.buttons.bgRgb,
|
||||||
hexToRgb(bgColor).join(', ')
|
hexToRgb(bgColor).join(', ')
|
||||||
)
|
)
|
||||||
|
|
||||||
documentStyle.setProperty(
|
documentStyle.setProperty(
|
||||||
cssVariableNames.chat.buttons.color,
|
cssVariableNames.chat.buttons.color,
|
||||||
buttons.color ?? defaultTheme.chat.buttons.color
|
buttons?.color ?? defaultButtonsColor
|
||||||
|
)
|
||||||
|
|
||||||
|
setBorderRadius(
|
||||||
|
buttons?.border ?? {
|
||||||
|
roundeness: legacyRoundness ?? defaultRoundness,
|
||||||
|
},
|
||||||
|
documentStyle,
|
||||||
|
cssVariableNames.chat.buttons.borderRadius
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.buttons.borderWidth,
|
||||||
|
isDefined(buttons?.border?.thickness)
|
||||||
|
? `${buttons?.border?.thickness}px`
|
||||||
|
: `${defaultButtonsBorderThickness}px`
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.buttons.borderColor,
|
||||||
|
hexToRgb(
|
||||||
|
buttons?.border?.color ??
|
||||||
|
buttons?.backgroundColor ??
|
||||||
|
defaultButtonsBackgroundColor
|
||||||
|
).join(', ')
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.buttons.borderOpacity,
|
||||||
|
isDefined(buttons?.border?.opacity)
|
||||||
|
? buttons.border.opacity.toString()
|
||||||
|
: defaultOpacity.toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.buttons.opacity,
|
||||||
|
isDefined(buttons?.opacity)
|
||||||
|
? buttons.opacity.toString()
|
||||||
|
: defaultOpacity.toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.buttons.blur,
|
||||||
|
isDefined(buttons?.blur) ? `${buttons.blur ?? 0}px` : defaultBlur.toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
setShadow(
|
||||||
|
buttons?.shadow,
|
||||||
|
documentStyle,
|
||||||
|
cssVariableNames.chat.buttons.boxShadow
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const setInputs = (inputs: InputColors, documentStyle: CSSStyleDeclaration) => {
|
const setInputs = (
|
||||||
|
inputs: InputTheme | undefined,
|
||||||
|
documentStyle: CSSStyleDeclaration,
|
||||||
|
legacyRoundness?: ChatTheme['roundness']
|
||||||
|
) => {
|
||||||
documentStyle.setProperty(
|
documentStyle.setProperty(
|
||||||
cssVariableNames.chat.inputs.bgColor,
|
cssVariableNames.chat.inputs.bgColor,
|
||||||
inputs.backgroundColor ?? defaultTheme.chat.inputs.backgroundColor
|
hexToRgb(inputs?.backgroundColor ?? defaultInputsBackgroundColor).join(', ')
|
||||||
)
|
)
|
||||||
|
|
||||||
documentStyle.setProperty(
|
documentStyle.setProperty(
|
||||||
cssVariableNames.chat.inputs.color,
|
cssVariableNames.chat.inputs.color,
|
||||||
inputs.color ?? defaultTheme.chat.inputs.color
|
inputs?.color ?? defaultInputsColor
|
||||||
)
|
)
|
||||||
|
|
||||||
documentStyle.setProperty(
|
documentStyle.setProperty(
|
||||||
cssVariableNames.chat.inputs.placeholderColor,
|
cssVariableNames.chat.inputs.placeholderColor,
|
||||||
inputs.placeholderColor ?? defaultTheme.chat.inputs.placeholderColor
|
inputs?.placeholderColor ?? defaultInputsPlaceholderColor
|
||||||
|
)
|
||||||
|
|
||||||
|
setBorderRadius(
|
||||||
|
inputs?.border ?? {
|
||||||
|
roundeness: legacyRoundness ?? defaultRoundness,
|
||||||
|
},
|
||||||
|
documentStyle,
|
||||||
|
cssVariableNames.chat.inputs.borderRadius
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.inputs.borderWidth,
|
||||||
|
isDefined(inputs?.border?.thickness)
|
||||||
|
? `${inputs?.border?.thickness}px`
|
||||||
|
: '0'
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.inputs.borderColor,
|
||||||
|
hexToRgb(inputs?.border?.color ?? '').join(', ')
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.inputs.opacity,
|
||||||
|
isDefined(inputs?.opacity)
|
||||||
|
? inputs.opacity.toString()
|
||||||
|
: defaultOpacity.toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.inputs.blur,
|
||||||
|
isDefined(inputs?.blur) ? `${inputs.blur ?? 0}px` : defaultBlur.toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
setShadow(
|
||||||
|
inputs?.shadow ?? defaultInputsShadow,
|
||||||
|
documentStyle,
|
||||||
|
cssVariableNames.chat.inputs.boxShadow
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const setTypebotBackground = (
|
const setCheckbox = (
|
||||||
background: Background,
|
container: ChatTheme['container'],
|
||||||
|
generalBackground: GeneralTheme['background'],
|
||||||
|
documentStyle: CSSStyleDeclaration
|
||||||
|
) => {
|
||||||
|
const chatContainerBgColor =
|
||||||
|
container?.backgroundColor ?? defaultContainerBackgroundColor
|
||||||
|
const isChatBgTransparent =
|
||||||
|
chatContainerBgColor === 'transparent' ||
|
||||||
|
isEmpty(chatContainerBgColor) ||
|
||||||
|
(container?.opacity ?? defaultOpacity) <= 0.2
|
||||||
|
|
||||||
|
if (isChatBgTransparent) {
|
||||||
|
const bgType = generalBackground?.type ?? defaultBackgroundType
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.checkbox.bgRgb,
|
||||||
|
bgType === BackgroundType.IMAGE
|
||||||
|
? 'rgba(255, 255, 255, 0.75)'
|
||||||
|
: hexToRgb(
|
||||||
|
(bgType === BackgroundType.COLOR
|
||||||
|
? generalBackground?.content
|
||||||
|
: '#ffffff') ?? '#ffffff'
|
||||||
|
).join(', ')
|
||||||
|
)
|
||||||
|
if (bgType === BackgroundType.IMAGE) {
|
||||||
|
documentStyle.setProperty(cssVariableNames.chat.checkbox.alphaRatio, '3')
|
||||||
|
} else {
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.checkbox.alphaRatio,
|
||||||
|
generalBackground?.content && isLight(generalBackground?.content)
|
||||||
|
? '1'
|
||||||
|
: '2'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.checkbox.bgRgb,
|
||||||
|
hexToRgb(chatContainerBgColor)
|
||||||
|
.concat(container?.opacity ?? 1)
|
||||||
|
.join(', ')
|
||||||
|
)
|
||||||
|
documentStyle.setProperty(
|
||||||
|
cssVariableNames.chat.checkbox.alphaRatio,
|
||||||
|
isLight(chatContainerBgColor) ? '1' : '2'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setGeneralBackground = (
|
||||||
|
background: Background | undefined,
|
||||||
documentStyle: CSSStyleDeclaration
|
documentStyle: CSSStyleDeclaration
|
||||||
) => {
|
) => {
|
||||||
documentStyle.setProperty(cssVariableNames.general.bgImage, null)
|
documentStyle.setProperty(cssVariableNames.general.bgImage, null)
|
||||||
documentStyle.setProperty(cssVariableNames.general.bgColor, null)
|
documentStyle.setProperty(cssVariableNames.general.bgColor, null)
|
||||||
documentStyle.setProperty(
|
documentStyle.setProperty(
|
||||||
background?.type === BackgroundType.IMAGE
|
(background?.type ?? defaultBackgroundType) === BackgroundType.IMAGE
|
||||||
? cssVariableNames.general.bgImage
|
? cssVariableNames.general.bgImage
|
||||||
: cssVariableNames.general.bgColor,
|
: cssVariableNames.general.bgColor,
|
||||||
parseBackgroundValue(background)
|
parseBackgroundValue({
|
||||||
|
type: background?.type ?? defaultBackgroundType,
|
||||||
|
content: background?.content ?? defaultBackgroundColor,
|
||||||
|
})
|
||||||
)
|
)
|
||||||
documentStyle.setProperty(
|
|
||||||
cssVariableNames.chat.checkbox.bgColor,
|
|
||||||
background?.type === BackgroundType.IMAGE
|
|
||||||
? 'rgba(255, 255, 255, 0.75)'
|
|
||||||
: (background?.type === BackgroundType.COLOR
|
|
||||||
? background.content
|
|
||||||
: '#ffffff') ?? '#ffffff'
|
|
||||||
)
|
|
||||||
const backgroundColor =
|
|
||||||
background.type === BackgroundType.IMAGE
|
|
||||||
? '#000000'
|
|
||||||
: background?.type === BackgroundType.COLOR &&
|
|
||||||
isNotEmpty(background.content)
|
|
||||||
? background.content
|
|
||||||
: '#ffffff'
|
|
||||||
documentStyle.setProperty(
|
|
||||||
cssVariableNames.general.color,
|
|
||||||
isLight(backgroundColor) ? '#303235' : '#ffffff'
|
|
||||||
)
|
|
||||||
if (background.type === BackgroundType.IMAGE) {
|
|
||||||
documentStyle.setProperty(cssVariableNames.chat.checkbox.baseAlpha, '0.40')
|
|
||||||
} else {
|
|
||||||
documentStyle.setProperty(cssVariableNames.chat.checkbox.baseAlpha, '0')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseBackgroundValue = ({ type, content }: Background = {}) => {
|
const parseBackgroundValue = ({ type, content }: Background = {}) => {
|
||||||
@ -261,25 +618,80 @@ const parseBackgroundValue = ({ type, content }: Background = {}) => {
|
|||||||
return 'transparent'
|
return 'transparent'
|
||||||
case undefined:
|
case undefined:
|
||||||
case BackgroundType.COLOR:
|
case BackgroundType.COLOR:
|
||||||
return content ?? defaultTheme.general.background.content
|
return content ?? defaultBackgroundColor
|
||||||
case BackgroundType.IMAGE:
|
case BackgroundType.IMAGE:
|
||||||
return `url(${content})`
|
return `url(${content})`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const setRoundness = (
|
const setBorderRadius = (
|
||||||
roundness: NonNullable<ChatTheme['roundness']>,
|
border: ContainerBorderTheme,
|
||||||
documentStyle: CSSStyleDeclaration
|
documentStyle: CSSStyleDeclaration,
|
||||||
|
variableName: string
|
||||||
) => {
|
) => {
|
||||||
switch (roundness) {
|
switch (border?.roundeness ?? defaultRoundness) {
|
||||||
|
case 'none': {
|
||||||
|
documentStyle.setProperty(variableName, '0')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'medium': {
|
||||||
|
documentStyle.setProperty(variableName, '6px')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'large': {
|
||||||
|
documentStyle.setProperty(variableName, '20px')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'custom': {
|
||||||
|
documentStyle.setProperty(
|
||||||
|
variableName,
|
||||||
|
`${border.customRoundeness ?? 6}px`
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Props taken from https://tailwindcss.com/docs/box-shadow
|
||||||
|
const setShadow = (
|
||||||
|
shadow: ContainerTheme['shadow'],
|
||||||
|
documentStyle: CSSStyleDeclaration,
|
||||||
|
variableName: string
|
||||||
|
) => {
|
||||||
|
if (shadow === undefined) {
|
||||||
|
documentStyle.setProperty(variableName, '0 0 #0000')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch (shadow) {
|
||||||
case 'none':
|
case 'none':
|
||||||
documentStyle.setProperty('--typebot-border-radius', '0')
|
documentStyle.setProperty(variableName, '0 0 #0000')
|
||||||
break
|
break
|
||||||
case 'medium':
|
case 'sm':
|
||||||
documentStyle.setProperty('--typebot-border-radius', '6px')
|
documentStyle.setProperty(variableName, '0 1px 2px 0 rgb(0 0 0 / 0.05)')
|
||||||
break
|
break
|
||||||
case 'large':
|
case 'md':
|
||||||
documentStyle.setProperty('--typebot-border-radius', '20px')
|
documentStyle.setProperty(
|
||||||
|
variableName,
|
||||||
|
'0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)'
|
||||||
|
)
|
||||||
|
break
|
||||||
|
case 'lg':
|
||||||
|
documentStyle.setProperty(
|
||||||
|
variableName,
|
||||||
|
'0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)'
|
||||||
|
)
|
||||||
|
break
|
||||||
|
case 'xl':
|
||||||
|
documentStyle.setProperty(
|
||||||
|
variableName,
|
||||||
|
'0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)'
|
||||||
|
)
|
||||||
|
break
|
||||||
|
case '2xl':
|
||||||
|
documentStyle.setProperty(
|
||||||
|
variableName,
|
||||||
|
'0 25px 50px -12px rgb(0 0 0 / 0.25)'
|
||||||
|
)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/nextjs",
|
"name": "@typebot.io/nextjs",
|
||||||
"version": "0.2.64",
|
"version": "0.2.65",
|
||||||
"description": "Convenient library to display typebots on your Next.js website",
|
"description": "Convenient library to display typebots on your Next.js website",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/react",
|
"name": "@typebot.io/react",
|
||||||
"version": "0.2.64",
|
"version": "0.2.65",
|
||||||
"description": "Convenient library to display typebots on your React app",
|
"description": "Convenient library to display typebots on your React app",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import { Theme } from './schema'
|
|
||||||
|
|
||||||
export enum BackgroundType {
|
export enum BackgroundType {
|
||||||
COLOR = 'Color',
|
COLOR = 'Color',
|
||||||
IMAGE = 'Image',
|
IMAGE = 'Image',
|
||||||
@ -11,37 +9,63 @@ export const fontTypes = ['Google', 'Custom'] as const
|
|||||||
export const progressBarPlacements = ['Top', 'Bottom'] as const
|
export const progressBarPlacements = ['Top', 'Bottom'] as const
|
||||||
export const progressBarPositions = ['fixed', 'absolute'] as const
|
export const progressBarPositions = ['fixed', 'absolute'] as const
|
||||||
|
|
||||||
export const defaultTheme = {
|
export const shadows = ['none', 'sm', 'md', 'lg', 'xl', '2xl'] as const
|
||||||
chat: {
|
export const borderRoundness = ['none', 'medium', 'large', 'custom'] as const
|
||||||
roundness: 'medium',
|
|
||||||
hostBubbles: { backgroundColor: '#F7F8FF', color: '#303235' },
|
export const defaultLightTextColor = '#303235'
|
||||||
guestBubbles: { backgroundColor: '#FF8E21', color: '#FFFFFF' },
|
export const defaultDarkTextColor = '#FFFFFF'
|
||||||
buttons: { backgroundColor: '#0042DA', color: '#FFFFFF' },
|
|
||||||
inputs: {
|
/*---- General ----*/
|
||||||
backgroundColor: '#FFFFFF',
|
|
||||||
color: '#303235',
|
// Font
|
||||||
placeholderColor: '#9095A0',
|
export const defaultFontType = 'Google'
|
||||||
},
|
export const defaultFontFamily = 'Open Sans'
|
||||||
hostAvatar: {
|
|
||||||
isEnabled: true,
|
// Background
|
||||||
},
|
export const defaultBackgroundType = BackgroundType.COLOR
|
||||||
guestAvatar: {
|
export const defaultBackgroundColor = '#ffffff'
|
||||||
isEnabled: false,
|
|
||||||
},
|
// Progress bar
|
||||||
},
|
export const defaultProgressBarIsEnabled = false
|
||||||
general: {
|
export const defaultProgressBarColor = '#0042DA'
|
||||||
font: {
|
export const defaultProgressBarBackgroundColor = '#e0edff'
|
||||||
type: 'Google',
|
export const defaultProgressBarThickness = 4
|
||||||
family: 'Open Sans',
|
export const defaultProgressBarPosition = 'absolute'
|
||||||
},
|
export const defaultProgressBarPlacement = 'Top'
|
||||||
background: { type: BackgroundType.COLOR, content: '#ffffff' },
|
|
||||||
progressBar: {
|
export const defaultRoundness = 'medium'
|
||||||
isEnabled: false,
|
export const defaultOpacity = 1
|
||||||
color: '#0042DA',
|
export const defaultBlur = 0
|
||||||
backgroundColor: '#e0edff',
|
|
||||||
thickness: 4,
|
/*---- Chat ----*/
|
||||||
position: 'absolute',
|
|
||||||
placement: 'Top',
|
// Container
|
||||||
},
|
export const defaultContainerMaxWidth = '800px'
|
||||||
},
|
export const defaultContainerMaxHeight = '100%'
|
||||||
} as const satisfies Theme
|
export const defaultContainerBackgroundColor = 'transparent'
|
||||||
|
export const defaultContainerColor = '#27272A'
|
||||||
|
|
||||||
|
// Host bubbles
|
||||||
|
export const defaultHostBubblesBackgroundColor = '#F7F8FF'
|
||||||
|
export const defaultHostBubblesColor = defaultLightTextColor
|
||||||
|
|
||||||
|
// Guest bubbles
|
||||||
|
export const defaultGuestBubblesBackgroundColor = '#FF8E21'
|
||||||
|
export const defaultGuestBubblesColor = defaultDarkTextColor
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
export const defaultButtonsBackgroundColor = '#0042DA'
|
||||||
|
export const defaultButtonsColor = defaultDarkTextColor
|
||||||
|
export const defaultButtonsBorderThickness = 1
|
||||||
|
|
||||||
|
// Inputs
|
||||||
|
export const defaultInputsBackgroundColor = '#FFFFFF'
|
||||||
|
export const defaultInputsColor = defaultLightTextColor
|
||||||
|
export const defaultInputsPlaceholderColor = '#9095A0'
|
||||||
|
export const defaultInputsShadow = 'md'
|
||||||
|
|
||||||
|
// Host avatar
|
||||||
|
export const defaultHostAvatarIsEnabled = true
|
||||||
|
|
||||||
|
// Guest avatar
|
||||||
|
export const defaultGuestAvatarIsEnabled = false
|
||||||
|
@ -2,9 +2,11 @@ import { ThemeTemplate as ThemeTemplatePrisma } from '@typebot.io/prisma'
|
|||||||
import { z } from '../../../zod'
|
import { z } from '../../../zod'
|
||||||
import {
|
import {
|
||||||
BackgroundType,
|
BackgroundType,
|
||||||
|
borderRoundness,
|
||||||
fontTypes,
|
fontTypes,
|
||||||
progressBarPlacements,
|
progressBarPlacements,
|
||||||
progressBarPositions,
|
progressBarPositions,
|
||||||
|
shadows,
|
||||||
} from './constants'
|
} from './constants'
|
||||||
|
|
||||||
const avatarPropsSchema = z.object({
|
const avatarPropsSchema = z.object({
|
||||||
@ -12,25 +14,48 @@ const avatarPropsSchema = z.object({
|
|||||||
url: z.string().optional(),
|
url: z.string().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const containerColorsSchema = z.object({
|
const containerBorderThemeSchema = z.object({
|
||||||
backgroundColor: z.string().optional(),
|
thickness: z.number().optional(),
|
||||||
color: z.string().optional(),
|
color: z.string().optional(),
|
||||||
|
roundeness: z.enum(borderRoundness).optional(),
|
||||||
|
customRoundeness: z.number().optional(),
|
||||||
|
opacity: z.number().min(0).max(1).optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const inputColorsSchema = containerColorsSchema.merge(
|
export type ContainerBorderTheme = z.infer<typeof containerBorderThemeSchema>
|
||||||
z.object({
|
|
||||||
placeholderColor: z.string().optional(),
|
const containerThemeSchema = z.object({
|
||||||
|
backgroundColor: z.string().optional(),
|
||||||
|
color: z.string().optional(),
|
||||||
|
blur: z.number().optional(),
|
||||||
|
opacity: z.number().min(0).max(1).optional(),
|
||||||
|
shadow: z.enum(shadows).optional(),
|
||||||
|
border: containerBorderThemeSchema.optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const inputThemeSchema = containerThemeSchema.extend({
|
||||||
|
placeholderColor: z.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const chatContainerSchema = z
|
||||||
|
.object({
|
||||||
|
maxWidth: z.string().optional(),
|
||||||
|
maxHeight: z.string().optional(),
|
||||||
})
|
})
|
||||||
)
|
.merge(containerThemeSchema)
|
||||||
|
|
||||||
export const chatThemeSchema = z.object({
|
export const chatThemeSchema = z.object({
|
||||||
|
container: chatContainerSchema.optional(),
|
||||||
hostAvatar: avatarPropsSchema.optional(),
|
hostAvatar: avatarPropsSchema.optional(),
|
||||||
guestAvatar: avatarPropsSchema.optional(),
|
guestAvatar: avatarPropsSchema.optional(),
|
||||||
hostBubbles: containerColorsSchema.optional(),
|
hostBubbles: containerThemeSchema.optional(),
|
||||||
guestBubbles: containerColorsSchema.optional(),
|
guestBubbles: containerThemeSchema.optional(),
|
||||||
buttons: containerColorsSchema.optional(),
|
buttons: containerThemeSchema.optional(),
|
||||||
inputs: inputColorsSchema.optional(),
|
inputs: inputThemeSchema.optional(),
|
||||||
roundness: z.enum(['none', 'medium', 'large']).optional(),
|
roundness: z
|
||||||
|
.enum(['none', 'medium', 'large'])
|
||||||
|
.optional()
|
||||||
|
.describe('Deprecated, use `container.border.roundeness` instead'),
|
||||||
})
|
})
|
||||||
|
|
||||||
const backgroundSchema = z.object({
|
const backgroundSchema = z.object({
|
||||||
@ -98,6 +123,6 @@ export type ChatTheme = z.infer<typeof chatThemeSchema>
|
|||||||
export type AvatarProps = z.infer<typeof avatarPropsSchema>
|
export type AvatarProps = z.infer<typeof avatarPropsSchema>
|
||||||
export type GeneralTheme = z.infer<typeof generalThemeSchema>
|
export type GeneralTheme = z.infer<typeof generalThemeSchema>
|
||||||
export type Background = z.infer<typeof backgroundSchema>
|
export type Background = z.infer<typeof backgroundSchema>
|
||||||
export type ContainerColors = z.infer<typeof containerColorsSchema>
|
export type ContainerTheme = z.infer<typeof containerThemeSchema>
|
||||||
export type InputColors = z.infer<typeof inputColorsSchema>
|
export type InputTheme = z.infer<typeof inputThemeSchema>
|
||||||
export type ThemeTemplate = z.infer<typeof themeTemplateSchema>
|
export type ThemeTemplate = z.infer<typeof themeTemplateSchema>
|
||||||
|
41
packages/theme/isChatContainerLight.ts
Normal file
41
packages/theme/isChatContainerLight.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { isLight } from '@typebot.io/lib/hexToRgb'
|
||||||
|
import { ContainerTheme, GeneralTheme } from '@typebot.io/schemas'
|
||||||
|
import {
|
||||||
|
BackgroundType,
|
||||||
|
defaultBackgroundColor,
|
||||||
|
defaultBackgroundType,
|
||||||
|
defaultContainerBackgroundColor,
|
||||||
|
defaultOpacity,
|
||||||
|
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||||
|
import { isEmpty, isNotEmpty } from '@typebot.io/lib'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
chatContainer: ContainerTheme | undefined
|
||||||
|
generalBackground: GeneralTheme['background']
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isChatContainerLight = ({
|
||||||
|
chatContainer,
|
||||||
|
generalBackground,
|
||||||
|
}: Props): boolean => {
|
||||||
|
const chatContainerBgColor =
|
||||||
|
chatContainer?.backgroundColor ?? defaultContainerBackgroundColor
|
||||||
|
const ignoreChatBackground =
|
||||||
|
(chatContainer?.opacity ?? defaultOpacity) <= 0.3 ||
|
||||||
|
chatContainerBgColor === 'transparent' ||
|
||||||
|
isEmpty(chatContainerBgColor)
|
||||||
|
|
||||||
|
if (ignoreChatBackground) {
|
||||||
|
const bgType = generalBackground?.type ?? defaultBackgroundType
|
||||||
|
const backgroundColor =
|
||||||
|
bgType === BackgroundType.IMAGE
|
||||||
|
? '#000000'
|
||||||
|
: bgType === BackgroundType.COLOR &&
|
||||||
|
isNotEmpty(generalBackground?.content)
|
||||||
|
? generalBackground.content
|
||||||
|
: '#ffffff'
|
||||||
|
return isLight(backgroundColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
return isLight(chatContainer?.backgroundColor ?? defaultBackgroundColor)
|
||||||
|
}
|
13
packages/theme/package.json
Normal file
13
packages/theme/package.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "@typebot.io/theme",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"scripts": {},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "Baptiste Arnaud",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@typebot.io/schemas": "workspace:*",
|
||||||
|
"@typebot.io/lib": "workspace:*"
|
||||||
|
}
|
||||||
|
}
|
15
pnpm-lock.yaml
generated
15
pnpm-lock.yaml
generated
@ -122,6 +122,9 @@ importers:
|
|||||||
'@typebot.io/nextjs':
|
'@typebot.io/nextjs':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/embeds/nextjs
|
version: link:../../packages/embeds/nextjs
|
||||||
|
'@typebot.io/theme':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/theme
|
||||||
'@udecode/cn':
|
'@udecode/cn':
|
||||||
specifier: 29.0.1
|
specifier: 29.0.1
|
||||||
version: 29.0.1(@types/react@18.2.15)(class-variance-authority@0.7.0)(react-dom@18.2.0)(react@18.2.0)(tailwind-merge@2.2.1)
|
version: 29.0.1(@types/react@18.2.15)(class-variance-authority@0.7.0)(react-dom@18.2.0)(react@18.2.0)(tailwind-merge@2.2.1)
|
||||||
@ -1048,6 +1051,9 @@ importers:
|
|||||||
'@typebot.io/schemas':
|
'@typebot.io/schemas':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../schemas
|
version: link:../../schemas
|
||||||
|
'@typebot.io/theme':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../theme
|
||||||
'@typebot.io/tsconfig':
|
'@typebot.io/tsconfig':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../tsconfig
|
version: link:../../tsconfig
|
||||||
@ -1925,6 +1931,15 @@ importers:
|
|||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../tsconfig
|
version: link:../tsconfig
|
||||||
|
|
||||||
|
packages/theme:
|
||||||
|
dependencies:
|
||||||
|
'@typebot.io/lib':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../lib
|
||||||
|
'@typebot.io/schemas':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../schemas
|
||||||
|
|
||||||
packages/transactional:
|
packages/transactional:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@react-email/components':
|
'@react-email/components':
|
||||||
|
Reference in New Issue
Block a user