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

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