Skip to main content

tablo_ui/components/primitives/
alert.rs

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