tablo_ui/components/composites/
toast.rs1use topcoat::{
4 Result,
5 icon::icon,
6 view::{Attributes, Child, StaticClass, View, attributes, class, component, view},
7};
8
9use crate::icons;
10
11const TOASTER: StaticClass = class!(
13 "pointer-events-none fixed right-4 bottom-4 z-50 flex w-[356px] \
14 max-w-[calc(100vw-2rem)] flex-col gap-3.5 sm:right-6 sm:bottom-6",
15);
16
17const TOAST: StaticClass = class!(
22 "pointer-events-auto relative flex w-full items-center gap-1.5 rounded-lg \
23 border border-border bg-background p-4 text-[13px] text-foreground shadow-lg \
24 [overflow-wrap:anywhere] \
25 invisible opacity-0 starting:visible starting:translate-y-full starting:opacity-100 \
26 [transition:translate_400ms_cubic-bezier(0.25,0.1,0.25,1),opacity_400ms_ease_4s,visibility_0s_linear_4.4s] \
27 hover:visible hover:opacity-100 hover:[transition-delay:0s] \
28 focus-within:visible focus-within:opacity-100 focus-within:[transition-delay:0s] \
29 has-[[data-dismiss]:checked]:hidden \
30 focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:outline-none",
31);
32
33const ICON: StaticClass = class!("flex size-4 shrink-0 items-center justify-center");
35
36const CONTENT: StaticClass = class!("flex min-w-0 flex-1 flex-col gap-0.5");
38
39const TITLE: StaticClass = class!("font-medium leading-normal");
41
42const DESCRIPTION: StaticClass = class!("leading-snug text-muted-foreground");
44
45const CLOSE: StaticClass = class!(
47 "absolute top-0 left-0 flex size-5 -translate-x-[35%] -translate-y-[35%] \
48 cursor-pointer items-center justify-center rounded-full border border-border \
49 bg-background text-foreground transition-colors hover:bg-muted",
50);
51
52#[component]
74pub async fn toaster(
75 #[into]
77 label: String,
78 #[default] mut attrs: Attributes,
79 #[default] child: Child<'_>,
80) -> Result<impl View> {
81 Ok(view! {
82 <section
83 aria-label=(label)
84 tabindex="-1"
85 aria-live="polite"
86 aria-relevant="additions text"
87 aria-atomic="false"
88 >
89 <ol
90 class=(class!(TOASTER, attrs.remove("class")))
91 data-sonner-toaster=""
92 data-y-position="bottom"
93 data-x-position="right"
94 (attrs)
95 >
96 (child)
97 </ol>
98 </section>
99 })
100}
101
102#[component]
104pub async fn toast(
105 #[default] mut attrs: Attributes,
106 #[default] child: Child<'_>,
107) -> Result<impl View> {
108 Ok(view! {
109 <li
110 tabindex="0"
111 class=(class!(TOAST, attrs.remove("class")))
112 data-sonner-toast=""
113 data-styled="true"
114 data-visible="true"
115 data-y-position="bottom"
116 data-x-position="right"
117 data-front="true"
118 (attrs)
119 >
120 (child)
121 </li>
122 })
123}
124
125#[component]
127pub async fn toast_icon(
128 #[default] mut attrs: Attributes,
129 #[default] child: Child<'_>,
130) -> Result<impl View> {
131 Ok(view! {
132 <div
133 class=(class!(ICON, attrs.remove("class")))
134 data-icon=""
135 aria-hidden="true"
136 (attrs)
137 >
138 (child)
139 </div>
140 })
141}
142
143#[component]
145pub async fn toast_content(
146 #[default] mut attrs: Attributes,
147 #[default] child: Child<'_>,
148) -> Result<impl View> {
149 Ok(view! {
150 <div class=(class!(CONTENT, attrs.remove("class"))) data-content="" (attrs)>
151 (child)
152 </div>
153 })
154}
155
156#[component]
158pub async fn toast_title(
159 #[default] mut attrs: Attributes,
160 #[default] child: Child<'_>,
161) -> Result<impl View> {
162 Ok(view! {
163 <div class=(class!(TITLE, attrs.remove("class"))) data-title="" (attrs)>
164 (child)
165 </div>
166 })
167}
168
169#[component]
171pub async fn toast_description(
172 #[default] mut attrs: Attributes,
173 #[default] child: Child<'_>,
174) -> Result<impl View> {
175 Ok(view! {
176 <div
177 class=(class!(DESCRIPTION, attrs.remove("class")))
178 data-description=""
179 (attrs)
180 >
181 (child)
182 </div>
183 })
184}
185
186#[component]
188pub async fn toast_close(
189 #[into]
191 label: String,
192 #[default] mut attrs: Attributes,
193) -> Result<impl View> {
194 Ok(view! {
195 <label
196 class=(class!(
197 CLOSE,
198 "has-focus-visible:ring-2 has-focus-visible:ring-ring/50",
199 attrs.remove("class"),
200 ))
201 data-close-button=""
202 (attrs)
203 >
204 <input type="checkbox" class="sr-only" aria-label=(label) data-dismiss="">
205 icon(data: icons::X, attrs: attributes! { class="size-3" })
206 </label>
207 })
208}