reactor: radio

This commit is contained in:
luoxiao 2024-05-23 14:48:06 +08:00
parent 97263ec80d
commit ae4a69fd15
7 changed files with 128 additions and 112 deletions

View file

@ -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())}

View file

@ -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>
}
}

View file

@ -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>
}
}

View file

@ -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;
}

View file

@ -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()
}
}

View file

@ -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())/> }
}
}

View file

@ -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(),