Skip to content

Commit

Permalink
Implement <Transition /> and <TransitionChild /> on top of data a…
Browse files Browse the repository at this point in the history
…ttributes (#3303)

* add optional `start` and `end` events to `useTransitionData`

This will be used when we implement the `<Transition />` component
purely using the `useTransitionData` information. But because there is a
hierarchy between `<Transition />` and `<TransitionChild />` we need to
know when transitions start and end.

* implement `<Transition />` and `<TransitionChild />` on top of `useTransitionData()`

* update tests

Due to a timing issue bug, we updated the snapshot tests in
#3273 incorrectly so this
commit fixes that which is why there are a lot of changes.

Most tests have `show={true}` but not `appear` which means that they
should _not_ transition which means that no data attributes should be
present.

* wait a microTask to ensure that `prepare()` has the time to render

Now that we set state instead of mutating the DOM directly we need to
wait a tiny bit and then we can trigger the transition to ensure
a smooth transition.

* cleanup `prepareTransition` now that it returns a cleanup function

* move `waitForTransition` and `prepareTransition` into `useTransitionData`

* remove existing `useTransition` hook and related utilities

* rename `useTransitionData` to `useTransition`

* update changelog

* Update packages/@headlessui-react/src/components/transition/transition.tsx

Co-authored-by: Jordan Pittman <jordan@cryptica.me>

* add missing `TransitionState.Enter`

This makes sure that the `Enter` state is applied initially when it has
to.

This also means that we can simplify the `prepareTransition` code again
because we don't need to wait for the next microTask which made sure
`TransitionState.Enter` was available.

* add transition playground page with both APIs

* update tests to reflect latest bug fix

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
  • Loading branch information
RobinMalfait and thecrypticace committed Jun 19, 2024
1 parent 2092049 commit 29e7d94
Show file tree
Hide file tree
Showing 14 changed files with 476 additions and 948 deletions.
1 change: 1 addition & 0 deletions packages/@headlessui-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Use `useId` instead of React internals (for React 19 compatibility) ([#3254](https://github.com/tailwindlabs/headlessui/pull/3254))
- Ensure `ComboboxInput` does not sync with current value while typing ([#3259](https://github.com/tailwindlabs/headlessui/pull/3259))
- Cancel outside click behavior on touch devices when scrolling ([#3266](https://github.com/tailwindlabs/headlessui/pull/3266))
- Correctly apply conditional classses when using `<Transition />` and `<TransitionChild />` components ([#3303](https://github.com/tailwindlabs/headlessui/pull/3303))

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import { useResolveButtonType } from '../../hooks/use-resolve-button-type'
import { useScrollLock } from '../../hooks/use-scroll-lock'
import { useSyncRefs } from '../../hooks/use-sync-refs'
import { useTrackedPointer } from '../../hooks/use-tracked-pointer'
import { useTransitionData, type TransitionData } from '../../hooks/use-transition-data'
import { useTransition, type TransitionData } from '../../hooks/use-transition'
import { useTreeWalker } from '../../hooks/use-tree-walker'
import { useWatch } from '../../hooks/use-watch'
import { useDisabled } from '../../internal/disabled'
Expand Down Expand Up @@ -1610,7 +1610,7 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
let ownerDocument = useOwnerDocument(data.optionsRef)

let usesOpenClosedState = useOpenClosed()
let [visible, transitionData] = useTransitionData(
let [visible, transitionData] = useTransition(
transition,
data.optionsRef,
usesOpenClosedState !== null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { useEvent } from '../../hooks/use-event'
import { useId } from '../../hooks/use-id'
import { useResolveButtonType } from '../../hooks/use-resolve-button-type'
import { optionalRef, useSyncRefs } from '../../hooks/use-sync-refs'
import { useTransitionData, type TransitionData } from '../../hooks/use-transition-data'
import { useTransition, type TransitionData } from '../../hooks/use-transition'
import { CloseProvider } from '../../internal/close-provider'
import { OpenClosedProvider, State, useOpenClosed } from '../../internal/open-closed'
import type { Props } from '../../types'
Expand Down Expand Up @@ -458,7 +458,7 @@ function PanelFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
}, [id, dispatch])

let usesOpenClosedState = useOpenClosed()
let [visible, transitionData] = useTransitionData(
let [visible, transitionData] = useTransition(
transition,
state.panelRef,
usesOpenClosedState !== null
Expand Down
4 changes: 2 additions & 2 deletions packages/@headlessui-react/src/components/listbox/listbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import { useScrollLock } from '../../hooks/use-scroll-lock'
import { useSyncRefs } from '../../hooks/use-sync-refs'
import { useTextValue } from '../../hooks/use-text-value'
import { useTrackedPointer } from '../../hooks/use-tracked-pointer'
import { useTransitionData, type TransitionData } from '../../hooks/use-transition-data'
import { useTransition, type TransitionData } from '../../hooks/use-transition'
import { useDisabled } from '../../internal/disabled'
import {
FloatingProvider,
Expand Down Expand Up @@ -919,7 +919,7 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
let ownerDocument = useOwnerDocument(data.optionsRef)

let usesOpenClosedState = useOpenClosed()
let [visible, transitionData] = useTransitionData(
let [visible, transitionData] = useTransition(
transition,
data.optionsRef,
usesOpenClosedState !== null
Expand Down
4 changes: 2 additions & 2 deletions packages/@headlessui-react/src/components/menu/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { useScrollLock } from '../../hooks/use-scroll-lock'
import { useSyncRefs } from '../../hooks/use-sync-refs'
import { useTextValue } from '../../hooks/use-text-value'
import { useTrackedPointer } from '../../hooks/use-tracked-pointer'
import { useTransitionData, type TransitionData } from '../../hooks/use-transition-data'
import { useTransition, type TransitionData } from '../../hooks/use-transition'
import { useTreeWalker } from '../../hooks/use-tree-walker'
import {
FloatingProvider,
Expand Down Expand Up @@ -612,7 +612,7 @@ function ItemsFn<TTag extends ElementType = typeof DEFAULT_ITEMS_TAG>(
}

let usesOpenClosedState = useOpenClosed()
let [visible, transitionData] = useTransitionData(
let [visible, transitionData] = useTransition(
transition,
state.itemsRef,
usesOpenClosedState !== null
Expand Down
6 changes: 3 additions & 3 deletions packages/@headlessui-react/src/components/popover/popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { useMainTreeNode, useRootContainers } from '../../hooks/use-root-contain
import { useScrollLock } from '../../hooks/use-scroll-lock'
import { optionalRef, useSyncRefs } from '../../hooks/use-sync-refs'
import { Direction as TabDirection, useTabDirection } from '../../hooks/use-tab-direction'
import { useTransitionData, type TransitionData } from '../../hooks/use-transition-data'
import { useTransition, type TransitionData } from '../../hooks/use-transition'
import { CloseProvider } from '../../internal/close-provider'
import {
FloatingProvider,
Expand Down Expand Up @@ -750,7 +750,7 @@ function OverlayFn<TTag extends ElementType = typeof DEFAULT_OVERLAY_TAG>(
let overlayRef = useSyncRefs(ref, internalOverlayRef)

let usesOpenClosedState = useOpenClosed()
let [visible, transitionData] = useTransitionData(
let [visible, transitionData] = useTransition(
transition,
internalOverlayRef,
usesOpenClosedState !== null
Expand Down Expand Up @@ -862,7 +862,7 @@ function PanelFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
}, [id, dispatch])

let usesOpenClosedState = useOpenClosed()
let [visible, transitionData] = useTransitionData(
let [visible, transitionData] = useTransition(
transition,
internalPanelRef,
usesOpenClosedState !== null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,11 @@ exports[`Setup API nested should be possible to change the underlying DOM tag of
<div
class="My Page"
>
<article
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
style=""
>
<aside
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
style=""
>
<article>
<aside>
Sidebar
</aside>
<section
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
style=""
>
<section>
Content
</section>
</article>
Expand All @@ -37,29 +19,11 @@ exports[`Setup API nested should be possible to change the underlying DOM tag of
<div
class="My Page"
>
<div
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
style=""
>
<aside
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
style=""
>
<div>
<aside>
Sidebar
</aside>
<section
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
style=""
>
<section>
Content
</section>
</div>
Expand All @@ -70,29 +34,11 @@ exports[`Setup API nested should be possible to nest transition components 1`] =
<div
class="My Page"
>
<div
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
style=""
>
<div
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
style=""
>
<div>
<div>
Sidebar
</div>
<div
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
style=""
>
<div>
Content
</div>
</div>
Expand All @@ -103,26 +49,11 @@ exports[`Setup API nested should be possible to use render props on the Transiti
<div
class="My Page"
>
<article
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
>
<aside
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
>
<article>
<aside>
Sidebar
</aside>
<section
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
>
<section>
Content
</section>
</article>
Expand All @@ -133,27 +64,11 @@ exports[`Setup API nested should be possible to use render props on the Transiti
<div
class="My Page"
>
<div
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
style=""
>
<aside
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
>
<div>
<aside>
Sidebar
</aside>
<section
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
>
<section>
Content
</section>
</div>
Expand All @@ -167,37 +82,21 @@ exports[`Setup API nested should yell at us when we forgot to forward a ref on t
exports[`Setup API nested should yell at us when we forgot to forward the ref on one of the Transition.Child components 1`] = `"Did you forget to passthrough the \`ref\` to the actual DOM node?"`;

exports[`Setup API shallow should be possible to change the underlying DOM tag 1`] = `
<span
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
style=""
>
<span>
Children
</span>
`;

exports[`Setup API shallow should be possible to use a render prop 1`] = `
<span
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
>
<span>
Children
</span>
`;

exports[`Setup API shallow should passthrough all the props (that we do not use internally) 1`] = `
<div
class="text-blue-400"
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
id="root"
style=""
>
Children
</div>
Expand All @@ -206,25 +105,14 @@ exports[`Setup API shallow should passthrough all the props (that we do not use
exports[`Setup API shallow should passthrough all the props (that we do not use internally) even when using an \`as\` prop 1`] = `
<a
class="text-blue-400"
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
href="/"
style=""
>
Children
</a>
`;

exports[`Setup API shallow should render another component if the \`as\` prop is used and its children by default 1`] = `
<a
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
style=""
>
<a>
Children
</a>
`;
Expand All @@ -234,13 +122,7 @@ exports[`Setup API shallow should render nothing when the show prop is false 1`]
exports[`Setup API shallow should yell at us when we forget to forward the ref when using a render prop 1`] = `"Did you forget to passthrough the \`ref\` to the actual DOM node?"`;

exports[`Setup API transition classes should be possible to passthrough the transition classes 1`] = `
<div
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
style=""
>
<div>
Children
</div>
`;
Expand All @@ -249,7 +131,9 @@ exports[`Setup API transition classes should be possible to passthrough the tran
<div
class="enter enter-from"
data-closed=""
data-headlessui-state="closed"
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
style=""
>
Children
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -358,11 +358,6 @@ describe('Setup API', () => {
<div
class="foo1
foo2"
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
style=""
>
Children
</div>
Expand All @@ -371,20 +366,16 @@ describe('Setup API', () => {

await click(getByText('toggle'))

// TODO: This is not quite right
// The `foo1\nfoo2` should be gone
// I think this is a quirk of JSDOM
expect(container.firstChild).toMatchInlineSnapshot(`
<div>
<button>
toggle
</button>
<div
class="foo1
foo2 foo1 foo2 leave"
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
foo2 leave"
data-headlessui-state="leave transition"
data-leave=""
data-transition=""
style=""
>
Expand Down
Loading

0 comments on commit 29e7d94

Please sign in to comment.