Skip to content

Commit

Permalink
Add new CloseButton component and useClose hook (#3096)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
RobinMalfait authored Apr 12, 2024
1 parent 00f84ac commit b86737b
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 39 deletions.
1 change: 1 addition & 0 deletions packages/@headlessui-react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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
21 changes: 12 additions & 9 deletions packages/@headlessui-react/src/components/dialog/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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>
Expand Down
31 changes: 17 additions & 14 deletions packages/@headlessui-react/src/components/disclosure/disclosure.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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>
)
Expand Down
35 changes: 19 additions & 16 deletions packages/@headlessui-react/src/components/popover/popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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>
Expand Down
4 changes: 4 additions & 0 deletions packages/@headlessui-react/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ it('should expose the correct components', () => {

'Checkbox',

'CloseButton',

'Combobox',
'ComboboxButton',
'ComboboxInput',
Expand Down Expand Up @@ -90,5 +92,7 @@ it('should expose the correct components', () => {

'Transition',
'TransitionChild',

'useClose',
])
})
2 changes: 2 additions & 0 deletions packages/@headlessui-react/src/index.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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'
11 changes: 11 additions & 0 deletions packages/@headlessui-react/src/internal/close-provider.tsx
Original file line number Diff line number Diff line change
@@ -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>
}

0 comments on commit b86737b

Please sign in to comment.