mirror of
https://github.com/adoyle0/thaw.git
synced 2025-02-02 08:34:15 -05:00
reactor: radio
This commit is contained in:
parent
97263ec80d
commit
ae4a69fd15
7 changed files with 128 additions and 112 deletions
|
@ -1,26 +1,12 @@
|
|||
# Radio
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(false);
|
||||
|
||||
view! {
|
||||
<Radio value>"Click"</Radio>
|
||||
}
|
||||
```
|
||||
|
||||
### Group
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(None);
|
||||
let value = RwSignal::new(None);
|
||||
|
||||
view! {
|
||||
<RadioGroup value>
|
||||
<RadioItem key="a">
|
||||
"Apple"
|
||||
</RadioItem>
|
||||
<RadioItem key="o">
|
||||
"Orange"
|
||||
</RadioItem>
|
||||
<Radio value="a" label="Apple"/>
|
||||
<Radio value="o" label="Orange"/>
|
||||
</RadioGroup>
|
||||
<div style="margin-top: 1rem">
|
||||
"value: " {move || format!("{:?}", value.get())}
|
||||
|
|
|
@ -25,7 +25,7 @@ pub fn Checkbox(
|
|||
};
|
||||
|
||||
view! {
|
||||
<div
|
||||
<span
|
||||
class=class_list![
|
||||
"thaw-checkbox", ("thaw-checkbox--checked", move || checked.get()), class.map(| c |
|
||||
move || c.get())
|
||||
|
@ -39,7 +39,7 @@ pub fn Checkbox(
|
|||
ref=input_ref
|
||||
on:change=on_change
|
||||
/>
|
||||
<div class="thaw-checkbox__indicator">
|
||||
<div aria-hidden="true" class="thaw-checkbox__indicator">
|
||||
{
|
||||
move || if checked.get() {
|
||||
view! {
|
||||
|
@ -55,6 +55,6 @@ pub fn Checkbox(
|
|||
<OptionComp value=children let:children>
|
||||
<label class="thaw-checkbox__label" for=id>{children()}</label>
|
||||
</OptionComp>
|
||||
</div>
|
||||
</span>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,45 +1,61 @@
|
|||
mod radio_group;
|
||||
mod radio_item;
|
||||
|
||||
pub use radio_group::RadioGroup;
|
||||
pub use radio_item::RadioItem;
|
||||
|
||||
use crate::{theme::use_theme, Theme};
|
||||
use leptos::*;
|
||||
use thaw_utils::{class_list, mount_style, Model, OptionalProp};
|
||||
use radio_group::RadioGroupInjection;
|
||||
use thaw_utils::{class_list, mount_style, OptionalProp};
|
||||
|
||||
#[component]
|
||||
pub fn Radio(
|
||||
#[prop(optional, into)] value: Model<bool>,
|
||||
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
|
||||
#[prop(optional)] children: Option<Children>,
|
||||
#[prop(optional, into)] value: String,
|
||||
#[prop(optional, into)] label: MaybeProp<String>,
|
||||
) -> impl IntoView {
|
||||
let theme = use_theme(Theme::light);
|
||||
mount_style("radio", include_str!("./radio.css"));
|
||||
|
||||
let css_vars = create_memo(move |_| {
|
||||
let mut css_vars = String::new();
|
||||
theme.with(|theme| {
|
||||
let bg_color = theme.common.color_primary.clone();
|
||||
css_vars.push_str(&format!("--thaw-background-color-checked: {bg_color};"));
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
let group = RadioGroupInjection::use_();
|
||||
let item_value = StoredValue::new(value);
|
||||
|
||||
let checked = Memo::new({
|
||||
let group = group.clone();
|
||||
move |_| {
|
||||
item_value.with_value(|value| {
|
||||
group
|
||||
.0
|
||||
.with(|group_value| group_value.as_ref() == Some(value))
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
css_vars
|
||||
});
|
||||
let on_change = move |_| {
|
||||
group.0.set(Some(item_value.get_value()));
|
||||
};
|
||||
|
||||
view! {
|
||||
<div
|
||||
<span
|
||||
class=class_list![
|
||||
"thaw-radio", ("thaw-radio--checked", move || value.get()), class.map(| c | move ||
|
||||
c.get())
|
||||
"thaw-radio", class.map(| c | move || c.get())
|
||||
]
|
||||
|
||||
style=move || css_vars.get()
|
||||
on:click=move |_| value.set(!value.get_untracked())
|
||||
>
|
||||
<input class="thaw-radio__input" type="radio" prop:value=move || value.get()/>
|
||||
<div class="thaw-radio__dot"></div>
|
||||
<div class="thaw-radio__label">{children.map(|children| children())}</div>
|
||||
</div>
|
||||
<input
|
||||
class="thaw-radio__input"
|
||||
type="radio"
|
||||
id=id.clone()
|
||||
prop:checked=move || checked.get()
|
||||
on:change=on_change
|
||||
/>
|
||||
<div aria-hidden="true" class="thaw-radio__indicator"></div>
|
||||
{
|
||||
move || if let Some(label) = label.get() {
|
||||
view! {
|
||||
<label class="thaw-radio__label" for=id.clone()>{label}</label>
|
||||
}.into()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,42 +1,81 @@
|
|||
.thaw-radio {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.thaw-radio__input {
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
width: calc(16px + 2 * var(--spacingHorizontalS));
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
margin: 0px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.thaw-radio__dot {
|
||||
display: inline-block;
|
||||
.thaw-radio__input:enabled {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.thaw-radio__indicator {
|
||||
position: relative;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 50%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
font-size: 12px;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
border: var(--strokeWidthThin) solid;
|
||||
border-radius: var(--borderRadiusCircular);
|
||||
margin: var(--spacingVerticalS) var(--spacingHorizontalS);
|
||||
fill: currentcolor;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.thaw-radio:hover .thaw-radio__dot,
|
||||
.thaw-radio--checked .thaw-radio__dot {
|
||||
border-color: var(--thaw-background-color-checked);
|
||||
.thaw-radio__input:enabled:not(:checked) ~ .thaw-radio__indicator {
|
||||
border-color: var(--colorNeutralStrokeAccessible);
|
||||
}
|
||||
|
||||
.thaw-radio--checked .thaw-radio__dot::before {
|
||||
content: "";
|
||||
.thaw-radio__input:checked ~ .thaw-radio__indicator {
|
||||
border-color: var(--colorCompoundBrandStroke);
|
||||
color: var(--colorCompoundBrandForeground1);
|
||||
}
|
||||
|
||||
.thaw-radio__indicator::after {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
bottom: 3px;
|
||||
left: 3px;
|
||||
right: 3px;
|
||||
background-color: var(--thaw-background-color-checked);
|
||||
border-radius: 50%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: var(--borderRadiusCircular);
|
||||
transform: scale(0.625);
|
||||
background-color: currentcolor;
|
||||
}
|
||||
|
||||
.thaw-radio__input:checked ~ .thaw-radio__indicator::after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
.thaw-radio__label {
|
||||
display: inline-block;
|
||||
padding: 0 6px;
|
||||
user-select: none;
|
||||
margin-bottom: calc((16px - var(--lineHeightBase300)) / 2);
|
||||
margin-top: calc((16px - var(--lineHeightBase300)) / 2);
|
||||
align-self: center;
|
||||
padding-bottom: var(--spacingVerticalS);
|
||||
padding-top: var(--spacingVerticalS);
|
||||
padding-left: var(--spacingHorizontalXS);
|
||||
padding-right: var(--spacingHorizontalS);
|
||||
line-height: var(--lineHeightBase300);
|
||||
font-size: var(--fontSizeBase300);
|
||||
font-family: var(--fontFamilyBase);
|
||||
color: var(--colorNeutralForeground1);
|
||||
}
|
||||
|
||||
.thaw-radio__input:enabled:not(:checked) ~ .thaw-radio__label {
|
||||
color: var(--colorNeutralForeground3);
|
||||
}
|
||||
|
||||
.thaw-radio__input:enabled ~ .thaw-radio__label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
@ -6,12 +6,20 @@ pub fn RadioGroup(
|
|||
#[prop(optional, into)] value: Model<Option<String>>,
|
||||
children: Children,
|
||||
) -> impl IntoView {
|
||||
view! { <Provider value=RadioGroupInjection(value) children/> }
|
||||
view! {
|
||||
<Provider value=RadioGroupInjection(value)>
|
||||
<div class="thaw-radio-group" role="radiogroup" style="display: flex;align-items: flex-start">
|
||||
{children()}
|
||||
</div>
|
||||
</Provider>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct RadioGroupInjection(pub Model<Option<String>>);
|
||||
|
||||
pub(crate) fn use_radio_group() -> RadioGroupInjection {
|
||||
impl RadioGroupInjection {
|
||||
pub fn use_() -> RadioGroupInjection {
|
||||
expect_context()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
use crate::radio::{radio_group::use_radio_group, Radio};
|
||||
use leptos::*;
|
||||
use thaw_utils::OptionalProp;
|
||||
|
||||
#[component]
|
||||
pub fn RadioItem(
|
||||
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
|
||||
#[prop(into)] key: String,
|
||||
#[prop(optional)] children: Option<Children>,
|
||||
) -> impl IntoView {
|
||||
let radio_group_value = use_radio_group().0;
|
||||
let checked = RwSignal::new(false);
|
||||
let item_key = store_value(key);
|
||||
|
||||
let is_checked = Memo::new(move |_| {
|
||||
radio_group_value.with(|value| item_key.with_value(|key| value.as_ref() == Some(key)))
|
||||
});
|
||||
|
||||
Effect::new(move |prev| {
|
||||
let checked = checked.get();
|
||||
if prev.is_some() {
|
||||
if !is_checked.get_untracked() {
|
||||
radio_group_value.set(Some(item_key.get_value()))
|
||||
}
|
||||
}
|
||||
|
||||
checked
|
||||
});
|
||||
|
||||
if let Some(children) = children {
|
||||
view! {
|
||||
<Radio class value=(is_checked, checked.write_only())>
|
||||
{children()}
|
||||
</Radio>
|
||||
}
|
||||
} else {
|
||||
view! { <Radio class value=(is_checked, checked.write_only())/> }
|
||||
}
|
||||
}
|
|
@ -35,6 +35,8 @@ pub struct ColorTheme {
|
|||
pub color_neutral_stroke_accessible_hover: String,
|
||||
pub color_neutral_stroke_accessible_pressed: String,
|
||||
|
||||
pub color_compound_brand_foreground_1: String,
|
||||
|
||||
pub color_compound_brand_background: String,
|
||||
pub color_compound_brand_background_hover: String,
|
||||
pub color_compound_brand_background_pressed: String,
|
||||
|
@ -90,6 +92,8 @@ impl ColorTheme {
|
|||
color_neutral_stroke_accessible_hover: "#575757".into(),
|
||||
color_neutral_stroke_accessible_pressed: "#4d4d4d".into(),
|
||||
|
||||
color_compound_brand_foreground_1: "#0f6cbd".into(),
|
||||
|
||||
color_compound_brand_background: "#0f6cbd".into(),
|
||||
color_compound_brand_background_hover: "#115ea3".into(),
|
||||
color_compound_brand_background_pressed: "#0f548c".into(),
|
||||
|
@ -145,6 +149,8 @@ impl ColorTheme {
|
|||
color_neutral_stroke_accessible_hover: "#bdbdbd".into(),
|
||||
color_neutral_stroke_accessible_pressed: "#b3b3b3".into(),
|
||||
|
||||
color_compound_brand_foreground_1: "#479ef5".into(),
|
||||
|
||||
color_compound_brand_background: "#479ef5".into(),
|
||||
color_compound_brand_background_hover: "#62abf5".into(),
|
||||
color_compound_brand_background_pressed: "#2886de".into(),
|
||||
|
|
Loading…
Add table
Reference in a new issue