Skip to content

Commit f688367

Browse files
committed
add Menu examples in Vue
1 parent bd389b3 commit f688367

File tree

6 files changed

+257
-89
lines changed

6 files changed

+257
-89
lines changed

packages/@headlessui-vue/examples/src/components/menu/menu-with-popper.vue

Lines changed: 25 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22
<div class="flex justify-center w-screen h-full p-12 bg-gray-50">
3-
<div class="relative inline-block text-left">
3+
<div class="relative inline-block mt-64 text-left">
44
<Menu>
55
<span class="inline-flex rounded-md shadow-sm">
66
<MenuButton
@@ -18,76 +18,45 @@
1818
</MenuButton>
1919
</span>
2020

21-
<transition
22-
enter-active-class="transition-opacity duration-100 ease-out"
23-
enter-from-class="transform scale-95 opacity-0"
24-
enter-to-class="transform scale-100 opacity-100"
25-
leave-active-class="transition-opacity duration-75 ease-out"
26-
leave-from-class="transform scale-100 opacity-100"
27-
leave-to-class="transform scale-95 opacity-0"
21+
<MenuItems
22+
ref="popper"
23+
class="absolute right-0 w-56 origin-top-right bg-white border border-gray-200 divide-y divide-gray-100 rounded-md shadow-lg outline-none"
2824
>
29-
<MenuItems
30-
ref="popper"
31-
class="absolute right-0 w-56 origin-top-right bg-white border border-gray-200 divide-y divide-gray-100 rounded-md shadow-lg outline-none"
32-
>
33-
<div class="px-4 py-3">
34-
<p class="text-sm leading-5">Signed in as</p>
35-
<p class="text-sm font-medium leading-5 text-gray-900 truncate">[email protected]</p>
36-
</div>
25+
<div class="px-4 py-3">
26+
<p class="text-sm leading-5">Signed in as</p>
27+
<p class="text-sm font-medium leading-5 text-gray-900 truncate">[email protected]</p>
28+
</div>
3729

38-
<div class="py-1">
39-
<MenuItem as="a" :className="resolveClass" href="#account-settings">
40-
Account Settings
41-
</MenuItem>
42-
<MenuItem v-slot="data">
43-
<a href="#support" :class="resolveClass(data)">Support</a>
44-
</MenuItem>
45-
<MenuItem as="a" :className="resolveClass" disabled href="#new-feature">
46-
New feature (soon)
47-
</MenuItem>
48-
<MenuItem as="a" :className="resolveClass" href="#license">License</MenuItem>
49-
</div>
50-
<div class="py-1">
51-
<MenuItem as="a" :className="resolveClass" href="#sign-out">Sign out</MenuItem>
52-
</div>
53-
</MenuItems>
54-
</transition>
30+
<div class="py-1">
31+
<MenuItem as="a" :className="resolveClass" href="#account-settings">
32+
Account Settings
33+
</MenuItem>
34+
<MenuItem v-slot="data">
35+
<a href="#support" :class="resolveClass(data)">Support</a>
36+
</MenuItem>
37+
<MenuItem as="a" :className="resolveClass" disabled href="#new-feature">
38+
New feature (soon)
39+
</MenuItem>
40+
<MenuItem as="a" :className="resolveClass" href="#license">License</MenuItem>
41+
</div>
42+
<div class="py-1">
43+
<MenuItem as="a" :className="resolveClass" href="#sign-out">Sign out</MenuItem>
44+
</div>
45+
</MenuItems>
5546
</Menu>
5647
</div>
5748
</div>
5849
</template>
5950

