English

hypersig_ui クレート

shadcn/ui の見た目を持つ、アクセシブルなコンポーネント

ボタン、入力欄、選択、タブ、ダイアログ、メニュー、ツールチップ、トーストを、ブラウザのネイティブ要素を使い、WAI-ARIA Authoring Practices に沿って作っています。どれも El を返すので、ほかの要素と同じようにハンドラや子要素を足せます。

ネイティブ要素を先に使う

<button>、<select>、<details>、<dialog>、popover にはブラウザがもともと持つ振る舞いがあり、コンポーネントはそれをそのまま使います。

WAI-ARIA のキーボード操作

タブ、リストボックスの選択肢、メニューの項目の間は、矢印キー、Home、End で移ります。コンボボックスはフォーカスをテキスト欄に置いたままにします。

WCAG 2.2 AA を満たすコントラスト

shadcn/ui の zinc テーマをもとに、コントロールの境界線を 3:1、補足のテキストを 4.5:1 にし、フォーカスを単色の outline で描きます。

数行で書けるダイアログ

const のハンドルで、ダイアログとそれを開閉するボタンを対応付けます。ブラウザは invoker commands を通して、ダイアログをモーダルとして開きます。

実行結果

dialog.rs
use hypersig::html::{div, p};
use hypersig::style::pattern::{stack, wrap};
use hypersig::style::value::rem;
use hypersig::{Page, Signals};
use hypersig_ui::{ButtonSize, ButtonVariant, Dialog, Side, TextField, dialog_footer};
use serde::{Deserialize, Serialize};

#[derive(Signals, Serialize, Deserialize)]
pub struct Project {
    name: String,
    status: String,
}

const RENAME: Dialog<Project> = Dialog::new("rename");
const DELETE: Dialog<Project> = Dialog::new("delete");
const DETAILS: Dialog<Project> = Dialog::new("details");

pub fn page() -> Page<Project> {
    let name = Project::name();
    let status = Project::status();
    let initial = Project {
        name: "hypersig".into(),
        status: String::new(),
    };
    Page::new(
        "プロジェクト",
        initial,
    )
    .child(
        div()
            .css(stack(rem(1)))
            .child(
                div()
                    .css(wrap(rem(0.5)))
                    .child(
                        RENAME
                            .trigger(ButtonVariant::Outline, ButtonSize::Default)
                            .text("名前を変更"),
                    )
                    .child(
                        DETAILS
                            .trigger(ButtonVariant::Outline, ButtonSize::Default)
                            .text("詳細"),
                    )
                    .child(
                        DELETE
                            .trigger(ButtonVariant::Destructive, ButtonSize::Default)
                            .text("削除"),
                    ),
            )
            .child(p().attr("role", "status").bind_text(status))
            .child(
                RENAME.dialog(
                    "プロジェクトの名前を変更",
                    "新しい名前は、プロジェクトが表示されるすべての場所に反映されます。",
                    div()
                        .css(stack(rem(1)))
                        .child(TextField::new(
                            "名前",
                            name,
                        ))
                        .child(
                            dialog_footer()
                                .child(
                                    RENAME
                                        .close(ButtonVariant::Outline, ButtonSize::Default)
                                        .text("キャンセル"),
                                )
                                .child(
                                    RENAME
                                        .close(ButtonVariant::Default, ButtonSize::Default)
                                        .on_click(status.set("プロジェクトの名前を変更しました。"))
                                        .text("保存"),
                                ),
                        ),
                ),
            )
            .child(
                DETAILS.sheet(
                    Side::Right,
                    "プロジェクトの詳細",
                    "3 月 3 日に作成し、12 回デプロイしました。",
                    dialog_footer().child(
                        DETAILS
                            .close(ButtonVariant::Outline, ButtonSize::Default)
                            .text("完了"),
                    ),
                ),
            )
            .child(
                DELETE.alert_dialog(

                    "このプロジェクトを削除しますか?",
                    "プロジェクトとそのデプロイが削除されます。この操作は取り消せません。",
                    dialog_footer()
                        .child(
                            DELETE
                                .close(ButtonVariant::Outline, ButtonSize::Default)
                                .text("キャンセル"),
                        )
                        .child(
                            DELETE
                                .close(ButtonVariant::Destructive, ButtonSize::Default)
                                .on_click(status.set("プロジェクトを削除しました。"))
                                .text("削除"),
                        ),
                ),
            ),
    )
}

すべてのコンポーネント

hypersig の一部