ui: Add a CopyButton component (#45821)

There were several places adding a copy icon button, so thought of
encapsulating the logic to copy a given string into the clipboard (and
other small details like swapping the icon and tooltip if copied) into a
component, making it easier to introduce this sort of functionality in
the future, with fewer lines of code.

All it takes (for the simplest case) is:

```rs
CopyButton::new(your_message)
```

<img width="600" height="714" alt="Screenshot 2025-12-29 at 10  50@2x"
src="https://github.com/user-attachments/assets/e6949863-a056-4855-82d8-e4ffb5d62c90"
/>

Release Notes:

- N/A
This commit is contained in:
Danilo Leal 2025-12-29 11:01:19 -03:00 committed by GitHub
parent db221ca72d
commit 6d947b7746
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 272 additions and 250 deletions

View file

@ -4,22 +4,20 @@ use std::{
fmt::Display,
rc::{Rc, Weak},
sync::Arc,
time::Duration,
};
use agent_client_protocol as acp;
use collections::HashMap;
use gpui::{
App, ClipboardItem, Empty, Entity, EventEmitter, FocusHandle, Focusable, Global, ListAlignment,
ListState, StyleRefinement, Subscription, Task, TextStyleRefinement, Window, actions, list,
prelude::*,
App, Empty, Entity, EventEmitter, FocusHandle, Focusable, Global, ListAlignment, ListState,
StyleRefinement, Subscription, Task, TextStyleRefinement, Window, actions, list, prelude::*,
};
use language::LanguageRegistry;
use markdown::{CodeBlockRenderer, Markdown, MarkdownElement, MarkdownStyle};
use project::Project;
use settings::Settings;
use theme::ThemeSettings;
use ui::{Tooltip, WithScrollbar, prelude::*};
use ui::{CopyButton, Tooltip, WithScrollbar, prelude::*};
use util::ResultExt as _;
use workspace::{
Item, ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
@ -544,15 +542,11 @@ impl Render for AcpTools {
pub struct AcpToolsToolbarItemView {
acp_tools: Option<Entity<AcpTools>>,
just_copied: bool,
}
impl AcpToolsToolbarItemView {
pub fn new() -> Self {
Self {
acp_tools: None,
just_copied: false,
}
Self { acp_tools: None }
}
}
@ -572,37 +566,14 @@ impl Render for AcpToolsToolbarItemView {
h_flex()
.gap_2()
.child({
let acp_tools = acp_tools.clone();
IconButton::new(
"copy_all_messages",
if self.just_copied {
IconName::Check
} else {
IconName::Copy
},
)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text(if self.just_copied {
"Copied!"
} else {
"Copy All Messages"
}))
.disabled(!has_messages)
.on_click(cx.listener(move |this, _, _window, cx| {
if let Some(content) = acp_tools.read(cx).serialize_observed_messages() {
cx.write_to_clipboard(ClipboardItem::new_string(content));
let message = acp_tools
.read(cx)
.serialize_observed_messages()
.unwrap_or_default();
this.just_copied = true;
cx.spawn(async move |this, cx| {
cx.background_executor().timer(Duration::from_secs(2)).await;
this.update(cx, |this, cx| {
this.just_copied = false;
cx.notify();
})
})
.detach();
}
}))
CopyButton::new(message)
.tooltip_label("Copy All Messages")
.disabled(!has_messages)
})
.child(
IconButton::new("clear_messages", IconName::Trash)

View file

@ -24,11 +24,11 @@ use file_icons::FileIcons;
use fs::Fs;
use futures::FutureExt as _;
use gpui::{
Action, Animation, AnimationExt, AnyView, App, BorderStyle, ClickEvent, ClipboardItem,
CursorStyle, EdgesRefinement, ElementId, Empty, Entity, FocusHandle, Focusable, Hsla, Length,
ListOffset, ListState, PlatformDisplay, SharedString, StyleRefinement, Subscription, Task,
TextStyle, TextStyleRefinement, UnderlineStyle, WeakEntity, Window, WindowHandle, div,
ease_in_out, linear_color_stop, linear_gradient, list, point, pulsating_between,
Action, Animation, AnimationExt, AnyView, App, BorderStyle, ClickEvent, CursorStyle,
EdgesRefinement, ElementId, Empty, Entity, FocusHandle, Focusable, Hsla, Length, ListOffset,
ListState, PlatformDisplay, SharedString, StyleRefinement, Subscription, Task, TextStyle,
TextStyleRefinement, UnderlineStyle, WeakEntity, Window, WindowHandle, div, ease_in_out,
linear_color_stop, linear_gradient, list, point, pulsating_between,
};
use language::Buffer;
@ -47,9 +47,9 @@ use terminal_view::terminal_panel::TerminalPanel;
use text::Anchor;
use theme::{AgentFontSize, ThemeSettings};
use ui::{
Callout, CommonAnimationExt, ContextMenu, ContextMenuEntry, Disclosure, Divider, DividerColor,
ElevationIndex, KeyBinding, PopoverMenuHandle, SpinnerLabel, TintColor, Tooltip, WithScrollbar,
prelude::*, right_click_menu,
Callout, CommonAnimationExt, ContextMenu, ContextMenuEntry, CopyButton, Disclosure, Divider,
DividerColor, ElevationIndex, KeyBinding, PopoverMenuHandle, SpinnerLabel, TintColor, Tooltip,
WithScrollbar, prelude::*, right_click_menu,
};
use util::{ResultExt, size::format_file_size, time::duration_alt_display};
use workspace::{CollaboratorId, NewTerminal, Workspace};
@ -5921,12 +5921,7 @@ impl AcpThreadView {
fn create_copy_button(&self, message: impl Into<String>) -> impl IntoElement {
let message = message.into();
IconButton::new("copy", IconName::Copy)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("Copy Error Message"))
.on_click(move |_, _, cx| {
cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
})
CopyButton::new(message).tooltip_label("Copy Error Message")
}
fn dismiss_error_button(&self, cx: &mut Context<Self>) -> impl IntoElement {

View file

@ -31,9 +31,9 @@ use smallvec::SmallVec;
use std::{mem, sync::Arc};
use theme::{ActiveTheme, ThemeSettings};
use ui::{
Avatar, AvatarAvailabilityIndicator, Button, Color, ContextMenu, Facepile, HighlightedLabel,
Icon, IconButton, IconName, IconSize, Indicator, Label, ListHeader, ListItem, Tab, Tooltip,
prelude::*, tooltip_container,
Avatar, AvatarAvailabilityIndicator, Button, Color, ContextMenu, CopyButton, Facepile,
HighlightedLabel, Icon, IconButton, IconName, IconSize, Indicator, Label, ListHeader, ListItem,
Tab, Tooltip, prelude::*, tooltip_container,
};
use util::{ResultExt, TryFutureExt, maybe};
use workspace::{
@ -2527,16 +2527,9 @@ impl CollabPanel {
let button = match section {
Section::ActiveCall => channel_link.map(|channel_link| {
let channel_link_copy = channel_link;
IconButton::new("channel-link", IconName::Copy)
.icon_size(IconSize::Small)
.size(ButtonSize::None)
CopyButton::new(channel_link)
.visible_on_hover("section-header")
.on_click(move |_, _, cx| {
let item = ClipboardItem::new_string(channel_link_copy.clone());
cx.write_to_clipboard(item)
})
.tooltip(Tooltip::text("Copy channel link"))
.tooltip_label("Copy Channel Link")
.into_any_element()
}),
Section::Contacts => Some(

View file

@ -8,8 +8,8 @@ use crate::{
};
use anyhow::Context as _;
use gpui::{
AnyElement, AsyncWindowContext, ClipboardItem, Context, Entity, Focusable as _, FontWeight,
Hsla, InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels, ScrollHandle, Size,
AnyElement, AsyncWindowContext, Context, Entity, Focusable as _, FontWeight, Hsla,
InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels, ScrollHandle, Size,
StatefulInteractiveElement, StyleRefinement, Styled, Subscription, Task, TextStyleRefinement,
Window, div, px,
};
@ -24,7 +24,7 @@ use std::{borrow::Cow, cell::RefCell};
use std::{ops::Range, sync::Arc, time::Duration};
use std::{path::PathBuf, rc::Rc};
use theme::ThemeSettings;
use ui::{Scrollbars, Tooltip, WithScrollbar, prelude::*, theme_is_transparent};
use ui::{CopyButton, Scrollbars, WithScrollbar, prelude::*, theme_is_transparent};
use url::Url;
use util::TryFutureExt;
use workspace::{OpenOptions, OpenVisible, Workspace};
@ -1026,25 +1026,7 @@ impl DiagnosticPopover {
)
.child({
let message = self.local_diagnostic.diagnostic.message.clone();
let copied = cx
.read_from_clipboard()
.map(|item| item.text().as_ref() == Some(&message))
.unwrap_or(false);
let (icon, color) = if copied {
(IconName::Check, Color::Success)
} else {
(IconName::Copy, Color::Muted)
};
IconButton::new("copy-diagnostic", icon)
.icon_color(color)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("Copy Diagnostic"))
.on_click(move |_, _, cx| {
cx.write_to_clipboard(ClipboardItem::new_string(
message.clone(),
));
})
CopyButton::new(message).tooltip_label("Copy Diagnostic")
}),
)
.custom_scrollbars(

View file

@ -13,7 +13,7 @@ use project::{git_store::Repository, project_settings::ProjectSettings};
use settings::Settings as _;
use theme::ThemeSettings;
use time::OffsetDateTime;
use ui::{ContextMenu, Divider, prelude::*, tooltip_container};
use ui::{ContextMenu, CopyButton, Divider, prelude::*, tooltip_container};
use workspace::Workspace;
const GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED: usize = 20;
@ -335,18 +335,10 @@ impl BlameRenderer for GitBlameRenderer {
cx.stop_propagation();
}),
)
.child(Divider::vertical())
.child(
IconButton::new("copy-sha-button", IconName::Copy)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.on_click(move |_, _, cx| {
cx.stop_propagation();
cx.write_to_clipboard(
ClipboardItem::new_string(
sha.to_string(),
),
)
}),
CopyButton::new(sha.to_string())
.tooltip_label("Copy SHA"),
),
),
),

View file

@ -5,7 +5,7 @@ use git::blame::BlameEntry;
use git::repository::CommitSummary;
use git::{GitRemote, commit::ParsedCommitMessage};
use gpui::{
App, Asset, ClipboardItem, Element, Entity, MouseButton, ParentElement, Render, ScrollHandle,
App, Asset, Element, Entity, MouseButton, ParentElement, Render, ScrollHandle,
StatefulInteractiveElement, WeakEntity, prelude::*,
};
use markdown::{Markdown, MarkdownElement};
@ -14,7 +14,7 @@ use settings::Settings;
use std::hash::Hash;
use theme::ThemeSettings;
use time::{OffsetDateTime, UtcOffset};
use ui::{Avatar, Divider, IconButtonShape, prelude::*, tooltip_container};
use ui::{Avatar, CopyButton, Divider, prelude::*, tooltip_container};
use workspace::Workspace;
#[derive(Clone, Debug)]
@ -315,8 +315,8 @@ impl Render for CommitTooltip {
cx.open_url(pr.url.as_str())
}),
)
.child(Divider::vertical())
})
.child(Divider::vertical())
.child(
Button::new(
"commit-sha-button",
@ -342,18 +342,8 @@ impl Render for CommitTooltip {
},
),
)
.child(
IconButton::new("copy-sha-button", IconName::Copy)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.on_click(move |_, _, cx| {
cx.stop_propagation();
cx.write_to_clipboard(
ClipboardItem::new_string(full_sha.clone()),
)
}),
),
.child(Divider::vertical())
.child(CopyButton::new(full_sha).tooltip_label("Copy SHA")),
),
),
)

