diff --git a/apps/builder/src/features/blocks/inputs/buttons/components/ButtonsBlockSettings.tsx b/apps/builder/src/features/blocks/inputs/buttons/components/ButtonsBlockSettings.tsx
index 129bf17b01..2a2db9e3b1 100644
--- a/apps/builder/src/features/blocks/inputs/buttons/components/ButtonsBlockSettings.tsx
+++ b/apps/builder/src/features/blocks/inputs/buttons/components/ButtonsBlockSettings.tsx
@@ -12,13 +12,15 @@ type Props = {
}
export const ButtonsBlockSettings = ({ options, onOptionsChange }: Props) => {
- const handleIsMultipleChange = (isMultipleChoice: boolean) =>
+ const updateIsMultiple = (isMultipleChoice: boolean) =>
options && onOptionsChange({ ...options, isMultipleChoice })
- const handleButtonLabelChange = (buttonLabel: string) =>
+ const updateIsSearchable = (isSearchable: boolean) =>
+ options && onOptionsChange({ ...options, isSearchable })
+ const updateButtonLabel = (buttonLabel: string) =>
options && onOptionsChange({ ...options, buttonLabel })
- const handleVariableChange = (variable?: Variable) =>
+ const updateSaveVariable = (variable?: Variable) =>
options && onOptionsChange({ ...options, variableId: variable?.id })
- const handleDynamicVariableChange = (variable?: Variable) =>
+ const updateDynamicDataVariable = (variable?: Variable) =>
options && onOptionsChange({ ...options, dynamicVariableId: variable?.id })
return (
@@ -26,18 +28,23 @@ export const ButtonsBlockSettings = ({ options, onOptionsChange }: Props) => {
+
{options?.isMultipleChoice && (
)}
- Dynamic items from variable:{' '}
+ Dynamic data:{' '}
If defined, buttons will be dynamically displayed based on what the
variable contains.
@@ -45,7 +52,7 @@ export const ButtonsBlockSettings = ({ options, onOptionsChange }: Props) => {
@@ -54,7 +61,7 @@ export const ButtonsBlockSettings = ({ options, onOptionsChange }: Props) => {
diff --git a/apps/docs/openapi/builder/_spec_.json b/apps/docs/openapi/builder/_spec_.json
index bf90b8ba23..47ac3d3ba8 100644
--- a/apps/docs/openapi/builder/_spec_.json
+++ b/apps/docs/openapi/builder/_spec_.json
@@ -295,6 +295,54 @@
"data"
],
"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": {
"type": "string"
+ },
+ "isSearchable": {
+ "type": "boolean"
}
},
"required": [
@@ -1743,6 +1794,121 @@
"options"
],
"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": {
"type": "number",
"nullable": true
+ },
+ "isQuarantined": {
+ "type": "boolean"
}
},
"required": [
@@ -3093,7 +3262,8 @@
"storageLimitSecondEmailSentAt",
"customChatsLimit",
"customStorageLimit",
- "customSeatsLimit"
+ "customSeatsLimit",
+ "isQuarantined"
],
"additionalProperties": false
}
@@ -3216,6 +3386,9 @@
"customSeatsLimit": {
"type": "number",
"nullable": true
+ },
+ "isQuarantined": {
+ "type": "boolean"
}
},
"required": [
@@ -3234,7 +3407,8 @@
"storageLimitSecondEmailSentAt",
"customChatsLimit",
"customStorageLimit",
- "customSeatsLimit"
+ "customSeatsLimit",
+ "isQuarantined"
],
"additionalProperties": false
}
@@ -3374,6 +3548,9 @@
"customSeatsLimit": {
"type": "number",
"nullable": true
+ },
+ "isQuarantined": {
+ "type": "boolean"
}
},
"required": [
@@ -3392,7 +3569,8 @@
"storageLimitSecondEmailSentAt",
"customChatsLimit",
"customStorageLimit",
- "customSeatsLimit"
+ "customSeatsLimit",
+ "isQuarantined"
],
"additionalProperties": false
}
@@ -4642,6 +4820,9 @@
"customSeatsLimit": {
"type": "number",
"nullable": true
+ },
+ "isQuarantined": {
+ "type": "boolean"
}
},
"required": [
@@ -4660,7 +4841,8 @@
"storageLimitSecondEmailSentAt",
"customChatsLimit",
"customStorageLimit",
- "customSeatsLimit"
+ "customSeatsLimit",
+ "isQuarantined"
],
"additionalProperties": false
}
diff --git a/apps/docs/openapi/chat/_spec_.json b/apps/docs/openapi/chat/_spec_.json
index da0aa9f527..211abae420 100644
--- a/apps/docs/openapi/chat/_spec_.json
+++ b/apps/docs/openapi/chat/_spec_.json
@@ -459,6 +459,9 @@
},
"dynamicVariableId": {
"type": "string"
+ },
+ "isSearchable": {
+ "type": "boolean"
}
},
"required": [
@@ -1404,6 +1407,121 @@
"options"
],
"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": {
"type": "string"
+ },
+ "isSearchable": {
+ "type": "boolean"
}
},
"required": [
diff --git a/packages/embeds/js/package.json b/packages/embeds/js/package.json
index 58e3de35b9..4aaf9647d4 100644
--- a/packages/embeds/js/package.json
+++ b/packages/embeds/js/package.json
@@ -1,6 +1,6 @@
{
"name": "@typebot.io/js",
- "version": "0.0.40",
+ "version": "0.0.41",
"description": "Javascript library to display typebots on your website",
"type": "module",
"main": "dist/index.js",
diff --git a/packages/embeds/js/src/assets/index.css b/packages/embeds/js/src/assets/index.css
index ac4ca45a1f..9355d173ec 100644
--- a/packages/embeds/js/src/assets/index.css
+++ b/packages/embeds/js/src/assets/index.css
@@ -286,3 +286,14 @@ textarea {
.typebot-popup-blocked-toast {
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 */
+}
diff --git a/packages/embeds/js/src/components/ConversationContainer/ChatChunk.tsx b/packages/embeds/js/src/components/ConversationContainer/ChatChunk.tsx
index 78270b1227..ca982179ba 100644
--- a/packages/embeds/js/src/components/ConversationContainer/ChatChunk.tsx
+++ b/packages/embeds/js/src/components/ConversationContainer/ChatChunk.tsx
@@ -47,38 +47,42 @@ export const ChatChunk = (props: Props) => {
return (
-
-
0
- }
- >
-
-
-
-
- {(message) => (
-
- )}
-
+
0}>
+
+
0
+ }
+ >
+
+
+
+
+
+ {(message) => (
+
+ )}
+
+
-
+
{props.input && displayedMessageIndex() === props.messages.length && (
@@ -159,30 +161,49 @@ const Input = (props: {
onSubmit={onSubmit}
/>
-
-
-
-
-
+
+ {(block) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
)
}
+
+const isButtonsBlock = (
+ block: ChatReply['input']
+): ChoiceInputBlock | undefined =>
+ block?.type === InputBlockType.CHOICE ? block : undefined
diff --git a/packages/embeds/js/src/components/icons/CloseIcon.tsx b/packages/embeds/js/src/components/icons/CloseIcon.tsx
new file mode 100644
index 0000000000..93827df09f
--- /dev/null
+++ b/packages/embeds/js/src/components/icons/CloseIcon.tsx
@@ -0,0 +1,17 @@
+import { JSX } from 'solid-js/jsx-runtime'
+
+export const CloseIcon = (props: JSX.SvgSVGAttributes) => (
+
+)
diff --git a/packages/embeds/js/src/components/inputs/SearchInput.tsx b/packages/embeds/js/src/components/inputs/SearchInput.tsx
new file mode 100644
index 0000000000..55457b46a8
--- /dev/null
+++ b/packages/embeds/js/src/components/inputs/SearchInput.tsx
@@ -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, '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 (
+
+ changeValue(e.currentTarget.value)}
+ {...others}
+ />
+ 0}>
+
+
+
+ )
+}
diff --git a/packages/embeds/js/src/features/blocks/inputs/buttons/components/MultipleChoicesForm.tsx b/packages/embeds/js/src/features/blocks/inputs/buttons/components/MultipleChoicesForm.tsx
index 95f162f180..351bbe86b0 100644
--- a/packages/embeds/js/src/features/blocks/inputs/buttons/components/MultipleChoicesForm.tsx
+++ b/packages/embeds/js/src/features/blocks/inputs/buttons/components/MultipleChoicesForm.tsx
@@ -72,13 +72,11 @@ export const MultipleChoicesForm = (props: Props) => {
)}
-
- {selectedIndices().length > 0 && (
-
- {props.options?.buttonLabel ?? 'Send'}
-
- )}
-
+ {selectedIndices().length > 0 && (
+
+ {props.options?.buttonLabel ?? 'Send'}
+
+ )}
)
}
diff --git a/packages/embeds/js/src/features/blocks/inputs/buttons/components/SearchableButtons.tsx b/packages/embeds/js/src/features/blocks/inputs/buttons/components/SearchableButtons.tsx
new file mode 100644
index 0000000000..6067a3100a
--- /dev/null
+++ b/packages/embeds/js/src/features/blocks/inputs/buttons/components/SearchableButtons.tsx
@@ -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 (
+
+
+ setFilteredItems(props.defaultItems)}
+ />
+
+
+
+
+ {(item, index) => (
+
+
+ {props.inputIndex === 0 && props.defaultItems.length === 1 && (
+
+
+
+
+ )}
+
+ )}
+
+
+
+ )
+}
diff --git a/packages/embeds/js/src/features/blocks/inputs/buttons/components/SearchableMultipleChoicesForm.tsx b/packages/embeds/js/src/features/blocks/inputs/buttons/components/SearchableMultipleChoicesForm.tsx
new file mode 100644
index 0000000000..bc362aa861
--- /dev/null
+++ b/packages/embeds/js/src/features/blocks/inputs/buttons/components/SearchableMultipleChoicesForm.tsx
@@ -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
([])
+
+ 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 (
+
+ )
+}
diff --git a/packages/embeds/react/package.json b/packages/embeds/react/package.json
index 18eb409911..daec988378 100644
--- a/packages/embeds/react/package.json
+++ b/packages/embeds/react/package.json
@@ -1,6 +1,6 @@
{
"name": "@typebot.io/react",
- "version": "0.0.40",
+ "version": "0.0.41",
"description": "React library to display typebots on your website",
"main": "dist/index.js",
"types": "dist/index.d.ts",
diff --git a/packages/schemas/features/blocks/inputs/choice.ts b/packages/schemas/features/blocks/inputs/choice.ts
index 6402fa7a48..933e3f0acf 100644
--- a/packages/schemas/features/blocks/inputs/choice.ts
+++ b/packages/schemas/features/blocks/inputs/choice.ts
@@ -10,12 +10,14 @@ export const choiceInputOptionsSchema = optionBaseSchema.merge(
isMultipleChoice: z.boolean(),
buttonLabel: z.string(),
dynamicVariableId: z.string().optional(),
+ isSearchable: z.boolean().optional(),
})
)
export const defaultChoiceInputOptions: ChoiceInputOptions = {
buttonLabel: defaultButtonLabel,
isMultipleChoice: false,
+ isSearchable: false,
}
export const buttonItemSchema = itemBaseSchema.merge(