summary refs log tree commit diff stats
path: root/src/components/input_placeholder.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/input_placeholder.rs')
-rw-r--r--src/components/input_placeholder.rs154
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,
+    }
+}