View file

@ -8,6 +8,7 @@ use language::LanguageName;
use log::Level;
pub use path_range::{LineCol, PathWithRange};
use ui::Checkbox;
use ui::CopyButton;
use std::borrow::Cow;
use std::iter;
@ -32,7 +33,7 @@ use parser::{MarkdownEvent, MarkdownTag, MarkdownTagEnd, parse_links_only, parse
use pulldown_cmark::Alignment;
use sum_tree::TreeMap;
use theme::SyntaxTheme;
use ui::{ScrollAxes, Scrollbars, Tooltip, WithScrollbar, prelude::*};
use ui::{ScrollAxes, Scrollbars, WithScrollbar, prelude::*};
use util::ResultExt;
use crate::parser::CodeBlockKind;
@ -1202,7 +1203,6 @@ impl Element for MarkdownElement {
range.end,
code,
self.markdown.clone(),
cx,
);
el.child(
h_flex()
@ -1233,7 +1233,6 @@ impl Element for MarkdownElement {
range.end,
code,
self.markdown.clone(),
cx,
);
el.child(
h_flex()
@ -1449,26 +1448,12 @@ fn render_copy_code_block_button(
id: usize,
code: String,
markdown: Entity<Markdown>,
cx: &App,
) -> impl IntoElement {
let id = ElementId::named_usize("copy-markdown-code", id);
let was_copied = markdown.read(cx).copied_code_blocks.contains(&id);
IconButton::new(
id.clone(),
if was_copied {
IconName::Check
} else {
IconName::Copy
},
)
.icon_color(Color::Muted)
.icon_size(IconSize::Small)
.style(ButtonStyle::Filled)
.shape(ui::IconButtonShape::Square)
.tooltip(Tooltip::text("Copy"))
.on_click({
CopyButton::new(code.clone()).custom_on_click({
let markdown = markdown;
move |_event, _window, cx| {
move |_window, cx| {
let id = id.clone();
markdown.update(cx, |this, cx| {
this.copied_code_blocks.insert(id.clone());

View file

@ -6,10 +6,10 @@ use crate::markdown_elements::{
};
use fs::normalize_path;
use gpui::{
AbsoluteLength, AnyElement, App, AppContext as _, ClipboardItem, Context, Div, Element,
ElementId, Entity, HighlightStyle, Hsla, ImageSource, InteractiveText, IntoElement, Keystroke,
Modifiers, ParentElement, Render, Resource, SharedString, Styled, StyledText, TextStyle,
WeakEntity, Window, div, img, px, rems,
AbsoluteLength, AnyElement, App, AppContext as _, Context, Div, Element, ElementId, Entity,
HighlightStyle, Hsla, ImageSource, InteractiveText, IntoElement, Keystroke, Modifiers,
ParentElement, Render, Resource, SharedString, Styled, StyledText, TextStyle, WeakEntity,
Window, div, img, px, rems,
};
use settings::Settings;
use std::{
@ -18,12 +18,7 @@ use std::{
vec,
};
use theme::{ActiveTheme, SyntaxTheme, ThemeSettings};
use ui::{
ButtonCommon, Clickable, Color, FluentBuilder, IconButton, IconName, IconSize,
InteractiveElement, Label, LabelCommon, LabelSize, LinkPreview, Pixels, Rems,
StatefulInteractiveElement, StyledExt, StyledImage, ToggleState, Tooltip, VisibleOnHover,
h_flex, tooltip_container, v_flex,
};
use ui::{CopyButton, LinkPreview, ToggleState, prelude::*, tooltip_container};
use workspace::{OpenOptions, OpenVisible, Workspace};
pub struct CheckboxClickedEvent {
@ -626,15 +621,8 @@ fn render_markdown_code_block(
StyledText::new(parsed.contents.clone())
};
let copy_block_button = IconButton::new("copy-code", IconName::Copy)
.icon_size(IconSize::Small)
.on_click({
let contents = parsed.contents.clone();
move |_, _window, cx| {
cx.write_to_clipboard(ClipboardItem::new_string(contents.to_string()));
}
})
.tooltip(Tooltip::text("Copy code block"))
let copy_block_button = CopyButton::new(parsed.contents.clone())
.tooltip_label("Copy Codeblock")
.visible_on_hover("markdown-block");
let font = gpui::Font {

View file

@ -37,10 +37,7 @@ use editor::{Editor, MultiBuffer};
use gpui::{AnyElement, ClipboardItem, Entity, Render, WeakEntity};
use language::Buffer;
use runtimelib::{ExecutionState, JupyterMessageContent, MimeBundle, MimeType};
use ui::{
ButtonStyle, CommonAnimationExt, Context, IconButton, IconName, IntoElement, Styled, Tooltip,
Window, div, h_flex, prelude::*, v_flex,
};
use ui::{CommonAnimationExt, CopyButton, IconButton, Tooltip, prelude::*};
mod image;
use image::ImageView;
@ -236,89 +233,62 @@ impl Output {
Self::Image { content, .. } => {
Self::render_output_controls(content.clone(), workspace, window, cx)
}
Self::ErrorOutput(err) => {
// Add buttons for the traceback section
Some(
h_flex()
.pl_1()
.child(
IconButton::new(
ElementId::Name("copy-full-error-traceback".into()),
IconName::Copy,
)
.style(ButtonStyle::Transparent)
.tooltip(Tooltip::text("Copy Full Error"))
.on_click({
let ename = err.ename.clone();
let evalue = err.evalue.clone();
let traceback = err.traceback.clone();
move |_, _window, cx| {
Self::ErrorOutput(err) => Some(
h_flex()
.pl_1()
.child({
let ename = err.ename.clone();
let evalue = err.evalue.clone();
let traceback = err.traceback.clone();
let traceback_text = traceback.read(cx).full_text();
let full_error = format!("{}: {}\n{}", ename, evalue, traceback_text);
CopyButton::new(full_error).tooltip_label("Copy Full Error")
})
.child(
IconButton::new(
ElementId::Name("open-full-error-in-buffer-traceback".into()),
IconName::FileTextOutlined,
)
.style(ButtonStyle::Transparent)
.tooltip(Tooltip::text("Open Full Error in Buffer"))
.on_click({
let ename = err.ename.clone();
let evalue = err.evalue.clone();
let traceback = err.traceback.clone();
move |_, window, cx| {
if let Some(workspace) = workspace.upgrade() {
let traceback_text = traceback.read(cx).full_text();
let full_error =
format!("{}: {}\n{}", ename, evalue, traceback_text);
let clipboard_content =
ClipboardItem::new_string(full_error);
cx.write_to_clipboard(clipboard_content);
}
}),
)
.child(
IconButton::new(
ElementId::Name("open-full-error-in-buffer-traceback".into()),
IconName::FileTextOutlined,
)
.style(ButtonStyle::Transparent)
.tooltip(Tooltip::text("Open Full Error in Buffer"))
.on_click({
let ename = err.ename.clone();
let evalue = err.evalue.clone();
let traceback = err.traceback.clone();
move |_, window, cx| {
if let Some(workspace) = workspace.upgrade() {
let traceback_text = traceback.read(cx).full_text();
let full_error = format!(
"{}: {}\n{}",
ename, evalue, traceback_text
let buffer = cx.new(|cx| {
let mut buffer = Buffer::local(full_error, cx)
.with_language(language::PLAIN_TEXT.clone(), cx);
buffer
.set_capability(language::Capability::ReadOnly, cx);
buffer
});
let editor = Box::new(cx.new(|cx| {
let multibuffer = cx.new(|cx| {
let mut multi_buffer =
MultiBuffer::singleton(buffer.clone(), cx);
multi_buffer
.set_title("Full Error".to_string(), cx);
multi_buffer
});
Editor::for_multibuffer(multibuffer, None, window, cx)
}));
workspace.update(cx, |workspace, cx| {
workspace.add_item_to_active_pane(
editor, None, true, window, cx,
);
let buffer = cx.new(|cx| {
let mut buffer = Buffer::local(full_error, cx)
.with_language(
language::PLAIN_TEXT.clone(),
cx,
);
buffer.set_capability(
language::Capability::ReadOnly,
cx,
);
buffer
});
let editor = Box::new(cx.new(|cx| {
let multibuffer = cx.new(|cx| {
let mut multi_buffer =
MultiBuffer::singleton(buffer.clone(), cx);
multi_buffer
.set_title("Full Error".to_string(), cx);
multi_buffer
});
Editor::for_multibuffer(
multibuffer,
None,
window,
cx,
)
}));
workspace.update(cx, |workspace, cx| {
workspace.add_item_to_active_pane(
editor, None, true, window, cx,
);
});
}
});
}
}),
)
.into_any_element(),
)
}
}
}),
)
.into_any_element(),
),
Self::Message(_) => None,
Self::Table { content, .. } => {
Self::render_output_controls(content.clone(), workspace, window, cx)

View file

@ -2,6 +2,7 @@ mod button;
mod button_icon;
mod button_like;
mod button_link;
mod copy_button;
mod icon_button;
mod split_button;
mod toggle_button;
@ -9,6 +10,7 @@ mod toggle_button;
pub use button::*;
pub use button_like::*;
pub use button_link::*;
pub use copy_button::*;
pub use icon_button::*;
pub use split_button::*;
pub use toggle_button::*;

View file

@ -0,0 +1,162 @@
use gpui::{
AnyElement, App, ClipboardItem, IntoElement, ParentElement, RenderOnce, Styled, Window,
};
use crate::{Tooltip, prelude::*};
#[derive(IntoElement, RegisterComponent)]
pub struct CopyButton {
message: SharedString,
icon_size: IconSize,
disabled: bool,
tooltip_label: SharedString,
visible_on_hover: Option<SharedString>,
custom_on_click: Option<Box<dyn Fn(&mut Window, &mut App) + 'static>>,
}
impl CopyButton {
pub fn new(message: impl Into<SharedString>) -> Self {
Self {
message: message.into(),
icon_size: IconSize::Small,
disabled: false,
tooltip_label: "Copy".into(),
visible_on_hover: None,
custom_on_click: None,
}
}
pub fn icon_size(mut self, icon_size: IconSize) -> Self {
self.icon_size = icon_size;
self
}
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
pub fn tooltip_label(mut self, tooltip_label: impl Into<SharedString>) -> Self {
self.tooltip_label = tooltip_label.into();
self
}
pub fn visible_on_hover(mut self, visible_on_hover: impl Into<SharedString>) -> Self {
self.visible_on_hover = Some(visible_on_hover.into());
self
}
pub fn custom_on_click(
mut self,
custom_on_click: impl Fn(&mut Window, &mut App) + 'static,
) -> Self {
self.custom_on_click = Some(Box::new(custom_on_click));
self
}
}
impl RenderOnce for CopyButton {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let message = self.message;
let message_clone = message.clone();
let id = format!("copy-button-{}", message_clone);
let copied = cx
.read_from_clipboard()
.map(|item| item.text().as_ref() == Some(&message_clone.into()))
.unwrap_or(false);
let (icon, color, tooltip) = if copied {
(IconName::Check, Color::Success, "Copied!".into())
} else {
(IconName::Copy, Color::Muted, self.tooltip_label)
};
let custom_on_click = self.custom_on_click;
let visible_on_hover = self.visible_on_hover;
let button = IconButton::new(id, icon)
.icon_color(color)
.icon_size(self.icon_size)
.disabled(self.disabled)
.tooltip(Tooltip::text(tooltip))
.on_click(move |_, window, cx| {
if let Some(custom_on_click) = custom_on_click.as_ref() {
(custom_on_click)(window, cx);
} else {
cx.stop_propagation();
cx.write_to_clipboard(ClipboardItem::new_string(message.clone().into()));
}
});
if let Some(visible_on_hover) = visible_on_hover {
button.visible_on_hover(visible_on_hover)
} else {
button
}
}
}
impl Component for CopyButton {
fn scope() -> ComponentScope {
ComponentScope::Input
}
fn description() -> Option<&'static str> {
Some("An icon button that encapsulates the logic to copy a string into the clipboard.")
}
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
let label_text = "Here's an example label";
let mut counter: usize = 0;
let mut copy_b = || {
counter += 1;
CopyButton::new(format!(
"Here's an example label (id for uniqueness: {} — ignore this)",
counter
))
};
let example = vec![
single_example(
"Default",
h_flex()
.gap_1()
.child(Label::new(label_text).size(LabelSize::Small))
.child(copy_b())
.into_any_element(),
),
single_example(
"Multiple Icon Sizes",
h_flex()
.gap_1()
.child(Label::new(label_text).size(LabelSize::Small))
.child(copy_b().icon_size(IconSize::XSmall))
.child(copy_b().icon_size(IconSize::Medium))
.child(copy_b().icon_size(IconSize::XLarge))
.into_any_element(),
),
single_example(
"Custom Tooltip Label",
h_flex()
.gap_1()
.child(Label::new(label_text).size(LabelSize::Small))
.child(copy_b().tooltip_label("Custom tooltip label"))
.into_any_element(),
),
single_example(
"Visible On Hover",
h_flex()
.group("container")
.gap_1()
.child(Label::new(label_text).size(LabelSize::Small))
.child(copy_b().visible_on_hover("container"))
.into_any_element(),
),
];
Some(example_group(example).vertical().into_any_element())
}
}

View file

@ -1,9 +1,9 @@
use crate::{SuppressNotification, Toast, Workspace};
use anyhow::Context as _;
use gpui::{
AnyView, App, AppContext as _, AsyncWindowContext, ClickEvent, ClipboardItem, Context,
DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, PromptLevel, Render, ScrollHandle,
Task, TextStyleRefinement, UnderlineStyle, svg,
AnyView, App, AppContext as _, AsyncWindowContext, ClickEvent, Context, DismissEvent, Entity,
EventEmitter, FocusHandle, Focusable, PromptLevel, Render, ScrollHandle, Task,
TextStyleRefinement, UnderlineStyle, svg,
};
use markdown::{Markdown, MarkdownElement, MarkdownStyle};
use parking_lot::Mutex;
@ -13,7 +13,7 @@ use theme::ThemeSettings;
use std::ops::Deref;
use std::sync::{Arc, LazyLock};
use std::{any::TypeId, time::Duration};
use ui::{Tooltip, prelude::*};
use ui::{CopyButton, Tooltip, prelude::*};
use util::ResultExt;
#[derive(Default)]
@ -308,16 +308,8 @@ impl Render for LanguageServerPrompt {
h_flex()
.gap_1()
.child(
IconButton::new("copy", IconName::Copy)
.on_click({
let message = request.message.clone();
move |_, _, cx| {
cx.write_to_clipboard(
ClipboardItem::new_string(message.clone()),
)
}
})
.tooltip(Tooltip::text("Copy Description")),
CopyButton::new(request.message.clone())
.tooltip_label("Copy Description"),
)
.child(
IconButton::new(close_id, close_icon)