Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: glitch on Popover with an hover #2282

Merged
merged 2 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 14 additions & 15 deletions docs/pages/components/popover.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -94,43 +94,42 @@ function() {
}
```

## Hover to open
## PopoverHover

Use `triggerMethod: 'hover'` on `usePopover` to open and close the popover by hovering it.
You can have a hover on Popover with `PopoverHover` and `usePopoverHover` from Popover.

```jsx
function() {
const popover = usePopover({ triggerMethod: 'hover' })
const popover = usePopoverHover()

return (
<>
<Popover.Trigger as={Button} store={popover}>
Open Popover
</Popover.Trigger>
<Popover aria-label="hover to open popover" store={popover}>
<Popover.Title>Amazing title</Popover.Title>
<Popover.Content>
<PopoverHover.Trigger as={Button} store={popover}>
Hover the button to open
</PopoverHover.Trigger>
<PopoverHover aria-label="hover to open popover" store={popover}>
<PopoverHover.Title>Amazing title</PopoverHover.Title>
<PopoverHover.Content>
Praesent sit amet quam ac velit faucibus dapibus.<br />
Quisque sapien ligula, rutrum quis aliquam nec, convallis sit amet erat.<br />
Mauris auctor blandit porta.
</Popover.Content>
</Popover>
</PopoverHover.Content>
</PopoverHover>
</>
)
}
```

## usePopover

We use `usePopover` from [Ariakit Popover](https://ariakit.org/reference/use-popover-store) for the state of the popover.
We use `usePopoverStore` from [Ariakit Popover](https://ariakit.org/reference/use-popover-store) for the state of the Popover and `useHovercardStore` from [Ariakit Hovercard](https://ariakit.org/reference/use-hovercard-store) for the state of the PopoverHover.

Pass options to `usePopover`:
Pass options to `usePopover` or `usePopoverHover`:

- `defaultOpen`: e.g. `const popover = usePopover({ defaultOpen: true })`
- `triggerMethod`: `click` or `hover`
- `withCloseButton`: `bool`, show/hide cross to close popover

When `triggerMethod` is set to hover
When you use `usePopoverHover` you can change:

- `showTimeout`: `number` by default to `500`, show after x milliseconds on hover the trigger
- `hideTimeout`: `number` by default to `300`, close after x milliseconds on mouse lease popover
Expand Down
2 changes: 1 addition & 1 deletion packages/Popover/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,6 @@
},
"gitHead": "974e7bfd71f8cfe846cbffd678c3860a8952f9e9",
"sideEffects": false,
"component": "Popover, usePopover",
"component": "Popover, usePopover, PopoverHover, usePopoverHover",
"homepage": "https://welcome-ui.com/components/popover"
}
31 changes: 31 additions & 0 deletions packages/Popover/src/Arrow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react'

import { UsePopover } from './usePopover'
import * as S from './styles'

const transformMap = {
top: 'rotateZ(180deg)',
right: 'rotateZ(-90deg)',
bottom: 'rotateZ(360deg)',
left: 'rotateZ(90deg)',
}

type ArrowProps = {
store: UsePopover
}

export const Arrow = ({ store }: ArrowProps) => {
const placement = store.useState('currentPlacement')

const [parentPlacement] = placement.split('-')
const transform = transformMap[parentPlacement as keyof typeof transformMap]

return (
<S.Arrow store={store}>
<S.ArrowItem $transform={transform} h={30} w={30} xmlns="http://www.w3.org/2000/svg">
<path d="M7 30L15 22L23 30H7Z" fill="currentColor" fillRule="nonzero" id="stroke" />
<path d="M8 30L15 23L22 30H8Z" fill="currentColor" fillRule="nonzero" />
</S.ArrowItem>
</S.Arrow>
)
}
46 changes: 46 additions & 0 deletions packages/Popover/src/Content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react'
import { Box } from '@welcome-ui/box'
import { Button } from '@welcome-ui/button'
import { CrossIcon } from '@welcome-ui/icons'

import { UsePopover, UsePopoverHover } from './usePopover'
import { Arrow } from './Arrow'
import { PopoverProps } from './Popover'

export interface ContentOptions {
children: PopoverProps['children']
/** call a function when popover closed */
onClose?: () => void
store: UsePopover | UsePopoverHover
}

export const Content = ({ children, onClose, store }: ContentOptions) => {
const handleClose = () => {
if (onClose) onClose()
store?.hide()
}

const { withCloseButton } = store

return (
<Box position="relative">
<Arrow store={store} />
{children as React.ReactElement}
{withCloseButton && (
<Button
flex="0 0 auto"
ml="md"
onClick={handleClose}
position="absolute"
right={1}
shape="square"
size="xs"
top={1}
variant="secondary"
>
<CrossIcon />
</Button>
)}
</Box>
)
}
42 changes: 42 additions & 0 deletions packages/Popover/src/Popover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react'
import { CreateWuiProps, forwardRef } from '@welcome-ui/system'
import * as Ariakit from '@ariakit/react'

import * as S from './styles'
import { PopoverTrigger } from './Trigger'
import { UsePopover } from './usePopover'
import { Content } from './Content'

export interface PopoverOptions extends Ariakit.PopoverProps {
/** call a function when popover closed */
onClose?: () => void
store: UsePopover
}

export type PopoverProps = CreateWuiProps<'div', PopoverOptions>

const PopoverComponent = forwardRef<'div', PopoverProps>(
({ children, onClose, store, ...rest }, ref) => {
const { withCloseButton } = store

return (
<S.Popover
store={store}
{...rest}
$withCloseButton={withCloseButton}
as={undefined}
ref={ref}
>
<Content onClose={onClose} store={store}>
{children}
</Content>
</S.Popover>
)
}
)

export const Popover = Object.assign(PopoverComponent, {
Content: S.Content,
Title: S.Title,
Trigger: PopoverTrigger,
})
42 changes: 42 additions & 0 deletions packages/Popover/src/PopoverHover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react'
import { CreateWuiProps, forwardRef } from '@welcome-ui/system'
import * as Ariakit from '@ariakit/react'

import * as S from './styles'
import { PopoverHoverTrigger } from './Trigger'
import { UsePopoverHover } from './usePopover'
import { Content } from './Content'

export interface PopoverHoverOptions extends Ariakit.HovercardProps {
/** call a function when popover closed */
onClose?: () => void
store: UsePopoverHover
}

export type PopoverHoverProps = CreateWuiProps<'div', PopoverHoverOptions>

const PopoverHoverComponent = forwardRef<'div', PopoverHoverProps>(
({ children, onClose, store, ...rest }, ref) => {
const { withCloseButton } = store

return (
<S.Popover
as={Ariakit.Hovercard}
store={store}
{...rest}
$withCloseButton={withCloseButton}
ref={ref}
>
<Content onClose={onClose} store={store}>
{children}
</Content>
</S.Popover>
)
}
)

export const PopoverHover = Object.assign(PopoverHoverComponent, {
Content: S.Content,
Title: S.Title,
Trigger: PopoverHoverTrigger,
})
58 changes: 11 additions & 47 deletions packages/Popover/src/Trigger.tsx
Original file line number Diff line number Diff line change
@@ -1,57 +1,21 @@
import { CreateWuiProps, forwardRef } from '@welcome-ui/system'
import React from 'react'
import { useIsomorphicLayoutEffect } from '@welcome-ui/utils'

import { UsePopover } from './usePopover'
import * as S from './styles'

export type TriggerProps = CreateWuiProps<'button', { store: UsePopover }>
export type PopoverTriggerProps = CreateWuiProps<'button', { store: UsePopover }>

export const Trigger = forwardRef<'button', TriggerProps>(({ as, store, ...rest }, ref) => {
const { triggerMethod } = store
const isHoverMethod = triggerMethod === 'hover'
const disclosureRef = store.useState('disclosureElement')
const popoverRef = store.useState('popoverElement')

const showPopover: () => void = () => {
if (isHoverMethod) {
// remove listeners on mouseenter
disclosureRef?.removeEventListener('mouseenter', showPopover)
popoverRef?.removeEventListener('mouseenter', showPopover)
// add listeners on mouseleave
disclosureRef?.addEventListener('mouseleave', hidePopover)
popoverRef?.addEventListener('mouseleave', hidePopover)
// show popover
store.show()
}
}

const hidePopover: () => void = () => {
if (isHoverMethod) {
// remove listeners on mouseleave
disclosureRef?.removeEventListener('mouseleave', hidePopover)
popoverRef?.removeEventListener('mouseleave', hidePopover)
// add listeners on mouseenter
disclosureRef?.addEventListener('mouseenter', showPopover)
popoverRef?.addEventListener('mouseenter', showPopover)
// hide popover
store.hide()
}
export const PopoverTrigger = forwardRef<'button', PopoverTriggerProps>(
({ as, store, ...rest }, ref) => {
return <S.PopoverTrigger store={store} {...rest} forwardedAs={as} ref={ref} />
}
)

useIsomorphicLayoutEffect(() => {
if (isHoverMethod && disclosureRef) {
// add listeners on mount
disclosureRef.addEventListener('mouseenter', showPopover)
disclosureRef.addEventListener('mouseleave', hidePopover)
return () => {
// remove listeners on unmount
disclosureRef.removeEventListener('mouseenter', showPopover)
disclosureRef.removeEventListener('mouseleave', hidePopover)
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [disclosureRef])
export type PopoverHoverTriggerProps = CreateWuiProps<'button', { store: UsePopover }>

return <S.PopoverTrigger store={store} {...rest} forwardedAs={as} ref={ref} />
})
export const PopoverHoverTrigger = forwardRef<'button', PopoverHoverTriggerProps>(
({ as, store, ...rest }, ref) => {
return <S.PopoverHoverTrigger store={store} {...rest} forwardedAs={as} ref={ref} />
}
)
77 changes: 2 additions & 75 deletions packages/Popover/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,76 +1,3 @@
import React from 'react'
import { Box } from '@welcome-ui/box'
import { Button } from '@welcome-ui/button'
import { CrossIcon } from '@welcome-ui/icons'
import { CreateWuiProps, forwardRef } from '@welcome-ui/system'

import * as S from './styles'
import { Trigger } from './Trigger'
import { UsePopover } from './usePopover'

export interface PopoverOptions {
/** call a function when popover closed */
onClose?: () => void
store: UsePopover
}

export type PopoverProps = CreateWuiProps<'div', PopoverOptions>

/* eslint-disable @typescript-eslint/no-unused-vars */
export const PopoverComponent = forwardRef<'div', PopoverProps>(
({ children, onClose, store, ...rest }, ref) => {
const closePopover = () => {
if (onClose) onClose()
store?.hide()
}

const placement = store.useState('currentPlacement')
const { withCloseButton } = store
// get the correct transform style for arrow
const [parentPlacement] = placement.split('-')
const transformMap: { [key: string]: string } = {
top: 'rotateZ(180deg)',
right: 'rotateZ(-90deg)',
bottom: 'rotateZ(360deg)',
left: 'rotateZ(90deg)',
}
const transform = transformMap[parentPlacement]

return (
<S.Popover store={store} {...rest} $withCloseButton={withCloseButton} ref={ref}>
<Box position="relative">
<S.Arrow store={store}>
<S.ArrowItem $transform={transform} h={30} w={30} xmlns="http://www.w3.org/2000/svg">
<path d="M7 30L15 22L23 30H7Z" fill="currentColor" fillRule="nonzero" id="stroke" />
<path d="M8 30L15 23L22 30H8Z" fill="currentColor" fillRule="nonzero" />
</S.ArrowItem>
</S.Arrow>
{children}
{withCloseButton && (
<Button
flex="0 0 auto"
ml="md"
onClick={closePopover}
position="absolute"
right={1}
shape="square"
size="xs"
top={1}
variant="secondary"
>
<CrossIcon />
</Button>
)}
</Box>
</S.Popover>
)
}
)

export const Popover = Object.assign(PopoverComponent, {
Content: S.Content,
Title: S.Title,
Trigger: Trigger,
})

export * from './Popover'
export * from './PopoverHover'
export * from './usePopover'
4 changes: 4 additions & 0 deletions packages/Popover/src/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,7 @@ export const Popover = styled(Ariakit.Popover)<{ $withCloseButton: boolean }>(
export const PopoverTrigger = styled(Ariakit.PopoverDisclosure)`
${system}
`

export const PopoverHoverTrigger = styled(Ariakit.HovercardAnchor)`
${system}
`
Loading