2
0
Files
bot/packages/embeds/js/src/features/blocks/inputs/pictureChoice/MultiplePictureChoice.tsx

218 lines
7.4 KiB
TypeScript
Raw Normal View History

import { InputSubmitContent } from '@/types'
import {
PictureChoiceBlock,
defaultPictureChoiceOptions,
} from '@typebot.io/schemas/features/blocks/inputs/pictureChoice'
import { For, Show, createSignal, onMount } from 'solid-js'
import { Checkbox } from '../buttons/components/Checkbox'
import { SendButton } from '@/components'
import { isDefined, isEmpty, isSvgSrc } from '@typebot.io/lib'
import { SearchInput } from '@/components/inputs/SearchInput'
import { isMobile } from '@/utils/isMobileSignal'
type Props = {
defaultItems: PictureChoiceBlock['items']
options: PictureChoiceBlock['options']
onSubmit: (value: InputSubmitContent) => void
}
export const MultiplePictureChoice = (props: Props) => {
let inputRef: HTMLInputElement | undefined
const [filteredItems, setFilteredItems] = createSignal(props.defaultItems)
const [selectedItemIds, setSelectedItemIds] = createSignal<string[]>([])
onMount(() => {
if (!isMobile() && inputRef) inputRef.focus()
})
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: selectedItemIds()
.map((selectedItemId) => {
const item = props.defaultItems.find(
(item) => item.id === selectedItemId
)
return item?.title ?? item?.pictureSrc
})
.join(', '),
})
const filterItems = (inputValue: string) => {
setFilteredItems(
props.defaultItems.filter(
(item) =>
item.title
?.toLowerCase()
.includes((inputValue ?? '').toLowerCase()) ||
item.description
?.toLowerCase()
.includes((inputValue ?? '').toLowerCase())
)
)
}
return (
<form class="flex flex-col gap-2 w-full items-end" onSubmit={handleSubmit}>
<Show when={props.options.isSearchable}>
<div class="flex items-end typebot-input w-full">
<SearchInput
ref={inputRef}
onInput={filterItems}
placeholder={
props.options.searchInputPlaceholder ??
defaultPictureChoiceOptions.searchInputPlaceholder
}
onClear={() => setFilteredItems(props.defaultItems)}
/>
</div>
</Show>
<div
class={
'flex flex-wrap justify-end gap-2' +
(props.options.isSearchable
? ' overflow-y-scroll max-h-[464px] rounded-md hide-scrollbar'
: '')
}
>
<For each={filteredItems()}>
{(item, index) => (
<div
role="checkbox"
aria-checked={selectedItemIds().some(
(selectedItemId) => selectedItemId === item.id
)}
on:click={() => handleClick(item.id)}
class={
'flex flex-col focus:outline-none cursor-pointer select-none typebot-selectable-picture' +
(selectedItemIds().some(
(selectedItemId) => selectedItemId === item.id
)
? ' selected'
: '') +
(isSvgSrc(item.pictureSrc) ? ' has-svg' : '')
}
data-itemid={item.id}
>
<img
src={item.pictureSrc}
alt={item.title ?? `Picture ${index() + 1}`}
elementtiming={`Picture choice ${index() + 1}`}
fetchpriority={'high'}
class="m-auto"
/>
<div
class={
'flex gap-3 py-2 flex-shrink-0' +
(isEmpty(item.title) && isEmpty(item.description)
? ' justify-center'
: ' pl-4')
}
>
<Checkbox
isChecked={selectedItemIds().some(
(selectedItemId) => selectedItemId === item.id
)}
class={item.title || item.description ? 'mt-1' : undefined}
/>
<Show when={item.title || item.description}>
<div class="flex flex-col gap-1 ">
<Show when={item.title}>
<span class="font-semibold">{item.title}</span>
</Show>
<Show when={item.description}>
<span class="text-sm">{item.description}</span>
</Show>
</div>
</Show>
</div>
</div>
)}
</For>
<For
each={selectedItemIds()
.filter((selectedItemId) =>
filteredItems().every((item) => item.id !== selectedItemId)
)
.map((selectedItemId) =>
props.defaultItems.find((item) => item.id === selectedItemId)
)
.filter(isDefined)}
>
{(selectedItem, index) => (
<div
role="checkbox"
aria-checked
on:click={() => handleClick(selectedItem.id)}
class={
'flex flex-col focus:outline-none cursor-pointer select-none typebot-selectable-picture selected'
}
data-itemid={selectedItem.id}
>
<img
src={
props.defaultItems.find((item) => item.id === selectedItem.id)
?.pictureSrc
}
alt={selectedItem.title ?? `Selected picture ${index() + 1}`}
elementtiming={`Selected picture choice ${index() + 1}`}
fetchpriority={'high'}
/>
<div
class={
'flex gap-3 py-2 flex-shrink-0' +
(isEmpty(selectedItem.title) &&
isEmpty(selectedItem.description)
? ' justify-center'
: ' pl-4')
}
>
<Checkbox
isChecked={selectedItemIds().some(
(selectedItemId) => selectedItemId === selectedItem.id
)}
class={
selectedItem.title || selectedItem.description
? 'mt-1'
: undefined
}
/>
<Show when={selectedItem.title || selectedItem.description}>
<div class="flex flex-col gap-1 ">
<Show when={selectedItem.title}>
<span class="font-semibold">{selectedItem.title}</span>
</Show>
<Show when={selectedItem.description}>
<span class="text-sm">{selectedItem.description}</span>
</Show>
</div>
</Show>
</div>
</div>
)}
</For>
</div>
{selectedItemIds().length > 0 && (
<SendButton disableIcon>
{props.options?.buttonLabel ??
defaultPictureChoiceOptions.buttonLabel}
</SendButton>
)}
</form>
)
}