Skip to content

Commit

Permalink
Add string shorthand for the anchor prop (#3133)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
RobinMalfait committed Apr 25, 2024
1 parent 36616b2 commit 8c7cbb3
Show file tree
Hide file tree
Showing 4 changed files with 19 additions and 16 deletions.
1 change: 1 addition & 0 deletions packages/@headlessui-react/CHANGELOG.md
Expand Up @@ -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

Expand Down
22 changes: 12 additions & 10 deletions packages/@headlessui-react/src/internal/floating.tsx
Expand Up @@ -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 & {
/**
Expand All @@ -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 & {
/**
Expand Down Expand Up @@ -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])
}

Expand All @@ -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(
Expand Down Expand Up @@ -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 }
}
Expand Down
4 changes: 2 additions & 2 deletions playgrounds/react/pages/menu/menu-with-transition.tsx
Expand Up @@ -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>
Expand Down
8 changes: 4 additions & 4 deletions playgrounds/react/pages/popover/popover.tsx
Expand Up @@ -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>
Expand All @@ -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>
Expand Down

0 comments on commit 8c7cbb3

Please sign in to comment.