Compare commits

...

2 Commits

Author SHA1 Message Date
spinline
9447a66cc1 feat: loading ekranı shadcn Skeleton ile değiştirildi
All checks were successful
Build MIPS Binary / build (push) Successful in 5m16s
- Yükleniyor... spinner yerine uygulamanın layout'unu simüle eden skeleton UI
- Sidebar, header, tablo satırları ve statusbar skeleton'ları
2026-02-11 00:43:05 +03:00
spinline
45247a020e fix: AddTorrent dialog stili düzeltildi, skeleton crate eklendi
Some checks failed
Build MIPS Binary / build (push) Has been cancelled
- Dialog buggy leptos-shadcn-dialog yerine doğrudan shadcn HTML markup ile yeniden yazıldı
- Backdrop overlay, card panel, X close butonu eklendi
- leptos-shadcn-skeleton dependency eklendi
- Tailwind CSS rebuild edildi
2026-02-11 00:40:39 +03:00
5 changed files with 143 additions and 61 deletions

16
Cargo.lock generated
View File

@@ -1274,6 +1274,7 @@ dependencies = [
"leptos-shadcn-scroll-area", "leptos-shadcn-scroll-area",
"leptos-shadcn-separator", "leptos-shadcn-separator",
"leptos-shadcn-sheet", "leptos-shadcn-sheet",
"leptos-shadcn-skeleton",
"leptos-shadcn-tabs", "leptos-shadcn-tabs",
"leptos-shadcn-toast", "leptos-shadcn-toast",
"leptos-shadcn-tooltip", "leptos-shadcn-tooltip",
@@ -2390,6 +2391,21 @@ dependencies = [
"thiserror 1.0.69", "thiserror 1.0.69",
] ]
[[package]]
name = "leptos-shadcn-skeleton"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c14b6bd0f2fe191e3e114a34cee889fc983546ad488e76e76511e3d75ea3f86"
dependencies = [
"leptos",
"leptos-node-ref",
"leptos-shadcn-signal-management",
"leptos-struct-component",
"leptos-style",
"tailwind_fuse",
"web-sys",
]
[[package]] [[package]]
name = "leptos-shadcn-tabs" name = "leptos-shadcn-tabs"
version = "0.8.1" version = "0.8.1"

View File

@@ -51,4 +51,5 @@ leptos-shadcn-label = "0.8"
leptos-shadcn-alert = "0.8" leptos-shadcn-alert = "0.8"
leptos-shadcn-toast = "0.8" leptos-shadcn-toast = "0.8"
leptos-shadcn-dropdown-menu = "0.8" leptos-shadcn-dropdown-menu = "0.8"
leptos-shadcn-tooltip = "0.8" leptos-shadcn-tooltip = "0.8"
leptos-shadcn-skeleton = "0.8"

View File

