feat(engine): ✨ Add Rating input
This commit is contained in:
@ -198,3 +198,23 @@ textarea {
|
||||
.ping span {
|
||||
background-color: var(--typebot-button-bg-color);
|
||||
}
|
||||
|
||||
.rating-icon-container svg {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
stroke: var(--typebot-button-bg-color);
|
||||
fill: var(--typebot-host-bubble-bg-color);
|
||||
transition: fill 100ms ease-out;
|
||||
}
|
||||
|
||||
.rating-icon-container.selected svg {
|
||||
fill: var(--typebot-button-bg-color);
|
||||
}
|
||||
|
||||
.rating-icon-container:hover svg {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
.rating-icon-container:active svg {
|
||||
filter: brightness(0.75);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import { useTypebot } from 'contexts/TypebotContext'
|
||||
import { parseVariables } from '../../../services/variable'
|
||||
import { isInputValid } from 'services/inputs'
|
||||
import { PaymentForm } from './inputs/PaymentForm'
|
||||
import { RatingForm } from './inputs/RatingForm'
|
||||
|
||||
export const InputChatStep = ({
|
||||
step,
|
||||
@ -115,5 +116,7 @@ const Input = ({
|
||||
onSuccess={() => onSubmit(step.options.labels.success ?? 'Success')}
|
||||
/>
|
||||
)
|
||||
case InputStepType.RATING:
|
||||
return <RatingForm step={step} onSubmit={onSubmit} />
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ export const ChoiceForm = ({ step, onSubmit }: ChoiceFormProps) => {
|
||||
</div>
|
||||
<div className="flex">
|
||||
{selectedIndices.length > 0 && (
|
||||
<SendButton label={step.options?.buttonLabel ?? 'Send'} />
|
||||
<SendButton label={step.options?.buttonLabel ?? 'Send'} disableIcon />
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
|
@ -0,0 +1,108 @@
|
||||
import { RatingInputOptions, RatingInputStep } from 'models'
|
||||
import React, { FormEvent, useRef, useState } from 'react'
|
||||
import { isDefined, isEmpty, isNotDefined } from 'utils'
|
||||
import { SendButton } from './SendButton'
|
||||
|
||||
type Props = {
|
||||
step: RatingInputStep
|
||||
onSubmit: (value: string) => void
|
||||
}
|
||||
|
||||
export const RatingForm = ({ step, onSubmit }: Props) => {
|
||||
const [rating, setRating] = useState<number>()
|
||||
const scaleElement = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
const handleSubmit = (e: FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (isNotDefined(rating)) return
|
||||
onSubmit(rating.toString())
|
||||
}
|
||||
|
||||
const handleClick = (rating: number) => setRating(rating)
|
||||
|
||||
return (
|
||||
<form className="flex flex-col" onSubmit={handleSubmit}>
|
||||
<div className="flex flex-col" ref={scaleElement}>
|
||||
<div className="flex">
|
||||
{Array.from(Array(step.options.length)).map((_, idx) => (
|
||||
<RatingButton
|
||||
{...step.options}
|
||||
key={idx}
|
||||
rating={rating}
|
||||
idx={idx + 1}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between mr-2 mb-2">
|
||||
{<span className="text-sm w-full ">{step.options.labels.left}</span>}
|
||||
{!isEmpty(step.options.labels.middle) && (
|
||||
<span className="text-sm w-full text-center">
|
||||
{step.options.labels.middle}
|
||||
</span>
|
||||
)}
|
||||
{
|
||||
<span className="text-sm w-full text-right">
|
||||
{step.options.labels.right}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end mr-2">
|
||||
{isDefined(rating) && (
|
||||
<SendButton
|
||||
label={step.options?.labels.button ?? 'Send'}
|
||||
disableIcon
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
const RatingButton = ({
|
||||
rating,
|
||||
idx,
|
||||
buttonType,
|
||||
customIcon,
|
||||
onClick,
|
||||
}: Pick<RatingInputOptions, 'buttonType' | 'customIcon'> & {
|
||||
rating: number | undefined
|
||||
idx: number
|
||||
onClick: (idx: number) => void
|
||||
}) => {
|
||||
if (buttonType === 'Numbers')
|
||||
return (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
onClick(idx)
|
||||
}}
|
||||
className={
|
||||
'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 ' +
|
||||
(isDefined(rating) && idx <= rating ? '' : 'selectable')
|
||||
}
|
||||
>
|
||||
{idx}
|
||||
</button>
|
||||
)
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
'flex justify-center items-center rating-icon-container cursor-pointer mr-2 mb-2 ' +
|
||||
(isDefined(rating) && idx <= rating ? 'selected' : '')
|
||||
}
|
||||
onClick={() => onClick(idx)}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html:
|
||||
customIcon.isEnabled && !isEmpty(customIcon.svg)
|
||||
? customIcon.svg
|
||||
: defaultIcon,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const defaultIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-star"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>`
|
Reference in New Issue
Block a user