1
- import { Listbox , Transition } from '@headlessui/react'
1
+ import {
2
+ Listbox ,
3
+ ListboxButton ,
4
+ ListboxOption ,
5
+ ListboxOptions
6
+ } from '@headlessui/react'
2
7
import cn from 'clsx'
3
- import { useMounted } from 'nextra/hooks'
4
8
import { CheckIcon } from 'nextra/icons'
5
- import type { ReactElement , ReactNode } from 'react'
6
- import { createPortal } from 'react-dom'
7
- import { usePopper } from '../utils'
9
+ import type { ReactElement } from 'react'
8
10
9
11
interface MenuOption {
10
12
key : string
@@ -26,28 +28,10 @@ export function Select({
26
28
title,
27
29
className
28
30
} : MenuProps ) : ReactElement {
29
- const [ trigger , container ] = usePopper ( {
30
- strategy : 'fixed' ,
31
- placement : 'top-start' ,
32
- modifiers : [
33
- { name : 'offset' , options : { offset : [ 0 , 10 ] } } ,
34
- {
35
- name : 'sameWidth' ,
36
- enabled : true ,
37
- fn ( { state } ) {
38
- state . styles . popper . minWidth = `${ state . rects . reference . width } px`
39
- } ,
40
- phase : 'beforeWrite' ,
41
- requires : [ 'computeStyles' ]
42
- }
43
- ]
44
- } )
45
-
46
31
return (
47
32
< Listbox value = { selected } onChange = { onChange } >
48
33
{ ( { open } ) => (
49
- < Listbox . Button
50
- ref = { trigger }
34
+ < ListboxButton
51
35
title = { title }
52
36
className = { cn (
53
37
'_h-7 _rounded-md _px-2 _text-left _text-xs _font-medium _text-gray-600 _transition-colors dark:_text-gray-400' ,
@@ -58,48 +42,39 @@ export function Select({
58
42
) }
59
43
>
60
44
{ selected . name }
61
- < Portal >
62
- < Transition
63
- ref = { container }
64
- show = { open }
65
- as = { Listbox . Options }
66
- className = "_z-20 _max-h-64 _overflow-auto _rounded-md _ring-1 _ring-black/5 _bg-white _py-1 _text-sm _shadow-lg dark:_ring-white/20 dark:_bg-neutral-800"
67
- leave = "_transition-opacity"
68
- leaveFrom = "_opacity-100"
69
- leaveTo = "_opacity-0"
70
- >
71
- { options . map ( option => (
72
- < Listbox . Option
73
- key = { option . key }
74
- value = { option }
75
- className = { ( { active } ) =>
76
- cn (
77
- active
78
- ? '_bg-primary-50 _text-primary-600 dark:_bg-primary-500/10'
79
- : '_text-gray-800 dark:_text-gray-100' ,
80
- '_relative _cursor-pointer _whitespace-nowrap _py-1.5' ,
81
- '_transition-colors ltr:_pl-3 ltr:_pr-9 rtl:_pr-3 rtl:_pl-9'
82
- )
83
- }
84
- >
85
- { option . name }
86
- { option . key === selected . key && (
87
- < span className = "_absolute _inset-y-0 _flex _items-center ltr:_right-3 rtl:_left-3" >
88
- < CheckIcon />
89
- </ span >
90
- ) }
91
- </ Listbox . Option >
92
- ) ) }
93
- </ Transition >
94
- </ Portal >
95
- </ Listbox . Button >
45
+ < ListboxOptions
46
+ transition
47
+ anchor = { {
48
+ to : 'top start' ,
49
+ gap : 10
50
+ } }
51
+ className = "_transition-opacity data-[closed]:_opacity-0 data-[open]:_opacity-100 _min-w-[--button-width] _z-20 _max-h-64 _overflow-auto _rounded-md _ring-1 _ring-black/5 _bg-white _py-1 _text-sm _shadow-lg dark:_ring-white/20 dark:_bg-neutral-800"
52
+ >
53
+ { options . map ( option => (
54
+ < ListboxOption
55
+ key = { option . key }
56
+ value = { option }
57
+ className = { ( { focus } ) =>
58
+ cn (
59
+ focus
60
+ ? '_bg-primary-50 _text-primary-600 dark:_bg-primary-500/10'
61
+ : '_text-gray-800 dark:_text-gray-100' ,
62
+ '_relative _cursor-pointer _whitespace-nowrap _py-1.5' ,
63
+ '_transition-colors ltr:_pl-3 ltr:_pr-9 rtl:_pr-3 rtl:_pl-9'
64
+ )
65
+ }
66
+ >
67
+ { option . name }
68
+ { option . key === selected . key && (
69
+ < span className = "_absolute _inset-y-0 _flex _items-center ltr:_right-3 rtl:_left-3" >
70
+ < CheckIcon />
71
+ </ span >
72
+ ) }
73
+ </ ListboxOption >
74
+ ) ) }
75
+ </ ListboxOptions >
76
+ </ ListboxButton >
96
77
) }
97
78
</ Listbox >
98
79
)
99
80
}
100
-
101
- function Portal ( props : { children : ReactNode } ) : ReactElement | null {
102
- const mounted = useMounted ( )
103
- if ( ! mounted ) return null
104
- return createPortal ( props . children , document . body )
105
- }
0 commit comments