2
0

(theme) Add corner roundness customization

Closes #415
This commit is contained in:
Baptiste Arnaud
2023-03-22 16:43:34 +01:00
parent 3992227afc
commit 65d33e04bc
30 changed files with 195 additions and 67 deletions

View File

@@ -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>
)

View File

@@ -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

View File

@@ -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

View File

@@ -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}

View File

@@ -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'

View File

@@ -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",

View File

@@ -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);
} }

View File

@@ -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) => (

View File

@@ -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()}
> >

View File

@@ -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
} }
> >

View File

@@ -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}

View File

@@ -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',

View File

@@ -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%',

View File

@@ -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={{

View File

@@ -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={{

View File

@@ -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%',

View File

@@ -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'}

View File

@@ -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

View File

@@ -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()

View File

@@ -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',

View File

@@ -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}
> >

View File

@@ -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',

View File

@@ -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()}>

View File

@@ -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>
{ {

View File

@@ -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)
? '' ? ''

View File

@@ -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',

View File

@@ -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',

View File

@@ -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
}
}

View File

@@ -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",

View File

@@ -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({