Dialogs, a11y · Dialog card names its role, title and detail; prompt error reads as a label; Cancel and Close read i18n; a disabled button says so in its description

This commit is contained in:
ZacharyZhang-NY committed 2026-10-02 18:52:08 -04:00
1 parent 290939f82f
commit cf9770b57e
6 files changed
+39 -11

No files matched your search

+5 -1
View File
@@ -5,6 +5,7 @@ use gpui::{
};
use crate::{
i18n,
motion::Spinner,
primitives::{FocusRing, Icon, IconName},
theme::{ActiveTheme, ControlSize, IconSize, Mix, Palette, Platform, Radius, TextSize},
@@ -291,7 +292,10 @@ impl RenderOnce for Button {
})
.map(|el| {
if self.disabled {
return el.opacity(0.45).cursor_not_allowed();
return el
.aria_description(i18n::text(cx, "state.unavailable", &[]))
.opacity(0.45)
.cursor_not_allowed();
}
let el = match &self.focus {
Some(handle) => el.track_focus(handle),
+5 -1
View File
@@ -5,6 +5,7 @@ use gpui::{
use super::button::{ButtonVariant, ClickHandler, label_size, tone};
use crate::{
i18n,
primitives::{FocusRing, Icon, IconName, Tooltip},
theme::{ActiveTheme, ControlSize, Radius},
};
@@ -92,7 +93,10 @@ impl RenderOnce for IconButton {
.when_some(self.tooltip, |el, text| el.tooltip(Tooltip::text(text)))
.map(|el| {
if self.disabled {
return el.opacity(0.45).cursor_not_allowed();
return el
.aria_description(i18n::text(cx, "state.unavailable", &[]))
.opacity(0.45)
.cursor_not_allowed();
}
el.cursor_pointer()
.tab_index(0)
+6
View File
@@ -32,6 +32,9 @@ const EN: &[(&str, &str)] = &[
("palette.branch.delete", "Delete"),
("palette.history.placeholder", "Search past commands"),
("palette.history.empty", "No past command fits"),
("dialog.cancel", "Cancel"),
("dialog.close", "Close"),
("state.unavailable", "Unavailable"),
];
const ZH_CN: &[(&str, &str)] = &[
@@ -62,6 +65,9 @@ const ZH_CN: &[(&str, &str)] = &[
("palette.branch.delete", "删除"),
("palette.history.placeholder", "搜索历史命令"),
("palette.history.empty", "没有匹配的历史命令"),
("dialog.cancel", "取消"),
("dialog.close", "关闭"),
("state.unavailable", "不可用"),
];
/// Ely's own message for `key` in `locale`, English for a locale Ely does not ship; fails on an unknown key.
+2 -1
View File
@@ -10,6 +10,7 @@ use super::TabOverflowMenu;
use crate::{
buttons::{ButtonVariant, IconButton},
forms::Choice,
i18n,
primitives::{DragGhost, Icon, IconName},
theme::{ActiveTheme, ControlSize, IconSize, TextSize},
};
@@ -206,7 +207,7 @@ impl RenderOnce for EditorTabs {
IconButton::new(("close", ix), IconName::X)
.size(ControlSize::Sm)
.variant(ButtonVariant::Ghost)
.tooltip("Close")
.tooltip(i18n::text(cx, "dialog.close", &[]))
.on_click(move |_, window, cx| {
cx.stop_propagation();
run(&close, "close", &slot_id, window, cx);
+11 -4
View File
@@ -2,15 +2,15 @@ use std::rc::Rc;
use gpui::{
Animation, AnimationExt, AnyElement, App, ElementId, FocusHandle, FontWeight,
InteractiveElement, IntoElement, ParentElement, Pixels, RenderOnce, SharedString, Styled,
Window, anchored, div, point, prelude::*,
InteractiveElement, IntoElement, ParentElement, Pixels, RenderOnce, Role, SharedString,
StatefulInteractiveElement, Styled, Window, anchored, div, point, prelude::*,
};
use smallvec::SmallVec;
use crate::{
buttons::{ButtonVariant, IconButton},
forms::{Enter, Run},
motion,
i18n, motion,
primitives::{Backdrop, FocusScope, IconName, give_back, raise, take_focus},
theme::{ActiveTheme, Elevation, Radius, TextSize},
};
@@ -119,6 +119,7 @@ impl RenderOnce for Dialog {
};
let theme = cx.theme();
let colors = &theme.colors;
let (title, description) = (self.title.clone(), self.detail.clone());
let heading = div()
.flex_none()
.flex()
@@ -164,6 +165,9 @@ impl RenderOnce for Dialog {
let card = if fullscreen {
div()
.id(self.id.clone())
.role(Role::Dialog)
.aria_label(title)
.when_some(description, |card, text| card.aria_description(text))
.flex()
.flex_col()
.size_full()
@@ -182,7 +186,7 @@ impl RenderOnce for Dialog {
.child(
IconButton::new((self.id.clone(), "close"), IconName::X)
.variant(ButtonVariant::Ghost)
.tooltip("Close")
.tooltip(i18n::text(cx, "dialog.close", &[]))
.on_click(move |_, window, cx| button(window, cx)),
),
)
@@ -214,6 +218,9 @@ impl RenderOnce for Dialog {
});
div()
.id(self.id.clone())
.role(Role::Dialog)
.aria_label(title)
.when_some(description, |card, text| card.aria_description(text))
.flex()
.flex_col()
.gap_5()
+10 -4
View File
@@ -1,14 +1,15 @@
use std::rc::Rc;
use gpui::{
App, ElementId, Entity, IntoElement, ParentElement, RenderOnce, SharedString, Styled, Window,
div, prelude::*,
App, ElementId, Entity, IntoElement, ParentElement, RenderOnce, Role, SharedString,
StatefulInteractiveElement, Styled, Window, div, prelude::*,
};
use super::{Close, Dialog};
use crate::{
buttons::{Button, ButtonVariant},
forms::{Input, Run, TextInput},
i18n,
primitives::{Icon, Severity},
theme::{ActiveTheme, ControlSize, IconSize, TextSize},
};
@@ -156,10 +157,11 @@ impl RenderOnce for ConfirmDialog {
};
let (cancel_id, confirm_id) = ((self.id.clone(), "cancel"), (self.id.clone(), "confirm"));
let on_close = self.on_close;
let cancel = i18n::text(cx, "dialog.cancel", &[]);
Dialog::new(self.id, self.title, move |window, cx| on_close(window, cx))
.child(said(None, self.message, cx))
.action(move |close| {
Button::new(cancel_id, "Cancel")
Button::new(cancel_id, cancel)
.variant(ButtonVariant::Ghost)
.on_click(move |_, window, cx| close(window, cx))
})
@@ -295,6 +297,9 @@ impl RenderOnce for PromptDialog {
|field, reason| {
field.child(
div()
.id((self.id.clone(), "error"))
.role(Role::Label)
.aria_label(reason.clone())
.text_size(theme.text_size(TextSize::Xs))
.text_color(colors.danger)
.child(reason),
@@ -306,13 +311,14 @@ impl RenderOnce for PromptDialog {
(self.id.clone(), "submit"),
self.submit,
);
let cancel = i18n::text(cx, "dialog.cancel", &[]);
let on_close = self.on_close;
Dialog::new(self.id, self.title, move |window, cx| on_close(window, cx))
.focus_first(self.field.read(cx).focus().clone())
.on_enter(move |close| Rc::new(move |window, cx| lifted(&close, window, cx)))
.child(field)
.action(move |close| {
Button::new(cancel_id, "Cancel")
Button::new(cancel_id, cancel)
.variant(ButtonVariant::Ghost)
.on_click(move |_, window, cx| close(window, cx))
})