2
0

feat(ui): 💄 Face lift

This commit is contained in:
Baptiste Arnaud
2022-01-19 09:44:21 +01:00
parent f49b5143cf
commit 44b478550f
23 changed files with 306 additions and 136 deletions

View File

@ -250,3 +250,12 @@ export const UserIcon = (props: IconProps) => (
<circle cx="12" cy="7" r="4"></circle> <circle cx="12" cy="7" r="4"></circle>
</Icon> </Icon>
) )
export const ExpandIcon = (props: IconProps) => (
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
<polyline points="15 3 21 3 21 9"></polyline>
<polyline points="9 21 3 21 3 15"></polyline>
<line x1="21" y1="3" x2="14" y2="10"></line>
<line x1="3" y1="21" x2="10" y2="14"></line>
</Icon>
)

View File

@ -16,7 +16,7 @@ export const Board = () => {
<StepTypesList /> <StepTypesList />
<GraphProvider> <GraphProvider>
<Graph flex="1" /> <Graph flex="1" />
<BoardMenuButton pos="absolute" right="20px" top="20px" /> <BoardMenuButton pos="absolute" right="40px" top="20px" />
{rightPanel === RightPanel.PREVIEW && <PreviewDrawer />} {rightPanel === RightPanel.PREVIEW && <PreviewDrawer />}
</GraphProvider> </GraphProvider>
</DndContext> </DndContext>

View File

@ -35,6 +35,8 @@ export const BoardMenuButton = (props: MenuButtonProps) => {
<Menu> <Menu>
<MenuButton <MenuButton
as={IconButton} as={IconButton}
variant="outline"
colorScheme="blue"
icon={<MoreVerticalIcon transform={'rotate(90deg)'} />} icon={<MoreVerticalIcon transform={'rotate(90deg)'} />}
isLoading={isDownloading} isLoading={isDownloading}
{...props} {...props}

View File

@ -1,4 +1,4 @@
import { Button, ButtonProps, Flex, HStack } from '@chakra-ui/react' import { Flex, HStack, StackProps, Text } from '@chakra-ui/react'
import { StepType, DraggableStepType } from 'models' import { StepType, DraggableStepType } from 'models'
import { useDnd } from 'contexts/DndContext' import { useDnd } from 'contexts/DndContext'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
@ -23,22 +23,31 @@ export const StepCard = ({
return ( return (
<Flex pos="relative"> <Flex pos="relative">
<Button <HStack
as={HStack}
borderWidth="1px" borderWidth="1px"
rounded="lg" rounded="lg"
flex="1" flex="1"
cursor={'grab'} cursor={'grab'}
opacity={isMouseDown ? '0.4' : '1'} opacity={isMouseDown ? '0.4' : '1'}
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
bgColor="white"
shadow="sm"
px="4"
py="2"
_hover={{ shadow: 'md' }}
transition="box-shadow 200ms"
> >
{!isMouseDown && ( {!isMouseDown ? (
<> <>
<StepIcon type={type} /> <StepIcon type={type} />
<StepTypeLabel type={type} /> <StepTypeLabel type={type} />
</> </>
) : (
<Text color="white" userSelect="none">
Placeholder
</Text>
)} )}
</Button> </HStack>
</Flex> </Flex>
) )
} }
@ -46,23 +55,24 @@ export const StepCard = ({
export const StepCardOverlay = ({ export const StepCardOverlay = ({
type, type,
...props ...props
}: Omit<ButtonProps, 'type'> & { type: StepType }) => { }: StackProps & { type: StepType }) => {
return ( return (
<Button <HStack
as={HStack}
borderWidth="1px" borderWidth="1px"
rounded="lg" rounded="lg"
cursor={'grab'} cursor={'grabbing'}
w="147px" w="147px"
pos="fixed"
top="0"
left="0"
transition="none" transition="none"
pointerEvents="none" pointerEvents="none"
px="4"
py="2"
bgColor="white"
shadow="xl"
zIndex={2}
{...props} {...props}
> >
<StepIcon type={type} /> <StepIcon type={type} />
<StepTypeLabel type={type} /> <StepTypeLabel type={type} />
</Button> </HStack>
) )
} }

View File

@ -4,6 +4,7 @@ import {
Text, Text,
SimpleGrid, SimpleGrid,
useEventListener, useEventListener,
Portal,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import { import {
BubbleStepType, BubbleStepType,
@ -38,10 +39,11 @@ export const StepTypesList = () => {
const handleMouseDown = (e: React.MouseEvent, type: DraggableStepType) => { const handleMouseDown = (e: React.MouseEvent, type: DraggableStepType) => {
const element = e.currentTarget as HTMLDivElement const element = e.currentTarget as HTMLDivElement
const rect = element.getBoundingClientRect() const rect = element.getBoundingClientRect()
const relativeX = e.clientX - rect.left setPosition({ x: rect.left, y: rect.top })
const relativeY = e.clientY - rect.top const x = e.clientX - rect.left
setPosition({ x: e.clientX - relativeX, y: e.clientY - relativeY }) const y = e.clientY - rect.top
setRelativeCoordinates({ x: relativeX, y: relativeY }) setRelativeCoordinates({ x, y })
console.log({ x: rect.left, y: rect.top })
setDraggedStepType(type) setDraggedStepType(type)
} }
@ -60,60 +62,76 @@ export const StepTypesList = () => {
w="320px" w="320px"
pos="absolute" pos="absolute"
left="10px" left="10px"
top="20px" top="10px"
h="calc(100vh - 100px)" h="calc(100vh - 80px)"
rounded="lg" rounded="lg"
shadow="lg" shadow="xl"
borderWidth="1px" borderWidth="1px"
zIndex="10" zIndex="2"
py="4" py="4"
px="2" px="2"
bgColor="white" bgColor="gray.50"
spacing={6}
userSelect="none"
> >
<Input placeholder="Search..." /> <Input placeholder="Search..." bgColor="white" />
<Text fontSize="sm" fontWeight="semibold" color="gray.600"> <Stack>
Bubbles <Text fontSize="sm" fontWeight="semibold" color="gray.600">
</Text> Bubbles
<SimpleGrid columns={2} spacing="2"> </Text>
{Object.values(BubbleStepType).map((type) => ( <SimpleGrid columns={2} spacing="2">
<StepCard key={type} type={type} onMouseDown={handleMouseDown} /> {Object.values(BubbleStepType).map((type) => (
))} <StepCard key={type} type={type} onMouseDown={handleMouseDown} />
</SimpleGrid> ))}
</SimpleGrid>
</Stack>
<Text fontSize="sm" fontWeight="semibold" color="gray.600"> <Stack>
Inputs <Text fontSize="sm" fontWeight="semibold" color="gray.600">
</Text> Inputs
<SimpleGrid columns={2} spacing="2"> </Text>
{Object.values(InputStepType).map((type) => ( <SimpleGrid columns={2} spacing="2">
<StepCard key={type} type={type} onMouseDown={handleMouseDown} /> {Object.values(InputStepType).map((type) => (
))} <StepCard key={type} type={type} onMouseDown={handleMouseDown} />
</SimpleGrid> ))}
</SimpleGrid>
</Stack>
<Text fontSize="sm" fontWeight="semibold" color="gray.600"> <Stack>
Logic <Text fontSize="sm" fontWeight="semibold" color="gray.600">
</Text> Logic
<SimpleGrid columns={2} spacing="2"> </Text>
{Object.values(LogicStepType).map((type) => ( <SimpleGrid columns={2} spacing="2">
<StepCard key={type} type={type} onMouseDown={handleMouseDown} /> {Object.values(LogicStepType).map((type) => (
))} <StepCard key={type} type={type} onMouseDown={handleMouseDown} />
</SimpleGrid> ))}
</SimpleGrid>
</Stack>
<Stack>
<Text fontSize="sm" fontWeight="semibold" color="gray.600">
Integrations
</Text>
<SimpleGrid columns={2} spacing="2">
{Object.values(IntegrationStepType).map((type) => (
<StepCard key={type} type={type} onMouseDown={handleMouseDown} />
))}
</SimpleGrid>
</Stack>
<Text fontSize="sm" fontWeight="semibold" color="gray.600">
Integrations
</Text>
<SimpleGrid columns={2} spacing="2">
{Object.values(IntegrationStepType).map((type) => (
<StepCard key={type} type={type} onMouseDown={handleMouseDown} />
))}
</SimpleGrid>
{draggedStepType && ( {draggedStepType && (
<StepCardOverlay <Portal>
type={draggedStepType} <StepCardOverlay
onMouseUp={handleMouseUp} type={draggedStepType}
style={{ onMouseUp={handleMouseUp}
transform: `translate(${position.x}px, ${position.y}px) rotate(-2deg)`, pos="fixed"
}} top="0"
/> left="0"
style={{
transform: `translate(${position.x}px, ${position.y}px) rotate(-2deg)`,
}}
/>
</Portal>
)} )}
</Stack> </Stack>
) )

View File

@ -25,7 +25,6 @@ export const BlockNode = ({ block }: Props) => {
const { setMouseOverBlockId } = useDnd() const { setMouseOverBlockId } = useDnd()
const { draggedStep, draggedStepType } = useDnd() const { draggedStep, draggedStepType } = useDnd()
const [isMouseDown, setIsMouseDown] = useState(false) const [isMouseDown, setIsMouseDown] = useState(false)
const [titleValue, setTitleValue] = useState(block.title)
const [isConnecting, setIsConnecting] = useState(false) const [isConnecting, setIsConnecting] = useState(false)
const isPreviewing = useMemo( const isPreviewing = useMemo(
() => () =>
@ -41,7 +40,7 @@ export const BlockNode = ({ block }: Props) => {
) )
}, [block.id, connectingIds]) }, [block.id, connectingIds])
const handleTitleChange = (title: string) => setTitleValue(title) const handleTitleSubmit = (title: string) => updateBlock(block.id, { title })
const handleMouseDown = () => { const handleMouseDown = () => {
setIsMouseDown(true) setIsMouseDown(true)
@ -49,6 +48,7 @@ export const BlockNode = ({ block }: Props) => {
const handleMouseUp = () => { const handleMouseUp = () => {
setIsMouseDown(false) setIsMouseDown(false)
} }
useEventListener('mouseup', handleMouseUp)
const handleMouseMove = (event: MouseEvent) => { const handleMouseMove = (event: MouseEvent) => {
if (!isMouseDown) return if (!isMouseDown) return
@ -85,24 +85,31 @@ export const BlockNode = ({ block }: Props) => {
p="4" p="4"
rounded="lg" rounded="lg"
bgColor="blue.50" bgColor="blue.50"
backgroundImage="linear-gradient(rgb(235, 239, 244), rgb(231, 234, 241))"
borderWidth="2px" borderWidth="2px"
borderColor={ borderColor={
isConnecting || isOpened || isPreviewing ? 'blue.400' : 'gray.400' isConnecting || isOpened || isPreviewing ? 'blue.400' : 'white'
} }
minW="300px" minW="300px"
transition="border 300ms" transition="border 300ms, box-shadow 200ms"
pos="absolute" pos="absolute"
style={{ style={{
transform: `translate(${block.graphCoordinates.x}px, ${block.graphCoordinates.y}px)`, transform: `translate(${block.graphCoordinates.x}px, ${block.graphCoordinates.y}px)`,
}} }}
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onMouseEnter={handleMouseEnter} onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave} onMouseLeave={handleMouseLeave}
cursor={isMouseDown ? 'grabbing' : 'pointer'}
boxShadow="0px 0px 0px 1px #e9edf3;"
_hover={{ shadow: 'lg' }}
> >
<Editable value={titleValue} onChange={handleTitleChange}> <Editable
defaultValue={block.title}
onSubmit={handleTitleSubmit}
fontWeight="semibold"
>
<EditablePreview <EditablePreview
_hover={{ bgColor: 'blue.100' }} _hover={{ bgColor: 'gray.300' }}
px="1" px="1"
userSelect={'none'} userSelect={'none'}
/> />

View File

@ -99,15 +99,17 @@ export const ChoiceItemNode = ({
onMouseEnter={handleMouseEnter} onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave} onMouseLeave={handleMouseLeave}
justifyContent="center" justifyContent="center"
shadow="sm"
_hover={{ shadow: 'md' }}
transition="box-shadow 200ms"
borderWidth="1px"
rounded="md"
px="4"
py="2"
borderColor={isOpened ? 'blue.400' : 'gray.300'}
> >
<Editable <Editable
ref={ref} ref={ref}
px="4"
py="2"
rounded="md"
bgColor="green.200"
borderWidth="2px"
borderColor={isOpened ? 'blue.400' : 'gray.400'}
defaultValue={item.content ?? 'Click to edit'} defaultValue={item.content ?? 'Click to edit'}
flex="1" flex="1"
startWithEditView={!isDefined(item.content)} startWithEditView={!isDefined(item.content)}
@ -115,7 +117,10 @@ export const ChoiceItemNode = ({
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove} onMouseMove={handleMouseMove}
> >
<EditablePreview /> <EditablePreview
w="full"
color={item.content !== 'Click to edit' ? 'inherit' : 'gray.500'}
/>
<EditableInput /> <EditableInput />
</Editable> </Editable>
{typebot && isSingleChoiceInput(typebot.steps.byId[item.stepId]) && ( {typebot && isSingleChoiceInput(typebot.steps.byId[item.stepId]) && (
@ -138,6 +143,8 @@ export const ChoiceItemNode = ({
aria-label="Add item" aria-label="Add item"
icon={<PlusIcon />} icon={<PlusIcon />}
size="xs" size="xs"
shadow="md"
colorScheme="blue"
onClick={handlePlusClick} onClick={handlePlusClick}
/> />
</Fade> </Fade>

View File

@ -15,11 +15,12 @@ export const ChoiceItemNodeOverlay = ({
px="4" px="4"
py="2" py="2"
rounded="md" rounded="md"
bgColor="green.200" bgColor="white"
borderWidth="2px" borderWidth="1px"
borderColor={'gray.400'} borderColor={'gray.300'}
w="212px" w="212px"
pointerEvents="none" pointerEvents="none"
shadow="lg"
{...props} {...props}
> >
{item.content ?? 'Click to edit'} {item.content ?? 'Click to edit'}

View File

@ -44,14 +44,17 @@ export const ChoiceItemsList = ({ step }: ChoiceItemsListProps) => {
} }
useEventListener('mousemove', handleStepMove) useEventListener('mousemove', handleStepMove)
const handleMouseUp = (e: React.MouseEvent<HTMLDivElement>) => { const handleMouseUp = (e: MouseEvent) => {
if (expandedPlaceholderIndex === undefined || !draggedChoiceItem) return if (!draggedChoiceItem) return
e.stopPropagation() if (expandedPlaceholderIndex !== -1) {
e.stopPropagation()
createChoiceItem(draggedChoiceItem, expandedPlaceholderIndex)
}
setMouseOverBlockId(undefined) setMouseOverBlockId(undefined)
setExpandedPlaceholderIndex(undefined) setExpandedPlaceholderIndex(undefined)
setDraggedChoiceItem(undefined) setDraggedChoiceItem(undefined)
createChoiceItem(draggedChoiceItem, expandedPlaceholderIndex)
} }
useEventListener('mouseup', handleMouseUp, undefined, { capture: true })
const handleStepMouseDown = ( const handleStepMouseDown = (
{ absolute, relative }: { absolute: Coordinates; relative: Coordinates }, { absolute, relative }: { absolute: Coordinates; relative: Coordinates },
@ -76,15 +79,10 @@ export const ChoiceItemsList = ({ step }: ChoiceItemsListProps) => {
const stopPropagating = (e: React.MouseEvent) => e.stopPropagation() const stopPropagating = (e: React.MouseEvent) => e.stopPropagation()
return ( return (
<Stack <Stack flex={1} spacing={1} onClick={stopPropagating}>
flex={1}
spacing={1}
onMouseUpCapture={handleMouseUp}
onClick={stopPropagating}
>
<Flex <Flex
h={expandedPlaceholderIndex === 0 ? '50px' : '2px'} h={expandedPlaceholderIndex === 0 ? '50px' : '2px'}
bgColor={'gray.400'} bgColor={'gray.300'}
visibility={showSortPlaceholders ? 'visible' : 'hidden'} visibility={showSortPlaceholders ? 'visible' : 'hidden'}
rounded="lg" rounded="lg"
transition={showSortPlaceholders ? 'height 200ms' : 'none'} transition={showSortPlaceholders ? 'height 200ms' : 'none'}
@ -107,7 +105,7 @@ export const ChoiceItemsList = ({ step }: ChoiceItemsListProps) => {
? '50px' ? '50px'
: '2px' : '2px'
} }
bgColor={'gray.400'} bgColor={'gray.300'}
visibility={showSortPlaceholders ? 'visible' : 'hidden'} visibility={showSortPlaceholders ? 'visible' : 'hidden'}
rounded="lg" rounded="lg"
transition={showSortPlaceholders ? 'height 200ms' : 'none'} transition={showSortPlaceholders ? 'height 200ms' : 'none'}
@ -118,13 +116,15 @@ export const ChoiceItemsList = ({ step }: ChoiceItemsListProps) => {
<Flex <Flex
px="4" px="4"
py="2" py="2"
bgColor="gray.200" borderWidth="1px"
borderWidth="2px" borderColor="gray.300"
bgColor="gray.50"
rounded="md" rounded="md"
pos="relative" pos="relative"
align="center" align="center"
cursor="not-allowed"
> >
<Text>Default</Text> <Text color="gray.500">Default</Text>
<SourceEndpoint <SourceEndpoint
source={{ source={{
blockId: step.blockId, blockId: step.blockId,
@ -140,7 +140,6 @@ export const ChoiceItemsList = ({ step }: ChoiceItemsListProps) => {
<Portal> <Portal>
<ChoiceItemNodeOverlay <ChoiceItemNodeOverlay
item={draggedChoiceItem} item={draggedChoiceItem}
onMouseUp={handleMouseUp}
pos="fixed" pos="fixed"
top="0" top="0"
left="0" left="0"

View File

@ -0,0 +1,34 @@
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
ModalFooter,
ModalBodyProps,
} from '@chakra-ui/react'
import React from 'react'
type Props = {
isOpen: boolean
onClose: () => void
}
export const SettingsModal = ({
isOpen,
onClose,
...props
}: Props & ModalBodyProps) => {
return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader />
<ModalCloseButton />
<ModalBody {...props}>{props.children}</ModalBody>
<ModalFooter />
</ModalContent>
</Modal>
)
}

View File

@ -4,7 +4,11 @@ import {
PopoverBody, PopoverBody,
useEventListener, useEventListener,
Portal, Portal,
Stack,
IconButton,
Flex,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import { ExpandIcon } from 'assets/icons'
import { useTypebot } from 'contexts/TypebotContext/TypebotContext' import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
import { import {
InputStep, InputStep,
@ -30,9 +34,10 @@ import { SetVariableSettingsBody } from './bodies/SetVariableSettingsBody'
type Props = { type Props = {
step: Step step: Step
onExpandClick: () => void
} }
export const SettingsPopoverContent = ({ step }: Props) => { export const SettingsPopoverContent = ({ step, onExpandClick }: Props) => {
const ref = useRef<HTMLDivElement | null>(null) const ref = useRef<HTMLDivElement | null>(null)
const handleMouseDown = (e: React.MouseEvent) => e.stopPropagation() const handleMouseDown = (e: React.MouseEvent) => e.stopPropagation()
@ -44,15 +49,33 @@ export const SettingsPopoverContent = ({ step }: Props) => {
<Portal> <Portal>
<PopoverContent onMouseDown={handleMouseDown}> <PopoverContent onMouseDown={handleMouseDown}>
<PopoverArrow /> <PopoverArrow />
<PopoverBody p="6" overflowY="scroll" maxH="400px" ref={ref}> <PopoverBody
<SettingsPopoverBodyContent step={step} /> px="6"
pb="6"
pt="4"
overflowY="scroll"
maxH="400px"
ref={ref}
shadow="lg"
>
<Stack>
<Flex justifyContent="flex-end">
<IconButton
aria-label="expand"
icon={<ExpandIcon />}
size="xs"
onClick={onExpandClick}
/>
</Flex>
<StepSettings step={step} />
</Stack>
</PopoverBody> </PopoverBody>
</PopoverContent> </PopoverContent>
</Portal> </Portal>
) )
} }
const SettingsPopoverBodyContent = ({ step }: Props) => { export const StepSettings = ({ step }: { step: Step }) => {
const { updateStep } = useTypebot() const { updateStep } = useTypebot()
const handleOptionsChange = (options: StepOptions) => const handleOptionsChange = (options: StepOptions) =>
updateStep(step.id, { options } as Partial<InputStep>) updateStep(step.id, { options } as Partial<InputStep>)

View File

@ -107,22 +107,20 @@ export const ComparisonsList = ({
> >
<Stack <Stack
key={comparisonId} key={comparisonId}
bgColor="blue.50"
p="4" p="4"
rounded="md" rounded="md"
flex="1" flex="1"
borderWidth="1px"
> >
<VariableSearchInput <VariableSearchInput
initialVariableId={comparisons.byId[comparisonId].variableId} initialVariableId={comparisons.byId[comparisonId].variableId}
onSelectVariable={handleVariableSelected(comparisonId)} onSelectVariable={handleVariableSelected(comparisonId)}
bgColor="white"
placeholder="Search for a variable" placeholder="Search for a variable"
/> />
<DropdownList<ComparisonOperators> <DropdownList<ComparisonOperators>
currentItem={comparisons.byId[comparisonId].comparisonOperator} currentItem={comparisons.byId[comparisonId].comparisonOperator}
onItemSelect={handleComparisonOperatorSelected(comparisonId)} onItemSelect={handleComparisonOperatorSelected(comparisonId)}
items={Object.values(ComparisonOperators)} items={Object.values(ComparisonOperators)}
bgColor="white"
/> />
{comparisons.byId[comparisonId].comparisonOperator !== {comparisons.byId[comparisonId].comparisonOperator !==
ComparisonOperators.IS_SET && ( ComparisonOperators.IS_SET && (
@ -130,7 +128,6 @@ export const ComparisonsList = ({
delay={100} delay={100}
initialValue={comparisons.byId[comparisonId].value ?? ''} initialValue={comparisons.byId[comparisonId].value ?? ''}
onChange={handleValueChange(comparisonId)} onChange={handleValueChange(comparisonId)}
bgColor="white"
placeholder="Type a value..." placeholder="Type a value..."
/> />
)} )}
@ -141,16 +138,22 @@ export const ComparisonsList = ({
aria-label="Remove comparison" aria-label="Remove comparison"
onClick={deleteComparison(comparisonId)} onClick={deleteComparison(comparisonId)}
pos="absolute" pos="absolute"
left="-10px" left="-15px"
top="-10px" top="-15px"
size="sm" size="sm"
shadow="md"
/> />
</Fade> </Fade>
</Flex> </Flex>
</> </>
))} ))}
<Button leftIcon={<PlusIcon />} onClick={createComparison} flexShrink={0}> <Button
Add leftIcon={<PlusIcon />}
onClick={createComparison}
flexShrink={0}
colorScheme="blue"
>
Add a comparison
</Button> </Button>
</Stack> </Stack>
) )

View File

@ -87,16 +87,22 @@ export const ExtractCellList = ({
aria-label="Remove cell" aria-label="Remove cell"
onClick={deleteCell(cellId)} onClick={deleteCell(cellId)}
pos="absolute" pos="absolute"
left="-10px" left="-15px"
top="-10px" top="-15px"
size="sm" size="sm"
shadow="md"
/> />
</Fade> </Fade>
</Flex> </Flex>
</> </>
))} ))}
<Button leftIcon={<PlusIcon />} onClick={createCell} flexShrink={0}> <Button
Add leftIcon={<PlusIcon />}
onClick={createCell}
flexShrink={0}
colorScheme="blue"
>
Add a value
</Button> </Button>
</Stack> </Stack>
) )
@ -118,12 +124,11 @@ export const CellWithVariableIdStack = ({
onCellChange({ ...cell, variableId: variable.id }) onCellChange({ ...cell, variableId: variable.id })
} }
return ( return (
<Stack bgColor="blue.50" p="4" rounded="md" flex="1"> <Stack p="4" rounded="md" flex="1" borderWidth="1px">
<DropdownList<string> <DropdownList<string>
currentItem={cell.column} currentItem={cell.column}
onItemSelect={handleColumnSelect} onItemSelect={handleColumnSelect}
items={columns} items={columns}
bgColor="white"
placeholder="Select a column" placeholder="Select a column"
/> />
<VariableSearchInput <VariableSearchInput

View File

@ -87,16 +87,22 @@ export const UpdateCellList = ({
aria-label="Remove cell" aria-label="Remove cell"
onClick={deleteCell(cellId)} onClick={deleteCell(cellId)}
pos="absolute" pos="absolute"
left="-10px" left="-15px"
top="-10px" top="-15px"
size="sm" size="sm"
shadow="md"
/> />
</Fade> </Fade>
</Flex> </Flex>
</> </>
))} ))}
<Button leftIcon={<PlusIcon />} onClick={createCell} flexShrink={0}> <Button
Add leftIcon={<PlusIcon />}
onClick={createCell}
flexShrink={0}
colorScheme="blue"
>
Add a value
</Button> </Button>
</Stack> </Stack>
) )
@ -118,12 +124,11 @@ export const CellWithValueStack = ({
onCellChange({ ...cell, value }) onCellChange({ ...cell, value })
} }
return ( return (
<Stack bgColor="blue.50" p="4" rounded="md" flex="1"> <Stack p="4" rounded="md" flex="1" borderWidth="1px">
<DropdownList<string> <DropdownList<string>
currentItem={cell.column} currentItem={cell.column}
onItemSelect={handleColumnSelect} onItemSelect={handleColumnSelect}
items={columns} items={columns}
bgColor="white"
placeholder="Select a column" placeholder="Select a column"
/> />
<InputWithVariable <InputWithVariable

View File

@ -1,4 +1,4 @@
import { Box, BoxProps } from '@chakra-ui/react' import { Box, BoxProps, Flex } from '@chakra-ui/react'
import { ConnectingSourceIds, useGraph } from 'contexts/GraphContext' import { ConnectingSourceIds, useGraph } from 'contexts/GraphContext'
import React, { MouseEvent, useEffect, useRef } from 'react' import React, { MouseEvent, useEffect, useRef } from 'react'
@ -28,14 +28,20 @@ export const SourceEndpoint = ({
}, [ref]) }, [ref])
return ( return (
<Box <Flex
ref={ref} ref={ref}
boxSize="15px" boxSize="18px"
rounded="full" rounded="full"
bgColor="gray.500"
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
cursor="pointer" cursor="copy"
borderWidth="1px"
borderColor="gray.400"
bgColor="white"
justify="center"
align="center"
{...props} {...props}
/> >
<Box bgColor="gray.400" rounded="full" boxSize="7px" />
</Flex>
) )
} }

View File

@ -4,6 +4,7 @@ import {
HStack, HStack,
Popover, Popover,
PopoverTrigger, PopoverTrigger,
useDisclosure,
useEventListener, useEventListener,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import React, { useEffect, useMemo, useState } from 'react' import React, { useEffect, useMemo, useState } from 'react'
@ -28,6 +29,8 @@ import { SourceEndpoint } from './SourceEndpoint'
import { hasDefaultConnector } from 'services/typebots' import { hasDefaultConnector } from 'services/typebots'
import { TargetEndpoint } from './TargetEndpoint' import { TargetEndpoint } from './TargetEndpoint'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { SettingsModal } from './SettingsPopoverContent/SettingsModal'
import { StepSettings } from './SettingsPopoverContent/SettingsPopoverContent'
export const StepNode = ({ export const StepNode = ({
step, step,
@ -54,6 +57,11 @@ export const StepNode = ({
const [isEditing, setIsEditing] = useState<boolean>( const [isEditing, setIsEditing] = useState<boolean>(
isTextBubbleStep(step) && step.content.plainText === '' isTextBubbleStep(step) && step.content.plainText === ''
) )
const {
isOpen: isModalOpen,
onOpen: onModalOpen,
onClose: onModalClose,
} = useDisclosure()
useEffect(() => { useEffect(() => {
setIsConnecting( setIsConnecting(
@ -190,8 +198,8 @@ export const StepNode = ({
flex="1" flex="1"
userSelect="none" userSelect="none"
p="3" p="3"
borderWidth="2px" borderWidth="1px"
borderColor={isConnecting || isOpened ? 'blue.400' : 'gray.400'} borderColor={isConnecting || isOpened ? 'blue.400' : 'gray.300'}
rounded="lg" rounded="lg"
cursor={'pointer'} cursor={'pointer'}
bgColor="white" bgColor="white"
@ -213,7 +221,7 @@ export const StepNode = ({
}} }}
pos="absolute" pos="absolute"
right="15px" right="15px"
top="19px" top="18px"
/> />
)} )}
</HStack> </HStack>
@ -237,7 +245,12 @@ export const StepNode = ({
)} )}
</Flex> </Flex>
</PopoverTrigger> </PopoverTrigger>
{hasPopover(step) && <SettingsPopoverContent step={step} />} {hasPopover(step) && (
<SettingsPopoverContent step={step} onExpandClick={onModalOpen} />
)}
<SettingsModal isOpen={isModalOpen} onClose={onModalClose}>
<StepSettings step={step} />
</SettingsModal>
</Popover> </Popover>
)} )}
</ContextMenu> </ContextMenu>

View File

@ -16,6 +16,7 @@ export const StepNodeOverlay = ({
cursor={'grab'} cursor={'grab'}
w="264px" w="264px"
pointerEvents="none" pointerEvents="none"
shadow="lg"
{...props} {...props}
> >
<StepIcon type={step.type} /> <StepIcon type={step.type} />

View File

@ -102,7 +102,7 @@ export const StepsList = ({
? '50px' ? '50px'
: '2px' : '2px'
} }
bgColor={'gray.400'} bgColor={'gray.300'}
visibility={showSortPlaceholders ? 'visible' : 'hidden'} visibility={showSortPlaceholders ? 'visible' : 'hidden'}
rounded="lg" rounded="lg"
transition={showSortPlaceholders ? 'height 200ms' : 'none'} transition={showSortPlaceholders ? 'height 200ms' : 'none'}
@ -125,7 +125,7 @@ export const StepsList = ({
? '50px' ? '50px'
: '2px' : '2px'
} }
bgColor={'gray.400'} bgColor={'gray.300'}
visibility={showSortPlaceholders ? 'visible' : 'hidden'} visibility={showSortPlaceholders ? 'visible' : 'hidden'}
rounded="lg" rounded="lg"
transition={showSortPlaceholders ? 'height 200ms' : 'none'} transition={showSortPlaceholders ? 'height 200ms' : 'none'}

View File

@ -103,9 +103,9 @@ export const DrawingEdge = () => {
return ( return (
<path <path
d={path} d={path}
stroke="#718096" stroke="#1a5fff"
strokeWidth="2px" strokeWidth="2px"
markerEnd="url(#arrow)" markerEnd="url(#blue-arrow)"
fill="none" fill="none"
/> />
) )

View File

@ -126,7 +126,7 @@ export const Edge = ({
d={path} d={path}
stroke={isPreviewing ? '#1a5fff' : '#718096'} stroke={isPreviewing ? '#1a5fff' : '#718096'}
strokeWidth="2px" strokeWidth="2px"
markerEnd="url(#arrow)" markerEnd={isPreviewing ? 'url(#blue-arrow)' : 'url(#arrow)'}
fill="none" fill="none"
/> />
) )

View File

@ -74,6 +74,21 @@ export const Edges = () => {
fill="#718096" fill="#718096"
/> />
</marker> </marker>
<marker
id={'blue-arrow'}
refX="8"
refY="4"
orient="auto"
viewBox="0 0 20 20"
markerUnits="userSpaceOnUse"
markerWidth="20"
markerHeight="20"
>
<path
d="M7.07138888,5.50174526 L2.43017246,7.82235347 C1.60067988,8.23709976 0.592024983,7.90088146 0.177278692,7.07138888 C0.0606951226,6.83822174 0,6.58111307 0,6.32042429 L0,1.67920787 C0,0.751806973 0.751806973,0 1.67920787,0 C1.93989666,0 2.19700532,0.0606951226 2.43017246,0.177278692 L7,3 C7.82949258,3.41474629 8.23709976,3.92128809 7.82235347,4.75078067 C7.6598671,5.07575341 7.39636161,5.33925889 7.07138888,5.50174526 Z"
fill="#1a5fff"
/>
</marker>
</chakra.svg> </chakra.svg>
) )
} }

View File

@ -6,6 +6,18 @@ const fonts = {
} }
const colors = { const colors = {
gray: {
50: '#F9FAFB',
100: '#F3F4F6',
200: '#E5E7EB',
300: '#D1D5DB',
400: '#9CA3AF',
500: '#6B7280',
600: '#4B5563',
700: '#374151',
800: '#1F2937',
900: '#111827',
},
blue: { blue: {
50: '#e0edff', 50: '#e0edff',
100: '#b0caff', 100: '#b0caff',

View File

@ -291,7 +291,7 @@ export const getEndpointTopOffset = (
const endpointRef = endpoints.byId[id]?.ref const endpointRef = endpoints.byId[id]?.ref
if (!endpointRef) return 0 if (!endpointRef) return 0
return ( return (
7 + 8 +
(endpointRef.current?.getBoundingClientRect().top ?? 0) - (endpointRef.current?.getBoundingClientRect().top ?? 0) -
graphPosition.y - graphPosition.y -
headerHeight headerHeight