Skip to content

Commit 8da4388

Browse files
committed
fix
1 parent ac22cf8 commit 8da4388

File tree

7 files changed

+223
-68
lines changed

7 files changed

+223
-68
lines changed

packages/storybook/src/stories/drawer.stories.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ScoutDrawer } from "@scouterna/ui-react";
2+
import { fn } from "storybook/test";
23
import preview from "#.storybook/preview";
34

45
const meta = preview.meta({
@@ -13,10 +14,12 @@ export default meta;
1314

1415
export const BasicExample = meta.story({
1516
args: {
16-
open: true,
17-
title: "Drawer Title",
18-
backButton: true,
19-
closeButton: true,
17+
open: false,
18+
heading: "Drawer Heading",
19+
showBackButton: true,
20+
showExitButton: true,
21+
onBackButtonClicked: fn(),
22+
onExitButtonClicked: fn(),
2023
},
2124
render: (args) => (
2225
<div>

packages/ui-webc/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"class-variance-authority": "^0.7.1",
5656
"clsx": "^2.1.1",
5757
"iconoir": "^7.11.0",
58+
"dom-focus-lock": "^1.1.0",
5859
"@tabler/icons": "^3.36.1"
5960
},
6061
"devDependencies": {
Lines changed: 93 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,94 @@
1-
.drawer--container-desktop {
2-
position: absolute;
3-
top: 0;
1+
.drawer--container {
2+
position: fixed;
43
bottom: 0;
54
right: 0;
6-
height: 100%;
7-
max-width: 90%;
8-
width: 400px;
9-
transition: 0.3s;
10-
transition-timing-function: ease-out;
11-
z-index: 1;
5+
left: 0;
6+
height: 90%;
7+
width: 100%;
8+
transform: translateY(100%);
129
background-color: #fefefe;
1310
box-shadow: 0 0 20px 3px var(--color-gray-200);
1411
border-top-left-radius: var(--spacing-5);
15-
border-bottom-left-radius: var(--spacing-5);
12+
border-top-right-radius: var(--spacing-5);
1613
overflow: hidden;
14+
margin: 0;
15+
z-index: 101;
16+
}
17+
18+
@keyframes drawerOpen {
19+
from {
20+
transform: translateY(100%);
21+
}
22+
to {
23+
transform: translateY(0);
24+
}
25+
}
26+
@keyframes drawerClose {
27+
from {
28+
transform: translateY(0);
29+
}
30+
to {
31+
transform: translateY(100%);
32+
}
1733
}
1834

1935
.open {
20-
transform: translateX(0);
36+
animation: drawerOpen 0.3s ease-out forwards;
2137
}
22-
.closed {
23-
transform: translateX(100%);
38+
.close {
39+
animation: drawerClose 0.3s ease-out forwards;
40+
}
41+
42+
/* Desktop drawer style */
43+
@media screen and (min-width: 901px) {
44+
@keyframes drawerOpen {
45+
from {
46+
transform: translateX(100%);
47+
}
48+
to {
49+
transform: translateX(0);
50+
}
51+
}
52+
@keyframes drawerClose {
53+
from {
54+
transform: translateX(0);
55+
}
56+
to {
57+
transform: translateX(100%);
58+
}
59+
}
60+
61+
.drawer--container {
62+
top: 0;
63+
bottom: 0;
64+
right: 0;
65+
left: unset;
66+
height: 100%;
67+
max-height: 100%;
68+
max-width: 90%;
69+
width: 430px;
70+
transform: translateX(100%);
71+
border-top-left-radius: var(--spacing-5);
72+
border-bottom-left-radius: var(--spacing-5);
73+
border-top-right-radius: 0;
74+
}
75+
.open {
76+
animation: drawerOpen 0.3s ease-out forwards;
77+
}
78+
.close {
79+
animation: drawerClose 0.3s ease-out forwards;
80+
}
2481
}
2582

83+
/* Backdrop styles */
2684
.backdrop {
27-
position: absolute;
85+
position: fixed;
2886
top: 0;
2987
bottom: 0;
3088
left: 0;
3189
right: 0;
3290
transition: opacity 0.2s;
91+
z-index: 100;
3392
}
3493

3594
.backdrop-hidden {
@@ -38,13 +97,14 @@
3897

3998
.backdrop-visible {
4099
opacity: 0.6;
100+
pointer-events: all;
41101
background-color: var(--color-gray-200);
42102
}
43103

44104
/**
45105
* Header styles
46106
*/
47-
.header {
107+
.header--wrapper {
48108
display: flex;
49109
position: relative;
50110
width: 100%;
@@ -54,12 +114,16 @@
54114
align-items: center;
55115
}
56116

57-
.title {
117+
.heading {
58118
font: var(--type-body-lg);
59119
font-weight: 600;
60120
color: var(--color-text-base);
61121
}
62122

123+
.content--wrapper {
124+
padding: 0 var(--spacing-7);
125+
}
126+
63127
button {
64128
z-index: 2;
65129
pointer-events: all;
@@ -77,9 +141,19 @@ button {
77141
justify-content: center;
78142
}
79143

80-
.close-button {
81-
right: 10px;
144+
.exit-button {
145+
right: var(--spacing-3);
82146
}
83147
.back-button {
84-
left: 10px;
148+
left: var(--spacing-3);
149+
}
150+
151+
.visually-hidden {
152+
clip: rect(0 0 0 0);
153+
clip-path: inset(50%);
154+
height: 1px;
155+
overflow: hidden;
156+
position: absolute;
157+
white-space: nowrap;
158+
width: 1px;
85159
}

packages/ui-webc/src/components/drawer/drawer.tsx

Lines changed: 86 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import {
22
Component,
33
type ComponentInterface,
4+
Element,
45
Event,
56
type EventEmitter,
67
h,
78
Prop,
89
State,
10+
Watch,
911
} from "@stencil/core";
10-
import { isMobile } from "../../utils/utils";
12+
import focusLock from "dom-focus-lock";
1113

1214
const backIcon =
1315
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-left"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l14 0" /><path d="M5 12l6 6" /><path d="M5 12l6 -6" /></svg>';
14-
const closeIcon =
16+
const exitIcon =
1517
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-x"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M18 6l-12 12" /><path d="M6 6l12 12" /></svg>';
1618

1719
@Component({
@@ -22,35 +24,57 @@ const closeIcon =
2224
},
2325
})
2426
export class ScoutDrawer implements ComponentInterface {
27+
@Element() rootElement: HTMLElement;
2528
/**
2629
* Open/closestate of the drawer.
2730
*/
2831
@Prop() open: boolean = false;
2932
/**
30-
* Open/closestate of the drawer.
33+
* Open/close state of the drawer.
3134
*/
32-
@Prop() title: string = "";
35+
@Prop() heading: string = "";
3336
/**
3437
* Render back button.
3538
*/
36-
@Prop() backButton: boolean = false;
39+
@Prop() showBackButton: boolean = false;
40+
/**
41+
* Back button label.
42+
*/
43+
@Prop() backButtonLabel: string = "";
44+
/**
45+
* Render exit button.
46+
*/
47+
@Prop() showExitButton: boolean = false;
48+
/**
49+
* Back button label.
50+
*/
51+
@Prop() exitButtonLabel: string = "";
3752
/**
38-
* Render close button.
53+
* Disable backdrop for the drawer. Will also make it clickable to close the drawer.
3954
*/
40-
@Prop() closeButton: boolean = false;
55+
@Prop() disableBackdrop: boolean = false;
56+
4157
/**
42-
* Backdrop for the drawer. Will also make it clickable to close the drawer.
58+
* Disable drawer content padding. Use only if you have specific use case and you need to use full width.
4359
*/
44-
@Prop() backdrop: boolean = false;
60+
@Prop() disableContentPadding: boolean = false;
4561

46-
@State() openState: "opening" | "closing" | "open" | "close" = "close";
62+
@State() drawerState: "opening" | "closing" | "open" | "closed" = "closed";
63+
@State() focusedNode: Element = null;
4764

65+
componentWillLoad(): Promise<void> | void {
66+
this.focusedNode = document.activeElement;
67+
}
68+
disconnectedCallback(): void {
69+
this.focusedNode;
70+
}
4871
/**
4972
* Fired when clicking backButton (<-)
5073
*/
5174
@Event() backButtonClicked: EventEmitter<void>;
75+
5276
/**
53-
* Fired when clicking backButton (X)
77+
* Fired when clicking backButton (X). Also sent when clicking the backdrop.
5478
*/
5579
@Event() exitButtonClicked: EventEmitter<void>;
5680

@@ -61,49 +85,82 @@ export class ScoutDrawer implements ComponentInterface {
6185
this.exitButtonClicked.emit();
6286
}
6387

88+
@Watch("open")
89+
setDialogOpenState(open: boolean) {
90+
const drawer = this.rootElement.shadowRoot.querySelector(
91+
".drawer--container",
92+
) as HTMLElement;
93+
if (open) {
94+
this.drawerState = "opening";
95+
focusLock.on(drawer);
96+
} else {
97+
focusLock.off(drawer);
98+
this.drawerState = "closing";
99+
}
100+
}
101+
64102
render() {
65103
const shouldRenderHeader =
66-
this.title || this.backButton || this.closeButton;
67-
// const animateDrawer = (direction: "open" | "close") => {};
104+
this.heading || this.showBackButton || this.showExitButton;
105+
106+
const getDrawerStateClass = (state: string) => {
107+
switch (state) {
108+
case "opening":
109+
case "open":
110+
return "open";
111+
case "closing":
112+
return "close";
113+
}
114+
};
115+
68116
return (
69-
<div>
70-
{this.backdrop && (
117+
<div class="drawer">
118+
{!this.disableBackdrop && (
71119
// biome-ignore lint/a11y/noStaticElementInteractions: <closable backdrop>
120+
// biome-ignore lint/a11y/useKeyWithClickEvents: <closable backdrop>
72121
<div
73-
onKeyDown={() => {}}
74-
onChange={() => this.onExitButtonClick()}
75-
class={`backdrop ${this.open ? "backdrop-visible" : "backdrop-hidden"}`}
122+
onClick={() => {
123+
this.onExitButtonClick();
124+
}}
125+
class={`backdrop ${this.drawerState !== "closed" ? "backdrop-visible" : "backdrop-hidden"}`}
76126
></div>
77127
)}
78128
<div
79-
onAnimationStart={() => {}}
80-
onAnimationEnd={() => {}}
81-
class={`drawer--container-${isMobile() ? "desktop" : "desktop"} ${this.open ? "open" : "closed"}`}
129+
class={`drawer--container ${getDrawerStateClass(this.drawerState)}`}
130+
onAnimationEnd={() => {
131+
this.drawerState = this.open ? "open" : "closed";
132+
}}
82133
>
83134
{shouldRenderHeader && (
84-
<div class="header">
85-
{this.backButton && (
135+
<div class="header--wrapper">
136+
{this.showBackButton && (
86137
// biome-ignore lint/a11y/useButtonType: <not needed>
87138
<button
88139
class="back-button"
89140
onClick={() => this.onBackButtonClick()}
90141
>
91-
<span class="icon" innerHTML={backIcon}></span>
142+
<span class="icon" innerHTML={backIcon}>
143+
<span class="visually-hidden">{this.backButtonLabel}</span>
144+
</span>
92145
</button>
93146
)}
94-
{this.closeButton && (
147+
{this.showExitButton && (
95148
// biome-ignore lint/a11y/useButtonType: <not needed>
96149
<button
97-
class="close-button"
150+
class="exit-button"
98151
onClick={() => this.onExitButtonClick()}
99152
>
100-
<span class="icon" innerHTML={closeIcon} />
153+
<span class="icon" innerHTML={exitIcon}>
154+
<span class="visually-hidden">{this.exitButtonLabel}</span>
155+
</span>
101156
</button>
102157
)}
103-
{this.title && <h3 class="title">{this.title}</h3>}
158+
{this.heading && <h3 class="title">{this.heading}</h3>}
104159
</div>
105160
)}
106-
<slot />
161+
<div class={!this.disableContentPadding && `content--wrapper`}>
162+
<slot />
163+
</div>
107164
</div>
108165
</div>
109166
);

0 commit comments

Comments
 (0)