Skip to content

Commit

Permalink
fix: improve INP for drawer content opening and closing (#642)
Browse files Browse the repository at this point in the history
* fix: improve INP for drawer content opening and closing

* fix: improve INP for drawer content opening and closing

* fix: improve INP for drawer content opening and closing

* fix: also remove RTG from scrim too

* fix: also remove RTG from scrim too

* chore: commit save point

* chore: commit save point

* feat: add should render logic to help with mounting and unmounting animations

* feat: add should render logic to help with mounting and unmounting animations

* chore: commit save point

* chore: commit save point

* chore: commit save point

* chore: commit save point

* chore: commit save point

* chore: commit save point

* chore: commit save point

* chore: commit save point

* chore: commit save point

* chore: commit save point

* chore: commit save point

* chore: commit save point

* chore: commit save point

* chore: commit save point

* chore: commit save point

* chore: commit save point
  • Loading branch information
artmsilva authored Jun 12, 2024
1 parent afcc8eb commit 8f6de93
Show file tree
Hide file tree
Showing 11 changed files with 651 additions and 1,067 deletions.
1 change: 0 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,4 @@ node_modules
out
jest-coverage
plop-templates
/ui/theme/src/tokens.ts
/packages/kit/src/theme/tokens.ts
8 changes: 4 additions & 4 deletions build.washingtonpost.com/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@
"@washingtonpost/site-footer": "0.25.3-alpha.1",
"@washingtonpost/tachyons-css": "^1.8.0",
"@washingtonpost/wpds-assets": "2.0.0",
"@washingtonpost/wpds-kitchen-sink": "2.3.0",
"@washingtonpost/wpds-tailwind-theme": "2.3.0",
"@washingtonpost/wpds-tokens": "2.3.0",
"@washingtonpost/wpds-ui-kit": "2.3.0",
"@washingtonpost/wpds-kitchen-sink": "*",
"@washingtonpost/wpds-tailwind-theme": "*",
"@washingtonpost/wpds-tokens": "*",
"@washingtonpost/wpds-ui-kit": "file:../packages/kit",
"fuse.js": "^6.6.2",
"gray-matter": "^4.0.2",
"lz-string": "^1.4.4",
Expand Down
1,243 changes: 396 additions & 847 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions packages/kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"build": "npm run build:transform && tsup && npm run build-types",
"build-types": "tsup src/index.ts --dts-only",
"dev": "tsup --watch",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
"canary": "npm run build && npm version prerelease --preid=canary && npm publish --tag=canary"
},
"bugs": {
"url": "https://github.com/washingtonpost/wpds-ui-kit/issues"
Expand All @@ -47,6 +48,8 @@
"@radix-ui/react-radio-group": "^1.0.0",
"@radix-ui/react-select": "^1.2.2",
"@radix-ui/react-separator": "^1.0.0",
"@radix-ui/react-switch": "^1.0.0",
"@radix-ui/react-navigation-menu": "^1.0.0",
"@radix-ui/react-slot": "^1.0.0",
"@radix-ui/react-tabs": "latest",
"@radix-ui/react-tooltip": "^1.0.0",
Expand All @@ -58,7 +61,10 @@
"match-sorter": "6.3.1",
"nanoid": "^3.3.4",
"react-swipeable": "^7.0.0",
"react-transition-group": "^4.4.5"
"react-transition-group": "^4.4.5",
"react-popper": "^2.2.5",
"popper-max-size-modifier": "^0.2.0",
"@popperjs/core": "^2.11.6"
},
"devDependencies": {
"tsup": "8.0.2",
Expand Down
304 changes: 146 additions & 158 deletions packages/kit/src/drawer/DrawerContent.tsx
Original file line number Diff line number Diff line change
@@ -1,138 +1,95 @@
import React from "react";
import { CSSTransition } from "react-transition-group";
import React, { useState, useEffect, useTransition } from "react";
import { FocusScope } from "@radix-ui/react-focus-scope";
import { styled, theme } from "../theme";
import { styled, theme, keyframes } from "../theme";
import type * as WPDS from "../theme";
import { DrawerContext } from "./DrawerRoot";

const drawerTransition = `transform ${theme.transitions.normal} ${theme.transitions.inOut}, opacity ${theme.transitions.normal} ${theme.transitions.inOut}`;

const animateInFromTop = keyframes({
from: { transform: "translateY(-100%)" },
to: { transform: "translateY(0)" },
});

const animationOutFromTop = keyframes({
from: { transform: "translateY(0)" },
to: { transform: "translateY(-100%)" },
});

const animateInFromRight = keyframes({
from: { transform: "translateX(100%)" },
to: { transform: "translateX(0)" },
});

const animateInFromBottom = keyframes({
from: { transform: "translateY(100%)" },
to: { transform: "translateY(0)" },
});

const animateInFromLeft = keyframes({
from: { transform: "translateX(-100%)" },
to: { transform: "translateX(0)" },
});

const StyledContainer = styled("div", {
backgroundColor: theme.colors.secondary,
boxShadow: theme.shadows["300"],
color: theme.colors.primary,
maxHeight: "100%",
overflow: "auto",
position: "fixed",
transition: drawerTransition,
contentVisibility: "auto",
variants: {
/** controls which side of the screen the drawer comes from @default bottom */
position: {
top: {
top: 0,
right: 0,
left: 0,
"&.wpds-drawer-enter": {
opacity: 0,
transform: "translateY(-100%)",
},
"&.wpds-drawer-enter-active": {
opacity: 1,
transform: "translateY(0%)",
transition: drawerTransition,
"@reducedMotion": {
transition: "none",
},
},
"&.wpds-drawer-exit": {
opacity: 1,
transform: "translateY(0%)",
},
"&.wpds-drawer-exit-active": {
opacity: 0,
transform: "translateY(-100%)",
transition: drawerTransition,
"@reducedMotion": {
transition: "none",
},
},
},
right: {
top: 0,
right: 0,
bottom: 0,
"&.wpds-drawer-enter": {
opacity: 0,
transform: "translateX(100%)",
},
"&.wpds-drawer-enter-active": {
opacity: 1,
transform: "translateX(0%)",
transition: drawerTransition,
"@reducedMotion": {
transition: "none",
},
},
"&.wpds-drawer-exit": {
opacity: 1,
transform: "translateX(0%)",
},
"&.wpds-drawer-exit-active": {
opacity: 0,
transform: "translateX(100%)",
transition: drawerTransition,
"@reducedMotion": {
transition: "none",
},
},
},
bottom: {
right: 0,
bottom: 0,
left: 0,
"&.wpds-drawer-enter": {
opacity: 0,
transform: "translateY(100%)",
},
"&.wpds-drawer-enter-active": {
opacity: 1,
transform: "translateY(0%)",
transition: drawerTransition,
"@reducedMotion": {
transition: "none",
},
},
"&.wpds-drawer-exit": {
opacity: 1,
transform: "translateY(0%)",
},
"&.wpds-drawer-exit-active": {
opacity: 0,
transform: "translateY(100%)",
transition: drawerTransition,
"@reducedMotion": {
transition: "none",
},
},
},
left: {
top: 0,
bottom: 0,
left: 0,
"&.wpds-drawer-enter": {
opacity: 0,
transform: "translateX(-100%)",
},
"&.wpds-drawer-enter-active": {
opacity: 1,
transform: "translateX(0%)",
transition: drawerTransition,
"@reducedMotion": {
transition: "none",
},
},
"&.wpds-drawer-exit": {
opacity: 1,
transform: "translateX(0%)",
},
"&.wpds-drawer-exit-active": {
opacity: 0,
transform: "translateX(-100%)",
transition: drawerTransition,
"@reducedMotion": {
transition: "none",
},
},
},
top: { top: 0, right: 0, left: 0 },
right: { top: 0, right: 0, bottom: 0 },
bottom: { right: 0, bottom: 0, left: 0 },
left: { top: 0, bottom: 0, left: 0 },
},
},
"@reducedMotion": {
transition: "none",
},
"&[data-state='open']": {
opacity: 1,
// data=position="top" or "bottom" or "left" or "right"
"&[data-position='top']": {
animation: `${animateInFromTop} ${theme.transitions.normal} ${theme.transitions.inOut}`,
transform: "translateY(0)",
},
"&[data-position='right']": {
animation: `${animateInFromRight} ${theme.transitions.normal} ${theme.transitions.inOut}`,
transform: "translateX(0)",
},
"&[data-position='bottom']": {
animation: `${animateInFromBottom} ${theme.transitions.normal} ${theme.transitions.inOut}`,
transform: "translateY(0)",
},
"&[data-position='left']": {
animation: `${animateInFromLeft} ${theme.transitions.normal} ${theme.transitions.inOut}`,
transform: "translateX(0)",
},
},
// data-state="closed"
"&[data-state='closed']": {
opacity: 0,
// data=position="top" or "bottom" or "left" or "right"
"&[data-position='top']": {
animate: `${animationOutFromTop} ${theme.transitions.normal} ${theme.transitions.inOut}`,
transform: "translateY(-100%)",
},
"&[data-position='right']": {
animate: `${animateInFromRight} ${theme.transitions.normal} ${theme.transitions.inOut}`,
transform: "translateX(100%)",
},
"&[data-position='bottom']": {
animate: `${animateInFromBottom} ${theme.transitions.normal} ${theme.transitions.inOut}`,
transform: "translateY(100%)",
},
"&[data-position='left']": {
animate: `${animateInFromLeft} ${theme.transitions.normal} ${theme.transitions.inOut}`,
transform: "translateX(-100%)",
},
},
});
Expand Down Expand Up @@ -175,52 +132,83 @@ export const DrawerContent = React.forwardRef<
ref
) => {
const context = React.useContext(DrawerContext);
const [isPending, startTransition] = useTransition();

const handleTransitionEnd = () => {
if (!context.open) {
handleExit();
setShouldRender(false);
}
};

function handleEnter() {
const handleEnter = () => {
document.addEventListener("keydown", handleKeyDown);
}
};

function handleExit() {
const handleExit = () => {
document.removeEventListener("keydown", handleKeyDown);
}
};

function handleKeyDown(event) {
const handleKeyDown = (event: { key: string }) => {
if (event.key === "Escape") {
context.onOpenChange(false);
}
}

return (
<CSSTransition
mountOnEnter
unmountOnExit
onEnter={handleEnter}
onExit={handleExit}
in={context.open}
timeout={300}
classNames="wpds-drawer"
>
<FocusScope loop={loopFocus} trapped={trapFocus} asChild>
<StyledContainer
id={context.contentId}
ref={ref}
style={{
width:
position === "left" || position === "right" ? width : undefined,
height:
position === "top" || position === "bottom"
? height
: undefined,
zIndex: context.zIndex as number,
}}
position={position}
{...props}
>
<StyledInner className={innerClassName}>{children}</StyledInner>
</StyledContainer>
</FocusScope>
</CSSTransition>
);
};

useEffect(() => {
startTransition(() => {
if (context.open) {
handleEnter();
} else {
handleExit();
}
});
}, [context.open]);

const [shouldRender, setShouldRender] = useState(false);

useEffect(() => {
if (context.open) {
setShouldRender(true);
}

// This is a workaround for a bug in Jest where animations are not run
// https://klaviyo.tech/hitting-a-moving-target-testing-javascript-animations-in-react-with-jest-8284a530a35a
if (process.env.NODE_ENV === "test" && !context.open) {
setShouldRender(false);
}
}, [context.open]);

const handleAnimationEnd = () => {
if (!isPending && !context.open) {
setShouldRender(false);
}
};

return shouldRender ? (
<FocusScope loop={loopFocus} trapped={trapFocus} asChild>
<StyledContainer
ref={ref}
id={context.contentId}
data-position={position}
data-state={context.open ? "open" : "closed"}
style={{
width:
position === "left" || position === "right" ? width : undefined,
height:
position === "top" || position === "bottom" ? height : undefined,
zIndex: context.zIndex as number,
}}
position={position}
onTransitionEnd={handleTransitionEnd}
onAnimationStart={handleEnter}
onAnimationEnd={handleAnimationEnd}
{...props}
>
<StyledInner className={innerClassName}>{children}</StyledInner>
</StyledContainer>
</FocusScope>
) : null;
}
);

Expand Down
Loading

0 comments on commit 8f6de93

Please sign in to comment.