diff --git a/frontend/src/components/hooks/mod.rs b/frontend/src/components/hooks/mod.rs index 8ed876e..b6ab82a 100644 --- a/frontend/src/components/hooks/mod.rs +++ b/frontend/src/components/hooks/mod.rs @@ -1,2 +1,3 @@ pub mod use_random; pub mod use_theme_mode; +pub mod use_can_scroll_vertical; \ No newline at end of file diff --git a/frontend/src/components/hooks/use_can_scroll_vertical.rs b/frontend/src/components/hooks/use_can_scroll_vertical.rs new file mode 100644 index 0000000..39d098b --- /dev/null +++ b/frontend/src/components/hooks/use_can_scroll_vertical.rs @@ -0,0 +1,25 @@ +use leptos::prelude::*; +use wasm_bindgen::JsCast; + +/// Hook to determine if an element can scroll vertically. +/// +/// Returns (on_scroll_callback, can_scroll_up_signal, can_scroll_down_signal) +pub fn use_can_scroll_vertical() -> (Callback, ReadSignal, ReadSignal) { + let can_scroll_up = RwSignal::new(false); + let can_scroll_down = RwSignal::new(false); + + let on_scroll = Callback::new(move |ev: web_sys::Event| { + if let Some(target) = ev.target() { + if let Some(el) = target.dyn_ref::() { + let scroll_top = el.scroll_top(); + let scroll_height = el.scroll_height(); + let client_height = el.client_height(); + + can_scroll_up.set(scroll_top > 0); + can_scroll_down.set(scroll_top + client_height < scroll_height - 1); + } + } + }); + + (on_scroll, can_scroll_up.read_only(), can_scroll_down.read_only()) +} diff --git a/frontend/src/components/ui/mod.rs b/frontend/src/components/ui/mod.rs index 3f8a65d..ee79598 100644 --- a/frontend/src/components/ui/mod.rs +++ b/frontend/src/components/ui/mod.rs @@ -11,7 +11,6 @@ pub mod input; pub mod multi_select; pub mod select; pub mod separator; -pub mod sonner; pub mod svg_icon; pub mod table; pub mod theme_toggle; diff --git a/frontend/src/components/ui/multi_select.rs b/frontend/src/components/ui/multi_select.rs index 4425afe..fcfc9f1 100644 --- a/frontend/src/components/ui/multi_select.rs +++ b/frontend/src/components/ui/multi_select.rs @@ -20,13 +20,6 @@ pub enum MultiSelectAlign { End, } -#[derive(Clone, Copy, PartialEq, Eq, Default)] -pub enum MultiSelectPosition { - #[default] - Below, - Above, -} - /* ========================================================== */ /* ✨ FUNCTIONS ✨ */ /* ========================================================== */ @@ -181,12 +174,13 @@ pub fn MultiSelectContent(children: Children, #[prop(optional, into)] class: Str ); let target_id_for_script = multi_select_ctx.target_id.clone(); + let target_id_for_script_2 = multi_select_ctx.target_id.clone(); // Scroll indicator signals let (on_scroll, can_scroll_up_signal, can_scroll_down_signal) = use_can_scroll_vertical(); view! { - +
{{ isOpen = true; - - // Lock all scrollable elements - window.ScrollLock.lock(); - + if (window.ScrollLock) window.ScrollLock.lock(); multiSelect.setAttribute('data-state', 'open'); multiSelect.style.pointerEvents = 'auto'; - - // Set min-width to match trigger const triggerRect = trigger.getBoundingClientRect(); multiSelect.style.minWidth = `${{triggerRect.width}}px`; - - // Trigger scroll event to update indicators multiSelect.dispatchEvent(new Event('scroll')); - - // Close on click outside setTimeout(() => {{ document.addEventListener('click', handleClickOutside); }}, 0); @@ -272,9 +259,7 @@ pub fn MultiSelectContent(children: Children, #[prop(optional, into)] class: Str multiSelect.setAttribute('data-state', 'closed'); multiSelect.style.pointerEvents = 'none'; document.removeEventListener('click', handleClickOutside); - - // Unlock scroll after animation (200ms delay) - window.ScrollLock.unlock(200); + if (window.ScrollLock) window.ScrollLock.unlock(200); }}; const handleClickOutside = (e) => {{ @@ -283,17 +268,11 @@ pub fn MultiSelectContent(children: Children, #[prop(optional, into)] class: Str }} }}; - // Toggle multi-select when trigger is clicked trigger.addEventListener('click', (e) => {{ e.stopPropagation(); - if (isOpen) {{ - closeMultiSelect(); - }} else {{ - openMultiSelect(); - }} + if (isOpen) closeMultiSelect(); else openMultiSelect(); }}); - // Handle ESC key to close document.addEventListener('keydown', (e) => {{ if (e.key === 'Escape' && isOpen) {{ e.preventDefault(); @@ -310,8 +289,8 @@ pub fn MultiSelectContent(children: Children, #[prop(optional, into)] class: Str }})(); "#, target_id_for_script, - target_id_for_script, + target_id_for_script_2, )} - } -} \ No newline at end of file + }.into_any() +} diff --git a/frontend/src/components/ui/select.rs b/frontend/src/components/ui/select.rs index 82dd1a8..a13ba7d 100644 --- a/frontend/src/components/ui/select.rs +++ b/frontend/src/components/ui/select.rs @@ -166,6 +166,7 @@ pub fn SelectContent( ); let target_id_for_script = ctx.target_id.clone(); + let target_id_for_script_2 = ctx.target_id.clone(); // Scroll indicator signals let (on_scroll, can_scroll_up_signal, can_scroll_down_signal) = use_can_scroll_vertical(); @@ -176,7 +177,6 @@ pub fn SelectContent(
}.into_any() -} +} \ No newline at end of file