Skip to main content

tablo_ui/components/primitives/
alert.rs

1// SYNC: topcoat-ui-registry@0.11.0 sha256:47c33b40d0a966b88a113ad13b6c17d9c3f104128b45b7fea297eb2a0b9c5f9c — do not hand-edit. Sync via `cargo xtask sync-topcoat-ui` (ADR-0007).
2use topcoat::{
3    Result,
4    view::{Attributes, Child, StaticClass, View, class, component, view},
5};
6
7/// The visual style of an [`alert`].
8///
9/// [`Default`] is `AlertVariant::Neutral`, used when no variant is given.
10#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
11#[allow(dead_code)]
12pub enum AlertVariant {
13    /// A plain alert for informational notices.
14    #[default]
15    Neutral,
16    /// A destructive-colored alert for errors and failures.
17    Destructive,
18}
19
20impl AlertVariant {
21    /// Classes for the variant's border and text colors.
22    fn classes(self) -> StaticClass {
23        match self {
24            Self::Neutral => class!("border-border text-foreground"),
25            Self::Destructive => class!("border-destructive/50 text-destructive"),
26        }
27    }
28}
29
30/// Classes for the alert layout. The icon column collapses when no icon is present.
31const BASE: StaticClass = class!(
32    "grid w-full grid-cols-[0_1fr] items-start gap-y-1 rounded-lg border",
33    "bg-background px-4 py-3 text-sm has-[>svg]:grid-cols-[1rem_1fr] has-[>svg]:gap-x-3",
34    "[&>svg]:size-4 [&>svg]:translate-y-0.5",
35);
36
37/// A notice displayed within the page.
38///
39/// Use `variant` to choose its style. Pass an optional icon, an `alert_title`, and an
40/// `alert_description` as children. `attrs` are forwarded to the `<div>`, with extra
41/// classes added to its classes.
42///
43/// ```ignore
44/// view! {
45///     alert(
46///         variant: AlertVariant::Destructive,
47///         icon(data: iconify_icon!("lucide:triangle-alert"))
48///         alert_title("Build failed")
49///         alert_description("The last deploy did not finish.")
50///     )
51/// }
52/// ```
53#[component]
54pub async fn alert(
55    /// The visual style of the notice.
56    #[default]
57    variant: AlertVariant,
58    /// Extra attributes for the `<div>` element.
59    #[default]
60    mut attrs: Attributes,
61    /// The alert's icon, title, and description.
62    #[default]
63    child: Child<'_>,
64) -> Result<impl View> {
65    // `role="alert"` is deliberately absent: it interrupts a screen reader
66    // the moment the element appears, which suits a message arriving during
67    // the visit, not one rendered with the page. Pass it among the `attrs`
68    // where that is what you want.
69    Ok(view! {
70        <div class=(class!(BASE, variant.classes(), attrs.remove("class"))) (attrs)>
71            (child)
72        </div>
73    })
74}
75
76/// The heading of an alert.
77#[component]
78pub async fn alert_title(
79    #[default] mut attrs: Attributes,
80    #[default] child: Child<'_>,
81) -> Result<impl View> {
82    Ok(view! {
83        <p
84            class=(class!(
85                "col-start-2 font-medium tracking-tight",
86                attrs.remove("class"),
87            ))
88            (attrs)
89        >
90            (child)
91        </p>
92    })
93}
94
95/// Text that explains the alert and any action the reader should take.
96#[component]
97pub async fn alert_description(
98    #[default] mut attrs: Attributes,
99    #[default] child: Child<'_>,
100) -> Result<impl View> {
101    Ok(view! {
102        <div
103            class=(class!(
104                "col-start-2 text-sm text-muted-foreground",
105                attrs.remove("class"),
106            ))
107            (attrs)
108        >
109            (child)
110        </div>
111    })
112}