use gpui::{AnyElement, ClickEvent, ElementId, IntoElement};
use crate::prelude::*;
#[derive(IntoElement, RegisterComponent)]
pub struct FileInput {
id: ElementId,
label: SharedString,
hint: Option<SharedString>,
disabled: bool,
on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
}
impl FileInput {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
label: "Click to upload".into(),
hint: None,
disabled: false,
on_click: None,
}
}
pub fn label(mut self, label: impl Into<SharedString>) -> Self {
self.label = label.into();
self
}
pub fn hint(mut self, hint: impl Into<SharedString>) -> Self {
self.hint = Some(hint.into());
self
}
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
pub fn on_click(
mut self,
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
) -> Self {
self.on_click = Some(Box::new(handler));
self
}
}
impl RenderOnce for FileInput {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
v_flex()
.id(self.id)
.items_center()
.justify_center()
.gap_2()
.w_full()
.py_8()
.rounded_lg()
.border_2()
.border_dashed()
.border_color(semantic::border(cx))
.when(!self.disabled, |this| this.cursor_pointer())
.when(self.disabled, |this| this.opacity(0.5))
.child(
Icon::new(IconName::Paperclip)
.size(IconSize::Medium)
.color(Color::Muted),
)
.child(Label::new(self.label).size(LabelSize::Small))
.when_some(self.hint, |this, hint| {
this.child(Label::new(hint).size(LabelSize::XSmall).color(Color::Muted))
})
.when_some(self.on_click.filter(|_| !self.disabled), |this, handler| {
this.on_click(handler)
})
}
}
impl Component for FileInput {
fn scope() -> ComponentScope {
ComponentScope::Input
}
fn description() -> Option<&'static str> {
Some(
"A styled file-upload trigger. No OS file-dialog API exists in this codebase — \
purely presentational, caller wires `on_click` to their own integration.",
)
}
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
Some(
v_flex()
.gap_4()
.child(
FileInput::new("file-input-default")
.hint("PNG, JPG up to 10MB")
.on_click(|_, _, _| {})
.into_any_element(),
)
.child(
FileInput::new("file-input-selected")
.label("resume.pdf")
.hint("245 KB — click to replace")
.on_click(|_, _, _| {})
.into_any_element(),
)
.child(
FileInput::new("file-input-disabled")
.disabled(true)
.into_any_element(),
)
.into_any_element(),
)
}
}