├── .bumpversion.toml ├── .github ├── dependabot.yml └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── DIOXUS.md ├── LEPTOS.md ├── LICENSE ├── README.md ├── YEW.md ├── assets ├── favicon.png ├── logo-new.png ├── pass-demo.gif ├── tel-demo.gif ├── text-demo.gif └── textarea-demo.gif ├── examples ├── dioxus │ ├── Cargo.toml │ ├── Dioxus.toml │ ├── README.md │ └── src │ │ └── main.rs ├── leptos │ ├── Cargo.toml │ ├── README.md │ ├── index.html │ └── src │ │ └── main.rs └── yew │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── Trunk.toml │ ├── _redirects │ ├── assets │ ├── contact-form-one.png │ ├── form-one.png │ ├── form-three.png │ ├── form-two.png │ └── multi-step-form-one.png │ ├── index.html │ ├── postcss.config.js │ ├── rustfmt.toml │ ├── src │ ├── api.rs │ ├── api │ │ └── auth.rs │ ├── app.rs │ ├── components.rs │ ├── components │ │ ├── common.rs │ │ ├── contact_form_one.rs │ │ ├── login_form_one.rs │ │ ├── login_form_three.rs │ │ ├── login_form_two.rs │ │ └── multi_step_form_one.rs │ ├── main.rs │ ├── pages.rs │ ├── pages │ │ ├── contact_page_one.rs │ │ ├── error.rs │ │ ├── login_page_one.rs │ │ ├── login_page_three.rs │ │ ├── login_page_two.rs │ │ └── multi_step_page_one.rs │ ├── router.rs │ └── tailwind.css │ └── tailwind.config.js └── src ├── countries.rs ├── dioxus.rs ├── leptos.rs ├── lib.rs └── yew.rs /.bumpversion.toml: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.2.5 3 | 4 | [bumpversion:file:Cargo.toml] 5 | search = version = "{current_version}" 6 | replace = version = "{new_version}" 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --all-features --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | **/target/** 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | **/dist/* 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "input-rs" 3 | version = "0.2.5" 4 | edition = "2021" 5 | rust-version = "1.82" 6 | description = "🔤 A highly customizable input component for WASM frameworks like Yew, Dioxus, and Leptos." 7 | license = "Apache-2.0" 8 | keywords = ["input", "yew", "dioxus", "leptos", "input-rs"] 9 | categories = ["web-programming", "science"] 10 | repository = "https://github.com/opensass/input-rs" 11 | documentation = "https://docs.rs/input-rs/" 12 | authors = ["Mahmoud Harmouch "] 13 | exclude = ["assets", "examples"] 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [dependencies] 18 | web-sys = { version = "0.3", default-features = false } 19 | yew = { version = "0.21.0", default-features = false, optional = true } 20 | dioxus = { version = "0.6.3", optional = true } 21 | leptos = { version = "0.7.7", optional = true } 22 | 23 | [dev-dependencies] 24 | bump2version = "0.1.4" 25 | regex = "1.10.2" 26 | serde = { version = "1.0.193", features = ["derive"] } 27 | 28 | [features] 29 | yew = ["dep:yew", ] 30 | dio = ["dioxus", ] 31 | lep = ["leptos", ] 32 | 33 | [profile.release] 34 | opt-level = "z" 35 | debug = false 36 | lto = "thin" 37 | codegen-units = 1 38 | panic = "abort" 39 | strip = "symbols" 40 | incremental = false 41 | 42 | [package.metadata.docs.rs] 43 | all-features = true 44 | rustdoc-args = ["--cfg", "docsrs"] 45 | 46 | [badges] 47 | maintenance = { status = "actively-developed" } 48 | -------------------------------------------------------------------------------- /DIOXUS.md: -------------------------------------------------------------------------------- 1 | # 🧬 Input RS Dioxus Usage 2 | 3 | Adding Input RS to your project is simple: 4 | 5 | 1. Make sure your project is set up with **Dioxus**. Refer to the [Dioxus Getting Started Guide](https://dioxuslabs.com/learn/0.6/getting_started) for setup instructions. 6 | 7 | 1. Add the Input component to your dependencies by including it in your `Cargo.toml` file. 8 | 9 | ```sh 10 | cargo add input-rs --features=dio 11 | ``` 12 | 13 | 1. Import the `Input` components into your Dioxus component and start using it in your app. 14 | 15 | ## 🛠️ Usage 16 | 17 | Incorporating the Input component into your application is easy. Follow these steps: 18 | 19 | 1. Import `Input` into your component: 20 | 21 | ```rust 22 | use dioxus::prelude::*; 23 | use input_rs::dioxus::Input; 24 | ``` 25 | 26 | 1. Use the `Input` component in your application: 27 | 28 | ```rust 29 | use dioxus::prelude::*; 30 | use input_rs::dioxus::Input; 31 | 32 | #[component] 33 | pub fn App() -> Element { 34 | let input_value = use_signal(|| String::new()); 35 | let is_valid = use_signal(|| true); 36 | 37 | fn validate_input(value: String) -> bool { 38 | !value.trim().is_empty() 39 | } 40 | 41 | rsx! { 42 | div { 43 | class: "app-container", 44 | h1 { "Custom Input Example" } 45 | Input { 46 | r#type: "text", 47 | label: "Enter your name:", 48 | id: "name-input", 49 | handle: input_value.clone(), 50 | valid_handle: is_valid.clone(), 51 | validate_function: validate_input, 52 | placeholder: "Type here...", 53 | class: "custom-input", 54 | label_class: "input-label", 55 | field_class: "input-field", 56 | error_message: "This field cannot be empty", 57 | error_class: "input-error", 58 | } 59 | if !is_valid() { 60 | p { class: "error-message", "Please correct the input." } 61 | } 62 | } 63 | } 64 | } 65 | ``` 66 | 67 | ## 🔧 Props 68 | 69 | ### Main Props 70 | 71 | | Property | Type | Description | Default | 72 | | ------------------- | -------------------- | ------------------------------------------------------------------------------------ | -------- | 73 | | `r#type` | `&'static str` | Type of the input (e.g., `text`, `password`, `email`, etc.). | `"text"` | 74 | | `label` | `&'static str` | Label text for the input field. | `""` | 75 | | `id` | `&'static str` | Unique ID for the input element. | `""` | 76 | | `placeholder` | `&'static str` | Placeholder text for the input. | `""` | 77 | | `handle` | `Signal` | Signal handle for the input value. | None | 78 | | `valid_handle` | `Signal` | Signal handle for the validity of the input value. | None | 79 | | `validate_function` | `fn(String) -> bool` | Validation function for the input value. Returns `true` if valid, `false` otherwise. | None | 80 | | `required` | `bool` | Indicates whether the input is required. | `false` | 81 | | `error_message` | `&'static str` | Error message to display if the input is invalid. | `""` | 82 | 83 | ### Styling Props 84 | 85 | ```sh 86 | +-----------------------------+ <-- `class` 87 | | | 88 | | +-----------------------+ | <-- `label_class` 89 | | | Label | | 90 | | +-----------------------+ | 91 | | | 92 | | +-----------------------+ | <-- `field_class` 93 | | | +-------+ +--------+ | | 94 | | | | Input | | Icon | | | <-- `input_class` and `icon_class` 95 | | | +-------+ +--------+ | | 96 | | +-----------------------+ | 97 | | | 98 | | +-----------------------+ | <-- `error_class` (if invalid) 99 | | | Error Message | | 100 | | +-----------------------+ | 101 | +-----------------------------+ 102 | ``` 103 | 104 | | Property | Type | Description | Default | 105 | | ------------- | -------------- | ------------------------------------------ | ------- | 106 | | `class` | `&'static str` | CSS class for the input container. | `""` | 107 | | `label_class` | `&'static str` | CSS class for the label element. | `""` | 108 | | `input_class` | `&'static str` | CSS class applied to the input element. | `""` | 109 | | `field_class` | `&'static str` | CSS class for the input field container. | `""` | 110 | | `error_class` | `&'static str` | CSS class for the error message container. | `""` | 111 | 112 | ### Accessibility Props 113 | 114 | | Property | Type | Description | Default | 115 | | ------------------ | -------------- | ----------------------------------------------- | -------- | 116 | | `aria_label` | `&'static str` | Label for accessibility. | `""` | 117 | | `aria_required` | `&'static str` | Accessibility hint for required status. | `"true"` | 118 | | `aria_invalid` | `&'static str` | Accessibility hint for invalid input. | `"true"` | 119 | | `aria_describedby` | `&'static str` | Links the input to a description (e.g., error). | `""` | 120 | 121 | ## 💡 Notes 122 | 123 | - The `Input` component can be used for various input types like text, password, etc. 124 | - You can bind the component to state hooks for two-way data binding. 125 | - Utilize `validate_function` to validate user input and display error messages. 126 | - The `eye_active` and `eye_disabled` props allow for password visibility toggling with FontAwesome icons. 127 | - Customize the appearance with CSS classes for better integration into your app's design. 128 | -------------------------------------------------------------------------------- /LEPTOS.md: -------------------------------------------------------------------------------- 1 | # 🌱 Leptos Input RS Usage 2 | 3 | Adding Input RS to your Leptos project is simple: 4 | 5 | 1. Make sure your project is set up with Leptos. Refer to their [Getting Started Guide](https://book.leptos.dev/getting_started/index.html) for setup instructions. 6 | 7 | 1. Add `input-rs` to your dependencies: 8 | 9 | ```sh 10 | cargo add input-rs --features=lep 11 | ``` 12 | 13 | 1. Import the `Input` component into your Leptos component and start using it in your app. 14 | 15 | ## 🛠️ Usage 16 | 17 | Incorporating the `Input` component into your Leptos application is easy. Follow these steps: 18 | 19 | 1. Import the `Input` component into your Leptos project: 20 | 21 | ```rust 22 | use leptos::{prelude::*, *}; 23 | use input_rs::leptos::Input; 24 | use regex::Regex; 25 | ``` 26 | 27 | 1. Use the `Input` component within your Leptos application: 28 | 29 | ```rust 30 | use leptos::{prelude::*, *}; 31 | use input_rs::leptos::Input; 32 | use regex::Regex; 33 | 34 | 35 | fn validate_input(value: String) -> bool { 36 | !value.trim().is_empty() 37 | } 38 | 39 | #[component] 40 | pub fn app() -> impl IntoView { 41 | let error_handle = signal(String::default()); 42 | let error = error_handle.0.get(); 43 | 44 | let email_valid_handle = signal(true); 45 | let email_valid = email_valid_handle.0.get(); 46 | 47 | let password_valid_handle = signal(true); 48 | let password_valid = password_valid_handle.0.get(); 49 | 50 | let email_handle = signal(String::default()); 51 | let email = email_handle.0.get(); 52 | 53 | let password_handle = signal(String::default()); 54 | let password = password_handle.0.get(); 55 | 56 | let onsubmit = move |ev: leptos::ev::SubmitEvent| { 57 | ev.prevent_default(); 58 | 59 | let email_ref = email.clone(); 60 | let password_ref = password.clone(); 61 | let error_handle = error_handle.clone(); 62 | 63 | // Custom logic for your endpoint goes here 64 | }; 65 | 66 | view! { 67 |
68 |
69 |

{"Sign In"}

70 | { move || if !error.is_empty() { 71 | Some(view! {
error
}) 72 | } 73 | else {None} 74 | } 75 |
76 |
77 | 91 | 107 |
108 | {"Forgot Password?"} 109 |
110 | 111 | 115 |
116 |
117 | } 118 | } 119 | ``` 120 | 121 | ## 🔧 Props 122 | 123 | ### `Input` Props 124 | 125 | #### Main Props 126 | 127 | | Property | Type | Description | Default | 128 | | ------------------- | ------------------------------------------- | ---------------------------------------------------------- | -------- | 129 | | `r#type` | `&'static str` | The type of the input element (e.g., `text`, `password`). | `"text"` | 130 | | `handle` | `(ReadSignal, WriteSignal)` | State handle for managing the value of the input. | `""` | 131 | | `valid_handle` | `(ReadSignal, WriteSignal)` | State handle for managing the validity state of the input. | `""` | 132 | | `validate_function` | `fn(String) -> bool` | Callback function to validate the input value. | `""` | 133 | | `error_message` | `&'static str` | Error message displayed when the input is invalid. | `""` | 134 | 135 | #### Accessibility and SEO Props 136 | 137 | | Property | Type | Description | Default | 138 | | ------------------ | -------------- | --------------------------------------------------------------- | -------- | 139 | | `id` | `&'static str` | The ID attribute of the input element. | `""` | 140 | | `aria_label` | `&'static str` | The aria-label for screen readers. | `""` | 141 | | `aria_required` | `&'static str` | Indicates whether the input is required. | `"true"` | 142 | | `aria_invalid` | `&'static str` | Indicates whether the input value is invalid. | `"true"` | 143 | | `aria_describedby` | `&'static str` | Describes the input element's error message for screen readers. | `""` | 144 | 145 | #### Styling Props 146 | 147 | | Property | Type | Description | Default | 148 | | ------------- | -------------- | -------------------------------------------------------------------- | ------- | 149 | | `class` | `&'static str` | The CSS class for the container element of the input. | `""` | 150 | | `input_class` | `&'static str` | The CSS class for the inner input element. | `""` | 151 | | `field_class` | `&'static str` | The CSS class for the input field. | `""` | 152 | | `label_class` | `&'static str` | The CSS class for the label element. | `""` | 153 | | `error_class` | `&'static str` | The CSS class for the error message container. | `""` | 154 | | `icon_class` | `&'static str` | The CSS class for the icon element (for password visibility toggle). | `""` | 155 | 156 | #### Behavioral Props 157 | 158 | | Property | Type | Description | Default | 159 | | -------------- | -------------- | ----------------------------------------------- | ------------------- | 160 | | `eye_active` | `&'static str` | The icon used when the password is visible. | `"fa fa-eye"` | 161 | | `eye_disabled` | `&'static str` | The icon used when the password is not visible. | `"fa fa-eye-slash"` | 162 | 163 | #### Additional Props 164 | 165 | | Property | Type | Description | Default | 166 | | ------------- | -------------- | ---------------------------------------------------- | ------- | 167 | | `placeholder` | `&'static str` | The placeholder text displayed in the input element. | `""` | 168 | | `required` | `bool` | Specifies whether the input is required or not. | `false` | 169 | | `disabled` | `bool` | Disables the input when true. | `false` | 170 | | `readonly` | `bool` | Makes the input read-only when true. | `false` | 171 | 172 | #### Input Element Specific Attributes 173 | 174 | | Property | Type | Description | Default | 175 | | ----------- | --------------- | --------------------------------------------------------------- | ------- | 176 | | `size` | `Option` | The size of the input element (character width). | `None` | 177 | | `maxlength` | `Option` | The maximum number of characters allowed in the input. | `None` | 178 | | `pattern` | `&'static str` | Regex pattern for input validation. | `".*"` | 179 | | `minlength` | `Option` | The minimum length of the input value. | `None` | 180 | | `multiple` | `bool` | Whether multiple values are allowed (for file or email inputs). | `false` | 181 | 182 | #### Behavioral Props 183 | 184 | | Property | Type | Description | Default | 185 | | ---------- | ------------------ | ------------------------------------------------ | ------- | 186 | | `onchange` | `Callback` | Callback triggered when the input value changes. | No-op | 187 | 188 | ## 💡 Notes 189 | 190 | - The `Input` component can be used for various input types like text, password, etc. 191 | - You can bind the component to state hooks for two-way data binding. 192 | - Utilize `validate_function` to validate user input and display error messages. 193 | - The `eye_active` and `eye_disabled` props allow for password visibility toggling with FontAwesome icons. 194 | - Customize the appearance with CSS classes for better integration into your app's design. 195 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2024 Open SASS Core Maintainers 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # 🔤 Input RS 4 | 5 | [![Crates.io](https://img.shields.io/crates/v/input-rs)](https://crates.io/crates/input-rs) 6 | [![Crates.io Downloads](https://img.shields.io/crates/d/input-rs)](https://crates.io/crates/input-rs) 7 | ![Crates.io License](https://img.shields.io/crates/l/input-rs) 8 | [![made-with-rust](https://img.shields.io/badge/Made%20with-Rust-1f425f.svg?logo=rust&logoColor=white)](https://www.rust-lang.org/) 9 | [![Rust](https://img.shields.io/badge/Rust-1.79%2B-blue.svg)](https://www.rust-lang.org) 10 | [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/wiseaidev) 11 | 12 | [![Open SASS Discord](https://dcbadge.limes.pink/api/server/b5JbvHW5nv)](https://discord.gg/b5JbvHW5nv) 13 | 14 | 15 | ![logo](https://raw.githubusercontent.com/opensass/input-rs/refs/heads/main/assets/logo-new.png) 16 | 17 |
18 | 19 | ## 🎬 Demo 20 | 21 | | Input Type | Demo | 22 | | ---------- | -------------------------------------------- | 23 | | Text | ![text-demo](https://raw.githubusercontent.com/opensass/input-rs/refs/heads/main/assets/text-demo.gif) | 24 | | Password | ![pass-demo](https://raw.githubusercontent.com/opensass/input-rs/refs/heads/main/assets/pass-demo.gif) | 25 | | Textarea | ![textarea-demo](https://raw.githubusercontent.com/opensass/input-rs/refs/heads/main/assets/textarea-demo.gif) | 26 | | Telephone | ![tel-demo](https://raw.githubusercontent.com/opensass/input-rs/refs/heads/main/assets/tel-demo.gif) | 27 | 28 | | Framework | Live Demo | 29 | | --- | --- | 30 | | Yew | [![Netlify Status](https://api.netlify.com/api/v1/badges/45d208ab-8b1b-4608-bcb1-f7d3d049cac5/deploy-status)](https://input-rs.netlify.app) | 31 | | Dioxus | [![Netlify Status](https://api.netlify.com/api/v1/badges/b94e6586-192b-4db8-bc09-b287366c9731/deploy-status)](https://input-rs-dioxus.netlify.app) | 32 | | Leptos | [![Netlify Status](https://api.netlify.com/api/v1/badges/7b6175fa-892b-4a05-9fd5-b425b399e48f/deploy-status)](https://input-rs-leptos.netlify.app) | 33 | 34 | ### 📜 Intro 35 | 36 | A reusable input component built for WASM frameworks like Yew, Dioxus, and Leptos. It's customizable, accessible, and designed to simplify creating dynamic input fields in your applications. 37 | 38 | ## 🤔 Why Use Input RS? 39 | 40 | The following features make Input RS a must-have for your WASM-based projects: 41 | 42 | 1. **🎨 Advanced Customization**: Style inputs with custom classes, inline styles, and themes to suit your app's design. 43 | 1. **🔑 Flexible Input Types**: Supports text, password, phone number, and more with built-in validation. 44 | 1. **⚡ Interactive Callbacks**: Efficiently handle value changes and validity checks with customizable callback functions. 45 | 1. **🧩 Accessibility**: Built-in ARIA attributes for screen readers and other assistive technologies. 46 | 1. **📞 Phone Number Validation**: Dynamic phone number parsing with country code support. 47 | 48 | ## Y Yew Usage 49 | 50 | 51 | Refer to [our guide](https://github.com/opensass/input-rs/blob/main/YEW.md) to integrate this component into your Yew app. 52 | 53 | ## 🧬 Dioxus Usage 54 | 55 | 56 | Refer to [our guide](https://github.com/opensass/input-rs/blob/main/DIOXUS.md) to integrate this component into your Dioxus app. 57 | 58 | ## 🌱 Leptos Usage 59 | 60 | 61 | Refer to [our guide](https://github.com/opensass/input-rs/blob/main/LEPTOS.md) to integrate this component into your Leptos app. 62 | 63 | ## 🤝 Contributions 64 | 65 | Contributions are welcome! Whether it's bug fixes, feature requests, or examples, we would love your help to make Input RS better. 66 | 67 | 1. Fork the repository. 68 | 1. Create a new branch for your feature/bugfix. 69 | 1. Submit a pull request for review. 70 | 71 | ## 📜 License 72 | 73 | Input RS is licensed under the [Apache License](LICENSE). You are free to use, modify, and distribute this library in your projects. 74 | -------------------------------------------------------------------------------- /YEW.md: -------------------------------------------------------------------------------- 1 | # Y Input RS Yew Usage 2 | 3 | Adding Input RS to your project is simple: 4 | 5 | 1. Make sure your project is set up with **Yew**. Follow their [Getting Started Guide](https://yew.rs/docs/getting-started/introduction) for setup instructions. 6 | 7 | 1. Add the Input RS component to your dependencies by including it in your `Cargo.toml` file: 8 | 9 | ```sh 10 | cargo add input-rs --features=yew 11 | ``` 12 | 13 | 1. Import the `Input` component into your Yew component and start using it in your app. 14 | 15 | ## 🛠️ Usage 16 | 17 | Incorporating Yew Input RS into your application is easy. Follow these steps: 18 | 19 | 1. Import the `Input` component into your Yew project: 20 | 21 | ```rust 22 | use yew::prelude::*; 23 | use input_rs::yew::Input; 24 | use regex::Regex; 25 | ``` 26 | 27 | 1. Use the `Input` component within your Yew application: 28 | 29 | ```rust 30 | use yew::prelude::*; 31 | use regex::Regex; 32 | use input_rs::yew::Input; 33 | 34 | fn validate_email(email: String) -> bool { 35 | let pattern = Regex::new(r"^[^ ]+@[^ ]+\.[a-z]{2,3}$").unwrap(); 36 | pattern.is_match(&email) 37 | } 38 | 39 | #[function_component(App)] 40 | pub fn app() -> Html { 41 | let input_email_ref = use_node_ref(); 42 | let input_email_handle = use_state(String::default); 43 | let email_valid_handle = use_state(|| true); 44 | 45 | html! { 46 |
47 | 63 |
64 | } 65 | } 66 | ``` 67 | 68 | ## 🔧 Props 69 | 70 | ### Main Props 71 | 72 | | Property | Type | Description | Default | 73 | | ------------------- | ------------------------ | ---------------------------------------------------------------------- | -------- | 74 | | `type` | `&'static str` | Input type, e.g., `"text"`, `"email"`, `"password"`, `"textarea"`. | `"text"` | 75 | | `name` | `&'static str` | Name attribute for the input element. | `""` | 76 | | `label` | `&'static str` | Text label displayed above the input. | `""` | 77 | | `placeholder` | `&'static str` | Placeholder text inside the input field. | `""` | 78 | | `id` | `&'static str` | ID attribute for the input element. | `""` | 79 | | `required` | `bool` | Indicates whether the field is required. | `false` | 80 | | `handle` | `UseStateHandle` | State handle for managing the value of the input. | None | 81 | | `valid_handle` | `UseStateHandle` | State handle for managing the validity of the input value. | None | 82 | | `validate_function` | `Callback` | Validation function that checks the input value and returns a boolean. | None | 83 | | `error_message` | `&'static str` | Message displayed when the input value is invalid. | `""` | 84 | 85 | ### Styling Props 86 | 87 | ```sh 88 | +-----------------------------+ <-- `class` 89 | | | 90 | | +-----------------------+ | <-- `label_class` 91 | | | Label | | 92 | | +-----------------------+ | 93 | | | 94 | | +-----------------------+ | <-- `field_class` 95 | | | +-------+ +--------+ | | 96 | | | | Input | | Icon | | | <-- `input_class` and `icon_class` 97 | | | +-------+ +--------+ | | 98 | | +-----------------------+ | 99 | | | 100 | | +-----------------------+ | <-- `error_class` (if invalid) 101 | | | Error Message | | 102 | | +-----------------------+ | 103 | +-----------------------------+ 104 | ``` 105 | 106 | | Property | Type | Description | Default | 107 | | ------------- | -------------- | ----------------------------------------------------------------------- | ------- | 108 | | `class` | `&'static str` | CSS class applied to the wrapper container. | `""` | 109 | | `label_class` | `&'static str` | CSS class applied to the label element. | `""` | 110 | | `input_class` | `&'static str` | CSS class applied to the input element. | `""` | 111 | | `field_class` | `&'static str` | CSS class applied to the input wrapper element (includes icons, etc.). | `""` | 112 | | `error_class` | `&'static str` | CSS class applied to the error message container when validation fails. | `""` | 113 | | `icon_class` | `&'static str` | CSS class applied to the optional icon (if specified). | `""` | 114 | 115 | ### Password Icon Props 116 | 117 | | Property | Type | Description | Default | 118 | | -------------- | -------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------- | 119 | | `eye_active` | `&'static str` | Icon CSS class for showing the "visible" state in password fields. | `"cursor-pointer right-4 top-1 text-2xl text-gray-600 toggle-button fa fa-eye"` | 120 | | `eye_disabled` | `&'static str` | Icon CSS class for showing the "hidden" state in password fields. | `"cursor-pointer right-4 top-1 text-2xl text-gray-600 toggle-button fa fa-eye-slash"` | 121 | 122 | ### Accessibility Props 123 | 124 | | Property | Type | Description | Default | 125 | | ------------------ | -------------- | --------------------------------------------------------------------------- | --------- | 126 | | `aria_label` | `&'static str` | Aria-label for the input element for screen reader users. | `""` | 127 | | `aria_required` | `&'static str` | Specifies whether the input is required for screen readers. | `"true"` | 128 | | `aria_invalid` | `&'static str` | Indicates whether the input value is invalid for screen readers. | `"false"` | 129 | | `aria_describedby` | `&'static str` | ID of the element that describes the input (e.g., error message container). | `""` | 130 | 131 | ## 💡 Notes 132 | 133 | - The `Input` component can be used for various input types like text, password, etc. 134 | - You can bind the component to state hooks for two-way data binding. 135 | - Utilize `validate_function` to validate user input and display error messages. 136 | - The `eye_active` and `eye_disabled` props allow for password visibility toggling with FontAwesome icons. 137 | - Customize the appearance with CSS classes for better integration into your app's design. 138 | -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensass/input-rs/a8cde136407a8507430fc95e2fe90c0afbc39388/assets/favicon.png -------------------------------------------------------------------------------- /assets/logo-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensass/input-rs/a8cde136407a8507430fc95e2fe90c0afbc39388/assets/logo-new.png -------------------------------------------------------------------------------- /assets/pass-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensass/input-rs/a8cde136407a8507430fc95e2fe90c0afbc39388/assets/pass-demo.gif -------------------------------------------------------------------------------- /assets/tel-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensass/input-rs/a8cde136407a8507430fc95e2fe90c0afbc39388/assets/tel-demo.gif -------------------------------------------------------------------------------- /assets/text-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensass/input-rs/a8cde136407a8507430fc95e2fe90c0afbc39388/assets/text-demo.gif -------------------------------------------------------------------------------- /assets/textarea-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensass/input-rs/a8cde136407a8507430fc95e2fe90c0afbc39388/assets/textarea-demo.gif -------------------------------------------------------------------------------- /examples/dioxus/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "input-rs-dioxus-example" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | dioxus = { version = "0.6.3", features = ["web"] } 8 | input-rs = { path = "../../", features = ["dio"] } 9 | dioxus-logger = "0.6.2" 10 | regex = "1.11.1" 11 | 12 | [profile] 13 | 14 | [profile.wasm-dev] 15 | inherits = "dev" 16 | opt-level = 1 17 | 18 | [profile.server-dev] 19 | inherits = "dev" 20 | 21 | [profile.android-dev] 22 | inherits = "dev" 23 | -------------------------------------------------------------------------------- /examples/dioxus/Dioxus.toml: -------------------------------------------------------------------------------- 1 | [application] 2 | 3 | # App (Project) Name 4 | name = "input-rs" 5 | 6 | # Dioxus App Default Platform 7 | # desktop, web 8 | default_platform = "web" 9 | 10 | # resource (assets) file folder 11 | asset_dir = "assets" 12 | 13 | [web.app] 14 | 15 | # HTML title tag content 16 | title = "input-rs" 17 | 18 | [web.watcher] 19 | 20 | # when watcher trigger, regenerate the `index.html` 21 | reload_html = true 22 | 23 | # which files or dirs will be watcher monitoring 24 | watch_path = ["src", "assets"] 25 | 26 | # include `assets` in web platform 27 | [web.resource] 28 | 29 | # CSS style file 30 | style = [] 31 | 32 | # Javascript code file 33 | script = [] 34 | 35 | [web.resource.dev] 36 | 37 | # Javascript code file 38 | # serve: [dev-server] only 39 | script = [] 40 | -------------------------------------------------------------------------------- /examples/dioxus/README.md: -------------------------------------------------------------------------------- 1 | # 📚 Input RS Dioxus Tailwind Components 2 | 3 | ## 🛠️ Pre-requisites: 4 | 5 | ### 🐧 **Linux Users** 6 | 7 | 1. **Install [`rustup`](https://www.rust-lang.org/tools/install)**: 8 | 9 | ```sh 10 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 11 | ``` 12 | 13 | 1. Install [`Dioxus CLI`](https://dioxuslabs.com/learn/0.5/getting_started): 14 | 15 | ```sh 16 | cargo install dioxus-cli 17 | ``` 18 | 19 | ### 🪟 **Windows Users** 20 | 21 | 1. **Download and install `rustup`**: Follow the installation instructions [here](https://www.rust-lang.org/tools/install). 22 | 23 | 1. **Install [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/install)**: Open PowerShell as administrator and run: 24 | 25 | ```sh 26 | wsl --install 27 | ``` 28 | 29 | 1. **Reset Network Stack**: In PowerShell (administrator mode), run: 30 | 31 | ```sh 32 | netsh int ip reset all 33 | netsh winsock reset 34 | ``` 35 | 36 | 1. **Install Linux packages in WSL**: Once inside your WSL terminal, update and install required dependencies: 37 | 38 | ```sh 39 | sudo apt update 40 | sudo apt install build-essential pkg-config libudev-dev 41 | ``` 42 | 43 | 1. Install [`Dioxus CLI`](https://dioxuslabs.com/learn/0.5/getting_started): 44 | 45 | ```sh 46 | cargo install dioxus-cli 47 | ``` 48 | 49 | ## 🚀 Building and Running 50 | 51 | 1. Fork/Clone the GitHub repository. 52 | 53 | ```sh 54 | git clone https://github.com/opensass/input-rs 55 | ``` 56 | 57 | 1. Navigate to the application directory. 58 | 59 | ```sh 60 | cd input-rs/examples/dioxus 61 | ``` 62 | 63 | 1. Run the client: 64 | 65 | ```sh 66 | dx serve --port 3000 67 | ``` 68 | 69 | Navigate to http://localhost:3000 to explore the landing page. 70 | -------------------------------------------------------------------------------- /examples/dioxus/src/main.rs: -------------------------------------------------------------------------------- 1 | use dioxus::prelude::*; 2 | use dioxus_logger::tracing; 3 | use input_rs::dioxus::Input; 4 | use regex::Regex; 5 | 6 | pub fn validate_email(email: String) -> bool { 7 | let pattern = Regex::new(r"^[^ ]+@[^ ]+\.[a-z]{2,3}$").unwrap(); 8 | pattern.is_match(&email) 9 | } 10 | 11 | pub fn validate_input(field: String) -> bool { 12 | !&field.is_empty() 13 | } 14 | 15 | fn main() { 16 | dioxus_logger::init(tracing::Level::INFO).expect("failed to init logger"); 17 | tracing::info!("starting app"); 18 | launch(app); 19 | } 20 | 21 | fn app() -> Element { 22 | rsx! { 23 | document::Script { src: "https://kit.fontawesome.com/8f223ead6e.js" }, 24 | document::Stylesheet { href: "https://unpkg.com/tailwindcss@2.2.19/dist/tailwind.min.css" }, 25 | MultiStepFormOne {} 26 | } 27 | } 28 | 29 | #[allow(non_snake_case)] 30 | pub fn MultiStepFormOne() -> Element { 31 | let mut error = use_signal(String::new); 32 | let email_valid = use_signal(|| true); 33 | let full_name_valid = use_signal(|| true); 34 | let phone_number_valid = use_signal(|| true); 35 | let address_valid = use_signal(|| true); 36 | let birthday_valid = use_signal(|| true); 37 | let mut gender_valid = use_signal(|| true); 38 | let username_valid = use_signal(|| true); 39 | let password_valid = use_signal(|| true); 40 | 41 | let input_email = use_signal(String::new); 42 | let input_full_name = use_signal(String::new); 43 | let input_phone_number = use_signal(String::new); 44 | let input_address = use_signal(String::new); 45 | let input_birthday = use_signal(String::new); 46 | let mut input_gender = use_signal(String::new); 47 | let input_username = use_signal(String::new); 48 | let input_password = use_signal(String::new); 49 | 50 | let mut current_step = use_signal(|| 0); 51 | 52 | let on_next = { 53 | move |_| match current_step() { 54 | 0 => { 55 | if full_name_valid() && email_valid() { 56 | current_step.set(current_step() + 1); 57 | error.set(String::new()); 58 | } else { 59 | error.set("Please provide a valid full name and email address!".to_string()); 60 | } 61 | } 62 | 1 => { 63 | if phone_number_valid() && address_valid() { 64 | current_step.set(current_step() + 1); 65 | error.set(String::new()); 66 | } else { 67 | error.set("Please provide a valid phone number and address!".to_string()); 68 | } 69 | } 70 | 2 => { 71 | if birthday_valid() && gender_valid() { 72 | current_step.set(current_step() + 1); 73 | error.set(String::new()); 74 | } else { 75 | error.set("Please provide a valid birth date and gender!".to_string()); 76 | } 77 | } 78 | _ => (), 79 | } 80 | }; 81 | 82 | let on_previous = { 83 | move |_| { 84 | if current_step() > 0 { 85 | current_step.set(current_step() - 1); 86 | } 87 | } 88 | }; 89 | 90 | let on_gender_change = { 91 | move |e: Event| { 92 | let value = e.value(); 93 | input_gender.set(value.clone()); 94 | gender_valid.set(validate_input(value)); 95 | } 96 | }; 97 | 98 | let render_progress_item = |index: usize| { 99 | rsx! { 100 | div { 101 | class: "flex items-center text-left justify-center my-10", 102 | for step in 0..4 { 103 | div { 104 | class: "step", 105 | p { 106 | class: "font-semibold mb-2 text-pink-800", 107 | "Step {step + 1}" 108 | } 109 | div { 110 | class: "flex items-center", 111 | div { 112 | class: "outer-check bullet border-2 border-pink-800 rounded-full h-7 w-7 flex items-center justify-center", 113 | span { 114 | class: "step-index font-semibold text-pink-800", 115 | "{step + 1}" 116 | } 117 | if index > step { 118 | span { 119 | class: "check bg-pink-800 z-50" 120 | } 121 | } 122 | } 123 | if step < 3 { 124 | span { 125 | class: if index > step { "line bg-pink-800 h-1 w-10" } else { "line bg-grey-400 h-1 w-10" } 126 | } 127 | } 128 | } 129 | } 130 | } 131 | } 132 | } 133 | }; 134 | 135 | let current_step_content = match current_step() { 136 | 0 => rsx! { 137 | div { 138 | class: "page ml-0 transition-transform duration-300", 139 | div { 140 | class: "title text-left text-xl font-semibold mb-4", 141 | "Personal Information" 142 | } 143 | Input { 144 | r#type: "text", 145 | label: "Full Name", 146 | handle: input_full_name, 147 | placeholder: "Full Name", 148 | error_message: "Full name can't be blank!", 149 | required: true, 150 | valid_handle: full_name_valid, 151 | validate_function: validate_input, 152 | class: "field mb-6", 153 | field_class: "validate-input mb-6", 154 | label_class: "label font-semibold text-pink-800", 155 | input_class: "w-full border border-pink-800 rounded px-4 py-2", 156 | error_class: "text-red-500 text-sm my-2", 157 | } 158 | Input { 159 | r#type: "text", 160 | label: "Email", 161 | handle: input_email, 162 | placeholder: "Email", 163 | error_message: "Enter a valid email address!", 164 | required: true, 165 | valid_handle: email_valid, 166 | validate_function: validate_email, 167 | class: "field mb-6", 168 | field_class: "validate-input mb-6", 169 | label_class: "label font-semibold text-pink-800", 170 | input_class: "w-full border border-pink-800 rounded px-4 py-2", 171 | error_class: "text-red-500 text-sm my-2", 172 | } 173 | button { 174 | class: "next bg-purple-800 hover:bg-purple-700 text-white rounded px-4 mt-10 py-2 w-full font-semibold mb-4", 175 | onclick: on_next, 176 | "Next" 177 | } 178 | } 179 | }, 180 | 1 => rsx! { 181 | div { 182 | class: "page transition-transform duration-300", 183 | div { 184 | class: "title text-left text-xl font-semibold mb-4", 185 | "Contact Details" 186 | } 187 | Input { 188 | r#type: "tel", 189 | label: "Phone Number", 190 | handle: input_phone_number, 191 | placeholder: "+19999", 192 | error_message: "Phone number can't be blank!", 193 | required: true, 194 | valid_handle: phone_number_valid, 195 | validate_function: validate_input, 196 | class: "field mb-6", 197 | field_class: "flex telephone-input validate-input mb-6", 198 | label_class: "label font-semibold text-pink-800", 199 | input_class: "w-full border border-pink-800 rounded px-4 py-2", 200 | error_class: "text-red-500 text-sm my-2", 201 | } 202 | Input { 203 | r#type: "text", 204 | label: "Address", 205 | handle: input_address, 206 | placeholder: "Address", 207 | error_message: "Address can't be blank!", 208 | required: true, 209 | valid_handle: address_valid, 210 | validate_function: validate_input, 211 | class: "field mb-6", 212 | field_class: "validate-input mb-6", 213 | label_class: "label font-semibold text-pink-800", 214 | input_class: "w-full border border-pink-800 rounded px-4 py-2", 215 | error_class: "text-red-500 text-sm my-2", 216 | } 217 | div { 218 | class: "justify-center flex field btns text-center space-x-5 mt-10", 219 | button { 220 | class: "prev bg-pink-800 hover:bg-pink-700 text-white rounded px-4 py-2 font-semibold", 221 | onclick: on_previous, 222 | "Previous" 223 | } 224 | button { 225 | class: "next bg-purple-800 hover:bg-purple-700 text-white rounded px-4 py-2 font-semibold", 226 | onclick: on_next, 227 | "Next" 228 | } 229 | } 230 | } 231 | }, 232 | 2 => rsx! { 233 | div { 234 | class: "page transition-transform duration-300", 235 | div { 236 | class: "title text-left text-xl font-semibold mb-4", 237 | "Date of Birth" 238 | } 239 | Input { 240 | r#type: "date", 241 | label: "Date of Birth", 242 | handle: input_birthday, 243 | placeholder: "Birthday", 244 | error_message: "Birthday can't be blank!", 245 | required: true, 246 | valid_handle: birthday_valid, 247 | validate_function: validate_input, 248 | class: "field mb-6", 249 | field_class: "validate-input mb-6", 250 | label_class: "label font-semibold text-pink-800", 251 | input_class: "w-full border border-pink-800 rounded px-4 py-2", 252 | error_class: "text-red-500 text-sm my-2", 253 | } 254 | div { 255 | class: "field mb-6", 256 | div { 257 | class: "label font-semibold text-pink-800", 258 | "Gender" 259 | } 260 | select { 261 | class: "w-full border border-pink-800 rounded px-4 py-2", 262 | id: "gender", 263 | required: true, 264 | aria_placeholder: "Gender", 265 | oninput: on_gender_change, 266 | option { "Male" } 267 | option { "Female" } 268 | option { "Non-binary" } 269 | option { "Other" } 270 | } 271 | } 272 | if !gender_valid() { 273 | div { 274 | class: "error-txt text-red-500 text-sm my-2", 275 | "Gender can't be blank!" 276 | } 277 | } 278 | div { 279 | class: "justify-center flex field btns text-center space-x-5 mt-10", 280 | button { 281 | class: "prev bg-pink-800 hover:bg-pink-700 text-white rounded px-4 py-2 font-semibold", 282 | onclick: on_previous, 283 | "Previous" 284 | } 285 | button { 286 | class: "next bg-purple-800 hover:bg-purple-700 text-white rounded px-4 py-2 font-semibold", 287 | onclick: on_next, 288 | "Next" 289 | } 290 | } 291 | } 292 | }, 293 | 3 => rsx! { 294 | div { 295 | class: "page transition-transform duration-300", 296 | div { 297 | class: "title text-left text-xl font-semibold mb-4", 298 | "Account Details" 299 | } 300 | Input { 301 | r#type: "text", 302 | label: "Username", 303 | handle: input_username, 304 | placeholder: "Username", 305 | error_message: "Username can't be blank!", 306 | required: true, 307 | valid_handle: username_valid, 308 | validate_function: validate_input, 309 | class: "field mb-6", 310 | field_class: "validate-input mb-6", 311 | label_class: "label font-semibold text-pink-800", 312 | input_class: "w-full border border-pink-800 rounded px-4 py-2", 313 | error_class: "text-red-500 text-sm my-2", 314 | } 315 | Input { 316 | r#type: "password", 317 | label: "Password", 318 | handle: input_password, 319 | placeholder: "Password", 320 | error_message: "Password can't be blank!", 321 | required: true, 322 | valid_handle: password_valid, 323 | validate_function: validate_input, 324 | field_class: "relative mt-2 mb-2", 325 | input_class: "input w-full px-4 py-2 rounded border border-pink-800 bg-gray-100", 326 | error_class: "text-red-500 absolute text-sm", 327 | eye_active: "cursor-pointer absolute right-4 top-1/2 transform -translate-y-1/2 text-2xl text-gray-600 toggle-button fa fa-eye", 328 | eye_disabled: "cursor-pointer absolute right-4 top-1/2 transform -translate-y-1/2 text-2xl text-gray-600 toggle-button fa fa-eye-slash", 329 | } 330 | div { 331 | class: "justify-center flex field btns text-center space-x-5 mt-10", 332 | button { 333 | class: "prev bg-pink-800 hover:bg-pink-700 text-white rounded px-4 py-2 font-semibold", 334 | onclick: on_previous, 335 | "Previous" 336 | } 337 | button { 338 | class: "submit bg-purple-800 hover:bg-purple-700 text-white rounded px-4 py-2 font-semibold", 339 | "Submit" 340 | } 341 | } 342 | } 343 | }, 344 | _ => rsx! {}, 345 | }; 346 | 347 | rsx! { 348 | div { 349 | class: "text-black min-h-screen bg-gradient-to-tr from-indigo-500 to-pink-500 flex items-center justify-center", 350 | style: "border: none; width: 100vw; height: 100vh; overflow: hidden; position: fixed; top: 0; left: 0;", 351 | div { 352 | class: "container mx-auto p-10 bg-white text-center rounded w-2/3 md:w-1/3 lg:w-1/3", 353 | if !error().is_empty() { 354 | div { 355 | class: "error bg-red-600 text-white px-4 py-3 mb-5 font-semibold rounded-md text-center text-base", 356 | "{error()}" 357 | } 358 | } 359 | header { 360 | class: "text-4xl font-semibold mb-3", 361 | "Multi-Step Form" 362 | } 363 | div { 364 | class: "flex items-center text-left justify-center my-10", 365 | { render_progress_item(current_step())} 366 | } 367 | div { 368 | class: "form-outer slide-page text-left", 369 | {current_step_content} 370 | } 371 | } 372 | } 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /examples/leptos/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "leptos-input-rs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | console_error_panic_hook = "0.1.7" 8 | input-rs = { path = "../../", features = ["lep"] } 9 | leptos = { version = "0.7.2", features = ["csr"] } 10 | regex = "1.11.1" 11 | serde = "1.0.215" 12 | wasm-logger = "0.2.0" 13 | -------------------------------------------------------------------------------- /examples/leptos/README.md: -------------------------------------------------------------------------------- 1 | # 📚 Input RS Leptos Example 2 | 3 | ## 🛠️ Pre-requisites: 4 | 5 | ### 🐧 **Linux Users** 6 | 7 | 1. **Install [`rustup`](https://www.rust-lang.org/tools/install)**: 8 | 9 | ```sh 10 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 11 | ``` 12 | 13 | 1. **Install [`trunk`](https://trunkrs.dev/)**: 14 | 15 | ```sh 16 | cargo install --locked trunk 17 | ``` 18 | 19 | 1. **Add the Wasm target**: 20 | 21 | ```sh 22 | rustup target add wasm32-unknown-unknown 23 | ``` 24 | 25 | ### 🪟 **Windows Users** 26 | 27 | 1. **Download and install `rustup`**: Follow the installation instructions [here](https://www.rust-lang.org/tools/install). 28 | 29 | 1. **Install [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/install)**: Open PowerShell as administrator and run: 30 | 31 | ```sh 32 | wsl --install 33 | ``` 34 | 35 | 1. **Reset Network Stack**: In PowerShell (administrator mode), run: 36 | 37 | ```sh 38 | netsh int ip reset all 39 | netsh winsock reset 40 | ``` 41 | 42 | 1. **Install Linux packages in WSL**: Once inside your WSL terminal, update and install required dependencies: 43 | 44 | ```sh 45 | sudo apt update 46 | sudo apt install build-essential pkg-config libudev-dev 47 | ``` 48 | 49 | 1. **Install `trunk`**: 50 | 51 | ```sh 52 | cargo install --locked trunk 53 | ``` 54 | 55 | 1. **Add the Wasm target**: 56 | 57 | ```sh 58 | rustup target add wasm32-unknown-unknown 59 | ``` 60 | 61 | ## 🚀 Building and Running 62 | 63 | 1. Fork/Clone the GitHub repository. 64 | 65 | ```bash 66 | git clone https://github.com/opensass/input-rs 67 | ``` 68 | 69 | 1. Navigate to the application directory. 70 | 71 | ```bash 72 | cd input-rs/examples/leptos 73 | ``` 74 | 75 | 1. Run the client: 76 | 77 | ```sh 78 | trunk serve --port 3000 79 | ``` 80 | 81 | Navigate to http://localhost:3000 to explore all available components. 82 | -------------------------------------------------------------------------------- /examples/leptos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/leptos/src/main.rs: -------------------------------------------------------------------------------- 1 | use input_rs::leptos::Input; 2 | use leptos::{prelude::*, task::spawn_local}; 3 | use regex::Regex; 4 | use serde::{Deserialize, Serialize}; 5 | use leptos::logging::log; 6 | 7 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 8 | struct LoginUserSchema { 9 | email: String, 10 | password: String, 11 | } 12 | 13 | fn validate_email(email: String) -> bool { 14 | let pattern = Regex::new(r"^[^ ]+@[^ ]+\.[a-z]{2,3}$").unwrap(); 15 | pattern.is_match(&email) 16 | } 17 | 18 | fn validate_password(password: String) -> bool { 19 | !&password.is_empty() 20 | } 21 | 22 | #[component] 23 | pub fn App() -> impl IntoView { 24 | view! { 25 | 26 | } 27 | } 28 | 29 | #[component] 30 | fn LoginForm() -> impl IntoView { 31 | let error_handle = signal(String::default()); 32 | let error = error_handle.0.get(); 33 | 34 | let email_valid_handle = signal(true); 35 | let email_valid = email_valid_handle.0.get(); 36 | 37 | let password_valid_handle = signal(true); 38 | let password_valid = password_valid_handle.0.get(); 39 | 40 | let email_handle = signal(String::default()); 41 | let email = email_handle.0.get(); 42 | 43 | let password_handle = signal(String::default()); 44 | let password = password_handle.0.get(); 45 | 46 | let onsubmit = move |ev: leptos::ev::SubmitEvent| { 47 | ev.prevent_default(); 48 | 49 | let email_ref = email.clone(); 50 | let password_ref = password.clone(); 51 | let error_handle = error_handle.clone(); 52 | 53 | spawn_local(async move { 54 | if email_valid && password_valid { 55 | // API call 56 | log!( 57 | "Logged in with Email: {}, Password: {}", 58 | email_ref, 59 | password_ref 60 | ); 61 | } else { 62 | error_handle 63 | .1 64 | .set("Please provide a valid email and password!".to_string()); 65 | } 66 | }); 67 | }; 68 | 69 | view! { 70 |
73 | // TODO: Why the flex styling is not applied? 74 | //
75 | //

{"Sign In"}

76 | // { move || if !error.is_empty() { 77 | // Some(view! {
error
}) 78 | // } 79 | // else {None} 80 | // } 81 | //
82 |
83 | 98 | 115 |
116 | 120 |
121 |
122 | {"Not a member?"} 123 | {"Sign Up Now"} 124 |
125 |
126 |
127 | {"Or Sign In With"} 128 |
129 |
130 |
131 |
132 |
133 | 136 | 139 | 142 | 145 |
146 |
147 |
148 |
149 | } 150 | } 151 | 152 | fn main() { 153 | console_error_panic_hook::set_once(); 154 | wasm_logger::init(wasm_logger::Config::default()); 155 | leptos::mount::mount_to_body(|| view! { }) 156 | } 157 | -------------------------------------------------------------------------------- /examples/yew/.gitignore: -------------------------------------------------------------------------------- 1 | target/**/* -------------------------------------------------------------------------------- /examples/yew/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yew-input-rs" 3 | version = "0.1.0" 4 | authors = ["Mahmoud Harmouch "] 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | input-rs = { path = "../..", features = ["yew"] } 11 | regex = { version = "1.9.1", default-features = false } 12 | reqwasm = { version = "0.5.0", default-features = false } 13 | serde = { version = "1.0.178", default-features = false } 14 | serde_json = { version = "1.0.104", default-features = false } 15 | wasm-bindgen = { version = "0.2.87", default-features = false } 16 | wasm-bindgen-futures = { version = "0.4.37", default-features = false } 17 | web-sys = { version = "0.3.64", default-features = false } 18 | yew = { version = "0.21.0", features = ["csr"], default-features = false } 19 | yew-router = { version = "0.18.0", default-features = false } 20 | 21 | [profile.release] 22 | codegen-units = 1 23 | opt-level = "z" 24 | lto = "thin" 25 | strip = "symbols" 26 | -------------------------------------------------------------------------------- /examples/yew/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2023 Mahmoud Harmouch 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /examples/yew/README.md: -------------------------------------------------------------------------------- 1 | # 📚 Input RS Yew Tailwind Components 2 | 3 | ## 🛠️ Pre-requisites: 4 | 5 | ### 🐧 **Linux Users** 6 | 7 | 1. **Install [`rustup`](https://www.rust-lang.org/tools/install)**: 8 | 9 | ```sh 10 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 11 | ``` 12 | 13 | 1. **Install [`trunk`](https://trunkrs.dev/)**: 14 | 15 | ```sh 16 | cargo install --locked trunk 17 | ``` 18 | 19 | 1. **Add the Wasm target**: 20 | 21 | ```sh 22 | rustup target add wasm32-unknown-unknown 23 | ``` 24 | 25 | ### 🪟 **Windows Users** 26 | 27 | 1. **Download and install `rustup`**: Follow the installation instructions [here](https://www.rust-lang.org/tools/install). 28 | 29 | 1. **Install [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/install)**: Open PowerShell as administrator and run: 30 | 31 | ```sh 32 | wsl --install 33 | ``` 34 | 35 | 1. **Reset Network Stack**: In PowerShell (administrator mode), run: 36 | 37 | ```sh 38 | netsh int ip reset all 39 | netsh winsock reset 40 | ``` 41 | 42 | 1. **Install Linux packages in WSL**: Once inside your WSL terminal, update and install required dependencies: 43 | 44 | ```sh 45 | sudo apt update 46 | sudo apt install build-essential pkg-config libudev-dev 47 | ``` 48 | 49 | 1. **Install `trunk`**: 50 | 51 | ```sh 52 | cargo install --locked trunk 53 | ``` 54 | 55 | 1. **Add the Wasm target**: 56 | 57 | ```sh 58 | rustup target add wasm32-unknown-unknown 59 | ``` 60 | 61 | ## 🚀 Building and Running 62 | 63 | 1. Fork/Clone the GitHub repository. 64 | 65 | ```bash 66 | git clone https://github.com/opensass/input-rs 67 | ``` 68 | 69 | 1. Navigate to the application directory. 70 | 71 | ```bash 72 | cd input-rs/examples/yew 73 | ``` 74 | 75 | 1. Run the client: 76 | 77 | ```sh 78 | trunk serve --port 3000 79 | ``` 80 | 81 | Navigate to http://localhost:3000 to explore all available components. 82 | 83 | ## 🌀 Tailwind CSS Components 84 | 85 | This section lists components implemented using the [Tailwind CSS](https://tailwindcss.com/) framework. 86 | 87 | ### 🔐 Login Forms 88 | 89 | | ID | Preview | Demo | Localhost | 90 | |---|---|---|---| 91 | | 1 | ![Component 1](./assets/form-one.png) | [![Netlify Status](https://api.netlify.com/api/v1/badges/68d1469e-05ee-4acd-9368-b67d9e53bc2e/deploy-status)](https://tailwind-login-form-1.netlify.app/) | [Localhost](http://localhost:3000/login/1) | 92 | | 2 | ![Component 2](./assets/form-two.png) | [![Netlify Status](https://api.netlify.com/api/v1/badges/68d1469e-05ee-4acd-9368-b67d9e53bc2e/deploy-status)](https://tailwind-login-form-2.netlify.app/) | [Localhost](http://localhost:3000/login/2) | 93 | | 3 | ![Component 3](./assets/form-three.png) | [![Netlify Status](https://api.netlify.com/api/v1/badges/68d1469e-05ee-4acd-9368-b67d9e53bc2e/deploy-status)](https://tailwind-login-form-3.netlify.app/) | [Localhost](http://localhost:3000/login/3) | 94 | 95 | ### 📬 Contact Forms 96 | 97 | | ID | Preview | Demo | Localhost | 98 | |---|---|---|---| 99 | | 1 | ![Component 1](./assets/contact-form-one.png) | [![Netlify Status](https://api.netlify.com/api/v1/badges/68d1469e-05ee-4acd-9368-b67d9e53bc2e/deploy-status)](https://tailwind-contact-form-1.netlify.app/) | [Localhost](http://localhost:3000/contact/1) | 100 | 101 | ### 🔢 Multi-Steps Forms 102 | 103 | | ID | Preview | Demo | Localhost | 104 | |---|---|---|---| 105 | | 1 | ![Component 1](./assets/multi-step-form-one.png) | [![Netlify Status](https://api.netlify.com/api/v1/badges/68d1469e-05ee-4acd-9368-b67d9e53bc2e/deploy-status)](https://tailwind-multi-step-form-1.netlify.app/) | [Localhost](http://localhost:3000/multi-step/1) | 106 | -------------------------------------------------------------------------------- /examples/yew/Trunk.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | tailwindcss = "3.4.17" 3 | -------------------------------------------------------------------------------- /examples/yew/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /examples/yew/assets/contact-form-one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensass/input-rs/a8cde136407a8507430fc95e2fe90c0afbc39388/examples/yew/assets/contact-form-one.png -------------------------------------------------------------------------------- /examples/yew/assets/form-one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensass/input-rs/a8cde136407a8507430fc95e2fe90c0afbc39388/examples/yew/assets/form-one.png -------------------------------------------------------------------------------- /examples/yew/assets/form-three.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensass/input-rs/a8cde136407a8507430fc95e2fe90c0afbc39388/examples/yew/assets/form-three.png -------------------------------------------------------------------------------- /examples/yew/assets/form-two.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensass/input-rs/a8cde136407a8507430fc95e2fe90c0afbc39388/examples/yew/assets/form-two.png -------------------------------------------------------------------------------- /examples/yew/assets/multi-step-form-one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensass/input-rs/a8cde136407a8507430fc95e2fe90c0afbc39388/examples/yew/assets/multi-step-form-one.png -------------------------------------------------------------------------------- /examples/yew/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | Yew Tailwind Forms 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/yew/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /examples/yew/rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" -------------------------------------------------------------------------------- /examples/yew/src/api.rs: -------------------------------------------------------------------------------- 1 | pub mod auth; 2 | -------------------------------------------------------------------------------- /examples/yew/src/api/auth.rs: -------------------------------------------------------------------------------- 1 | use reqwasm::http::{Request, RequestCredentials}; 2 | use serde::{Deserialize, Serialize}; 3 | use serde_json::json; 4 | 5 | const BASE_URL: &str = "http://localhost:8080/api/v1"; 6 | 7 | #[derive(Serialize, Deserialize, Debug)] 8 | pub struct UserLoginResponse { 9 | pub status: String, 10 | pub token: String, 11 | } 12 | 13 | #[derive(Serialize, Deserialize, Debug)] 14 | pub struct ErrorResponse { 15 | pub status: String, 16 | pub message: String, 17 | } 18 | 19 | pub async fn login_user(username: String, password: String) -> Result { 20 | let response = match Request::post(&format!("{}/auth/login", BASE_URL)) 21 | .header("Content-Type", "application/json") 22 | .credentials(RequestCredentials::Include) 23 | .body( 24 | json!({ 25 | "username": username, 26 | "password": password 27 | }) 28 | .to_string(), 29 | ) 30 | .send() 31 | .await 32 | { 33 | Ok(res) => res, 34 | Err(_) => { 35 | return Err("Network Error!".to_string()); 36 | } 37 | }; 38 | 39 | if response.status() != 200 { 40 | let error_response = response.json::().await; 41 | if let Ok(error_response) = error_response { 42 | return Err(error_response.message); 43 | } else { 44 | return Err(format!("Network Error: {}", response.status())); 45 | } 46 | } 47 | 48 | let res_json = response.json::().await; 49 | match res_json { 50 | Ok(data) => Ok(data), 51 | Err(_) => Err("Failed to parse response".to_string()), 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/yew/src/app.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_router::prelude::*; 3 | 4 | use crate::router::{switch, Route}; 5 | 6 | #[function_component(App)] 7 | pub fn app() -> Html { 8 | html! { 9 | 10 | render={switch} /> 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/yew/src/components.rs: -------------------------------------------------------------------------------- 1 | pub mod common; 2 | pub mod contact_form_one; 3 | pub mod login_form_one; 4 | pub mod login_form_three; 5 | pub mod login_form_two; 6 | pub mod multi_step_form_one; 7 | -------------------------------------------------------------------------------- /examples/yew/src/components/common.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 5 | pub struct LoginUserSchema { 6 | email: String, 7 | password: String, 8 | } 9 | 10 | pub fn validate_email(email: String) -> bool { 11 | let pattern = Regex::new(r"^[^ ]+@[^ ]+\.[a-z]{2,3}$").unwrap(); 12 | pattern.is_match(&email) 13 | } 14 | 15 | pub fn validate_input(field: String) -> bool { 16 | !&field.is_empty() 17 | } 18 | -------------------------------------------------------------------------------- /examples/yew/src/components/contact_form_one.rs: -------------------------------------------------------------------------------- 1 | use crate::components::common::{validate_email, validate_input, LoginUserSchema}; 2 | use input_rs::yew::Input; 3 | use serde::{Deserialize, Serialize}; 4 | use wasm_bindgen_futures::spawn_local; 5 | use web_sys::{console, HtmlInputElement, Window}; 6 | use yew::prelude::*; 7 | 8 | use crate::api::auth::login_user; 9 | 10 | #[function_component(ContactFormOne)] 11 | pub fn contact_form_one() -> Html { 12 | let error_handle = use_state(String::default); 13 | let error = (*error_handle).clone(); 14 | 15 | let email_valid_handle = use_state(|| true); 16 | let email_valid = (*email_valid_handle).clone(); 17 | 18 | let name_valid_handle = use_state(|| true); 19 | let name_valid = (*name_valid_handle).clone(); 20 | 21 | let subject_valid_handle = use_state(|| true); 22 | let subject_valid = (*subject_valid_handle).clone(); 23 | 24 | let message_valid_handle = use_state(|| true); 25 | let message_valid = (*message_valid_handle).clone(); 26 | 27 | let input_email_ref = use_node_ref(); 28 | let input_email_handle = use_state(String::default); 29 | let input_email = (*input_email_handle).clone(); 30 | 31 | let input_name_ref = use_node_ref(); 32 | let input_name_handle = use_state(|| "afasfasf".to_string()); 33 | let input_name = (*input_name_handle).clone(); 34 | 35 | let input_subject_ref = use_node_ref(); 36 | let input_subject_handle = use_state(String::default); 37 | let input_subject = (*input_subject_handle).clone(); 38 | 39 | let input_message_ref = use_node_ref(); 40 | let input_message_handle = use_state(String::default); 41 | let input_message = (*input_message_handle).clone(); 42 | 43 | let onsubmit = Callback::from(move |event: SubmitEvent| { 44 | event.prevent_default(); 45 | 46 | let email_ref = input_email.clone(); 47 | let name_ref = input_name.clone(); 48 | let subject_ref = input_subject.clone(); 49 | let message_ref = input_message.clone(); 50 | let error_handle = error_handle.clone(); 51 | console::log_1( 52 | &format!( 53 | "Email: {}, Name: {}, Subject: {}, Message: {}", 54 | input_email, input_name, input_subject, input_message 55 | ) 56 | .into(), 57 | ); 58 | 59 | spawn_local(async move { 60 | let email_val = email_ref.clone(); 61 | let _name_val = name_ref.clone(); 62 | let subject_val = subject_ref.clone(); 63 | let _message_val = message_ref.clone(); 64 | 65 | let error_handle = error_handle.clone(); 66 | if email_valid && name_valid && subject_valid && message_valid { 67 | // TODO: create a contact us endpoint 68 | let response = login_user(email_val.to_string(), subject_val.to_string()).await; 69 | match response { 70 | Ok(_) => { 71 | console::log_1(&"success".into()); 72 | let window: Window = web_sys::window().expect("window not available"); 73 | let location = window.location(); 74 | let _ = location.set_href("/home"); 75 | } 76 | Err(err) => { 77 | error_handle.set(err); 78 | } 79 | } 80 | } else { 81 | error_handle.set("Please provide valid contact information!".into()); 82 | } 83 | }); 84 | }); 85 | 86 | html! { 87 |
88 |
89 |
92 | 103 |
104 | if !error.is_empty() { 105 |
108 | { error } 109 |
110 | } 111 | 112 | { "Contact US" } 113 | 114 | 128 | 142 | 156 | 170 |
171 |