From d1c19c591213b03c8e3c3fd347d3034050ce7ea6 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Wed, 31 Jul 2024 14:24:57 +0800 Subject: [PATCH] Add Link (#91) --- README.md | 1 + crates/app/src/story_workspace.rs | 12 +-- crates/story/src/lib.rs | 4 +- .../src/{checkbox_story.rs => text_story.rs} | 21 ++++- crates/ui/src/lib.rs | 1 + crates/ui/src/link.rs | 86 +++++++++++++++++++ crates/ui/src/theme.rs | 5 ++ 7 files changed, 118 insertions(+), 12 deletions(-) rename crates/story/src/{checkbox_story.rs => text_story.rs} (89%) create mode 100644 crates/ui/src/link.rs diff --git a/README.md b/README.md index fa55f4f4..ed97beb1 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ A UI components for building desktop application by using [GPUI](https://gpui.rs - [x] IconButton - [x] Glost / Outline Button - [x] Loading +- [x] Link - [x] Label - [x] Icon - [x] Checkbox diff --git a/crates/app/src/story_workspace.rs b/crates/app/src/story_workspace.rs index 49a419fe..3f2d428b 100644 --- a/crates/app/src/story_workspace.rs +++ b/crates/app/src/story_workspace.rs @@ -1,9 +1,9 @@ use gpui::*; use prelude::FluentBuilder as _; use story::{ - ButtonStory, CheckboxStory, DropdownStory, IconStory, ImageStory, InputStory, ListStory, - PickerStory, PopoverStory, ProgressStory, ResizableStory, ScrollableStory, StoryContainer, - SwitchStory, TableStory, TooltipStory, + ButtonStory, DropdownStory, IconStory, ImageStory, InputStory, ListStory, PickerStory, + PopoverStory, ProgressStory, ResizableStory, ScrollableStory, StoryContainer, SwitchStory, + TableStory, TextStory, TooltipStory, }; use workspace::{TitleBar, Workspace}; @@ -60,9 +60,9 @@ impl StoryWorkspace { .detach(); StoryContainer::add_pane( - "Checkbox", - "A control that allows the user to toggle between checked and not checked.", - CheckboxStory::view(cx).into(), + "Text", + "Links, paragraphs, checkboxes, and more.", + TextStory::view(cx).into(), workspace.clone(), cx, ) diff --git a/crates/story/src/lib.rs b/crates/story/src/lib.rs index 541c1bf3..26224a9a 100644 --- a/crates/story/src/lib.rs +++ b/crates/story/src/lib.rs @@ -1,5 +1,4 @@ mod button_story; -mod checkbox_story; mod dropdown_story; mod icon_story; mod image_story; @@ -12,11 +11,11 @@ mod resizable_story; mod scrollable_story; mod switch_story; mod table_story; +mod text_story; mod tooltip_story; mod webview_story; pub use button_story::ButtonStory; -pub use checkbox_story::CheckboxStory; pub use dropdown_story::DropdownStory; pub use icon_story::IconStory; pub use image_story::ImageStory; @@ -29,6 +28,7 @@ pub use resizable_story::ResizableStory; pub use scrollable_story::ScrollableStory; pub use switch_story::SwitchStory; pub use table_story::TableStory; +pub use text_story::TextStory; pub use tooltip_story::TooltipStory; pub use webview_story::WebViewStory; diff --git a/crates/story/src/checkbox_story.rs b/crates/story/src/text_story.rs similarity index 89% rename from crates/story/src/checkbox_story.rs rename to crates/story/src/text_story.rs index afcd4705..fc80a1a4 100644 --- a/crates/story/src/checkbox_story.rs +++ b/crates/story/src/text_story.rs @@ -8,13 +8,14 @@ use ui::{ checkbox::Checkbox, h_flex, label::Label, + link::Link, radio::Radio, v_flex, Clickable, Disableable as _, IconName, Selection, StyledExt, }; use crate::section; -pub struct CheckboxStory { +pub struct TextStory { check1: Selection, check2: Selection, check3: Selection, @@ -23,7 +24,7 @@ pub struct CheckboxStory { masked: bool, } -impl CheckboxStory { +impl TextStory { pub(crate) fn new(_cx: &mut WindowContext) -> Self { Self { check1: Selection::Unselected, @@ -45,7 +46,7 @@ impl CheckboxStory { } } -impl Render for CheckboxStory { +impl Render for TextStory { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { v_flex() .gap_6() @@ -74,7 +75,19 @@ impl Render for CheckboxStory { .text_left() .line_height(rems(1.8)), ), - ), + ) + + ) + .child( + section("Link", cx).child( + h_flex().items_start().gap_3() + .child(Link::new("link1").href("https://github.com").child("GitHub")) + .child(Link::new("link2").href("https://github.com").text_color(ui::red_500()).text_decoration_color(ui::red_500()).child("Red Link")) + .child(Link::new("link3").child(h_flex().gap_1().child(IconName::GitHub).child("GitHub")).on_click(cx.listener(|_, _, cx| { + cx.open_url("https://google.com") + }))) + .child(div().w(px(250.)).child(Link::new("link4").child("https://github.com/huacnlee/gpui-component").href("https://github.com/huacnlee/gpui-component"))) + ) ) .child( section("Maksed Label", cx).child( diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs index 08838a3c..01c38597 100644 --- a/crates/ui/src/lib.rs +++ b/crates/ui/src/lib.rs @@ -17,6 +17,7 @@ pub mod dropdown; pub mod indicator; pub mod input; pub mod label; +pub mod link; pub mod list; pub mod popover; pub mod popup_menu; diff --git a/crates/ui/src/link.rs b/crates/ui/src/link.rs new file mode 100644 index 00000000..bc83500e --- /dev/null +++ b/crates/ui/src/link.rs @@ -0,0 +1,86 @@ +use gpui::{ + div, ClickEvent, Div, ElementId, InteractiveElement, IntoElement, MouseButton, ParentElement, + RenderOnce, SharedString, Stateful, StatefulInteractiveElement, Styled, +}; + +use crate::theme::{ActiveTheme as _, Colorize}; + +#[derive(IntoElement)] +pub struct Link { + base: Stateful
, + href: Option, + on_click: Option>, +} + +impl Link { + pub fn new(id: impl Into) -> Self { + Self { + base: div().id(id), + href: None, + on_click: None, + } + } + + pub fn href(mut self, href: impl Into) -> Self { + self.href = Some(href.into()); + self + } + + pub fn on_click( + mut self, + handler: impl Fn(&ClickEvent, &mut gpui::WindowContext) + 'static, + ) -> Self { + self.on_click = Some(Box::new(handler)); + self + } +} + +impl Styled for Link { + fn style(&mut self) -> &mut gpui::StyleRefinement { + self.base.style() + } +} + +impl ParentElement for Link { + fn extend(&mut self, elements: impl IntoIterator) { + self.base.extend(elements) + } +} + +impl RenderOnce for Link { + fn render(self, cx: &mut gpui::WindowContext) -> impl IntoElement { + let href = self.href.clone(); + let on_click = self.on_click; + + div() + .text_color(cx.theme().link) + .text_decoration_1() + .text_decoration_color(cx.theme().link) + .hover(|this| { + this.text_color(cx.theme().link.opacity(0.8)) + .text_decoration_1() + }) + .cursor_pointer() + .child( + self.base + .active(|this| { + this.text_color(cx.theme().link.opacity(0.6)) + .text_decoration_1() + }) + .on_mouse_down(MouseButton::Left, |_, cx| { + cx.prevent_default(); + cx.stop_propagation(); + }) + .on_click({ + move |e, cx| { + if let Some(href) = &href { + cx.open_url(&href.clone()); + } + if let Some(on_click) = &on_click { + on_click(e, cx); + } + } + }), + ) + } +} diff --git a/crates/ui/src/theme.rs b/crates/ui/src/theme.rs index ea465e11..aa4234e7 100644 --- a/crates/ui/src/theme.rs +++ b/crates/ui/src/theme.rs @@ -161,6 +161,7 @@ struct Colors { pub list_even: Hsla, pub list_active: Hsla, pub list_head: Hsla, + pub link: Hsla, } impl Colors { @@ -201,6 +202,7 @@ impl Colors { list_even: hsl(240.0, 5.0, 96.0), list_active: hsl(240.0, 7., 88.0), list_head: hsl(240.0, 0., 94.), + link: hsl(221.0, 83.0, 53.0), } } @@ -241,6 +243,7 @@ impl Colors { list_even: hsl(240.0, 3.7, 8.0), list_active: hsl(240.0, 3.7, 15.0), list_head: hsl(240.0, 3.7, 10.9), + link: hsl(221.0, 83.0, 53.0), } } } @@ -304,6 +307,7 @@ pub struct Theme { pub table_head: Hsla, pub table_active: Hsla, pub table_hover: Hsla, + pub link: Hsla, } impl Global for Theme {} @@ -379,6 +383,7 @@ impl From for Theme { table_even: colors.list_even, table_active: colors.list_active, table_hover: colors.list_active.opacity(0.6), + link: colors.link, } } }