Fix focus styles showing up when using the mouse (#2347)
* update playground examples to use a shared `Button` * expose a `ui-focus-visible` variant * keep track of a `data-headlessui-focus-visible` attribute * do not set the `tabindex` The focus was always set, but the ring wasn't showing up. This was also focusing a ring when the browser decided not the add one. Let's make the browser decide when to show this or not. * update changelog
This commit is contained in:
@@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- Nothing yet!
|
||||
### Fixed
|
||||
|
||||
- Fix focus styles showing up when using the mouse ([#2347](https://github.com/tailwindlabs/headlessui/pull/2347))
|
||||
|
||||
## [1.7.13] - 2023-03-03
|
||||
|
||||
|
||||
@@ -116,6 +116,47 @@ export function restoreFocusIfNecessary(element: HTMLElement | null) {
|
||||
})
|
||||
}
|
||||
|
||||
// The method of triggering an action, this is used to determine how we should
|
||||
// restore focus after an action has been performed.
|
||||
enum ActivationMethod {
|
||||
/* If the action was triggered by a keyboard event. */
|
||||
Keyboard = 0,
|
||||
|
||||
/* If the action was triggered by a mouse / pointer / ... event.*/
|
||||
Mouse = 1,
|
||||
}
|
||||
|
||||
// We want to be able to set and remove the `data-headlessui-mouse` attribute on the `html` element.
|
||||
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
||||
document.addEventListener(
|
||||
'keydown',
|
||||
(event) => {
|
||||
if (event.metaKey || event.altKey || event.ctrlKey) {
|
||||
return
|
||||
}
|
||||
|
||||
document.documentElement.dataset.headlessuiFocusVisible = ''
|
||||
},
|
||||
true
|
||||
)
|
||||
|
||||
document.addEventListener(
|
||||
'click',
|
||||
(event) => {
|
||||
// Event originated from an actual mouse click
|
||||
if (event.detail === ActivationMethod.Mouse) {
|
||||
delete document.documentElement.dataset.headlessuiFocusVisible
|
||||
}
|
||||
|
||||
// Event originated from a keyboard event that triggered the `click` event
|
||||
else if (event.detail === ActivationMethod.Keyboard) {
|
||||
document.documentElement.dataset.headlessuiFocusVisible = ''
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
export function focusElement(element: HTMLElement | null) {
|
||||
element?.focus({ preventScroll: true })
|
||||
}
|
||||
@@ -232,14 +273,5 @@ export function focusIn(
|
||||
next.select()
|
||||
}
|
||||
|
||||
// This is a little weird, but let me try and explain: There are a few scenario's
|
||||
// in chrome for example where a focused `<a>` tag does not get the default focus
|
||||
// styles and sometimes they do. This highly depends on whether you started by
|
||||
// clicking or by using your keyboard. When you programmatically add focus `anchor.focus()`
|
||||
// then the active element (document.activeElement) is this anchor, which is expected.
|
||||
// However in that case the default focus styles are not applied *unless* you
|
||||
// also add this tabindex.
|
||||
if (!next.hasAttribute('tabindex')) next.setAttribute('tabindex', '0')
|
||||
|
||||
return FocusResult.Success
|
||||
}
|
||||
|
||||
@@ -37,6 +37,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.11.18",
|
||||
"tailwindcss": "^3.2.4"
|
||||
"tailwindcss": "^3.2.7"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ let css = String.raw
|
||||
function run(input: string, config: any, plugin = tailwind) {
|
||||
let { currentTestName } = expect.getState()
|
||||
|
||||
// @ts-ignore
|
||||
return postcss(plugin(config)).process(input, {
|
||||
from: `${path.resolve(__filename)}?test=${currentTestName}`,
|
||||
})
|
||||
@@ -52,6 +53,21 @@ it('should generate the inverse "not" css for an exposed state', async () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should generate the ui-focus-visible variant', async () => {
|
||||
let config = {
|
||||
content: [{ raw: html`<div class="ui-focus-visible:underline"></div>` }],
|
||||
plugins: [hui],
|
||||
}
|
||||
|
||||
return run('@tailwind utilities', config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(css`
|
||||
:where([data-headlessui-focus-visible]) .ui-focus-visible\:underline:focus {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('custom prefix', () => {
|
||||
it('should generate css for an exposed state', async () => {
|
||||
let config = {
|
||||
|
||||
@@ -31,6 +31,8 @@ export default plugin.withOptions<Options>(({ prefix = 'ui' } = {}) => {
|
||||
`&[data-headlessui-state]:not([data-headlessui-state~="${state}"])`,
|
||||
`:where([data-headlessui-state]:not([data-headlessui-state~="${state}"])) &:not([data-headlessui-state])`,
|
||||
])
|
||||
|
||||
addVariant(`${prefix}-focus-visible`, ':where([data-headlessui-focus-visible]) &:focus')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- Nothing yet!
|
||||
### Fixed
|
||||
|
||||
- Fix focus styles showing up when using the mouse ([#2347](https://github.com/tailwindlabs/headlessui/pull/2347))
|
||||
|
||||
## [1.7.12] - 2023-03-03
|
||||
|
||||
|
||||
@@ -109,6 +109,47 @@ export function restoreFocusIfNecessary(element: HTMLElement | null) {
|
||||
})
|
||||
}
|
||||
|
||||
// The method of triggering an action, this is used to determine how we should
|
||||
// restore focus after an action has been performed.
|
||||
enum ActivationMethod {
|
||||
/* If the action was triggered by a keyboard event. */
|
||||
Keyboard = 0,
|
||||
|
||||
/* If the action was triggered by a mouse / pointer / ... event.*/
|
||||
Mouse = 1,
|
||||
}
|
||||
|
||||
// We want to be able to set and remove the `data-headlessui-mouse` attribute on the `html` element.
|
||||
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
||||
document.addEventListener(
|
||||
'keydown',
|
||||
(event) => {
|
||||
if (event.metaKey || event.altKey || event.ctrlKey) {
|
||||
return
|
||||
}
|
||||
|
||||
document.documentElement.dataset.headlessuiFocusVisible = ''
|
||||
},
|
||||
true
|
||||
)
|
||||
|
||||
document.addEventListener(
|
||||
'click',
|
||||
(event) => {
|
||||
// Event originated from an actual mouse click
|
||||
if (event.detail === ActivationMethod.Mouse) {
|
||||
delete document.documentElement.dataset.headlessuiFocusVisible
|
||||
}
|
||||
|
||||
// Event originated from a keyboard event that triggered the `click` event
|
||||
else if (event.detail === ActivationMethod.Keyboard) {
|
||||
document.documentElement.dataset.headlessuiFocusVisible = ''
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
export function focusElement(element: HTMLElement | null) {
|
||||
element?.focus({ preventScroll: true })
|
||||
}
|
||||
@@ -226,14 +267,5 @@ export function focusIn(
|
||||
next.select()
|
||||
}
|
||||
|
||||
// This is a little weird, but let me try and explain: There are a few scenario's
|
||||
// in chrome for example where a focused `<a>` tag does not get the default focus
|
||||
// styles and sometimes they do. This highly depends on whether you started by
|
||||
// clicking or by using your keyboard. When you programmatically add focus `anchor.focus()`
|
||||
// then the active element (document.activeElement) is this anchor, which is expected.
|
||||
// However in that case the default focus styles are not applied *unless* you
|
||||
// also add this tabindex.
|
||||
if (!next.hasAttribute('tabindex')) next.setAttribute('tabindex', '0')
|
||||
|
||||
return FocusResult.Success
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { ComponentProps, forwardRef, ReactNode } from 'react'
|
||||
|
||||
function classNames(...classes: (string | false | undefined | null)[]) {
|
||||
return classes.filter(Boolean).join(' ')
|
||||
}
|
||||
|
||||
export let Button = forwardRef<
|
||||
HTMLButtonElement,
|
||||
ComponentProps<'button'> & { children?: ReactNode }
|
||||
>(({ className, ...props }, ref) => (
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
className={classNames(
|
||||
'ui-focus-visible:ring-2 ui-focus-visible:ring-offset-2 flex items-center rounded-md border border-gray-300 bg-white px-2 py-1 ring-gray-500 ring-offset-gray-100 focus:outline-none',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
@@ -27,6 +27,6 @@
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-flatpickr": "^3.10.9",
|
||||
"tailwindcss": "^0.0.0-insiders.83b4811"
|
||||
"tailwindcss": "^3.2.7"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react'
|
||||
import { Switch, RadioGroup, Listbox, Combobox } from '@headlessui/react'
|
||||
import { classNames } from '../../utils/class-names'
|
||||
import { Button } from '../../components/button'
|
||||
|
||||
function Section({ title, children }) {
|
||||
return (
|
||||
@@ -170,26 +171,24 @@ export default function App() {
|
||||
{({ value }) => (
|
||||
<>
|
||||
<div className="relative">
|
||||
<span className="inline-block w-full rounded-md shadow-sm">
|
||||
<Listbox.Button className="relative w-full cursor-default rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 text-left focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 sm:text-sm sm:leading-5">
|
||||
<span className="block truncate">{value?.name?.first}</span>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<svg
|
||||
className="h-5 w-5 text-gray-400"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M7 7l3-3 3 3m0 6l-3 3-3-3"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</Listbox.Button>
|
||||
</span>
|
||||
<Listbox.Button as={Button} className="w-full">
|
||||
<span className="block truncate">{value?.name?.first}</span>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<svg
|
||||
className="h-5 w-5 text-gray-400"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M7 7l3-3 3 3m0 6l-3 3-3-3"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</Listbox.Button>
|
||||
|
||||
<div className="absolute z-10 mt-1 w-full rounded-md bg-white shadow-lg">
|
||||
<Listbox.Options className="shadow-xs max-h-60 overflow-auto rounded-md py-1 text-base leading-6 focus:outline-none sm:text-sm sm:leading-5">
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
import { useState } from 'react'
|
||||
import { Dialog, Tab } from '@headlessui/react'
|
||||
import { Button } from '../../components/button'
|
||||
|
||||
export default function App() {
|
||||
let [open, setOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => setOpen(true)}>Open dialog</button>
|
||||
<div className="p-12">
|
||||
<Button onClick={() => setOpen(true)}>Open dialog</Button>
|
||||
<Dialog open={open} onClose={setOpen} className="fixed inset-0 grid place-content-center">
|
||||
<div className="fixed inset-0 bg-gray-500/70" />
|
||||
<Dialog.Panel className="inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle">
|
||||
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||
<Tab.Group>
|
||||
<Tab.List>
|
||||
<Tab className="px-3 py-2">Tab 1</Tab>
|
||||
<Tab className="px-3 py-2">Tab 2</Tab>
|
||||
<Tab className="px-3 py-2">Tab 3</Tab>
|
||||
<Tab.List className="flex gap-4 py-4">
|
||||
<Tab as={Button}>Tab 1</Tab>
|
||||
<Tab as={Button}>Tab 2</Tab>
|
||||
<Tab as={Button}>Tab 3</Tab>
|
||||
</Tab.List>
|
||||
<Tab.Panels>
|
||||
<Tab.Panel className="px-3 py-2">Panel 1</Tab.Panel>
|
||||
@@ -26,6 +27,6 @@ export default function App() {
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'
|
||||
import { Combobox } from '@headlessui/react'
|
||||
|
||||
import { classNames } from '../../utils/class-names'
|
||||
import { Button } from '../../components/button'
|
||||
|
||||
let everybody = [
|
||||
'Wade Cooper',
|
||||
@@ -64,7 +65,7 @@ export default function Home() {
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
className="border-none px-3 py-1 outline-none"
|
||||
/>
|
||||
<Combobox.Button className="cursor-default border-l bg-gray-100 px-1 text-indigo-600 focus:outline-none">
|
||||
<Combobox.Button as={Button}>
|
||||
<span className="pointer-events-none flex items-center px-2">
|
||||
<svg
|
||||
className="h-5 w-5 text-gray-400"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
import { Dialog } from '@headlessui/react'
|
||||
import { Button } from '../../components/button'
|
||||
|
||||
function Modal(props) {
|
||||
return (
|
||||
@@ -7,9 +8,9 @@ function Modal(props) {
|
||||
<div className="fixed inset-0 bg-green-500 bg-opacity-90 backdrop-blur backdrop-filter" />
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4">
|
||||
<Dialog.Panel className="relative m-5 w-full max-w-3xl rounded-lg bg-white p-10 shadow">
|
||||
<button className="m-5 rounded-lg bg-blue-600 py-2 px-5 text-white">One</button>
|
||||
<button className="m-5 rounded-lg bg-blue-600 py-2 px-5 text-white">Two</button>
|
||||
<Dialog.Panel className="relative m-5 flex w-full max-w-3xl gap-4 rounded-lg bg-white p-10 shadow">
|
||||
<Button>One</Button>
|
||||
<Button>Two</Button>
|
||||
</Dialog.Panel>
|
||||
</div>
|
||||
</div>
|
||||
@@ -23,12 +24,7 @@ export default function DialogFocusIssue() {
|
||||
return (
|
||||
<div className="p-10">
|
||||
<h1 className="py-2 text-3xl font-semibold">Headless UI Focus Jump</h1>
|
||||
<button
|
||||
className="my-5 rounded-lg bg-blue-600 py-2 px-5 text-white"
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
Open Dialog
|
||||
</button>
|
||||
<Button onClick={() => setIsOpen(true)}>Open Dialog</Button>
|
||||
<div className="bg-white p-20"></div>
|
||||
<div className="bg-gray-100 p-20"></div>
|
||||
<div className="bg-gray-200 p-20"></div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { Dialog } from '@headlessui/react'
|
||||
import { Button } from '../../components/button'
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
class MyCustomElement extends HTMLElement {
|
||||
@@ -49,12 +50,7 @@ export default function App() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
className="m-4 rounded border-0 bg-gray-500 px-3 py-1 font-medium text-white hover:bg-gray-600"
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
open
|
||||
</button>
|
||||
<Button onClick={() => setOpen(true)}>open</Button>
|
||||
<Dialog open={open} onClose={() => setOpen(false)}>
|
||||
<div className="fixed inset-0 z-50 bg-gray-900/75 backdrop-blur-lg">
|
||||
<div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import Flatpickr from 'react-flatpickr'
|
||||
import { Dialog, Menu, Portal, Transition } from '@headlessui/react'
|
||||
import { usePopper } from '../../utils/hooks/use-popper'
|
||||
import { classNames } from '../../utils/class-names'
|
||||
import { Button } from '../../components/button'
|
||||
|
||||
import 'flatpickr/dist/themes/light.css'
|
||||
|
||||
@@ -14,16 +15,6 @@ function resolveClass({ active, disabled }) {
|
||||
)
|
||||
}
|
||||
|
||||
function Button(props: React.ComponentProps<'button'>) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="rounded bg-gray-200 px-2 py-1 ring-gray-500 ring-offset-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function Nested({ onClose, level = 0 }) {
|
||||
let [showChild, setShowChild] = useState(false)
|
||||
|
||||
@@ -38,7 +29,7 @@ function Nested({ onClose, level = 0 }) {
|
||||
}}
|
||||
>
|
||||
<p>Level: {level}</p>
|
||||
<div className="space-x-4">
|
||||
<div className="flex gap-4">
|
||||
<Button onClick={() => setShowChild(true)}>Open (1)</Button>
|
||||
<Button onClick={() => setShowChild(true)}>Open (2)</Button>
|
||||
<Button onClick={() => setShowChild(true)}>Open (3)</Button>
|
||||
@@ -70,11 +61,6 @@ export default function Home() {
|
||||
</div>
|
||||
{nested && <Nested onClose={() => setNested(false)} />}
|
||||
|
||||
<div
|
||||
data-preload
|
||||
className="translate-y-4 translate-y-0 translate-y-0 translate-y-4 scale-95 scale-100 scale-100 scale-95 transform transform transform transform transform transform opacity-0 opacity-75 opacity-75 opacity-0 opacity-75 opacity-0 opacity-100 opacity-100 opacity-0 opacity-0 opacity-100 opacity-100 opacity-0 transition transition duration-1000 duration-300 duration-200 duration-300 duration-200 duration-300 duration-75 ease-out ease-in ease-out ease-in ease-out ease-out sm:translate-y-0 sm:translate-y-0 sm:scale-95 sm:scale-100 sm:scale-100 sm:scale-95"
|
||||
/>
|
||||
|
||||
<Transition
|
||||
data-debug="Dialog"
|
||||
show={isOpen}
|
||||
@@ -161,27 +147,22 @@ export default function Home() {
|
||||
Are you sure you want to deactivate your account? All of your data will
|
||||
be permanently removed. This action cannot be undone.
|
||||
</p>
|
||||
<div className="relative mt-10 inline-block text-left">
|
||||
<div className="relative mt-10 inline-flex gap-4 text-left">
|
||||
<Menu>
|
||||
<span className="rounded-md shadow-sm">
|
||||
<Menu.Button
|
||||
ref={trigger}
|
||||
className="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"
|
||||
<Menu.Button as={Button} ref={trigger}>
|
||||
<span>Choose a reason</span>
|
||||
<svg
|
||||
className="ml-2 -mr-1 h-5 w-5"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<span>Choose a reason</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>
|
||||
<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>
|
||||
|
||||
<Transition
|
||||
enter="transition duration-300 ease-out"
|
||||
@@ -242,21 +223,9 @@ export default function Home() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="focus:shadow-outline-red inline-flex w-full justify-center rounded-md border border-transparent bg-red-600 px-4 py-2 text-base font-medium text-white shadow-sm hover:bg-red-700 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm"
|
||||
>
|
||||
Deactivate
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="focus:shadow-outline-indigo mt-3 inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium text-gray-700 shadow-sm hover:text-gray-500 focus:outline-none sm:mt-0 sm:w-auto sm:text-sm"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<div className="flex bg-gray-50 px-4 py-3 sm:flex-row-reverse sm:gap-2">
|
||||
<Button onClick={() => setIsOpen(false)}>Deactivate</Button>
|
||||
<Button onClick={() => setIsOpen(false)}>Cancel</Button>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from 'react'
|
||||
import React, { forwardRef } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { Menu } from '@headlessui/react'
|
||||
import { Menu, MenuItemProps } from '@headlessui/react'
|
||||
import { AnimatePresence, motion } from 'framer-motion'
|
||||
|
||||
import { classNames } from '../../utils/class-names'
|
||||
import { Button } from '../../components/button'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
@@ -13,7 +14,7 @@ export default function Home() {
|
||||
{({ open }) => (
|
||||
<>
|
||||
<span className="rounded-md shadow-sm">
|
||||
<Menu.Button className="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">
|
||||
<Menu.Button as={Button}>
|
||||
<span>Options</span>
|
||||
<svg className="ml-2 -mr-1 h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
@@ -67,16 +68,18 @@ export default function Home() {
|
||||
)
|
||||
}
|
||||
|
||||
function NextLink(props: React.ComponentProps<'a'>) {
|
||||
let NextLink = forwardRef<HTMLAnchorElement>((props: React.ComponentProps<'a'>, ref) => {
|
||||
let { href, children, ...rest } = props
|
||||
return (
|
||||
<Link href={href}>
|
||||
<a {...rest}>{children}</a>
|
||||
<a ref={ref} {...rest}>
|
||||
{children}
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
function SignOutButton(props) {
|
||||
let SignOutButton = forwardRef<HTMLButtonElement>((props, ref) => {
|
||||
return (
|
||||
<form
|
||||
method="POST"
|
||||
@@ -87,16 +90,17 @@ function SignOutButton(props) {
|
||||
}}
|
||||
className="w-full"
|
||||
>
|
||||
<button type="submit" {...props}>
|
||||
<button ref={ref} type="submit" {...props}>
|
||||
Sign out
|
||||
</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
function Item(props) {
|
||||
let Item = forwardRef<HTMLAnchorElement, MenuItemProps<any>>((props, ref) => {
|
||||
return (
|
||||
<Menu.Item
|
||||
ref={ref}
|
||||
as="a"
|
||||
className={({ active, disabled }) =>
|
||||
classNames(
|
||||
@@ -108,4 +112,4 @@ function Item(props) {
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Menu } from '@headlessui/react'
|
||||
|
||||
import { usePopper } from '../../utils/hooks/use-popper'
|
||||
import { classNames } from '../../utils/class-names'
|
||||
import { Button } from '../../components/button'
|
||||
|
||||
export default function Home() {
|
||||
let [trigger, container] = usePopper({
|
||||
@@ -25,10 +26,7 @@ export default function Home() {
|
||||
<div className="mt-64 inline-block text-left">
|
||||
<Menu>
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<Menu.Button
|
||||
ref={trigger}
|
||||
className="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"
|
||||
>
|
||||
<Menu.Button ref={trigger} as={Button}>
|
||||
<span>Options</span>
|
||||
<svg className="ml-2 -mr-1 h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Menu, Transition } from '@headlessui/react'
|
||||
|
||||
import { usePopper } from '../../utils/hooks/use-popper'
|
||||
import { classNames } from '../../utils/class-names'
|
||||
import { Button } from '../../components/button'
|
||||
|
||||
export default function Home() {
|
||||
let [trigger, container] = usePopper({
|
||||
@@ -24,10 +25,7 @@ export default function Home() {
|
||||
<div className="mt-64 inline-block text-left">
|
||||
<Menu>
|
||||
<span className="rounded-md shadow-sm">
|
||||
<Menu.Button
|
||||
ref={trigger}
|
||||
className="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"
|
||||
>
|
||||
<Menu.Button ref={trigger} as={Button}>
|
||||
<span>Options</span>
|
||||
<svg className="ml-2 -mr-1 h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react'
|
||||
import { Menu, Transition } from '@headlessui/react'
|
||||
import { classNames } from '../../utils/class-names'
|
||||
import { Button } from '../../components/button'
|
||||
|
||||
export default function Home() {
|
||||
function resolveClass({ active, disabled }) {
|
||||
@@ -14,13 +15,9 @@ export default function Home() {
|
||||
return (
|
||||
<div className="flex h-full w-screen justify-center bg-gray-50 p-12">
|
||||
<div className="relative inline-block text-left">
|
||||
<div
|
||||
data-preload
|
||||
className="scale-95 scale-100 scale-100 scale-95 transform transform transform transform opacity-0 opacity-100 opacity-100 opacity-0 transition transition duration-1000 duration-1000 ease-out ease-out"
|
||||
/>
|
||||
<Menu>
|
||||
<span className="rounded-md shadow-sm">
|
||||
<Menu.Button className="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">
|
||||
<Menu.Button as={Button}>
|
||||
<span>Options</span>
|
||||
<svg className="ml-2 -mr-1 h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react'
|
||||
import { Menu } from '@headlessui/react'
|
||||
|
||||
import { classNames } from '../../utils/class-names'
|
||||
import { Button } from '../../components/button'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
@@ -9,7 +10,7 @@ export default function Home() {
|
||||
<div className="relative inline-block text-left">
|
||||
<Menu>
|
||||
<span className="rounded-md shadow-sm">
|
||||
<Menu.Button className="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">
|
||||
<Menu.Button as={Button}>
|
||||
<span>Options</span>
|
||||
<svg
|
||||
className="ml-2 -mr-1 h-5 w-5 transition-transform duration-150"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react'
|
||||
import { Menu } from '@headlessui/react'
|
||||
import { classNames } from '../../utils/class-names'
|
||||
import { Button } from '../../components/button'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
@@ -34,7 +35,7 @@ function Dropdown() {
|
||||
<div className="relative inline-block text-left">
|
||||
<Menu>
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<Menu.Button className="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">
|
||||
<Menu.Button as={Button}>
|
||||
<span>Options</span>
|
||||
<svg className="ml-2 -mr-1 h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
|
||||
@@ -17,10 +17,6 @@ export default function Home() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
data-preload
|
||||
className="translate-y-4 translate-y-0 translate-y-0 translate-y-4 opacity-0 opacity-100 opacity-100 opacity-0 opacity-0 opacity-100 opacity-100 opacity-0 duration-300 duration-200 duration-300 duration-200 ease-out ease-in ease-out ease-in sm:translate-y-0 sm:translate-y-0 sm:scale-95 sm:scale-100 sm:scale-100 sm:scale-95"
|
||||
/>
|
||||
<div className="flex space-x-4 p-12">
|
||||
<div className="inline-block p-12">
|
||||
<span className="mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto">
|
||||
|
||||
@@ -18,12 +18,6 @@ export default function Home() {
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<div
|
||||
hidden
|
||||
data-preload
|
||||
className="transform transform transform transform rounded-md rounded-md bg-red-500 bg-blue-500 bg-red-500 bg-green-500 bg-green-500 bg-blue-500 bg-white p-4 p-4 opacity-0 opacity-100 opacity-100 opacity-0 shadow shadow transition-colors transition-colors transition-colors transition transition duration-[5s] duration-1000 duration-1000 duration-1000 duration-1000 ease-out ease-in ease-out ease-in"
|
||||
/>
|
||||
|
||||
<Transition
|
||||
show={isOpen}
|
||||
appear={false}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
class="ui-focus-visible:ring-2 ui-focus-visible:ring-offset-2 flex items-center rounded-md border border-gray-300 bg-white px-2 py-1 ring-gray-500 ring-offset-gray-100 focus:outline-none"
|
||||
v-bind="$props"
|
||||
>
|
||||
<slot></slot>
|
||||
</button>
|
||||
</template>
|
||||
@@ -72,25 +72,19 @@
|
||||
</p>
|
||||
<div class="relative mt-10 inline-block text-left">
|
||||
<Menu>
|
||||
<span class="rounded-md shadow-sm">
|
||||
<MenuButton
|
||||
ref="trigger"
|
||||
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>Choose a reason</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>
|
||||
<MenuButton
|
||||
ref="trigger"
|
||||
class="ui-focus-visible:ring-2 ui-focus-visible:ring-offset-2 flex items-center rounded-md border border-gray-300 bg-white px-2 py-1 ring-gray-500 ring-offset-gray-100 focus:outline-none"
|
||||
>
|
||||
<span>Choose a reason</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>
|
||||
|
||||
<TransitionRoot
|
||||
enter="transition duration-300 ease-out"
|
||||
@@ -152,20 +146,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
|
||||
<button
|
||||
type="button"
|
||||
@click="setIsOpen(false)"
|
||||
class="focus:shadow-outline-red inline-flex w-full justify-center rounded-md border border-transparent bg-red-600 px-4 py-2 text-base font-medium text-white shadow-sm hover:bg-red-700 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm"
|
||||
>
|
||||
Deactivate
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@click="setIsOpen(false)"
|
||||
class="focus:shadow-outline-indigo mt-3 inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium text-gray-700 shadow-sm hover:text-gray-500 focus:outline-none sm:mt-0 sm:w-auto sm:text-sm"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<Button type="button" @click="setIsOpen(false)"> Deactivate </Button>
|
||||
<Button @click="setIsOpen(false)"> Cancel </Button>
|
||||
</div>
|
||||
</DialogPanel>
|
||||
</TransitionChild>
|
||||
@@ -192,6 +174,7 @@ import {
|
||||
} from '@headlessui/vue'
|
||||
import Flatpickr from 'vue-flatpickr-component'
|
||||
import { usePopper } from '../../playground-utils/hooks/use-popper'
|
||||
import Button from '../Button.vue'
|
||||
|
||||
import 'flatpickr/dist/themes/light.css'
|
||||
|
||||
@@ -207,22 +190,6 @@ function resolveClass({ active, disabled }) {
|
||||
)
|
||||
}
|
||||
|
||||
let Button = defineComponent({
|
||||
setup(props, { slots }) {
|
||||
return () =>
|
||||
h(
|
||||
'button',
|
||||
{
|
||||
type: 'button',
|
||||
class:
|
||||
'rounded bg-gray-200 px-2 py-1 ring-gray-500 ring-offset-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2',
|
||||
...props,
|
||||
},
|
||||
slots.default?.()
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
let Nested = defineComponent({
|
||||
components: { Dialog, DialogOverlay },
|
||||
emits: ['close'],
|
||||
@@ -245,7 +212,7 @@ let Nested = defineComponent({
|
||||
},
|
||||
[
|
||||
h('p', `Level: ${level}`),
|
||||
h('div', { class: 'space-x-4' }, [
|
||||
h('div', { class: 'flex gap-4' }, [
|
||||
h(Button, { onClick: () => (showChild.value = true) }, () => `Open ${level + 1} a`),
|
||||
h(Button, { onClick: () => (showChild.value = true) }, () => `Open ${level + 1} b`),
|
||||
h(Button, { onClick: () => (showChild.value = true) }, () => `Open ${level + 1} c`),
|
||||
@@ -290,6 +257,7 @@ export default {
|
||||
let date = ref(new Date())
|
||||
|
||||
return {
|
||||
Button,
|
||||
nested,
|
||||
date,
|
||||
isOpen,
|
||||
|
||||
@@ -4504,6 +4504,14 @@ postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.6:
|
||||
cssesc "^3.0.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
postcss-selector-parser@^6.0.11:
|
||||
version "6.0.11"
|
||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz#2e41dc39b7ad74046e1615185185cd0b17d0c8dc"
|
||||
integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==
|
||||
dependencies:
|
||||
cssesc "^3.0.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
|
||||
@@ -4518,6 +4526,15 @@ postcss@8.4.5:
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.1"
|
||||
|
||||
postcss@^8.0.9:
|
||||
version "8.4.21"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4"
|
||||
integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==
|
||||
dependencies:
|
||||
nanoid "^3.3.4"
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
postcss@^8.1.10:
|
||||
version "8.4.8"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.8.tgz#dad963a76e82c081a0657d3a2f3602ce10c2e032"
|
||||
@@ -4545,15 +4562,6 @@ postcss@^8.4.16:
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
postcss@^8.4.18:
|
||||
version "8.4.19"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.19.tgz#61178e2add236b17351897c8bcc0b4c8ecab56fc"
|
||||
integrity sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==
|
||||
dependencies:
|
||||
nanoid "^3.3.4"
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
prelude-ls@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
||||
@@ -5383,10 +5391,10 @@ tailwindcss@^0.0.0-insiders.83b4811:
|
||||
quick-lru "^5.1.1"
|
||||
resolve "^1.22.0"
|
||||
|
||||
tailwindcss@^3.2.4:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.2.4.tgz#afe3477e7a19f3ceafb48e4b083e292ce0dc0250"
|
||||
integrity sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==
|
||||
tailwindcss@^3.2.7:
|
||||
version "3.2.7"
|
||||
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.2.7.tgz#5936dd08c250b05180f0944500c01dce19188c07"
|
||||
integrity sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==
|
||||
dependencies:
|
||||
arg "^5.0.2"
|
||||
chokidar "^3.5.3"
|
||||
@@ -5402,12 +5410,12 @@ tailwindcss@^3.2.4:
|
||||
normalize-path "^3.0.0"
|
||||
object-hash "^3.0.0"
|
||||
picocolors "^1.0.0"
|
||||
postcss "^8.4.18"
|
||||
postcss "^8.0.9"
|
||||
postcss-import "^14.1.0"
|
||||
postcss-js "^4.0.0"
|
||||
postcss-load-config "^3.1.4"
|
||||
postcss-nested "6.0.0"
|
||||
postcss-selector-parser "^6.0.10"
|
||||
postcss-selector-parser "^6.0.11"
|
||||
postcss-value-parser "^4.2.0"
|
||||
quick-lru "^5.1.1"
|
||||
resolve "^1.22.1"
|
||||
|
||||
Reference in New Issue
Block a user