Compare commits

...

2 Commits

Author SHA1 Message Date
spinline
945f4718eb feat: match bulk action button style with columns button and add progress bar to toasts
All checks were successful
Build MIPS Binary / build (push) Successful in 5m33s
2026-02-12 19:42:32 +03:00
spinline
6a2952c6f3 fix: resolve 404 error for lock_scroll.js by including it in Trunk build and loading globally
All checks were successful
Build MIPS Binary / build (push) Successful in 5m32s
2026-02-12 01:51:38 +03:00
6 changed files with 38 additions and 19 deletions

View File

@@ -25,6 +25,8 @@
<link data-trunk rel="copy-file" href="manifest.json" /> <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-192.png" />
<link data-trunk rel="copy-file" href="icon-512.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" /> <link data-trunk rel="copy-file" href="sw.js" />
<script> <script>
(function () { (function () {

View File

@@ -237,8 +237,6 @@ pub fn ContextMenuContent(
let target_id_for_script = ctx.target_id.clone(); let target_id_for_script = ctx.target_id.clone();
view! { view! {
<script src="/lock_scroll.js"></script>
<div <div
data-name="ContextMenuContent" data-name="ContextMenuContent"
class=class class=class

View File

@@ -280,8 +280,6 @@ pub fn DropdownMenuContent(
}; };
view! { view! {
<script src="/hooks/lock_scroll.js"></script>
<div <div
data-name="DropdownMenuContent" data-name="DropdownMenuContent"
class=class class=class

View File

@@ -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(); let (on_scroll, can_scroll_up_signal, can_scroll_down_signal) = use_can_scroll_vertical();
view! { view! {
<script src="/lock_scroll.js"></script>
<div <div
data-name="MultiSelectContent" data-name="MultiSelectContent"
class=class class=class

View File

@@ -172,8 +172,6 @@ pub fn SelectContent(
let (on_scroll, can_scroll_up_signal, can_scroll_down_signal) = use_can_scroll_vertical(); let (on_scroll, can_scroll_up_signal, can_scroll_down_signal) = use_can_scroll_vertical();
view! { view! {
<script src="/lock_scroll.js"></script>
<div <div
data-name="SelectContent" data-name="SelectContent"
class=merged_class class=merged_class

View File

@@ -56,6 +56,14 @@ pub fn SonnerTrigger(
ToastType::Loading => "bg-background text-foreground border-border", ToastType::Loading => "bg-background text-foreground border-border",
}; };
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",
};
// Sonner Stacking Logic // Sonner Stacking Logic
let inverse_index = index; let inverse_index = index;
let offset = inverse_index as f64 * 12.0; let offset = inverse_index as f64 * 12.0;
@@ -83,9 +91,21 @@ pub fn SonnerTrigger(
}; };
view! { view! {
<style>
"
@keyframes sonner-progress {
from { transform: scaleX(1); }
to { transform: scaleX(0); }
}
.sonner-progress-bar {
animation: sonner-progress linear forwards;
transform-origin: left;
}
"
</style>
<div <div
class=tw_merge!( 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", "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 is_bottom { "bottom-0" } else { "top-0" },
variant_classes variant_classes
@@ -98,10 +118,16 @@ pub fn SonnerTrigger(
} }
> >
{icon} {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> <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> })} {move || toast.description.as_ref().map(|d| view! { <div class="text-xs opacity-70 truncate">{d.clone()}</div> })}
</div> </div>
// Progress Bar
<div
class=tw_merge!("absolute bottom-0 left-0 h-1 w-full sonner-progress-bar opacity-40", bar_color)
style=format!("animation-duration: {}ms;", toast.duration)
/>
</div> </div>
}.into_any() }.into_any()
} }
@@ -122,13 +148,13 @@ pub fn Toaster(#[prop(default = SonnerPosition::default())] position: SonnerPosi
let toasts = store.toasts; let toasts = store.toasts;
let is_hovered = RwSignal::new(false); let is_hovered = RwSignal::new(false);
let (container_class, mobile_class) = match position { let container_class = match position {
SonnerPosition::TopLeft => ("left-6 top-6 items-start", "left-4 top-4"), SonnerPosition::TopLeft => "left-6 top-6 items-start",
SonnerPosition::TopRight => ("right-6 top-6 items-end", "right-4 top-4"), SonnerPosition::TopRight => ("right-6 top-6 items-end"),
SonnerPosition::TopCenter => ("left-1/2 -translate-x-1/2 top-6 items-center", "left-1/2 -translate-x-1/2 top-4"), 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", "left-1/2 -translate-x-1/2 bottom-4"), SonnerPosition::BottomCenter => ("left-1/2 -translate-x-1/2 bottom-6 items-center"),
SonnerPosition::BottomLeft => ("left-6 bottom-6 items-start", "left-4 bottom-4"), SonnerPosition::BottomLeft => ("left-6 bottom-6 items-start"),
SonnerPosition::BottomRight => ("right-6 bottom-6 items-end", "right-4 bottom-4"), SonnerPosition::BottomRight => ("right-6 bottom-6 items-end"),
}; };
view! { view! {
@@ -136,7 +162,6 @@ pub fn Toaster(#[prop(default = SonnerPosition::default())] position: SonnerPosi
class=tw_merge!( class=tw_merge!(
"fixed z-[100] flex flex-col pointer-events-none min-h-[100px] w-full sm:w-[400px]", "fixed z-[100] flex flex-col pointer-events-none min-h-[100px] w-full sm:w-[400px]",
container_class, container_class,
// Safe areas for mobile
"pb-[env(safe-area-inset-bottom)] pt-[env(safe-area-inset-top)] px-4 sm:px-0" "pb-[env(safe-area-inset-bottom)] pt-[env(safe-area-inset-top)] px-4 sm:px-0"
) )
on:mouseenter=move |_| is_hovered.set(true) on:mouseenter=move |_| is_hovered.set(true)
@@ -217,4 +242,4 @@ pub fn toast_error(title: impl Into<String>) { toast(title, ToastType::Error); }
#[allow(dead_code)] #[allow(dead_code)]
pub fn toast_warning(title: impl Into<String>) { toast(title, ToastType::Warning); } pub fn toast_warning(title: impl Into<String>) { toast(title, ToastType::Warning); }
#[allow(dead_code)] #[allow(dead_code)]
pub fn toast_info(title: impl Into<String>) { toast(title, ToastType::Info); } pub fn toast_info(title: impl Into<String>) { toast(title, ToastType::Info); }