Skip to content

Commit

Permalink
Feat(web): Introduce Drawer component #DS-1580
Browse files Browse the repository at this point in the history
  • Loading branch information
curdaj committed Jan 14, 2025
1 parent ec153bd commit 00ac421
Show file tree
Hide file tree
Showing 14 changed files with 455 additions and 0 deletions.
87 changes: 87 additions & 0 deletions packages/web/src/scss/components/Drawer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Drawer

The Drawer component is a container that slides in from side of the screen. It can be used to display additional content or actions that are not part of the main view.

The Drawer is a composition of several subcomponents:

- [Drawer](#drawer)
- [DrawerCloseButton](#drawerclosebutton)
- [DrawerPanel](#drawerpanel)

👉 The animation effect of this component is dependent on the
`prefers-reduced-motion` media query.

## Drawer

```html
<dialog id="my-drawer-dialog" class="Drawer Drawer--right">
<!-- Drawer panel goes here -->
</dialog>
```

### Alignment

The `Drawer` component allows aligning the content panel horizontally to the left or right side of the screen using `--left` or `--right` modifier. The default alignment of the drawer content panel is to the right.

```html
<dialog id="my-drawer-dialog" class="Drawer Drawer--left">
<!-- Drawer panel goes here -->
</dialog>
```

## DrawerCloseButton

The `DrawerCloseButton` component is a button that closes the drawer when clicked.

```html
<button
type="button"
class="Button Button--tertiary Button--medium Button--symmetrical DrawerCloseButton"
data-spirit-dismiss="offcanvas"
data-spirit-target="#my-drawer-dialog"
aria-controls="my-drawer-dialog"
aria-expanded="false"
>
<svg width="24" height="24" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#close" />
</svg>
<span class="accessibility-hidden">Close</span>
</button>
```

## DrawerPanel

The `DrawerPanel` component is a container for the content that will be displayed in the drawer.

```html
<div class="DrawerPanel">
<div class="DrawerPanel__content">
<!-- Drawer content goes here -->
</div>
</div>
```

## Full Example

```html
<dialog id="drawer-example" class="Drawer Drawer--right">
<div class="DrawerPanel">
<div class="DrawerPanel__content">
<button
type="button"
aria-expanded="false"
class="Button Button--tertiary Button--medium Button--symmetrical DrawerCloseButton"
data-spirit-dismiss="offcanvas"
data-spirit-target="#drawer-example"
aria-controls="drawer-example"
>
<svg width="24" height="24" aria-hidden="true">
<use xlink:href="/icons/svg/sprite.svg#close" />
</svg>
<span class="accessibility-hidden">Close</span>
</button>
<!-- Drawer content goes here -->
</div>
</div>
</dialog>
```
49 changes: 49 additions & 0 deletions packages/web/src/scss/components/Drawer/_Drawer.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
@use '../../tools/typography';
@use '@tokens' as tokens;
@use 'theme';

.Drawer {
@include typography.generate(theme.$drawer-typography);

--#{tokens.$css-variable-prefix}drawer-translate-x: 0%;

all: unset;
position: fixed;
inset: 0;
z-index: 1;
display: flex;
width: 100%;
max-width: none;
height: 100%;
max-height: none;
border: none;
background-color: theme.$drawer-backdrop-background-color;
visibility: hidden;

&::backdrop {
background-color: transparent;
}

@media (prefers-reduced-motion: no-preference) {
transition-property: visibility, opacity;
transition-duration: theme.$drawer-transition-duration;
}
}

.Drawer--left {
--#{tokens.$css-variable-prefix}drawer-translate-x: -100%;

justify-content: start;
}

.Drawer--right {
--#{tokens.$css-variable-prefix}drawer-translate-x: 100%;

justify-content: end;
}

.Drawer[open] {
--#{tokens.$css-variable-prefix}drawer-translate-x: 0%;

visibility: visible;
}
11 changes: 11 additions & 0 deletions packages/web/src/scss/components/Drawer/_DrawerCloseButton.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@use '../../tools/accessibility';
@use '../../tools/reset';
@use 'theme';

.DrawerCloseButton {
@include accessibility.min-tap-target(theme.$drawer-close-button-size);

align-self: flex-end;
margin-inline-end: theme.$drawer-close-button-padding-x;
margin-block-start: theme.$drawer-close-button-padding-y;
}
29 changes: 29 additions & 0 deletions packages/web/src/scss/components/Drawer/_DrawerPanel.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
@use '@tokens' as tokens;
@use 'theme';

.DrawerPanel {
display: flex;
flex-direction: column;
box-sizing: border-box;
width: var(--#{tokens.$css-variable-prefix}drawer-panel-width, #{theme.$drawer-panel-width});
height: theme.$drawer-panel-height;
max-height: theme.$drawer-panel-max-height;
color: theme.$drawer-panel-text-color;
background-color: theme.$drawer-panel-background-color;
box-shadow: theme.$drawer-panel-shadow;
transform: translateX(var(--#{tokens.$css-variable-prefix}drawer-translate-x));

@media (prefers-reduced-motion: no-preference) {
transition-property: transform;
transition-duration: theme.$drawer-transition-duration;
transition-timing-function: theme.$drawer-transition-timing;
}
}

.DrawerPanel__content {
display: flex;
flex-grow: 1;
flex-direction: column;
overflow-y: auto;
overscroll-behavior: contain;
}
22 changes: 22 additions & 0 deletions packages/web/src/scss/components/Drawer/_theme.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@use '@tokens' as tokens;
@use '../../settings/transitions';

$drawer-typography: tokens.$body-medium-semibold;

// Transition
$drawer-transition-duration: transitions.$duration-200;
$drawer-transition-timing: transitions.$timing-eased-in-out;
$drawer-backdrop-background-color: tokens.$background-backdrop;

// DrawerPanel
$drawer-panel-width: 288px;
$drawer-panel-height: auto;
$drawer-panel-max-height: none;
$drawer-panel-text-color: tokens.$text-primary;
$drawer-panel-background-color: tokens.$background-primary;
$drawer-panel-shadow: tokens.$shadow-400;

// DrawerCloseButton
$drawer-close-button-size: tokens.$space-1200;
$drawer-close-button-padding-x: tokens.$space-900;
$drawer-close-button-padding-y: tokens.$space-700;
163 changes: 163 additions & 0 deletions packages/web/src/scss/components/Drawer/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
{{#> web/layout/default title="Drawer" parentPageName="Components" }}

<script>
document.addEventListener("DOMContentLoaded", () => {
const radios = document.querySelectorAll('input[name="drawer-alignment"]');
const drawer = document.getElementById("drawer-example");
const backdropCheckbox = document.getElementById("drawer-is-closable-on-backdrop-click");
const escapeKeyCheckbox = document.getElementById("drawer-is-closable-on-escape-key");
const contentTextArea = document.getElementById("drawer-content");
const drawerContentDiv = drawer.querySelector("[data-drawer-demo-content]");

let isClosableOnBackdropClick = backdropCheckbox.checked;
let isClosableOnEscapeKey = escapeKeyCheckbox.checked;

radios.forEach(radio => {
radio.addEventListener("change", () => {
if (radio.checked) {
if (radio.value === "left") {
drawer.classList.replace("Drawer--right", "Drawer--left");
} else if (radio.value === "right") {
drawer.classList.replace("Drawer--left", "Drawer--right");
}
}
});
});

backdropCheckbox.addEventListener("change", () => {
isClosableOnBackdropClick = backdropCheckbox.checked;
});

escapeKeyCheckbox.addEventListener("change", () => {
isClosableOnEscapeKey = escapeKeyCheckbox.checked;
});

contentTextArea.addEventListener("input", () => {
drawerContentDiv.textContent = contentTextArea.value;
});

const closeDrawer = () => {
drawer.close();
drawer.setAttribute("aria-expanded", "false");
};

const openDrawerButton = document.querySelector('button[data-testid="drawer-open-button"]');
openDrawerButton.addEventListener("click", () => {
drawer.show();
drawer.setAttribute("aria-expanded", "true");

drawer.addEventListener("click", (event) => {
if (isClosableOnBackdropClick && event.target === drawer) {
closeDrawer();
}
});

document.addEventListener("keydown", (event) => {
if (isClosableOnEscapeKey && event.key === "Escape") {
closeDrawer();
}
});
});

const closeButton = drawer.querySelector(".DrawerCloseButton");
closeButton.addEventListener("click", closeDrawer);
});
</script>


<section class="UNSTABLE_Section">

<div class="Container">
<h2 class="docs-Heading">Drawer</h2>

<div class="docs-Stack docs-Stack--start">

<form class="mb-600">
<fieldset class="border-0">
<legend>Drawer alignment:</legend>
<label for="drawer-alignment-left" class="Radio mr-600">
<input name="drawer-alignment" autocomplete="off" aria-describedby="drawer-alignment-left__helperText" type="radio" id="drawer-alignment-left" class="Radio__input" value="left">
<span class="Radio__text">
<span class="Radio__label">
Left
</span>
</span>
</label>
<label for="drawer-alignment-right" class="Radio">
<input name="drawer-alignment" autocomplete="off" aria-describedby="drawer-alignment-right__helperText" type="radio" id="drawer-alignment-right" class="Radio__input" value="right" checked="">
<span class="Radio__text">
<span class="Radio__label">
Right
</span>
</span>
</label>
</fieldset>
<fieldset class="border-0">
<div class="Stack Stack--hasSpacing">
<label class="Checkbox" for="drawer-is-closable-on-backdrop-click">
<input name="is-closable-on-backdrop-click" aria-describedby="drawer-is-closable-on-backdrop-click__helperText" type="checkbox" id="drawer-is-closable-on-backdrop-click" class="Checkbox__input" value="" checked="">
<span class="Checkbox__text">
<span class="Checkbox__label">
Closable on Backdrop Click
</span>
</span>
</label>
<label class="Checkbox" for="drawer-is-closable-on-escape-key">
<input name="is-closable-on-escape-key" aria-describedby="drawer-is-closable-on-escape-key__helperText" type="checkbox" id="drawer-is-closable-on-escape-key" class="Checkbox__input" value="" checked="">
<span class="Checkbox__text">
<span class="Checkbox__label">
Closable on Escape Key Down
</span>
</span>
</label>
<div class="TextArea">
<label for="drawer-content" class="TextArea__label">Drawer content</label>
<textarea aria-describedby="drawer-content__helperText" name="content" class="TextArea__input" id="drawer-content">This is a Drawer content.</textarea>
<div class="TextArea__helperText" id="drawer-content__helperText">
Can contain HTML.
</div>
</div>
</div>
</fieldset>
</form>

<button
type="button"
data-testid="drawer-open-button"
class="Button Button--primary Button--medium"
data-spirit-toggle="offcanvas"
data-spirit-target="#drawer-example"
aria-controls="drawer-example"
aria-expanded="false"
>
Open Drawer
</button>

<!-- Drawer: start -->
<dialog id="drawer-example" class="Drawer Drawer--right" aria-label="example-basic__title">
<div class="DrawerPanel" data-testid="drawer-panel">
<div class="DrawerPanel__content">
<button
type="button"
aria-expanded="false"
class="Button Button--tertiary Button--medium Button--symmetrical DrawerCloseButton"
data-spirit-dismiss="offcanvas"
data-spirit-target="#drawer-example"
aria-controls="drawer-example"
>
<svg width="24" height="24" aria-hidden="true">
<use xlink:href="/assets/icons/svg/sprite.svg#close" />
</svg>
<span class="accessibility-hidden">Close</span>
</button>
<div class="p-800" data-drawer-demo-content>This is a Drawer content.</div>
</div>
</div>
</dialog>
<!-- Drawer: end -->

</div>
</div>
</section>

{{/web/layout/default }}
3 changes: 3 additions & 0 deletions packages/web/src/scss/components/Drawer/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@forward 'Drawer';
@forward 'DrawerPanel';
@forward 'DrawerCloseButton';
1 change: 1 addition & 0 deletions packages/web/src/scss/components/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
@forward 'Collapse';
@forward 'Container';
@forward 'Divider';
@forward 'Drawer';
@forward 'Dropdown';
@forward 'FieldGroup';
@forward 'FileUploader';
Expand Down
Loading

0 comments on commit 00ac421

Please sign in to comment.