Skip to content

Commit

Permalink
Support motion overrides (#6679)
Browse files Browse the repository at this point in the history
  • Loading branch information
segunadebayo committed Sep 16, 2022
1 parent 99329e4 commit 1b89467
Show file tree
Hide file tree
Showing 22 changed files with 256 additions and 161 deletions.
10 changes: 10 additions & 0 deletions .changeset/lazy-ants-attack.md
@@ -0,0 +1,10 @@
---
"@chakra-ui/accordion": minor
"@chakra-ui/checkbox": minor
"@chakra-ui/menu": minor
"@chakra-ui/modal": minor
"@chakra-ui/popover": minor
"@chakra-ui/tooltip": minor
---

Allow control of `framer-motion` elements via the `motionProps` prop.
2 changes: 1 addition & 1 deletion .changeset/ninety-pugs-suffer.md
Expand Up @@ -2,4 +2,4 @@
"@chakra-ui/popover": patch
---

Refactor popover
Refactor popover to reduce bundle size
4 changes: 3 additions & 1 deletion .changeset/perfect-cycles-argue.md
Expand Up @@ -2,4 +2,6 @@
"@chakra-ui/tooltip": patch
---

Fixed the wrapping children display + changed mouse events with pointer events
Fixed issue where disabled tooltip triggers require an extra wrapper (via
`shouldWrapChildren). This was fixed by switching from mouse events to pointer
events
21 changes: 16 additions & 5 deletions packages/components/accordion/src/accordion-panel.tsx
@@ -1,13 +1,18 @@
import { chakra, forwardRef, HTMLChakraProps } from "@chakra-ui/system"
import { Collapse } from "@chakra-ui/transition"
import { Collapse, CollapseProps } from "@chakra-ui/transition"
import { cx } from "@chakra-ui/shared-utils"
import {
useAccordionItemContext,
useAccordionStyles,
} from "./accordion-context"
import { useAccordionContext } from "./use-accordion"

export interface AccordionPanelProps extends HTMLChakraProps<"div"> {}
export interface AccordionPanelProps extends HTMLChakraProps<"div"> {
/**
* The properties passed to the underlying `Collapse` component.
*/
motionProps?: CollapseProps
}

/**
* Accordion panel that holds the content for each accordion.
Expand All @@ -17,13 +22,15 @@ export interface AccordionPanelProps extends HTMLChakraProps<"div"> {}
*/
export const AccordionPanel = forwardRef<AccordionPanelProps, "div">(
function AccordionPanel(props, ref) {
const { className, motionProps, ...rest } = props

const { reduceMotion } = useAccordionContext()
const { getPanelProps, isOpen } = useAccordionItemContext()

// remove `hidden` prop, 'coz we're using height animation
const panelProps = getPanelProps(props, ref)
const panelProps = getPanelProps(rest, ref)

const _className = cx("chakra-accordion__panel", props.className)
const _className = cx("chakra-accordion__panel", className)
const styles = useAccordionStyles()

if (!reduceMotion) {
Expand All @@ -35,7 +42,11 @@ export const AccordionPanel = forwardRef<AccordionPanelProps, "div">(
)

if (!reduceMotion) {
return <Collapse in={isOpen}>{child}</Collapse>
return (
<Collapse in={isOpen} {...motionProps}>
{child}
</Collapse>
)
}

return child
Expand Down
17 changes: 3 additions & 14 deletions packages/components/checkbox/src/checkbox-icon.tsx
@@ -1,18 +1,7 @@
import { chakra, PropsOf, ChakraComponent } from "@chakra-ui/system"
import { AnimatePresence, CustomDomComponent, motion } from "framer-motion"
import { chakra, PropsOf } from "@chakra-ui/system"
import { AnimatePresence, motion } from "framer-motion"

function __motion<T extends ChakraComponent<any, any>>(
el: T,
): CustomDomComponent<PropsOf<T>> {
const m = motion as any
if ("custom" in m && typeof m.custom === "function") {
return m.custom(el)
}
return m(el)
}

// @future: only call `motion(chakra.svg)` when we drop framer-motion v3 support
const MotionSvg = __motion(chakra.svg)
const MotionSvg = chakra(motion.svg)

function CheckIcon(props: PropsOf<typeof MotionSvg>) {
return (
Expand Down
60 changes: 22 additions & 38 deletions packages/components/menu/src/menu-list.tsx
@@ -1,13 +1,7 @@
import {
forwardRef,
ChakraComponent,
PropsOf,
HTMLChakraProps,
chakra,
} from "@chakra-ui/system"
import { cx, callAll } from "@chakra-ui/shared-utils"
import { callAll, cx } from "@chakra-ui/shared-utils"
import { chakra, forwardRef, HTMLChakraProps } from "@chakra-ui/system"

import { CustomDomComponent, motion, Variants } from "framer-motion"
import { HTMLMotionProps, motion, Variants } from "framer-motion"
import { useMenuStyles } from "./menu"
import { useMenuContext, useMenuList, useMenuPositioner } from "./use-menu"

Expand All @@ -16,6 +10,10 @@ export interface MenuListProps extends HTMLChakraProps<"div"> {
* Props for the root element that positions the menu.
*/
rootProps?: HTMLChakraProps<"div">
/**
* The framer-motion props to animate the menu list
*/
motionProps?: HTMLMotionProps<"div">
}

const motionVariants: Variants = {
Expand All @@ -41,28 +39,20 @@ const motionVariants: Variants = {
},
}

function __motion<T extends ChakraComponent<any, any>>(
el: T,
): CustomDomComponent<PropsOf<T>> {
const m = motion as any
if ("custom" in m && typeof m.custom === "function") {
return m.custom(el)
}
return m(el)
}
const MenuTransition = chakra(motion.div)

// @future: only call `motion(chakra.div)` when we drop framer-motion v3 support
const MenuTransition = __motion(chakra.div)

export const MenuList = forwardRef<MenuListProps, "div">((props, ref) => {
const { rootProps, ...rest } = props
export const MenuList = forwardRef<MenuListProps, "div">(function MenuList(
props,
ref,
) {
const { rootProps, motionProps, ...rest } = props
const {
isOpen,
onTransitionEnd,
unstable__animationState: animated,
} = useMenuContext()

const ownProps = useMenuList(rest, ref) as any
const listProps = useMenuList(rest, ref) as any
const positionerProps = useMenuPositioner(rootProps)

const styles = useMenuStyles()
Expand All @@ -73,24 +63,18 @@ export const MenuList = forwardRef<MenuListProps, "div">((props, ref) => {
__css={{ zIndex: props.zIndex ?? styles.list?.zIndex }}
>
<MenuTransition
{...ownProps}
/**
* We could call this on either `onAnimationComplete` or `onUpdate`.
* It seems the re-focusing works better with the `onUpdate`
*/
variants={motionVariants}
initial={false}
animate={isOpen ? "enter" : "exit"}
__css={{ outline: 0, ...styles.list }}
{...motionProps}
className={cx("chakra-menu__menu-list", listProps.className)}
{...listProps}
onUpdate={onTransitionEnd}
onAnimationComplete={callAll(
animated.onComplete,
ownProps.onAnimationComplete,
listProps.onAnimationComplete,
)}
className={cx("chakra-menu__menu-list", ownProps.className)}
variants={motionVariants}
initial={false}
animate={isOpen ? "enter" : "exit"}
__css={{
outline: 0,
...styles.list,
}}
/>
</chakra.div>
)
Expand Down
18 changes: 11 additions & 7 deletions packages/components/modal/src/drawer-content.tsx
@@ -1,27 +1,30 @@
import { cx } from "@chakra-ui/shared-utils"
import {
chakra,
forwardRef,
HTMLChakraProps,
SystemStyleObject,
forwardRef,
} from "@chakra-ui/system"
import { Slide } from "@chakra-ui/transition"
import type { HTMLMotionProps } from "framer-motion"

import { useDrawerContext } from "./drawer"
import { useModalContext, useModalStyles } from "./modal"
import { ModalFocusScope } from "./modal-focus"

const StyledSlide = chakra(Slide)
const MotionDiv = chakra(Slide)

export interface DrawerContentProps extends HTMLChakraProps<"section"> {}
export interface DrawerContentProps extends HTMLChakraProps<"section"> {
motionProps?: HTMLMotionProps<"section">
}

/**
* ModalContent is used to group modal's content. It has all the
* necessary `aria-*` properties to indicate that it is a modal
*/
export const DrawerContent = forwardRef<DrawerContentProps, "section">(
(props, ref) => {
const { className, children, ...rest } = props
const { className, children, motionProps, ...rest } = props

const { getDialogProps, getDialogContainerProps, isOpen } =
useModalContext()
Expand All @@ -45,7 +48,7 @@ export const DrawerContent = forwardRef<DrawerContentProps, "section">(
const dialogContainerStyles: SystemStyleObject = {
display: "flex",
width: "100vw",
height: "100vh",
height: "$100vh",
position: "fixed",
left: 0,
top: 0,
Expand All @@ -61,15 +64,16 @@ export const DrawerContent = forwardRef<DrawerContentProps, "section">(
__css={dialogContainerStyles}
>
<ModalFocusScope>
<StyledSlide
<MotionDiv
motionProps={motionProps}
direction={placement}
in={isOpen}
className={_className}
{...dialogProps}
__css={dialogStyles}
>
{children}
</StyledSlide>
</MotionDiv>
</ModalFocusScope>
</chakra.div>
)
Expand Down
20 changes: 14 additions & 6 deletions packages/components/modal/src/modal-content.tsx
Expand Up @@ -5,6 +5,7 @@ import {
SystemStyleObject,
forwardRef,
} from "@chakra-ui/system"
import { HTMLMotionProps } from "framer-motion"

import { useModalContext, useModalStyles } from "./modal"
import { ModalFocusScope } from "./modal-focus"
Expand All @@ -15,6 +16,10 @@ export interface ModalContentProps extends HTMLChakraProps<"section"> {
* The props to forward to the modal's content wrapper
*/
containerProps?: HTMLChakraProps<"div">
/**
* The custom framer-motion transition to use for the modal
*/
motionProps?: HTMLMotionProps<"section">
}

/**
Expand All @@ -23,7 +28,13 @@ export interface ModalContentProps extends HTMLChakraProps<"section"> {
*/
export const ModalContent = forwardRef<ModalContentProps, "section">(
(props, ref) => {
const { className, children, containerProps: rootProps, ...rest } = props
const {
className,
children,
containerProps: rootProps,
motionProps,
...rest
} = props

const { getDialogProps, getDialogContainerProps } = useModalContext()

Expand All @@ -46,10 +57,7 @@ export const ModalContent = forwardRef<ModalContentProps, "section">(
const dialogContainerStyles: SystemStyleObject = {
display: "flex",
width: "100vw",
height: "100vh",
"@supports(height: -webkit-fill-available)": {
height: "-webkit-fill-available",
},
height: "$100vh",
position: "fixed",
left: 0,
top: 0,
Expand All @@ -63,12 +71,12 @@ export const ModalContent = forwardRef<ModalContentProps, "section">(
<chakra.div
{...containerProps}
className="chakra-modal__content-container"
// tabindex="-1" means that the element is not reachable via sequential keyboard navigation, @see #4686
tabIndex={-1}
__css={dialogContainerStyles}
>
<ModalTransition
preset={motionPreset}
motionProps={motionProps}
className={_className}
{...dialogProps}
__css={dialogStyles}
Expand Down
8 changes: 6 additions & 2 deletions packages/components/modal/src/modal-overlay.tsx
Expand Up @@ -16,6 +16,7 @@ export interface ModalOverlayProps
extends Omit<HTMLMotionProps<"div">, "color" | "transition">,
ChakraProps {
children?: React.ReactNode
motionProps?: HTMLMotionProps<"div">
}

/**
Expand All @@ -26,7 +27,7 @@ export interface ModalOverlayProps
*/
export const ModalOverlay = forwardRef<ModalOverlayProps, "div">(
(props, ref) => {
const { className, transition, ...rest } = props
const { className, transition, motionProps: _motionProps, ...rest } = props
const _className = cx("chakra-modal__overlay", className)

const styles = useModalStyles()
Expand All @@ -40,7 +41,10 @@ export const ModalOverlay = forwardRef<ModalOverlayProps, "div">(
}

const { motionPreset } = useModalContext()
const motionProps: any = motionPreset === "none" ? {} : fadeConfig
const defaultMotionProps: HTMLMotionProps<"div"> =
motionPreset === "none" ? {} : fadeConfig

const motionProps: any = _motionProps || defaultMotionProps

return (
<MotionDiv
Expand Down
16 changes: 11 additions & 5 deletions packages/components/modal/src/modal-transition.tsx
Expand Up @@ -6,7 +6,8 @@ import { forwardRef } from "react"
export interface ModalTransitionProps
extends Omit<HTMLMotionProps<"section">, "color" | "transition">,
ChakraProps {
preset: "slideInBottom" | "slideInRight" | "scale" | "none"
preset?: "slideInBottom" | "slideInRight" | "scale" | "none"
motionProps?: HTMLMotionProps<"section">
}

const transitions = {
Expand All @@ -25,13 +26,18 @@ const transitions = {
none: {},
}

const Motion = chakra(motion.section)
const MotionSection = chakra(motion.section)

const getMotionProps = (preset: ModalTransitionProps["preset"]) => {
return transitions[preset || "none"]
}

export const ModalTransition = forwardRef(
(props: ModalTransitionProps, ref: React.Ref<any>) => {
const { preset, ...rest } = props
const motionProps = transitions[preset]
return <Motion ref={ref} {...(motionProps as ChakraProps)} {...rest} />
const { preset, motionProps = getMotionProps(preset), ...rest } = props
return (
<MotionSection ref={ref} {...(motionProps as ChakraProps)} {...rest} />
)
},
)

Expand Down

1 comment on commit 1b89467

@vercel
Copy link

@vercel vercel bot commented on 1b89467 Sep 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.