{
return (
{
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')
diff --git a/packages/embeds/js/src/utils/setCssVariablesValue.ts b/packages/embeds/js/src/utils/setCssVariablesValue.ts
index 417339a45..2e7bb059c 100644
--- a/packages/embeds/js/src/utils/setCssVariablesValue.ts
+++ b/packages/embeds/js/src/utils/setCssVariablesValue.ts
@@ -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,
+ 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,
- 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
}
}
diff --git a/packages/embeds/nextjs/package.json b/packages/embeds/nextjs/package.json
index 2aa2ff854..1562082e3 100644
--- a/packages/embeds/nextjs/package.json
+++ b/packages/embeds/nextjs/package.json
@@ -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",
diff --git a/packages/embeds/react/package.json b/packages/embeds/react/package.json
index d80f3ff6b..1f54f82f7 100644
--- a/packages/embeds/react/package.json
+++ b/packages/embeds/react/package.json
@@ -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",
diff --git a/packages/schemas/features/typebot/theme/constants.ts b/packages/schemas/features/typebot/theme/constants.ts
index 78ca74600..b2b5bccb2 100644
--- a/packages/schemas/features/typebot/theme/constants.ts
+++ b/packages/schemas/features/typebot/theme/constants.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
diff --git a/packages/schemas/features/typebot/theme/schema.ts b/packages/schemas/features/typebot/theme/schema.ts
index 2d99176a6..d5cb86ace 100644
--- a/packages/schemas/features/typebot/theme/schema.ts
+++ b/packages/schemas/features/typebot/theme/schema.ts
@@ -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
+
+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
export type AvatarProps = z.infer
export type GeneralTheme = z.infer
export type Background = z.infer
-export type ContainerColors = z.infer
-export type InputColors = z.infer
+export type ContainerTheme = z.infer
+export type InputTheme = z.infer
export type ThemeTemplate = z.infer
diff --git a/packages/theme/isChatContainerLight.ts b/packages/theme/isChatContainerLight.ts
new file mode 100644
index 000000000..78662cfbe
--- /dev/null
+++ b/packages/theme/isChatContainerLight.ts
@@ -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)
+}
diff --git a/packages/theme/package.json b/packages/theme/package.json
new file mode 100644
index 000000000..c9caec17a
--- /dev/null
+++ b/packages/theme/package.json
@@ -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:*"
+ }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5a612ca09..eb173c666 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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':