feat: add field component

This commit is contained in:
2026-11-03 19:07:15 +07:00
parent 7399d92f36
commit 0c8d7f05d4
6 changed files with 404 additions and 7 deletions
+115
View File
@@ -0,0 +1,115 @@
use gpui::{
AppContext as _, IntoElement, ParentElement as _, Render, Styled as _, TestAppContext,
VisualTestContext, Window, div,
};
use gpui_native_core::button::{Button, ButtonVariant};
use gpui_native_core::field::{Field, FieldVariant};
use gpui_native_theme::Theme;
struct FieldHost {
variant: FieldVariant,
with_error: bool,
}
impl Render for FieldHost {
fn render(&mut self, _window: &mut Window, _cx: &mut gpui::Context<Self>) -> impl IntoElement {
let mut field = Field::new("test-field", "Server URL")
.description("Where patches are sent.")
.variant(self.variant)
.child(
Button::new("test-field-button")
.label("Edit")
.variant(ButtonVariant::Ghost),
);
if self.with_error {
field = field.error(Some("Required"));
}
div().size_full().p_4().child(field)
}
}
fn field_window(
variant: FieldVariant,
with_error: bool,
) -> (TestAppContext, gpui::AnyWindowHandle) {
let mut cx = TestAppContext::single();
cx.update(Theme::init);
let window = cx.add_window(move |_, _| FieldHost {
variant,
with_error,
});
let window: gpui::AnyWindowHandle = window.into();
cx.update_window(window, |_, window, cx| {
window.draw(cx).clear(cx);
})
.unwrap();
(cx, window)
}
#[test]
fn block_stacks_the_label_above_the_control() {
let (cx, window) = field_window(FieldVariant::Block, false);
let mut visual = VisualTestContext::from_window(window, &cx);
let label = visual
.debug_bounds("test-field-label")
.expect("label missing");
let control = visual
.debug_bounds("test-field-control")
.expect("control missing");
assert!(
label.bottom() <= control.top(),
"label must sit above the control: {label:?} vs {control:?}"
);
assert!(
(label.left() - control.left()).abs() < gpui::px(1.),
"label and control must share the left edge"
);
}
#[test]
fn row_leads_with_the_label_and_trails_the_control() {
let (cx, window) = field_window(FieldVariant::Row, false);
let mut visual = VisualTestContext::from_window(window, &cx);
let label = visual
.debug_bounds("test-field-label")
.expect("label missing");
let control = visual
.debug_bounds("test-field-control")
.expect("control missing");
assert!(
control.left() >= label.right(),
"the control must trail the label: {label:?} vs {control:?}"
);
let label_center = (label.top() + label.bottom()) / 2.;
let control_center = (control.top() + control.bottom()) / 2.;
assert!(
(label_center - control_center).abs() < gpui::px(1.5),
"the control must center on the title line: {label:?} vs {control:?}"
);
}
#[test]
fn the_error_renders_under_the_control_only_when_set() {
let (cx, window) = field_window(FieldVariant::Block, false);
let mut visual = VisualTestContext::from_window(window, &cx);
assert!(
visual.debug_bounds("test-field-error").is_none(),
"no error must render when none is set"
);
let (cx, window) = field_window(FieldVariant::Block, true);
let mut visual = VisualTestContext::from_window(window, &cx);
let error = visual
.debug_bounds("test-field-error")
.expect("error missing");
let control = visual
.debug_bounds("test-field-control")
.expect("control missing");
assert!(
error.top() >= control.bottom(),
"the error must sit under the control: {control:?} vs {error:?}"
);
}