Add new CloseButton component and useClose hook (#3096)
* add `useClose` hook and `CloseButton` component * expose `useClose` hook and `CloseButton` components * use `CloseProvider` in the `Popover` component * use `CloseProvider` in the `Dialog` component * use `CloseProvider` in the `Disclosure` component * update changelog
This commit is contained in:
@@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Expose `--input-width` and `--button-width` CSS variables on the `ComboboxOptions` component ([#3057](https://github.com/tailwindlabs/headlessui/pull/3057))
|
||||
- Expose the `--button-width` CSS variable on the `PopoverPanel` component ([#3058](https://github.com/tailwindlabs/headlessui/pull/3058))
|
||||
- Close the `Combobox`, `Dialog`, `Listbox`, `Menu` and `Popover` components when the trigger disappears ([#3075](https://github.com/tailwindlabs/headlessui/pull/3075))
|
||||
- Add new `CloseButton` component and `useClose` hook ([#3096](https://github.com/tailwindlabs/headlessui/pull/3096))
|
||||
|
||||
## [2.0.0-alpha.4] - 2024-01-03
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
'use client'
|
||||
|
||||
import React, { type ElementType, type Ref } from 'react'
|
||||
import { useClose } from '../../internal/close-provider'
|
||||
import { forwardRefWithAs, mergeProps } from '../../utils/render'
|
||||
import { Button, type ButtonProps, type _internal_ComponentButton } from '../button/button'
|
||||
|
||||
let DEFAULT_BUTTON_TAG = 'button' as const
|
||||
|
||||
export type CloseButtonProps<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG> =
|
||||
ButtonProps<TTag>
|
||||
|
||||
function CloseButtonFn<TTag extends ElementType = typeof DEFAULT_BUTTON_TAG>(
|
||||
props: ButtonProps<TTag>,
|
||||
ref: Ref<HTMLElement>
|
||||
) {
|
||||
let close = useClose()
|
||||
return <Button ref={ref} {...mergeProps({ onClick: close }, props)} />
|
||||
}
|
||||
|
||||
export let CloseButton = forwardRefWithAs(CloseButtonFn) as _internal_ComponentButton
|
||||
@@ -30,6 +30,7 @@ import { useOwnerDocument } from '../../hooks/use-owner'
|
||||
import { useRootContainers } from '../../hooks/use-root-containers'
|
||||
import { useServerHandoffComplete } from '../../hooks/use-server-handoff-complete'
|
||||
import { useSyncRefs } from '../../hooks/use-sync-refs'
|
||||
import { CloseProvider } from '../../internal/close-provider'
|
||||
import { HoistFormFields } from '../../internal/form-fields'
|
||||
import { State, useOpenClosed } from '../../internal/open-closed'
|
||||
import { ForcePortalRoot } from '../../internal/portal-force-root'
|
||||
@@ -410,15 +411,17 @@ function DialogFn<TTag extends ElementType = typeof DEFAULT_DIALOG_TAG>(
|
||||
containers={resolveRootContainers}
|
||||
features={focusTrapFeatures}
|
||||
>
|
||||
{render({
|
||||
ourProps,
|
||||
theirProps,
|
||||
slot,
|
||||
defaultTag: DEFAULT_DIALOG_TAG,
|
||||
features: DialogRenderFeatures,
|
||||
visible: dialogState === DialogStates.Open,
|
||||
name: 'Dialog',
|
||||
})}
|
||||
<CloseProvider value={close}>
|
||||
{render({
|
||||
ourProps,
|
||||
theirProps,
|
||||
slot,
|
||||
defaultTag: DEFAULT_DIALOG_TAG,
|
||||
features: DialogRenderFeatures,
|
||||
visible: dialogState === DialogStates.Open,
|
||||
name: 'Dialog',
|
||||
})}
|
||||
</CloseProvider>
|
||||
</FocusTrap>
|
||||
</PortalWrapper>
|
||||
</DescriptionProvider>
|
||||
|
||||
@@ -24,6 +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 { CloseProvider } from '../../internal/close-provider'
|
||||
import { OpenClosedProvider, State, useOpenClosed } from '../../internal/open-closed'
|
||||
import type { Props } from '../../types'
|
||||
import { isDisabledReactIssue7711 } from '../../utils/bugs'
|
||||
@@ -234,20 +235,22 @@ function DisclosureFn<TTag extends ElementType = typeof DEFAULT_DISCLOSURE_TAG>(
|
||||
return (
|
||||
<DisclosureContext.Provider value={reducerBag}>
|
||||
<DisclosureAPIContext.Provider value={api}>
|
||||
<OpenClosedProvider
|
||||
value={match(disclosureState, {
|
||||
[DisclosureStates.Open]: State.Open,
|
||||
[DisclosureStates.Closed]: State.Closed,
|
||||
})}
|
||||
>
|
||||
{render({
|
||||
ourProps,
|
||||
theirProps,
|
||||
slot,
|
||||
defaultTag: DEFAULT_DISCLOSURE_TAG,
|
||||
name: 'Disclosure',
|
||||
})}
|
||||
</OpenClosedProvider>
|
||||
<CloseProvider value={close}>
|
||||
<OpenClosedProvider
|
||||
value={match(disclosureState, {
|
||||
[DisclosureStates.Open]: State.Open,
|
||||
[DisclosureStates.Closed]: State.Closed,
|
||||
})}
|
||||
>
|
||||
{render({
|
||||
ourProps,
|
||||
theirProps,
|
||||
slot,
|
||||
defaultTag: DEFAULT_DISCLOSURE_TAG,
|
||||
name: 'Disclosure',
|
||||
})}
|
||||
</OpenClosedProvider>
|
||||
</CloseProvider>
|
||||
</DisclosureAPIContext.Provider>
|
||||
</DisclosureContext.Provider>
|
||||
)
|
||||
|
||||
@@ -36,6 +36,7 @@ import { useResolveButtonType } from '../../hooks/use-resolve-button-type'
|
||||
import { useMainTreeNode, useRootContainers } from '../../hooks/use-root-containers'
|
||||
import { optionalRef, useSyncRefs } from '../../hooks/use-sync-refs'
|
||||
import { Direction as TabDirection, useTabDirection } from '../../hooks/use-tab-direction'
|
||||
import { CloseProvider } from '../../internal/close-provider'
|
||||
import {
|
||||
FloatingProvider,
|
||||
useFloatingPanel,
|
||||
@@ -410,23 +411,25 @@ function PopoverFn<TTag extends ElementType = typeof DEFAULT_POPOVER_TAG>(
|
||||
<PopoverPanelContext.Provider value={null}>
|
||||
<PopoverContext.Provider value={reducerBag}>
|
||||
<PopoverAPIContext.Provider value={api}>
|
||||
<OpenClosedProvider
|
||||
value={match(popoverState, {
|
||||
[PopoverStates.Open]: State.Open,
|
||||
[PopoverStates.Closed]: State.Closed,
|
||||
})}
|
||||
>
|
||||
<PortalWrapper>
|
||||
{render({
|
||||
ourProps,
|
||||
theirProps,
|
||||
slot,
|
||||
defaultTag: DEFAULT_POPOVER_TAG,
|
||||
name: 'Popover',
|
||||
<CloseProvider value={close}>
|
||||
<OpenClosedProvider
|
||||
value={match(popoverState, {
|
||||
[PopoverStates.Open]: State.Open,
|
||||
[PopoverStates.Closed]: State.Closed,
|
||||
})}
|
||||
<root.MainTreeNode />
|
||||
</PortalWrapper>
|
||||
</OpenClosedProvider>
|
||||
>
|
||||
<PortalWrapper>
|
||||
{render({
|
||||
ourProps,
|
||||
theirProps,
|
||||
slot,
|
||||
defaultTag: DEFAULT_POPOVER_TAG,
|
||||
name: 'Popover',
|
||||
})}
|
||||
<root.MainTreeNode />
|
||||
</PortalWrapper>
|
||||
</OpenClosedProvider>
|
||||
</CloseProvider>
|
||||
</PopoverAPIContext.Provider>
|
||||
</PopoverContext.Provider>
|
||||
</PopoverPanelContext.Provider>
|
||||
|
||||
@@ -10,6 +10,8 @@ it('should expose the correct components', () => {
|
||||
|
||||
'Checkbox',
|
||||
|
||||
'CloseButton',
|
||||
|
||||
'Combobox',
|
||||
'ComboboxButton',
|
||||
'ComboboxInput',
|
||||
@@ -90,5 +92,7 @@ it('should expose the correct components', () => {
|
||||
|
||||
'Transition',
|
||||
'TransitionChild',
|
||||
|
||||
'useClose',
|
||||
])
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './components/button/button'
|
||||
export * from './components/checkbox/checkbox'
|
||||
export * from './components/close-button/close-button'
|
||||
export * from './components/combobox/combobox'
|
||||
export * from './components/data-interactive/data-interactive'
|
||||
export { Description, type DescriptionProps } from './components/description/description'
|
||||
@@ -20,6 +21,7 @@ export * from './components/select/select'
|
||||
export * from './components/switch/switch'
|
||||
export * from './components/tabs/tabs'
|
||||
export * from './components/textarea/textarea'
|
||||
export { useClose } from './internal/close-provider'
|
||||
// TODO: Enable when ready
|
||||
// export * from './components/tooltip/tooltip'
|
||||
export * from './components/transition/transition'
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import React, { createContext, useContext } from 'react'
|
||||
|
||||
let CloseContext = createContext(() => {})
|
||||
|
||||
export function useClose() {
|
||||
return useContext(CloseContext)
|
||||
}
|
||||
|
||||
export function CloseProvider({ value, children }: React.PropsWithChildren<{ value: () => void }>) {
|
||||
return <CloseContext.Provider value={value}>{children}</CloseContext.Provider>
|
||||
}
|
||||
Reference in New Issue
Block a user