日本語

Conditions

Style::on applies part of a style only under a condition. The condition can be a pseudo-class, a media feature, a container width, or the state of a marked ancestor or sibling.

Pseudo-classes, media, and containers

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 and PseudoElement are generated from MDN's data. pseudo puts declarations on a pseudo-element. A pseudo-class written inside it applies to the element itself, so pseudo(Before, Style::new().on(Hover, ..)) becomes :hover::before.
  • The user-action pseudo-classes are written to the stylesheet in a fixed order. As a result, :active takes precedence over :hover while a button is pressed, and :disabled takes precedence over all of them.
  • Media has the breakpoints Sm, Md, Lg, Xl, and Xxl, min-width queries at 40, 48, 64, 80, and 96rem, the widths Tailwind CSS and Panda CSS use. It also has Dark, Light, ReducedMotion, Print, Landscape, Portrait, CanHover, CoarsePointer, ForcedColors, and MoreContrast.
  • Container has Panda CSS's container sizes from 16rem to 90rem. An ancestor becomes a query container with container_type, and without one the condition never matches.
  • Conditions can be nested. A style passed to on can use on itself, and the inner declarations then apply when both conditions hold.

Group and peer

Style::group marks an ancestor, and Condition::Group(PseudoClass::Hover) applies a style only while that ancestor is hovered. Style::peer marks an earlier sibling, and Condition::Peer(PseudoClass::Checked) applies a style only while that sibling is checked. Both work like the group and peer of Tailwind CSS and Panda CSS.

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"),
    );

Result

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("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(" →"),
                ),
        )
        .child(
            p().css(Style::new().color(token::muted_foreground().var()).on(
                Condition::Group(PseudoClass::Hover),
                Style::new().text_decoration_line(TextDecorationLine::Underline),
            ))
            .text("Hover anywhere on the card."),
        );
    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("I agree to the terms"),
        );
    Page::new(
        "Settings",
        Settings { agreed: false },
    )
    .child(
        div()
            .css(stack(rem(1.25)).max_width(rem(20)))
            .child(card)
            .child(agree),
    )
}