Skip to content

Commit

Permalink
feat(web): provide a Button component
Browse files Browse the repository at this point in the history
  • Loading branch information
LeoBorai committed Jun 14, 2024
1 parent 6460e22 commit 695672f
Show file tree
Hide file tree
Showing 16 changed files with 240 additions and 1 deletion.
43 changes: 43 additions & 0 deletions Cargo.lock

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

6 changes: 5 additions & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ e2e_tests *args='':

# Runs formatting tool against Leptos source
web-fmt:
leptosfmt ./crates/web/src/*.rs
leptosfmt ./crates/web/src/*.rs

# Runs Web UI for Development
web-dev:
Expand All @@ -38,3 +38,7 @@ web-dev:
# Builds Web UI for Production
web-build:
cd ./crates/web && trunk build --release --locked --config ./Trunk.toml

# Runs Web Crate Tests
web-test:
cd ./crates/web && wasm-pack test --headless --firefox
5 changes: 5 additions & 0 deletions crates/web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@ path = "src/bin/main.rs"
leptos = { workspace = true, features = ["csr"] }
leptos_meta = { workspace = true }
leptos_router = { workspace = true, features = ["csr"] }

[dev-dependencies]
wasm-bindgen = "0.2"
wasm-bindgen-test = "0.3"
web-sys = "0.3"
20 changes: 20 additions & 0 deletions crates/web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,26 @@ rustup target add wasm32-unknown-unknown
just web-dev
```

### Running Tests

Tests runs in a Headless Browser using `wasm-bindgen-test` and `wasm-bindgen-test-runner`

You must install `wasm-pack`:

```bash
cargo install wasm-pack
```

Then run the tests using `just web-test` command

```bash
just web-test
```

> The first time the Headless Browser is installed to the temporal directory
> for your system. Then it is cached for future runs, so expect a bit of delay
> the first time you run the tests.
## License

Licensed under the MIT License
1 change: 1 addition & 0 deletions crates/web/public/styles.css
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
68 changes: 68 additions & 0 deletions crates/web/src/components/button.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use std::collections::HashSet;

use leptos::{component, create_memo, view, Children, IntoView, MaybeProp, SignalGet, TextProp};

use super::helper::maybe_children::MaybeChildren;

#[derive(Clone, Debug, Default)]
pub enum ButtonVariant {
Text,
#[default]
Contained,
Outlined,
}

#[component]
pub fn Button(
#[prop(optional, into)] id: TextProp,
#[prop(optional, into)] disabled: MaybeProp<bool>,
#[prop(optional, into)] variant: MaybeProp<ButtonVariant>,
#[prop(optional)] children: Option<Children>,
) -> impl IntoView {
let class_names = create_memo(move |_| {
let mut classes: HashSet<&str> = HashSet::new();

// Default Classes
classes.insert("px-4");
classes.insert("py-2");
classes.insert("rounded-md");
classes.insert("cursor-pointer");
classes.insert("font-semibold");
classes.insert("text-sm");

if let Some(is_disabled) = disabled.get() {
if is_disabled {
classes.insert("opacity-70");
classes.insert("!cursor-not-allowed");
}
}

match variant.get().unwrap_or_default() {
ButtonVariant::Text => {
classes.insert("btn-text");
classes.insert("bg-transparent");
}
ButtonVariant::Contained => {
classes.insert("btn-contained");
classes.insert("bg-purple-500");
classes.insert("text-white");
}
ButtonVariant::Outlined => {
classes.insert("btn-outlined");
classes.insert("bg-transparent");
classes.insert("border-2");
classes.insert("border-purple-500");
}
}

classes.into_iter().collect::<Vec<&str>>().join(" ")
});

view! {
<button id={id} class={class_names} disabled=move || disabled.get()>
<MaybeChildren value=children let:children>
{children()}
</MaybeChildren>
</button>
}
}
25 changes: 25 additions & 0 deletions crates/web/src/components/helper/maybe_children.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use leptos::*;

#[slot]
pub struct Fallback {
children: ChildrenFn,
}

#[component]
pub fn MaybeChildren<T, CF, IV>(
value: Option<T>,
children: CF,
#[prop(optional)] fallback: Option<Fallback>,
) -> impl IntoView
where
CF: FnOnce(T) -> IV + 'static,
IV: IntoView,
{
if let Some(value) = value {
children(value).into_view()
} else if let Some(fallback) = fallback {
(fallback.children)().into_view()
} else {
().into_view()
}
}
1 change: 1 addition & 0 deletions crates/web/src/components/helper/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod maybe_children;
3 changes: 3 additions & 0 deletions crates/web/src/components/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod helper;

pub mod button;
1 change: 1 addition & 0 deletions crates/web/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod components;
pub mod views;

use leptos::{component, view, IntoView};
Expand Down
5 changes: 5 additions & 0 deletions crates/web/src/views/home.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use leptos::{component, view, IntoView};

use crate::components::button::{Button, ButtonVariant};

#[component]
pub fn Home() -> impl IntoView {
view! {
<section class="flex flex-col justify-center items-center">
<img src="/assets/img/townhall.png" alt="TownHall AI Generated" height="500" width="500" />
<h1>{"AI Generated Picture of a TownHall"}</h1>
<Button variant={ButtonVariant::Text}>{"Text"}</Button>
<Button variant={ButtonVariant::Contained}>{"Contained"}</Button>
<Button variant={ButtonVariant::Outlined}>{"Outlined"}</Button>
</section>
}
}
8 changes: 8 additions & 0 deletions crates/web/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.rs'],
theme: {
extend: {}
},
plugins: []
};
43 changes: 43 additions & 0 deletions crates/web/tests/components/button.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use leptos::{mount_to, view};

use wasm_bindgen::JsCast;
use wasm_bindgen_test::*;

use web::components::button::{Button, ButtonVariant};

wasm_bindgen_test_configure!(run_in_browser);

#[wasm_bindgen_test]
fn default_text_button_class_names_integrity() {
let document = leptos::document();
let test_wrapper = document.create_element("div").unwrap();
let _ = document.body().unwrap().append_child(&test_wrapper);

mount_to(
test_wrapper.clone().unchecked_into(),
|| view! { <Button id="btn-text" variant={ButtonVariant::Text}>"Click Me!"</Button> },
);

let button_el = test_wrapper
.query_selector("#btn-text")
.expect("Failed to access document")
.expect("Button Element not found")
.unchecked_into::<web_sys::HtmlButtonElement>();

let want_class_names = vec![
"px-4",
"py-2",
"rounded-md",
"cursor-pointer",
"font-semibold",
"text-sm",
"btn-text",
"bg-transparent"
];

let have_class_names = button_el.get_attribute("class").unwrap();

for class_name in want_class_names.iter() {
assert!(have_class_names.contains(class_name));
}
}
1 change: 1 addition & 0 deletions crates/web/tests/components/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod button;
1 change: 1 addition & 0 deletions crates/web/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod components;
10 changes: 10 additions & 0 deletions crates/web/webdriver.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"moz:firefoxOptions": {
"args": [
"-headless"
],
"log": {
"level": "trace"
}
}
}

0 comments on commit 695672f

Please sign in to comment.