This commit is contained in:
Cecile Tonglet 2020-10-25 08:29:19 +01:00 committed by GitHub
parent 6887677ff8
commit a1744ce86c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 468 additions and 2 deletions

View file

@ -93,8 +93,8 @@ Roadmap
- [ ] [ResizeSensor](https://blueprintjs.com/docs/#core/components/resize-sensor)
- [ ] [Skeleton](https://blueprintjs.com/docs/#core/components/skeleton)
- [ ] [Spinner](https://blueprintjs.com/docs/#core/components/spinner)
- [ ] [Tabs](https://blueprintjs.com/docs/#core/components/tabs)
- [ ] [Tag](https://blueprintjs.com/docs/#core/components/tag)
- [x] [Tabs](https://blueprintjs.com/docs/#core/components/tabs)
- [x] [Tag](https://blueprintjs.com/docs/#core/components/tag)
- depends on: Text
- [x] [Text](https://blueprintjs.com/docs/#core/components/text)
- [x] [Tree](https://blueprintjs.com/docs/#core/components/tree)

View file

@ -39,6 +39,8 @@ fn main() {
println!("cargo:rerun-if-changed={}", path.display());
}
}
println!("cargo:rerun-if-changed=./src/lib.rs");
println!("cargo:rerun-if-changed=build.rs");
}
recursive(

View file

@ -7,6 +7,7 @@ use crate::divider::*;
use crate::html_select::*;
use crate::icon::*;
use crate::progressbar::*;
use crate::tabs::*;
use crate::tag::*;
use crate::text::*;
use crate::tree::*;
@ -167,6 +168,11 @@ impl Component for App {
href="#switch"
onclick=self.link.callback(|_| Msg::GoToMenu(DocMenu::Switch))
/>
<MenuItem
text={html!("Tabs")}
href="#tabs"
onclick=self.link.callback(|_| Msg::GoToMenu(DocMenu::Tabs))
/>
<MenuItem
text={html!("Tag")}
href="#tag"
@ -213,6 +219,7 @@ impl Component for App {
DocMenu::Tree => html!(<TreeDoc />),
DocMenu::Icon => html!(<IconDoc />),
DocMenu::ProgressBar => html!(<ProgressBarDoc />),
DocMenu::Tabs => html!(<TabsDoc />),
DocMenu::Tag => html!(<TagDoc />),
DocMenu::Menu => html!(),
}
@ -250,6 +257,8 @@ pub enum DocMenu {
ProgressBar,
#[to = "/#switch"]
Switch,
#[to = "/#tabs"]
Tabs,
#[to = "/#tag"]
Tag,
#[to = "/#text"]

View file

@ -10,6 +10,7 @@ mod html_select;
mod icon;
mod progressbar;
mod switch;
mod tabs;
mod tag;
mod text;
mod tree;

View file

@ -0,0 +1,148 @@
use yew::prelude::*;
use yewprint::{Tab, Tabs};
pub struct Example {
link: ComponentLink<Self>,
props: ExampleProps,
selected: Civilization,
}
#[derive(Clone, PartialEq, Properties)]
pub struct ExampleProps {
pub animate: bool,
pub vertical: bool,
}
impl Component for Example {
type Message = Civilization;
type Properties = ExampleProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
Example {
link,
props,
selected: Civilization::Minoan,
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
if self.selected != msg {
self.selected = msg;
true
} else {
false
}
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! {
<div>
<Tabs<Civilization>
animate=self.props.animate
vertical=self.props.vertical
selected_tab_id=self.selected
onchange=self.link.callback(|x| x)
tabs=vec![
Tab {
disabled: false,
id: Civilization::Sumer,
panel: html! { <>
<strong>{"Sumer"}</strong>
{" is the earliest known civilization in the historical \
region of southern Mesopotamia (now southern Iraq), \
emerging during the Chalcolithic and early Bronze Ages \
between the sixth and fifth millennium BCE. It is also \
one of the first civilizations in the world, along with \
Ancient Egypt, Norte Chico, Minoan civilization, Ancient \
China, Mesoamerica and the Indus Valley. Living along \
the valleys of the Tigris and Euphrates, Sumerian farmers \
grew an abundance of grain and other crops, the surplus \
from which enabled them to form urban settlements. \
Prehistoric proto-writing dates back before 3000 BCE. The \
earliest texts come from the cities of Uruk and Jemdet \
Nasr, and date to between c. 3500 and c. 3000 BCE."}
</> },
title: html!(<strong>{"Sumer"}</strong>),
panel_class: None,
title_class: None,
},
Tab {
disabled: false,
id: Civilization::Minoan,
panel: html! { <>
{"The "}<strong>{"Minoan civilization"}</strong>
{" was a Bronze Age Aegean civilization on the island \
of Crete and other Aegean Islands, flourishing from c. \
3000 BC to c. 1450 BC until a late period of decline, \
finally ending around 1100 BC. It represents the first \
advanced civilization in Europe, leaving behind massive \
building complexes, tools, artwork, writing systems, and \
a massive network of trade. The civilization was \
rediscovered at the beginning of the 20th century through \
the work of British archaeologist Sir Arthur Evans. The \
name \"Minoan\" derives from the mythical King Minos and \
was coined by Evans, who identified the site at Knossos \
with the labyrinth and the Minotaur. The Minoan \
civilization has been described as the earliest of its \
kind in Europe, and historian Will Durant called the \
Minoans \"the first link in the European chain\"."}
</> },
title: html!(<em>{"Minoan"}</em>),
panel_class: None,
title_class: None,
},
Tab {
disabled: true,
id: Civilization::AncientEgypt,
panel: html!(),
title: html!(<u>{"Ancient Egypt"}</u>),
panel_class: None,
title_class: None,
},
Tab {
disabled: false,
id: Civilization::IndusValley,
panel: html! { <>
{"The "}<strong>{"Indus Valley Civilisation (IVC)"}</strong>
{" was a Bronze Age civilisation in the northwestern \
regions of South Asia, lasting from 3300 BCE to 1300 BCE, \
and in its mature form from 2600 BCE to 1900 BCE. \
Together with ancient Egypt and Mesopotamia, it was one \
of three early civilisations of the Near East and South \
Asia, and of the three, the most widespread, its sites \
spanning an area stretching from northeast Afghanistan, \
through much of Pakistan, and into western and \
northwestern India. It flourished in the basins of the \
Indus River, which flows through the length of Pakistan, \
and along a system of perennial, mostly monsoon-fed, \
rivers that once coursed in the vicinity of the seasonal \
Ghaggar-Hakra river in northwest India and eastern \
Pakistan."}
</> },
title: html!("Indus Valley"),
panel_class: None,
title_class: None,
},
]
/>
</div>
}
}
}
#[derive(Clone, Copy, PartialEq, Hash)]
pub enum Civilization {
Sumer,
Minoan,
AncientEgypt,
IndusValley,
}

View file

@ -0,0 +1,89 @@
mod example;
use crate::ExampleContainer;
use example::*;
use yew::prelude::*;
use yewprint::{Switch, H1, H5};
pub struct TabsDoc {
callback: Callback<ExampleProps>,
state: ExampleProps,
}
impl Component for TabsDoc {
type Message = ExampleProps;
type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
TabsDoc {
callback: link.callback(|x| x),
state: ExampleProps {
animate: true,
vertical: false,
},
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
self.state = msg;
true
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
true
}
fn view(&self) -> Html {
let example_props = self.state.clone();
let source = crate::include_raw_html!(
concat!(env!("OUT_DIR"), "/", file!(), ".html"),
"bp3-code-block"
);
html! {
<div>
<H1 class="docs-title">{"Tabs"}</H1>
<div>
<ExampleContainer
source=source
props=Some(html! {
<TabsProps
callback={self.callback.clone()}
props=example_props.clone()
/>
})
>
<Example with example_props />
</ExampleContainer>
</div>
</div>
}
}
}
crate::build_example_prop_component! {
TabsProps for ExampleProps =>
fn view(&self) -> Html {
html! {
<div>
<H5>{"Props"}</H5>
<Switch
onclick=self.update_props(|props, _| ExampleProps {
animate: !props.animate,
..props
})
checked=self.props.animate
label="Animate indicator"
/>
<Switch
onclick=self.update_props(|props, _| ExampleProps {
vertical: !props.vertical,
..props
})
checked=self.props.vertical
label="Use vertical tabs"
/>
</div>
}
}
}

View file

@ -10,6 +10,7 @@ mod icon;
mod menu;
mod progressbar;
mod switch;
mod tabs;
mod tag;
mod text;
#[cfg(feature = "tree")]
@ -29,6 +30,7 @@ pub use id_tree;
pub use menu::*;
pub use progressbar::*;
pub use switch::*;
pub use tabs::*;
pub use tag::*;
pub use text::*;
#[cfg(feature = "tree")]

215
yewprint/src/tabs.rs Normal file
View file

@ -0,0 +1,215 @@
use crate::ConditionalClass;
use std::collections::{hash_map::DefaultHasher, HashMap};
use std::hash::{Hash, Hasher};
use web_sys::HtmlElement;
use yew::prelude::*;
pub struct Tabs<T: Clone + PartialEq + Hash + 'static> {
link: ComponentLink<Self>,
props: TabsProps<T>,
tab_refs: HashMap<u64, NodeRef>,
}
#[derive(Clone, PartialEq, Properties)]
pub struct TabsProps<T: Clone + PartialEq> {
#[prop_or_default]
pub animate: bool,
#[prop_or_default]
pub default_selected_tab_id: Option<T>,
#[prop_or_default]
pub id: String,
#[prop_or_default]
pub large: ConditionalClass,
#[prop_or_default]
pub render_active_panel_only: bool,
pub selected_tab_id: T,
#[prop_or_default]
pub vertical: ConditionalClass,
#[prop_or_default]
pub onchange: Callback<T>,
#[prop_or_default]
pub class: String,
pub tabs: Vec<Tab<T>>,
}
impl<T: Clone + PartialEq + Hash + 'static> Component for Tabs<T> {
type Message = ();
type Properties = TabsProps<T>;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
let tab_refs = props
.tabs
.iter()
.map(|x| {
let mut hasher = DefaultHasher::new();
x.id.hash(&mut hasher);
let id = hasher.finish();
(id, NodeRef::default())
})
.collect::<HashMap<_, _>>();
Tabs {
props,
tab_refs,
link,
}
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
true
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn rendered(&mut self, first_render: bool) {
if first_render && self.props.animate {
self.link.send_message(());
}
}
fn view(&self) -> Html {
let tabs = self
.props
.tabs
.iter()
.map(|x| {
let mut hasher = DefaultHasher::new();
x.id.hash(&mut hasher);
let id = hasher.finish();
let title_id = format!("bp3-tab-title_{}_{}", self.props.id, id);
let panel_id = format!("bp3-tab-panel_{}_{}", self.props.id, id);
let selected = self.props.selected_tab_id == x.id;
(x, id, title_id, panel_id, selected)
})
.collect::<Vec<_>>();
html! {
<div
class=(
"bp3-tabs",
self.props.vertical.map_some("bp3-vertical"),
self.props.class.clone(),
)
>
<div
class=(
"bp3-tab-list",
self.props.large.map_some("bp3-large"),
)
>
{
if self.props.animate {
let mut hasher = DefaultHasher::new();
self.props.selected_tab_id.hash(&mut hasher);
let id = hasher.finish();
if let Some(element) = self.tab_refs[&id].cast::<HtmlElement>()
{
let indicator_style = format!(
"height: {}px; width: {}px; \
transform: translateX({}px) translateY({}px);",
element.client_height(),
element.client_width(),
element.offset_left(),
element.offset_top(),
);
html! {
<div class="bp3-tab-indicator-wrapper" style=indicator_style>
<div class="bp3-tab-indicator" />
</div>
}
} else {
html!()
}
} else {
html!()
}
}
{
tabs
.iter()
.map(|(props, id, title_id, panel_id, selected)| html! {
<div
class=(
"bp3-tab",
props.title_class.clone(),
)
aria-disabled=props.disabled
aria-expanded=selected
aria-selected=selected
role="tab"
tabIndex?={
if props.disabled {
Some("0")
} else {
None
}
}
id=title_id
aria-controls=panel_id
data-tab-id=id
onclick?={
if props.disabled {
None
} else {
let tab_id = props.id.clone();
Some(self
.props
.onchange
.reform(move |_| tab_id.clone()))
}
}
key=*id
ref=self.tab_refs[&id].clone()
>
{ props.title.clone() }
</div>
})
.collect::<Html>()
}
</div>
{
tabs
.iter()
.filter(|(_, _, _, _, selected)| {
!self.props.render_active_panel_only || *selected
})
.map(|(props, id, title_id, panel_id, selected)| html! {
<div
class=(
"bp3-tab-panel",
props.panel_class.clone(),
)
aria-labelledby=title_id
aria-hidden=!selected
role="tabpanel"
id=panel_id
key=*id
>
{ props.panel.clone() }
</div>
})
.collect::<Html>()
}
</div>
}
}
}
#[derive(Clone, PartialEq)]
pub struct Tab<T> {
pub disabled: bool,
pub id: T,
pub title: Html,
pub panel: Html,
pub title_class: Option<String>,
pub panel_class: Option<String>,
}