mirror of
https://github.com/yewprint/yewprint
synced 2024-11-22 03:23:03 +00:00
Implement Dialog and Alert (#172)
This commit is contained in:
parent
993b8cc8bd
commit
ad55431263
16 changed files with 1051 additions and 114 deletions
|
@ -55,7 +55,7 @@ Roadmap
|
||||||
- [ ] [Breadcrumbs](https://blueprintjs.com/docs/#core/components/breadcrumbs)
|
- [ ] [Breadcrumbs](https://blueprintjs.com/docs/#core/components/breadcrumbs)
|
||||||
- [x] [Button](https://blueprintjs.com/docs/#core/components/button)
|
- [x] [Button](https://blueprintjs.com/docs/#core/components/button)
|
||||||
- [ ] Complete Button API
|
- [ ] Complete Button API
|
||||||
- [ ] AnchorButton
|
- [x] AnchorButton
|
||||||
- [x] [ButtonGroup](https://blueprintjs.com/docs/#core/components/button-group)
|
- [x] [ButtonGroup](https://blueprintjs.com/docs/#core/components/button-group)
|
||||||
- depends on: Button
|
- depends on: Button
|
||||||
- [x] [Callout](https://blueprintjs.com/docs/#core/components/callout)
|
- [x] [Callout](https://blueprintjs.com/docs/#core/components/callout)
|
||||||
|
@ -103,11 +103,11 @@ Roadmap
|
||||||
- [x] [Overlay](https://blueprintjs.com/docs/#core/components/overlay)
|
- [x] [Overlay](https://blueprintjs.com/docs/#core/components/overlay)
|
||||||
- depends on: Portal
|
- depends on: Portal
|
||||||
- [x] [Portal](https://blueprintjs.com/docs/#core/components/portal)
|
- [x] [Portal](https://blueprintjs.com/docs/#core/components/portal)
|
||||||
- [ ] [Alert](https://blueprintjs.com/docs/#core/components/alert)
|
- [x] [Alert](https://blueprintjs.com/docs/#core/components/alert)
|
||||||
- depends on: Button, Dialog
|
- depends on: Button, Dialog
|
||||||
- [ ] [Context menu](https://blueprintjs.com/docs/#core/components/context-menu)
|
- [ ] [Context menu](https://blueprintjs.com/docs/#core/components/context-menu)
|
||||||
- depends on: Popover
|
- depends on: Popover
|
||||||
- [ ] [Dialog](https://blueprintjs.com/docs/#core/components/dialog)
|
- [x] [Dialog](https://blueprintjs.com/docs/#core/components/dialog)
|
||||||
- depends on: Icon, Overlay, Button
|
- depends on: Icon, Overlay, Button
|
||||||
- [ ] [Drawer](https://blueprintjs.com/docs/#core/components/drawer)
|
- [ ] [Drawer](https://blueprintjs.com/docs/#core/components/drawer)
|
||||||
- depends on: Icon, Overlay, Button
|
- depends on: Icon, Overlay, Button
|
||||||
|
|
131
src/alert.rs
Normal file
131
src/alert.rs
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
use crate::{Button, Dialog, Icon, Intent};
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Alert {
|
||||||
|
cb: MsgCallbacks<Self>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Properties)]
|
||||||
|
pub struct AlertProps {
|
||||||
|
#[prop_or_default]
|
||||||
|
pub dark: Option<bool>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub class: Classes,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub style: Option<AttrValue>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub open: bool,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub icon: Option<Icon>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub intent: Option<Intent>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub loading: bool,
|
||||||
|
#[prop_or(html!("OK"))]
|
||||||
|
pub confirm_button: Html,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub cancel_button: Option<Html>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub onclose: Callback<bool>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub children: Children,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(yew_callbacks::Callbacks)]
|
||||||
|
pub enum Msg {
|
||||||
|
OnCancel,
|
||||||
|
OnConfirmClick(MouseEvent),
|
||||||
|
OnCancelClick(MouseEvent),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Alert {
|
||||||
|
type Properties = AlertProps;
|
||||||
|
type Message = Msg;
|
||||||
|
|
||||||
|
fn create(ctx: &Context<Self>) -> Self {
|
||||||
|
Self {
|
||||||
|
cb: ctx.link().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
|
let Self::Properties { onclose, .. } = ctx.props();
|
||||||
|
let Self::Properties { loading, .. } = ctx.props();
|
||||||
|
|
||||||
|
match msg {
|
||||||
|
Msg::OnCancel => {
|
||||||
|
if !loading {
|
||||||
|
onclose.emit(false);
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Msg::OnConfirmClick(_event) => {
|
||||||
|
if !loading {
|
||||||
|
onclose.emit(true);
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Msg::OnCancelClick(_event) => {
|
||||||
|
if !loading {
|
||||||
|
onclose.emit(false);
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
let Self::Properties {
|
||||||
|
dark,
|
||||||
|
class,
|
||||||
|
style,
|
||||||
|
open,
|
||||||
|
icon,
|
||||||
|
intent,
|
||||||
|
loading,
|
||||||
|
confirm_button,
|
||||||
|
cancel_button,
|
||||||
|
onclose: _,
|
||||||
|
children,
|
||||||
|
} = ctx.props();
|
||||||
|
|
||||||
|
let cancel_button_html = cancel_button.clone().map(|x| {
|
||||||
|
html! {
|
||||||
|
<Button
|
||||||
|
disabled={loading}
|
||||||
|
onclick={self.cb.on_cancel_click()}
|
||||||
|
>
|
||||||
|
{x}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<Dialog
|
||||||
|
{dark}
|
||||||
|
class={classes!("bp3-alert", class.clone())}
|
||||||
|
{style}
|
||||||
|
{open}
|
||||||
|
onclose={self.cb.on_cancel()}
|
||||||
|
>
|
||||||
|
<div class={classes!("bp3-alert-body")}>
|
||||||
|
<Icon {icon} size={40} {intent} />
|
||||||
|
<div class={classes!("bp3-alert-contents")}>
|
||||||
|
{for children.iter()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class={classes!("bp3-alert-footer")}>
|
||||||
|
<Button
|
||||||
|
{loading}
|
||||||
|
{intent}
|
||||||
|
onclick={self.cb.on_confirm_click()}
|
||||||
|
>
|
||||||
|
{confirm_button.clone()}
|
||||||
|
</Button>
|
||||||
|
{cancel_button_html}
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
242
src/buttons.rs
242
src/buttons.rs
|
@ -2,105 +2,187 @@ use crate::{Icon, IconSize, Intent, Spinner};
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew::virtual_dom::AttrValue;
|
use yew::virtual_dom::AttrValue;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Properties)]
|
macro_rules! generate_with_common_props {
|
||||||
pub struct ButtonProps {
|
(
|
||||||
#[prop_or_default]
|
$(#[$attr:meta])*
|
||||||
pub fill: bool,
|
$vis:vis struct $name:ident {
|
||||||
#[prop_or_default]
|
$(
|
||||||
pub minimal: bool,
|
$(#[$field_attr:meta])*
|
||||||
#[prop_or_default]
|
$field_vis:vis $field_name:ident : $field_ty:ty,
|
||||||
pub small: bool,
|
)*
|
||||||
#[prop_or_default]
|
}
|
||||||
pub outlined: bool,
|
) => {
|
||||||
#[prop_or_default]
|
$(#[$attr])*
|
||||||
pub loading: bool,
|
$vis struct $name {
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub large: bool,
|
pub fill: bool,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub active: bool,
|
pub minimal: bool,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub disabled: bool,
|
pub small: bool,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub icon: Option<Icon>,
|
pub outlined: bool,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub right_icon: Option<Icon>,
|
pub loading: bool,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub intent: Option<Intent>,
|
pub large: bool,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub title: Option<AttrValue>,
|
pub active: bool,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub onclick: Callback<MouseEvent>,
|
pub disabled: bool,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub class: Classes,
|
pub icon: Option<Icon>,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub style: Option<AttrValue>,
|
pub right_icon: Option<Icon>,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub button_ref: NodeRef,
|
pub intent: Option<Intent>,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub children: Children,
|
pub title: Option<AttrValue>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub class: Classes,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub style: Option<AttrValue>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub button_ref: NodeRef,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub left_element: Option<Html>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub right_element: Option<Html>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub aria_label: Option<AttrValue>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub children: Children,
|
||||||
|
|
||||||
|
$(
|
||||||
|
$(#[$field_attr])*
|
||||||
|
$field_vis $field_name: $field_ty,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $name {
|
||||||
|
fn common_classes(&self) -> Classes {
|
||||||
|
let disabled = self.disabled || self.loading;
|
||||||
|
|
||||||
|
classes!(
|
||||||
|
"bp3-button",
|
||||||
|
self.fill.then_some("bp3-fill"),
|
||||||
|
self.minimal.then_some("bp3-minimal"),
|
||||||
|
self.small.then_some("bp3-small"),
|
||||||
|
self.outlined.then_some("bp3-outlined"),
|
||||||
|
self.loading.then_some("bp3-loading"),
|
||||||
|
self.large.then_some("bp3-large"),
|
||||||
|
(self.active && !disabled).then_some("bp3-active"),
|
||||||
|
disabled.then_some("bp3-disabled"),
|
||||||
|
self.intent,
|
||||||
|
self.class.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_children(&self) -> Html {
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
self.loading
|
||||||
|
.then(|| html! {
|
||||||
|
<Spinner
|
||||||
|
class={classes!("bp3-button-spinner")}
|
||||||
|
size={IconSize::LARGE}
|
||||||
|
/>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
<Icon icon={self.icon.clone()} />
|
||||||
|
{self.left_element.clone()}
|
||||||
|
{
|
||||||
|
(!self.children.is_empty())
|
||||||
|
.then(|| html! {
|
||||||
|
<span class="bp3-button-text">
|
||||||
|
{for self.children.iter()}
|
||||||
|
</span>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
<Icon icon={self.right_icon.clone()} />
|
||||||
|
{self.right_element.clone()}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_with_common_props! {
|
||||||
|
#[derive(Clone, PartialEq, Properties)]
|
||||||
|
pub struct ButtonProps {
|
||||||
|
#[prop_or_default]
|
||||||
|
pub onclick: Callback<MouseEvent>,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[function_component(Button)]
|
#[function_component(Button)]
|
||||||
pub fn button(props: &ButtonProps) -> Html {
|
pub fn button(props: &ButtonProps) -> Html {
|
||||||
let ButtonProps {
|
let ButtonProps {
|
||||||
fill,
|
|
||||||
minimal,
|
|
||||||
small,
|
|
||||||
outlined,
|
|
||||||
loading,
|
loading,
|
||||||
large,
|
|
||||||
active,
|
|
||||||
disabled,
|
disabled,
|
||||||
icon,
|
|
||||||
right_icon,
|
|
||||||
intent,
|
|
||||||
title,
|
title,
|
||||||
onclick,
|
|
||||||
class,
|
|
||||||
style,
|
style,
|
||||||
button_ref,
|
button_ref,
|
||||||
children,
|
aria_label,
|
||||||
|
onclick,
|
||||||
|
..
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
let disabled = *disabled || *loading;
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<button
|
<button
|
||||||
class={classes!(
|
class={props.common_classes()}
|
||||||
"bp3-button",
|
|
||||||
fill.then_some("bp3-fill"),
|
|
||||||
minimal.then_some("bp3-minimal"),
|
|
||||||
small.then_some("bp3-small"),
|
|
||||||
outlined.then_some("bp3-outlined"),
|
|
||||||
loading.then_some("bp3-loading"),
|
|
||||||
large.then_some("bp3-large"),
|
|
||||||
(*active && !disabled).then_some("bp3-active"),
|
|
||||||
disabled.then_some("bp3-disabled"),
|
|
||||||
intent,
|
|
||||||
class.clone(),
|
|
||||||
)}
|
|
||||||
{style}
|
{style}
|
||||||
{title}
|
{title}
|
||||||
|
aria-label={aria_label}
|
||||||
onclick={(!disabled).then_some(onclick.clone())}
|
onclick={(!disabled).then_some(onclick.clone())}
|
||||||
ref={button_ref.clone()}
|
ref={button_ref.clone()}
|
||||||
>
|
>
|
||||||
{
|
{props.render_children()}
|
||||||
loading
|
|
||||||
.then(|| html! {
|
|
||||||
<Spinner
|
|
||||||
class={classes!("bp3-button-spinner")}
|
|
||||||
size={IconSize::LARGE}
|
|
||||||
/>
|
|
||||||
})
|
|
||||||
}
|
|
||||||
<Icon {icon} />
|
|
||||||
{
|
|
||||||
(!children.is_empty())
|
|
||||||
.then(|| html! {
|
|
||||||
<span class="bp3-button-text">
|
|
||||||
{for children.iter()}
|
|
||||||
</span>
|
|
||||||
})
|
|
||||||
}
|
|
||||||
<Icon icon={right_icon} />
|
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generate_with_common_props! {
|
||||||
|
#[derive(Clone, PartialEq, Properties)]
|
||||||
|
pub struct AnchorButtonProps {
|
||||||
|
#[prop_or_default]
|
||||||
|
pub href: AttrValue,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub target: Option<AttrValue>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component(AnchorButton)]
|
||||||
|
pub fn anchor_button(props: &AnchorButtonProps) -> Html {
|
||||||
|
let AnchorButtonProps {
|
||||||
|
loading,
|
||||||
|
disabled,
|
||||||
|
title,
|
||||||
|
style,
|
||||||
|
button_ref,
|
||||||
|
aria_label,
|
||||||
|
href,
|
||||||
|
target,
|
||||||
|
..
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
let disabled = *disabled || *loading;
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<a
|
||||||
|
class={props.common_classes()}
|
||||||
|
{style}
|
||||||
|
{title}
|
||||||
|
aria-label={aria_label}
|
||||||
|
href={(!disabled).then_some(href.clone())}
|
||||||
|
{target}
|
||||||
|
ref={button_ref.clone()}
|
||||||
|
>
|
||||||
|
{props.render_children()}
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
221
src/dialog.rs
Normal file
221
src/dialog.rs
Normal file
|
@ -0,0 +1,221 @@
|
||||||
|
use crate::{Button, Icon, IconSize, Overlay, H4};
|
||||||
|
use std::cell::Cell;
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Dialog {
|
||||||
|
title_id: AttrValue,
|
||||||
|
cb: MsgCallbacks<Self>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Properties)]
|
||||||
|
pub struct DialogProps {
|
||||||
|
#[prop_or_default]
|
||||||
|
pub dark: Option<bool>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub class: Classes,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub style: Option<AttrValue>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub open: bool,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub onclose: Callback<()>,
|
||||||
|
#[prop_or(true)]
|
||||||
|
pub show_close_button: bool,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub icon: Option<Icon>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub title: Option<Html>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub container_ref: NodeRef,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub aria_labelledby: Option<AttrValue>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub aria_describedby: Option<AttrValue>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub children: Children,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(yew_callbacks::Callbacks)]
|
||||||
|
pub enum Msg {
|
||||||
|
OnClose(MouseEvent),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Dialog {
|
||||||
|
type Properties = DialogProps;
|
||||||
|
type Message = Msg;
|
||||||
|
|
||||||
|
fn create(ctx: &Context<Self>) -> Self {
|
||||||
|
thread_local! {
|
||||||
|
static ID: Cell<usize> = Default::default();
|
||||||
|
}
|
||||||
|
let id = ID.with(|x| {
|
||||||
|
let next = x.get().wrapping_add(1);
|
||||||
|
x.replace(next)
|
||||||
|
});
|
||||||
|
let title_id = AttrValue::from(format!("title-bp-dialog-{id}"));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
title_id,
|
||||||
|
cb: ctx.link().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
|
match msg {
|
||||||
|
Msg::OnClose(_event) => {
|
||||||
|
ctx.props().onclose.emit(());
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
let Self::Properties {
|
||||||
|
dark,
|
||||||
|
class,
|
||||||
|
style,
|
||||||
|
open,
|
||||||
|
onclose,
|
||||||
|
show_close_button,
|
||||||
|
icon,
|
||||||
|
title,
|
||||||
|
container_ref,
|
||||||
|
aria_labelledby,
|
||||||
|
aria_describedby,
|
||||||
|
children,
|
||||||
|
} = ctx.props();
|
||||||
|
|
||||||
|
let aria_labelledby = aria_labelledby
|
||||||
|
.clone()
|
||||||
|
.or(title.is_some().then(|| self.title_id.clone()));
|
||||||
|
|
||||||
|
let close_button = show_close_button.then(|| {
|
||||||
|
html! {
|
||||||
|
<Button
|
||||||
|
aria_label="Close"
|
||||||
|
class={classes!("bp3-dialog-close-button")}
|
||||||
|
left_element={html!(<Icon icon={Icon::SmallCross} size={IconSize::LARGE} />)}
|
||||||
|
minimal=true
|
||||||
|
onclick={self.cb.on_close()}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let header = title.clone().map(|title| {
|
||||||
|
html! {
|
||||||
|
<div class={classes!("bp3-dialog-header")}>
|
||||||
|
<Icon {icon} size={IconSize::LARGE} aria_hidden=true tab_index={-1} />
|
||||||
|
<H4 id={&self.title_id}>{title}</H4>
|
||||||
|
{close_button}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<Overlay
|
||||||
|
{container_ref}
|
||||||
|
{dark}
|
||||||
|
class={classes!("bp3-dialog-container")}
|
||||||
|
{open}
|
||||||
|
{onclose}
|
||||||
|
scrollable=true
|
||||||
|
backdrop=true
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class={classes!("bp3-dialog", class.clone())}
|
||||||
|
role="dialog"
|
||||||
|
aria-labelledby={aria_labelledby}
|
||||||
|
aria-describedby={aria_describedby}
|
||||||
|
{style}
|
||||||
|
>
|
||||||
|
{header}
|
||||||
|
{for children.iter()}
|
||||||
|
</div>
|
||||||
|
</Overlay>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Properties)]
|
||||||
|
pub struct DialogFooterProps {
|
||||||
|
#[prop_or_default]
|
||||||
|
pub class: Classes,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub style: Option<AttrValue>,
|
||||||
|
#[prop_or(false)]
|
||||||
|
pub minimal: bool,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub actions: Option<Html>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub children: Children,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component(DialogFooter)]
|
||||||
|
pub fn dialog_footer(props: &DialogFooterProps) -> Html {
|
||||||
|
let DialogFooterProps {
|
||||||
|
class,
|
||||||
|
style,
|
||||||
|
minimal,
|
||||||
|
actions,
|
||||||
|
children,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
let actions_html = actions.clone().map(|html| {
|
||||||
|
html! {
|
||||||
|
<div class="bp3-dialog-footer-actions">{html}</div>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<div
|
||||||
|
class={classes!(
|
||||||
|
"bp3-dialog-footer",
|
||||||
|
(!minimal).then_some("bp3-dialog-footer-fixed"),
|
||||||
|
class.clone(),
|
||||||
|
)}
|
||||||
|
{style}
|
||||||
|
>
|
||||||
|
<div class="bp3-dialog-footer-main-section">
|
||||||
|
{for children.iter()}
|
||||||
|
</div>
|
||||||
|
{actions_html}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Properties)]
|
||||||
|
pub struct DialogBodyProps {
|
||||||
|
#[prop_or_default]
|
||||||
|
pub class: Classes,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub style: Option<AttrValue>,
|
||||||
|
#[prop_or(true)]
|
||||||
|
pub use_overflow_scroll_container: bool,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub children: Children,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component(DialogBody)]
|
||||||
|
pub fn dialog_body(props: &DialogBodyProps) -> Html {
|
||||||
|
let DialogBodyProps {
|
||||||
|
class,
|
||||||
|
style,
|
||||||
|
use_overflow_scroll_container,
|
||||||
|
children,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<div
|
||||||
|
role="dialogbody"
|
||||||
|
class={classes!(
|
||||||
|
"bp3-dialog-body",
|
||||||
|
use_overflow_scroll_container.then_some("bp3-dialog-body-scroll-container"),
|
||||||
|
class.clone(),
|
||||||
|
)}
|
||||||
|
{style}
|
||||||
|
>
|
||||||
|
{for children.iter()}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,8 @@ use yew::prelude::*;
|
||||||
|
|
||||||
#[derive(Properties, PartialEq)]
|
#[derive(Properties, PartialEq)]
|
||||||
pub struct ChildrenOnlyProps {
|
pub struct ChildrenOnlyProps {
|
||||||
|
#[prop_or_default]
|
||||||
|
pub id: Option<AttrValue>,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub class: Classes,
|
pub class: Classes,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
|
@ -11,9 +13,15 @@ pub struct ChildrenOnlyProps {
|
||||||
macro_rules! build_component {
|
macro_rules! build_component {
|
||||||
($name:ident, $tag:tt, $class:literal) => {
|
($name:ident, $tag:tt, $class:literal) => {
|
||||||
#[function_component($name)]
|
#[function_component($name)]
|
||||||
pub fn $tag(ChildrenOnlyProps { class, children }: &ChildrenOnlyProps) -> Html {
|
pub fn $tag(
|
||||||
|
ChildrenOnlyProps {
|
||||||
|
id,
|
||||||
|
class,
|
||||||
|
children,
|
||||||
|
}: &ChildrenOnlyProps,
|
||||||
|
) -> Html {
|
||||||
html! {
|
html! {
|
||||||
<$tag class={classes!($class, class.clone())}>
|
<$tag {id} class={classes!($class, class.clone())}>
|
||||||
{children.clone()}
|
{children.clone()}
|
||||||
</$tag>
|
</$tag>
|
||||||
}
|
}
|
||||||
|
|
16
src/icon.rs
16
src/icon.rs
|
@ -73,6 +73,12 @@ impl IntoPropValue<f64> for IconSize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IntoPropValue<IconSize> for Option<IconSize> {
|
||||||
|
fn into_prop_value(self) -> IconSize {
|
||||||
|
self.unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Icon {
|
impl Default for Icon {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
@ -97,6 +103,8 @@ impl Icon {
|
||||||
intent,
|
intent,
|
||||||
size,
|
size,
|
||||||
onclick,
|
onclick,
|
||||||
|
aria_hidden,
|
||||||
|
tab_index,
|
||||||
}: &IconProps,
|
}: &IconProps,
|
||||||
) -> Html {
|
) -> Html {
|
||||||
if let Icon::Custom(html) = icon {
|
if let Icon::Custom(html) = icon {
|
||||||
|
@ -117,10 +125,14 @@ impl Icon {
|
||||||
let width = AttrValue::from(format!("{size}"));
|
let width = AttrValue::from(format!("{size}"));
|
||||||
let height = width.clone();
|
let height = width.clone();
|
||||||
|
|
||||||
|
let aria_hidden = aria_hidden.or(title.is_some().then_some(true));
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<span
|
<span
|
||||||
class={classes!("bp3-icon", class.clone(), intent)}
|
class={classes!("bp3-icon", class.clone(), intent)}
|
||||||
{onclick}
|
{onclick}
|
||||||
|
aria-hidden={aria_hidden.and_then(|x| x.then_some(AttrValue::Static("true")))}
|
||||||
|
tabIndex={tab_index.map(|x| x.to_string())}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
{fill}
|
{fill}
|
||||||
|
@ -159,6 +171,10 @@ pub struct IconProps {
|
||||||
pub size: IconSize,
|
pub size: IconSize,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub onclick: Callback<MouseEvent>,
|
pub onclick: Callback<MouseEvent>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub aria_hidden: Option<bool>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub tab_index: Option<isize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Icon {
|
impl Component for Icon {
|
||||||
|
|
63
src/lib.rs
63
src/lib.rs
|
@ -5,9 +5,11 @@
|
||||||
clippy::type_complexity,
|
clippy::type_complexity,
|
||||||
clippy::derive_partial_eq_without_eq,
|
clippy::derive_partial_eq_without_eq,
|
||||||
clippy::uninlined_format_args,
|
clippy::uninlined_format_args,
|
||||||
clippy::derivable_impls
|
clippy::derivable_impls,
|
||||||
|
clippy::enum_variant_names
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
mod alert;
|
||||||
mod button_group;
|
mod button_group;
|
||||||
mod buttons;
|
mod buttons;
|
||||||
mod callout;
|
mod callout;
|
||||||
|
@ -15,6 +17,7 @@ mod card;
|
||||||
mod checkbox;
|
mod checkbox;
|
||||||
mod collapse;
|
mod collapse;
|
||||||
mod control_group;
|
mod control_group;
|
||||||
|
mod dialog;
|
||||||
mod divider;
|
mod divider;
|
||||||
mod html_elements;
|
mod html_elements;
|
||||||
mod html_select;
|
mod html_select;
|
||||||
|
@ -38,6 +41,7 @@ mod text_area;
|
||||||
#[cfg(feature = "tree")]
|
#[cfg(feature = "tree")]
|
||||||
mod tree;
|
mod tree;
|
||||||
|
|
||||||
|
pub use alert::*;
|
||||||
pub use button_group::*;
|
pub use button_group::*;
|
||||||
pub use buttons::*;
|
pub use buttons::*;
|
||||||
pub use callout::*;
|
pub use callout::*;
|
||||||
|
@ -45,6 +49,7 @@ pub use card::*;
|
||||||
pub use checkbox::*;
|
pub use checkbox::*;
|
||||||
pub use collapse::*;
|
pub use collapse::*;
|
||||||
pub use control_group::*;
|
pub use control_group::*;
|
||||||
|
pub use dialog::*;
|
||||||
pub use divider::*;
|
pub use divider::*;
|
||||||
pub use html_elements::*;
|
pub use html_elements::*;
|
||||||
pub use html_select::*;
|
pub use html_select::*;
|
||||||
|
@ -71,6 +76,8 @@ pub use text_area::*;
|
||||||
pub use tree::*;
|
pub use tree::*;
|
||||||
|
|
||||||
use implicit_clone::ImplicitClone;
|
use implicit_clone::ImplicitClone;
|
||||||
|
use std::cell::Cell;
|
||||||
|
use yew::classes;
|
||||||
use yew::Classes;
|
use yew::Classes;
|
||||||
|
|
||||||
// See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
|
// See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
|
||||||
|
@ -180,3 +187,57 @@ impl From<&Elevation> for Classes {
|
||||||
Self::from(*elevation)
|
Self::from(*elevation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Dark;
|
||||||
|
|
||||||
|
impl Dark {
|
||||||
|
pub fn with<T>(&self, f: impl FnOnce(&Cell<bool>) -> T) -> T {
|
||||||
|
thread_local! {
|
||||||
|
static DARK: Cell<bool> = {
|
||||||
|
Cell::new(web_sys::window()
|
||||||
|
.and_then(|x| x.match_media("(prefers-color-scheme: dark)").ok().flatten())
|
||||||
|
.map(|x| x.matches())
|
||||||
|
.unwrap_or(true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DARK.with(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self) -> bool {
|
||||||
|
self.with(|x| x.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&self, value: bool) {
|
||||||
|
self.with(|x| x.set(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn replace(&self, value: bool) -> bool {
|
||||||
|
self.with(|x| x.replace(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle(&self) -> bool {
|
||||||
|
self.with(|x| {
|
||||||
|
let value = x.get();
|
||||||
|
x.set(!value);
|
||||||
|
value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn classes(&self) -> Classes {
|
||||||
|
self.classes_with_override(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn classes_with_override(&self, force: impl Into<Option<bool>>) -> Classes {
|
||||||
|
if force.into().unwrap_or(self.get()) {
|
||||||
|
thread_local! {
|
||||||
|
static CLASSES: Classes = classes!("bp3-dark");
|
||||||
|
}
|
||||||
|
CLASSES.with(|x| x.clone())
|
||||||
|
} else {
|
||||||
|
thread_local! {
|
||||||
|
static CLASSES: Classes = classes!();
|
||||||
|
}
|
||||||
|
CLASSES.with(|x| x.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::Portal;
|
use crate::{Dark, Portal};
|
||||||
use gloo::timers::callback::Timeout;
|
use gloo::timers::callback::Timeout;
|
||||||
use wasm_bindgen::closure::Closure;
|
use wasm_bindgen::closure::Closure;
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
|
@ -25,10 +25,15 @@ pub struct Overlay {
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Properties)]
|
#[derive(Debug, PartialEq, Properties)]
|
||||||
pub struct OverlayProps {
|
pub struct OverlayProps {
|
||||||
|
#[prop_or_default]
|
||||||
|
pub dark: Option<bool>,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub class: Classes,
|
pub class: Classes,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub style: Option<AttrValue>,
|
pub style: Option<AttrValue>,
|
||||||
|
// NOTE: this should have been false by default
|
||||||
|
#[prop_or(true)]
|
||||||
|
pub scrollable: bool,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub open: bool,
|
pub open: bool,
|
||||||
#[prop_or(true)]
|
#[prop_or(true)]
|
||||||
|
@ -36,6 +41,8 @@ pub struct OverlayProps {
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub onclose: Callback<()>,
|
pub onclose: Callback<()>,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
|
pub container_ref: NodeRef,
|
||||||
|
#[prop_or_default]
|
||||||
pub children: Children,
|
pub children: Children,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,11 +190,14 @@ impl Component for Overlay {
|
||||||
|
|
||||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
let Self::Properties {
|
let Self::Properties {
|
||||||
|
dark,
|
||||||
class,
|
class,
|
||||||
style,
|
style,
|
||||||
|
scrollable,
|
||||||
open,
|
open,
|
||||||
backdrop,
|
backdrop,
|
||||||
onclose: _,
|
onclose: _,
|
||||||
|
container_ref,
|
||||||
children,
|
children,
|
||||||
} = ctx.props();
|
} = ctx.props();
|
||||||
|
|
||||||
|
@ -230,10 +240,12 @@ impl Component for Overlay {
|
||||||
html! {
|
html! {
|
||||||
<Portal>
|
<Portal>
|
||||||
<div
|
<div
|
||||||
|
ref={container_ref}
|
||||||
class={classes!(
|
class={classes!(
|
||||||
"bp3-overlay",
|
"bp3-overlay",
|
||||||
"bp3-overlay-scroll-container",
|
scrollable.then_some("bp3-overlay-scroll-container"),
|
||||||
open.then_some("bp3-overlay-open"),
|
open.then_some("bp3-overlay-open"),
|
||||||
|
Dark.classes_with_override(*dark),
|
||||||
)}
|
)}
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
onkeydown={ctx.link().callback(Msg::OnKeyDown)}
|
onkeydown={ctx.link().callback(Msg::OnKeyDown)}
|
||||||
|
|
106
yewprint-doc/src/alert/example.rs
Normal file
106
yewprint-doc/src/alert/example.rs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
use gloo::timers::callback::Timeout;
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yewprint::{Alert, Button, Icon, Intent};
|
||||||
|
|
||||||
|
pub struct Example {
|
||||||
|
open_error: bool,
|
||||||
|
open_deletion: bool,
|
||||||
|
loading: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Properties)]
|
||||||
|
pub struct ExampleProps {
|
||||||
|
pub will_load: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Msg {
|
||||||
|
OpenFileError,
|
||||||
|
OpenFileDeletion,
|
||||||
|
Close(bool),
|
||||||
|
FinishClosing,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Example {
|
||||||
|
type Message = Msg;
|
||||||
|
type Properties = ExampleProps;
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Example {
|
||||||
|
open_error: false,
|
||||||
|
open_deletion: false,
|
||||||
|
loading: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
|
let Self::Properties { will_load, .. } = *ctx.props();
|
||||||
|
|
||||||
|
match msg {
|
||||||
|
Msg::OpenFileError => {
|
||||||
|
self.loading = false;
|
||||||
|
self.open_error = true;
|
||||||
|
}
|
||||||
|
Msg::OpenFileDeletion => {
|
||||||
|
self.loading = false;
|
||||||
|
self.open_deletion = true;
|
||||||
|
}
|
||||||
|
Msg::Close(res) => {
|
||||||
|
if res {
|
||||||
|
if will_load {
|
||||||
|
self.loading = true;
|
||||||
|
let callback = ctx.link().callback(|()| Msg::FinishClosing);
|
||||||
|
Timeout::new(2000, move || callback.emit(())).forget();
|
||||||
|
} else {
|
||||||
|
self.open_error = false;
|
||||||
|
self.open_deletion = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.open_error = false;
|
||||||
|
self.open_deletion = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Msg::FinishClosing => {
|
||||||
|
self.open_error = false;
|
||||||
|
self.open_deletion = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
onclick={ctx.link().callback(|_| Msg::OpenFileError)}
|
||||||
|
>
|
||||||
|
{"Open file error alert"}
|
||||||
|
</Button>
|
||||||
|
<Alert
|
||||||
|
open={self.open_error}
|
||||||
|
onclose={ctx.link().callback(|res| Msg::Close(res))}
|
||||||
|
loading={self.loading}
|
||||||
|
>
|
||||||
|
<p>{"Couldn't create the file because the containing folder doesn't \
|
||||||
|
exist anymore. You will be assimilated."}</p>
|
||||||
|
</Alert>
|
||||||
|
<Button
|
||||||
|
onclick={ctx.link().callback(|_| Msg::OpenFileDeletion)}
|
||||||
|
>
|
||||||
|
{"Open file deletion alert"}
|
||||||
|
</Button>
|
||||||
|
<Alert
|
||||||
|
open={self.open_deletion}
|
||||||
|
onclose={ctx.link().callback(|res| Msg::Close(res))}
|
||||||
|
loading={self.loading}
|
||||||
|
icon={Icon::Trash}
|
||||||
|
intent={Intent::Danger}
|
||||||
|
confirm_button={html!("Move to Trash")}
|
||||||
|
cancel_button={html!("Cancel")}
|
||||||
|
>
|
||||||
|
<p>{"Are you sure you want to move "}<b>{"filename"}</b>{" to Trash? \
|
||||||
|
You will be able to restore it later, but it will become Borg."}</p>
|
||||||
|
</Alert>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
79
yewprint-doc/src/alert/mod.rs
Normal file
79
yewprint-doc/src/alert/mod.rs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
mod example;
|
||||||
|
|
||||||
|
use crate::ExampleContainer;
|
||||||
|
use example::*;
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yewprint::{Switch, H1, H5};
|
||||||
|
|
||||||
|
pub struct AlertDoc {
|
||||||
|
callback: Callback<ExampleProps>,
|
||||||
|
state: ExampleProps,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for AlertDoc {
|
||||||
|
type Message = ExampleProps;
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(ctx: &Context<Self>) -> Self {
|
||||||
|
AlertDoc {
|
||||||
|
callback: ctx.link().callback(|x| x),
|
||||||
|
state: ExampleProps { will_load: true },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
|
self.state = msg;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
let example_props = self.state.clone();
|
||||||
|
let source = crate::include_raw_html!(
|
||||||
|
concat!(env!("OUT_DIR"), "/", file!(), ".html"),
|
||||||
|
"bp3-code-block"
|
||||||
|
);
|
||||||
|
|
||||||
|
let props_component = html! {
|
||||||
|
<AlertProps
|
||||||
|
callback={self.callback.clone()}
|
||||||
|
example_props={example_props.clone()}
|
||||||
|
/>
|
||||||
|
};
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<div>
|
||||||
|
<H1 class={classes!("docs-title")}>{"Alert"}</H1>
|
||||||
|
<SourceCodeUrl />
|
||||||
|
<div>
|
||||||
|
<ExampleContainer
|
||||||
|
source={source}
|
||||||
|
props={Some(props_component)}
|
||||||
|
>
|
||||||
|
<Example ..example_props />
|
||||||
|
</ExampleContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::build_example_prop_component! {
|
||||||
|
AlertProps for ExampleProps =>
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
<div>
|
||||||
|
<H5>{"Props"}</H5>
|
||||||
|
<Switch
|
||||||
|
onclick={self.update_props(ctx, |props, _| ExampleProps {
|
||||||
|
will_load: !props.will_load,
|
||||||
|
..props
|
||||||
|
})}
|
||||||
|
checked={ctx.props().example_props.will_load}
|
||||||
|
label={html!("Does alert use loading state")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::build_source_code_component!();
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::alert::*;
|
||||||
use crate::button_group::*;
|
use crate::button_group::*;
|
||||||
use crate::buttons::*;
|
use crate::buttons::*;
|
||||||
use crate::callout::*;
|
use crate::callout::*;
|
||||||
|
@ -5,6 +6,7 @@ use crate::card::*;
|
||||||
use crate::checkbox::*;
|
use crate::checkbox::*;
|
||||||
use crate::collapse::*;
|
use crate::collapse::*;
|
||||||
use crate::control_group::*;
|
use crate::control_group::*;
|
||||||
|
use crate::dialog::*;
|
||||||
use crate::divider::*;
|
use crate::divider::*;
|
||||||
use crate::html_select::*;
|
use crate::html_select::*;
|
||||||
use crate::icon::*;
|
use crate::icon::*;
|
||||||
|
@ -23,10 +25,9 @@ use crate::tag::*;
|
||||||
use crate::text::*;
|
use crate::text::*;
|
||||||
use crate::text_area::*;
|
use crate::text_area::*;
|
||||||
use crate::tree::*;
|
use crate::tree::*;
|
||||||
use crate::DARK;
|
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew_router::prelude::*;
|
use yew_router::prelude::*;
|
||||||
use yewprint::{Icon, Menu, MenuItem};
|
use yewprint::{Dark, Icon, Menu, MenuItem};
|
||||||
|
|
||||||
#[function_component(AppRoot)]
|
#[function_component(AppRoot)]
|
||||||
pub fn app_root() -> Html {
|
pub fn app_root() -> Html {
|
||||||
|
@ -55,7 +56,7 @@ impl Component for App {
|
||||||
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
match msg {
|
match msg {
|
||||||
Msg::ToggleLight => {
|
Msg::ToggleLight => {
|
||||||
DARK.with(|x| x.replace(!x.get()));
|
Dark.toggle();
|
||||||
}
|
}
|
||||||
Msg::GoToMenu(event, doc_menu) => {
|
Msg::GoToMenu(event, doc_menu) => {
|
||||||
event.prevent_default();
|
event.prevent_default();
|
||||||
|
@ -70,7 +71,7 @@ impl Component for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
let dark = DARK.with(|x| x.get());
|
let dark = Dark.get();
|
||||||
|
|
||||||
let netlify_badge = if dark {
|
let netlify_badge = if dark {
|
||||||
"https://www.netlify.com/img/global/badges/netlify-color-accent.svg"
|
"https://www.netlify.com/img/global/badges/netlify-color-accent.svg"
|
||||||
|
@ -88,6 +89,12 @@ impl Component for App {
|
||||||
.callback(|_| Msg::ToggleLight)}
|
.callback(|_| Msg::ToggleLight)}
|
||||||
icon={go_to_theme_icon}
|
icon={go_to_theme_icon}
|
||||||
/>
|
/>
|
||||||
|
<MenuItem
|
||||||
|
text={html!("Alert")}
|
||||||
|
href="/alert"
|
||||||
|
onclick={ctx.link()
|
||||||
|
.callback(|e| Msg::GoToMenu(e, DocMenu::Alert))}
|
||||||
|
/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
text={html!("Button")}
|
text={html!("Button")}
|
||||||
href="/button"
|
href="/button"
|
||||||
|
@ -130,6 +137,12 @@ impl Component for App {
|
||||||
onclick={ctx.link()
|
onclick={ctx.link()
|
||||||
.callback(|e| Msg::GoToMenu(e, DocMenu::ControlGroup))}
|
.callback(|e| Msg::GoToMenu(e, DocMenu::ControlGroup))}
|
||||||
/>
|
/>
|
||||||
|
<MenuItem
|
||||||
|
text={html!("Dialog")}
|
||||||
|
href="/dialog"
|
||||||
|
onclick={ctx.link()
|
||||||
|
.callback(|e| Msg::GoToMenu(e, DocMenu::Dialog))}
|
||||||
|
/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
text={html!("Divider")}
|
text={html!("Divider")}
|
||||||
href="/divider"
|
href="/divider"
|
||||||
|
@ -276,7 +289,7 @@ impl Component for App {
|
||||||
};
|
};
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div class={classes!("docs-root", dark.then_some("bp3-dark"))}>
|
<div class={classes!("docs-root", Dark.classes())}>
|
||||||
<div class={classes!("docs-app")}>
|
<div class={classes!("docs-app")}>
|
||||||
{{ navigation }}
|
{{ navigation }}
|
||||||
<main class={classes!("docs-content-wrapper")} role="main">
|
<main class={classes!("docs-content-wrapper")} role="main">
|
||||||
|
@ -292,6 +305,7 @@ impl Component for App {
|
||||||
|
|
||||||
fn switch(route: DocMenu) -> Html {
|
fn switch(route: DocMenu) -> Html {
|
||||||
match route {
|
match route {
|
||||||
|
DocMenu::Alert => html! (<AlertDoc />),
|
||||||
DocMenu::Button | DocMenu::Home => html! (<ButtonDoc />),
|
DocMenu::Button | DocMenu::Home => html! (<ButtonDoc />),
|
||||||
DocMenu::ButtonGroup => html! (<ButtonGroupDoc />),
|
DocMenu::ButtonGroup => html! (<ButtonGroupDoc />),
|
||||||
DocMenu::Callout => html!(<CalloutDoc />),
|
DocMenu::Callout => html!(<CalloutDoc />),
|
||||||
|
@ -299,6 +313,7 @@ fn switch(route: DocMenu) -> Html {
|
||||||
DocMenu::Checkbox => html!(<CheckboxDoc />),
|
DocMenu::Checkbox => html!(<CheckboxDoc />),
|
||||||
DocMenu::Collapse => html!(<CollapseDoc />),
|
DocMenu::Collapse => html!(<CollapseDoc />),
|
||||||
DocMenu::ControlGroup => html!(<ControlGroupDoc />),
|
DocMenu::ControlGroup => html!(<ControlGroupDoc />),
|
||||||
|
DocMenu::Dialog => html! (<DialogDoc />),
|
||||||
DocMenu::Divider => html!(<DividerDoc />),
|
DocMenu::Divider => html!(<DividerDoc />),
|
||||||
DocMenu::HtmlSelect => html!(<HtmlSelectDoc />),
|
DocMenu::HtmlSelect => html!(<HtmlSelectDoc />),
|
||||||
DocMenu::Icon => html!(<IconDoc />),
|
DocMenu::Icon => html!(<IconDoc />),
|
||||||
|
@ -322,6 +337,8 @@ fn switch(route: DocMenu) -> Html {
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Clone, Copy, Routable)]
|
#[derive(PartialEq, Eq, Hash, Clone, Copy, Routable)]
|
||||||
pub enum DocMenu {
|
pub enum DocMenu {
|
||||||
|
#[at("/alert")]
|
||||||
|
Alert,
|
||||||
#[at("/button-group")]
|
#[at("/button-group")]
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
#[at("/button")]
|
#[at("/button")]
|
||||||
|
@ -336,10 +353,12 @@ pub enum DocMenu {
|
||||||
Collapse,
|
Collapse,
|
||||||
#[at("/control-group")]
|
#[at("/control-group")]
|
||||||
ControlGroup,
|
ControlGroup,
|
||||||
#[at("/html-select")]
|
#[at("/dialog")]
|
||||||
HtmlSelect,
|
Dialog,
|
||||||
#[at("/divider")]
|
#[at("/divider")]
|
||||||
Divider,
|
Divider,
|
||||||
|
#[at("/html-select")]
|
||||||
|
HtmlSelect,
|
||||||
#[at("/icon")]
|
#[at("/icon")]
|
||||||
Icon,
|
Icon,
|
||||||
#[at("/input-group")]
|
#[at("/input-group")]
|
||||||
|
|
177
yewprint-doc/src/dialog/example.rs
Normal file
177
yewprint-doc/src/dialog/example.rs
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yewprint::{AnchorButton, Button, Dialog, DialogBody, DialogFooter, Icon, Intent};
|
||||||
|
|
||||||
|
pub struct Example {
|
||||||
|
show_dialog: bool,
|
||||||
|
show_dialog_with_title: bool,
|
||||||
|
show_dialog_with_title_and_footer: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Properties)]
|
||||||
|
pub struct ExampleProps {}
|
||||||
|
|
||||||
|
pub enum Msg {
|
||||||
|
ShowDialog,
|
||||||
|
ShowDialogWithTitle,
|
||||||
|
ShowDialogWithTitleAndFooter,
|
||||||
|
Close,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Example {
|
||||||
|
type Message = Msg;
|
||||||
|
type Properties = ExampleProps;
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Example {
|
||||||
|
show_dialog: false,
|
||||||
|
show_dialog_with_title: false,
|
||||||
|
show_dialog_with_title_and_footer: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
|
match msg {
|
||||||
|
Msg::ShowDialog => {
|
||||||
|
self.show_dialog = true;
|
||||||
|
}
|
||||||
|
Msg::ShowDialogWithTitle => {
|
||||||
|
self.show_dialog_with_title = true;
|
||||||
|
}
|
||||||
|
Msg::ShowDialogWithTitleAndFooter => {
|
||||||
|
self.show_dialog_with_title_and_footer = true;
|
||||||
|
}
|
||||||
|
Msg::Close => {
|
||||||
|
self.show_dialog = false;
|
||||||
|
self.show_dialog_with_title = false;
|
||||||
|
self.show_dialog_with_title_and_footer = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
onclick={ctx.link().callback(|_| Msg::ShowDialog)}
|
||||||
|
>
|
||||||
|
{"Show dialog"}
|
||||||
|
</Button>
|
||||||
|
<Dialog
|
||||||
|
open={self.show_dialog}
|
||||||
|
onclose={ctx.link().callback(|_| Msg::Close)}
|
||||||
|
>
|
||||||
|
<div style="margin: 0px 20px;">
|
||||||
|
<div class="bp3-popover2-target" aria-haspopup="true">
|
||||||
|
<InterestingDialogBody />
|
||||||
|
<VisitChatGptWebsiteButton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
<Button
|
||||||
|
onclick={ctx.link().callback(|_| Msg::ShowDialogWithTitle)}
|
||||||
|
>
|
||||||
|
{"Show dialog with title"}
|
||||||
|
</Button>
|
||||||
|
<Dialog
|
||||||
|
open={self.show_dialog_with_title}
|
||||||
|
onclose={ctx.link().callback(|_| Msg::Close)}
|
||||||
|
title={html!("Embracing disruptive approach")}
|
||||||
|
icon={Icon::InfoSign}
|
||||||
|
>
|
||||||
|
<div style="margin: 0px 20px;">
|
||||||
|
<div class="bp3-popover2-target" aria-haspopup="true">
|
||||||
|
<InterestingDialogBody />
|
||||||
|
<VisitChatGptWebsiteButton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
<Button
|
||||||
|
onclick={ctx.link().callback(|_| Msg::ShowDialogWithTitleAndFooter)}
|
||||||
|
>
|
||||||
|
{"Show dialog with title and footer"}
|
||||||
|
</Button>
|
||||||
|
<Dialog
|
||||||
|
open={self.show_dialog_with_title_and_footer}
|
||||||
|
onclose={ctx.link().callback(|_| Msg::Close)}
|
||||||
|
title={html!("Embracing disruptive approach")}
|
||||||
|
icon={Icon::InfoSign}
|
||||||
|
>
|
||||||
|
<InterestingDialogBody />
|
||||||
|
<DialogFooter
|
||||||
|
actions={html! {
|
||||||
|
<>
|
||||||
|
<Button onclick={ctx.link().callback(|_| Msg::Close)}>
|
||||||
|
{"Close"}
|
||||||
|
</Button>
|
||||||
|
<VisitChatGptWebsiteButton fill=false />
|
||||||
|
</>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component(InterestingDialogBody)]
|
||||||
|
pub fn interesting_content(_: &()) -> Html {
|
||||||
|
html! {
|
||||||
|
<DialogBody>
|
||||||
|
<p><strong>{"Our transformative solution optimizes paperclip management \
|
||||||
|
through advanced technology, revolutionizing office supply logistics and \
|
||||||
|
driving operational efficiency."}</strong></p>
|
||||||
|
<p>{"Leveraging synergistic ideation and cutting-edge paradigm shifts,
|
||||||
|
our groundbreaking solution addresses the pervasive issue of inefficient \
|
||||||
|
paperclip utilization. We recognize the urgent need to optimize paperclip
|
||||||
|
distribution and deployment, revolutionizing the way organizations engage
|
||||||
|
with this crucial office asset. By seamlessly integrating blockchain
|
||||||
|
technology, artificial intelligence algorithms, and quantum-inspired
|
||||||
|
computing, our transformative platform offers a paradigmatic shift in the
|
||||||
|
paperclip management landscape."}</p>
|
||||||
|
<p>{"Our innovative solution streamlines paperclip logistics through a \
|
||||||
|
gamified interface that incentivizes employees to achieve maximum paperclip
|
||||||
|
productivity. Leveraging the power of machine learning, our algorithm
|
||||||
|
predicts demand patterns, ensuring optimal inventory levels and eliminating
|
||||||
|
wasteful overstocking. Furthermore, our blockchain-based tracking system
|
||||||
|
guarantees immutable traceability, empowering organizations to perform
|
||||||
|
data-driven analysis on paperclip utilization, leading to unprecedented
|
||||||
|
insights and cost-saving opportunities."}</p>
|
||||||
|
<p>{"By embracing this disruptive approach, organizations can transcend \
|
||||||
|
traditional paperclip constraints and unlock a new era of operational
|
||||||
|
efficiency. Our solution not only resolves the current paperclip dilemma
|
||||||
|
but also paves the way for a sustainable future, reducing environmental
|
||||||
|
impact through our proprietary green paperclip technology. Join us in the
|
||||||
|
paperclip revolution and embrace the future of office supplies where
|
||||||
|
mundane tasks are transformed into innovative experiences that drive
|
||||||
|
productivity and fuel growth."}</p>
|
||||||
|
<p>{"Unlock the power of seamless paperclip management with our innovative
|
||||||
|
solution - experience unparalleled efficiency and cost-saving opportunities
|
||||||
|
for your organization. Try it today and revolutionize your office supply
|
||||||
|
logistics!"}</p>
|
||||||
|
</DialogBody>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Properties)]
|
||||||
|
pub struct VisitChatGptWebsiteButtonProps {
|
||||||
|
#[prop_or(true)]
|
||||||
|
pub fill: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component(VisitChatGptWebsiteButton)]
|
||||||
|
pub fn visit_chat_gpt_website_button(
|
||||||
|
VisitChatGptWebsiteButtonProps { fill }: &VisitChatGptWebsiteButtonProps,
|
||||||
|
) -> Html {
|
||||||
|
html! {
|
||||||
|
<AnchorButton
|
||||||
|
href="https://chat.openai.com/"
|
||||||
|
target="_blank"
|
||||||
|
{fill}
|
||||||
|
intent={Intent::Primary}
|
||||||
|
icon={Icon::Share}
|
||||||
|
>
|
||||||
|
{"Generate all sorts of bs by visiting ChatGPT"}
|
||||||
|
</AnchorButton>
|
||||||
|
}
|
||||||
|
}
|
42
yewprint-doc/src/dialog/mod.rs
Normal file
42
yewprint-doc/src/dialog/mod.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
mod example;
|
||||||
|
|
||||||
|
use crate::ExampleContainer;
|
||||||
|
use example::*;
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yewprint::H1;
|
||||||
|
|
||||||
|
pub struct DialogDoc {
|
||||||
|
callback: Callback<ExampleProps>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for DialogDoc {
|
||||||
|
type Message = ExampleProps;
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(ctx: &Context<Self>) -> Self {
|
||||||
|
DialogDoc {
|
||||||
|
callback: ctx.link().callback(|x| x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
let source = crate::include_raw_html!(
|
||||||
|
concat!(env!("OUT_DIR"), "/", file!(), ".html"),
|
||||||
|
"bp3-code-block"
|
||||||
|
);
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<div>
|
||||||
|
<H1 class={classes!("docs-title")}>{"Dialog"}</H1>
|
||||||
|
<SourceCodeUrl />
|
||||||
|
<div>
|
||||||
|
<ExampleContainer source={source}>
|
||||||
|
<Example />
|
||||||
|
</ExampleContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::build_source_code_component!();
|
|
@ -8,6 +8,7 @@
|
||||||
clippy::uninlined_format_args
|
clippy::uninlined_format_args
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
mod alert;
|
||||||
mod app;
|
mod app;
|
||||||
mod button_group;
|
mod button_group;
|
||||||
mod buttons;
|
mod buttons;
|
||||||
|
@ -16,6 +17,7 @@ mod card;
|
||||||
mod checkbox;
|
mod checkbox;
|
||||||
mod collapse;
|
mod collapse;
|
||||||
mod control_group;
|
mod control_group;
|
||||||
|
mod dialog;
|
||||||
mod divider;
|
mod divider;
|
||||||
mod example;
|
mod example;
|
||||||
mod html_select;
|
mod html_select;
|
||||||
|
@ -40,16 +42,6 @@ mod tree;
|
||||||
pub use app::*;
|
pub use app::*;
|
||||||
pub use example::*;
|
pub use example::*;
|
||||||
pub use logo::*;
|
pub use logo::*;
|
||||||
use std::cell::Cell;
|
|
||||||
|
|
||||||
thread_local! {
|
|
||||||
pub static DARK: Cell<bool> = {
|
|
||||||
Cell::new(web_sys::window()
|
|
||||||
.and_then(|x| x.match_media("(prefers-color-scheme: dark)").ok().flatten())
|
|
||||||
.map(|x| x.matches())
|
|
||||||
.unwrap_or(true))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! include_raw_html {
|
macro_rules! include_raw_html {
|
||||||
|
|
|
@ -15,10 +15,6 @@ impl Component for MenuDoc {
|
||||||
MenuDoc
|
MenuDoc
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, _ctx: &Context<Self>, _msg: Self::Message) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
let source = crate::include_raw_html!(
|
let source = crate::include_raw_html!(
|
||||||
concat!(env!("OUT_DIR"), "/", file!(), ".html"),
|
concat!(env!("OUT_DIR"), "/", file!(), ".html"),
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
use crate::DARK;
|
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yewprint::{Button, Card, Elevation, Icon, Intent, Overlay, H3};
|
use yewprint::{Button, Card, Elevation, Icon, Intent, Overlay, H3};
|
||||||
|
|
||||||
pub struct Example {
|
pub struct Example {
|
||||||
open: bool,
|
open: bool,
|
||||||
tall: bool,
|
tall: bool,
|
||||||
show_button_ref: NodeRef,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Properties)]
|
#[derive(Clone, PartialEq, Properties)]
|
||||||
|
@ -27,7 +25,6 @@ impl Component for Example {
|
||||||
Example {
|
Example {
|
||||||
open: false,
|
open: false,
|
||||||
tall: false,
|
tall: false,
|
||||||
show_button_ref: NodeRef::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +51,6 @@ impl Component for Example {
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
onclick={ctx.link().callback(|_| Msg::Open)}
|
onclick={ctx.link().callback(|_| Msg::Open)}
|
||||||
button_ref={self.show_button_ref.clone()}
|
|
||||||
>
|
>
|
||||||
{"Show overlay"}
|
{"Show overlay"}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -63,7 +59,6 @@ impl Component for Example {
|
||||||
onclose={ctx.link().callback(|_| Msg::Close)}
|
onclose={ctx.link().callback(|_| Msg::Close)}
|
||||||
{backdrop}
|
{backdrop}
|
||||||
class={classes!(
|
class={classes!(
|
||||||
DARK.with(|x| x.get().then_some("bp3-dark")),
|
|
||||||
self.tall.then_some("docs-overlay-example-tall"),
|
self.tall.then_some("docs-overlay-example-tall"),
|
||||||
)}
|
)}
|
||||||
style="left: calc(50vw - 200px); margin: 10vh 0; top: 0; width: 400px;"
|
style="left: calc(50vw - 200px); margin: 10vh 0; top: 0; width: 400px;"
|
||||||
|
|
Loading…
Reference in a new issue