leptos_forms_rs/components/
file_upload_input.rs1use crate::core::types::{FieldValue, FileConstraints};
2use leptos::prelude::*;
3
4#[component]
6pub fn FileUploadInput(
7 #[prop(into)]
9 name: String,
10 #[prop(into)]
12 value: Signal<FieldValue>,
13 #[prop(into)]
15 _on_change: Callback<FieldValue>,
16 #[prop(optional)]
18 required: Option<bool>,
19 #[prop(optional)]
21 disabled: Option<bool>,
22 #[prop(optional, into)]
24 class: Option<String>,
25 #[prop(optional, into)]
27 error: Option<String>,
28 #[prop(optional)]
30 _has_error: Option<bool>,
31 #[prop(optional)]
33 constraints: Option<FileConstraints>,
34 #[prop(optional)]
36 max_files: Option<usize>,
37 #[prop(optional)]
39 show_preview: Option<bool>,
40 #[prop(optional)]
42 show_progress: Option<bool>,
43 #[prop(optional)]
45 multiple: Option<bool>,
46) -> impl IntoView {
47 let max_files = max_files.unwrap_or(10);
48 let show_preview = show_preview.unwrap_or(true);
49 let _show_progress = show_progress.unwrap_or(true);
50 let multiple = multiple.unwrap_or(true);
51
52 let current_files = move || match value.get() {
53 FieldValue::Array(files) => files,
54 _ => Vec::new(),
55 };
56
57 let file_count = move || current_files().len();
58
59 let constraints_clone = constraints.clone();
60 let constraints_info = move || {
61 if let Some(constraints) = constraints_clone.as_ref() {
62 let mut info = Vec::new();
63
64 if let Some(max_size) = constraints.max_size {
65 info.push(format!("Max size: {}MB", max_size / (1024 * 1024)));
66 }
67
68 if !constraints.accept.is_empty() {
69 info.push(format!("Types: {}", constraints.accept.join(", ")));
70 }
71
72 if constraints.multiple {
73 info.push("Multiple files allowed".to_string());
74 }
75
76 info.join(" • ")
77 } else {
78 "No restrictions".to_string()
79 }
80 };
81
82 let file_list_view = move || {
83 if show_preview && file_count() > 0 {
84 Some(view! {
85 <div class="file-list">
86 <div class="file-list-header">
87 <h4>"Selected Files"</h4>
88 </div>
89 <div class="file-grid">
90 {current_files().iter().map(|file| {
91 match file {
92 FieldValue::String(file_name) => {
93 view! {
94 <div class="file-preview">
95 <div class="file-icon">"📁"</div>
96 <div class="file-info">
97 <div class="file-name">{file_name.clone()}</div>
98 <div class="file-size">"Unknown size"</div>
99 </div>
100 </div>
101 }
102 }
103 _ => view! {
104 <div class="file-preview hidden">
105 <div class="file-icon">"📁"</div>
106 <div class="file-info">
107 <div class="file-name">{"Unknown file".to_string()}</div>
108 <div class="file-size">"Unknown size"</div>
109 </div>
110 </div>
111 }
112 }
113 }).collect::<Vec<_>>()}
114 </div>
115 </div>
116 })
117 } else {
118 None
119 }
120 };
121
122 let error_view = move || {
123 error.as_ref().map(|error_msg| {
124 view! {
125 <div class="error-message">
126 {error_msg.clone()}
127 </div>
128 }
129 })
130 };
131
132 view! {
133 <div class={format!("file-upload-input {}", class.unwrap_or_default())}>
134 <div class="file-upload-area">
135 <div class="drop-zone">
136 <div class="upload-icon">"📁"</div>
137 <div class="upload-text">
138 <p>"Drag and drop files here or"</p>
139 <label class="file-input-label">
140 <input
141 type="file"
142 name={name.clone()}
143 multiple={constraints.as_ref().map(|c| c.multiple).unwrap_or(multiple)}
144 accept={constraints.as_ref().map(|c| c.accept.join(",")).unwrap_or_default()}
145 required={required}
146 disabled={disabled}
147 class="file-input"
148 />
149 "Browse Files"
150 </label>
151 </div>
152 <div class="upload-constraints">{constraints_info()}</div>
153 <div class="upload-limit">"Files: " {file_count} "/" {max_files}</div>
154 </div>
155 </div>
156
157 {file_list_view}
158 {error_view}
159 </div>
160 }
161}