Skip to content

Commit 797b343

Browse files
authored
Fix keyboard navigation for the dialog and toast components (#47)
* implement focus trap * fix escape modal * integrate focus trap into dialog * handle keyboard navigation for the toast component * fix clippy
1 parent bd67af5 commit 797b343

File tree

18 files changed

+490
-157
lines changed

18 files changed

+490
-157
lines changed

Cargo.lock

Lines changed: 12 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

preview/src/components/alert_dialog/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ pub(super) fn Demo() -> Element {
1717
onclick: move |_| open.set(true),
1818
"Show Alert Dialog"
1919
}
20-
AlertDialogRoot { open: open(), on_open_change: move |v| open.set(v),
20+
AlertDialogRoot {
21+
open: open(),
22+
on_open_change: move |v| open.set(v),
23+
class: "alert-dialog-backdrop",
2124
AlertDialogContent { class: "alert-dialog",
2225
AlertDialogTitle { "Delete item" }
2326
AlertDialogDescription { "Are you sure you want to delete this item? This action cannot be undone." }

preview/src/components/alert_dialog/style.css

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,21 @@
44
inset: 0;
55
background: rgba(0, 0, 0, 0.3);
66
z-index: 1000;
7-
animation: fadeIn 0.2s ease;
7+
will-change: transform, opacity;
8+
}
9+
10+
.alert-dialog-backdrop[data-state="closed"] {
11+
pointer-events: none;
12+
opacity: 0;
13+
transform: scale(0.95) translateY(-2px);
14+
transition: opacity 150ms ease-in, transform 150ms ease-in;
15+
}
16+
17+
.alert-dialog-backdrop[data-state="open"] {
18+
opacity: 1;
19+
transform: scale(1) translateY(0);
20+
transition: opacity 200ms ease-out,
21+
transform 200ms cubic-bezier(0.16, 1, 0.3, 1);
822
}
923

1024
/* Alert Dialog Container - improved for theme consistency */

preview/src/components/dialog/mod.rs

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use dioxus::prelude::*;
2-
use dioxus_primitives::{dialog::{Dialog, DialogDescription, DialogTitle}};
2+
use dioxus_primitives::dialog::{DialogContent, DialogDescription, DialogRoot, DialogTitle};
33

44
#[component]
55
pub(super) fn Demo() -> Element {
@@ -16,23 +16,27 @@ pub(super) fn Demo() -> Element {
1616
onclick: move |_| open.set(true),
1717
"Show Dialog"
1818
}
19-
Dialog {
20-
class: "dialog",
19+
DialogRoot {
20+
class: "dialog-backdrop",
2121
open: open(),
2222
on_open_change: move |v| open.set(v),
23-
button {
24-
class: "dialog-close",
25-
aria_label: "Close",
26-
onclick: move |_| open.set(false),
27-
"×"
28-
}
29-
DialogTitle {
30-
class: "dialog-title",
31-
"Item information"
32-
}
33-
DialogDescription {
34-
class: "dialog-description",
35-
"Here is some additional information about the item."
23+
DialogContent {
24+
class: "dialog",
25+
button {
26+
class: "dialog-close",
27+
aria_label: "Close",
28+
tabindex: if open() { "0" } else { "-1" },
29+
onclick: move |_| open.set(false),
30+
"×"
31+
}
32+
DialogTitle {
33+
class: "dialog-title",
34+
"Item information"
35+
}
36+
DialogDescription {
37+
class: "dialog-description",
38+
"Here is some additional information about the item."
39+
}
3640
}
3741
}
3842
}

preview/src/components/dialog/style.css

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,30 @@
1-
/* Alert Dialog Container - improved for theme consistency */
1+
/* Dialog Backdrop */
2+
.dialog-backdrop {
3+
position: fixed;
4+
inset: 0;
5+
background: rgba(0, 0, 0, 0.3);
6+
z-index: 1000;
7+
will-change: transform, opacity;
8+
}
9+
10+
.dialog-backdrop[data-state="closed"] {
11+
pointer-events: none;
12+
opacity: 0;
13+
transform: scale(0.95) translateY(-2px);
14+
transition: opacity 150ms ease-in, transform 150ms ease-in;
15+
}
16+
17+
.dialog-backdrop[data-state="open"] {
18+
opacity: 1;
19+
transform: scale(1) translateY(0);
20+
transition: opacity 200ms ease-out,
21+
transform 200ms cubic-bezier(0.16, 1, 0.3, 1);
22+
}
23+
24+
/* Dialog Container - improved for theme consistency */
225
.dialog {
26+
display: flex;
27+
flex-direction: column;
328
text-align: center;
429
position: fixed;
530
top: 50%;
@@ -22,12 +47,6 @@
2247
box-sizing: border-box;
2348
}
2449

25-
26-
.dialog[open] {
27-
display: flex;
28-
flex-direction: column;
29-
}
30-
3150
.dialog-title {
3251
font-size: 1.25rem;
3352
font-weight: 700;

preview/src/components/toast/docs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
The Toast component is used to display brief messages to the user, typically for notifications or alerts.
1+
The Toast component is used to display brief messages to the user, typically for notifications or alerts. The toast messages can be focused with the keyboard with the `f6` key.
22

33
## Component Structure
44

preview/src/components/toast/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ fn ToastButton() -> Element {
3333
description: Some(
3434
"Some info you need".to_string(),
3535
),
36-
duration: Some(Duration::from_secs(10)),
36+
duration: Some(Duration::from_secs(60)),
3737
permanent: false,
3838
}),
3939
);
4040
},
41-
"Info (10s)"
41+
"Info (60s)"
4242
}
4343
style {
4444
""

preview/src/components/toast/style.css

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515
margin-top: -4rem;
1616
overflow: hidden;
1717
transition: transform 0.2s ease, margin-top 0.2s ease, opacity 0.2s ease;
18-
transform: scale(calc(100% - var(--toast-index) * 5%), calc(100% - var(--toast-index) * 2%));
18+
transform: scale(
19+
calc(100% - var(--toast-index) * 5%),
20+
calc(100% - var(--toast-index) * 2%)
21+
);
1922
box-sizing: border-box;
2023
z-index: calc(var(--toast-count) - var(--toast-index));
2124
opacity: calc(1 - var(--toast-hidden));
@@ -37,53 +40,79 @@
3740
}
3841
}
3942

40-
.toast-container:not(:hover) .toast[data-toast-even]:not([data-top]) {
43+
.toast-container:not(:hover):not(:focus-within)
44+
.toast[data-toast-even]:not([data-top]) {
4145
animation: slide-up-even 0.2s ease-out;
4246
}
43-
.toast-container:not(:hover) .toast[data-toast-odd]:not([data-top]) {
47+
.toast-container:not(:hover):not(:focus-within)
48+
.toast[data-toast-odd]:not([data-top]) {
4449
animation: slide-up-odd 0.2s ease-out;
4550
}
4651

4752
@keyframes slide-up-even {
4853
from {
49-
transform: translateY(0.5rem) scale(calc(100% - var(--toast-index) * 5%), calc(100% - var(--toast-index) * 2%));
54+
transform: translateY(0.5rem)
55+
scale(
56+
calc(100% - var(--toast-index) * 5%),
57+
calc(100% - var(--toast-index) * 2%)
58+
);
5059
}
5160
to {
5261
transform: translateY(0)
53-
scale(calc(100% - var(--toast-index) * 5%), calc(100% - var(--toast-index) * 2%));
62+
scale(
63+
calc(100% - var(--toast-index) * 5%),
64+
calc(100% - var(--toast-index) * 2%)
65+
);
5466
}
5567
}
5668

5769
@keyframes slide-up-odd {
5870
from {
59-
transform: translateY(0.5rem) scale(calc(100% - var(--toast-index) * 5%), calc(100% - var(--toast-index) * 2%));
71+
transform: translateY(0.5rem)
72+
scale(
73+
calc(100% - var(--toast-index) * 5%),
74+
calc(100% - var(--toast-index) * 2%)
75+
);
6076
}
6177
to {
6278
transform: translateY(0)
63-
scale(calc(100% - var(--toast-index) * 5%), calc(100% - var(--toast-index) * 2%));
79+
scale(
80+
calc(100% - var(--toast-index) * 5%),
81+
calc(100% - var(--toast-index) * 2%)
82+
);
6483
}
6584
}
6685

6786
.toast[data-top] {
6887
animation: slide-in 0.2s ease-out;
6988
}
7089

71-
.toast-container:hover .toast[data-top] {
90+
.toast-container:hover .toast[data-top],
91+
.toast-container:focus-within .toast[data-top] {
7292
animation: slide-in 0 ease-out;
7393
}
7494

7595
@keyframes slide-in {
7696
from {
77-
transform: translateY(100%) scale(calc(110% - var(--toast-index) * 5%), calc(110% - var(--toast-index) * 2%));
97+
transform: translateY(100%)
98+
scale(
99+
calc(110% - var(--toast-index) * 5%),
100+
calc(110% - var(--toast-index) * 2%)
101+
);
78102
opacity: 0;
79103
}
80104
to {
81-
transform: translateY(0) scale(calc(100% - var(--toast-index) * 5%), calc(100% - var(--toast-index) * 2%));
105+
transform: translateY(0)
106+
scale(
107+
calc(100% - var(--toast-index) * 5%),
108+
calc(100% - var(--toast-index) * 2%)
109+
);
82110
opacity: 1;
83111
}
84112
}
85113

86-
.toast-container:hover .toast {
114+
.toast-container:hover .toast,
115+
.toast-container:focus-within .toast {
87116
margin-top: var(--toast-padding);
88117
transform: scale(calc(100%));
89118
opacity: 1;

primitives/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ dioxus-lib.workspace = true
1515
dioxus.workspace = true
1616
dioxus-time = "=0.1.0-alpha.1"
1717
tracing.workspace = true
18+
19+
[build-dependencies]
20+
lazy-js-bundle = "0.6.2"

primitives/build.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
fn main() {
2+
// If any TS files change, re-run the build script
3+
lazy_js_bundle::LazyTypeScriptBindings::new()
4+
.with_watching("./src/ts")
5+
.with_binding("./src/ts/focus-trap.ts", "./src/js/focus-trap.js")
6+
.run();
7+
}

0 commit comments

Comments
 (0)