Skip to content

Commit

Permalink
Add Floating UI examples in combination with the Menu component (#2612)
Browse files Browse the repository at this point in the history
* add Floating UI example for React with Menu

* add Floating UI example for Vue with Menu
  • Loading branch information
RobinMalfait committed Jul 25, 2023
1 parent 1739edb commit 2eabdd4
Show file tree
Hide file tree
Showing 5 changed files with 988 additions and 737 deletions.
3 changes: 3 additions & 0 deletions packages/playground-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,8 @@
"react-dom": "^18.0.0",
"react-flatpickr": "^3.10.9",
"tailwindcss": "^3.2.7"
},
"devDependencies": {
"@floating-ui/react": "^0.24.8"
}
}
94 changes: 94 additions & 0 deletions packages/playground-react/pages/menu/menu-with-floating-ui.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React, { ReactNode, useState, useEffect } from 'react'
import { createPortal } from 'react-dom'
import { Menu } from '@headlessui/react'
import { useFloating, offset } from '@floating-ui/react'

import { classNames } from '../../utils/class-names'
import { Button } from '../../components/button'

export default function Home() {
let { refs, floatingStyles } = useFloating({
placement: 'bottom-end',
strategy: 'fixed',
middleware: [offset(10)],
})

function resolveClass({ active, disabled }) {
return classNames(
'block w-full text-left px-4 py-2 text-sm leading-5 text-gray-700',
active && 'bg-gray-100 text-gray-900',
disabled && 'cursor-not-allowed opacity-50'
)
}

return (
<div className="flex h-full w-screen justify-center bg-gray-50 p-12">
<div className="mt-64 inline-block text-left">
<Menu>
<span className="inline-flex rounded-md shadow-sm">
<Menu.Button ref={refs.setReference} as={Button}>
<span>Options</span>
<svg className="ml-2 -mr-1 h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path
fillRule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
</Menu.Button>
</span>

<Portal>
<Menu.Items
className="w-56 divide-y divide-gray-100 rounded-md border border-gray-200 bg-white shadow-lg outline-none"
ref={refs.setFloating}
style={floatingStyles}
>
<div className="px-4 py-3">
<p className="text-sm leading-5">Signed in as</p>
<p className="truncate text-sm font-medium leading-5 text-gray-900">
[email protected]
</p>
</div>

<div className="py-1">
<Menu.Item as="a" href="#account-settings" className={resolveClass}>
Account settings
</Menu.Item>
<Menu.Item>
{(data) => (
<a href="#support" className={resolveClass(data)}>
Support
</a>
)}
</Menu.Item>
<Menu.Item as="a" disabled href="#new-feature" className={resolveClass}>
New feature (soon)
</Menu.Item>
<Menu.Item as="a" href="#license" className={resolveClass}>
License
</Menu.Item>
</div>

<div className="py-1">
<Menu.Item as="a" href="#sign-out" className={resolveClass}>
Sign out
</Menu.Item>
</div>
</Menu.Items>
</Portal>
</Menu>
</div>
</div>
)
}

function Portal(props: { children: ReactNode }) {
let { children } = props
let [mounted, setMounted] = useState(false)

useEffect(() => setMounted(true), [])

if (!mounted) return null
return createPortal(children, document.body)
}
1 change: 1 addition & 0 deletions packages/playground-vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"vue-router": "^4.0.0"
},
"devDependencies": {
"@floating-ui/vue": "^1.0.2",
"@vitejs/plugin-vue": "^3.0.3",
"vite": "^3.0.0"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<template>
<div class="flex h-full w-screen justify-center bg-gray-50 p-12">
<div class="mt-64 inline-block text-left">
<Menu>
<span class="inline-flex rounded-md shadow-sm">
<MenuButton
ref="reference"
class="focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none active:bg-gray-50 active:text-gray-800"
>
<span>Options</span>
<svg class="ml-2 -mr-1 h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path
fillRule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
</MenuButton>
</span>

<teleport to="body">
<MenuItems
ref="floating"
:style="floatingStyles"
class="absolute right-0 w-56 origin-top-right divide-y divide-gray-100 rounded-md border border-gray-200 bg-white shadow-lg outline-none"
>
<div class="px-4 py-3">
<p class="text-sm leading-5">Signed in as</p>
<p class="truncate text-sm font-medium leading-5 text-gray-900">[email protected]</p>
</div>

<div class="py-1">
<MenuItem as="a" href="#account-settings" :className="resolveClass">
Account settings
</MenuItem>
<MenuItem v-slot="data">
<a href="#support" :class="resolveClass(data)">Support</a>
</MenuItem>
<MenuItem as="a" disabled href="#new-feature" :className="resolveClass">
New feature (soon)
</MenuItem>
<MenuItem as="a" href="#license" :className="resolveClass">License</MenuItem>
</div>
<div class="py-1">
<MenuItem as="a" href="#sign-out" :className="resolveClass">Sign out</MenuItem>
</div>
</MenuItems>
</teleport>
</Menu>
</div>
</div>
</template>

<script>
import { defineComponent, h, ref, onMounted, watchEffect, watch, computed } from 'vue'
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
import { useFloating, offset } from '@floating-ui/vue'
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export default {
components: { Menu, MenuButton, MenuItems, MenuItem },
setup(props, context) {
let reference = ref(null)
let floating = ref(null)
let { floatingStyles } = useFloating(
computed(() => reference.value?.el),
computed(() => floating.value?.el),
{
placement: 'bottom-end',
strategy: 'fixed',
middleware: [offset(10)],
}
)
function resolveClass({ active, disabled }) {
return classNames(
'flex justify-between w-full px-4 py-2 text-sm leading-5 text-left',
active ? 'bg-gray-100 text-gray-900' : 'text-gray-700',
disabled && 'cursor-not-allowed opacity-50'
)
}
return {
reference,
floating,
floatingStyles,
resolveClass,
}
},
}
</script>

2 comments on commit 2eabdd4

@vercel
Copy link

@vercel vercel bot commented on 2eabdd4 Jul 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

headlessui-vue – ./packages/playground-vue

headlessui-vue.vercel.app
headlessui-vue-git-main-tailwindlabs.vercel.app
headlessui-vue-tailwindlabs.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 2eabdd4 Jul 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

headlessui-react – ./packages/playground-react

headlessui-react-git-main-tailwindlabs.vercel.app
headlessui-react.vercel.app
headlessui-react-tailwindlabs.vercel.app

Please sign in to comment.