diff --git a/assets/icons/thread.svg b/assets/icons/thread.svg index 496cf42e3a3ee1439f36b8e2479d05564362e628..569a6f3aec7e3b8742d3d7d23fe11db5aea199ba 100644 --- a/assets/icons/thread.svg +++ b/assets/icons/thread.svg @@ -1,3 +1,4 @@ - + + diff --git a/crates/ui/src/components/ai.rs b/crates/ui/src/components/ai.rs index a31db264e985b3adbca26b9e8d3fb2bdca306dcb..de6b74afb02e23d5fa87a01ae448d63979815870 100644 --- a/crates/ui/src/components/ai.rs +++ b/crates/ui/src/components/ai.rs @@ -1,5 +1,7 @@ mod configured_api_card; mod thread_item; +mod thread_sidebar_toggle; pub use configured_api_card::*; pub use thread_item::*; +pub use thread_sidebar_toggle::*; diff --git a/crates/ui/src/components/ai/configured_api_card.rs b/crates/ui/src/components/ai/configured_api_card.rs index 37f9ac7602d676906565a911f1bbca6d2b40f755..2104e816811a68776f69f3970b53636dbbd63e17 100644 --- a/crates/ui/src/components/ai/configured_api_card.rs +++ b/crates/ui/src/components/ai/configured_api_card.rs @@ -1,7 +1,7 @@ use crate::{Tooltip, prelude::*}; use gpui::{ClickEvent, IntoElement, ParentElement, SharedString}; -#[derive(IntoElement)] +#[derive(IntoElement, RegisterComponent)] pub struct ConfiguredApiCard { label: SharedString, button_label: Option, @@ -52,6 +52,59 @@ impl ConfiguredApiCard { } } +impl Component for ConfiguredApiCard { + fn scope() -> ComponentScope { + ComponentScope::Agent + } + + fn preview(_window: &mut Window, cx: &mut App) -> Option { + let container = || { + v_flex() + .w_72() + .p_2() + .gap_2() + .border_1() + .border_color(cx.theme().colors().border_variant) + .bg(cx.theme().colors().panel_background) + }; + + let examples = vec![ + single_example( + "Default", + container() + .child(ConfiguredApiCard::new("API key is configured")) + .into_any_element(), + ), + single_example( + "Custom Button Label", + container() + .child( + ConfiguredApiCard::new("OpenAI API key configured") + .button_label("Remove Key"), + ) + .into_any_element(), + ), + single_example( + "With Tooltip", + container() + .child( + ConfiguredApiCard::new("Anthropic API key configured") + .tooltip_label("Click to reset your API key"), + ) + .into_any_element(), + ), + single_example( + "Disabled", + container() + .child(ConfiguredApiCard::new("API key is configured").disabled(true)) + .into_any_element(), + ), + ]; + + Some(example_group(examples).into_any_element()) + } +} + impl RenderOnce for ConfiguredApiCard { fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { let button_label = self.button_label.unwrap_or("Reset Key".into()); diff --git a/crates/ui/src/components/ai/copilot_configuration_callout.rs b/crates/ui/src/components/ai/copilot_configuration_callout.rs deleted file mode 100644 index 8b137891791fe96927ad78e64b0aad7bded08bdc..0000000000000000000000000000000000000000 --- a/crates/ui/src/components/ai/copilot_configuration_callout.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/ui/src/components/ai/thread_sidebar_toggle.rs b/crates/ui/src/components/ai/thread_sidebar_toggle.rs new file mode 100644 index 0000000000000000000000000000000000000000..606d7f1eed6852f677b7167e0b868c1c1e3847c2 --- /dev/null +++ b/crates/ui/src/components/ai/thread_sidebar_toggle.rs @@ -0,0 +1,177 @@ +use gpui::{AnyView, ClickEvent}; +use ui_macros::RegisterComponent; + +use crate::prelude::*; +use crate::{IconButton, IconName, Tooltip}; + +#[derive(IntoElement, RegisterComponent)] +pub struct ThreadSidebarToggle { + sidebar_selected: bool, + thread_selected: bool, + flipped: bool, + sidebar_tooltip: Option AnyView + 'static>>, + thread_tooltip: Option AnyView + 'static>>, + on_sidebar_click: Option>, + on_thread_click: Option>, +} + +impl ThreadSidebarToggle { + pub fn new() -> Self { + Self { + sidebar_selected: false, + thread_selected: false, + flipped: false, + sidebar_tooltip: None, + thread_tooltip: None, + on_sidebar_click: None, + on_thread_click: None, + } + } + + pub fn sidebar_selected(mut self, selected: bool) -> Self { + self.sidebar_selected = selected; + self + } + + pub fn thread_selected(mut self, selected: bool) -> Self { + self.thread_selected = selected; + self + } + + pub fn flipped(mut self, flipped: bool) -> Self { + self.flipped = flipped; + self + } + + pub fn sidebar_tooltip( + mut self, + tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static, + ) -> Self { + self.sidebar_tooltip = Some(Box::new(tooltip)); + self + } + + pub fn thread_tooltip( + mut self, + tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static, + ) -> Self { + self.thread_tooltip = Some(Box::new(tooltip)); + self + } + + pub fn on_sidebar_click( + mut self, + handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static, + ) -> Self { + self.on_sidebar_click = Some(Box::new(handler)); + self + } + + pub fn on_thread_click( + mut self, + handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static, + ) -> Self { + self.on_thread_click = Some(Box::new(handler)); + self + } +} + +impl RenderOnce for ThreadSidebarToggle { + fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { + let sidebar_icon = match (self.sidebar_selected, self.flipped) { + (true, false) => IconName::ThreadsSidebarLeftOpen, + (false, false) => IconName::ThreadsSidebarLeftClosed, + (true, true) => IconName::ThreadsSidebarRightOpen, + (false, true) => IconName::ThreadsSidebarRightClosed, + }; + + h_flex() + .min_w_0() + .rounded_sm() + .gap_px() + .border_1() + .border_color(cx.theme().colors().border) + .when(self.flipped, |this| this.flex_row_reverse()) + .child( + IconButton::new("sidebar-toggle", sidebar_icon) + .icon_size(IconSize::Small) + .toggle_state(self.sidebar_selected) + .when_some(self.sidebar_tooltip, |this, tooltip| this.tooltip(tooltip)) + .when_some(self.on_sidebar_click, |this, handler| { + this.on_click(handler) + }), + ) + .child(div().h_4().w_px().bg(cx.theme().colors().border)) + .child( + IconButton::new("thread-toggle", IconName::Thread) + .icon_size(IconSize::Small) + .toggle_state(self.thread_selected) + .when_some(self.thread_tooltip, |this, tooltip| this.tooltip(tooltip)) + .when_some(self.on_thread_click, |this, handler| this.on_click(handler)), + ) + } +} + +impl Component for ThreadSidebarToggle { + fn scope() -> ComponentScope { + ComponentScope::Agent + } + + fn preview(_window: &mut Window, cx: &mut App) -> Option { + let container = || div().p_2().bg(cx.theme().colors().status_bar_background); + + let examples = vec![ + single_example( + "Both Unselected", + container() + .child(ThreadSidebarToggle::new()) + .into_any_element(), + ), + single_example( + "Sidebar Selected", + container() + .child(ThreadSidebarToggle::new().sidebar_selected(true)) + .into_any_element(), + ), + single_example( + "Thread Selected", + container() + .child(ThreadSidebarToggle::new().thread_selected(true)) + .into_any_element(), + ), + single_example( + "Both Selected", + container() + .child( + ThreadSidebarToggle::new() + .sidebar_selected(true) + .thread_selected(true), + ) + .into_any_element(), + ), + single_example( + "Flipped", + container() + .child( + ThreadSidebarToggle::new() + .sidebar_selected(true) + .thread_selected(true) + .flipped(true), + ) + .into_any_element(), + ), + single_example( + "With Tooltips", + container() + .child( + ThreadSidebarToggle::new() + .sidebar_tooltip(Tooltip::text("Toggle Sidebar")) + .thread_tooltip(Tooltip::text("Toggle Thread")), + ) + .into_any_element(), + ), + ]; + + Some(example_group(examples).into_any_element()) + } +}