Skip to content

Commit

Permalink
Remove transition state from render props (#3312)
Browse files Browse the repository at this point in the history
* add function to map transition data to data attributes

* use transition data attributes in props

Instead of in the `slot` because this would also expose this information
as render props but we just want to set it as props without exposing it
as render props.

* rename `slot` to `transitionData` for consistency

* update changelog
  • Loading branch information
RobinMalfait authored Jun 21, 2024
1 parent 1f1e290 commit f144666
Show file tree
Hide file tree
Showing 10 changed files with 47 additions and 39 deletions.
2 changes: 1 addition & 1 deletion packages/@headlessui-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Add ability to render multiple `Dialog` components at once (without nesting them) ([#3242](https://github.com/tailwindlabs/headlessui/pull/3242))
- Add new data-attribute-based transition API ([#3273](https://github.com/tailwindlabs/headlessui/pull/3273), [#3285](https://github.com/tailwindlabs/headlessui/pull/3285), [#3307](https://github.com/tailwindlabs/headlessui/pull/3307), [#3309](https://github.com/tailwindlabs/headlessui/pull/3309))
- Add new data-attribute-based transition API ([#3273](https://github.com/tailwindlabs/headlessui/pull/3273), [#3285](https://github.com/tailwindlabs/headlessui/pull/3285), [#3307](https://github.com/tailwindlabs/headlessui/pull/3307), [#3309](https://github.com/tailwindlabs/headlessui/pull/3309), [#3312](https://github.com/tailwindlabs/headlessui/pull/3312))
- Add `DialogBackdrop` component ([#3307](https://github.com/tailwindlabs/headlessui/pull/3307), [#3310](https://github.com/tailwindlabs/headlessui/pull/3310))
- Add `PopoverBackdrop` component to replace `PopoverOverlay` ([#3308](https://github.com/tailwindlabs/headlessui/pull/3308))

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 { useTransition, type TransitionData } from '../../hooks/use-transition'
import { transitionDataAttributes, useTransition } 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 @@ -1564,7 +1564,7 @@ let DEFAULT_OPTIONS_TAG = 'div' as const
type OptionsRenderPropArg = {
open: boolean
option: unknown
} & TransitionData
}
type OptionsPropsWeControl = 'aria-labelledby' | 'aria-multiselectable' | 'role' | 'tabIndex'

let OptionsRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static
Expand Down Expand Up @@ -1665,9 +1665,8 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
return {
open: data.comboboxState === ComboboxState.Open,
option: undefined,
...transitionData,
} satisfies OptionsRenderPropArg
}, [data.comboboxState, transitionData])
}, [data.comboboxState])

// When the user scrolls **using the mouse** (so scroll event isn't appropriate)
// we want to make sure that the current activation trigger is set to pointer.
Expand Down Expand Up @@ -1706,6 +1705,7 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
} as CSSProperties,
onWheel: data.activationTrigger === ActivationTrigger.Pointer ? undefined : handleWheel,
onMouseDown: handleMouseDown,
...transitionDataAttributes(transitionData),
})

// We should freeze when the combobox is visible but "closed". This means that
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 { useTransition, type TransitionData } from '../../hooks/use-transition'
import { transitionDataAttributes, useTransition } from '../../hooks/use-transition'
import { CloseProvider } from '../../internal/close-provider'
import {
OpenClosedProvider,
Expand Down Expand Up @@ -425,7 +425,7 @@ let DEFAULT_PANEL_TAG = 'div' as const
type PanelRenderPropArg = {
open: boolean
close: (focusableElement?: HTMLElement | MutableRefObject<HTMLElement | null>) => void
} & TransitionData
}
type DisclosurePanelPropsWeControl = never

let PanelRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static
Expand Down Expand Up @@ -475,13 +475,13 @@ function PanelFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
return {
open: state.disclosureState === DisclosureStates.Open,
close,
...transitionData,
} satisfies PanelRenderPropArg
}, [state.disclosureState, close, transitionData])
}, [state.disclosureState, close])

let ourProps = {
ref: panelRef,
id,
...transitionDataAttributes(transitionData),
}

