Skip to content

Commit

Permalink
✨ Add new Jump block
Browse files Browse the repository at this point in the history
Also improve Select input with a clear button

Closes #186
  • Loading branch information
baptisteArno committed Mar 3, 2023
1 parent f1a9a1c commit 022c5a5
Show file tree
Hide file tree
Showing 32 changed files with 594 additions and 238 deletions.
7 changes: 7 additions & 0 deletions apps/builder/src/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -545,3 +545,10 @@ export const PackageIcon = (props: IconProps) => (
<line x1="12" y1="22.08" x2="12" y2="12"></line>
</Icon>
)

export const CloseIcon = (props: IconProps) => (
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</Icon>
)
46 changes: 40 additions & 6 deletions apps/builder/src/components/inputs/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,24 @@ import {
InputRightElement,
Text,
Box,
IconButton,
HStack,
} from '@chakra-ui/react'
import { useState, useRef, ChangeEvent } from 'react'
import { isDefined } from 'utils'
import { useOutsideClick } from '@/hooks/useOutsideClick'
import { useParentModal } from '@/features/graph/providers/ParentModalProvider'
import { ChevronDownIcon } from '../icons'
import { ChevronDownIcon, CloseIcon } from '../icons'

const dropdownCloseAnimationDuration = 200
const dropdownCloseAnimationDuration = 300

type Item = string | { icon?: JSX.Element; label: string; value: string }

type Props = {
selectedItem?: string
items: Item[]
placeholder?: string
onSelect?: (value: string) => void
onSelect?: (value: string | undefined) => void
}

