impulse_thaw/upload/
mod.rs

1mod upload_dragger;
2
3pub use upload_dragger::UploadDragger;
4pub use web_sys::FileList;
5
6use leptos::{ev, html, prelude::*};
7use thaw_utils::{add_event_listener, class_list, mount_style, ArcOneCallback};
8
9#[component]
10pub fn Upload(
11    #[prop(optional, into)] class: MaybeProp<String>,
12    #[prop(optional, into)] id: MaybeProp<String>,
13    /// A string specifying a name for the input control.
14    /// This name is submitted along with the control's value when the form data is submitted.
15    #[prop(optional, into)]
16    name: MaybeProp<String>,
17    /// The accept type of upload.
18    #[prop(optional, into)]
19    accept: Signal<String>,
20    /// Allow multiple files to be selected.
21    #[prop(optional, into)]
22    multiple: Signal<bool>,
23    /// Customize upload request.
24    #[prop(optional, into)]
25    custom_request: Option<ArcOneCallback<FileList>>,
26    children: Children,
27) -> impl IntoView {
28    mount_style("upload", include_str!("./upload.css"));
29
30    let input_ref = NodeRef::<html::Input>::new();
31    let trigger_ref = NodeRef::<html::Div>::new();
32
33    Effect::new(move |_| {
34        let Some(trigger_el) = trigger_ref.get() else {
35            return;
36        };
37        let handle = add_event_listener(trigger_el, ev::click, move |_| {
38            if let Some(input_ref) = input_ref.get_untracked() {
39                input_ref.click();
40            }
41        });
42        on_cleanup(move || {
43            handle.remove();
44        });
45    });
46
47    let on_file_addition = move |files: FileList| {
48        if let Some(custom_request) = custom_request.as_ref() {
49            custom_request(files);
50        }
51    };
52
53    let on_change = {
54        let on_file_addition = on_file_addition.clone();
55        move |_| {
56            if let Some(input_ref) = input_ref.get_untracked() {
57                if let Some(files) = input_ref.files() {
58                    on_file_addition(files);
59                }
60                input_ref.set_value("");
61            }
62        }
63    };
64
65    let is_trigger_dragover = RwSignal::new(false);
66    let on_trigger_drop = move |event: ev::DragEvent| {
67        event.prevent_default();
68        if let Some(data) = event.data_transfer() {
69            if let Some(files) = data.files() {
70                on_file_addition(files);
71            }
72        }
73        is_trigger_dragover.set(false);
74    };
75    let on_trigger_dragover = move |event: ev::DragEvent| {
76        event.prevent_default();
77        is_trigger_dragover.set(true);
78    };
79    let on_trigger_dragenter = move |event: ev::DragEvent| {
80        event.prevent_default();
81    };
82    let on_trigger_dragleave = move |event: ev::DragEvent| {
83        event.prevent_default();
84        is_trigger_dragover.set(false);
85    };
86
87    view! {
88        <div class=class_list![
89            "thaw-upload",
90                ("thaw-upload--drag-over", move || is_trigger_dragover.get()),
91                class
92        ]>
93            <input
94                class="thaw-upload__input"
95                id=move || id.get()
96                name=move || name.get()
97                node_ref=input_ref
98                type="file"
99                accept=move || accept.get()
100                multiple=move || multiple.get()
101                on:change=on_change
102            />
103            <div
104                class="thaw-upload__trigger"
105                node_ref=trigger_ref
106                on:drop=on_trigger_drop
107                on:dragover=on_trigger_dragover
108                on:dragenter=on_trigger_dragenter
109                on:dragleave=on_trigger_dragleave
110            >
111                {children()}
112            </div>
113        </div>
114    }
115}