Skip to content

Commit

Permalink
refactor(SidePanel): remove framer motion (carbon-design-system#6757)
Browse files Browse the repository at this point in the history
* refactor(SidePanel): remove framer motion

* fix: datagrid row click story

* test(usePresence): add test coverage for hook

* chore: add license

* refactor(usePresence): track value based on animation end event

* chore: update test name

* chore: cleanup test

* fix(SidePanel): address issue with placement prop animation

* refactor(SidePanel): use carbon duration directly

* chore(SidePanel): use integer syntax

Co-authored-by: Alexander Melo <[email protected]>

---------

Co-authored-by: Alexander Melo <[email protected]>
  • Loading branch information
matthewgallo and AlexanderMelox authored Feb 4, 2025
1 parent d0b1481 commit c5fe9f9
Show file tree
Hide file tree
Showing 8 changed files with 317 additions and 147 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//
// Copyright IBM Corp. 2025, 2025
//
// This source code is licensed under the Apache-2.0 license found in the
// LICENSE file in the root directory of this source tree.
//

@keyframes side-panel-entrance-reduced {
0% {
opacity: 0;
}

100% {
opacity: 1;
}
}

@keyframes side-panel-exit-reduced {
0% {
opacity: 1;
}

100% {
opacity: 0;
}
}

@keyframes side-panel-entrance-right {
0% {
opacity: 0;
/* stylelint-disable-next-line carbon/layout-use */
transform: translateX(var(--panel-transform));
}

100% {
opacity: 1;
transform: translateX(0);
}
}

@keyframes side-panel-entrance-left {
0% {
opacity: 0;
/* stylelint-disable-next-line carbon/layout-use */
transform: translateX(calc(var(--panel-transform) * -1));
}

100% {
opacity: 1;
transform: translateX(0);
}
}

@keyframes side-panel-exit-right {
0% {
opacity: 1;
transform: translateX(0);
}

100% {
opacity: 0;
/* stylelint-disable-next-line carbon/layout-use */
transform: translateX(var(--panel-transform));
}
}

@keyframes side-panel-exit-left {
0% {
opacity: 1;
transform: translateX(0);
}

100% {
opacity: 0;
/* stylelint-disable-next-line carbon/layout-use */
transform: translateX(calc(var(--panel-transform) * -1));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright IBM Corp. 2020, 2023
// Copyright IBM Corp. 2020, 2025
//
// This source code is licensed under the Apache-2.0 license found in the
// LICENSE file in the root directory of this source tree.
Expand All @@ -21,6 +21,7 @@
@use '../../global/styles/project-settings' as c4p-settings;
@use '../../global/styles/mixins' as *;
@use './side-panel-variables' as *;
@use './animations' as *;

// SidePanel uses the following IBM Products components:
// ActionSet
Expand All @@ -29,6 +30,12 @@
$block-class: #{c4p-settings.$pkg-prefix}--side-panel;
$action-set-block-class: #{c4p-settings.$pkg-prefix}--action-set;

@property --panel-transform {
inherits: true;
initial-value: 320px;
syntax: '<integer>';
}

@mixin setPanelSize($size: map.get($side-panel-sizes, md)) {
/* any value is single value list */
inline-size: list.nth($size, 1);
Expand Down Expand Up @@ -69,6 +76,7 @@ $action-set-block-class: #{c4p-settings.$pkg-prefix}--action-set;
}

.#{$block-class} {
--panel-transform: 320px;
--#{$block-class}--title-stop: #{$spacing-05};
--#{$block-class}--scroll-animation-progress: 0;
--#{$block-class}--title-padding-right: #{$spacing-07};
Expand All @@ -85,12 +93,78 @@ $action-set-block-class: #{c4p-settings.$pkg-prefix}--action-set;
grid-template-rows: auto 1fr auto; /* titles content and actions */
inset-block-start: $spacing-09;

transform: translateX(0);
transition-behavior: allow-discrete;
transition-duration: $duration-moderate-01;
transition-property: display, opacity, transform;

@starting-style {
opacity: 0;
/* stylelint-disable-next-line carbon/layout-use */
transform: translateX(var(--panel-transform));
}

@each $size, $size_value in $side-panel-sizes {
&.#{$block-class}--#{$size} {
@include setPanelSize($size_value);
}
}

&.#{$block-class}--open.#{$block-class}--right-placement {
animation: side-panel-entrance-right $duration-moderate-01
motion(entrance, productive) forwards;
}

// animation only set for older browsers that do not support `transition-behavior: allow-discrete;`
&.#{$block-class}--open.#{$block-class}--left-placement {
animation: side-panel-entrance-left $duration-moderate-01
motion(entrance, productive) forwards;
}

// animation only set for older browsers that do not support `transition-behavior: allow-discrete;`
&.#{$block-class}--closing.#{$block-class}--right-placement {
animation: side-panel-exit-right $duration-moderate-01
motion(exit, productive) forwards;
}

&.#{$block-class}--closing.#{$block-class}--left-placement {
animation: side-panel-exit-left $duration-moderate-01
motion(exit, productive) forwards;
}

@supports (transition-behavior: allow-discrete) {
&.#{$block-class}--closing {
opacity: 0;
/* stylelint-disable-next-line carbon/layout-use */
transform: translateX(var(--panel-transform, 320px));
transition-duration: $duration-moderate-01;
}
&.#{$block-class}--closing.#{$block-class}--left-placement {
/* stylelint-disable-next-line carbon/layout-use */
transform: translateX(calc(-1 * var(--panel-transform, 320px)));
}
}

@media (prefers-reduced-motion) {
/* styles to apply if a user's device settings are set to reduced motion */
&.#{$block-class}--open {
animation: side-panel-entrance-reduced $duration-moderate-01
motion(exit, productive) forwards;
@starting-style {
opacity: 0;
transform: translateX(0);
}
}
&.#{$block-class}--closing {
animation: side-panel-exit-reduced $duration-moderate-01
motion(exit, productive) forwards;
@starting-style {
opacity: 1;
transform: translateX(0);
}
}
}

&.#{$block-class}--animated-title {
grid-template-rows: 1fr auto; /* titles and content (actions are external to this element) */
}
Expand Down Expand Up @@ -307,8 +381,7 @@ $action-set-block-class: #{c4p-settings.$pkg-prefix}--action-set;
padding-block-start: $spacing-05;
}
.#{$block-class}__inner-content {
padding: $spacing-05;
padding-block-start: 0;
padding: 0 $spacing-05 $spacing-05;
}

.#{$block-class}__inner-content--no-animated-title {
Expand Down Expand Up @@ -476,12 +549,19 @@ $action-set-block-class: #{c4p-settings.$pkg-prefix}--action-set;
.#{$block-class}__overlay {
position: fixed;
z-index: utilities.z('overlay');
animation: side-panel-overlay-entrance $duration-moderate-01
motion(entrance, productive) forwards;
background-color: $overlay;
block-size: 100%;
inline-size: 100%;
inset: 0;
}

.#{$block-class}__overlay--closing {
animation: side-panel-overlay-exit $duration-moderate-01
motion(exit, productive) forwards;
}

/* stylelint-disable-next-line carbon/theme-token-use */
.#{$block-class}--has-slug + .#{$block-class}__overlay,
.#{$block-class}--has-ai-label + .#{$block-class}__overlay,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ const ClickableRowWithPanel = ({ ...args }) => {
const sidePanelRef = useRef(undefined);

