Skip to content

Commit e6923d9

Browse files
authored
Add animation to Submenu Tray when navigating between levels (#5984)
1 parent 24d931b commit e6923d9

File tree

4 files changed

+107
-11
lines changed

4 files changed

+107
-11
lines changed

packages/@adobe/spectrum-css-temp/components/menu/index.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,18 +261,25 @@ governing permissions and limitations under the License.
261261
display: flex;
262262
flex-direction: column;
263263
overflow: auto;
264+
265+
&.spectrum-Submenu-wrapper--isMobile {
266+
overflow-x: hidden; /* Prevents scrollbar from appearing during submenu transition */
267+
}
264268
}
265269

266270
&.spectrum-Menu-wrapper--isMobile {
267271
width: 100%;
268272
border: none;
269273
background-color: unset;
274+
opacity: 1;
275+
transition: opacity var(--spectrum-global-animation-duration-600) var(--spectrum-global-animation-ease-out);
270276

271277
&.is-expanded {
272278
position: absolute;
273279
left: 100%;
274280
height: 0;
275281
border: 0;
282+
opacity: 0;
276283
}
277284

278285
.spectrum-Submenu-headingWrapper {

packages/@adobe/spectrum-css-temp/components/menu/skin.css

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,62 @@ governing permissions and limitations under the License.
100100
color: var(--spectrum-heading-subtitle3-text-color);
101101
}
102102

103+
@keyframes slideInFromRight {
104+
from {
105+
transform: translateX(100%);
106+
}
107+
to {
108+
transform: translateX(0);
109+
}
110+
}
111+
112+
@keyframes slideOutToRight {
113+
from {
114+
transform: translateX(0)
115+
}
116+
to {
117+
transform: translateX(100%);
118+
}
119+
}
120+
121+
@keyframes slideInFromLeft {
122+
from {
123+
transform: translateX(-100%);
124+
}
125+
to {
126+
transform: translateX(0);
127+
}
128+
}
129+
130+
@keyframes slideOutToLeft {
131+
from {
132+
transform: translateX(0)
133+
}
134+
to {
135+
transform: translateX(-100%);
136+
}
137+
}
138+
139+
.spectrum-TraySubmenu-enter {
140+
animation-name: slideInFromRight;
141+
animation-duration: var(--spectrum-global-animation-duration-400);
142+
animation-fill-mode: forwards;
143+
animation-timing-function: var(--spectrum-global-animation-ease-out);
144+
&:dir(rtl) {
145+
animation-name: slideInFromLeft;
146+
}
147+
}
148+
149+
.spectrum-TraySubmenu-exit {
150+
animation-name: slideOutToRight;
151+
animation-duration: var(--spectrum-global-animation-duration-400);
152+
animation-fill-mode: forwards;
153+
animation-timing-function: var(--spectrum-global-animation-ease-in);
154+
&:dir(rtl) {
155+
animation-name: slideOutToLeft;
156+
}
157+
}
158+
103159
@media (forced-colors: active) {
104160
.spectrum-Menu-divider {
105161
background-color: CanvasText;

packages/@react-spectrum/menu/src/ContextualHelpTrigger.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {getInteractionModality} from '@react-aria/interactions';
1616
import helpStyles from '@adobe/spectrum-css-temp/components/contextualhelp/vars.css';
1717
import {ItemProps, Key} from '@react-types/shared';
1818
import {Popover} from '@react-spectrum/overlays';
19-
import React, {JSX, ReactElement, useRef, useState} from 'react';
19+
import React, {JSX, ReactElement, useEffect, useRef, useState} from 'react';
2020
import ReactDOM from 'react-dom';
2121
import styles from '@adobe/spectrum-css-temp/components/menu/vars.css';
2222
import {SubmenuTriggerContext, useMenuStateContext} from './context';
@@ -55,6 +55,12 @@ function ContextualHelpTrigger(props: InternalMenuDialogTriggerProps): ReactElem
5555
isDisabled: !isUnavailable
5656
}, submenuTriggerState, triggerRef);
5757
let isMobile = useIsMobileDevice();
58+
let [traySubmenuAnimation, setTraySubmenuAnimation] = useState('');
59+
useEffect(() => {
60+
if (submenuTriggerState.isOpen) {
61+
setTraySubmenuAnimation('spectrum-TraySubmenu-enter');
62+
}
63+
}, [submenuTriggerState.isOpen]);
5864
let slots = {};
5965
if (isUnavailable) {
6066
slots = {
@@ -68,7 +74,8 @@ function ContextualHelpTrigger(props: InternalMenuDialogTriggerProps): ReactElem
6874
classNames(
6975
styles,
7076
{
71-
'spectrum-Menu-subdialog': !isMobile
77+
'spectrum-Menu-subdialog': !isMobile,
78+
[traySubmenuAnimation]: isMobile
7279
}
7380
)
7481
)
@@ -91,10 +98,13 @@ function ContextualHelpTrigger(props: InternalMenuDialogTriggerProps): ReactElem
9198
let overlay;
9299
let tray;
93100
let onBackButtonPress = () => {
94-
submenuTriggerState.close();
95-
if (parentMenuRef.current && !parentMenuRef.current.contains(document.activeElement)) {
96-
parentMenuRef.current.focus();
97-
}
101+
setTraySubmenuAnimation('spectrum-TraySubmenu-exit');
102+
setTimeout(() => {
103+
submenuTriggerState.close();
104+
if (parentMenuRef.current && !parentMenuRef.current.contains(document.activeElement)) {
105+
parentMenuRef.current.focus();
106+
}
107+
}, 220); // Matches transition duration
98108
};
99109
let [offset, setOffset] = useState(0);
100110
useLayoutEffect(() => {

packages/@react-spectrum/menu/src/Menu.tsx

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import intlMessages from '../intl/*.json';
2020
import {MenuContext, MenuStateContext, useMenuStateContext} from './context';
2121
import {MenuItem} from './MenuItem';
2222
import {MenuSection} from './MenuSection';
23-
import {mergeProps, useSlotId, useSyncRef} from '@react-aria/utils';
23+
import {mergeProps, useLayoutEffect, useSlotId, useSyncRef} from '@react-aria/utils';
2424
import React, {ReactElement, useContext, useEffect, useRef, useState} from 'react';
2525
import {SpectrumMenuProps} from '@react-types/menu';
2626
import styles from '@adobe/spectrum-css-temp/components/menu/vars.css';
@@ -70,7 +70,6 @@ function Menu<T extends object>(props: SpectrumMenuProps<T>, ref: DOMRef<HTMLDiv
7070
isDisabled: isSubmenu || !hasOpenSubmenu
7171
});
7272

73-
// TODO: add slide transition
7473
return (
7574
<MenuStateContext.Provider value={{popoverContainer, trayContainerRef, menu: domRef, submenu: submenuRef, rootMenuTriggerState, state}}>
7675
<div style={{height: hasOpenSubmenu ? '100%' : undefined}} ref={trayContainerRef} />
@@ -136,6 +135,29 @@ export function TrayHeaderWrapper(props) {
136135
let isMobile = useIsMobileDevice();
137136
let {direction} = useLocale();
138137

138+
let [traySubmenuAnimation, setTraySubmenuAnimation] = useState('');
139+
useLayoutEffect(() => {
140+
if (!hasOpenSubmenu) {
141+
setTraySubmenuAnimation('spectrum-TraySubmenu-enter');
142+
}
143+
}, [hasOpenSubmenu, isMobile]);
144+
145+
let timeoutRef = useRef(null);
146+
let handleBackButtonPress = () => {
147+
setTraySubmenuAnimation('spectrum-TraySubmenu-exit');
148+
timeoutRef.current = setTimeout(() => {
149+
onBackButtonPress();
150+
}, 220); // Matches transition duration
151+
};
152+
153+
useEffect(() => {
154+
return () => {
155+
if (timeoutRef.current) {
156+
clearTimeout(timeoutRef.current);
157+
}
158+
};
159+
}, []);
160+
139161
return (
140162
<>
141163
<div
@@ -149,17 +171,18 @@ export function TrayHeaderWrapper(props) {
149171
'spectrum-Menu-wrapper',
150172
{
151173
'spectrum-Menu-wrapper--isMobile': isMobile,
152-
'is-expanded': hasOpenSubmenu
174+
'is-expanded': hasOpenSubmenu,
175+
[traySubmenuAnimation]: isMobile
153176
}
154177
)
155178
}>
156-
<div role="presentation" className={classNames(styles, 'spectrum-Submenu-wrapper')} onKeyDown={wrapperKeyDown}>
179+
<div role="presentation" className={classNames(styles, 'spectrum-Submenu-wrapper', {'spectrum-Submenu-wrapper--isMobile': isMobile})} onKeyDown={wrapperKeyDown}>
157180
{isMobile && isSubmenu && !hasOpenSubmenu && (
158181
<div className={classNames(styles, 'spectrum-Submenu-headingWrapper')}>
159182
<ActionButton
160183
aria-label={backButtonLabel}
161184
isQuiet
162-
onPress={onBackButtonPress}>
185+
onPress={handleBackButtonPress}>
163186
{/* We don't have a ArrowLeftSmall so make due with ArrowDownSmall and transforms */}
164187
{direction === 'rtl' ? <ArrowDownSmall UNSAFE_style={{rotate: '270deg'}} /> : <ArrowDownSmall UNSAFE_style={{rotate: '90deg'}} />}
165188
</ActionButton>

0 commit comments

Comments
 (0)