app: Create a component for modals
This commit is contained in:
parent
1eb815ada9
commit
a90b87e849
2 changed files with 240 additions and 80 deletions
205
app/src/bootstrap.rs
Normal file
205
app/src/bootstrap.rs
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
use yew::prelude::*;
|
||||
|
||||
pub mod bs {
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen(js_namespace = bootstrap)]
|
||||
extern "C" {
|
||||
pub type Modal;
|
||||
|
||||
#[wasm_bindgen(static_method_of = Modal, js_name = "getInstance")]
|
||||
pub fn get_instance(selector: &str) -> Modal;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn hide(this: &Modal);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct ModalProps {
|
||||
pub id: AttrValue,
|
||||
#[prop_or(true)]
|
||||
pub fade: bool,
|
||||
#[prop_or_default]
|
||||
pub centered: bool,
|
||||
#[prop_or_default]
|
||||
pub labeled_by: Option<AttrValue>,
|
||||
pub children: Children,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn Modal(props: &ModalProps) -> Html {
|
||||
let mut class = classes!("modal");
|
||||
|
||||
if props.fade {
|
||||
class.push("fade");
|
||||
}
|
||||
|
||||
let mut dialog_class = classes!("modal-dialog");
|
||||
|
||||
if props.centered {
|
||||
dialog_class.push("modal-dialog-centered");
|
||||
}
|
||||
|
||||
html! {
|
||||
<div
|
||||
{class}
|
||||
id={props.id.clone()}
|
||||
tabindex="-1"
|
||||
aria-labelledby={props.labeled_by.clone()}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class={dialog_class}>
|
||||
<div class="modal-content">
|
||||
{ for props.children.iter() }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct TitledModalProps {
|
||||
pub id: AttrValue,
|
||||
#[prop_or(true)]
|
||||
pub fade: bool,
|
||||
#[prop_or_default]
|
||||
pub centered: bool,
|
||||
pub title: AttrValue,
|
||||
pub children: Children,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn TitledModal(
|
||||
TitledModalProps {
|
||||
id,
|
||||
fade,
|
||||
centered,
|
||||
children,
|
||||
title,
|
||||
}: &TitledModalProps,
|
||||
) -> Html {
|
||||
let label = format!("{id}Label");
|
||||
html! {
|
||||
<Modal {id} {fade} {centered} labeled_by={label.clone()}>
|
||||
<ModalHeader>
|
||||
<h1 class={classes!("modal-title", "fs-5")} id={label}>{title}</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
>
|
||||
</button>
|
||||
</ModalHeader>
|
||||
{ for children.iter() }
|
||||
</Modal>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
pub struct FormModalProps {
|
||||
pub id: AttrValue,
|
||||
#[prop_or(true)]
|
||||
pub fade: bool,
|
||||
#[prop_or_default]
|
||||
pub centered: bool,
|
||||
#[prop_or("Submit".into())]
|
||||
pub submit_label: AttrValue,
|
||||
pub on_submit: Callback<()>,
|
||||
pub title: AttrValue,
|
||||
pub children: Children,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn FormModal(
|
||||
FormModalProps {
|
||||
id,
|
||||
fade,
|
||||
centered,
|
||||
submit_label,
|
||||
title,
|
||||
on_submit,
|
||||
children,
|
||||
}: &FormModalProps,
|
||||
) -> Html {
|
||||
let form_id = format!("{id}Form");
|
||||
|
||||
let on_submit = on_submit.clone();
|
||||
let onsubmit = Callback::from(move |e: SubmitEvent| {
|
||||
e.prevent_default();
|
||||
|
||||
on_submit.emit(());
|
||||
});
|
||||
|
||||
html! {
|
||||
<TitledModal {id} {fade} {centered} {title}>
|
||||
<ModalBody>
|
||||
<form id={form_id.clone()} {onsubmit}>
|
||||
{ for children.iter() }
|
||||
</form>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<button type="button" class={classes!("btn", "btn-danger")} data-bs-dismiss="modal">
|
||||
{"Cancel"}
|
||||
</button>
|
||||
<button type="submit" class={classes!("btn", "btn-primary")} form={form_id}>
|
||||
{submit_label}
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</TitledModal>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct ModalToggleProps {
|
||||
#[prop_or(classes!("btn", "btn-primary"))]
|
||||
pub classes: Classes,
|
||||
pub modal_id: AttrValue,
|
||||
pub children: Children,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn ModalToggleButton(props: &ModalToggleProps) -> Html {
|
||||
html! {
|
||||
<button
|
||||
class={props.classes.clone()}
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target={format!("#{}", props.modal_id)}
|
||||
>
|
||||
{ for props.children.iter() }
|
||||
</button>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct ModalContentProps {
|
||||
pub children: Children,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn ModalHeader(props: &ModalContentProps) -> Html {
|
||||
html! {
|
||||
<div class="modal-header">
|
||||
{ for props.children.iter() }
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn ModalBody(props: &ModalContentProps) -> Html {
|
||||
html! {
|
||||
<div class="modal-body">
|
||||
{ for props.children.iter() }
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn ModalFooter(props: &ModalContentProps) -> Html {
|
||||
html! {
|
||||
<div class="modal-footer">
|
||||
{ for props.children.iter() }
|
||||
</div>
|
||||
}
|
||||
}
|
||||
115
app/src/main.rs
115
app/src/main.rs
|
|
@ -1,18 +1,23 @@
|
|||
use api::{
|
||||
CreateHouseholdRequest, CreateHouseholdResponse, Household, LoginRequest, LoginResponse,
|
||||
};
|
||||
use gloo_storage::{errors::StorageError, LocalStorage, Storage};
|
||||
use itertools::Itertools;
|
||||
use log::Level;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
use wasm_bindgen::{prelude::*, JsCast};
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::HtmlInputElement;
|
||||
use yew::{prelude::*, suspense::use_future};
|
||||
use itertools::Itertools;
|
||||
use yew_router::prelude::*;
|
||||
|
||||
use crate::sidebar::RegaladeSidebar;
|
||||
use api::{
|
||||
CreateHouseholdRequest, CreateHouseholdResponse, Household, LoginRequest, LoginResponse,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
bootstrap::{bs, FormModal, ModalToggleButton},
|
||||
sidebar::RegaladeSidebar,
|
||||
};
|
||||
|
||||
mod bootstrap;
|
||||
mod sidebar;
|
||||
|
||||
const API_ROUTE: &str = match option_env!("REGALADE_API_SERVER_BASE") {
|
||||
|
|
@ -145,17 +150,6 @@ async fn do_new_household(token: String, name: String) -> anyhow::Result<Uuid> {
|
|||
Ok(rsp.id)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_namespace = bootstrap)]
|
||||
extern "C" {
|
||||
type Modal;
|
||||
|
||||
#[wasm_bindgen(static_method_of = Modal, js_name = "getInstance")]
|
||||
fn get_instance(selector: &str) -> Modal;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
fn hide(this: &Modal);
|
||||
}
|
||||
|
||||
async fn fetch_households(token: String) -> anyhow::Result<api::Households> {
|
||||
let rsp = gloo_net::http::Request::get(api!("household"))
|
||||
.header("Authorization", &format!("Bearer {token}"))
|
||||
|
|
@ -214,7 +208,7 @@ fn HouseholdListSelect() -> HtmlResult {
|
|||
.clone()
|
||||
.into_iter()
|
||||
.sorted_by_key(|(_,i)| i.name.clone())
|
||||
.map(mk_household)
|
||||
.map(mk_household)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
|
|
@ -238,9 +232,7 @@ fn HouseholdSelection() -> Html {
|
|||
|
||||
let err = error.clone();
|
||||
let tok = token.clone();
|
||||
let onsubmit = Callback::from(move |e: SubmitEvent| {
|
||||
e.prevent_default();
|
||||
|
||||
let on_submit = Callback::from(move |()| {
|
||||
let document = gloo_utils::document();
|
||||
let token = tok.as_ref().unwrap().to_owned();
|
||||
|
||||
|
|
@ -262,7 +254,7 @@ fn HouseholdSelection() -> Html {
|
|||
log::error!("Could not switch to new household: {e:?}")
|
||||
}
|
||||
|
||||
let modal = Modal::get_instance("#newHsModal");
|
||||
let modal = bs::Modal::get_instance("#newHsModal");
|
||||
modal.hide();
|
||||
|
||||
navigator.push(&Route::Index);
|
||||
|
|
@ -290,70 +282,33 @@ fn HouseholdSelection() -> Html {
|
|||
<HouseholdListSelect />
|
||||
</Suspense>
|
||||
<hr />
|
||||
<button
|
||||
class={classes!("btn", "btn-lg", "btn-primary")}
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#newHsModal"
|
||||
<ModalToggleButton
|
||||
classes={classes!("btn", "btn-lg", "btn-primary")}
|
||||
modal_id="newHsModal"
|
||||
>
|
||||
{"New household"}
|
||||
</button>
|
||||
<div
|
||||
class={classes!("modal", "fade")}
|
||||
</ModalToggleButton>
|
||||
<FormModal
|
||||
id="newHsModal"
|
||||
tabindex="-1"
|
||||
aria-labelledby="newHsModalLabel"
|
||||
aria-hidden="true"
|
||||
centered={true}
|
||||
submit_label={"Create"}
|
||||
title="Create a Household"
|
||||
{on_submit}
|
||||
>
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class={classes!("modal-title", "fs-5")} id="newHsModalLabel">
|
||||
{"Create a household"}
|
||||
</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="newHsForm" {onsubmit}>
|
||||
if let Some(err) = &*error {
|
||||
<div class={classes!("alert", "alert-danger")} role="alert">
|
||||
{err}
|
||||
</div>
|
||||
}
|
||||
<div class="form-floating">
|
||||
<input
|
||||
id="newHsName"
|
||||
class={classes!("form-control")}
|
||||
placeholder="Household name"
|
||||
/>
|
||||
<label for="newHsName">{"Household name"}</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
class={classes!("btn", "btn-secondary")}
|
||||
data-bs-dismiss="modal"
|
||||
>
|
||||
{"Cancel"}
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class={classes!("btn", "btn-primary")}
|
||||
form="newHsForm"
|
||||
>
|
||||
{"Create"}
|
||||
</button>
|
||||
</div>
|
||||
if let Some(err) = &*error {
|
||||
<div class={classes!("alert", "alert-danger")} role="alert">
|
||||
{err}
|
||||
</div>
|
||||
}
|
||||
<div class="form-floating">
|
||||
<input
|
||||
id="newHsName"
|
||||
class={classes!("form-control")}
|
||||
placeholder="Household name"
|
||||
/>
|
||||
<label for="newHsName">{"Household name"}</label>
|
||||
</div>
|
||||
</div>
|
||||
</FormModal>
|
||||
</div>
|
||||
</>},
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue