Skip to content

Commit

Permalink
Merge pull request #17 from gaucho-labs/api-improvements
Browse files Browse the repository at this point in the history
Variant API Improvements
  • Loading branch information
nicoburniske committed Apr 2, 2024
2 parents d1e2045 + b5ffaa3 commit e5cef51
Show file tree
Hide file tree
Showing 10 changed files with 473 additions and 370 deletions.
33 changes: 17 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,20 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose --all-features
- name: Run clippy
run: cargo clippy --verbose -- -D warnings
- name: Check README.md
id: check_readme
run: |
cargo install cargo-rdme
cargo rdme --check
continue-on-error: true
- name: Warning for README.md check failure
if: failure() && steps.check_readme.outcome == 'failure'
run: echo "README.md check failed. Please run 'cargo rdme' to update your README.md based on doc comments."
- uses: actions/checkout@v3
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose --all-features
- name: Run clippy
run: cargo clippy --verbose -- -D warnings
- name: Check README.md
run: |
set -e
cargo install cargo-rdme
cargo rdme --check
- name: Warning for README.md check failure
if: ${{ failure() }}
run: |
echo "README.md check failed. Please run 'cargo rdme' to update your README.md based on doc comments."
exit 1
69 changes: 35 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ Two main utils are included in this crate:

## Installation

Variants requires the `variants` feature to be enabled.
Variants requires the `variant` feature to be enabled.

#### With variants
#### With variant
```bash
cargo add tailwind-fuse --features variant
```

#### Without variants
#### Without variant
```bash
cargo add tailwind-fuse
```
Expand All @@ -32,33 +32,23 @@ cargo add tailwind-fuse

You can use [`tw_join!`] to join Tailwind classes, and [`tw_merge!`] to merge Tailwind Classes handling conflicts.


You can use anything that implements [`AsRef<str>`] or [`AsTailwindClass`]

```rust
use tailwind_fuse::*;

// No conflict resolution
// "flex items-center justify-center"
let joined_class = tw_join!("flex items-center", "justify-center");

// You can use Option to handle conditional rendering
// You can pass in &str, String, Option<String>, or Option<&str>
// "text-sm font-bold"
let classes = tw_join!(
"text-sm",
Some("font-bold"),
None::<String>,
Some("ring").filter(|_| false),
Some(" "),
"".to_string(),
);

// Conflict resolution
// Right most class takes precedence
// p-4
let merged_class = tw_merge!("py-2 px-4", "p-4");
assert_eq!("p-4", tw_merge!("py-2 px-4", "p-4"));

// Refinements are permitted
// p-4 py-2
let merged_class = tw_merge!("p-4", "py-2");
assert_eq!("p-4 py-2", tw_merge!("p-4", "py-2"));
```

## Usage: Variants
Expand Down Expand Up @@ -114,29 +104,40 @@ let button = Btn {
size: BtnSize::Default,
color: BtnColor::Blue,
};
// h-9 px-4 py-2 bg-blue-500 text-blue-100
button.to_class();
// Conflicts are resolved.
// h-9 px-4 py-2 text-blue-100 bg-green-500
button.with_class("bg-green-500");

assert_eq!(
"flex h-9 px-4 py-2 bg-blue-500 text-blue-100",
button.to_class()
);

// Conflicts are resolved (bg-blue-500 is knocked out in favor of override)
assert_eq!(
"flex h-9 px-4 py-2 text-blue-100 bg-green-500",
button.with_class("bg-green-500")
);
```

### Builder Syntax
You access the builder using the `variants` method. Every variant that is not provided will be replaced with the default variant.

```rust

// h-8 px-3 bg-red-500 text-red-100
let class = Btn::variant()
.size(BtnSize::Sm)
.color(BtnColor::Red)
.to_class();

// h-8 px-3 text-red-100 bg-green-500
let class = Btn::variant()
.size(BtnSize::Sm)
.color(BtnColor::Red)
.with_class("bg-green-500");
assert_eq!(
"flex h-8 px-3 bg-red-500 text-red-100",
Btn::builder()
.size(BtnSize::Sm)
.color(BtnColor::Red)
.to_class()
);

