Compare commits
3 Commits
release-20
...
release-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48d8a8e0ee | ||
|
|
945f4718eb | ||
|
|
6a2952c6f3 |
@@ -25,6 +25,8 @@
|
||||
<link data-trunk rel="copy-file" href="manifest.json" />
|
||||
<link data-trunk rel="copy-file" href="icon-192.png" />
|
||||
<link data-trunk rel="copy-file" href="icon-512.png" />
|
||||
<link data-trunk rel="copy-file" href="public/lock_scroll.js" />
|
||||
<script src="/lock_scroll.js"></script>
|
||||
<link data-trunk rel="copy-file" href="sw.js" />
|
||||
<script>
|
||||
(function () {
|
||||
|
||||
@@ -237,8 +237,6 @@ pub fn ContextMenuContent(
|
||||
let target_id_for_script = ctx.target_id.clone();
|
||||
|
||||
view! {
|
||||
<script src="/lock_scroll.js"></script>
|
||||
|
||||
<div
|
||||
data-name="ContextMenuContent"
|
||||
class=class
|
||||
|
||||
@@ -280,8 +280,6 @@ pub fn DropdownMenuContent(
|
||||
};
|
||||
|
||||
view! {
|
||||
<script src="/hooks/lock_scroll.js"></script>
|
||||
|
||||
<div
|
||||
data-name="DropdownMenuContent"
|
||||
class=class
|
||||
|
||||
@@ -180,8 +180,6 @@ pub fn MultiSelectContent(children: Children, #[prop(optional, into)] class: Str
|
||||
let (on_scroll, can_scroll_up_signal, can_scroll_down_signal) = use_can_scroll_vertical();
|
||||
|
||||
view! {
|
||||
<script src="/lock_scroll.js"></script>
|
||||
|
||||
<div
|
||||
data-name="MultiSelectContent"
|
||||
class=class
|
||||
|
||||
@@ -172,8 +172,6 @@ pub fn SelectContent(
|
||||
let (on_scroll, can_scroll_up_signal, can_scroll_down_signal) = use_can_scroll_vertical();
|
||||
|
||||
view! {
|
||||
<script src="/lock_scroll.js"></script>
|
||||
|
||||
<div
|
||||
data-name="SelectContent"
|
||||
class=merged_class
|
||||
|
||||
@@ -45,6 +45,7 @@ pub fn SonnerTrigger(
|
||||
index: usize,
|
||||
total: usize,
|
||||
position: SonnerPosition,
|
||||
is_expanded: Signal<bool>,
|
||||
#[prop(optional)] on_dismiss: Option<Callback<()>>,
|
||||
) -> impl IntoView {
|
||||
let variant_classes = match toast.variant {
|
||||
@@ -56,23 +57,39 @@ pub fn SonnerTrigger(
|
||||
ToastType::Loading => "bg-background text-foreground border-border",
|
||||
};
|
||||
|
||||
// Sonner Stacking Logic
|
||||
let inverse_index = index;
|
||||
let offset = inverse_index as f64 * 12.0;
|
||||
let scale = 1.0 - (inverse_index as f64 * 0.05);
|
||||
let opacity = if inverse_index > 2 { 0.0 } else { 1.0 - (inverse_index as f64 * 0.15) };
|
||||
|
||||
let is_bottom = position.to_string().contains("Bottom");
|
||||
let y_direction = if is_bottom { -1.0 } else { 1.0 };
|
||||
let translate_y = offset * y_direction;
|
||||
let bar_color = match toast.variant {
|
||||
ToastType::Success => "bg-green-500",
|
||||
ToastType::Error => "bg-destructive",
|
||||
ToastType::Warning => "bg-yellow-500",
|
||||
ToastType::Info => "bg-blue-500",
|
||||
_ => "bg-primary",
|
||||
};
|
||||
|
||||
let style = format!(
|
||||
"z-index: {}; transform: translateY({}px) scale({}); opacity: {};",
|
||||
total - index,
|
||||
translate_y,
|
||||
scale,
|
||||
opacity
|
||||
);
|
||||
// Stacking & Expansion Logic
|
||||
let style = move || {
|
||||
let is_bottom = position.to_string().contains("Bottom");
|
||||
let y_direction = if is_bottom { -1.0 } else { 1.0 };
|
||||
|
||||
let (translate_y, scale, opacity) = if is_expanded.get() {
|
||||
// Expanded state: Full list layout
|
||||
let y = index as f64 * 70.0; // height + gap
|
||||
(y * y_direction, 1.0, 1.0)
|
||||
} else {
|
||||
// Stacked state: Sonner look
|
||||
let y = index as f64 * 10.0;
|
||||
let s = 1.0 - (index as f64 * 0.05);
|
||||
let o = if index > 2 { 0.0 } else { 1.0 - (index as f64 * 0.2) };
|
||||
(y * y_direction, s, o)
|
||||
};
|
||||
|
||||
format!(
|
||||
"z-index: {}; transform: translateY({}px) scale({}); opacity: {};",
|
||||
total - index,
|
||||
translate_y,
|
||||
scale,
|
||||
opacity
|
||||
)
|
||||
};
|
||||
|
||||
let icon = match toast.variant {
|
||||
ToastType::Success => Some(view! { <span class="icon font-bold">"✓"</span> }.into_any()),
|
||||
@@ -85,9 +102,9 @@ pub fn SonnerTrigger(
|
||||
view! {
|
||||
<div
|
||||
class=tw_merge!(
|
||||
"absolute transition-all duration-300 ease-in-out cursor-pointer pointer-events-auto",
|
||||
"absolute transition-all duration-300 ease-in-out cursor-pointer pointer-events-auto overflow-hidden",
|
||||
"flex items-center gap-3 w-full max-w-[calc(100vw-2rem)] sm:max-w-[380px] p-4 rounded-lg border shadow-lg bg-card",
|
||||
if is_bottom { "bottom-0" } else { "top-0" },
|
||||
if position.to_string().contains("Bottom") { "bottom-0" } else { "top-0" },
|
||||
variant_classes
|
||||
)
|
||||
style=style
|
||||
@@ -98,10 +115,19 @@ pub fn SonnerTrigger(
|
||||
}
|
||||
>
|
||||
{icon}
|
||||
<div class="flex flex-col gap-0.5 overflow-hidden">
|
||||
<div class="flex flex-col gap-0.5 overflow-hidden flex-1">
|
||||
<div class="text-sm font-semibold truncate leading-tight">{toast.title}</div>
|
||||
{move || toast.description.as_ref().map(|d| view! { <div class="text-xs opacity-70 truncate">{d.clone()}</div> })}
|
||||
</div>
|
||||
|
||||
// Progress Bar
|
||||
<div
|
||||
class=tw_merge!("absolute bottom-0 left-0 h-1 w-full opacity-20", bar_color)
|
||||
style=format!(
|
||||
"animation: sonner-progress {}ms linear forwards; transform-origin: left;",
|
||||
toast.duration
|
||||
)
|
||||
/>
|
||||
</div>
|
||||
}.into_any()
|
||||
}
|
||||
@@ -122,21 +148,23 @@ pub fn Toaster(#[prop(default = SonnerPosition::default())] position: SonnerPosi
|
||||
let toasts = store.toasts;
|
||||
let is_hovered = RwSignal::new(false);
|
||||
|
||||
let (container_class, mobile_class) = match position {
|
||||
SonnerPosition::TopLeft => ("left-6 top-6 items-start", "left-4 top-4"),
|
||||
SonnerPosition::TopRight => ("right-6 top-6 items-end", "right-4 top-4"),
|
||||
SonnerPosition::TopCenter => ("left-1/2 -translate-x-1/2 top-6 items-center", "left-1/2 -translate-x-1/2 top-4"),
|
||||
SonnerPosition::BottomCenter => ("left-1/2 -translate-x-1/2 bottom-6 items-center", "left-1/2 -translate-x-1/2 bottom-4"),
|
||||
SonnerPosition::BottomLeft => ("left-6 bottom-6 items-start", "left-4 bottom-4"),
|
||||
SonnerPosition::BottomRight => ("right-6 bottom-6 items-end", "right-4 bottom-4"),
|
||||
let container_class = match position {
|
||||
SonnerPosition::TopLeft => "left-6 top-6 items-start",
|
||||
SonnerPosition::TopRight => "right-6 top-6 items-end",
|
||||
SonnerPosition::TopCenter => "left-1/2 -translate-x-1/2 top-6 items-center",
|
||||
SonnerPosition::BottomCenter => "left-1/2 -translate-x-1/2 bottom-6 items-center",
|
||||
SonnerPosition::BottomLeft => "left-6 bottom-6 items-start",
|
||||
SonnerPosition::BottomRight => "right-6 bottom-6 items-end",
|
||||
};
|
||||
|
||||
view! {
|
||||
<style>
|
||||
"@keyframes sonner-progress { from { transform: scaleX(1); } to { transform: scaleX(0); } }"
|
||||
</style>
|
||||
<div
|
||||
class=tw_merge!(
|
||||
"fixed z-[100] flex flex-col pointer-events-none min-h-[100px] w-full sm:w-[400px]",
|
||||
container_class,
|
||||
// Safe areas for mobile
|
||||
"pb-[env(safe-area-inset-bottom)] pt-[env(safe-area-inset-top)] px-4 sm:px-0"
|
||||
)
|
||||
on:mouseenter=move |_| is_hovered.set(true)
|
||||
@@ -152,29 +180,17 @@ pub fn Toaster(#[prop(default = SonnerPosition::default())] position: SonnerPosi
|
||||
let id = toast.id;
|
||||
let total = toasts.with(|t| t.len());
|
||||
|
||||
let expanded_style = move || {
|
||||
if is_hovered.get() {
|
||||
let offset = index as f64 * 64.0;
|
||||
let is_bottom = position.to_string().contains("Bottom");
|
||||
let y_dir = if is_bottom { -1.0 } else { 1.0 };
|
||||
format!("transform: translateY({}px) scale(1); opacity: 1;", offset * y_dir)
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
};
|
||||
|
||||
view! {
|
||||
<div class="contents" style=expanded_style>
|
||||
<SonnerTrigger
|
||||
toast=toast
|
||||
index=index
|
||||
total=total
|
||||
position=position
|
||||
on_dismiss=Callback::new(move |_| {
|
||||
toasts.update(|vec| vec.retain(|t| t.id != id));
|
||||
})
|
||||
/>
|
||||
</div>
|
||||
<SonnerTrigger
|
||||
toast=toast
|
||||
index=index
|
||||
total=total
|
||||
position=position
|
||||
is_expanded=is_hovered.into()
|
||||
on_dismiss=Callback::new(move |_| {
|
||||
toasts.update(|vec| vec.retain(|t| t.id != id));
|
||||
})
|
||||
/>
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user