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
36 changes: 0 additions & 36 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion crates/site-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ tracing.workspace = true

gloo-net = { version = "0.6", default-features = false, features = [ "http", "json" ] }
leptos-fetch = { version = "0.4", features = [ "devtools" ] }
leptos-use = { version = "0.16" }
leptos-use = { version = "0.16", default-features = false, features = [
"on_click_outside",
"use_event_listener",
"use_window",
"use_interval_fn",
"use_window_focus",
"use_clipboard",
] }

[features]
default = [ ]
Expand Down
18 changes: 18 additions & 0 deletions crates/site-app/src/components/icons.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ pub fn ArchiveBoxHeroIcon() -> impl IntoView {
}
}

#[component]
pub fn ArrowPathHeroIcon() -> impl IntoView {
view! {
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="animate-[spin_3s_linear_infinite]">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
</svg>
}
}

#[component]
pub fn CheckHeroIcon() -> impl IntoView {
view! {
Expand Down Expand Up @@ -102,6 +111,15 @@ pub fn UserHeroIcon() -> impl IntoView {
}
}

#[component]
pub fn XMarkHeroIcon() -> 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="M6 18 18 6M6 6l12 12" />
</svg>
}
}

mod other {
use leptos::prelude::*;

Expand Down
23 changes: 19 additions & 4 deletions crates/site-app/src/components/input_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,35 @@ pub fn InputField(
const INPUT_WRAPPER_CLASS: &str = "input-field max-w-80";
const INPUT_CLASS: &str = "w-full py-2 focus-visible:outline-none";
const HINT_WRAPPER_CLASS: &str = "flex flex-col";
const ERROR_HINT_CLASS: &str = "text-critical-11 text-sm";
const WARN_HINT_CLASS: &str = "text-warn-11 text-sm";
const ERROR_HINT_CLASS: &str = "animate-fade-down text-critical-11 text-sm";
const WARN_HINT_CLASS: &str = "animate-fade-down text-warn-11 text-sm";

let before_click_callback = move |ce| {
if let Some(callback) = before_click_callback {
callback.run(ce);
}
};
let after_click_callback = move |ce| {
if let Some(callback) = after_click_callback {
callback.run(ce);
}
};

view! {
<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(before_click_callback)) }
{ move || before().map(move |i| view! {
<InputIconComponent icon={i} {..} on:click=before_click_callback />
}) }
<input
class=INPUT_CLASS type=input_type autofocus=autofocus
placeholder=placeholder id=id
on:input={move |ev| output_signal.run(ev)} prop:value={move || input_signal.run(())}
/>
{ move || after().map(|i| i.into_any(after_click_callback)) }
{ move || after().map(move |i| view! {
<InputIconComponent icon={i} {..} on:click=after_click_callback />
}) }
</div>
<div class=HINT_WRAPPER_CLASS>
{ move || error_hint().map(|e| view! {
Expand Down
65 changes: 26 additions & 39 deletions crates/site-app/src/components/input_field/icon.rs
Original file line number Diff line number Diff line change
@@ -1,58 +1,45 @@
use leptos::{ev::MouseEvent, prelude::*};
use leptos::{either::EitherOf11, prelude::*};

use crate::components::{
icons::LockClosedHeroIcon, ArchiveBoxHeroIcon, EnvelopeHeroIcon, EyeHeroIcon,
EyeSlashHeroIcon, GlobeAltHeroIcon, KeyHeroIcon, UserHeroIcon,
icons::LockClosedHeroIcon, ArchiveBoxHeroIcon, ArrowPathHeroIcon,
CheckHeroIcon, EnvelopeHeroIcon, EyeHeroIcon, EyeSlashHeroIcon,
GlobeAltHeroIcon, KeyHeroIcon, UserHeroIcon, XMarkHeroIcon,
};

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

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(),
)*
}
#[component]
pub fn InputIconComponent(icon: InputIcon) -> impl IntoView {
let icon = match icon {
InputIcon::ArchiveBox => EitherOf11::A(view! { <ArchiveBoxHeroIcon />}),
InputIcon::Check => EitherOf11::B(view! { <CheckHeroIcon /> }),
InputIcon::Envelope => EitherOf11::C(view! { <EnvelopeHeroIcon /> }),
InputIcon::Eye => EitherOf11::D(view! { <EyeHeroIcon /> }),
InputIcon::EyeSlash => EitherOf11::E(view! { <EyeSlashHeroIcon /> }),
InputIcon::GlobeAlt => EitherOf11::F(view! { <GlobeAltHeroIcon /> }),
InputIcon::Key => EitherOf11::G(view! { <KeyHeroIcon /> }),
InputIcon::Loading => EitherOf11::H(view! { <ArrowPathHeroIcon /> }),
InputIcon::LockClosed => EitherOf11::I(view! { <LockClosedHeroIcon /> }),
InputIcon::User => EitherOf11::J(view! { <UserHeroIcon /> }),
InputIcon::XMark => EitherOf11::K(view! { <XMarkHeroIcon /> }),
};
}

impl InputIcon {
pub 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,
})
view! {
<div class="size-6 shrink-0">
{ icon }
</div>
}
}
2 changes: 1 addition & 1 deletion crates/site-app/src/components/navbar/org_selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub(super) fn OrgSelectorPopover(user: AuthUser) -> impl IntoView {
<div class="relative">
<div class=CONTAINER_CLASS on:click=toggle>
<span class="text-base-12 text-sm">{ user.name.to_string() }</span>
<div class="flex flex-row items-center gap-0.5">
<div class="flex flex-row items-center gap-0">
<span class="text-sm">
<Suspense fallback=|| "[loading]">
{ move || Suspend::new(active_org_descriptor) }
Expand Down
32 changes: 23 additions & 9 deletions crates/site-app/src/pages/create_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,23 @@ pub fn CreateCachePage() -> impl IntoView {
let (read_name, write_name) = touched_input_bindings(name);
let visibility = RwSignal::new(Visibility::Private);

let query_client = expect_context::<QueryClient>();
let is_available_key_fn = move || sanitized_name().map(|n| n.to_string());
let is_available_query_scope =
crate::resources::cache::cache_name_is_available_query_scope();
let is_available_resource = expect_context::<QueryClient>()
.local_resource(is_available_query_scope, move || {
sanitized_name().map(|n| n.to_string())
});
.local_resource(is_available_query_scope.clone(), is_available_key_fn);
let is_available_fetching = query_client
.subscribe_is_fetching(is_available_query_scope, is_available_key_fn);

let name_after_icon = Memo::new(move |_| {
match (is_available_fetching(), is_available_resource.get()) {
(true, _) => Some(InputIcon::Loading),
(_, Some(Some(Ok(true)))) => Some(InputIcon::Check),
(_, Some(Some(Ok(false)))) => Some(InputIcon::XMark),
_ => None,
}
});

let action = ServerAction::<CreateCache>::new();
let loading = {
Expand All @@ -63,12 +74,14 @@ pub fn CreateCachePage() -> impl IntoView {
None
});
let name_error_hint = MaybeProp::derive(move || {
if let (Some(Some(Ok(false))), Some(sanitized_name)) =
(is_available_resource.get(), sanitized_name())
{
Some(format!("The name \"{sanitized_name}\" is unavailable."))
} else {
None
match (is_available_resource.get(), sanitized_name()) {
(Some(Some(Ok(false))), Some(sanitized_name)) => {
Some(format!("The name \"{sanitized_name}\" is unavailable."))
}
(Some(Some(Err(_))), _) => {
Some("Sorry, something went wrong.".to_owned())
}
_ => None,
}
});

Expand Down Expand Up @@ -107,6 +120,7 @@ pub fn CreateCachePage() -> impl IntoView {
<InputField
id="name" label_text="Cache Name" input_type="text" placeholder=""
before=InputIcon::ArchiveBox
after=name_after_icon
input_signal=read_name output_signal=write_name
error_hint=name_error_hint warn_hint=name_warn_hint autofocus=true
/>
Expand Down
12 changes: 7 additions & 5 deletions crates/site-app/style/src/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@
rgba(27, 50, 73, 0.08) 0px 2px 12px 0px;

/* animations */
--animate-fade-in: fade-in 0.15s ease-out;
--animate-fade-out: fade-out 0.15s ease-in;
--animate-fade-in: fade-in 0.15s linear;
--animate-fade-down: fade-down 0.10s linear;

/* custom grid template columns */
--form-grid-cols: calc(48 * var(--spacing)) minmax(0, 1fr)
Expand All @@ -62,12 +62,14 @@
}
}

@keyframes fade-out {
@keyframes fade-down {
from {
opacity: 1;
opacity: 0;
transform: scaleY(0);
}
to {
opacity: 0;
opacity: 1;
transform: scaleY(1);
}
}

Expand Down