a11y: a skip link, color-blind safe charts
i18n::SkipLink holds a Tab stop in flow at zero size, first where it sits, and while focused raises a pill over the page; Enter, Space or a press hands focus to its target. gpui orders stops by tab_index before paint and registers none for an invisible element, so the stop stays in flow and only the pill is raised. Theme::set_color_blind_safe swaps Ely's chart hues for a set fitted to stay apart under Machado's protanopia, deuteranopia and tritanopia; a test holds every pair 0.10 apart in Oklab and each hue 3:1 on its page, and red against green shows the measure bites. KeyboardNavigationHelper points to interaction::RovingFocus. The announcer, labels and screen-reader text stay blocked: gpui 0.2.2 has no accessibility tree, and nothing here is trusted to observe VoiceOver. The i18n tests move under src/i18n/tests.
This commit is contained in:
1 parent
aa39fba87f
commit
1ac5c04fb0
10 files changed
+463
-7
No files matched your search
@@ -0,0 +1,70 @@
|
||||
use gpui::{
|
||||
App, ElementId, FocusHandle, InteractiveElement, IntoElement, ParentElement, RenderOnce,
|
||||
SharedString, StatefulInteractiveElement, Styled, Window, anchored, div, prelude::*,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
primitives::{raise, tab_stop},
|
||||
theme::{ActiveTheme, Elevation, Radius, TextSize},
|
||||
};
|
||||
|
||||
/// A link that shows only while focused and hands focus to `target`. Put it before what a keyboard would otherwise tab through, such as a long nav; it takes no room at rest and lies over the page while shown.
|
||||
#[derive(IntoElement)]
|
||||
pub struct SkipLink {
|
||||
id: ElementId,
|
||||
label: SharedString,
|
||||
target: FocusHandle,
|
||||
}
|
||||
|
||||
impl SkipLink {
|
||||
pub fn new(
|
||||
id: impl Into<ElementId>,
|
||||
label: impl Into<SharedString>,
|
||||
target: &FocusHandle,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
label: label.into(),
|
||||
target: target.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for SkipLink {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let handle = tab_stop(self.id.clone(), true, window, cx);
|
||||
let shown = handle.is_focused(window);
|
||||
let theme = cx.theme();
|
||||
let (target, label) = (self.target, self.label.clone());
|
||||
let skip = move |window: &mut Window| {
|
||||
log::info!("skip link {label:?}: focus to its target");
|
||||
window.focus(&target);
|
||||
};
|
||||
let press = skip.clone();
|
||||
let pill = div()
|
||||
.id((self.id.clone(), "pill"))
|
||||
.occlude()
|
||||
.px_3()
|
||||
.py_1p5()
|
||||
.rounded(theme.radius(Radius::Md))
|
||||
.bg(theme.colors.overlay)
|
||||
.border_1()
|
||||
.border_color(theme.colors.focus)
|
||||
.shadow(theme.elevation(Elevation::Floating))
|
||||
.text_size(theme.text_size(TextSize::Sm))
|
||||
.text_color(theme.colors.fg)
|
||||
.cursor_pointer()
|
||||
.child(self.label)
|
||||
.on_click(move |_, window, _| press(window));
|
||||
div()
|
||||
.id(self.id.clone())
|
||||
.track_focus(&handle)
|
||||
.on_click(move |_, window, _| skip(window))
|
||||
.when(shown, |link| {
|
||||
link.child(raise(
|
||||
self.id.clone(),
|
||||
anchored().snap_to_window().child(div().p_2().child(pill)),
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user