Add string shorthand for the anchor prop (#3133)

* allow to define `anchor` as a string. E.g.: `anchor="bottom"`

* use `--anchor-gap`, `--anchor-offset` and `--anchor-padding` variables by default

This way simply adding `anchor="bottom"` to one of the anchorable
components will also use these variables defined on the component.

* update playgrounds to use new string-based `anchor` prop

+ CSS variables

* update changelog
This commit is contained in:
Robin Malfait
2024-04-25 02:13:25 +02:00
committed by GitHub
parent 36616b217e
commit 8c7cbb3b09
4 changed files with 19 additions and 16 deletions
+1
View File
@@ -47,6 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Allow passing a boolean to the `anchor` prop ([#3121](https://github.com/tailwindlabs/headlessui/pull/3121))
- Add `portal` prop to `Combobox`, `Listbox`, `Menu` and `Popover` components ([#3124](https://github.com/tailwindlabs/headlessui/pull/3124))
- Add frozen value to `ComboboxOptions` component ([#3126](https://github.com/tailwindlabs/headlessui/pull/3126))
- Add string shorthand for the `anchor` prop ([#3133](https://github.com/tailwindlabs/headlessui/pull/3133))
## [1.7.19] - 2024-04-15
@@ -38,7 +38,8 @@ type BaseAnchorProps = {
}
export type AnchorProps =
| boolean // Enable with defaults, or disable entirely
| false // Disable entirely
| (`${Placement}` | `${Placement} ${Align}`) // String value to define the placement
| Partial<
BaseAnchorProps & {
/**
@@ -50,7 +51,8 @@ export type AnchorProps =
>
export type AnchorPropsWithSelection =
| boolean // Enable with defaults, or disable entirely
| false // Disable entirely
| (`${Placement | 'selection'}` | `${Placement | 'selection'} ${Align}`)
| Partial<
BaseAnchorProps & {
/**
@@ -93,11 +95,11 @@ PlacementContext.displayName = 'PlacementContext'
export function useResolvedAnchor<T extends AnchorProps | AnchorPropsWithSelection>(
anchor?: T
): Exclude<T, boolean> | null {
): Exclude<T, boolean | string> | 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
if (typeof anchor === 'string') return { to: anchor } as Exclude<T, boolean | string> // Simple string based value,
return anchor as Exclude<T, boolean | string> // User-provided value
}, [anchor])
}
@@ -124,8 +126,8 @@ export function useFloatingPanelProps() {
export function useFloatingPanel(
placement: (AnchorPropsWithSelection & InternalFloatingPanelProps) | null = null
) {
if (placement === true) placement = {} // Enable with defaults
if (placement === false) placement = null // Disable entirely
if (typeof placement === 'string') placement = { to: placement } // Simple string based value
let updatePlacementConfig = useContext(PlacementContext)
let stablePlacement = useMemo(
@@ -389,12 +391,12 @@ function useFixScrollingPixel(element: HTMLElement | null) {
}
function useResolvedConfig(
config: (Exclude<AnchorPropsWithSelection, boolean> & InternalFloatingPanelProps) | null,
config: (Exclude<AnchorPropsWithSelection, boolean | string> & InternalFloatingPanelProps) | null,
element?: HTMLElement | null
) {
let gap = useResolvePxValue(config?.gap, element)
let offset = useResolvePxValue(config?.offset, element)
let padding = useResolvePxValue(config?.padding, element)
let gap = useResolvePxValue(config?.gap ?? 'var(--anchor-gap, 0)', element)
let offset = useResolvePxValue(config?.offset ?? 'var(--anchor-offset, 0)', element)
let padding = useResolvePxValue(config?.padding ?? 'var(--anchor-padding, 0)', element)
return { ...config, gap, offset, padding }
}
@@ -41,8 +41,8 @@ export default function Home() {
afterLeave={() => console.log('After leave')}
>
<Menu.Items
anchor={{ to: 'bottom start', gap: 'var(--gap)' }}
className="w-[calc(var(--button-width)*2)] divide-y divide-gray-100 rounded-md border border-gray-200 bg-white shadow-lg outline-none [--gap:theme(spacing.2)]"
anchor="bottom start"
className="w-[calc(var(--button-width)*2)] divide-y divide-gray-100 rounded-md border border-gray-200 bg-white shadow-lg outline-none [--anchor-gap:theme(spacing.2)]"
>
<div className="px-4 py-3">
<p className="text-sm leading-5">Signed in as</p>
+4 -4
View File
@@ -60,8 +60,8 @@ export default function Home() {
<Popover as="div" className="relative">
<Button>Portal</Button>
<Popover.Panel
anchor={{ to: 'bottom start', gap: 4 }}
className="flex w-64 flex-col border-2 border-blue-900 bg-gray-100"
anchor="bottom start"
className="flex w-64 flex-col border-2 border-blue-900 bg-gray-100 [--anchor-gap:theme(spacing.1)]"
>
{items.map((item) => (
<Button key={item}>Portal - {item}</Button>
@@ -72,9 +72,9 @@ export default function Home() {
<Popover as="div" className="relative">
<Button>Focus in Portal</Button>
<Popover.Panel
anchor={{ to: 'bottom start', gap: 4 }}
focus
className="flex w-64 flex-col border-2 border-blue-900 bg-gray-100"
anchor="bottom start"
className="flex w-64 flex-col border-2 border-blue-900 bg-gray-100 [--anchor-gap:theme(spacing.1)]"
>
{items.map((item) => (
<Button key={item}>Focus in Portal - {item}</Button>