#element #savory #seed #spa #component

savory-elements-derive

Crate 提供了 Element 推导

1 个不稳定版本

0.6.0 2021 年 3 月 6 日

#6 in #savory


用于 savory-elements

MIT/Apache

23KB
502

Savory

Rust/Wasm 前端库,用于构建用户界面。

master docs · crate info · pipeline · rustc version · unsafe forbidden

Savory 是一个基于 Seed 的构建用户界面的库

文档

功能

  • 设计系统:元素完全使用 DesignSystem 进行样式化。
  • 可重用性:元素高度可重用/可组合。
  • 解耦开发:设计系统可以在不接触元素代码的情况下单独开发,同样,元素开发也与设计系统分离,这归功于 DesignSystemImpl 特性。
  • 干净视图:以干净和声明性的方式构建您的视图,不再需要任何宏。
  • 基于特质:拥抱 Rust 特质系统,所有 Savory 元素都实现了 Element 和/或 View 特质。
  • 类型化 HTML:使用类型化 CSS 和 HTML 属性,Savory 尽量不依赖字符串来创建 CSS 和 HTML 属性,因为这些可以产生难以调试的错误。
  • UI 元素集合:Savory 附带了一组可重用和可主题化的 UI 元素。
  • 增强 Seed API:对 Seed API 的增强,使使用 NodeOrders 变得有趣。

Savory 努力让编写 UI 元素变得有趣且无需样板代码。

截图

Screenshot

入门

最快速入门的方法是遵循 Savory 入门 仓库说明。

示例

在这里,我们将创建一个与 Elm 教程 中找到的相同计数器应用程序,然后我们将用样式化和可重用的元素编写相同的应用程序。

简单计数器

use savory::prelude::*;

// app element (the model)
pub struct Counter(i32);

// app message
pub enum Msg {
    Increment,
    Decrement,
}

impl Element for Counter {
    type Message = Msg;
    type Config = Url;

    // initialize the app in this function
    fn init(_: Url, _: &mut impl Orders<Msg>) -> Self {
        Self(0)
    }

    // handle app messages
    fn update(&mut self, msg: Msg, _: &mut impl Orders<Msg>) {
        match msg {
            Msg::Increment => self.0 += 1,
            Msg::Decrement => self.0 -= 1,
        }
    }
}

impl View<Node<Msg>> for Counter {
    // view the app
    fn view(&self) -> Node<Msg> {
        let inc_btn = html::button().push("Increment").on_click(|_| Msg::Increment);
        let dec_btn = html::button().push("Decrement").on_click(|_| Msg::Decrement);

        html::div()
            .push(inc_btn)
            .push(self.0.to_string())
            .push(dec_btn)
    }
}

#[wasm_bindgen(start)]
pub fn view() {
    // mount and start the app at `app` element
    Counter::start();
}

预览:截图

源代码

Counter 作为元素

现在我们将创建计数器元素和应用程序元素,这将说明如何创建父元素和子元素,以及如何创建可重用和可定制的元素。

use savory::prelude::*;
use savory_elements::prelude::*;
use savory_style::{
    css::{unit::px, values as val, Color, St},
    prelude::*,
};

#[derive(Element)]
pub struct Counter {
    #[element(config(default = "10"))]
    value: i32,
}

pub enum Msg {
    Increment,
    Decrement,
}

impl Element for Counter {
    type Message = Msg;
    type Config = Config;

    fn init(config: Self::Config, _: &mut impl Orders<Msg>) -> Self {
        Self {
            value: config.value,
        }
    }

    fn update(&mut self, msg: Msg, _: &mut impl Orders<Msg>) {
        match msg {
            Msg::Increment => self.value += 1,
            Msg::Decrement => self.value -= 1,
        }
    }
}

impl View<Node<Msg>> for Counter {
    fn view(&self) -> Node<Msg> {
        // sharde style for buttons
        let style_btns = |conf: css::Style| {
            conf.push(St::Appearance, val::None)
                .background(Color::SlateBlue)
                .text(Color::White)
                .and_border(|conf| conf.none().radius(px(4)))
                .margin(px(4))
                .padding(px(4))
        };

        // increment button node
        let inc_btn = html::button()
            .class("inc-btn")
            .and_style(style_btns)
            .on_click(|_| Msg::Increment)
            .push("Increment");

        // decrement button node
        let dec_btn = html::button()
            .class("dec-btn")
            .and_style(style_btns)
            .on_click(|_| Msg::Decrement)
            .push("Decrement");

        // contianer node
        html::div()
            .push(dec_btn)
            .push(self.value.to_string())
            .push(inc_btn)
    }
}

// convenient way to convert Config into Counter
impl Config {
    pub fn init(self, orders: &mut impl Orders<Msg>) -> Counter {
        Counter::init(self, orders)
    }
}

// App Element ---

pub enum AppMsg {
    Counter(Msg),
}

pub struct MyApp {
    counter_element: Counter,
}

impl Element for MyApp {
    type Message = AppMsg;
    type Config = Url;

    fn init(_: Url, orders: &mut impl Orders<AppMsg>) -> Self {
        Self {
            counter_element: Counter::config()
                // give it starting value. 10 will be used as default value if
                // we didn't pass value
                .value(100)
                .init(&mut orders.proxy(AppMsg::Counter)),
        }
    }

    fn update(&mut self, msg: AppMsg, orders: &mut impl Orders<AppMsg>) {
        match msg {
            AppMsg::Counter(msg) => self
                .counter_element
                .update(msg, &mut orders.proxy(AppMsg::Counter)),
        }
    }
}

impl View<Node<AppMsg>> for MyApp {
    fn view(&self) -> Node<AppMsg> {
        self.counter_element.view().map_msg(AppMsg::Counter)
    }
}

#[wasm_bindgen(start)]
pub fn view() {
    // mount and start the app at `app` element
    MyApp::start();
}

预览:截图

源代码

在这个例子中有许多事情发生,首先我们创建了一个名为 Counter 的元素结构,并定义了它的属性、事件和样式类型,这一切都是通过 derive 宏 Element 完成的,我们将在后面解释它是如何工作的,然后我们定义了一个包含计数器元素的应用程序元素,并在 init 函数中初始化它。最后,我们只需调用 start 方法来挂载并启动应用程序。

Counter 使用 Savory Elements!

Savory 随带一系列元素,我们将使用它们来构建计数器应用程序并查看 Savory 元素提供的功能。

待办事项:添加示例

生态系统

许可证

根据您的选择,受 Apache License 2.0 或 MIT 许可证的许可。请参阅 Apache 许可证版本 2.0MIT 许可证

除非您明确声明,否则根据 Apache-2.0 许可证定义,您提交给 Savory 的任何有意贡献将如上所述双重许可,不附加任何额外条款或条件。

依赖项

~2MB
~42K SLoC