hero-install-command
インストールコマンドのコピー欄を備えたヒーロー block です。新規 UI 部品は作らず badge / heading / text / clipboard / code / input-group / input / button / breadcrumb の 9 部品を合成します。配置・コピー欄形式の差分を 1 つの Demo 内へ 3 インスタンス静的に並記して示します。A/C は実アプリへ組み込んだときにそのまま使える idle(未コピー)状態で初期化します。
- A(中央寄せ・
clipboard形式・idle): バッジ + 見出し + リード文の下にclipboardのコピー欄(idle 表示)と CTA ボタン 2 個を配置。 - B(左寄せ・
input-group形式):field+input-group+ 読み取り専用input+ addon ボタンで 1 行のコピー欄を表現。コピー状態そのものは持たず、addon ボタンはdisabledで押下不能です(下記「コピー操作について」参照)。 - C(パンくず付き左寄せ・
clipboard形式・idle):breadcrumbを導入要素に置き、clipboardのvalue_textにcodeを重ねてコマンドを等幅表示。indicator は idle 側のみ可視。
md 未満(< 48rem)で CTA を全幅縦積みにし、>= 48rem で横並びに戻ります。<form> は持たず、ボタンはすべて type="button" です。コピー対象のコマンド・遷移先はすべて無害な自前の値のみです。CTA ボタン(「はじめる」「ドキュメントを見る」「詳しく見る」)は遷移先・クリック処理を持たない合成例のため、いずれも disabled で押下不能を明示します。
Demo
コマンド一つで始める
テンプレートをそのまま実行するだけで動きます。
既存プロジェクトに追加
依存クレートを追加する
`Cargo.toml` に 1 行追加するだけで導入できます。
新規プロジェクトを作る
コピーしたコマンドをターミナルに貼り付けて実行します。
fw new my-app使用部品
コピー操作について
A/C の「Copy」ボタンは fandhe-frontend-pre-styled-ui の clipboard 部品そのものです。本 block を無 JS の docs サイトではなく実アプリへ組み込み、fandhe-frontend-wasm-full でハイドレーションすると navigator.clipboard.writeText への実書き込みが自動配線されます(Clipboard 参照)。一方 B の addon ボタンは clipboard 部品を使わない別構成のため配線対象にならず、常に押下不能な静的表示です。
headless_clipboard は「1 root : 1 状態機械契約」(同一マウントルート配下の全 clipboard パーツへコピー済み表示をまとめて反映する簡略化)を持つため、実アプリで A・C を両方使う場合は同一マウントルートへまとめて mount/hydrate すると一方のコピーで他方も「Copied!」表示になってしまいます。これを避けられるよう、A の外枠には id="blocks-hero-install-command-a"、C の外枠には id="blocks-hero-install-command-c" を付与してあります。両方を使う場合はこの 2 つの id の要素それぞれへ個別に mount/hydrate を呼んでください。
Rust コード
use fandhe_frontend_core::{div, text, Node};
use fandhe_frontend_pre_styled_ui::badge::{self, BadgeProps};
use fandhe_frontend_pre_styled_ui::breadcrumb::{self, BreadcrumbVariant};
use fandhe_frontend_pre_styled_ui::button::{self, ButtonProps, ButtonVariant};
use fandhe_frontend_pre_styled_ui::clipboard;
use fandhe_frontend_pre_styled_ui::code::{self, CodeProps};
use fandhe_frontend_pre_styled_ui::field::{
self, FieldIds, FieldOrientation, FieldProps, FieldRootProps,
};
use fandhe_frontend_pre_styled_ui::heading::{self, HeadingLevel, HeadingProps, HeadingSize};
use fandhe_frontend_pre_styled_ui::input::{self, InputProps};
use fandhe_frontend_pre_styled_ui::input_group::{self, InputGroupAlign, InputGroupProps};
use fandhe_frontend_pre_styled_ui::text::{self as styled_text, TextProps, TextSize, TextVariant};
use fandhe_frontend_pre_styled_ui::visually_hidden;
use fandhe_frontend_pre_styled_ui::Size;
/// 本フレームワーク自身の公開リポジトリ URL(`breadcrumb::link` の唯一の
/// 遷移先。`href="#"`・死にリンクを避けるための固定値、`hero_terminal`
/// 等と同型の判断)。
const REPO_URL: &str = "https://github.com/Fandhe-AI/fandhe-frontend";
/// A(中央寄せ・`clipboard` 形式・idle)を組み立てる。`id` はレビュー
/// 指摘対応(P1、イシュー #2786 Codex 指摘)で付与した独立マウント
/// ルート識別子(モジュール doc「コピー配線の範囲」節参照。実アプリで
/// C と併用する場合はこの `id` の要素へ個別に `mount`/`hydrate` を
/// 呼ぶことで `headless_clipboard` の「1 root : 1 状態機械契約」による
/// A/C 連動を避けられる)。
fn instance_a() -> Node {
let value = "cargo install fandhe-frontend-cli";
let input_id = "blocks-hero-install-command-a-input";
div(
vec![
("id", "blocks-hero-install-command-a"),
("data-blocks-hero-install-command-hero", ""),
("data-align", "center"),
],
vec![
badge::badge(&BadgeProps::default(), vec![], vec![text("v0.1 公開中")]),
heading::heading(
HeadingLevel::H3,
&HeadingProps {
size: HeadingSize::Xl4,
..HeadingProps::default()
},
vec![],
vec![text("コマンド一つで始める")],
),
styled_text::text(
&TextProps {
size: TextSize::Lg,
variant: TextVariant::Muted,
..TextProps::default()
},
vec![],
vec![text("テンプレートをそのまま実行するだけで動きます。")],
),
clipboard::root(
value,
false,
vec![("data-blocks-hero-install-command-command", "")],
vec![
visually_hidden::root(
vec![],
vec![clipboard::label(
false,
Some(input_id),
vec![],
vec![text("インストールコマンド")],
)],
),
clipboard::control(
false,
vec![],
vec![
clipboard::input(value, false, vec![("id", input_id)]),
clipboard::trigger(
false,
vec![],
vec![
clipboard::indicator(false, false, vec![], vec![text("Copy")]),
clipboard::indicator(
true,
false,
vec![],
vec![text("Copied!")],
),
],
),
],
),
],
),
div(
vec![("class", "blocks-hero-install-command-actions")],
vec![
// レビュー指摘対応(P2、イシュー #2786 Codex 指摘):
// 遷移先・クリック処理を持たない CTA のため、addon
// ボタン(instance_b)と同型の判断で `disabled: true`
// にして「押しても何も起きない」ことを明示する。
button::button(
&ButtonProps {
disabled: true,
..ButtonProps::default()
},
vec![("data-blocks-hero-install-command-cta", "")],
vec![text("はじめる")],
),
button::button(
&ButtonProps {
variant: ButtonVariant::Outline,
disabled: true,
..ButtonProps::default()
},
vec![("data-blocks-hero-install-command-cta", "")],
vec![text("ドキュメントを見る")],
),
],
),
],
)
}
/// B(左寄せ・`input-group` 形式)を組み立てる。コピー状態そのものは
/// 持たない構成(モジュール doc「3 インスタンスの併記」節参照)。
fn instance_b() -> Node {
let field_id = "blocks-hero-install-command-b-input";
let field_props = FieldProps {
id: field_id,
ids: FieldIds::default(),
disabled: false,
invalid: false,
required: false,
readonly: true,
has_helper_text: false,
};
let group_props = InputGroupProps {
disabled: false,
invalid: false,
};
div(
vec![
("data-blocks-hero-install-command-hero", ""),
("data-align", "start"),
],
vec![
styled_text::text(
&TextProps {
size: TextSize::Sm,
..TextProps::default()
},
vec![],
vec![text("既存プロジェクトに追加")],
),
heading::heading(
HeadingLevel::H3,
&HeadingProps {
size: HeadingSize::Xl4,
..HeadingProps::default()
},
vec![],
vec![text("依存クレートを追加する")],
),
styled_text::text(
&TextProps {
size: TextSize::Lg,
variant: TextVariant::Muted,
..TextProps::default()
},
vec![],
vec![text("`Cargo.toml` に 1 行追加するだけで導入できます。")],
),
field::root(
&FieldRootProps {
orientation: FieldOrientation::Vertical,
},
&field_props,
vec![],
vec![
visually_hidden::root(
vec![],
vec![field::label(
&field_props,
vec![],
vec![text("追加コマンド")],
)],
),
input_group::root(
&group_props,
vec![("data-blocks-hero-install-command-command", "")],
vec![
input::input(
&InputProps::default(),
&field_props,
vec![("value", "cargo add fandhe-frontend-core")],
),
input_group::addon(
InputGroupAlign::InlineEnd,
&group_props,
vec![],
vec![input_group::button(
// レビュー指摘対応(P1、イシュー #2786
// codex 指摘): この addon ボタンは
// `clipboard` scope の外側にあるため
// `fandhe-frontend-wasm-full` の
// `headless_clipboard` 配線が届かず、
// 実アプリに組み込んでも押下時に
// コピーは起きない。`gallery_carousel`
// の prev/next trigger・
// `feature_tabs_panel` の CTA と同型の
// 判断で `disabled: true`
// (ボタン単体のみ、`group_props` 自体は
// 変更せず addon/input の見た目は保つ)
// にして「押しても何も起きない」ことを
// 明示する。
&InputGroupProps {
disabled: true,
..group_props
},
vec![],
vec![text("コピー")],
)],
),
],
),
],
),
div(
vec![("class", "blocks-hero-install-command-actions")],
vec![button::button(
// レビュー指摘対応(P2、イシュー #2786 Codex 指摘):
// 遷移先・クリック処理を持たない CTA のため disabled で
// 明示する(A と同型の判断)。
&ButtonProps {
disabled: true,
..ButtonProps::default()
},
vec![("data-blocks-hero-install-command-cta", "")],
vec![text("詳しく見る")],
)],
),
],
)
}
/// C(パンくず付き左寄せ・`clipboard` 形式・idle)を組み立てる。`id` は
/// [`instance_a`] と対になる独立マウントルート識別子(同関数 doc 参照)。
fn instance_c() -> Node {
let value = "fw new my-app";
div(
vec![
("id", "blocks-hero-install-command-c"),
("data-blocks-hero-install-command-hero", ""),
("data-align", "start"),
],
vec![
breadcrumb::root(
Size::Md,
BreadcrumbVariant::default(),
Some("Breadcrumb"),
vec![],
vec![breadcrumb::list(
vec![],
vec![
breadcrumb::item(
vec![],
vec![breadcrumb::link(REPO_URL, vec![], vec![text("Docs")])],
),
breadcrumb::separator(vec![], vec![text("/")]),
breadcrumb::item(
vec![],
vec![breadcrumb::current_link(vec![], vec![text("Quick Start")])],
),
],
)],
),
heading::heading(
HeadingLevel::H3,
&HeadingProps {
size: HeadingSize::Xl4,
..HeadingProps::default()
},
vec![],
vec![text("新規プロジェクトを作る")],
),
styled_text::text(
&TextProps {
size: TextSize::Lg,
variant: TextVariant::Muted,
..TextProps::default()
},
vec![],
vec![text(
"コピーしたコマンドをターミナルに貼り付けて実行します。",
)],
),
clipboard::root(
value,
false,
vec![("data-blocks-hero-install-command-command", "")],
vec![clipboard::control(
false,
vec![],
vec![
clipboard::value_text(
vec![],
vec![code::code(&CodeProps::default(), vec![], vec![text(value)])],
),
clipboard::trigger(
false,
vec![],
vec![
clipboard::indicator(false, false, vec![], vec![text("Copy")]),
clipboard::indicator(true, false, vec![], vec![text("Copied!")]),
],
),
],
)],
),
],
)
}
pub fn demo() -> Node {
div(
vec![("class", "blocks-hero-install-command-layout")],
vec![instance_a(), instance_b(), instance_c()],
)
}原案差分メモ
- R0521/R0123(主参照)を基準形(A)とし、R0522(B: input-group 形式)・R0523(C: パンくず付き左寄せ)の構成差分を集約しました。
- 参照元の文言・配色・アイコンは持ち込まず、コマンド文字列・見出し等はすべて独自に作成した架空の値です。
href="#"は使わず、breadcrumb::linkの遷移先は本リポジトリの公開 URL 固定 1 件のみです。