add Menu examples in React
This commit is contained in:
@@ -2,11 +2,11 @@ import * as React from 'react'
|
||||
import * as ReactDOM from 'react-dom'
|
||||
import Head from 'next/head'
|
||||
import Link from 'next/link'
|
||||
import { createPopper, Options } from '@popperjs/core'
|
||||
import { Menu } from '@headlessui/react'
|
||||
|
||||
import { classNames } from '../../src/utils/class-names'
|
||||
import { PropsOf } from '../../src/types'
|
||||
import { usePopper } from '../../playground-utils/hooks/use-popper'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
@@ -22,41 +22,6 @@ export default function Home() {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Example implementation to use Popper: https://popper.js.org/
|
||||
*/
|
||||
function usePopper(
|
||||
options?: Partial<Options>
|
||||
): [React.RefCallback<Element | null>, React.RefCallback<HTMLElement | null>] {
|
||||
const reference = React.useRef<Element>(null)
|
||||
const popper = React.useRef<HTMLElement>(null)
|
||||
|
||||
const cleanupCallback = React.useRef(() => {})
|
||||
|
||||
const instantiatePopper = React.useCallback(() => {
|
||||
if (!reference.current) return
|
||||
if (!popper.current) return
|
||||
|
||||
if (cleanupCallback.current) cleanupCallback.current()
|
||||
|
||||
cleanupCallback.current = createPopper(reference.current, popper.current, options).destroy
|
||||
}, [reference, popper, cleanupCallback, options])
|
||||
|
||||
return React.useMemo(
|
||||
() => [
|
||||
referenceDomNode => {
|
||||
reference.current = referenceDomNode
|
||||
instantiatePopper()
|
||||
},
|
||||
popperDomNode => {
|
||||
popper.current = popperDomNode
|
||||
instantiatePopper()
|
||||
},
|
||||
],
|
||||
[reference, popper, instantiatePopper]
|
||||
)
|
||||
}
|
||||
|
||||
function Portal(props: { children: React.ReactNode }) {
|
||||
const { children } = props
|
||||
const [mounted, setMounted] = React.useState(false)
|
||||
@@ -74,6 +39,14 @@ function Dropdown() {
|
||||
modifiers: [{ name: 'offset', options: { offset: [0, 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="inline-block mt-64 text-left">
|
||||
<Menu>
|
||||
@@ -95,12 +68,6 @@ function Dropdown() {
|
||||
|
||||
<Portal>
|
||||
<Menu.Items
|
||||
enter="transition-opacity ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition-opacity ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
className="w-56 bg-white border border-gray-200 divide-y divide-gray-100 rounded-md shadow-lg outline-none"
|
||||
ref={container}
|
||||
>
|
||||
@@ -112,18 +79,22 @@ function Dropdown() {
|
||||
</div>
|
||||
|
||||
<div className="py-1">
|
||||
<Item href="#account-settings">Account settings</Item>
|
||||
<Item as={NextLink} href="#support">
|
||||
<Menu.Item as="a" href="#account-settings" className={resolveClass}>
|
||||
Account settings
|
||||
</Menu.Item>
|
||||
<Menu.Item as={NextLink} href="#support" className={resolveClass}>
|
||||
Support
|
||||
</Item>
|
||||
<Item href="#new-feature" disabled>
|
||||
</Menu.Item>
|
||||
<Menu.Item as="a" href="#new-feature" disabled className={resolveClass}>
|
||||
New feature (soon)
|
||||
</Item>
|
||||
<Item href="#license">License</Item>
|
||||
</Menu.Item>
|
||||
<Menu.Item as="a" href="#license" className={resolveClass}>
|
||||
License
|
||||
</Menu.Item>
|
||||
</div>
|
||||
|
||||
<div className="py-1">
|
||||
<Item as={SignOutButton} />
|
||||
<Menu.Item as={SignOutButton} className={resolveClass} />
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Portal>
|
||||
@@ -158,19 +129,3 @@ function SignOutButton(props) {
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
function Item(props: PropsOf<typeof Menu.Item>) {
|
||||
return (
|
||||
<Menu.Item
|
||||
as="a"
|
||||
className={({ active, disabled }) =>
|
||||
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'
|
||||
)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
import * as React from 'react'
|
||||
import Head from 'next/head'
|
||||
import Link from 'next/link'
|
||||
import { Menu } from '@headlessui/react'
|
||||
|
||||
import { classNames } from '../../src/utils/class-names'
|
||||
import { PropsOf } from '../../src/types'
|
||||
import { usePopper } from '../../playground-utils/hooks/use-popper'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Menu with pure Tailwind- Playground</title>
|
||||
</Head>
|
||||
|
||||
<div className="flex justify-center w-screen h-full p-12 bg-gray-50">
|
||||
<Dropdown />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function Dropdown() {
|
||||
const [trigger, container] = usePopper({
|
||||
placement: 'bottom-end',
|
||||
strategy: 'fixed',
|
||||
modifiers: [{ name: 'offset', options: { offset: [0, 10] } }],
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="relative inline-block mt-64 text-left">
|
||||
<Menu>
|
||||
<span className="rounded-md shadow-sm">
|
||||
<Menu.Button
|
||||
ref={trigger}
|
||||
className="inline-flex justify-center w-full px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800"
|
||||
>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<span>Options</span>
|
||||
<svg
|
||||
className={classNames(
|
||||
'w-5 h-5 ml-2 -mr-1 transition-transform duration-150',
|
||||
!open && 'transform -rotate-90'
|
||||
)}
|
||||
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>
|
||||
|
||||
<div ref={container} className="relative w-56">
|
||||
<Menu.Items
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
className="bg-white border border-gray-200 divide-y divide-gray-100 rounded-md shadow-lg outline-none"
|
||||
>
|
||||
<div className="px-4 py-3">
|
||||
<p className="text-sm leading-5">Signed in as</p>
|
||||
<p className="text-sm font-medium leading-5 text-gray-900 truncate">
|
||||
tom@example.com
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="py-1">
|
||||
<Item href="#account-settings">Account settings</Item>
|
||||
<Item as={NextLink} href="#support">
|
||||
Support
|
||||
</Item>
|
||||
<Item href="#new-feature" disabled>
|
||||
New feature (soon)
|
||||
</Item>
|
||||
<Item href="#license">License</Item>
|
||||
</div>
|
||||
|
||||
<div className="py-1">
|
||||
<SignOutButton />
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</div>
|
||||
</Menu>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function NextLink(props: PropsOf<'a'>) {
|
||||
const { href, children, ...rest } = props
|
||||
return (
|
||||
<Link href={href}>
|
||||
<a {...rest}>{children}</a>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
function SignOutButton() {
|
||||
return (
|
||||
<Menu.Item>
|
||||
{props => {
|
||||
const { active, disabled } = props
|
||||
return (
|
||||
<form
|
||||
method="POST"
|
||||
action="#"
|
||||
onSubmit={e => {
|
||||
e.preventDefault()
|
||||
alert('SIGNED OUT')
|
||||
}}
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
className={classNames(
|
||||
'w-full',
|
||||
'flex justify-between w-full text-left px-4 py-2 text-sm leading-5',
|
||||
active ? 'bg-indigo-500 text-white' : 'text-gray-700',
|
||||
disabled && 'cursor-not-allowed opacity-50'
|
||||
)}
|
||||
>
|
||||
<span className={classNames(active && 'font-bold')}>Sign out</span>
|
||||
<kbd className={classNames('font-sans', active && 'text-indigo-50')}>⌘K</kbd>
|
||||
</button>
|
||||
</form>
|
||||
)
|
||||
}}
|
||||
</Menu.Item>
|
||||
)
|
||||
}
|
||||
|
||||
function Item({ children, as = 'a', ...props }: PropsOf<typeof Menu.Item>) {
|
||||
return (
|
||||
<Menu.Item
|
||||
className={({ active, disabled }) => {
|
||||
return classNames(
|
||||
'flex justify-between w-full text-left px-4 py-2 text-sm leading-5',
|
||||
active ? 'bg-indigo-500 text-white' : 'text-gray-700',
|
||||
disabled && 'cursor-not-allowed opacity-50'
|
||||
)
|
||||
}}
|
||||
as={as}
|
||||
{...props}
|
||||
>
|
||||
{({ active }) => (
|
||||
<>
|
||||
<span className={classNames(active && 'font-bold')}>{children}</span>
|
||||
<kbd className={classNames('font-sans', active && 'text-indigo-50')}>⌘K</kbd>
|
||||
</>
|
||||
)}
|
||||
</Menu.Item>
|
||||
)
|
||||
}
|
||||
+6
-5
@@ -27,7 +27,7 @@ function Dropdown() {
|
||||
<span className="rounded-md shadow-sm">
|
||||
<Menu.Button className="inline-flex justify-center w-full px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800">
|
||||
{({ open }) => (
|
||||
<button>
|
||||
<>
|
||||
<span>Options</span>
|
||||
<svg
|
||||
className={classNames(
|
||||
@@ -43,7 +43,7 @@ function Dropdown() {
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</Menu.Button>
|
||||
</span>
|
||||
@@ -124,7 +124,7 @@ function SignOutButton() {
|
||||
)
|
||||
}
|
||||
|
||||
function Item({ children, href, ...props }: PropsOf<typeof Menu.Item>) {
|
||||
function Item({ children, as = 'a', ...props }: PropsOf<typeof Menu.Item>) {
|
||||
return (
|
||||
<Menu.Item
|
||||
className={({ active, disabled }) => {
|
||||
@@ -134,13 +134,14 @@ function Item({ children, href, ...props }: PropsOf<typeof Menu.Item>) {
|
||||
disabled && 'cursor-not-allowed opacity-50'
|
||||
)
|
||||
}}
|
||||
as={as}
|
||||
{...props}
|
||||
>
|
||||
{({ active }) => (
|
||||
<a href={href}>
|
||||
<>
|
||||
<span className={classNames(active && 'font-bold')}>{children}</span>
|
||||
<kbd className={classNames('font-sans', active && 'text-indigo-50')}>⌘K</kbd>
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</Menu.Item>
|
||||
)
|
||||
@@ -0,0 +1,122 @@
|
||||
import * as React from 'react'
|
||||
import Head from 'next/head'
|
||||
import Link from 'next/link'
|
||||
import { Menu } from '@headlessui/react'
|
||||
|
||||
import { classNames } from '../../src/utils/class-names'
|
||||
import { PropsOf } from '../../src/types'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Menu with pure Tailwind- Playground</title>
|
||||
</Head>
|
||||
|
||||
<div className="flex justify-center w-screen h-full p-12 bg-gray-50">
|
||||
<Dropdown />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function Dropdown() {
|
||||
function resolveClass({ active, disabled }) {
|
||||
return classNames(
|
||||
'flex justify-between w-full text-left px-4 py-2 text-sm leading-5',
|
||||
active ? 'bg-indigo-500 text-white' : 'text-gray-700',
|
||||
disabled && 'cursor-not-allowed opacity-50'
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative inline-block text-left">
|
||||
<Menu>
|
||||
<span className="rounded-md shadow-sm">
|
||||
<Menu.Button className="inline-flex justify-center w-full px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800">
|
||||
<span>Options</span>
|
||||
<svg
|
||||
className="w-5 h-5 ml-2 -mr-1 transition-transform duration-150"
|
||||
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>
|
||||
|
||||
<Menu.Items className="absolute right-0 w-56 mt-2 origin-top-right bg-white border border-gray-200 divide-y divide-gray-100 rounded-md shadow-lg outline-none">
|
||||
<div className="px-4 py-3">
|
||||
<p className="text-sm leading-5">Signed in as</p>
|
||||
<p className="text-sm font-medium leading-5 text-gray-900 truncate">tom@example.com</p>
|
||||
</div>
|
||||
|
||||
<div className="py-1">
|
||||
<Menu.Item as="a" href="#account-settings" className={resolveClass}>
|
||||
Account settings
|
||||
</Menu.Item>
|
||||
<Menu.Item as={NextLink} href="#support" className={resolveClass}>
|
||||
Support
|
||||
</Menu.Item>
|
||||
<Menu.Item as="a" href="#new-feature" disabled className={resolveClass}>
|
||||
New feature (soon)
|
||||
</Menu.Item>
|
||||
<Menu.Item as="a" href="#license" className={resolveClass}>
|
||||
License
|
||||
</Menu.Item>
|
||||
</div>
|
||||
|
||||
<div className="py-1">
|
||||
<SignOutButton />
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Menu>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function NextLink(props: PropsOf<'a'>) {
|
||||
const { href, children, ...rest } = props
|
||||
return (
|
||||
<Link href={href}>
|
||||
<a {...rest}>{children}</a>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
function SignOutButton() {
|
||||
return (
|
||||
<Menu.Item>
|
||||
{props => {
|
||||
const { active, disabled } = props
|
||||
return (
|
||||
<form
|
||||
method="POST"
|
||||
action="#"
|
||||
onSubmit={e => {
|
||||
e.preventDefault()
|
||||
alert('SIGNED OUT')
|
||||
}}
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
className={classNames(
|
||||
'w-full',
|
||||
'flex justify-between w-full text-left px-4 py-2 text-sm leading-5',
|
||||
active ? 'bg-indigo-500 text-white' : 'text-gray-700',
|
||||
disabled && 'cursor-not-allowed opacity-50'
|
||||
)}
|
||||
>
|
||||
<span className={classNames(active && 'font-bold')}>Sign out</span>
|
||||
<kbd className={classNames('font-sans', active && 'text-indigo-50')}>⌘K</kbd>
|
||||
</button>
|
||||
</form>
|
||||
)
|
||||
}}
|
||||
</Menu.Item>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from 'react'
|
||||
import { createPopper, Options } from '@popperjs/core'
|
||||
|
||||
/**
|
||||
* Example implementation to use Popper: https://popper.js.org/
|
||||
*/
|
||||
export function usePopper(
|
||||
options?: Partial<Options>
|
||||
): [React.RefCallback<Element | null>, React.RefCallback<HTMLElement | null>] {
|
||||
const reference = React.useRef<Element>(null)
|
||||
const popper = React.useRef<HTMLElement>(null)
|
||||
|
||||
const cleanupCallback = React.useRef(() => {})
|
||||
|
||||
const instantiatePopper = React.useCallback(() => {
|
||||
if (!reference.current) return
|
||||
if (!popper.current) return
|
||||
|
||||
if (cleanupCallback.current) cleanupCallback.current()
|
||||
|
||||
cleanupCallback.current = createPopper(reference.current, popper.current, options).destroy
|
||||
}, [reference, popper, cleanupCallback, options])
|
||||
|
||||
return React.useMemo(
|
||||
() => [
|
||||
referenceDomNode => {
|
||||
reference.current = referenceDomNode
|
||||
instantiatePopper()
|
||||
},
|
||||
popperDomNode => {
|
||||
popper.current = popperDomNode
|
||||
instantiatePopper()
|
||||
},
|
||||
],
|
||||
[reference, popper, instantiatePopper]
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user