return (
Expand Down
8 changes: 4 additions & 4 deletions packages/@headlessui-react/src/components/listbox/listbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,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 { useTransition, type TransitionData } from '../../hooks/use-transition'
import { transitionDataAttributes, useTransition } from '../../hooks/use-transition'
import { useDisabled } from '../../internal/disabled'
import {
FloatingProvider,
Expand Down Expand Up @@ -870,7 +870,7 @@ let SelectedOptionContext = createContext(false)
let DEFAULT_OPTIONS_TAG = 'div' as const
type OptionsRenderPropArg = {
open: boolean
} & TransitionData
}
type OptionsPropsWeControl =
| 'aria-activedescendant'
| 'aria-labelledby'
Expand Down Expand Up @@ -1090,9 +1090,8 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
let slot = useMemo(() => {
return {
open: data.listboxState === ListboxStates.Open,
...transitionData,
} satisfies OptionsRenderPropArg
}, [data.listboxState, transitionData])
}, [data.listboxState])

let ourProps = mergeProps(anchor ? getFloatingPanelProps() : {}, {
id,
Expand All @@ -1113,6 +1112,7 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
...style,
'--button-width': useElementSize(data.buttonRef, true).width,
} as CSSProperties,
...transitionDataAttributes(transitionData),
})

// We should freeze when the listbox is visible but "closed". This means that
Expand Down
8 changes: 4 additions & 4 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 { useTransition, type TransitionData } from '../../hooks/use-transition'
import { transitionDataAttributes, useTransition } from '../../hooks/use-transition'
import { useTreeWalker } from '../../hooks/use-tree-walker'
import {
FloatingProvider,
Expand Down Expand Up @@ -565,7 +565,7 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
let DEFAULT_ITEMS_TAG = 'div' as const
type ItemsRenderPropArg = {
open: boolean
} & TransitionData
}
type ItemsPropsWeControl = 'aria-activedescendant' | 'aria-labelledby' | 'role' | 'tabIndex'

let ItemsRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static
Expand Down Expand Up @@ -760,9 +760,8 @@ function ItemsFn<TTag extends ElementType = typeof DEFAULT_ITEMS_TAG>(
let slot = useMemo(() => {
return {
open: state.menuState === MenuStates.Open,
...transitionData,
} satisfies ItemsRenderPropArg
}, [state.menuState, transitionData])
}, [state.menuState])

let ourProps = mergeProps(anchor ? getFloatingPanelProps() : {}, {
'aria-activedescendant':
Expand All @@ -782,6 +781,7 @@ function ItemsFn<TTag extends ElementType = typeof DEFAULT_ITEMS_TAG>(
...style,
'--button-width': useElementSize(state.buttonRef, true).width,
} as CSSProperties,
...transitionDataAttributes(transitionData),
})

return (
Expand Down
14 changes: 7 additions & 7 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 { useTransition, type TransitionData } from '../../hooks/use-transition'
import { transitionDataAttributes, useTransition } from '../../hooks/use-transition'
import { CloseProvider } from '../../internal/close-provider'
import {
FloatingProvider,
Expand Down Expand Up @@ -732,7 +732,7 @@ function ButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
let DEFAULT_BACKDROP_TAG = 'div' as const
type BackdropRenderPropArg = {
open: boolean
} & TransitionData
}
type BackdropPropsWeControl = 'aria-hidden'

let BackdropRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static
Expand Down Expand Up @@ -778,15 +778,15 @@ function BackdropFn<TTag extends ElementType = typeof DEFAULT_BACKDROP_TAG>(
let slot = useMemo(() => {
return {
open: popoverState === PopoverStates.Open,
...transitionData,
} satisfies BackdropRenderPropArg
}, [popoverState, transitionData])
}, [popoverState])

let ourProps = {
ref: backdropRef,
id,
'aria-hidden': true,
onClick: handleClick,
...transitionDataAttributes(transitionData),
}

return render({
Expand All @@ -806,7 +806,7 @@ let DEFAULT_PANEL_TAG = 'div' as const
type PanelRenderPropArg = {
open: boolean
close: (focusableElement?: HTMLElement | MutableRefObject<HTMLElement | null>) => void
} & TransitionData
}

let PanelRenderFeatures = RenderFeatures.RenderStrategy | RenderFeatures.Static

Expand Down Expand Up @@ -936,9 +936,8 @@ function PanelFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
return {
open: state.popoverState === PopoverStates.Open,
close,
...transitionData,
} satisfies PanelRenderPropArg
}, [state.popoverState, close, transitionData])
}, [state.popoverState, close])

