Compare commits

...

11 Commits

Author SHA1 Message Date
spinline
8fc3571848 fix: restore missing BASE64 import
All checks were successful
Build MIPS Binary / build (push) Successful in 1m51s
2026-02-14 02:23:08 +03:00
spinline
792b6bc97b fix: resolve toolbar syntax error and unused imports
Some checks failed
Build MIPS Binary / build (push) Failing after 34s
2026-02-14 02:20:53 +03:00
spinline
6efa6cd2d9 fix: resolve unused variable warning
Some checks failed
Build MIPS Binary / build (push) Failing after 45s
2026-02-14 02:18:45 +03:00
spinline
89caa17b92 fix: correct malformed import in multi_select.rs
Some checks failed
Build MIPS Binary / build (push) Failing after 46s
2026-02-14 02:16:53 +03:00
spinline
47bb60d7d8 fix: explicit type fixes for toolbar and multi_select
Some checks failed
Build MIPS Binary / build (push) Failing after 45s
2026-02-14 02:14:40 +03:00
spinline
7730250b61 fix: resolve updated compilation errors
Some checks failed
Build MIPS Binary / build (push) Failing after 45s
2026-02-14 02:11:58 +03:00
spinline
73d111124a refactor: simplify toolbar toggle button structure
Some checks failed
Build MIPS Binary / build (push) Failing after 32s
2026-02-14 02:10:43 +03:00
spinline
670c5a653b fix: resolve type inference error in toolbar callback
Some checks failed
Build MIPS Binary / build (push) Failing after 40s
2026-02-14 02:09:12 +03:00
spinline
9394a56e7d fix: compile error in protected layout
Some checks failed
Build MIPS Binary / build (push) Failing after 45s
2026-02-14 02:07:43 +03:00
spinline
105388eec3 feat: refine responsive sidebar behavior with auto-collapse
Some checks failed
Build MIPS Binary / build (push) Failing after 30s
2026-02-14 02:06:20 +03:00
spinline
f5d9cb642c feat: implement collapsible sidebar
Some checks failed
Build MIPS Binary / build (push) Failing after 34s
2026-02-14 01:57:40 +03:00
11 changed files with 81 additions and 31 deletions

View File

