Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve floating element adaptive width api #20

Merged
merged 2 commits into from
Sep 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions examples/example-react-ts/src/components/ExampleCombobox.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react'
import { Fragment, useState } from 'react'
import { Combobox } from '@headlessui/react'
import { Float } from '@headlessui-float/react'
import Block from '@/components/Block'
Expand Down Expand Up @@ -32,16 +32,19 @@ export default function ExampleCombobox() {
<Block title="Combobox (Autocomplete)" titleClass="text-teal-400">
<Combobox value={selected} onChange={setSelected}>
<Float
as="div"
className="relative w-64"
placement="bottom-start"
offset={4}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
floatingAs={Fragment}
onHide={() => setQuery('')}
>
<div className="relative w-64 text-left bg-white border border-gray-200 rounded-lg shadow-md cursor-default focus:outline-none sm:text-sm overflow-hidden">
<div className="relative w-full text-left bg-white border border-gray-200 rounded-lg shadow-md cursor-default focus:outline-none sm:text-sm overflow-hidden">
<Combobox.Input
className="w-64 border-none py-2 pl-3 pr-10 text-sm leading-5 text-gray-900 focus:outline-none focus:ring-0"
className="w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-gray-900 focus:outline-none focus:ring-0"
displayValue={(person: any) => person.name}
onChange={event => setQuery(event.target.value)}
/>
Expand All @@ -51,7 +54,7 @@ export default function ExampleCombobox() {
</Combobox.Button>
</div>

<Combobox.Options className="absolute w-64 py-1 overflow-auto text-base bg-white rounded-md shadow-lg max-h-60 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
<Combobox.Options className="absolute w-full py-1 overflow-auto text-base bg-white rounded-md shadow-lg max-h-60 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
{filteredPeople.length === 0 && query !== '' ? (
<div className="relative py-2 px-4 text-gray-700 cursor-default select-none">
Nothing found.
Expand Down
15 changes: 11 additions & 4 deletions examples/example-react-ts/src/components/ExampleListbox.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react'
import { Fragment, useState } from 'react'
import { Listbox } from '@headlessui/react'
import { Float } from '@headlessui-float/react'
import Block from '@/components/Block'
Expand All @@ -19,15 +19,22 @@ export default function ExampleListbox() {
return (
<Block title="Listbox (Select)" titleClass="text-amber-400">
<Listbox value={selected} onChange={setSelected}>
<Float placement="bottom" offset={4} flip={10}>
<Listbox.Button className="relative w-56 bg-white pl-3.5 pr-10 py-2 text-left text-amber-500 text-sm leading-5 border border-gray-200 rounded-lg shadow-md">
<Float
as="div"
className="relative w-56"
placement="bottom"
offset={4}
flip={10}
floatingAs={Fragment}
>
<Listbox.Button className="relative w-full bg-white pl-3.5 pr-10 py-2 text-left text-amber-500 text-sm leading-5 border border-gray-200 rounded-lg shadow-md">
{selected.name}
<span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<HeroiconsOutlineSelector className="w-5 h-5 text-gray-400" aria-hidden="true" />
</span>
</Listbox.Button>

<Listbox.Options className="w-56 bg-white border border-gray-200 rounded-md shadow-lg overflow-hidden focus:outline-none">
<Listbox.Options className="w-full bg-white border border-gray-200 rounded-md shadow-lg overflow-hidden focus:outline-none">
{people.map(person => (
<Listbox.Option
key={person.id}
Expand Down
21 changes: 12 additions & 9 deletions examples/example-react/src/components/ExampleCombobox.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react'
import { Fragment, useState } from 'react'
import { Combobox } from '@headlessui/react'
import { Float } from '@headlessui-float/react'
import Block from '@/components/Block'
Expand All @@ -22,26 +22,29 @@ export default function ExampleCombobox() {
query === ''
? people
: people.filter(person =>
person.name
.toLowerCase()
.replace(/\s+/g, '')
.includes(query.toLowerCase().replace(/\s+/g, ''))
)
person.name
.toLowerCase()
.replace(/\s+/g, '')
.includes(query.toLowerCase().replace(/\s+/g, ''))
)

return (
<Block title="Combobox (Autocomplete)" titleClass="text-teal-400">
<Combobox value={selected} onChange={setSelected}>
<Float
as="div"
className="relative w-64"
placement="bottom-start"
offset={4}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
floatingAs={Fragment}
onHide={() => setQuery('')}
>
<div className="relative w-64 text-left bg-white border border-gray-200 rounded-lg shadow-md cursor-default focus:outline-none sm:text-sm overflow-hidden">
<div className="relative w-full text-left bg-white border border-gray-200 rounded-lg shadow-md cursor-default focus:outline-none sm:text-sm overflow-hidden">
<Combobox.Input
className="w-64 border-none py-2 pl-3 pr-10 text-sm leading-5 text-gray-900 focus:outline-none focus:ring-0"
className="w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-gray-900 focus:outline-none focus:ring-0"
displayValue={person => person.name}
onChange={event => setQuery(event.target.value)}
/>
Expand All @@ -51,7 +54,7 @@ export default function ExampleCombobox() {
</Combobox.Button>
</div>

<Combobox.Options className="absolute w-64 py-1 overflow-auto text-base bg-white rounded-md shadow-lg max-h-60 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
<Combobox.Options className="absolute w-full py-1 overflow-auto text-base bg-white rounded-md shadow-lg max-h-60 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
{filteredPeople.length === 0 && query !== '' ? (
<div className="relative py-2 px-4 text-gray-700 cursor-default select-none">
Nothing found.
Expand Down
15 changes: 11 additions & 4 deletions examples/example-react/src/components/ExampleListbox.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react'
import { Fragment, useState } from 'react'
import { Listbox } from '@headlessui/react'
import { Float } from '@headlessui-float/react'
import Block from '@/components/Block'
Expand All @@ -19,15 +19,22 @@ export default function ExampleListbox() {
return (
<Block title="Listbox (Select)" titleClass="text-amber-400">
<Listbox value={selected} onChange={setSelected}>
<Float placement="bottom" offset={4} flip={10}>
<Listbox.Button className="relative w-56 bg-white pl-3.5 pr-10 py-2 text-left text-amber-500 text-sm leading-5 border border-gray-200 rounded-lg shadow-md">
<Float
as="div"
className="relative w-56"
placement="bottom"
offset={4}
flip={10}
floatingAs={Fragment}
>
<Listbox.Button className="relative w-full bg-white pl-3.5 pr-10 py-2 text-left text-amber-500 text-sm leading-5 border border-gray-200 rounded-lg shadow-md">
{selected.name}
<span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<HeroiconsOutlineSelector className="w-5 h-5 text-gray-400" aria-hidden="true" />
</span>
</Listbox.Button>

<Listbox.Options className="w-56 bg-white border border-gray-200 rounded-md shadow-lg overflow-hidden focus:outline-none">
<Listbox.Options className="w-full bg-white border border-gray-200 rounded-md shadow-lg overflow-hidden focus:outline-none">
{people.map(person => (
<Listbox.Option
key={person.id}
Expand Down
9 changes: 6 additions & 3 deletions examples/example-vue-ts/src/components/ExampleCombobox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@
<Block title="Combobox (Autocomplete)" title-class="text-teal-400">
<Combobox v-model="selected">
<Float
as="div"
class="relative w-64"
placement="bottom-start"
:offset="4"
leave="transition ease-in duration-100"
leave-from="opacity-100"
leave-to="opacity-0"
floating-as="template"
@hide="query = ''"
>
<div class="relative w-64 text-left bg-white border border-gray-200 rounded-lg shadow-md cursor-default focus:outline-none sm:text-sm overflow-hidden">
<div class="relative w-full text-left bg-white border border-gray-200 rounded-lg shadow-md cursor-default focus:outline-none sm:text-sm overflow-hidden">
<ComboboxInput
class="w-64 border-none py-2 pl-3 pr-10 text-sm leading-5 text-gray-900 focus:outline-none focus:ring-0"
class="w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-gray-900 focus:outline-none focus:ring-0"
:display-value="(person: any) => person.name"
@change="query = $event.target.value"
/>
Expand All @@ -21,7 +24,7 @@
</ComboboxButton>
</div>

<ComboboxOptions class="absolute w-64 py-1 overflow-auto text-base bg-white rounded-md shadow-lg max-h-60 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
<ComboboxOptions class="absolute w-full py-1 overflow-auto text-base bg-white rounded-md shadow-lg max-h-60 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
<div
v-if="filteredPeople.length === 0 && query !== ''"
class="relative py-2 px-4 text-gray-700 cursor-default select-none"
Expand Down
13 changes: 10 additions & 3 deletions examples/example-vue-ts/src/components/ExampleListbox.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
<template>
<Block title="Listbox (Select)" title-class="text-amber-400">
<Listbox v-model="selected">
<Float placement="bottom" :offset="4" :flip="10">
<ListboxButton class="relative w-56 bg-white pl-3.5 pr-10 py-2 text-left text-amber-500 text-sm leading-5 border border-gray-200 rounded-lg shadow-md">
<Float
as="div"
class="relative w-56"
placement="bottom"
:offset="4"
:flip="10"
floating-as="template"
>
<ListboxButton class="relative w-full bg-white pl-3.5 pr-10 py-2 text-left text-amber-500 text-sm leading-5 border border-gray-200 rounded-lg shadow-md">
{{ selected.name }}
<span class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<HeroiconsOutlineSelector class="w-5 h-5 text-gray-400" aria-hidden="true" />
</span>
</ListboxButton>

<ListboxOptions class="w-56 bg-white border border-gray-200 rounded-md shadow-lg overflow-hidden focus:outline-none">
<ListboxOptions class="w-full bg-white border border-gray-200 rounded-md shadow-lg overflow-hidden focus:outline-none">
<ListboxOption
v-for="person in people"
v-slot="{ active, selected }"
Expand Down
9 changes: 6 additions & 3 deletions examples/example-vue/src/components/ExampleCombobox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@
<Block title="Combobox (Autocomplete)" title-class="text-teal-400">
<Combobox v-model="selected">
<Float
as="div"
class="relative w-64"
placement="bottom-start"
:offset="4"
leave="transition ease-in duration-100"
leave-from="opacity-100"
leave-to="opacity-0"
floating-as="template"
@hide="query = ''"
>
<div class="relative w-64 text-left bg-white border border-gray-200 rounded-lg shadow-md cursor-default focus:outline-none sm:text-sm overflow-hidden">
<div class="relative w-full text-left bg-white border border-gray-200 rounded-lg shadow-md cursor-default focus:outline-none sm:text-sm overflow-hidden">
<ComboboxInput
class="w-64 border-none py-2 pl-3 pr-10 text-sm leading-5 text-gray-900 focus:outline-none focus:ring-0"
class="w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-gray-900 focus:outline-none focus:ring-0"
:display-value="person => person.name"
@change="query = $event.target.value"
/>
Expand All @@ -21,7 +24,7 @@
</ComboboxButton>
</div>

<ComboboxOptions class="absolute w-64 py-1 overflow-auto text-base bg-white rounded-md shadow-lg max-h-60 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
<ComboboxOptions class="absolute w-full py-1 overflow-auto text-base bg-white rounded-md shadow-lg max-h-60 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
<div
v-if="filteredPeople.length === 0 && query !== ''"
class="relative py-2 px-4 text-gray-700 cursor-default select-none"
Expand Down
13 changes: 10 additions & 3 deletions examples/example-vue/src/components/ExampleListbox.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
<template>
<Block title="Listbox (Select)" title-class="text-amber-400">
<Listbox v-model="selected">
<Float placement="bottom" :offset="4" :flip="10">
<ListboxButton class="relative w-56 bg-white pl-3.5 pr-10 py-2 text-left text-amber-500 text-sm leading-5 border border-gray-200 rounded-lg shadow-md">
<Float
as="div"
class="relative w-56"
placement="bottom"
:offset="4"
:flip="10"
floating-as="template"
>
<ListboxButton class="relative w-full bg-white pl-3.5 pr-10 py-2 text-left text-amber-500 text-sm leading-5 border border-gray-200 rounded-lg shadow-md">
{{ selected.name }}
<span class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<HeroiconsOutlineSelector class="w-5 h-5 text-gray-400" aria-hidden="true" />
</span>
</ListboxButton>

<ListboxOptions class="w-56 bg-white border border-gray-200 rounded-md shadow-lg overflow-hidden focus:outline-none">
<ListboxOptions class="w-full bg-white border border-gray-200 rounded-md shadow-lg overflow-hidden focus:outline-none">
<ListboxOption
v-for="person in people"
v-slot="{ active, selected }"
Expand Down
60 changes: 42 additions & 18 deletions packages/react/src/float.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
Fragment,
createContext,
forwardRef,
isValidElement,
useCallback,
useContext,
Expand Down Expand Up @@ -51,6 +52,7 @@ function useArrowContext(component: string) {

export interface FloatProps {
as?: ElementType
floatingAs?: ElementType
show?: boolean
placement?: Placement
strategy?: Strategy
Expand All @@ -76,13 +78,16 @@ export interface FloatProps {
referenceEl: MutableRefObject<Element | VirtualElement | null>
floatingEl: MutableRefObject<HTMLElement | null>
}) => Middleware[])

className?: string | undefined
children: ReactElement[]

onShow?: () => void
onHide?: () => void
onUpdate?: () => void
}

function FloatRoot(props: FloatProps) {
const FloatRoot = forwardRef<ElementType, FloatProps>((props, ref) => {
const id = useId()

const [isMounted, setIsMounted] = useState(false)
Expand Down Expand Up @@ -282,6 +287,19 @@ function FloatRoot(props: FloatProps) {
},
}

const renderWrapper = (children: ReactElement[]) => {
if (props.as === Fragment) {
return <Fragment>{children}</Fragment>
}

const Wrapper = props.as || 'div'
return (
<Wrapper ref={ref} className={props.className}>
{children}
</Wrapper>
)
}

const renderPortal = (children: ReactElement) => {
if (isMounted && props.portal) {
const root = document?.querySelector(props.portal === true ? 'body' : props.portal)
Expand All @@ -293,33 +311,39 @@ function FloatRoot(props: FloatProps) {
}

const renderFloating = (Children: ReactElement) => {
if (props.as === Fragment) {
if (props.floatingAs === Fragment) {
return <Children.type {...Children.props} {...floatingProps} />
}

const FloatingWrapper = props.as || 'div'
const FloatingWrapper = props.floatingAs || 'div'
return (
<FloatingWrapper {...floatingProps}>
<Children.type {...Children.props} />
</FloatingWrapper>
)
}

return (
<>
<ReferenceNode.type {...ReferenceNode.props} ref={reference} />
<ArrowContext.Provider value={arrowApi}>
{renderPortal(
renderFloating(
<Transition as={Fragment} {...transitionProps}>
<FloatingNode.type {...FloatingNode.props} />
</Transition>
)
)}
</ArrowContext.Provider>
</>
)
}
return renderWrapper([
<ReferenceNode.type
key="ReferenceNode"
{...ReferenceNode.props}
ref={reference}
/>,
<ArrowContext.Provider
key="FloatingNode"
value={arrowApi}
>
{renderPortal(
renderFloating(
<Transition as={Fragment} {...transitionProps}>
<FloatingNode.type {...FloatingNode.props} />
</Transition>
)
)}
</ArrowContext.Provider>,
])
})
FloatRoot.displayName = 'Float'

export interface FloatArrowProps {
as?: ElementType
Expand Down
Loading