6051
<script>
6152
import { defineComponent, h, ref, onMounted, watchEffect, watch } from 'vue'
62-
63-
import { createPopper } from '@popperjs/core'
53+
import { usePopper } from '../../hooks/use-popper'
6454
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
6555
6656
function classNames(...classes) {
6757
return classes.filter(Boolean).join(' ')
6858
}
6959
70-
function usePopper(options) {
71-
const reference = ref(null)
72-
const popper = ref(null)
73-
74-
onMounted(() => {
75-
watchEffect(onInvalidate => {
76-
const popperEl = popper.value.el || popper.value
77-
const referenceEl = reference.value.el || reference.value
78-
79-
if (!(referenceEl instanceof HTMLElement)) return
80-
if (!(popperEl instanceof HTMLElement)) return
81-
82-
const { destroy } = createPopper(referenceEl, popperEl, options)
83-
84-
onInvalidate(destroy)
85-
})
86-
})
87-
88-
return [reference, popper]
89-
}
90-
9160
export default {
9261
components: { Menu, MenuButton, MenuItems, MenuItem },
9362
setup(props, context) {
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<template>
2+
<div class="flex justify-center w-screen h-full p-12 bg-gray-50">
3+
<div class="relative inline-block mt-64 text-left">
4+
<Menu>
5+
<span class="inline-flex rounded-md shadow-sm">
6+
<MenuButton
7+
ref="reference"
8+
class="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"
9+
>
10+
<span>Options</span>
11+
<svg class="w-5 h-5 ml-2 -mr-1" viewBox="0 0 20 20" fill="currentColor">
12+
<path
13+
fillRule="evenodd"
14+
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"
15+
clipRule="evenodd"
16+
/>
17+
</svg>
18+
</MenuButton>
19+
</span>
20+
21+
<div ref="popper" class="w-56">
22+
<transition
23+
enter-active-class="transition duration-100 ease-out"
24+
enter-from-class="transform scale-95 opacity-0"
25+
enter-to-class="transform scale-100 opacity-100"
26+
leave-active-class="transition duration-75 ease-out"
27+
leave-from-class="transform scale-100 opacity-100"
28+
leave-to-class="transform scale-95 opacity-0"
29+
>
30+
<MenuItems
31+
class="w-full bg-white border border-gray-200 divide-y divide-gray-100 rounded-md shadow-lg outline-none"
32+
>
33+
<div class="px-4 py-3">
34+
<p class="text-sm leading-5">Signed in as</p>
35+
<p class="text-sm font-medium leading-5 text-gray-900 truncate">[email protected]</p>
36+
</div>
37+
38+
<div class="py-1">
39+
<MenuItem as="a" :className="resolveClass" href="#account-settings">
40+
Account Settings
41+
</MenuItem>
42+
<MenuItem v-slot="data">
43+
<a href="#support" :class="resolveClass(data)">Support</a>
44+
</MenuItem>
45+
<MenuItem as="a" :className="resolveClass" disabled href="#new-feature">
46+
New feature (soon)
47+
</MenuItem>
48+
<MenuItem as="a" :className="resolveClass" href="#license">License</MenuItem>
49+
</div>
50+
<div class="py-1">
51+
<MenuItem as="a" :className="resolveClass" href="#sign-out">Sign out</MenuItem>
52+
</div>
53+
</MenuItems>
54+
</transition>
55+
</div>
56+
</Menu>
57+
</div>
58+
</div>
59+
</template>
60+
61+
<script>
62+
import { defineComponent, h, ref, onMounted, watchEffect, watch } from 'vue'
63+
import { usePopper } from '../../hooks/use-popper'
64+
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
65+
66+
function classNames(...classes) {
67+
return classes.filter(Boolean).join(' ')
68+
}
69+
70+
export default {
71+
components: { Menu, MenuButton, MenuItems, MenuItem },
72+
setup(props, context) {
73+
const [reference, popper] = usePopper({
74+
placement: 'bottom-end',
75+
strategy: 'fixed',
76+
modifiers: [{ name: 'offset', options: { offset: [0, 10] } }],
77+
})
78+
79+
return {
80+
reference,
81+
popper,
82+
resolveClass({ active, disabled }) {
83+
return classNames(
84+
'flex justify-between w-full px-4 py-2 text-sm leading-5 text-left',
85+
active ? 'bg-gray-100 text-gray-900' : 'text-gray-700',
86+
disabled && 'cursor-not-allowed opacity-50'
87+
)
88+
},
89+
}
90+
},
91+
}
92+
</script>

packages/@headlessui-vue/examples/src/components/menu/menu-with-tailwind.vue renamed to packages/@headlessui-vue/examples/src/components/menu/menu-with-transition.vue

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,17 @@
3434
</div>
3535

3636
<div class="py-1">
37-
<CustomMenuItem href="#account-settings">Account Settings</CustomMenuItem>
38-
<CustomMenuItem href="#support">Support</CustomMenuItem>
39-
<CustomMenuItem disabled href="#new-feature">New feature (soon)</CustomMenuItem>
40-
<CustomMenuItem href="#license">License</CustomMenuItem>
37+
<MenuItem as="a" :className="resolveClass" href="#account-settings">
38+
Account Settings
39+
</MenuItem>
40+
<MenuItem as="a" :className="resolveClass" href="#support">Support</MenuItem>
41+
<MenuItem as="a" disabled :className="resolveClass" href="#new-feature">
42+
New feature (soon)
43+
</MenuItem>
44+
<MenuItem as="a" :className="resolveClass" href="#license">License</MenuItem>
4145
</div>
4246
<div class="py-1">
43-
<CustomMenuItem href="#sign-out">Sign out</CustomMenuItem>
47+
<MenuItem as="a" :className="resolveClass" href="#sign-out">Sign out</MenuItem>
4448
</div>
4549
</MenuItems>
4650
</transition>
@@ -57,37 +61,23 @@ function classNames(...classes) {
5761
return classes.filter(Boolean).join(' ')
5862
}
5963
60-
const CustomMenuItem = defineComponent({
61-
components: { Menu, MenuButton, MenuItems, MenuItem },
62-
setup(props, { slots }) {
63-
return () => {
64-
return h(MenuItem, ({ active, disabled }) => {
65-
return h(
66-
'a',
67-
{
68-
class: classNames(
69-
'flex justify-between w-full text-left px-4 py-2 text-sm leading-5',
70-
active ? 'bg-indigo-500 text-white' : 'text-gray-700',
71-
disabled && 'cursor-not-allowed opacity-50'
72-
),
73-
},
74-
[
75-
h('span', { class: classNames(active && 'font-bold') }, slots.default()),
76-
h('kbd', { class: classNames('font-sans', active && 'text-indigo-50') }, '⌘K'),
77-
]
78-
)
79-
})
80-
}
81-
},
82-
})
83-
8464
export default {
8565
components: {
8666
Menu,
8767
MenuButton,
8868
MenuItems,
8969
MenuItem,
90-
CustomMenuItem,
70+
},
71+
setup() {
72+
return {
73+
resolveClass({ active, disabled }) {
74+
return classNames(
75+
'flex justify-between w-full px-4 py-2 text-sm leading-5 text-left',
76+
active ? 'bg-gray-100 text-gray-900' : 'text-gray-700',
77+
disabled && 'cursor-not-allowed opacity-50'
78+
)
79+
},
80+
}
9181
},
9282
}
9383
</script>
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<template>
2+
<div class="flex justify-center w-screen h-full p-12 bg-gray-50">
3+
<div class="relative inline-block text-left">
4+
<Menu>
5+
<span class="rounded-md shadow-sm">
6+
<MenuButton
7+
class="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"
8+
>
9+
<span>Options</span>
10+
<svg class="w-5 h-5 ml-2 -mr-1" viewBox="0 0 20 20" fill="currentColor">
11+
<path
12+
fillRule="evenodd"
13+
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"
14+
clipRule="evenodd"
15+
/>
16+
</svg>
17+
</MenuButton>
18+
</span>
19+
20+
<MenuItems
21+
class="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"
22+
>
23+
<div class="px-4 py-3">
24+
<p class="text-sm leading-5">Signed in as</p>
25+
<p class="text-sm font-medium leading-5 text-gray-900 truncate">[email protected]</p>
26+
</div>
27+
28+
<div class="py-1">
29+
<CustomMenuItem href="#account-settings">Account Settings</CustomMenuItem>
30+
<CustomMenuItem href="#support">Support</CustomMenuItem>
31+
<CustomMenuItem disabled href="#new-feature">New feature (soon)</CustomMenuItem>
32+
<CustomMenuItem href="#license">License</CustomMenuItem>
33+
</div>
34+
<div class="py-1">
35+
<CustomMenuItem href="#sign-out">Sign out</CustomMenuItem>
36+
</div>
37+
</MenuItems>
38+
</Menu>
39+
</div>
40+
</div>
41+
</template>
42+
43+
<script>
44+
import { defineComponent, h } from 'vue'
45+
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
46+
47+
function classNames(...classes) {
48+
return classes.filter(Boolean).join(' ')
49+
}
50+
51+
const CustomMenuItem = defineComponent({
52+
components: { Menu, MenuButton, MenuItems, MenuItem },
53+
setup(props, { slots }) {
54+
return () => {
55+
return h(MenuItem, ({ active, disabled }) => {
56+
return h(
57+
'a',
58+
{
59+
class: classNames(
60+
'flex justify-between w-full text-left px-4 py-2 text-sm leading-5',
61+
active ? 'bg-indigo-500 text-white' : 'text-gray-700',
62+
disabled && 'cursor-not-allowed opacity-50'
63+
),
64+
},
65+
[
66+
h('span', { class: classNames(active && 'font-bold') }, slots.default()),
67+
h('kbd', { class: classNames('font-sans', active && 'text-indigo-50') }, '⌘K'),
68+
]
69+
)
70+
})
71+
}
72+
},
73+
})
74+
75+
export default {
76+
components: {
77+
Menu,
78+
MenuButton,
79+
MenuItems,
80+
MenuItem,
81+
CustomMenuItem,
82+
},
83+
}
84+
</script>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ref, onMounted, watchEffect } from 'vue'
2+
import { createPopper } from '@popperjs/core'
3+
4+
export function usePopper(options) {
5+
const reference = ref(null)
6+
const popper = ref(null)
7+
8+
onMounted(() => {
9+
watchEffect(onInvalidate => {
10+
const popperEl = popper.value.el || popper.value
11+
const referenceEl = reference.value.el || reference.value
12+
13+
if (!(referenceEl instanceof HTMLElement)) return
14+
if (!(popperEl instanceof HTMLElement)) return
15+
16+
const { destroy } = createPopper(referenceEl, popperEl, options)
17+
18+
onInvalidate(destroy)
19+
})
20+
})
21+
22+
return [reference, popper]
23+
}

0 commit comments

Comments
 (0)