✨ (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
|
||||
|
||||
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,
|
||||
"typescript.updateImportsOnFileMove.enabled": "always",
|
||||
"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",
|
||||
"NEXTAUTH_URL": "http://localhost:3000"
|
||||
"NEXTAUTH_URL": "http://localhost:3000",
|
||||
"ENCRYPTION_SECRET": "H+KbL/OFrqbEuDy/1zX8bsPG+spXri3S"
|
||||
},
|
||||
"[prisma]": {
|
||||
"editor.defaultFormatter": "Prisma.prisma"
|
||||
|
@ -13,6 +13,7 @@
|
||||
"format:check": "prettier --check ./src --ignore-path ../../.prettierignore"
|
||||
},
|
||||
"dependencies": {
|
||||
"@typebot.io/theme": "workspace:*",
|
||||
"@braintree/sanitize-url": "7.0.1",
|
||||
"@chakra-ui/anatomy": "2.1.1",
|
||||
"@chakra-ui/react": "2.7.1",
|
||||
|
@ -35,10 +35,16 @@ const colorsSelection: `#${string}`[] = [
|
||||
type Props = {
|
||||
value?: string
|
||||
defaultValue?: string
|
||||
isDisabled?: boolean
|
||||
onColorChange: (color: string) => void
|
||||
}
|
||||
|
||||
export const ColorPicker = ({ value, defaultValue, onColorChange }: Props) => {
|
||||
export const ColorPicker = ({
|
||||
value,
|
||||
defaultValue,
|
||||
isDisabled,
|
||||
onColorChange,
|
||||
}: Props) => {
|
||||
const { t } = useTranslate()
|
||||
const [color, setColor] = useState(defaultValue ?? '')
|
||||
const displayedValue = value ?? color
|
||||
@ -63,6 +69,7 @@ export const ColorPicker = ({ value, defaultValue, onColorChange }: Props) => {
|
||||
padding={0}
|
||||
borderRadius={3}
|
||||
borderWidth={1}
|
||||
isDisabled={isDisabled}
|
||||
>
|
||||
<Box rounded="full" boxSize="14px" bgColor={displayedValue} />
|
||||
</Button>
|
||||
|
@ -141,6 +141,8 @@ export const UnsplashPicker = ({ imageSize, onImageSelect }: Props) => {
|
||||
fetchNewImages(query, 0)
|
||||
}}
|
||||
withVariableButton={false}
|
||||
debounceTimeout={500}
|
||||
forceDebounce
|
||||
/>
|
||||
<Link
|
||||
isExternal
|
||||
|
@ -24,6 +24,7 @@ import { env } from '@typebot.io/env'
|
||||
import { MoreInfoTooltip } from '../MoreInfoTooltip'
|
||||
|
||||
export type TextInputProps = {
|
||||
forceDebounce?: boolean
|
||||
defaultValue?: string
|
||||
onChange?: (value: string) => void
|
||||
debounceTimeout?: number
|
||||
@ -62,6 +63,7 @@ export const TextInput = forwardRef(function TextInput(
|
||||
autoComplete,
|
||||
isDisabled,
|
||||
autoFocus,
|
||||
forceDebounce,
|
||||
onChange: _onChange,
|
||||
onFocus,
|
||||
onKeyUp,
|
||||
@ -83,7 +85,7 @@ export const TextInput = forwardRef(function TextInput(
|
||||
const onChange = useDebouncedCallback(
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
_onChange ?? (() => {}),
|
||||
env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
|
||||
env.NEXT_PUBLIC_E2E_TEST && !forceDebounce ? 0 : debounceTimeout
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -73,7 +73,6 @@ export const PreviewDrawer = () => {
|
||||
right="0"
|
||||
top={`0`}
|
||||
h={`100%`}
|
||||
w={`${width}px`}
|
||||
bgColor={useColorModeValue('white', 'gray.900')}
|
||||
borderLeftWidth={'1px'}
|
||||
shadow="lg"
|
||||
@ -82,6 +81,7 @@ export const PreviewDrawer = () => {
|
||||
onMouseLeave={() => setIsResizeHandleVisible(false)}
|
||||
p="6"
|
||||
zIndex={10}
|
||||
style={{ width: `${width}px` }}
|
||||
>
|
||||
<Fade in={isResizeHandleVisible}>
|
||||
<ResizeHandle
|
||||
|
@ -5,13 +5,13 @@ import { Typebot } from '@typebot.io/schemas'
|
||||
import { useState } from 'react'
|
||||
import { BubbleSettings } from '../../../settings/BubbleSettings/BubbleSettings'
|
||||
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) => ({
|
||||
button: {
|
||||
backgroundColor:
|
||||
typebot?.theme.chat?.buttons?.backgroundColor ??
|
||||
defaultTheme.chat.buttons.backgroundColor,
|
||||
defaultButtonsBackgroundColor,
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -8,6 +8,7 @@ export const DefaultAvatar = (props: IconProps) => {
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
boxSize="40px"
|
||||
borderRadius="full"
|
||||
data-testid="default-avatar"
|
||||
{...props}
|
||||
>
|
||||
|
@ -35,7 +35,7 @@ export const ThemeSideMenu = () => {
|
||||
typebot &&
|
||||
updateTypebot({ updates: { theme: { ...typebot.theme, customCss } } })
|
||||
|
||||
const selectedTemplate = (
|
||||
const selectTemplate = (
|
||||
selectedTemplate: Partial<Pick<ThemeTemplate, 'id' | 'theme'>>
|
||||
) => {
|
||||
if (!typebot) return
|
||||
@ -56,6 +56,8 @@ export const ThemeSideMenu = () => {
|
||||
},
|
||||
})
|
||||
|
||||
const templateId = typebot?.selectedThemeTemplateId ?? undefined
|
||||
|
||||
return (
|
||||
<Stack
|
||||
flex="1"
|
||||
@ -84,12 +86,10 @@ export const ThemeSideMenu = () => {
|
||||
<AccordionPanel pb={12}>
|
||||
{typebot && (
|
||||
<ThemeTemplates
|
||||
selectedTemplateId={
|
||||
typebot.selectedThemeTemplateId ?? undefined
|
||||
}
|
||||
selectedTemplateId={templateId}
|
||||
currentTheme={typebot.theme}
|
||||
workspaceId={typebot.workspaceId}
|
||||
onTemplateSelect={selectedTemplate}
|
||||
onTemplateSelect={selectTemplate}
|
||||
/>
|
||||
)}
|
||||
</AccordionPanel>
|
||||
@ -106,6 +106,7 @@ export const ThemeSideMenu = () => {
|
||||
<AccordionPanel pb={4}>
|
||||
{typebot && (
|
||||
<GeneralSettings
|
||||
key={templateId}
|
||||
isBrandingEnabled={
|
||||
typebot.settings.general?.isBrandingEnabled ??
|
||||
defaultSettings.general.isBrandingEnabled
|
||||
@ -128,9 +129,11 @@ export const ThemeSideMenu = () => {
|
||||
<AccordionPanel pb={4}>
|
||||
{typebot && (
|
||||
<ChatThemeSettings
|
||||
key={templateId}
|
||||
workspaceId={typebot.workspaceId}
|
||||
typebotId={typebot.id}
|
||||
chatTheme={typebot.theme.chat}
|
||||
generalBackground={typebot.theme.general?.background}
|
||||
onChatThemeChange={updateChatTheme}
|
||||
/>
|
||||
)}
|
||||
@ -147,6 +150,7 @@ export const ThemeSideMenu = () => {
|
||||
<AccordionPanel pb={4}>
|
||||
{typebot && (
|
||||
<CustomCssSettings
|
||||
key={templateId}
|
||||
customCss={typebot.theme.customCss}
|
||||
onCustomCssChange={updateCustomCss}
|
||||
/>
|
||||
|
@ -19,8 +19,13 @@ import { Theme, ThemeTemplate } from '@typebot.io/schemas'
|
||||
import { useState } from 'react'
|
||||
import { DefaultAvatar } from './DefaultAvatar'
|
||||
import {
|
||||
defaultButtonsBackgroundColor,
|
||||
BackgroundType,
|
||||
defaultTheme,
|
||||
defaultGuestAvatarIsEnabled,
|
||||
defaultGuestBubblesBackgroundColor,
|
||||
defaultHostAvatarIsEnabled,
|
||||
defaultBackgroundColor,
|
||||
defaultHostBubblesBackgroundColor,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
@ -71,28 +76,28 @@ export const ThemeTemplateCard = ({
|
||||
const hostAvatar = {
|
||||
isEnabled:
|
||||
themeTemplate.theme.chat?.hostAvatar?.isEnabled ??
|
||||
defaultTheme.chat.hostAvatar.isEnabled,
|
||||
defaultHostAvatarIsEnabled,
|
||||
url: themeTemplate.theme.chat?.hostAvatar?.url,
|
||||
}
|
||||
|
||||
const hostBubbleBgColor =
|
||||
themeTemplate.theme.chat?.hostBubbles?.backgroundColor ??
|
||||
defaultTheme.chat.hostBubbles.backgroundColor
|
||||
defaultHostBubblesBackgroundColor
|
||||
|
||||
const guestAvatar = {
|
||||
isEnabled:
|
||||
themeTemplate.theme.chat?.guestAvatar?.isEnabled ??
|
||||
defaultTheme.chat.guestAvatar.isEnabled,
|
||||
defaultGuestAvatarIsEnabled,
|
||||
url: themeTemplate.theme.chat?.guestAvatar?.url,
|
||||
}
|
||||
|
||||
const guestBubbleBgColor =
|
||||
themeTemplate.theme.chat?.guestBubbles?.backgroundColor ??
|
||||
defaultTheme.chat.guestBubbles.backgroundColor
|
||||
defaultGuestBubblesBackgroundColor
|
||||
|
||||
const buttonBgColor =
|
||||
themeTemplate.theme.chat?.buttons?.backgroundColor ??
|
||||
defaultTheme.chat.buttons.backgroundColor
|
||||
defaultButtonsBackgroundColor
|
||||
|
||||
return (
|
||||
<Stack
|
||||
@ -197,8 +202,7 @@ const parseBackground = (
|
||||
case undefined:
|
||||
case BackgroundType.COLOR:
|
||||
return {
|
||||
backgroundColor:
|
||||
background?.content ?? defaultTheme.general.background.content,
|
||||
backgroundColor: background?.content ?? defaultBackgroundColor,
|
||||
}
|
||||
case BackgroundType.IMAGE:
|
||||
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 { AvatarProps, ChatTheme, Theme } from '@typebot.io/schemas'
|
||||
import {
|
||||
AvatarProps,
|
||||
ChatTheme,
|
||||
GeneralTheme,
|
||||
Theme,
|
||||
} from '@typebot.io/schemas'
|
||||
import React from 'react'
|
||||
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 { 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 = {
|
||||
workspaceId: string
|
||||
typebotId: string
|
||||
generalBackground: GeneralTheme['background']
|
||||
chatTheme: Theme['chat']
|
||||
onChatThemeChange: (chatTheme: ChatTheme) => void
|
||||
}
|
||||
@ -26,6 +39,7 @@ export const ChatThemeSettings = ({
|
||||
workspaceId,
|
||||
typebotId,
|
||||
chatTheme,
|
||||
generalBackground,
|
||||
onChatThemeChange,
|
||||
}: Props) => {
|
||||
const { t } = useTranslate()
|
||||
@ -44,6 +58,16 @@ export const ChatThemeSettings = ({
|
||||
const updateInputs = (inputs: NonNullable<Theme['chat']>['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) =>
|
||||
onChatThemeChange({ ...chatTheme, hostAvatar })
|
||||
|
||||
@ -52,6 +76,14 @@ export const ChatThemeSettings = ({
|
||||
|
||||
return (
|
||||
<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
|
||||
uploadFileProps={{
|
||||
workspaceId,
|
||||
@ -59,7 +91,7 @@ export const ChatThemeSettings = ({
|
||||
fileName: 'hostAvatar',
|
||||
}}
|
||||
title={t('theme.sideMenu.chat.botAvatar')}
|
||||
avatarProps={chatTheme?.hostAvatar ?? defaultTheme.chat.hostAvatar}
|
||||
avatarProps={chatTheme?.hostAvatar}
|
||||
isDefaultCheck
|
||||
onAvatarChange={updateHostAvatar}
|
||||
/>
|
||||
@ -70,63 +102,83 @@ export const ChatThemeSettings = ({
|
||||
fileName: 'guestAvatar',
|
||||
}}
|
||||
title={t('theme.sideMenu.chat.userAvatar')}
|
||||
avatarProps={chatTheme?.guestAvatar ?? defaultTheme.chat.guestAvatar}
|
||||
avatarProps={chatTheme?.guestAvatar}
|
||||
onAvatarChange={updateGuestAvatar}
|
||||
/>
|
||||
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
||||
<Heading fontSize="lg">{t('theme.sideMenu.chat.botBubbles')}</Heading>
|
||||
<HostBubbles
|
||||
hostBubbles={chatTheme?.hostBubbles ?? defaultTheme.chat.hostBubbles}
|
||||
onHostBubblesChange={updateHostBubbles}
|
||||
<ContainerThemeForm
|
||||
testId="hostBubblesTheme"
|
||||
theme={chatTheme?.hostBubbles}
|
||||
onThemeChange={updateHostBubbles}
|
||||
defaultTheme={{
|
||||
backgroundColor: defaultHostBubblesBackgroundColor,
|
||||
color: defaultHostBubblesColor,
|
||||
opacity: defaultOpacity,
|
||||
blur: defaultBlur,
|
||||
border: {
|
||||
roundeness: defaultRoundness,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
||||
<Heading fontSize="lg">{t('theme.sideMenu.chat.userBubbles')}</Heading>
|
||||
<GuestBubbles
|
||||
guestBubbles={
|
||||
chatTheme?.guestBubbles ?? defaultTheme.chat.guestBubbles
|
||||
}
|
||||
onGuestBubblesChange={updateGuestBubbles}
|
||||
<ContainerThemeForm
|
||||
testId="guestBubblesTheme"
|
||||
theme={chatTheme?.guestBubbles}
|
||||
onThemeChange={updateGuestBubbles}
|
||||
defaultTheme={{
|
||||
backgroundColor: defaultGuestBubblesBackgroundColor,
|
||||
color: defaultGuestBubblesColor,
|
||||
opacity: defaultOpacity,
|
||||
blur: defaultBlur,
|
||||
border: {
|
||||
roundeness: defaultRoundness,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
||||
<Heading fontSize="lg">{t('theme.sideMenu.chat.buttons')}</Heading>
|
||||
<ButtonsTheme
|
||||
buttons={chatTheme?.buttons ?? defaultTheme.chat.buttons}
|
||||
onButtonsChange={updateButtons}
|
||||
<ContainerThemeForm
|
||||
testId="buttonsTheme"
|
||||
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 borderWidth={1} rounded="md" p="4" spacing={4}>
|
||||
<Heading fontSize="lg">{t('theme.sideMenu.chat.inputs')}</Heading>
|
||||
<InputsTheme
|
||||
inputs={chatTheme?.inputs ?? defaultTheme.chat.inputs}
|
||||
onInputsChange={updateInputs}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
||||
<Heading fontSize="lg">
|
||||
{t('theme.sideMenu.chat.cornersRoundness')}
|
||||
</Heading>
|
||||
<RadioButtons
|
||||
options={[
|
||||
{
|
||||
label: <NoRadiusIcon />,
|
||||
value: 'none',
|
||||
<ContainerThemeForm
|
||||
testId="inputsTheme"
|
||||
theme={chatTheme?.inputs}
|
||||
onThemeChange={updateInputs}
|
||||
onPlaceholderColorChange={updateInputsPlaceholderColor}
|
||||
defaultTheme={{
|
||||
backgroundColor: defaultInputsBackgroundColor,
|
||||
color: defaultInputsColor,
|
||||
placeholderColor: defaultInputsPlaceholderColor,
|
||||
shadow: defaultInputsShadow,
|
||||
opacity: defaultOpacity,
|
||||
blur: defaultBlur,
|
||||
border: {
|
||||
roundeness: defaultRoundness,
|
||||
},
|
||||
{
|
||||
label: <MediumRadiusIcon />,
|
||||
value: 'medium',
|
||||
},
|
||||
{
|
||||
label: <LargeRadiusIcon />,
|
||||
value: 'large',
|
||||
},
|
||||
]}
|
||||
value={chatTheme?.roundness ?? defaultTheme.chat.roundness}
|
||||
onSelect={(roundness) =>
|
||||
onChatThemeChange({ ...chatTheme, roundness })
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</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 { ContainerColors } from '@typebot.io/schemas'
|
||||
import { ContainerTheme } from '@typebot.io/schemas'
|
||||
import React from 'react'
|
||||
import { ColorPicker } from '../../../../components/ColorPicker'
|
||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import {
|
||||
defaultGuestBubblesBackgroundColor,
|
||||
defaultGuestBubblesColor,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
|
||||
type Props = {
|
||||
guestBubbles: ContainerColors | undefined
|
||||
onGuestBubblesChange: (hostBubbles: ContainerColors | undefined) => void
|
||||
guestBubbles: ContainerTheme | undefined
|
||||
onGuestBubblesChange: (hostBubbles: ContainerTheme | undefined) => void
|
||||
}
|
||||
|
||||
export const GuestBubbles = ({ guestBubbles, onGuestBubblesChange }: Props) => {
|
||||
@ -25,8 +28,7 @@ export const GuestBubbles = ({ guestBubbles, onGuestBubblesChange }: Props) => {
|
||||
<Text>{t('theme.sideMenu.chat.theme.background')}</Text>
|
||||
<ColorPicker
|
||||
value={
|
||||
guestBubbles?.backgroundColor ??
|
||||
defaultTheme.chat.guestBubbles.backgroundColor
|
||||
guestBubbles?.backgroundColor ?? defaultGuestBubblesBackgroundColor
|
||||
}
|
||||
onColorChange={updateBackground}
|
||||
/>
|
||||
@ -34,7 +36,7 @@ export const GuestBubbles = ({ guestBubbles, onGuestBubblesChange }: Props) => {
|
||||
<Flex justify="space-between" align="center">
|
||||
<Text>{t('theme.sideMenu.chat.theme.text')}</Text>
|
||||
<ColorPicker
|
||||
value={guestBubbles?.color ?? defaultTheme.chat.guestBubbles.color}
|
||||
value={guestBubbles?.color ?? defaultGuestBubblesColor}
|
||||
onColorChange={updateText}
|
||||
/>
|
||||
</Flex>
|
||||
|
@ -1,13 +1,16 @@
|
||||
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 { ColorPicker } from '../../../../components/ColorPicker'
|
||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import {
|
||||
defaultHostBubblesBackgroundColor,
|
||||
defaultHostBubblesColor,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
|
||||
type Props = {
|
||||
hostBubbles: ContainerColors | undefined
|
||||
onHostBubblesChange: (hostBubbles: ContainerColors | undefined) => void
|
||||
hostBubbles: ContainerTheme | undefined
|
||||
onHostBubblesChange: (hostBubbles: ContainerTheme | undefined) => void
|
||||
}
|
||||
|
||||
export const HostBubbles = ({ hostBubbles, onHostBubblesChange }: Props) => {
|
||||
@ -24,8 +27,7 @@ export const HostBubbles = ({ hostBubbles, onHostBubblesChange }: Props) => {
|
||||
<Text>{t('theme.sideMenu.chat.theme.background')}</Text>
|
||||
<ColorPicker
|
||||
value={
|
||||
hostBubbles?.backgroundColor ??
|
||||
defaultTheme.chat.hostBubbles.backgroundColor
|
||||
hostBubbles?.backgroundColor ?? defaultHostBubblesBackgroundColor
|
||||
}
|
||||
onColorChange={handleBackgroundChange}
|
||||
/>
|
||||
@ -33,7 +35,7 @@ export const HostBubbles = ({ hostBubbles, onHostBubblesChange }: Props) => {
|
||||
<Flex justify="space-between" align="center">
|
||||
<Text>{t('theme.sideMenu.chat.theme.text')}</Text>
|
||||
<ColorPicker
|
||||
value={hostBubbles?.color ?? defaultTheme.chat.hostBubbles.color}
|
||||
value={hostBubbles?.color ?? defaultHostBubblesColor}
|
||||
onColorChange={handleTextChange}
|
||||
/>
|
||||
</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 {
|
||||
BackgroundType,
|
||||
defaultTheme,
|
||||
defaultBackgroundColor,
|
||||
defaultBackgroundType,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
@ -34,10 +35,7 @@ export const BackgroundContent = ({
|
||||
const handleContentChange = (content: string) =>
|
||||
onBackgroundContentChange(content)
|
||||
|
||||
if (
|
||||
(background?.type ?? defaultTheme.general.background.type) ===
|
||||
BackgroundType.IMAGE
|
||||
) {
|
||||
if ((background?.type ?? defaultBackgroundType) === BackgroundType.IMAGE) {
|
||||
if (!typebot) return null
|
||||
return (
|
||||
<Popover isLazy placement="top">
|
||||
@ -76,15 +74,12 @@ export const BackgroundContent = ({
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
if (
|
||||
(background?.type ?? defaultTheme.general.background.type) ===
|
||||
BackgroundType.COLOR
|
||||
) {
|
||||
if ((background?.type ?? defaultBackgroundType) === BackgroundType.COLOR) {
|
||||
return (
|
||||
<Flex justify="space-between" align="center">
|
||||
<Text>{t('theme.sideMenu.global.background.color')}</Text>
|
||||
<ColorPicker
|
||||
value={background?.content ?? defaultTheme.general.background.content}
|
||||
value={background?.content ?? defaultBackgroundColor}
|
||||
onColorChange={handleContentChange}
|
||||
/>
|
||||
</Flex>
|
||||
|
@ -1,11 +1,11 @@
|
||||
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 React from 'react'
|
||||
import { BackgroundContent } from './BackgroundContent'
|
||||
import {
|
||||
BackgroundType,
|
||||
defaultTheme,
|
||||
defaultBackgroundType,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
@ -28,7 +28,6 @@ export const BackgroundSelector = ({
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Text>{t('theme.sideMenu.global.background')}</Text>
|
||||
<RadioButtons
|
||||
options={[
|
||||
{
|
||||
@ -44,7 +43,7 @@ export const BackgroundSelector = ({
|
||||
value: BackgroundType.NONE,
|
||||
},
|
||||
]}
|
||||
value={background?.type ?? defaultTheme.general.background.type}
|
||||
value={background?.type ?? defaultBackgroundType}
|
||||
onSelect={handleBackgroundTypeChange}
|
||||
/>
|
||||
<BackgroundContent
|
||||
|
@ -4,7 +4,11 @@ import {
|
||||
Stack,
|
||||
Switch,
|
||||
useDisclosure,
|
||||
Text,
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
AccordionButton,
|
||||
AccordionPanel,
|
||||
AccordionIcon,
|
||||
} from '@chakra-ui/react'
|
||||
import { Background, Font, ProgressBar, Theme } from '@typebot.io/schemas'
|
||||
import React from 'react'
|
||||
@ -16,7 +20,7 @@ import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
||||
import { ChangePlanModal } from '@/features/billing/components/ChangePlanModal'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import {
|
||||
defaultTheme,
|
||||
defaultFontType,
|
||||
fontTypes,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
@ -91,7 +95,7 @@ export const GeneralSettings = ({
|
||||
const fontType =
|
||||
(typeof generalTheme?.font === 'string'
|
||||
? 'Google'
|
||||
: generalTheme?.font?.type) ?? defaultTheme.general.font.type
|
||||
: generalTheme?.font?.type) ?? defaultFontType
|
||||
|
||||
return (
|
||||
<Stack spacing={6}>
|
||||
@ -115,23 +119,46 @@ export const GeneralSettings = ({
|
||||
onChange={updateBranding}
|
||||
/>
|
||||
</Flex>
|
||||
<ProgressBarForm
|
||||
progressBar={generalTheme?.progressBar}
|
||||
onProgressBarChange={updateProgressBar}
|
||||
/>
|
||||
<Stack>
|
||||
<Text>{t('theme.sideMenu.global.font')}</Text>
|
||||
<RadioButtons
|
||||
options={fontTypes}
|
||||
defaultValue={fontType}
|
||||
onSelect={updateFontType}
|
||||
/>
|
||||
<FontForm font={generalTheme?.font} onFontChange={updateFont} />
|
||||
</Stack>
|
||||
<BackgroundSelector
|
||||
background={generalTheme?.background}
|
||||
onBackgroundChange={handleBackgroundChange}
|
||||
/>
|
||||
<Accordion allowToggle>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Progress Bar
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel>
|
||||
<ProgressBarForm
|
||||
progressBar={generalTheme?.progressBar}
|
||||
onProgressBarChange={updateProgressBar}
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
{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>
|
||||
)
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Select } from '@/components/inputs/Select'
|
||||
import { env } from '@typebot.io/env'
|
||||
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'
|
||||
|
||||
type Props = {
|
||||
@ -11,8 +11,7 @@ type Props = {
|
||||
|
||||
export const GoogleFontForm = ({ font, onFontChange }: Props) => {
|
||||
const [currentFont, setCurrentFont] = useState(
|
||||
(typeof font === 'string' ? font : font?.family) ??
|
||||
defaultTheme.general.font.family
|
||||
(typeof font === 'string' ? font : font?.family) ?? defaultFontFamily
|
||||
)
|
||||
const [googleFonts, setGoogleFonts] = useState<string[]>([])
|
||||
|
||||
|
@ -5,7 +5,11 @@ import { NumberInput } from '@/components/inputs'
|
||||
import { FormLabel, HStack } from '@chakra-ui/react'
|
||||
import { ProgressBar } from '@typebot.io/schemas'
|
||||
import {
|
||||
defaultTheme,
|
||||
defaultProgressBarColor,
|
||||
defaultProgressBarIsEnabled,
|
||||
defaultProgressBarPlacement,
|
||||
defaultProgressBarPosition,
|
||||
defaultProgressBarThickness,
|
||||
progressBarPlacements,
|
||||
progressBarPositions,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
@ -37,18 +41,14 @@ export const ProgressBarForm = ({
|
||||
return (
|
||||
<SwitchWithRelatedSettings
|
||||
label={'Enable progress bar?'}
|
||||
initialValue={
|
||||
progressBar?.isEnabled ?? defaultTheme.general.progressBar.isEnabled
|
||||
}
|
||||
initialValue={progressBar?.isEnabled ?? defaultProgressBarIsEnabled}
|
||||
onCheckChange={updateEnabled}
|
||||
>
|
||||
<DropdownList
|
||||
size="sm"
|
||||
direction="row"
|
||||
label="Placement:"
|
||||
currentItem={
|
||||
progressBar?.placement ?? defaultTheme.general.progressBar.placement
|
||||
}
|
||||
currentItem={progressBar?.placement ?? defaultProgressBarPlacement}
|
||||
onItemSelect={updatePlacement}
|
||||
items={progressBarPlacements}
|
||||
/>
|
||||
@ -58,9 +58,7 @@ export const ProgressBarForm = ({
|
||||
Color:
|
||||
</FormLabel>
|
||||
<ColorPicker
|
||||
defaultValue={
|
||||
progressBar?.color ?? defaultTheme.general.progressBar.color
|
||||
}
|
||||
defaultValue={progressBar?.color ?? defaultProgressBarColor}
|
||||
onColorChange={updateColor}
|
||||
/>
|
||||
</HStack>
|
||||
@ -69,9 +67,7 @@ export const ProgressBarForm = ({
|
||||
direction="row"
|
||||
withVariableButton={false}
|
||||
maxW="100px"
|
||||
defaultValue={
|
||||
progressBar?.thickness ?? defaultTheme.general.progressBar.thickness
|
||||
}
|
||||
defaultValue={progressBar?.thickness ?? defaultProgressBarThickness}
|
||||
onValueChange={updateThickness}
|
||||
size="sm"
|
||||
/>
|
||||
@ -80,9 +76,7 @@ export const ProgressBarForm = ({
|
||||
direction="row"
|
||||
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.'
|
||||
currentItem={
|
||||
progressBar?.position ?? defaultTheme.general.progressBar.position
|
||||
}
|
||||
currentItem={progressBar?.position ?? defaultProgressBarPosition}
|
||||
onItemSelect={updatePosition}
|
||||
items={progressBarPositions}
|
||||
/>
|
||||
|
@ -3,6 +3,10 @@ import test, { expect } from '@playwright/test'
|
||||
import { createId } from '@paralleldrive/cuid2'
|
||||
import { importTypebotInDatabase } from '@typebot.io/playwright/databaseActions'
|
||||
import { freeWorkspaceId } from '@typebot.io/playwright/databaseSetup'
|
||||
import {
|
||||
defaultContainerMaxHeight,
|
||||
defaultContainerMaxWidth,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
|
||||
const hostAvatarUrl =
|
||||
'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()
|
||||
|
||||
// Font
|
||||
await page.getByRole('button', { name: 'Font' }).click()
|
||||
await page.getByRole('textbox').fill('Roboto Slab')
|
||||
await page.getByRole('menuitem', { name: 'Roboto Slab' }).click()
|
||||
await expect(page.locator('.typebot-container')).toHaveCSS(
|
||||
@ -42,6 +47,7 @@ test.describe.parallel('Theme page', () => {
|
||||
'background-color',
|
||||
'rgba(0, 0, 0, 0)'
|
||||
)
|
||||
await page.getByRole('button', { name: 'Background' }).click()
|
||||
await page.click('text=Color')
|
||||
await page.waitForTimeout(100)
|
||||
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 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
|
||||
await expect(
|
||||
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()
|
||||
|
||||
// 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
|
||||
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.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')
|
||||
const hostBubble = page.locator('[data-testid="host-bubble"] >> nth=-1')
|
||||
@ -146,11 +158,11 @@ test.describe.parallel('Theme page', () => {
|
||||
|
||||
// Buttons
|
||||
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.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')
|
||||
const button = page.getByRole('button', { name: 'Go' })
|
||||
@ -159,11 +171,11 @@ test.describe.parallel('Theme page', () => {
|
||||
|
||||
// Guest bubbles
|
||||
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.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.getByRole('button', { name: 'Go' }).click()
|
||||
@ -192,11 +204,11 @@ test.describe.parallel('Theme page', () => {
|
||||
await page.waitForTimeout(1000)
|
||||
// Input
|
||||
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.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')
|
||||
const input = page.locator('.typebot-input')
|
||||
|
@ -21342,6 +21342,70 @@
|
||||
"chat": {
|
||||
"type": "object",
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -21372,6 +21436,53 @@
|
||||
},
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -21383,6 +21494,53 @@
|
||||
},
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -21394,6 +21552,53 @@
|
||||
},
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -21406,6 +21611,53 @@
|
||||
"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
|
||||
}
|
||||
}
|
||||
},
|
||||
"placeholderColor": {
|
||||
"type": "string"
|
||||
}
|
||||
@ -21417,7 +21669,8 @@
|
||||
"none",
|
||||
"medium",
|
||||
"large"
|
||||
]
|
||||
],
|
||||
"description": "Deprecated, use `container.border.roundeness` instead"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -6821,6 +6821,70 @@
|
||||
"chat": {
|
||||
"type": "object",
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -6851,6 +6915,53 @@
|
||||
},
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -6862,6 +6973,53 @@
|
||||
},
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -6873,6 +7031,53 @@
|
||||
},
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -6885,6 +7090,53 @@
|
||||
"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
|
||||
}
|
||||
}
|
||||
},
|
||||
"placeholderColor": {
|
||||
"type": "string"
|
||||
}
|
||||
@ -6896,7 +7148,8 @@
|
||||
"none",
|
||||
"medium",
|
||||
"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 { env } from '@typebot.io/env'
|
||||
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 {
|
||||
defaultBackgroundColor,
|
||||
defaultBackgroundType,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
|
||||
// Browsers that doesn't support ES modules and/or web components
|
||||
const incompatibleBrowsers = [
|
||||
@ -109,9 +112,10 @@ const getTypebotFromPublicId = async (publicId?: string) => {
|
||||
? ({
|
||||
name: publishedTypebot.typebot.name,
|
||||
publicId: publishedTypebot.typebot.publicId ?? null,
|
||||
background:
|
||||
publishedTypebot.theme.general?.background ??
|
||||
defaultTheme.general.background,
|
||||
background: publishedTypebot.theme.general?.background ?? {
|
||||
type: defaultBackgroundType,
|
||||
content: defaultBackgroundColor,
|
||||
},
|
||||
isHideQueryParamsEnabled:
|
||||
publishedTypebot.settings.general?.isHideQueryParamsEnabled ??
|
||||
defaultSettings.general.isHideQueryParamsEnabled,
|
||||
@ -156,9 +160,10 @@ const getTypebotFromCustomDomain = async (customDomain: string) => {
|
||||
? ({
|
||||
name: publishedTypebot.typebot.name,
|
||||
publicId: publishedTypebot.typebot.publicId ?? null,
|
||||
background:
|
||||
publishedTypebot.theme.general?.background ??
|
||||
defaultTheme.general.background,
|
||||
background: publishedTypebot.theme.general?.background ?? {
|
||||
type: defaultBackgroundType,
|
||||
content: defaultBackgroundColor,
|
||||
},
|
||||
isHideQueryParamsEnabled:
|
||||
publishedTypebot.settings.general?.isHideQueryParamsEnabled ??
|
||||
defaultSettings.general.isHideQueryParamsEnabled,
|
||||
@ -233,7 +238,10 @@ const App = ({
|
||||
defaultSettings.general.isHideQueryParamsEnabled
|
||||
}
|
||||
background={
|
||||
publishedTypebot.background ?? defaultTheme.general.background
|
||||
publishedTypebot.background ?? {
|
||||
type: defaultBackgroundType,
|
||||
content: defaultBackgroundColor,
|
||||
}
|
||||
}
|
||||
metadata={publishedTypebot.metadata ?? {}}
|
||||
font={publishedTypebot.font}
|
||||
|
@ -34,11 +34,14 @@ import { continueBotFlow } from './continueBotFlow'
|
||||
import { parseVariables } from '@typebot.io/variables/parseVariables'
|
||||
import { defaultSettings } from '@typebot.io/schemas/features/typebot/settings/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 { env } from '@typebot.io/env'
|
||||
import { getFirstEdgeId } from './getFirstEdgeId'
|
||||
import { Reply } from './types'
|
||||
import {
|
||||
defaultGuestAvatarIsEnabled,
|
||||
defaultHostAvatarIsEnabled,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
|
||||
type StartParams =
|
||||
| ({
|
||||
@ -385,12 +388,11 @@ const getResult = async ({
|
||||
|
||||
const parseDynamicThemeInState = (theme: Theme) => {
|
||||
const hostAvatarUrl =
|
||||
theme.chat?.hostAvatar?.isEnabled ?? defaultTheme.chat.hostAvatar.isEnabled
|
||||
theme.chat?.hostAvatar?.isEnabled ?? defaultHostAvatarIsEnabled
|
||||
? theme.chat?.hostAvatar?.url
|
||||
: undefined
|
||||
const guestAvatarUrl =
|
||||
theme.chat?.guestAvatar?.isEnabled ??
|
||||
defaultTheme.chat.guestAvatar.isEnabled
|
||||
theme.chat?.guestAvatar?.isEnabled ?? defaultGuestAvatarIsEnabled
|
||||
? theme.chat?.guestAvatar?.url
|
||||
: undefined
|
||||
if (!hostAvatarUrl?.startsWith('{{') && !guestAvatarUrl?.startsWith('{{'))
|
||||
|
@ -1,9 +1,9 @@
|
||||
import {
|
||||
Background,
|
||||
ChatTheme,
|
||||
ContainerColors,
|
||||
ContainerTheme,
|
||||
GeneralTheme,
|
||||
InputColors,
|
||||
InputTheme,
|
||||
Theme,
|
||||
} from '@typebot.io/schemas'
|
||||
import { BackgroundType } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
@ -66,7 +66,7 @@ const setChatTheme = (
|
||||
}
|
||||
|
||||
const setHostBubbles = (
|
||||
hostBubbles: ContainerColors,
|
||||
hostBubbles: ContainerTheme,
|
||||
documentStyle: CSSStyleDeclaration
|
||||
) => {
|
||||
if (hostBubbles.backgroundColor)
|
||||
@ -82,7 +82,7 @@ const setHostBubbles = (
|
||||
}
|
||||
|
||||
const setGuestBubbles = (
|
||||
guestBubbles: ContainerColors,
|
||||
guestBubbles: any,
|
||||
documentStyle: CSSStyleDeclaration
|
||||
) => {
|
||||
if (guestBubbles.backgroundColor)
|
||||
@ -98,7 +98,7 @@ const setGuestBubbles = (
|
||||
}
|
||||
|
||||
const setButtons = (
|
||||
buttons: ContainerColors,
|
||||
buttons: ContainerTheme,
|
||||
documentStyle: CSSStyleDeclaration
|
||||
) => {
|
||||
if (buttons.backgroundColor)
|
||||
@ -113,7 +113,7 @@ const setButtons = (
|
||||
)
|
||||
}
|
||||
|
||||
const setInputs = (inputs: InputColors, documentStyle: CSSStyleDeclaration) => {
|
||||
const setInputs = (inputs: InputTheme, documentStyle: CSSStyleDeclaration) => {
|
||||
if (inputs.backgroundColor)
|
||||
documentStyle.setProperty(
|
||||
cssVariableNames.chat.inputs.bgColor,
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/js",
|
||||
"version": "0.2.64",
|
||||
"version": "0.2.65",
|
||||
"description": "Javascript library to display typebots on your website",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
@ -31,6 +31,7 @@
|
||||
"@typebot.io/env": "workspace:*",
|
||||
"@typebot.io/lib": "workspace:*",
|
||||
"@typebot.io/schemas": "workspace:*",
|
||||
"@typebot.io/theme": "workspace:*",
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"@types/dompurify": "3.0.3",
|
||||
"autoprefixer": "10.4.14",
|
||||
|
@ -2,47 +2,6 @@
|
||||
@tailwind components;
|
||||
@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 */
|
||||
.scrollable-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
@ -161,67 +120,100 @@ pre {
|
||||
.typebot-container {
|
||||
background-image: var(--typebot-container-bg-image);
|
||||
background-color: var(--typebot-container-bg-color);
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
font-family: var(--typebot-container-font-family), -apple-system,
|
||||
BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,
|
||||
'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 {
|
||||
color: var(--typebot-button-color);
|
||||
background-color: var(--typebot-button-bg-color);
|
||||
border: 1px solid var(--typebot-button-bg-color);
|
||||
border-radius: var(--typebot-border-radius);
|
||||
background-color: rgba(
|
||||
var(--typebot-button-bg-rgb),
|
||||
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;
|
||||
}
|
||||
|
||||
.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 {
|
||||
border: 1px solid
|
||||
rgba(
|
||||
var(--typebot-button-bg-color-rgb),
|
||||
calc(var(--selectable-base-alpha) + 0.25)
|
||||
);
|
||||
border-radius: var(--typebot-border-radius);
|
||||
color: var(--typebot-container-color);
|
||||
border-width: var(--typebot-button-border-width);
|
||||
border-color: rgba(
|
||||
var(--typebot-button-border-rgb),
|
||||
calc(var(--selectable-alpha-ratio) * 0.25)
|
||||
);
|
||||
border-radius: var(--typebot-button-border-radius);
|
||||
color: rgb(var(--typebot-chat-container-color));
|
||||
background-color: rgba(
|
||||
var(--typebot-button-bg-color-rgb),
|
||||
calc(var(--selectable-base-alpha) + 0.08)
|
||||
var(--typebot-button-bg-rgb),
|
||||
calc(var(--selectable-alpha-ratio) * 0.08)
|
||||
);
|
||||
transition: all 0.3s ease;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.typebot-selectable:hover {
|
||||
background-color: rgba(
|
||||
var(--typebot-button-bg-color-rgb),
|
||||
calc(var(--selectable-base-alpha) + 0.12)
|
||||
var(--typebot-button-bg-rgb),
|
||||
calc(var(--selectable-alpha-ratio) * 0.12)
|
||||
);
|
||||
border-color: rgba(
|
||||
var(--typebot-button-bg-color-rgb),
|
||||
calc(var(--selectable-base-alpha) + 0.3)
|
||||
var(--typebot-button-border-rgb),
|
||||
calc(var(--selectable-alpha-ratio) * 0.3)
|
||||
);
|
||||
}
|
||||
|
||||
.typebot-selectable.selected {
|
||||
background-color: rgba(
|
||||
var(--typebot-button-bg-color-rgb),
|
||||
calc(var(--selectable-base-alpha) + 0.18)
|
||||
var(--typebot-button-bg-rgb),
|
||||
calc(var(--selectable-alpha-ratio) * 0.18)
|
||||
);
|
||||
border-color: rgba(
|
||||
var(--typebot-button-bg-color-rgb),
|
||||
calc(var(--selectable-base-alpha) + 0.35)
|
||||
var(--typebot-button-border-rgb),
|
||||
calc(var(--selectable-alpha-ratio) * 0.35)
|
||||
);
|
||||
}
|
||||
|
||||
.typebot-checkbox {
|
||||
border: 1px solid var(--typebot-button-bg-color);
|
||||
border-radius: var(--typebot-border-radius);
|
||||
background-color: var(--typebot-checkbox-bg-color);
|
||||
border: 1px solid
|
||||
rgba(var(--typebot-button-bg-rgb), var(--typebot-button-opacity));
|
||||
border-radius: var(--typebot-button-border-radius);
|
||||
background-color: rgba(var(--typebot-checkbox-bg-rgb));
|
||||
color: var(--typebot-button-color);
|
||||
padding: 1px;
|
||||
border-radius: 2px;
|
||||
@ -229,7 +221,7 @@ pre {
|
||||
}
|
||||
|
||||
.typebot-checkbox.checked {
|
||||
background-color: var(--typebot-button-bg-color);
|
||||
background-color: rgb(var(--typebot-button-bg-rgb));
|
||||
}
|
||||
|
||||
.typebot-host-bubble {
|
||||
@ -237,22 +229,56 @@ pre {
|
||||
}
|
||||
|
||||
.typebot-host-bubble > .bubble-typing {
|
||||
background-color: var(--typebot-host-bubble-bg-color);
|
||||
border: var(--typebot-host-bubble-border);
|
||||
background-color: rgba(
|
||||
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;
|
||||
}
|
||||
|
||||
.typebot-guest-bubble {
|
||||
color: var(--typebot-guest-bubble-color);
|
||||
background-color: var(--typebot-guest-bubble-bg-color);
|
||||
border-radius: 6px;
|
||||
background-color: rgba(
|
||||
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 {
|
||||
color: var(--typebot-input-color);
|
||||
background-color: var(--typebot-input-bg-color);
|
||||
box-shadow: 0 2px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
border-radius: var(--typebot-border-radius);
|
||||
background-color: rgba(
|
||||
var(--typebot-input-bg-rgb),
|
||||
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 {
|
||||
@ -263,24 +289,20 @@ pre {
|
||||
fill: var(--typebot-button-color);
|
||||
}
|
||||
|
||||
.typebot-chat-view {
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
.ping span {
|
||||
background-color: var(--typebot-button-bg-color);
|
||||
background-color: rgb(var(--typebot-button-bg-rgb));
|
||||
}
|
||||
|
||||
.rating-icon-container svg {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
stroke: var(--typebot-button-bg-color);
|
||||
stroke: rgb(var(--typebot-button-bg-rgb));
|
||||
fill: var(--typebot-host-bubble-bg-color);
|
||||
transition: fill 100ms ease-out;
|
||||
}
|
||||
|
||||
.rating-icon-container.selected svg {
|
||||
fill: var(--typebot-button-bg-color);
|
||||
fill: rgb(var(--typebot-button-bg-rgb));
|
||||
}
|
||||
|
||||
.rating-icon-container:hover svg {
|
||||
@ -292,59 +314,60 @@ pre {
|
||||
}
|
||||
|
||||
.upload-progress-bar {
|
||||
background-color: var(--typebot-button-bg-color);
|
||||
border-radius: var(--typebot-border-radius);
|
||||
background-color: rgb(var(--typebot-button-bg-rgb));
|
||||
border-radius: var(--typebot-input-border-radius);
|
||||
}
|
||||
|
||||
.total-files-indicator {
|
||||
background-color: var(--typebot-button-bg-color);
|
||||
background-color: rgb(var(--typebot-button-bg-rgb));
|
||||
color: var(--typebot-button-color);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.typebot-upload-input {
|
||||
transition: border-color 100ms ease-out;
|
||||
border-radius: var(--typebot-border-radius);
|
||||
border-radius: var(--typebot-input-border-radius);
|
||||
}
|
||||
|
||||
.typebot-upload-input.dragging-over {
|
||||
border-color: var(--typebot-button-bg-color);
|
||||
border-color: rgb(var(--typebot-button-bg-rgb));
|
||||
}
|
||||
|
||||
.secondary-button {
|
||||
background-color: var(--typebot-host-bubble-bg-color);
|
||||
color: var(--typebot-host-bubble-color);
|
||||
border-radius: var(--typebot-border-radius);
|
||||
border-radius: var(--typebot-button-border-radius);
|
||||
}
|
||||
|
||||
.typebot-country-select {
|
||||
color: var(--typebot-input-color);
|
||||
background-color: var(--typebot-input-bg-color);
|
||||
border-radius: var(--typebot-border-radius);
|
||||
border-radius: var(--typebot-button-border-radius);
|
||||
}
|
||||
|
||||
.typebot-date-input {
|
||||
color-scheme: light;
|
||||
color: var(--typebot-input-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 {
|
||||
border-radius: var(--typebot-border-radius);
|
||||
border-radius: var(--typebot-input-border-radius);
|
||||
}
|
||||
|
||||
.typebot-picture-button {
|
||||
color: var(--typebot-button-color);
|
||||
background-color: var(--typebot-button-bg-color);
|
||||
border-radius: var(--typebot-border-radius);
|
||||
background-color: rgb(var(--typebot-button-bg-rgb));
|
||||
border-radius: var(--typebot-button-border-radius);
|
||||
transition: all 0.3s ease;
|
||||
width: 236px;
|
||||
}
|
||||
|
||||
.typebot-picture-button > 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;
|
||||
width: 100%;
|
||||
max-height: 200px;
|
||||
@ -362,14 +385,14 @@ pre {
|
||||
.typebot-selectable-picture {
|
||||
border: 1px solid
|
||||
rgba(
|
||||
var(--typebot-button-bg-color-rgb),
|
||||
calc(var(--selectable-base-alpha) + 0.25)
|
||||
var(--typebot-button-bg-rgb),
|
||||
calc(var(--selectable-alpha-ratio) * 0.25)
|
||||
);
|
||||
border-radius: var(--typebot-border-radius);
|
||||
color: var(--typebot-container-color);
|
||||
border-radius: var(--typebot-button-border-radius);
|
||||
color: rgb(var(--typebot-chat-container-color));
|
||||
background-color: rgba(
|
||||
var(--typebot-button-bg-color-rgb),
|
||||
calc(var(--selectable-base-alpha) + 0.08)
|
||||
var(--typebot-button-bg-rgb),
|
||||
calc(var(--selectable-alpha-ratio) * 0.08)
|
||||
);
|
||||
transition: all 0.3s ease;
|
||||
width: 236px;
|
||||
@ -377,23 +400,23 @@ pre {
|
||||
|
||||
.typebot-selectable-picture:hover {
|
||||
background-color: rgba(
|
||||
var(--typebot-button-bg-color-rgb),
|
||||
calc(var(--selectable-base-alpha) + 0.12)
|
||||
var(--typebot-button-bg-rgb),
|
||||
calc(var(--selectable-alpha-ratio) * 0.12)
|
||||
);
|
||||
border-color: rgba(
|
||||
var(--typebot-button-bg-color-rgb),
|
||||
calc(var(--selectable-base-alpha) + 0.3)
|
||||
var(--typebot-button-bg-rgb),
|
||||
calc(var(--selectable-alpha-ratio) * 0.3)
|
||||
);
|
||||
}
|
||||
|
||||
.typebot-selectable-picture.selected {
|
||||
background-color: rgba(
|
||||
var(--typebot-button-bg-color-rgb),
|
||||
calc(var(--selectable-base-alpha) + 0.18)
|
||||
var(--typebot-button-bg-rgb),
|
||||
calc(var(--selectable-alpha-ratio) * 0.18)
|
||||
);
|
||||
border-color: rgba(
|
||||
var(--typebot-button-bg-color-rgb),
|
||||
calc(var(--selectable-base-alpha) + 0.35)
|
||||
var(--typebot-button-bg-rgb),
|
||||
calc(var(--selectable-alpha-ratio) * 0.35)
|
||||
);
|
||||
}
|
||||
|
||||
@ -404,8 +427,8 @@ select option {
|
||||
|
||||
.typebot-progress-bar-container {
|
||||
background-color: rgba(
|
||||
var(--typebot-button-bg-color-rgb),
|
||||
calc(var(--selectable-base-alpha) + 0.12)
|
||||
var(--typebot-button-bg-rgb),
|
||||
calc(var(--selectable-alpha-ratio) * 0.12)
|
||||
);
|
||||
|
||||
height: var(--typebot-progress-bar-height);
|
||||
|
@ -16,7 +16,6 @@ import {
|
||||
import { setCssVariablesValue } from '@/utils/setCssVariablesValue'
|
||||
import immutableCss from '../assets/immutable.css'
|
||||
import { Font, InputBlock, StartFrom } from '@typebot.io/schemas'
|
||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { clsx } from 'clsx'
|
||||
import { HTTPError } from 'ky'
|
||||
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 { persist } from '@/utils/persist'
|
||||
import { setBotContainerHeight } from '@/utils/botContainerHeightSignal'
|
||||
import {
|
||||
defaultFontFamily,
|
||||
defaultFontType,
|
||||
defaultProgressBarPosition,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
|
||||
export type BotProps = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@ -262,8 +266,10 @@ const BotContent = (props: BotContentProps) => {
|
||||
|
||||
createEffect(() => {
|
||||
injectFont(
|
||||
props.initialChatReply.typebot.theme.general?.font ??
|
||||
defaultTheme.general.font
|
||||
props.initialChatReply.typebot.theme.general?.font ?? {
|
||||
type: defaultFontType,
|
||||
family: defaultFontFamily,
|
||||
}
|
||||
)
|
||||
if (!botContainer) return
|
||||
setCssVariablesValue(
|
||||
@ -282,7 +288,7 @@ const BotContent = (props: BotContentProps) => {
|
||||
<div
|
||||
ref={botContainer}
|
||||
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
|
||||
)}
|
||||
>
|
||||
@ -296,8 +302,7 @@ const BotContent = (props: BotContentProps) => {
|
||||
when={
|
||||
props.progressBarRef &&
|
||||
(props.initialChatReply.typebot.theme.general?.progressBar
|
||||
?.position ?? defaultTheme.general.progressBar.position) ===
|
||||
'fixed'
|
||||
?.position ?? defaultProgressBarPosition) === 'fixed'
|
||||
}
|
||||
fallback={<ProgressBar value={progressValue() as number} />}
|
||||
>
|
||||
@ -306,7 +311,7 @@ const BotContent = (props: BotContentProps) => {
|
||||
</Portal>
|
||||
</Show>
|
||||
</Show>
|
||||
<div class="flex w-full h-full justify-center">
|
||||
<div class="flex w-full h-full justify-center items-center">
|
||||
<ConversationContainer
|
||||
context={props.context}
|
||||
initialChatReply={props.initialChatReply}
|
||||
|
@ -6,8 +6,11 @@ import { HostBubble } from '../bubbles/HostBubble'
|
||||
import { InputChatBlock } from '../InputChatBlock'
|
||||
import { AvatarSideContainer } from './AvatarSideContainer'
|
||||
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 {
|
||||
defaultGuestAvatarIsEnabled,
|
||||
defaultHostAvatarIsEnabled,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
|
||||
type Props = Pick<ContinueChatResponse, 'messages' | 'input'> & {
|
||||
theme: Theme
|
||||
@ -77,7 +80,7 @@ export const ChatChunk = (props: Props) => {
|
||||
<Show
|
||||
when={
|
||||
(props.theme.chat?.hostAvatar?.isEnabled ??
|
||||
defaultTheme.chat.hostAvatar.isEnabled) &&
|
||||
defaultHostAvatarIsEnabled) &&
|
||||
props.messages.length > 0
|
||||
}
|
||||
>
|
||||
@ -93,7 +96,7 @@ export const ChatChunk = (props: Props) => {
|
||||
style={{
|
||||
'max-width':
|
||||
props.theme.chat?.guestAvatar?.isEnabled ??
|
||||
defaultTheme.chat.guestAvatar.isEnabled
|
||||
defaultGuestAvatarIsEnabled
|
||||
? isMobile()
|
||||
? 'calc(100% - 32px - 32px)'
|
||||
: 'calc(100% - 48px - 48px)'
|
||||
@ -131,7 +134,7 @@ export const ChatChunk = (props: Props) => {
|
||||
chunkIndex={props.index}
|
||||
hasHostAvatar={
|
||||
props.theme.chat?.hostAvatar?.isEnabled ??
|
||||
defaultTheme.chat.hostAvatar.isEnabled
|
||||
defaultHostAvatarIsEnabled
|
||||
}
|
||||
guestAvatar={props.theme.chat?.guestAvatar}
|
||||
context={props.context}
|
||||
@ -151,7 +154,7 @@ export const ChatChunk = (props: Props) => {
|
||||
<Show
|
||||
when={
|
||||
props.theme.chat?.hostAvatar?.isEnabled ??
|
||||
defaultTheme.chat.hostAvatar.isEnabled
|
||||
defaultHostAvatarIsEnabled
|
||||
}
|
||||
>
|
||||
<AvatarSideContainer
|
||||
@ -165,7 +168,7 @@ export const ChatChunk = (props: Props) => {
|
||||
style={{
|
||||
'max-width':
|
||||
props.theme.chat?.hostAvatar?.isEnabled ??
|
||||
defaultTheme.chat.hostAvatar.isEnabled
|
||||
defaultHostAvatarIsEnabled
|
||||
? isMobile()
|
||||
? 'calc(100% - 32px - 32px)'
|
||||
: 'calc(100% - 48px - 48px)'
|
||||
|
@ -284,7 +284,7 @@ export const ConversationContainer = (props: Props) => {
|
||||
return (
|
||||
<div
|
||||
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()}>
|
||||
{(chatChunk, index) => (
|
||||
|
@ -2,7 +2,7 @@ import { Theme } from '@typebot.io/schemas'
|
||||
import { Show } from 'solid-js'
|
||||
import { LoadingBubble } from '../bubbles/LoadingBubble'
|
||||
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 = {
|
||||
theme: Theme
|
||||
@ -15,7 +15,7 @@ export const LoadingChunk = (props: Props) => (
|
||||
<Show
|
||||
when={
|
||||
props.theme.chat?.hostAvatar?.isEnabled ??
|
||||
defaultTheme.chat.hostAvatar.isEnabled
|
||||
defaultHostAvatarIsEnabled
|
||||
}
|
||||
>
|
||||
<AvatarSideContainer
|
||||
|
@ -35,8 +35,8 @@ import { MultiplePictureChoice } from '@/features/blocks/inputs/pictureChoice/Mu
|
||||
import { formattedMessages } from '@/utils/formattedMessagesSignal'
|
||||
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/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 { defaultGuestAvatarIsEnabled } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
|
||||
type Props = {
|
||||
ref: HTMLDivElement | undefined
|
||||
@ -82,8 +82,7 @@ export const InputChatBlock = (props: Props) => {
|
||||
<GuestBubble
|
||||
message={formattedMessage() ?? (answer() as string)}
|
||||
showAvatar={
|
||||
props.guestAvatar?.isEnabled ??
|
||||
defaultTheme.chat.guestAvatar.isEnabled
|
||||
props.guestAvatar?.isEnabled ?? defaultGuestAvatarIsEnabled
|
||||
}
|
||||
avatarSrc={props.guestAvatar?.url && props.guestAvatar.url}
|
||||
/>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { SendButton } from '@/components/SendButton'
|
||||
import { InputSubmitContent } from '@/types'
|
||||
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 { Button } from '@/components/Button'
|
||||
import { defaultRatingInputOptions } from '@typebot.io/schemas/features/blocks/inputs/rating/constants'
|
||||
@ -107,17 +107,24 @@ const RatingButton = (props: RatingButtonProps) => {
|
||||
'Numbers'
|
||||
}
|
||||
>
|
||||
<Button
|
||||
on:click={handleClick}
|
||||
class={
|
||||
props.isOneClickSubmitEnabled ||
|
||||
(isDefined(props.rating) && props.idx <= props.rating)
|
||||
? ''
|
||||
: 'selectable'
|
||||
}
|
||||
>
|
||||
{props.idx}
|
||||
</Button>
|
||||
<Show when={props.isOneClickSubmitEnabled}>
|
||||
<Button on:click={handleClick}>{props.idx}</Button>
|
||||
</Show>
|
||||
<Show when={!props.isOneClickSubmitEnabled}>
|
||||
<div
|
||||
role="checkbox"
|
||||
aria-checked={isDefined(props.rating) && props.idx <= props.rating}
|
||||
on:click={handleClick}
|
||||
class={
|
||||
'py-2 px-4 font-semibold focus:outline-none cursor-pointer select-none typebot-selectable' +
|
||||
(isDefined(props.rating) && props.idx <= props.rating
|
||||
? ' selected'
|
||||
: '')
|
||||
}
|
||||
>
|
||||
{props.idx}
|
||||
</div>
|
||||
</Show>
|
||||
</Match>
|
||||
<Match
|
||||
when={
|
||||
|
@ -6,6 +6,7 @@ import { isMobile } from '@/utils/isMobileSignal'
|
||||
import type { TextInputBlock } from '@typebot.io/schemas'
|
||||
import { createSignal, onCleanup, onMount } from 'solid-js'
|
||||
import { defaultTextInputOptions } from '@typebot.io/schemas/features/blocks/inputs/text/constants'
|
||||
import clsx from 'clsx'
|
||||
|
||||
type Props = {
|
||||
block: TextInputBlock
|
||||
@ -55,7 +56,10 @@ export const TextInput = (props: Props) => {
|
||||
|
||||
return (
|
||||
<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"
|
||||
style={{
|
||||
'max-width': props.block.options?.isLong ? undefined : '350px',
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { isNotEmpty } from '@typebot.io/lib'
|
||||
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 elementId = 'typebot-font'
|
||||
@ -10,8 +10,7 @@ export const injectFont = (font: Font) => {
|
||||
|
||||
if (typeof font === 'string' || font.type === 'Google') {
|
||||
const fontFamily =
|
||||
(typeof font === 'string' ? font : font.family) ??
|
||||
defaultTheme.general.font.family
|
||||
(typeof font === 'string' ? font : font.family) ?? defaultFontFamily
|
||||
if (existingFont?.getAttribute('href')?.includes(fontFamily)) return
|
||||
existingFont?.remove()
|
||||
const fontElement = document.createElement('link')
|
||||
|
@ -1,24 +1,51 @@
|
||||
import {
|
||||
Background,
|
||||
ChatTheme,
|
||||
ContainerColors,
|
||||
ContainerBorderTheme,
|
||||
ContainerTheme,
|
||||
GeneralTheme,
|
||||
InputColors,
|
||||
InputTheme,
|
||||
Theme,
|
||||
} from '@typebot.io/schemas'
|
||||
import { isLight, hexToRgb } from '@typebot.io/lib/hexToRgb'
|
||||
import { isNotEmpty } from '@typebot.io/lib'
|
||||
import { isDefined, isEmpty } from '@typebot.io/lib'
|
||||
import {
|
||||
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'
|
||||
import { isChatContainerLight } from '@typebot.io/theme/isChatContainerLight'
|
||||
|
||||
const cssVariableNames = {
|
||||
general: {
|
||||
bgImage: '--typebot-container-bg-image',
|
||||
bgColor: '--typebot-container-bg-color',
|
||||
fontFamily: '--typebot-container-font-family',
|
||||
color: '--typebot-container-color',
|
||||
progressBar: {
|
||||
position: '--typebot-progress-bar-position',
|
||||
color: '--typebot-progress-bar-color',
|
||||
@ -29,28 +56,67 @@ const cssVariableNames = {
|
||||
},
|
||||
},
|
||||
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: {
|
||||
bgColor: '--typebot-host-bubble-bg-color',
|
||||
bgColor: '--typebot-host-bubble-bg-rgb',
|
||||
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: {
|
||||
bgColor: '--typebot-guest-bubble-bg-color',
|
||||
bgColor: '--typebot-guest-bubble-bg-rgb',
|
||||
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: {
|
||||
bgColor: '--typebot-input-bg-color',
|
||||
bgColor: '--typebot-input-bg-rgb',
|
||||
color: '--typebot-input-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: {
|
||||
bgColor: '--typebot-button-bg-color',
|
||||
bgColorRgb: '--typebot-button-bg-color-rgb',
|
||||
bgRgb: '--typebot-button-bg-rgb',
|
||||
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: {
|
||||
bgColor: '--typebot-checkbox-bg-color',
|
||||
color: '--typebot-checkbox-color',
|
||||
baseAlpha: '--selectable-base-alpha',
|
||||
bgRgb: '--typebot-checkbox-bg-rgb',
|
||||
alphaRatio: '--selectable-alpha-ratio',
|
||||
},
|
||||
},
|
||||
} as const
|
||||
@ -63,43 +129,31 @@ export const setCssVariablesValue = (
|
||||
if (!theme) return
|
||||
const documentStyle = container?.style
|
||||
if (!documentStyle) return
|
||||
setGeneralTheme(
|
||||
theme.general ?? defaultTheme.general,
|
||||
documentStyle,
|
||||
isPreview
|
||||
)
|
||||
setChatTheme(theme.chat ?? defaultTheme.chat, documentStyle)
|
||||
setGeneralTheme(theme.general, documentStyle, isPreview)
|
||||
setChatTheme(theme.chat, theme.general?.background, documentStyle)
|
||||
}
|
||||
|
||||
const setGeneralTheme = (
|
||||
generalTheme: GeneralTheme,
|
||||
generalTheme: GeneralTheme | undefined,
|
||||
documentStyle: CSSStyleDeclaration,
|
||||
isPreview?: boolean
|
||||
) => {
|
||||
setTypebotBackground(
|
||||
generalTheme.background ?? defaultTheme.general.background,
|
||||
documentStyle
|
||||
)
|
||||
setGeneralBackground(generalTheme?.background, documentStyle)
|
||||
documentStyle.setProperty(
|
||||
cssVariableNames.general.fontFamily,
|
||||
(typeof generalTheme.font === 'string'
|
||||
(typeof generalTheme?.font === 'string'
|
||||
? generalTheme.font
|
||||
: generalTheme.font?.family) ?? defaultTheme.general.font.family
|
||||
)
|
||||
setProgressBar(
|
||||
generalTheme.progressBar ?? defaultTheme.general.progressBar,
|
||||
documentStyle,
|
||||
isPreview
|
||||
: generalTheme?.font?.family) ?? defaultFontFamily
|
||||
)
|
||||
setProgressBar(generalTheme?.progressBar, documentStyle, isPreview)
|
||||
}
|
||||
|
||||
const setProgressBar = (
|
||||
progressBar: NonNullable<GeneralTheme['progressBar']>,
|
||||
progressBar: GeneralTheme['progressBar'],
|
||||
documentStyle: CSSStyleDeclaration,
|
||||
isPreview?: boolean
|
||||
) => {
|
||||
const position =
|
||||
progressBar.position ?? defaultTheme.general.progressBar.position
|
||||
const position = progressBar?.position ?? defaultProgressBarPosition
|
||||
|
||||
documentStyle.setProperty(
|
||||
cssVariableNames.general.progressBar.position,
|
||||
@ -107,22 +161,20 @@ const setProgressBar = (
|
||||
)
|
||||
documentStyle.setProperty(
|
||||
cssVariableNames.general.progressBar.color,
|
||||
progressBar.color ?? defaultTheme.general.progressBar.color
|
||||
progressBar?.color ?? defaultProgressBarColor
|
||||
)
|
||||
documentStyle.setProperty(
|
||||
cssVariableNames.general.progressBar.colorRgb,
|
||||
hexToRgb(
|
||||
progressBar.backgroundColor ??
|
||||
defaultTheme.general.progressBar.backgroundColor
|
||||
progressBar?.backgroundColor ?? defaultProgressBarBackgroundColor
|
||||
).join(', ')
|
||||
)
|
||||
documentStyle.setProperty(
|
||||
cssVariableNames.general.progressBar.height,
|
||||
`${progressBar.thickness ?? defaultTheme.general.progressBar.thickness}px`
|
||||
`${progressBar?.thickness ?? defaultProgressBarThickness}px`
|
||||
)
|
||||
|
||||
const placement =
|
||||
progressBar.placement ?? defaultTheme.general.progressBar.placement
|
||||
const placement = progressBar?.placement ?? defaultProgressBarPlacement
|
||||
|
||||
documentStyle.setProperty(
|
||||
cssVariableNames.general.progressBar.top,
|
||||
@ -136,123 +188,428 @@ const setProgressBar = (
|
||||
}
|
||||
|
||||
const setChatTheme = (
|
||||
chatTheme: ChatTheme,
|
||||
chatTheme: ChatTheme | undefined,
|
||||
generalBackground: GeneralTheme['background'],
|
||||
documentStyle: CSSStyleDeclaration
|
||||
) => {
|
||||
setHostBubbles(
|
||||
chatTheme.hostBubbles ?? defaultTheme.chat.hostBubbles,
|
||||
documentStyle
|
||||
setChatContainer(
|
||||
chatTheme?.container,
|
||||
generalBackground,
|
||||
documentStyle,
|
||||
chatTheme?.roundness
|
||||
)
|
||||
setGuestBubbles(
|
||||
chatTheme.guestBubbles ?? defaultTheme.chat.guestBubbles,
|
||||
documentStyle
|
||||
setHostBubbles(chatTheme?.hostBubbles, documentStyle, chatTheme?.roundness)
|
||||
setGuestBubbles(chatTheme?.guestBubbles, documentStyle, chatTheme?.roundness)
|
||||
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)
|
||||
setRoundness(
|
||||
chatTheme.roundness ?? defaultTheme.chat.roundness,
|
||||
documentStyle
|
||||
|
||||
documentStyle.setProperty(
|
||||
cssVariableNames.chat.container.color,
|
||||
hexToRgb(
|
||||
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 = (
|
||||
hostBubbles: ContainerColors,
|
||||
documentStyle: CSSStyleDeclaration
|
||||
hostBubbles: ContainerTheme | undefined,
|
||||
documentStyle: CSSStyleDeclaration,
|
||||
legacyRoundness?: ChatTheme['roundness']
|
||||
) => {
|
||||
documentStyle.setProperty(
|
||||
cssVariableNames.chat.hostBubbles.bgColor,
|
||||
hostBubbles.backgroundColor ?? defaultTheme.chat.hostBubbles.backgroundColor
|
||||
hexToRgb(
|
||||
hostBubbles?.backgroundColor ?? defaultHostBubblesBackgroundColor
|
||||
).join(', ')
|
||||
)
|
||||
documentStyle.setProperty(
|
||||
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 = (
|
||||
guestBubbles: ContainerColors,
|
||||
documentStyle: CSSStyleDeclaration
|
||||
guestBubbles: ContainerTheme | undefined,
|
||||
documentStyle: CSSStyleDeclaration,
|
||||
legacyRoundness?: ChatTheme['roundness']
|
||||
) => {
|
||||
documentStyle.setProperty(
|
||||
cssVariableNames.chat.guestBubbles.bgColor,
|
||||
guestBubbles.backgroundColor ??
|
||||
defaultTheme.chat.guestBubbles.backgroundColor
|
||||
hexToRgb(
|
||||
guestBubbles?.backgroundColor ?? defaultGuestBubblesBackgroundColor
|
||||
).join(', ')
|
||||
)
|
||||
|
||||
documentStyle.setProperty(
|
||||
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 = (
|
||||
buttons: ContainerColors,
|
||||
documentStyle: CSSStyleDeclaration
|
||||
buttons: ContainerTheme | undefined,
|
||||
documentStyle: CSSStyleDeclaration,
|
||||
legacyRoundness?: ChatTheme['roundness']
|
||||
) => {
|
||||
const bgColor =
|
||||
buttons.backgroundColor ?? defaultTheme.chat.buttons.backgroundColor
|
||||
documentStyle.setProperty(cssVariableNames.chat.buttons.bgColor, bgColor)
|
||||
const bgColor = buttons?.backgroundColor ?? defaultButtonsBackgroundColor
|
||||
|
||||
documentStyle.setProperty(
|
||||
cssVariableNames.chat.buttons.bgColorRgb,
|
||||
cssVariableNames.chat.buttons.bgRgb,
|
||||
hexToRgb(bgColor).join(', ')
|
||||
)
|
||||
|
||||
documentStyle.setProperty(
|
||||
cssVariableNames.chat.buttons.bgRgb,
|
||||
hexToRgb(bgColor).join(', ')
|
||||
)
|
||||
|
||||
documentStyle.setProperty(
|
||||
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(
|
||||
cssVariableNames.chat.inputs.bgColor,
|
||||
inputs.backgroundColor ?? defaultTheme.chat.inputs.backgroundColor
|
||||
hexToRgb(inputs?.backgroundColor ?? defaultInputsBackgroundColor).join(', ')
|
||||
)
|
||||
|
||||
documentStyle.setProperty(
|
||||
cssVariableNames.chat.inputs.color,
|
||||
inputs.color ?? defaultTheme.chat.inputs.color
|
||||
inputs?.color ?? defaultInputsColor
|
||||
)
|
||||
|
||||
documentStyle.setProperty(
|
||||
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 = (
|
||||
background: Background,
|
||||
const setCheckbox = (
|
||||
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.setProperty(cssVariableNames.general.bgImage, null)
|
||||
documentStyle.setProperty(cssVariableNames.general.bgColor, null)
|
||||
documentStyle.setProperty(
|
||||
background?.type === BackgroundType.IMAGE
|
||||
(background?.type ?? defaultBackgroundType) === BackgroundType.IMAGE
|
||||
? cssVariableNames.general.bgImage
|
||||
: 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 = {}) => {
|
||||
@ -261,25 +618,80 @@ const parseBackgroundValue = ({ type, content }: Background = {}) => {
|
||||
return 'transparent'
|
||||
case undefined:
|
||||
case BackgroundType.COLOR:
|
||||
return content ?? defaultTheme.general.background.content
|
||||
return content ?? defaultBackgroundColor
|
||||
case BackgroundType.IMAGE:
|
||||
return `url(${content})`
|
||||
}
|
||||
}
|
||||
|
||||
const setRoundness = (
|
||||
roundness: NonNullable<ChatTheme['roundness']>,
|
||||
documentStyle: CSSStyleDeclaration
|
||||
const setBorderRadius = (
|
||||
border: ContainerBorderTheme,
|
||||
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':
|
||||
documentStyle.setProperty('--typebot-border-radius', '0')
|
||||
documentStyle.setProperty(variableName, '0 0 #0000')
|
||||
break
|
||||
case 'medium':
|
||||
documentStyle.setProperty('--typebot-border-radius', '6px')
|
||||
case 'sm':
|
||||
documentStyle.setProperty(variableName, '0 1px 2px 0 rgb(0 0 0 / 0.05)')
|
||||
break
|
||||
case 'large':
|
||||
documentStyle.setProperty('--typebot-border-radius', '20px')
|
||||
case 'md':
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/nextjs",
|
||||
"version": "0.2.64",
|
||||
"version": "0.2.65",
|
||||
"description": "Convenient library to display typebots on your Next.js website",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/react",
|
||||
"version": "0.2.64",
|
||||
"version": "0.2.65",
|
||||
"description": "Convenient library to display typebots on your React app",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { Theme } from './schema'
|
||||
|
||||
export enum BackgroundType {
|
||||
COLOR = 'Color',
|
||||
IMAGE = 'Image',
|
||||
@ -11,37 +9,63 @@ export const fontTypes = ['Google', 'Custom'] as const
|
||||
export const progressBarPlacements = ['Top', 'Bottom'] as const
|
||||
export const progressBarPositions = ['fixed', 'absolute'] as const
|
||||
|
||||
export const defaultTheme = {
|
||||
chat: {
|
||||
roundness: 'medium',
|
||||
hostBubbles: { backgroundColor: '#F7F8FF', color: '#303235' },
|
||||
guestBubbles: { backgroundColor: '#FF8E21', color: '#FFFFFF' },
|
||||
buttons: { backgroundColor: '#0042DA', color: '#FFFFFF' },
|
||||
inputs: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
color: '#303235',
|
||||
placeholderColor: '#9095A0',
|
||||
},
|
||||
hostAvatar: {
|
||||
isEnabled: true,
|
||||
},
|
||||
guestAvatar: {
|
||||
isEnabled: false,
|
||||
},
|
||||
},
|
||||
general: {
|
||||
font: {
|
||||
type: 'Google',
|
||||
family: 'Open Sans',
|
||||
},
|
||||
background: { type: BackgroundType.COLOR, content: '#ffffff' },
|
||||
progressBar: {
|
||||
isEnabled: false,
|
||||
color: '#0042DA',
|
||||
backgroundColor: '#e0edff',
|
||||
thickness: 4,
|
||||
position: 'absolute',
|
||||
placement: 'Top',
|
||||
},
|
||||
},
|
||||
} as const satisfies Theme
|
||||
export const shadows = ['none', 'sm', 'md', 'lg', 'xl', '2xl'] as const
|
||||
export const borderRoundness = ['none', 'medium', 'large', 'custom'] as const
|
||||
|
||||
export const defaultLightTextColor = '#303235'
|
||||
export const defaultDarkTextColor = '#FFFFFF'
|
||||
|
||||
/*---- General ----*/
|
||||
|
||||
// Font
|
||||
export const defaultFontType = 'Google'
|
||||
export const defaultFontFamily = 'Open Sans'
|
||||
|
||||
// Background
|
||||
export const defaultBackgroundType = BackgroundType.COLOR
|
||||
export const defaultBackgroundColor = '#ffffff'
|
||||
|
||||
// Progress bar
|
||||
export const defaultProgressBarIsEnabled = false
|
||||
export const defaultProgressBarColor = '#0042DA'
|
||||
export const defaultProgressBarBackgroundColor = '#e0edff'
|
||||
export const defaultProgressBarThickness = 4
|
||||
export const defaultProgressBarPosition = 'absolute'
|
||||
export const defaultProgressBarPlacement = 'Top'
|
||||
|
||||
export const defaultRoundness = 'medium'
|
||||
export const defaultOpacity = 1
|
||||
export const defaultBlur = 0
|
||||
|
||||
/*---- Chat ----*/
|
||||
|
||||
// Container
|
||||
export const defaultContainerMaxWidth = '800px'
|
||||
export const defaultContainerMaxHeight = '100%'
|
||||
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 {
|
||||
BackgroundType,
|
||||
borderRoundness,
|
||||
fontTypes,
|
||||
progressBarPlacements,
|
||||
progressBarPositions,
|
||||
shadows,
|
||||
} from './constants'
|
||||
|
||||
const avatarPropsSchema = z.object({
|
||||
@ -12,25 +14,48 @@ const avatarPropsSchema = z.object({
|
||||
url: z.string().optional(),
|
||||
})
|
||||
|
||||
const containerColorsSchema = z.object({
|
||||
backgroundColor: z.string().optional(),
|
||||
const containerBorderThemeSchema = z.object({
|
||||
thickness: z.number().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(
|
||||
z.object({
|
||||
placeholderColor: z.string().optional(),
|
||||
export type ContainerBorderTheme = z.infer<typeof containerBorderThemeSchema>
|
||||
|
||||
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({
|
||||
container: chatContainerSchema.optional(),
|
||||
hostAvatar: avatarPropsSchema.optional(),
|
||||
guestAvatar: avatarPropsSchema.optional(),
|
||||
hostBubbles: containerColorsSchema.optional(),
|
||||
guestBubbles: containerColorsSchema.optional(),
|
||||
buttons: containerColorsSchema.optional(),
|
||||
inputs: inputColorsSchema.optional(),
|
||||
roundness: z.enum(['none', 'medium', 'large']).optional(),
|
||||
hostBubbles: containerThemeSchema.optional(),
|
||||
guestBubbles: containerThemeSchema.optional(),
|
||||
buttons: containerThemeSchema.optional(),
|
||||
inputs: inputThemeSchema.optional(),
|
||||
roundness: z
|
||||
.enum(['none', 'medium', 'large'])
|
||||
.optional()
|
||||
.describe('Deprecated, use `container.border.roundeness` instead'),
|
||||
})
|
||||
|
||||
const backgroundSchema = z.object({
|
||||
@ -98,6 +123,6 @@ export type ChatTheme = z.infer<typeof chatThemeSchema>
|
||||
export type AvatarProps = z.infer<typeof avatarPropsSchema>
|
||||
export type GeneralTheme = z.infer<typeof generalThemeSchema>
|
||||
export type Background = z.infer<typeof backgroundSchema>
|
||||
export type ContainerColors = z.infer<typeof containerColorsSchema>
|
||||
export type InputColors = z.infer<typeof inputColorsSchema>
|
||||
export type ContainerTheme = z.infer<typeof containerThemeSchema>
|
||||
export type InputTheme = z.infer<typeof inputThemeSchema>
|
||||
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':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/embeds/nextjs
|
||||
'@typebot.io/theme':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/theme
|
||||
'@udecode/cn':
|
||||
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)
|
||||
@ -1048,6 +1051,9 @@ importers:
|
||||
'@typebot.io/schemas':
|
||||
specifier: workspace:*
|
||||
version: link:../../schemas
|
||||
'@typebot.io/theme':
|
||||
specifier: workspace:*
|
||||
version: link:../../theme
|
||||
'@typebot.io/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../../tsconfig
|
||||
@ -1925,6 +1931,15 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:../tsconfig
|
||||
|
||||
packages/theme:
|
||||
dependencies:
|
||||
'@typebot.io/lib':
|
||||
specifier: workspace:*
|
||||
version: link:../lib
|
||||
'@typebot.io/schemas':
|
||||
specifier: workspace:*
|
||||
version: link:../schemas
|
||||
|
||||
packages/transactional:
|
||||
dependencies:
|
||||
'@react-email/components':
|
||||
|
Reference in New Issue
Block a user