promo-offers-split
上段にオファー 3 件を横に並べた帯(各オファーは短い見出しと説明をまとめた 1 個のリンク)、下段に左テキスト・右画像の 2 列を置くブロックです。heading / text / image / link / separator の 5 部品を合成します。Blocks は既存部品の合成例であり、新しい UI 部品は追加しません。
主参照は対応表 ID R1200 です。
オファー文言・リード文はすべて架空のデータであり、実在の企業・ブランド・PII・実クレデンシャルは含みません。画像はビルド時生成の同梱プレースホルダー SVG です。
本 Demo は無 JS の静的表示のみであり、<form> を含みません。送信処理・状態管理は一切持ちません。
Demo
使用部品
Rust コード
Rust
use crate::blocks::dummy_assets;
use fandhe_frontend_core::{div, section, text, Node};
use fandhe_frontend_pre_styled_ui::heading::{self, HeadingLevel, HeadingProps, HeadingSize};
use fandhe_frontend_pre_styled_ui::image::{self, ImageProps};
use fandhe_frontend_pre_styled_ui::link::{self, LinkProps};
use fandhe_frontend_pre_styled_ui::separator::{self, SeparatorProps};
use fandhe_frontend_pre_styled_ui::text::{
self as styled_text, TextProps, TextSize, TextVariant, TextWeight,
};
use fandhe_frontend_pre_styled_ui::Orientation;
/// リンク先の固定外部 URL(モジュール doc「リンク先の方針」節参照)。
const REPO: &str = "https://github.com/Fandhe-AI/fandhe-frontend";
/// オファー帯 3 件分の(短い見出し, 説明)の組。架空の文言であり、実在の
/// キャンペーン条件を示すものではない。
const OFFERS: [(&str, &str); 3] = [
("送料無料", "全国どこでも配送料がかかりません"),
("30 日間返品可", "気に入らなければ全額返金いたします"),
(
"会員限定ポイント",
"次回以降のお買い物に使えるポイントを進呈",
),
];
/// オファー帯の区切り線 1 組(横向き・縦向きを両方出力し、
/// `64rem` 境界で表示側を切り替える。モジュール冒頭「区切り線を横・縦の
/// 2 個で切り替える理由」節参照)。
fn offer_divider() -> Vec<Node> {
vec![
separator::separator(
&SeparatorProps {
orientation: Orientation::Horizontal,
..SeparatorProps::default()
},
vec![("data-blocks-promo-offers-split-divider", "horizontal")],
),
separator::separator(
&SeparatorProps {
orientation: Orientation::Vertical,
..SeparatorProps::default()
},
vec![("data-blocks-promo-offers-split-divider", "vertical")],
),
]
}
/// オファー 1 件(短い見出し + 説明をまとめたリンク 1 個)。
fn offer(title: &'static str, description: &'static str) -> Node {
link::root(
REPO,
&LinkProps::default(),
vec![("data-blocks-promo-offers-split-offer", "")],
vec![
styled_text::text(
&TextProps {
size: TextSize::Sm,
weight: TextWeight::Semibold,
..TextProps::default()
},
vec![("data-blocks-promo-offers-split-offer-title", "")],
vec![text(title)],
),
styled_text::text(
&TextProps {
size: TextSize::Sm,
variant: TextVariant::Muted,
..TextProps::default()
},
vec![("data-blocks-promo-offers-split-offer-desc", "")],
vec![text(description)],
),
],
)
}
/// 上段のオファー帯(オファー 3 件 + 区切り線 2 組)。
fn offers_strip() -> Node {
let mut children = Vec::new();
for (index, (title, description)) in OFFERS.iter().enumerate() {
if index > 0 {
children.extend(offer_divider());
}
children.push(offer(title, description));
}
div(
vec![("class", "blocks-promo-offers-split-offers")],
children,
)
}
/// 下段(左テキスト・右画像の 2 列)。
fn split() -> Node {
section(
vec![("class", "blocks-promo-offers-split-split")],
vec![
div(
vec![("class", "blocks-promo-offers-split-content")],
vec![
heading::heading(
HeadingLevel::H3,
&HeadingProps {
size: HeadingSize::Xl3,
..HeadingProps::default()
},
vec![("data-blocks-promo-offers-split-title", "")],
vec![text("季節の特典をまとめてご案内")],
),
styled_text::text(
&TextProps {
size: TextSize::Lg,
..TextProps::default()
},
vec![("data-blocks-promo-offers-split-lead", "")],
vec![text(
"対象商品のご購入で、上段 3 つの特典をすべて自動的に適用します。",
)],
),
link::root(
REPO,
&LinkProps::default(),
vec![("data-blocks-promo-offers-split-cta", "")],
vec![text("対象商品を見る")],
),
],
),
image::image(
&ImageProps::new(dummy_assets::PRODUCT_SRC, ""),
vec![("data-blocks-promo-offers-split-image", "")],
),
],
)
}
/// `promo-offers-split` の Demo 本体。呼び出しごとに同一の `Node` を返す
/// 純関数(他 block と同じ契約)。
pub fn demo() -> Node {
div(
vec![("class", "blocks-promo-offers-split-layout")],
vec![offers_strip(), split()],
)
}差分メモ
集約元は主参照 R1200 の 1 件のみです。
- R1200(主参照・唯一の集約元): オファー帯 + 左テキスト・右画像の 2 列構成をそのまま採用しています。集約元が 1 件のため並記すべき差分はありません。
- 区切り線の向き切替:
separatorはrole="separator"とaria-orientationを常時出力し、装飾専用のオプションを持ちません。見た目だけを CSS で回転させるとaria-orientationが表示と食い違うため、横向き・縦向きの区切り線をそれぞれ出力しdisplay: noneで表示/非表示を切り替えています(表示中の向きと aria は常に一致します)。 - オファー見出しを
textで表す理由:## Demo自体が h2・下段の本見出しが h3 の文書構造の中で、オファー 3 件へさらに見出し要素を使うと見出し構造が騒がしくなるため、TextWeight::Semiboldのtextで表しています。 - 状態表示: 無 JS のため本 block はトグル系の状態(開閉・選択等)を持たず、状態違いの静的並記は行っていません。
- lg 未満の縦積み:
64remはBreakpoint::Lg(fandhe_frontend_pre_styled_ui::recipe::Breakpoint::Lg)と一致するリテラル値です。