Skip to content

Commit

Permalink
Initial support for material tabs
Browse files Browse the repository at this point in the history
  • Loading branch information
Follpvosten committed Apr 2, 2020
1 parent f6ce50f commit 750d5a5
Show file tree
Hide file tree
Showing 8 changed files with 423 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ default = [
"slider",
"data-table",
"image-list",
"tabs",
]
button = []
fab = []
Expand All @@ -38,6 +39,7 @@ list = []
slider = []
data-table = []
image-list = []
tabs = ["serde", "serde_json", "wasm-bindgen/serde-serialize"]

[dependencies]
yew = { version = "0.14", default-features = false, features = ["web_sys"] }
Expand Down
7 changes: 7 additions & 0 deletions src/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,10 @@ cfg_if! {
pub use image_list::Supporting;
}
}

cfg_if! {
if #[cfg(feature = "tabs")] {
pub mod tabs;
pub use tabs::*;
}
}
9 changes: 9 additions & 0 deletions src/components/tabs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pub mod tab;
pub mod tab_bar;
pub mod tab_indicator;
pub mod tab_scroller;

pub use tab::Tab;
pub use tab_bar::TabBar;
pub use tab_indicator::TabIndicator;
pub use tab_scroller::TabScroller;
73 changes: 73 additions & 0 deletions src/components/tabs/tab.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//use crate::mdc_sys::MDCTab;
use yew::prelude::*;

use super::TabIndicator;

pub struct Tab {
props: Props,
//inner: Option<MDCTab>,
}

#[derive(Properties, Clone, PartialEq)]
pub struct Props {
pub children: Children,

#[prop_or_default]
pub id: String,
#[prop_or_default]
pub active: bool,

/// If set to true, this tab's indicator will only span the tab's content.
#[prop_or_default]
pub content_only_indicator: bool,
#[prop_or_default]
pub fading_indicator: bool,
}

impl Component for Tab {
type Message = ();
type Properties = Props;

fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
Self { props }
}

fn mounted(&mut self) -> ShouldRender {
false
}

fn update(&mut self, _msg: Self::Message) -> ShouldRender {
false
}

fn view(&self) -> Html {
let indicator = html! {
<TabIndicator
active=self.props.active
fading=self.props.fading_indicator
/>
};
let (outer_indic, inner_indic) = if self.props.content_only_indicator {
(html! {}, indicator)
} else {
(indicator, html! {})
};
let classes = if self.props.active {
"mdc-tab mdc-tab--active"
} else {
"mdc-tab"
};
html! {
<button class=classes id=&self.props.id>
<span class="mdc-tab__content">
{ self.props.children.render() }
{ inner_indic }
</span>
{ outer_indic }
<span class="mdc-tab__ripple"></span>
</button>
}
}

fn destroy(&mut self) {}
}
116 changes: 116 additions & 0 deletions src/components/tabs/tab_bar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use crate::mdc_sys::MDCTabBar;
use wasm_bindgen::{prelude::*, JsCast};
use yew::prelude::*;

pub struct TabBar {
props: Props,
inner: Option<MDCTabBar>,
node_ref: NodeRef,
activated_callback: Closure<dyn FnMut(web_sys::Event)>,
current_tab: u64,
}

#[derive(Properties, Clone, PartialEq)]
pub struct Props {
pub children: Children,

#[prop_or_default]
pub id: String,

/// If set to true (default), tabs are focused on activation.
#[prop_or(true)]
pub focus_tabs_on_activate: bool,
/// If set to true (default), tabs get activated when focused with the arrow keys.
/// If false, they only get activated on enter/space bar press.
#[prop_or(true)]
pub arrow_key_tab_activation: bool,

/// Tab to activate after rendering.
#[prop_or_default]
pub activated_tab: Option<u32>,

#[prop_or_else(Callback::noop)]
pub ontabactivate: Callback<u64>,
}

pub enum Msg {
TabActivated(u64),
}

