diff options
Diffstat (limited to 'src/components/input_placeholder.rs')
| -rw-r--r-- | src/components/input_placeholder.rs | 154 |
1 files changed, 141 insertions, 13 deletions
diff --git a/src/components/input_placeholder.rs b/src/components/input_placeholder.rs index aeef838..99b3196 100644 --- a/src/components/input_placeholder.rs +++ b/src/components/input_placeholder.rs @@ -1,27 +1,40 @@ use leptos::{ IntoView, component, + error::Error, html::Input, - prelude::{ClassAttribute, ElementChild, GlobalAttributes, NodeRef, NodeRefAttribute}, + prelude::{ + ClassAttribute, CollectView, ElementChild, Get, GlobalAttributes, LocalResource, NodeRef, + NodeRefAttribute, OnAttribute, OnTargetAttribute, PropAttribute, Set, Show, WriteSignal, + signal, + }, view, }; +use log::{error, info}; use crate::components::get_id; - #[component] +#[expect(clippy::too_many_lines)] pub fn InputPlaceholder( input_type: &'static str, label: &'static str, node_ref: NodeRef<Input>, #[prop(default = None)] initial_value: Option<String>, + #[prop(default = None)] reactive: Option<WriteSignal<Option<String>>>, + #[prop(default = None)] auto_complete: Option< + LocalResource<Result<Option<Vec<String>>, Error>>, + >, ) -> impl IntoView { let id = get_id(); + let (autocomplete_signal, autocomplete_set) = signal(String::new()); + view! { <div class="relative h-14"> <input id=id.to_string() type=input_type + autocomplete="off" class="\ absolute \ bottom-0 \ @@ -32,7 +45,8 @@ pub fn InputPlaceholder( border-gray-200 \ focus:outline-none \ h-10 \ - peer \ + peer/input \ + group/input \ placeholder-transparent \ rounded-t-lg \ text-gray-900 \ @@ -41,16 +55,23 @@ pub fn InputPlaceholder( placeholder="sentinel value" node_ref=node_ref value=initial_value + on:input:target=move |ev| { + if let Some(signal) = reactive { + signal.set(Some(ev.target().value())); + autocomplete_set.set(ev.target().value()); + } + } + prop:value=autocomplete_signal /> // TODO: Reference `var(--tw-border-2)` instead of the `2 px` <2025-10-01> - <div class=" + <div class="\ absolute \ bottom-0 \ h-[2px] \ w-full \ bg-gray-300 \ - peer-focus:bg-indigo-600 \ + peer-focus/input:bg-indigo-600 \ " /> <label @@ -62,18 +83,125 @@ pub fn InputPlaceholder( text-gray-700 \ text-sm \ transition-all \ - peer-focus:bottom-10 \ - peer-focus:left-0 \ - peer-focus:text-gray-700 \ - peer-focus:text-sm \ - peer-placeholder-shown:text-base \ - peer-placeholder-shown:text-gray-400 \ - peer-placeholder-shown:bottom-2 \ - peer-placeholder-shown:left-2 \ + peer-focus/input:bottom-10 \ + peer-focus/input:left-0 \ + peer-focus/input:text-gray-700 \ + peer-focus/input:text-sm \ + peer-placeholder-shown/input:text-base \ + peer-placeholder-shown/input:text-gray-400 \ + peer-placeholder-shown/input:bottom-2 \ + peer-placeholder-shown/input:left-2 \ " > {label} </label> + + <Show + when=move || { + !autocomplete_signal.get().is_empty() + } + fallback=move || () + > + <div class="\ + absolute \ + top-0 \ + left-0 \ + invisible \ + peer-focus/input:visible \ + in-focus:visible \ + "> + <div class="\ + flex \ + flex-row \ + g-0 \ + "> + // TODO: Reference `var(--tw-border-8)` instead of the `8 px` <2025-10-11> + <div class="w-[8px] h-full" /> + <div class="\ + flex \ + flex-col \ + g-0 \ + "> + <div class="h-14 w-full peer/div" /> + <div class="\ + bg-white \ + shadow \ + outline \ + outline-black/5 \ + rounded-lg \ + z-50 \ + p-2 \ + visible \ + "> + {move || { + auto_complete + .map(|auto_complete| { + provide_auto_completion( + auto_complete, + autocomplete_set, + reactive + ) + }) + }} + </div> + </div> + </div> + </div> + </Show> </div> } } + +fn provide_auto_completion( + auto_complete: LocalResource<Result<Option<Vec<String>>, Error>>, + autocomplete_set: WriteSignal<String>, + reactive: Option<WriteSignal<Option<String>>>, +) -> impl IntoView { + match auto_complete.get() { + Some(resource_result) => match resource_result { + Ok(resource_fetch) => resource_fetch.map(|_| { + view! { + <div class="flex flex-col g-1"> + {move || { + auto_complete + .get() + .expect("Worked before") + .unwrap() + .unwrap() + .into_iter() + .map(|item| { + let item2 = item.clone(); + view! { + <button + type="button" + on:click=move |_| { + autocomplete_set.set(item2.clone()); + reactive + .expect( + "Should be set, \ + when autocomplete is used") + .set(Some(item2.clone())); + + info!("Set autocomplete to {item2}."); + } + > + {item} + </button> + } + }) + .collect_view() + }} + </div> + } + }), + Err(err) => { + error!( + "Error while loading \ + autocompletion: {err}" + ); + None + } + }, + None => None, + } +} |
