@@ -552,3 +552,42 @@ export const CloseIcon = (props: IconProps) => (
|
|||||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||||
</Icon>
|
</Icon>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const NoRadiusIcon = (props: IconProps) => (
|
||||||
|
<Icon viewBox="0 0 20 20" {...props}>
|
||||||
|
<mask id="path-1-inside-1_1009_3" fill="white">
|
||||||
|
<path d="M0 0H20V20H0V0Z" />
|
||||||
|
</mask>
|
||||||
|
<path
|
||||||
|
d="M0 0V-2H-2V0H0ZM0 20H-2V22H0V20ZM0 2H20V-2H0V2ZM20 18H0V22H20V18ZM2 20V0H-2V20H2Z"
|
||||||
|
fill="black"
|
||||||
|
mask="url(#path-1-inside-1_1009_3)"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const MediumRadiusIcon = (props: IconProps) => (
|
||||||
|
<Icon viewBox="0 0 20 20" {...props}>
|
||||||
|
<mask id="path-1-inside-1_1009_4" fill="white">
|
||||||
|
<path d="M0 4C0 1.79086 1.79086 0 4 0H20V20H4C1.79086 20 0 18.2091 0 16V4Z" />
|
||||||
|
</mask>
|
||||||
|
<path
|
||||||
|
d="M-2 4C-2 0.686292 0.686292 -2 4 -2H20V2H4C2.89543 2 2 2.89543 2 4H-2ZM20 22H4C0.686292 22 -2 19.3137 -2 16H2C2 17.1046 2.89543 18 4 18H20V22ZM4 22C0.686292 22 -2 19.3137 -2 16V4C-2 0.686292 0.686292 -2 4 -2V2C2.89543 2 2 2.89543 2 4V16C2 17.1046 2.89543 18 4 18V22ZM20 0V20V0Z"
|
||||||
|
fill="black"
|
||||||
|
mask="url(#path-1-inside-1_1009_4)"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const LargeRadiusIcon = (props: IconProps) => (
|
||||||
|
<Icon viewBox="0 0 20 20" {...props}>
|
||||||
|
<mask id="path-1-inside-1_1009_5" fill="white">
|
||||||
|
<path d="M0 10C0 4.47715 4.47715 0 10 0H20V20H10C4.47715 20 0 15.5228 0 10V10Z" />
|
||||||
|
</mask>
|
||||||
|
<path
|
||||||
|
d="M-2 10C-2 3.37258 3.37258 -2 10 -2H20V2H10C5.58172 2 2 5.58172 2 10H-2ZM20 22H10C3.37258 22 -2 16.6274 -2 10H2C2 14.4183 5.58172 18 10 18H20V22ZM10 22C3.37258 22 -2 16.6274 -2 10C-2 3.37258 3.37258 -2 10 -2V2C5.58172 2 2 5.58172 2 10C2 14.4183 5.58172 18 10 18V22ZM20 0V20V0Z"
|
||||||
|
fill="black"
|
||||||
|
mask="url(#path-1-inside-1_1009_5)"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
)
|
||||||
|
|||||||
@@ -7,35 +7,32 @@ import {
|
|||||||
useRadioGroup,
|
useRadioGroup,
|
||||||
UseRadioProps,
|
UseRadioProps,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { BackgroundType } from '@typebot.io/schemas'
|
|
||||||
import { ReactNode } from 'react'
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
type Props = {
|
type Props<T extends string> = {
|
||||||
backgroundType: BackgroundType
|
options: (T | { value: T; label: ReactNode })[]
|
||||||
onBackgroundTypeChange: (type: BackgroundType) => void
|
defaultValue: T
|
||||||
|
onSelect: (newValue: T) => void
|
||||||
}
|
}
|
||||||
export const BackgroundTypeRadioButtons = ({
|
export const RadioButtons = <T extends string>({
|
||||||
backgroundType,
|
options,
|
||||||
onBackgroundTypeChange,
|
defaultValue,
|
||||||
}: Props) => {
|
onSelect,
|
||||||
const options = ['Color', 'Image', 'None']
|
}: Props<T>) => {
|
||||||
|
|
||||||
const { getRootProps, getRadioProps } = useRadioGroup({
|
const { getRootProps, getRadioProps } = useRadioGroup({
|
||||||
name: 'background-type',
|
defaultValue,
|
||||||
defaultValue: backgroundType,
|
onChange: onSelect,
|
||||||
onChange: (nextVal: string) =>
|
|
||||||
onBackgroundTypeChange(nextVal as BackgroundType),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const group = getRootProps()
|
const group = getRootProps()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack {...group}>
|
<HStack {...group}>
|
||||||
{options.map((value) => {
|
{options.map((item) => {
|
||||||
const radio = getRadioProps({ value })
|
const radio = getRadioProps({ value: parseValue(item) })
|
||||||
return (
|
return (
|
||||||
<RadioCard key={value} {...radio}>
|
<RadioCard key={parseValue(item)} {...radio}>
|
||||||
{value}
|
{parseLabel(item)}
|
||||||
</RadioCard>
|
</RadioCard>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -76,3 +73,9 @@ export const RadioCard = (props: UseRadioProps & { children: ReactNode }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const parseValue = (item: string | { value: string; label: ReactNode }) =>
|
||||||
|
typeof item === 'string' ? item : item.value
|
||||||
|
|
||||||
|
const parseLabel = (item: string | { value: string; label: ReactNode }) =>
|
||||||
|
typeof item === 'string' ? item : item.label
|
||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import {
|
||||||
|
LargeRadiusIcon,
|
||||||
|
MediumRadiusIcon,
|
||||||
|
NoRadiusIcon,
|
||||||
|
} from '@/components/icons'
|
||||||
|
import { RadioButtons } from '@/components/inputs/RadioButtons'
|
||||||
import { Heading, Stack } from '@chakra-ui/react'
|
import { Heading, Stack } from '@chakra-ui/react'
|
||||||
import {
|
import {
|
||||||
AvatarProps,
|
AvatarProps,
|
||||||
@@ -59,6 +65,29 @@ export const ChatThemeSettings = ({
|
|||||||
onHostBubblesChange={handleHostBubblesChange}
|
onHostBubblesChange={handleHostBubblesChange}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
||||||
|
<Heading fontSize="lg">Corners roundness</Heading>
|
||||||
|
<RadioButtons
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
label: <NoRadiusIcon />,
|
||||||
|
value: 'none',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: <MediumRadiusIcon />,
|
||||||
|
value: 'medium',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: <LargeRadiusIcon />,
|
||||||
|
value: 'large',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
defaultValue={chatTheme.roundness ?? 'medium'}
|
||||||
|
onSelect={(roundness) =>
|
||||||
|
onChatThemeChange({ ...chatTheme, roundness })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
||||||
<Heading fontSize="lg">User bubbles</Heading>
|
<Heading fontSize="lg">User bubbles</Heading>
|
||||||
<GuestBubbles
|
<GuestBubbles
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import { RadioButtons } from '@/components/inputs/RadioButtons'
|
||||||
import { Stack, Text } from '@chakra-ui/react'
|
import { Stack, Text } from '@chakra-ui/react'
|
||||||
import { Background, BackgroundType } from '@typebot.io/schemas'
|
import { Background, BackgroundType } from '@typebot.io/schemas'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { BackgroundContent } from './BackgroundContent'
|
import { BackgroundContent } from './BackgroundContent'
|
||||||
import { BackgroundTypeRadioButtons } from './BackgroundTypeRadioButtons'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
background?: Background
|
background?: Background
|
||||||
@@ -25,9 +25,14 @@ export const BackgroundSelector = ({
|
|||||||
return (
|
return (
|
||||||
<Stack spacing={4}>
|
<Stack spacing={4}>
|
||||||
<Text>Background</Text>
|
<Text>Background</Text>
|
||||||
<BackgroundTypeRadioButtons
|
<RadioButtons
|
||||||
backgroundType={background?.type ?? defaultBackgroundType}
|
options={[
|
||||||
onBackgroundTypeChange={handleBackgroundTypeChange}
|
BackgroundType.COLOR,
|
||||||
|
BackgroundType.IMAGE,
|
||||||
|
BackgroundType.NONE,
|
||||||
|
]}
|
||||||
|
defaultValue={background?.type ?? defaultBackgroundType}
|
||||||
|
onSelect={handleBackgroundTypeChange}
|
||||||
/>
|
/>
|
||||||
<BackgroundContent
|
<BackgroundContent
|
||||||
background={background}
|
background={background}
|
||||||
|
|||||||
@@ -90,6 +90,32 @@ test.describe.parallel('Theme page', () => {
|
|||||||
|
|
||||||
await expect(page.locator('.typebot-container img')).toBeHidden()
|
await expect(page.locator('.typebot-container img')).toBeHidden()
|
||||||
|
|
||||||
|
// Roundness
|
||||||
|
await expect(page.getByRole('button', { name: 'Go' })).toHaveCSS(
|
||||||
|
'border-radius',
|
||||||
|
'6px'
|
||||||
|
)
|
||||||
|
await page
|
||||||
|
.getByRole('region', { name: 'Chat' })
|
||||||
|
.getByRole('radiogroup')
|
||||||
|
.locator('div')
|
||||||
|
.first()
|
||||||
|
.click()
|
||||||
|
await expect(page.getByRole('button', { name: 'Go' })).toHaveCSS(
|
||||||
|
'border-radius',
|
||||||
|
'0px'
|
||||||
|
)
|
||||||
|
await page
|
||||||
|
.getByRole('region', { name: 'Chat' })
|
||||||
|
.getByRole('radiogroup')
|
||||||
|
.locator('div')
|
||||||
|
.nth(2)
|
||||||
|
.click()
|
||||||
|
await expect(page.getByRole('button', { name: 'Go' })).toHaveCSS(
|
||||||
|
'border-radius',
|
||||||
|
'20px'
|
||||||
|
)
|
||||||
|
|
||||||
// Host bubbles
|
// Host bubbles
|
||||||
await page.click(
|
await page.click(
|
||||||
'[data-testid="host-bubbles-theme"] >> [aria-label="Pick a color"] >> nth=0'
|
'[data-testid="host-bubbles-theme"] >> [aria-label="Pick a color"] >> nth=0'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/js",
|
"name": "@typebot.io/js",
|
||||||
"version": "0.0.28",
|
"version": "0.0.29",
|
||||||
"description": "Javascript library to display typebots on your website",
|
"description": "Javascript library to display typebots on your website",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
@@ -23,6 +23,8 @@
|
|||||||
--typebot-header-bg-color: #ffffff;
|
--typebot-header-bg-color: #ffffff;
|
||||||
--typebot-header-color: #303235;
|
--typebot-header-color: #303235;
|
||||||
|
|
||||||
|
--typebot-border-radius: 6px;
|
||||||
|
|
||||||
/* Phone input */
|
/* Phone input */
|
||||||
--PhoneInputCountryFlag-borderColor: transparent;
|
--PhoneInputCountryFlag-borderColor: transparent;
|
||||||
--PhoneInput-color--focus: transparent;
|
--PhoneInput-color--focus: transparent;
|
||||||
@@ -127,15 +129,11 @@ textarea {
|
|||||||
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-header {
|
|
||||||
color: var(--typebot-header-color);
|
|
||||||
background-color: var(--typebot-header-bg-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.typebot-button {
|
.typebot-button {
|
||||||
color: var(--typebot-button-color);
|
color: var(--typebot-button-color);
|
||||||
background-color: var(--typebot-button-bg-color);
|
background-color: var(--typebot-button-bg-color);
|
||||||
border: 1px solid var(--typebot-button-bg-color);
|
border: 1px solid var(--typebot-button-bg-color);
|
||||||
|
border-radius: var(--typebot-border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-button.selectable {
|
.typebot-button.selectable {
|
||||||
@@ -151,17 +149,26 @@ textarea {
|
|||||||
.typebot-host-bubble > .bubble-typing {
|
.typebot-host-bubble > .bubble-typing {
|
||||||
background-color: var(--typebot-host-bubble-bg-color);
|
background-color: var(--typebot-host-bubble-bg-color);
|
||||||
border: var(--typebot-host-bubble-border);
|
border: var(--typebot-host-bubble-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typebot-host-bubble img,
|
||||||
|
.typebot-host-bubble iframe,
|
||||||
|
.typebot-host-bubble video {
|
||||||
|
border-radius: var(--typebot-border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-guest-bubble {
|
.typebot-guest-bubble {
|
||||||
color: var(--typebot-guest-bubble-color);
|
color: var(--typebot-guest-bubble-color);
|
||||||
background-color: var(--typebot-guest-bubble-bg-color);
|
background-color: var(--typebot-guest-bubble-bg-color);
|
||||||
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-input {
|
.typebot-input {
|
||||||
color: var(--typebot-input-color);
|
color: var(--typebot-input-color);
|
||||||
background-color: var(--typebot-input-bg-color);
|
background-color: var(--typebot-input-bg-color);
|
||||||
box-shadow: 0 2px 6px -1px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 6px -1px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: var(--typebot-border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-input-error-message {
|
.typebot-input-error-message {
|
||||||
@@ -202,6 +209,7 @@ textarea {
|
|||||||
|
|
||||||
.upload-progress-bar {
|
.upload-progress-bar {
|
||||||
background-color: var(--typebot-button-bg-color);
|
background-color: var(--typebot-button-bg-color);
|
||||||
|
border-radius: var(--typebot-border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.total-files-indicator {
|
.total-files-indicator {
|
||||||
@@ -221,15 +229,22 @@ textarea {
|
|||||||
.secondary-button {
|
.secondary-button {
|
||||||
background-color: var(--typebot-host-bubble-bg-color);
|
background-color: var(--typebot-host-bubble-bg-color);
|
||||||
color: var(--typebot-host-bubble-color);
|
color: var(--typebot-host-bubble-color);
|
||||||
|
border-radius: var(--typebot-border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-country-select {
|
.typebot-country-select {
|
||||||
color: var(--typebot-input-color);
|
color: var(--typebot-input-color);
|
||||||
background-color: var(--typebot-input-bg-color);
|
background-color: var(--typebot-input-bg-color);
|
||||||
|
border-radius: var(--typebot-border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.typebot-date-input {
|
.typebot-date-input {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
color: var(--typebot-input-color);
|
color: var(--typebot-input-color);
|
||||||
background-color: var(--typebot-input-bg-color);
|
background-color: var(--typebot-input-bg-color);
|
||||||
|
border-radius: var(--typebot-border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.typebot-popup-blocked-toast {
|
||||||
|
border-radius: var(--typebot-border-radius);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ export const ConversationContainer = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={chatContainer}
|
ref={chatContainer}
|
||||||
class="overflow-y-scroll w-full min-h-full rounded px-3 pt-10 relative scrollable-container typebot-chat-view scroll-smooth"
|
class="overflow-y-scroll w-full min-h-full px-3 pt-10 relative scrollable-container typebot-chat-view scroll-smooth"
|
||||||
>
|
>
|
||||||
<For each={chatChunks()}>
|
<For each={chatChunks()}>
|
||||||
{(chatChunk, index) => (
|
{(chatChunk, index) => (
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ type Props = {
|
|||||||
export const PopupBlockedToast = (props: Props) => {
|
export const PopupBlockedToast = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class="w-full max-w-xs p-4 text-gray-500 bg-white rounded-lg shadow flex flex-col gap-2"
|
class="w-full max-w-xs p-4 text-gray-500 bg-white shadow flex flex-col gap-2 typebot-popup-blocked-toast"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
<span class="mb-1 text-sm font-semibold text-gray-900">
|
<span class="mb-1 text-sm font-semibold text-gray-900">
|
||||||
@@ -19,7 +19,7 @@ export const PopupBlockedToast = (props: Props) => {
|
|||||||
<a
|
<a
|
||||||
href={props.url}
|
href={props.url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="py-1 px-4 justify-center text-sm font-semibold rounded-md text-white focus:outline-none flex items-center disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 transition-all filter hover:brightness-90 active:brightness-75 typebot-button"
|
class="py-1 px-4 justify-center text-sm font-semibold text-white focus:outline-none flex items-center disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 transition-all filter hover:brightness-90 active:brightness-75 typebot-button"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
onClick={() => props.onLinkClick()}
|
onClick={() => props.onLinkClick()}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const SendButton = (props: SendButtonProps) => {
|
|||||||
disabled={props.isDisabled || props.isLoading}
|
disabled={props.isDisabled || props.isLoading}
|
||||||
{...props}
|
{...props}
|
||||||
class={
|
class={
|
||||||
'py-2 px-4 justify-center font-semibold rounded-md text-white focus:outline-none flex items-center disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 transition-all filter hover:brightness-90 active:brightness-75 typebot-button ' +
|
'py-2 px-4 justify-center font-semibold text-white focus:outline-none flex items-center disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 transition-all filter hover:brightness-90 active:brightness-75 typebot-button ' +
|
||||||
props.class
|
props.class
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const GuestBubble = (props: Props) => (
|
|||||||
style={{ 'margin-left': '50px' }}
|
style={{ 'margin-left': '50px' }}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="px-4 py-2 rounded-lg mr-2 whitespace-pre-wrap max-w-full typebot-guest-bubble"
|
class="px-4 py-2 mr-2 whitespace-pre-wrap max-w-full typebot-guest-bubble"
|
||||||
data-testid="guest-bubble"
|
data-testid="guest-bubble"
|
||||||
>
|
>
|
||||||
{props.message}
|
{props.message}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const LoadingBubble = () => (
|
|||||||
<div class="flex mb-2 w-full items-center">
|
<div class="flex mb-2 w-full items-center">
|
||||||
<div class={'flex relative items-start typebot-host-bubble'}>
|
<div class={'flex relative items-start typebot-host-bubble'}>
|
||||||
<div
|
<div
|
||||||
class="flex items-center absolute px-4 py-2 rounded-lg bubble-typing "
|
class="flex items-center absolute px-4 py-2 bubble-typing "
|
||||||
style={{
|
style={{
|
||||||
width: '64px',
|
width: '64px',
|
||||||
height: '32px',
|
height: '32px',
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export const AudioBubble = (props: Props) => {
|
|||||||
<div class="flex mb-2 w-full items-center">
|
<div class="flex mb-2 w-full items-center">
|
||||||
<div class={'flex relative z-10 items-start typebot-host-bubble'}>
|
<div class={'flex relative z-10 items-start typebot-host-bubble'}>
|
||||||
<div
|
<div
|
||||||
class="flex items-center absolute px-4 py-2 rounded-lg bubble-typing z-10 "
|
class="flex items-center absolute px-4 py-2 bubble-typing z-10 "
|
||||||
style={{
|
style={{
|
||||||
width: isTyping() ? '64px' : '100%',
|
width: isTyping() ? '64px' : '100%',
|
||||||
height: isTyping() ? '32px' : '100%',
|
height: isTyping() ? '32px' : '100%',
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export const EmbedBubble = (props: Props) => {
|
|||||||
class={'flex relative z-10 items-start typebot-host-bubble w-full'}
|
class={'flex relative z-10 items-start typebot-host-bubble w-full'}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex items-center absolute px-4 py-2 rounded-lg bubble-typing z-10 "
|
class="flex items-center absolute px-4 py-2 bubble-typing z-10 "
|
||||||
style={{
|
style={{
|
||||||
width: isTyping() ? '64px' : '100%',
|
width: isTyping() ? '64px' : '100%',
|
||||||
height: isTyping() ? '32px' : '100%',
|
height: isTyping() ? '32px' : '100%',
|
||||||
@@ -46,7 +46,7 @@ export const EmbedBubble = (props: Props) => {
|
|||||||
id="embed-bubble-content"
|
id="embed-bubble-content"
|
||||||
src={props.content.url}
|
src={props.content.url}
|
||||||
class={
|
class={
|
||||||
'w-full z-20 p-4 text-fade-in rounded-2xl ' +
|
'w-full z-20 p-4 text-fade-in ' +
|
||||||
(isTyping() ? 'opacity-0' : 'opacity-100')
|
(isTyping() ? 'opacity-0' : 'opacity-100')
|
||||||
}
|
}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export const ImageBubble = (props: Props) => {
|
|||||||
<div class="flex mb-2 w-full items-center">
|
<div class="flex mb-2 w-full items-center">
|
||||||
<div class={'flex relative z-10 items-start typebot-host-bubble'}>
|
<div class={'flex relative z-10 items-start typebot-host-bubble'}>
|
||||||
<div
|
<div
|
||||||
class="flex items-center absolute px-4 py-2 rounded-lg bubble-typing z-10 "
|
class="flex items-center absolute px-4 py-2 bubble-typing z-10 "
|
||||||
style={{
|
style={{
|
||||||
width: isTyping() ? '64px' : '100%',
|
width: isTyping() ? '64px' : '100%',
|
||||||
height: isTyping() ? '32px' : '100%',
|
height: isTyping() ? '32px' : '100%',
|
||||||
@@ -56,7 +56,7 @@ export const ImageBubble = (props: Props) => {
|
|||||||
ref={image}
|
ref={image}
|
||||||
src={props.url}
|
src={props.url}
|
||||||
class={
|
class={
|
||||||
'text-fade-in w-full rounded-md ' +
|
'text-fade-in w-full ' +
|
||||||
(isTyping() ? 'opacity-0' : 'opacity-100')
|
(isTyping() ? 'opacity-0' : 'opacity-100')
|
||||||
}
|
}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ export const TextBubble = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div class="flex flex-col animate-fade-in">
|
<div class="flex flex-col animate-fade-in">
|
||||||
<div class="flex mb-2 w-full items-center">
|
<div class="flex mb-2 w-full items-center">
|
||||||
<div class={'flex relative items-start typebot-host-bubble'}>
|
<div class="flex relative items-start typebot-host-bubble">
|
||||||
<div
|
<div
|
||||||
class="flex items-center absolute px-4 py-2 rounded-lg bubble-typing "
|
class="flex items-center absolute px-4 py-2 bubble-typing "
|
||||||
style={{
|
style={{
|
||||||
width: isTyping() ? '64px' : '100%',
|
width: isTyping() ? '64px' : '100%',
|
||||||
height: isTyping() ? '32px' : '100%',
|
height: isTyping() ? '32px' : '100%',
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export const VideoBubble = (props: Props) => {
|
|||||||
<div class="flex mb-2 w-full items-center">
|
<div class="flex mb-2 w-full items-center">
|
||||||
<div class={'flex relative z-10 items-start typebot-host-bubble'}>
|
<div class={'flex relative z-10 items-start typebot-host-bubble'}>
|
||||||
<div
|
<div
|
||||||
class="flex items-center absolute px-4 py-2 rounded-lg bubble-typing z-10 "
|
class="flex items-center absolute px-4 py-2 bubble-typing z-10 "
|
||||||
style={{
|
style={{
|
||||||
width: isTyping() ? '64px' : '100%',
|
width: isTyping() ? '64px' : '100%',
|
||||||
height: isTyping() ? '32px' : '100%',
|
height: isTyping() ? '32px' : '100%',
|
||||||
@@ -68,7 +68,7 @@ const VideoContent = (props: VideoContentProps) => {
|
|||||||
<video
|
<video
|
||||||
controls
|
controls
|
||||||
class={
|
class={
|
||||||
'p-4 focus:outline-none w-full z-10 text-fade-in rounded-md ' +
|
'p-4 focus:outline-none w-full z-10 text-fade-in ' +
|
||||||
(props.isTyping ? 'opacity-0' : 'opacity-100')
|
(props.isTyping ? 'opacity-0' : 'opacity-100')
|
||||||
}
|
}
|
||||||
style={{
|
style={{
|
||||||
@@ -97,7 +97,7 @@ const VideoContent = (props: VideoContentProps) => {
|
|||||||
: 'https://www.youtube.com/embed'
|
: 'https://www.youtube.com/embed'
|
||||||
}/${props.content.id}`}
|
}/${props.content.id}`}
|
||||||
class={
|
class={
|
||||||
'w-full p-4 text-fade-in z-10 rounded-md ' +
|
'w-full p-4 text-fade-in z-10 ' +
|
||||||
(props.isTyping ? 'opacity-0' : 'opacity-100')
|
(props.isTyping ? 'opacity-0' : 'opacity-100')
|
||||||
}
|
}
|
||||||
height={props.isTyping ? '32px' : '200px'}
|
height={props.isTyping ? '32px' : '200px'}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export const ChoiceForm = (props: Props) => {
|
|||||||
type="button"
|
type="button"
|
||||||
on:click={() => handleClick(index())}
|
on:click={() => handleClick(index())}
|
||||||
class={
|
class={
|
||||||
'py-2 px-4 text-left font-semibold rounded-md transition-all filter hover:brightness-90 active:brightness-75 duration-100 focus:outline-none typebot-button ' +
|
'py-2 px-4 text-left font-semibold transition-all filter hover:brightness-90 active:brightness-75 duration-100 focus:outline-none typebot-button ' +
|
||||||
(selectedIndices().some(
|
(selectedIndices().some(
|
||||||
(selectedIndex) => selectedIndex === index()
|
(selectedIndex) => selectedIndex === index()
|
||||||
) || !props.block.options?.isMultipleChoice
|
) || !props.block.options?.isMultipleChoice
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const DateForm = (props: Props) => {
|
|||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<form
|
<form
|
||||||
class={'flex justify-between rounded-lg typebot-input pr-2 items-end'}
|
class={'flex justify-between typebot-input pr-2 items-end'}
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
if (inputValues().from === '' && inputValues().to === '') return
|
if (inputValues().from === '' && inputValues().to === '') return
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|||||||
@@ -34,9 +34,7 @@ export const EmailInput = (props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={
|
class={'flex items-end justify-between pr-2 typebot-input w-full'}
|
||||||
'flex items-end justify-between rounded-lg pr-2 typebot-input w-full'
|
|
||||||
}
|
|
||||||
data-testid="input"
|
data-testid="input"
|
||||||
style={{
|
style={{
|
||||||
'max-width': '350px',
|
'max-width': '350px',
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export const FileUploadForm = (props: Props) => {
|
|||||||
<label
|
<label
|
||||||
for="dropzone-file"
|
for="dropzone-file"
|
||||||
class={
|
class={
|
||||||
'typebot-upload-input py-6 flex flex-col justify-center items-center w-full bg-gray-50 rounded-lg border-2 border-gray-300 border-dashed cursor-pointer hover:bg-gray-100 px-8 mb-2 ' +
|
'typebot-upload-input py-6 flex flex-col justify-center items-center w-full bg-gray-50 border-2 border-gray-300 border-dashed cursor-pointer hover:bg-gray-100 px-8 mb-2 ' +
|
||||||
(isDraggingOver() ? 'dragging-over' : '')
|
(isDraggingOver() ? 'dragging-over' : '')
|
||||||
}
|
}
|
||||||
onDragOver={handleDragOver}
|
onDragOver={handleDragOver}
|
||||||
@@ -181,7 +181,7 @@ export const FileUploadForm = (props: Props) => {
|
|||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<button
|
<button
|
||||||
class={
|
class={
|
||||||
'py-2 px-4 justify-center font-semibold rounded-md text-white focus:outline-none flex items-center disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 transition-all filter hover:brightness-90 active:brightness-75 typebot-button '
|
'py-2 px-4 justify-center font-semibold text-white focus:outline-none flex items-center disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 transition-all filter hover:brightness-90 active:brightness-75 typebot-button '
|
||||||
}
|
}
|
||||||
on:click={() =>
|
on:click={() =>
|
||||||
props.onSkip(
|
props.onSkip(
|
||||||
@@ -207,7 +207,7 @@ export const FileUploadForm = (props: Props) => {
|
|||||||
<Show when={selectedFiles().length}>
|
<Show when={selectedFiles().length}>
|
||||||
<button
|
<button
|
||||||
class={
|
class={
|
||||||
'secondary-button py-2 px-4 justify-center font-semibold rounded-md text-white focus:outline-none flex items-center disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 transition-all filter hover:brightness-90 active:brightness-75 mr-2'
|
'secondary-button py-2 px-4 justify-center font-semibold text-white focus:outline-none flex items-center disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 transition-all filter hover:brightness-90 active:brightness-75 mr-2'
|
||||||
}
|
}
|
||||||
on:click={clearFiles}
|
on:click={clearFiles}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -34,9 +34,7 @@ export const NumberInput = (props: NumberInputProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={
|
class={'flex items-end justify-between pr-2 typebot-input w-full'}
|
||||||
'flex items-end justify-between rounded-lg pr-2 typebot-input w-full'
|
|
||||||
}
|
|
||||||
data-testid="input"
|
data-testid="input"
|
||||||
style={{
|
style={{
|
||||||
'max-width': '350px',
|
'max-width': '350px',
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export const StripePaymentForm = (props: Props) => {
|
|||||||
<form
|
<form
|
||||||
id="payment-form"
|
id="payment-form"
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
class="flex flex-col rounded-lg p-4 typebot-input w-full items-center"
|
class="flex flex-col p-4 typebot-input w-full items-center"
|
||||||
>
|
>
|
||||||
<slot name={slotName} ref={paymentElementSlot} />
|
<slot name={slotName} ref={paymentElementSlot} />
|
||||||
<Show when={isMounted()}>
|
<Show when={isMounted()}>
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export const PhoneInput = (props: PhoneInputProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={'flex items-end justify-between rounded-lg pr-2 typebot-input'}
|
class={'flex items-end justify-between pr-2 typebot-input'}
|
||||||
data-testid="input"
|
data-testid="input"
|
||||||
style={{
|
style={{
|
||||||
'max-width': '400px',
|
'max-width': '400px',
|
||||||
@@ -99,7 +99,7 @@ export const PhoneInput = (props: PhoneInputProps) => {
|
|||||||
onKeyDown={submitWhenEnter}
|
onKeyDown={submitWhenEnter}
|
||||||
>
|
>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="relative typebot-country-select flex justify-center items-center rounded-md">
|
<div class="relative typebot-country-select flex justify-center items-center">
|
||||||
<div class="pl-2 pr-1 flex items-center gap-2">
|
<div class="pl-2 pr-1 flex items-center gap-2">
|
||||||
<span>
|
<span>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ const RatingButton = (props: RatingButtonProps) => {
|
|||||||
props.onClick(props.idx)
|
props.onClick(props.idx)
|
||||||
}}
|
}}
|
||||||
class={
|
class={
|
||||||
'py-2 px-4 mr-2 mb-2 text-left font-semibold rounded-md transition-all filter hover:brightness-90 active:brightness-75 duration-100 focus:outline-none typebot-button ' +
|
'py-2 px-4 mr-2 mb-2 text-left font-semibold transition-all filter hover:brightness-90 active:brightness-75 duration-100 focus:outline-none typebot-button ' +
|
||||||
(props.isOneClickSubmitEnabled ||
|
(props.isOneClickSubmitEnabled ||
|
||||||
(isDefined(props.rating) && props.idx <= props.rating)
|
(isDefined(props.rating) && props.idx <= props.rating)
|
||||||
? ''
|
? ''
|
||||||
|
|||||||
@@ -35,9 +35,7 @@ export const TextInput = (props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={
|
class={'flex items-end justify-between pr-2 typebot-input w-full'}
|
||||||
'flex items-end justify-between rounded-lg pr-2 typebot-input w-full'
|
|
||||||
}
|
|
||||||
data-testid="input"
|
data-testid="input"
|
||||||
style={{
|
style={{
|
||||||
'max-width': props.block.options.isLong ? undefined : '350px',
|
'max-width': props.block.options.isLong ? undefined : '350px',
|
||||||
|
|||||||
@@ -40,9 +40,7 @@ export const UrlInput = (props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={
|
class={'flex items-end justify-between pr-2 typebot-input w-full'}
|
||||||
'flex items-end justify-between rounded-lg pr-2 typebot-input w-full'
|
|
||||||
}
|
|
||||||
data-testid="input"
|
data-testid="input"
|
||||||
style={{
|
style={{
|
||||||
'max-width': '350px',
|
'max-width': '350px',
|
||||||
|
|||||||
@@ -59,11 +59,12 @@ const setChatTheme = (
|
|||||||
chatTheme: ChatTheme,
|
chatTheme: ChatTheme,
|
||||||
documentStyle: CSSStyleDeclaration
|
documentStyle: CSSStyleDeclaration
|
||||||
) => {
|
) => {
|
||||||
const { hostBubbles, guestBubbles, buttons, inputs } = chatTheme
|
const { hostBubbles, guestBubbles, buttons, inputs, roundness } = chatTheme
|
||||||
if (hostBubbles) setHostBubbles(hostBubbles, documentStyle)
|
if (hostBubbles) setHostBubbles(hostBubbles, documentStyle)
|
||||||
if (guestBubbles) setGuestBubbles(guestBubbles, documentStyle)
|
if (guestBubbles) setGuestBubbles(guestBubbles, documentStyle)
|
||||||
if (buttons) setButtons(buttons, documentStyle)
|
if (buttons) setButtons(buttons, documentStyle)
|
||||||
if (inputs) setInputs(inputs, documentStyle)
|
if (inputs) setInputs(inputs, documentStyle)
|
||||||
|
if (roundness) setRoundness(roundness, documentStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
const setHostBubbles = (
|
const setHostBubbles = (
|
||||||
@@ -151,3 +152,20 @@ const parseBackgroundValue = ({ type, content }: Background) => {
|
|||||||
return `url(${content})`
|
return `url(${content})`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setRoundness = (
|
||||||
|
roundness: NonNullable<ChatTheme['roundness']>,
|
||||||
|
documentStyle: CSSStyleDeclaration
|
||||||
|
) => {
|
||||||
|
switch (roundness) {
|
||||||
|
case 'none':
|
||||||
|
documentStyle.setProperty('--typebot-border-radius', '0')
|
||||||
|
break
|
||||||
|
case 'medium':
|
||||||
|
documentStyle.setProperty('--typebot-border-radius', '6px')
|
||||||
|
break
|
||||||
|
case 'large':
|
||||||
|
documentStyle.setProperty('--typebot-border-radius', '20px')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/react",
|
"name": "@typebot.io/react",
|
||||||
"version": "0.0.28",
|
"version": "0.0.29",
|
||||||
"description": "React library to display typebots on your website",
|
"description": "React library to display typebots on your website",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export const chatThemeSchema = z.object({
|
|||||||
guestBubbles: containerColorsSchema,
|
guestBubbles: containerColorsSchema,
|
||||||
buttons: containerColorsSchema,
|
buttons: containerColorsSchema,
|
||||||
inputs: inputColorsSchema,
|
inputs: inputColorsSchema,
|
||||||
|
roundness: z.enum(['none', 'medium', 'large']).optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const backgroundSchema = z.object({
|
const backgroundSchema = z.object({
|
||||||
|
|||||||
Reference in New Issue
Block a user