Add Floating UI examples in combination with the Menu component (#2612)

* add Floating UI example for React with Menu

* add Floating UI example for Vue with Menu
This commit is contained in:
Robin Malfait
2023-07-25 14:45:14 +02:00
committed by GitHub
parent 1739edb479
commit 2eabdd4f1f
5 changed files with 988 additions and 737 deletions
+3
View File
@@ -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"
}
}
@@ -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">
tom@example.com
</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
View File
@@ -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"
}
@@ -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">tom@example.com</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>
+795 -737
View File
File diff suppressed because it is too large Load Diff