条件
Style::on はスタイルの一部を条件の下でだけ適用します。条件には擬似クラス、メディア特性、コンテナの幅、印を付けた祖先や兄弟の状態を使えます。
擬似クラス、メディア、コンテナ
use hypersig::style::value::{Color, Display, Opacity, pct, quoted, rem};
use hypersig::style::{Container, Media, PseudoClass, PseudoElement, Style};
let link = Style::new()
.color(Color::hex(0x0025_63eb))
// A pseudo-class.
.on(PseudoClass::Hover, Style::new().color(Color::hex(0x001d_4ed8)))
// Conditions nest: hovered and focused at once.
.on(
PseudoClass::Hover,
Style::new().on(PseudoClass::FocusVisible, Style::new().opacity(Opacity::Number(0.8))),
)
// A pseudo-element.
.pseudo(PseudoElement::After, Style::new().content(quoted(" →")));
let column = Style::new()
.width(pct(100))
// From 48rem wide, and from 64rem wide.
.on(Media::Md, Style::new().width(pct(50)))
.on(Media::Lg, Style::new().width(pct(33)))
// The user's settings.
.on(Media::ReducedMotion, Style::new().display(Display::Block))
// The nearest query container, from 24rem wide.
.on(Container::Sm, Style::new().padding(rem(2)));PseudoClassとPseudoElementは MDN のデータから生成しています。pseudoは擬似要素に宣言を付けます。その中に書いた擬似クラスは元の要素に対して適用されるので、pseudo(Before, Style::new().on(Hover, ..))は:hover::beforeになります。- ユーザー操作の擬似クラスは決まった順でスタイルシートに書かれます。そのため、ボタンを押している間は
:activeが:hoverより優先され、:disabledはそのどれよりも優先されます。 MediaにはブレークポイントSm、Md、Lg、Xl、Xxlがあり、それぞれ 40、48、64、80、96rem の min-width のメディアクエリです。幅は Tailwind CSS と Panda CSS が使うものと同じです。ほかにDark、Light、ReducedMotion、Print、Landscape、Portrait、CanHover、CoarsePointer、ForcedColors、MoreContrastがあります。Containerには、Panda CSS と同じ 16rem から 90rem までのコンテナの大きさがあります。祖先はcontainer_typeでクエリコンテナになります。そうした祖先がなければ、条件は一度も当てはまりません。- 条件は入れ子にできます。
onに渡すスタイルの中でもonを使えます。その場合、内側の宣言は両方の条件が成り立つときに適用されます。
group と peer
Style::group は祖先に印を付け、Condition::Group(PseudoClass::Hover) はその祖先がホバーされている間だけスタイルを適用します。Style::peer は前にある兄弟に印を付け、Condition::Peer(PseudoClass::Checked) はその兄弟がチェックされている間だけスタイルを適用します。どちらも Tailwind CSS や Panda CSS の group と peer と同じように動きます。
use hypersig::html::{a, div, input, label, span};
use hypersig::style::value::{Color, Opacity};
use hypersig::style::{Condition, PseudoClass, Style};
use hypersig::{SafeUrl, Signals};
use serde::{Deserialize, Serialize};
#[derive(Signals, Serialize, Deserialize)]
struct Settings {
agreed: bool,
}
// The arrow shows while the pointer is anywhere on the card.
let card = a::<Settings>()
.href(SafeUrl::new("/docs"))
.css(Style::new().group())
.text("Read the docs")
.child(
span()
.css(
Style::new()
.opacity(Opacity::Number(0.0))
.on(Condition::Group(PseudoClass::Hover), Style::new().opacity(Opacity::Number(1.0))),
)
.text(" →"),
);
// The text turns green while the checkbox before it is checked.
let agree = label::<Settings>()
.child(input().attr("type", "checkbox").css(Style::new().peer()).bind(Settings::agreed()))
.child(
span()
.css(Style::new().on(Condition::Peer(PseudoClass::Checked), Style::new().color(Color::GREEN)))
.text("I agree"),
);実行結果
hover.rs
use hypersig::html::{a, div, input, label, p, span};
use hypersig::style::pattern::stack;
use hypersig::style::value::{
BorderStyle, Color, FontWeight, Opacity, TextDecorationLine, px, rem,
};
use hypersig::style::{Condition, PseudoClass, Style};
use hypersig::{Page, SafeUrl, Signals};
use hypersig_ui::token;
use serde::{Deserialize, Serialize};
#[derive(Signals, Serialize, Deserialize)]
pub struct Settings {
agreed: bool,
}
pub fn page() -> Page<Settings> {
let card = a()
.href(SafeUrl::new("#"))
.css(
stack(rem(0.25))
.group()
.padding(rem(1))
.border_width(px(1))
.border_style(BorderStyle::Solid)
.border_color(token::border().var())
.border_radius(px(8))
.on(
PseudoClass::Hover,
Style::new().background_color(token::muted().var()),
),
)
.child(
p().css(Style::new().font_weight(FontWeight::Number(600.0)))
.text("ドキュメントを読む")
.child(
span()
.css(Style::new().opacity(Opacity::Number(0.0)).on(
Condition::Group(PseudoClass::Hover),
Style::new().opacity(Opacity::Number(1.0)),
))
.text(" →"),
),
)
.child(
p().css(Style::new().color(token::muted_foreground().var()).on(
Condition::Group(PseudoClass::Hover),
Style::new().text_decoration_line(TextDecorationLine::Underline),
))
.text("カードのどこかにポインタを乗せてください。"),
);
let agree = label()
.child(
input()
.attr("type", "checkbox")
.css(Style::new().peer())
.bind(Settings::agreed()),
)
.child(
span()
.css(
Style::new().margin_left(rem(0.5)).on(
Condition::Peer(PseudoClass::Checked),
Style::new()
.color(Color::hex(0x0015_803d))
.font_weight(FontWeight::Number(600.0)),
),
)
.text("利用規約に同意します"),
);
Page::new(
"設定",
Settings { agreed: false },
)
.child(
div()
.css(stack(rem(1.25)).max_width(rem(20)))
.child(card)
.child(agree),
)
}