export const Select = ({
Expand Down Expand Up @@ -119,6 +121,14 @@ export const Select = ({
}
}

const clearSelection = (e: React.MouseEvent) => {
e.preventDefault()
setInputValue('')
onSelect?.(undefined)
setKeyboardFocusIndex(undefined)
closeDropwdown()
}

const resetIsTouched = () => {
setTimeout(() => {
setIsTouched(false)
Expand All @@ -136,7 +146,15 @@ export const Select = ({
>
<PopoverAnchor>
<InputGroup>
<Box pos="absolute" py={2} pl={4} pr={6}>
<Box
pos="absolute"
pb={2}
// We need absolute positioning the overlay match the underlying input
pt="8.5px"
pl="17px"
pr={selectedItem ? 16 : 8}
w="full"
>
{!isTouched && (
<Text noOfLines={1} data-testid="selected-item-label">
{inputValue}
Expand All @@ -156,10 +174,26 @@ export const Select = ({
onChange={handleInputChange}
onFocus={onOpen}
onKeyDown={handleKeyUp}
pr={selectedItem ? 16 : undefined}
/>

<InputRightElement pointerEvents="none" cursor="pointer">
<ChevronDownIcon />
<InputRightElement
width={selectedItem ? '5rem' : undefined}
pointerEvents="none"
>
<HStack>
{selectedItem && (
<IconButton
onClick={clearSelection}
icon={<CloseIcon />}
aria-label={'Clear'}
size="sm"
variant="ghost"
pointerEvents="all"
/>
)}
<ChevronDownIcon />
</HStack>
</InputRightElement>
</InputGroup>
</PopoverAnchor>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ export const GoogleSheetsSettingsBody = ({
)
const handleCredentialsIdChange = (credentialsId?: string) =>
onOptionsChange({ ...omit(options, 'credentialsId'), credentialsId })
const handleSpreadsheetIdChange = (spreadsheetId: string) =>
const handleSpreadsheetIdChange = (spreadsheetId: string | undefined) =>
onOptionsChange({ ...options, spreadsheetId })
const handleSheetIdChange = (sheetId: string) =>
const handleSheetIdChange = (sheetId: string | undefined) =>
onOptionsChange({ ...options, sheetId })

const handleActionChange = (action: GoogleSheetsAction) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type Props = {
sheets: Sheet[]
isLoading: boolean
sheetId?: string
onSelectSheetId: (id: string) => void
onSelectSheetId: (id: string | undefined) => void
}

export const SheetsDropdown = ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useSpreadsheets } from '../../hooks/useSpreadsheets'
type Props = {
credentialsId: string
spreadsheetId?: string
onSelectSpreadsheetId: (id: string) => void
onSelectSpreadsheetId: (id: string | undefined) => void
}

export const SpreadsheetsDropdown = ({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { featherIconsBaseProps } from '@/components/icons'
import { Icon, IconProps } from '@chakra-ui/react'
import React from 'react'

export const JumpIcon = (props: IconProps) => (
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
<polygon points="13 19 22 12 13 5 13 19"></polygon>
<polygon points="2 19 11 12 2 5 2 19"></polygon>
</Icon>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react'
import { Tag, Text } from '@chakra-ui/react'
import { useTypebot } from '@/features/editor'
import { byId, isDefined } from 'utils'
import { JumpBlock } from 'models/features/blocks/logic/jump'

type Props = {
options: JumpBlock['options']
}

export const JumpNodeBody = ({ options }: Props) => {
const { typebot } = useTypebot()
const selectedGroup = typebot?.groups.find(byId(options.groupId))
const blockIndex = selectedGroup?.blocks.findIndex(byId(options.blockId))
if (!selectedGroup) return <Text color="gray.500">Configure...</Text>
return (
<Text>
Jump to <Tag colorScheme="blue">{selectedGroup.title}</Tag>{' '}
{isDefined(blockIndex) && blockIndex >= 0 ? (
<>
at block <Tag colorScheme="blue">{blockIndex + 1}</Tag>
</>
) : null}
</Text>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Select } from '@/components/inputs/Select'
import { useTypebot } from '@/features/editor'
import { Stack } from '@chakra-ui/react'
import { JumpBlock } from 'models/features/blocks/logic/jump'
import React from 'react'
import { byId } from 'utils'

type Props = {
groupId: string
options: JumpBlock['options']
onOptionsChange: (options: JumpBlock['options']) => void
}

export const JumpSettings = ({ groupId, options, onOptionsChange }: Props) => {
const { typebot } = useTypebot()

const handleGroupIdChange = (groupId?: string) =>
onOptionsChange({ ...options, groupId })

const handleBlockIdChange = (blockId?: string) =>
onOptionsChange({ ...options, blockId })

const currentGroupId = typebot?.groups.find(byId(groupId))?.id

const selectedGroup = typebot?.groups.find(byId(options.groupId))

if (!typebot) return null

return (
<Stack spacing={4}>
<Select
items={typebot.groups
.filter((group) => group.id !== currentGroupId)
.map((group) => ({
label: group.title,
value: group.id,
}))}
selectedItem={selectedGroup?.id}
onSelect={handleGroupIdChange}
placeholder="Select a group"
/>
{selectedGroup && selectedGroup.blocks.length > 1 && (
<Select
selectedItem={options.blockId}
items={selectedGroup.blocks.map((block, index) => ({
label: `Block #${(index + 1).toString()}`,
value: block.id,
}))}
onSelect={handleBlockIdChange}
placeholder="Select a block"
/>
)}
</Stack>
)
}
28 changes: 28 additions & 0 deletions apps/builder/src/features/blocks/logic/jump/jump.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import test, { expect } from '@playwright/test'
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
import { createId } from '@paralleldrive/cuid2'
import { getTestAsset } from '@/test/utils/playwright'

test('should work as expected', async ({ page }) => {
const typebotId = createId()
await importTypebotInDatabase(getTestAsset('typebots/logic/jump.json'), {
id: typebotId,
})

await page.goto(`/typebots/${typebotId}/edit`)
await page.getByText('Configure...').click()
await page.getByPlaceholder('Select a group').click()
await expect(page.getByRole('menuitem', { name: 'Group #2' })).toBeHidden()
await page.getByRole('menuitem', { name: 'Group #1' }).click()
await page.getByPlaceholder('Select a block').click()
await page.getByRole('menuitem', { name: 'Block #2' }).click()
await page.getByRole('button', { name: 'Preview' }).click()
await page.getByPlaceholder('Type your answer...').fill('Hi there!')
await page.getByRole('button', { name: 'Send' }).click()
await expect(
page.locator('typebot-standard').getByText('How are you?').nth(1)
).toBeVisible()
await expect(
page.locator('typebot-standard').getByText('Hello this is a test!').nth(1)
).toBeHidden()
})
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Group } from 'models'
type Props = {
groups: Group[]
groupId?: string
onGroupIdSelected: (groupId: string) => void
onGroupIdSelected: (groupId: string | undefined) => void
isLoading?: boolean
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ type Props = {
export const TypebotLinkForm = ({ options, onOptionsChange }: Props) => {
const { linkedTypebots, typebot } = useTypebot()

const handleTypebotIdChange = (typebotId: string | 'current') =>
const handleTypebotIdChange = (typebotId: string | 'current' | undefined) =>
onOptionsChange({ ...options, typebotId })
const handleGroupIdChange = (groupId: string) =>
const handleGroupIdChange = (groupId: string | undefined) =>
onOptionsChange({ ...options, groupId })

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type Props = {
idsToExclude: string[]
typebotId?: string | 'current'
currentWorkspaceId: string
onSelect: (typebotId: string | 'current') => void
onSelect: (typebotId: string | 'current' | undefined) => void
}

export const TypebotsDropdown = ({
Expand Down
Loading

0 comments on commit 022c5a5

Please sign in to comment.