Skip to content

Commit

Permalink
fix custom select (#1002)
Browse files Browse the repository at this point in the history
## Done

- align dropdown width to custom select wrapper width
- focus wrapper after selection of an option in the dropdown
- align z-index of dropdown to custom select parents, so when the custom
select is in a modal, its dropdown is rendered above the modal.
- avoid bubbling up mouse down events on the dropdown, so a modal stays
open
  • Loading branch information
mas-who authored Nov 25, 2024
2 parents 6e05ea3 + 952cffb commit 46ee650
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 4 deletions.
2 changes: 2 additions & 0 deletions src/components/select/CustomSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ const CustomSelect: FC<Props> = ({
);

const handleSelect = (value: string) => {
document.getElementById(selectId)?.focus();
setIsOpen(false);
onChange(value);
};
Expand Down Expand Up @@ -197,6 +198,7 @@ const CustomSelect: FC<Props> = ({
document.getElementById(selectId)?.focus();
}}
header={header}
toggleId={selectId}
/>
)}
</ContextualMenu>
Expand Down
26 changes: 26 additions & 0 deletions src/components/select/CustomSelectDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import classnames from "classnames";
import { adjustDropdownHeight } from "util/customSelect";
import useEventListener from "@use-it/event-listener";
import { getNearestParentsZIndex } from "util/zIndex";

export type CustomSelectOption = LiHTMLAttributes<HTMLLIElement> & {
value: string;
Expand All @@ -29,6 +30,7 @@ interface Props {
onSelect: (value: string) => void;
onClose: () => void;
header?: ReactNode;
toggleId: string;
}

export const getOptionText = (option: CustomSelectOption): string => {
Expand Down Expand Up @@ -62,6 +64,7 @@ const CustomSelectDropdown: FC<Props> = ({
onSelect,
onClose,
header,
toggleId,
}) => {
const [search, setSearch] = useState("");
// track selected option index for keyboard actions
Expand All @@ -77,6 +80,24 @@ const CustomSelectDropdown: FC<Props> = ({
(searchable === "always" || (searchable === "auto" && options.length >= 5));

useEffect(() => {
if (dropdownRef.current) {
const toggle = document.getElementById(toggleId);

// align width with wrapper toggle width
const toggleWidth = toggle?.getBoundingClientRect()?.width ?? 0;
dropdownRef.current.style.setProperty("min-width", `${toggleWidth}px`);

// align z-index: when we are in a modal context, we want the dropdown to be above the modal
// apply the nearest parents z-index + 1
const zIndex = getNearestParentsZIndex(toggle);
if (parseInt(zIndex) > 0) {
dropdownRef.current.parentElement?.style.setProperty(
"z-index",
zIndex + 1,
);
}
}

setTimeout(() => {
if (isSearchable) {
searchRef.current?.focus();
Expand Down Expand Up @@ -210,6 +231,11 @@ const CustomSelectDropdown: FC<Props> = ({
// allow focus on the dropdown so that keyboard actions can be captured
tabIndex={-1}
ref={dropdownRef}
onMouseDown={(e) => {
// when custom select is used in a modal, which is a portal, a dropdown click
// should not close the modal itself, so we stop the event right here.
e.stopPropagation();
}}
>
{isSearchable && (
<div className="p-custom-select__search u-no-padding--bottom">
Expand Down
5 changes: 1 addition & 4 deletions src/sass/_custom_select.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
center;
background-repeat: no-repeat;
background-size: map-get($icon-sizes, default);
border-top: none;
box-shadow: none;
min-height: map-get($line-heights, default-text);
padding-right: calc($default-icon-size + 2 * $sph--small);
Expand Down Expand Up @@ -74,7 +75,3 @@
width: 20rem;
}
}

.p-custom-select__wrapper {
z-index: 400;
}
19 changes: 19 additions & 0 deletions src/util/zIndex.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,20 @@
export const TOOLTIP_OVER_MODAL_ZINDEX = 150;

// nearest parents z-index that is not 0 or auto
export const getNearestParentsZIndex = (
element: HTMLElement | null,
): string => {
if (!document.defaultView || !element) {
return "0";
}
const zIndex = document.defaultView
.getComputedStyle(element, null)
.getPropertyValue("z-index");
if (!element.parentElement) {
return zIndex;
}
if (zIndex === "auto" || zIndex === "0" || zIndex === "") {
return getNearestParentsZIndex(element.parentElement);
}
return zIndex;
};

0 comments on commit 46ee650

Please sign in to comment.