diff --git a/demo/src/app.rs b/demo/src/app.rs index 5351827..c7f4575 100644 --- a/demo/src/app.rs +++ b/demo/src/app.rs @@ -86,13 +86,14 @@ fn TheRouter() -> impl IntoView { + - - }.into_inner()} {view!{ + + @@ -107,10 +108,10 @@ fn TheRouter() -> impl IntoView { - - }.into_inner()} {view!{ + + }.into_inner()} diff --git a/demo/src/pages/components.rs b/demo/src/pages/components.rs index 27454eb..d7567ea 100644 --- a/demo/src/pages/components.rs +++ b/demo/src/pages/components.rs @@ -220,6 +220,10 @@ pub(crate) fn gen_nav_data() -> Vec { value: "/components/layout", label: "Layout", }, + NavItemOption { + value: "/components/link", + label: "Link", + }, NavItemOption { value: "/components/loading-bar", label: "Loading Bar", diff --git a/demo_markdown/src/lib.rs b/demo_markdown/src/lib.rs index 4d3bcd4..c83793b 100644 --- a/demo_markdown/src/lib.rs +++ b/demo_markdown/src/lib.rs @@ -47,6 +47,7 @@ pub fn include_md(_token_stream: proc_macro::TokenStream) -> proc_macro::TokenSt "ImageMdPage" => "../../thaw/src/image/docs/mod.md", "InputMdPage" => "../../thaw/src/input/docs/mod.md", "LayoutMdPage" => "../../thaw/src/layout/docs/mod.md", + "LinkMdPage" => "../../thaw/src/link/docs/mod.md", "LoadingBarMdPage" => "../../thaw/src/loading_bar/docs/mod.md", "MenuMdPage" => "../../thaw/src/menu/docs/mod.md", "MessageBarMdPage" => "../../thaw/src/message_bar/docs/mod.md", diff --git a/demo_markdown/src/markdown/mod.rs b/demo_markdown/src/markdown/mod.rs index 904737f..7555099 100644 --- a/demo_markdown/src/markdown/mod.rs +++ b/demo_markdown/src/markdown/mod.rs @@ -160,9 +160,9 @@ fn iter_nodes<'a>( let NodeLink { url, title } = node_link; quote!( - + #(#children)* - + ) } NodeValue::Image(_) => quote!("Image todo!!!"), diff --git a/thaw/src/lib.rs b/thaw/src/lib.rs index e93188a..7c222e5 100644 --- a/thaw/src/lib.rs +++ b/thaw/src/lib.rs @@ -24,6 +24,7 @@ mod icon; mod image; mod input; mod layout; +mod link; mod loading_bar; mod menu; mod message_bar; @@ -75,6 +76,7 @@ pub use icon::*; pub use image::*; pub use input::*; pub use layout::*; +pub use link::*; pub use loading_bar::*; pub use menu::*; pub use message_bar::*; diff --git a/thaw/src/link/docs/mod.md b/thaw/src/link/docs/mod.md new file mode 100644 index 0000000..030efaf --- /dev/null +++ b/thaw/src/link/docs/mod.md @@ -0,0 +1,79 @@ +# Link + +```rust demo +view! { + + + "This is a link" + + + "This is a link" + + + "This is a link" + + +} +``` + +### Inline + +```rust demo +view! { +
+ "This is an " + + "inline link" + + " used alongside other text." +
+} +``` + +### Disabled + +```rust demo +view! { + + + "This is a link" + + + "This is a link" + + + "This is a link" + + +} +``` + +### Disabled Focusable + +```rust demo +view! { + + + "This is a link" + + + "This is a link" + + + "This is a link" + + +} +``` + +### Link Props + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| class | `MaybeProp` | `Default::default()` | | +| span | `bool` | `false` | | +| href | `Option>` | `None` | | +| inline | `MaybeSignal` | `false` | If true, changes styling when the link is being used alongside other text content. | +| disabled | `MaybeSignal` | `false` | Whether the link is disabled. | +| disabled_focusable | `MaybeSignal` | `false` | When set, allows the link to be focusable even when it has been disabled. | +| children | `Children` | | | diff --git a/thaw/src/link/link.css b/thaw/src/link/link.css new file mode 100644 index 0000000..bc976c2 --- /dev/null +++ b/thaw/src/link/link.css @@ -0,0 +1,69 @@ +.thaw-link { + display: inline; + background-color: transparent; + color: var(--colorBrandForegroundLink); + font-size: inherit; + font-weight: var(--fontWeightRegular); + font-family: var(--fontFamilyBase); + text-align: left; + overflow: inherit; + padding: 0px; + margin: 0px; + user-select: text; + text-overflow: inherit; + text-decoration-thickness: var(--strokeWidthThin); + text-decoration-line: none; + box-sizing: border-box; + cursor: pointer; +} + +.thaw-link--disabled { + color: var(--colorNeutralForegroundDisabled); +} + +button.thaw-link { + border-style: none; + font-size: var(--fontSizeBase300); +} + +span.thaw-link, +.thaw-link--inline { + text-decoration-line: underline; +} + +.thaw-link:hover { + color: var(--colorBrandForegroundLinkHover); + text-decoration-line: underline; +} + +.thaw-link--disabled:hover { + color: var(--colorNeutralForegroundDisabled); +} + +.thaw-link--disabled:not(span):hover { + text-decoration-line: none; +} + +.thaw-link:active { + color: var(--colorBrandForegroundLinkPressed); + text-decoration-line: underline; +} + +.thaw-link--disabled:active { + color: var(--colorNeutralForegroundDisabled); +} + +.thaw-link--disabled:not(span):active { + text-decoration-line: none; +} + +.thaw-link:focus-visible { + outline-style: none; +} + +.thaw-link:not(.thaw-link--disabled):focus-visible, +.thaw-link--disabled-focusable:focus-visible { + text-decoration-style: double; + text-decoration-line: underline; + text-decoration-color: var(--colorStrokeFocus2); +} diff --git a/thaw/src/link/link.rs b/thaw/src/link/link.rs new file mode 100644 index 0000000..551879b --- /dev/null +++ b/thaw/src/link/link.rs @@ -0,0 +1,70 @@ +use leptos::{either::EitherOf3, prelude::*}; +use thaw_utils::{class_list, mount_style}; + +#[component] +pub fn Link( + #[prop(optional, into)] class: MaybeProp, + #[prop(optional)] span: bool, + /// If true, changes styling when the link is being used alongside other text content. + #[prop(optional, into)] + inline: MaybeSignal, + #[prop(optional, into)] href: Option>, + /// Whether the link is disabled. + #[prop(optional, into)] + disabled: MaybeSignal, + /// When set, allows the link to be focusable even when it has been disabled. + #[prop(optional, into)] + disabled_focusable: MaybeSignal, + children: Children, +) -> impl IntoView { + mount_style("link", include_str!("./link.css")); + + let link_disabled = Memo::new(move |_| disabled.get() || disabled_focusable.get()); + let class = class_list![ + "thaw-link", + ("thaw-link--inline", move || inline.get()), + ("thaw-link--disabled", move || link_disabled.get()), + ("thaw-link--disabled-focusable", move || link_disabled.get()), + class + ]; + + let tabindex = Memo::new(move |_| { + if disabled_focusable.get() { + Some("0") + } else if disabled.get() { + Some("-1") + } else { + None + } + }); + + if let Some(href) = href { + EitherOf3::A(view! { + + {children()} + + }) + } else if span { + EitherOf3::B(view! { + + {children()} + + }) + } else { + EitherOf3::C(view! { + + }) + } +} diff --git a/thaw/src/link/mod.rs b/thaw/src/link/mod.rs new file mode 100644 index 0000000..6c0f628 --- /dev/null +++ b/thaw/src/link/mod.rs @@ -0,0 +1,3 @@ +mod link; + +pub use link::*; diff --git a/thaw/src/theme/color.rs b/thaw/src/theme/color.rs index ad7bf07..a962fbc 100644 --- a/thaw/src/theme/color.rs +++ b/thaw/src/theme/color.rs @@ -68,6 +68,9 @@ pub struct ColorTheme { pub color_brand_stroke_1: String, pub color_brand_stroke_2: String, pub color_brand_stroke_2_contrast: String, + pub color_brand_foreground_link: String, + pub color_brand_foreground_link_hover: String, + pub color_brand_foreground_link_pressed: String, pub color_stroke_focus_2: String, @@ -188,6 +191,9 @@ impl ColorTheme { color_brand_stroke_1: "#0f6cbd".into(), color_brand_stroke_2: "#b4d6fa".into(), color_brand_stroke_2_contrast: "#b4d6fa".into(), + color_brand_foreground_link: "#115ea3".into(), + color_brand_foreground_link_hover: "#0f548c".into(), + color_brand_foreground_link_pressed: "#0c3b5e".into(), color_stroke_focus_2: "#000000".into(), @@ -308,6 +314,9 @@ impl ColorTheme { color_brand_stroke_1: "#479ef5".into(), color_brand_stroke_2: "#0e4775".into(), color_brand_stroke_2_contrast: "#0e4775".into(), + color_brand_foreground_link: "#479ef5".into(), + color_brand_foreground_link_hover: "#62abf5".into(), + color_brand_foreground_link_pressed: "#2886de".into(), color_stroke_focus_2: "#ffffff".into(),