2
0

(buttons) Add searchable choices

Closes #473
This commit is contained in:
Baptiste Arnaud
2023-04-26 17:54:00 +02:00
parent 124f350aa2
commit 5b4a6c523d
14 changed files with 687 additions and 77 deletions

View File

@ -12,13 +12,15 @@ type Props = {
} }
export const ButtonsBlockSettings = ({ options, onOptionsChange }: Props) => { export const ButtonsBlockSettings = ({ options, onOptionsChange }: Props) => {
const handleIsMultipleChange = (isMultipleChoice: boolean) => const updateIsMultiple = (isMultipleChoice: boolean) =>
options && onOptionsChange({ ...options, isMultipleChoice }) options && onOptionsChange({ ...options, isMultipleChoice })
const handleButtonLabelChange = (buttonLabel: string) => const updateIsSearchable = (isSearchable: boolean) =>
options && onOptionsChange({ ...options, isSearchable })
const updateButtonLabel = (buttonLabel: string) =>
options && onOptionsChange({ ...options, buttonLabel }) options && onOptionsChange({ ...options, buttonLabel })
const handleVariableChange = (variable?: Variable) => const updateSaveVariable = (variable?: Variable) =>
options && onOptionsChange({ ...options, variableId: variable?.id }) options && onOptionsChange({ ...options, variableId: variable?.id })
const handleDynamicVariableChange = (variable?: Variable) => const updateDynamicDataVariable = (variable?: Variable) =>
options && onOptionsChange({ ...options, dynamicVariableId: variable?.id }) options && onOptionsChange({ ...options, dynamicVariableId: variable?.id })
return ( return (
@ -26,18 +28,23 @@ export const ButtonsBlockSettings = ({ options, onOptionsChange }: Props) => {
<SwitchWithLabel <SwitchWithLabel
label="Multiple choice?" label="Multiple choice?"
initialValue={options?.isMultipleChoice ?? false} initialValue={options?.isMultipleChoice ?? false}
onCheckChange={handleIsMultipleChange} onCheckChange={updateIsMultiple}
/>
<SwitchWithLabel
label="Is searchable?"
initialValue={options?.isSearchable ?? false}
onCheckChange={updateIsSearchable}
/> />
{options?.isMultipleChoice && ( {options?.isMultipleChoice && (
<TextInput <TextInput
label="Button label:" label="Button label:"
defaultValue={options?.buttonLabel ?? 'Send'} defaultValue={options?.buttonLabel ?? 'Send'}
onChange={handleButtonLabelChange} onChange={updateButtonLabel}
/> />
)} )}
<FormControl> <FormControl>
<FormLabel> <FormLabel>
Dynamic items from variable:{' '} Dynamic data:{' '}
<MoreInfoTooltip> <MoreInfoTooltip>
If defined, buttons will be dynamically displayed based on what the If defined, buttons will be dynamically displayed based on what the
variable contains. variable contains.
@ -45,7 +52,7 @@ export const ButtonsBlockSettings = ({ options, onOptionsChange }: Props) => {
</FormLabel> </FormLabel>
<VariableSearchInput <VariableSearchInput
initialVariableId={options?.dynamicVariableId} initialVariableId={options?.dynamicVariableId}
onSelectVariable={handleDynamicVariableChange} onSelectVariable={updateDynamicDataVariable}
/> />
</FormControl> </FormControl>
<Stack> <Stack>
@ -54,7 +61,7 @@ export const ButtonsBlockSettings = ({ options, onOptionsChange }: Props) => {
</FormLabel> </FormLabel>
<VariableSearchInput <VariableSearchInput
initialVariableId={options?.variableId} initialVariableId={options?.variableId}
onSelectVariable={handleVariableChange} onSelectVariable={updateSaveVariable}
/> />
</Stack> </Stack>
</Stack> </Stack>

View File

@ -295,6 +295,54 @@
"data" "data"
], ],
"additionalProperties": false "additionalProperties": false
},
{
"type": "object",
"properties": {
"userId": {
"type": "string"
},
"workspaceId": {
"type": "string"
},
"name": {
"type": "string",
"enum": [
"Workspace limit reached"
]
},
"data": {
"type": "object",
"properties": {
"chatsLimit": {
"type": "number"
},
"storageLimit": {
"type": "number"
},
"totalChatsUsed": {
"type": "number"
},
"totalStorageUsed": {
"type": "number"
}
},
"required": [
"chatsLimit",
"storageLimit",
"totalChatsUsed",
"totalStorageUsed"
],
"additionalProperties": false
}
},
"required": [
"userId",
"workspaceId",
"name",
"data"
],
"additionalProperties": false
} }
] ]
} }
@ -798,6 +846,9 @@
}, },
"dynamicVariableId": { "dynamicVariableId": {
"type": "string" "type": "string"
},
"isSearchable": {
"type": "boolean"
} }
}, },
"required": [ "required": [
@ -1743,6 +1794,121 @@
"options" "options"
], ],
"additionalProperties": false "additionalProperties": false
},
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"groupId": {
"type": "string"
},
"outgoingEdgeId": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"AB test"
]
},
"items": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": [
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"blockId": {
"type": "string"
},
"outgoingEdgeId": {
"type": "string"
},
"type": {
"type": "number",
"enum": [
2
]
},
"path": {
"type": "string",
"enum": [
"a"
]
}
},
"required": [
"id",
"blockId",
"type",
"path"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"blockId": {
"type": "string"
},
"outgoingEdgeId": {
"type": "string"
},
"type": {
"type": "number",
"enum": [
2
]
},
"path": {
"type": "string",
"enum": [
"b"
]
}
},
"required": [
"id",
"blockId",
"type",
"path"
],
"additionalProperties": false
}
]
},
"options": {
"type": "object",
"properties": {
"aPercent": {
"type": "number",
"minimum": 0,
"maximum": 100
}
},
"required": [
"aPercent"
],
"additionalProperties": false
}
},
"required": [
"id",
"groupId",
"type",
"items",
"options"
],
"additionalProperties": false
} }
] ]
}, },
@ -3075,6 +3241,9 @@
"customSeatsLimit": { "customSeatsLimit": {
"type": "number", "type": "number",
"nullable": true "nullable": true
},
"isQuarantined": {
"type": "boolean"
} }
}, },
"required": [ "required": [
@ -3093,7 +3262,8 @@
"storageLimitSecondEmailSentAt", "storageLimitSecondEmailSentAt",
"customChatsLimit", "customChatsLimit",
"customStorageLimit", "customStorageLimit",
"customSeatsLimit" "customSeatsLimit",
"isQuarantined"
], ],
"additionalProperties": false "additionalProperties": false
} }
@ -3216,6 +3386,9 @@
"customSeatsLimit": { "customSeatsLimit": {
"type": "number", "type": "number",
"nullable": true "nullable": true
},
"isQuarantined": {
"type": "boolean"
} }
}, },
"required": [ "required": [
@ -3234,7 +3407,8 @@
"storageLimitSecondEmailSentAt", "storageLimitSecondEmailSentAt",
"customChatsLimit", "customChatsLimit",
"customStorageLimit", "customStorageLimit",
"customSeatsLimit" "customSeatsLimit",
"isQuarantined"
], ],
"additionalProperties": false "additionalProperties": false
} }
@ -3374,6 +3548,9 @@
"customSeatsLimit": { "customSeatsLimit": {
"type": "number", "type": "number",
"nullable": true "nullable": true
},
"isQuarantined": {
"type": "boolean"
} }
}, },
"required": [ "required": [
@ -3392,7 +3569,8 @@
"storageLimitSecondEmailSentAt", "storageLimitSecondEmailSentAt",
"customChatsLimit", "customChatsLimit",
"customStorageLimit", "customStorageLimit",
"customSeatsLimit" "customSeatsLimit",
"isQuarantined"
], ],
"additionalProperties": false "additionalProperties": false
} }
@ -4642,6 +4820,9 @@
"customSeatsLimit": { "customSeatsLimit": {
"type": "number", "type": "number",
"nullable": true "nullable": true
},
"isQuarantined": {
"type": "boolean"
} }
}, },
"required": [ "required": [
@ -4660,7 +4841,8 @@
"storageLimitSecondEmailSentAt", "storageLimitSecondEmailSentAt",
"customChatsLimit", "customChatsLimit",
"customStorageLimit", "customStorageLimit",
"customSeatsLimit" "customSeatsLimit",
"isQuarantined"
], ],
"additionalProperties": false "additionalProperties": false
} }

