@@ -116,6 +116,7 @@ dependencies = [
"time",
"time_format",
"ui",
+ "ui_input",
"util",
"uuid",
"vim_mode_setting",
@@ -15310,8 +15311,10 @@ dependencies = [
name = "ui_input"
version = "0.1.0"
dependencies = [
+ "component",
"editor",
"gpui",
+ "linkme",
"settings",
"theme",
"ui",
@@ -1,17 +1,17 @@
use context_server::{ContextServerSettings, ServerCommand, ServerConfig};
-use editor::Editor;
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, WeakEntity, prelude::*};
use serde_json::json;
use settings::update_settings_file;
use ui::{Modal, ModalFooter, ModalHeader, Section, Tooltip, prelude::*};
+use ui_input::SingleLineInput;
use workspace::{ModalView, Workspace};
use crate::AddContextServer;
pub struct AddContextServerModal {
workspace: WeakEntity<Workspace>,
- name_editor: Entity<Editor>,
- command_editor: Entity<Editor>,
+ name_editor: Entity<SingleLineInput>,
+ command_editor: Entity<SingleLineInput>,
}
impl AddContextServerModal {
@@ -33,15 +33,10 @@ impl AddContextServerModal {
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
- let name_editor = cx.new(|cx| Editor::single_line(window, cx));
- let command_editor = cx.new(|cx| Editor::single_line(window, cx));
-
- name_editor.update(cx, |editor, cx| {
- editor.set_placeholder_text("Context server name", cx);
- });
-
- command_editor.update(cx, |editor, cx| {
- editor.set_placeholder_text("Command to run the context server", cx);
+ let name_editor =
+ cx.new(|cx| SingleLineInput::new(window, cx, "Your server name").label("Name"));
+ let command_editor = cx.new(|cx| {
+ SingleLineInput::new(window, cx, "Command").label("Command to run the context server")
});
Self {
@@ -52,8 +47,22 @@ impl AddContextServerModal {
}
fn confirm(&mut self, cx: &mut Context<Self>) {
- let name = self.name_editor.read(cx).text(cx).trim().to_string();
- let command = self.command_editor.read(cx).text(cx).trim().to_string();
+ let name = self
+ .name_editor
+ .read(cx)
+ .editor()
+ .read(cx)
+ .text(cx)
+ .trim()
+ .to_string();
+ let command = self
+ .command_editor
+ .read(cx)
+ .editor()
+ .read(cx)
+ .text(cx)
+ .trim()
+ .to_string();
if name.is_empty() || command.is_empty() {
return;
@@ -104,8 +113,8 @@ impl EventEmitter<DismissEvent> for AddContextServerModal {}
impl Render for AddContextServerModal {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- let is_name_empty = self.name_editor.read(cx).text(cx).trim().is_empty();
- let is_command_empty = self.command_editor.read(cx).text(cx).trim().is_empty();
+ let is_name_empty = self.name_editor.read(cx).is_empty(cx);
+ let is_command_empty = self.command_editor.read(cx).is_empty(cx);
div()
.elevation_3(cx)
@@ -122,18 +131,8 @@ impl Render for AddContextServerModal {
.header(ModalHeader::new().headline("Add Context Server"))
.section(
Section::new()
- .child(
- v_flex()
- .gap_1()
- .child(Label::new("Name"))
- .child(self.name_editor.clone()),
- )
- .child(
- v_flex()
- .gap_1()
- .child(Label::new("Command"))
- .child(self.command_editor.clone()),
- ),
+ .child(self.name_editor.clone())
+ .child(self.command_editor.clone()),
)
.footer(
ModalFooter::new()
@@ -5,20 +5,14 @@
//! It can't be located in the `ui` crate because it depends on `editor`.
//!
+use component::{ComponentPreview, example_group, single_example};
use editor::{Editor, EditorElement, EditorStyle};
use gpui::{App, Entity, FocusHandle, Focusable, FontStyle, Hsla, TextStyle};
use settings::Settings;
use theme::ThemeSettings;
use ui::prelude::*;
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum FieldLabelLayout {
- Hidden,
- Inline,
- Stacked,
-}
-
-pub struct TextFieldStyle {
+pub struct SingleLineInputStyle {
text_color: Hsla,
background_color: Hsla,
border_color: Hsla,
@@ -27,11 +21,13 @@ pub struct TextFieldStyle {
/// A Text Field that can be used to create text fields like search inputs, form fields, etc.
///
/// It wraps a single line [`Editor`] and allows for common field properties like labels, placeholders, icons, etc.
-pub struct TextField {
+#[derive(IntoComponent)]
+#[component(scope = "Input")]
+pub struct SingleLineInput {
/// An optional label for the text field.
///
/// Its position is determined by the [`FieldLabelLayout`].
- label: SharedString,
+ label: Option<SharedString>,
/// The placeholder text for the text field.
placeholder: SharedString,
/// Exposes the underlying [`Model<Editor>`] to allow for customizing the editor beyond the provided API.
@@ -42,25 +38,18 @@ pub struct TextField {
///
/// For example, a magnifying glass icon in a search field.
start_icon: Option<IconName>,
- /// The layout of the label relative to the text field.
- with_label: FieldLabelLayout,
/// Whether the text field is disabled.
disabled: bool,
}
-impl Focusable for TextField {
+impl Focusable for SingleLineInput {
fn focus_handle(&self, cx: &App) -> FocusHandle {
self.editor.focus_handle(cx)
}
}
-impl TextField {
- pub fn new(
- window: &mut Window,
- cx: &mut App,
- label: impl Into<SharedString>,
- placeholder: impl Into<SharedString>,
- ) -> Self {
+impl SingleLineInput {
+ pub fn new(window: &mut Window, cx: &mut App, placeholder: impl Into<SharedString>) -> Self {
let placeholder_text = placeholder.into();
let editor = cx.new(|cx| {
@@ -70,11 +59,10 @@ impl TextField {
});
Self {
- label: label.into(),
+ label: None,
placeholder: placeholder_text,
editor,
start_icon: None,
- with_label: FieldLabelLayout::Hidden,
disabled: false,
}
}
@@ -84,8 +72,8 @@ impl TextField {
self
}
- pub fn with_label(mut self, layout: FieldLabelLayout) -> Self {
- self.with_label = layout;
+ pub fn label(mut self, label: impl Into<SharedString>) -> Self {
+ self.label = Some(label.into());
self
}
@@ -95,25 +83,29 @@ impl TextField {
.update(cx, |editor, _| editor.set_read_only(disabled))
}
+ pub fn is_empty(&self, cx: &App) -> bool {
+ self.editor().read(cx).text(cx).trim().is_empty()
+ }
+
pub fn editor(&self) -> &Entity<Editor> {
&self.editor
}
}
-impl Render for TextField {
+impl Render for SingleLineInput {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let theme_color = cx.theme().colors();
- let mut style = TextFieldStyle {
+ let mut style = SingleLineInputStyle {
text_color: theme_color.text,
- background_color: theme_color.ghost_element_background,
- border_color: theme_color.border,
+ background_color: theme_color.editor_background,
+ border_color: theme_color.border_variant,
};
if self.disabled {
style.text_color = theme_color.text_disabled;
- style.background_color = theme_color.ghost_element_disabled;
+ style.background_color = theme_color.editor_background;
style.border_color = theme_color.border_disabled;
}
@@ -123,8 +115,8 @@ impl Render for TextField {
// }
let text_style = TextStyle {
- font_family: settings.buffer_font.family.clone(),
- font_features: settings.buffer_font.features.clone(),
+ font_family: settings.ui_font.family.clone(),
+ font_features: settings.ui_font.features.clone(),
font_size: rems(0.875).into(),
font_weight: settings.buffer_font.weight,
font_style: FontStyle::Normal,
@@ -140,13 +132,13 @@ impl Render for TextField {
..Default::default()
};
- div()
+ v_flex()
.id(self.placeholder.clone())
- .group("text-field")
.w_full()
- .when(self.with_label == FieldLabelLayout::Stacked, |this| {
+ .gap_1()
+ .when_some(self.label.clone(), |this, label| {
this.child(
- Label::new(self.label.clone())
+ Label::new(label)
.size(LabelSize::Default)
.color(if self.disabled {
Color::Disabled
@@ -156,35 +148,37 @@ impl Render for TextField {
)
})
.child(
- v_flex().w_full().child(
- h_flex()
- .w_full()
- .flex_grow()
- .gap_2()
- .when(self.with_label == FieldLabelLayout::Inline, |this| {
- this.child(Label::new(self.label.clone()).size(LabelSize::Default))
- })
- .child(
- h_flex()
- .px_2()
- .py_1()
- .bg(style.background_color)
- .text_color(style.text_color)
- .rounded_lg()
- .border_1()
- .border_color(style.border_color)
- .min_w_48()
- .w_full()
- .flex_grow()
- .gap_1()
- .when_some(self.start_icon, |this, icon| {
- this.child(
- Icon::new(icon).size(IconSize::Small).color(Color::Muted),
- )
- })
- .child(EditorElement::new(&self.editor, editor_style)),
- ),
- ),
+ h_flex()
+ .px_2()
+ .py_1()
+ .bg(style.background_color)
+ .text_color(style.text_color)
+ .rounded_md()
+ .border_1()
+ .border_color(style.border_color)
+ .min_w_48()
+ .w_full()
+ .flex_grow()
+ .when_some(self.start_icon, |this, icon| {
+ this.gap_1()
+ .child(Icon::new(icon).size(IconSize::Small).color(Color::Muted))
+ })
+ .child(EditorElement::new(&self.editor, editor_style)),
)
}
}
+
+impl ComponentPreview for SingleLineInput {
+ fn preview(window: &mut Window, cx: &mut App) -> AnyElement {
+ let input_1 =
+ cx.new(|cx| SingleLineInput::new(window, cx, "placeholder").label("Some Label"));
+
+ v_flex()
+ .gap_6()
+ .children(vec![example_group(vec![single_example(
+ "Default",
+ div().child(input_1.clone()).into_any_element(),
+ )])])
+ .into_any_element()
+ }
+}