Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions crates/site-app/src/components/icons.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,25 @@ pub fn EnvelopeHeroIcon() -> impl IntoView {
}
}

#[component]
pub fn EyeHeroIcon() -> impl IntoView {
view! {
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
}
}

#[component]
pub fn EyeSlashHeroIcon() -> impl IntoView {
view! {
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88" />
</svg>
}
}

#[component]
pub fn HashtagHeroIcon() -> impl IntoView {
view! {
Expand All @@ -47,6 +66,24 @@ pub fn HashtagHeroIcon() -> impl IntoView {
}
}

#[component]
pub fn GlobeAltHeroIcon() -> impl IntoView {
view! {
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 0 1 3 12c0-1.605.42-3.113 1.157-4.418" />
</svg>
}
}

#[component]
pub fn KeyHeroIcon() -> impl IntoView {
view! {
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z" />
</svg>
}
}

#[component]
pub fn LockClosedHeroIcon() -> impl IntoView {
view! {
Expand Down
126 changes: 101 additions & 25 deletions crates/site-app/src/components/input_field.rs
Original file line number Diff line number Diff line change
@@ -1,47 +1,79 @@
mod specialized;

use leptos::{ev::Event, prelude::*};
use leptos::{
ev::{Event, MouseEvent},
prelude::*,
};

pub use self::specialized::*;
use crate::components::{
icons::LockClosedHeroIcon, ArchiveBoxHeroIcon, EnvelopeHeroIcon, UserHeroIcon,
icons::LockClosedHeroIcon, ArchiveBoxHeroIcon, EnvelopeHeroIcon, EyeHeroIcon,
EyeSlashHeroIcon, GlobeAltHeroIcon, KeyHeroIcon, UserHeroIcon,
};

#[derive(Clone, Copy)]
pub enum InputIcon {
ArchiveBox,
Envelope,
Eye,
EyeSlash,
GlobeAlt,
Key,
LockClosed,
User,
}

impl IntoAny for InputIcon {
fn into_any(self) -> AnyView {
match self {
InputIcon::ArchiveBox => {
view! { <ArchiveBoxHeroIcon {..} class="size-6" /> }.into_any()
}
InputIcon::Envelope => {
view! { <EnvelopeHeroIcon {..} class="size-6" /> }.into_any()
}
InputIcon::LockClosed => {
view! { <LockClosedHeroIcon {..} class="size-6" /> }.into_any()
}
InputIcon::User => {
view! { <UserHeroIcon {..} class="size-6" /> }.into_any()
}
macro_rules! icon_match {
($self_expr:expr, $click_handler:expr, $icon_class:expr, {
$($variant:ident => $component:ident),* $(,)?
}) => {
match $self_expr {
$(
InputIcon::$variant => view! {
<$component {..} class=$icon_class on:click=$click_handler />
}.into_any(),
)*
}
};
}

impl InputIcon {
fn into_any(self, click_handler: Option<Callback<MouseEvent>>) -> AnyView {
const ICON_CLASS: &str = "size-6";

let click_handler = move |e| {
if let Some(h) = click_handler {
h.run(e)
}
};

icon_match!(self, click_handler, ICON_CLASS, {
ArchiveBox => ArchiveBoxHeroIcon,
Envelope => EnvelopeHeroIcon,
Eye => EyeHeroIcon,
EyeSlash => EyeSlashHeroIcon,
GlobeAlt => GlobeAltHeroIcon,
Key => KeyHeroIcon,
LockClosed => LockClosedHeroIcon,
User => UserHeroIcon,
})
}
}

#[component]
pub fn InputField(
id: &'static str,
label_text: &'static str,
input_type: &'static str,
placeholder: &'static str,
#[prop(optional_no_strip)] before: Option<InputIcon>,
#[prop(optional_no_strip)] after: Option<InputIcon>,
#[prop(into)] id: Signal<&'static str>,
#[prop(into)] label_text: Signal<&'static str>,
#[prop(into)] input_type: Signal<&'static str>,
#[prop(into)] placeholder: Signal<&'static str>,
#[prop(into, optional_no_strip)] before: MaybeProp<InputIcon>,
#[prop(into, optional_no_strip)] after: MaybeProp<InputIcon>,
#[prop(into, optional_no_strip)] before_click_callback: Option<
Callback<MouseEvent>,
>,
#[prop(into, optional_no_strip)] after_click_callback: Option<
Callback<MouseEvent>,
>,
input_signal: impl Fn() -> String + Send + 'static,
output_signal: impl Fn(Event) + Send + 'static,
#[prop(default = false)] autofocus: bool,
Expand All @@ -60,13 +92,13 @@ pub fn InputField(
<label for=id class=OUTER_WRAPPER_CLASS>
<p class=LABEL_CLASS>{ label_text }</p>
<div class=INPUT_WRAPPER_CLASS>
{ move || before.map(|i| i.into_any()).unwrap_or(().into_any()) }
{ move || before().map(|i| i.into_any(before_click_callback)) }
<input
class=INPUT_CLASS type=input_type autofocus=autofocus
placeholder=placeholder id=id
on:input={move |ev| output_signal(ev)} prop:value={move || input_signal()}
/>
{ move || after.map(|i| i.into_any()).unwrap_or(().into_any()) }
{ move || after().map(|i| i.into_any(after_click_callback)) }
</div>
<div class=HINT_WRAPPER_CLASS>
{ move || error_hint().map(|e| view! {
Expand All @@ -79,3 +111,47 @@ pub fn InputField(
</label>
}
}

#[component]
pub fn HideableInputField(
#[prop(default = true)] hidden_by_default: bool,
id: &'static str,
label_text: &'static str,
unhidden_input_type: &'static str,
placeholder: &'static str,
#[prop(into, optional_no_strip)] before: MaybeProp<InputIcon>,
#[prop(into, optional_no_strip)] before_click_callback: Option<
Callback<MouseEvent>,
>,
input_signal: impl Fn() -> String + Send + 'static,
output_signal: impl Fn(Event) + Send + 'static,
#[prop(default = false)] autofocus: bool,
#[prop(into)] error_hint: MaybeProp<String>,
#[prop(into)] warn_hint: MaybeProp<String>,
) -> impl IntoView {
let input_visible = RwSignal::new(!hidden_by_default);
let input_type = Signal::derive(move || match input_visible() {
true => unhidden_input_type,
false => "password",
});
let after = Signal::derive(move || match input_visible() {
true => InputIcon::Eye,
false => InputIcon::EyeSlash,
});
let after_click_callback = Callback::new(move |_| {
input_visible.update(move |v| {
*v = !*v;
})
});

view! {
<InputField
id=id label_text=label_text input_type=input_type placeholder=placeholder
before=before after=after
before_click_callback=before_click_callback after_click_callback=after_click_callback
input_signal=input_signal output_signal=output_signal
autofocus=autofocus
error_hint=error_hint warn_hint=warn_hint
/>
}
}
34 changes: 17 additions & 17 deletions crates/site-app/src/components/input_field/specialized.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
use leptos::{ev::Event, prelude::*};

use super::{InputField, InputIcon};
use crate::components::HideableInputField;

#[component]
pub fn NameInputField(
#[prop(default = "name")] id: &'static str,
#[prop(default = "Full Name")] label_text: &'static str,
#[prop(default = "text")] input_type: &'static str,
#[prop(default = "")] placeholder: &'static str,
#[prop(default =
Some(InputIcon::User)
#[prop(into, default =
InputIcon::User.into()
)]
before: Option<InputIcon>,
#[prop(optional)] after: Option<InputIcon>,
before: MaybeProp<InputIcon>,
#[prop(optional)] after: MaybeProp<InputIcon>,
input_signal: impl Fn() -> String + Send + 'static,
output_signal: impl Fn(Event) + Send + 'static,
#[prop(default = false)] autofocus: bool,
Expand All @@ -36,11 +37,11 @@ pub fn EmailInputField(
#[prop(default = "Email Address")] label_text: &'static str,
#[prop(default = "text")] input_type: &'static str,
#[prop(default = "")] placeholder: &'static str,
#[prop(default =
Some(InputIcon::Envelope)
#[prop(into, default =
InputIcon::Envelope.into()
)]
before: Option<InputIcon>,
#[prop(optional)] after: Option<InputIcon>,
before: MaybeProp<InputIcon>,
#[prop(into, optional)] after: MaybeProp<InputIcon>,
input_signal: impl Fn() -> String + Send + 'static,
output_signal: impl Fn(Event) + Send + 'static,
#[prop(default = false)] autofocus: bool,
Expand All @@ -62,24 +63,23 @@ pub fn EmailInputField(
pub fn PasswordInputField(
#[prop(default = "password")] id: &'static str,
#[prop(default = "Password")] label_text: &'static str,
#[prop(default = "password")] input_type: &'static str,
#[prop(default = "text")] unhidden_input_type: &'static str,
#[prop(default = "")] placeholder: &'static str,
#[prop(default =
Some(InputIcon::LockClosed)
#[prop(into, default =
InputIcon::LockClosed.into()
)]
before: Option<InputIcon>,
#[prop(optional)] after: Option<InputIcon>,
before: MaybeProp<InputIcon>,
input_signal: impl Fn() -> String + Send + 'static,
output_signal: impl Fn(Event) + Send + 'static,
#[prop(default = false)] autofocus: bool,
#[prop(into)] error_hint: MaybeProp<String>,
#[prop(into)] warn_hint: MaybeProp<String>,
) -> impl IntoView {
view! {
<InputField
id=id label_text=label_text
input_type=input_type placeholder=placeholder autofocus=autofocus
before=before after=after
<HideableInputField
id=id label_text=label_text unhidden_input_type=unhidden_input_type
placeholder=placeholder autofocus=autofocus
before=before
input_signal=input_signal output_signal=output_signal
error_hint=error_hint warn_hint=warn_hint
/>
Expand Down
2 changes: 1 addition & 1 deletion crates/site-app/src/pages/create_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ pub fn CreateCachePage() -> impl IntoView {
<div class="flex flex-col gap-4">
<InputField
id="name" label_text="Cache Name" input_type="text" placeholder=""
before=Some(InputIcon::ArchiveBox)
before=InputIcon::ArchiveBox
input_signal=read_name output_signal=write_name
error_hint=name_error_hint warn_hint=name_warn_hint autofocus=true
/>
Expand Down
6 changes: 3 additions & 3 deletions crates/site-app/src/pages/create_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ pub fn CreateStorePage() -> impl IntoView {
<div class="h-0 border-t-[1.5px] border-base-6 w-full" />

<InputField
id="name" label_text="Cache Name" input_type="text" placeholder=""
before=Some(InputIcon::ArchiveBox)
id="name" label_text="Store Name" input_type="text" placeholder=""
before={InputIcon::ArchiveBox}
input_signal=read_name output_signal=write_name
error_hint=name_error_hint warn_hint=name_warn_hint autofocus=true
/>
Expand All @@ -141,7 +141,7 @@ pub fn CreateStorePage() -> impl IntoView {
on:click=submit_action
>
<div class="size-4" />
"Create Cache"
"Create Store"
<LoadingCircle {..}
class="size-4 transition-opacity"
class=("opacity-0", move || { !loading() })
Expand Down
17 changes: 12 additions & 5 deletions crates/site-app/src/pages/create_store/credentials_input.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use leptos::prelude::*;
use models::R2StorageCredentials;

use crate::{components::InputField, reactive_utils::touched_input_bindings};
use crate::{
components::{HideableInputField, InputField, InputIcon},
reactive_utils::touched_input_bindings,
};

#[component]
pub fn CredentialsInput(
Expand Down Expand Up @@ -84,25 +87,29 @@ pub fn CredentialsInput(

view! {
<div class="flex flex-col gap-2">
<InputField
id="access_key" label_text="Access Key" input_type="password" placeholder=""
<HideableInputField
id="access_key" label_text="Access Key" unhidden_input_type="text" placeholder=""
input_signal=read_access_key output_signal=write_access_key
error_hint=access_key_error_hint warn_hint={ MaybeProp::derive(|| None) }
before={InputIcon::Key}
/>
<InputField
id="secret_access_key" label_text="Secret Access Key" input_type="password" placeholder=""
<HideableInputField
id="secret_access_key" label_text="Secret Access Key" unhidden_input_type="text" placeholder=""
input_signal=read_secret_access_key output_signal=write_secret_access_key
error_hint=secret_access_key_error_hint warn_hint={ MaybeProp::derive(|| None) }
before={InputIcon::Key}
/>
<InputField
id="bucket" label_text="Bucket" input_type="text" placeholder=""
input_signal=read_bucket output_signal=write_bucket
error_hint=bucket_error_hint warn_hint={ MaybeProp::derive(|| None) }
before={InputIcon::ArchiveBox}
/>
<InputField
id="endpoint" label_text="Endpoint" input_type="text" placeholder=""
input_signal=read_endpoint output_signal=write_endpoint
error_hint=endpoint_error_hint warn_hint={ MaybeProp::derive(|| None) }
before={InputIcon::GlobeAlt}
/>
</div>
}
Expand Down