import React, { useContext, useState } from "react";
import { useSpring, animated } from "react-spring";
import classNames from "classnames";
import { easeCubicInOut } from "d3-ease";
import { bemHOF } from "../../utilities/bem";
import { DrawerContext } from "../../contexts/drawer";
import { Box, BoxProps } from "../Box";
import { TopBar } from "../TopBar";
import { CloseButton } from "../CloseButton";
import { Flex } from "../Flex";
const cn = bemHOF("Drawer");
export interface WithDrawerProps {
/**
* The content of the drawer
*/
drawerSlot: React.ReactNode;
/**
* Called when the close button is clicked
*/
onClose: () => void;
/**
* Determines whether the `Drawer` is open
*/
isOpen?: boolean;
/**
* Content that will shrink as the Drawer animates in
*/
fixedSlotTop?: React.ReactNode;
/**
* Content that renders below the `scrollableSlot` and will shrink as the Drawer animates in
*/
fixedSlotBottom?: React.ReactNode;
/**
* Scrollable content that will be pushed off screen as the Drawer animates in
*/
scrollableSlot?: React.ReactNode;
/**
* Puts the drawer on the left or right of the content
*/
drawerSide?: "left" | "right";
/**
* Removes the animation
*/
noAnimation?: boolean;
/**
* Overrides the default drawer width
*/
drawerWidth?: number;
}
const DEFAULT_DRAWER_WIDTH = 264;
const calculateTransformValue = (
isOpen: boolean,
drawerOnRight: boolean,
drawerWidth: number,
) => {
const translateZero = "translateX(0px)";
const translateNegWidth = `translateX(${-drawerWidth}px)`;
if (isOpen) {
return drawerOnRight ? translateNegWidth : translateZero;
}
return drawerOnRight ? translateZero : translateNegWidth;
};
export const WithDrawer = ({
drawerSlot,
onClose,
isOpen = false,
fixedSlotTop,
fixedSlotBottom,
scrollableSlot,
drawerSide = "left",
noAnimation = false,
drawerWidth = DEFAULT_DRAWER_WIDTH,
}: WithDrawerProps) => {
const drawerOnRight = drawerSide === "right";
const [isClosedAtRest, setIsClosedAtRest] = useState(false);
const { transform, paddingLeft, paddingRight } = useSpring({
transform: calculateTransformValue(isOpen, drawerOnRight, drawerWidth),
paddingLeft: isOpen && !drawerOnRight ? drawerWidth : 0,
paddingRight: isOpen && drawerOnRight ? drawerWidth : 0,
onFrame: ({
paddingLeft: pl,
paddingRight: pr,
}: {
paddingLeft: number;
paddingRight: number;
}) => {
const padding = drawerOnRight ? pr : pl;
if (padding === 0 && !isClosedAtRest) {
setIsClosedAtRest(true);
} else if (padding !== 0 && isClosedAtRest) {
setIsClosedAtRest(false);
}
},
config: {
duration: noAnimation ? 0 : 300,
easing: easeCubicInOut,
},
});
const { transform: transformReverse } = useSpring({
transform:
isOpen && !drawerOnRight
? `translateX(${drawerWidth}px)`
: "translateX(0px)",
});
// Setting the width in js so we don't have to keep css/js in sync
const wrapperCombinedStyle = {
width: drawerWidth,
transform,
};
const scrollableSlotPaddingToInterpolate = drawerOnRight
? paddingRight
: paddingLeft;
return (
{drawerSlot}
{fixedSlotTop && (
{fixedSlotTop}
)}
{scrollableSlot && (
`calc(100% - ${w}px)`,
),
}}
className={cn({ e: "scrollableSlot" })}
>
{scrollableSlot}
)}
{fixedSlotBottom && (
{fixedSlotBottom}
)}
);
};
export interface DrawerProps extends BoxProps {
/**
* The content on the left side of `Drawer`'s `TopBar`
*/
headingSlot: React.ReactNode;
/**
* Optional footer content in the `Drawer`
*/
footerSlot?: React.ReactNode;
/**
* Changes styles based on whether the `Drawer` is on the left or ride side
*/
drawerSide?: "left" | "right";
}
export const Drawer = ({
children,
headingSlot,
footerSlot,
className,
drawerSide,
...rest
}: DrawerProps) => {
const { onClose } = useContext(DrawerContext);
return (
}
/>
{children}
{!!footerSlot && {footerSlot}}
);
};