2
0

💄 Improve editor header responsiveness

Closes #1204
This commit is contained in:
Baptiste Arnaud
2024-03-07 10:09:00 +01:00
parent c2003dab91
commit 5dafb64963
2 changed files with 293 additions and 236 deletions

View File

@@ -8,6 +8,8 @@ import {
Text, Text,
useColorModeValue, useColorModeValue,
useDisclosure, useDisclosure,
StackProps,
chakra,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import { import {
BuoyIcon, BuoyIcon,
@@ -37,70 +39,12 @@ import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
import { Plan } from '@typebot.io/prisma' import { Plan } from '@typebot.io/prisma'
export const TypebotHeader = () => { export const TypebotHeader = () => {
const { t } = useTranslate() const { typebot, publishedTypebot, currentUserMode } = useTypebot()
const router = useRouter()
const {
typebot,
publishedTypebot,
updateTypebot,
save,
undo,
redo,
canUndo,
canRedo,
isSavingLoading,
currentUserMode,
} = useTypebot()
const { workspace } = useWorkspace() const { workspace } = useWorkspace()
const {
setRightPanel,
rightPanel,
setStartPreviewAtGroup,
setStartPreviewAtEvent,
} = useEditor()
const [isUndoShortcutTooltipOpen, setUndoShortcutTooltipOpen] =
useState(false)
const hideUndoShortcutTooltipLater = useDebouncedCallback(() => {
setUndoShortcutTooltipOpen(false)
}, 1000)
const [isRedoShortcutTooltipOpen, setRedoShortcutTooltipOpen] =
useState(false)
const hideRedoShortcutTooltipLater = useDebouncedCallback(() => {
setRedoShortcutTooltipOpen(false)
}, 1000)
const { isOpen, onOpen } = useDisclosure() const { isOpen, onOpen } = useDisclosure()
const headerBgColor = useColorModeValue('white', 'gray.900') const headerBgColor = useColorModeValue('white', 'gray.900')
const handleNameSubmit = (name: string) =>
updateTypebot({ updates: { name } })
const handleChangeIcon = (icon: string) =>
updateTypebot({ updates: { icon } })
const handlePreviewClick = async () => {
setStartPreviewAtGroup(undefined)
setStartPreviewAtEvent(undefined)
save().then()
setRightPanel(RightPanel.PREVIEW)
}
useKeyboardShortcuts({
undo: () => {
if (!canUndo) return
hideUndoShortcutTooltipLater.flush()
setUndoShortcutTooltipOpen(true)
hideUndoShortcutTooltipLater()
undo()
},
redo: () => {
if (!canRedo) return
hideUndoShortcutTooltipLater.flush()
setRedoShortcutTooltipOpen(true)
hideRedoShortcutTooltipLater()
redo()
},
})
const handleHelpClick = () => { const handleHelpClick = () => {
isCloudProdInstance() && workspace?.plan && workspace.plan !== Plan.FREE isCloudProdInstance() && workspace?.plan && workspace.plan !== Plan.FREE
? onOpen() ? onOpen()
@@ -121,66 +65,76 @@ export const TypebotHeader = () => {
flexShrink={0} flexShrink={0}
> >
{isOpen && <SupportBubble autoShowDelay={0} />} {isOpen && <SupportBubble autoShowDelay={0} />}
<HStack <LeftElements pos="absolute" left="1rem" onHelpClick={handleHelpClick} />
display={['none', 'flex']} <TypebotNav
pos={{ base: 'absolute', xl: 'static' }} display={{ base: 'none', xl: 'flex' }}
right={{ base: isDefined(publishedTypebot) ? 340 : 295, xl: 0 }} pos={{ base: 'absolute' }}
> typebotId={typebot?.id}
<Button isResultsDisplayed={isDefined(publishedTypebot)}
as={Link} />
href={`/typebots/${typebot?.id}/edit`} <RightElements
colorScheme={router.pathname.includes('/edit') ? 'blue' : 'gray'} right="40px"
variant={router.pathname.includes('/edit') ? 'outline' : 'ghost'}
size="sm"
>
{t('editor.header.flowButton.label')}
</Button>
<Button
as={Link}
href={`/typebots/${typebot?.id}/theme`}
colorScheme={router.pathname.endsWith('theme') ? 'blue' : 'gray'}
variant={router.pathname.endsWith('theme') ? 'outline' : 'ghost'}
size="sm"
>
{t('editor.header.themeButton.label')}
</Button>
<Button
as={Link}
href={`/typebots/${typebot?.id}/settings`}
colorScheme={router.pathname.endsWith('settings') ? 'blue' : 'gray'}
variant={router.pathname.endsWith('settings') ? 'outline' : 'ghost'}
size="sm"
>
{t('editor.header.settingsButton.label')}
</Button>
<Button
as={Link}
href={`/typebots/${typebot?.id}/share`}
colorScheme={router.pathname.endsWith('share') ? 'blue' : 'gray'}
variant={router.pathname.endsWith('share') ? 'outline' : 'ghost'}
size="sm"
>
{t('share.button.label')}
</Button>
{isDefined(publishedTypebot) && (
<Button
as={Link}
href={`/typebots/${typebot?.id}/results`}
colorScheme={router.pathname.includes('results') ? 'blue' : 'gray'}
variant={router.pathname.includes('results') ? 'outline' : 'ghost'}
size="sm"
>
{t('editor.header.resultsButton.label')}
</Button>
)}
</HStack>
<HStack
pos="absolute" pos="absolute"
left="1rem" display={['none', 'flex']}
justify="center" isResultsDisplayed={isDefined(publishedTypebot)}
align="center" />
spacing="6" </Flex>
> )
}
const LeftElements = (props: StackProps & { onHelpClick: () => void }) => {
const { t } = useTranslate()
const router = useRouter()
const {
typebot,
updateTypebot,
canUndo,
canRedo,
undo,
redo,
currentUserMode,
isSavingLoading,
} = useTypebot()
const [isRedoShortcutTooltipOpen, setRedoShortcutTooltipOpen] =
useState(false)
const [isUndoShortcutTooltipOpen, setUndoShortcutTooltipOpen] =
useState(false)
const hideUndoShortcutTooltipLater = useDebouncedCallback(() => {
setUndoShortcutTooltipOpen(false)
}, 1000)
const hideRedoShortcutTooltipLater = useDebouncedCallback(() => {
setRedoShortcutTooltipOpen(false)
}, 1000)
const handleNameSubmit = (name: string) =>
updateTypebot({ updates: { name } })
const handleChangeIcon = (icon: string) =>
updateTypebot({ updates: { icon } })
useKeyboardShortcuts({
undo: () => {
if (!canUndo) return
hideUndoShortcutTooltipLater.flush()
setUndoShortcutTooltipOpen(true)
hideUndoShortcutTooltipLater()
undo()
},
redo: () => {
if (!canRedo) return
hideUndoShortcutTooltipLater.flush()
setRedoShortcutTooltipOpen(true)
hideRedoShortcutTooltipLater()
redo()
},
})
return (
<HStack justify="center" align="center" spacing="6" {...props}>
<HStack alignItems="center" spacing={3}> <HStack alignItems="center" spacing={3}>
<IconButton <IconButton
as={Link} as={Link}
@@ -266,8 +220,15 @@ export const TypebotHeader = () => {
</Tooltip> </Tooltip>
</HStack> </HStack>
)} )}
<Button leftIcon={<BuoyIcon />} onClick={handleHelpClick} size="sm"> <Button
leftIcon={<BuoyIcon />}
onClick={props.onHelpClick}
size="sm"
iconSpacing={{ base: 0, xl: 2 }}
>
<chakra.span display={{ base: 'none', xl: 'inline' }}>
{t('editor.header.helpButton.label')} {t('editor.header.helpButton.label')}
</chakra.span>
</Button> </Button>
</HStack> </HStack>
{isSavingLoading && ( {isSavingLoading && (
@@ -279,8 +240,34 @@ export const TypebotHeader = () => {
</HStack> </HStack>
)} )}
</HStack> </HStack>
)
}
<HStack right="40px" pos="absolute" display={['none', 'flex']}> const RightElements = (props: StackProps & { isResultsDisplayed: boolean }) => {
const router = useRouter()
const { t } = useTranslate()
const { typebot, currentUserMode, save } = useTypebot()
const {
setRightPanel,
rightPanel,
setStartPreviewAtGroup,
setStartPreviewAtEvent,
} = useEditor()
const handlePreviewClick = async () => {
setStartPreviewAtGroup(undefined)
setStartPreviewAtEvent(undefined)
save().then()
setRightPanel(RightPanel.PREVIEW)
}
return (
<HStack {...props}>
<TypebotNav
display={{ base: 'none', md: 'flex', xl: 'none' }}
typebotId={typebot?.id}
isResultsDisplayed={props.isResultsDisplayed}
/>
<Flex pos="relative"> <Flex pos="relative">
<ShareTypebotButton isLoading={isNotDefined(typebot)} /> <ShareTypebotButton isLoading={isNotDefined(typebot)} />
</Flex> </Flex>
@@ -291,12 +278,78 @@ export const TypebotHeader = () => {
isLoading={isNotDefined(typebot)} isLoading={isNotDefined(typebot)}
leftIcon={<PlayIcon />} leftIcon={<PlayIcon />}
size="sm" size="sm"
iconSpacing={{ base: 0, xl: 2 }}
> >
<chakra.span display={{ base: 'none', xl: 'inline' }}>
{t('editor.header.previewButton.label')} {t('editor.header.previewButton.label')}
</chakra.span>
</Button> </Button>
)} )}
{currentUserMode === 'write' && <PublishButton size="sm" />} {currentUserMode === 'write' && <PublishButton size="sm" />}
</HStack> </HStack>
</Flex> )
}
const TypebotNav = ({
typebotId,
isResultsDisplayed,
...stackProps
}: {
typebotId?: string
isResultsDisplayed: boolean
} & StackProps) => {
const { t } = useTranslate()
const router = useRouter()
return (
<HStack {...stackProps}>
<Button
as={Link}
href={`/typebots/${typebotId}/edit`}
colorScheme={router.pathname.includes('/edit') ? 'blue' : 'gray'}
variant={router.pathname.includes('/edit') ? 'outline' : 'ghost'}
size="sm"
>
{t('editor.header.flowButton.label')}
</Button>
<Button
as={Link}
href={`/typebots/${typebotId}/theme`}
colorScheme={router.pathname.endsWith('theme') ? 'blue' : 'gray'}
variant={router.pathname.endsWith('theme') ? 'outline' : 'ghost'}
size="sm"
>
{t('editor.header.themeButton.label')}
</Button>
<Button
as={Link}
href={`/typebots/${typebotId}/settings`}
colorScheme={router.pathname.endsWith('settings') ? 'blue' : 'gray'}
variant={router.pathname.endsWith('settings') ? 'outline' : 'ghost'}
size="sm"
>
{t('editor.header.settingsButton.label')}
</Button>
<Button
as={Link}
href={`/typebots/${typebotId}/share`}
colorScheme={router.pathname.endsWith('share') ? 'blue' : 'gray'}
variant={router.pathname.endsWith('share') ? 'outline' : 'ghost'}
size="sm"
>
{t('share.button.label')}
</Button>
{isResultsDisplayed && (
<Button
as={Link}
href={`/typebots/${typebotId}/results`}
colorScheme={router.pathname.includes('results') ? 'blue' : 'gray'}
variant={router.pathname.includes('results') ? 'outline' : 'ghost'}
size="sm"
>
{t('editor.header.resultsButton.label')}
</Button>
)}
</HStack>
) )
} }

View File

@@ -3,6 +3,7 @@ import {
PopoverTrigger, PopoverTrigger,
PopoverContent, PopoverContent,
Button, Button,
chakra,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import { UsersIcon } from '@/components/icons' import { UsersIcon } from '@/components/icons'
import React from 'react' import React from 'react'
@@ -20,8 +21,11 @@ export const ShareTypebotButton = ({ isLoading }: { isLoading: boolean }) => {
leftIcon={<UsersIcon />} leftIcon={<UsersIcon />}
aria-label={t('share.button.popover.ariaLabel')} aria-label={t('share.button.popover.ariaLabel')}
size="sm" size="sm"
iconSpacing={{ base: 0, xl: 2 }}
> >
<chakra.span display={{ base: 'none', xl: 'inline' }}>
{t('share.button.label')} {t('share.button.label')}
</chakra.span>
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent <PopoverContent