🎨 style: adjust the popover position based on the user screen

This commit is contained in:
Wahyu Kurniawan 2024-02-24 05:10:35 +07:00
parent b7fe301c81
commit c950bd8d93
No known key found for this signature in database
GPG Key ID: 040A1549143A8E33

View File

@ -5,6 +5,8 @@ import React, {
useMemo, useMemo,
useCallback, useCallback,
MouseEvent, MouseEvent,
useRef,
useEffect,
} from 'react'; } from 'react';
import { useMultipleSelection, useCombobox } from 'downshift'; import { useMultipleSelection, useCombobox } from 'downshift';
import { SelectTheme, selectTheme } from './Select.theme'; import { SelectTheme, selectTheme } from './Select.theme';
@ -62,6 +64,34 @@ export const MultiSelect: React.FC<MultiSelectProps> = ({
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const [selectedItem, setSelectedItem] = useState<DropdownItem | null>(null); const [selectedItem, setSelectedItem] = useState<DropdownItem | null>(null);
const [dropdownOpen, setDropdownOpen] = useState(false); const [dropdownOpen, setDropdownOpen] = useState(false);
const [dropdownPosition, setDropdownPosition] = useState<'top' | 'bottom'>(
'bottom',
);
const popoverRef = useRef(null); // Ref for the popover
const inputWrapperRef = useRef(null); // Ref for the input wrapper
// Calculate and update popover position
useEffect(() => {
if (dropdownOpen && popoverRef.current && inputWrapperRef.current) {
const popover = popoverRef.current;
// @ts-expect-error we know it's not null
const input = inputWrapperRef.current.getBoundingClientRect();
const spaceBelow = window.innerHeight - input.bottom;
const spaceAbove = input.top;
// @ts-expect-error we know it's not null
const popoverHeight = popover.offsetHeight;
// Determine if there's enough space below
if (spaceBelow >= popoverHeight) {
setDropdownPosition('bottom');
} else if (spaceAbove >= popoverHeight) {
setDropdownPosition('top');
} else {
// Default to bottom if neither has enough space, but you could also set logic to choose the side with more space
setDropdownPosition('bottom');
}
}
}, [dropdownOpen]); // Re-calculate whenever the dropdown is opened
const handleSelectedItemChange = (selectedItem: DropdownItem | null) => { const handleSelectedItemChange = (selectedItem: DropdownItem | null) => {
setSelectedItem(selectedItem); setSelectedItem(selectedItem);
@ -211,6 +241,7 @@ export const MultiSelect: React.FC<MultiSelectProps> = ({
<div className={theme.container()}> <div className={theme.container()}>
{renderLabels} {renderLabels}
<div <div
ref={inputWrapperRef}
className={theme.inputWrapper({ className={theme.inputWrapper({
hasValue: multiple && selectedItems.length > 0, hasValue: multiple && selectedItems.length > 0,
})} })}
@ -250,7 +281,15 @@ export const MultiSelect: React.FC<MultiSelectProps> = ({
{renderRightIcon} {renderRightIcon}
</div> </div>
{renderHelperText} {renderHelperText}
<ul {...getMenuProps()} className={theme.popover({ isOpen })}> <ul
{...getMenuProps()}
id="popover"
ref={popoverRef}
className={cn(theme.popover({ isOpen }), {
'top-1/4': dropdownPosition === 'bottom',
'bottom-[95%]': dropdownPosition === 'top',
})}
>
{isOpen && filteredItems.length !== 0 ? ( {isOpen && filteredItems.length !== 0 ? (
filteredItems.map((item, index) => ( filteredItems.map((item, index) => (
<li <li