From 67e9a3ef5c7e6a2cd01200bf6c453d66d45f8d46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABlle=20Huisman?= Date: Sat, 28 Jun 2025 19:48:20 +0200 Subject: [PATCH] feat(shield-dioxus): add style --- Cargo.lock | 10 +++ Cargo.toml | 1 + examples/dioxus-axum/Cargo.toml | 1 + examples/dioxus-axum/src/app.rs | 8 +- examples/dioxus-axum/src/main.rs | 12 ++- packages/core/shield/src/action.rs | 15 ++-- packages/core/shield/src/form.rs | 78 ++++++++----------- packages/core/shield/src/shield.rs | 7 +- packages/core/shield/src/shield_dyn.rs | 2 +- .../shield-dioxus-axum/src/integration.rs | 10 ++- .../integrations/shield-dioxus/src/form.rs | 59 +++++++------- .../integrations/shield-dioxus/src/lib.rs | 2 + .../integrations/shield-dioxus/src/style.rs | 21 +++++ .../shield-credentials/src/actions/sign_in.rs | 2 +- .../shield-credentials/src/email_password.rs | 3 - .../src/username_password.rs | 3 - .../shield-oauth/src/actions/sign_in.rs | 7 +- .../src/actions/sign_in_callback.rs | 7 +- .../shield-oauth/src/actions/sign_out.rs | 7 +- .../shield-oidc/src/actions/sign_in.rs | 4 +- .../src/actions/sign_in_callback.rs | 7 +- .../shield-oidc/src/actions/sign_out.rs | 7 +- packages/styles/shield-bootstrap/Cargo.toml | 17 ++++ packages/styles/shield-bootstrap/README.md | 13 ++++ .../styles/shield-bootstrap/src/dioxus.rs | 54 +++++++++++++ packages/styles/shield-bootstrap/src/lib.rs | 2 + 26 files changed, 227 insertions(+), 132 deletions(-) create mode 100644 packages/integrations/shield-dioxus/src/style.rs create mode 100644 packages/styles/shield-bootstrap/Cargo.toml create mode 100644 packages/styles/shield-bootstrap/README.md create mode 100644 packages/styles/shield-bootstrap/src/dioxus.rs create mode 100644 packages/styles/shield-bootstrap/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index bd83c2f..5f6e762 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5319,6 +5319,15 @@ dependencies = [ "utoipa", ] +[[package]] +name = "shield-bootstrap" +version = "0.0.4" +dependencies = [ + "dioxus", + "shield", + "shield-dioxus", +] + [[package]] name = "shield-credentials" version = "0.0.4" @@ -5383,6 +5392,7 @@ dependencies = [ "axum", "dioxus", "shield", + "shield-bootstrap", "shield-dioxus", "shield-dioxus-axum", "shield-memory", diff --git a/Cargo.toml b/Cargo.toml index f93fe02..f80c162 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ serde_json = "1.0.133" shield = { path = "./packages/core/shield", version = "0.0.4" } shield-actix = { path = "./packages/integrations/shield-actix", version = "0.0.4" } shield-axum = { path = "./packages/integrations/shield-axum", version = "0.0.4" } +shield-bootstrap = { path = "./packages/styles/shield-bootstrap", version = "0.0.4" } shield-credentials = { path = "./packages/methods/shield-credentials", version = "0.0.4" } shield-diesel = { path = "./packages/storage/shield-diesel", version = "0.0.4" } shield-dioxus = { path = "./packages/integrations/shield-dioxus", version = "0.0.4" } diff --git a/examples/dioxus-axum/Cargo.toml b/examples/dioxus-axum/Cargo.toml index a697e58..68f891e 100644 --- a/examples/dioxus-axum/Cargo.toml +++ b/examples/dioxus-axum/Cargo.toml @@ -30,6 +30,7 @@ web = ["dioxus/web"] axum = { workspace = true, optional = true } dioxus = { workspace = true, features = ["router", "fullstack"] } shield.workspace = true +shield-bootstrap = { workspace = true, features = ["dioxus"] } shield-dioxus.workspace = true shield-dioxus-axum = { workspace = true, optional = true } shield-memory = { workspace = true, optional = true } diff --git a/examples/dioxus-axum/src/app.rs b/examples/dioxus-axum/src/app.rs index edecc7f..8c8ad0c 100644 --- a/examples/dioxus-axum/src/app.rs +++ b/examples/dioxus-axum/src/app.rs @@ -1,4 +1,4 @@ -use dioxus::prelude::*; +use dioxus::{document::Stylesheet, prelude::*}; use shield_dioxus::ShieldRouter; use crate::home::Home; @@ -17,6 +17,12 @@ enum Route { #[component] pub fn App() -> Element { rsx! { + Stylesheet { + href: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css", + integrity: "sha384-LN+7fdVzj6u52u30Kp6M/trliBMCMKTyK833zpbD+pXdCLuTusPj697FH4R/5mcr", + crossorigin: "anonymous" + } + main { Router:: {} } diff --git a/examples/dioxus-axum/src/main.rs b/examples/dioxus-axum/src/main.rs index f34f878..b4e0ed1 100644 --- a/examples/dioxus-axum/src/main.rs +++ b/examples/dioxus-axum/src/main.rs @@ -5,7 +5,11 @@ use crate::app::App; #[cfg(not(feature = "server"))] fn main() { - dioxus::launch(App); + use shield_bootstrap::dioxus::BootstrapStyle; + + dioxus::LaunchBuilder::new() + .with_context(BootstrapStyle::default().context()) + .launch(App) } #[cfg(feature = "server")] @@ -19,7 +23,8 @@ async fn main() { prelude::{DioxusRouterExt, *}, }; use shield::{Shield, ShieldOptions}; - use shield_dioxus_axum::{ShieldLayer, provide_axum_integration}; + use shield_bootstrap::dioxus::BootstrapStyle; + use shield_dioxus_axum::{DioxusAxumIntegration, ShieldLayer}; use shield_memory::{MemoryStorage, User}; use shield_oidc::{Keycloak, OidcMethod}; use tokio::net::TcpListener; @@ -61,7 +66,8 @@ async fn main() { let router = Router::new() .serve_dioxus_application( ServeConfigBuilder::new() - .context_provider(provide_axum_integration::) + .context(DioxusAxumIntegration::::default().context()) + .context(BootstrapStyle::default().context()) .build() .unwrap(), App, diff --git a/packages/core/shield/src/action.rs b/packages/core/shield/src/action.rs index d65f1a3..2872b6f 100644 --- a/packages/core/shield/src/action.rs +++ b/packages/core/shield/src/action.rs @@ -15,7 +15,7 @@ pub const SIGN_OUT_ACTION_ID: &str = "sign-out"; pub trait Action: ErasedAction + Send + Sync { fn id(&self) -> String; - fn render(&self, provider: P) -> Form; + fn form(&self, provider: P) -> Form; async fn call( &self, @@ -29,7 +29,7 @@ pub trait Action: ErasedAction + Send + Sync { pub trait ErasedAction: Send + Sync { fn erased_id(&self) -> String; - fn erased_render(&self, provider: Box) -> Form; + fn erased_form(&self, provider: Box) -> Form; async fn erased_call( &self, @@ -48,8 +48,8 @@ macro_rules! erased_action { self.id() } - fn erased_render(&self, provider: Box) -> $crate::Form { - self.render(*provider.downcast().expect("TODO")) + fn erased_form(&self, provider: Box) -> $crate::Form { + self.form(*provider.downcast().expect("TODO")) } async fn erased_call( @@ -87,11 +87,8 @@ pub(crate) mod tests { TEST_ACTION_ID.to_owned() } - fn render(&self, _provider: TestProvider) -> Form { - Form { - inputs: vec![], - attributes: None, - } + fn form(&self, _provider: TestProvider) -> Form { + Form { inputs: vec![] } } async fn call( diff --git a/packages/core/shield/src/form.rs b/packages/core/shield/src/form.rs index 8ea6932..4304357 100644 --- a/packages/core/shield/src/form.rs +++ b/packages/core/shield/src/form.rs @@ -1,32 +1,18 @@ -use std::collections::HashMap; - use serde::{Deserialize, Serialize}; -/// HTML [attribute](https://html.spec.whatwg.org/multipage/syntax.html#attributes-2). -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum Attribute { - Boolean(bool), - String(String), -} - -/// HTML [form](https://html.spec.whatwg.org/multipage/forms.html#the-form-element). #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Form { pub inputs: Vec, - pub attributes: Option>, } -/// HTML [input](https://html.spec.whatwg.org/multipage/input.html#the-input-element). #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Input { pub name: String, pub label: Option, pub r#type: InputType, pub value: Option, - pub attributes: Option>, } -/// HTML input [type](https://html.spec.whatwg.org/multipage/input.html#attr-input-type) and [attributes](https://html.spec.whatwg.org/multipage/input.html#input-type-attr-summary). #[derive(Clone, Debug, Deserialize, Serialize)] pub enum InputType { Button(InputTypeButton), @@ -53,12 +39,38 @@ pub enum InputType { Week(InputTypeWeek), } -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct InputTypeButton { - pub popovertarget: Option, - pub popovertargetaction: Option, +impl InputType { + pub fn as_str(&self) -> &'static str { + match self { + InputType::Button(_) => "button", + InputType::Checkbox(_) => "checkbox", + InputType::Color(_) => "color", + InputType::Date(_) => "date", + InputType::DatetimeLocal(_) => "datetime-local", + InputType::Email(_) => "email", + InputType::File(_) => "file", + InputType::Hidden(_) => "hidden", + InputType::Image(_) => "image", + InputType::Month(_) => "month", + InputType::Number(_) => "number", + InputType::Password(_) => "password", + InputType::Radio(_) => "radio", + InputType::Range(_) => "range", + InputType::Reset(_) => "reset", + InputType::Search(_) => "search", + InputType::Submit(_) => "submit", + InputType::Tel(_) => "tel", + InputType::Text(_) => "text", + InputType::Time(_) => "time", + InputType::Url(_) => "url", + InputType::Week(_) => "week", + } + } } +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct InputTypeButton {} + #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct InputTypeCheckbox { pub checked: Option, @@ -98,7 +110,6 @@ pub struct InputTypeDatetimeLocal { #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct InputTypeEmail { pub autocomplete: Option, - pub dirname: Option, pub list: Option, pub maxlength: Option, pub minlength: Option, @@ -120,21 +131,13 @@ pub struct InputTypeFile { #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct InputTypeHidden { pub autocomplete: Option, - pub dirname: Option, pub required: Option, } #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct InputTypeImage { pub alt: Option, - pub formaction: Option, - pub formenctype: Option, - pub formmethod: Option, - pub formnovalidate: Option, - pub formtarget: Option, pub height: Option, - pub popovertarget: Option, - pub popovertargetaction: Option, pub src: Option, pub width: Option, } @@ -165,7 +168,6 @@ pub struct InputTypeNumber { #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct InputTypePassword { pub autocomplete: Option, - pub dirname: Option, pub maxlength: Option, pub minlength: Option, pub pattern: Option, @@ -191,15 +193,11 @@ pub struct InputTypeRange { } #[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct InputTypeReset { - pub popovertarget: Option, - pub popovertargetaction: Option, -} +pub struct InputTypeReset {} #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct InputTypeSearch { pub autocomplete: Option, - pub dirname: Option, pub list: Option, pub maxlength: Option, pub minlength: Option, @@ -211,21 +209,11 @@ pub struct InputTypeSearch { } #[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct InputTypeSubmit { - pub dirname: Option, - pub formaction: Option, - pub formenctype: Option, - pub formmethod: Option, - pub formnovalidate: Option, - pub formtarget: Option, - pub popovertarget: Option, - pub popovertargetaction: Option, -} +pub struct InputTypeSubmit {} #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct InputTypeTel { pub autocomplete: Option, - pub dirname: Option, pub list: Option, pub maxlength: Option, pub minlength: Option, @@ -239,7 +227,6 @@ pub struct InputTypeTel { #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct InputTypeText { pub autocomplete: Option, - pub dirname: Option, pub list: Option, pub maxlength: Option, pub minlength: Option, @@ -264,7 +251,6 @@ pub struct InputTypeTime { #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct InputTypeUrl { pub autocomplete: Option, - pub dirname: Option, pub list: Option, pub maxlength: Option, pub minlength: Option, diff --git a/packages/core/shield/src/shield.rs b/packages/core/shield/src/shield.rs index 91f4ea5..0af2dbf 100644 --- a/packages/core/shield/src/shield.rs +++ b/packages/core/shield/src/shield.rs @@ -3,7 +3,7 @@ use std::{any::Any, collections::HashMap, sync::Arc}; use futures::future::try_join_all; use crate::{ - Form, error::ShieldError, method::ErasedMethod, options::ShieldOptions, storage::Storage, + error::ShieldError, form::Form, method::ErasedMethod, options::ShieldOptions, storage::Storage, user::User, }; @@ -73,7 +73,8 @@ impl Shield { }; for provider in method.erased_providers().await? { - let form = action.erased_render(provider); + let form = action.erased_form(provider); + forms.push(form); } } @@ -85,7 +86,7 @@ impl Shield { #[cfg(test)] mod tests { use crate::{ - ShieldOptions, + options::ShieldOptions, storage::tests::{TEST_STORAGE_ID, TestStorage}, }; diff --git a/packages/core/shield/src/shield_dyn.rs b/packages/core/shield/src/shield_dyn.rs index a8cd474..ffcd4d3 100644 --- a/packages/core/shield/src/shield_dyn.rs +++ b/packages/core/shield/src/shield_dyn.rs @@ -2,7 +2,7 @@ use std::{any::Any, sync::Arc}; use async_trait::async_trait; -use crate::{Form, error::ShieldError, shield::Shield, user::User}; +use crate::{error::ShieldError, form::Form, shield::Shield, user::User}; #[async_trait] pub trait DynShield: Send + Sync { diff --git a/packages/integrations/shield-dioxus-axum/src/integration.rs b/packages/integrations/shield-dioxus-axum/src/integration.rs index b1d3980..28dabdb 100644 --- a/packages/integrations/shield-dioxus-axum/src/integration.rs +++ b/packages/integrations/shield-dioxus-axum/src/integration.rs @@ -9,6 +9,12 @@ use shield_dioxus::{DioxusIntegration, DioxusIntegrationDyn}; pub struct DioxusAxumIntegration(PhantomData); +impl DioxusAxumIntegration { + pub fn context(self) -> DioxusIntegrationDyn { + DioxusIntegrationDyn::new(self) + } +} + impl Default for DioxusAxumIntegration { fn default() -> Self { Self(Default::default()) @@ -29,7 +35,3 @@ impl DioxusIntegration for DioxusAxumIntegration { session } } - -pub fn provide_axum_integration() -> DioxusIntegrationDyn { - DioxusIntegrationDyn::new(DioxusAxumIntegration::::default()) -} diff --git a/packages/integrations/shield-dioxus/src/form.rs b/packages/integrations/shield-dioxus/src/form.rs index 6bd830f..b5ae8c9 100644 --- a/packages/integrations/shield-dioxus/src/form.rs +++ b/packages/integrations/shield-dioxus/src/form.rs @@ -18,19 +18,19 @@ impl ToRsx for Form { impl ToRsx for Input { fn to_rsx(&self) -> Element { let input = match &self.r#type { - InputType::Button(button) => rsx! { + InputType::Button(_button) => rsx! { input { name: self.name.clone(), value: self.value.clone(), + r#type: "button", - popovertarget: button.popovertarget.clone(), - popovertargetaction: button.popovertargetaction.clone(), } }, InputType::Checkbox(checkbox) => rsx! { input { name: self.name.clone(), value: self.value.clone(), + r#type: "checkbox", checked: checkbox.checked, required: checkbox.required, @@ -40,6 +40,7 @@ impl ToRsx for Input { input { name: self.name.clone(), value: self.value.clone(), + r#type: "color", "alpha": color.alpha, autocomplete: color.autocomplete.clone(), @@ -51,6 +52,7 @@ impl ToRsx for Input { input { name: self.name.clone(), value: self.value.clone(), + r#type: "date", autocomplete: date.autocomplete.clone(), list: date.list.clone(), @@ -65,6 +67,7 @@ impl ToRsx for Input { input { name: self.name.clone(), value: self.value.clone(), + r#type: "datetime-local", autocomplete: datetime_local.autocomplete.clone(), list: datetime_local.list.clone(), @@ -79,9 +82,9 @@ impl ToRsx for Input { input { name: self.name.clone(), value: self.value.clone(), + r#type: "email", autocomplete: email.autocomplete.clone(), - "dirname": email.dirname.clone(), list: email.list.clone(), maxlength: email.maxlength.clone(), minlength: email.minlength.clone(), @@ -97,6 +100,7 @@ impl ToRsx for Input { input { name: self.name.clone(), value: self.value.clone(), + r#type: "file", accept: file.accept.clone(), multiple: file.multiple, @@ -107,9 +111,9 @@ impl ToRsx for Input { input { name: self.name.clone(), value: self.value.clone(), + r#type: "hidden", autocomplete: hidden.autocomplete.clone(), - "dirname": hidden.dirname.clone(), required: hidden.required, } }, @@ -117,16 +121,10 @@ impl ToRsx for Input { input { name: self.name.clone(), value: self.value.clone(), + r#type: "image", alt: image.alt.clone(), - formaction: image.formaction.clone(), - formenctype: image.formenctype.clone(), - formmethod: image.formmethod.clone(), - formnovalidate: image.formnovalidate, - formtarget: image.formtarget.clone(), height: image.height.clone(), - popovertarget: image.popovertarget.clone(), - popovertargetaction: image.popovertargetaction.clone(), src: image.src.clone(), width: image.width.clone(), } @@ -135,6 +133,7 @@ impl ToRsx for Input { input { name: self.name.clone(), value: self.value.clone(), + r#type: "month", autocomplete: month.autocomplete.clone(), list: month.list.clone(), @@ -149,6 +148,7 @@ impl ToRsx for Input { input { name: self.name.clone(), value: self.value.clone(), + r#type: "number", autocomplete: number.autocomplete.clone(), list: number.list.clone(), @@ -164,9 +164,9 @@ impl ToRsx for Input { input { name: self.name.clone(), value: self.value.clone(), + r#type: "password", autocomplete: password.autocomplete.clone(), - "dirname": password.dirname.clone(), maxlength: password.maxlength.clone(), minlength: password.minlength.clone(), pattern: password.pattern.clone(), @@ -180,6 +180,7 @@ impl ToRsx for Input { input { name: self.name.clone(), value: self.value.clone(), + r#type: "radio", checked: radio.checked, required: radio.required, @@ -189,6 +190,7 @@ impl ToRsx for Input { input { name: self.name.clone(), value: self.value.clone(), + r#type: "range", autocomplete: range.autocomplete.clone(), list: range.list.clone(), @@ -197,22 +199,21 @@ impl ToRsx for Input { step: range.step.clone(), } }, - InputType::Reset(reset) => rsx! { + InputType::Reset(_reset) => rsx! { input { name: self.name.clone(), value: self.value.clone(), + r#type: "reset", - popovertarget: reset.popovertarget.clone(), - popovertargetaction: reset.popovertargetaction.clone(), } }, InputType::Search(search) => rsx! { input { name: self.name.clone(), value: self.value.clone(), + r#type: "search", autocomplete: search.autocomplete.clone(), - "dirname": search.dirname.clone(), list: search.list.clone(), maxlength: search.maxlength.clone(), minlength: search.minlength.clone(), @@ -223,28 +224,21 @@ impl ToRsx for Input { size: search.size.clone(), } }, - InputType::Submit(submit) => rsx! { + InputType::Submit(_submit) => rsx! { input { name: self.name.clone(), value: self.value.clone(), + r#type: "submit", - "dirname": submit.dirname.clone(), - formaction: submit.formaction.clone(), - formenctype: submit.formenctype.clone(), - formmethod: submit.formmethod.clone(), - formnovalidate: submit.formnovalidate, - formtarget: submit.formtarget.clone(), - popovertarget: submit.popovertarget.clone(), - popovertargetaction: submit.popovertargetaction.clone(), } }, InputType::Tel(tel) => rsx! { input { name: self.name.clone(), value: self.value.clone(), + r#type: "tel", autocomplete: tel.autocomplete.clone(), - "dirname": tel.dirname.clone(), list: tel.list.clone(), maxlength: tel.maxlength.clone(), minlength: tel.minlength.clone(), @@ -259,9 +253,9 @@ impl ToRsx for Input { input { name: self.name.clone(), value: self.value.clone(), + r#type: "text", autocomplete: text.autocomplete.clone(), - "dirname": text.dirname.clone(), list: text.list.clone(), maxlength: text.maxlength.clone(), minlength: text.minlength.clone(), @@ -276,6 +270,7 @@ impl ToRsx for Input { input { name: self.name.clone(), value: self.value.clone(), + r#type: "time", autocomplete: time.autocomplete.clone(), list: time.list.clone(), @@ -290,9 +285,9 @@ impl ToRsx for Input { input { name: self.name.clone(), value: self.value.clone(), + r#type: "url", autocomplete: url.autocomplete.clone(), - "dirname": url.dirname.clone(), list: url.list.clone(), maxlength: url.maxlength.clone(), minlength: url.minlength.clone(), @@ -307,6 +302,7 @@ impl ToRsx for Input { input { name: self.name.clone(), value: self.value.clone(), + r#type: "week", autocomplete: week.autocomplete.clone(), list: week.list.clone(), @@ -321,8 +317,11 @@ impl ToRsx for Input { rsx! { div { + // TODO: Generate input ID and set for attribute. if let Some(label) = &self.label { - label { "{label}" } + label { + "{label}" + } } {input} diff --git a/packages/integrations/shield-dioxus/src/lib.rs b/packages/integrations/shield-dioxus/src/lib.rs index ac97ca9..303b34a 100644 --- a/packages/integrations/shield-dioxus/src/lib.rs +++ b/packages/integrations/shield-dioxus/src/lib.rs @@ -2,6 +2,8 @@ mod form; mod integration; mod router; mod routes; +mod style; pub use integration::*; pub use router::*; +pub use style::*; diff --git a/packages/integrations/shield-dioxus/src/style.rs b/packages/integrations/shield-dioxus/src/style.rs new file mode 100644 index 0000000..93a0ca8 --- /dev/null +++ b/packages/integrations/shield-dioxus/src/style.rs @@ -0,0 +1,21 @@ +use std::sync::Arc; + +use dioxus::prelude::Element; +use shield::Form; + +pub trait DioxusStyle: Send + Sync { + fn render(&self, forms: &[Form]) -> Element; +} + +#[derive(Clone)] +pub struct ErasedDioxusStyle(Arc); + +impl ErasedDioxusStyle { + pub fn new(integration: I) -> Self { + Self(Arc::new(integration)) + } + + pub fn render(&self, forms: &[Form]) -> Element { + self.0.render(forms) + } +} diff --git a/packages/methods/shield-credentials/src/actions/sign_in.rs b/packages/methods/shield-credentials/src/actions/sign_in.rs index cf6a9b2..89503d8 100644 --- a/packages/methods/shield-credentials/src/actions/sign_in.rs +++ b/packages/methods/shield-credentials/src/actions/sign_in.rs @@ -27,7 +27,7 @@ impl Action Form { + fn form(&self, _provider: CredentialsProvider) -> Form { self.credentials.form() } diff --git a/packages/methods/shield-credentials/src/email_password.rs b/packages/methods/shield-credentials/src/email_password.rs index 011ae1e..06aa745 100644 --- a/packages/methods/shield-credentials/src/email_password.rs +++ b/packages/methods/shield-credentials/src/email_password.rs @@ -51,7 +51,6 @@ impl Credentials for EmailPasswordCredentials ..Default::default() }), value: None, - attributes: None, }, Input { name: "password".to_owned(), @@ -63,10 +62,8 @@ impl Credentials for EmailPasswordCredentials ..Default::default() }), value: None, - attributes: None, }, ], - attributes: None, } } diff --git a/packages/methods/shield-credentials/src/username_password.rs b/packages/methods/shield-credentials/src/username_password.rs index 5e502e2..6f2d18b 100644 --- a/packages/methods/shield-credentials/src/username_password.rs +++ b/packages/methods/shield-credentials/src/username_password.rs @@ -51,7 +51,6 @@ impl Credentials for UsernamePasswordCredentia ..Default::default() }), value: None, - attributes: None, }, Input { name: "password".to_owned(), @@ -63,10 +62,8 @@ impl Credentials for UsernamePasswordCredentia ..Default::default() }), value: None, - attributes: None, }, ], - attributes: None, } } diff --git a/packages/methods/shield-oauth/src/actions/sign_in.rs b/packages/methods/shield-oauth/src/actions/sign_in.rs index d1291b6..cb8d450 100644 --- a/packages/methods/shield-oauth/src/actions/sign_in.rs +++ b/packages/methods/shield-oauth/src/actions/sign_in.rs @@ -19,11 +19,8 @@ impl Action for OauthSignInAction { SIGN_IN_ACTION_ID.to_owned() } - fn render(&self, _provider: OauthProvider) -> Form { - Form { - inputs: vec![], - attributes: None, - } + fn form(&self, _provider: OauthProvider) -> Form { + Form { inputs: vec![] } } async fn call( diff --git a/packages/methods/shield-oauth/src/actions/sign_in_callback.rs b/packages/methods/shield-oauth/src/actions/sign_in_callback.rs index 0f9ff18..d29ac3e 100644 --- a/packages/methods/shield-oauth/src/actions/sign_in_callback.rs +++ b/packages/methods/shield-oauth/src/actions/sign_in_callback.rs @@ -135,11 +135,8 @@ impl Action for OauthSignInCallbackAction { SIGN_IN_CALLBACK_ACTION_ID.to_owned() } - fn render(&self, _provider: OauthProvider) -> Form { - Form { - inputs: vec![], - attributes: None, - } + fn form(&self, _provider: OauthProvider) -> Form { + Form { inputs: vec![] } } async fn call( diff --git a/packages/methods/shield-oauth/src/actions/sign_out.rs b/packages/methods/shield-oauth/src/actions/sign_out.rs index 40b4d4c..614be40 100644 --- a/packages/methods/shield-oauth/src/actions/sign_out.rs +++ b/packages/methods/shield-oauth/src/actions/sign_out.rs @@ -13,11 +13,8 @@ impl Action for OauthSignOutAction { SIGN_OUT_ACTION_ID.to_owned() } - fn render(&self, _provider: OauthProvider) -> Form { - Form { - inputs: vec![], - attributes: None, - } + fn form(&self, _provider: OauthProvider) -> Form { + Form { inputs: vec![] } } async fn call( diff --git a/packages/methods/shield-oidc/src/actions/sign_in.rs b/packages/methods/shield-oidc/src/actions/sign_in.rs index e53afac..b85bd0d 100644 --- a/packages/methods/shield-oidc/src/actions/sign_in.rs +++ b/packages/methods/shield-oidc/src/actions/sign_in.rs @@ -22,16 +22,14 @@ impl Action for OidcSignInAction { SIGN_IN_ACTION_ID.to_owned() } - fn render(&self, provider: OidcProvider) -> Form { + fn form(&self, provider: OidcProvider) -> Form { Form { inputs: vec![Input { name: "submit".to_owned(), label: None, r#type: InputType::Submit(InputTypeSubmit::default()), value: Some(format!("Sign in with {}", provider.name())), - attributes: None, }], - attributes: None, } } diff --git a/packages/methods/shield-oidc/src/actions/sign_in_callback.rs b/packages/methods/shield-oidc/src/actions/sign_in_callback.rs index 2f6fff6..b97aa87 100644 --- a/packages/methods/shield-oidc/src/actions/sign_in_callback.rs +++ b/packages/methods/shield-oidc/src/actions/sign_in_callback.rs @@ -146,11 +146,8 @@ impl Action for OidcSignInCallbackAction { SIGN_IN_CALLBACK_ACTION_ID.to_owned() } - fn render(&self, _provider: OidcProvider) -> Form { - Form { - inputs: vec![], - attributes: None, - } + fn form(&self, _provider: OidcProvider) -> Form { + Form { inputs: vec![] } } async fn call( diff --git a/packages/methods/shield-oidc/src/actions/sign_out.rs b/packages/methods/shield-oidc/src/actions/sign_out.rs index 3c1adde..9cc21ee 100644 --- a/packages/methods/shield-oidc/src/actions/sign_out.rs +++ b/packages/methods/shield-oidc/src/actions/sign_out.rs @@ -13,11 +13,8 @@ impl Action for OidcSignOutAction { SIGN_OUT_ACTION_ID.to_owned() } - fn render(&self, _provider: OidcProvider) -> Form { - Form { - inputs: vec![], - attributes: None, - } + fn form(&self, _provider: OidcProvider) -> Form { + Form { inputs: vec![] } } async fn call( diff --git a/packages/styles/shield-bootstrap/Cargo.toml b/packages/styles/shield-bootstrap/Cargo.toml new file mode 100644 index 0000000..020264a --- /dev/null +++ b/packages/styles/shield-bootstrap/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "shield-bootstrap" +description = "Bootstrap style for Shield." + +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +[features] +dioxus = ["dep:dioxus", "dep:shield-dioxus"] + +[dependencies] +dioxus = { workspace = true, optional = true } +shield.workspace = true +shield-dioxus = { workspace = true, optional = true } diff --git a/packages/styles/shield-bootstrap/README.md b/packages/styles/shield-bootstrap/README.md new file mode 100644 index 0000000..46fa725 --- /dev/null +++ b/packages/styles/shield-bootstrap/README.md @@ -0,0 +1,13 @@ +

Shield Bootstrap

+ +Bootstrap style for Shield. + +## Documentation + +See [the Shield book](https://shield.rustforweb.org/) for documentation. + +## Rust for Web + +The Shield project is part of [Rust for Web](https://github.com/RustForWeb). + +[Rust for Web](https://github.com/RustForWeb) creates and ports web libraries for Rust. All projects are free and open source. diff --git a/packages/styles/shield-bootstrap/src/dioxus.rs b/packages/styles/shield-bootstrap/src/dioxus.rs new file mode 100644 index 0000000..efb75e8 --- /dev/null +++ b/packages/styles/shield-bootstrap/src/dioxus.rs @@ -0,0 +1,54 @@ +use dioxus::prelude::*; +use shield::Form; +use shield_dioxus::{DioxusStyle, ErasedDioxusStyle}; + +#[derive(Default)] +pub struct BootstrapStyle {} + +impl BootstrapStyle { + pub fn context(self) -> ErasedDioxusStyle { + ErasedDioxusStyle::new(self) + } +} + +impl DioxusStyle for BootstrapStyle { + fn render(&self, forms: &[Form]) -> Element { + rsx! { + div { + class: "container", + + h1 { + // TODO: Get from action. + "Sign in" + } + + for form in forms { + form { + for input in &form.inputs { + div { + class: "mb-3", + + if let Some(label) = &input.label { + label { + class: "form-label", + + strong { + "{label}" + } + } + } + + input { + class: "form-control", + name: input.name.clone(), + type: input.r#type.as_str(), + value: input.value.clone(), + } + } + } + } + } + } + } + } +} diff --git a/packages/styles/shield-bootstrap/src/lib.rs b/packages/styles/shield-bootstrap/src/lib.rs new file mode 100644 index 0000000..341e5a0 --- /dev/null +++ b/packages/styles/shield-bootstrap/src/lib.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "dioxus")] +pub mod dioxus;