#savory #css #style #seed #themes

savory-style

为 Savory 定制的类型化 CSS 样式

1 个不稳定版本

0.6.0 2021年3月6日

#846 in GUI


savory-elements 中使用

MIT/Apache

150KB
4K SLoC

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

预览: 截图

源代码

计数器作为元素

现在我们将制作计数器元素和应用元素,这展示了如何创建父元素和子元素,以及如何创建可重用和可定制的元素。

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 方法来挂载并启动应用程序。

使用 Savory 元素进行计数!

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

待办事项:添加示例

生态系统

许可

许可协议为 Apache License, Version 2.0 或 MIT 许可证,由您选择。

除非您明确声明,否则您提交给 Savory 的任何有意包含的贡献,根据 Apache-2.0 许可证的定义,将按上述方式双许可,不附加任何其他条款或条件。

依赖项

~18MB
~335K SLoC