ycs77 / headlessui-float

Easily use Headless UI with Floating UI to position floating elements.
https://headlessui-float.vercel.app
MIT License
344 stars 11 forks source link

Issue with animation when using flip #57

Closed VinceBT-BG closed 9 months ago

VinceBT-BG commented 1 year ago

Use Version

Describe the bug

Flip has a weird behavior if using transitions

To Reproduce

  1. Use flip attribute
  2. Use transition origin for a menu popup for example
  3. Make it so the menu has no space to open
  4. The first time it opens, it will open like it came from the other side
  5. The following times it will open normally

I saved the blitz here: https://stackblitz.com/edit/github-avrwwh?file=src/pages/transition.jsx Go to Transition and click the button

You can see the menu opens from the top left when it has space to open, then I scroll and I press it again, now it opens from the top left one last time before opening from the bottom left after that

Screenshots 2023-04-29 23 48 59

Expected behavior The block animates from the right spot

ycs77 commented 1 year ago

Hi @VinceBT-BG, This issue is the same as #49, we need to wait for Headless UI to solve it, I have raised this problem at https://github.com/tailwindlabs/headlessui/discussions/2432, and I need to wait for the reply there.

VinceBT-BG commented 1 year ago

Can we do something by using a force re-rendering by using a key={placement} on the Transition for example ?

VinceBT-BG commented 1 year ago

@ycs77 I think I achieved it using the key prop : https://codesandbox.io/s/naughty-pare-14ty2i?file=/src/App.js

VinceBT-BG commented 11 months ago

What is the status ? I see you got an answer on https://github.com/tailwindlabs/headlessui/issues/2550

ycs77 commented 11 months ago

Now I'm looking for a solution.

VinceBT-BG commented 10 months ago

Why not get out of Headless UI's Transition (that has bugs btw when you spam open/hide an entering/hiding animation) and use something such as https://reactcommunity.org/react-transition-group/transition ? That's what I use in local and I have no issues

ycs77 commented 9 months ago

Thank you for reporting this issue. However, I will not be fixing this issue.

This issue stems from a core problem: "Headless UI Float is a wrapper for Floating UI to make it easy to use in Headless UI". Therefore, many of the designs of this package must be subject to these upstream packages. In this case, the issue is that Headless UI's <Transition> component cannot correctly transition based on Floating UI's placement. Headless UI does not consider this to be a bug (see https://github.com/tailwindlabs/headlessui/issues/2550 and https://github.com/tailwindlabs/headlessui/issues/2729). This is not a Headless UI bug, but rather an issue with using Headless UI and Floating UI together.

To implement this feature using Floating UI

Headless UI Float is essentially just a wrapper. In other words, the functionality of Headless UI Float can be implemented using Floating UI. Floating UI also has a feature called useTransitionStatus() that can solve this issue. For more advanced use cases, please use Floating UI to implement them. Therefore, this is an example of using Floating UI's useTransitionStatus():

import { useState } from 'react'
import { Menu } from '@headlessui/react'
import {
  autoUpdate,
  flip,
  useClick,
  useDismiss,
  useFloating,
  useInteractions,
  useTransitionStatus,
} from '@floating-ui/react'

export default function ExampleMenu() {
  const [show, setShow] = useState(false)

  const { context, refs, x, y, strategy, placement } = useFloating({
    open: show,
    onOpenChange: setShow,
    placement: 'bottom-start',
    middleware: [flip()],
    whileElementsMounted: autoUpdate,
  })

  const click = useClick(context)
  const dismiss = useDismiss(context)

  const { getReferenceProps, getFloatingProps } = useInteractions([
    click,
    dismiss,
  ])

  const { isMounted, status } = useTransitionStatus(context)

  return (
    <div className="relative h-[200px] p-4 border overflow-y-auto">
      <Menu as="div" className="py-[130px]">
        <Menu.Button
          ref={refs.setReference}
          className="flex justify-center items-center px-5 py-2 bg-indigo-50 hover:bg-indigo-100 text-indigo-500 text-sm rounded-md"
          {...getReferenceProps()}
        >
          Options
        </Menu.Button>

        {isMounted && (
          <Menu.Items
            static
            className="
              w-48 bg-white border border-gray-200 rounded-md shadow-lg overflow-hidden focus:outline-none
              data-[status=initial]:opacity-0
              data-[status=initial]:scale-75
              data-[status=open]:transition
              data-[status=open]:duration-200
              data-[status=open]:ease-out
              data-[status=open]:data-[placement^=top]:origin-bottom-left
              data-[status=open]:data-[placement^=bottom]:origin-top-left
              data-[status=close]:transition
              data-[status=close]:duration-150
              data-[status=close]:ease-in
              data-[status=close]:opacity-0
              data-[status=close]:scale-75
              data-[status=close]:data-[placement^=top]:origin-bottom-left
              data-[status=close]:data-[placement^=bottom]:origin-top-left
            "
            ref={refs.setFloating}
            style={{
              position: strategy,
              top: y ?? 0,
              left: x ?? 0,
            }}
            data-placement={placement}
            data-status={status}
            {...getFloatingProps()}
          >
            <Menu.Item>
              {({ active }) => (
                <button type="button" className={`block w-full px-4 py-1.5 text-left text-sm ${
                  active ? 'bg-indigo-500 text-white' : ''
                }`} onClick={() => setShow(false)}>
                  Account settings
                </button>
              )}
            </Menu.Item>
            <Menu.Item>
              {({ active }) => (
                <button type="button" className={`block w-full px-4 py-1.5 text-left text-sm ${
                  active ? 'bg-indigo-500 text-white' : ''
                }`} onClick={() => setShow(false)}>
                  Documentation
                </button>
              )}
            </Menu.Item>
            <Menu.Item disabled>
              <span className="block w-full px-4 py-1.5 text-left text-sm opacity-50 cursor-default">
                Invite a friend (coming soon!)
              </span>
            </Menu.Item>
          </Menu.Items>
        )}
      </Menu>
    </div>
  )
}

In this example, the positioning of <Float> is changed to use useFloating(), and Headless UI's <Transition> is changed to use useTransitionStatus(). The transition class is set by changing the data-status property. For more explanation, please refer to the documentation: https://floating-ui.com/docs/useTransition

Regarding the react-transition-group package

I do not use it for two reasons:

  1. I do not normally use React, and I have never used react-transition-group.
  2. I developed Headless UI Float intending to have the same API as Headless UI. react-transition-group would make the Headless UI Float API different in React and Vue.

I hope these responses are helpful.