tablo_ui/components/primitives/alert.rs
1// SYNC: topcoat-ui-registry@0.12.0 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}