View File

@ -459,6 +459,9 @@
}, },
"dynamicVariableId": { "dynamicVariableId": {
"type": "string" "type": "string"
},
"isSearchable": {
"type": "boolean"
} }
}, },
"required": [ "required": [
@ -1404,6 +1407,121 @@
"options" "options"
], ],
"additionalProperties": false "additionalProperties": false
},
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"groupId": {
"type": "string"
},
"outgoingEdgeId": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"AB test"
]
},
"items": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": [
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"blockId": {
"type": "string"
},
"outgoingEdgeId": {
"type": "string"
},
"type": {
"type": "number",
"enum": [
2
]
},
"path": {
"type": "string",
"enum": [
"a"
]
}
},
"required": [
"id",
"blockId",
"type",
"path"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"blockId": {
"type": "string"
},
"outgoingEdgeId": {
"type": "string"
},
"type": {
"type": "number",
"enum": [
2
]
},
"path": {
"type": "string",
"enum": [
"b"
]
}
},
"required": [
"id",
"blockId",
"type",
"path"
],
"additionalProperties": false
}
]
},
"options": {
"type": "object",
"properties": {
"aPercent": {
"type": "number",
"minimum": 0,
"maximum": 100
}
},
"required": [
"aPercent"
],
"additionalProperties": false
}
},
"required": [
"id",
"groupId",
"type",
"items",
"options"
],
"additionalProperties": false
} }
] ]
}, },
@ -3182,6 +3300,9 @@
}, },
"dynamicVariableId": { "dynamicVariableId": {
"type": "string" "type": "string"
},
"isSearchable": {
"type": "boolean"
} }
}, },
"required": [ "required": [

View File

@ -1,6 +1,6 @@
{ {
"name": "@typebot.io/js", "name": "@typebot.io/js",
"version": "0.0.40", "version": "0.0.41",
"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

@ -286,3 +286,14 @@ textarea {
.typebot-popup-blocked-toast { .typebot-popup-blocked-toast {
border-radius: var(--typebot-border-radius); border-radius: var(--typebot-border-radius);
} }
/* Hide scrollbar for Chrome, Safari and Opera */
.hide-scrollbar::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
.hide-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}

View File

@ -47,38 +47,42 @@ export const ChatChunk = (props: Props) => {
return ( return (
<div class="flex flex-col w-full min-w-0 gap-2"> <div class="flex flex-col w-full min-w-0 gap-2">
<div class={'flex' + (isMobile() ? ' gap-1' : ' gap-2')}> <Show when={props.messages.length > 0}>
<Show <div class={'flex' + (isMobile() ? ' gap-1' : ' gap-2')}>
when={ <Show
props.theme.chat.hostAvatar?.isEnabled && props.messages.length > 0 when={
} props.theme.chat.hostAvatar?.isEnabled &&
> props.messages.length > 0
<AvatarSideContainer }
hostAvatarSrc={props.theme.chat.hostAvatar?.url} >
hideAvatar={props.hideAvatar} <AvatarSideContainer
/> hostAvatarSrc={props.theme.chat.hostAvatar?.url}
</Show> hideAvatar={props.hideAvatar}
<div />
class="flex flex-col flex-1 gap-2" </Show>
style={{
'margin-right': props.theme.chat.guestAvatar?.isEnabled <div
? isMobile() class="flex flex-col flex-1 gap-2"
? '32px' style={{
: '48px' 'margin-right': props.theme.chat.guestAvatar?.isEnabled
: undefined, ? isMobile()
}} ? '32px'
> : '48px'
<For each={props.messages.slice(0, displayedMessageIndex() + 1)}> : undefined,
{(message) => ( }}
<HostBubble >
message={message} <For each={props.messages.slice(0, displayedMessageIndex() + 1)}>
typingEmulation={props.settings.typingEmulation} {(message) => (
onTransitionEnd={displayNextMessage} <HostBubble
/> message={message}
)} typingEmulation={props.settings.typingEmulation}
</For> onTransitionEnd={displayNextMessage}
/>
)}
</For>
</div>
</div> </div>
</div> </Show>
{props.input && displayedMessageIndex() === props.messages.length && ( {props.input && displayedMessageIndex() === props.messages.length && (
<InputChatBlock <InputChatBlock
block={props.input} block={props.input}

View File

@ -30,6 +30,8 @@ import { isMobile } from '@/utils/isMobileSignal'
import { PaymentForm } from '@/features/blocks/inputs/payment' import { PaymentForm } from '@/features/blocks/inputs/payment'
import { MultipleChoicesForm } from '@/features/blocks/inputs/buttons/components/MultipleChoicesForm' import { MultipleChoicesForm } from '@/features/blocks/inputs/buttons/components/MultipleChoicesForm'
import { Buttons } from '@/features/blocks/inputs/buttons/components/Buttons' import { Buttons } from '@/features/blocks/inputs/buttons/components/Buttons'
import { SearchableButtons } from '@/features/blocks/inputs/buttons/components/SearchableButtons'
import { SearchableMultipleChoicesForm } from '@/features/blocks/inputs/buttons/components/SearchableMultipleChoicesForm'
type Props = { type Props = {
block: NonNullable<ChatReply['input']> block: NonNullable<ChatReply['input']>
@ -159,30 +161,49 @@ const Input = (props: {
onSubmit={onSubmit} onSubmit={onSubmit}
/> />
</Match> </Match>
<Match <Match when={isButtonsBlock(props.block)} keyed>
when={ {(block) => (
props.block.type === InputBlockType.CHOICE && <Switch>
props.block.options.isMultipleChoice <Match when={!block.options.isMultipleChoice}>
} <Switch>
> <Match when={block.options.isSearchable}>
<MultipleChoicesForm <SearchableButtons
inputIndex={props.inputIndex} inputIndex={props.inputIndex}
items={(props.block as ChoiceInputBlock).items} defaultItems={block.items}
options={props.block.options as ChoiceInputBlock['options']} onSubmit={onSubmit}
onSubmit={onSubmit} />
/> </Match>
</Match> <Match when={!block.options.isSearchable}>
<Match <Buttons
when={ inputIndex={props.inputIndex}
props.block.type === InputBlockType.CHOICE && items={block.items}
!props.block.options.isMultipleChoice onSubmit={onSubmit}
} />
> </Match>
<Buttons </Switch>
inputIndex={props.inputIndex} </Match>
items={(props.block as ChoiceInputBlock).items} <Match when={block.options.isMultipleChoice}>
onSubmit={onSubmit} <Switch>
/> <Match when={block.options.isSearchable}>
<SearchableMultipleChoicesForm
inputIndex={props.inputIndex}
defaultItems={block.items}
options={block.options}
onSubmit={onSubmit}
/>
</Match>
<Match when={!block.options.isSearchable}>
<MultipleChoicesForm
inputIndex={props.inputIndex}
items={block.items}
options={block.options}
onSubmit={onSubmit}
/>
</Match>
</Switch>
</Match>
</Switch>
)}
</Match> </Match>
<Match when={props.block.type === InputBlockType.RATING}> <Match when={props.block.type === InputBlockType.RATING}>
<RatingForm <RatingForm
@ -214,3 +235,8 @@ const Input = (props: {
</Switch> </Switch>
) )
} }
const isButtonsBlock = (
block: ChatReply['input']
): ChoiceInputBlock | undefined =>
block?.type === InputBlockType.CHOICE ? block : undefined

View File

@ -0,0 +1,17 @@
import { JSX } from 'solid-js/jsx-runtime'
export const CloseIcon = (props: JSX.SvgSVGAttributes<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2px"
stroke-linecap="round"
stroke-linejoin="round"
{...props}
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
)

View File

@ -0,0 +1,43 @@
import { Show, createSignal, splitProps } from 'solid-js'
import { JSX } from 'solid-js/jsx-runtime'
import { CloseIcon } from '../icons/CloseIcon'
type Props = {
ref: HTMLInputElement | undefined
onInput: (value: string) => void
onClear: () => void
} & Omit<JSX.InputHTMLAttributes<HTMLInputElement>, 'onInput'>
export const SearchInput = (props: Props) => {
const [value, setValue] = createSignal('')
const [local, others] = splitProps(props, ['onInput', 'ref'])
const changeValue = (value: string) => {
setValue(value)
local.onInput(value)
}
const clearValue = () => {
setValue('')
props.onClear()
}
return (
<div class="flex justify-between items-center gap-2 w-full pr-4">
<input
ref={props.ref}
class="focus:outline-none bg-transparent px-4 py-4 flex-1 w-full text-input"
type="text"
style={{ 'font-size': '16px' }}
value={value()}
onInput={(e) => changeValue(e.currentTarget.value)}
{...others}
/>
<Show when={value().length > 0}>
<button class="w-5 h-5" on:click={clearValue}>
<CloseIcon />
</button>
</Show>
</div>
)
}

View File

@ -72,13 +72,11 @@ export const MultipleChoicesForm = (props: Props) => {
)} )}
</For> </For>
</div> </div>
<div class="flex"> {selectedIndices().length > 0 && (
{selectedIndices().length > 0 && ( <SendButton disableIcon>
<SendButton disableIcon> {props.options?.buttonLabel ?? 'Send'}
{props.options?.buttonLabel ?? 'Send'} </SendButton>
</SendButton> )}
)}
</div>
</form> </form>
) )
} }

View File

@ -0,0 +1,68 @@
import { Button } from '@/components/Button'
import { SearchInput } from '@/components/inputs/SearchInput'
import { InputSubmitContent } from '@/types'
import { isMobile } from '@/utils/isMobileSignal'
import type { ChoiceInputBlock } from '@typebot.io/schemas'
import { For, createSignal, onMount } from 'solid-js'
type Props = {
inputIndex: number
defaultItems: ChoiceInputBlock['items']
onSubmit: (value: InputSubmitContent) => void
}
export const SearchableButtons = (props: Props) => {
let inputRef: HTMLInputElement | undefined
const [filteredItems, setFilteredItems] = createSignal(props.defaultItems)
onMount(() => {
if (!isMobile() && inputRef) inputRef.focus()
})
// eslint-disable-next-line solid/reactivity
const handleClick = (itemIndex: number) => () =>
props.onSubmit({ value: filteredItems()[itemIndex].content ?? '' })
const filterItems = (inputValue: string) => {
setFilteredItems(
props.defaultItems.filter((item) =>
item.content?.toLowerCase().includes((inputValue ?? '').toLowerCase())
)
)
}
return (
<div class="flex flex-col gap-2 w-full">
<div class="flex items-end typebot-input w-full">
<SearchInput
ref={inputRef}
onInput={filterItems}
placeholder="Filter the options..."
onClear={() => setFilteredItems(props.defaultItems)}
/>
</div>
<div class="flex flex-wrap justify-end gap-2 overflow-y-scroll max-h-80 rounded-md hide-scrollbar">
<For each={filteredItems()}>
{(item, index) => (
<span class={'relative' + (isMobile() ? ' w-full' : '')}>
<Button
on:click={handleClick(index())}
data-itemid={item.id}
class="w-full"
>
{item.content}
</Button>
{props.inputIndex === 0 && props.defaultItems.length === 1 && (
<span class="flex h-3 w-3 absolute top-0 right-0 -mt-1 -mr-1 ping">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full brightness-200 opacity-75" />
<span class="relative inline-flex rounded-full h-3 w-3 brightness-150" />
</span>
)}
</span>
)}
</For>
</div>
</div>
)
}

View File

@ -0,0 +1,131 @@
import { SendButton } from '@/components/SendButton'
import { InputSubmitContent } from '@/types'
import { isMobile } from '@/utils/isMobileSignal'
import type { ChoiceInputBlock } from '@typebot.io/schemas'
import { createSignal, For } from 'solid-js'
import { Checkbox } from './Checkbox'
import { SearchInput } from '@/components/inputs/SearchInput'
type Props = {
inputIndex: number
defaultItems: ChoiceInputBlock['items']
options: ChoiceInputBlock['options']
onSubmit: (value: InputSubmitContent) => void
}
export const SearchableMultipleChoicesForm = (props: Props) => {
let inputRef: HTMLInputElement | undefined
const [filteredItems, setFilteredItems] = createSignal(props.defaultItems)
const [selectedItemIds, setSelectedItemIds] = createSignal<string[]>([])
const handleClick = (itemId: string) => {
toggleSelectedItemId(itemId)
}
const toggleSelectedItemId = (itemId: string) => {
const existingIndex = selectedItemIds().indexOf(itemId)
if (existingIndex !== -1) {
setSelectedItemIds((selectedItemIds) =>
selectedItemIds.filter((selectedItemId) => selectedItemId !== itemId)
)
} else {
setSelectedItemIds((selectedIndices) => [...selectedIndices, itemId])
}
}
const handleSubmit = () =>
props.onSubmit({
value: props.defaultItems
.filter((item) => selectedItemIds().includes(item.id))
.join(', '),
})
const filterItems = (inputValue: string) => {
setFilteredItems(
props.defaultItems.filter((item) =>
item.content?.toLowerCase().includes((inputValue ?? '').toLowerCase())
)
)
}
return (
<form class="flex flex-col items-end gap-2 w-full" onSubmit={handleSubmit}>
<div class="flex items-end typebot-input w-full">
<SearchInput
ref={inputRef}
onInput={filterItems}
placeholder="Filter the options..."
onClear={() => setFilteredItems(props.defaultItems)}
/>
</div>
<div class="flex flex-wrap justify-end gap-2 overflow-y-scroll max-h-80 rounded-md hide-scrollbar">
<For each={filteredItems()}>
{(item) => (
<span class={'relative' + (isMobile() ? ' w-full' : '')}>
<div
role="checkbox"
aria-checked={selectedItemIds().some(
(selectedItemId) => selectedItemId === item.id
)}
on:click={() => handleClick(item.id)}
class={
'w-full py-2 px-4 font-semibold focus:outline-none cursor-pointer select-none typebot-selectable' +
(selectedItemIds().some(
(selectedItemId) => selectedItemId === item.id
)
? ' selected'
: '')
}
data-itemid={item.id}
>
<div class="flex items-center gap-2">
<Checkbox
isChecked={selectedItemIds().some(
(selectedItemId) => selectedItemId === item.id
)}
/>
<span>{item.content}</span>
</div>
</div>
</span>
)}
</For>
<For
each={selectedItemIds().filter((selectedItemId) =>
filteredItems().every((item) => item.id !== selectedItemId)
)}
>
{(selectedItemId) => (
<span class={'relative' + (isMobile() ? ' w-full' : '')}>
<div
role="checkbox"
aria-checked
on:click={() => handleClick(selectedItemId)}
class={
'w-full py-2 px-4 font-semibold focus:outline-none cursor-pointer select-none typebot-selectable selected'
}
data-itemid={selectedItemId}
>
<div class="flex items-center gap-2">
<Checkbox isChecked />
<span>
{
props.defaultItems.find(
(item) => item.id === selectedItemId
)?.content
}
</span>
</div>
</div>
</span>
)}
</For>
</div>
{selectedItemIds().length > 0 && (
<SendButton disableIcon>
{props.options?.buttonLabel ?? 'Send'}
</SendButton>
)}
</form>
)
}

View File

@ -1,6 +1,6 @@
{ {
"name": "@typebot.io/react", "name": "@typebot.io/react",
"version": "0.0.40", "version": "0.0.41",
"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

@ -10,12 +10,14 @@ export const choiceInputOptionsSchema = optionBaseSchema.merge(
isMultipleChoice: z.boolean(), isMultipleChoice: z.boolean(),
buttonLabel: z.string(), buttonLabel: z.string(),
dynamicVariableId: z.string().optional(), dynamicVariableId: z.string().optional(),
isSearchable: z.boolean().optional(),
}) })
) )
export const defaultChoiceInputOptions: ChoiceInputOptions = { export const defaultChoiceInputOptions: ChoiceInputOptions = {
buttonLabel: defaultButtonLabel, buttonLabel: defaultButtonLabel,
isMultipleChoice: false, isMultipleChoice: false,
isSearchable: false,
} }
export const buttonItemSchema = itemBaseSchema.merge( export const buttonItemSchema = itemBaseSchema.merge(