Skip to content

Commit eff5bde

Browse files
authored
feat(SidePanel): side panel animates on open and close (#1243)
1 parent 465a812 commit eff5bde

File tree

3 files changed

+151
-97
lines changed

3 files changed

+151
-97
lines changed

src/components/SidePanel/SidePanel.scss

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,15 @@
4343
bottom: 0;
4444
}
4545
}
46+
47+
.slide-in {
48+
animation: slideIn 0.1s;
49+
}
50+
51+
@keyframes slideIn {
52+
from {
53+
box-shadow: 0 0 0 0 transparent;
54+
transform: translateX(100%);
55+
visibility: hidden;
56+
}
57+
}

src/components/SidePanel/SidePanel.stories.tsx

Lines changed: 108 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -72,103 +72,101 @@ const StoryExample = (args: Story["args"]) => {
7272
Close side panel
7373
</Button>
7474
</AppMain>
75-
{isOpen && (
76-
<SidePanel
77-
overlay={args.overlay}
78-
loading={args.loading}
79-
hasError={args.hasError}
80-
parentId={parentId}
81-
pinned={args.pinned}
82-
width={args.width}
83-
className="u-no-padding--top u-no-padding--bottom"
84-
>
85-
<SidePanel.Sticky>
86-
<SidePanel.Header>
87-
<SidePanel.HeaderTitle>Edit panel</SidePanel.HeaderTitle>
88-
<SidePanel.HeaderControls>
89-
<Button
90-
appearance="base"
91-
className="u-no-margin--bottom"
92-
hasIcon
93-
onClick={closePanel}
94-
aria-label="Close"
95-
>
96-
<Icon name="close" />
97-
</Button>
98-
</SidePanel.HeaderControls>
99-
</SidePanel.Header>
100-
</SidePanel.Sticky>
101-
<SidePanel.Content className="u-no-padding">
102-
<p>Here be dragons!</p>
103-
<p>
104-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras
105-
varius mi eu pretium vulputate. Nunc commodo sit amet nibh quis
106-
rhoncus. Aliquam rhoncus porttitor semper. Aenean faucibus
107-
consectetur neque in sodales. Sed cursus mauris id ex sollicitudin
108-
sodales. Quisque molestie rutrum odio, ornare pharetra ligula.
109-
Pellentesque ornare tristique feugiat. In a augue neque. Aenean
110-
eget arcu quis lacus tempus posuere in sit amet dui. Suspendisse
111-
faucibus sapien nisl, nec laoreet sem convallis nec.
112-
</p>
113-
<p>
114-
Vestibulum sed placerat lorem. Nam luctus ex id nisi luctus, id
115-
vestibulum sem bibendum. Vivamus turpis sem, pellentesque
116-
fermentum malesuada eu, faucibus porta libero. Duis eget venenatis
117-
odio. Etiam sed volutpat magna, non tempus erat. Nunc id tortor ac
118-
quam consectetur dapibus ac ut tellus. Pellentesque ut tellus
119-
venenatis elit vehicula condimentum eget quis lorem. Ut non
120-
consectetur est, a fringilla ipsum. Nunc vitae ligula ipsum. Etiam
121-
suscipit, libero ut lacinia viverra, nunc urna consequat ex, vel
122-
eleifend eros mauris vitae ipsum. Pellentesque sed dictum augue.
123-
Ut sit amet ullamcorper mauris. Nunc congue orci mollis purus
124-
sodales facilisis ac ut arcu. Maecenas feugiat sapien ac massa
125-
mollis sodales. Donec vitae turpis eu nisi laoreet pulvinar quis
126-
at nisl. Integer volutpat, metus eget elementum dictum, lacus
127-
sapien viverra felis, consequat fermentum nisl mi ac dui.
128-
</p>
129-
<p>
130-
Nullam nulla turpis, dignissim vel dapibus ut, volutpat ac dui.
131-
Donec vel elementum lacus. Mauris maximus nec felis at faucibus.
132-
Nunc faucibus gravida velit, id blandit lectus tincidunt ac.
133-
Vestibulum orci diam, elementum in congue eu, placerat id risus.
134-
Sed tempor tempus tellus, vitae iaculis turpis fringilla nec.
135-
Phasellus imperdiet facilisis velit, sit amet lobortis odio
136-
dignissim ut.
137-
</p>
138-
<p>
139-
Nam placerat urna vitae ligula hendrerit, ac tincidunt lorem
140-
maximus. Mauris eu odio nisi. Nulla facilisi. Sed egestas elit sed
141-
velit rutrum, sit amet bibendum metus hendrerit. Pellentesque
142-
luctus placerat tellus, eu bibendum justo. Cras eget leo ac ex
143-
volutpat gravida. Duis vitae mollis ante. Duis a congue nunc.
144-
Aenean aliquet, sapien quis tincidunt tincidunt, odio eros
145-
consectetur lacus, vel finibus mauris tortor id velit. Donec
146-
tincidunt vitae purus eu interdum. Pellentesque scelerisque dui
147-
viverra ex ullamcorper volutpat. Vestibulum lacinia vitae arcu
148-
volutpat porta. Etiam et cursus nulla, id aliquet felis. Nam
149-
ultricies, urna id mattis pretium, velit erat viverra elit, eu
150-
maximus diam eros id nisi.
151-
</p>
152-
<p>
153-
Nullam eget nisl lectus. Pellentesque eu mauris ut tortor
154-
malesuada sagittis. Cras dictum cursus est non ultricies. Duis
155-
mollis non neque at commodo. Nunc feugiat justo et consequat
156-
aliquam. Ut consectetur libero eu erat feugiat finibus. Duis
157-
varius convallis quam eu sagittis. Maecenas ac est arcu.
158-
Suspendisse at enim eget nibh ultricies dictum. Etiam aliquet
159-
tellus vel felis malesuada laoreet.
160-
</p>
161-
</SidePanel.Content>
162-
<SidePanel.Sticky position="bottom">
163-
<SidePanel.Footer className="u-align--right">
164-
<Button appearance="base" onClick={closePanel}>
165-
Cancel
75+
<SidePanel
76+
overlay={args.overlay}
77+
loading={args.loading}
78+
hasError={args.hasError}
79+
parentId={parentId}
80+
pinned={args.pinned}
81+
width={args.width}
82+
className="u-no-padding--top u-no-padding--bottom"
83+
isOpen={isOpen}
84+
isAnimated={args.isAnimated}
85+
>
86+
<SidePanel.Sticky>
87+
<SidePanel.Header>
88+
<SidePanel.HeaderTitle>Edit panel</SidePanel.HeaderTitle>
89+
<SidePanel.HeaderControls>
90+
<Button
91+
appearance="base"
92+
className="u-no-margin--bottom"
93+
hasIcon
94+
onClick={closePanel}
95+
aria-label="Close"
96+
>
97+
<Icon name="close" />
16698
</Button>
167-
<ActionButton appearance="positive">Save changes</ActionButton>
168-
</SidePanel.Footer>
169-
</SidePanel.Sticky>
170-
</SidePanel>
171-
)}
99+
</SidePanel.HeaderControls>
100+
</SidePanel.Header>
101+
</SidePanel.Sticky>
102+
<SidePanel.Content className="u-no-padding">
103+
<p>Here be dragons!</p>
104+
<p>
105+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras varius
106+
mi eu pretium vulputate. Nunc commodo sit amet nibh quis rhoncus.
107+
Aliquam rhoncus porttitor semper. Aenean faucibus consectetur neque
108+
in sodales. Sed cursus mauris id ex sollicitudin sodales. Quisque
109+
molestie rutrum odio, ornare pharetra ligula. Pellentesque ornare
110+
tristique feugiat. In a augue neque. Aenean eget arcu quis lacus
111+
tempus posuere in sit amet dui. Suspendisse faucibus sapien nisl,
112+
nec laoreet sem convallis nec.
113+
</p>
114+
<p>
115+
Vestibulum sed placerat lorem. Nam luctus ex id nisi luctus, id
116+
vestibulum sem bibendum. Vivamus turpis sem, pellentesque fermentum
117+
malesuada eu, faucibus porta libero. Duis eget venenatis odio. Etiam
118+
sed volutpat magna, non tempus erat. Nunc id tortor ac quam
119+
consectetur dapibus ac ut tellus. Pellentesque ut tellus venenatis
120+
elit vehicula condimentum eget quis lorem. Ut non consectetur est, a
121+
fringilla ipsum. Nunc vitae ligula ipsum. Etiam suscipit, libero ut
122+
lacinia viverra, nunc urna consequat ex, vel eleifend eros mauris
123+
vitae ipsum. Pellentesque sed dictum augue. Ut sit amet ullamcorper
124+
mauris. Nunc congue orci mollis purus sodales facilisis ac ut arcu.
125+
Maecenas feugiat sapien ac massa mollis sodales. Donec vitae turpis
126+
eu nisi laoreet pulvinar quis at nisl. Integer volutpat, metus eget
127+
elementum dictum, lacus sapien viverra felis, consequat fermentum
128+
nisl mi ac dui.
129+
</p>
130+
<p>
131+
Nullam nulla turpis, dignissim vel dapibus ut, volutpat ac dui.
132+
Donec vel elementum lacus. Mauris maximus nec felis at faucibus.
133+
Nunc faucibus gravida velit, id blandit lectus tincidunt ac.
134+
Vestibulum orci diam, elementum in congue eu, placerat id risus. Sed
135+
tempor tempus tellus, vitae iaculis turpis fringilla nec. Phasellus
136+
imperdiet facilisis velit, sit amet lobortis odio dignissim ut.
137+
</p>
138+
<p>
139+
Nam placerat urna vitae ligula hendrerit, ac tincidunt lorem
140+
maximus. Mauris eu odio nisi. Nulla facilisi. Sed egestas elit sed
141+
velit rutrum, sit amet bibendum metus hendrerit. Pellentesque luctus
142+
placerat tellus, eu bibendum justo. Cras eget leo ac ex volutpat
143+
gravida. Duis vitae mollis ante. Duis a congue nunc. Aenean aliquet,
144+
sapien quis tincidunt tincidunt, odio eros consectetur lacus, vel
145+
finibus mauris tortor id velit. Donec tincidunt vitae purus eu
146+
interdum. Pellentesque scelerisque dui viverra ex ullamcorper
147+
volutpat. Vestibulum lacinia vitae arcu volutpat porta. Etiam et
148+
cursus nulla, id aliquet felis. Nam ultricies, urna id mattis
149+
pretium, velit erat viverra elit, eu maximus diam eros id nisi.
150+
</p>
151+
<p>
152+
Nullam eget nisl lectus. Pellentesque eu mauris ut tortor malesuada
153+
sagittis. Cras dictum cursus est non ultricies. Duis mollis non
154+
neque at commodo. Nunc feugiat justo et consequat aliquam. Ut
155+
consectetur libero eu erat feugiat finibus. Duis varius convallis
156+
quam eu sagittis. Maecenas ac est arcu. Suspendisse at enim eget
157+
nibh ultricies dictum. Etiam aliquet tellus vel felis malesuada
158+
laoreet.
159+
</p>
160+
</SidePanel.Content>
161+
<SidePanel.Sticky position="bottom">
162+
<SidePanel.Footer className="u-align--right">
163+
<Button appearance="base" onClick={closePanel}>
164+
Cancel
165+
</Button>
166+
<ActionButton appearance="positive">Save changes</ActionButton>
167+
</SidePanel.Footer>
168+
</SidePanel.Sticky>
169+
</SidePanel>
172170
</Application>
173171
);
174172
};
@@ -255,3 +253,17 @@ export const Wide: Story = {
255253
},
256254
render: StoryExample,
257255
};
256+
257+
export const Animated: Story = {
258+
args: {
259+
className: "",
260+
hasError: false,
261+
parentId: "l-application-default",
262+
pinned: false,
263+
loading: false,
264+
overlay: false,
265+
width: "",
266+
isAnimated: true,
267+
},
268+
render: StoryExample,
269+
};

