Skip to main content

ui/components/
search_input.rs

1use gpui::{AnyElement, Context, Entity, Render};
2
3use crate::prelude::*;
4use crate::{InputGroup, TextInput};
5
6/// A search field: `TextInput` with a leading `MagnifyingGlass` icon and a
7/// trailing clear (`XMark`) button shown once the input is non-empty.
8/// Stateful view — create with `cx.new(|cx| SearchInput::new(cx, "Search…"))`.
9pub struct SearchInput {
10    input: Entity<TextInput>,
11}
12
13impl SearchInput {
14    pub fn new(cx: &mut Context<Self>, placeholder: impl Into<SharedString>) -> Self {
15        let input = cx.new(|cx| TextInput::new(cx).placeholder(placeholder));
16        cx.observe(&input, |_, _, cx| cx.notify()).detach();
17        Self { input }
18    }
19
20    /// The current search query text.
21    pub fn query(&self, cx: &App) -> String {
22        self.input.read(cx).text().to_string()
23    }
24}
25
26impl Render for SearchInput {
27    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28        let is_empty = self.input.read(cx).text().is_empty();
29
30        let mut group = InputGroup::new(self.input.clone()).leading(
31            Icon::new(IconName::MagnifyingGlass)
32                .size(IconSize::Small)
33                .color(Color::Muted),
34        );
35
36        if !is_empty {
37            group = group.trailing(
38                div()
39                    .id("search-input-clear")
40                    .cursor_pointer()
41                    .child(
42                        Icon::new(IconName::XMark)
43                            .size(IconSize::Small)
44                            .color(Color::Muted),
45                    )
46                    .on_click(cx.listener(|this, _, _, cx| {
47                        this.input.update(cx, |input, cx| input.clear(cx));
48                        cx.notify();
49                    })),
50            );
51        }
52
53        group
54    }
55}
56
57/// Standalone gallery preview for `SearchInput` (not registered in the
58/// `Component` catalog since it is a stateful `Entity`, matching `Select`'s
59/// existing convention in this crate).
60pub fn search_input_preview(_window: &mut Window, cx: &mut App) -> AnyElement {
61    v_flex()
62        .gap_4()
63        .child(cx.new(|cx| SearchInput::new(cx, "Search…")))
64        .into_any_element()
65}