Skip to content

Commit 29e7d94

Browse files
Implement <Transition /> and <TransitionChild /> on top of data attributes (#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 <[email protected]> * 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 <[email protected]>
1 parent 2092049 commit 29e7d94

File tree

14 files changed

+476
-948
lines changed

14 files changed

+476
-948
lines changed

packages/@headlessui-react/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2121
- Use `useId` instead of React internals (for React 19 compatibility) ([#3254](https://github.com/tailwindlabs/headlessui/pull/3254))
2222
- Ensure `ComboboxInput` does not sync with current value while typing ([#3259](https://github.com/tailwindlabs/headlessui/pull/3259))
2323
- Cancel outside click behavior on touch devices when scrolling ([#3266](https://github.com/tailwindlabs/headlessui/pull/3266))
24+
- Correctly apply conditional classses when using `<Transition />` and `<TransitionChild />` components ([#3303](https://github.com/tailwindlabs/headlessui/pull/3303))
2425

2526
### Changed
2627

packages/@headlessui-react/src/components/combobox/combobox.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import { useResolveButtonType } from '../../hooks/use-resolve-button-type'
4141
import { useScrollLock } from '../../hooks/use-scroll-lock'
4242
import { useSyncRefs } from '../../hooks/use-sync-refs'
4343
import { useTrackedPointer } from '../../hooks/use-tracked-pointer'
44-
import { useTransitionData, type TransitionData } from '../../hooks/use-transition-data'
44+
import { useTransition, type TransitionData } from '../../hooks/use-transition'
4545
import { useTreeWalker } from '../../hooks/use-tree-walker'
4646
import { useWatch } from '../../hooks/use-watch'
4747
import { useDisabled } from '../../internal/disabled'
@@ -1610,7 +1610,7 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
16101610
let ownerDocument = useOwnerDocument(data.optionsRef)
16111611

16121612
let usesOpenClosedState = useOpenClosed()
1613-
let [visible, transitionData] = useTransitionData(
1613+
let [visible, transitionData] = useTransition(
16141614
transition,
16151615
data.optionsRef,
16161616
usesOpenClosedState !== null

packages/@headlessui-react/src/components/disclosure/disclosure.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { useEvent } from '../../hooks/use-event'
2424
import { useId } from '../../hooks/use-id'
2525
import { useResolveButtonType } from '../../hooks/use-resolve-button-type'
2626
import { optionalRef, useSyncRefs } from '../../hooks/use-sync-refs'
27-
import { useTransitionData, type TransitionData } from '../../hooks/use-transition-data'
27+
import { useTransition, type TransitionData } from '../../hooks/use-transition'
2828
import { CloseProvider } from '../../internal/close-provider'
2929
import { OpenClosedProvider, State, useOpenClosed } from '../../internal/open-closed'
3030
import type { Props } from '../../types'
@@ -458,7 +458,7 @@ function PanelFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
458458
}, [id, dispatch])
459459

460460
let usesOpenClosedState = useOpenClosed()
461-
let [visible, transitionData] = useTransitionData(
461+
let [visible, transitionData] = useTransition(
462462
transition,
463463
state.panelRef,
464464
usesOpenClosedState !== null

packages/@headlessui-react/src/components/listbox/listbox.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import { useScrollLock } from '../../hooks/use-scroll-lock'
4242
import { useSyncRefs } from '../../hooks/use-sync-refs'
4343
import { useTextValue } from '../../hooks/use-text-value'
4444
import { useTrackedPointer } from '../../hooks/use-tracked-pointer'
45-
import { useTransitionData, type TransitionData } from '../../hooks/use-transition-data'
45+
import { useTransition, type TransitionData } from '../../hooks/use-transition'
4646
import { useDisabled } from '../../internal/disabled'
4747
import {
4848
FloatingProvider,
@@ -919,7 +919,7 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
919919
let ownerDocument = useOwnerDocument(data.optionsRef)
920920

921921
let usesOpenClosedState = useOpenClosed()
922-
let [visible, transitionData] = useTransitionData(
922+
let [visible, transitionData] = useTransition(
923923
transition,
924924
data.optionsRef,
925925
usesOpenClosedState !== null

packages/@headlessui-react/src/components/menu/menu.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import { useScrollLock } from '../../hooks/use-scroll-lock'
3737
import { useSyncRefs } from '../../hooks/use-sync-refs'
3838
import { useTextValue } from '../../hooks/use-text-value'
3939
import { useTrackedPointer } from '../../hooks/use-tracked-pointer'
40-
import { useTransitionData, type TransitionData } from '../../hooks/use-transition-data'
40+
import { useTransition, type TransitionData } from '../../hooks/use-transition'
4141
import { useTreeWalker } from '../../hooks/use-tree-walker'
4242
import {
4343
FloatingProvider,
@@ -612,7 +612,7 @@ function ItemsFn<TTag extends ElementType = typeof DEFAULT_ITEMS_TAG>(
612612
}
613613

614614
let usesOpenClosedState = useOpenClosed()
615-
let [visible, transitionData] = useTransitionData(
615+
let [visible, transitionData] = useTransition(
616616
transition,
617617
state.itemsRef,
618618
usesOpenClosedState !== null

packages/@headlessui-react/src/components/popover/popover.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import { useMainTreeNode, useRootContainers } from '../../hooks/use-root-contain
3636
import { useScrollLock } from '../../hooks/use-scroll-lock'
3737
import { optionalRef, useSyncRefs } from '../../hooks/use-sync-refs'
3838
import { Direction as TabDirection, useTabDirection } from '../../hooks/use-tab-direction'
39-
import { useTransitionData, type TransitionData } from '../../hooks/use-transition-data'
39+
import { useTransition, type TransitionData } from '../../hooks/use-transition'
4040
import { CloseProvider } from '../../internal/close-provider'
4141
import {
4242
FloatingProvider,
@@ -750,7 +750,7 @@ function OverlayFn<TTag extends ElementType = typeof DEFAULT_OVERLAY_TAG>(
750750
let overlayRef = useSyncRefs(ref, internalOverlayRef)
751751

752752
let usesOpenClosedState = useOpenClosed()
753-
let [visible, transitionData] = useTransitionData(
753+
let [visible, transitionData] = useTransition(
754754
transition,
755755
internalOverlayRef,
756756
usesOpenClosedState !== null
@@ -862,7 +862,7 @@ function PanelFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
862862
}, [id, dispatch])
863863

864864
let usesOpenClosedState = useOpenClosed()
865-
let [visible, transitionData] = useTransitionData(
865+
let [visible, transitionData] = useTransition(
866866
transition,
867867
internalPanelRef,
868868
usesOpenClosedState !== null

packages/@headlessui-react/src/components/transition/__snapshots__/transition.test.tsx.snap

Lines changed: 22 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,11 @@ exports[`Setup API nested should be possible to change the underlying DOM tag of
44
<div
55
class="My Page"
66
>
7-
<article
8-
data-closed=""
9-
data-enter=""
10-
data-headlessui-state="closed enter transition"
11-
data-transition=""
12-
style=""
13-
>
14-
<aside
15-
data-closed=""
16-
data-enter=""
17-
data-headlessui-state="closed enter transition"
18-
data-transition=""
19-
style=""
20-
>
7+
<article>
8+
<aside>
219
Sidebar
2210
</aside>
23-
<section
24-
data-closed=""
25-
data-enter=""
26-
data-headlessui-state="closed enter transition"
27-
data-transition=""
28-
style=""
29-
>
11+
<section>
3012
Content
3113
</section>
3214
</article>
@@ -37,29 +19,11 @@ exports[`Setup API nested should be possible to change the underlying DOM tag of
3719
<div
3820
class="My Page"
3921
>
40-
<div
41-
data-closed=""
42-
data-enter=""
43-
data-headlessui-state="closed enter transition"
44-
data-transition=""
45-
style=""
46-
>
47-
<aside
48-
data-closed=""
49-
data-enter=""
50-
data-headlessui-state="closed enter transition"
51-
data-transition=""
52-
style=""
53-
>
22+
<div>
23+
<aside>
5424
Sidebar
5525
</aside>
56-
<section
57-
data-closed=""
58-
data-enter=""
59-
data-headlessui-state="closed enter transition"
60-
data-transition=""
61-
style=""
62-
>
26+
<section>
6327
Content
6428
</section>
6529
</div>
@@ -70,29 +34,11 @@ exports[`Setup API nested should be possible to nest transition components 1`] =
7034
<div
7135
class="My Page"
7236
>
73-
<div
74-
data-closed=""
75-
data-enter=""
76-
data-headlessui-state="closed enter transition"
77-
data-transition=""
78-
style=""
79-
>
80-
<div
81-
data-closed=""
82-
data-enter=""
83-
data-headlessui-state="closed enter transition"
84-
data-transition=""
85-
style=""
86-
>
37+
<div>
38+
<div>
8739
Sidebar
8840
</div>
89-
<div
90-
data-closed=""
91-
data-enter=""
92-
data-headlessui-state="closed enter transition"
93-
data-transition=""
94-
style=""
95-
>
41+
<div>
9642
Content
9743
</div>
9844
</div>
@@ -103,26 +49,11 @@ exports[`Setup API nested should be possible to use render props on the Transiti
10349
<div
10450
class="My Page"
10551
>
106-
<article
107-
data-closed=""
108-
data-enter=""
109-
data-headlessui-state="closed enter transition"
110-
data-transition=""
111-
>
112-
<aside
113-
data-closed=""
114-
data-enter=""
115-
data-headlessui-state="closed enter transition"
116-
data-transition=""
117-
>
52+
<article>
53+
<aside>
11854
Sidebar
11955
</aside>
120-
<section
121-
data-closed=""
122-
data-enter=""
123-
data-headlessui-state="closed enter transition"
124-
data-transition=""
125-
>
56+
<section>
12657
Content
12758
</section>
12859
</article>
@@ -133,27 +64,11 @@ exports[`Setup API nested should be possible to use render props on the Transiti
13364
<div
13465
class="My Page"
13566
>
136-
<div
137-
data-closed=""
138-
data-enter=""
139-
data-headlessui-state="closed enter transition"
140-
data-transition=""
141-
style=""
142-
>
143-
<aside
144-
data-closed=""
145-
data-enter=""
146-
data-headlessui-state="closed enter transition"
147-
data-transition=""
148-
>
67+
<div>
68+
<aside>
14969
Sidebar
15070
</aside>
151-
<section
152-
data-closed=""
153-
data-enter=""
154-
data-headlessui-state="closed enter transition"
155-
data-transition=""
156-
>
71+
<section>
15772
Content
15873
</section>
15974
</div>
@@ -167,37 +82,21 @@ exports[`Setup API nested should yell at us when we forgot to forward a ref on t
16782
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?"`;
16883

16984
exports[`Setup API shallow should be possible to change the underlying DOM tag 1`] = `
170-
<span
171-
data-closed=""
172-
data-enter=""
173-
data-headlessui-state="closed enter transition"
174-
data-transition=""
175-
style=""
176-
>
85+
<span>
17786
Children
17887
</span>
17988
`;
18089

18190
exports[`Setup API shallow should be possible to use a render prop 1`] = `
182-
<span
183-
data-closed=""
184-
data-enter=""
185-
data-headlessui-state="closed enter transition"
186-
data-transition=""
187-
>
91+
<span>
18892
Children
18993
</span>
19094
`;
19195

19296
exports[`Setup API shallow should passthrough all the props (that we do not use internally) 1`] = `
19397
<div
19498
class="text-blue-400"
195-
data-closed=""
196-
data-enter=""
197-
data-headlessui-state="closed enter transition"
198-
data-transition=""
19999
id="root"
200-
style=""
201100
>
202101
Children
203102
</div>
@@ -206,25 +105,14 @@ exports[`Setup API shallow should passthrough all the props (that we do not use
206105
exports[`Setup API shallow should passthrough all the props (that we do not use internally) even when using an \`as\` prop 1`] = `
207106
<a
208107
class="text-blue-400"
209-
data-closed=""
210-
data-enter=""
211-
data-headlessui-state="closed enter transition"
212-
data-transition=""
213108
href="/"
214-
style=""
215109
>
216110
Children
217111
</a>
218112
`;
219113

220114
exports[`Setup API shallow should render another component if the \`as\` prop is used and its children by default 1`] = `
221-
<a
222-
data-closed=""
223-
data-enter=""
224-
data-headlessui-state="closed enter transition"
225-
data-transition=""
226-
style=""
227-
>
115+
<a>
228116
Children
229117
</a>
230118
`;
@@ -234,13 +122,7 @@ exports[`Setup API shallow should render nothing when the show prop is false 1`]
234122
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?"`;
235123

236124
exports[`Setup API transition classes should be possible to passthrough the transition classes 1`] = `
237-
<div
238-
data-closed=""
239-
data-enter=""
240-
data-headlessui-state="closed enter transition"
241-
data-transition=""
242-
style=""
243-
>
125+
<div>
244126
Children
245127
</div>
246128
`;
@@ -249,7 +131,9 @@ exports[`Setup API transition classes should be possible to passthrough the tran
249131
<div
250132
class="enter enter-from"
251133
data-closed=""
252-
data-headlessui-state="closed"
134+
data-enter=""
135+
data-headlessui-state="closed enter transition"
136+
data-transition=""
253137
style=""
254138
>
255139
Children

packages/@headlessui-react/src/components/transition/transition.test.tsx

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -358,11 +358,6 @@ describe('Setup API', () => {
358358
<div
359359
class="foo1
360360
foo2"
361-
data-closed=""
362-
data-enter=""
363-
data-headlessui-state="closed enter transition"
364-
data-transition=""
365-
style=""
366361
>
367362
Children
368363
</div>
@@ -371,20 +366,16 @@ describe('Setup API', () => {
371366

372367
await click(getByText('toggle'))
373368

374-
// TODO: This is not quite right
375-
// The `foo1\nfoo2` should be gone
376-
// I think this is a quirk of JSDOM
377369
expect(container.firstChild).toMatchInlineSnapshot(`
378370
<div>
379371
<button>
380372
toggle
381373
</button>
382374
<div
383375
class="foo1
384-
foo2 foo1 foo2 leave"
385-
data-closed=""
386-
data-enter=""
387-
data-headlessui-state="closed enter transition"
376+
foo2 leave"
377+
data-headlessui-state="leave transition"
378+
data-leave=""
388379
data-transition=""
389380
style=""
390381
>

0 commit comments

Comments
 (0)