From 45d3fd16e32927bdc5f1a982665879f184d50ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominika=20Podgo=CC=81rska?= Date: Thu, 13 Apr 2023 17:26:10 +0200 Subject: [PATCH 1/8] Do not block pointer events on touch screens so triggers work properly --- src/components/select-menu/SelectMenu.tsx | 9 ++++----- src/components/select-menu/useFloatingSelectMenu.tsx | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/select-menu/SelectMenu.tsx b/src/components/select-menu/SelectMenu.tsx index 6c3e46d259..e073987a7d 100644 --- a/src/components/select-menu/SelectMenu.tsx +++ b/src/components/select-menu/SelectMenu.tsx @@ -405,8 +405,11 @@ const SelectMenu = React.forwardRef( // this is to not block clicking and hovering outside // when the exit animation plays + // and when on touch screen const overlayPointerEvents = - status === 'open' || status === 'initial' ? 'all' : 'none'; + (status === 'open' || status === 'initial') && !isTouchScreen() + ? 'auto' + : 'none'; return (
@@ -424,10 +427,6 @@ const SelectMenu = React.forwardRef( aria-multiselectable={multiSelect} data-status={status} {...interactions.getReferenceProps({ - // Handle pointer - onClick() { - if (!disabled) onOpenChange(!isExpanded); - }, // Handle keyboard onKeyDown(event) { if ((event.key === 'Enter' || event.key === ' ') && !disabled) { diff --git a/src/components/select-menu/useFloatingSelectMenu.tsx b/src/components/select-menu/useFloatingSelectMenu.tsx index b9a017cb7a..1cf2c8e19b 100644 --- a/src/components/select-menu/useFloatingSelectMenu.tsx +++ b/src/components/select-menu/useFloatingSelectMenu.tsx @@ -52,7 +52,7 @@ const useFloatingSelectMenu = (props: UseFloatingSelectMenuPropsType) => { }, }); - const click = useClick(context, {event: 'mousedown'}); + const click = useClick(context); const dismiss = useDismiss(context); const role = useRole(context, {role: 'listbox'}); From 4c31fa4bf26311ba44968a47f046de26f504a3e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominika=20Podgo=CC=81rska?= Date: Thu, 13 Apr 2023 17:55:34 +0200 Subject: [PATCH 2/8] Fix popup position --- .../select-menu/useSelectMenuAnimations.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/select-menu/useSelectMenuAnimations.tsx b/src/components/select-menu/useSelectMenuAnimations.tsx index 96ae2e9e63..03f3f72f9c 100644 --- a/src/components/select-menu/useSelectMenuAnimations.tsx +++ b/src/components/select-menu/useSelectMenuAnimations.tsx @@ -44,7 +44,7 @@ const useSelectMenuAnimations = (props: UseSelectMenuAnimationsPropsType) => { floatingContainerClassName, selectElementClassName, } = props; - const lastRef = React.useRef(); + const initialElementRef = React.useRef(); const selectRef = React.useRef(); const hasReduceMotion = useReducedMotion(); @@ -70,8 +70,8 @@ const useSelectMenuAnimations = (props: UseSelectMenuAnimationsPropsType) => { requestAnimationFrame(() => { popupContainer.classList.add(ANIMATE_CLASSNAME); floatingContainer.classList.add(ANIMATE_CLASSNAME); - resetFloatingContainerTopPosition(floatingContainer, lastRef); + resetFloatingContainerTopPosition(floatingContainer, initialElementRef); if (callback) callback(); }); }; @@ -98,9 +98,10 @@ const useSelectMenuAnimations = (props: UseSelectMenuAnimationsPropsType) => { )[0] as HTMLDivElement; // Register desired position - lastRef.current = floatingContainer.getBoundingClientRect(); + if (!initialElementRef.current) + initialElementRef.current = floatingContainer.getBoundingClientRect(); selectRef.current = selectElement.getBoundingClientRect(); - const initialContainerSize = lastRef.current; + const initialContainerSize = initialElementRef.current; const selectElementSize = selectRef.current; popupContent.style.width = `${initialContainerSize.width}px`; @@ -116,7 +117,7 @@ const useSelectMenuAnimations = (props: UseSelectMenuAnimationsPropsType) => { // should be the same as element select width popupContainer.style.width = `${selectElementSize.width}px`; - resetFloatingContainerTopPosition(floatingContainer, lastRef); + resetFloatingContainerTopPosition(floatingContainer, initialElementRef); } // Wait for the next frame so we From db835d067a19645394512758e7c92d0f9bdbeaf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominika=20Podgo=CC=81rska?= Date: Fri, 14 Apr 2023 09:52:42 +0200 Subject: [PATCH 3/8] Fix capturing popup clicks --- src/components/select-menu/SelectMenu.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/select-menu/SelectMenu.tsx b/src/components/select-menu/SelectMenu.tsx index e073987a7d..cdf1330f85 100644 --- a/src/components/select-menu/SelectMenu.tsx +++ b/src/components/select-menu/SelectMenu.tsx @@ -470,6 +470,7 @@ const SelectMenu = React.forwardRef( width: 'max-content', maxWidth: 320, zIndex: 1, + pointerEvents: 'auto', }} {...interactions.getFloatingProps()} tabIndex={-1} From 46ed01bb28eb47e6e0d4055c9283efcd5c290fab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominika=20Podgo=CC=81rska?= Date: Mon, 17 Apr 2023 17:11:31 +0200 Subject: [PATCH 4/8] Fix focus management --- src/components/select-menu/SelectMenu.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/select-menu/SelectMenu.tsx b/src/components/select-menu/SelectMenu.tsx index cdf1330f85..3c9082cf73 100644 --- a/src/components/select-menu/SelectMenu.tsx +++ b/src/components/select-menu/SelectMenu.tsx @@ -418,7 +418,7 @@ const SelectMenu = React.forwardRef( id={id} className={selectElementClassName} role="combobox" - tabIndex={disabled ? -1 : 0} + tabIndex={!disabled ? 0 : -1} aria-disabled={disabled} aria-invalid={invalid ? true : undefined} aria-controls={`${id}-listbox`} @@ -458,6 +458,8 @@ const SelectMenu = React.forwardRef( context={context} modal={false} visuallyHiddenDismiss + order={['reference', 'content']} + initialFocus={-1} >
( pointerEvents: 'auto', }} {...interactions.getFloatingProps()} - tabIndex={-1} data-placement={floatingProps.placement} >
Date: Mon, 17 Apr 2023 17:24:28 +0200 Subject: [PATCH 5/8] Indicate hover on checkbox when hovering option --- src/components/select-menu/_select-menu.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/select-menu/_select-menu.scss b/src/components/select-menu/_select-menu.scss index 3295e83e7f..b588d1f179 100644 --- a/src/components/select-menu/_select-menu.scss +++ b/src/components/select-menu/_select-menu.scss @@ -282,6 +282,11 @@ &:hover { background-color: var(--gray-10); + + .sg-checkbox { + --checkboxColor: var(--checkboxHoverColor); + --checkboxCheckedColor: var(--checkboxHoverColor); + } } &--selected { From f209a4d94c617eae37ec513757110930c676e452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominika=20Podgo=CC=81rska?= Date: Thu, 27 Apr 2023 09:23:24 +0200 Subject: [PATCH 6/8] Change docs order --- .../select-menu/SelectMenu.stories.mdx | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/components/select-menu/SelectMenu.stories.mdx b/src/components/select-menu/SelectMenu.stories.mdx index bb90e73699..0ca102f07d 100644 --- a/src/components/select-menu/SelectMenu.stories.mdx +++ b/src/components/select-menu/SelectMenu.stories.mdx @@ -70,6 +70,24 @@ const Container = (Story, options) => { SelectMenu +- [Stories](#stories) +- [Accessibility](#accessibility) + +## Overview + + + + {args => } + + + + + This component is a controlled component, so you have to handle selecting options. ```jsx @@ -89,24 +107,6 @@ return ( ); ``` -- [Stories](#stories) -- [Accessibility](#accessibility) - -## Overview - - - - {args => } - - - - - ## Stories ### Controlled From 2b3bc5cd5c1fd3cc722942490a5935d86b1664d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominika=20Podgo=CC=81rska?= Date: Fri, 28 Apr 2023 17:33:21 +0200 Subject: [PATCH 7/8] Fix SelectMenu animations --- src/components/select-menu/_select-menu.scss | 51 +++++++++++---- .../select-menu/useSelectMenuAnimations.tsx | 63 +++++-------------- 2 files changed, 55 insertions(+), 59 deletions(-) diff --git a/src/components/select-menu/_select-menu.scss b/src/components/select-menu/_select-menu.scss index 847a379b6d..1f3ba7b9c8 100644 --- a/src/components/select-menu/_select-menu.scss +++ b/src/components/select-menu/_select-menu.scss @@ -173,17 +173,16 @@ &__options-floating-container { overflow: auto; + transition: transform $durationGentle1 Cubic-Bezier(0.35, 0, 0.2, 1); + transform: translateY(-8px); - &.open { - overflow: visible; - } - - &.sg-animate-on-transforms { - transition: top $durationGentle1 Cubic-Bezier(0.35, 0, 0.2, 1); + @media (prefers-reduced-motion) { + transition-duration: 0s; } &[data-placement^='top'] { transform-origin: bottom left; + transform: translateY(8px); } &[data-placement^='top-end'] { @@ -193,6 +192,22 @@ &[data-placement^='bottom-end'] { transform-origin: top right; } + + &.open { + overflow: visible; + } + + &.sg-animate-on-transforms { + transform: translateY(0); + } + + &.exit-state { + transform: translateY(-8px); + + &[data-placement^='top'] { + transform: translateY(8px); + } + } } &__popup { @@ -208,6 +223,7 @@ display: flex; flex-direction: column; flex: 1 0 auto; + opacity: 0; @media (forced-colors: active) { border: 2px solid FieldText; @@ -216,20 +232,31 @@ } } - &__popup.sg-animate-on-transforms { - opacity: 1; + &__popup { transition: height $durationGentle1 $easingRegular, width $durationGentle1 $easingRegular, opacity $durationQuick1 $easingLinear; - position: absolute; - transform-origin: top left; + @media (prefers-reduced-motion) { + transition-duration: 0s; + } - &.exit-animation { - transition-delay: 0, 0, 0.07s; + &.exit-state { + transition-delay: 0s, 0s, 7ms; transition-duration: $durationGentle1, $durationGentle1, $durationModerate1; + opacity: 0; + + @media (prefers-reduced-motion) { + transition-duration: 0s; + } } + } + + &__popup.sg-animate-on-transforms { + position: absolute; + transform-origin: top left; + opacity: 1; &[data-placement^='top'] { bottom: 0; diff --git a/src/components/select-menu/useSelectMenuAnimations.tsx b/src/components/select-menu/useSelectMenuAnimations.tsx index 03f3f72f9c..8e24737d9c 100644 --- a/src/components/select-menu/useSelectMenuAnimations.tsx +++ b/src/components/select-menu/useSelectMenuAnimations.tsx @@ -11,31 +11,11 @@ type UseSelectMenuAnimationsPropsType = { const MIN_POPUP_WIDTH = 120; const ANIMATE_CLASSNAME = 'sg-animate-on-transforms'; +const EXIT_STATE_CLASSNAME = 'exit-state'; const SCROLL_HIDE_CLASSNAME = 'hide-scroll'; const OPEN_CLASSNAME = 'open'; const MINIMAL_POPUP_TO_INPUT_RATIO = 0.7; -/** - * Move floating container by 8px from the initial top position. - */ -const resetFloatingContainerTopPosition = ( - floatingContainerElement, - originalElementRef -) => { - let transformTopAmount = -8; - const placement = floatingContainerElement.getAttribute('data-placement'); - - if (placement.includes('top')) { - transformTopAmount = 8; - } - - if (!originalElementRef.current) return; - - floatingContainerElement.style.top = `${ - originalElementRef.current.top + transformTopAmount - }px`; -}; - const useSelectMenuAnimations = (props: UseSelectMenuAnimationsPropsType) => { const { selectId, @@ -60,18 +40,15 @@ const useSelectMenuAnimations = (props: UseSelectMenuAnimationsPropsType) => { popupContentClassName )[0] as HTMLDivElement; - popupContainer.classList.add('exit-animation'); + popupContainer.style.opacity = `0`; popupContainer.style.height = `0px`; if (selectRef.current) popupContainer.style.width = `${selectRef.current.width}px`; - popupContainer.style.opacity = `0`; popupContent.classList.add(SCROLL_HIDE_CLASSNAME); + popupContainer.classList.add(EXIT_STATE_CLASSNAME); + floatingContainer.classList.add(EXIT_STATE_CLASSNAME); requestAnimationFrame(() => { - popupContainer.classList.add(ANIMATE_CLASSNAME); - floatingContainer.classList.add(ANIMATE_CLASSNAME); - - resetFloatingContainerTopPosition(floatingContainer, initialElementRef); if (callback) callback(); }); }; @@ -98,8 +75,7 @@ const useSelectMenuAnimations = (props: UseSelectMenuAnimationsPropsType) => { )[0] as HTMLDivElement; // Register desired position - if (!initialElementRef.current) - initialElementRef.current = floatingContainer.getBoundingClientRect(); + initialElementRef.current = floatingContainer.getBoundingClientRect(); selectRef.current = selectElement.getBoundingClientRect(); const initialContainerSize = initialElementRef.current; const selectElementSize = selectRef.current; @@ -108,30 +84,25 @@ const useSelectMenuAnimations = (props: UseSelectMenuAnimationsPropsType) => { popupContent.style.height = `${initialContainerSize.height}px`; popupContent.classList.add(SCROLL_HIDE_CLASSNAME); - if (!hasReduceMotion) { - // Reset the popup height to the pre-appear position - popupContainer.style.height = `0`; - popupContainer.style.opacity = `0`; + // Popup width at the start of animation + // should be the same as element select width + popupContainer.style.width = `${selectElementSize.width}px`; - // Popup width at the start of animation - // should be the same as element select width - popupContainer.style.width = `${selectElementSize.width}px`; + popupContainer.style.height = `0px`; - resetFloatingContainerTopPosition(floatingContainer, initialElementRef); - } + // Reset the popup and container to the pre-appear position + floatingContainer.classList.add(EXIT_STATE_CLASSNAME); + popupContainer.classList.add(EXIT_STATE_CLASSNAME); // Wait for the next frame so we // know all the style changes have // taken hold. requestAnimationFrame(() => { - if (!hasReduceMotion) { - popupContainer.classList.add(ANIMATE_CLASSNAME); - floatingContainer.classList.add(ANIMATE_CLASSNAME); - } - + floatingContainer.classList.remove(EXIT_STATE_CLASSNAME); + popupContainer.classList.remove(EXIT_STATE_CLASSNAME); floatingContainer.classList.add(OPEN_CLASSNAME); - - popupContainer.style.opacity = `1`; + popupContainer.classList.add(ANIMATE_CLASSNAME); + floatingContainer.classList.add(ANIMATE_CLASSNAME); popupContainer.style.height = `${initialContainerSize.height}px`; const popupWidth: number = Math.max( @@ -143,8 +114,6 @@ const useSelectMenuAnimations = (props: UseSelectMenuAnimationsPropsType) => { popupContainer.style.width = `${popupWidth}px`; popupContent.style.width = `${popupWidth}px`; - // Animate the floating container position back to it's initial state - floatingContainer.style.top = `${initialContainerSize.top}px`; // Ensure manipulating popup height doesn't affect the floating container floatingContainer.style.height = `${initialContainerSize.height}px`; floatingContainer.style.width = `${Math.max( From 4ef7ec220bfcb95bdb69d9780795591afec82888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominika=20Podgo=CC=81rska?= Date: Fri, 28 Apr 2023 17:37:39 +0200 Subject: [PATCH 8/8] Make default story functional --- .../select-menu/SelectMenu.stories.mdx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/components/select-menu/SelectMenu.stories.mdx b/src/components/select-menu/SelectMenu.stories.mdx index 0ca102f07d..af7df57293 100644 --- a/src/components/select-menu/SelectMenu.stories.mdx +++ b/src/components/select-menu/SelectMenu.stories.mdx @@ -82,13 +82,27 @@ const Container = (Story, options) => { chromatic: {disableSnapshot: true}, }} > - {args => } + {args => { + const [selectedOptions, setSelectedOptions] = React.useState([]); + return ( +
+ { + setSelectedOptions([option]); + }} + /> +
+ ); + }} -This component is a controlled component, so you have to handle selecting options. +Note: This component is a controlled component, so you have to handle selecting options. +Example: ```jsx const [selectedOptions, setSelectedOptions] = React.useState([]);