Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduser bundle-størrelse for Framer Motion #4116

Merged
merged 6 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/contextual-menu-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"@fremtind/jkl-react-hooks": "^12.2.1",
"@fremtind/jkl-toggle-switch": "^13.2.1",
"classnames": "^2.3.2",
"framer-motion": "^7.10.3"
"framer-motion": ">7.10.3 <12"
},
"devDependencies": {
"@fremtind/jkl-icon-button-react": "^5.0.10"
Expand Down
166 changes: 85 additions & 81 deletions packages/contextual-menu-react/src/ContextualMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
import { type DataTestAutoId, WithChildren } from "@fremtind/jkl-core";
import { useId } from "@fremtind/jkl-react-hooks";
import cn from "classnames";
import { AnimatePresence, motion } from "framer-motion";
import { AnimatePresence, LazyMotion, domAnimation, m } from "framer-motion";
import React, { type ButtonHTMLAttributes, forwardRef, type ReactNode, useEffect, useRef, useState } from "react";
import * as ReactIs from "react-is";
import { useMenuWideEvents } from "./useMenuWideEvents";
Expand Down Expand Up @@ -172,88 +172,92 @@ const ContextualMenuComponent = forwardRef<HTMLButtonElement, ContextualMenuProp
})
: // Ellers, rendre elementet as-is, uten interaktivitet. Krev en ferdig brukbar button for å åpne menyen.
triggerElement}
<AnimatePresence>
{isOpen && (
<FloatingPortal>
<FloatingFocusManager
context={context}
// Prevent outside content interference.
modal={false}
// Only initially focus the root floating menu.
initialFocus={isNested ? -1 : 0}
// Only return focus to the root menu's reference when menus close.
returnFocus={!isNested}
>
<motion.div
className={cn("jkl jkl-contextual-menu", className)}
data-theme={theme}
data-layout-density={density}
role="menu"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ ease: "easeIn", duration: 0.1 }}
data-placement={placement}
aria-live="assertive"
aria-hidden={!isOpen}
ref={refs.setFloating}
{...getFloatingProps({
id: contextualMenuId,
style: {
position: strategy,
top: y ?? "",
left: x ?? "",
},
})}
<LazyMotion features={domAnimation}>
<AnimatePresence>
{isOpen && (
<FloatingPortal>
<FloatingFocusManager
context={context}
// Prevent outside content interference.
modal={false}
// Only initially focus the root floating menu.
initialFocus={isNested ? -1 : 0}
// Only return focus to the root menu's reference when menus close.
returnFocus={!isNested}
>
{React.Children.map(children, (child, index) => {
if (React.isValidElement(child) && ReactIs.isForwardRef(child)) {
return React.cloneElement(
child,
getItemProps({
...child.props,
tabIndex: activeIndex === index ? 0 : -1,
role: "menuitem",
ref(node: HTMLButtonElement) {
listItemsRef.current[index] = node;
},
onClick(event) {
child.props.onClick?.(event as React.MouseEvent<HTMLButtonElement>);
if (event.defaultPrevented) {
return;
}
tree?.events.emit("click");
},
onKeyDown(event) {
child.props.onKeyDown?.(event);
if (event.defaultPrevented) {
return;
}
tree?.events.emit("keydown");
if (
event.currentTarget.role === "menuitemcheckbox" &&
event.key === "Enter"
) {
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/menuitemcheckbox_role#keyboard_interactions
setIsOpen(false);
}
},
onMouseEnter() {
if (allowHover && isOpen) {
setActiveIndex(index);
}
},
}),
);
}
<m.div
className={cn("jkl jkl-contextual-menu", className)}
data-theme={theme}
data-layout-density={density}
role="menu"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ ease: "easeIn", duration: 0.1 }}
data-placement={placement}
aria-live="assertive"
aria-hidden={!isOpen}
ref={refs.setFloating}
{...getFloatingProps({
id: contextualMenuId,
style: {
position: strategy,
top: y ?? "",
left: x ?? "",
},
})}
>
{React.Children.map(children, (child, index) => {
if (React.isValidElement(child) && ReactIs.isForwardRef(child)) {
return React.cloneElement(
child,
getItemProps({
...child.props,
tabIndex: activeIndex === index ? 0 : -1,
role: "menuitem",
ref(node: HTMLButtonElement) {
listItemsRef.current[index] = node;
},
onClick(event) {
child.props.onClick?.(
event as React.MouseEvent<HTMLButtonElement>,
);
if (event.defaultPrevented) {
return;
}
tree?.events.emit("click");
},
onKeyDown(event) {
child.props.onKeyDown?.(event);
if (event.defaultPrevented) {
return;
}
tree?.events.emit("keydown");
if (
event.currentTarget.role === "menuitemcheckbox" &&
event.key === "Enter"
) {
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/menuitemcheckbox_role#keyboard_interactions
setIsOpen(false);
}
},
onMouseEnter() {
if (allowHover && isOpen) {
setActiveIndex(index);
}
},
}),
);
}

return child;
})}
</motion.div>
</FloatingFocusManager>
</FloatingPortal>
)}
</AnimatePresence>
return child;
})}
</m.div>
</FloatingFocusManager>
</FloatingPortal>
)}
</AnimatePresence>
</LazyMotion>
</FloatingNode>
);
});
Expand Down
8 changes: 4 additions & 4 deletions packages/jokul/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,9 @@
"react": "^18.0.0"
},
"optionalDependencies": {
"@floating-ui/react": "^0.24.2",
"@floating-ui/react": ">0.24.1 <0.27",
"date-fns": "^2.30.0",
"framer-motion": "^7.10.3",
"framer-motion": ">7.10.2 <12",
"react-a11y-dialog": "^6.2.0"
},
"devDependencies": {
Expand All @@ -113,8 +113,8 @@
"autoprefixer": "^10.4.14",
"change-case": "^4.1.2",
"clsx": "^2.1.1",
"cssnano": "^6.0.1",
"cssnano-preset-lite": "^3.0.0",
"cssnano": "^7.0.6",
"cssnano-preset-lite": "^4.0.3",
"glob": "^11.0.0",
"postcss": "^8.4.24",
"react-hook-form": "^7.44.3",
Expand Down
8 changes: 4 additions & 4 deletions packages/jokul/src/components/tooltip/Tooltip.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { render, screen } from "@testing-library/react";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { axe } from "jest-axe";
import React from "react";
Expand All @@ -20,7 +20,7 @@ describe("Tooltip", () => {
expect(screen.queryByText(/Forklarende tekst/, { ignore: "[hidden]" })).not.toBeInTheDocument();

await user.hover(tooltipTrigger as HTMLElement);
expect(screen.queryByText(/Forklarende tekst/, { ignore: "[hidden]" })).toBeVisible();
await waitFor(() => expect(screen.queryByText(/Forklarende tekst/, { ignore: "[hidden]" })).toBeVisible());
});

it("should NOT show tooltip on hover when triggerOn is 'click'", async () => {
Expand All @@ -36,7 +36,7 @@ describe("Tooltip", () => {
expect(screen.queryByText(/Forklarende tekst/)).not.toBeInTheDocument();

await user.hover(tooltipTrigger as HTMLElement);
expect(screen.queryByText(/Forklarende tekst/)).not.toBeInTheDocument();
() => expect(screen.queryByText(/Forklarende tekst/)).not.toBeInTheDocument();
});

it("should show tooltip on click when triggerOn is 'click'", async () => {
Expand All @@ -52,7 +52,7 @@ describe("Tooltip", () => {
expect(screen.queryByText(/Forklarende tekst/)).not.toBeInTheDocument();

await user.click(tooltipTrigger as HTMLElement);
expect(screen.queryByText(/Forklarende tekst/)).toBeVisible();
await waitFor(() => expect(screen.queryByText(/Forklarende tekst/)).toBeVisible());
});

describe("Trigger", () => {
Expand Down
82 changes: 42 additions & 40 deletions packages/jokul/src/components/tooltip/TooltipContent.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type Placement, useMergeRefs, FloatingPortal } from "@floating-ui/react";
import clsx from "clsx";
import { AnimatePresence, motion } from "framer-motion";
import { AnimatePresence, LazyMotion, domAnimation, m } from "framer-motion";
import React, { HTMLProps, forwardRef } from "react";
import { useId } from "../../hooks";
import { useTooltipContext } from "./Tooltip";
Expand Down Expand Up @@ -65,48 +65,50 @@ export const TooltipContent = forwardRef<HTMLDivElement, HTMLProps<HTMLDivElemen

return (
<FloatingPortal>
<AnimatePresence>
{/* For å kunne bruke tekstinnholdet i tooltip som beskrivende tekst, selv når ikke
<LazyMotion features={domAnimation}>
<AnimatePresence>
{/* For å kunne bruke tekstinnholdet i tooltip som beskrivende tekst, selv når ikke
tooltip er synlig, må vi rendre et skjult element å referere til for å hente innholdet. */}
{triggerOn === "hover" && (
<span ref={refs.setDescription} hidden key={`${contentId}-trigger`}>
{children}
</span>
)}
{isOpen && (
<span className="jkl" key={`${contentId}-wrapper`}>
<motion.span
key={contentId}
ref={ref}
initial={{ opacity: 0, ...getPositionAnimation(placement, 5) }}
animate={{ opacity: 1, ...getPositionAnimation(placement, 0) }}
exit={{
opacity: 0,
...getPositionAnimation(placement, -5),
transition: { ease: "easeIn", duration: 0.15 },
}}
transition={{ ease: "easeOut", duration: 0.25 }}
data-placement={placement}
aria-live={triggerOn === "click" ? "assertive" : undefined}
className={clsx("jkl-tooltip-content", className)}
{...getFloatingProps({ ...props, id: contentId })}
style={{ ...floatingStyles }}
data-theme={theme}
>
{triggerOn === "hover" && (
<span ref={refs.setDescription} hidden key={`${contentId}-trigger`}>
{children}
<span
aria-hidden
className="jkl-tooltip-content__arrow"
ref={arrowElement}
style={{
left: isPositioned ? `${arrow?.x}px` : "",
top: isPositioned ? `${arrow?.y}px` : "",
</span>
)}
{isOpen && (
<span className="jkl" key={`${contentId}-wrapper`}>
<m.span
key={contentId}
ref={ref}
initial={{ opacity: 0, ...getPositionAnimation(placement, 5) }}
animate={{ opacity: 1, ...getPositionAnimation(placement, 0) }}
exit={{
opacity: 0,
...getPositionAnimation(placement, -5),
transition: { ease: "easeIn", duration: 0.15 },
}}
/>
</motion.span>
</span>
)}
</AnimatePresence>
transition={{ ease: "easeOut", duration: 0.25 }}
data-placement={placement}
aria-live={triggerOn === "click" ? "assertive" : undefined}
className={clsx("jkl-tooltip-content", className)}
{...getFloatingProps({ ...props, id: contentId })}
style={{ ...floatingStyles }}
data-theme={theme}
>
{children}
<span
aria-hidden
className="jkl-tooltip-content__arrow"
ref={arrowElement}
style={{
left: isPositioned ? `${arrow?.x}px` : "",
top: isPositioned ? `${arrow?.y}px` : "",
}}
/>
</m.span>
</span>
)}
</AnimatePresence>
</LazyMotion>
</FloatingPortal>
);
});
2 changes: 1 addition & 1 deletion packages/tooltip-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"@fremtind/jkl-react-hooks": "^12.2.1",
"@fremtind/jkl-tooltip": "^4.2.1",
"classnames": "^2.3.2",
"framer-motion": "^7.10.3"
"framer-motion": ">7.10.3 <12"
},
"peerDependencies": {
"@types/react": "^16.8.6 || ^17.0.0 || ^18.0.0",
Expand Down
Loading
Loading