├── .github ├── FUNDING.yml └── workflows │ ├── deploy_website.yml │ └── lint.yml ├── .gitignore ├── .nlsp-settings └── rust_analyzer.json ├── .pre-commit-config.yaml ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── README.md ├── build ├── core.js ├── mwc-button.js ├── mwc-check-list-item.js ├── mwc-checkbox.js ├── mwc-circular-progress-four-color.js ├── mwc-circular-progress.js ├── mwc-dialog.js ├── mwc-drawer.js ├── mwc-fab.js ├── mwc-formfield.js ├── mwc-icon-button-toggle.js ├── mwc-icon-button.js ├── mwc-icon.js ├── mwc-linear-progress.js ├── mwc-list-item.js ├── mwc-list.js ├── mwc-menu.js ├── mwc-radio-list-item.js ├── mwc-radio.js ├── mwc-select.js ├── mwc-snackbar.js ├── mwc-switch.js ├── mwc-tab-bar.js ├── mwc-tab.js ├── mwc-textarea.js ├── mwc-textfield.js ├── mwc-top-app-bar-fixed.js ├── mwc-top-app-bar.js └── slider.js ├── demo ├── Cargo.toml ├── Dioxus.toml └── src │ └── main.rs ├── dprint.json ├── package-lock.json ├── package.json ├── postprocess.mjs ├── rollup.config.mjs ├── screenshots ├── Cargo.toml ├── index.html ├── src │ └── main.rs └── styles │ └── styles.scss ├── src ├── button.rs ├── checkbox.rs ├── circular_progress.rs ├── circular_progress_four_color.rs ├── dialog.rs ├── dialog │ └── dialog_action.rs ├── drawer.rs ├── drawer │ ├── drawer_app_content.rs │ ├── drawer_header.rs │ ├── drawer_subtitle.rs │ └── drawer_title.rs ├── fab.rs ├── form_field.rs ├── icon.rs ├── icon_button.rs ├── icon_button_toggle.rs ├── icon_button_toggle │ ├── off_icon.rs │ └── on_icon.rs ├── lib.rs ├── linear_progress.rs ├── list.rs ├── list │ ├── action_detail.rs │ ├── check_list_item.rs │ ├── graphic_type.rs │ ├── list_index.rs │ ├── list_item.rs │ ├── radio_list_item.rs │ ├── request_selected.rs │ ├── selected_detail.rs │ └── separator.rs ├── menu.rs ├── menu │ └── models.rs ├── palette.rs ├── radio.rs ├── select.rs ├── slider.rs ├── snackbar.rs ├── switch.rs ├── tabs │ ├── mod.rs │ ├── tab.rs │ ├── tab_bar.rs │ └── tab_icon.rs ├── text_inputs │ ├── mod.rs │ ├── text_field_type.rs │ ├── textarea.rs │ ├── textfield.rs │ └── validity_state.rs ├── theming.rs ├── top_app_bar.rs ├── top_app_bar │ ├── action_items.rs │ ├── navigation_icon.rs │ └── title.rs ├── top_app_bar_fixed.rs └── utils │ ├── mod.rs │ └── weak_component_link.rs └── website ├── .gitignore ├── Cargo.toml ├── assets ├── button.png ├── checkbox.png ├── circular-progress.png ├── components.png ├── dialog.png ├── floating-action-button.png ├── formfield.png ├── github.png ├── icon-button-toggle.png ├── icon-button.png ├── icon.png ├── linear-progress.png ├── list.png ├── menu.png ├── radio.png ├── select.png ├── slider.png ├── snackbar.png ├── switch.png ├── tabs.png ├── textarea.png └── textfield.png ├── build.rs ├── index.html ├── robots.txt ├── src ├── components │ ├── button.rs │ ├── checkbox.rs │ ├── circular_progress.rs │ ├── code_block.rs │ ├── components_list.rs │ ├── dialog.rs │ ├── drawer.rs │ ├── fab.rs │ ├── form_field.rs │ ├── home.rs │ ├── icon.rs │ ├── icon_button.rs │ ├── icon_button_toggle.rs │ ├── linear_progress.rs │ ├── list.rs │ ├── menu.rs │ ├── mod.rs │ ├── radio.rs │ ├── select.rs │ ├── slider.rs │ ├── snackbar.rs │ ├── switch.rs │ ├── tabs.rs │ ├── textarea.rs │ └── textfield.rs ├── lib.rs ├── macros.rs └── main.rs ├── styles ├── jetbrains-mono.css └── styles.scss └── syntect-dumps ├── Material-Theme-Lighter.theme ├── Material-Theme.theme ├── rust.syntax └── syntax-set.syntax /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: rubixdev 2 | -------------------------------------------------------------------------------- /.github/workflows/deploy_website.yml: -------------------------------------------------------------------------------- 1 | name: Deploy website 2 | on: 3 | push: 4 | branches: [master] 5 | 6 | jobs: 7 | deploy_website: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Install rust stable 12 | uses: actions-rs/toolchain@v1 13 | with: 14 | toolchain: stable 15 | target: wasm32-unknown-unknown 16 | override: true 17 | 18 | - uses: actions/cache@v2 19 | with: 20 | path: | 21 | ~/.cargo/registry 22 | ~/.cargo/git 23 | target 24 | key: cargo-${{ runner.os }}-deploy-${{ hashFiles('**/Cargo.toml') }} 25 | restore-keys: | 26 | cargo-${{ runner.os }}- 27 | 28 | - name: Install dioxus-cli 29 | uses: taiki-e/install-action@v2 30 | with: 31 | tool: dioxus-cli 32 | 33 | - name: Build website 34 | run: | 35 | cd demo 36 | dx build --release 37 | 38 | # - name: Install rust (nightly) 39 | # uses: actions-rs/toolchain@v1 40 | # with: 41 | # toolchain: nightly 42 | # target: wasm32-unknown-unknown 43 | # override: true 44 | # 45 | # - name: Build docs 46 | # uses: actions-rs/cargo@v1 47 | # with: 48 | # command: doc 49 | # toolchain: nightly 50 | # args: --no-deps --target wasm32-unknown-unknown --all-features -p material-yew --release 51 | # 52 | # - name: Copy docs to correct location 53 | # run: cp -r target/wasm32-unknown-unknown/doc/ website/dist/docs/ 54 | 55 | - name: Deploy 56 | uses: JamesIves/github-pages-deploy-action@v4 57 | with: 58 | folder: demo/dist 59 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint & Fmt 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | 8 | jobs: 9 | lint: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions-rs/toolchain@v1 14 | with: 15 | toolchain: nightly 16 | profile: minimal 17 | components: rustfmt 18 | 19 | - uses: actions/cache@v2 20 | with: 21 | path: | 22 | ~/.cargo/registry 23 | ~/.cargo/git 24 | target 25 | key: cargo-${{ runner.os }}-lint-${{ hashFiles('**/Cargo.toml') }} 26 | restore-keys: | 27 | cargo-${{ runner.os }}- 28 | 29 | - name: Run cargo fmt 30 | uses: actions-rs/cargo@v1 31 | with: 32 | command: fmt 33 | toolchain: nightly 34 | args: --all -- --check 35 | 36 | - uses: actions-rs/toolchain@v1 37 | with: 38 | toolchain: stable 39 | profile: minimal 40 | components: clippy 41 | 42 | - name: Run clippy 43 | uses: actions-rs/cargo@v1 44 | with: 45 | command: clippy 46 | toolchain: stable 47 | args: --all-features -- -D warnings 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | **/target/ 4 | 5 | # These are backup files generated by rustfmt 6 | **/*.rs.bk 7 | 8 | # Node stuff 9 | node_modules 10 | 11 | # Other 12 | **/dist/ 13 | ignored/ 14 | -------------------------------------------------------------------------------- /.nlsp-settings/rust_analyzer.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.features": "all" 3 | } 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | exclude: | 4 | (?x)^( 5 | build/.*| 6 | website/.*| 7 | screenshots/.*| 8 | )$ 9 | repos: 10 | - repo: https://github.com/pre-commit/pre-commit-hooks 11 | rev: v4.4.0 12 | hooks: 13 | - id: trailing-whitespace 14 | - id: end-of-file-fixer 15 | - id: check-yaml 16 | - id: check-toml 17 | - id: check-added-large-files 18 | - repo: https://github.com/doublify/pre-commit-rust 19 | rev: v1.0 20 | hooks: 21 | - id: clippy 22 | - repo: https://github.com/RubixDev/pre-commit-dprint 23 | rev: v0.40.2 24 | hooks: 25 | - id: dprint 26 | pass_filenames: false 27 | - repo: https://github.com/crate-ci/typos 28 | rev: typos-dict-v0.10.8 29 | hooks: 30 | - id: typos 31 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "demo", 4 | # "website", 5 | # "screenshots" 6 | ] 7 | 8 | [package] 9 | name = "material-dioxus" 10 | version = "0.0.3-dev" 11 | authors = ["RubixDev ", "Hamza "] 12 | categories = ["web-programming", "wasm", "api-bindings", "gui"] 13 | edition = "2021" 14 | include = ["src/**/*", "Cargo.toml", "build"] 15 | keywords = ["material-design", "dioxus", "wrapper", "wasm"] 16 | license = "Apache-2.0" 17 | repository = "https://github.com/RubixDev/material-dioxus" 18 | description = "Dioxus wrapper for Material Web Components" 19 | 20 | [dependencies] 21 | dioxus = "0.4.0" 22 | getrandom = { version = "0.2.10", features = ["js"] } 23 | gloo = "0.10.0" 24 | js-sys = "0.3.64" 25 | palette = { version = "0.7.3", optional = true } 26 | paste = "1.0.14" 27 | rand = "0.8.5" 28 | wasm-bindgen = "0.2.87" 29 | 30 | [dependencies.web-sys] 31 | version = "0.3.64" 32 | features = [ 33 | "Window", 34 | "Document", 35 | "Element", 36 | "EventTarget", 37 | "ValidityState", 38 | "CustomEvent", 39 | ] 40 | 41 | [features] 42 | button = [] 43 | circular-progress = [] 44 | checkbox = [] 45 | circular-progress-four-color = [] 46 | # drawer = [] 47 | # top-app-bar = [] 48 | icon-button = [] 49 | fab = [] 50 | formfield = [] 51 | # linear-progress = [] 52 | icon = [] 53 | radio = [] 54 | switch = [] 55 | # top-app-bar-fixed = [] 56 | dialog = [] 57 | list = [] 58 | # icon-button-toggle = [] 59 | # slider = [] 60 | # tabs = [] 61 | # snackbar = [] 62 | textfield = [] 63 | textarea = [] 64 | # select = [] 65 | # menu = [] 66 | theming = ["palette"] 67 | palette = ["dep:palette"] 68 | all-components = [ 69 | "button", 70 | "circular-progress", 71 | "checkbox", 72 | "circular-progress-four-color", 73 | # "drawer", 74 | # "top-app-bar", 75 | "icon-button", 76 | "fab", 77 | "formfield", 78 | # "linear-progress", 79 | "icon", 80 | "radio", 81 | "switch", 82 | # "top-app-bar-fixed", 83 | "dialog", 84 | "list", 85 | # "icon-button-toggle", 86 | # "slider", 87 | # "tabs", 88 | # "snackbar", 89 | "textfield", 90 | "textarea", 91 | # "select", 92 | # "menu", 93 | ] 94 | full = ["all-components", "theming"] 95 | default = [] 96 | 97 | [package.metadata.docs.rs] 98 | all-features = true 99 | default-target = "wasm32-unknown-unknown" 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Material Dioxus 2 | 3 | _Material Dioxus_ is a components library wrapper around Google's 4 | [Material Web Components](https://github.com/material-components/material-components-web-components) 5 | for the [Dioxus framework](https://dioxuslabs.com/). Only the `dioxus-web` 6 | renderer is supported. 7 | 8 | ## Example 9 | 10 | ```rust 11 | use material_dioxus::MatButton; 12 | use dioxus::prelude::*; 13 | 14 | rsx! { 15 | MatButton { 16 | label: "Click me!", 17 | } 18 | }; 19 | ``` 20 | 21 | ## Getting started 22 | 23 | ### Installation 24 | 25 | `Cargo.toml`: 26 | 27 | ```bash 28 | cargo add material-dioxus --features full 29 | ``` 30 | 31 | Material icons and a Material font must be imported for full functionality.\ 32 | `Dioxus.toml`: 33 | 34 | ```toml 35 | [web.resource] 36 | style = [ 37 | "https://fonts.googleapis.com/css?family=Roboto:300,400,500", 38 | "https://fonts.googleapis.com/css?family=Material+Icons&display=block", 39 | # ... 40 | ] 41 | ``` 42 | 43 | ### Feature flags 44 | 45 | There is one cargo feature for each component: 46 | 47 | 48 | - `button` 49 | - `circular-progress` 50 | - `checkbox` 51 | - `circular-progress-four-color` 52 | 53 | 54 | - `icon-button` 55 | - `fab` 56 | - `formfield` 57 | 58 | - `icon` 59 | - `radio` 60 | - `switch` 61 | 62 | - `dialog` 63 | - `list` 64 | 65 | 66 | 67 | 68 | - `textfield` 69 | - `textarea` 70 | 71 | 72 | 73 | 74 | The `all-components` feature enables all components. 75 | 76 | Additionally, there are two features related to theming. 77 | 78 | - `theming` &emdash; Provides a `MatTheme` component for setting a color theme. 79 | - `palette` &emdash; Provides constants for the material color palette 80 | (automatically enabled by `theming`). 81 | 82 | The `full` feature enables all features. 83 | 84 | ## Theming 85 | 86 | These components respect the theming applied to Material Web Components using 87 | stylesheets. 88 | [Learn about how to theme Material Web Components.](https://github.com/material-components/material-web/blob/mwc/docs/theming.md) 89 | 90 | For convenience, the `theming` feature provides a `MatTheme` component, which 91 | takes a few colors and sets all required CSS variables. Just include that in the 92 | root of your application once. 93 | 94 | ## Event handling 95 | 96 | Due to lifetime limitations of the normal Dioxus event handlers, material-dioxus 97 | cannot make use of them. The exposed events instead use a custom callback type. 98 | For simple buttons that are never disabled you can also just wrap them in a 99 | `span` and use a normal event handler on that. For example 100 | 101 | ```rust 102 | use dioxus::prelude::*; 103 | use material_dioxus::MatButton; 104 | 105 | #[allow(non_snake_case)] 106 | fn Counter(cx: Scope) -> Element { 107 | let mut counter = use_state(cx, || 0); 108 | 109 | render! { 110 | // option 1: wrap the component in a span and use normal event handling 111 | span { 112 | onclick: move |_| counter += 1, 113 | MatButton { label: "click me: {counter}" } 114 | } 115 | // option 2: use the event handlers exposed by the component to respect 116 | // thinks like a button being disabled. 117 | // The closure must be 'static so we make use of `to_owned!`. 118 | MatButton { 119 | label: "click me: {counter}", 120 | _onclick: { 121 | to_owned![counter]; 122 | move |_| counter += 1 123 | }, 124 | } 125 | } 126 | } 127 | ``` 128 | 129 | ## Documentation 130 | 131 | Full API documentation can be found [here](https://docs.rs/material-dioxus/). 132 | Demos of components can be found [here](https://material-dioxus.rubixdev.de/). 133 | -------------------------------------------------------------------------------- /build/mwc-check-list-item.js: -------------------------------------------------------------------------------- 1 | import{_ as e,i as t,n as s,O as c,x as i,o as h,N as r,P as o,d as a}from"./core.js";import"./mwc-checkbox.js";class d extends c{constructor(){super(...arguments),this.left=!1,this.graphic="control"}render(){const e={"mdc-deprecated-list-item__graphic":this.left,"mdc-deprecated-list-item__meta":!this.left},t=this.renderText(),s=this.graphic&&"control"!==this.graphic&&!this.left?this.renderGraphic():i``,c=this.hasMeta&&this.left?this.renderMeta():i``,r=this.renderRipple();return i` 2 | ${r} 3 | ${s} 4 | ${this.left?"":t} 5 | 6 | 12 | 13 | 14 | ${this.left?t:""} 15 | ${c}`}async onChange(e){const t=e.target;this.selected===t.checked||(this._skipPropRequest=!0,this.selected=t.checked,await this.updateComplete,this._skipPropRequest=!1)}}e([t("slot")],d.prototype,"slotElement",void 0),e([t("mwc-checkbox")],d.prototype,"checkboxElement",void 0),e([s({type:Boolean})],d.prototype,"left",void 0),e([s({type:String,reflect:!0})],d.prototype,"graphic",void 0);let p=class extends d{};p.styles=[r,o],p=e([a("mwc-check-list-item")],p);export{p as CheckListItem}; 16 | -------------------------------------------------------------------------------- /build/mwc-circular-progress-four-color.js: -------------------------------------------------------------------------------- 1 | import{C as r,x as e,c,_ as i,d as a}from"./core.js";class t extends r{renderIndeterminateContainer(){return e` 2 |
3 |
4 | ${this.renderIndeterminateSpinnerLayer()} 5 |
6 |
7 | ${this.renderIndeterminateSpinnerLayer()} 8 |
9 |
10 | ${this.renderIndeterminateSpinnerLayer()} 11 |
12 |
13 | ${this.renderIndeterminateSpinnerLayer()} 14 |
15 |
`}}const o=c`.mdc-circular-progress__determinate-circle,.mdc-circular-progress__indeterminate-circle-graphic{stroke:#6200ee;stroke:var(--mdc-theme-primary, #6200ee)}.mdc-circular-progress__determinate-track{stroke:transparent}@keyframes mdc-circular-progress-container-rotate{to{transform:rotate(360deg)}}@keyframes mdc-circular-progress-spinner-layer-rotate{12.5%{transform:rotate(135deg)}25%{transform:rotate(270deg)}37.5%{transform:rotate(405deg)}50%{transform:rotate(540deg)}62.5%{transform:rotate(675deg)}75%{transform:rotate(810deg)}87.5%{transform:rotate(945deg)}100%{transform:rotate(1080deg)}}@keyframes mdc-circular-progress-color-1-fade-in-out{from{opacity:.99}25%{opacity:.99}26%{opacity:0}89%{opacity:0}90%{opacity:.99}to{opacity:.99}}@keyframes mdc-circular-progress-color-2-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:.99}50%{opacity:.99}51%{opacity:0}to{opacity:0}}@keyframes mdc-circular-progress-color-3-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:.99}75%{opacity:.99}76%{opacity:0}to{opacity:0}}@keyframes mdc-circular-progress-color-4-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:.99}90%{opacity:.99}to{opacity:0}}@keyframes mdc-circular-progress-left-spin{from{transform:rotate(265deg)}50%{transform:rotate(130deg)}to{transform:rotate(265deg)}}@keyframes mdc-circular-progress-right-spin{from{transform:rotate(-265deg)}50%{transform:rotate(-130deg)}to{transform:rotate(-265deg)}}.mdc-circular-progress{display:inline-flex;position:relative;direction:ltr;line-height:0;transition:opacity 250ms 0ms cubic-bezier(0.4, 0, 0.6, 1)}.mdc-circular-progress__determinate-container,.mdc-circular-progress__indeterminate-circle-graphic,.mdc-circular-progress__indeterminate-container,.mdc-circular-progress__spinner-layer{position:absolute;width:100%;height:100%}.mdc-circular-progress__determinate-container{transform:rotate(-90deg)}.mdc-circular-progress__indeterminate-container{font-size:0;letter-spacing:0;white-space:nowrap;opacity:0}.mdc-circular-progress__determinate-circle-graphic,.mdc-circular-progress__indeterminate-circle-graphic{fill:transparent}.mdc-circular-progress__determinate-circle{transition:stroke-dashoffset 500ms 0ms cubic-bezier(0, 0, 0.2, 1)}.mdc-circular-progress__gap-patch{position:absolute;top:0;left:47.5%;box-sizing:border-box;width:5%;height:100%;overflow:hidden}.mdc-circular-progress__gap-patch .mdc-circular-progress__indeterminate-circle-graphic{left:-900%;width:2000%;transform:rotate(180deg)}.mdc-circular-progress__circle-clipper{display:inline-flex;position:relative;width:50%;height:100%;overflow:hidden}.mdc-circular-progress__circle-clipper .mdc-circular-progress__indeterminate-circle-graphic{width:200%}.mdc-circular-progress__circle-right .mdc-circular-progress__indeterminate-circle-graphic{left:-100%}.mdc-circular-progress--indeterminate .mdc-circular-progress__determinate-container{opacity:0}.mdc-circular-progress--indeterminate .mdc-circular-progress__indeterminate-container{opacity:1}.mdc-circular-progress--indeterminate .mdc-circular-progress__indeterminate-container{animation:mdc-circular-progress-container-rotate 1568.2352941176ms linear infinite}.mdc-circular-progress--indeterminate .mdc-circular-progress__spinner-layer{animation:mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.mdc-circular-progress--indeterminate .mdc-circular-progress__color-1{animation:mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,mdc-circular-progress-color-1-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.mdc-circular-progress--indeterminate .mdc-circular-progress__color-2{animation:mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,mdc-circular-progress-color-2-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.mdc-circular-progress--indeterminate .mdc-circular-progress__color-3{animation:mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,mdc-circular-progress-color-3-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.mdc-circular-progress--indeterminate .mdc-circular-progress__color-4{animation:mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,mdc-circular-progress-color-4-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.mdc-circular-progress--indeterminate .mdc-circular-progress__circle-left .mdc-circular-progress__indeterminate-circle-graphic{animation:mdc-circular-progress-left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.mdc-circular-progress--indeterminate .mdc-circular-progress__circle-right .mdc-circular-progress__indeterminate-circle-graphic{animation:mdc-circular-progress-right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.mdc-circular-progress--closed{opacity:0}:host{display:inline-flex}.mdc-circular-progress__determinate-track{stroke:transparent;stroke:var(--mdc-circular-progress-track-color, transparent)}.mdc-circular-progress__color-1 .mdc-circular-progress__indeterminate-circle-graphic{stroke:#6200ee;stroke:var(--mdc-circular-progress-bar-color-1, var(--mdc-theme-primary, #6200ee))}.mdc-circular-progress__color-2 .mdc-circular-progress__indeterminate-circle-graphic{stroke:#6200ee;stroke:var(--mdc-circular-progress-bar-color-2, var(--mdc-theme-primary, #6200ee))}.mdc-circular-progress__color-3 .mdc-circular-progress__indeterminate-circle-graphic{stroke:#6200ee;stroke:var(--mdc-circular-progress-bar-color-3, var(--mdc-theme-primary, #6200ee))}.mdc-circular-progress__color-4 .mdc-circular-progress__indeterminate-circle-graphic{stroke:#6200ee;stroke:var(--mdc-circular-progress-bar-color-4, var(--mdc-theme-primary, #6200ee))}:host{display:inline-flex}`;let s=class extends t{};s.styles=[o],s=i([a("mwc-circular-progress-four-color")],s);export{s as CircularProgressFourColor}; 16 | -------------------------------------------------------------------------------- /build/mwc-circular-progress.js: -------------------------------------------------------------------------------- 1 | import{c as r,_ as e,C as c,d as i}from"./core.js";const t=r`.mdc-circular-progress__determinate-circle,.mdc-circular-progress__indeterminate-circle-graphic{stroke:#6200ee;stroke:var(--mdc-theme-primary, #6200ee)}.mdc-circular-progress__determinate-track{stroke:transparent}@keyframes mdc-circular-progress-container-rotate{to{transform:rotate(360deg)}}@keyframes mdc-circular-progress-spinner-layer-rotate{12.5%{transform:rotate(135deg)}25%{transform:rotate(270deg)}37.5%{transform:rotate(405deg)}50%{transform:rotate(540deg)}62.5%{transform:rotate(675deg)}75%{transform:rotate(810deg)}87.5%{transform:rotate(945deg)}100%{transform:rotate(1080deg)}}@keyframes mdc-circular-progress-color-1-fade-in-out{from{opacity:.99}25%{opacity:.99}26%{opacity:0}89%{opacity:0}90%{opacity:.99}to{opacity:.99}}@keyframes mdc-circular-progress-color-2-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:.99}50%{opacity:.99}51%{opacity:0}to{opacity:0}}@keyframes mdc-circular-progress-color-3-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:.99}75%{opacity:.99}76%{opacity:0}to{opacity:0}}@keyframes mdc-circular-progress-color-4-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:.99}90%{opacity:.99}to{opacity:0}}@keyframes mdc-circular-progress-left-spin{from{transform:rotate(265deg)}50%{transform:rotate(130deg)}to{transform:rotate(265deg)}}@keyframes mdc-circular-progress-right-spin{from{transform:rotate(-265deg)}50%{transform:rotate(-130deg)}to{transform:rotate(-265deg)}}.mdc-circular-progress{display:inline-flex;position:relative;direction:ltr;line-height:0;transition:opacity 250ms 0ms cubic-bezier(0.4, 0, 0.6, 1)}.mdc-circular-progress__determinate-container,.mdc-circular-progress__indeterminate-circle-graphic,.mdc-circular-progress__indeterminate-container,.mdc-circular-progress__spinner-layer{position:absolute;width:100%;height:100%}.mdc-circular-progress__determinate-container{transform:rotate(-90deg)}.mdc-circular-progress__indeterminate-container{font-size:0;letter-spacing:0;white-space:nowrap;opacity:0}.mdc-circular-progress__determinate-circle-graphic,.mdc-circular-progress__indeterminate-circle-graphic{fill:transparent}.mdc-circular-progress__determinate-circle{transition:stroke-dashoffset 500ms 0ms cubic-bezier(0, 0, 0.2, 1)}.mdc-circular-progress__gap-patch{position:absolute;top:0;left:47.5%;box-sizing:border-box;width:5%;height:100%;overflow:hidden}.mdc-circular-progress__gap-patch .mdc-circular-progress__indeterminate-circle-graphic{left:-900%;width:2000%;transform:rotate(180deg)}.mdc-circular-progress__circle-clipper{display:inline-flex;position:relative;width:50%;height:100%;overflow:hidden}.mdc-circular-progress__circle-clipper .mdc-circular-progress__indeterminate-circle-graphic{width:200%}.mdc-circular-progress__circle-right .mdc-circular-progress__indeterminate-circle-graphic{left:-100%}.mdc-circular-progress--indeterminate .mdc-circular-progress__determinate-container{opacity:0}.mdc-circular-progress--indeterminate .mdc-circular-progress__indeterminate-container{opacity:1}.mdc-circular-progress--indeterminate .mdc-circular-progress__indeterminate-container{animation:mdc-circular-progress-container-rotate 1568.2352941176ms linear infinite}.mdc-circular-progress--indeterminate .mdc-circular-progress__spinner-layer{animation:mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.mdc-circular-progress--indeterminate .mdc-circular-progress__color-1{animation:mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,mdc-circular-progress-color-1-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.mdc-circular-progress--indeterminate .mdc-circular-progress__color-2{animation:mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,mdc-circular-progress-color-2-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.mdc-circular-progress--indeterminate .mdc-circular-progress__color-3{animation:mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,mdc-circular-progress-color-3-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.mdc-circular-progress--indeterminate .mdc-circular-progress__color-4{animation:mdc-circular-progress-spinner-layer-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,mdc-circular-progress-color-4-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.mdc-circular-progress--indeterminate .mdc-circular-progress__circle-left .mdc-circular-progress__indeterminate-circle-graphic{animation:mdc-circular-progress-left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.mdc-circular-progress--indeterminate .mdc-circular-progress__circle-right .mdc-circular-progress__indeterminate-circle-graphic{animation:mdc-circular-progress-right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.mdc-circular-progress--closed{opacity:0}:host{display:inline-flex}.mdc-circular-progress__determinate-track{stroke:transparent;stroke:var(--mdc-circular-progress-track-color, transparent)}`;let a=class extends c{};a.styles=[t],a=e([i("mwc-circular-progress")],a);export{a as CircularProgress}; 2 | -------------------------------------------------------------------------------- /build/mwc-formfield.js: -------------------------------------------------------------------------------- 1 | import{f as t,g as e,M as i,_ as r,y as a,n,m as o,i as l,B as d,F as c,x as s,o as p,c as f,d as m}from"./core.js";var g={ROOT:"mdc-form-field"},h={LABEL_SELECTOR:".mdc-form-field > label"},y=function(i){function r(t){var a=i.call(this,e(e({},r.defaultAdapter),t))||this;return a.click=function(){a.handleClick()},a}return t(r,i),Object.defineProperty(r,"cssClasses",{get:function(){return g},enumerable:!1,configurable:!0}),Object.defineProperty(r,"strings",{get:function(){return h},enumerable:!1,configurable:!0}),Object.defineProperty(r,"defaultAdapter",{get:function(){return{activateInputRipple:function(){},deactivateInputRipple:function(){},deregisterInteractionHandler:function(){},registerInteractionHandler:function(){}}},enumerable:!1,configurable:!0}),r.prototype.init=function(){this.adapter.registerInteractionHandler("click",this.click)},r.prototype.destroy=function(){this.adapter.deregisterInteractionHandler("click",this.click)},r.prototype.handleClick=function(){var t=this;this.adapter.activateInputRipple(),requestAnimationFrame((function(){t.adapter.deactivateInputRipple()}))},r}(i);class b extends d{constructor(){super(...arguments),this.alignEnd=!1,this.spaceBetween=!1,this.nowrap=!1,this.label="",this.mdcFoundationClass=y}createAdapter(){return{registerInteractionHandler:(t,e)=>{this.labelEl.addEventListener(t,e)},deregisterInteractionHandler:(t,e)=>{this.labelEl.removeEventListener(t,e)},activateInputRipple:async()=>{const t=this.input;if(t instanceof c){const e=await t.ripple;e&&e.startPress()}},deactivateInputRipple:async()=>{const t=this.input;if(t instanceof c){const e=await t.ripple;e&&e.endPress()}}}}get input(){var t,e;return null!==(e=null===(t=this.slottedInputs)||void 0===t?void 0:t[0])&&void 0!==e?e:null}render(){const t={"mdc-form-field--align-end":this.alignEnd,"mdc-form-field--space-between":this.spaceBetween,"mdc-form-field--nowrap":this.nowrap};return s` 2 |
3 | 4 | 6 |
`}click(){this._labelClick()}_labelClick(){const t=this.input;t&&(t.focus(),t.click())}}r([n({type:Boolean})],b.prototype,"alignEnd",void 0),r([n({type:Boolean})],b.prototype,"spaceBetween",void 0),r([n({type:Boolean})],b.prototype,"nowrap",void 0),r([n({type:String}),o((async function(t){var e;null===(e=this.input)||void 0===e||e.setAttribute("aria-label",t)}))],b.prototype,"label",void 0),r([l(".mdc-form-field")],b.prototype,"mdcRoot",void 0),r([a("",!0,"*")],b.prototype,"slottedInputs",void 0),r([l("label")],b.prototype,"labelEl",void 0);const u=f`.mdc-form-field{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-body2-font-size, 0.875rem);line-height:1.25rem;line-height:var(--mdc-typography-body2-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-body2-font-weight, 400);letter-spacing:0.0178571429em;letter-spacing:var(--mdc-typography-body2-letter-spacing, 0.0178571429em);text-decoration:inherit;text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-body2-text-transform, inherit);color:rgba(0, 0, 0, 0.87);color:var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87));display:inline-flex;align-items:center;vertical-align:middle}.mdc-form-field>label{margin-left:0;margin-right:auto;padding-left:4px;padding-right:0;order:0}[dir=rtl] .mdc-form-field>label,.mdc-form-field>label[dir=rtl]{margin-left:auto;margin-right:0}[dir=rtl] .mdc-form-field>label,.mdc-form-field>label[dir=rtl]{padding-left:0;padding-right:4px}.mdc-form-field--nowrap>label{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.mdc-form-field--align-end>label{margin-left:auto;margin-right:0;padding-left:0;padding-right:4px;order:-1}[dir=rtl] .mdc-form-field--align-end>label,.mdc-form-field--align-end>label[dir=rtl]{margin-left:0;margin-right:auto}[dir=rtl] .mdc-form-field--align-end>label,.mdc-form-field--align-end>label[dir=rtl]{padding-left:4px;padding-right:0}.mdc-form-field--space-between{justify-content:space-between}.mdc-form-field--space-between>label{margin:0}[dir=rtl] .mdc-form-field--space-between>label,.mdc-form-field--space-between>label[dir=rtl]{margin:0}:host{display:inline-flex}.mdc-form-field{width:100%}::slotted(*){-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:Roboto, sans-serif;font-family:var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif));font-size:0.875rem;font-size:var(--mdc-typography-body2-font-size, 0.875rem);line-height:1.25rem;line-height:var(--mdc-typography-body2-line-height, 1.25rem);font-weight:400;font-weight:var(--mdc-typography-body2-font-weight, 400);letter-spacing:0.0178571429em;letter-spacing:var(--mdc-typography-body2-letter-spacing, 0.0178571429em);text-decoration:inherit;text-decoration:var(--mdc-typography-body2-text-decoration, inherit);text-transform:inherit;text-transform:var(--mdc-typography-body2-text-transform, inherit);color:rgba(0, 0, 0, 0.87);color:var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87))}::slotted(mwc-switch){margin-right:10px}[dir=rtl] ::slotted(mwc-switch),::slotted(mwc-switch[dir=rtl]){margin-left:10px}`;let v=class extends b{};v.styles=[u],v=r([m("mwc-formfield")],v);export{v as Formfield}; 7 | -------------------------------------------------------------------------------- /build/mwc-icon-button-toggle.js: -------------------------------------------------------------------------------- 1 | import{_ as e,t,i,a as o,n as s,e as n,b as l,s as a,R as p,x as r,o as d,l as c,z as h,d as u}from"./core.js";class b extends a{constructor(){super(...arguments),this.disabled=!1,this.onIcon="",this.offIcon="",this.on=!1,this.shouldRenderRipple=!1,this.rippleHandlers=new p((()=>(this.shouldRenderRipple=!0,this.ripple)))}handleClick(){this.on=!this.on,this.dispatchEvent(new CustomEvent("icon-button-toggle-change",{detail:{isOn:this.on},bubbles:!0}))}click(){this.mdcRoot.focus(),this.mdcRoot.click()}focus(){this.rippleHandlers.startFocus(),this.mdcRoot.focus()}blur(){this.rippleHandlers.endFocus(),this.mdcRoot.blur()}renderRipple(){return this.shouldRenderRipple?r` 2 | 5 | `:""}render(){const e={"mdc-icon-button--on":this.on},t=void 0!==this.ariaLabelOn&&void 0!==this.ariaLabelOff,i=t?void 0:this.on,o=t?this.on?this.ariaLabelOn:this.ariaLabelOff:this.ariaLabel;return r``}handleRippleMouseDown(e){const t=()=>{window.removeEventListener("mouseup",t),this.handleRippleDeactivate()};window.addEventListener("mouseup",t),this.rippleHandlers.startPress(e)}handleRippleTouchStart(e){this.rippleHandlers.startPress(e)}handleRippleDeactivate(){this.rippleHandlers.endPress()}handleRippleMouseEnter(){this.rippleHandlers.startHover()}handleRippleMouseLeave(){this.rippleHandlers.endHover()}handleRippleFocus(){this.rippleHandlers.startFocus()}handleRippleBlur(){this.rippleHandlers.endFocus()}}e([i(".mdc-icon-button")],b.prototype,"mdcRoot",void 0),e([o,s({type:String,attribute:"aria-label"})],b.prototype,"ariaLabel",void 0),e([s({type:Boolean,reflect:!0})],b.prototype,"disabled",void 0),e([s({type:String})],b.prototype,"onIcon",void 0),e([s({type:String})],b.prototype,"offIcon",void 0),e([s({type:String})],b.prototype,"ariaLabelOn",void 0),e([s({type:String})],b.prototype,"ariaLabelOff",void 0),e([s({type:Boolean,reflect:!0})],b.prototype,"on",void 0),e([n("mwc-ripple")],b.prototype,"ripple",void 0),e([t()],b.prototype,"shouldRenderRipple",void 0),e([l({passive:!0})],b.prototype,"handleRippleMouseDown",null),e([l({passive:!0})],b.prototype,"handleRippleTouchStart",null);let R=class extends b{};R.styles=[h],R=e([u("mwc-icon-button-toggle")],R);export{R as IconButtonToggle}; 31 | -------------------------------------------------------------------------------- /build/mwc-icon-button.js: -------------------------------------------------------------------------------- 1 | import{_ as e,t,n as s,a as i,i as p,e as l,b as a,s as o,R as r,x as n,l as d,z as h,d as u}from"./core.js";class c extends o{constructor(){super(...arguments),this.disabled=!1,this.icon="",this.shouldRenderRipple=!1,this.rippleHandlers=new r((()=>(this.shouldRenderRipple=!0,this.ripple)))}renderRipple(){return this.shouldRenderRipple?n` 2 | 5 | `:""}focus(){const e=this.buttonElement;e&&(this.rippleHandlers.startFocus(),e.focus())}blur(){const e=this.buttonElement;e&&(this.rippleHandlers.endFocus(),e.blur())}render(){return n``}handleRippleMouseDown(e){const t=()=>{window.removeEventListener("mouseup",t),this.handleRippleDeactivate()};window.addEventListener("mouseup",t),this.rippleHandlers.startPress(e)}handleRippleTouchStart(e){this.rippleHandlers.startPress(e)}handleRippleDeactivate(){this.rippleHandlers.endPress()}handleRippleMouseEnter(){this.rippleHandlers.startHover()}handleRippleMouseLeave(){this.rippleHandlers.endHover()}handleRippleFocus(){this.rippleHandlers.startFocus()}handleRippleBlur(){this.rippleHandlers.endFocus()}}e([s({type:Boolean,reflect:!0})],c.prototype,"disabled",void 0),e([s({type:String})],c.prototype,"icon",void 0),e([i,s({type:String,attribute:"aria-label"})],c.prototype,"ariaLabel",void 0),e([i,s({type:String,attribute:"aria-haspopup"})],c.prototype,"ariaHasPopup",void 0),e([p("button")],c.prototype,"buttonElement",void 0),e([l("mwc-ripple")],c.prototype,"ripple",void 0),e([t()],c.prototype,"shouldRenderRipple",void 0),e([a({passive:!0})],c.prototype,"handleRippleMouseDown",null),e([a({passive:!0})],c.prototype,"handleRippleTouchStart",null);let R=class extends c{};R.styles=[h],R=e([u("mwc-icon-button")],R);export{R as IconButton}; 24 | -------------------------------------------------------------------------------- /build/mwc-icon.js: -------------------------------------------------------------------------------- 1 | export{ar as Icon}from"./core.js"; 2 | -------------------------------------------------------------------------------- /build/mwc-list-item.js: -------------------------------------------------------------------------------- 1 | import{N as s,_ as e,O as t,d as a}from"./core.js";let l=class extends t{};l.styles=[s],l=e([a("mwc-list-item")],l);export{l as ListItem}; 2 | -------------------------------------------------------------------------------- /build/mwc-radio-list-item.js: -------------------------------------------------------------------------------- 1 | import{_ as e,i as t,n as i,O as s,x as r,o as c,l as o,N as h,P as a,d as l}from"./core.js";import"./mwc-radio.js";class d extends s{constructor(){super(...arguments),this.left=!1,this.graphic="control",this._changeFromClick=!1}render(){const e={"mdc-deprecated-list-item__graphic":this.left,"mdc-deprecated-list-item__meta":!this.left},t=this.renderText(),i=this.graphic&&"control"!==this.graphic&&!this.left?this.renderGraphic():r``,s=this.hasMeta&&this.left?this.renderMeta():r``,h=this.renderRipple();return r` 2 | ${h} 3 | ${i} 4 | ${this.left?"":t} 5 | 13 | 14 | ${this.left?t:""} 15 | ${s}`}onClick(){this._changeFromClick=!0,super.onClick()}async onChange(e){const t=e.target;this.selected===t.checked||(this._skipPropRequest=!0,this.selected=t.checked,await this.updateComplete,this._skipPropRequest=!1,this._changeFromClick||this.fireRequestSelected(this.selected,"interaction")),this._changeFromClick=!1}}e([t("slot")],d.prototype,"slotElement",void 0),e([t("mwc-radio")],d.prototype,"radioElement",void 0),e([i({type:Boolean})],d.prototype,"left",void 0),e([i({type:String,reflect:!0})],d.prototype,"graphic",void 0);let n=class extends d{};n.styles=[h,a],n=e([l("mwc-radio-list-item")],n);export{n as RadioListItem}; 16 | -------------------------------------------------------------------------------- /build/mwc-textarea.js: -------------------------------------------------------------------------------- 1 | import{_ as e,i as t,n as i,aj as l,x as s,o as r,l as n,ak as a,c as d,al as o,d as h}from"./core.js";const c={fromAttribute:e=>null!==e&&(""===e||e),toAttribute:e=>"boolean"==typeof e?e?"":null:e};class u extends l{constructor(){super(...arguments),this.rows=2,this.cols=20,this.charCounter=!1}render(){const e=this.charCounter&&-1!==this.maxLength,t=e&&"internal"===this.charCounter,i=e&&!t,l=!!this.helper||!!this.validationMessage||i,n={"mdc-text-field--disabled":this.disabled,"mdc-text-field--no-label":!this.label,"mdc-text-field--filled":!this.outlined,"mdc-text-field--outlined":this.outlined,"mdc-text-field--end-aligned":this.endAligned,"mdc-text-field--with-internal-counter":t};return s` 2 | 9 | ${this.renderHelperText(l,i)} 10 | `}renderInput(){const e=this.label?"label":void 0,t=-1===this.minLength?void 0:this.minLength,i=-1===this.maxLength?void 0:this.maxLength,l=this.autocapitalize?this.autocapitalize:void 0;return s` 11 | `}}e([t("textarea")],u.prototype,"formElement",void 0),e([i({type:Number})],u.prototype,"rows",void 0),e([i({type:Number})],u.prototype,"cols",void 0),e([i({converter:c})],u.prototype,"charCounter",void 0);const p=d`.mdc-text-field{height:100%}.mdc-text-field__input{resize:none}`;let m=class extends u{};m.styles=[o,p],m=e([h("mwc-textarea")],m);export{m as TextArea}; 29 | -------------------------------------------------------------------------------- /build/mwc-textfield.js: -------------------------------------------------------------------------------- 1 | import{al as s,_ as e,aj as a,d as t}from"./core.js";let l=class extends a{};l.styles=[s],l=e([t("mwc-textfield")],l);export{l as TextField}; 2 | -------------------------------------------------------------------------------- /build/mwc-top-app-bar-fixed.js: -------------------------------------------------------------------------------- 1 | import{f as s,am as e,an as a,ao as r,ap as t,aq as l,_ as o,d as i}from"./core.js";var n=function(a){function r(){var s=null!==a&&a.apply(this,arguments)||this;return s.wasScrolled=!1,s}return s(r,a),r.prototype.handleTargetScroll=function(){this.adapter.getViewportScrollY()<=0?this.wasScrolled&&(this.adapter.removeClass(e.FIXED_SCROLLED_CLASS),this.wasScrolled=!1):this.wasScrolled||(this.adapter.addClass(e.FIXED_SCROLLED_CLASS),this.wasScrolled=!0)},r}(a);class c extends r{constructor(){super(...arguments),this.mdcFoundationClass=n}barClasses(){return Object.assign(Object.assign({},super.barClasses()),{"mdc-top-app-bar--fixed":!0})}registerListeners(){this.scrollTarget.addEventListener("scroll",this.handleTargetScroll,t)}unregisterListeners(){this.scrollTarget.removeEventListener("scroll",this.handleTargetScroll)}}let d=class extends c{};d.styles=[l],d=o([i("mwc-top-app-bar-fixed")],d);export{d as TopAppBarFixed}; 2 | -------------------------------------------------------------------------------- /build/mwc-top-app-bar.js: -------------------------------------------------------------------------------- 1 | import{aq as s,_ as a,ao as e,d as o}from"./core.js";let t=class extends e{};t.styles=[s],t=a([o("mwc-top-app-bar")],t);export{t as TopAppBar}; 2 | -------------------------------------------------------------------------------- /demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "material-dioxus-demo" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | dioxus = "0.4.0" 10 | dioxus-web = "0.4.0" 11 | gloo-console = "0.3.0" 12 | 13 | material-dioxus = { version = "0.0.3-dev", path = "../", features = ["full"] } 14 | -------------------------------------------------------------------------------- /demo/Dioxus.toml: -------------------------------------------------------------------------------- 1 | [application] 2 | name = "mdc-web-test" 3 | default_platform = "web" 4 | out_dir = "dist" 5 | asset_dir = "public" 6 | 7 | [web.app] 8 | title = "Material Dioxus" 9 | 10 | [web.watcher] 11 | index_on_404 = true 12 | watch_path = ["src"] 13 | 14 | [web.resource] 15 | style = [ 16 | "https://fonts.googleapis.com/css?family=Roboto:300,400,500", 17 | "https://fonts.googleapis.com/css?family=Material+Icons&display=block", 18 | ] 19 | script = [] 20 | 21 | [web.resource.dev] 22 | script = [] 23 | -------------------------------------------------------------------------------- /dprint.json: -------------------------------------------------------------------------------- 1 | { 2 | "lineWidth": 120, 3 | "indentWidth": 4, 4 | "useTabs": false, 5 | 6 | "typescript": { 7 | "semiColons": "asi", 8 | "quoteStyle": "preferSingle", 9 | "quoteProps": "asNeeded", 10 | "singleBodyPosition": "sameLine", 11 | "nextControlFlowPosition": "sameLine", 12 | "arrowFunction.useParentheses": "preferNone", 13 | "enumDeclaration.memberSpacing": "newLine" 14 | }, 15 | 16 | "json": { 17 | "indentWidth": 2, 18 | "array.preferSingleLine": true 19 | }, 20 | 21 | "markdown": { 22 | "lineWidth": 80, 23 | "textWrap": "always" 24 | }, 25 | 26 | "toml": { 27 | "comment.forceLeadingSpace": false 28 | }, 29 | 30 | "prettier": { 31 | "printWidth": 100, 32 | "tabWidth": 4, 33 | "semi": false, 34 | "singleQuote": true, 35 | "trailingComma": "all", 36 | "arrowParens": "avoid", 37 | "proseWrap": "always", 38 | 39 | "plugin.jsdoc": true, 40 | "yml.tabWidth": 2, 41 | "yaml.tabWidth": 2, 42 | "json.tabWidth": 2, 43 | "jsonc.tabWidth": 2, 44 | "json5.tabWidth": 2 45 | }, 46 | 47 | "exec": { 48 | "commands": [{ 49 | "command": "rustfmt --edition 2021", 50 | "exts": ["rs"] 51 | }] 52 | }, 53 | 54 | "includes": ["**/*"], 55 | "excludes": [ 56 | "**/target", 57 | "**/node_modules", 58 | "**/*-lock.json", 59 | "ignored/**", 60 | "build/**", 61 | "website/**", 62 | "screenshots/**" 63 | ], 64 | "plugins": [ 65 | "https://plugins.dprint.dev/typescript-0.87.1.wasm", 66 | "https://plugins.dprint.dev/json-0.17.4.wasm", 67 | "https://plugins.dprint.dev/markdown-0.16.1.wasm", 68 | "https://plugins.dprint.dev/toml-0.5.4.wasm", 69 | "https://plugins.dprint.dev/prettier-0.27.0.json@3557a62b4507c55a47d8cde0683195b14d13c41dda66d0f0b0e111aed107e2fe", 70 | "https://plugins.dprint.dev/exec-0.4.3.json@42343548b8022c99b1d750be6b894fe6b6c7ee25f72ae9f9082226dd2e515072" 71 | ] 72 | } 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "material-yew", 3 | "version": "1.0.0", 4 | "description": "WIP", 5 | "main": "rollup.config.mjs", 6 | "dependencies": { 7 | "@material/mwc-button": "0.27.0", 8 | "@material/mwc-checkbox": "0.27.0", 9 | "@material/mwc-circular-progress": "0.27.0", 10 | "@material/mwc-circular-progress-four-color": "0.27.0", 11 | "@material/mwc-dialog": "0.27.0", 12 | "@material/mwc-drawer": "0.27.0", 13 | "@material/mwc-fab": "0.27.0", 14 | "@material/mwc-formfield": "0.27.0", 15 | "@material/mwc-icon": "0.27.0", 16 | "@material/mwc-icon-button": "0.27.0", 17 | "@material/mwc-icon-button-toggle": "0.27.0", 18 | "@material/mwc-linear-progress": "0.27.0", 19 | "@material/mwc-list": "0.27.0", 20 | "@material/mwc-menu": "0.27.0", 21 | "@material/mwc-radio": "0.27.0", 22 | "@material/mwc-select": "0.27.0", 23 | "@material/mwc-slider": "0.27.0", 24 | "@material/mwc-snackbar": "0.27.0", 25 | "@material/mwc-switch": "0.27.0", 26 | "@material/mwc-tab": "0.27.0", 27 | "@material/mwc-tab-bar": "0.27.0", 28 | "@material/mwc-textarea": "0.27.0", 29 | "@material/mwc-textfield": "0.27.0", 30 | "@material/mwc-top-app-bar": "0.27.0", 31 | "@material/mwc-top-app-bar-fixed": "0.27.0" 32 | }, 33 | "devDependencies": { 34 | "@rollup/plugin-node-resolve": "^9.0.0", 35 | "@rollup/plugin-terser": "^0.4.3", 36 | "rollup": "^2.79.1" 37 | }, 38 | "scripts": { 39 | "build": "rollup -c rollup.config.mjs", 40 | "postbuild": "node postprocess.mjs" 41 | }, 42 | "keywords": [], 43 | "author": "", 44 | "license": "ISC" 45 | } 46 | -------------------------------------------------------------------------------- /postprocess.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | 3 | let core_js = fs.readFileSync('./build/core.js').toString() 4 | 5 | // customization of textfield icon color 6 | core_js = core_js.replace( 7 | /(\.mdc-text-field:not\(\.mdc-text-field--disabled\) \.mdc-text-field__icon--(?:leading|trailing){color:)(rgba\(0, 0, 0, 0\.54\))(})/g, 8 | '$1var(--mdc-text-field-icon-color, $2)$3', 9 | ).replace( 10 | /(\.mdc-text-field--disabled \.mdc-text-field__icon--(?:leading|trailing){color:)(rgba\(0, 0, 0, 0\.3\))(})/g, 11 | '$1var(--mdc-text-field-disabled-icon-color, $2)$3', 12 | ) 13 | 14 | // allow using native WebKit calendar picker 15 | core_js = core_js.replace( 16 | /(\.mdc-text-field__input)(::-webkit-calendar-picker-indicator)({display:none})/g, 17 | '$1$2$3.mdc-text-field--webkit-date-picker $1{position:relative}.mdc-text-field--webkit-date-picker $1$2{display:inline-block;position:absolute;inset:0;width:auto;height:auto;color:transparent;background:transparent}', 18 | ).replace( 19 | /(r\(\[c\({type:Boolean)(,reflect:!0)(}\)],gr\.prototype,")(disabled)(",void 0\))/g, 20 | '$1$2$3$4$5,$1$3webkitDatePicker$5', 21 | ).replace( 22 | /(this\.iconTrailing="",this\.disabled=!1)/g, 23 | '$1,this.webkitDatePicker=!1', 24 | ).replace( 25 | /("mdc-text-field--disabled":this\.disabled)/g, 26 | '$1,"mdc-text-field--webkit-date-picker":this.webkitDatePicker', 27 | ) 28 | 29 | // fix textarea internal char counter color 30 | core_js = core_js.replace( 31 | /(\.mdc-text-field-character-counter{color:)(rgba\(0, 0, 0, 0\.6\))(})/g, 32 | '$1var(--mdc-text-field-label-ink-color, $2)$3', 33 | ) 34 | 35 | fs.writeFileSync('./build/core.js', core_js) 36 | 37 | let list_js = fs.readFileSync('./build/mwc-list.js').toString() 38 | 39 | // customization of list divider color 40 | list_js = list_js.replace( 41 | /(\.mdc-deprecated-list ::slotted\(\[divider\]\){[^\}]*border-bottom-color:)(rgba\(0, 0, 0, 0\.12\))(})/g, 42 | '$1var(--mdc-deprecated-list-divider-color, $2)$3', 43 | ) 44 | 45 | fs.writeFileSync('./build/mwc-list.js', list_js) 46 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import { nodeResolve } from '@rollup/plugin-node-resolve' 2 | import terser from '@rollup/plugin-terser' 3 | 4 | const COMPONENTS = [ 5 | 'button', 6 | 'checkbox', 7 | 'circular-progress', 8 | 'circular-progress-four-color', 9 | 'dialog', 10 | 'drawer', 11 | 'fab', 12 | 'formfield', 13 | 'icon-button-toggle', 14 | 'icon-button', 15 | 'icon', 16 | 'linear-progress', 17 | 'list', 18 | 'list/mwc-list-item', 19 | 'list/mwc-check-list-item', 20 | 'list/mwc-radio-list-item', 21 | 'menu', 22 | 'radio', 23 | 'select', 24 | 'slider', 25 | 'snackbar', 26 | 'switch', 27 | 'tab-bar', 28 | 'tab', 29 | 'textarea', 30 | 'textfield', 31 | 'top-app-bar-fixed', 32 | 'top-app-bar', 33 | ] 34 | 35 | export default { 36 | input: COMPONENTS.map(component => `@material/mwc-${component}`), 37 | plugins: [nodeResolve(), terser({ format: { comments: false } })], 38 | output: { 39 | dir: `build`, 40 | chunkFileNames: '[name].js', 41 | manualChunks: (id, { getModuleInfo }) => { 42 | const info = getModuleInfo(id) 43 | if (info.importers.length <= 1) { 44 | // This will be inlined anyway 45 | return 46 | } 47 | 48 | return 'core' 49 | }, 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /screenshots/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "screenshots" 3 | version = "0.1.0" 4 | authors = ["Hamza "] 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 | wasm-bindgen = "0.2" 11 | yew = { version="0.20.0", features = ["csr"] } 12 | yew-router = "0.17.0" 13 | material-yew = { path = "../material-yew", features = ["full"] } 14 | weblog = "0.3" 15 | gloo-console = "0.2.3" 16 | partial_application = "0.2.1" 17 | -------------------------------------------------------------------------------- /screenshots/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Yew Material Screenshots 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /screenshots/styles/styles.scss: -------------------------------------------------------------------------------- 1 | @mixin screenshot-container { 2 | //width: 100%; 3 | //height: 100%; 4 | 5 | width: 360px; 6 | height: 200px; 7 | 8 | margin-top: 1em; 9 | 10 | background-color: rgba(0, 0, 0, 0.07); // maybe 0.06? 11 | } 12 | 13 | * { 14 | margin: 0; 15 | } 16 | 17 | a { text-decoration: none; } 18 | 19 | #screenshots { 20 | $components: ( 21 | 'mwc-button', 22 | 'mwc-circular-progress', 23 | 'mwc-checkbox', 24 | 'mwc-circular-progress-four-color', 25 | 'mwc-drawer', 26 | 'mwc-top-app-bar', 27 | 'mwc-icon-button', 28 | 'mwc-fab', 29 | 'mwc-formfield', 30 | 'mwc-linear-progress', 31 | 'mwc-icon', 32 | 'mwc-radio', 33 | 'mwc-switch', 34 | 'mwc-top-app-bar-fixed', 35 | 'mwc-dialog', 36 | 'mwc-list', 37 | 'mwc-list-item', 38 | 'mwc-check-list-item', 39 | 'mwc-radio-list-item', 40 | 'mwc-icon-button-toggle', 41 | 'mwc-slider', 42 | 'mwc-tab', 43 | 'mwc-tab-bar', 44 | 'mwc-snackbar', 45 | 'mwc-textfield', 46 | 'mwc-textarea', 47 | 'mwc-select', 48 | 'mwc-menu', 49 | ); 50 | 51 | .container { 52 | @include screenshot-container; 53 | 54 | display: flex; 55 | gap: 2em; 56 | } 57 | 58 | .grid { 59 | display: grid; 60 | align-content: center; 61 | grid-template-columns: 1fr 1fr; 62 | gap: 3em; 63 | 64 | @include screenshot-container; 65 | 66 | @each $name in $components { 67 | #{$name} { 68 | margin: auto; 69 | } 70 | } 71 | } 72 | 73 | #fab { 74 | grid-template-columns: 1fr; 75 | 76 | div { 77 | display: flex; 78 | } 79 | } 80 | 81 | #icon-button { 82 | gap: 1.5em; 83 | } 84 | 85 | #circular-progress { 86 | grid-template-columns: 1fr 1fr 1fr; 87 | } 88 | 89 | #form-field { 90 | display: flex; 91 | 92 | mwc-formfield { 93 | margin: auto; 94 | } 95 | } 96 | 97 | #linear-progress { 98 | grid-template-columns: 1fr; 99 | 100 | mwc-linear-progress { 101 | margin: 1em; 102 | } 103 | } 104 | 105 | #list { 106 | flex-direction: column; 107 | 108 | mwc-list { 109 | margin: 1em; 110 | border: 1px solid rgba(0, 0, 0, 0.2); 111 | } 112 | } 113 | 114 | #slider { 115 | grid-template-columns: 1fr; 116 | } 117 | 118 | #textfield { 119 | grid-template-columns: 1fr; 120 | } 121 | 122 | #textarea { 123 | grid-template-columns: 1fr; 124 | gap: 1.2em; 125 | } 126 | 127 | #select { 128 | flex-direction: column; 129 | 130 | mwc-select { 131 | margin: 1.3em auto; 132 | } 133 | } 134 | 135 | #menu { 136 | flex-direction: column; 137 | gap: 0; 138 | 139 | div { 140 | position: relative; 141 | padding: 0; 142 | margin: 0 4em; 143 | } 144 | 145 | span { 146 | margin: 0 3em; 147 | } 148 | } 149 | 150 | #tabs { 151 | mwc-tab-bar { 152 | flex: 1; 153 | margin-top: 2em; 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/button.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use dioxus::prelude::*; 4 | use gloo::events::EventListener; 5 | use wasm_bindgen::prelude::*; 6 | 7 | use crate::StaticCallback; 8 | 9 | #[wasm_bindgen(module = "/build/mwc-button.js")] 10 | extern "C" { 11 | #[derive(Debug)] 12 | type Button; 13 | 14 | // This needs to be added to each component 15 | #[wasm_bindgen(getter, static_method_of = Button)] 16 | fn _dummy_loader() -> JsValue; 17 | } 18 | 19 | // call the macro with the type 20 | loader_hack!(Button); 21 | 22 | /// Props for [`MatButton`] 23 | /// 24 | /// [MWC Documentation for properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/button#propertiesattributes) 25 | #[derive(Props)] 26 | pub struct ButtonProps<'a> { 27 | #[props(into)] 28 | pub label: String, 29 | #[props(into)] 30 | pub icon: Option, 31 | // TODO: variant enum 32 | #[props(default)] 33 | pub raised: bool, 34 | #[props(default)] 35 | pub unelevated: bool, 36 | #[props(default)] 37 | pub outlined: bool, 38 | #[props(default)] 39 | pub dense: bool, 40 | #[props(default)] 41 | pub disabled: bool, 42 | #[props(default)] 43 | pub trailing_icon: bool, 44 | 45 | #[props(into)] 46 | // the name cannot start with `on` or dioxus will expect an `EventHandler` which aren't static 47 | // and thus cannot be used here 48 | pub _onclick: Option>, 49 | _lifetime: Option>, 50 | 51 | #[props(into, default)] 52 | pub style: String, 53 | #[props(into, default)] 54 | pub class: String, 55 | #[props(into)] 56 | pub slot: Option, 57 | #[props(default)] 58 | pub dialog_initial_focus: bool, 59 | } 60 | 61 | fn render<'a>(cx: Scope<'a, ButtonProps<'a>>) -> Element<'a> { 62 | let id = crate::use_id(cx, "button"); 63 | let click_listener = cx.use_hook(|| None); 64 | if let Some(elem) = crate::get_elem_by_id(id) { 65 | let target = elem; 66 | if let Some(listener) = cx.props._onclick.clone() { 67 | *click_listener = Some(EventListener::new(&target, "click", move |_| { 68 | listener.call(()) 69 | })); 70 | } 71 | } 72 | 73 | render! { 74 | mwc-button { 75 | id: id, 76 | 77 | icon: optional_string_attr!(cx.props.icon), 78 | label: string_attr!(cx.props.label), 79 | disabled: bool_attr!(cx.props.disabled), 80 | raised: bool_attr!(cx.props.raised), 81 | unelevated: bool_attr!(cx.props.unelevated), 82 | outlined: bool_attr!(cx.props.outlined), 83 | dense: bool_attr!(cx.props.dense), 84 | trailingIcon: bool_attr!(cx.props.trailing_icon), 85 | 86 | style: string_attr!(cx.props.style), 87 | class: string_attr!(cx.props.class), 88 | slot: optional_string_attr!(cx.props.slot), 89 | dialogInitialFocus: bool_attr!(cx.props.dialog_initial_focus), 90 | } 91 | } 92 | } 93 | 94 | component!('a, MatButton, ButtonProps, render, Button, "button"); 95 | -------------------------------------------------------------------------------- /src/checkbox.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use dioxus::prelude::*; 4 | use gloo::events::EventListener; 5 | use wasm_bindgen::prelude::*; 6 | use web_sys::Node; 7 | 8 | use crate::utils::StaticCallback; 9 | 10 | #[wasm_bindgen(module = "/build/mwc-checkbox.js")] 11 | extern "C" { 12 | #[derive(Debug)] 13 | #[wasm_bindgen(extends = Node)] 14 | type Checkbox; 15 | 16 | #[wasm_bindgen(getter, static_method_of = Checkbox)] 17 | fn _dummy_loader() -> JsValue; 18 | 19 | #[wasm_bindgen(method, setter)] 20 | fn set_checked(this: &Checkbox, value: bool); 21 | 22 | #[wasm_bindgen(method, getter)] 23 | fn checked(this: &Checkbox) -> bool; 24 | } 25 | 26 | loader_hack!(Checkbox); 27 | 28 | /// Props for [`MatCheckbox`] 29 | /// 30 | /// MWC Documentation: 31 | /// 32 | /// - [Properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/checkbox#propertiesattributes) 33 | /// - [Events](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/checkbox#events) 34 | #[derive(Props)] 35 | pub struct CheckboxProps<'a> { 36 | #[props(default)] 37 | pub checked: bool, 38 | #[props(default)] 39 | pub indeterminate: bool, 40 | #[props(default)] 41 | pub disabled: bool, 42 | #[props(into)] 43 | pub value: Option, 44 | #[props(default)] 45 | pub reduced_touch_target: bool, 46 | /// Binds to `change` event on `mwc-checkbox` 47 | /// 48 | /// See events docs to learn more. 49 | #[props(into)] 50 | // the name cannot start with `on` or dioxus will expect an `EventHandler` which aren't static 51 | // and thus cannot be used here 52 | pub _onchange: Option>, 53 | _lifetime: Option>, 54 | 55 | #[props(into, default)] 56 | pub style: String, 57 | #[props(into, default)] 58 | pub class: String, 59 | #[props(into)] 60 | pub slot: Option, 61 | #[props(default)] 62 | pub dialog_initial_focus: bool, 63 | } 64 | 65 | fn render<'a>(cx: Scope<'a, CheckboxProps<'a>>) -> Element<'a> { 66 | let id = crate::use_id(cx, "checkbox"); 67 | let change_listener = cx.use_hook(|| None); 68 | if let Some(elem) = crate::get_elem_by_id(id) { 69 | let target = elem.clone(); 70 | let cb = JsValue::from(elem).dyn_into::().unwrap(); 71 | cb.set_checked(cx.props.checked); 72 | if let Some(listener) = cx.props._onchange.clone() { 73 | *change_listener = Some(EventListener::new(&target, "change", move |_| { 74 | listener.call(cb.checked()) 75 | })); 76 | } 77 | } 78 | render! { 79 | mwc-checkbox { 80 | id: id, 81 | 82 | indeterminate: bool_attr!(cx.props.indeterminate), 83 | disabled: cx.props.disabled, 84 | value: optional_string_attr!(cx.props.value), 85 | reducedTouchTarget: bool_attr!(cx.props.reduced_touch_target), 86 | 87 | style: string_attr!(cx.props.style), 88 | class: string_attr!(cx.props.class), 89 | slot: optional_string_attr!(cx.props.slot), 90 | dialogInitialFocus: bool_attr!(cx.props.dialog_initial_focus), 91 | } 92 | } 93 | } 94 | 95 | component!('a, MatCheckbox, CheckboxProps, render, Checkbox, "checkbox"); 96 | -------------------------------------------------------------------------------- /src/circular_progress.rs: -------------------------------------------------------------------------------- 1 | use dioxus::{core::AttributeValue, prelude::*}; 2 | use wasm_bindgen::prelude::*; 3 | 4 | #[wasm_bindgen(module = "/build/mwc-circular-progress.js")] 5 | extern "C" { 6 | #[derive(Debug)] 7 | type CircularProgress; 8 | 9 | // This needs to be added to each component 10 | #[wasm_bindgen(getter, static_method_of = CircularProgress)] 11 | fn _dummy_loader() -> JsValue; 12 | } 13 | 14 | // call the macro with the type 15 | loader_hack!(CircularProgress); 16 | 17 | /// Props for [`MatCircularProgress`] 18 | /// 19 | /// [MWC Documentation for properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/circular-progress#propertiesattributes) 20 | #[derive(Props, PartialEq)] 21 | pub struct CircularProgressProps { 22 | #[props(default)] 23 | pub indeterminate: bool, 24 | #[props(default)] 25 | pub progress: f32, 26 | #[props(default)] 27 | pub density: i64, 28 | #[props(default)] 29 | pub closed: bool, 30 | 31 | #[props(into, default)] 32 | pub style: String, 33 | #[props(into, default)] 34 | pub class: String, 35 | #[props(into)] 36 | pub slot: Option, 37 | } 38 | 39 | fn render(cx: Scope) -> Element { 40 | render! { 41 | mwc-circular-progress { 42 | indeterminate: bool_attr!(cx.props.indeterminate), 43 | progress: AttributeValue::Float(cx.props.progress.into()), 44 | density: AttributeValue::Int(cx.props.density), 45 | closed: bool_attr!(cx.props.closed), 46 | 47 | style: string_attr!(cx.props.style), 48 | class: string_attr!(cx.props.class), 49 | slot: optional_string_attr!(cx.props.slot), 50 | } 51 | } 52 | } 53 | 54 | component!( 55 | MatCircularProgress, 56 | CircularProgressProps, 57 | render, 58 | CircularProgress, 59 | "circular-progress" 60 | ); 61 | -------------------------------------------------------------------------------- /src/circular_progress_four_color.rs: -------------------------------------------------------------------------------- 1 | use dioxus::{core::AttributeValue, prelude::*}; 2 | use wasm_bindgen::prelude::*; 3 | 4 | #[wasm_bindgen(module = "/build/mwc-circular-progress-four-color.js")] 5 | extern "C" { 6 | #[derive(Debug)] 7 | type CircularProgressFourColor; 8 | 9 | // This needs to be added to each component 10 | #[wasm_bindgen(getter, static_method_of = CircularProgressFourColor)] 11 | fn _dummy_loader() -> JsValue; 12 | } 13 | 14 | // call the macro with the type 15 | loader_hack!(CircularProgressFourColor); 16 | 17 | /// Props for [`MatCircularProgressFourColor`] 18 | /// 19 | /// [MWC Documentation for properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/circular-progress-four-color#propertiesattributes) 20 | #[derive(Props, PartialEq)] 21 | pub struct CircularProgressFourColorProps { 22 | #[props(default)] 23 | pub indeterminate: bool, 24 | #[props(default)] 25 | pub progress: f32, 26 | #[props(default)] 27 | pub density: u32, 28 | #[props(default)] 29 | pub closed: bool, 30 | 31 | #[props(into, default)] 32 | pub style: String, 33 | #[props(into, default)] 34 | pub class: String, 35 | #[props(into)] 36 | pub slot: Option, 37 | } 38 | 39 | fn render(cx: Scope) -> Element { 40 | render! { 41 | mwc-circular-progress-four-color { 42 | indeterminate: bool_attr!(cx.props.indeterminate), 43 | progress: AttributeValue::Float(cx.props.progress.into()), 44 | density: AttributeValue::Int(cx.props.density.into()), 45 | closed: bool_attr!(cx.props.closed), 46 | 47 | style: string_attr!(cx.props.style), 48 | class: string_attr!(cx.props.class), 49 | slot: optional_string_attr!(cx.props.slot), 50 | } 51 | } 52 | } 53 | 54 | component!( 55 | MatCircularProgressFourColor, 56 | CircularProgressFourColorProps, 57 | render, 58 | CircularProgressFourColor, 59 | "circular-progress-four-color" 60 | ); 61 | -------------------------------------------------------------------------------- /src/dialog/dialog_action.rs: -------------------------------------------------------------------------------- 1 | use dioxus::prelude::*; 2 | use std::fmt; 3 | 4 | /// Dialog action type. 5 | #[derive(Clone, PartialEq)] 6 | pub enum ActionType { 7 | /// Binds `to slot` of `primaryAction` 8 | Primary, 9 | /// Binds `to slot` of `secondaryAction` 10 | Secondary, 11 | } 12 | 13 | impl ActionType { 14 | pub fn as_str(&self) -> &'static str { 15 | match self { 16 | ActionType::Primary => "primaryAction", 17 | ActionType::Secondary => "secondaryAction", 18 | } 19 | } 20 | } 21 | 22 | impl fmt::Display for ActionType { 23 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 24 | write!(f, "{}", self.as_str()) 25 | } 26 | } 27 | 28 | /// Props for [`MatDialogAction`] 29 | #[derive(Props)] 30 | pub struct ActionProps<'a> { 31 | pub action_type: ActionType, 32 | #[props(into)] 33 | pub action: Option, 34 | pub children: Element<'a>, 35 | } 36 | 37 | /// Defines actions for [`MatDialog`][crate::MatDialog]. 38 | /// 39 | /// The passed children are wrapped in a `span` with the required attributes 40 | /// set. 41 | #[allow(non_snake_case)] 42 | pub fn MatDialogAction<'a>(cx: Scope<'a, ActionProps<'a>>) -> Element<'a> { 43 | render! { 44 | span { 45 | slot: "{cx.props.action_type}", 46 | "dialogAction": optional_string_attr!(cx.props.action), 47 | &cx.props.children 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/drawer.rs: -------------------------------------------------------------------------------- 1 | mod drawer_app_content; 2 | mod drawer_header; 3 | mod drawer_subtitle; 4 | mod drawer_title; 5 | 6 | pub use drawer_app_content::*; 7 | pub use drawer_header::*; 8 | pub use drawer_subtitle::*; 9 | pub use drawer_title::*; 10 | 11 | use crate::{bool_to_option, WeakComponentLink}; 12 | use gloo::events::EventListener; 13 | use wasm_bindgen::prelude::*; 14 | use web_sys::Node; 15 | use yew::prelude::*; 16 | use yew::virtual_dom::AttrValue; 17 | 18 | #[wasm_bindgen(module = "/build/mwc-drawer.js")] 19 | extern "C" { 20 | #[derive(Debug)] 21 | #[wasm_bindgen(extends = Node)] 22 | type Drawer; 23 | 24 | #[wasm_bindgen(getter, static_method_of = Drawer)] 25 | fn _dummy_loader() -> JsValue; 26 | 27 | #[wasm_bindgen(method, getter)] 28 | fn open(this: &Drawer) -> bool; 29 | 30 | #[wasm_bindgen(method, setter)] 31 | fn set_open(this: &Drawer, value: bool); 32 | 33 | #[wasm_bindgen(method, setter)] 34 | fn set_type(this: &Drawer, value: &JsValue); 35 | } 36 | 37 | loader_hack!(Drawer); 38 | 39 | /// The `mwc-drawer` component 40 | /// 41 | /// [MWC Documentation](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/drawer) 42 | pub struct MatDrawer { 43 | node_ref: NodeRef, 44 | opened_listener: Option, 45 | closed_listener: Option, 46 | } 47 | 48 | /// Props for [`MatDrawer`] 49 | /// 50 | /// MWC Documentation: 51 | /// 52 | /// - [Properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/drawer#propertiesattributes) 53 | /// - [Events](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/drawer#events) 54 | #[derive(Properties, PartialEq, Clone)] 55 | pub struct DrawerProps { 56 | #[prop_or_default] 57 | pub open: bool, 58 | #[prop_or_default] 59 | pub has_header: bool, 60 | #[prop_or_default] 61 | pub drawer_type: Option, 62 | /// Binds to `opened` event on `mwc-drawer` 63 | /// 64 | /// See events docs to learn more. 65 | #[prop_or_default] 66 | pub onopened: Callback<()>, 67 | /// Binds to `closed` event on `mwc-drawer` 68 | /// 69 | /// See events docs to learn more. 70 | #[prop_or_default] 71 | pub onclosed: Callback<()>, 72 | #[prop_or_default] 73 | pub drawer_link: WeakComponentLink, 74 | pub children: Children, 75 | } 76 | 77 | impl Component for MatDrawer { 78 | type Message = (); 79 | type Properties = DrawerProps; 80 | 81 | fn create(ctx: &Context) -> Self { 82 | ctx.props() 83 | .drawer_link 84 | .borrow_mut() 85 | .replace(ctx.link().clone()); 86 | Drawer::ensure_loaded(); 87 | Self { 88 | node_ref: NodeRef::default(), 89 | opened_listener: None, 90 | closed_listener: None, 91 | } 92 | } 93 | 94 | fn view(&self, ctx: &Context) -> Html { 95 | let props = ctx.props(); 96 | html! { 97 | 98 | {props.children.clone()} 99 | 100 | } 101 | } 102 | 103 | fn rendered(&mut self, ctx: &Context, _first_render: bool) { 104 | let props = ctx.props(); 105 | let element = self.node_ref.cast::().unwrap(); 106 | element.set_type(&JsValue::from( 107 | props 108 | .drawer_type 109 | .as_ref() 110 | .map(|s| s.as_ref()) 111 | .unwrap_or_default(), 112 | )); 113 | element.set_open(props.open); 114 | 115 | if self.opened_listener.is_none() { 116 | let onopen_callback = props.onopened.clone(); 117 | self.opened_listener = Some(EventListener::new( 118 | &element, 119 | "MDCDrawer:opened", 120 | move |_| { 121 | onopen_callback.emit(()); 122 | }, 123 | )); 124 | } 125 | 126 | if self.closed_listener.is_none() { 127 | let onclose_callback = props.onclosed.clone(); 128 | self.closed_listener = Some(EventListener::new( 129 | &element, 130 | "MDCDrawer:closed", 131 | move |_| { 132 | onclose_callback.emit(()); 133 | }, 134 | )); 135 | } 136 | } 137 | } 138 | 139 | impl WeakComponentLink { 140 | /// A convenience method to for `drawer.open = !drawer.open` 141 | pub fn flip_open_state(&self) { 142 | let node_ref = self 143 | .borrow() 144 | .as_ref() 145 | .unwrap() 146 | .get_component() 147 | .unwrap() 148 | .node_ref 149 | .clone(); 150 | let element = node_ref.cast::().unwrap(); 151 | let open = element.open(); 152 | element.set_open(!open); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/drawer/drawer_app_content.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | const SLOT: &str = "appContent"; 4 | 5 | /// Props for [`MatDrawerAppContent`] 6 | #[derive(Properties, PartialEq, Clone)] 7 | pub struct DrawerAppContentProps { 8 | pub children: Children, 9 | } 10 | 11 | /// Defines `appContent` for [`MatDrawer`][crate::MatDrawer]. 12 | /// 13 | /// If the child passed is an element (a `VTag`), then it is modified to include 14 | /// the appropriate attributes. Otherwise, the child is wrapped in a `span` 15 | /// containing said attributes. 16 | pub struct MatDrawerAppContent {} 17 | 18 | impl Component for MatDrawerAppContent { 19 | type Message = (); 20 | type Properties = DrawerAppContentProps; 21 | 22 | fn create(_: &Context) -> Self { 23 | Self {} 24 | } 25 | 26 | fn view(&self, ctx: &Context) -> Html { 27 | let props = ctx.props(); 28 | let children = props 29 | .children 30 | .iter() 31 | .map(|child| match child { 32 | Html::VTag(mut vtag) => { 33 | vtag.add_attribute("slot", "appContent"); 34 | Html::VTag(vtag) 35 | } 36 | _ => { 37 | html! { 38 | 39 | {child} 40 | 41 | } 42 | } 43 | }) 44 | .collect::(); 45 | 46 | html! { 47 | {children} 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/drawer/drawer_header.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | const SLOT: &str = "header"; 4 | 5 | /// Props for [`MatDrawerHeader`] 6 | #[derive(Properties, PartialEq, Clone)] 7 | pub struct DrawerHeaderProps { 8 | pub children: Children, 9 | } 10 | 11 | /// Defines header for [`MatDrawer`][crate::MatDrawer]. 12 | /// 13 | /// If the child passed is an element (a `VTag`), then it is modified to include 14 | /// the appropriate attributes. Otherwise, the child is wrapped in a `span` 15 | /// containing said attributes. 16 | pub struct MatDrawerHeader {} 17 | 18 | impl Component for MatDrawerHeader { 19 | type Message = (); 20 | type Properties = DrawerHeaderProps; 21 | 22 | fn create(_: &Context) -> Self { 23 | Self {} 24 | } 25 | 26 | fn view(&self, ctx: &Context) -> Html { 27 | let props = ctx.props(); 28 | let children = props 29 | .children 30 | .iter() 31 | .map(|child| match child { 32 | Html::VTag(mut vtag) => { 33 | vtag.add_attribute("slot", SLOT); 34 | Html::VTag(vtag) 35 | } 36 | _ => { 37 | html! { 38 | 39 | {child} 40 | 41 | } 42 | } 43 | }) 44 | .collect::(); 45 | 46 | html! { 47 | {children} 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/drawer/drawer_subtitle.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | const SLOT: &str = "subtitle"; 4 | 5 | /// Props for [`MatDrawerSubtitle`] 6 | #[derive(Properties, PartialEq, Clone)] 7 | pub struct DrawerSubtitleProps { 8 | pub children: Children, 9 | } 10 | 11 | /// Defines sub title for [`MatDrawer`][crate::MatDrawer]. 12 | /// 13 | /// If the child passed is an element (a `VTag`), then it is modified to include 14 | /// the appropriate attributes. Otherwise, the child is wrapped in a `span` 15 | /// containing said attributes. 16 | pub struct MatDrawerSubtitle {} 17 | 18 | impl Component for MatDrawerSubtitle { 19 | type Message = (); 20 | type Properties = DrawerSubtitleProps; 21 | 22 | fn create(_: &Context) -> Self { 23 | Self {} 24 | } 25 | 26 | fn view(&self, ctx: &Context) -> Html { 27 | let props = ctx.props(); 28 | let children = props 29 | .children 30 | .iter() 31 | .map(|child| match child { 32 | Html::VTag(mut vtag) => { 33 | vtag.add_attribute("slot", "subtitle"); 34 | Html::VTag(vtag) 35 | } 36 | _ => { 37 | html! { 38 | 39 | {child} 40 | 41 | } 42 | } 43 | }) 44 | .collect::(); 45 | 46 | html! { 47 | {children} 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/drawer/drawer_title.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | const SLOT: &str = "title"; 4 | 5 | /// Props for [`MatDrawerTitle`] 6 | #[derive(Properties, PartialEq, Clone)] 7 | pub struct DrawerTitleProps { 8 | pub children: Children, 9 | } 10 | 11 | /// Defines title for [`MatDrawer`][crate::MatDrawer]. 12 | /// 13 | /// If the child passed is an element (a `VTag`), then it is modified to include 14 | /// the appropriate attributes. Otherwise, the child is wrapped in a `span` 15 | /// containing said attributes. 16 | pub struct MatDrawerTitle {} 17 | 18 | impl Component for MatDrawerTitle { 19 | type Message = (); 20 | type Properties = DrawerTitleProps; 21 | 22 | fn create(_: &Context) -> Self { 23 | Self {} 24 | } 25 | 26 | fn view(&self, ctx: &Context) -> Html { 27 | let props = ctx.props(); 28 | let children = props 29 | .children 30 | .iter() 31 | .map(|child| match child { 32 | Html::VTag(mut vtag) => { 33 | vtag.add_attribute("slot", "title"); 34 | Html::VTag(vtag) 35 | } 36 | _ => { 37 | html! { 38 | 39 | {child} 40 | 41 | } 42 | } 43 | }) 44 | .collect::(); 45 | 46 | html! { 47 | {children} 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/fab.rs: -------------------------------------------------------------------------------- 1 | use dioxus::prelude::*; 2 | use wasm_bindgen::prelude::*; 3 | 4 | #[wasm_bindgen(module = "/build/mwc-fab.js")] 5 | extern "C" { 6 | #[derive(Debug)] 7 | type Fab; 8 | 9 | #[wasm_bindgen(getter, static_method_of = Fab)] 10 | fn _dummy_loader() -> JsValue; 11 | } 12 | 13 | loader_hack!(Fab); 14 | 15 | /// Props for [`MatFab`] 16 | /// 17 | /// [MWC Documentation for properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/fab#propertiesattributes) 18 | #[derive(Props)] 19 | pub struct FabProps<'a> { 20 | #[props(into)] 21 | pub icon: Option, 22 | #[props(into)] 23 | pub label: Option, 24 | #[props(default)] 25 | pub mini: bool, 26 | #[props(default)] 27 | pub reduced_touch_target: bool, 28 | #[props(default)] 29 | pub extended: bool, 30 | #[props(default)] 31 | pub show_icon_at_end: bool, 32 | #[props(default)] 33 | pub children: Element<'a>, 34 | 35 | #[props(into, default)] 36 | pub style: String, 37 | #[props(into, default)] 38 | pub class: String, 39 | #[props(into)] 40 | pub slot: Option, 41 | #[props(default)] 42 | pub dialog_initial_focus: bool, 43 | } 44 | 45 | fn render<'a>(cx: Scope<'a, FabProps<'a>>) -> Element<'a> { 46 | match &cx.props.children { 47 | Some(children) => { 48 | render! { 49 | mwc-fab { 50 | label: optional_string_attr!(cx.props.label), 51 | icon: optional_string_attr!(cx.props.icon), 52 | mini: bool_attr!(cx.props.mini), 53 | reducedTouchTarget: bool_attr!(cx.props.reduced_touch_target), 54 | extended: bool_attr!(cx.props.extended), 55 | showIconAtEnd: bool_attr!(cx.props.show_icon_at_end), 56 | 57 | style: string_attr!(cx.props.style), 58 | class: string_attr!(cx.props.class), 59 | slot: optional_string_attr!(cx.props.slot), 60 | dialogInitialFocus: bool_attr!(cx.props.dialog_initial_focus), 61 | 62 | children 63 | } 64 | } 65 | } 66 | None => { 67 | render! { 68 | mwc-fab { 69 | label: optional_string_attr!(cx.props.label), 70 | icon: optional_string_attr!(cx.props.icon), 71 | mini: bool_attr!(cx.props.mini), 72 | reducedTouchTarget: bool_attr!(cx.props.reduced_touch_target), 73 | extended: bool_attr!(cx.props.extended), 74 | showIconAtEnd: bool_attr!(cx.props.show_icon_at_end), 75 | 76 | style: string_attr!(cx.props.style), 77 | class: string_attr!(cx.props.class), 78 | slot: optional_string_attr!(cx.props.slot), 79 | dialogInitialFocus: bool_attr!(cx.props.dialog_initial_focus), 80 | } 81 | } 82 | } 83 | } 84 | } 85 | 86 | component!('a, MatFab, FabProps, render, Fab, "fab"); 87 | -------------------------------------------------------------------------------- /src/form_field.rs: -------------------------------------------------------------------------------- 1 | use dioxus::prelude::*; 2 | use wasm_bindgen::prelude::*; 3 | 4 | #[wasm_bindgen(module = "/build/mwc-formfield.js")] 5 | extern "C" { 6 | #[derive(Debug)] 7 | type Formfield; 8 | 9 | #[wasm_bindgen(getter, static_method_of = Formfield)] 10 | fn _dummy_loader() -> JsValue; 11 | } 12 | 13 | loader_hack!(Formfield); 14 | 15 | /// Props for [`MatFormfield`] 16 | /// 17 | /// [MWC Documentation for properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/formfield#propertiesattributes) 18 | #[derive(Props)] 19 | pub struct FormfieldProps<'a> { 20 | pub children: Element<'a>, 21 | #[props(into)] 22 | pub label: Option, 23 | #[props(default)] 24 | pub align_end: bool, 25 | #[props(default)] 26 | pub space_between: bool, 27 | #[props(default)] 28 | pub nowrap: bool, 29 | 30 | #[props(into, default)] 31 | pub style: String, 32 | #[props(into, default)] 33 | pub class: String, 34 | #[props(into)] 35 | pub slot: Option, 36 | #[props(default)] 37 | pub dialog_initial_focus: bool, 38 | } 39 | 40 | fn render<'a>(cx: Scope<'a, FormfieldProps<'a>>) -> Element<'a> { 41 | render! { 42 | mwc-formfield { 43 | label: optional_string_attr!(cx.props.label), 44 | alignEnd: bool_attr!(cx.props.align_end), 45 | spaceBetween: bool_attr!(cx.props.space_between), 46 | nowrap: bool_attr!(cx.props.nowrap), 47 | 48 | style: string_attr!(cx.props.style), 49 | class: string_attr!(cx.props.class), 50 | slot: optional_string_attr!(cx.props.slot), 51 | dialogInitialFocus: bool_attr!(cx.props.dialog_initial_focus), 52 | 53 | &cx.props.children 54 | } 55 | } 56 | } 57 | 58 | component!('a, MatFormfield, FormfieldProps, render, Formfield, "formfield"); 59 | -------------------------------------------------------------------------------- /src/icon.rs: -------------------------------------------------------------------------------- 1 | use dioxus::prelude::*; 2 | use wasm_bindgen::prelude::*; 3 | 4 | #[wasm_bindgen(module = "/build/mwc-icon.js")] 5 | extern "C" { 6 | #[derive(Debug)] 7 | type Icon; 8 | 9 | #[wasm_bindgen(getter, static_method_of = Icon)] 10 | fn _dummy_loader() -> JsValue; 11 | } 12 | 13 | loader_hack!(Icon); 14 | 15 | /// Props for [`MatIcon`] 16 | /// 17 | /// [MWC Documentation for properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/icon#propertiesattributes) 18 | #[derive(Props)] 19 | pub struct IconProps<'a> { 20 | pub children: Element<'a>, 21 | 22 | #[props(into, default)] 23 | pub style: String, 24 | #[props(into, default)] 25 | pub class: String, 26 | #[props(into)] 27 | pub slot: Option, 28 | } 29 | 30 | fn render<'a>(cx: Scope<'a, IconProps<'a>>) -> Element<'a> { 31 | render! { 32 | mwc-icon { 33 | style: string_attr!(cx.props.style), 34 | class: string_attr!(cx.props.class), 35 | slot: optional_string_attr!(cx.props.slot), 36 | 37 | &cx.props.children 38 | } 39 | } 40 | } 41 | 42 | component!('a, MatIcon, IconProps, render, Icon, "icon"); 43 | -------------------------------------------------------------------------------- /src/icon_button.rs: -------------------------------------------------------------------------------- 1 | use dioxus::prelude::*; 2 | use wasm_bindgen::prelude::*; 3 | 4 | #[wasm_bindgen(module = "/build/mwc-icon-button.js")] 5 | extern "C" { 6 | #[derive(Debug)] 7 | type IconButton; 8 | 9 | #[wasm_bindgen(getter, static_method_of = IconButton)] 10 | fn _dummy_loader() -> JsValue; 11 | } 12 | 13 | loader_hack!(IconButton); 14 | 15 | /// Props for [`MatIconButton`] 16 | /// 17 | /// [MWC Documentation for properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/icon-button#propertiesattributes) 18 | #[derive(Props)] 19 | pub struct IconButtonProps<'a> { 20 | #[props(into)] 21 | pub label: Option, 22 | #[props(into)] 23 | pub icon: Option, 24 | #[props(default)] 25 | pub disabled: bool, 26 | #[props(default)] 27 | pub children: Element<'a>, 28 | 29 | #[props(into, default)] 30 | pub style: String, 31 | #[props(into, default)] 32 | pub class: String, 33 | #[props(into)] 34 | pub slot: Option, 35 | #[props(default)] 36 | pub dialog_initial_focus: bool, 37 | } 38 | 39 | fn render<'a>(cx: Scope<'a, IconButtonProps<'a>>) -> Element<'a> { 40 | match &cx.props.children { 41 | Some(children) => { 42 | render! { 43 | mwc-icon-button { 44 | label: optional_string_attr!(cx.props.label), 45 | icon: optional_string_attr!(cx.props.icon), 46 | disabled: bool_attr!(cx.props.disabled), 47 | 48 | style: string_attr!(cx.props.style), 49 | class: string_attr!(cx.props.class), 50 | slot: optional_string_attr!(cx.props.slot), 51 | dialogInitialFocus: bool_attr!(cx.props.dialog_initial_focus), 52 | 53 | children 54 | } 55 | } 56 | } 57 | None => { 58 | render! { 59 | mwc-icon-button { 60 | label: optional_string_attr!(cx.props.label), 61 | icon: optional_string_attr!(cx.props.icon), 62 | disabled: bool_attr!(cx.props.disabled), 63 | 64 | style: string_attr!(cx.props.style), 65 | class: string_attr!(cx.props.class), 66 | slot: optional_string_attr!(cx.props.slot), 67 | dialogInitialFocus: bool_attr!(cx.props.dialog_initial_focus), 68 | } 69 | } 70 | } 71 | } 72 | } 73 | 74 | component!('a, MatIconButton, IconButtonProps, render, IconButton, "icon-button"); 75 | -------------------------------------------------------------------------------- /src/icon_button_toggle.rs: -------------------------------------------------------------------------------- 1 | mod off_icon; 2 | mod on_icon; 3 | 4 | pub use off_icon::*; 5 | pub use on_icon::*; 6 | 7 | use crate::bool_to_option; 8 | use gloo::events::EventListener; 9 | use wasm_bindgen::prelude::*; 10 | use web_sys::Node; 11 | use yew::prelude::*; 12 | use yew::virtual_dom::AttrValue; 13 | 14 | #[wasm_bindgen(module = "/build/mwc-icon-button-toggle.js")] 15 | extern "C" { 16 | #[derive(Debug)] 17 | #[wasm_bindgen(extends = Node)] 18 | type IconButtonToggle; 19 | 20 | #[wasm_bindgen(getter, static_method_of = IconButtonToggle)] 21 | fn _dummy_loader() -> JsValue; 22 | 23 | #[wasm_bindgen(method, getter)] 24 | fn on(this: &IconButtonToggle) -> bool; 25 | } 26 | 27 | loader_hack!(IconButtonToggle); 28 | 29 | /// The `mwc-icon-button-toggle` component 30 | /// 31 | /// [MWC Documentation](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/icon-button-toggle) 32 | pub struct MatIconButtonToggle { 33 | node_ref: NodeRef, 34 | change_listener: Option, 35 | } 36 | 37 | /// Props for [`MatIconButtonToggle`] 38 | /// 39 | /// MWC Documentation: 40 | /// 41 | /// - [Properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/icon-button-toggle#propertiesattributes) 42 | /// - [Events](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/icon-button-toggle#events) 43 | #[derive(Debug, Properties, PartialEq, Clone)] 44 | pub struct IconButtonToggleProps { 45 | #[prop_or_default] 46 | pub on: bool, 47 | #[prop_or_default] 48 | pub on_icon: Option, 49 | #[prop_or_default] 50 | pub off_icon: Option, 51 | #[prop_or_default] 52 | pub label: Option, 53 | #[prop_or_default] 54 | pub disabled: bool, 55 | /// Binds to `MDCIconButtonToggle:change`. 56 | /// 57 | /// Callback's parameter is the `isOn` value passed 58 | /// 59 | /// See events docs to learn more. 60 | #[prop_or_default] 61 | pub onchange: Callback, 62 | #[prop_or_default] 63 | pub children: Children, 64 | } 65 | 66 | impl Component for MatIconButtonToggle { 67 | type Message = (); 68 | type Properties = IconButtonToggleProps; 69 | 70 | fn create(_: &Context) -> Self { 71 | IconButtonToggle::ensure_loaded(); 72 | Self { 73 | node_ref: NodeRef::default(), 74 | change_listener: None, 75 | } 76 | } 77 | 78 | fn view(&self, ctx: &Context) -> Html { 79 | let props = ctx.props(); 80 | html! { 81 | {props.children.clone()} 89 | } 90 | } 91 | 92 | fn changed(&mut self, _ctx: &Context, _old_props: &Self::Properties) -> bool { 93 | // clear event listener in case the props changed 94 | self.change_listener = None; 95 | true 96 | } 97 | 98 | fn rendered(&mut self, ctx: &Context, _first_render: bool) { 99 | let props = ctx.props(); 100 | if self.change_listener.is_none() { 101 | let element = self.node_ref.cast::().unwrap(); 102 | 103 | let callback = props.onchange.clone(); 104 | self.change_listener = Some(EventListener::new( 105 | &element.clone(), 106 | "MDCIconButtonToggle:change", 107 | move |_| callback.emit(element.on()), 108 | )); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/icon_button_toggle/off_icon.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | const SLOT: &str = "offIcon"; 4 | 5 | /// Props for [`MatOffIconButtonToggle`] 6 | #[derive(Properties, PartialEq, Clone)] 7 | pub struct OffIconButtonToggleProps { 8 | pub children: Children, 9 | } 10 | 11 | /// Defines header for [`MatIconButtonToggle`][crate::MatIconButtonToggle]. 12 | /// 13 | /// If the child passed is an element (a `VTag`), then it is modified to include 14 | /// the appropriate attributes. Otherwise, the child is wrapped in a `span` 15 | /// containing said attributes. 16 | pub struct MatOffIconButtonToggle {} 17 | 18 | impl Component for MatOffIconButtonToggle { 19 | type Message = (); 20 | type Properties = OffIconButtonToggleProps; 21 | 22 | fn create(_: &Context) -> Self { 23 | Self {} 24 | } 25 | 26 | fn view(&self, ctx: &Context) -> Html { 27 | let props = ctx.props(); 28 | let children = props 29 | .children 30 | .iter() 31 | .map(|child| match child { 32 | Html::VTag(mut vtag) => { 33 | vtag.add_attribute("slot", SLOT); 34 | Html::VTag(vtag) 35 | } 36 | _ => { 37 | html! { 38 | 39 | {child} 40 | 41 | } 42 | } 43 | }) 44 | .collect::(); 45 | 46 | html! { 47 | {children} 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/icon_button_toggle/on_icon.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | const SLOT: &str = "onIcon"; 4 | 5 | /// Props for [`MatOnIconButtonToggle`] 6 | #[derive(Properties, PartialEq, Clone)] 7 | pub struct OnIconButtonToggleProps { 8 | pub children: Children, 9 | } 10 | 11 | /// Defines header for [`MatIconButtonToggle`][crate::MatIconButtonToggle]. 12 | /// 13 | /// If the child passed is an element (a `VTag`), then it is modified to include 14 | /// the appropriate attributes. Otherwise, the child is wrapped in a `span` 15 | /// containing said attributes. 16 | pub struct MatOnIconButtonToggle {} 17 | 18 | impl Component for MatOnIconButtonToggle { 19 | type Message = (); 20 | type Properties = OnIconButtonToggleProps; 21 | 22 | fn create(_: &Context) -> Self { 23 | Self {} 24 | } 25 | 26 | fn view(&self, ctx: &Context) -> Html { 27 | let props = ctx.props(); 28 | let children = props 29 | .children 30 | .iter() 31 | .map(|child| match child { 32 | Html::VTag(mut vtag) => { 33 | vtag.add_attribute("slot", SLOT); 34 | Html::VTag(vtag) 35 | } 36 | _ => { 37 | html! { 38 | 39 | {child} 40 | 41 | } 42 | } 43 | }) 44 | .collect::(); 45 | 46 | html! { 47 | {children} 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/linear_progress.rs: -------------------------------------------------------------------------------- 1 | use crate::{bool_to_option, to_option_string}; 2 | use wasm_bindgen::prelude::*; 3 | use yew::prelude::*; 4 | 5 | #[wasm_bindgen(module = "/build/mwc-linear-progress.js")] 6 | extern "C" { 7 | #[derive(Debug)] 8 | type LinearProgress; 9 | 10 | #[wasm_bindgen(getter, static_method_of = LinearProgress)] 11 | fn _dummy_loader() -> JsValue; 12 | } 13 | 14 | loader_hack!(LinearProgress); 15 | 16 | /// Props for [`MatLinearProgress`] 17 | /// 18 | /// [MWC Documentation for properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/linear-progress#propertiesattributes) 19 | #[derive(Debug, Properties, PartialEq, Clone)] 20 | pub struct LinearProgressProps { 21 | #[prop_or_default] 22 | pub indeterminate: bool, 23 | #[prop_or_default] 24 | pub progress: f32, 25 | #[prop_or_default] 26 | pub buffer: f32, 27 | #[prop_or_default] 28 | pub reverse: bool, 29 | #[prop_or_default] 30 | pub closed: bool, 31 | } 32 | 33 | component!( 34 | MatLinearProgress, 35 | LinearProgressProps, 36 | |props: &LinearProgressProps| { 37 | html! { 38 | 45 | } 46 | }, 47 | LinearProgress, 48 | "linear-progress" 49 | ); 50 | -------------------------------------------------------------------------------- /src/list.rs: -------------------------------------------------------------------------------- 1 | mod list_item; 2 | pub use list_item::*; 3 | 4 | mod check_list_item; 5 | pub use check_list_item::*; 6 | 7 | mod radio_list_item; 8 | pub use radio_list_item::*; 9 | 10 | mod separator; 11 | pub use separator::*; 12 | 13 | mod list_index; 14 | pub use list_index::ListIndex; 15 | 16 | mod selected_detail; 17 | pub use selected_detail::{IndexDiff, SelectedDetail}; 18 | 19 | mod action_detail; 20 | pub use action_detail::ActionDetail; 21 | 22 | mod request_selected; 23 | pub use request_selected::{RequestSelectedDetail, RequestSelectedSource}; 24 | 25 | mod graphic_type; 26 | pub use graphic_type::GraphicType; 27 | 28 | use dioxus::prelude::*; 29 | use gloo::events::EventListener; 30 | use wasm_bindgen::prelude::*; 31 | use web_sys::Node; 32 | 33 | use crate::{event_into_details, StaticCallback}; 34 | 35 | #[wasm_bindgen(module = "/build/mwc-list.js")] 36 | extern "C" { 37 | #[derive(Debug)] 38 | #[wasm_bindgen(extends = Node)] 39 | type List; 40 | 41 | #[wasm_bindgen(getter, static_method_of = List)] 42 | fn _dummy_loader() -> JsValue; 43 | 44 | #[wasm_bindgen(method, getter)] 45 | fn index(this: &List) -> JsValue; 46 | 47 | #[wasm_bindgen(method)] 48 | fn toggle(this: &List, index: usize, force: bool); 49 | 50 | #[wasm_bindgen(method, js_name = getFocusedItemIndex)] 51 | fn get_focused_item_index(this: &List) -> usize; 52 | 53 | #[wasm_bindgen(method, js_name = focusItemAtIndex)] 54 | fn focus_item_at_index(this: &List, index: usize); 55 | } 56 | 57 | loader_hack!(List); 58 | 59 | /// Props for [`MatList`] 60 | /// 61 | /// MWC Documentation: 62 | /// 63 | /// - [Properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/list#mwc-list-1) 64 | /// - [Events](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/list#mwc-list-2) 65 | #[derive(Props)] 66 | pub struct ListProps<'a> { 67 | #[props(default)] 68 | pub activatable: bool, 69 | #[props(default)] 70 | pub root_tabbable: bool, 71 | #[props(default)] 72 | pub multi: bool, 73 | #[props(default)] 74 | pub wrap_focus: bool, 75 | #[props(into)] 76 | pub item_roles: Option, 77 | #[props(into)] 78 | pub inner_role: Option, 79 | #[props(default)] 80 | pub noninteractive: bool, 81 | /// Binds to `action` event on `mwc-list` 82 | #[props(into)] 83 | // the name cannot start with `on` or dioxus will expect an `EventHandler` which aren't static 84 | // and thus cannot be used here 85 | pub _onaction: Option>, 86 | /// Binds to `selected` event `mwc-list` 87 | #[props(into)] 88 | pub _onselected: Option>, 89 | // TODO: make methods callable 90 | // /// [`WeakComponentLink`] for `MatList` which provides the following methods 91 | // /// - ```toggle(&self, index: usize, force: bool)``` 92 | // /// - ```get_focused_item_index(&self) -> usize``` 93 | // /// - ```focus_item_at_index(&self, index: usize)``` 94 | // /// 95 | // /// See [`WeakComponentLink`] documentation for more information 96 | // #[props(default)] 97 | // pub list_link: WeakComponentLink, 98 | pub children: Element<'a>, 99 | 100 | #[props(into, default)] 101 | pub style: String, 102 | #[props(into, default)] 103 | pub class: String, 104 | #[props(into)] 105 | pub slot: Option, 106 | } 107 | 108 | fn render<'a>(cx: Scope<'a, ListProps<'a>>) -> Element<'a> { 109 | let id = crate::use_id(cx, "list"); 110 | let selected_listener = cx.use_hook(|| None); 111 | let action_listener = cx.use_hook(|| None); 112 | if let Some(elem) = crate::get_elem_by_id(id) { 113 | let target = elem.clone(); 114 | let list = JsValue::from(elem).dyn_into::().unwrap(); 115 | if let Some(listener) = cx.props._onselected.clone() { 116 | *selected_listener = Some(EventListener::new(&target, "selected", move |event| { 117 | let val = SelectedDetail::from(event_into_details(event)); 118 | listener.call(val) 119 | })); 120 | } 121 | if let Some(listener) = cx.props._onaction.clone() { 122 | *action_listener = Some(EventListener::new(&target, "action", move |_| { 123 | let val: JsValue = list.index(); 124 | let index = ListIndex::from(val); 125 | listener.call(index) 126 | })); 127 | } 128 | } 129 | 130 | render! { 131 | mwc-list { 132 | id: id, 133 | 134 | activatable: bool_attr!(cx.props.activatable), 135 | rootTabbable: bool_attr!(cx.props.root_tabbable), 136 | multi: bool_attr!(cx.props.multi), 137 | wrapFocus: bool_attr!(cx.props.wrap_focus), 138 | itemRoles: optional_string_attr!(cx.props.item_roles), 139 | innerRole: optional_string_attr!(cx.props.inner_role), 140 | noninteractive: bool_attr!(cx.props.noninteractive), 141 | 142 | style: string_attr!(cx.props.style), 143 | class: string_attr!(cx.props.class), 144 | slot: optional_string_attr!(cx.props.slot), 145 | 146 | &cx.props.children 147 | } 148 | } 149 | } 150 | 151 | component!('a, MatList, ListProps, render, List, "list"); 152 | -------------------------------------------------------------------------------- /src/list/action_detail.rs: -------------------------------------------------------------------------------- 1 | use crate::list::ListIndex; 2 | use js_sys::Object; 3 | use wasm_bindgen::prelude::*; 4 | use wasm_bindgen::JsCast; 5 | 6 | /// The `ActionDetail` type 7 | /// 8 | /// [MWC Documentation](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/list#mwc-list-2) 9 | #[derive(Debug)] 10 | pub struct ActionDetail { 11 | #[allow(dead_code)] 12 | index: ListIndex, 13 | } 14 | 15 | impl From for ActionDetail { 16 | fn from(value: JsValue) -> Self { 17 | let detail = value.unchecked_into::(); 18 | let index = ListIndex::from(detail.index()); 19 | Self { index } 20 | } 21 | } 22 | 23 | #[wasm_bindgen] 24 | extern "C" { 25 | #[derive(Debug)] 26 | #[wasm_bindgen(extends = Object)] 27 | type ActionDetailJs; 28 | 29 | #[wasm_bindgen(method, getter)] 30 | pub fn index(this: &ActionDetailJs) -> JsValue; 31 | } 32 | -------------------------------------------------------------------------------- /src/list/check_list_item.rs: -------------------------------------------------------------------------------- 1 | use crate::{list::request_selected::make_request_selected_listener, StaticCallback}; 2 | use dioxus::prelude::*; 3 | use wasm_bindgen::prelude::*; 4 | 5 | use super::{GraphicType, RequestSelectedDetail}; 6 | 7 | #[wasm_bindgen(module = "/build/mwc-check-list-item.js")] 8 | extern "C" { 9 | #[derive(Debug)] 10 | type CheckListItem; 11 | 12 | #[wasm_bindgen(getter, static_method_of = CheckListItem)] 13 | fn _dummy_loader() -> JsValue; 14 | 15 | #[wasm_bindgen(method, setter)] 16 | fn set_selected(this: &CheckListItem, value: bool); 17 | } 18 | 19 | loader_hack!(CheckListItem); 20 | 21 | /// Props for [`MatCheckListItem`] 22 | /// 23 | /// MWC Documentation for [properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/list#mwc-check-list-item) 24 | /// and [events](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/list#mwc-check-list-item-1) 25 | #[derive(Props)] 26 | pub struct CheckListItemProps<'a> { 27 | #[props(default)] 28 | pub left: bool, 29 | #[props(default = GraphicType::Control)] 30 | pub graphic: GraphicType, 31 | #[props(default)] 32 | pub disabled: bool, 33 | #[props(into)] 34 | pub _on_request_selected: Option>, 35 | #[props(default)] 36 | pub initially_selected: bool, 37 | pub children: Element<'a>, 38 | 39 | #[props(into, default)] 40 | pub style: String, 41 | #[props(into, default)] 42 | pub class: String, 43 | } 44 | 45 | fn render<'a>(cx: Scope<'a, CheckListItemProps<'a>>) -> Element<'a> { 46 | let id = crate::use_id(cx, "check-list-item"); 47 | let request_selected_listener = cx.use_hook(|| None); 48 | if let Some(elem) = crate::get_elem_by_id(id) { 49 | let target = elem; 50 | if let Some(listener) = cx.props._on_request_selected.clone() { 51 | *request_selected_listener = Some(make_request_selected_listener(&target, listener)); 52 | } 53 | } 54 | 55 | render! { 56 | mwc-check-list-item { 57 | onmounted: move |_| { 58 | if let Some(elem) = crate::get_elem_by_id(id) { 59 | let item = JsValue::from(elem).dyn_into::().unwrap(); 60 | item.set_selected(cx.props.initially_selected); 61 | } 62 | }, 63 | 64 | id: id, 65 | 66 | left: bool_attr!(cx.props.left), 67 | graphic: cx.props.graphic.as_str(), 68 | disabled: bool_attr!(cx.props.disabled), 69 | 70 | style: string_attr!(cx.props.style), 71 | class: string_attr!(cx.props.class), 72 | 73 | &cx.props.children 74 | } 75 | } 76 | } 77 | 78 | component!('a, MatCheckListItem, CheckListItemProps, render, CheckListItem, "check-list-item"); 79 | -------------------------------------------------------------------------------- /src/list/graphic_type.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | /// Equivalent to typescript type 4 | /// `'avatar'|'icon'|'medium'|'large'|'control'|null` 5 | /// 6 | /// See `GraphicType` [here](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/list#mwc-list-item-1) 7 | #[derive(Clone, PartialEq, Debug)] 8 | pub enum GraphicType { 9 | Avatar, 10 | Icon, 11 | Medium, 12 | Large, 13 | Control, 14 | Null, 15 | } 16 | 17 | impl GraphicType { 18 | pub fn as_str(&self) -> &'static str { 19 | use GraphicType::*; 20 | match self { 21 | Avatar => "avatar", 22 | Icon => "icon", 23 | Medium => "medium", 24 | Large => "large", 25 | Control => "control", 26 | Null => "null", 27 | } 28 | } 29 | } 30 | 31 | impl fmt::Display for GraphicType { 32 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 33 | write!(f, "{}", self.as_str()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/list/list_index.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use wasm_bindgen::{JsCast, JsValue}; 3 | 4 | /// The `MWCListIndex` type 5 | /// 6 | /// [MWC Documentation](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/list#mwc-list-1) 7 | #[derive(Debug, Clone)] 8 | pub enum ListIndex { 9 | /// Provided when `multi` prop is set to `false` (default) on the component 10 | /// 11 | /// `None` denotes value of `-1` 12 | Single(Option), 13 | /// Provided when `multi` prop is set to `true` on the component 14 | Multi(HashSet), 15 | } 16 | 17 | impl ListIndex { 18 | pub fn unwrap_single(self) -> Option { 19 | match self { 20 | ListIndex::Single(val) => val, 21 | ListIndex::Multi(_) => panic!("called `unwrap_single` on {self:?}"), 22 | } 23 | } 24 | 25 | pub fn unwrap_multi(self) -> HashSet { 26 | match self { 27 | ListIndex::Multi(val) => val, 28 | ListIndex::Single(_) => panic!("called `unwrap_multi` on {self:?}"), 29 | } 30 | } 31 | } 32 | 33 | impl From for ListIndex { 34 | fn from(val: JsValue) -> Self { 35 | if let Ok(set) = val.clone().dyn_into::() { 36 | let indices = set 37 | .values() 38 | .into_iter() 39 | .filter_map(|item| item.ok()) 40 | .filter_map(|value| value.as_f64()) 41 | .map(|num| num as usize) 42 | .collect(); 43 | ListIndex::Multi(indices) 44 | } else if let Some(value) = val.as_f64() { 45 | ListIndex::Single(if value != -1.0 { 46 | Some(value as usize) 47 | } else { 48 | None 49 | }) 50 | } else { 51 | panic!("This should never happen") 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/list/list_item.rs: -------------------------------------------------------------------------------- 1 | use crate::{list::request_selected::make_request_selected_listener, StaticCallback}; 2 | use dioxus::prelude::*; 3 | use wasm_bindgen::prelude::*; 4 | 5 | use super::{GraphicType, RequestSelectedDetail}; 6 | 7 | #[wasm_bindgen(module = "/build/mwc-list-item.js")] 8 | extern "C" { 9 | #[derive(Debug)] 10 | type ListItem; 11 | 12 | #[wasm_bindgen(getter, static_method_of = ListItem)] 13 | fn _dummy_loader() -> JsValue; 14 | 15 | #[wasm_bindgen(method, setter)] 16 | fn set_activated(this: &ListItem, value: bool); 17 | 18 | #[wasm_bindgen(method, setter)] 19 | fn set_selected(this: &ListItem, value: bool); 20 | } 21 | 22 | loader_hack!(ListItem); 23 | 24 | /// Props for [`MatListItem`] 25 | /// 26 | /// MWC Documentation [properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/list#mwc-list-item-1) 27 | /// and [events](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/list#mwc-list-item-2) 28 | #[derive(Props)] 29 | pub struct ListItemProps<'a> { 30 | #[props(into)] 31 | pub value: Option, 32 | #[props(default)] 33 | pub group: bool, 34 | #[props(default = -1)] 35 | pub tabindex: i32, 36 | #[props(default)] 37 | pub disabled: bool, 38 | #[props(default)] 39 | pub twoline: bool, 40 | #[props(default)] 41 | pub initially_activated: bool, 42 | #[props(default = GraphicType::Null)] 43 | pub graphic: GraphicType, 44 | #[props(default)] 45 | pub multiple_graphics: bool, 46 | #[props(default)] 47 | pub has_meta: bool, 48 | #[props(default)] 49 | pub noninteractive: bool, 50 | #[props(default)] 51 | pub initially_selected: bool, 52 | /// Binds to `request-selected` event on `mwc-list-item`. 53 | #[props(into)] 54 | pub _on_request_selected: Option>, 55 | pub children: Element<'a>, 56 | 57 | #[props(into, default)] 58 | pub style: String, 59 | #[props(into, default)] 60 | pub class: String, 61 | } 62 | 63 | fn render<'a>(cx: Scope<'a, ListItemProps<'a>>) -> Element<'a> { 64 | let id = crate::use_id(cx, "list-item"); 65 | let request_selected_listener = cx.use_hook(|| None); 66 | if let Some(elem) = crate::get_elem_by_id(id) { 67 | let target = elem; 68 | if let Some(listener) = cx.props._on_request_selected.clone() { 69 | *request_selected_listener = Some(make_request_selected_listener(&target, listener)); 70 | } 71 | } 72 | 73 | render! { 74 | mwc-list-item { 75 | onmounted: move |_| { 76 | if let Some(elem) = crate::get_elem_by_id(id) { 77 | let item = JsValue::from(elem).dyn_into::().unwrap(); 78 | item.set_activated(cx.props.initially_activated); 79 | item.set_selected(cx.props.initially_selected); 80 | } 81 | }, 82 | 83 | id: id, 84 | 85 | value: optional_string_attr!(cx.props.value), 86 | group: bool_attr!(cx.props.group), 87 | tabindex: cx.props.tabindex as i64, 88 | disabled: bool_attr!(cx.props.disabled), 89 | twoline: bool_attr!(cx.props.twoline), 90 | graphic: cx.props.graphic.as_str(), 91 | multipleGraphics: bool_attr!(cx.props.multiple_graphics), 92 | hasMeta: bool_attr!(cx.props.has_meta), 93 | noninteractive: bool_attr!(cx.props.noninteractive), 94 | 95 | style: string_attr!(cx.props.style), 96 | class: string_attr!(cx.props.class), 97 | 98 | &cx.props.children 99 | } 100 | } 101 | } 102 | 103 | component!('a, MatListItem, ListItemProps, render, ListItem, "list-item"); 104 | -------------------------------------------------------------------------------- /src/list/radio_list_item.rs: -------------------------------------------------------------------------------- 1 | use crate::{list::request_selected::make_request_selected_listener, StaticCallback}; 2 | use dioxus::prelude::*; 3 | use wasm_bindgen::prelude::*; 4 | 5 | use super::{GraphicType, RequestSelectedDetail}; 6 | 7 | #[wasm_bindgen(module = "/build/mwc-radio-list-item.js")] 8 | extern "C" { 9 | #[derive(Debug)] 10 | type RadioListItem; 11 | 12 | #[wasm_bindgen(getter, static_method_of = RadioListItem)] 13 | fn _dummy_loader() -> JsValue; 14 | 15 | #[wasm_bindgen(method, setter)] 16 | fn set_selected(this: &RadioListItem, value: bool); 17 | } 18 | 19 | loader_hack!(RadioListItem); 20 | 21 | /// Props for [`MatRadioListItem`] 22 | /// 23 | /// MWC Documentation [properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/list#mwc-radio-list-item-1) 24 | /// and [events](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/list#mwc-radio-list-item-2) 25 | #[derive(Props)] 26 | pub struct RadioListItemProps<'a> { 27 | #[props(default)] 28 | pub left: bool, 29 | #[props(into)] 30 | pub group: Option, 31 | #[props(default = GraphicType::Control)] 32 | pub graphic: GraphicType, 33 | /// Binds to `request-selected` event on `mwc-list-item`. 34 | #[props(into)] 35 | pub _on_request_selected: Option>, 36 | #[props(default)] 37 | pub initially_selected: bool, 38 | pub children: Element<'a>, 39 | 40 | #[props(into, default)] 41 | pub style: String, 42 | #[props(into, default)] 43 | pub class: String, 44 | } 45 | 46 | fn render<'a>(cx: Scope<'a, RadioListItemProps<'a>>) -> Element<'a> { 47 | let id = crate::use_id(cx, "radio-list-item"); 48 | let request_selected_listener = cx.use_hook(|| None); 49 | if let Some(elem) = crate::get_elem_by_id(id) { 50 | let target = elem; 51 | if let Some(listener) = cx.props._on_request_selected.clone() { 52 | *request_selected_listener = Some(make_request_selected_listener(&target, listener)); 53 | } 54 | } 55 | 56 | render! { 57 | mwc-radio-list-item { 58 | onmounted: move |_| { 59 | if let Some(elem) = crate::get_elem_by_id(id) { 60 | let item = JsValue::from(elem).dyn_into::().unwrap(); 61 | item.set_selected(cx.props.initially_selected); 62 | } 63 | }, 64 | 65 | id: id, 66 | 67 | left: bool_attr!(cx.props.left), 68 | graphic: cx.props.graphic.as_str(), 69 | group: optional_string_attr!(cx.props.group), 70 | 71 | style: string_attr!(cx.props.style), 72 | class: string_attr!(cx.props.class), 73 | 74 | &cx.props.children 75 | } 76 | } 77 | } 78 | 79 | component!('a, MatRadioListItem, RadioListItemProps, render, RadioListItem, "radio-list-item"); 80 | -------------------------------------------------------------------------------- /src/list/request_selected.rs: -------------------------------------------------------------------------------- 1 | use crate::{event_details_into, StaticCallback}; 2 | use gloo::events::EventListener; 3 | use js_sys::Object; 4 | use wasm_bindgen::prelude::*; 5 | 6 | /// Type for [`RequestSelectedDetail::source`] 7 | #[derive(Debug, Clone)] 8 | pub enum RequestSelectedSource { 9 | Interaction, 10 | Property, 11 | } 12 | 13 | /// The `RequestSelectedDetail` type 14 | /// 15 | /// [MWC Documentation](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/list#mwc-list-item-2) 16 | #[derive(Debug, Clone)] 17 | pub struct RequestSelectedDetail { 18 | pub selected: bool, 19 | pub source: RequestSelectedSource, 20 | } 21 | 22 | #[wasm_bindgen] 23 | extern "C" { 24 | #[derive(Debug)] 25 | #[wasm_bindgen(extends = Object)] 26 | type RequestSelectedDetailJS; 27 | 28 | #[wasm_bindgen(method, getter)] 29 | fn selected(this: &RequestSelectedDetailJS) -> bool; 30 | 31 | #[wasm_bindgen(method, getter)] 32 | fn source(this: &RequestSelectedDetailJS) -> String; 33 | } 34 | 35 | pub fn make_request_selected_listener( 36 | target: &web_sys::Element, 37 | callback: StaticCallback, 38 | ) -> EventListener { 39 | EventListener::new(target, "request-selected", move |event| { 40 | let selected_detail = event_details_into::(event); 41 | let selected_detail = RequestSelectedDetail { 42 | selected: selected_detail.selected(), 43 | source: match selected_detail.source().as_str() { 44 | "interaction" => RequestSelectedSource::Interaction, 45 | "property" => RequestSelectedSource::Property, 46 | val => { 47 | panic!( 48 | "invalid `source` value {} received. This should never happen", 49 | val 50 | ) 51 | } 52 | }, 53 | }; 54 | callback.call(selected_detail); 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /src/list/selected_detail.rs: -------------------------------------------------------------------------------- 1 | use crate::list::ListIndex; 2 | use js_sys::Object; 3 | use wasm_bindgen::prelude::*; 4 | use wasm_bindgen::JsCast; 5 | 6 | /// The `RequestSelectedDetail` type 7 | /// 8 | /// [MWC Documentation](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/list#mwc-list-2) 9 | #[derive(Debug, Clone)] 10 | pub struct SelectedDetail { 11 | pub index: ListIndex, 12 | pub diff: Option, 13 | } 14 | 15 | /// Type for [`SelectedDetail::diff`] 16 | /// 17 | /// See `**` [here on MWC documentation](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/list#mwc-list-2). 18 | #[derive(Debug, Clone)] 19 | pub struct IndexDiff { 20 | pub added: Vec, 21 | pub removed: Vec, 22 | } 23 | 24 | impl From for SelectedDetail { 25 | fn from(value: JsValue) -> Self { 26 | let detail = value.unchecked_into::(); 27 | let index = ListIndex::from(detail.index()); 28 | 29 | let diff = if detail.diff().is_undefined() { 30 | None 31 | } else { 32 | let diff = detail.diff(); 33 | Some(IndexDiff { 34 | added: diff.added(), 35 | removed: diff.removed(), 36 | }) 37 | }; 38 | Self { index, diff } 39 | } 40 | } 41 | 42 | #[wasm_bindgen] 43 | extern "C" { 44 | #[derive(Debug)] 45 | #[wasm_bindgen(extends = Object)] 46 | type SelectedDetailJS; 47 | 48 | #[wasm_bindgen(method, getter)] 49 | pub fn index(this: &SelectedDetailJS) -> JsValue; 50 | 51 | #[wasm_bindgen(method, getter)] 52 | pub fn diff(this: &SelectedDetailJS) -> IndexDiffJS; 53 | 54 | #[derive(Debug)] 55 | #[wasm_bindgen(extends = Object)] 56 | type IndexDiffJS; 57 | 58 | #[wasm_bindgen(method, getter)] 59 | pub fn added(this: &IndexDiffJS) -> Vec; 60 | 61 | #[wasm_bindgen(method, getter)] 62 | pub fn removed(this: &IndexDiffJS) -> Vec; 63 | } 64 | -------------------------------------------------------------------------------- /src/list/separator.rs: -------------------------------------------------------------------------------- 1 | use dioxus::prelude::*; 2 | 3 | /// Docs [here](https://github.com/material-components/material-web/tree/mwc/packages/list#dividers). 4 | #[derive(Props, PartialEq)] 5 | pub struct ListSeparatorProps { 6 | #[props(default)] 7 | pub padded: bool, 8 | #[props(default)] 9 | pub inset: bool, 10 | } 11 | 12 | #[allow(non_snake_case)] 13 | pub fn MatListSeparator(cx: Scope) -> Element { 14 | render! { 15 | li { 16 | "divider": true, 17 | role: "separator", 18 | "padded": bool_attr!(cx.props.padded), 19 | "inset": bool_attr!(cx.props.inset), 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/menu/models.rs: -------------------------------------------------------------------------------- 1 | /// The `Corner` type 2 | /// 3 | /// [MWC Documentation](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/menu#propertiesattributes) 4 | #[derive(Clone, PartialEq)] 5 | pub enum Corner { 6 | TopLeft, 7 | TopRight, 8 | BottomLeft, 9 | BottomRight, 10 | TopStart, 11 | TopEnd, 12 | BottomStart, 13 | BottomEnd, 14 | } 15 | 16 | impl ToString for Corner { 17 | fn to_string(&self) -> String { 18 | use Corner::*; 19 | match self { 20 | TopLeft => "TOP_LEFT", 21 | TopRight => "TOP_RIGHT", 22 | BottomLeft => "BOTTOM_LEFT", 23 | BottomRight => "BOTTOM_RIGHT", 24 | TopStart => "TOP_START", 25 | TopEnd => "TOP_END ", 26 | BottomStart => "BOTTOM_START", 27 | BottomEnd => "BOTTOM_END", 28 | } 29 | .to_string() 30 | } 31 | } 32 | 33 | /// The `MenuCorner` type 34 | /// 35 | /// [MWC Documentation](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/menu#propertiesattributes) 36 | #[derive(Clone, PartialEq)] 37 | pub enum MenuCorner { 38 | Start, 39 | End, 40 | } 41 | 42 | impl ToString for MenuCorner { 43 | fn to_string(&self) -> String { 44 | use MenuCorner::*; 45 | match self { 46 | Start => "START", 47 | End => "END", 48 | } 49 | .to_string() 50 | } 51 | } 52 | 53 | /// The `DefaultFocusState` type 54 | /// 55 | /// [MWC Documentation](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/menu#propertiesattributes) 56 | #[derive(Clone, PartialEq)] 57 | pub enum DefaultFocusState { 58 | None, 59 | ListRoot, 60 | FirstItem, 61 | LastItem, 62 | } 63 | 64 | impl ToString for DefaultFocusState { 65 | fn to_string(&self) -> String { 66 | use DefaultFocusState::*; 67 | match self { 68 | None => "NONE", 69 | ListRoot => "LIST_ROOT", 70 | FirstItem => "FIRST_ITEM", 71 | LastItem => "LAST_ITEM", 72 | } 73 | .to_string() 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/radio.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use dioxus::prelude::*; 4 | use gloo::events::EventListener; 5 | use wasm_bindgen::prelude::*; 6 | use web_sys::Node; 7 | 8 | use crate::utils::StaticCallback; 9 | 10 | #[wasm_bindgen(module = "/build/mwc-radio.js")] 11 | extern "C" { 12 | #[derive(Debug)] 13 | #[wasm_bindgen(extends = Node)] 14 | type Radio; 15 | 16 | #[wasm_bindgen(getter, static_method_of = Radio)] 17 | fn _dummy_loader() -> JsValue; 18 | 19 | #[wasm_bindgen(method, getter)] 20 | fn checked(this: &Radio) -> bool; 21 | 22 | #[wasm_bindgen(method, setter)] 23 | fn set_checked(this: &Radio, value: bool); 24 | } 25 | 26 | loader_hack!(Radio); 27 | 28 | /// Props for [`MatRadio`] 29 | /// 30 | /// MWC Documentation: 31 | /// 32 | /// - [Properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/radio#propertiesattributes) 33 | /// - [Events](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/radio#events) 34 | #[derive(Props)] 35 | pub struct RadioProps<'a> { 36 | #[props(default)] 37 | pub checked: bool, 38 | #[props(default)] 39 | pub disabled: bool, 40 | #[props(into)] 41 | pub name: Option, 42 | #[props(into)] 43 | pub value: Option, 44 | #[props(default)] 45 | pub global: bool, 46 | #[props(default)] 47 | pub reduced_touch_target: bool, 48 | /// Binds to `change`. 49 | /// 50 | /// Callback's parameter of type denotes if the radio is checked or not. 51 | /// 52 | /// See events docs to learn more. 53 | #[props(into)] 54 | // the name cannot start with `on` or dioxus will expect an `EventHandler` which aren't static 55 | // and thus cannot be used here 56 | pub _onchange: Option>, 57 | _lifetime: Option>, 58 | 59 | #[props(into, default)] 60 | pub style: String, 61 | #[props(into, default)] 62 | pub class: String, 63 | #[props(into)] 64 | pub slot: Option, 65 | #[props(default)] 66 | pub dialog_initial_focus: bool, 67 | } 68 | 69 | fn render<'a>(cx: Scope<'a, RadioProps<'a>>) -> Element<'a> { 70 | let id = crate::use_id(cx, "radio"); 71 | let change_listener = cx.use_hook(|| None); 72 | if let Some(elem) = crate::get_elem_by_id(id) { 73 | let target = elem.clone(); 74 | let radio = JsValue::from(elem).dyn_into::().unwrap(); 75 | radio.set_checked(cx.props.checked); 76 | if let Some(listener) = cx.props._onchange.clone() { 77 | *change_listener = Some(EventListener::new(&target, "change", move |_| { 78 | listener.call(radio.checked()) 79 | })); 80 | } 81 | } 82 | render! { 83 | mwc-radio { 84 | id: id, 85 | 86 | disabled: bool_attr!(cx.props.disabled), 87 | name: optional_string_attr!(cx.props.name), 88 | value: optional_string_attr!(cx.props.value), 89 | global: bool_attr!(cx.props.global), 90 | reducedTouchTarget: bool_attr!(cx.props.reduced_touch_target), 91 | 92 | style: string_attr!(cx.props.style), 93 | class: string_attr!(cx.props.class), 94 | slot: optional_string_attr!(cx.props.slot), 95 | dialogInitialFocus: bool_attr!(cx.props.dialog_initial_focus), 96 | } 97 | } 98 | } 99 | 100 | component!('a, MatRadio, RadioProps, render, Radio, "radio"); 101 | -------------------------------------------------------------------------------- /src/slider.rs: -------------------------------------------------------------------------------- 1 | use crate::{bool_to_option, to_option_string}; 2 | use gloo::events::EventListener; 3 | use wasm_bindgen::prelude::*; 4 | use wasm_bindgen::JsCast; 5 | use web_sys::{CustomEvent, Element}; 6 | use yew::prelude::*; 7 | 8 | #[wasm_bindgen(module = "/build/mwc-slider.js")] 9 | extern "C" { 10 | #[derive(Debug)] 11 | type Slider; 12 | 13 | // This needs to be added to each component 14 | #[wasm_bindgen(getter, static_method_of = Slider)] 15 | fn _dummy_loader() -> JsValue; 16 | } 17 | 18 | // call the macro with the type 19 | loader_hack!(Slider); 20 | 21 | /// The `mwc-snackbar` component 22 | /// 23 | /// [MWC Documentation](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/slider) 24 | pub struct MatSlider { 25 | node_ref: NodeRef, 26 | input_listener: Option, 27 | change_listener: Option, 28 | } 29 | 30 | /// Props for [`MatSlider`] 31 | /// 32 | /// MWC Documentation: 33 | /// 34 | /// - [Properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/slider#propertiesattributes) 35 | /// - [Events](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/slider#events) 36 | #[derive(Debug, Properties, PartialEq, Clone)] 37 | pub struct SliderProps { 38 | #[prop_or(0)] 39 | pub value: u32, 40 | #[prop_or(0)] 41 | pub min: u32, 42 | #[prop_or(100)] 43 | pub max: u32, 44 | #[prop_or(1)] 45 | pub step: u32, 46 | #[prop_or(false)] 47 | pub pin: bool, 48 | #[prop_or(false)] 49 | pub markers: bool, 50 | /// Binds to input on `mwc-slider` 51 | /// Type passed to callback is `CustomEvent` because `Slider` is 52 | /// undocumented See: 53 | #[prop_or_default] 54 | pub oninput: Callback, 55 | /// Binds to change on `mwc-slider` 56 | /// Type passed to callback is `CustomEvent` because `Slider` is 57 | /// undocumented See: 58 | #[prop_or_default] 59 | pub onchange: Callback, 60 | } 61 | 62 | impl Component for MatSlider { 63 | type Message = (); 64 | type Properties = SliderProps; 65 | 66 | fn create(_: &Context) -> Self { 67 | Slider::ensure_loaded(); 68 | Self { 69 | node_ref: NodeRef::default(), 70 | input_listener: None, 71 | change_listener: None, 72 | } 73 | } 74 | 75 | fn view(&self, ctx: &Context) -> Html { 76 | let props = ctx.props(); 77 | html! { 78 | 87 | } 88 | } 89 | 90 | fn rendered(&mut self, ctx: &Context, _first_render: bool) { 91 | let props = ctx.props(); 92 | let element = self.node_ref.cast::().unwrap(); 93 | if self.input_listener.is_none() { 94 | let oninput = props.oninput.clone(); 95 | self.input_listener = Some(EventListener::new(&element, "input", move |event| { 96 | oninput.emit(JsValue::from(event).unchecked_into::()) 97 | })); 98 | }; 99 | 100 | if self.change_listener.is_none() { 101 | let onchange = props.onchange.clone(); 102 | self.change_listener = Some(EventListener::new(&element, "change", move |event| { 103 | onchange.emit(JsValue::from(event).unchecked_into::()) 104 | })); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/switch.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use dioxus::prelude::*; 4 | use gloo::events::EventListener; 5 | use wasm_bindgen::prelude::*; 6 | 7 | use crate::StaticCallback; 8 | 9 | #[wasm_bindgen(module = "/build/mwc-switch.js")] 10 | extern "C" { 11 | #[derive(Debug)] 12 | type Switch; 13 | 14 | #[wasm_bindgen(getter, static_method_of = Switch)] 15 | fn _dummy_loader() -> JsValue; 16 | } 17 | 18 | loader_hack!(Switch); 19 | 20 | /// Props for [`MatSwitch`] 21 | /// 22 | /// MWC Documentation: 23 | /// 24 | /// - [Properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/switch#propertiesattributes) 25 | /// - [Events](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/switch#events) 26 | #[derive(Props)] 27 | pub struct SwitchProps<'a> { 28 | #[props(default)] 29 | pub selected: bool, 30 | #[props(default)] 31 | pub disabled: bool, 32 | #[props(into)] 33 | pub name: Option, 34 | #[props(into)] 35 | pub value: Option, 36 | #[props(into)] 37 | // the name cannot start with `on` or dioxus will expect an `EventHandler` which aren't static 38 | // and thus cannot be used here 39 | pub _onclick: Option>, 40 | _lifetime: Option>, 41 | 42 | #[props(into, default)] 43 | pub style: String, 44 | #[props(into, default)] 45 | pub class: String, 46 | #[props(into)] 47 | pub slot: Option, 48 | #[props(default)] 49 | pub dialog_initial_focus: bool, 50 | } 51 | 52 | fn render<'a>(cx: Scope<'a, SwitchProps<'a>>) -> Element<'a> { 53 | let id = crate::use_id(cx, "switch"); 54 | let click_listener = cx.use_hook(|| None); 55 | if let Some(elem) = crate::get_elem_by_id(id) { 56 | let target = elem; 57 | if let Some(listener) = cx.props._onclick.clone() { 58 | *click_listener = Some(EventListener::new(&target, "click", move |_| { 59 | listener.call(()) 60 | })); 61 | } 62 | } 63 | 64 | render! { 65 | mwc-switch { 66 | id: id, 67 | 68 | selected: bool_attr!(cx.props.selected), 69 | disabled: bool_attr!(cx.props.disabled), 70 | name: optional_string_attr!(cx.props.name), 71 | value: optional_string_attr!(cx.props.value), 72 | 73 | style: string_attr!(cx.props.style), 74 | class: string_attr!(cx.props.class), 75 | slot: optional_string_attr!(cx.props.slot), 76 | dialogInitialFocus: bool_attr!(cx.props.dialog_initial_focus), 77 | } 78 | } 79 | } 80 | 81 | component!('a, MatSwitch, SwitchProps, render, Switch, "switch"); 82 | -------------------------------------------------------------------------------- /src/tabs/mod.rs: -------------------------------------------------------------------------------- 1 | mod tab; 2 | pub use tab::*; 3 | 4 | mod tab_bar; 5 | pub use tab_bar::*; 6 | 7 | mod tab_icon; 8 | pub use tab_icon::*; 9 | -------------------------------------------------------------------------------- /src/tabs/tab.rs: -------------------------------------------------------------------------------- 1 | use crate::{bool_to_option, event_details_into}; 2 | use gloo::events::EventListener; 3 | use js_sys::Object; 4 | use wasm_bindgen::prelude::*; 5 | use web_sys::Element; 6 | use yew::prelude::*; 7 | use yew::virtual_dom::AttrValue; 8 | 9 | #[wasm_bindgen(module = "/build/mwc-tab.js")] 10 | extern "C" { 11 | #[derive(Debug)] 12 | type Tab; 13 | 14 | #[wasm_bindgen(getter, static_method_of = Tab)] 15 | fn _dummy_loader() -> JsValue; 16 | } 17 | 18 | loader_hack!(Tab); 19 | 20 | /// The `mwc-tab` component 21 | /// 22 | /// [MWC Documentation](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/tab) 23 | pub struct MatTab { 24 | node_ref: NodeRef, 25 | interacted_listener: Option, 26 | } 27 | 28 | /// Props for `MatTab` 29 | /// 30 | /// MWC Documentation [properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/tab#propertiesattributes) 31 | /// and [events](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/tab#events) 32 | #[derive(Debug, Properties, PartialEq, Clone)] 33 | pub struct TabProps { 34 | #[prop_or_default] 35 | pub label: Option, 36 | #[prop_or_default] 37 | pub icon: Option, 38 | #[prop_or_default] 39 | pub has_image_icon: bool, 40 | #[prop_or_default] 41 | pub indicator_icon: Option, 42 | #[prop_or_default] 43 | pub is_fading_indicator: bool, 44 | #[prop_or_default] 45 | pub min_width: bool, 46 | #[prop_or_default] 47 | pub is_min_width_indicator: bool, 48 | #[prop_or_default] 49 | pub stacked: bool, 50 | /// Binds to `MDCTab:interacted` event on `mwc-tab` 51 | /// 52 | /// See events docs to learn more. 53 | #[prop_or_default] 54 | pub oninteracted: Callback, 55 | #[prop_or_default] 56 | pub children: Children, 57 | } 58 | 59 | impl Component for MatTab { 60 | type Message = (); 61 | type Properties = TabProps; 62 | 63 | fn create(_: &Context) -> Self { 64 | Tab::ensure_loaded(); 65 | Self { 66 | node_ref: NodeRef::default(), 67 | interacted_listener: None, 68 | } 69 | } 70 | 71 | fn view(&self, ctx: &Context) -> Html { 72 | let props = ctx.props(); 73 | html! { 74 | {props.children.clone()} 85 | } 86 | } 87 | 88 | fn rendered(&mut self, ctx: &Context, _first_render: bool) { 89 | let props = ctx.props(); 90 | if self.interacted_listener.is_none() { 91 | let element = self.node_ref.cast::().unwrap(); 92 | 93 | let on_interacted = props.oninteracted.clone(); 94 | self.interacted_listener = Some(EventListener::new( 95 | &element, 96 | "MDCTab:interacted", 97 | move |event| { 98 | let detail = event_details_into::(event); 99 | on_interacted.emit(detail.tab_id()); 100 | }, 101 | )); 102 | } 103 | } 104 | } 105 | 106 | #[wasm_bindgen] 107 | extern "C" { 108 | #[derive(Debug)] 109 | #[wasm_bindgen(extends = Object)] 110 | type InteractedDetailJS; 111 | 112 | #[wasm_bindgen(method, getter, js_name=tabId)] 113 | fn tab_id(this: &InteractedDetailJS) -> String; 114 | } 115 | -------------------------------------------------------------------------------- /src/tabs/tab_bar.rs: -------------------------------------------------------------------------------- 1 | use crate::{event_details_into, to_option_string}; 2 | use gloo::events::EventListener; 3 | use js_sys::Object; 4 | use wasm_bindgen::prelude::*; 5 | use web_sys::Element; 6 | use yew::prelude::*; 7 | 8 | #[wasm_bindgen(module = "/build/mwc-tab-bar.js")] 9 | extern "C" { 10 | #[derive(Debug)] 11 | type TabBar; 12 | 13 | #[wasm_bindgen(getter, static_method_of = TabBar)] 14 | fn _dummy_loader() -> JsValue; 15 | } 16 | 17 | loader_hack!(TabBar); 18 | 19 | /// The `mwc-tab-bar` component 20 | /// 21 | /// [MWC Documentation](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/tab-bar) 22 | pub struct MatTabBar { 23 | node_ref: NodeRef, 24 | activated_listener: Option, 25 | } 26 | 27 | /// Props for `MatTabBar`. 28 | /// 29 | /// MWC Documentation [properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/tab-bar#propertiesattributes) 30 | /// and [events](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/tab-bar#events) 31 | #[derive(Debug, Properties, PartialEq, Clone)] 32 | pub struct TabBarProps { 33 | #[prop_or_default] 34 | pub active_index: u32, 35 | /// Binds to `MDCTabBar:activated` event on `mwc-tab` 36 | /// 37 | /// See events docs to learn more. 38 | #[prop_or_default] 39 | pub onactivated: Callback, 40 | #[prop_or_default] 41 | pub children: Children, 42 | } 43 | 44 | impl Component for MatTabBar { 45 | type Message = (); 46 | type Properties = TabBarProps; 47 | 48 | fn create(_: &Context) -> Self { 49 | TabBar::ensure_loaded(); 50 | Self { 51 | node_ref: NodeRef::default(), 52 | activated_listener: None, 53 | } 54 | } 55 | 56 | fn view(&self, ctx: &Context) -> Html { 57 | let props = ctx.props(); 58 | html! { 59 | {props.children.clone()} 63 | } 64 | } 65 | 66 | fn rendered(&mut self, ctx: &Context, _first_render: bool) { 67 | let props = ctx.props(); 68 | if self.activated_listener.is_none() { 69 | let element = self.node_ref.cast::().unwrap(); 70 | 71 | let on_activated = props.onactivated.clone(); 72 | self.activated_listener = Some(EventListener::new( 73 | &element, 74 | "MDCTabBar:activated", 75 | move |event| { 76 | let detail = event_details_into::(event); 77 | on_activated.emit(detail.index()); 78 | }, 79 | )); 80 | } 81 | } 82 | } 83 | 84 | #[wasm_bindgen] 85 | extern "C" { 86 | #[derive(Debug)] 87 | #[wasm_bindgen(extends = Object)] 88 | type ActivatedDetailJS; 89 | 90 | #[wasm_bindgen(method, getter)] 91 | fn index(this: &ActivatedDetailJS) -> usize; 92 | } 93 | -------------------------------------------------------------------------------- /src/tabs/tab_icon.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | const SLOT: &str = "icon"; 4 | 5 | /// Props for [`MatTabIcon`] 6 | #[derive(Properties, PartialEq, Clone)] 7 | pub struct TabIconProps { 8 | pub children: Children, 9 | } 10 | 11 | /// Defines title for [`MatTab`][crate::MatTab]. 12 | /// 13 | /// If the child passed is an element (a `VTag`), then it is modified to include 14 | /// the appropriate attributes. Otherwise, the child is wrapped in a `span` 15 | /// containing said attributes. 16 | pub struct MatTabIcon {} 17 | 18 | impl Component for MatTabIcon { 19 | type Message = (); 20 | type Properties = TabIconProps; 21 | 22 | fn create(_: &Context) -> Self { 23 | Self {} 24 | } 25 | 26 | fn view(&self, ctx: &Context) -> Html { 27 | let props = ctx.props(); 28 | let children = props 29 | .children 30 | .iter() 31 | .map(|child| match child { 32 | Html::VTag(mut vtag) => { 33 | vtag.add_attribute("slot", "title"); 34 | Html::VTag(vtag) 35 | } 36 | _ => { 37 | html! { 38 | 39 | {child} 40 | 41 | } 42 | } 43 | }) 44 | .collect::(); 45 | 46 | html! { 47 | {children} 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/text_inputs/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "textfield")] 2 | mod textfield; 3 | #[cfg(feature = "textfield")] 4 | pub use textfield::*; 5 | 6 | #[cfg(any(feature = "textfield", feature = "textarea"))] 7 | pub(crate) mod validity_state; 8 | #[cfg(any(feature = "textfield", feature = "textarea"))] 9 | pub use validity_state::ValidityState; 10 | 11 | #[cfg(any(feature = "textfield", feature = "textarea"))] 12 | pub(crate) mod text_field_type; 13 | #[cfg(any(feature = "textfield", feature = "textarea"))] 14 | pub use text_field_type::*; 15 | 16 | #[cfg(feature = "textarea")] 17 | mod textarea; 18 | #[cfg(feature = "textarea")] 19 | pub use textarea::*; 20 | 21 | #[cfg(any(feature = "textfield", feature = "textarea"))] 22 | pub use web_sys::ValidityState as NativeValidityState; 23 | 24 | use std::rc::Rc; 25 | 26 | use gloo::events::EventListener; 27 | use wasm_bindgen::JsValue; 28 | use web_sys::Event; 29 | 30 | use crate::StaticCallback; 31 | 32 | #[cfg(any(feature = "textfield", feature = "textarea"))] 33 | pub(crate) type ValidityTransformFn = dyn Fn(String, NativeValidityState) -> ValidityState; 34 | 35 | #[cfg(any(feature = "textfield", feature = "textarea"))] 36 | #[derive(Clone)] 37 | /// Owned function for validity props 38 | pub struct ValidityTransform(pub(crate) Rc); 39 | 40 | #[cfg(any(feature = "textfield", feature = "textarea"))] 41 | impl ValidityTransform { 42 | pub fn new ValidityState + 'static>( 43 | func: F, 44 | ) -> ValidityTransform { 45 | ValidityTransform(Rc::new(func)) 46 | } 47 | } 48 | 49 | impl PartialEq for ValidityTransform { 50 | #[allow(clippy::vtable_address_comparisons)] 51 | fn eq(&self, other: &Self) -> bool { 52 | Rc::ptr_eq(&self.0, &other.0) 53 | } 54 | } 55 | 56 | fn set_on_input_handler( 57 | target: &web_sys::Element, 58 | callback: StaticCallback, 59 | convert: impl Fn((Event, JsValue)) -> String + 'static, 60 | ) -> EventListener { 61 | EventListener::new(target, "input", move |event: &Event| { 62 | let js_value = JsValue::from(event); 63 | 64 | callback.call(convert((event.clone(), js_value))) 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /src/text_inputs/text_field_type.rs: -------------------------------------------------------------------------------- 1 | /// The `TextFieldType` type 2 | #[derive(Debug, Clone, PartialEq)] 3 | pub enum TextFieldType { 4 | Text, 5 | Search, 6 | Tel, 7 | Url, 8 | Email, 9 | Password, 10 | Date, 11 | Month, 12 | Week, 13 | Time, 14 | DatetimeLocal, 15 | Number, 16 | Color, 17 | } 18 | 19 | impl TextFieldType { 20 | pub fn as_str(&self) -> &'static str { 21 | use TextFieldType::*; 22 | match self { 23 | Text => "text", 24 | Search => "search", 25 | Tel => "tel", 26 | Url => "url", 27 | Email => "email", 28 | Password => "password", 29 | Date => "date", 30 | Month => "month", 31 | Week => "week", 32 | Time => "time", 33 | DatetimeLocal => "datetime-local", 34 | Number => "number", 35 | Color => "color", 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/text_inputs/validity_state.rs: -------------------------------------------------------------------------------- 1 | use js_sys::Object; 2 | use wasm_bindgen::prelude::*; 3 | use wasm_bindgen::JsCast; 4 | 5 | #[wasm_bindgen] 6 | extern "C" { 7 | #[derive(Debug)] 8 | #[wasm_bindgen(extends = Object)] 9 | pub(crate) type ValidityStateJS; 10 | 11 | #[wasm_bindgen(method, setter = badInput)] 12 | pub fn set_bad_input(this: &ValidityStateJS, val: bool); 13 | 14 | #[wasm_bindgen(method, setter = customError)] 15 | pub fn set_custom_error(this: &ValidityStateJS, val: bool); 16 | 17 | #[wasm_bindgen(method, setter = patternMismatch)] 18 | pub fn set_pattern_mismatch(this: &ValidityStateJS, val: bool); 19 | 20 | #[wasm_bindgen(method, setter = rangeOverflow)] 21 | pub fn set_range_overflow(this: &ValidityStateJS, val: bool); 22 | 23 | #[wasm_bindgen(method, setter = rangeUnderflow)] 24 | pub fn set_range_underflow(this: &ValidityStateJS, val: bool); 25 | 26 | #[wasm_bindgen(method, setter = too_long)] 27 | pub fn set_too_long(this: &ValidityStateJS, val: bool); 28 | 29 | #[wasm_bindgen(method, setter = tooShort)] 30 | pub fn set_too_short(this: &ValidityStateJS, val: bool); 31 | 32 | #[wasm_bindgen(method, setter = type_mismatch)] 33 | pub fn set_type_mismatch(this: &ValidityStateJS, val: bool); 34 | 35 | #[wasm_bindgen(method, setter = valid)] 36 | pub fn set_valid(this: &ValidityStateJS, val: bool); 37 | 38 | #[wasm_bindgen(method, setter = valueMissing)] 39 | pub fn set_value_missing(this: &ValidityStateJS, val: bool); 40 | } 41 | 42 | impl Default for ValidityStateJS { 43 | fn default() -> Self { 44 | Object::new().unchecked_into() 45 | } 46 | } 47 | 48 | /// Rust type for validity props 49 | pub struct ValidityState { 50 | bad_input: bool, 51 | custom_error: bool, 52 | pattern_mismatch: bool, 53 | range_overflow: bool, 54 | range_underflow: bool, 55 | too_long: bool, 56 | too_short: bool, 57 | type_mismatch: bool, 58 | valid: bool, 59 | value_missing: bool, 60 | } 61 | 62 | impl ValidityState { 63 | /// Creates a new `ValidityState`. 64 | /// 65 | /// All the fields except `valid` is set to false except `valid`, which is 66 | /// set to `true` 67 | pub fn new() -> Self { 68 | Self { 69 | bad_input: false, 70 | custom_error: false, 71 | pattern_mismatch: false, 72 | range_overflow: false, 73 | range_underflow: false, 74 | too_long: false, 75 | too_short: false, 76 | type_mismatch: false, 77 | valid: true, 78 | value_missing: false, 79 | } 80 | } 81 | 82 | pub fn bad_input(&self) -> bool { 83 | self.bad_input 84 | } 85 | pub fn custom_error(&self) -> bool { 86 | self.custom_error 87 | } 88 | pub fn pattern_mismatch(&self) -> bool { 89 | self.pattern_mismatch 90 | } 91 | pub fn range_overflow(&self) -> bool { 92 | self.range_overflow 93 | } 94 | pub fn range_underflow(&self) -> bool { 95 | self.range_underflow 96 | } 97 | pub fn too_long(&self) -> bool { 98 | self.too_long 99 | } 100 | pub fn too_short(&self) -> bool { 101 | self.too_short 102 | } 103 | pub fn type_mismatch(&self) -> bool { 104 | self.type_mismatch 105 | } 106 | pub fn valid(&self) -> bool { 107 | self.valid 108 | } 109 | pub fn value_missing(&self) -> bool { 110 | self.value_missing 111 | } 112 | 113 | pub fn set_bad_input(&mut self, value: bool) -> &mut Self { 114 | self.bad_input = value; 115 | self 116 | } 117 | pub fn set_custom_error(&mut self, value: bool) -> &mut Self { 118 | self.custom_error = value; 119 | self 120 | } 121 | pub fn set_pattern_mismatch(&mut self, value: bool) -> &mut Self { 122 | self.pattern_mismatch = value; 123 | self 124 | } 125 | pub fn set_range_overflow(&mut self, value: bool) -> &mut Self { 126 | self.range_overflow = value; 127 | self 128 | } 129 | pub fn set_range_underflow(&mut self, value: bool) -> &mut Self { 130 | self.range_underflow = value; 131 | self 132 | } 133 | pub fn set_too_long(&mut self, value: bool) -> &mut Self { 134 | self.too_long = value; 135 | self 136 | } 137 | pub fn set_too_short(&mut self, value: bool) -> &mut Self { 138 | self.too_short = value; 139 | self 140 | } 141 | pub fn set_type_mismatch(&mut self, value: bool) -> &mut Self { 142 | self.type_mismatch = value; 143 | self 144 | } 145 | pub fn set_valid(&mut self, value: bool) -> &mut Self { 146 | self.valid = value; 147 | self 148 | } 149 | pub fn set_value_missing(&mut self, value: bool) -> &mut Self { 150 | self.value_missing = value; 151 | self 152 | } 153 | } 154 | 155 | impl From for ValidityStateJS { 156 | fn from(validity_state: ValidityState) -> Self { 157 | let validity_state_js = ValidityStateJS::default(); 158 | validity_state_js.set_bad_input(validity_state.bad_input()); 159 | validity_state_js.set_custom_error(validity_state.custom_error()); 160 | validity_state_js.set_pattern_mismatch(validity_state.pattern_mismatch()); 161 | validity_state_js.set_range_overflow(validity_state.range_overflow()); 162 | validity_state_js.set_range_underflow(validity_state.range_underflow()); 163 | validity_state_js.set_too_long(validity_state.too_long()); 164 | validity_state_js.set_too_short(validity_state.too_short()); 165 | validity_state_js.set_type_mismatch(validity_state.type_mismatch()); 166 | validity_state_js.set_valid(validity_state.valid()); 167 | validity_state_js.set_value_missing(validity_state.value_missing()); 168 | validity_state_js 169 | } 170 | } 171 | 172 | impl Default for ValidityState { 173 | fn default() -> Self { 174 | Self::new() 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/top_app_bar.rs: -------------------------------------------------------------------------------- 1 | mod action_items; 2 | mod navigation_icon; 3 | mod title; 4 | 5 | pub use action_items::*; 6 | pub use navigation_icon::*; 7 | pub use title::*; 8 | 9 | use crate::bool_to_option; 10 | use gloo::events::EventListener; 11 | use wasm_bindgen::prelude::*; 12 | use web_sys::Element; 13 | use yew::prelude::*; 14 | 15 | #[wasm_bindgen(module = "/build/mwc-top-app-bar.js")] 16 | extern "C" { 17 | #[derive(Debug)] 18 | type TopAppBar; 19 | 20 | #[wasm_bindgen(getter, static_method_of = TopAppBar)] 21 | fn _dummy_loader() -> JsValue; 22 | } 23 | 24 | loader_hack!(TopAppBar); 25 | 26 | /// The `mwc-top-app-bar` component 27 | /// 28 | /// [MWC Documentation](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/top-app-bar) 29 | pub struct MatTopAppBar { 30 | node_ref: NodeRef, 31 | nav_listener: Option, 32 | } 33 | 34 | /// Props for [`MatTopAppBar`] 35 | /// 36 | /// MWC Documentation: 37 | /// 38 | /// - [Properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/top-app-bar#propertiesattributes) 39 | /// - [Events](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/top-app-bar#events) 40 | #[derive(Debug, Properties, PartialEq, Clone)] 41 | pub struct TopAppBarProps { 42 | pub children: Children, 43 | #[prop_or_default] 44 | pub center_title: bool, 45 | #[prop_or_default] 46 | pub dense: bool, 47 | #[prop_or_default] 48 | pub prominent: bool, 49 | /// Binds to `MDCTopAppBar:nav` 50 | /// 51 | /// See events docs to learn more. 52 | #[prop_or_default] 53 | pub onnavigationiconclick: Callback<()>, 54 | } 55 | 56 | impl Component for MatTopAppBar { 57 | type Message = (); 58 | type Properties = TopAppBarProps; 59 | 60 | fn create(_: &Context) -> Self { 61 | TopAppBar::ensure_loaded(); 62 | Self { 63 | node_ref: NodeRef::default(), 64 | nav_listener: None, 65 | } 66 | } 67 | 68 | fn view(&self, ctx: &Context) -> Html { 69 | let props = ctx.props(); 70 | html! { 71 | 77 | {props.children.clone()} 78 | 79 | } 80 | } 81 | 82 | fn rendered(&mut self, ctx: &Context, _first_render: bool) { 83 | let props = ctx.props(); 84 | 85 | if self.nav_listener.is_none() { 86 | let callback = props.onnavigationiconclick.clone(); 87 | let element = self.node_ref.cast::().unwrap(); 88 | 89 | self.nav_listener = Some(EventListener::new( 90 | &element, 91 | "MDCTopAppBar:nav", 92 | move |_| { 93 | callback.emit(()); 94 | }, 95 | )); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/top_app_bar/action_items.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | const SLOT: &str = "actionItems"; 4 | 5 | /// Props for [`MatTopAppBarActionItems`] 6 | #[derive(Properties, PartialEq, Clone)] 7 | pub struct TopAppBarActionItemsProps { 8 | pub children: Children, 9 | } 10 | 11 | /// Defines header for [`MatTopAppBar`][crate::MatTopAppBar] or 12 | /// [`MatTopAppBarFixed`][crate::MatTopAppBarFixed]. 13 | /// 14 | /// If the child passed is an element (a `VTag`), then it is modified to include 15 | /// the appropriate attributes. Otherwise, the child is wrapped in a `span` 16 | /// containing said attributes. 17 | pub struct MatTopAppBarActionItems {} 18 | 19 | impl Component for MatTopAppBarActionItems { 20 | type Message = (); 21 | type Properties = TopAppBarActionItemsProps; 22 | 23 | fn create(_: &Context) -> Self { 24 | Self {} 25 | } 26 | 27 | fn view(&self, ctx: &Context) -> Html { 28 | let props = ctx.props(); 29 | let children = props 30 | .children 31 | .iter() 32 | .map(|child| match child { 33 | Html::VTag(mut vtag) => { 34 | vtag.add_attribute("slot", SLOT); 35 | Html::VTag(vtag) 36 | } 37 | _ => { 38 | html! { 39 | 40 | {child} 41 | 42 | } 43 | } 44 | }) 45 | .collect::(); 46 | 47 | html! { 48 | {children} 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/top_app_bar/navigation_icon.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | const SLOT: &str = "navigationIcon"; 4 | 5 | /// Props for [`MatTopAppBarNavigationIcon`] 6 | #[derive(Properties, PartialEq, Clone)] 7 | pub struct TopAppBarNavigationIconProps { 8 | pub children: Children, 9 | } 10 | 11 | /// Defines header for [`MatTopAppBar`][crate::MatTopAppBar] or 12 | /// [`MatTopAppBarFixed`][crate::MatTopAppBarFixed]. 13 | /// 14 | /// If the child passed is an element (a `VTag`), then it is modified to include 15 | /// the appropriate attributes. Otherwise, the child is wrapped in a `span` 16 | /// containing said attributes. 17 | pub struct MatTopAppBarNavigationIcon {} 18 | 19 | impl Component for MatTopAppBarNavigationIcon { 20 | type Message = (); 21 | type Properties = TopAppBarNavigationIconProps; 22 | 23 | fn create(_: &Context) -> Self { 24 | Self {} 25 | } 26 | 27 | fn view(&self, ctx: &Context) -> Html { 28 | let props = ctx.props(); 29 | let children = props 30 | .children 31 | .iter() 32 | .map(|child| match child { 33 | Html::VTag(mut vtag) => { 34 | vtag.add_attribute("slot", SLOT); 35 | Html::VTag(vtag) 36 | } 37 | _ => { 38 | html! { 39 | 40 | {child} 41 | 42 | } 43 | } 44 | }) 45 | .collect::(); 46 | 47 | html! { 48 | {children} 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/top_app_bar/title.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | const SLOT: &str = "title"; 4 | 5 | /// Props for [`MatTopAppBarTitle`] 6 | #[derive(Properties, PartialEq, Clone)] 7 | pub struct TopAppBarTitleProps { 8 | pub children: Children, 9 | } 10 | 11 | /// Defines header for [`MatTopAppBar`][crate::MatTopAppBar] or 12 | /// [`MatTopAppBarFixed`][crate::MatTopAppBarFixed]. 13 | /// 14 | /// If the child passed is an element (a `VTag`), then it is modified to include 15 | /// the appropriate attributes. Otherwise, the child is wrapped in a `span` 16 | /// containing said attributes. 17 | pub struct MatTopAppBarTitle {} 18 | 19 | impl Component for MatTopAppBarTitle { 20 | type Message = (); 21 | type Properties = TopAppBarTitleProps; 22 | 23 | fn create(_: &Context) -> Self { 24 | Self {} 25 | } 26 | 27 | fn view(&self, ctx: &Context) -> Html { 28 | let props = ctx.props(); 29 | let children = props 30 | .children 31 | .iter() 32 | .map(|child| match child { 33 | Html::VTag(mut vtag) => { 34 | vtag.add_attribute("slot", SLOT); 35 | Html::VTag(vtag) 36 | } 37 | _ => { 38 | html! { 39 | 40 | {child} 41 | 42 | } 43 | } 44 | }) 45 | .collect::(); 46 | 47 | html! { 48 | {children} 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/top_app_bar_fixed.rs: -------------------------------------------------------------------------------- 1 | use crate::bool_to_option; 2 | #[doc(inline)] 3 | pub use crate::top_app_bar::{ 4 | MatTopAppBarActionItems, MatTopAppBarNavigationIcon, MatTopAppBarTitle, 5 | }; 6 | use gloo::events::EventListener; 7 | use wasm_bindgen::prelude::*; 8 | use web_sys::Element; 9 | use yew::prelude::*; 10 | 11 | #[wasm_bindgen(module = "/build/mwc-top-app-bar-fixed.js")] 12 | extern "C" { 13 | #[derive(Debug)] 14 | type TopAppBarFixed; 15 | 16 | #[wasm_bindgen(getter, static_method_of = TopAppBarFixed)] 17 | fn _dummy_loader() -> JsValue; 18 | } 19 | 20 | loader_hack!(TopAppBarFixed); 21 | 22 | /// The `mwc-top-app-bar-fixed` component 23 | /// 24 | /// [MWC Documentation](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/top-app-bar-fixed) 25 | pub struct MatTopAppBarFixed { 26 | node_ref: NodeRef, 27 | nav_listener: Option, 28 | } 29 | 30 | /// Props for [`MatTopAppBarFixed`] 31 | /// 32 | /// MWC Documentation: 33 | /// 34 | /// - [Properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/top-app-bar-fixed#propertiesattributes) 35 | /// - [Events](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/top-app-bar-fixed#events) 36 | #[derive(Debug, Properties, PartialEq, Clone)] 37 | pub struct TopAppBarFixedProps { 38 | pub children: Children, 39 | #[prop_or_default] 40 | pub center_title: bool, 41 | #[prop_or_default] 42 | pub dense: bool, 43 | #[prop_or_default] 44 | pub prominent: bool, 45 | #[prop_or_default] 46 | pub short: bool, 47 | /// Binds to `MDCTopAppBar:nav` 48 | /// 49 | /// See events docs to learn more. 50 | #[prop_or_default] 51 | pub onnavigationiconclick: Callback<()>, 52 | } 53 | 54 | impl Component for MatTopAppBarFixed { 55 | type Message = (); 56 | type Properties = TopAppBarFixedProps; 57 | 58 | fn create(_: &Context) -> Self { 59 | TopAppBarFixed::ensure_loaded(); 60 | Self { 61 | node_ref: NodeRef::default(), 62 | nav_listener: None, 63 | } 64 | } 65 | 66 | fn view(&self, ctx: &Context) -> Html { 67 | let props = ctx.props(); 68 | html! { 69 | {props.children.clone()} 76 | } 77 | } 78 | 79 | fn changed(&mut self, _ctx: &Context, _old_props: &Self::Properties) -> bool { 80 | // clear nav_listener in case a new callback was registered 81 | self.nav_listener = None; 82 | true 83 | } 84 | 85 | fn rendered(&mut self, ctx: &Context, _first_render: bool) { 86 | let props = ctx.props(); 87 | if self.nav_listener.is_none() { 88 | let callback = props.onnavigationiconclick.clone(); 89 | let element = self.node_ref.cast::().unwrap(); 90 | 91 | self.nav_listener = Some(EventListener::new( 92 | &element, 93 | "MDCTopAppBar:nav", 94 | move |_| { 95 | callback.emit(()); 96 | }, 97 | )); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | // mod weak_component_link; 2 | // pub use weak_component_link::*; 3 | 4 | use std::rc::Rc; 5 | 6 | use dioxus::prelude::*; 7 | 8 | /// See 9 | #[derive(Clone)] 10 | pub struct StaticCallback { 11 | #[allow(clippy::type_complexity)] 12 | inner: Rc>>, 13 | } 14 | 15 | impl StaticCallback { 16 | pub fn call(&self, arg: T) { 17 | (self.inner.borrow_mut())(arg) 18 | } 19 | } 20 | 21 | impl From for StaticCallback 22 | where 23 | F: FnMut(T) + 'static, 24 | { 25 | fn from(f: F) -> Self { 26 | Self { 27 | inner: Rc::new(RefCell::new(Box::new(f))), 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/weak_component_link.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::ops::Deref; 3 | use std::rc::Rc; 4 | use yew::html::Component; 5 | use yew::html::Scope; 6 | 7 | pub struct WeakComponentLink(Rc>>>); 8 | 9 | impl Clone for WeakComponentLink { 10 | fn clone(&self) -> Self { 11 | Self(Rc::clone(&self.0)) 12 | } 13 | } 14 | 15 | impl Default for WeakComponentLink { 16 | fn default() -> Self { 17 | Self(Rc::default()) 18 | } 19 | } 20 | 21 | impl Deref for WeakComponentLink { 22 | type Target = Rc>>>; 23 | 24 | fn deref(&self) -> &Self::Target { 25 | &self.0 26 | } 27 | } 28 | 29 | impl PartialEq for WeakComponentLink { 30 | fn eq(&self, other: &Self) -> bool { 31 | Rc::ptr_eq(&self.0, &other.0) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /dist 3 | /readme.html -------------------------------------------------------------------------------- /website/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "website" 3 | version = "0.1.0" 4 | authors = ["Hamza "] 5 | edition = "2021" 6 | build = "build.rs" 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | wasm-bindgen = "0.2" 11 | yew = { version="0.20.0", features = ["csr"] } 12 | yew-router = "0.17.0" 13 | gloo-utils = "0.1" 14 | gloo-console = "0.2" 15 | 16 | material-yew = { path = "../material-yew", features = ["full"] } 17 | web-sys = { version = "0.3", features = ["HtmlMetaElement", "Document", "Element", "DocumentFragment", "HtmlTemplateElement", "MediaQueryList"] } 18 | unindent = "0.1" 19 | 20 | [dependencies.syntect] 21 | version = "4.5" 22 | default-features = false 23 | features = [ 24 | "html", 25 | "dump-load", 26 | "regex-fancy" 27 | ] 28 | 29 | [build-dependencies] 30 | pulldown-cmark = "0.8" 31 | syntect = { version = "4.5", default-features = false, features = ["default-fancy"] } 32 | -------------------------------------------------------------------------------- /website/assets/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/button.png -------------------------------------------------------------------------------- /website/assets/checkbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/checkbox.png -------------------------------------------------------------------------------- /website/assets/circular-progress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/circular-progress.png -------------------------------------------------------------------------------- /website/assets/components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/components.png -------------------------------------------------------------------------------- /website/assets/dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/dialog.png -------------------------------------------------------------------------------- /website/assets/floating-action-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/floating-action-button.png -------------------------------------------------------------------------------- /website/assets/formfield.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/formfield.png -------------------------------------------------------------------------------- /website/assets/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/github.png -------------------------------------------------------------------------------- /website/assets/icon-button-toggle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/icon-button-toggle.png -------------------------------------------------------------------------------- /website/assets/icon-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/icon-button.png -------------------------------------------------------------------------------- /website/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/icon.png -------------------------------------------------------------------------------- /website/assets/linear-progress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/linear-progress.png -------------------------------------------------------------------------------- /website/assets/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/list.png -------------------------------------------------------------------------------- /website/assets/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/menu.png -------------------------------------------------------------------------------- /website/assets/radio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/radio.png -------------------------------------------------------------------------------- /website/assets/select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/select.png -------------------------------------------------------------------------------- /website/assets/slider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/slider.png -------------------------------------------------------------------------------- /website/assets/snackbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/snackbar.png -------------------------------------------------------------------------------- /website/assets/switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/switch.png -------------------------------------------------------------------------------- /website/assets/tabs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/tabs.png -------------------------------------------------------------------------------- /website/assets/textarea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/textarea.png -------------------------------------------------------------------------------- /website/assets/textfield.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/assets/textfield.png -------------------------------------------------------------------------------- /website/build.rs: -------------------------------------------------------------------------------- 1 | use syntect::html::highlighted_html_for_string; 2 | use syntect::parsing::SyntaxSet; 3 | 4 | use pulldown_cmark::Event; 5 | use pulldown_cmark::Options; 6 | use pulldown_cmark::Parser; 7 | use pulldown_cmark::Tag; 8 | use pulldown_cmark::{html, CodeBlockKind, CowStr}; 9 | 10 | fn main() { 11 | println!("cargo:rerun-if-changed=../README.md"); 12 | 13 | let markdown_input = include_str!("../README.md"); 14 | let markdown_input = markdown_input.replace("# Material Yew", ""); 15 | 16 | // Set up options and parser. Strikethroughs are not part of the CommonMark 17 | // standard and we therefore must enable it explicitly. 18 | let mut options = Options::empty(); 19 | options.insert(Options::ENABLE_STRIKETHROUGH); 20 | let parser = Parser::new_ext(&markdown_input, options); 21 | 22 | let mut new_parser = Vec::new(); 23 | let mut to_highlight = String::new(); 24 | let mut in_code_block = false; 25 | 26 | let mut lang = "".to_string(); 27 | 28 | for event in parser { 29 | match event { 30 | Event::Start(Tag::CodeBlock(c)) => { 31 | if let CodeBlockKind::Fenced(block_lang) = c { 32 | let block_lang = block_lang.to_string(); 33 | lang = if block_lang == *"rust" { 34 | "rs".to_string() 35 | } else { 36 | block_lang 37 | } 38 | }; 39 | // In actual use you'd probably want to keep track of what language this code is 40 | in_code_block = true; 41 | } 42 | Event::End(Tag::CodeBlock(_)) => { 43 | if in_code_block { 44 | // Format the whole multi-line code block as HTML all at once 45 | let ext = lang.trim(); 46 | let syntax_set = match ext { 47 | "html" => SyntaxSet::load_defaults_newlines(), 48 | "rs" | "toml" => { 49 | syntect::dumps::from_binary(include_bytes!( 50 | "syntect-dumps/syntax-set.syntax" 51 | )) 52 | } 53 | _ => panic!("language other than html, toml, rust, used"), 54 | }; 55 | 56 | let syntax = syntax_set 57 | .find_syntax_by_extension(ext) 58 | .unwrap_or_else(|| panic!("{}", ext)); 59 | let theme = syntect::dumps::from_binary(include_bytes!( 60 | "syntect-dumps/Material-Theme-Lighter.theme" 61 | )); 62 | let html = 63 | highlighted_html_for_string(&to_highlight, &syntax_set, syntax, &theme); 64 | // And put it into the vector 65 | new_parser.push(Event::Html(CowStr::from(html))); 66 | to_highlight = String::new(); 67 | in_code_block = false; 68 | } 69 | } 70 | Event::Text(t) => { 71 | if in_code_block { 72 | // If we're in a code block, build up the string of text 73 | to_highlight.push_str(&t); 74 | } else { 75 | new_parser.push(Event::Text(t)) 76 | } 77 | } 78 | e => { 79 | new_parser.push(e); 80 | } 81 | } 82 | } 83 | 84 | let mut html_output = String::with_capacity(markdown_input.len() * 3 / 2); 85 | html::push_html(&mut html_output, new_parser.into_iter()); 86 | 87 | std::fs::write( 88 | "readme.html", 89 | format!(r#"
{}
"#, html_output), 90 | ) 91 | .unwrap(); 92 | } 93 | -------------------------------------------------------------------------------- /website/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Material Yew 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /website/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /assets/ 3 | Disallow: /docs/* 4 | Allow: /docs/material_yew/* 5 | -------------------------------------------------------------------------------- /website/src/components/button.rs: -------------------------------------------------------------------------------- 1 | use crate::components::Codeblock; 2 | use crate::with_raw_code; 3 | use material_yew::MatButton; 4 | use yew::prelude::*; 5 | use yew::virtual_dom::AttrValue; 6 | 7 | pub struct Button {} 8 | 9 | impl Component for Button { 10 | type Message = (); 11 | type Properties = (); 12 | 13 | fn create(_: &Context) -> Self { 14 | Self {} 15 | } 16 | 17 | fn view(&self, _: &Context) -> Html { 18 | let standard_button = with_raw_code!(standard_button { html! { 19 |
20 | 21 | 22 |
23 | }}); 24 | 25 | let outlined_button = with_raw_code!(outlined_button { html! { 26 |
27 | 28 | 29 |
30 | }}); 31 | 32 | let raised_button = with_raw_code!(raised_button { html! { 33 |
34 | 35 | 36 |
37 | }}); 38 | 39 | let unelevated_button = with_raw_code!(unelevated_button { html! { 40 |
41 | 42 | 43 |
44 | }}); 45 | 46 | let dense_button = with_raw_code!(dense_button { html! { 47 |
48 | 49 | 50 |
51 | }}); 52 | 53 | let trailing_icon_button = with_raw_code!(trailing_icon_button { html! { 54 |
55 | 56 | 57 | 58 | 59 | 60 |
61 | }}); 62 | 63 | let disabled_button = with_raw_code!(disabled_button { html! { 64 |
65 | 66 | 67 | 68 | 69 | 70 |
71 | }}); 72 | 73 | html! {<> 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /website/src/components/checkbox.rs: -------------------------------------------------------------------------------- 1 | use crate::components::Codeblock; 2 | use crate::with_raw_code; 3 | use material_yew::MatCheckbox; 4 | use yew::prelude::*; 5 | 6 | pub struct Checkbox {} 7 | 8 | impl Component for Checkbox { 9 | type Message = (); 10 | type Properties = (); 11 | 12 | fn create(_: &Context) -> Self { 13 | Self {} 14 | } 15 | 16 | fn view(&self, _: &Context) -> Html { 17 | let standard_checkbox = with_raw_code!(standard_checkbox { html! { 18 |
19 | 20 |

{"Standard checkbox"}

21 |
22 | }}); 23 | 24 | let checked_checkbox = with_raw_code!(checked_checkbox { html! { 25 |
26 | 27 |

{"Checked checkbox"}

28 |
29 | }}); 30 | 31 | let indeterminate_checkbox = with_raw_code!(indeterminate_checkbox { html! { 32 |
33 | 34 |

{"Indeterminate checkbox"}

35 |
36 | }}); 37 | 38 | let disabled_checkbox = with_raw_code!(disabled_checkbox { html! { 39 |
40 | 41 |

{"Disabled checkbox"}

42 |
43 | }}); 44 | 45 | html! {<> 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /website/src/components/circular_progress.rs: -------------------------------------------------------------------------------- 1 | use crate::components::Codeblock; 2 | use crate::with_raw_code; 3 | use material_yew::{MatButton, MatCircularProgress, MatCircularProgressFourColor}; 4 | use yew::prelude::*; 5 | 6 | pub struct CircularProgress { 7 | closed: bool, 8 | progress: f32, 9 | } 10 | 11 | pub enum Msg { 12 | Close, 13 | ChangeProgress, 14 | } 15 | 16 | impl Component for CircularProgress { 17 | type Message = Msg; 18 | type Properties = (); 19 | 20 | fn create(_: &Context) -> Self { 21 | Self { 22 | closed: false, 23 | progress: 0.0, 24 | } 25 | } 26 | 27 | fn update(&mut self, _: &Context, msg: Self::Message) -> bool { 28 | match msg { 29 | Msg::ChangeProgress => { 30 | self.progress += 0.1; 31 | } 32 | Msg::Close => { 33 | self.closed = !self.closed; 34 | } 35 | } 36 | true 37 | } 38 | 39 | fn view(&self, ctx: &Context) -> Html { 40 | let link = ctx.link(); 41 | let toggle_example = with_raw_code!(toggle_example { html! { 42 |
43 | 44 | 45 |
46 | 47 |
48 | }}); 49 | 50 | let indeterminate_ex = with_raw_code!(indeterminate_ex { html! { 51 | 52 | }}); 53 | 54 | let determinate_ex = with_raw_code!(determinate_ex { html! { 55 |
56 | 57 | 58 | 59 | 60 |
61 | }}); 62 | 63 | let four_color_ex = with_raw_code!(four_color_ex { html! { 64 | 65 | }}); 66 | 67 | html! {<> 68 | 69 | 70 | 71 | 72 | 73 | 74 |

75 | {"Note:"} {" Four colored variants of circular progress is available under "} {"MatCircularProgressFourColor"} 76 |

77 | 78 |
79 | 80 |
81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /website/src/components/code_block.rs: -------------------------------------------------------------------------------- 1 | use crate::html_to_element; 2 | use material_yew::MatIconButton; 3 | use yew::prelude::*; 4 | 5 | pub struct Codeblock { 6 | showing_code: bool, 7 | } 8 | 9 | pub enum Msg { 10 | FlipShowCode, 11 | } 12 | 13 | #[derive(Properties, PartialEq, Clone)] 14 | pub struct Props { 15 | // pub children: Children, 16 | // pub code: String, 17 | pub title: String, 18 | pub code_and_html: (String, Html), 19 | #[prop_or(45)] 20 | pub max_width: u32, 21 | } 22 | 23 | impl Component for Codeblock { 24 | type Message = Msg; 25 | type Properties = Props; 26 | 27 | fn create(_: &Context) -> Self { 28 | Self { 29 | showing_code: false, 30 | } 31 | } 32 | 33 | fn update(&mut self, _: &Context, msg: Self::Message) -> bool { 34 | match msg { 35 | Msg::FlipShowCode => { 36 | self.showing_code = !self.showing_code; 37 | true 38 | } 39 | } 40 | } 41 | 42 | fn view(&self, ctx: &Context) -> Html { 43 | let props = ctx.props(); 44 | let link = ctx.link(); 45 | let code = html_to_element(&props.code_and_html.0); 46 | html! { <> 47 |
48 |
49 |

{&props.title}

50 | 51 | 52 | 53 |
54 | 55 | { 56 | if self.showing_code { 57 | {code} 58 | } else { html!{}} 59 | } 60 | 61 | {props.code_and_html.1.clone()} 62 |
63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /website/src/components/components_list.rs: -------------------------------------------------------------------------------- 1 | use crate::{AppLink, AppRoute}; 2 | use yew::prelude::*; 3 | 4 | pub struct Components {} 5 | 6 | impl Component for Components { 7 | type Message = (); 8 | type Properties = (); 9 | 10 | fn create(_: &Context) -> Self { 11 | Self {} 12 | } 13 | 14 | fn view(&self, _: &Context) -> Html { 15 | let component_card = |name: &str, route| { 16 | let path = join("-", name.split(' ')).to_lowercase(); 17 | 18 | html! { 19 | 20 | 21 |
{name}
22 |
23 | } 24 | }; 25 | html! { 26 |
27 | {component_card("Button", AppRoute::Button)} 28 | {component_card("Checkbox", AppRoute::Checkbox)} 29 | {component_card("Radio", AppRoute::Radio)} 30 | {component_card("Switch", AppRoute::Switch)} 31 | {component_card("Floating Action Button", AppRoute::Fab)} 32 | {component_card("Icon Button", AppRoute::IconButton)} 33 | {component_card("Icon", AppRoute::Icon)} 34 | {component_card("Circular Progress", AppRoute::CircularProgress)} 35 | {component_card("Dialog", AppRoute::Dialog)} 36 | // TODO { component_card("Drawer", AppRoute::Drawer)} 37 | {component_card("Formfield", AppRoute::FormField)} 38 | {component_card("Linear Progress", AppRoute::LinearProgress)} 39 | {component_card("List", AppRoute::List)} 40 | {component_card("Icon Button Toggle", AppRoute::IconButtonToggle)} 41 | {component_card("Slider", AppRoute::Slider)} 42 | {component_card("Tabs", AppRoute::Tabs)} 43 | {component_card("Snackbar", AppRoute::Snackbar)} 44 | {component_card("TextField", AppRoute::Textfield)} 45 | {component_card("TextArea", AppRoute::TextArea)} 46 | {component_card("Select", AppRoute::Select)} 47 | {component_card("Menu", AppRoute::Menu)} 48 |
49 | } 50 | } 51 | } 52 | 53 | fn join<'a, T>(separator: &str, collection: T) -> String 54 | where 55 | T: IntoIterator, 56 | T::Item: Into<&'a str>, 57 | { 58 | let mut out = String::new(); 59 | collection.into_iter().for_each(|it| { 60 | out.push_str(it.into()); 61 | out.push_str(separator); 62 | }); 63 | let out = out.strip_suffix('-'); 64 | out.unwrap().to_owned() 65 | } 66 | -------------------------------------------------------------------------------- /website/src/components/drawer.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | pub struct Drawer {} 4 | 5 | impl Component for Drawer { 6 | type Message = (); 7 | type Properties = (); 8 | 9 | fn create(_: &Context) -> Self { 10 | Self {} 11 | } 12 | 13 | fn view(&self, _: &Context) -> Html { 14 | html! {<> 15 |
16 | {"Need a way to iframe here. I apparently can't have 2 top app bars"} 17 |
18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /website/src/components/fab.rs: -------------------------------------------------------------------------------- 1 | use crate::components::Codeblock; 2 | use crate::with_raw_code; 3 | use material_yew::MatFab; 4 | use yew::prelude::*; 5 | 6 | pub struct Fab {} 7 | 8 | impl Component for Fab { 9 | type Message = (); 10 | type Properties = (); 11 | 12 | fn create(_: &Context) -> Self { 13 | Self {} 14 | } 15 | 16 | fn view(&self, _: &Context) -> Html { 17 | let standard_fab = with_raw_code!(standard_fab { html! { 18 |
19 | 20 |
21 | }}); 22 | 23 | let mini_fab = with_raw_code!(mini_fab { html! { 24 |
25 | 26 |
27 | }}); 28 | 29 | let extended_fab = with_raw_code!(extended_fab { html! { 30 |
31 | 32 |
33 | }}); 34 | 35 | html! {<> 36 | 37 | 38 | 39 | 40 | 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /website/src/components/form_field.rs: -------------------------------------------------------------------------------- 1 | use crate::components::Codeblock; 2 | use crate::with_raw_code; 3 | use material_yew::{MatCheckbox, MatFormfield, MatRadio, MatSwitch}; 4 | use yew::prelude::*; 5 | 6 | pub struct FormField {} 7 | 8 | impl Component for FormField { 9 | type Message = (); 10 | type Properties = (); 11 | 12 | fn create(_: &Context) -> Self { 13 | Self {} 14 | } 15 | 16 | fn view(&self, _: &Context) -> Html { 17 | let checkbox = with_raw_code!(checkbox { html! { 18 |
19 |
20 |

{"Standard"}

21 | 22 | 23 | 24 |
25 | 26 |
27 |

{"Align End"}

28 | 29 | 30 | 31 |
32 |
}}); 33 | 34 | let switch = with_raw_code!(switch { html! { 35 |
36 | 37 | 38 | 39 |
40 | }}); 41 | 42 | let radio = with_raw_code!(radio { html! { 43 |
44 | 45 | 46 | 47 |
48 | }}); 49 | 50 | html! {<> 51 | 52 | 53 | 54 | 55 | 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /website/src/components/home.rs: -------------------------------------------------------------------------------- 1 | use crate::html_to_element; 2 | use yew::prelude::*; 3 | 4 | pub struct Home {} 5 | 6 | impl Component for Home { 7 | type Message = (); 8 | type Properties = (); 9 | 10 | fn create(_: &Context) -> Self { 11 | Self {} 12 | } 13 | 14 | fn view(&self, _: &Context) -> Html { 15 | let readme = include_str!("../../readme.html"); 16 | 17 | html_to_element(readme) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /website/src/components/icon.rs: -------------------------------------------------------------------------------- 1 | use crate::components::Codeblock; 2 | use crate::with_raw_code; 3 | use material_yew::MatIcon; 4 | use yew::prelude::*; 5 | 6 | pub struct Icon {} 7 | 8 | impl Component for Icon { 9 | type Message = (); 10 | type Properties = (); 11 | 12 | fn create(_: &Context) -> Self { 13 | Self {} 14 | } 15 | 16 | fn view(&self, _: &Context) -> Html { 17 | let icons = with_raw_code!(icons { html! { 18 |
19 | {"sentiment_very_dissatisfied"} 20 | {"sentiment_dissatisfied"} 21 | {"sentiment_very_dissatisfied"} 22 | {"sentiment_very_dissatisfied"} 23 | {"sentiment_very_dissatisfied"} 24 |
25 | }}); 26 | html! {<> 27 | 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /website/src/components/icon_button.rs: -------------------------------------------------------------------------------- 1 | use crate::components::Codeblock; 2 | use crate::with_raw_code; 3 | use material_yew::MatIconButton; 4 | use yew::prelude::*; 5 | 6 | pub struct IconButton {} 7 | 8 | impl Component for IconButton { 9 | type Message = (); 10 | type Properties = (); 11 | 12 | fn create(_: &Context) -> Self { 13 | Self {} 14 | } 15 | 16 | fn view(&self, _: &Context) -> Html { 17 | let standard_icon_button = with_raw_code!(standard_icon_button { html! { 18 |
19 | 20 |
21 | }}); 22 | let svg_icon_button = with_raw_code!(svg_icon_button { html! { 23 |
24 | 25 | 26 | 27 |
28 | }}); 29 | let disabled_icon_button = with_raw_code!(disabled_icon_button { html! { 30 |
31 | 32 |
33 | }}); 34 | html! {<> 35 | 36 | 37 | 38 | 39 | 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /website/src/components/icon_button_toggle.rs: -------------------------------------------------------------------------------- 1 | use crate::components::Codeblock; 2 | use crate::with_raw_code; 3 | use material_yew::{ 4 | icon_button_toggle::{MatOffIconButtonToggle, MatOnIconButtonToggle}, 5 | MatIconButtonToggle, 6 | }; 7 | use yew::prelude::*; 8 | 9 | pub struct IconButtonToggle { 10 | state: bool, 11 | } 12 | 13 | pub enum Msg { 14 | Change(bool), 15 | } 16 | 17 | impl Component for IconButtonToggle { 18 | type Message = Msg; 19 | type Properties = (); 20 | 21 | fn create(_: &Context) -> Self { 22 | Self { state: false } 23 | } 24 | 25 | fn update(&mut self, _: &Context, msg: Self::Message) -> bool { 26 | match msg { 27 | Msg::Change(state) => self.state = state, 28 | }; 29 | true 30 | } 31 | 32 | fn view(&self, ctx: &Context) -> Html { 33 | let link = ctx.link(); 34 | let icon_button_toggle = with_raw_code!(icon_button_toggle { html! { 35 |
36 |
37 | 38 |
39 | {"State: "} {self.state} 40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
65 | }}); 66 | 67 | html! {<> 68 | 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /website/src/components/linear_progress.rs: -------------------------------------------------------------------------------- 1 | use crate::components::Codeblock; 2 | use crate::with_raw_code; 3 | use material_yew::{MatButton, MatLinearProgress}; 4 | use yew::prelude::*; 5 | 6 | pub struct LinearProgress { 7 | closed: bool, 8 | progress: f32, 9 | } 10 | 11 | pub enum Msg { 12 | Close, 13 | ChangeProgress, 14 | } 15 | 16 | impl Component for LinearProgress { 17 | type Message = Msg; 18 | type Properties = (); 19 | 20 | fn create(_: &Context) -> Self { 21 | Self { 22 | closed: false, 23 | progress: 0.0, 24 | } 25 | } 26 | 27 | fn update(&mut self, _: &Context, msg: Self::Message) -> bool { 28 | match msg { 29 | Msg::ChangeProgress => { 30 | self.progress += 0.1; 31 | } 32 | Msg::Close => { 33 | self.closed = !self.closed; 34 | } 35 | } 36 | true 37 | } 38 | 39 | fn view(&self, ctx: &Context) -> Html { 40 | let link = ctx.link(); 41 | let buffer = self.progress - 0.3; 42 | 43 | let toggle = with_raw_code!(toggle { html! { 44 |
45 | 46 | 47 |
48 |
49 | 50 |
51 |
52 | }}); 53 | 54 | let indeterminate = with_raw_code!(indeterminate { html! { 55 |
56 |
57 | 58 |
59 |
60 | }}); 61 | 62 | let determinate = with_raw_code!(determinate { html! { 63 |
64 | 65 | 66 |
67 |
68 | 69 |
70 |
71 | }}); 72 | html! {<> 73 | 74 | 75 | 76 | 77 | 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /website/src/components/mod.rs: -------------------------------------------------------------------------------- 1 | mod button; 2 | mod checkbox; 3 | mod circular_progress; 4 | mod code_block; 5 | mod components_list; 6 | mod dialog; 7 | mod drawer; 8 | mod fab; 9 | mod form_field; 10 | mod home; 11 | mod icon; 12 | mod icon_button; 13 | mod icon_button_toggle; 14 | mod linear_progress; 15 | mod list; 16 | mod menu; 17 | mod radio; 18 | mod select; 19 | mod slider; 20 | mod snackbar; 21 | mod switch; 22 | mod tabs; 23 | mod textarea; 24 | mod textfield; 25 | 26 | pub use button::Button; 27 | pub use checkbox::Checkbox; 28 | pub use circular_progress::CircularProgress; 29 | pub use code_block::Codeblock; 30 | pub use components_list::Components; 31 | pub use dialog::Dialog; 32 | pub use drawer::Drawer; 33 | pub use fab::Fab; 34 | pub use form_field::FormField; 35 | pub use home::Home; 36 | pub use icon::Icon; 37 | pub use icon_button::IconButton; 38 | pub use icon_button_toggle::IconButtonToggle; 39 | pub use linear_progress::LinearProgress; 40 | pub use list::List; 41 | pub use menu::Menu; 42 | pub use radio::Radio; 43 | pub use select::Select; 44 | pub use slider::Slider; 45 | pub use snackbar::Snackbar; 46 | pub use switch::Switch; 47 | pub use tabs::Tabs; 48 | pub use textarea::TextArea; 49 | pub use textfield::Textfield; 50 | -------------------------------------------------------------------------------- /website/src/components/radio.rs: -------------------------------------------------------------------------------- 1 | use crate::components::Codeblock; 2 | use crate::with_raw_code; 3 | use material_yew::MatRadio; 4 | use yew::prelude::*; 5 | 6 | pub struct Radio {} 7 | 8 | impl Component for Radio { 9 | type Message = (); 10 | type Properties = (); 11 | 12 | fn create(_: &Context) -> Self { 13 | Self {} 14 | } 15 | 16 | fn view(&self, _: &Context) -> Html { 17 | let standard = with_raw_code!(standard { html! { 18 |
19 | 20 |
21 | }}); 22 | let checked = with_raw_code!(checked { html! { 23 |
24 | 25 |
26 | }}); 27 | let disabled = with_raw_code!(disabled { html! { 28 |
29 | 30 |
31 | }}); 32 | 33 | html! {<> 34 | 35 | 36 | 37 | 38 | 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /website/src/components/slider.rs: -------------------------------------------------------------------------------- 1 | use crate::components::Codeblock; 2 | use crate::with_raw_code; 3 | use material_yew::MatSlider; 4 | use yew::prelude::*; 5 | 6 | pub struct Slider {} 7 | 8 | impl Component for Slider { 9 | type Message = (); 10 | type Properties = (); 11 | 12 | fn create(_: &Context) -> Self { 13 | Self {} 14 | } 15 | 16 | fn view(&self, _: &Context) -> Html { 17 | let continuous = with_raw_code!(continuous { html! { 18 |
19 | 20 |
21 | }}); 22 | 23 | let discrete = with_raw_code!(discrete { html! { 24 |
25 | 26 |
27 | }}); 28 | 29 | html! {
30 | 31 | 32 | 33 | 34 | /* Undocumented so `disabled` exist 35 |
36 |

{"Disabled"}

37 | 38 |
*/ 39 |
} 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /website/src/components/snackbar.rs: -------------------------------------------------------------------------------- 1 | use crate::components::Codeblock; 2 | use crate::with_raw_code; 3 | use material_yew::{MatButton, MatIconButton, MatSnackbar, WeakComponentLink}; 4 | use yew::prelude::*; 5 | 6 | pub struct Snackbar { 7 | default_link: WeakComponentLink, 8 | leading_link: WeakComponentLink, 9 | stacked_link: WeakComponentLink, 10 | } 11 | 12 | pub enum Msg { 13 | OpenDefault, 14 | OpenLeading, 15 | OpenStacked, 16 | DefaultClosed(Option), 17 | LeadingClosed(Option), 18 | StackedClosed(Option), 19 | } 20 | 21 | impl Component for Snackbar { 22 | type Message = Msg; 23 | type Properties = (); 24 | 25 | fn create(_: &Context) -> Self { 26 | Self { 27 | default_link: WeakComponentLink::default(), 28 | leading_link: WeakComponentLink::default(), 29 | stacked_link: WeakComponentLink::default(), 30 | } 31 | } 32 | 33 | fn update(&mut self, _: &Context, msg: Self::Message) -> bool { 34 | match msg { 35 | Msg::OpenDefault => { 36 | self.default_link.show(); 37 | } 38 | Msg::OpenLeading => { 39 | self.leading_link.show(); 40 | } 41 | Msg::OpenStacked => { 42 | self.stacked_link.show(); 43 | } 44 | Msg::DefaultClosed(reason) => { 45 | gloo_console::log!(&format!("default closed with reason {:?}", reason)) 46 | } 47 | Msg::LeadingClosed(reason) => { 48 | gloo_console::log!(&format!("leading closed with reason {:?}", reason)) 49 | } 50 | Msg::StackedClosed(reason) => { 51 | gloo_console::log!(&format!("stacked closed with reason {:?}", reason)) 52 | } 53 | } 54 | false 55 | } 56 | 57 | fn view(&self, ctx: &Context) -> Html { 58 | let link = ctx.link(); 59 | let default = with_raw_code!(default { html! { 60 |
61 | 62 | 63 | 64 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
75 | }}); 76 | 77 | let leading = with_raw_code!(leading { html! { 78 |
79 | 80 | 81 | 82 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 |
93 | }}); 94 | 95 | let stacked = with_raw_code!(stacked { html! { 96 |
97 | 98 | 99 | 100 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 |
111 | }}); 112 | 113 | html! {<> 114 | 115 | 116 | 117 | 118 | 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /website/src/components/switch.rs: -------------------------------------------------------------------------------- 1 | use crate::components::Codeblock; 2 | use crate::with_raw_code; 3 | use material_yew::MatSwitch; 4 | use yew::prelude::*; 5 | 6 | pub struct Switch {} 7 | 8 | impl Component for Switch { 9 | type Message = (); 10 | type Properties = (); 11 | 12 | fn create(_: &Context) -> Self { 13 | Self {} 14 | } 15 | 16 | fn view(&self, _: &Context) -> Html { 17 | let standard = with_raw_code!(standard { html! { 18 |
19 |

{"Switch"}

20 | 21 |
22 | }}); 23 | 24 | let checked = with_raw_code!(checked { html! { 25 |
26 |

{"Switch"}

27 | 28 |
29 | }}); 30 | 31 | let disabled = with_raw_code!(disabled { html! { 32 |
33 |

{"Switch"}

34 | 35 |
36 | }}); 37 | 38 | let disabled_checked = with_raw_code!(disabled_checked { html! { 39 |
40 |

{"Switch"}

41 | 42 |
43 | }}); 44 | 45 | html! {<> 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /website/src/components/textarea.rs: -------------------------------------------------------------------------------- 1 | use crate::components::Codeblock; 2 | use crate::with_raw_code; 3 | use material_yew::text_inputs::TextAreaCharCounter; 4 | use material_yew::{text_inputs::ValidityState, MatTextArea}; 5 | use yew::prelude::*; 6 | 7 | pub struct TextArea {} 8 | 9 | impl Component for TextArea { 10 | type Message = (); 11 | type Properties = (); 12 | 13 | fn create(_: &Context) -> Self { 14 | Self {} 15 | } 16 | 17 | fn view(&self, _: &Context) -> Html { 18 | let validity_transform = MatTextArea::validity_transform(move |_, _| { 19 | let mut state = ValidityState::new(); 20 | state.set_valid(false).set_bad_input(true); 21 | state 22 | }); 23 | 24 | let standard = with_raw_code!(standard { html! { 25 |
26 | 27 | 28 |
29 |

{"Note: validation for both of these text areas always fail"}

30 |
31 | }}); 32 | 33 | let with_char_counter = with_raw_code!(with_char_counter { html! { 34 |
35 | 36 | 37 |
38 | }}); 39 | 40 | let with_helper_text = with_raw_code!(with_helper_text { html! { 41 |
42 | 43 | 44 |
45 | }}); 46 | 47 | html! {
48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
} 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /website/src/components/textfield.rs: -------------------------------------------------------------------------------- 1 | use crate::components::Codeblock; 2 | use crate::with_raw_code; 3 | use material_yew::{ 4 | text_inputs::{TextFieldType, ValidityState}, 5 | MatTextField, 6 | }; 7 | use yew::prelude::*; 8 | 9 | pub struct Textfield {} 10 | 11 | impl Component for Textfield { 12 | type Message = (); 13 | type Properties = (); 14 | 15 | fn create(_: &Context) -> Self { 16 | Self {} 17 | } 18 | 19 | fn view(&self, _: &Context) -> Html { 20 | let validity_transform = MatTextField::validity_transform(move |_, _| { 21 | let mut state = ValidityState::new(); 22 | state.set_valid(false).set_bad_input(true); 23 | state 24 | }); 25 | 26 | let filled = with_raw_code!(filled { html! { 27 |
28 | 29 | 30 | 31 |
32 | }}); 33 | 34 | let outlined = with_raw_code!(outlined { html! { 35 |
36 | 37 | 38 | 39 |
40 | }}); 41 | 42 | let without_label = with_raw_code!(without_label { html! { 43 |
44 | 45 | 46 | 47 | 48 | 49 |
50 | }}); 51 | 52 | let with_char_counter = with_raw_code!(with_char_counter { html! { 53 |
54 | 55 | 56 | 57 | 58 | 59 |
60 | }}); 61 | 62 | html! { 63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /website/src/macros.rs: -------------------------------------------------------------------------------- 1 | pub fn read_until_close(s: &str) -> &str { 2 | let mut depth = 1; 3 | for (i, c) in s.char_indices() { 4 | match c { 5 | '{' => depth += 1, 6 | '}' => depth -= 1, 7 | _ => {} 8 | } 9 | 10 | if depth == 0 { 11 | return &s[..i]; 12 | } 13 | } 14 | 15 | s 16 | } 17 | 18 | pub fn highlight(code: &str, extension: &str) -> String { 19 | crate::SYNTECT_DATA.with(|cell| { 20 | let data = cell.borrow(); 21 | let syntax = data 22 | .syntax_set 23 | .as_ref() 24 | .unwrap() 25 | .find_syntax_by_extension(extension) 26 | .unwrap(); 27 | syntect::html::highlighted_html_for_string( 28 | code, 29 | data.syntax_set.as_ref().unwrap(), 30 | syntax, 31 | data.theme.as_ref().unwrap(), 32 | ) 33 | }) 34 | } 35 | 36 | #[macro_export] 37 | macro_rules! with_raw_code { 38 | ($key:ident { $expr:expr }) => {{ 39 | const CODE: &str = include_str!(concat!("../../../", file!())); 40 | 41 | let marker_start = CODE 42 | .find(concat!("with_raw_code!(", stringify!($key))) 43 | .expect(&format!("marker {:?} not found", stringify!($key))); 44 | let body_offset = CODE[marker_start..].find('{').unwrap(); 45 | let code = $crate::macros::read_until_close(&CODE[marker_start + body_offset + 9..]); 46 | let code = unindent::unindent(code); 47 | 48 | let html_string = $crate::macros::highlight(&code, "rs"); 49 | 50 | (html_string, $expr) 51 | }}; 52 | } 53 | -------------------------------------------------------------------------------- /website/src/main.rs: -------------------------------------------------------------------------------- 1 | use website::{App, SYNTECT_DATA}; 2 | 3 | fn main() { 4 | SYNTECT_DATA.with(|cell| { 5 | let mut data = cell.borrow_mut(); 6 | data.theme = Some(syntect::dumps::from_binary(include_bytes!( 7 | "../syntect-dumps/Material-Theme-Lighter.theme" 8 | ))); 9 | data.syntax_set = Some(syntect::dumps::from_binary(include_bytes!( 10 | "../syntect-dumps/syntax-set.syntax" 11 | ))); 12 | }); 13 | yew::Renderer::::new().render(); 14 | } 15 | -------------------------------------------------------------------------------- /website/styles/jetbrains-mono.css: -------------------------------------------------------------------------------- 1 | @font-face{ 2 | font-family: 'JetBrains Mono'; 3 | src: url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/web/JetBrainsMono-Regular.woff2') format('woff2'); 4 | font-weight: normal; 5 | font-style: normal; 6 | } 7 | 8 | @font-face{ 9 | font-family: 'JetBrains Mono'; 10 | src: url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/web/JetBrainsMono-Bold.woff2') format('woff2'); 11 | font-weight: bold; 12 | font-style: normal; 13 | } 14 | -------------------------------------------------------------------------------- /website/syntect-dumps/Material-Theme-Lighter.theme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/syntect-dumps/Material-Theme-Lighter.theme -------------------------------------------------------------------------------- /website/syntect-dumps/Material-Theme.theme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/syntect-dumps/Material-Theme.theme -------------------------------------------------------------------------------- /website/syntect-dumps/rust.syntax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/syntect-dumps/rust.syntax -------------------------------------------------------------------------------- /website/syntect-dumps/syntax-set.syntax: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RubixDev/material-dioxus/e9ce8d472faaf17062f73e1c771dbaa1cb925360/website/syntect-dumps/syntax-set.syntax --------------------------------------------------------------------------------