introduce lepty-scoping, which is more effective and more nice - especially with leptos
This commit is contained in:
parent
6aab34b751
commit
1e04148df2
9 changed files with 162 additions and 35 deletions
3
.github/workflows/cbt.yml
vendored
3
.github/workflows/cbt.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1012,6 +1012,7 @@ version = "0.0.1-alpha.1"
|
|||
dependencies = [
|
||||
"base64",
|
||||
"blake3",
|
||||
"cfg-if",
|
||||
"const_format",
|
||||
"grass",
|
||||
"lightningcss",
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ sabry_intrnl = {workspace = true}
|
|||
|
||||
[features]
|
||||
default = ["procmacro"]
|
||||
lepty-scoping = [
|
||||
"sabry_intrnl/lepty-scoping"
|
||||
]
|
||||
procmacro = [
|
||||
"dep:sabry_procmacro"
|
||||
]
|
||||
|
|
|
|||
69
README.md
69
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 */
|
||||
<h1>"Head"</h1>
|
||||
<p>"text"</p>
|
||||
}
|
||||
}
|
||||
```
|
||||
... 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,
|
||||
<h1>
|
||||
"Head"
|
||||
<img src="whatever"/>
|
||||
</h1>
|
||||
<ul class=scope::breadcumbs>
|
||||
<li class=scope::___item(scope::breadcumbs)>
|
||||
"Home page"
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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! {
|
||||
<section class=s::table>
|
||||
view! {class = STYLE,
|
||||
<section class=style::table>
|
||||
<h1>"Welcome to Leptos!"</h1>
|
||||
<button class=s::btn on:click=on_click>"Ima CRAZY button, clicked: " {count} " times!"</button>
|
||||
<span class=s2::card>"DISCLAIMER:"</span>
|
||||
<p class=s2::pocwarn>"This isn't a design example! Its just proof of sabry's capabilities."</p>
|
||||
<p class=s2::pocwarn>"Plz dont make websites/apps which look like this page :D"</p>
|
||||
<button class=style::btn on:click=on_click>"Ima CRAZY button, clicked: " {count} " times!"</button>
|
||||
<button class=style::_dark(style::btn) on:click=on_click>"Ima CRAZY button, clicked: " {count} " times!"</button>
|
||||
<span class=style::card>"DISCLAIMER:"</span>
|
||||
<p class=style::pocwarn>"This isn't a design example! Its just proof of sabry's capabilities."</p>
|
||||
<p class=style::pocwarn>"Plz dont make websites/apps which look like this page :D"</p>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
|
|
|
|||
|
|
@ -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<String> {
|
||||
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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue