login-01
fandhe-frontend-pre-styled-ui の card / field / input / button 部品を合成した、shadcn/ui Blocks の login-01(カード型のシンプルなログインフォーム)に相当する合成例です。Blocks セクションは新規部品を追加するものではなく、既存の Themes/Primitives 部品を組み合わせた実例集であることに注意してください。
本 Demo は静的な表示例であり、<form> 要素を持たず、入力値の送信・検証・認証処理を一切行いません(ボタンは type="button" のまま、「パスワードを忘れた」「サインアップ」はいずれもページ遷移しないリンク風ボタン、「Login with SSO」も送信先を持たない静的なボタンです)。実際のログインフォームを実装する場合は、送信処理・バリデーションを利用者自身の Rust コードで書いてください(docs/policy/intentional-non-adoption.md §3.25 の責務境界: UI コンポーネント層はアプリケーションロジックを内包しません)。
Demo
Login to your account
Enter your email below to login to your account
Don't have an account?
使用部品
Rust コード
Rust
use fandhe_frontend_core::{div, text, Node};
use fandhe_frontend_pre_styled_ui::button::{self, ButtonProps, ButtonVariant};
use fandhe_frontend_pre_styled_ui::card::{self, CardProps};
use fandhe_frontend_pre_styled_ui::field::{self, FieldOrientation, FieldRootProps};
use fandhe_frontend_pre_styled_ui::input::{self, FieldIds, FieldProps, InputProps};
/// `login-01` の Demo 本体。呼び出しごとに同一の `Node` を返す純関数。
pub fn demo() -> Node {
let email_field = FieldProps {
id: "blocks-login-01-email",
ids: FieldIds::default(),
disabled: false,
invalid: false,
required: true,
readonly: false,
has_helper_text: false,
};
let password_field = FieldProps {
id: "blocks-login-01-password",
ids: FieldIds::default(),
disabled: false,
invalid: false,
required: true,
readonly: false,
has_helper_text: false,
};
let orientation = FieldRootProps {
orientation: FieldOrientation::Vertical,
};
let link_button = ButtonProps {
variant: ButtonVariant::Link,
..ButtonProps::default()
};
card::root(
CardProps::default(),
vec![("data-blocks-login-01-card", "")],
vec![
card::header(
vec![],
vec![
card::title(vec![], vec![text("Login to your account")]),
card::description(
vec![],
vec![text("Enter your email below to login to your account")],
),
],
),
card::body(
vec![],
vec![field::group(
vec![],
vec![
field::root(
&orientation,
&email_field,
vec![("data-blocks-login-01-field", "")],
vec![
field::label(&email_field, vec![], vec![text("Email")]),
input::input(
&InputProps::default(),
&email_field,
vec![("type", "email"), ("placeholder", "m@example.com")],
),
],
),
field::root(
&orientation,
&password_field,
vec![("data-blocks-login-01-field", "")],
vec![
div(
vec![("class", "blocks-login-01-password-row")],
vec![
field::label(
&password_field,
vec![],
vec![text("Password")],
),
button::button(
&link_button,
vec![],
vec![text("Forgot your password?")],
),
],
),
input::input(
&InputProps::default(),
&password_field,
vec![("type", "password")],
),
],
),
div(
vec![("class", "blocks-login-01-actions")],
vec![
button::button(
&ButtonProps::default(),
vec![("data-blocks-login-01-submit", "")],
vec![text("Login")],
),
button::button(
&ButtonProps {
variant: ButtonVariant::Outline,
..ButtonProps::default()
},
vec![("data-blocks-login-01-submit", "")],
vec![text("Login with SSO")],
),
div(
vec![("class", "blocks-login-01-signup-row")],
vec![
text("Don't have an account? "),
button::button(&link_button, vec![], vec![text("Sign up")]),
],
),
],
),
],
)],
),
],
)
}shadcn 側との差分メモ
shadcn/ui login-01(apps/v4/registry/new-york-v4/blocks/login-01/)との突合により、以下の判断で構成しています(#2092 で確定)。
<form>を使わない: 本サイトは無 JS 前提で Enter キーの暗黙 submit を避けるため(crate::layoutモジュール doc)、<form>要素は出力せずdivで構造化しています。送信ボタンはtype="button"のまま、「パスワードを忘れた」「サインアップ」は死リンク(href="#")ではなく見た目だけリンク風のButtonVariant::Linkボタンとして実装しています。- 「Login with Google」→「Login with SSO」: shadcn 側は Outline variant の「Login with Google」ボタンを持ちますが、実企業名・実ブランドは持ち込まない方針(
docs/design/docs-site-blocks-section.md§8)のため、ラベルを一般名詞「Login with SSO」へ置換しています。 field::groupを採用: shadcnFieldGroup(gapを持つ縦積みコンテナ)に相当するものとして、2 つのfield::rootとボタン群をfield::groupでまとめています。- ボタン群は
card::body内に配置: shadcn の login-01 はCardContent内の 3 つ目のFieldとしてボタン群を配置しておりCardFooterを使わないため、本実装もcard::footerではなくcard::body内のfield::group末尾に配置する構成に合わせています。 - フォント・色はテーマトークン準拠: レイアウト(カード幅 24rem・中央寄せ・見出し階層・ボタン配置)は shadcn 側スクリーンショットと一致させ、配色・タイポグラフィは本リポジトリの
Themeトークンをそのまま用いています。