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:
Robin Malfait
2024-04-12 15:54:01 +02:00
committed by GitHub
parent 00f84acb21
commit b86737b698
8 changed files with 87 additions and 39 deletions
+1
View File
@@ -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',
])
})
+2
View File
@@ -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>
}