Allow passing a boolean to the anchor prop (#3121)
This commit is contained in:
@@ -41,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- 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))
|
||||
- Allow passing a boolean to the `anchor` prop ([#3121](https://github.com/tailwindlabs/headlessui/pull/3121))
|
||||
|
||||
## [1.7.19] - 2024-04-15
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
useFloatingPanel,
|
||||
useFloatingPanelProps,
|
||||
useFloatingReference,
|
||||
useResolvedAnchor,
|
||||
type AnchorProps,
|
||||
} from '../../internal/floating'
|
||||
import { FormFields } from '../../internal/form-fields'
|
||||
@@ -1546,11 +1547,12 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
|
||||
let {
|
||||
id = `headlessui-combobox-options-${internalId}`,
|
||||
hold = false,
|
||||
anchor,
|
||||
anchor: rawAnchor,
|
||||
...theirProps
|
||||
} = props
|
||||
let data = useData('Combobox.Options')
|
||||
let actions = useActions('Combobox.Options')
|
||||
let anchor = useResolvedAnchor(rawAnchor)
|
||||
|
||||
let [floatingRef, style] = useFloatingPanel(anchor)
|
||||
let getFloatingPanelProps = useFloatingPanelProps()
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
useFloatingPanelProps,
|
||||
useFloatingReference,
|
||||
useFloatingReferenceProps,
|
||||
useResolvedAnchor,
|
||||
type AnchorPropsWithSelection,
|
||||
} from '../../internal/floating'
|
||||
import { FormFields } from '../../internal/form-fields'
|
||||
@@ -878,13 +879,17 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
|
||||
ref: Ref<HTMLElement>
|
||||
) {
|
||||
let internalId = useId()
|
||||
let { id = `headlessui-listbox-options-${internalId}`, anchor, modal, ...theirProps } = props
|
||||
let {
|
||||
id = `headlessui-listbox-options-${internalId}`,
|
||||
anchor: rawAnchor,
|
||||
modal,
|
||||
...theirProps
|
||||
} = props
|
||||
let anchor = useResolvedAnchor(rawAnchor)
|
||||
|
||||
// Always use `modal` when `anchor` is passed in
|
||||
if (anchor != null && modal == null) {
|
||||
modal = true
|
||||
} else if (modal == null) {
|
||||
modal = false
|
||||
if (modal == null) {
|
||||
modal = Boolean(anchor)
|
||||
}
|
||||
|
||||
let data = useData('Listbox.Options')
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
useFloatingPanelProps,
|
||||
useFloatingReference,
|
||||
useFloatingReferenceProps,
|
||||
useResolvedAnchor,
|
||||
type AnchorProps,
|
||||
} from '../../internal/floating'
|
||||
import { Modal, ModalFeatures, type ModalProps } from '../../internal/modal'
|
||||
@@ -589,7 +590,13 @@ function ItemsFn<TTag extends ElementType = typeof DEFAULT_ITEMS_TAG>(
|
||||
ref: Ref<HTMLDivElement>
|
||||
) {
|
||||
let internalId = useId()
|
||||
let { id = `headlessui-menu-items-${internalId}`, anchor, modal, ...theirProps } = props
|
||||
let {
|
||||
id = `headlessui-menu-items-${internalId}`,
|
||||
anchor: rawAnchor,
|
||||
modal,
|
||||
...theirProps
|
||||
} = props
|
||||
let anchor = useResolvedAnchor(rawAnchor)
|
||||
let [state, dispatch] = useMenuContext('Menu.Items')
|
||||
let [floatingRef, style] = useFloatingPanel(anchor)
|
||||
let getFloatingPanelProps = useFloatingPanelProps()
|
||||
@@ -597,10 +604,8 @@ function ItemsFn<TTag extends ElementType = typeof DEFAULT_ITEMS_TAG>(
|
||||
let ownerDocument = useOwnerDocument(state.itemsRef)
|
||||
|
||||
// Always use `modal` when `anchor` is passed in
|
||||
if (anchor != null && modal == null) {
|
||||
modal = true
|
||||
} else if (modal == null) {
|
||||
modal = false
|
||||
if (modal == null) {
|
||||
modal = Boolean(anchor)
|
||||
}
|
||||
|
||||
let searchDisposables = useDisposables()
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
useFloatingPanel,
|
||||
useFloatingPanelProps,
|
||||
useFloatingReference,
|
||||
useResolvedAnchor,
|
||||
type AnchorProps,
|
||||
} from '../../internal/floating'
|
||||
import { Hidden, HiddenFeatures } from '../../internal/hidden'
|
||||
@@ -815,7 +816,7 @@ function PanelFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
|
||||
let {
|
||||
id = `headlessui-popover-panel-${internalId}`,
|
||||
focus = false,
|
||||
anchor,
|
||||
anchor: rawAnchor,
|
||||
modal,
|
||||
...theirProps
|
||||
} = props
|
||||
@@ -827,14 +828,13 @@ function PanelFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
|
||||
let afterPanelSentinelId = `headlessui-focus-sentinel-after-${internalId}`
|
||||
|
||||
let internalPanelRef = useRef<HTMLDivElement | null>(null)
|
||||
let anchor = useResolvedAnchor(rawAnchor)
|
||||
let [floatingRef, style] = useFloatingPanel(anchor)
|
||||
let getFloatingPanelProps = useFloatingPanelProps()
|
||||
|
||||
// Always use `modal` when `anchor` is passed in
|
||||
if (anchor != null && modal == null) {
|
||||
modal = true
|
||||
} else if (modal == null) {
|
||||
modal = false
|
||||
if (modal == null) {
|
||||
modal = Boolean(anchor)
|
||||
}
|
||||
|
||||
let panelRef = useSyncRefs(internalPanelRef, ref, anchor ? floatingRef : null, (panel) => {
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
FloatingProvider,
|
||||
useFloatingPanel,
|
||||
useFloatingReference,
|
||||
useResolvedAnchor,
|
||||
type AnchorProps,
|
||||
} from '../../internal/floating'
|
||||
import { State, useOpenClosed } from '../../internal/open-closed'
|
||||
@@ -422,15 +423,7 @@ function PanelFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
|
||||
props: TooltipPanelProps<TTag>,
|
||||
ref: Ref<HTMLElement>
|
||||
) {
|
||||
let {
|
||||
anchor = {
|
||||
to: 'top',
|
||||
padding: 8,
|
||||
gap: 8,
|
||||
offset: -4,
|
||||
} as AnchorProps,
|
||||
...theirProps
|
||||
} = props
|
||||
let { anchor: rawAnchor, ...theirProps } = props
|
||||
let data = useData('TooltipPanel')
|
||||
|
||||
let usesOpenClosedState = useOpenClosed()
|
||||
@@ -443,6 +436,7 @@ function PanelFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
|
||||
})()
|
||||
|
||||
let internalPanelRef = useRef<HTMLElement | null>(null)
|
||||
let anchor = useResolvedAnchor(rawAnchor ?? { to: 'top', padding: 8, gap: 8, offset: -4 })
|
||||
let [floatingRef, style] = useFloatingPanel(visible ? anchor : undefined)
|
||||
let panelRef = useSyncRefs(internalPanelRef, ref, floatingRef)
|
||||
|
||||
|
||||
@@ -37,25 +37,29 @@ type BaseAnchorProps = {
|
||||
padding: number | string // For `var()` support
|
||||
}
|
||||
|
||||
export type AnchorProps = Partial<
|
||||
BaseAnchorProps & {
|
||||
/**
|
||||
* The `to` value defines which side of the trigger the panel should be placed on and its
|
||||
* alignment.
|
||||
*/
|
||||
to: `${Placement}` | `${Placement} ${Align}`
|
||||
}
|
||||
>
|
||||
export type AnchorProps =
|
||||
| boolean // Enable with defaults, or disable entirely
|
||||
| Partial<
|
||||
BaseAnchorProps & {
|
||||
/**
|
||||
* The `to` value defines which side of the trigger the panel should be placed on and its
|
||||
* alignment.
|
||||
*/
|
||||
to: `${Placement}` | `${Placement} ${Align}`
|
||||
}
|
||||
>
|
||||
|
||||
export type AnchorPropsWithSelection = Partial<
|
||||
BaseAnchorProps & {
|
||||
/**
|
||||
* The `to` value defines which side of the trigger the panel should be placed on and its
|
||||
* alignment.
|
||||
*/
|
||||
to: `${Placement | 'selection'}` | `${Placement | 'selection'} ${Align}`
|
||||
}
|
||||
>
|
||||
export type AnchorPropsWithSelection =
|
||||
| boolean // Enable with defaults, or disable entirely
|
||||
| Partial<
|
||||
BaseAnchorProps & {
|
||||
/**
|
||||
* The `to` value defines which side of the trigger the panel should be placed on and its
|
||||
* alignment.
|
||||
*/
|
||||
to: `${Placement | 'selection'}` | `${Placement | 'selection'} ${Align}`
|
||||
}
|
||||
>
|
||||
|
||||
export type InternalFloatingPanelProps = Partial<{
|
||||
inner: {
|
||||
@@ -82,11 +86,21 @@ let FloatingContext = createContext<{
|
||||
slot: {},
|
||||
})
|
||||
FloatingContext.displayName = 'FloatingContext'
|
||||
let PlacementContext = createContext<((value: AnchorPropsWithSelection | null) => void) | null>(
|
||||
null
|
||||
)
|
||||
let PlacementContext = createContext<
|
||||
((value: Exclude<AnchorPropsWithSelection, boolean> | null) => void) | null
|
||||
>(null)
|
||||
PlacementContext.displayName = 'PlacementContext'
|
||||
|
||||
export function useResolvedAnchor<T extends AnchorProps | AnchorPropsWithSelection>(
|
||||
anchor?: T
|
||||
): Exclude<T, boolean> | null {
|
||||
return useMemo(() => {
|
||||
if (anchor === true) return {} as Exclude<T, boolean> // Enable with defaults
|
||||
if (!anchor) return null // Disable entirely
|
||||
return anchor as Exclude<T, boolean> // User-provided value
|
||||
}, [anchor])
|
||||
}
|
||||
|
||||
export function useFloatingReference() {
|
||||
return useContext(FloatingContext).setReference
|
||||
}
|
||||
@@ -108,8 +122,11 @@ export function useFloatingPanelProps() {
|
||||
}
|
||||
|
||||
export function useFloatingPanel(
|
||||
placement?: AnchorPropsWithSelection & InternalFloatingPanelProps
|
||||
placement: (AnchorPropsWithSelection & InternalFloatingPanelProps) | null = null
|
||||
) {
|
||||
if (placement === true) placement = {} // Enable with defaults
|
||||
if (placement === false) placement = null // Disable entirely
|
||||
|
||||
let updatePlacementConfig = useContext(PlacementContext)
|
||||
let stablePlacement = useMemo(
|
||||
() => placement,
|
||||
@@ -372,7 +389,7 @@ function useFixScrollingPixel(element: HTMLElement | null) {
|
||||
}
|
||||
|
||||
function useResolvedConfig(
|
||||
config: (AnchorPropsWithSelection & InternalFloatingPanelProps) | null,
|
||||
config: (Exclude<AnchorPropsWithSelection, boolean> & InternalFloatingPanelProps) | null,
|
||||
element?: HTMLElement | null
|
||||
) {
|
||||
let gap = useResolvePxValue(config?.gap, element)
|
||||
|
||||
Reference in New Issue
Block a user