✨ (theme) Add container theme options: border, shadow, filter (#1436)
Closes #1332
This commit is contained in:
@ -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}
|
||||
|
Reference in New Issue
Block a user