@@ -1,4 +1,4 @@
use leptos::prelude::*; // use leptos::prelude::*;
pub fn use_random_id_for(prefix: &str) -> String { pub fn use_random_id_for(prefix: &str) -> String {
format!("{}_{}", prefix, js_sys::Math::random().to_string().replace(".", "")) format!("{}_{}", prefix, js_sys::Math::random().to_string().replace(".", ""))

View File

@@ -3,20 +3,53 @@ use crate::components::layout::sidebar::Sidebar;
use crate::components::layout::toolbar::Toolbar; use crate::components::layout::toolbar::Toolbar;
use crate::components::layout::footer::Footer; use crate::components::layout::footer::Footer;
use crate::components::ui::sidenav::{SidenavWrapper, Sidenav, SidenavInset}; use crate::components::ui::sidenav::{SidenavWrapper, Sidenav, SidenavInset};
use wasm_bindgen::JsCast;
#[component] #[component]
pub fn Protected(children: Children) -> impl IntoView { pub fn Protected(children: Children) -> impl IntoView {
let (collapsed, set_collapsed) = signal(false);
// Responsive Sidebar Logic
Effect::new(move |_| {
let window = web_sys::window().expect("window missing");
// Initial check
let width = window.inner_width().unwrap().as_f64().unwrap_or(1920.0);
if width < 1280.0 {
set_collapsed.set(true);
} else {
set_collapsed.set(false);
}
// Listener
let closure = wasm_bindgen::closure::Closure::<dyn FnMut(_)>::new(move |_: web_sys::Event| {
let window = web_sys::window().expect("window missing");
let width = window.inner_width().unwrap().as_f64().unwrap_or(1920.0);
if width < 1280.0 {
set_collapsed.set(true);
} else {
set_collapsed.set(false);
}
});
let _ = window.add_event_listener_with_callback("resize", closure.as_ref().unchecked_ref());
closure.forget(); // Leak memory intentionally for global listener (or store in a cleanup handle if needed, but for layout component it's fine)
});
view! { view! {
<SidenavWrapper attr:style="--sidenav-width:16rem; --sidenav-width-icon:3rem;"> <SidenavWrapper attr:style="--sidenav-width:16rem; --sidenav-width-icon:3rem;">
// Masaüstü Sidenav // Masaüstü Sidenav
<Sidenav> <Sidenav
data_collapsible=crate::components::ui::sidenav::SidenavCollapsible::Icon
data_state=if collapsed.get() { crate::components::ui::sidenav::SidenavState::Collapsed } else { crate::components::ui::sidenav::SidenavState::Expanded }
>
<Sidebar /> <Sidebar />
</Sidenav> </Sidenav>
// İçerik Alanı // İçerik Alanı
<SidenavInset class="flex flex-col h-screen overflow-hidden"> <SidenavInset class="flex flex-col h-screen overflow-hidden">
// Toolbar (Üst Bar) // Toolbar (Üst Bar)
<Toolbar /> <Toolbar on_toggle_sidebar=Callback::new(move |_| set_collapsed.update(|c| *c = !*c)) />
// Ana İçerik // Ana İçerik
<main class="flex-1 overflow-y-auto relative bg-background flex flex-col"> <main class="flex-1 overflow-y-auto relative bg-background flex flex-col">

View File

@@ -87,7 +87,7 @@ pub fn Sidebar() -> impl IntoView {
<path stroke-linecap="round" stroke-linejoin="round" d="M15.362 5.214A8.252 8.252 0 0112 21 8.25 8.25 0 016.038 7.048 8.287 8.287 0 009 9.6a8.983 8.983 0 013.361-6.867 8.21 8.25 0 003 2.48z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M15.362 5.214A8.252 8.252 0 0112 21 8.25 8.25 0 016.038 7.048 8.287 8.287 0 009 9.6a8.983 8.983 0 013.361-6.867 8.21 8.25 0 003 2.48z" />
</svg> </svg>
</div> </div>
<div class="grid flex-1 text-left text-sm leading-tight overflow-hidden"> <div class="grid flex-1 text-left text-sm leading-tight overflow-hidden group-data-[state=Collapsed]:hidden">
<span class="truncate font-semibold text-foreground text-base">"VibeTorrent"</span> <span class="truncate font-semibold text-foreground text-base">"VibeTorrent"</span>
<span class="truncate text-[10px] text-muted-foreground opacity-70">"v3.0.0"</span> <span class="truncate text-[10px] text-muted-foreground opacity-70">"v3.0.0"</span>
</div> </div>
@@ -150,26 +150,28 @@ pub fn Sidebar() -> impl IntoView {
<div class="flex flex-col gap-4 p-4"> <div class="flex flex-col gap-4 p-4">
// Push Notification Toggle // Push Notification Toggle
<div class="flex items-center justify-between px-2 py-1 bg-muted/20 rounded-md border border-border/50"> <div class="flex items-center justify-between px-2 py-1 bg-muted/20 rounded-md border border-border/50">
<div class="flex flex-col gap-0.5"> <div class="flex flex-col gap-0.5 group-data-[state=Collapsed]:hidden">
<span class="text-[10px] font-bold uppercase tracking-wider text-foreground/70">"Bildirimler"</span> <span class="text-[10px] font-bold uppercase tracking-wider text-foreground/70">"Bildirimler"</span>
<span class="text-[9px] text-muted-foreground">"Web Push"</span> <span class="text-[9px] text-muted-foreground">"Web Push"</span>
</div> </div>
<Switch <div class="group-data-[state=Collapsed]:hidden">
checked=Signal::from(store.push_enabled) <Switch
on_checked_change=Callback::new(on_push_toggle) checked=Signal::from(store.push_enabled)
/> on_checked_change=Callback::new(on_push_toggle)
/>
</div>
</div> </div>
<div class="flex items-center gap-3 p-2 rounded-lg border bg-muted/30 shadow-xs overflow-hidden"> <div class="flex items-center gap-3 p-2 rounded-lg border bg-muted/30 shadow-xs overflow-hidden group-data-[state=Collapsed]:gap-0 group-data-[state=Collapsed]:justify-center group-data-[state=Collapsed]:p-0 group-data-[state=Collapsed]:border-none group-data-[state=Collapsed]:bg-transparent group-data-[state=Collapsed]:shadow-none">
<div class="h-8 w-8 rounded-full bg-primary text-primary-foreground flex items-center justify-center text-xs font-medium shrink-0 border border-primary-foreground/10"> <div class="h-8 w-8 rounded-full bg-primary text-primary-foreground flex items-center justify-center text-xs font-medium shrink-0 border border-primary-foreground/10">
{first_letter} {first_letter}
</div> </div>
<div class="flex-1 overflow-hidden"> <div class="flex-1 overflow-hidden group-data-[state=Collapsed]:hidden">
<div class="font-medium text-[11px] truncate text-foreground leading-tight">{username}</div> <div class="font-medium text-[11px] truncate text-foreground leading-tight">{username}</div>
<div class="text-[9px] text-muted-foreground truncate opacity-70">"Yönetici"</div> <div class="text-[9px] text-muted-foreground truncate opacity-70">"Yönetici"</div>
</div> </div>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1 group-data-[state=Collapsed]:hidden">
<ThemeToggle /> <ThemeToggle />
<Button <Button
@@ -217,8 +219,8 @@ fn SidebarItem(
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4 shrink-0"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4 shrink-0">
<path stroke-linecap="round" stroke-linejoin="round" d=icon.clone() /> <path stroke-linecap="round" stroke-linejoin="round" d=icon.clone() />
</svg> </svg>
<span class="flex-1 truncate">{label}</span> <span class="flex-1 truncate group-data-[state=Collapsed]:hidden">{label}</span>
<span class="text-[10px] font-mono opacity-50">{count}</span> <span class="text-[10px] font-mono opacity-50 group-data-[state=Collapsed]:hidden">{count}</span>
</SidenavMenuButton> </SidenavMenuButton>
</SidenavMenuItem> </SidenavMenuItem>
} }

View File

@@ -1,24 +1,38 @@
use leptos::prelude::*; use leptos::prelude::*;
use icons::{PanelLeft, Plus}; use icons::{PanelLeft, Plus};
use crate::components::torrent::add_torrent::AddTorrentDialogContent; use crate::components::torrent::add_torrent::AddTorrentDialogContent;
use crate::components::ui::button::{ButtonVariant, ButtonSize}; use crate::components::ui::button::{Button, ButtonVariant, ButtonSize};
use crate::components::ui::sheet::{Sheet, SheetContent, SheetTrigger, SheetDirection}; use crate::components::ui::sheet::{Sheet, SheetContent, SheetTrigger, SheetDirection};
use crate::components::ui::dialog::{Dialog, DialogContent, DialogTrigger}; use crate::components::ui::dialog::{Dialog, DialogContent, DialogTrigger};
use crate::components::layout::sidebar::Sidebar; use crate::components::layout::sidebar::Sidebar;
#[component] #[component]
pub fn Toolbar() -> impl IntoView { pub fn Toolbar(
on_toggle_sidebar: Callback<()>,
) -> impl IntoView {
view! { 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);"> <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 (Mobil) + Add Torrent // Sol kısım: Menü butonu (Mobil) + Add Torrent
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
// Desktop Toggle
// --- MOBILE SHEET (SIDEBAR) --- <div class="hidden lg:block">
<Button
variant=ButtonVariant::Ghost
size=ButtonSize::Icon
class="size-9"
on:click=move |_| { on_toggle_sidebar.run(()); }
>
<PanelLeft class="size-5" />
<span class="hidden">"Toggle Sidebar"</span>
</Button>
</div>
// Mobile Toggle (Sheet)
<div class="lg:hidden"> <div class="lg:hidden">
<Sheet> <Sheet>
<SheetTrigger variant=ButtonVariant::Ghost size=ButtonSize::Icon class="size-9"> <SheetTrigger variant=ButtonVariant::Ghost size=ButtonSize::Icon class="size-9">
<PanelLeft class="size-5" /> <PanelLeft class="size-5" />
<span class="hidden">"Menüyü Aç"</span> <span class="hidden">"Open Menu"</span>
</SheetTrigger> </SheetTrigger>
<SheetContent <SheetContent
direction=SheetDirection::Left direction=SheetDirection::Left

View File

@@ -139,7 +139,7 @@ pub fn ContextMenuTrigger(
class=trigger_class class=trigger_class
data-name="ContextMenuTrigger" data-name="ContextMenuTrigger"
data-context-trigger=ctx.target_id data-context-trigger=ctx.target_id
on:contextmenu=move |e: web_sys::MouseEvent| { on:contextmenu=move |_e: web_sys::MouseEvent| {
if let Some(cb) = on_open { if let Some(cb) = on_open {
cb.run(()); cb.run(());
} }

View File

@@ -1,6 +1,6 @@
// * Reuse @table.rs // * Reuse @table.rs
pub use crate::components::ui::table::{ pub use crate::components::ui::table::{
Table as DataTable, TableBody as DataTableBody, TableCaption as DataTableCaption, TableCell as DataTableCell, Table as DataTable, TableBody as DataTableBody, TableCell as DataTableCell,
TableFooter as DataTableFooter, TableHead as DataTableHead, TableHeader as DataTableHeader, TableHead as DataTableHead, TableHeader as DataTableHeader,
TableRow as DataTableRow, TableWrapper as DataTableWrapper, TableRow as DataTableRow, TableWrapper as DataTableWrapper,
}; };

View File

@@ -5,7 +5,7 @@ use leptos_ui::clx;
use tw_merge::*; use tw_merge::*;
use crate::components::hooks::use_random::use_random_id_for; use crate::components::hooks::use_random::use_random_id_for;
pub use crate::components::ui::separator::Separator as DropdownMenuSeparator; // pub use crate::components::ui::separator::Separator as DropdownMenuSeparator;
mod components { mod components {
use super::*; use super::*;

View File

@@ -9,7 +9,7 @@ use crate::components::hooks::use_can_scroll_vertical::use_can_scroll_vertical;
use crate::components::hooks::use_random::use_random_id_for; use crate::components::hooks::use_random::use_random_id_for;
// * Reuse @select.rs // * Reuse @select.rs
pub use crate::components::ui::select::{ pub use crate::components::ui::select::{
SelectGroup as MultiSelectGroup, SelectItem as MultiSelectItem, SelectLabel as MultiSelectLabel, SelectGroup as MultiSelectGroup, SelectItem as MultiSelectItem,
}; };
#[derive(Clone, Copy, PartialEq, Eq, Default)] #[derive(Clone, Copy, PartialEq, Eq, Default)]

View File

@@ -16,7 +16,7 @@ mod components {
clx! {SheetFooter, footer, "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end"} clx! {SheetFooter, footer, "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end"}
} }
pub use components::*; // pub use components::*;
/* ========================================================== */ /* ========================================================== */
/* ✨ CONTEXT ✨ */ /* ✨ CONTEXT ✨ */

View File

@@ -145,7 +145,8 @@ pub fn Sidenav(
data-name="Sidenav" data-name="Sidenav"
data-sidenav=data_state.to_string() data-sidenav=data_state.to_string()
data-side=data_side.to_string() data-side=data_side.to_string()
class="hidden md:block group peer text-sidenav-foreground data-[state=Collapsed]:hidden" data-collapsible=data_collapsible.to_string()
class="hidden md:block group peer text-sidenav-foreground group-data-[collapsible=Offcanvas]:data-[state=Collapsed]:hidden"
> >
// * SidenavGap: This is what handles the sidenav gap on desktop // * SidenavGap: This is what handles the sidenav gap on desktop
<div <div
@@ -155,9 +156,9 @@ pub fn Sidenav(
"group-data-[collapsible=Offcanvas]:w-0", "group-data-[collapsible=Offcanvas]:w-0",
"group-data-[side=Right]:rotate-180", "group-data-[side=Right]:rotate-180",
match variant { match variant {
SidenavVariant::Sidenav => "group-data-[collapsible=Icon]:w-(--sidenav-width-icon)", SidenavVariant::Sidenav => "group-data-[collapsible=Icon]:group-data-[state=Collapsed]:w-(--sidenav-width-icon)",
SidenavVariant::Floating | SidenavVariant::Inset => SidenavVariant::Floating | SidenavVariant::Inset =>
"group-data-[collapsible=Icon]:w-[calc(var(--sidenav-width-icon)+(--spacing(4)))]", "group-data-[collapsible=Icon]:group-data-[state=Collapsed]:w-[calc(var(--sidenav-width-icon)+(--spacing(4)))]",
} }
) )
/> />
@@ -171,9 +172,9 @@ pub fn Sidenav(
SidenavSide::Right => "right-0 group-data-[collapsible=Offcanvas]:right-[calc(var(--sidenav-width)*-1)]" SidenavSide::Right => "right-0 group-data-[collapsible=Offcanvas]:right-[calc(var(--sidenav-width)*-1)]"
}, },
match variant { match variant {
SidenavVariant::Sidenav => "group-data-[collapsible=Icon]:w-(--sidenav-width-icon) group-data-[side=Left]:border-r group-data-[side=Right]:border-l", SidenavVariant::Sidenav => "group-data-[collapsible=Icon]:group-data-[state=Collapsed]:w-(--sidenav-width-icon) group-data-[side=Left]:border-r group-data-[side=Right]:border-l",
SidenavVariant::Floating | SidenavVariant::Inset => SidenavVariant::Floating | SidenavVariant::Inset =>
"p-2 group-data-[collapsible=Icon]:w-[calc(var(--sidenav-width-icon)+(--spacing(4))+2px)]", "p-2 group-data-[collapsible=Icon]:group-data-[state=Collapsed]:w-[calc(var(--sidenav-width-icon)+(--spacing(4))+2px)]",
}, },
) )
> >

View File

@@ -192,7 +192,7 @@ pub async fn subscribe_to_push_notifications() {
let key_array = js_sys::Uint8Array::from(&decoded_key[..]); let key_array = js_sys::Uint8Array::from(&decoded_key[..]);
// 3. Prepare Options // 3. Prepare Options
let mut options = web_sys::PushSubscriptionOptionsInit::new(); let options = web_sys::PushSubscriptionOptionsInit::new();
options.set_user_visible_only(true); options.set_user_visible_only(true);
options.set_application_server_key(&key_array.into()); options.set_application_server_key(&key_array.into());