Repository navigation
fix: view transition animations - #10684
Open
snowystinger wants to merge 14 commits into
Open
snowystinger wants to merge 14 commits into
snowystinger wants to merge 14 commits into
Conversation
|
Build successful! 🎉 |
|
Build successful! 🎉 |
7 tasks
# Conflicts: # packages/@react-spectrum/s2/src/SideNav.tsx # packages/@react-spectrum/s2/stories/SideNav.stories.tsx
|
Build successful! 🎉 |
snowystinger
marked this pull request as ready for review
October 7, 2026 05:43
|
Build successful! 🎉 |
# Conflicts: # packages/@react-spectrum/s2/src/SideNav.module.css # packages/@react-spectrum/s2/src/SideNav.tsx # packages/@react-spectrum/s2/src/SideNavPanel.tsx # packages/@react-spectrum/s2/test/SideNavPanel.test.tsx # packages/dev/s2-docs/pages/s2/SideNavPanel.mdx
|
Build successful! 🎉 |
|
Build successful! 🎉 |
|
Build successful! 🎉 |
## API Changes
react-aria-components/react-aria-components:GridListItem GridListItem <T> {
allowsArrowNavigation?: boolean
children?: ChildrenOrFunction<GridListItemRenderProps>
className?: ClassNameOrFunction<GridListItemRenderProps> = 'react-aria-GridListItem'
download?: boolean | string
focusMode?: 'child' | 'row'
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onAction?: () => void
- onBlur?: (FocusEvent<HTMLDivElement>) => void
onClick?: (MouseEvent<FocusableElement>) => void
- onFocus?: (FocusEvent<HTMLDivElement>) => void
- onFocusChange?: (boolean) => void
onHoverChange?: (boolean) => void
onHoverEnd?: (HoverEvent) => void
onHoverStart?: (HoverEvent) => void
onPress?: (PressEvent) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
onPressUp?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, GridListItemRenderProps>
routerOptions?: RouterOptions
style?: StyleOrFunction<GridListItemRenderProps>
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
}/react-aria-components:GridListItemProps GridListItemProps <T = {}> {
allowsArrowNavigation?: boolean
children?: ChildrenOrFunction<GridListItemRenderProps>
className?: ClassNameOrFunction<GridListItemRenderProps> = 'react-aria-GridListItem'
download?: boolean | string
focusMode?: 'child' | 'row'
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onAction?: () => void
- onBlur?: (FocusEvent<HTMLDivElement>) => void
onClick?: (MouseEvent<FocusableElement>) => void
- onFocus?: (FocusEvent<HTMLDivElement>) => void
- onFocusChange?: (boolean) => void
onHoverChange?: (boolean) => void
onHoverEnd?: (HoverEvent) => void
onHoverStart?: (HoverEvent) => void
onPress?: (PressEvent) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
onPressUp?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, GridListItemRenderProps>
routerOptions?: RouterOptions
style?: StyleOrFunction<GridListItemRenderProps>
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
}@react-spectrum/ai/@react-spectrum/ai:Attachment Attachment {
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
children: ReactNode
density?: 'compact' | 'regular' | 'spacious' = 'regular'
download?: boolean | string
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
isInvalid?: boolean
onAction?: () => void
- onBlur?: (FocusEvent<HTMLDivElement>) => void
- onFocus?: (FocusEvent<HTMLDivElement>) => void
- onFocusChange?: (boolean) => void
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, TagRenderProps>
routerOptions?: RouterOptions
size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
styles?: StyleString
target?: HTMLAttributeAnchorTarget
textValue?: string
uploadProgress?: number
value?: T
variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary'
}/@react-spectrum/ai:AttachmentProps AttachmentProps {
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
children: ReactNode
density?: 'compact' | 'regular' | 'spacious' = 'regular'
download?: boolean | string
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
isInvalid?: boolean
onAction?: () => void
- onBlur?: (FocusEvent<HTMLDivElement>) => void
- onFocus?: (FocusEvent<HTMLDivElement>) => void
- onFocusChange?: (boolean) => void
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, TagRenderProps>
routerOptions?: RouterOptions
size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
styles?: StyleString
target?: HTMLAttributeAnchorTarget
textValue?: string
uploadProgress?: number
value?: T
variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary'
}@react-spectrum/s2/@react-spectrum/s2:Card Card {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
allowsArrowNavigation?: boolean
children: ReactNode | (CardRenderProps) => ReactNode
density?: 'compact' | 'regular' | 'spacious' = 'regular'
download?: boolean | string
focusMode?: 'child' | 'row'
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onAction?: () => void
- onBlur?: (FocusEvent<HTMLDivElement>) => void
- onFocus?: (FocusEvent<HTMLDivElement>) => void
- onFocusChange?: (boolean) => void
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
routerOptions?: RouterOptions
size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
styles?: StylesProp
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary'
}/@react-spectrum/s2:AssetCard AssetCard {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
allowsArrowNavigation?: boolean
children: ReactNode | (CardRenderProps) => ReactNode
download?: boolean | string
focusMode?: 'child' | 'row'
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onAction?: () => void
- onBlur?: (FocusEvent<HTMLDivElement>) => void
- onFocus?: (FocusEvent<HTMLDivElement>) => void
- onFocusChange?: (boolean) => void
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
routerOptions?: RouterOptions
size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
styles?: StylesProp
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary'
}/@react-spectrum/s2:UserCard UserCard {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
allowsArrowNavigation?: boolean
children: ReactNode | (CardRenderProps) => ReactNode
download?: boolean | string
focusMode?: 'child' | 'row'
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onAction?: () => void
- onBlur?: (FocusEvent<HTMLDivElement>) => void
- onFocus?: (FocusEvent<HTMLDivElement>) => void
- onFocusChange?: (boolean) => void
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
routerOptions?: RouterOptions
size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
styles?: StylesProp
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
variant?: 'primary' | 'secondary' | 'tertiary'
}/@react-spectrum/s2:ProductCard ProductCard {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
allowsArrowNavigation?: boolean
children: ReactNode | (CardRenderProps) => ReactNode
download?: boolean | string
focusMode?: 'child' | 'row'
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onAction?: () => void
- onBlur?: (FocusEvent<HTMLDivElement>) => void
- onFocus?: (FocusEvent<HTMLDivElement>) => void
- onFocusChange?: (boolean) => void
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
routerOptions?: RouterOptions
size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
styles?: StylesProp
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
variant?: 'primary' | 'secondary' | 'tertiary'
}/@react-spectrum/s2:ListViewItem ListViewItem {
allowsArrowNavigation?: boolean
children: ReactNode
download?: boolean | string
focusMode?: 'child' | 'row'
hasChildItems?: boolean
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onAction?: () => void
- onBlur?: (FocusEvent<HTMLDivElement>) => void
- onFocus?: (FocusEvent<HTMLDivElement>) => void
- onFocusChange?: (boolean) => void
onHoverChange?: (boolean) => void
onHoverEnd?: (HoverEvent) => void
onHoverStart?: (HoverEvent) => void
onPress?: (PressEvent) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
onPressUp?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
routerOptions?: RouterOptions
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
}/@react-spectrum/s2:SideNavHeader SideNavHeader {
- children?: ReactNode
- id?: string
+ props: SideNavHeaderProps
+ returnVal: undefined
}/@react-spectrum/s2:SideNavPanel SideNavPanel {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
children?: ReactNode
defaultCollapsed?: boolean
isCollapsed?: boolean
onCollapsedChange?: (boolean) => void
styles?: StylesPropWithHeight
+ width?: number = 208
}/@react-spectrum/s2:CardProps CardProps {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
allowsArrowNavigation?: boolean
children: ReactNode | (CardRenderProps) => ReactNode
density?: 'compact' | 'regular' | 'spacious' = 'regular'
download?: boolean | string
focusMode?: 'child' | 'row'
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onAction?: () => void
- onBlur?: (FocusEvent<HTMLDivElement>) => void
- onFocus?: (FocusEvent<HTMLDivElement>) => void
- onFocusChange?: (boolean) => void
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
routerOptions?: RouterOptions
size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
styles?: StylesProp
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary'
}/@react-spectrum/s2:AssetCardProps AssetCardProps {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
allowsArrowNavigation?: boolean
children: ReactNode | (CardRenderProps) => ReactNode
download?: boolean | string
focusMode?: 'child' | 'row'
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onAction?: () => void
- onBlur?: (FocusEvent<HTMLDivElement>) => void
- onFocus?: (FocusEvent<HTMLDivElement>) => void
- onFocusChange?: (boolean) => void
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
routerOptions?: RouterOptions
size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
styles?: StylesProp
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary'
}/@react-spectrum/s2:ProductCardProps ProductCardProps {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
allowsArrowNavigation?: boolean
children: ReactNode | (CardRenderProps) => ReactNode
download?: boolean | string
focusMode?: 'child' | 'row'
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onAction?: () => void
- onBlur?: (FocusEvent<HTMLDivElement>) => void
- onFocus?: (FocusEvent<HTMLDivElement>) => void
- onFocusChange?: (boolean) => void
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
routerOptions?: RouterOptions
size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
styles?: StylesProp
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
variant?: 'primary' | 'secondary' | 'tertiary'
}/@react-spectrum/s2:UserCardProps UserCardProps {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
allowsArrowNavigation?: boolean
children: ReactNode | (CardRenderProps) => ReactNode
download?: boolean | string
focusMode?: 'child' | 'row'
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onAction?: () => void
- onBlur?: (FocusEvent<HTMLDivElement>) => void
- onFocus?: (FocusEvent<HTMLDivElement>) => void
- onFocusChange?: (boolean) => void
onPress?: (PressEvent) => void
onPressChange?: (boolean) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
routerOptions?: RouterOptions
size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M'
styles?: StylesProp
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
variant?: 'primary' | 'secondary' | 'tertiary'
}/@react-spectrum/s2:ListViewItemProps ListViewItemProps {
allowsArrowNavigation?: boolean
children: ReactNode
download?: boolean | string
focusMode?: 'child' | 'row'
hasChildItems?: boolean
href?: Href
hrefLang?: string
id?: Key
isDisabled?: boolean
onAction?: () => void
- onBlur?: (FocusEvent<HTMLDivElement>) => void
- onFocus?: (FocusEvent<HTMLDivElement>) => void
- onFocusChange?: (boolean) => void
onHoverChange?: (boolean) => void
onHoverEnd?: (HoverEvent) => void
onHoverStart?: (HoverEvent) => void
onPress?: (PressEvent) => void
onPressEnd?: (PressEvent) => void
onPressStart?: (PressEvent) => void
onPressUp?: (PressEvent) => void
ping?: string
referrerPolicy?: HTMLAttributeReferrerPolicy
rel?: string
routerOptions?: RouterOptions
target?: HTMLAttributeAnchorTarget
textValue?: string
value?: T
}/@react-spectrum/s2:SideNavPanelProps SideNavPanelProps {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
children?: ReactNode
defaultCollapsed?: boolean
isCollapsed?: boolean
onCollapsedChange?: (boolean) => void
styles?: StylesPropWithHeight
+ width?: number = 208
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The general idea, create two animations,
side-panelandside-nav-item.side-panelanimated the panel's inner content div as a single "unit". The panel can expand or collapse.side-nav-itemhandles each row individually. Items can expand or collapse.Because the application document flow should animate automatically with the SidePanel expand/collapse, there is a width css transition that is not a view transition but is in lock step with the view transition's animation. This way the SidePanel takes up real space during the animation.
Both sets of transitions use
default: nonewhich is passed to the React ViewTransition and opts that element out of any transitions they aren't part of, you can fiddle with this demo to get a feel for what it does.default-none-demo.html
There are a couple other things which help this work:
width/height: 100%+object-fit: none— You can think of the snapshot like an img tag, this prevents distortion as the container changes shape and doesn't alter the aspect ratio. So rows fade and get clipped by the shrinking box instead of squashing.object-positionanchors the edge that stays put for the animation:left top, flipped toright topin RTL (the pseudo-elements live on:rootand can't inherit the panel'sdirection, which is why we need theside-panel-rtltype layered on top).animation: noneon::view-transition-old/new(root)— Shows the "application document flow animation" from underneath, rather than cross-fade.Finally, there is the clipping. Ordinarily, a ViewTransition, escapes all overflow hidden/containing/everything because it's in a layer on top of the entire page. And we can't change that until
view-transition-group: containis supported in more places than just Chrome.So instead, we apply a clip-path which is measured as the intersection of the side nav and the side panel. This is recomputed every animation frame and is applied so that the view transition snapshot picks it up as well, making it appear that the contents are contained within the SidePanel and SideNav.
This clip path is most of the logic in this PR.
A closing note, ViewTransitions are non-interruptable. So you cannot click the button fast to open/close repeatedly. You have to wait for each animation.
📝 Test Instructions:
Go to SidePanel (NavPanel) docs, put Chrome's animation tab speed to 10%, then expand/collapse the SidePanel and the SideNav Items and make sure they don't leak out.
Additional fixes: