feature-four-column-grid
heading / text / card / icon / link-overlay / highlight の 6 部品を合成した、中央寄せ見出し + 導入文の下へアイコン付き feature カードを 4 枚並べるグリッドです。
3 つの静的インスタンスを縦に並べています。1 つ目は md(48rem)以上で 2 列・lg(64rem)以上で 4 列に広がる基準形、2 つ目は lg 幅になっても 2×2 のまま列数を増やさない配置、3 つ目はカード全体がリンクになる全面リンクカードです(右上の矢印アイコンは装飾で、実際のリンク意味はカードタイトルの aria-label が担います)。いずれも幅 md 未満は 1 列です。文言・データはすべて架空のもので、データ取得・送信は行わない静的な表示例です。<form> は使用しません。
Demo
アイコンで示す4 つの特長(基準形)
4 つの特長をアイコン付きカードで紹介する構成例です。
md 2 列 → lg 4 列の基準形。
決定的なビルド
同じ入力からは常に同じ静的ファイルを生成します。
型で表す構造
スロットと props は Rust の型で表現されます。
既定エスケープ
テキスト補間は既定でエスケープされます。
外部依存ゼロの描画コア
描画コアは外部クレートに依存しません。
狭い密度で示す4 つの特長(lg でも 2×2)
4 つの特長をアイコン付きカードで紹介する構成例です。
lg 幅になっても 2×2 のまま列数を増やさない配置例。
決定的なビルド
同じ入力からは常に同じ静的ファイルを生成します。
型で表す構造
スロットと props は Rust の型で表現されます。
既定エスケープ
テキスト補間は既定でエスケープされます。
外部依存ゼロの描画コア
描画コアは外部クレートに依存しません。
使用部品
Rust コード
Rust
use fandhe_frontend_core::{div, el, text, Node};
use fandhe_frontend_pre_styled_ui::card::{self, CardProps};
use fandhe_frontend_pre_styled_ui::heading::{
self, HeadingLevel, HeadingProps, HeadingSize, HeadingWeight,
};
use fandhe_frontend_pre_styled_ui::highlight::{highlight, HighlightProps};
use fandhe_frontend_pre_styled_ui::icon::{icon, IconProps};
use fandhe_frontend_pre_styled_ui::link_overlay::{self, overlay};
use fandhe_frontend_pre_styled_ui::text::{self as styled_text, TextProps, TextSize, TextVariant};
use fandhe_frontend_pre_styled_ui::Size;
/// リンク先の固定外部 URL(モジュール doc「リンク先の方針」節参照)。
const REPO: &str = "https://github.com/Fandhe-AI/fandhe-frontend";
/// カード 1 件分の架空データ(見出し・説明・自作アイコンのパス)。
struct FeatureCard {
title: &'static str,
body: &'static str,
icon_path_d: &'static str,
}
/// カード 4 件(架空、実在の企業・製品とは無関係)。自作の単純な幾何パス
/// のみを使い、lucide 等の著作物は複製しない。
const CARDS: [FeatureCard; 4] = [
FeatureCard {
title: "決定的なビルド",
body: "同じ入力からは常に同じ静的ファイルを生成します。",
icon_path_d: "M12 3l9 6-9 6-9-6z",
},
FeatureCard {
title: "型で表す構造",
body: "スロットと props は Rust の型で表現されます。",
icon_path_d: "M4 4h16v16H4z",
},
FeatureCard {
title: "既定エスケープ",
body: "テキスト補間は既定でエスケープされます。",
icon_path_d: "M12 21a9 9 0 100-18 9 9 0 000 18z",
},
FeatureCard {
title: "外部依存ゼロの描画コア",
body: "描画コアは外部クレートに依存しません。",
icon_path_d: "M12 2v8M8 6l4-4 4 4M4 14h16v8H4z",
},
];
/// 装飾用の自作幾何アイコン(`fill="none"` + `stroke="currentColor"` の
/// 線画、`feature_side_heading_grid::geo_icon` と同型の判断)。
fn geo_icon(path_d: &'static str, attrs: Vec<(&'static str, &'static str)>) -> Node {
icon(
&IconProps {
size: Size::Lg,
..IconProps::default()
},
attrs,
vec![el(
"path",
vec![
("d", path_d),
("fill", "none"),
("stroke", "currentColor"),
("stroke-width", "2"),
("stroke-linecap", "round"),
("stroke-linejoin", "round"),
],
vec![],
)],
)
}
/// 右上向きの装飾用矢印アイコン(全面リンクカード用、`banner_floating_card
/// ::arrow_icon` と同型の自作パス)。
fn top_right_arrow_icon() -> Node {
icon(
&IconProps {
size: Size::Sm,
..IconProps::default()
},
vec![("data-blocks-feature-four-column-grid-arrow", "")],
vec![el(
"path",
vec![
("d", "M7 17L17 7M9 7h8v8"),
("fill", "none"),
("stroke", "currentColor"),
("stroke-width", "2"),
("stroke-linecap", "round"),
("stroke-linejoin", "round"),
],
vec![],
)],
)
}
/// 中央寄せの導入部(見出しの一部を `highlight` で強調 + リード文)。
fn header(
title_prefix: &'static str,
highlighted: &'static str,
title_suffix: &'static str,
) -> Node {
div(
vec![("class", "blocks-feature-four-column-grid-header")],
vec![
heading::heading(
HeadingLevel::H3,
&HeadingProps {
size: HeadingSize::Xl3,
weight: HeadingWeight::Bold,
},
vec![],
vec![
text(title_prefix),
highlight(
&HighlightProps {
query: &[highlighted],
..HighlightProps::default()
},
vec![],
highlighted,
),
text(title_suffix),
],
),
styled_text::text(
&TextProps {
variant: TextVariant::Muted,
..TextProps::default()
},
vec![("data-blocks-feature-four-column-grid-lead", "")],
vec![text("4 つの特長をアイコン付きカードで紹介する構成例です。")],
),
],
)
}
/// カード 1 枚(アイコン → 見出し → 説明)を組み立てる。`overlay_href` が
/// `Some` のとき `link_overlay` でカード全体をクリック可能にする
/// (R0106 系インスタンス用)。
fn feature_card(data: &FeatureCard, overlay_href: Option<&'static str>) -> Node {
let mut body: Vec<Node> = vec![
geo_icon(
data.icon_path_d,
vec![("data-blocks-feature-four-column-grid-icon", "")],
),
heading::heading(
HeadingLevel::H4,
&HeadingProps::default(),
vec![],
vec![text(data.title)],
),
styled_text::text(
&TextProps {
size: TextSize::Sm,
variant: TextVariant::Muted,
..TextProps::default()
},
vec![("data-blocks-feature-four-column-grid-desc", "")],
vec![text(data.body)],
),
];
let card_body = card::body(
vec![("data-blocks-feature-four-column-grid-card-body", "")],
{
if overlay_href.is_some() {
body.push(top_right_arrow_icon());
}
body
},
);
let card_children = match overlay_href {
None => vec![card_body],
Some(href) => vec![link_overlay::root(
vec![("data-blocks-feature-four-column-grid-link", "")],
vec![
card_body,
overlay(
href,
vec![
("aria-label", data.title),
("data-blocks-feature-four-column-grid-overlay", ""),
],
vec![],
),
],
)],
};
card::root(
CardProps::default(),
vec![("data-blocks-feature-four-column-grid-card", "")],
card_children,
)
}
/// 1 インスタンス分(導入部 + カードグリッド)を組み立てる。`lg_columns_2`
/// が `true` のとき lg でも 2 列(R0945 相当)のまま、`false` なら lg で
/// 4 列(基準形/全面リンク形)になる。`overlay_href` は R0106 相当の全面
/// リンクカードにのみ渡す。
fn instance(
header_node: Node,
note: &'static str,
lg_columns_2: bool,
overlay_href: Option<&'static str>,
) -> Node {
let mut grid_attrs = vec![("class", "blocks-feature-four-column-grid-grid")];
if lg_columns_2 {
grid_attrs.push(("data-columns", "2"));
}
let cards: Vec<Node> = CARDS
.iter()
.map(|c| feature_card(c, overlay_href))
.collect();
div(
vec![("class", "blocks-feature-four-column-grid-instance")],
vec![
header_node,
styled_text::text(
&TextProps {
size: TextSize::Xs,
variant: TextVariant::Muted,
..TextProps::default()
},
vec![("data-blocks-feature-four-column-grid-note", "")],
vec![text(note)],
),
div(grid_attrs, cards),
],
)
}
/// `feature-four-column-grid` の Demo 本体。呼び出しごとに同一の `Node` を
/// 返す純関数(他 block と同じ状態を持たない設計)。
#[must_use]
pub fn demo() -> Node {
div(
vec![("class", "blocks-feature-four-column-grid-layout")],
vec![
instance(
header("アイコンで示す", "4 つの特長", "(基準形)"),
"md 2 列 → lg 4 列の基準形。",
false,
None,
),
instance(
header("狭い密度で示す", "4 つの特長", "(lg でも 2×2)"),
"lg 幅になっても 2×2 のまま列数を増やさない配置例。",
true,
None,
),
instance(
header("カード全体で示す", "4 つの特長", "(全面リンク)"),
"カード全体がリンクになる配置例。",
false,
Some(REPO),
),
],
)
}原案差分メモ
参照(対応表 ID R0490・R0106・R0945。出典の固有名・ファイル名は記載しません)からの意図的な差分は次のとおりです。
- R0490 を基準形(中央寄せ見出し + アイコン付き feature カード 4 枚、md 2 列/lg 4 列)とし、最初のインスタンスにしました。
- R0945 の「lg でも 2×2 に留める」列数違いを 2 つ目のインスタンスにし、
[data-columns="2"]属性で lg のグリッド上書きのみを行いました(カードデータ自体は基準形と共通です)。 - R0106 の「4 枚とも全面リンクカード」を 3 つ目のインスタンスにし、
link-overlayでカード全体をクリック可能にしました。リンク先は実在の外部 URL(このリポジトリ自体)を使い、href="#"の死リンクは使いません。 - 3 インスタンスとも同じ 4 件のカードデータを使い回し、差分を配置(列数・全面リンク化)のみで示しました。
- 見出しの一部を
highlightで強調し、h3/h4に下げました(ページ側が## Demoとしてh2を出すため)。 - カードのアイコンは自作の単純な幾何パス(正三角形・正方形・円・矢印)のみを使い、lucide 等の著作物は複製していません。