@@ -69,7 +80,7 @@ export default {
const LongContent = () => {
const paragraphs = useMemo(() => {
return faker.lorem
- .paragraphs(20, '\n')
+ .paragraphs(30, '\n')
.split('\n')
.map((p, i) => {p});
}, []);
@@ -115,7 +126,7 @@ const TemplateComponent: StoryFn
= ({
@@ -137,9 +148,29 @@ const TemplateComponent: StoryFn = ({
) : (
-
- {renderTrigger()}
- {renderDrawer()}
+
+
+
+ {renderTrigger()}
+
+
+ {renderDrawer()}
+
);
@@ -149,7 +180,6 @@ export const LiveExample: StoryObj
= {
render: TemplateComponent,
args: {
children: ,
- initialOpen: false,
},
parameters: {
chromatic: {
@@ -228,13 +258,6 @@ export const MultipleDrawers: StoryObj = {
},
};
-const snapshotStoryExcludedControlParams = [
- ...defaultExcludedControls,
- 'darkMode',
- 'displayMode',
- 'title',
-];
-
export const LightModeOverlay: StoryObj = {
render: TemplateComponent,
args: {
diff --git a/packages/drawer/src/Drawer/Drawer.constants.ts b/packages/drawer/src/Drawer/Drawer.constants.ts
index 7b4b80c9ee..2240190c59 100644
--- a/packages/drawer/src/Drawer/Drawer.constants.ts
+++ b/packages/drawer/src/Drawer/Drawer.constants.ts
@@ -1,3 +1,4 @@
export const HEADER_HEIGHT = 48;
export const MOBILE_BREAKPOINT = 390;
export const PANEL_WIDTH = 432;
+export const TOOLBAR_WIDTH = 48;
diff --git a/packages/drawer/src/Drawer/Drawer.styles.ts b/packages/drawer/src/Drawer/Drawer.styles.ts
index 2d751f00fc..ca10fd014c 100644
--- a/packages/drawer/src/Drawer/Drawer.styles.ts
+++ b/packages/drawer/src/Drawer/Drawer.styles.ts
@@ -1,5 +1,5 @@
-import { css, cx } from '@leafygreen-ui/emotion';
-import { Theme } from '@leafygreen-ui/lib';
+import { css, cx, keyframes } from '@leafygreen-ui/emotion';
+import { createUniqueClassName, Theme } from '@leafygreen-ui/lib';
import {
addOverflowShadow,
color,
@@ -17,15 +17,50 @@ import { DisplayMode } from './Drawer.types';
export const drawerTransitionDuration = transitionDuration.slower;
-const getBaseStyles = ({ open, theme }: { open: boolean; theme: Theme }) => css`
+export const drawerClassName = createUniqueClassName('lg-drawer');
+
+// Because of .show() and .close() in the drawer component, transitioning from 0px to (x)px does not transition correctly. Having the drawer start at the open position while hidden, moving to the closed position, and then animating to the open position is a workaround to get the animation to work.
+// These styles are used for a standalone drawer in overlay mode since it is not part of a grid layout.
+const drawerIn = keyframes`
+ 0% {
+ transform: translate3d(0%, 0, 0);
+ opacity: 0;
+ visibility: visible; // Adding visibility: hidden break autoFocus of the close button when using the dialog component
+ }
+ 1% {
+ transform: translate3d(100%, 0, 0);
+ opacity: 1;
+ }
+ 100% {
+ transform: translate3d(0%, 0, 0);
+ }
+`;
+
+// Keep the drawer opacity at 1 until the end of the animation. The inner container opacity is transitioned separately.
+const drawerOut = keyframes`
+ 0% {
+ transform: translate3d(0%, 0, 0);
+ }
+ 99% {
+ transform: translate3d(100%, 0, 0);
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ visibility: hidden;
+ }
+`;
+
+const getBaseStyles = ({ theme }: { theme: Theme }) => css`
all: unset;
+ padding: 0;
background-color: ${color[theme].background.primary.default};
- border: ${open
- ? `1px solid ${color[theme].border.secondary.default}`
- : 'none'};
+ border: 1px solid ${color[theme].border.secondary.default};
width: 100%;
max-width: ${PANEL_WIDTH}px;
height: 100%;
+ overflow: hidden;
+ box-sizing: border-box;
@media only screen and (max-width: ${MOBILE_BREAKPOINT}px) {
max-width: 100%;
@@ -35,113 +70,126 @@ const getBaseStyles = ({ open, theme }: { open: boolean; theme: Theme }) => css`
const overlayOpenStyles = css`
opacity: 1;
- transform: none;
+ animation-name: ${drawerIn};
+ // On mobile, the drawer should be positioned at the bottom of the screen when closed, and slide up to the top when opened.
@media only screen and (max-width: ${MOBILE_BREAKPOINT}px) {
transform: none;
}
`;
const overlayClosedStyles = css`
- opacity: 0;
- transform: translate3d(100%, 0, 0);
pointer-events: none;
+ animation-name: ${drawerOut};
+ // On mobile, the drawer should be positioned at the bottom of the screen when closed, and slide up to the top when opened.
@media only screen and (max-width: ${MOBILE_BREAKPOINT}px) {
transform: translate3d(0, 100%, 0);
+ opacity: 0;
}
`;
const getOverlayStyles = ({
open,
+ shouldAnimate,
zIndex,
}: {
open: boolean;
+ shouldAnimate: boolean;
zIndex: number;
}) =>
cx(
css`
- position: fixed;
+ position: absolute;
z-index: ${zIndex};
top: 0;
bottom: 0;
right: 0;
overflow: visible;
- transition: transform ${drawerTransitionDuration}ms ease-in-out,
- opacity ${drawerTransitionDuration}ms ease-in-out
- ${open ? '0ms' : `${drawerTransitionDuration}ms`};
+
+ // By default, the drawer is positioned off-screen to the right.
+ transform: translate3d(100%, 0, 0);
+ animation-timing-function: ease-in-out;
+ animation-duration: ${drawerTransitionDuration}ms;
+ animation-fill-mode: forwards;
@media only screen and (max-width: ${MOBILE_BREAKPOINT}px) {
top: unset;
left: 0;
+ // Since the drawer has position: fixed, we can use normal transitions
+ animation: none;
+ position: fixed;
+ transform: translate3d(0, 100%, 0);
+ transition: transform ${drawerTransitionDuration}ms ease-in-out,
+ opacity ${drawerTransitionDuration}ms ease-in-out
+ ${open ? '0ms' : `${drawerTransitionDuration}ms`};
}
`,
{
[overlayOpenStyles]: open,
- [overlayClosedStyles]: !open,
- },
- );
-
-const embeddedOpenStyles = css`
- width: 100%;
-
- @media only screen and (max-width: ${MOBILE_BREAKPOINT}px) {
- height: 50vh;
- }
-`;
-
-const embeddedClosedStyles = css`
- width: 0;
-
- @media only screen and (max-width: ${MOBILE_BREAKPOINT}px) {
- width: 100%;
- height: 0;
- }
-`;
-
-const getEmbeddedStyles = ({ open }: { open: boolean }) =>
- cx(
- css`
- position: relative;
- overflow: auto;
- `,
- {
- [embeddedOpenStyles]: open,
- [embeddedClosedStyles]: !open,
+ [overlayClosedStyles]: !open && shouldAnimate, // This ensures that the drawer does not animate closed on initial render
},
);
const getDisplayModeStyles = ({
displayMode,
open,
+ shouldAnimate,
zIndex,
}: {
displayMode: DisplayMode;
open: boolean;
+ shouldAnimate: boolean;
zIndex: number;
}) =>
cx({
- [getOverlayStyles({ open, zIndex })]: displayMode === DisplayMode.Overlay,
- [getEmbeddedStyles({ open })]: displayMode === DisplayMode.Embedded,
+ [getOverlayStyles({ open, shouldAnimate, zIndex })]:
+ displayMode === DisplayMode.Overlay,
});
export const getDrawerStyles = ({
className,
displayMode,
open,
+ shouldAnimate,
theme,
zIndex,
}: {
className?: string;
displayMode: DisplayMode;
open: boolean;
+ shouldAnimate: boolean;
theme: Theme;
zIndex: number;
}) =>
cx(
- getBaseStyles({ open, theme }),
- getDisplayModeStyles({ displayMode, open, zIndex }),
+ getBaseStyles({ theme }),
+ getDisplayModeStyles({ displayMode, open, shouldAnimate, zIndex }),
className,
+ drawerClassName,
+ );
+
+export const getDrawerShadowStyles = ({
+ theme,
+ displayMode,
+}: {
+ theme: Theme;
+ displayMode: DisplayMode;
+}) =>
+ cx(
+ css`
+ height: 100%;
+ background-color: ${color[theme].background.primary.default};
+ `,
+ {
+ [css`
+ ${addOverflowShadow({ isInside: false, side: Side.Left, theme })};
+
+ @media only screen and (max-width: ${MOBILE_BREAKPOINT}px) {
+ ${addOverflowShadow({ isInside: false, side: Side.Top, theme })};
+ }
+ `]: displayMode === DisplayMode.Overlay,
+ },
);
const getBaseInnerContainerStyles = ({ theme }: { theme: Theme }) => css`
@@ -150,43 +198,40 @@ const getBaseInnerContainerStyles = ({ theme }: { theme: Theme }) => css`
display: flex;
flex-direction: column;
background-color: ${color[theme].background.primary.default};
+ opacity: 0;
+ transition-property: opacity;
+ transition-duration: ${transitionDuration.faster}ms;
+ transition-timing-function: linear;
`;
-const getDrawerShadowStyles = ({ theme }: { theme: Theme }) => css`
- ${addOverflowShadow({ isInside: false, side: Side.Left, theme })};
-
- @media only screen and (max-width: ${MOBILE_BREAKPOINT}px) {
- ${addOverflowShadow({ isInside: false, side: Side.Top, theme })};
- }
+const getInnerOpenContainerStyles = css`
+ transition-property: opacity;
+ transition-duration: ${transitionDuration.slowest}ms;
+ transition-timing-function: linear;
+ opacity: 1;
`;
export const getInnerContainerStyles = ({
- displayMode,
theme,
+ open,
}: {
- displayMode: DisplayMode;
theme: Theme;
+ open: boolean;
}) =>
cx(getBaseInnerContainerStyles({ theme }), {
- [getDrawerShadowStyles({ theme })]: displayMode === DisplayMode.Overlay,
+ [getInnerOpenContainerStyles]: open,
});
-export const getHeaderStyles = ({
- hasTabs,
- theme,
-}: {
- hasTabs: boolean;
- theme: Theme;
-}) => css`
+export const getHeaderStyles = ({ theme }: { theme: Theme }) => css`
height: ${HEADER_HEIGHT}px;
padding: ${spacing[400]}px;
display: flex;
justify-content: space-between;
align-items: center;
- border-bottom: ${hasTabs
- ? 'none'
- : `1px solid ${color[theme].border.secondary.default}`};
- transition: box-shadow ${transitionDuration.faster}ms ease-in-out;
+ border-bottom: 1px solid ${color[theme].border.secondary.default};
+ transition-property: box-shadow;
+ transition-duration: ${transitionDuration.faster}ms;
+ transition-timing-function: ease-in-out;
`;
const baseChildrenContainerStyles = css`
@@ -216,11 +261,7 @@ const scrollContainerStyles = css`
overscroll-behavior: contain;
`;
-export const getInnerChildrenContainerStyles = ({
- hasTabs,
-}: {
- hasTabs: boolean;
-}) =>
- cx(baseInnerChildrenContainerStyles, {
- [scrollContainerStyles]: !hasTabs,
- });
+export const innerChildrenContainerStyles = cx(
+ baseInnerChildrenContainerStyles,
+ scrollContainerStyles,
+);
diff --git a/packages/drawer/src/Drawer/Drawer.tsx b/packages/drawer/src/Drawer/Drawer.tsx
index 27785b8f74..87ce6c223b 100644
--- a/packages/drawer/src/Drawer/Drawer.tsx
+++ b/packages/drawer/src/Drawer/Drawer.tsx
@@ -15,17 +15,17 @@ import { usePolymorphic } from '@leafygreen-ui/polymorphic';
import { BaseFontSize } from '@leafygreen-ui/tokens';
import { Body } from '@leafygreen-ui/typography';
-import { DrawerContext } from '../DrawerContext';
import { useDrawerStackContext } from '../DrawerStackContext';
import { DEFAULT_LGID_ROOT, getLgIds } from '../utils';
import {
drawerTransitionDuration,
getChildrenContainerStyles,
+ getDrawerShadowStyles,
getDrawerStyles,
getHeaderStyles,
- getInnerChildrenContainerStyles,
getInnerContainerStyles,
+ innerChildrenContainerStyles,
} from './Drawer.styles';
import { DisplayMode, DrawerProps } from './Drawer.types';
@@ -50,12 +50,10 @@ export const Drawer = forwardRef(
);
const { getDrawerIndex, registerDrawer, unregisterDrawer } =
useDrawerStackContext();
-
+ const [shouldAnimate, setShouldAnimate] = useState(false);
const ref = useRef(null);
const drawerRef = useMergeRefs([fwdRef, ref]);
- const [hasTabs, setHasTabs] = useState(false);
-
const lgIds = getLgIds(dataLgId);
const id = useIdAllocator({ prefix: 'drawer', id: idProp });
const titleId = useIdAllocator({ prefix: 'drawer' });
@@ -78,6 +76,7 @@ export const Drawer = forwardRef(
if (open) {
drawerElement.show();
+ setShouldAnimate(true);
} else {
drawerElement.close();
}
@@ -93,33 +92,31 @@ export const Drawer = forwardRef(
return (
- setHasTabs(true) }}
+
-
+
@@ -127,9 +124,8 @@ export const Drawer = forwardRef(
as={typeof title === 'string' ? 'h2' : 'div'}
baseFontSize={BaseFontSize.Body2}
id={titleId}
- weight="medium"
>
- {title}
+ {title}