useEffect(() => {
if (openSidePanel) {
if (openSidePanel && sidePanelRef?.current) {
const focusableElements = sidePanelRef.current.querySelectorAll(
'button, [href], input, select, [tabindex]:not([tabindex="-1"])'
);
Expand Down
110 changes: 46 additions & 64 deletions packages/ibm-products/src/components/SidePanel/SidePanel.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
/**
* Copyright IBM Corp. 2020, 2024
* Copyright IBM Corp. 2020, 2025
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import { AnimatePresence, motion } from 'framer-motion';
import { ArrowLeft, Close } from '@carbon/react/icons';
// Carbon and package components we use.
import { Button, IconButton } from '@carbon/react';
Expand All @@ -20,14 +19,10 @@ import React, {
useRef,
useState,
} from 'react';
import {
actionSetVariants,
overlayVariants,
panelVariants,
} from './motion/variants';
import {
useFocus,
usePreviousValue,
usePresence,
usePrefersReducedMotion,
} from '../../global/js/hooks';

Expand Down Expand Up @@ -226,9 +221,6 @@ type SidePanelSlideInProps =

export type SidePanelProps = SidePanelBaseProps & SidePanelSlideInProps;

// `any` is a work around until ActionSet is migrated to TS
const MotionActionSet = motion(ActionSet);

// Default values for props
const defaults = {
animateTitle: true,
Expand Down Expand Up @@ -300,6 +292,9 @@ export let SidePanel = React.forwardRef(
const previousOpen = usePreviousValue(open);

const shouldReduceMotion = usePrefersReducedMotion();
const exitAnimationName =
placement === 'right' ? 'side-panel-exit-right' : 'side-panel-exit-left';
const { shouldRender } = usePresence(open, sidePanelRef, exitAnimationName);

// Title animation on scroll related state
const [labelTextHeight, setLabelTextHeight] = useState<any>(0);
Expand Down Expand Up @@ -683,6 +678,9 @@ export let SidePanel = React.forwardRef(
[`${blockClass}--has-ai-label`]: aiLabel,
[`${blockClass}--condensed-actions`]: condensedActions,
[`${blockClass}--has-overlay`]: includeOverlay,
[`${blockClass}--open`]: open,
[`${blockClass}--closing`]: !open,
[`${blockClass}--reduced-motion`]: shouldReduceMotion,
},
]);

Expand Down Expand Up @@ -875,7 +873,7 @@ export let SidePanel = React.forwardRef(
`${blockClass}__inner-content`,
`${blockClass}--scrolls`,
`${
!doAnimateTitle
!doAnimateTitle && !animateTitle
? `${blockClass}__inner-content--no-animated-title`
: ''
}`
Expand All @@ -893,60 +891,44 @@ export let SidePanel = React.forwardRef(
}
};

return (
<AnimatePresence>
{open && (
<>
<motion.div
{...getDevtoolsProps(componentName)}
{...rest}
id={id}
className={mainPanelClassNames}
ref={sidePanelRef}
role="complementary"
aria-label={title}
onAnimationComplete={onAnimationEnd}
onAnimationStart={onAnimationStart}
variants={panelVariants}
initial="hidden"
animate="visible"
exit="exit"
custom={{ placement, shouldReduceMotion }}
onKeyDown={handleKeyDown}
>
<>
{/* header */}
{renderHeader()}

{/* main */}
{renderMain()}
</>

{/* footer */}
<MotionActionSet
actions={actions ?? []}
className={primaryActionContainerClassNames}
size={size === 'xs' ? 'sm' : size}
custom={shouldReduceMotion}
variants={actionSetVariants}
/>
</motion.div>
<AnimatePresence>
{includeOverlay && (
<motion.div
variants={overlayVariants}
initial="hidden"
animate="visible"
exit="exit"
ref={overlayRef}
className={`${blockClass}__overlay`}
/>
)}
</AnimatePresence>
</>
return shouldRender ? (
<>
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */}
<div
{...getDevtoolsProps(componentName)}
{...rest}
id={id}
className={mainPanelClassNames}
ref={sidePanelRef}
role="complementary"
aria-label={title}
onAnimationEnd={onAnimationEnd}
onAnimationStart={onAnimationStart}
onKeyDown={handleKeyDown}
>
{/* header */}
{renderHeader()}

{/* main */}
{renderMain()}

{/* footer */}
<ActionSet
actions={actions ?? []}
className={primaryActionContainerClassNames}
size={size === 'xs' ? 'sm' : size}
/>
</div>
{includeOverlay && (
<div
ref={overlayRef}
className={cx(`${blockClass}__overlay`, {
[`${blockClass}__overlay--closing`]: !open,
})}
/>
)}
</AnimatePresence>
);
</>
) : null;
}
);

Expand Down
Loading

0 comments on commit c5fe9f9

Please sign in to comment.