Skip to main content

hypersig_ui/
alert.rs

1//! Alerts: a message that stands out from the content around it.
2
3use hypersig::html::{El, div};
4use hypersig::style::Style;
5use hypersig::style::pattern::stack;
6use hypersig::style::value::{BorderStyle, FontWeight, px, rem};
7
8use crate::look;
9use crate::token;
10
11/// The look and the urgency of an alert.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13#[non_exhaustive]
14pub enum AlertVariant {
15    /// Information, read out when the screen reader is idle (`role="status"`).
16    Default,
17    /// An error, read out at once (`role="alert"`).
18    Destructive,
19}
20
21/// A bordered box for a message, holding an [`alert_title`] and an
22/// [`alert_description`].
23///
24/// Both roles are live regions. A screen reader reads a `role="alert"` box
25/// when a patch puts it on the page, as for a server error. A
26/// `role="status"` box is read when its text changes, and some screen
27/// readers skip one that arrives with its text already in it, so keep a
28/// status box on the page and change its text with `bind_text`.
29///
30/// ```
31/// use hypersig::Signals;
32/// use hypersig_ui::{AlertVariant, alert, alert_description, alert_title};
33/// use serde::{Deserialize, Serialize};
34///
35/// #[derive(Signals, Serialize, Deserialize)]
36/// struct App {}
37///
38/// let failed = alert::<App>(AlertVariant::Destructive)
39///     .child(alert_title().text("Payment failed"))
40///     .child(alert_description().text("The card was declined."));
41/// assert!(failed.render().contains(r#"role="alert""#));
42/// ```
43#[must_use]
44pub fn alert<S>(variant: AlertVariant) -> El<S> {
45    let (role, color) = match variant {
46        AlertVariant::Default => ("status", token::card_foreground().var()),
47        AlertVariant::Destructive => ("alert", token::destructive().var()),
48    };
49    div().attr("role", role).css(
50        stack(rem(0.125))
51            .padding_block(rem(0.75))
52            .padding_inline(rem(1))
53            .border_width(px(1))
54            .border_style(BorderStyle::Solid)
55            .border_color(token::border().var())
56            .border_radius(token::radius().var())
57            .background_color(token::card().var())
58            .color(color)
59            .merge(look::small_text()),
60    )
61}
62
63/// The first line of an [`alert`].
64#[must_use]
65pub fn alert_title<S>() -> El<S> {
66    div().css(Style::new().font_weight(FontWeight::Number(500.0)))
67}
68
69/// The text under the title of an [`alert`].
70#[must_use]
71pub fn alert_description<S>() -> El<S> {
72    div().css(Style::new().color(token::muted_foreground().var()))
73}