diff --git a/.github/workflows/cbt.yml b/.github/workflows/cbt.yml
index aed0fdd..b8773f0 100644
--- a/.github/workflows/cbt.yml
+++ b/.github/workflows/cbt.yml
@@ -6,7 +6,7 @@ on:
pull_request:
branches: ["window"]
-env:
+env:
CARGO_TERM_COLOR: always
jobs:
@@ -22,4 +22,5 @@ jobs:
- uses: actions/checkout@v4
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
- run: cargo build --verbose
+ - run: cargo test --verbose --all
- run: cargo test --verbose --all --all-features
diff --git a/Cargo.lock b/Cargo.lock
index 91a3997..bd98667 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1012,6 +1012,7 @@ version = "0.0.1-alpha.1"
dependencies = [
"base64",
"blake3",
+ "cfg-if",
"const_format",
"grass",
"lightningcss",
diff --git a/Cargo.toml b/Cargo.toml
index d244cf6..1cc9c1a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -43,6 +43,9 @@ sabry_intrnl = {workspace = true}
[features]
default = ["procmacro"]
+lepty-scoping = [
+ "sabry_intrnl/lepty-scoping"
+]
procmacro = [
"dep:sabry_procmacro"
]
diff --git a/README.md b/README.md
index ea37bb7..34f0d86 100644
--- a/README.md
+++ b/README.md
@@ -268,6 +268,73 @@ fn render() -> HtmlElement {
```
So the `mixins!` macro we just passed to the `usey!` macro inside of `buildy` function call is now accessible with simple and natural `@use "mixins"` SASS rule!
+### Leptos specials
+
+With [leptos](https://github.com/leptos-rs/leptos) framework you can do the trick to apply some class to most of HtmlElements for the entire component/island:
+
+```rust,ignore
+#[component]
+fn component() -> impl IntoView {
+ view! {class="cls1", /* <- here */
+
"Head"
+
"text"
+ }
+}
+```
+... and have "cls1" on both `h1` and `p` auto-assigned. Isn't this cool?
+
+So, if you're on leptos, and don't mind to take this approach, I'd highly recommend turning the *lepty-scoping* feature on:
+
+```toml
+# Cargo.toml
+
+[dependencies]
+sabry = {version = "0.0.1", features = ["lepty-scoping"]}
+
+[build-dependencies]
+sabry = {version = "0.0.1", features = ["build", "lepty-scoping"]}
+```
+
+```rust,ignore
+styly!(scope {
+ @use "tokens";
+ h1 {
+ @include tokens.sectionhead();
+ img {
+ @include tokens.sectionimg();
+ }
+ }
+ .breadcumbs {
+ @include tokens.badgelist();
+ &__item {
+ @include tokens.badge(secondary);
+ }
+ }
+});
+
+#[component]
+fn component() -> impl IntoView {
+ view! {class=SCOPE,
+
+ "Head"
+
+
+
+
+ "Home page"
+
+
+ }
+}
+```
+
+That will perform *much* better:
+
+- All scope members - like `scope::breadcumbs` - wont contain repeating scope hash, just the original class/id selector
+- You will not encounter [the catch](#scoping) with nested tagname selectors
+
+Also, this isn't really exclusive leptos-supporting feature. It just changes scoping behavour.
+
## Configuration
Sabry configuration lives in `[package.metadata.sabry]` table of the manifest file.
@@ -535,6 +602,8 @@ Different selector types are scoped differently:
>
> Also if there are nested tagname selectors - sabry won't take this into account and you'll
> have to wrap them with `SCOPE` class as well
+>
+> **However** If you are able to apply some class to all html elements you have - like `view!{class=CLASS...}` with Leptos - you could use *lepty-scoping* feature flag for sabry and get rid of this catch! See more [here](#leptos-specials).
As for **SASS parent selectors**: they are currently handled in different way. Instead of
walking up the syntax tree sabry just creates function member for the scope and leave the rest to grass:
diff --git a/examples/leptos-axum/Cargo.toml b/examples/leptos-axum/Cargo.toml
index 1b30dbb..39e1e43 100644
--- a/examples/leptos-axum/Cargo.toml
+++ b/examples/leptos-axum/Cargo.toml
@@ -23,11 +23,11 @@ tracing = { version = "0.1", optional = true }
http = "1"
# sabry with sass
-sabry = {path = "../../"}
+sabry = {path = "../../", features = ["lepty-scoping"]}
[build-dependencies]
# sabry for build with sass
-sabry = {path = "../../", features = ["build"]}
+sabry = {path = "../../", features = ["build", "lepty-scoping"]}
# lets depend on our styles
define-styles = {path = "../define-styles"}
@@ -51,11 +51,12 @@ css.minify = true
css.bundle = "target/.sabry/target/bundle.css"
[package.metadata.leptos]
+# Sabry-specific quality of life config
+style-file = "target/.sabry/target/bundle.css"
+
output-name = "leptos-axum"
site-root = "target/site"
site-pkg-dir = "pkg"
-# Sabry-generated bundle for leptos
-style-file = "target/.sabry/target/bundle.css"
assets-dir = "public"
site-addr = "127.0.0.1:3000"
reload-port = 3001
diff --git a/examples/leptos-axum/src/routes/home.rs b/examples/leptos-axum/src/routes/home.rs
index 0e06caf..1c006db 100644
--- a/examples/leptos-axum/src/routes/home.rs
+++ b/examples/leptos-axum/src/routes/home.rs
@@ -1,18 +1,7 @@
use leptos::prelude::*;
use sabry::styly;
-styly!(s "src/routes/home.scss");
-
-styly!(s2 {
- @use "theme";
- .card {
- @include theme.surface(secondary);
- }
- .pocwarn {
- @include theme.surface(secondary);
- text-transform: uppercase;
- }
-});
+styly!(style "src/routes/home.scss");
/// Renders the home page of your application.
#[component]
@@ -21,13 +10,14 @@ pub fn Route() -> impl IntoView {
let count = RwSignal::new(0);
let on_click = move |_| *count.write() += 1;
- view! {
-
+ view! {class = STYLE,
+
"Welcome to Leptos!"
-
- "DISCLAIMER:"
-
"This isn't a design example! Its just proof of sabry's capabilities."
-
"Plz dont make websites/apps which look like this page :D"
+
+
+ "DISCLAIMER:"
+
"This isn't a design example! Its just proof of sabry's capabilities."
+
"Plz dont make websites/apps which look like this page :D"
}
}
diff --git a/examples/leptos-axum/src/routes/home.scss b/examples/leptos-axum/src/routes/home.scss
index 67e7ec1..d9ef063 100644
--- a/examples/leptos-axum/src/routes/home.scss
+++ b/examples/leptos-axum/src/routes/home.scss
@@ -4,9 +4,20 @@
.btn {
@include tokens.clickable(accent);
+ &-dark {
+ @include tokens.clickable(secondary, 0.8, 1.1);
+ }
}
.table {
@include utils.flex(column, center, center);
@include theme.surface(primary);
}
+
+.card {
+ @include theme.surface(secondary);
+}
+.pocwarn {
+ @include theme.surface(secondary);
+ text-transform: uppercase;
+}
diff --git a/sabry_intrnl/Cargo.toml b/sabry_intrnl/Cargo.toml
index 19d7f51..41c5cbc 100644
--- a/sabry_intrnl/Cargo.toml
+++ b/sabry_intrnl/Cargo.toml
@@ -23,5 +23,7 @@ const_format = {version = "^0.2"}
thiserror = {version = "^1.0"}
serde = {version = "^1.0", features = ["derive"]}
toml = {version = "^0.8"}
+cfg-if = {version = "^1.0"}
[features]
+lepty-scoping = []
diff --git a/sabry_intrnl/src/scoper/mod.rs b/sabry_intrnl/src/scoper/mod.rs
index 658e8f0..0d82a7d 100644
--- a/sabry_intrnl/src/scoper/mod.rs
+++ b/sabry_intrnl/src/scoper/mod.rs
@@ -1,3 +1,4 @@
+use cfg_if::cfg_if;
use hash::ScopeHash;
use raffia::Spanned;
use regex::Regex;
@@ -187,10 +188,22 @@ impl HashedSelector {
// As we dont want to use two HTML props for a single ID,
// ID scoping is done with id modification
ScopedSelector::Id(a) => {
- format!("{}-{}", hash.as_str(), a.ident)
+ cfg_if! {
+ if #[cfg(feature = "lepty-scoping")] {
+ format!("{}.{}", a.ident, hash.as_str())
+ } else {
+ format!("{}-{}", hash.as_str(), a.ident)
+ }
+ }
}
ScopedSelector::Tag(a) => {
- format!(".{} {}", hash.as_str(), a.ident)
+ cfg_if! {
+ if #[cfg(feature = "lepty-scoping")] {
+ format!("{}.{}", a.ident, hash.as_str())
+ } else {
+ format!(".{} {}", hash.as_str(), a.ident)
+ }
+ }
}
}
}
@@ -205,10 +218,26 @@ impl HashedSelector {
pub fn make_hashed_html(value: &ScopedSelector, hash: &ScopeHash) -> Option {
match value {
// Class scoping is done with class composition
- ScopedSelector::Class(a) => Some(format!("{} {}", hash.as_str(), a.ident)),
+ ScopedSelector::Class(a) => {
+ cfg_if! {
+ if #[cfg(feature = "lepty-scoping")] {
+ Some(a.ident.to_string())
+ } else {
+ Some(format!("{} {}", hash.as_str(), a.ident))
+ }
+ }
+ },
// As we dont want to use two HTML props for a single ID,
// ID scoping is done with id modification
- ScopedSelector::Id(a) => Some(format!("{}-{}", hash.as_str(), a.ident)),
+ ScopedSelector::Id(a) => {
+ cfg_if! {
+ if #[cfg(feature = "lepty-scoping")] {
+ Some(a.ident.to_string())
+ } else {
+ Some(format!("{}-{}", hash.as_str(), a.ident))
+ }
+ }
+ },
ScopedSelector::Tag(_) => None,
}
}
@@ -330,6 +359,7 @@ pub fn apply_basic_rusty_member_gen_rules(source: &str) -> String {
mod test {
use std::collections::HashSet;
+ use cfg_if::cfg_if;
use syn::Ident;
use crate::{
@@ -345,14 +375,33 @@ mod test {
".cls1{color:red; &-dark{color: black;} #id1 {color:green;} div {color:blue;}} .cls3#id2{color: black;}";
let hash = ScopeHash::test_init("F2kf8nMs".into());
- let expect_code = ".F2kf8nMs.cls1{color:red; &-dark{color: black;} #F2kf8nMs-id1 {color:green;} .F2kf8nMs div {color:blue;}} .F2kf8nMs.cls3#F2kf8nMs-id2{color: black;}";
- let expect_selector_htmls = HashSet::from([
- "F2kf8nMs cls1".to_string(),
- "F2kf8nMs-id1".to_string(),
- "F2kf8nMs cls3".to_string(),
- "F2kf8nMs-id2".to_string(),
- "".to_string(), // comes from `div` which has no html hashed code, still want to check presense
- ]);
+ cfg_if! {
+ if #[cfg(feature = "lepty-scoping")]{
+ let expect_code = ".F2kf8nMs.cls1{color:red; &-dark{color: black;} #id1.F2kf8nMs {color:green;} div.F2kf8nMs {color:blue;}} .F2kf8nMs.cls3#id2.F2kf8nMs{color: black;}";
+ } else {
+ let expect_code = ".F2kf8nMs.cls1{color:red; &-dark{color: black;} #F2kf8nMs-id1 {color:green;} .F2kf8nMs div {color:blue;}} .F2kf8nMs.cls3#F2kf8nMs-id2{color: black;}";
+ }
+ }
+
+ cfg_if! {
+ if #[cfg(feature = "lepty-scoping")] {
+ let expect_selector_htmls = HashSet::from([
+ "cls1".to_string(),
+ "id1".to_string(),
+ "cls3".to_string(),
+ "id2".to_string(),
+ "".to_string(), // comes from `div` which has no html hashed code, still want to check presense
+ ]);
+ } else {
+ let expect_selector_htmls = HashSet::from([
+ "F2kf8nMs cls1".to_string(),
+ "F2kf8nMs-id1".to_string(),
+ "F2kf8nMs cls3".to_string(),
+ "F2kf8nMs-id2".to_string(),
+ "".to_string(), // comes from `div` which has no html hashed code, still want to check presense
+ ]);
+ }
+ }
let scope = ArbitraryScope::from_source(
raffia::Syntax::Scss,