@@ -79,7 +79,6 @@
--font-weight-normal: 400; --font-weight-normal: 400;
--font-weight-medium: 500; --font-weight-medium: 500;
--font-weight-semibold: 600; --font-weight-semibold: 600;
--font-weight-bold: 700;
--tracking-tight: -0.025em; --tracking-tight: -0.025em;
--tracking-wider: 0.05em; --tracking-wider: 0.05em;
--tracking-widest: 0.1em; --tracking-widest: 0.1em;
@@ -614,9 +613,15 @@
.w-3\/4 { .w-3\/4 {
width: calc(3/4 * 100%); width: calc(3/4 * 100%);
} }
.w-3\/5 {
width: calc(3/5 * 100%);
}
.w-4 { .w-4 {
width: calc(var(--spacing) * 4); width: calc(var(--spacing) * 4);
} }
.w-4\/5 {
width: calc(4/5 * 100%);
}
.w-5 { .w-5 {
width: calc(var(--spacing) * 5); width: calc(var(--spacing) * 5);
} }
@@ -659,9 +664,15 @@
.w-48 { .w-48 {
width: calc(var(--spacing) * 48); width: calc(var(--spacing) * 48);
} }
.w-56 {
width: calc(var(--spacing) * 56);
}
.w-64 { .w-64 {
width: calc(var(--spacing) * 64); width: calc(var(--spacing) * 64);
} }
.w-96 {
width: calc(var(--spacing) * 96);
}
.w-\[100px\] { .w-\[100px\] {
width: 100px; width: 100px;
} }
@@ -857,6 +868,13 @@
margin-block-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse))); margin-block-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)));
} }
} }
.space-y-3 {
:where(& > :not(:last-child)) {
--tw-space-y-reverse: 0;
margin-block-start: calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));
margin-block-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)));
}
}
.space-y-4 { .space-y-4 {
:where(& > :not(:last-child)) { :where(& > :not(:last-child)) {
--tw-space-y-reverse: 0; --tw-space-y-reverse: 0;
@@ -953,10 +971,6 @@
border-style: var(--tw-border-style); border-style: var(--tw-border-style);
border-width: 2px; border-width: 2px;
} }
.border-4 {
border-style: var(--tw-border-style);
border-width: 4px;
}
.border-y { .border-y {
border-block-style: var(--tw-border-style); border-block-style: var(--tw-border-style);
border-block-width: 1px; border-block-width: 1px;
@@ -1578,6 +1592,11 @@
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration)); transition-duration: var(--tw-duration, var(--default-transition-duration));
} }
.transition-opacity {
transition-property: opacity;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.transition-shadow { .transition-shadow {
transition-property: box-shadow; transition-property: box-shadow;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));

View File

@@ -8,6 +8,7 @@ use leptos::prelude::*;
use leptos::task::spawn_local; use leptos::task::spawn_local;
use leptos_router::components::{Router, Routes, Route}; use leptos_router::components::{Router, Routes, Route};
use leptos_router::hooks::use_navigate; use leptos_router::hooks::use_navigate;
use leptos_shadcn_skeleton::Skeleton;
#[component] #[component]
pub fn App() -> impl IntoView { pub fn App() -> impl IntoView {
@@ -116,10 +117,40 @@ pub fn App() -> impl IntoView {
view! { view! {
<Show when=move || !is_loading.0.get() fallback=|| view! { <Show when=move || !is_loading.0.get() fallback=|| view! {
<div class="flex items-center justify-center h-screen bg-background"> <div class="flex h-screen bg-background">
<div class="flex flex-col items-center gap-4"> // Sidebar skeleton
<div class="animate-spin h-8 w-8 border-4 border-primary border-t-transparent rounded-full"></div> <div class="w-56 border-r border-border p-4 space-y-4">
<p class="text-sm text-muted-foreground">"Yükleniyor..."</p> <Skeleton class="h-8 w-3/4" />
<div class="space-y-2">
<Skeleton class="h-6 w-full" />
<Skeleton class="h-6 w-full" />
<Skeleton class="h-6 w-4/5" />
<Skeleton class="h-6 w-full" />
<Skeleton class="h-6 w-3/5" />
<Skeleton class="h-6 w-full" />
</div>
</div>
// Main content skeleton
<div class="flex-1 flex flex-col">
// Header skeleton
<div class="border-b border-border p-4 flex items-center gap-4">
<Skeleton class="h-8 w-48" />
<Skeleton class="h-8 w-64" />
<div class="ml-auto"><Skeleton class="h-8 w-24" /></div>
</div>
// Table skeleton rows
<div class="flex-1 p-4 space-y-3">
<Skeleton class="h-10 w-full" />
<Skeleton class="h-10 w-full" />
<Skeleton class="h-10 w-full" />
<Skeleton class="h-10 w-full" />
<Skeleton class="h-10 w-full" />
<Skeleton class="h-10 w-3/4" />
</div>
// Status bar skeleton
<div class="border-t border-border p-3">
<Skeleton class="h-5 w-96" />
</div>
</div> </div>
</div> </div>
}.into_any()> }.into_any()>

View File

@@ -1,6 +1,5 @@
use leptos::prelude::*; use leptos::prelude::*;
use leptos::task::spawn_local; use leptos::task::spawn_local;
use leptos_shadcn_dialog::{Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter};
use leptos_shadcn_input::Input; use leptos_shadcn_input::Input;
use leptos_shadcn_button::{Button, ButtonVariant}; use leptos_shadcn_button::{Button, ButtonVariant};
use leptos_shadcn_alert::{Alert, AlertDescription, AlertVariant}; use leptos_shadcn_alert::{Alert, AlertDescription, AlertVariant};
@@ -17,7 +16,6 @@ pub fn AddTorrentDialog(
let uri = signal(String::new()); let uri = signal(String::new());
let is_loading = signal(false); let is_loading = signal(false);
let error_msg = signal(Option::<String>::None); let error_msg = signal(Option::<String>::None);
let is_open = signal(true);
let handle_submit = move |ev: web_sys::SubmitEvent| { let handle_submit = move |ev: web_sys::SubmitEvent| {
ev.prevent_default(); ev.prevent_default();
@@ -41,7 +39,6 @@ pub fn AddTorrentDialog(
shared::NotificationLevel::Success, shared::NotificationLevel::Success,
"Torrent başarıyla eklendi" "Torrent başarıyla eklendi"
); );
is_open.1.set(false);
on_close.run(()); on_close.run(());
} }
Err(e) => { Err(e) => {
@@ -53,58 +50,76 @@ pub fn AddTorrentDialog(
}); });
}; };
let handle_backdrop = {
let on_close = on_close.clone();
move |e: web_sys::MouseEvent| {
e.stop_propagation();
on_close.run(());
}
};
view! { view! {
<Dialog // Backdrop overlay
open=Signal::derive(move || is_open.0.get()) <div
on_open_change=Callback::new(move |open: bool| { class="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm"
is_open.1.set(open); on:click=handle_backdrop
if !open { />
on_close.run(()); // Dialog panel
} <div class="fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border bg-card p-6 shadow-lg rounded-lg sm:max-w-[425px]">
}) // Header
> <div class="flex flex-col space-y-1.5 text-center sm:text-left">
<DialogContent class="sm:max-w-[425px]"> <h2 class="text-lg font-semibold leading-none tracking-tight">"Add Torrent"</h2>
<DialogHeader> <p class="text-sm text-muted-foreground">"Enter a Magnet link or a .torrent file URL."</p>
<DialogTitle>"Add Torrent"</DialogTitle> </div>
<DialogDescription>"Enter a Magnet link or a .torrent file URL."</DialogDescription>
</DialogHeader> <form on:submit=handle_submit class="space-y-4">
<Input
input_type="text"
placeholder="magnet:?xt=urn:btih:..."
value=MaybeProp::derive(move || Some(uri.0.get()))
on_change=Callback::new(move |val: String| uri.1.set(val))
disabled=Signal::derive(move || is_loading.0.get())
/>
<form on:submit=handle_submit class="space-y-4"> {move || error_msg.0.get().map(|msg| view! {
<Input <Alert variant=AlertVariant::Destructive>
input_type="text" <AlertDescription>{msg}</AlertDescription>
placeholder="magnet:?xt=urn:btih:..." </Alert>
value=MaybeProp::derive(move || Some(uri.0.get())) })}
on_change=Callback::new(move |val: String| uri.1.set(val))
disabled=Signal::derive(move || is_loading.0.get())
/>
{move || error_msg.0.get().map(|msg| view! {
<Alert variant=AlertVariant::Destructive>
<AlertDescription>{msg}</AlertDescription>
</Alert>
})}
<DialogFooter> <div class="flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2">
<Button <Button
variant=ButtonVariant::Ghost variant=ButtonVariant::Ghost
on_click=Callback::new(move |()| { on_click=Callback::new(move |()| {
is_open.1.set(false); on_close.run(());
on_close.run(()); })
>
"Cancel"
</Button>
<Button disabled=Signal::derive(move || is_loading.0.get())>
{move || if is_loading.0.get() {
leptos::either::Either::Left(view! {
<span class="animate-spin mr-2 h-4 w-4 border-2 border-current border-t-transparent rounded-full"></span>
"Adding..."
}) })
> } else {
"Cancel" leptos::either::Either::Right(view! { "Add" })
</Button> }}
<Button disabled=Signal::derive(move || is_loading.0.get())> </Button>
{move || if is_loading.0.get() { </div>
leptos::either::Either::Left(view! { <span class="loading loading-spinner"></span> "Adding..." }) </form>
} else {
leptos::either::Either::Right(view! { "Add" }) // Close button (X)
}} <button
</Button> class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none"
</DialogFooter> on:click=move |_| on_close.run(())
</form> >
</DialogContent> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4">
</Dialog> <path d="M18 6 6 18"></path>
<path d="m6 6 12 12"></path>
</svg>
<span class="sr-only">"Close"</span>
</button>
</div>
} }
} }