let ourProps: Record<string, any> = mergeProps(anchor ? getFloatingPanelProps() : {}, {
ref: panelRef,
Expand Down Expand Up @@ -968,6 +967,7 @@ function PanelFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
...style,
'--button-width': useElementSize(state.button, true).width,
} as React.CSSProperties,
...transitionDataAttributes(transitionData),
})

let direction = useTabDirection()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ exports[`Setup API transition classes should be possible to passthrough the tran
class="enter enter-from"
data-closed=""
data-enter=""
data-headlessui-state="closed enter transition"
data-transition=""
style=""
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,6 @@ describe('Setup API', () => {
<div
class="foo1
foo2 leave"
data-headlessui-state="leave transition"
data-leave=""
data-transition=""
style=""
Expand Down
24 changes: 12 additions & 12 deletions packages/@headlessui-react/src/components/transition/transition.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { useLatestValue } from '../../hooks/use-latest-value'
import { useOnDisappear } from '../../hooks/use-on-disappear'
import { useServerHandoffComplete } from '../../hooks/use-server-handoff-complete'
import { useSyncRefs } from '../../hooks/use-sync-refs'
import { useTransition } from '../../hooks/use-transition'
import { transitionDataAttributes, useTransition } from '../../hooks/use-transition'
import { OpenClosedProvider, State, useOpenClosed } from '../../internal/open-closed'
import type { Props, ReactTag } from '../../types'
import { classNames } from '../../utils/class-names'
Expand Down Expand Up @@ -437,7 +437,7 @@ function TransitionChildFn<TTag extends ElementType = typeof DEFAULT_TRANSITION_
// a leave transition on the `<Transition>` is done, but there is still a
// child `<TransitionChild>` busy, then `visible` would be `false`, while
// `state` would still be `TreeStates.Visible`.
let [, slot] = useTransition(enabled, container, show, { start, end })
let [, transitionData] = useTransition(enabled, container, show, { start, end })

let ourProps = compact({
ref: transitionRef,
Expand All @@ -451,33 +451,33 @@ function TransitionChildFn<TTag extends ElementType = typeof DEFAULT_TRANSITION_
immediate && enterFrom,

// Map data attributes to `enter`, `enterFrom` and `enterTo` classes
slot.enter && enter,
slot.enter && slot.closed && enterFrom,
slot.enter && !slot.closed && enterTo,
transitionData.enter && enter,
transitionData.enter && transitionData.closed && enterFrom,
transitionData.enter && !transitionData.closed && enterTo,

// Map data attributes to `leave`, `leaveFrom` and `leaveTo` classes
slot.leave && leave,
slot.leave && !slot.closed && leaveFrom,
slot.leave && slot.closed && leaveTo,
transitionData.leave && leave,
transitionData.leave && !transitionData.closed && leaveFrom,
transitionData.leave && transitionData.closed && leaveTo,

// Map data attributes to `entered` class (backwards compatibility)
!slot.transition && show && entered
!transitionData.transition && show && entered
)?.trim() || undefined, // If `className` is an empty string, we can omit it
...transitionDataAttributes(transitionData),
})

let openClosedState = 0
if (state === TreeStates.Visible) openClosedState |= State.Open
if (state === TreeStates.Hidden) openClosedState |= State.Closed
if (slot.enter) openClosedState |= State.Opening
if (slot.leave) openClosedState |= State.Closing
if (transitionData.enter) openClosedState |= State.Opening
if (transitionData.leave) openClosedState |= State.Closing

return (
<NestingContext.Provider value={nesting}>
<OpenClosedProvider value={openClosedState}>
{render({
ourProps,
theirProps,
slot,
defaultTag: DEFAULT_TRANSITION_CHILD_TAG,
features: TransitionChildRenderFeatures,
visible: state === TreeStates.Visible,
Expand Down
12 changes: 11 additions & 1 deletion packages/@headlessui-react/src/hooks/use-transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,23 @@ enum TransitionState {
Leave = 1 << 2,
}

export type TransitionData = {
type TransitionData = {
closed?: boolean
enter?: boolean
leave?: boolean
transition?: boolean
}

export function transitionDataAttributes(data: TransitionData) {
let attributes: Record<string, string> = {}
for (let key in data) {
if (data[key as keyof TransitionData] === true) {
attributes[`data-${key}`] = ''
}
}
return attributes
}

export function useTransition(
enabled: boolean,
elementRef: MutableRefObject<HTMLElement | null>,
Expand Down

0 comments on commit f144666

Please sign in to comment.