use dioxus::prelude::*;
#[derive(strum::AsRefStr, Clone, Copy, Debug, Default, Eq, PartialEq)]
#[strum(serialize_all = "kebab-case")]
pub enum Side {
Top,
Right,
#[default]
Bottom,
Left,
}
pub struct Controllable<T: Clone + PartialEq + 'static> {
signal: Signal<T>,
controlled: bool,
on_change: Option<EventHandler<T>>,
}
impl<T: Clone + PartialEq + 'static> Controllable<T> {
pub fn get(&self) -> T {
self.signal.read().clone()
}
pub fn set(&self, next: T) {
if !self.controlled {
let mut sig = self.signal;
sig.set(next.clone());
}
if let Some(handler) = &self.on_change {
handler.call(next);
}
}
}
impl<T: Clone + PartialEq + 'static> Clone for Controllable<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: Clone + PartialEq + 'static> Copy for Controllable<T> {}
pub fn use_controllable<T: Clone + PartialEq + 'static>(controlled: Option<T>, default: T, on_change: Option<EventHandler<T>>) -> Controllable<T> {
let mut signal = use_signal(|| controlled.clone().unwrap_or(default));
if let Some(value) = controlled.clone()
&& *signal.peek() != value
{
signal.set(value);
}
Controllable {
signal,
controlled: controlled.is_some(),
on_change,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::uikit::test_util::render;
#[test]
fn uncontrolled_uses_default_then_updates() {
fn app() -> Element {
let state = use_controllable::<bool>(None, false, None);
let label = if state.get() { "on" } else { "off" };
rsx! {
button {
onclick: move |_| state.set(true),
{label}
}
}
}
assert!(render(app).contains("off"));
}
#[test]
fn controlled_reflects_external_value() {
fn app() -> Element {
let state = use_controllable::<bool>(Some(true), false, None);
let label = if state.get() { "on" } else { "off" };
rsx! { span { {label} } }
}
assert!(render(app).contains("on"));
}
}