dwui 0.9.0

UI Component library built on the DWIND style crate!
Documentation
use crate::theme::prelude::*;
use dominator::{clone, events, html, Dom, EventOptions};
use dwind::prelude::*;
use futures_signals::map_ref;
use futures_signals::signal::SignalExt;
use futures_signals::signal_vec::SignalVecExt;
use futures_signals_component_macro::component;
use std::sync::Arc;

#[component(render_fn = pretty_list)]
struct List {
    #[signal_vec]
    #[default(vec ! [])]
    items: Dom,

    #[signal]
    #[default(None)]
    selected_index: Option<usize>,

    #[default(Box::new(|_|{}))]
    item_click_handler: dyn Fn(usize) + 'static,
}

pub fn pretty_list(props: ListProps) -> Dom {
    let ListProps {
        items,
        selected_index,
        item_click_handler,
        apply,
    } = props;

    let item_click_handler = Arc::new(item_click_handler);
    let selected_index = selected_index.broadcast();

    html!("ul", {
        .apply_if(apply.is_some(), |b| b.apply(apply.unwrap()))
        .children_signal_vec(items.enumerate().map(clone!(item_click_handler => move |(index, item)| {
            let selected_signal = map_ref! {
                let selected = selected_index.signal(),
                let index = index.signal() => {
                    index == selected
                }
            }.broadcast();

            html!("li", {
                .attr("tabindex", "0")
                .attr_signal("aria-current", selected_signal.signal().map(|selected| {
                    if selected { Some("true") } else { None }
                }))
                .dwclass!("border-l h-6 cursor-pointer")
                .dwclass!("dwui-border-void-600 is(.light *):dwui-border-void-300")
                .dwclass!("dwui-text-on-primary-300 is(.light *):dwui-text-on-primary-700")
                .dwclass!("hover:dwui-text-on-primary-50 is(.light *):hover:dwui-text-on-primary-950 hover:dwui-border-void-300")
                .dwclass!("transition-colors focus-visible:ring-1")
                .style("padding-left", "10px")
                .style("outline", "none")
                .child(item)
                .dwclass_signal!("dwui-text-primary-300 is(.light *):dwui-text-primary-700", selected_signal.signal())
                .dwclass_signal!("hover:dwui-text-primary-300 is(.light *):hover:dwui-text-primary-700", selected_signal.signal())
                .dwclass_signal!("font-bold", selected_signal.signal())
                .dwclass_signal!("dwui-border-primary-400 is(.light *):dwui-border-primary-600", selected_signal.signal())
                .apply(clone!(item_click_handler, index =>move |b| {
                    b.event(clone!(item_click_handler, index => move |_: events::Click| {
                        if let Some(idx) = index.get() {
                            item_click_handler(idx);
                        }
                    }))
                    .event_with_options(&EventOptions::preventable(), clone!(item_click_handler, index => move |e: events::KeyDown| {
                        if e.key() == "Enter" || e.key() == " " {
                            e.prevent_default();

                            if let Some(idx) = index.get() {
                                item_click_handler(idx);
                            }
                        }
                    }))
                }))
            })
        })))
    })
}