assert_eq!(
"flex h-8 px-3 text-red-100 bg-green-500",
Btn::builder()
.size(BtnSize::Sm)
.color(BtnColor::Red)
.with_class("bg-green-500")
);

```

#### VSCode Intellisense
Expand Down
4 changes: 2 additions & 2 deletions example/demo/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ fn Header() -> impl IntoView {
<div class="flex items-center justify-between w-full py-4 px-8">
<div class="flex items-center">
<a
class=ButtonClass::variant()
class=ButtonClass::builder()
.size(ButtonSize::Sm)
.variant(ButtonVariant::Link)
.to_class()
Expand All @@ -155,7 +155,7 @@ fn Header() -> impl IntoView {
</a>
<span> / </span>
<a
class=ButtonClass::variant()
class=ButtonClass::builder()
.size(ButtonSize::Sm)
.variant(ButtonVariant::Link)
.to_class()
Expand Down
131 changes: 98 additions & 33 deletions fuse/src/core/join.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,104 @@
/// Joins the given classes into a single string.
///
/// Items can be of type &[`str`], [`String`], [`Option<&str>`] or [`Option<String>`].
/// Items can be of type &[`str`] or [`String`].
///
/// If you want to handle conflicts use [`crate::tw_merge!`].
///
/// If you want a custom type to be used with this macro, implement the [`crate::MaybeIntoTailwindClass`] trait.
/// If you want a custom type to be used with this macro, implement the [`crate::AsTailwindClass`] trait.
#[macro_export]
macro_rules! tw_join {
($item:expr) => {{
use $crate::MaybeIntoTailwindClass;
let tailwind_class = $item.to_tailwind_class();
if let Some(class) = tailwind_class {
class.trim().into()
} else {
String::new()
}
use $crate::AsTailwindClass;
let tailwind_class = $item.as_class();
tailwind_class.trim().to_string()
}};
($a:expr, $b:expr) => {{
use $crate::AsTailwindClass;
let a = $a;
let a_class = a.as_class().trim();
let b = $b;
let b_class = b.as_class().trim();
format!(
"{}{}{}",
a_class,
if b_class.is_empty() { "" } else { " " },
b_class
)
}};
($a:expr, $b:expr, $c:expr) => {{
use $crate::AsTailwindClass;
let a = $a;
let a_class = a.as_class().trim();
let b = $b;
let b_class = b.as_class().trim();
let c = $c;
let c_class = c.as_class().trim();
format!(
"{}{}{}{}{}",
a_class,
if b_class.is_empty() { "" } else { " " },
b_class,
if c_class.is_empty() { "" } else { " " },
c_class
)
}};
($a:expr, $b:expr, $c:expr, $d:expr) => {{
use $crate::AsTailwindClass;
let a = $a;
let a_class = a.as_class().trim();
let b = $b;
let b_class = b.as_class().trim();
let c = $c;
let c_class = c.as_class().trim();
let d = $d;
let d_class = d.as_class().trim();
format!(
"{}{}{}{}{}{}{}",
a_class,
if b_class.is_empty() { "" } else { " " },
b_class,
if c_class.is_empty() { "" } else { " " },
c_class,
if d_class.is_empty() { "" } else { " " },
d_class
)
}};
($a:expr, $b:expr, $c:expr, $d:expr, $e:expr) => {{
use $crate::AsTailwindClass;
let a = $a;
let a_class = a.as_class().trim();
let b = $b;
let b_class = b.as_class().trim();
let c = $c;
let c_class = c.as_class().trim();
let d = $d;
let d_class = d.as_class().trim();
let e = $e;
let e_class = e.as_class().trim();
format!(
"{}{}{}{}{}{}{}{}{}",
a_class,
if b_class.is_empty() { "" } else { " " },
b_class,
if c_class.is_empty() { "" } else { " " },
c_class,
if d_class.is_empty() { "" } else { " " },
d_class,
if e_class.is_empty() { "" } else { " " },
e_class
)
}};
($($item:expr),+ $(,)?) => {{
use $crate::MaybeIntoTailwindClass;
use $crate::AsTailwindClass;
let mut result = String::new();
$(
// Long lived expressions.
let item = $item;
let tailwind_class = item.to_tailwind_class();
if let Some(class) = tailwind_class {
let class = class.trim();
if !class.is_empty() {
if !result.is_empty() { result.push(' '); }
result.push_str(class);
}
let class = $item;
let class = class.as_class();
let class = class.trim();
if !class.is_empty() {
if !result.is_empty() { result.push(' '); }
result.push_str(class);
}
)*
result
Expand All @@ -37,21 +107,16 @@ macro_rules! tw_join {

#[test]
fn test_tw() {
assert_eq!(tw_join!("hello"), "hello");
assert_eq!(tw_join!("one", "two"), "one two")
}
assert_eq!(tw_join!("a"), "a");
assert_eq!(tw_join!("a", "b"), "a b");
assert_eq!(tw_join!("a", "b", "c"), "a b c");
assert_eq!(tw_join!("a", "b", "c", "d"), "a b c d");
assert_eq!(tw_join!("a", "b", "c", "d", "e"), "a b c d e");
assert_eq!(tw_join!("a", "b", "c", "d", "e", "f"), "a b c d e f");

#[test]
fn test_option() {
let classes = tw_join!(
"text-sm",
Some("font-bold"),
Some("ring").filter(|_| false),
None::<String>,
"bg-white",
Some(" "),
"".to_string(),
"italic text-green-500"
assert_eq!(
tw_join!(" one", "two ", " three".to_string()),
"one two three"
);
assert_eq!(classes, "text-sm font-bold bg-white italic text-green-500");
assert_eq!(tw_join!("a", " ", "b", "c", " "), "a b c");
}
18 changes: 7 additions & 11 deletions fuse/src/core/merge/merge_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,17 +110,13 @@ struct Collision<'a> {
impl<'a> Collision<'a> {
fn check_arbitrary(style: AstStyle<'a>) -> Option<Self> {
let arbitrary = style.arbitrary?;
if arbitrary.contains(':') {
let mut parts = arbitrary.split(':');
let collision_id = parts.next()?;
Some(Self {
collision_id,
important: style.important,
variants: style.variants,
})
} else {
None
}
let index = arbitrary.find(':')?;
let (collision_id, _) = arbitrary.split_at(index);
Some(Self {
collision_id,
important: style.important,
variants: style.variants,
})
}
}

Expand Down
33 changes: 9 additions & 24 deletions fuse/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,16 @@ pub mod merge;
/// Used to extract a &str from a type
///
/// Implement this trait for your type to use it with the [`tw_join!`] and [`tw_merge!`] macros
pub trait MaybeIntoTailwindClass<'a> {
/// Attempt to extract a Tailwind class
fn to_tailwind_class(&'a self) -> Option<&'a str>;
pub trait AsTailwindClass {
/// Extract a Tailwind class
fn as_class(&self) -> &str;
}

impl<'a> MaybeIntoTailwindClass<'a> for String {
fn to_tailwind_class(&'a self) -> Option<&'a str> {
Some(self.as_str())
}
}

impl<'a> MaybeIntoTailwindClass<'a> for str {
fn to_tailwind_class(&'a self) -> Option<&'a str> {
Some(self)
}
}

impl<'a> MaybeIntoTailwindClass<'a> for Option<String> {
fn to_tailwind_class(&'a self) -> Option<&'a str> {
self.as_deref()
}
}

impl<'a> MaybeIntoTailwindClass<'a> for Option<&'a str> {
fn to_tailwind_class(&'a self) -> Option<&'a str> {
*self
impl<T> AsTailwindClass for T
where
T: AsRef<str>,
{
fn as_class(&self) -> &str {
self.as_ref()
}
}

0 comments on commit e5cef51

Please sign in to comment.