impl Component for TabBar {
type Message = Msg;
type Properties = Props;

fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
let callback = link.callback(Msg::TabActivated);
let closure = Closure::wrap(Box::new(move |e: web_sys::Event| {
if let Some(e) = e.dyn_ref::<web_sys::CustomEvent>() {
e.stop_propagation();
if let Ok(value) = e.detail().into_serde::<serde_json::Value>() {
if let Some(index) = value.get("index").and_then(|v| v.as_u64()) {
callback.emit(index);
}
}
}
e.stop_propagation();
}) as Box<dyn FnMut(web_sys::Event)>);
Self {
props,
inner: None,
node_ref: NodeRef::default(),
activated_callback: closure,
current_tab: 0,
}
}

fn mounted(&mut self) -> ShouldRender {
if let Some(tab_bar) = self.node_ref.cast::<web_sys::Element>().map(MDCTabBar::new) {
tab_bar.focus_on_activate(self.props.focus_tabs_on_activate);
tab_bar.use_automatic_activation(self.props.arrow_key_tab_activation);
tab_bar.listen("MDCTabBar:activated", &self.activated_callback);
if let Some(index) = self.props.activated_tab {
self.current_tab = index as u64;
tab_bar.activate_tab(index);
}
self.inner = Some(tab_bar);
}
false
}

fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props {
self.props = props;
true
} else {
false
}
}

fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::TabActivated(index) => {
self.current_tab = index;
self.props.ontabactivate.emit(index);
}
}
false
}

fn view(&self) -> Html {
html! {
<div class="mdc-tab-bar"
ref=self.node_ref.clone()
id=&self.props.id
>
{ self.props.children.render() }
</div>
}
}

fn destroy(&mut self) {
if let Some(ref inner) = self.inner {
inner.unlisten("MDCTabBar:activated", &self.activated_callback);
inner.destroy();
}
}
}
56 changes: 56 additions & 0 deletions src/components/tabs/tab_indicator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//use crate::mdc_sys::MDCTabIndicator;
use yew::prelude::*;

pub struct TabIndicator {
props: Props,
//inner: Option<MDCTabIndicator>,
}

#[derive(Properties, Clone, PartialEq)]
pub struct Props {
#[prop_or_default]
pub id: String,

#[prop_or_default]
pub active: bool,
#[prop_or_default]
pub fading: bool,
}

impl Component for TabIndicator {
type Message = ();
type Properties = Props;

fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
Self { props }
}

fn mounted(&mut self) -> ShouldRender {
false
}

fn update(&mut self, _msg: Self::Message) -> ShouldRender {
false
}

fn view(&self) -> Html {
let fading = if self.props.fading {
" mdc-tab-indicator--fade"
} else {
""
};
let active = if self.props.active {
" mdc-tab-indicator--active"
} else {
""
};
let classes = format!("mdc-tab-indicator{}{}", fading, active);
html! {
<span class=classes id=&self.props.id>
<span class="mdc-tab-indicator__content mdc-tab-indicator__content--underline"></span>
</span>
}
}

fn destroy(&mut self) {}
}
46 changes: 46 additions & 0 deletions src/components/tabs/tab_scroller.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//use crate::mdc_sys::MDCTabScroller;
use yew::prelude::*;

pub struct TabScroller {
props: Props,
//inner: Option<MDCTabScroller>,
}

#[derive(Properties, Clone, PartialEq)]
pub struct Props {
pub children: Children,

#[prop_or_default]
pub id: String,
}

impl Component for TabScroller {
type Message = ();
type Properties = Props;

fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
Self { props }
}

fn mounted(&mut self) -> ShouldRender {
false
}

fn update(&mut self, _msg: Self::Message) -> ShouldRender {
false
}

fn view(&self) -> Html {
html! {
<div class="mdc-tab-scroller" id=&self.props.id>
<div class="mdc-tab-scroller__scroll-area">
<div class="mdc-tab-scroller__scroll-content">
{ self.props.children.render() }
</div>
</div>
</div>
}
}

fn destroy(&mut self) {}
}
Loading

0 comments on commit 750d5a5

Please sign in to comment.