feat: shadcn -> rust-ui.com migration + TorrentDetail silme
All checks were successful
Build MIPS Binary / build (push) Successful in 5m15s
All checks were successful
Build MIPS Binary / build (push) Successful in 5m15s
- Tüm leptos-shadcn-* paketleri kaldırıldı (19 dependency) - leptos_ui, tw_merge, strum eklendi - components/ui/ modülü oluşturuldu (Button, Card, Input) - TorrentDetail bileşeni tamamen silindi - sidebar.rs: saf HTML+Tailwind ile SidebarButton yardımcı bileşeni - toolbar.rs, login.rs, setup.rs, add_torrent.rs: saf HTML+Tailwind - table.rs: shadcn Card -> rust-ui Card - app.rs: Skeleton -> animate-pulse div
This commit is contained in:
@@ -1,8 +1,5 @@
|
||||
use leptos::prelude::*;
|
||||
use leptos::task::spawn_local;
|
||||
use leptos_shadcn_button::{Button, ButtonVariant, ButtonSize};
|
||||
use leptos_shadcn_avatar::{Avatar, AvatarFallback};
|
||||
use leptos_shadcn_separator::Separator;
|
||||
|
||||
use leptos_use::storage::use_local_storage;
|
||||
use ::codee::string::FromToStringCodec;
|
||||
@@ -84,7 +81,6 @@ pub fn Sidebar() -> impl IntoView {
|
||||
let theme = current_theme.get().to_lowercase();
|
||||
if let Some(doc) = document().document_element() {
|
||||
let _ = doc.set_attribute("data-theme", &theme);
|
||||
// Also set class for Shadcn dark mode support
|
||||
if theme == "dark" || theme == "dracula" || theme == "dim" || theme == "abyss" || theme == "sunset" || theme == "cyberpunk" || theme == "nord" || theme == "business" || theme == "night" || theme == "black" || theme == "luxury" || theme == "coffee" || theme == "forest" || theme == "halloween" || theme == "synthwave" {
|
||||
let _ = doc.class_list().add_1("dark");
|
||||
} else {
|
||||
@@ -93,8 +89,6 @@ pub fn Sidebar() -> impl IntoView {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
let toggle_theme = move |_| {
|
||||
let new_theme = if current_theme.get() == "dark" { "light" } else { "dark" };
|
||||
set_current_theme.set(new_theme.to_string());
|
||||
@@ -110,110 +104,70 @@ pub fn Sidebar() -> impl IntoView {
|
||||
<div class="space-y-1">
|
||||
<h4 class="mb-1 rounded-md px-2 py-1 text-sm font-semibold text-muted-foreground">"Filters"</h4>
|
||||
|
||||
<Button
|
||||
variant=MaybeProp::derive(move || Some(if is_active(crate::store::FilterStatus::All) { ButtonVariant::Secondary } else { ButtonVariant::Ghost }))
|
||||
size=ButtonSize::Sm
|
||||
class="w-full justify-start gap-2"
|
||||
on_click=Callback::new(move |()| set_filter(crate::store::FilterStatus::All))
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||
</svg>
|
||||
"All"
|
||||
<span class="ml-auto text-xs font-mono opacity-70">{total_count}</span>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant=MaybeProp::derive(move || Some(if is_active(crate::store::FilterStatus::Downloading) { ButtonVariant::Secondary } else { ButtonVariant::Ghost }))
|
||||
size=ButtonSize::Sm
|
||||
class="w-full justify-start gap-2"
|
||||
on_click=Callback::new(move |()| set_filter(crate::store::FilterStatus::Downloading))
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" />
|
||||
</svg>
|
||||
"Downloading"
|
||||
<span class="ml-auto text-xs font-mono opacity-70">{downloading_count}</span>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant=MaybeProp::derive(move || Some(if is_active(crate::store::FilterStatus::Seeding) { ButtonVariant::Secondary } else { ButtonVariant::Ghost }))
|
||||
size=ButtonSize::Sm
|
||||
class="w-full justify-start gap-2"
|
||||
on_click=Callback::new(move |()| set_filter(crate::store::FilterStatus::Seeding))
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5" />
|
||||
</svg>
|
||||
"Seeding"
|
||||
<span class="ml-auto text-xs font-mono opacity-70">{seeding_count}</span>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant=MaybeProp::derive(move || Some(if is_active(crate::store::FilterStatus::Completed) { ButtonVariant::Secondary } else { ButtonVariant::Ghost }))
|
||||
size=ButtonSize::Sm
|
||||
class="w-full justify-start gap-2"
|
||||
on_click=Callback::new(move |()| set_filter(crate::store::FilterStatus::Completed))
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
"Completed"
|
||||
<span class="ml-auto text-xs font-mono opacity-70">{completed_count}</span>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant=MaybeProp::derive(move || Some(if is_active(crate::store::FilterStatus::Paused) { ButtonVariant::Secondary } else { ButtonVariant::Ghost }))
|
||||
size=ButtonSize::Sm
|
||||
class="w-full justify-start gap-2"
|
||||
on_click=Callback::new(move |()| set_filter(crate::store::FilterStatus::Paused))
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25v13.5m-7.5-13.5v13.5" />
|
||||
</svg>
|
||||
"Paused"
|
||||
<span class="ml-auto text-xs font-mono opacity-70">{paused_count}</span>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant=MaybeProp::derive(move || Some(if is_active(crate::store::FilterStatus::Inactive) { ButtonVariant::Secondary } else { ButtonVariant::Ghost }))
|
||||
size=ButtonSize::Sm
|
||||
class="w-full justify-start gap-2"
|
||||
on_click=Callback::new(move |()| set_filter(crate::store::FilterStatus::Inactive))
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
|
||||
</svg>
|
||||
"Inactive"
|
||||
<span class="ml-auto text-xs font-mono opacity-70">{inactive_count}</span>
|
||||
</Button>
|
||||
<SidebarButton
|
||||
active=Signal::derive(move || is_active(crate::store::FilterStatus::All))
|
||||
on_click=move |_| set_filter(crate::store::FilterStatus::All)
|
||||
icon="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
|
||||
label="All"
|
||||
count=Signal::derive(total_count)
|
||||
/>
|
||||
<SidebarButton
|
||||
active=Signal::derive(move || is_active(crate::store::FilterStatus::Downloading))
|
||||
on_click=move |_| set_filter(crate::store::FilterStatus::Downloading)
|
||||
icon="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3"
|
||||
label="Downloading"
|
||||
count=Signal::derive(downloading_count)
|
||||
/>
|
||||
<SidebarButton
|
||||
active=Signal::derive(move || is_active(crate::store::FilterStatus::Seeding))
|
||||
on_click=move |_| set_filter(crate::store::FilterStatus::Seeding)
|
||||
icon="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"
|
||||
label="Seeding"
|
||||
count=Signal::derive(seeding_count)
|
||||
/>
|
||||
<SidebarButton
|
||||
active=Signal::derive(move || is_active(crate::store::FilterStatus::Completed))
|
||||
on_click=move |_| set_filter(crate::store::FilterStatus::Completed)
|
||||
icon="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
label="Completed"
|
||||
count=Signal::derive(completed_count)
|
||||
/>
|
||||
<SidebarButton
|
||||
active=Signal::derive(move || is_active(crate::store::FilterStatus::Paused))
|
||||
on_click=move |_| set_filter(crate::store::FilterStatus::Paused)
|
||||
icon="M15.75 5.25v13.5m-7.5-13.5v13.5"
|
||||
label="Paused"
|
||||
count=Signal::derive(paused_count)
|
||||
/>
|
||||
<SidebarButton
|
||||
active=Signal::derive(move || is_active(crate::store::FilterStatus::Inactive))
|
||||
on_click=move |_| set_filter(crate::store::FilterStatus::Inactive)
|
||||
icon="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"
|
||||
label="Inactive"
|
||||
count=Signal::derive(inactive_count)
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
// Separator
|
||||
<div class="border-t border-border" />
|
||||
|
||||
<div class="p-4 bg-card" style="padding-bottom: calc(1rem + env(safe-area-inset-bottom));">
|
||||
<div class="flex items-center gap-3">
|
||||
<Avatar class="h-8 w-8">
|
||||
<AvatarFallback class="bg-primary text-primary-foreground text-xs font-medium">
|
||||
{first_letter}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
// Avatar
|
||||
<div class="h-8 w-8 rounded-full bg-primary text-primary-foreground flex items-center justify-center text-xs font-medium shrink-0">
|
||||
{first_letter}
|
||||
</div>
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<div class="font-medium text-sm truncate text-foreground">{username}</div>
|
||||
<div class="text-[10px] text-muted-foreground truncate">"Online"</div>
|
||||
</div>
|
||||
|
||||
// --- THEME BUTTON ---
|
||||
<Button
|
||||
variant=ButtonVariant::Ghost
|
||||
size=ButtonSize::Icon
|
||||
class="h-8 w-8 text-muted-foreground hover:text-foreground"
|
||||
on_click=Callback::new(toggle_theme)
|
||||
// Theme toggle button
|
||||
<button
|
||||
class="inline-flex items-center justify-center size-8 rounded-md hover:bg-accent hover:text-accent-foreground text-muted-foreground hover:text-foreground transition-colors"
|
||||
on:click=toggle_theme
|
||||
>
|
||||
// Sun icon for dark mode (to switch to light), Moon for light (to switch to dark)
|
||||
// Actually show current state or action? Usually action.
|
||||
// If dark, show Sun. If light, show Moon.
|
||||
<Show when=move || current_theme.get() == "dark" fallback=|| view! {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z" />
|
||||
@@ -223,26 +177,51 @@ pub fn Sidebar() -> impl IntoView {
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" />
|
||||
</svg>
|
||||
</Show>
|
||||
</Button>
|
||||
<Button
|
||||
variant=ButtonVariant::Ghost
|
||||
size=ButtonSize::Icon
|
||||
class="text-destructive h-8 w-8"
|
||||
on_click=Callback::new(move |()| {
|
||||
</button>
|
||||
// Logout button
|
||||
<button
|
||||
class="inline-flex items-center justify-center size-8 rounded-md hover:bg-accent text-destructive transition-colors"
|
||||
on:click=move |_| {
|
||||
spawn_local(async move {
|
||||
if shared::server_fns::auth::logout().await.is_ok() {
|
||||
let window = web_sys::window().expect("window should exist");
|
||||
let _ = window.location().set_href("/login");
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75" />
|
||||
</svg>
|
||||
</Button>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn SidebarButton(
|
||||
active: Signal<bool>,
|
||||
on_click: impl Fn(web_sys::MouseEvent) + 'static,
|
||||
#[prop(into)] icon: String,
|
||||
#[prop(into)] label: &'static str,
|
||||
count: Signal<usize>,
|
||||
) -> impl IntoView {
|
||||
view! {
|
||||
<button
|
||||
class=move || if active.get() {
|
||||
"inline-flex items-center justify-start gap-2 w-full h-8 rounded-md px-3 text-sm font-medium bg-secondary text-secondary-foreground transition-colors"
|
||||
} else {
|
||||
"inline-flex items-center justify-start gap-2 w-full h-8 rounded-md px-3 text-sm font-medium hover:bg-accent hover:text-accent-foreground transition-colors"
|
||||
}
|
||||
on:click=on_click
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d=icon.clone() />
|
||||
</svg>
|
||||
{label}
|
||||
<span class="ml-auto text-xs font-mono opacity-70">{count}</span>
|
||||
</button>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use leptos::prelude::*;
|
||||
use leptos_shadcn_input::Input;
|
||||
use leptos_shadcn_button::{Button, ButtonVariant, ButtonSize};
|
||||
use crate::components::torrent::add_torrent::AddTorrentDialog;
|
||||
|
||||
#[component]
|
||||
@@ -9,30 +7,36 @@ pub fn Toolbar() -> impl IntoView {
|
||||
let store = use_context::<crate::store::TorrentStore>().expect("store not provided");
|
||||
let is_mobile_menu_open = use_context::<RwSignal<bool>>().expect("mobile menu state not provided");
|
||||
|
||||
let search_value = RwSignal::new(String::new());
|
||||
|
||||
// Sync search_value to store
|
||||
Effect::new(move |_| {
|
||||
let val = search_value.get();
|
||||
store.search_query.set(val);
|
||||
});
|
||||
|
||||
view! {
|
||||
<div class="flex min-h-14 h-auto items-center border-b border-border bg-background px-4" style="padding-top: env(safe-area-inset-top);">
|
||||
// Sol kısım: Menü butonu + Add Torrent
|
||||
<div class="flex items-center gap-3">
|
||||
// Mobile Menu Trigger
|
||||
<Button
|
||||
variant=ButtonVariant::Ghost
|
||||
size=ButtonSize::Icon
|
||||
class="lg:hidden"
|
||||
on_click=Callback::new(move |()| is_mobile_menu_open.update(|v| *v = !*v))
|
||||
<button
|
||||
class="inline-flex items-center justify-center size-9 rounded-md hover:bg-accent hover:text-accent-foreground lg:hidden"
|
||||
on:click=move |_| is_mobile_menu_open.update(|v| *v = !*v)
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="w-5 h-5 stroke-current"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
|
||||
</Button>
|
||||
</button>
|
||||
|
||||
<Button
|
||||
class="gap-2 shadow"
|
||||
on_click=Callback::new(move |()| show_add_modal.1.set(true))
|
||||
<button
|
||||
class="inline-flex items-center justify-center gap-2 h-9 px-4 py-2 rounded-md text-sm font-medium bg-primary text-primary-foreground shadow-xs hover:bg-primary/90 transition-all active:scale-[0.98]"
|
||||
on:click=move |_| show_add_modal.1.set(true)
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-4 h-4 md:w-5 md:h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
</svg>
|
||||
<span class="hidden sm:inline">"Add Torrent"</span>
|
||||
<span class="sm:hidden">"Add"</span>
|
||||
</Button>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
// Sağ kısım: Search kutusu
|
||||
@@ -42,12 +46,11 @@ pub fn Toolbar() -> impl IntoView {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="absolute left-2.5 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground pointer-events-none">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
|
||||
</svg>
|
||||
<Input
|
||||
input_type="search"
|
||||
<input
|
||||
type="search"
|
||||
placeholder="Search..."
|
||||
value=MaybeProp::derive(move || Some(store.search_query.get()))
|
||||
on_change=Callback::new(move |val: String| store.search_query.set(val))
|
||||
class="pl-8 h-9"
|
||||
class="file:text-foreground placeholder:text-muted-foreground border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-2 md:text-sm pl-8"
|
||||
bind:value=search_value
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user