Skip to content

Some Leptos Components #55

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Jul 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[package]
name = "rustlanges-components"
version = "0.1.0"
edition = "2024"

[lib]
path = "crates/lib.rs"
Expand All @@ -9,7 +10,8 @@ path = "crates/lib.rs"
leptos = ["leptos-components"]

[workspace]
members = ["crates/leptos"]
members = [ "crates/core", "crates/leptos" ]

[dependencies]
components-core = { package = "rustlanges-components-core", version = "0.1.0", path = "./crates/core" }
leptos-components = { path = "./crates/leptos", optional = true }
7 changes: 7 additions & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "rustlanges-components-core"
version = "0.1.0"
edition = "2024"

[dependencies]
constcat = "0.6.1"
3 changes: 3 additions & 0 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub const BASE_CLASS: &str = "rustlanges";

pub use constcat::{concat, concat_bytes, concat_slices};
1 change: 1 addition & 0 deletions crates/leptos/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ edition = "2024"

[dependencies]
leptos = { version = "0.8.2", default-features = false }
components-core = { package = "rustlanges-components-core", version = "0.1.0", path = "../core" }
tailwind_fuse = { version = "0.3", features = ["variant", "tailwind_fuse_macro"] }
22 changes: 22 additions & 0 deletions crates/leptos/src/avatar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use components_core::{BASE_CLASS, concat};
use leptos::prelude::*;

#[component]
pub fn Avatar(
#[prop(into)] url: String,
#[prop(into, optional)] alt: String,
#[prop(into, optional, default = 32)] size: i32,
#[prop(into, optional)] class: String,
) -> impl IntoView {
let class = crate::tw!(concat!(BASE_CLASS, "-avatar"), class);

view! {
<div
class={class}
style:width={format!("{size}px")}
style:height={format!("{size}px")}
>
<img class="rustlanges-avatar__img" src={url} alt={alt} />
</div>
}
}
69 changes: 69 additions & 0 deletions crates/leptos/src/badge.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use components_core::{BASE_CLASS, concat};
use leptos::prelude::*;
use leptos::{IntoView, component, view};

const LIMIT_NUMERIC: isize = 9;
const LIMIT_NUMERIC_NEGATIVE: isize = -9;

#[derive(Default, Debug, PartialEq)]
pub enum Variant {
Completed,
Reading,
#[default]
Pending,
Unread,
}

impl Variant {
pub fn text(&self) -> &'static str {
match self {
Variant::Completed => "Completo",
Variant::Reading => "Leyendo",
Variant::Pending => "Pendiente",
Variant::Unread => "No leido",
}
}
}

#[derive(Default, Debug, PartialEq)]
pub enum Type {
#[default]
Default,
Numeric,
Text,
}

#[component]
pub fn Badge(
#[prop(into)] count: ReadSignal<isize>,
#[prop(into, optional)] variant: Variant,
#[prop(into, optional)] r#type: Type,
) -> impl IntoView {
let class = crate::tw!(
concat!("text-paragraph-2 ", BASE_CLASS, "-badge"),
format!("{BASE_CLASS}-badge--variant-{variant:?}"),
format!("{BASE_CLASS}-badge--type-{type:?}"),
);

let display_value = move || match r#type {
Type::Default => count.get().to_string(),
Type::Numeric => {
let count = count.get();
match count {
..=LIMIT_NUMERIC_NEGATIVE => LIMIT_NUMERIC_NEGATIVE.to_string(),
LIMIT_NUMERIC.. => format!("+{LIMIT_NUMERIC}"),
_ => count.to_string(),
}
}
Type::Text => variant.text().to_string(),
};

view! {
<div
class={class}
>
<div class={concat!(BASE_CLASS, "-badge__dot")} />
<span>{display_value}</span>
</div>
}
}
36 changes: 36 additions & 0 deletions crates/leptos/src/button.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use components_core::{BASE_CLASS, concat};
use leptos::prelude::*;
use tailwind_fuse::{AsTailwindClass, TwVariant};

#[derive(TwVariant, PartialEq)]
pub enum Variant {
#[tw(default, class = "rustlanges-button--primary")]
Primary,
#[tw(class = "rustlanges-button--secondary")]
Secondary,
#[tw(class = "rustlanges-button--text")]
Text,
#[tw(class = "rustlanges-button--icon")]
Icon,
}

#[component]
pub fn Button(
#[prop(into, optional)] variant: Variant,
#[prop(into, optional)] label: String,
#[prop(into, optional)] class: String,
#[prop(into)] icon: Option<Children>,
) -> impl IntoView {
let class = crate::tw!(
variant,
concat!("text-button ", BASE_CLASS, "-button"),
class
);

view! {
<button class={class}>
{(variant != Variant::Icon).then_some(label)}
{(variant == Variant::Icon).then(|| icon.map(|icon| icon())).flatten()}
</button>
}
}
23 changes: 23 additions & 0 deletions crates/leptos/src/card.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use components_core::{BASE_CLASS, concat};
use leptos::prelude::*;

#[component]
pub fn Card(
#[prop(into)] child: Children,
#[prop(into, optional)] class: String,
#[prop(into, optional, default = false)] clickable: bool,
#[prop(into, optional, default = false)] disabled: bool,
) -> impl IntoView {
let class = crate::tw!(
concat!(BASE_CLASS, "-card"),
clickable.then_some(concat!(BASE_CLASS, "-card--clickable")),
disabled.then_some(concat!("disabled ", BASE_CLASS, "-card--disabled")),
class
);

view! {
<div class={class}>
{child()}
</div>
}
}
45 changes: 45 additions & 0 deletions crates/leptos/src/chip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use components_core::{BASE_CLASS, concat};
use leptos::prelude::*;
use leptos::{IntoView, component, view};

