@@ -27,10 +27,15 @@ interface MultiSelectContextProps {
2727 setActiveIndex : React . Dispatch < React . SetStateAction < number > >
2828 size : MultiSelectorProps [ 'size' ]
2929 disabled ?: boolean
30+ dropdownPlacement : 'top' | 'bottom'
31+ dropdownMaxHeight : number
3032}
3133
3234const MultiSelectContext = React . createContext < MultiSelectContextProps | null > ( null )
3335
36+ const DROPDOWN_MAX_HEIGHT = 300
37+ const DROPDOWN_GAP = 8
38+
3439const commandItemClass = cn (
3540 'relative text-foreground-lighter text-left px-2 py-1.5 rounded' ,
3641 'hover:text-foreground hover:!bg-overlay-hover w-full flex items-center space-x-2' ,
@@ -81,6 +86,9 @@ function MultiSelector({
8186 const [ inputValue , setInputValue ] = React . useState < string > ( '' )
8287 const [ activeIndex , setActiveIndex ] = React . useState < number > ( - 1 )
8388
89+ const [ dropdownPlacement , setDropdownPlacement ] = React . useState < 'top' | 'bottom' > ( 'bottom' )
90+ const [ dropdownMaxHeight , setDropdownMaxHeight ] = React . useState < number > ( DROPDOWN_MAX_HEIGHT )
91+
8492 const toggleValue = React . useCallback (
8593 ( toggledValue : string ) => {
8694 if ( values . includes ( toggledValue ) ) {
@@ -92,10 +100,42 @@ function MultiSelector({
92100 [ values ]
93101 )
94102
103+ const updateDropdownMetrics = React . useCallback ( ( ) => {
104+ if ( typeof window === 'undefined' ) return
105+ const triggerEl = ref . current as HTMLDivElement | null
106+ if ( ! triggerEl ) return
107+
108+ const rect = triggerEl . getBoundingClientRect ( )
109+ const viewportHeight = window . innerHeight
110+ const spaceBelow = viewportHeight - rect . bottom - DROPDOWN_GAP
111+ const spaceAbove = rect . top - DROPDOWN_GAP
112+ const shouldDropUp = spaceBelow < DROPDOWN_MAX_HEIGHT && spaceAbove > spaceBelow
113+ const placement = shouldDropUp ? 'top' : 'bottom'
114+ const availableSpace = Math . max ( placement === 'top' ? spaceAbove : spaceBelow , 0 )
115+ const nextHeight =
116+ availableSpace > 0 ? Math . min ( DROPDOWN_MAX_HEIGHT , availableSpace ) : DROPDOWN_MAX_HEIGHT
117+
118+ setDropdownPlacement ( placement )
119+ setDropdownMaxHeight ( nextHeight )
120+ } , [ ] )
121+
122+ useEffect ( ( ) => {
123+ if ( ! open ) return
124+ const controller = new AbortController ( )
125+ const { signal } = controller
126+
127+ const handleUpdate = updateDropdownMetrics
128+ handleUpdate ( )
129+ window . addEventListener ( 'resize' , handleUpdate , { signal } )
130+ window . addEventListener ( 'scroll' , handleUpdate , { capture : true , passive : true , signal } )
131+
132+ return ( ) => controller . abort ( )
133+ } , [ open , updateDropdownMetrics ] )
134+
95135 // detect clicks from outside
96136 useOnClickOutside ( ref , ( ) => {
97137 if ( open ) {
98- setOpen ( ! open )
138+ setOpen ( false )
99139 }
100140 } )
101141
@@ -143,6 +183,8 @@ function MultiSelector({
143183 setActiveIndex,
144184 size : size || 'small' ,
145185 disabled,
186+ dropdownPlacement,
187+ dropdownMaxHeight,
146188 } }
147189 >
148190 < Command
@@ -183,7 +225,8 @@ const MultiSelectorTrigger = React.forwardRef<HTMLButtonElement, MultiSelectorTr
183225 } ,
184226 ref
185227 ) => {
186- const { activeIndex, values, setInputValue, toggleValue, disabled, setOpen } = useMultiSelect ( )
228+ const { activeIndex, values, setInputValue, toggleValue, disabled, open, setOpen } =
229+ useMultiSelect ( )
187230
188231 const inputRef = React . useRef < HTMLButtonElement > ( null )
189232
@@ -216,19 +259,27 @@ const MultiSelectorTrigger = React.forwardRef<HTMLButtonElement, MultiSelectorTr
216259
217260 const handleTriggerClick : React . MouseEventHandler < HTMLButtonElement > = React . useCallback (
218261 ( event ) => {
219- setOpen ( true )
220- setInputValue ( '' )
221-
222262 if ( IS_INLINE_MODE ) {
263+ if ( ! open ) {
264+ setOpen ( true )
265+ setInputValue ( '' )
266+ }
267+
223268 event . stopPropagation ( )
224269 event . preventDefault ( )
225270
226271 setTimeout ( ( ) => {
227272 inlineInputRef . current ?. focus ( )
228273 } , 100 )
274+
275+ return
229276 }
277+
278+ const willOpen = ! open
279+ setOpen ( willOpen )
280+ if ( willOpen ) setInputValue ( '' )
230281 } ,
231- [ ]
282+ [ open , setOpen , setInputValue , IS_INLINE_MODE ]
232283 )
233284
234285 return (
@@ -404,16 +455,21 @@ MultiSelector.Input = MultiSelectorInput
404455
405456const MultiSelectorContent = React . forwardRef < HTMLDivElement , React . HTMLAttributes < HTMLDivElement > > (
406457 ( { className, children } , ref ) => {
407- const { open } = useMultiSelect ( )
458+ const { open, dropdownPlacement } = useMultiSelect ( )
459+
460+ const closedTranslationClass = dropdownPlacement === 'top' ? 'translate-y-3' : '-translate-y-3'
408461
409462 return (
410463 < div
411464 ref = { ref }
412465 className = { cn (
413- 'absolute w-full bg-overlay shadow-md z-10 border border-overlay top-[calc(100%+0.25rem)] rounded-md transition-all -translate-y-3' ,
466+ 'absolute w-full bg-overlay shadow-md z-10 border border-overlay rounded-md transition-all' ,
467+ dropdownPlacement === 'top'
468+ ? 'bottom-[calc(100%+0.25rem)] origin-bottom'
469+ : 'top-[calc(100%+0.25rem)] origin-top' ,
414470 open
415471 ? 'opacity-100 translate-y-0 visible duration-150 ease-[.76,0,.23,1]'
416- : 'opacity-0 -translate-y-3 invisible duration-0' ,
472+ : cn ( 'opacity-0 invisible duration-0' , closedTranslationClass ) ,
417473 className
418474 ) }
419475 >
@@ -432,7 +488,7 @@ const MultiSelectorList = React.forwardRef<
432488 creatable ?: boolean
433489 }
434490> ( ( { className, children, creatable = false } , ref ) => {
435- const { open, inputValue, setInputValue, toggleValue } = useMultiSelect ( )
491+ const { open, inputValue, setInputValue, toggleValue, dropdownMaxHeight } = useMultiSelect ( )
436492
437493 const options = ! ! children
438494 ? Array . isArray ( children )
@@ -452,9 +508,10 @@ const MultiSelectorList = React.forwardRef<
452508 className = { cn (
453509 'p-2 flex flex-col gap-2 scrollbar-thin scrollbar-track-transparent transition-colors' ,
454510 'scrollbar-thumb-muted-foreground dark:scrollbar-thumb-muted' ,
455- 'scrollbar-thumb-rounded-lg w-full max-h-[300px] overflow-y-auto' ,
511+ 'scrollbar-thumb-rounded-lg w-full overflow-y-auto' ,
456512 className
457513 ) }
514+ style = { { maxHeight : dropdownMaxHeight } }
458515 >
459516 { children }
460517 { creatable && inputValue . length > 0 && ! isOptionExists ? (
0 commit comments