@realmorg/ui 0.0.0-stage → 2.0.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +44 -2
- package/components/accordion.html +139 -0
- package/components/alert-dialog.html +236 -0
- package/components/alert.html +83 -0
- package/components/aspect-ratio.html +29 -0
- package/components/attachment.html +98 -0
- package/components/avatar.html +149 -0
- package/components/badge.html +62 -0
- package/components/breadcrumb.html +159 -0
- package/components/bubble.html +77 -0
- package/components/button-group.html +98 -0
- package/components/button.html +194 -0
- package/components/calendar.html +176 -0
- package/components/card.html +143 -0
- package/components/carousel.html +208 -0
- package/components/chart.html +168 -0
- package/components/checkbox.html +99 -0
- package/components/code-block.html +64 -0
- package/components/collapsible.html +90 -0
- package/components/combobox.html +237 -0
- package/components/command.html +201 -0
- package/components/context-menu.html +81 -0
- package/components/dialog.html +327 -0
- package/components/direction.html +23 -0
- package/components/drawer.html +267 -0
- package/components/dropdown-menu.html +421 -0
- package/components/empty.html +132 -0
- package/components/field.html +143 -0
- package/components/hover-card.html +111 -0
- package/components/input-group.html +91 -0
- package/components/input-otp.html +151 -0
- package/components/input.html +94 -0
- package/components/item.html +208 -0
- package/components/kbd.html +45 -0
- package/components/label.html +46 -0
- package/components/marker.html +41 -0
- package/components/menubar.html +137 -0
- package/components/message-scroller.html +68 -0
- package/components/message.html +95 -0
- package/components/navigation-menu.html +233 -0
- package/components/pagination.html +160 -0
- package/components/popover.html +164 -0
- package/components/progress.html +45 -0
- package/components/radio-group.html +120 -0
- package/components/resizable.html +170 -0
- package/components/scroll-area.html +56 -0
- package/components/select.html +103 -0
- package/components/separator.html +31 -0
- package/components/sheet.html +173 -0
- package/components/sidebar.html +393 -0
- package/components/skeleton.html +36 -0
- package/components/slider.html +108 -0
- package/components/spinner.html +53 -0
- package/components/switch.html +95 -0
- package/components/table.html +81 -0
- package/components/tabs.html +182 -0
- package/components/textarea.html +80 -0
- package/components/toaster.html +134 -0
- package/components/toggle-group.html +71 -0
- package/components/toggle.html +87 -0
- package/components/tooltip.html +114 -0
- package/dist/theme.css +158 -0
- package/dist/ui.html +7923 -0
- package/package.json +40 -3
- package/theme.css +158 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
<custom-element name="ui-dialog">
|
|
2
|
+
<script>
|
|
3
|
+
const props = defineProps({
|
|
4
|
+
open: prop.boolean().reflect(),
|
|
5
|
+
side: prop.enum(["center", "top", "right", "bottom", "left"]).default("center"),
|
|
6
|
+
hideClose: prop.boolean().attribute("hide-close"),
|
|
7
|
+
persistent: prop.boolean(),
|
|
8
|
+
label: prop.string(),
|
|
9
|
+
description: prop.string(),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const parts = useChildren("ui-dialog-header, ui-dialog-title, ui-dialog-description");
|
|
13
|
+
const dialog = useState({
|
|
14
|
+
get label() {
|
|
15
|
+
return props.label ?? text("ui-dialog-title");
|
|
16
|
+
},
|
|
17
|
+
get description() {
|
|
18
|
+
return props.description ?? text("ui-dialog-description");
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
function text(selector) {
|
|
23
|
+
const found = parts.flatMap((part) => (part.matches(selector) ? [part] : [...part.querySelectorAll(selector)]));
|
|
24
|
+
return found.map((part) => part.textContent.trim()).join(" ") || null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function show() {
|
|
28
|
+
props.open = true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function showFrom(trigger) {
|
|
32
|
+
trigger.firstElementChild?.focus();
|
|
33
|
+
show();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function close(value) {
|
|
37
|
+
if (value !== undefined) refs.dialog.returnValue = String(value);
|
|
38
|
+
props.open = false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function requestClose(value) {
|
|
42
|
+
if (dispatch("cancel", undefined, { bubbles: false, composed: false })) close(value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function closed() {
|
|
46
|
+
if (refs.dialog.open) return;
|
|
47
|
+
props.open = false;
|
|
48
|
+
dispatch("close", undefined, { bubbles: false, composed: false });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function cancelled(event) {
|
|
52
|
+
event.preventDefault();
|
|
53
|
+
if (!props.persistent) requestClose();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function backdrop(event) {
|
|
57
|
+
if (event.target === refs.dialog && !props.persistent) requestClose();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const COMMANDS = { "show-modal": show, close: () => close(), "request-close": () => requestClose() };
|
|
61
|
+
|
|
62
|
+
defineMethods({
|
|
63
|
+
showModal: show,
|
|
64
|
+
show,
|
|
65
|
+
close,
|
|
66
|
+
requestClose,
|
|
67
|
+
get returnValue() {
|
|
68
|
+
return refs.dialog.returnValue;
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
listen("command", (event) => COMMANDS[event.command.replace(/^--/, "")]?.());
|
|
73
|
+
|
|
74
|
+
observe(() => {
|
|
75
|
+
const dialog = refs.dialog;
|
|
76
|
+
if (!dialog || props.open === dialog.open) return;
|
|
77
|
+
if (!props.open) return dialog.close();
|
|
78
|
+
dialog.showModal();
|
|
79
|
+
// Left to the browser, focus lands on the first button (often close) with a focus ring after a
|
|
80
|
+
// tap, and Firefox and WebKit ignore autofocus on slotted content.
|
|
81
|
+
(host.querySelector("[autofocus]") ?? dialog).focus();
|
|
82
|
+
});
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<style>
|
|
86
|
+
/* A double tap must not zoom the page. Set inside the shadow root, because a page-level
|
|
87
|
+
touch-action does not reach shadow content in iOS Safari. */
|
|
88
|
+
:host,
|
|
89
|
+
* {
|
|
90
|
+
touch-action: manipulation;
|
|
91
|
+
}
|
|
92
|
+
:host {
|
|
93
|
+
display: contents;
|
|
94
|
+
}
|
|
95
|
+
dialog {
|
|
96
|
+
box-sizing: border-box;
|
|
97
|
+
width: 100%;
|
|
98
|
+
max-width: min(calc(var(--spacing) * 128), calc(100% - calc(var(--spacing) * 8)));
|
|
99
|
+
/* Without an explicit height, a dialog pinned to both edges of the screen can stretch to
|
|
100
|
+
full height. svh stays the same while a phone's address bar shows and hides. */
|
|
101
|
+
height: fit-content;
|
|
102
|
+
max-height: calc(100svh - calc(var(--spacing) * 8));
|
|
103
|
+
padding: calc(var(--spacing) * 6);
|
|
104
|
+
border: 1px solid var(--border);
|
|
105
|
+
border-radius: var(--radius-lg);
|
|
106
|
+
background: var(--background);
|
|
107
|
+
color: var(--foreground);
|
|
108
|
+
box-shadow: var(--shadow-lg);
|
|
109
|
+
overflow: auto;
|
|
110
|
+
}
|
|
111
|
+
dialog:focus {
|
|
112
|
+
outline: none;
|
|
113
|
+
}
|
|
114
|
+
dialog[open] {
|
|
115
|
+
display: grid;
|
|
116
|
+
/* Safari stretches a grid overlay to its max-height unless the rows are packed at the start. */
|
|
117
|
+
align-content: start;
|
|
118
|
+
gap: calc(var(--spacing) * 4);
|
|
119
|
+
animation: zoom 0.2s ease-out;
|
|
120
|
+
}
|
|
121
|
+
dialog::backdrop {
|
|
122
|
+
background: rgb(0 0 0 / 0.5);
|
|
123
|
+
animation: fade 0.2s ease-out;
|
|
124
|
+
}
|
|
125
|
+
:host(:state(attr-side):not(:state(attr-side-is-center))) dialog {
|
|
126
|
+
margin: 0;
|
|
127
|
+
max-width: none;
|
|
128
|
+
max-height: none;
|
|
129
|
+
border-radius: 0;
|
|
130
|
+
animation: none;
|
|
131
|
+
}
|
|
132
|
+
:host(:is(:state(attr-side-is-left), :state(attr-side-is-right))) dialog[open] {
|
|
133
|
+
display: flex;
|
|
134
|
+
flex-direction: column;
|
|
135
|
+
}
|
|
136
|
+
[part="body"] {
|
|
137
|
+
display: contents;
|
|
138
|
+
}
|
|
139
|
+
:host(:is(:state(attr-side-is-left), :state(attr-side-is-right))) [part="body"] {
|
|
140
|
+
display: block;
|
|
141
|
+
flex: 1;
|
|
142
|
+
}
|
|
143
|
+
:host(:state(attr-side-is-right)) dialog {
|
|
144
|
+
inset: 0 0 0 auto;
|
|
145
|
+
width: min(calc(var(--spacing) * 96), 75%);
|
|
146
|
+
height: 100%;
|
|
147
|
+
}
|
|
148
|
+
:host(:state(attr-side-is-left)) dialog {
|
|
149
|
+
inset: 0 auto 0 0;
|
|
150
|
+
width: min(calc(var(--spacing) * 96), 75%);
|
|
151
|
+
height: 100%;
|
|
152
|
+
}
|
|
153
|
+
:host(:state(attr-side-is-top)) dialog {
|
|
154
|
+
inset: 0 0 auto 0;
|
|
155
|
+
width: 100%;
|
|
156
|
+
height: auto;
|
|
157
|
+
}
|
|
158
|
+
:host(:state(attr-side-is-bottom)) dialog {
|
|
159
|
+
inset: auto 0 0 0;
|
|
160
|
+
width: 100%;
|
|
161
|
+
height: auto;
|
|
162
|
+
}
|
|
163
|
+
.close {
|
|
164
|
+
all: unset;
|
|
165
|
+
position: absolute;
|
|
166
|
+
top: calc(var(--spacing) * 4);
|
|
167
|
+
right: calc(var(--spacing) * 4);
|
|
168
|
+
display: grid;
|
|
169
|
+
place-content: center;
|
|
170
|
+
width: calc(var(--spacing) * 6);
|
|
171
|
+
height: calc(var(--spacing) * 6);
|
|
172
|
+
border-radius: var(--radius-xs);
|
|
173
|
+
opacity: 0.7;
|
|
174
|
+
cursor: pointer;
|
|
175
|
+
}
|
|
176
|
+
.close:hover {
|
|
177
|
+
opacity: 1;
|
|
178
|
+
}
|
|
179
|
+
.close:focus-visible {
|
|
180
|
+
box-shadow: 0 0 0 2px var(--ring);
|
|
181
|
+
}
|
|
182
|
+
.close svg {
|
|
183
|
+
width: calc(var(--spacing) * 4);
|
|
184
|
+
height: calc(var(--spacing) * 4);
|
|
185
|
+
}
|
|
186
|
+
@keyframes zoom {
|
|
187
|
+
from {
|
|
188
|
+
opacity: 0;
|
|
189
|
+
scale: 0.95;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
@keyframes fade {
|
|
193
|
+
from {
|
|
194
|
+
opacity: 0;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
@media (prefers-reduced-motion: reduce) {
|
|
198
|
+
dialog[open],
|
|
199
|
+
dialog::backdrop {
|
|
200
|
+
animation: none;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
</style>
|
|
204
|
+
|
|
205
|
+
<slot name="trigger"></slot>
|
|
206
|
+
<dialog
|
|
207
|
+
ref="dialog"
|
|
208
|
+
part="dialog"
|
|
209
|
+
tabindex="-1"
|
|
210
|
+
_aria-label="#dialog.label"
|
|
211
|
+
_aria-description="#dialog.description"
|
|
212
|
+
onclose="closed()"
|
|
213
|
+
oncancel="cancelled(event)"
|
|
214
|
+
onclick="backdrop(event)">
|
|
215
|
+
<slot name="header"></slot>
|
|
216
|
+
<div part="body"><slot></slot></div>
|
|
217
|
+
<slot name="footer"></slot>
|
|
218
|
+
<button type="button" class="close" part="close" aria-label="Close" _hidden="@hideClose" onclick="requestClose()">
|
|
219
|
+
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12" /></svg>
|
|
220
|
+
</button>
|
|
221
|
+
</dialog>
|
|
222
|
+
</custom-element>
|
|
223
|
+
|
|
224
|
+
<custom-element name="ui-dialog-trigger" slot="trigger">
|
|
225
|
+
<style>
|
|
226
|
+
:host,
|
|
227
|
+
* {
|
|
228
|
+
touch-action: manipulation;
|
|
229
|
+
}
|
|
230
|
+
:host,
|
|
231
|
+
span {
|
|
232
|
+
display: contents;
|
|
233
|
+
}
|
|
234
|
+
</style>
|
|
235
|
+
|
|
236
|
+
<span part="trigger" onclick="showFrom(host)"><slot></slot></span>
|
|
237
|
+
</custom-element>
|
|
238
|
+
|
|
239
|
+
<custom-element name="ui-dialog-close">
|
|
240
|
+
<script>
|
|
241
|
+
const props = defineProps({ value: prop.string() });
|
|
242
|
+
</script>
|
|
243
|
+
|
|
244
|
+
<style>
|
|
245
|
+
:host,
|
|
246
|
+
* {
|
|
247
|
+
touch-action: manipulation;
|
|
248
|
+
}
|
|
249
|
+
:host,
|
|
250
|
+
span {
|
|
251
|
+
display: contents;
|
|
252
|
+
}
|
|
253
|
+
</style>
|
|
254
|
+
|
|
255
|
+
<span part="close" onclick="close(props.value)"><slot></slot></span>
|
|
256
|
+
</custom-element>
|
|
257
|
+
|
|
258
|
+
<custom-element name="ui-dialog-header" slot="header">
|
|
259
|
+
<style>
|
|
260
|
+
:host,
|
|
261
|
+
* {
|
|
262
|
+
touch-action: manipulation;
|
|
263
|
+
}
|
|
264
|
+
:host {
|
|
265
|
+
display: grid;
|
|
266
|
+
gap: calc(var(--spacing) * 2);
|
|
267
|
+
}
|
|
268
|
+
</style>
|
|
269
|
+
|
|
270
|
+
<slot></slot>
|
|
271
|
+
</custom-element>
|
|
272
|
+
|
|
273
|
+
<custom-element name="ui-dialog-title">
|
|
274
|
+
<style>
|
|
275
|
+
:host,
|
|
276
|
+
* {
|
|
277
|
+
touch-action: manipulation;
|
|
278
|
+
}
|
|
279
|
+
:host {
|
|
280
|
+
display: block;
|
|
281
|
+
}
|
|
282
|
+
h2 {
|
|
283
|
+
margin: 0;
|
|
284
|
+
font-size: var(--text-lg);
|
|
285
|
+
font-weight: var(--font-weight-semibold);
|
|
286
|
+
line-height: 1;
|
|
287
|
+
}
|
|
288
|
+
</style>
|
|
289
|
+
|
|
290
|
+
<h2 part="title"><slot></slot></h2>
|
|
291
|
+
</custom-element>
|
|
292
|
+
|
|
293
|
+
<custom-element name="ui-dialog-description">
|
|
294
|
+
<style>
|
|
295
|
+
:host,
|
|
296
|
+
* {
|
|
297
|
+
touch-action: manipulation;
|
|
298
|
+
}
|
|
299
|
+
:host {
|
|
300
|
+
display: block;
|
|
301
|
+
}
|
|
302
|
+
p {
|
|
303
|
+
margin: 0;
|
|
304
|
+
font-size: var(--text-sm);
|
|
305
|
+
color: var(--muted-foreground);
|
|
306
|
+
}
|
|
307
|
+
</style>
|
|
308
|
+
|
|
309
|
+
<p part="description"><slot></slot></p>
|
|
310
|
+
</custom-element>
|
|
311
|
+
|
|
312
|
+
<custom-element name="ui-dialog-footer" slot="footer">
|
|
313
|
+
<style>
|
|
314
|
+
:host,
|
|
315
|
+
* {
|
|
316
|
+
touch-action: manipulation;
|
|
317
|
+
}
|
|
318
|
+
:host {
|
|
319
|
+
display: flex;
|
|
320
|
+
flex-wrap: wrap;
|
|
321
|
+
justify-content: flex-end;
|
|
322
|
+
gap: calc(var(--spacing) * 2);
|
|
323
|
+
}
|
|
324
|
+
</style>
|
|
325
|
+
|
|
326
|
+
<slot></slot>
|
|
327
|
+
</custom-element>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<custom-element name="ui-direction">
|
|
2
|
+
<script>
|
|
3
|
+
const props = defineProps({ dir: prop.enum(["ltr", "rtl"]).default("ltr") });
|
|
4
|
+
|
|
5
|
+
observe(() => {
|
|
6
|
+
attrs.dir = props.dir;
|
|
7
|
+
});
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<style>
|
|
11
|
+
/* A double tap must not zoom the page. Set inside the shadow root, because a page-level
|
|
12
|
+
touch-action does not reach shadow content in iOS Safari. */
|
|
13
|
+
:host,
|
|
14
|
+
* {
|
|
15
|
+
touch-action: manipulation;
|
|
16
|
+
}
|
|
17
|
+
:host {
|
|
18
|
+
display: contents;
|
|
19
|
+
}
|
|
20
|
+
</style>
|
|
21
|
+
|
|
22
|
+
<slot></slot>
|
|
23
|
+
</custom-element>
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
<custom-element name="ui-drawer">
|
|
2
|
+
<script>
|
|
3
|
+
const props = defineProps({
|
|
4
|
+
open: prop.boolean().reflect(),
|
|
5
|
+
side: prop.enum(["bottom", "top", "left", "right"]).default("bottom"),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const parts = useChildren("ui-drawer-header, ui-drawer-title, ui-drawer-description");
|
|
9
|
+
const drawer = useState({
|
|
10
|
+
get label() {
|
|
11
|
+
return text("ui-drawer-title");
|
|
12
|
+
},
|
|
13
|
+
get description() {
|
|
14
|
+
return text("ui-drawer-description");
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
let start = null;
|
|
19
|
+
|
|
20
|
+
function text(selector) {
|
|
21
|
+
const found = parts.flatMap((part) => (part.matches(selector) ? [part] : [...part.querySelectorAll(selector)]));
|
|
22
|
+
return found.map((part) => part.textContent.trim()).join(" ") || null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function show() {
|
|
26
|
+
props.open = true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function showFrom(trigger) {
|
|
30
|
+
trigger.firstElementChild?.focus();
|
|
31
|
+
show();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function close() {
|
|
35
|
+
props.open = false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function closed() {
|
|
39
|
+
if (refs.dialog.open) return;
|
|
40
|
+
props.open = false;
|
|
41
|
+
dispatch("close", undefined, { bubbles: false, composed: false });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function backdrop(event) {
|
|
45
|
+
if (event.target === refs.dialog) close();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function grab(event) {
|
|
49
|
+
start = event.clientY;
|
|
50
|
+
if (event.isTrusted) event.target.setPointerCapture(event.pointerId);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function release(event) {
|
|
54
|
+
if (start !== null && event.clientY - start > 80) close();
|
|
55
|
+
start = null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
defineMethods({ showModal: show, show, close });
|
|
59
|
+
|
|
60
|
+
const COMMANDS = { "show-modal": show, close, "request-close": close };
|
|
61
|
+
|
|
62
|
+
listen("command", (event) => COMMANDS[event.command.replace(/^--/, "")]?.());
|
|
63
|
+
|
|
64
|
+
observe(() => {
|
|
65
|
+
const dialog = refs.dialog;
|
|
66
|
+
if (!dialog || props.open === dialog.open) return;
|
|
67
|
+
if (!props.open) return dialog.close();
|
|
68
|
+
dialog.showModal();
|
|
69
|
+
// Left to the browser, focus lands on the first button (often close) with a focus ring after a
|
|
70
|
+
// tap, and Firefox and WebKit ignore autofocus on slotted content.
|
|
71
|
+
(host.querySelector("[autofocus]") ?? dialog).focus();
|
|
72
|
+
});
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<style>
|
|
76
|
+
/* A double tap must not zoom the page. Set inside the shadow root, because a page-level
|
|
77
|
+
touch-action does not reach shadow content in iOS Safari. */
|
|
78
|
+
:host,
|
|
79
|
+
* {
|
|
80
|
+
touch-action: manipulation;
|
|
81
|
+
}
|
|
82
|
+
:host {
|
|
83
|
+
display: contents;
|
|
84
|
+
}
|
|
85
|
+
dialog:focus {
|
|
86
|
+
outline: none;
|
|
87
|
+
}
|
|
88
|
+
dialog {
|
|
89
|
+
box-sizing: border-box;
|
|
90
|
+
width: 100%;
|
|
91
|
+
max-width: none;
|
|
92
|
+
height: fit-content;
|
|
93
|
+
max-height: 80svh;
|
|
94
|
+
margin: auto 0 0;
|
|
95
|
+
padding: calc(var(--spacing) * 4) calc(var(--spacing) * 6) calc(var(--spacing) * 6);
|
|
96
|
+
border: 1px solid var(--border);
|
|
97
|
+
border-radius: var(--radius-2xl) var(--radius-2xl) 0 0;
|
|
98
|
+
background: var(--background);
|
|
99
|
+
color: var(--foreground);
|
|
100
|
+
}
|
|
101
|
+
:host(:state(attr-side-is-top)) dialog {
|
|
102
|
+
margin: 0 0 auto;
|
|
103
|
+
border-radius: 0 0 var(--radius-2xl) var(--radius-2xl);
|
|
104
|
+
}
|
|
105
|
+
:host(:state(attr-side-is-left)) dialog,
|
|
106
|
+
:host(:state(attr-side-is-right)) dialog {
|
|
107
|
+
width: min(calc(var(--spacing) * 96), 80%);
|
|
108
|
+
height: 100dvh;
|
|
109
|
+
max-height: none;
|
|
110
|
+
margin: 0 auto 0 0;
|
|
111
|
+
border-radius: 0 var(--radius-2xl) var(--radius-2xl) 0;
|
|
112
|
+
}
|
|
113
|
+
:host(:state(attr-side-is-right)) dialog {
|
|
114
|
+
margin: 0 0 0 auto;
|
|
115
|
+
border-radius: var(--radius-2xl) 0 0 var(--radius-2xl);
|
|
116
|
+
}
|
|
117
|
+
dialog[open] {
|
|
118
|
+
display: grid;
|
|
119
|
+
/* Safari stretches a grid overlay to its max-height unless the rows are packed at the start. */
|
|
120
|
+
align-content: start;
|
|
121
|
+
gap: calc(var(--spacing) * 4);
|
|
122
|
+
animation: slide 0.25s ease-out;
|
|
123
|
+
}
|
|
124
|
+
[part="body"] {
|
|
125
|
+
display: contents;
|
|
126
|
+
}
|
|
127
|
+
dialog::backdrop {
|
|
128
|
+
background: rgb(0 0 0 / 0.5);
|
|
129
|
+
}
|
|
130
|
+
.handle {
|
|
131
|
+
justify-self: center;
|
|
132
|
+
width: calc(var(--spacing) * 24);
|
|
133
|
+
height: calc(var(--spacing) * 2);
|
|
134
|
+
border-radius: var(--radius-full);
|
|
135
|
+
background: var(--muted);
|
|
136
|
+
cursor: grab;
|
|
137
|
+
touch-action: none;
|
|
138
|
+
}
|
|
139
|
+
:host(:not(:state(attr-side-is-bottom))) .handle {
|
|
140
|
+
display: none;
|
|
141
|
+
}
|
|
142
|
+
@keyframes slide {
|
|
143
|
+
from {
|
|
144
|
+
translate: 0 calc(var(--spacing) * 8);
|
|
145
|
+
opacity: 0;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
@media (prefers-reduced-motion: reduce) {
|
|
149
|
+
dialog[open] {
|
|
150
|
+
animation: none;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
</style>
|
|
154
|
+
|
|
155
|
+
<slot name="trigger"></slot>
|
|
156
|
+
<dialog
|
|
157
|
+
ref="dialog"
|
|
158
|
+
part="dialog"
|
|
159
|
+
tabindex="-1"
|
|
160
|
+
_aria-label="#drawer.label"
|
|
161
|
+
_aria-description="#drawer.description"
|
|
162
|
+
onclose="closed()"
|
|
163
|
+
onclick="backdrop(event)">
|
|
164
|
+
<div class="handle" part="handle" onpointerdown="grab(event)" onpointerup="release(event)"></div>
|
|
165
|
+
<slot name="header"></slot>
|
|
166
|
+
<div part="body"><slot></slot></div>
|
|
167
|
+
<slot name="footer"></slot>
|
|
168
|
+
</dialog>
|
|
169
|
+
</custom-element>
|
|
170
|
+
|
|
171
|
+
<custom-element name="ui-drawer-trigger" slot="trigger">
|
|
172
|
+
<style>
|
|
173
|
+
:host,
|
|
174
|
+
* {
|
|
175
|
+
touch-action: manipulation;
|
|
176
|
+
}
|
|
177
|
+
:host,
|
|
178
|
+
span {
|
|
179
|
+
display: contents;
|
|
180
|
+
}
|
|
181
|
+
</style>
|
|
182
|
+
|
|
183
|
+
<span part="trigger" onclick="showFrom(host)"><slot></slot></span>
|
|
184
|
+
</custom-element>
|
|
185
|
+
|
|
186
|
+
<custom-element name="ui-drawer-close">
|
|
187
|
+
<style>
|
|
188
|
+
:host,
|
|
189
|
+
* {
|
|
190
|
+
touch-action: manipulation;
|
|
191
|
+
}
|
|
192
|
+
:host,
|
|
193
|
+
span {
|
|
194
|
+
display: contents;
|
|
195
|
+
}
|
|
196
|
+
</style>
|
|
197
|
+
|
|
198
|
+
<span part="close" onclick="close()"><slot></slot></span>
|
|
199
|
+
</custom-element>
|
|
200
|
+
|
|
201
|
+
<custom-element name="ui-drawer-header" slot="header">
|
|
202
|
+
<style>
|
|
203
|
+
:host,
|
|
204
|
+
* {
|
|
205
|
+
touch-action: manipulation;
|
|
206
|
+
}
|
|
207
|
+
:host {
|
|
208
|
+
display: grid;
|
|
209
|
+
gap: calc(var(--spacing) * 1.5);
|
|
210
|
+
}
|
|
211
|
+
</style>
|
|
212
|
+
|
|
213
|
+
<slot></slot>
|
|
214
|
+
</custom-element>
|
|
215
|
+
|
|
216
|
+
<custom-element name="ui-drawer-title">
|
|
217
|
+
<style>
|
|
218
|
+
:host,
|
|
219
|
+
* {
|
|
220
|
+
touch-action: manipulation;
|
|
221
|
+
}
|
|
222
|
+
:host {
|
|
223
|
+
display: block;
|
|
224
|
+
}
|
|
225
|
+
h2 {
|
|
226
|
+
margin: 0;
|
|
227
|
+
font-size: var(--text-base);
|
|
228
|
+
font-weight: var(--font-weight-semibold);
|
|
229
|
+
}
|
|
230
|
+
</style>
|
|
231
|
+
|
|
232
|
+
<h2 part="title"><slot></slot></h2>
|
|
233
|
+
</custom-element>
|
|
234
|
+
|
|
235
|
+
<custom-element name="ui-drawer-description">
|
|
236
|
+
<style>
|
|
237
|
+
:host,
|
|
238
|
+
* {
|
|
239
|
+
touch-action: manipulation;
|
|
240
|
+
}
|
|
241
|
+
:host {
|
|
242
|
+
display: block;
|
|
243
|
+
}
|
|
244
|
+
p {
|
|
245
|
+
margin: 0;
|
|
246
|
+
font-size: var(--text-sm);
|
|
247
|
+
color: var(--muted-foreground);
|
|
248
|
+
}
|
|
249
|
+
</style>
|
|
250
|
+
|
|
251
|
+
<p part="description"><slot></slot></p>
|
|
252
|
+
</custom-element>
|
|
253
|
+
|
|
254
|
+
<custom-element name="ui-drawer-footer" slot="footer">
|
|
255
|
+
<style>
|
|
256
|
+
:host,
|
|
257
|
+
* {
|
|
258
|
+
touch-action: manipulation;
|
|
259
|
+
}
|
|
260
|
+
:host {
|
|
261
|
+
display: grid;
|
|
262
|
+
gap: calc(var(--spacing) * 2);
|
|
263
|
+
}
|
|
264
|
+
</style>
|
|
265
|
+
|
|
266
|
+
<slot></slot>
|
|
267
|
+
</custom-element>
|