Skip to content

Commit 92fe753

Browse files
Merge pull request #630 from universal-ember/copilot/add-drawer-component
Add Drawer component
2 parents 734531c + cb43dc5 commit 92fe753

6 files changed

Lines changed: 681 additions & 1 deletion

File tree

dev/bin/lint.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ async function run() {
6161
}
6262

6363
function turbo(cmd) {
64-
const args = ['turbo', '--color', '--no-update-notifier', '--output-logs', 'errors-only', cmd];
64+
const args = ['turbo', '--color', '--no-update-notifier', cmd];
6565

6666
console.info(chalk.blueBright('Running:\n', args.join(' ')));
6767

docs-app/app/routes/application.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import Route from '@ember/routing/route';
22

33
import rehypeShikiFromHighlighter from '@shikijs/rehype/core';
44
import { SetupInstructions } from 'docs-app/components/setup.gts';
5+
import { KeyCombo } from 'ember-primitives/components/keys';
56
import { setupTabster } from 'ember-primitives/tabster';
67
import { setupKolay } from 'kolay/setup';
78
import { createHighlighterCore } from 'shiki/core';
@@ -57,6 +58,7 @@ export default class Application extends Route {
5758
ModifierSignature,
5859
Comment,
5960
Tabs,
61+
KeyCombo,
6062
comment,
6163
},
6264
modules: {
@@ -119,6 +121,7 @@ export default class Application extends Route {
119121
'ember-primitives/resize-observer': () => import('ember-primitives/resize-observer'),
120122
'ember-primitives/color-scheme': () => import('ember-primitives/color-scheme'),
121123
'ember-primitives/components/form': () => import('ember-primitives/components/form'),
124+
'ember-primitives/components/drawer': () => import('ember-primitives/components/drawer'),
122125
'ember-primitives/components/heading': () =>
123126
import('ember-primitives/components/heading'),
124127
'ember-primitives/components/tabs': () => import('ember-primitives/components/tabs'),
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
# Drawer
2+
3+
A drawer is a sliding panel that appears from the side of the screen, typically used for navigation, filters, or additional content.
4+
It is built on top of the native `<dialog>` element, similar to modals, but designed to slide in from a specific side of the viewport.
5+
6+
<Callout>
7+
8+
This pattern of a sliding drawer / sheet can be done with only the native `<dialog>` and some `<button>` to open it. This component is a _very_ small wrapper for this pattern -- primarily a state container for easily controlling the open state of the `<dialog>` element without wiring it up yourself.
9+
10+
</Callout>
11+
12+
13+
<div class="featured-demo">
14+
15+
```gjs live preview no-shadow
16+
import { Drawer } from 'ember-primitives/components/drawer';
17+
import { on } from '@ember/modifier';
18+
19+
<template>
20+
<Drawer as |d|>
21+
<button {{on 'click' d.open}}>Open Drawer</button>
22+
23+
<d.Drawer class="not-prose">
24+
<header>
25+
<h2>Example Drawer</h2>
26+
<button {{on 'click' d.close}}>Close</button>
27+
</header>
28+
<form method="dialog">
29+
<main>
30+
Drawer content here
31+
<br>
32+
Because this is a native dialog element, it captures focus,
33+
and pressing escape will close the dialog and return focus to the
34+
original button.
35+
</main>
36+
37+
<footer>
38+
<button type="submit" value="confirm">Confirm</button>
39+
<button type="submit" value="create">Create</button>
40+
</footer>
41+
</form>
42+
</d.Drawer>
43+
</Drawer>
44+
45+
<link rel="stylesheet" href="https://unpkg.com/open-props/easings.min.css"/>
46+
<style>
47+
@scope {
48+
dialog {
49+
transition:
50+
display 0.125s allow-discrete,
51+
overlay 0.125s allow-discrete;
52+
animation: close 0.125s forwards;
53+
54+
&[open] {
55+
animation: open 0.25s forwards;
56+
}
57+
}
58+
59+
@keyframes open {
60+
from {
61+
opacity: 0;
62+
transform: translate(0, 100%);
63+
}
64+
to {
65+
opacity: 1;
66+
transform: translate(0, 0);
67+
}
68+
}
69+
70+
@keyframes close {
71+
from {
72+
opacity: 1;
73+
transform: translate(0, 0);
74+
}
75+
to {
76+
opacity: 0;
77+
transform: translate(0, 100%);
78+
}
79+
}
80+
81+
}
82+
83+
@scope {
84+
[data-repl-output] {
85+
display: block;
86+
}
87+
button {
88+
padding: 0.5rem 1rem;
89+
border-radius: 0.25rem;;
90+
91+
background-color: #2563eb; /* blue */
92+
color: #ffffff;
93+
94+
transition:
95+
background-color 120ms ease-out,
96+
box-shadow 120ms ease-out,
97+
transform 120ms ease-out;
98+
}
99+
100+
button:hover {
101+
background-color: #1d4ed8;
102+
}
103+
104+
button:active {
105+
transform: translateY(1px);
106+
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.3) inset;
107+
}
108+
}
109+
110+
@scope {
111+
/* Basic reset for the dialog */
112+
dialog {
113+
position: fixed;
114+
top: unset;
115+
bottom: 0px;
116+
color: black;
117+
background: white;
118+
margin: 0 auto;
119+
min-width: 80dvw;
120+
border-top-right-radius: 0.5rem;
121+
border-top-left-radius: 0.5rem;
122+
123+
main {
124+
padding: 1rem;
125+
}
126+
127+
header {
128+
display: flex;
129+
align-items: center;
130+
justify-content: space-between;
131+
padding: 0.5rem 1rem;
132+
border-bottom: 1px solid color-mix(in srgb, currentColor 10%, transparent);
133+
134+
h2 {
135+
font-size: 1.5rem;
136+
}
137+
}
138+
139+
footer {
140+
display: flex;
141+
justify-content: flex-end;
142+
gap: 2rem;
143+
padding: 1rem;
144+
}
145+
}
146+
147+
/* backdrop */
148+
dialog::backdrop {
149+
background: color-mix(in srgb, #020617 60%, transparent);
150+
}
151+
}
152+
</style>
153+
</template>
154+
```
155+
156+
</div>
157+
158+
## Install
159+
160+
```hbs live
161+
<SetupInstructions @src="components/drawer.gts" />
162+
```
163+
164+
## Anatomy
165+
166+
```gjs
167+
import { Drawer } from 'ember-primitives/components/drawer';
168+
169+
<template>
170+
<Drawer as |d|>
171+
172+
d.isOpen - boolean
173+
d.open - function
174+
d.close - function
175+
176+
<d.Drawer ...attributes>
177+
this is just the HTMLDialogElement
178+
</d.Drawer>
179+
</Drawer>
180+
</template>
181+
```
182+
183+
## API Reference
184+
185+
```gjs live no-shadow
186+
import { ComponentSignature } from 'kolay';
187+
188+
<template>
189+
<ComponentSignature
190+
@package="ember-primitives"
191+
@module="declarations/components/drawer"
192+
@name="Signature" />
193+
</template>
194+
```
195+
196+
### State Attributes
197+
198+
There is no root element for this component, so there are no state attributes to use.
199+
Since this component uses the `<dialog>` element, it will still use the `open` attribute.
200+
201+
## Accessibility
202+
203+
Once the drawer is open, the browser will focus on the first button (in this case, it's our close button on the drawer header) or any button with the autofocus attribute within the drawer. When you close the drawer, the browser restores the focus on the button we used to open it.
204+
205+
### Keyboard Interactions
206+
207+
Because this builds on `<dialog>`, all behaviors are already built in to the browser.
208+
209+
Pressing the <kbd>esc</kbd> key closes the dialog, focusing the last-focused element before the dialog was opened.
210+
211+
## References
212+
213+
- [MDN HTMLDialogElement](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog)
214+
- [web.dev : Building a dialog component](https://web.dev/building-a-dialog-component/)
215+
216+

0 commit comments

Comments
 (0)