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}