From 18a709de3b0f3aa10e3d2e8921733f0df1a02fa1 Mon Sep 17 00:00:00 2001 From: David Edler Date: Sat, 23 Nov 2024 01:28:49 +0100 Subject: [PATCH 1/2] fix(custom-select) align z-index to parent, align width to wrapper width, focus wrapper after select Signed-off-by: David Edler --- src/components/select/CustomSelect.tsx | 2 ++ .../select/CustomSelectDropdown.tsx | 34 +++++++++++++++++++ src/sass/_custom_select.scss | 4 --- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/components/select/CustomSelect.tsx b/src/components/select/CustomSelect.tsx index 5c9c643aa3..219b12ee1b 100644 --- a/src/components/select/CustomSelect.tsx +++ b/src/components/select/CustomSelect.tsx @@ -132,6 +132,7 @@ const CustomSelect: FC = ({ ); const handleSelect = (value: string) => { + document.getElementById(selectId)?.focus(); setIsOpen(false); onChange(value); }; @@ -197,6 +198,7 @@ const CustomSelect: FC = ({ document.getElementById(selectId)?.focus(); }} header={header} + toggleId={selectId} /> )} diff --git a/src/components/select/CustomSelectDropdown.tsx b/src/components/select/CustomSelectDropdown.tsx index 32904c2ac0..cfe341d5d6 100644 --- a/src/components/select/CustomSelectDropdown.tsx +++ b/src/components/select/CustomSelectDropdown.tsx @@ -29,6 +29,7 @@ interface Props { onSelect: (value: string) => void; onClose: () => void; header?: ReactNode; + toggleId: string; } export const getOptionText = (option: CustomSelectOption): string => { @@ -62,6 +63,7 @@ const CustomSelectDropdown: FC = ({ onSelect, onClose, header, + toggleId, }) => { const [search, setSearch] = useState(""); // track selected option index for keyboard actions @@ -77,6 +79,38 @@ const CustomSelectDropdown: FC = ({ (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: if in a modal context, take the next z-index we find above + 1 + const getRecursiveZIndex = (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 getRecursiveZIndex(element.parentElement); + } + return zIndex; + }; + const zIndex = getRecursiveZIndex(toggle); + if (parseInt(zIndex) > 0) { + dropdownRef.current.parentElement?.style.setProperty( + "z-index", + zIndex + 1, + ); + } + } + setTimeout(() => { if (isSearchable) { searchRef.current?.focus(); diff --git a/src/sass/_custom_select.scss b/src/sass/_custom_select.scss index f113c2cd65..4cdb322c5f 100644 --- a/src/sass/_custom_select.scss +++ b/src/sass/_custom_select.scss @@ -74,7 +74,3 @@ width: 20rem; } } - -.p-custom-select__wrapper { - z-index: 400; -} From 952cffbef11761966f2c15c45f4d5d55822fffcc Mon Sep 17 00:00:00 2001 From: David Edler Date: Sat, 23 Nov 2024 10:05:31 +0100 Subject: [PATCH 2/2] fix(custom-select) avoid bubbling up mouse down events from the dropdown, so a modal stays open Signed-off-by: David Edler --- .../select/CustomSelectDropdown.tsx | 26 +++++++------------ src/sass/_custom_select.scss | 1 + src/util/zIndex.tsx | 19 ++++++++++++++ 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/components/select/CustomSelectDropdown.tsx b/src/components/select/CustomSelectDropdown.tsx index cfe341d5d6..7601561084 100644 --- a/src/components/select/CustomSelectDropdown.tsx +++ b/src/components/select/CustomSelectDropdown.tsx @@ -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 & { value: string; @@ -86,23 +87,9 @@ const CustomSelectDropdown: FC = ({ const toggleWidth = toggle?.getBoundingClientRect()?.width ?? 0; dropdownRef.current.style.setProperty("min-width", `${toggleWidth}px`); - // align z-index: if in a modal context, take the next z-index we find above + 1 - const getRecursiveZIndex = (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 getRecursiveZIndex(element.parentElement); - } - return zIndex; - }; - const zIndex = getRecursiveZIndex(toggle); + // 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", @@ -244,6 +231,11 @@ const CustomSelectDropdown: FC = ({ // 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 && (
diff --git a/src/sass/_custom_select.scss b/src/sass/_custom_select.scss index 4cdb322c5f..3254e1d4d9 100644 --- a/src/sass/_custom_select.scss +++ b/src/sass/_custom_select.scss @@ -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); diff --git a/src/util/zIndex.tsx b/src/util/zIndex.tsx index 1d2e5837ee..b64610c792 100644 --- a/src/util/zIndex.tsx +++ b/src/util/zIndex.tsx @@ -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; +};