2
0

chore(lp): 📦️ Import existing Landing page

This commit is contained in:
Baptiste Arnaud
2022-02-09 18:40:40 +01:00
parent 65b30bfc48
commit 36be3577e1
136 changed files with 14867 additions and 20 deletions

View File

@ -0,0 +1,5 @@
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)

View File

@ -0,0 +1,142 @@
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/signup"
colorScheme="blue"
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" isDark={mode(false, true)} />
<Heading as="p" fontSize="lg">
Typebot
</Heading>
</HStack>
<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>
<HStack spacing="8" minW="240px" justify="space-between">
<Box
as={NextChakraLink}
href="https://app.typebot.io/signin"
colorScheme="blue"
variant="ghost"
fontWeight="bold"
>
Sign in
</Box>
<Button
as={NextChakraLink}
href="https://app.typebot.io/signup"
colorScheme="blue"
fontWeight="bold"
>
Create a typebot
</Button>
</HStack>
</Flex>
)
}
export const NavContent = {
Mobile: MobileNavContext,
Desktop: DesktopNavContent,
}

View File

@ -0,0 +1,48 @@
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,
}

View File

@ -0,0 +1,45 @@
import { useColorModeValue } from '@chakra-ui/react'
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"
bg={useColorModeValue('white', 'gray.700')}
w="full"
shadow="lg"
px="4"
pos="absolute"
insetX="0"
pt="6"
pb="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',
},
},
}

View File

@ -0,0 +1,30 @@
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>
)
}

View File

@ -0,0 +1,95 @@
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,
}

View File

@ -0,0 +1,75 @@
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={mode('blue.600', 'blue.400')}
>
{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>
)
}

View File

@ -0,0 +1,65 @@
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>
)
}

View File

@ -0,0 +1,53 @@
import { BookIcon } from 'assets/icons/BookIcon'
import { DocIcon } from 'assets/icons/DocIcon'
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',
children: [
{
label: 'Blog',
description:
"Content about high-performing forms and guides on how to leverage Typebot's power",
href: '/blog',
icon: <BookIcon />,
},
{
label: 'Documentation',
description:
"Everything you need to know about how to use Typebot's builder",
href: 'https://docs.typebot.io',
icon: <DocIcon />,
},
{
label: 'Roadmap',
description:
"Follow the development and make suggestions for which features you'd like to see",
href: 'https://feedback.typebot.io/roadmap',
icon: <MapIcon />,
},
{
label: 'Community',
description:
'Join our facebook community and get insights on how to create high performing surveys',
href: 'https://www.facebook.com/groups/262165102257585',
icon: <PeopleCircleIcon />,
},
],
},
{ label: 'Pricing', href: '/pricing' },
]

View File

@ -0,0 +1,127 @@
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,
}
}