refactor(lp): ♻️ Simplify header
This commit is contained in:
@ -394,3 +394,12 @@ export const CopyIcon = (props: IconProps) => (
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</Icon>
|
||||
)
|
||||
|
||||
export const TemplateIcon = (props: IconProps) => (
|
||||
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||
<rect x="3" y="3" width="7" height="7"></rect>
|
||||
<rect x="14" y="3" width="7" height="7"></rect>
|
||||
<rect x="14" y="14" width="7" height="7"></rect>
|
||||
<rect x="3" y="14" width="7" height="7"></rect>
|
||||
</Icon>
|
||||
)
|
||||
|
119
apps/builder/components/templates/CreateNewTypebotButtons.tsx
Normal file
119
apps/builder/components/templates/CreateNewTypebotButtons.tsx
Normal file
@ -0,0 +1,119 @@
|
||||
import {
|
||||
VStack,
|
||||
Heading,
|
||||
Stack,
|
||||
Tooltip,
|
||||
Button,
|
||||
useDisclosure,
|
||||
useToast,
|
||||
} from '@chakra-ui/react'
|
||||
import { ToolIcon, TemplateIcon, DownloadIcon } from 'assets/icons'
|
||||
import { useUser } from 'contexts/UserContext'
|
||||
import { Typebot } from 'models'
|
||||
import { useRouter } from 'next/router'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { importTypebot, createTypebot } from 'services/typebots'
|
||||
import { ImportTypebotFromFileButton } from './ImportTypebotFromFileButton'
|
||||
import { TemplatesModal } from './TemplatesModal'
|
||||
|
||||
export const CreateNewTypebotButtons = () => {
|
||||
const { user } = useUser()
|
||||
const router = useRouter()
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const [isFromScratchTooltipOpened, setIsFromScratchTooltipOpened] =
|
||||
useState(false)
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const toast = useToast({
|
||||
position: 'top-right',
|
||||
status: 'error',
|
||||
title: 'An error occured',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return
|
||||
const isFirstBot = router.query.isFirstBot as string | undefined
|
||||
if (isFirstBot) setIsFromScratchTooltipOpened(true)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [router.isReady])
|
||||
|
||||
const handleCreateSubmit = async (typebot?: Typebot) => {
|
||||
if (!user) return
|
||||
setIsLoading(true)
|
||||
const folderId = router.query.folderId?.toString() ?? null
|
||||
const { error, data } = typebot
|
||||
? await importTypebot(
|
||||
{
|
||||
...typebot,
|
||||
ownerId: user.id,
|
||||
folderId,
|
||||
theme: {
|
||||
...typebot.theme,
|
||||
chat: {
|
||||
...typebot.theme.chat,
|
||||
hostAvatar: { isEnabled: true, url: user.image ?? undefined },
|
||||
},
|
||||
},
|
||||
},
|
||||
user
|
||||
)
|
||||
: await createTypebot({
|
||||
folderId,
|
||||
})
|
||||
if (error) toast({ description: error.message })
|
||||
if (data) router.push(`/typebots/${data.id}/edit`)
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<VStack maxW="600px" w="full" flex="1" pt="20" spacing={10}>
|
||||
<Heading>Create a new typebot</Heading>
|
||||
<Stack w="full" spacing={6}>
|
||||
<Tooltip
|
||||
isOpen={isFromScratchTooltipOpened}
|
||||
label="Strongly suggested if you're new to Typebot."
|
||||
maxW="200px"
|
||||
hasArrow
|
||||
placement="right"
|
||||
rounded="md"
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
w="full"
|
||||
py="8"
|
||||
fontSize="lg"
|
||||
leftIcon={<ToolIcon color="blue.500" boxSize="25px" mr="2" />}
|
||||
onClick={() => handleCreateSubmit()}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Start from scratch
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button
|
||||
variant="outline"
|
||||
w="full"
|
||||
py="8"
|
||||
fontSize="lg"
|
||||
leftIcon={<TemplateIcon color="orange.500" boxSize="25px" mr="2" />}
|
||||
onClick={onOpen}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Start from a template
|
||||
</Button>
|
||||
<ImportTypebotFromFileButton
|
||||
variant="outline"
|
||||
w="full"
|
||||
py="8"
|
||||
fontSize="lg"
|
||||
leftIcon={<DownloadIcon color="purple.500" boxSize="25px" mr="2" />}
|
||||
isLoading={isLoading}
|
||||
onNewTypebot={handleCreateSubmit}
|
||||
>
|
||||
Import a file
|
||||
</ImportTypebotFromFileButton>
|
||||
</Stack>
|
||||
<TemplatesModal isOpen={isOpen} onClose={onClose} />
|
||||
</VStack>
|
||||
)
|
||||
}
|
@ -1,15 +1,16 @@
|
||||
import { chakra, MenuItem, MenuItemProps, useToast } from '@chakra-ui/react'
|
||||
import { FileIcon } from 'assets/icons'
|
||||
import { MoreButton } from 'components/dashboard/FolderContent/MoreButton'
|
||||
import { Button, ButtonProps, chakra, useToast } from '@chakra-ui/react'
|
||||
import { Typebot } from 'models'
|
||||
import React, { ChangeEvent } from 'react'
|
||||
import { readFile } from 'services/utils'
|
||||
|
||||
type Props = {
|
||||
onNewTypebot: (typebot: Typebot) => void
|
||||
} & MenuItemProps
|
||||
} & ButtonProps
|
||||
|
||||
export const CreateTypebotMoreButton = ({ onNewTypebot }: Props) => {
|
||||
export const ImportTypebotFromFileButton = ({
|
||||
onNewTypebot,
|
||||
...props
|
||||
}: Props) => {
|
||||
const toast = useToast({
|
||||
position: 'top-right',
|
||||
status: 'error',
|
||||
@ -36,16 +37,9 @@ export const CreateTypebotMoreButton = ({ onNewTypebot }: Props) => {
|
||||
onChange={handleInputChange}
|
||||
accept=".json"
|
||||
/>
|
||||
<MoreButton>
|
||||
<MenuItem
|
||||
as="label"
|
||||
cursor="pointer"
|
||||
icon={<FileIcon />}
|
||||
htmlFor="file-input"
|
||||
>
|
||||
Import from file
|
||||
</MenuItem>
|
||||
</MoreButton>
|
||||
<Button as="label" htmlFor="file-input" cursor="pointer" {...props}>
|
||||
{props.children}
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
@ -9,10 +9,10 @@ import {
|
||||
Button,
|
||||
} from '@chakra-ui/react'
|
||||
import { TypebotViewer } from 'bot-engine'
|
||||
import { TemplateProps } from 'layouts/dashboard/TemplatesContent'
|
||||
import { Typebot } from 'models'
|
||||
import React from 'react'
|
||||
import { parseTypebotToPublicTypebot } from 'services/publicTypebot'
|
||||
import { TemplateProps } from './data'
|
||||
|
||||
type Props = {
|
||||
template: TemplateProps
|
||||
|
@ -8,10 +8,10 @@ import {
|
||||
useDisclosure,
|
||||
} from '@chakra-ui/react'
|
||||
import { EyeIcon } from 'assets/icons'
|
||||
import { TemplateProps } from 'layouts/dashboard/TemplatesContent'
|
||||
import { Typebot } from 'models'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { sendRequest } from 'utils'
|
||||
import { TemplateProps } from './data'
|
||||
import { PreviewModal } from './PreviewModal'
|
||||
|
||||
type Props = {
|
||||
|
62
apps/builder/components/templates/TemplatesModal.tsx
Normal file
62
apps/builder/components/templates/TemplatesModal.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import {
|
||||
Button,
|
||||
Heading,
|
||||
HStack,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
Stack,
|
||||
useToast,
|
||||
} from '@chakra-ui/react'
|
||||
import { TypebotViewer } from 'bot-engine'
|
||||
import { Typebot } from 'models'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { parseTypebotToPublicTypebot } from 'services/publicTypebot'
|
||||
import { sendRequest } from 'utils'
|
||||
import { TemplateProps, templates } from './data'
|
||||
|
||||
type Props = { isOpen: boolean; onClose: () => void }
|
||||
|
||||
export const TemplatesModal = ({ isOpen, onClose }: Props) => {
|
||||
const [typebot, setTypebot] = useState<Typebot>()
|
||||
|
||||
const toast = useToast({
|
||||
position: 'top-right',
|
||||
status: 'error',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
fetchTemplate(templates[0])
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const fetchTemplate = async (template: TemplateProps) => {
|
||||
const { data, error } = await sendRequest(`/templates/${template.fileName}`)
|
||||
if (error) return toast({ title: error.name, description: error.message })
|
||||
setTypebot(data as Typebot)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} size="6xl">
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader as={HStack} justifyContent="space-between" w="full">
|
||||
<Heading fontSize="xl">Lead generation</Heading>
|
||||
</ModalHeader>
|
||||
<ModalBody as={HStack}>
|
||||
{typebot && (
|
||||
<TypebotViewer typebot={parseTypebotToPublicTypebot(typebot)} />
|
||||
)}
|
||||
<Stack>
|
||||
<Button colorScheme="blue">Use this template</Button>
|
||||
</Stack>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
6
apps/builder/components/templates/data.ts
Normal file
6
apps/builder/components/templates/data.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export type TemplateProps = { name: string; emoji: string; fileName: string }
|
||||
|
||||
export const templates: TemplateProps[] = [
|
||||
{ name: 'Lead Generation', emoji: '🤝', fileName: 'lead-gen.json' },
|
||||
{ name: 'Customer Support', emoji: '😍', fileName: 'customer-support.json' },
|
||||
]
|
@ -1,110 +0,0 @@
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Flex,
|
||||
SimpleGrid,
|
||||
Text,
|
||||
Stack,
|
||||
useToast,
|
||||
Tooltip,
|
||||
} from '@chakra-ui/react'
|
||||
import { CreateTypebotMoreButton } from 'components/templates/ImportFileMenuItem'
|
||||
import { TemplateButton } from 'components/templates/TemplateButton'
|
||||
import { useUser } from 'contexts/UserContext'
|
||||
import { Typebot } from 'models'
|
||||
import { useRouter } from 'next/router'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { createTypebot, importTypebot } from 'services/typebots/typebots'
|
||||
|
||||
export type TemplateProps = { name: string; emoji: string; fileName: string }
|
||||
const templates: TemplateProps[] = [
|
||||
{ name: 'Lead Generation', emoji: '🤝', fileName: 'lead-gen.json' },
|
||||
{ name: 'Customer Support', emoji: '😍', fileName: 'customer-support.json' },
|
||||
]
|
||||
export const TemplatesContent = () => {
|
||||
const { user } = useUser()
|
||||
const router = useRouter()
|
||||
const [isFromScratchTooltipOpened, setIsFromScratchTooltipOpened] =
|
||||
useState(false)
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const toast = useToast({
|
||||
position: 'top-right',
|
||||
status: 'error',
|
||||
title: 'An error occured',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return
|
||||
const isFirstBot = router.query.isFirstBot as string | undefined
|
||||
if (isFirstBot) setIsFromScratchTooltipOpened(true)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [router.isReady])
|
||||
|
||||
const handleCreateSubmit = async (typebot?: Typebot) => {
|
||||
if (!user) return
|
||||
setIsLoading(true)
|
||||
const folderId = router.query.folderId?.toString() ?? null
|
||||
const { error, data } = typebot
|
||||
? await importTypebot(
|
||||
{
|
||||
...typebot,
|
||||
ownerId: user.id,
|
||||
folderId,
|
||||
theme: {
|
||||
...typebot.theme,
|
||||
chat: {
|
||||
...typebot.theme.chat,
|
||||
hostAvatar: { isEnabled: true, url: user.image ?? undefined },
|
||||
},
|
||||
},
|
||||
},
|
||||
user
|
||||
)
|
||||
: await createTypebot({
|
||||
folderId,
|
||||
})
|
||||
if (error) toast({ description: error.message })
|
||||
if (data) router.push(`/typebots/${data.id}/edit`)
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex w="full" justifyContent="center">
|
||||
<Stack maxW="1000px" flex="1" pt="6" spacing={4}>
|
||||
<Flex justifyContent="space-between">
|
||||
<Tooltip
|
||||
isOpen={isFromScratchTooltipOpened}
|
||||
label="Strongly suggested if you're new to Typebot."
|
||||
maxW="200px"
|
||||
hasArrow
|
||||
placement="right"
|
||||
rounded="md"
|
||||
>
|
||||
<Button
|
||||
onClick={() => handleCreateSubmit()}
|
||||
isLoading={isLoading}
|
||||
colorScheme="blue"
|
||||
>
|
||||
Start from scratch
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<CreateTypebotMoreButton onNewTypebot={handleCreateSubmit} />
|
||||
</Flex>
|
||||
<Divider />
|
||||
<Text>Or start from a template</Text>
|
||||
<SimpleGrid columns={4} spacing={4}>
|
||||
{templates.map((template) => (
|
||||
<TemplateButton
|
||||
key={template.name}
|
||||
onClick={handleCreateSubmit}
|
||||
template={template}
|
||||
/>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
</Flex>
|
||||
)
|
||||
}
|
@ -1,15 +1,15 @@
|
||||
import React from 'react'
|
||||
import { Stack } from '@chakra-ui/react'
|
||||
import { VStack } from '@chakra-ui/react'
|
||||
import { Seo } from 'components/Seo'
|
||||
import { DashboardHeader } from 'components/dashboard/DashboardHeader'
|
||||
import { TemplatesContent } from 'layouts/dashboard/TemplatesContent'
|
||||
import { CreateNewTypebotButtons } from 'components/templates/CreateNewTypebotButtons'
|
||||
|
||||
const TemplatesPage = () => (
|
||||
<Stack>
|
||||
<VStack>
|
||||
<Seo title="Templates" />
|
||||
<DashboardHeader />
|
||||
<TemplatesContent />
|
||||
</Stack>
|
||||
<CreateNewTypebotButtons />
|
||||
</VStack>
|
||||
)
|
||||
|
||||
export default TemplatesPage
|
||||
|
@ -1,13 +1,15 @@
|
||||
import React, { SVGProps } from 'react'
|
||||
import { Icon, IconProps } from '@chakra-ui/react'
|
||||
import React from 'react'
|
||||
import { featherIconsBaseProps } from './HamburgerIcon'
|
||||
|
||||
export const CloseIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
export const CloseIcon = (props: IconProps) => (
|
||||
<Icon
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
fill="#CBD5E0"
|
||||
viewBox="0 0 24 24"
|
||||
{...featherIconsBaseProps}
|
||||
{...props}
|
||||
>
|
||||
<title>Close Circle</title>
|
||||
<path d="M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208 208-93.31 208-208S370.69 48 256 48zm75.31 260.69a16 16 0 11-22.62 22.62L256 278.63l-52.69 52.68a16 16 0 01-22.62-22.62L233.37 256l-52.68-52.69a16 16 0 0122.62-22.62L256 233.37l52.69-52.68a16 16 0 0122.62 22.62L278.63 256z" />
|
||||
</svg>
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</Icon>
|
||||
)
|
||||
|
24
apps/landing-page/assets/icons/HamburgerIcon.tsx
Normal file
24
apps/landing-page/assets/icons/HamburgerIcon.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import Icon, { IconProps } from '@chakra-ui/icon'
|
||||
import React from 'react'
|
||||
|
||||
export const featherIconsBaseProps: IconProps = {
|
||||
fill: 'none',
|
||||
stroke: 'currentColor',
|
||||
strokeWidth: '2px',
|
||||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round',
|
||||
}
|
||||
|
||||
export const HamburgerIcon = (props: IconProps) => (
|
||||
<Icon
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentcolor"
|
||||
{...featherIconsBaseProps}
|
||||
{...props}
|
||||
>
|
||||
<line x1="3" y1="12" x2="21" y2="12"></line>
|
||||
<line x1="3" y1="6" x2="21" y2="6"></line>
|
||||
<line x1="3" y1="18" x2="21" y2="18"></line>
|
||||
</Icon>
|
||||
)
|
@ -1 +1,2 @@
|
||||
export { GitHubIcon } from './GithubLogo'
|
||||
export * from './HamburgerIcon'
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
} from '@chakra-ui/react'
|
||||
import { NextChakraLink } from 'components/common/nextChakraAdapters/NextChakraLink'
|
||||
import * as React from 'react'
|
||||
import { Navbar } from '../../common/Navbar/Navbar'
|
||||
import { Header } from '../../common/Header/Header'
|
||||
import { BackgroundPolygons } from './BackgroundPolygons'
|
||||
import * as Logos from './Brands'
|
||||
import Image from 'next/image'
|
||||
@ -19,7 +19,7 @@ import builderScreenshotSrc from 'public/images/homepage/builder.png'
|
||||
export const Hero = () => {
|
||||
return (
|
||||
<Box as="section" overflow="hidden">
|
||||
<Navbar />
|
||||
<Header />
|
||||
<Stack mx="auto" py="10" pos="relative" pb="32" px={[4, 0]}>
|
||||
<BackgroundPolygons />
|
||||
<VStack mb="20" spacing={20} alignItems="center">
|
||||
|
109
apps/landing-page/components/common/Header/Header.tsx
Executable file
109
apps/landing-page/components/common/Header/Header.tsx
Executable file
@ -0,0 +1,109 @@
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
Heading,
|
||||
HStack,
|
||||
IconButton,
|
||||
useColorModeValue as mode,
|
||||
useDisclosure,
|
||||
Box,
|
||||
} from '@chakra-ui/react'
|
||||
import { HamburgerIcon } from 'assets/icons'
|
||||
import { ChevronDownIcon } from 'assets/icons/ChevronDownIcon'
|
||||
import { CloseIcon } from 'assets/icons/CloseIcon'
|
||||
import { Logo } from 'assets/icons/Logo'
|
||||
import * as React from 'react'
|
||||
import { NextChakraLink } from '../nextChakraAdapters/NextChakraLink'
|
||||
import { MobileMenu } from './MobileMenu'
|
||||
import { ResourcesMenu } from './ResourcesMenu'
|
||||
|
||||
export const Header = () => {
|
||||
const { isOpen, onToggle } = useDisclosure()
|
||||
const { isOpen: isMobileMenuOpen, onToggle: onMobileMenuToggle } =
|
||||
useDisclosure()
|
||||
|
||||
return (
|
||||
<Flex pos="relative" zIndex={10} w="full">
|
||||
<HStack
|
||||
as="header"
|
||||
aria-label="Main navigation"
|
||||
maxW="7xl"
|
||||
w="full"
|
||||
mx="auto"
|
||||
px={{ base: '6', md: '8' }}
|
||||
py="4"
|
||||
justify="space-between"
|
||||
>
|
||||
<Flex
|
||||
align="center"
|
||||
justify="space-between"
|
||||
className="nav-content__mobile"
|
||||
color={mode('white', 'white')}
|
||||
>
|
||||
<HStack as={NextChakraLink} href="/" rel="home" ml="2">
|
||||
<Logo boxSize="35px" />
|
||||
<Heading as="p" fontSize="lg">
|
||||
Typebot
|
||||
</Heading>
|
||||
</HStack>
|
||||
</Flex>
|
||||
<Box display={['block', 'block', 'none']}>
|
||||
<IconButton
|
||||
aria-label={'Open menu'}
|
||||
icon={
|
||||
isMobileMenuOpen ? (
|
||||
<CloseIcon boxSize="20px" />
|
||||
) : (
|
||||
<HamburgerIcon boxSize="20px" />
|
||||
)
|
||||
}
|
||||
variant="ghost"
|
||||
colorScheme="gray"
|
||||
onClick={onMobileMenuToggle}
|
||||
/>
|
||||
<MobileMenu isOpen={isMobileMenuOpen} />
|
||||
</Box>
|
||||
<HStack as="nav" spacing={4} display={['none', 'none', 'flex']}>
|
||||
<Flex>
|
||||
<Button
|
||||
rightIcon={<ChevronDownIcon />}
|
||||
onClick={onToggle}
|
||||
variant="ghost"
|
||||
colorScheme="gray"
|
||||
fontWeight={700}
|
||||
>
|
||||
Resources
|
||||
</Button>
|
||||
<ResourcesMenu isOpen={isOpen} />
|
||||
</Flex>
|
||||
<Button
|
||||
as={NextChakraLink}
|
||||
href="/pricing"
|
||||
variant="ghost"
|
||||
colorScheme="gray"
|
||||
fontWeight={700}
|
||||
>
|
||||
Pricing
|
||||
</Button>
|
||||
<Button
|
||||
as={NextChakraLink}
|
||||
href="https://app.typebot.io/signin"
|
||||
colorScheme="blue"
|
||||
variant="outline"
|
||||
fontWeight={700}
|
||||
>
|
||||
Sign in
|
||||
</Button>
|
||||
<Button
|
||||
as={NextChakraLink}
|
||||
href="https://app.typebot.io/register"
|
||||
colorScheme="orange"
|
||||
fontWeight={700}
|
||||
>
|
||||
Create a typebot
|
||||
</Button>
|
||||
</HStack>
|
||||
</HStack>
|
||||
</Flex>
|
||||
)
|
||||
}
|
59
apps/landing-page/components/common/Header/MobileMenu.tsx
Normal file
59
apps/landing-page/components/common/Header/MobileMenu.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
import { Collapse, Stack, Button, Text } from '@chakra-ui/react'
|
||||
import { NextChakraLink } from '../nextChakraAdapters/NextChakraLink'
|
||||
import { links } from './_data'
|
||||
|
||||
type Props = { isOpen: boolean }
|
||||
|
||||
export const MobileMenu = ({ isOpen }: Props) => (
|
||||
<Collapse in={isOpen}>
|
||||
<Stack
|
||||
pos="absolute"
|
||||
insetX={0}
|
||||
bgGradient="linear(to-b, gray.900, gray.800)"
|
||||
px="6"
|
||||
py="10"
|
||||
spacing={4}
|
||||
>
|
||||
<Button
|
||||
as={NextChakraLink}
|
||||
href="https://app.typebot.io/signin"
|
||||
colorScheme="blue"
|
||||
variant="outline"
|
||||
fontWeight={700}
|
||||
>
|
||||
Sign in
|
||||
</Button>
|
||||
<Button
|
||||
as={NextChakraLink}
|
||||
href="https://app.typebot.io/register"
|
||||
colorScheme="orange"
|
||||
fontWeight={700}
|
||||
>
|
||||
Create a typebot
|
||||
</Button>
|
||||
<Button
|
||||
as={NextChakraLink}
|
||||
href="/pricing"
|
||||
variant="outline"
|
||||
colorScheme="gray"
|
||||
fontWeight={700}
|
||||
>
|
||||
Pricing
|
||||
</Button>
|
||||
<Text fontWeight="700">Resources:</Text>
|
||||
{links[0].children?.map((link, idx) => (
|
||||
<Button
|
||||
as={NextChakraLink}
|
||||
href={link.href}
|
||||
key={idx}
|
||||
variant="outline"
|
||||
colorScheme="gray"
|
||||
fontWeight={700}
|
||||
py="6"
|
||||
>
|
||||
{link.label}
|
||||
</Button>
|
||||
))}
|
||||
</Stack>
|
||||
</Collapse>
|
||||
)
|
89
apps/landing-page/components/common/Header/ResourcesMenu.tsx
Executable file
89
apps/landing-page/components/common/Header/ResourcesMenu.tsx
Executable file
@ -0,0 +1,89 @@
|
||||
import {
|
||||
Box,
|
||||
Center,
|
||||
Collapse,
|
||||
HStack,
|
||||
SimpleGrid,
|
||||
Text,
|
||||
useColorModeValue as mode,
|
||||
} from '@chakra-ui/react'
|
||||
import { ChevronRightIcon } from 'assets/icons/ChevronRightIcon'
|
||||
import * as React from 'react'
|
||||
import { NextChakraLink } from '../nextChakraAdapters/NextChakraLink'
|
||||
import { links } from './_data'
|
||||
|
||||
type Props = { isOpen: boolean }
|
||||
|
||||
export const ResourcesMenu = ({ isOpen }: Props) => (
|
||||
<Collapse in={isOpen} animateOpacity unmountOnExit={false}>
|
||||
<Box
|
||||
w="full"
|
||||
shadow="lg"
|
||||
pos="absolute"
|
||||
insetX={0}
|
||||
top="16"
|
||||
py="12"
|
||||
px="4"
|
||||
bgGradient="linear(to-b, gray.900, gray.800)"
|
||||
>
|
||||
<Box maxW="7xl" mx="auto" px="8">
|
||||
<SimpleGrid spacing="10" columns={2}>
|
||||
{links[0].children?.map((item, idx) => (
|
||||
<NextChakraLink
|
||||
key={idx}
|
||||
className="group"
|
||||
href={item.href}
|
||||
m="-3"
|
||||
p="3"
|
||||
display="flex"
|
||||
alignItems="flex-start"
|
||||
transition="all 0.2s"
|
||||
rounded="lg"
|
||||
_hover={{ bg: mode('gray.50', 'gray.600') }}
|
||||
_focus={{ shadow: 'outline' }}
|
||||
isExternal={
|
||||
item.href.startsWith('https') &&
|
||||
!item.href.includes('app.typebot.io')
|
||||
}
|
||||
>
|
||||
<Center
|
||||
aria-hidden
|
||||
as="span"
|
||||
flexShrink={0}
|
||||
w="10"
|
||||
h="10"
|
||||
fontSize="3xl"
|
||||
color={'blue.300'}
|
||||
>
|
||||
{item.icon}
|
||||
</Center>
|
||||
<Box marginStart="3" as="dl">
|
||||
<HStack as="dt">
|
||||
<Text
|
||||
fontWeight="semibold"
|
||||
color={mode('gray.900', 'white')}
|
||||
_groupHover={{ color: mode('blue.600', 'inherit') }}
|
||||
>
|
||||
{item.label}
|
||||
</Text>
|
||||
<Box
|
||||
fontSize="xs"
|
||||
as={ChevronRightIcon}
|
||||
transition="all 0.2s"
|
||||
_groupHover={{
|
||||
color: mode('blue.600', 'inherit'),
|
||||
transform: 'translateX(2px)',
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
<Text as="dd" color={mode('gray.500', 'gray.400')}>
|
||||
{item.description}
|
||||
</Text>
|
||||
</Box>
|
||||
</NextChakraLink>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
</Box>
|
||||
</Collapse>
|
||||
)
|
@ -4,17 +4,6 @@ import { MapIcon } from 'assets/icons/MapIcon'
|
||||
import { PeopleCircleIcon } from 'assets/icons/PeopleCircleIcon'
|
||||
import * as React from 'react'
|
||||
|
||||
export interface Link {
|
||||
label: string
|
||||
href?: string
|
||||
children?: Array<{
|
||||
label: string
|
||||
description?: string
|
||||
href: string
|
||||
icon?: React.ReactElement
|
||||
}>
|
||||
}
|
||||
|
||||
export const links = [
|
||||
{
|
||||
label: 'Resources',
|
@ -1,5 +0,0 @@
|
||||
import { Box, BoxProps } from '@chakra-ui/react'
|
||||
import { HTMLMotionProps, motion } from 'framer-motion'
|
||||
|
||||
export type MotionBoxProps = BoxProps & HTMLMotionProps<'div'>
|
||||
export const MotionBox = motion<BoxProps>(Box)
|
@ -1,143 +0,0 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
FlexProps,
|
||||
HStack,
|
||||
useDisclosure,
|
||||
useColorModeValue as mode,
|
||||
Heading,
|
||||
} from '@chakra-ui/react'
|
||||
import * as React from 'react'
|
||||
import { Logo } from 'assets/icons/Logo'
|
||||
import { NextChakraLink } from '../nextChakraAdapters/NextChakraLink'
|
||||
import { NavLink } from './NavLink'
|
||||
import { NavMenu } from './NavMenu'
|
||||
import { Submenu } from './Submenu'
|
||||
import { ToggleButton } from './ToggleButton'
|
||||
import { Link } from './_data'
|
||||
|
||||
const MobileNavContext = ({
|
||||
links,
|
||||
...props
|
||||
}: { links: Link[] } & FlexProps) => {
|
||||
const { isOpen, onToggle } = useDisclosure()
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
align="center"
|
||||
justify="space-between"
|
||||
className="nav-content__mobile"
|
||||
color={mode('white', 'white')}
|
||||
{...props}
|
||||
>
|
||||
<HStack as={NextChakraLink} href="/" rel="home" ml="2">
|
||||
<Logo boxSize="35px" />
|
||||
<Heading as="p" fontSize="lg">
|
||||
Typebot
|
||||
</Heading>
|
||||
</HStack>
|
||||
<Box>
|
||||
<ToggleButton isOpen={isOpen} onClick={onToggle} />
|
||||
</Box>
|
||||
</Flex>
|
||||
<NavMenu animate={isOpen ? 'open' : 'closed'}>
|
||||
{links.map((link, idx) =>
|
||||
link.children ? (
|
||||
<Submenu.Mobile key={idx} link={link} />
|
||||
) : (
|
||||
<NavLink.Mobile key={idx} href={link.href ?? '#'}>
|
||||
{link.label}
|
||||
</NavLink.Mobile>
|
||||
)
|
||||
)}
|
||||
<Button
|
||||
as={NextChakraLink}
|
||||
href="https://app.typebot.io/signin"
|
||||
colorScheme="blue"
|
||||
variant="outline"
|
||||
w="full"
|
||||
size="lg"
|
||||
mt="5"
|
||||
>
|
||||
Sign in
|
||||
</Button>
|
||||
<Button
|
||||
as={NextChakraLink}
|
||||
href="https://app.typebot.io/register"
|
||||
colorScheme="orange"
|
||||
w="full"
|
||||
size="lg"
|
||||
mt="5"
|
||||
>
|
||||
Create a typebot for free
|
||||
</Button>
|
||||
</NavMenu>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const DesktopNavContent = ({
|
||||
links,
|
||||
...props
|
||||
}: { links: Link[] } & FlexProps) => {
|
||||
return (
|
||||
<Flex
|
||||
className="nav-content__desktop"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
{...props}
|
||||
color={mode('bg.gray800', 'white')}
|
||||
>
|
||||
<HStack as={NextChakraLink} href="/" rel="home">
|
||||
<Logo boxSize="35px" />
|
||||
<Heading as="p" fontSize="lg">
|
||||
Typebot
|
||||
</Heading>
|
||||
</HStack>
|
||||
|
||||
<HStack spacing="4" minW="240px" justify="space-between">
|
||||
<HStack
|
||||
as="ul"
|
||||
id="nav__primary-menu"
|
||||
aria-label="Main Menu"
|
||||
listStyleType="none"
|
||||
>
|
||||
{links.map((link, idx) => (
|
||||
<Box as="li" key={idx} id={`nav__menuitem-${idx}`}>
|
||||
{link.children ? (
|
||||
<Submenu.Desktop link={link} />
|
||||
) : (
|
||||
<NavLink.Desktop href={link.href ?? '#'}>
|
||||
{link.label}
|
||||
</NavLink.Desktop>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</HStack>
|
||||
<Button
|
||||
as={NextChakraLink}
|
||||
href="https://app.typebot.io/signin"
|
||||
colorScheme="blue"
|
||||
variant="outline"
|
||||
fontWeight="bold"
|
||||
>
|
||||
Sign in
|
||||
</Button>
|
||||
<Button
|
||||
as={NextChakraLink}
|
||||
href="https://app.typebot.io/register"
|
||||
colorScheme="orange"
|
||||
fontWeight="bold"
|
||||
>
|
||||
Create a typebot
|
||||
</Button>
|
||||
</HStack>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export const NavContent = {
|
||||
Mobile: MobileNavContext,
|
||||
Desktop: DesktopNavContent,
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
import { LinkProps as ChakraLinkProps, Button } from '@chakra-ui/react'
|
||||
import { LinkProps as NextLinkProps } from 'next/link'
|
||||
import * as React from 'react'
|
||||
import { NextChakraLink } from '../nextChakraAdapters/NextChakraLink'
|
||||
|
||||
type NavLinkProps = NextLinkProps &
|
||||
Omit<ChakraLinkProps, 'as'> & {
|
||||
active?: boolean
|
||||
}
|
||||
|
||||
const DesktopNavLink = (props: NavLinkProps) => {
|
||||
const { href, children } = props
|
||||
return (
|
||||
<Button
|
||||
as={NextChakraLink}
|
||||
href={href}
|
||||
isExternal={href.startsWith('https') && !href.includes('app.typebot.io')}
|
||||
variant="ghost"
|
||||
colorScheme="gray"
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
DesktopNavLink.displayName = 'DesktopNavLink'
|
||||
|
||||
export const MobileNavLink = (props: NavLinkProps) => {
|
||||
const { href, children } = props
|
||||
return (
|
||||
<Button
|
||||
as={NextChakraLink}
|
||||
href={href ?? '#'}
|
||||
isExternal={href.startsWith('https') && !href.includes('app.typebot.io')}
|
||||
variant="ghost"
|
||||
colorScheme="gray"
|
||||
w="full"
|
||||
h="3rem"
|
||||
justifyContent="flex-start"
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export const NavLink = {
|
||||
Mobile: MobileNavLink,
|
||||
Desktop: DesktopNavLink,
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
import { Variants } from 'framer-motion'
|
||||
import * as React from 'react'
|
||||
import { MotionBox, MotionBoxProps } from './MotionBox'
|
||||
|
||||
export const NavMenu = (props: MotionBoxProps) => (
|
||||
<MotionBox
|
||||
initial="init"
|
||||
variants={variants}
|
||||
outline="0"
|
||||
opacity="0"
|
||||
bgGradient="linear(to-b, gray.900, gray.800)"
|
||||
w="full"
|
||||
shadow="lg"
|
||||
px="4"
|
||||
pos="absolute"
|
||||
insetX="0"
|
||||
py="12"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
const variants: Variants = {
|
||||
init: {
|
||||
opacity: 0,
|
||||
y: -4,
|
||||
display: 'none',
|
||||
transition: { duration: 0 },
|
||||
},
|
||||
open: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
display: 'block',
|
||||
transition: { duration: 0.15 },
|
||||
},
|
||||
closed: {
|
||||
opacity: 0,
|
||||
y: -4,
|
||||
transition: { duration: 0.1 },
|
||||
transitionEnd: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
import { Box } from '@chakra-ui/react'
|
||||
import * as React from 'react'
|
||||
import { NavContent } from './NavContent'
|
||||
import { links } from './_data'
|
||||
|
||||
export const Navbar = () => {
|
||||
return (
|
||||
<Box w="full">
|
||||
<Box as="header" position="relative" zIndex="10">
|
||||
<Box
|
||||
as="nav"
|
||||
aria-label="Main navigation"
|
||||
maxW="7xl"
|
||||
mx="auto"
|
||||
px={{ base: '6', md: '8' }}
|
||||
py="4"
|
||||
>
|
||||
<NavContent.Mobile
|
||||
display={{ base: 'flex', lg: 'none' }}
|
||||
links={links}
|
||||
/>
|
||||
<NavContent.Desktop
|
||||
display={{ base: 'none', lg: 'flex' }}
|
||||
links={links}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
import { useNavMenu } from './useNavMenu'
|
||||
import {
|
||||
Box,
|
||||
Collapse,
|
||||
SimpleGrid,
|
||||
useDisclosure,
|
||||
Button,
|
||||
} from '@chakra-ui/react'
|
||||
import * as React from 'react'
|
||||
import { Link } from './_data'
|
||||
import { NavLink } from './NavLink'
|
||||
import { NavMenu } from './NavMenu'
|
||||
import { SubmenuItem as DesktopMenuItem } from './SubmenuItem'
|
||||
import { ChevronDownIcon } from '../../../assets/icons/ChevronDownIcon'
|
||||
|
||||
interface SubmenuProps {
|
||||
link: Link
|
||||
}
|
||||
|
||||
const DesktopSubmenu = (props: SubmenuProps) => {
|
||||
const { link } = props
|
||||
const { isOpen, getMenuProps, getTriggerProps } = useNavMenu()
|
||||
return (
|
||||
<>
|
||||
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
|
||||
{/* @ts-ignore */}
|
||||
<Button
|
||||
rightIcon={<ChevronDownIcon />}
|
||||
variant="ghost"
|
||||
colorScheme="gray"
|
||||
{...getTriggerProps()}
|
||||
>
|
||||
{link.label}
|
||||
</Button>
|
||||
<NavMenu {...getMenuProps()} animate={isOpen ? 'open' : 'closed'}>
|
||||
<Box maxW="7xl" mx="auto" px="8">
|
||||
<SimpleGrid spacing="10" columns={2}>
|
||||
{link.children?.map((item, idx) => (
|
||||
<DesktopMenuItem
|
||||
key={idx}
|
||||
title={item.label}
|
||||
href={item.href ?? '#'}
|
||||
icon={item.icon}
|
||||
>
|
||||
{item.description}
|
||||
</DesktopMenuItem>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
</NavMenu>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const MobileSubMenu = (props: SubmenuProps) => {
|
||||
const { link } = props
|
||||
const { isOpen, onToggle } = useDisclosure()
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Button
|
||||
textAlign="start"
|
||||
type="button"
|
||||
cursor="pointer"
|
||||
onClick={onToggle}
|
||||
paddingEnd="4"
|
||||
variant="ghost"
|
||||
colorScheme="gray"
|
||||
h="3rem"
|
||||
w="full"
|
||||
justifyContent="flex-start"
|
||||
>
|
||||
<Box mr="4">{link.label}</Box>
|
||||
<Box
|
||||
as={ChevronDownIcon}
|
||||
transform={`rotate(${isOpen ? '180deg' : '0deg'})`}
|
||||
/>
|
||||
</Button>
|
||||
<Collapse in={isOpen}>
|
||||
<Box pl="5">
|
||||
{link.children?.map((item, idx) => (
|
||||
<NavLink.Mobile key={idx} href={item.href ?? '#'}>
|
||||
{item.label}
|
||||
</NavLink.Mobile>
|
||||
))}
|
||||
</Box>
|
||||
</Collapse>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export const Submenu = {
|
||||
Mobile: MobileSubMenu,
|
||||
Desktop: DesktopSubmenu,
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
import {
|
||||
Box,
|
||||
Center,
|
||||
HStack,
|
||||
Text,
|
||||
useColorModeValue as mode,
|
||||
LinkProps as ChakraLinkProps,
|
||||
} from '@chakra-ui/react'
|
||||
import { LinkProps } from 'next/link'
|
||||
import * as React from 'react'
|
||||
import { ChevronRightIcon } from '../../../assets/icons/ChevronRightIcon'
|
||||
import { NextChakraLink } from '../nextChakraAdapters/NextChakraLink'
|
||||
|
||||
type SubmenuItemProps = LinkProps &
|
||||
Omit<ChakraLinkProps, 'as'> & {
|
||||
title: string
|
||||
icon?: React.ReactElement
|
||||
children: React.ReactNode
|
||||
href: string
|
||||
}
|
||||
|
||||
export const SubmenuItem = (props: SubmenuItemProps) => {
|
||||
const { title, icon, children, href, ...rest } = props
|
||||
return (
|
||||
<NextChakraLink
|
||||
className="group"
|
||||
href={href}
|
||||
m="-3"
|
||||
p="3"
|
||||
display="flex"
|
||||
alignItems="flex-start"
|
||||
transition="all 0.2s"
|
||||
rounded="lg"
|
||||
_hover={{ bg: mode('gray.50', 'gray.600') }}
|
||||
_focus={{ shadow: 'outline' }}
|
||||
isExternal={href.startsWith('https') && !href.includes('app.typebot.io')}
|
||||
{...rest}
|
||||
>
|
||||
<Center
|
||||
aria-hidden
|
||||
as="span"
|
||||
flexShrink={0}
|
||||
w="10"
|
||||
h="10"
|
||||
fontSize="3xl"
|
||||
color={'blue.300'}
|
||||
>
|
||||
{icon}
|
||||
</Center>
|
||||
<Box marginStart="3" as="dl">
|
||||
<HStack as="dt">
|
||||
<Text
|
||||
fontWeight="semibold"
|
||||
color={mode('gray.900', 'white')}
|
||||
_groupHover={{ color: mode('blue.600', 'inherit') }}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
<Box
|
||||
fontSize="xs"
|
||||
as={ChevronRightIcon}
|
||||
transition="all 0.2s"
|
||||
_groupHover={{
|
||||
color: mode('blue.600', 'inherit'),
|
||||
transform: 'translateX(2px)',
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
<Text as="dd" color={mode('gray.500', 'gray.400')}>
|
||||
{children}
|
||||
</Text>
|
||||
</Box>
|
||||
</NextChakraLink>
|
||||
)
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
import { Box, Center, chakra, VisuallyHidden } from '@chakra-ui/react'
|
||||
import React from 'react'
|
||||
|
||||
const Bar = chakra('span', {
|
||||
baseStyle: {
|
||||
display: 'block',
|
||||
pos: 'absolute',
|
||||
w: '1.25rem',
|
||||
h: '0.125rem',
|
||||
rounded: 'full',
|
||||
bg: 'currentcolor',
|
||||
mx: 'auto',
|
||||
insetStart: '0.125rem',
|
||||
transition: 'all 0.12s',
|
||||
},
|
||||
})
|
||||
|
||||
const ToggleIcon = (props: { active: boolean }) => {
|
||||
const { active } = props
|
||||
return (
|
||||
<Box
|
||||
className="group"
|
||||
data-active={active ? '' : undefined}
|
||||
as="span"
|
||||
display="block"
|
||||
w="1.5rem"
|
||||
h="1.5rem"
|
||||
pos="relative"
|
||||
aria-hidden
|
||||
pointerEvents="none"
|
||||
>
|
||||
<Bar
|
||||
top="0.4375rem"
|
||||
_groupActive={{ top: '0.6875rem', transform: 'rotate(45deg)' }}
|
||||
/>
|
||||
<Bar
|
||||
bottom="0.4375rem"
|
||||
_groupActive={{ bottom: '0.6875rem', transform: 'rotate(-45deg)' }}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
interface ToggleButtonProps {
|
||||
isOpen: boolean
|
||||
onClick(): void
|
||||
}
|
||||
|
||||
export const ToggleButton = (props: ToggleButtonProps) => {
|
||||
const { isOpen, onClick } = props
|
||||
return (
|
||||
<Center
|
||||
marginStart="-6"
|
||||
px="4"
|
||||
py="4"
|
||||
as="button"
|
||||
color="gray.400"
|
||||
_active={{ color: 'blue.600' }}
|
||||
onClick={onClick}
|
||||
>
|
||||
<ToggleIcon active={isOpen} />
|
||||
<VisuallyHidden>Toggle Menu</VisuallyHidden>
|
||||
</Center>
|
||||
)
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
import { useDisclosure } from '@chakra-ui/react'
|
||||
import { isFocusable, getOwnerDocument, isRightClick } from '@chakra-ui/utils'
|
||||
import * as React from 'react'
|
||||
|
||||
const getTarget = (event: React.FocusEvent) =>
|
||||
(event.relatedTarget || document.activeElement) as HTMLElement
|
||||
|
||||
type OmitMotionProps<T> = Omit<
|
||||
T,
|
||||
'onAnimationStart' | 'onDragStart' | 'onDragEnd' | 'onDrag'
|
||||
>
|
||||
|
||||
export function useNavMenu() {
|
||||
const { isOpen, onClose, onToggle, onOpen } = useDisclosure()
|
||||
const menuRef = React.useRef<HTMLDivElement>(null)
|
||||
const triggerRef = React.useRef<HTMLAnchorElement>(null)
|
||||
const timeoutRef = React.useRef<number>()
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
if (timeoutRef.current) {
|
||||
window.clearTimeout(timeoutRef.current)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const focusMenu = () => {
|
||||
timeoutRef.current = window.setTimeout(() => {
|
||||
menuRef.current?.focus({ preventScroll: true })
|
||||
}, 100)
|
||||
}
|
||||
|
||||
const getTriggerProps = () => {
|
||||
const triggerProps: React.ComponentPropsWithRef<'a'> = {
|
||||
ref: triggerRef,
|
||||
'aria-expanded': isOpen,
|
||||
'aria-controls': 'menu',
|
||||
'aria-haspopup': 'true',
|
||||
}
|
||||
|
||||
triggerProps.onClick = (event: React.MouseEvent) => {
|
||||
if (isRightClick(event)) return
|
||||
onToggle()
|
||||
if (!isOpen) {
|
||||
focusMenu()
|
||||
}
|
||||
}
|
||||
|
||||
triggerProps.onBlur = (event: React.FocusEvent) => {
|
||||
const target = getTarget(event)
|
||||
if (isOpen && !menuRef.current?.contains(target)) {
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
|
||||
triggerProps.onKeyDown = (event: React.KeyboardEvent) => {
|
||||
if (isOpen && event.key === 'Escape') {
|
||||
onClose()
|
||||
triggerRef.current?.focus({ preventScroll: true })
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
if (!isOpen) onOpen()
|
||||
focusMenu()
|
||||
}
|
||||
}
|
||||
|
||||
return triggerProps
|
||||
}
|
||||
|
||||
const getMenuProps = () => {
|
||||
const menuProps: OmitMotionProps<React.ComponentPropsWithRef<'div'>> = {
|
||||
ref: menuRef,
|
||||
id: 'menu',
|
||||
tabIndex: -1,
|
||||
}
|
||||
|
||||
menuProps.onKeyDown = (event: React.KeyboardEvent) => {
|
||||
if (!isOpen) return
|
||||
|
||||
switch (event.key) {
|
||||
case 'Escape': {
|
||||
onClose()
|
||||
return triggerRef.current?.focus()
|
||||
}
|
||||
case 'ArrowDown': {
|
||||
const doc = getOwnerDocument(menuRef.current)
|
||||
const next = doc?.activeElement
|
||||
?.nextElementSibling as HTMLAnchorElement | null
|
||||
return next?.focus()
|
||||
}
|
||||
case 'ArrowUp': {
|
||||
const doc = getOwnerDocument(menuRef.current)
|
||||
const prev = doc?.activeElement
|
||||
?.previousElementSibling as HTMLAnchorElement | null
|
||||
const el = (prev ?? triggerRef.current) as HTMLElement
|
||||
return el.focus()
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
menuProps.onBlur = (event: React.FocusEvent) => {
|
||||
const target = getTarget(event)
|
||||
const shouldBlur =
|
||||
isOpen &&
|
||||
!target.isSameNode(triggerRef.current) &&
|
||||
!menuRef.current?.contains(target)
|
||||
if (shouldBlur) {
|
||||
onClose()
|
||||
if (!isFocusable(target)) {
|
||||
triggerRef.current?.focus({ preventScroll: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return menuProps
|
||||
}
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
onClose,
|
||||
getTriggerProps,
|
||||
getMenuProps,
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import {
|
||||
Stack,
|
||||
} from '@chakra-ui/react'
|
||||
import { Footer } from 'components/common/Footer'
|
||||
import { Navbar } from 'components/common/Navbar/Navbar'
|
||||
import { Header } from 'components/common/Header/Header'
|
||||
import { NextChakraLink } from 'components/common/nextChakraAdapters/NextChakraLink'
|
||||
import { SocialMetaTags } from 'components/common/SocialMetaTags'
|
||||
import { BackgroundPolygons } from 'components/Homepage/Hero/BackgroundPolygons'
|
||||
@ -38,7 +38,7 @@ const Pricing = () => {
|
||||
<SocialMetaTags currentUrl={`https://www.typebot.io/pricing`} />
|
||||
<BackgroundPolygons />
|
||||
<DarkMode>
|
||||
<Navbar />
|
||||
<Header />
|
||||
</DarkMode>
|
||||
|
||||
<VStack spacing={40} w="full">
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { Heading } from '@chakra-ui/layout'
|
||||
import { Stack } from '@chakra-ui/react'
|
||||
import { Navbar } from 'components/common/Navbar/Navbar'
|
||||
import { Header } from 'components/common/Header/Header'
|
||||
import { SocialMetaTags } from 'components/common/SocialMetaTags'
|
||||
import React from 'react'
|
||||
|
||||
const PrivacyPolicies = () => {
|
||||
return (
|
||||
<div className="flex flex-col items-center w-full overflow-x-hidden ">
|
||||
<Navbar />
|
||||
<Header />
|
||||
<SocialMetaTags currentUrl={`https://www.typebot.io/privacy-policies`} />
|
||||
<Stack spacing={10} mx="auto" maxW="3xl" my="20">
|
||||
<Heading as="h1">Privacy Policy for Typebot</Heading>
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Stack, Heading } from '@chakra-ui/react'
|
||||
import { Navbar } from 'components/common/Navbar/Navbar'
|
||||
import { Header } from 'components/common/Header/Header'
|
||||
import { SocialMetaTags } from 'components/common/SocialMetaTags'
|
||||
|
||||
const PrivacyPolicies = () => {
|
||||
return (
|
||||
<div className="flex flex-col items-center w-full overflow-x-hidden ">
|
||||
<SocialMetaTags currentUrl={`https://www.typebot.io/terms-of-service`} />
|
||||
<Navbar />
|
||||
<Header />
|
||||
<Stack spacing={10} mx="auto" maxW="3xl" my="20">
|
||||
<Heading as="h1">Website Terms and Conditions of Use</Heading>
|
||||
|
||||
|
Reference in New Issue
Block a user