Skip to content

Commit

Permalink
Floating element adaptive width (#20)
Browse files Browse the repository at this point in the history
* Add `floatingAs` prop & fix render position of `as` prop
* Update some example has adaptive width
  • Loading branch information
ycs77 committed Sep 12, 2022
1 parent 38219b3 commit 266d4dd
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 60 deletions.
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

0 comments on commit 266d4dd

Please sign in to comment.