use crate::icons::{Location, StarBold};

#[derive(Default, Debug, PartialEq)]
pub enum Variant {
#[default]
Featured,
Numeric,
Description,
Location,
Small,
}

impl Variant {
pub fn icon(&self) -> Option<AnyView> {
match self {
Variant::Featured => Some(view! { <StarBold /> }.into_any()),
Variant::Small | Variant::Location => Some(view! { <Location /> }.into_any()),
_ => None,
}
}
}

#[component]
pub fn Chip(
#[prop(into)] label: String,
#[prop(into, optional)] class: String,
#[prop(into, optional)] variant: Variant,
) -> impl IntoView {
let class = crate::tw!(
concat!(BASE_CLASS, "-chip"),
format!("{BASE_CLASS}-chip--{variant:?}"),
class
);

view! {
<div class=class>
{variant.icon()}
{(variant != Variant::Numeric).then_some(format!("#{label}")).unwrap_or(label)}
</div>
}
}
53 changes: 53 additions & 0 deletions crates/leptos/src/flap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use components_core::{BASE_CLASS, concat};
use leptos::prelude::*;
use leptos::{IntoView, component, view};
use tailwind_fuse::{AsTailwindClass, TwVariant};

use crate::icons::StarBold;

#[derive(TwVariant, PartialEq)]
pub enum Variant {
#[tw(default, class = "text-primary-400")]
Highlight,
#[tw(class = "text-primary-200")]
Numeric,
#[tw(class = "text-secondary-400")]
Descritive,
}

#[component]
pub fn Flap(
#[prop(into)] label: String,
#[prop(into, optional)] variant: Variant,
#[prop(into, optional)] class: String,
) -> impl IntoView {
let class = crate::tw!(concat!(BASE_CLASS, "-flap"), class);

view! {
<div title={label.clone()} class={class}>
<svg
viewBox="0 0 145 49"
fill="none"
preserveAspectRatio="none"
class={crate::tw!(concat!(BASE_CLASS, "-flap__svg"), variant)}
>
<path
d="M120.962 5.00869L141.872 30.4082C147.78 37.5847 142.676 48.3997 133.38 48.3997L12.488 48.3996C3.19249 48.3996 -1.91244 37.5847 3.99561 30.4082L24.906 5.00869C26.9955 2.47056 30.1108 1.00009 33.3984 1.00009L112.47 1.0001C115.757 1.0001 118.872 2.47057 120.962 5.00869Z"
fill="currentColor"
stroke="black"
/>
</svg>
<span
class={crate::tw!(
concat!(BASE_CLASS, "-flap__view"),
(variant == Variant::Highlight).then_some(concat!(BASE_CLASS, "-flap__view--icon")),
)}
>
{(variant == Variant::Highlight).then_some(view! { <StarBold /> })}
<span class={concat!("text-paragraph-2 ", BASE_CLASS, "-flap__view-text")}>
{label.clone()}
</span>
</span>
</div>
}
}
53 changes: 53 additions & 0 deletions crates/leptos/src/icons.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
mod alert;
mod arrow_down;
mod arrow_left;
mod arrow_right;
mod arrow_up;
mod book;
mod close;
mod discord;
mod ferris;
mod file;
mod filter;
mod github;
mod link;
mod linkedin;
mod location;
mod menu;
mod moon;
mod project;
mod roadmap;
mod search;
mod share;
mod star_bold;
mod sun_line;
mod telegram;
mod twitter;
mod youtube;

pub use alert::Alert;
pub use arrow_down::ArrowDown;
pub use arrow_left::ArrowLeft;
pub use arrow_right::ArrowRight;
pub use arrow_up::ArrowUp;
pub use book::Book;
pub use close::Close;
pub use discord::Discord;
pub use ferris::Ferris;
pub use file::File;
pub use filter::Filter;
pub use github::Github;
pub use link::Link;
pub use linkedin::Linkedin;
pub use location::Location;
pub use menu::Menu;
pub use moon::Moon;
pub use project::Project;
pub use roadmap::Roadmap;
pub use search::Search;
pub use share::Share;
pub use star_bold::StarBold;
pub use sun_line::SunLine;
pub use telegram::Telegram;
pub use twitter::Twitter;
pub use youtube::Youtube;
19 changes: 19 additions & 0 deletions crates/leptos/src/icons/alert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use leptos::prelude::*;

#[component]
pub fn Alert(#[prop(into, optional, default = 24)] size: u32) -> impl IntoView {
view! {
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12.0001 16H12.0081M12.0001 9.99997V13M10.5751 5.21697L3.51705 17C3.37163 17.252 3.29472 17.5377 3.29395 17.8287C3.29319 18.1197 3.3686 18.4058 3.51269 18.6586C3.65679 18.9114 3.86454 19.1221 4.1153 19.2697C4.36606 19.4173 4.65109 19.4967 4.94205 19.5H19.0581C19.3491 19.497 19.6343 19.4178 19.8853 19.2702C20.1362 19.1227 20.3441 18.912 20.4883 18.6591C20.6324 18.4062 20.7078 18.1199 20.7069 17.8288C20.706 17.5377 20.6288 17.252 20.4831 17L13.4261 5.21697C13.2776 4.9719 13.0685 4.76925 12.8189 4.6286C12.5692 4.48796 12.2876 4.41406 12.0011 4.41406C11.7145 4.41406 11.4329 4.48796 11.1832 4.6286C10.9336 4.76925 10.7245 4.9719 10.5761 5.21697"
fill="currentColor"
/>
</svg>
}
}
Loading