refactor(♻️ Add defaults everywhere (+ settings page)):
This commit is contained in:
@@ -1,22 +1,27 @@
|
||||
import { ChangeEvent, FormEvent, useState } from 'react'
|
||||
import { ChangeEvent, FormEvent, useEffect, useState } from 'react'
|
||||
import { Button, HStack, Input, Stack } from '@chakra-ui/react'
|
||||
import { SearchContextManager } from '@giphy/react-components'
|
||||
import { UploadButton } from '../buttons/UploadButton'
|
||||
import { GiphySearch } from './GiphySearch'
|
||||
import { useTypebot } from 'contexts/TypebotContext'
|
||||
import { ImageBubbleContent } from 'models'
|
||||
import { useDebounce } from 'use-debounce'
|
||||
|
||||
type Props = {
|
||||
content?: ImageBubbleContent
|
||||
onSubmit: (content: ImageBubbleContent) => void
|
||||
url?: string
|
||||
onSubmit: (url: string) => void
|
||||
isGiphyEnabled?: boolean
|
||||
}
|
||||
|
||||
export const ImageUploadContent = ({ content, onSubmit }: Props) => {
|
||||
export const ImageUploadContent = ({
|
||||
url,
|
||||
onSubmit,
|
||||
isGiphyEnabled = true,
|
||||
}: Props) => {
|
||||
const [currentTab, setCurrentTab] = useState<'link' | 'upload' | 'giphy'>(
|
||||
'upload'
|
||||
)
|
||||
|
||||
const handleSubmit = (url: string) => onSubmit({ url })
|
||||
const handleSubmit = (url: string) => onSubmit(url)
|
||||
return (
|
||||
<Stack>
|
||||
<HStack>
|
||||
@@ -34,7 +39,7 @@ export const ImageUploadContent = ({ content, onSubmit }: Props) => {
|
||||
>
|
||||
Embed link
|
||||
</Button>
|
||||
{process.env.NEXT_PUBLIC_GIPHY_API_KEY && (
|
||||
{process.env.NEXT_PUBLIC_GIPHY_API_KEY && isGiphyEnabled && (
|
||||
<Button
|
||||
variant={currentTab === 'giphy' ? 'solid' : 'ghost'}
|
||||
onClick={() => setCurrentTab('giphy')}
|
||||
@@ -45,11 +50,7 @@ export const ImageUploadContent = ({ content, onSubmit }: Props) => {
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
<BodyContent
|
||||
tab={currentTab}
|
||||
onSubmit={handleSubmit}
|
||||
url={content?.url}
|
||||
/>
|
||||
<BodyContent tab={currentTab} onSubmit={handleSubmit} url={url} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
@@ -93,26 +94,23 @@ const UploadFileContent = ({ onNewUrl }: ContentProps) => {
|
||||
|
||||
const EmbedLinkContent = ({ initialUrl, onNewUrl }: ContentProps) => {
|
||||
const [imageUrl, setImageUrl] = useState(initialUrl ?? '')
|
||||
const [debouncedImageUrl] = useDebounce(imageUrl, 100)
|
||||
|
||||
useEffect(() => {
|
||||
if (initialUrl === debouncedImageUrl) return
|
||||
onNewUrl(imageUrl)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [debouncedImageUrl])
|
||||
|
||||
const handleImageUrlChange = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
setImageUrl(e.target.value)
|
||||
|
||||
const handleUrlSubmit = (e: FormEvent) => {
|
||||
e.preventDefault()
|
||||
onNewUrl(imageUrl)
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack as="form" onSubmit={handleUrlSubmit}>
|
||||
<Input
|
||||
placeholder={'Paste the image link...'}
|
||||
onChange={handleImageUrlChange}
|
||||
value={imageUrl}
|
||||
/>
|
||||
<Button type="submit" disabled={imageUrl === ''} colorScheme="blue">
|
||||
Embed image
|
||||
</Button>
|
||||
</Stack>
|
||||
<Input
|
||||
placeholder={'Paste the image link...'}
|
||||
onChange={handleImageUrlChange}
|
||||
value={imageUrl}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
9
apps/builder/components/shared/Info.tsx
Normal file
9
apps/builder/components/shared/Info.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Alert, AlertIcon, AlertProps } from '@chakra-ui/react'
|
||||
import React from 'react'
|
||||
|
||||
export const Info = (props: AlertProps) => (
|
||||
<Alert status="info" bgColor={'blue.50'} rounded="md" {...props}>
|
||||
<AlertIcon />
|
||||
{props.children}
|
||||
</Alert>
|
||||
)
|
||||
39
apps/builder/components/shared/SmartNumberInput.tsx
Normal file
39
apps/builder/components/shared/SmartNumberInput.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import {
|
||||
NumberInputProps,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberDecrementStepper,
|
||||
} from '@chakra-ui/react'
|
||||
import { useState } from 'react'
|
||||
|
||||
export const SmartNumberInput = ({
|
||||
value,
|
||||
onValueChange,
|
||||
...props
|
||||
}: {
|
||||
value?: number
|
||||
onValueChange: (value?: number) => void
|
||||
} & NumberInputProps) => {
|
||||
const [currentValue, setCurrentValue] = useState(value?.toString() ?? '')
|
||||
|
||||
const handleValueChange = (value: string) => {
|
||||
setCurrentValue(value)
|
||||
if (value.endsWith('.') || value.endsWith(',')) return
|
||||
if (value === '') return onValueChange(undefined)
|
||||
const newValue = parseFloat(value)
|
||||
if (isNaN(newValue)) return
|
||||
onValueChange(newValue)
|
||||
}
|
||||
|
||||
return (
|
||||
<NumberInput onChange={handleValueChange} value={currentValue} {...props}>
|
||||
<NumberInputField />
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Input } from '@chakra-ui/react'
|
||||
import React from 'react'
|
||||
import {
|
||||
TextBoxWithVariableButton,
|
||||
TextBoxWithVariableButtonProps,
|
||||
} from './TextboxWithVariableButton'
|
||||
|
||||
export const InputWithVariableButton = (
|
||||
props: Omit<TextBoxWithVariableButtonProps, 'TextBox'>
|
||||
) => <TextBoxWithVariableButton TextBox={Input} {...props} />
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Textarea } from '@chakra-ui/react'
|
||||
import React from 'react'
|
||||
import {
|
||||
TextBoxWithVariableButton,
|
||||
TextBoxWithVariableButtonProps,
|
||||
} from './TextboxWithVariableButton'
|
||||
|
||||
export const TextareaWithVariableButton = (
|
||||
props: Omit<TextBoxWithVariableButtonProps, 'TextBox'>
|
||||
) => <TextBoxWithVariableButton TextBox={Textarea} {...props} />
|
||||
@@ -1,31 +1,40 @@
|
||||
import {
|
||||
ComponentWithAs,
|
||||
Flex,
|
||||
HStack,
|
||||
IconButton,
|
||||
Input,
|
||||
InputProps,
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
TextareaProps,
|
||||
Tooltip,
|
||||
} from '@chakra-ui/react'
|
||||
import { UserIcon } from 'assets/icons'
|
||||
import { Variable } from 'models'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { useDebounce } from 'use-debounce'
|
||||
import { VariableSearchInput } from './VariableSearchInput'
|
||||
import { VariableSearchInput } from '../VariableSearchInput'
|
||||
|
||||
export const InputWithVariableButton = ({
|
||||
initialValue,
|
||||
onChange,
|
||||
delay,
|
||||
...props
|
||||
}: {
|
||||
export type TextBoxWithVariableButtonProps = {
|
||||
initialValue: string
|
||||
onChange: (value: string) => void
|
||||
delay?: number
|
||||
} & Omit<InputProps, 'onChange'>) => {
|
||||
const inputRef = useRef<HTMLInputElement | null>(null)
|
||||
TextBox:
|
||||
| ComponentWithAs<'textarea', TextareaProps>
|
||||
| ComponentWithAs<'input', InputProps>
|
||||
} & Omit<InputProps & TextareaProps, 'onChange'>
|
||||
|
||||
export const TextBoxWithVariableButton = ({
|
||||
initialValue,
|
||||
onChange,
|
||||
delay,
|
||||
TextBox,
|
||||
...props
|
||||
}: TextBoxWithVariableButtonProps) => {
|
||||
const textBoxRef = useRef<(HTMLInputElement & HTMLTextAreaElement) | null>(
|
||||
null
|
||||
)
|
||||
const [value, setValue] = useState(initialValue)
|
||||
const [debouncedValue] = useDebounce(value, delay ?? 100)
|
||||
const [carretPosition, setCarretPosition] = useState<number>(0)
|
||||
@@ -36,48 +45,49 @@ export const InputWithVariableButton = ({
|
||||
}, [debouncedValue])
|
||||
|
||||
const handleVariableSelected = (variable?: Variable) => {
|
||||
if (!inputRef.current || !variable) return
|
||||
if (!textBoxRef.current || !variable) return
|
||||
const cursorPosition = carretPosition
|
||||
const textBeforeCursorPosition = inputRef.current.value.substring(
|
||||
const textBeforeCursorPosition = textBoxRef.current.value.substring(
|
||||
0,
|
||||
cursorPosition
|
||||
)
|
||||
const textAfterCursorPosition = inputRef.current.value.substring(
|
||||
const textAfterCursorPosition = textBoxRef.current.value.substring(
|
||||
cursorPosition,
|
||||
inputRef.current.value.length
|
||||
textBoxRef.current.value.length
|
||||
)
|
||||
setValue(
|
||||
textBeforeCursorPosition +
|
||||
`{{${variable.name}}}` +
|
||||
textAfterCursorPosition
|
||||
)
|
||||
inputRef.current.focus()
|
||||
textBoxRef.current.focus()
|
||||
setTimeout(() => {
|
||||
if (!inputRef.current) return
|
||||
inputRef.current.selectionStart = inputRef.current.selectionEnd =
|
||||
if (!textBoxRef.current) return
|
||||
textBoxRef.current.selectionStart = textBoxRef.current.selectionEnd =
|
||||
carretPosition + `{{${variable.name}}}`.length
|
||||
setCarretPosition(inputRef.current.selectionStart)
|
||||
setCarretPosition(textBoxRef.current.selectionStart)
|
||||
}, 100)
|
||||
}
|
||||
|
||||
const handleKeyUp = () => {
|
||||
if (!inputRef.current?.selectionStart) return
|
||||
setCarretPosition(inputRef.current.selectionStart)
|
||||
if (!textBoxRef.current?.selectionStart) return
|
||||
setCarretPosition(textBoxRef.current.selectionStart)
|
||||
}
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setValue(e.target.value)
|
||||
const handleChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
||||
) => setValue(e.target.value)
|
||||
|
||||
return (
|
||||
<HStack spacing={0}>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
<HStack spacing={0} align={'flex-end'}>
|
||||
<TextBox
|
||||
ref={textBoxRef}
|
||||
onKeyUp={handleKeyUp}
|
||||
onClick={handleKeyUp}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
{...props}
|
||||
bgColor={'white'}
|
||||
{...props}
|
||||
/>
|
||||
<Popover matchWidth isLazy>
|
||||
<PopoverTrigger>
|
||||
@@ -0,0 +1,2 @@
|
||||
export { InputWithVariableButton } from './InputWithVariableButton'
|
||||
export { TextareaWithVariableButton } from './TextareaWithVariableButton'
|
||||
Reference in New Issue
Block a user