src/components/SidePanel/SidePanel.tsx

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from "react";
1+
import React, { useState } from "react";
22
import classnames from "classnames";
33
import { FC, PropsWithChildren, ReactNode } from "react";
44
import { createPortal } from "react-dom";
@@ -33,6 +33,16 @@ export type Props = {
3333
*/
3434
hasError?: boolean;
3535

36+
/**
37+
* Whether the side panel animates on open and close. This requires the side panel to be controlled by the `isOpen` prop instead of conditional rendering. If you use `overlay: true`, you must also include `overflow: hidden` on the document body.
38+
*/
39+
isAnimated?: boolean;
40+
41+
/**
42+
* Whether the side panel is open or not.
43+
*/
44+
isOpen?: boolean;
45+
3646
/**
3747
* Whether the side panel is currently loading. This will show a spinner in the panel instead of rendering the children.
3848
*/
@@ -108,20 +118,40 @@ const SidePanelComponent = ({
108118
pinned,
109119
width = "",
110120
parentId = "l-application",
121+
isOpen = true,
122+
isAnimated,
111123
}: Props): React.JSX.Element => {
124+
const [hiding, setHiding] = useState(true);
125+
const [previousIsOpen, setPreviousIsOpen] = useState(false);
126+
112127
const container = document.getElementById(parentId) || document.body;
113128

129+
if (isOpen !== previousIsOpen) {
130+
setPreviousIsOpen(isOpen);
131+
setHiding(true);
132+
}
133+
134+
// Pinned side panels don't get animation in Vanilla
135+
const isAnimatedFinal = isAnimated && !pinned;
136+
137+
if (!isOpen && !(isAnimatedFinal && hiding)) {
138+
return null;
139+
}
140+
114141
return (
115142
<>
116143
{createPortal(
117144
<AppAside
118145
className={classnames("side-panel", className, {
119146
"is-overlay": overlay,
147+
"slide-in": isAnimatedFinal,
120148
})}
149+
collapsed={!isOpen}
121150
aria-label="Side panel"
122151
pinned={pinned}
123152
narrow={width === "narrow"}
124153
wide={width === "wide"}
154+
onTransitionEnd={() => setHiding(false)}
125155
>
126156
{loading ? (
127157
<div className="loading">

0 commit comments

Comments
 (0)