@ -1,6 +1,5 @@
|
||||
import {
|
||||
useDisclosure,
|
||||
useOutsideClick,
|
||||
Flex,
|
||||
Popover,
|
||||
Input,
|
||||
@ -16,6 +15,7 @@ import { useState, useRef, useEffect, ChangeEvent, ReactNode } from 'react'
|
||||
import { useDebouncedCallback } from 'use-debounce'
|
||||
import { env, isDefined } from 'utils'
|
||||
import { VariablesButton } from '@/features/variables'
|
||||
import { useOutsideClick } from '@/hooks/useOutsideClick'
|
||||
|
||||
type Props = {
|
||||
selectedItem?: string
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
HStack,
|
||||
useColorModeValue,
|
||||
PopoverAnchor,
|
||||
useOutsideClick,
|
||||
Portal,
|
||||
} from '@chakra-ui/react'
|
||||
import { EditIcon, PlusIcon, TrashIcon } from '@/components/icons'
|
||||
import { useTypebot } from '@/features/editor'
|
||||
@ -19,6 +19,7 @@ import { Variable } from 'models'
|
||||
import React, { useState, useRef, ChangeEvent, useEffect } from 'react'
|
||||
import { useDebouncedCallback } from 'use-debounce'
|
||||
import { byId, env, isDefined, isNotDefined } from 'utils'
|
||||
import { useOutsideClick } from '@/hooks/useOutsideClick'
|
||||
|
||||
type Props = {
|
||||
initialVariableId?: string
|
||||
@ -188,75 +189,78 @@ export const VariableSearchInput = ({
|
||||
{...inputProps}
|
||||
/>
|
||||
</PopoverAnchor>
|
||||
<PopoverContent
|
||||
maxH="35vh"
|
||||
overflowY="scroll"
|
||||
role="menu"
|
||||
w="inherit"
|
||||
shadow="lg"
|
||||
>
|
||||
{isCreateVariableButtonDisplayed && (
|
||||
<Button
|
||||
ref={createVariableItemRef}
|
||||
role="menuitem"
|
||||
minH="40px"
|
||||
onClick={handleCreateNewVariableClick}
|
||||
fontSize="16px"
|
||||
fontWeight="normal"
|
||||
rounded="none"
|
||||
colorScheme="gray"
|
||||
variant="ghost"
|
||||
justifyContent="flex-start"
|
||||
leftIcon={<PlusIcon />}
|
||||
bgColor={keyboardFocusIndex === 0 ? bg : 'transparent'}
|
||||
>
|
||||
Create "{inputValue}"
|
||||
</Button>
|
||||
)}
|
||||
{filteredItems.length > 0 && (
|
||||
<>
|
||||
{filteredItems.map((item, idx) => {
|
||||
const indexInList = isCreateVariableButtonDisplayed
|
||||
? idx + 1
|
||||
: idx
|
||||
return (
|
||||
<Button
|
||||
ref={(el) => (itemsRef.current[idx] = el)}
|
||||
role="menuitem"
|
||||
minH="40px"
|
||||
key={idx}
|
||||
onClick={handleVariableNameClick(item)}
|
||||
fontSize="16px"
|
||||
fontWeight="normal"
|
||||
rounded="none"
|
||||
colorScheme="gray"
|
||||
variant="ghost"
|
||||
justifyContent="space-between"
|
||||
bgColor={
|
||||
keyboardFocusIndex === indexInList ? bg : 'transparent'
|
||||
}
|
||||
>
|
||||
{item.name}
|
||||
<HStack>
|
||||
<IconButton
|
||||
icon={<EditIcon />}
|
||||
aria-label="Rename variable"
|
||||
size="xs"
|
||||
onClick={handleRenameVariableClick(item)}
|
||||
/>
|
||||
<IconButton
|
||||
icon={<TrashIcon />}
|
||||
aria-label="Remove variable"
|
||||
size="xs"
|
||||
onClick={handleDeleteVariableClick(item)}
|
||||
/>
|
||||
</HStack>
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</PopoverContent>
|
||||
<Portal>
|
||||
<PopoverContent
|
||||
maxH="35vh"
|
||||
overflowY="scroll"
|
||||
role="menu"
|
||||
w="inherit"
|
||||
shadow="lg"
|
||||
>
|
||||
{isCreateVariableButtonDisplayed && (
|
||||
<Button
|
||||
ref={createVariableItemRef}
|
||||
role="menuitem"
|
||||
minH="40px"
|
||||
onClick={handleCreateNewVariableClick}
|
||||
fontSize="16px"
|
||||
fontWeight="normal"
|
||||
rounded="none"
|
||||
colorScheme="gray"
|
||||
variant="ghost"
|
||||
justifyContent="flex-start"
|
||||
leftIcon={<PlusIcon />}
|
||||
bgColor={keyboardFocusIndex === 0 ? bg : 'transparent'}
|
||||
>
|
||||
Create "{inputValue}"
|
||||
</Button>
|
||||
)}
|
||||
{filteredItems.length > 0 && (
|
||||
<>
|
||||
{filteredItems.map((item, idx) => {
|
||||
const indexInList = isCreateVariableButtonDisplayed
|
||||
? idx + 1
|
||||
: idx
|
||||
return (
|
||||
<Button
|
||||
ref={(el) => (itemsRef.current[idx] = el)}
|
||||
role="menuitem"
|
||||
minH="40px"
|
||||
key={idx}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onClick={handleVariableNameClick(item)}
|
||||
fontSize="16px"
|
||||
fontWeight="normal"
|
||||
rounded="none"
|
||||
colorScheme="gray"
|
||||
variant="ghost"
|
||||
justifyContent="space-between"
|
||||
bgColor={
|
||||
keyboardFocusIndex === indexInList ? bg : 'transparent'
|
||||
}
|
||||
>
|
||||
{item.name}
|
||||
<HStack>
|
||||
<IconButton
|
||||
icon={<EditIcon />}
|
||||
aria-label="Rename variable"
|
||||
size="xs"
|
||||
onClick={handleRenameVariableClick(item)}
|
||||
/>
|
||||
<IconButton
|
||||
icon={<TrashIcon />}
|
||||
aria-label="Remove variable"
|
||||
size="xs"
|
||||
onClick={handleDeleteVariableClick(item)}
|
||||
/>
|
||||
</HStack>
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</PopoverContent>
|
||||
</Portal>
|
||||
</Popover>
|
||||
</Flex>
|
||||
)
|
||||
|
@ -3,7 +3,6 @@ import {
|
||||
Stack,
|
||||
useColorModeValue,
|
||||
useEventListener,
|
||||
useOutsideClick,
|
||||
} from '@chakra-ui/react'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
@ -22,6 +21,7 @@ import { serializeHtml } from '@udecode/plate-serializer-html'
|
||||
import { parseHtmlStringToPlainText } from '../../utils'
|
||||
import { VariableSearchInput } from '@/components/VariableSearchInput'
|
||||
import { colors } from '@/lib/theme'
|
||||
import { useOutsideClick } from '@/hooks/useOutsideClick'
|
||||
|
||||
type TextBubbleEditorContentProps = {
|
||||
id: string
|
||||
|
@ -193,7 +193,7 @@ export const WebhookSettings = ({
|
||||
Query params
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={4} as={Stack} spacing="6">
|
||||
<AccordionPanel py={4} as={Stack} spacing="6">
|
||||
<TableList<KeyValue>
|
||||
initialItems={localWebhook.queryParams}
|
||||
onItemsChange={handleQueryParamsChange}
|
||||
@ -208,7 +208,7 @@ export const WebhookSettings = ({
|
||||
Headers
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={4} as={Stack} spacing="6">
|
||||
<AccordionPanel py={4} as={Stack} spacing="6">
|
||||
<TableList<KeyValue>
|
||||
initialItems={localWebhook.headers}
|
||||
onItemsChange={handleHeadersChange}
|
||||
@ -223,7 +223,7 @@ export const WebhookSettings = ({
|
||||
Body
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={4} as={Stack} spacing="6">
|
||||
<AccordionPanel py={4} as={Stack} spacing="6">
|
||||
<SwitchWithLabel
|
||||
label="Custom body"
|
||||
initialValue={options.isCustomBody ?? true}
|
||||
@ -244,7 +244,7 @@ export const WebhookSettings = ({
|
||||
Variable values for test
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={4} as={Stack} spacing="6">
|
||||
<AccordionPanel py={4} as={Stack} spacing="6">
|
||||
<TableList<VariableForTest>
|
||||
initialItems={
|
||||
options?.variablesForTest ?? { byId: {}, allIds: [] }
|
||||
@ -279,7 +279,7 @@ export const WebhookSettings = ({
|
||||
Save in variables
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={4} as={Stack} spacing="6">
|
||||
<AccordionPanel py={4} as={Stack} spacing="6">
|
||||
<TableList<ResponseVariableMapping>
|
||||
initialItems={options.responseVariableMapping}
|
||||
onItemsChange={handleResponseMappingChange}
|
||||
|
@ -120,10 +120,6 @@ const NonMemoizedDraggableGroupNode = ({
|
||||
const handleTitleSubmit = (title: string) =>
|
||||
title.length > 0 ? updateGroup(groupIndex, { title }) : undefined
|
||||
|
||||
const handleMouseDown = (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (isReadOnly) return
|
||||
if (mouseOverGroup?.id !== group.id && !isStartGroup)
|
||||
@ -200,7 +196,6 @@ const NonMemoizedDraggableGroupNode = ({
|
||||
currentCoordinates?.y ?? 0
|
||||
}px)`,
|
||||
}}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
cursor={isMouseDown ? 'grabbing' : 'pointer'}
|
||||
|
@ -342,7 +342,7 @@ export const getEndpointTopOffset = ({
|
||||
if (!endpointId) return
|
||||
const endpointRef = endpoints[endpointId]?.ref
|
||||
if (!endpointRef?.current) return
|
||||
const endpointHeight = 28 * graphScale
|
||||
const endpointHeight = 34 * graphScale
|
||||
return (
|
||||
(endpointRef.current.getBoundingClientRect().y +
|
||||
endpointHeight / 2 -
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
InputRightElement,
|
||||
ModalFooter,
|
||||
Link,
|
||||
useColorModeValue,
|
||||
} from '@chakra-ui/react'
|
||||
import { ExternalLinkIcon } from '@/components/icons'
|
||||
import { env, getViewerUrl } from 'utils'
|
||||
@ -44,7 +45,7 @@ export const WordpressModal = ({
|
||||
<Link
|
||||
href="https://wordpress.org/plugins/typebot/"
|
||||
isExternal
|
||||
color="blue.500"
|
||||
color={useColorModeValue('blue.500', 'blue.300')}
|
||||
>
|
||||
the official Typebot WordPress plugin
|
||||
<ExternalLinkIcon mx="2px" />
|
||||
|
@ -68,8 +68,8 @@ export const TemplatesModal = ({ isOpen, onClose, onTypebotChoose }: Props) => {
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent h="85vh">
|
||||
<ModalBody as={HStack} p="0">
|
||||
<Stack w="full" h="full">
|
||||
<ModalBody as={HStack} p="0" spacing="0">
|
||||
<Stack w="full" h="full" spacing="4">
|
||||
<Heading pl="10" pt="4" fontSize="2xl">
|
||||
{selectedTemplate.emoji}{' '}
|
||||
<chakra.span ml="2">{selectedTemplate.name}</chakra.span>
|
||||
@ -79,6 +79,7 @@ export const TemplatesModal = ({ isOpen, onClose, onTypebotChoose }: Props) => {
|
||||
apiHost={getViewerUrl({ isBuilder: true })}
|
||||
typebot={parseTypebotToPublicTypebot(typebot)}
|
||||
key={typebot.id}
|
||||
style={{ borderRadius: '0.25rem' }}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
|
26
apps/builder/src/hooks/useOutsideClick.ts
Normal file
26
apps/builder/src/hooks/useOutsideClick.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { useEventListener } from '@chakra-ui/react'
|
||||
import { RefObject } from 'react'
|
||||
|
||||
type Handler = (event: MouseEvent) => void
|
||||
|
||||
type Props<T> = {
|
||||
ref: RefObject<T>
|
||||
handler: Handler
|
||||
mouseEvent?: 'mousedown' | 'mouseup'
|
||||
}
|
||||
|
||||
export const useOutsideClick = <T extends HTMLElement = HTMLElement>({
|
||||
ref,
|
||||
handler,
|
||||
mouseEvent = 'mousedown',
|
||||
}: Props<T>): void => {
|
||||
const triggerHandlerIfOutside = (event: MouseEvent) => {
|
||||
const el = ref?.current
|
||||
if (!el || el.contains(event.target as Node)) {
|
||||
return
|
||||
}
|
||||
handler(event)
|
||||
}
|
||||
|
||||
useEventListener(mouseEvent, triggerHandlerIfOutside)
|
||||
}
|
Reference in New Issue
Block a user