mirror of
https://github.com/yewprint/yewprint
synced 2024-11-22 03:23:03 +00:00
Tabs (#60)
This commit is contained in:
parent
6887677ff8
commit
a1744ce86c
8 changed files with 468 additions and 2 deletions
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -10,6 +10,7 @@ mod html_select;
|
|||
mod icon;
|
||||
mod progressbar;
|
||||
mod switch;
|
||||
mod tabs;
|
||||
mod tag;
|
||||
mod text;
|
||||
mod tree;
|
||||
|
|
148
yewprint-doc/src/tabs/example.rs
Normal file
148
yewprint-doc/src/tabs/example.rs
Normal 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,
|
||||
}
|
89
yewprint-doc/src/tabs/mod.rs
Normal file
89
yewprint-doc/src/tabs/mod.rs
Normal 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>
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
215
yewprint/src/tabs.rs
Normal 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>,
|
||||
}
|
Loading…
Reference in a new issue