コンテンツにスキップ

steps ステップバー —— ナビゲーション

ステップバーは「プロセスの進捗インジケーター」です:ショッピングカートの「カート → 情報入力 → 支払い → 完了」、ウィザード形式のフォーム。tabs との違いは順序のセマンティクスです——ステップには前後があり、状態が位置から派生します(完了 / 進行中 / 未処理)。core/steps が管理するのは状態の導出と、とても間違いやすいあるレイアウトの細部です:横向きステップバーが折り返すとき、行末の接続線を隠すこと。

  • 各端の <LhSteps> / <LhStep> の実装:注文プロセス、ウィザードフォーム、タスクの進捗、複数ステップの設定。
  • プロセスに明確な順序があり、かつユーザーが「どこまで進んだか」を知る必要がある場合。
  • 使うべきでない:同列のコンテンツ切り替え(tabs を使います——順序のセマンティクスがありません);単純な進捗パーセンテージ(progress を使います);階層ナビゲーション(menu か breadcrumb を使います)。
import {
getStepStatus,
getStepsClassNames,
getStepClassNames,
getStepsLineLastIndices,
getStepAria,
} from '@lh-ui/share/core/steps'
import type {
LhStepStatus, LhStepsDirection, LhStepStatusState, LhStepClassState,
} from '@lh-ui/share/core/steps'

状態は位置から派生する:getStepStatus のコアルールは index < current → finish、=== → process、> → wait です。ほとんどのシーンでは current だけを渡せば状態が自動的に算出されます——UI 層が各ステップの状態を明示的に宣言する必要はありません。

forced は全体上書き:Steps レベルの forced 状態(例:error)はすべてのステップをその状態にします。これは「プロセス全体の失敗」というセマンティクスです:支払いが失敗したとき、失敗するのはある 1 ステップではなく、プロセス全体が継続不可になります。優先度が最も高い(位置の導出より先に return する)点に注意してください。

末項はクラス名を出力せず :last-child に任せる:これがこのモジュールで最も繊細な設計です。コメントに理由が書かれています:子項はそれぞれの setup 段階でレンダリングされ、このとき後続の兄弟はまだ登録されていないため、登録総数から算出した isLast では初回画面(SSR / SSG を含む)ですべての項が末項だと誤判定されます。構造的な :last-child セレクタは CSS のレンダリング時に初めて評価されるため、自然と正しいです——代償は、接続線のスタイルを構造疑似クラスで実装しなければならないことです。

getStepsLineLastIndices が折り返し問題を解決する:横向きステップバーが定幅で折り返した後、各行の最後の項の接続線が行末の空白(次行の行頭)を指してしまい、視覚的に途切れます。この関数は隣り合う項の offsetTop の差から折り返し点を探します:offsetTops[i+1] > offsetTops[i] なら i+1 が次の行に移ったことを示し、i が行末です。行末項(グローバルな末項を含む)に対して UI 層が line-last クラスを追加して接続線を隠します。

lineLast と isLast は別物:isLast(グローバルな末項)は :last-child が担います;lineLast(行末項)は測定して求めるもので、UI 層がレンダリング後に offsetTop を読んで埋め戻します——横向きの折り返しシーンでのみ必要です。

関数 シグネチャ 戻り値 役割
getStepStatus (state: LhStepStatusState) => LhStepStatus 状態 forced 上書き / 位置から派生
getStepsClassNames (direction: LhStepsDirection) => string[] クラス名 方向
getStepClassNames (state: LhStepClassState) => string[] クラス名 状態 + クリック可 + 行末
getStepsLineLastIndices (offsetTops: number[]) => number[] 行末の添字 横向き折り返し検出
getStepAria (status: LhStepStatus) => LhAriaAttrs aria パック aria-current=“step”
export type LhStepStatus = 'wait' | 'process' | 'finish' | 'error'
export type LhStepsDirection = 'horizontal' | 'vertical'
export interface LhStepStatusState {
index: number // ステップの添字(0 始まり)
current: number // 現在のステップの添字
forced?: LhStepStatus // Steps レベルの全体上書き状態
}
export interface LhStepClassState {
status: LhStepStatus
clickable: boolean
lineLast?: boolean // 現在の行の最後の項かどうか(横向き折り返し時に UI 層が測定)
}
getStepAria('process') // { 'aria-current': 'step' }
getStepAria('finish') // {}
getStepAria('error') // {}
  • aria-current=“step” で現在のステップを示す:これは WAI-ARIA の専用セマンティクスで、スクリーンリーダーは「現在のステップ」と読み上げます——「進行中」というテキストによるヒントよりも標準的です。process 状態のときだけ出力します。
  • 順序付きリストのセマンティクス:ステップバーは <ol> でレンダリングすることが推奨されます——順序のセマンティクスが自然と内蔵されており、スクリーンリーダーは「2 項目目、全 4 項目中」と読み上げます。<div> で実装する場合は role="list" を補い、各項に role="listitem" を付けます。
  • 色だけでなくテキストで状態を伝える:finish のチェックマーク、process の数字、wait のグレー——色覚障害のユーザーには区別できません。各ステップのタイトルテキスト(「情報を入力」)+ アイコンの組み合わせで初めて完全になります。
  • error ステップの読み上げ:エラー状態は UI 層で aria-invalid を使うか、ステップタイトルの後に「(失敗)」というテキストを追加すべきです——赤いアイコンだけでは伝わりません。
  • クリック可能なステップのボタンのセマンティクス:clickable: true のとき、ステップタイトルは <button> としてレンダリングすべきです(完了済みのステップに戻るジャンプが可能)で、キーボードから到達可能です;クリック不可のときは単なるテキストです。
import { getStepStatus, getStepsLineLastIndices } from '@lh-ui/share/core/steps'
getStepStatus({ index: 0, current: 2 }) // 'finish'
getStepStatus({ index: 2, current: 2 }) // 'process'
getStepStatus({ index: 3, current: 2 }) // 'wait'
getStepStatus({ index: 0, current: 2, forced: 'error' }) // 'error'(forced が全体を上書き)
getStepStatus({ index: 2, current: 2, forced: 'wait' }) // 'wait'
// 横向き折り返し(1 行 2 項で、4 項が 2 行に折り返る):
getStepsLineLastIndices([10, 10, 60, 60]) // [1, 3](添字 2 の offsetTop のほうが大きい → 1 が行末;3 はグローバルな末項)
getStepsLineLastIndices([10, 10, 10]) // [2](折り返しなし、グローバルな末項のみ)
getStepsLineLastIndices([]) // []
// UI 層:レンダリング後に offsetTop を測定し、lineLast クラスを埋め戻す(横向きでのみ必要)
const items = ref<HTMLElement[]>([])
const lineLastSet = ref<Set<number>>(new Set())
onMounted(() => {
const offsetTops = items.value.map((el) => el.offsetTop)
lineLastSet.value = new Set(getStepsLineLastIndices(offsetTops))
})
// ウィンドウの resize を監視:折り返し点は幅に伴って変化する
<!-- Vue3:注文プロセス -->
<template>
<LhSteps :current="1">
<LhStep title="ショッピングカート" description="選択済み 3 点" />
<LhStep title="情報入力" description="お届け先と払込票" />
<LhStep title="支払い" />
<LhStep title="完了" />
</LhSteps>
</template>
<!-- 全体失敗(支払い失敗時にプロセス全体を赤くする) -->
<template>
<LhSteps :current="2" status="error">
<LhStep title="ショッピングカート" /><LhStep title="情報入力" />
<LhStep title="支払い" description="支払いがタイムアウトしました。やり直してください" />
<LhStep title="完了" />
</LhSteps>
</template>
// React:useLayoutEffect で offsetTop を測定(初回フレームの点滅を避ける)、resize 監視で再計算
// uniapp:<view> + flex;横向き折り返しはミニプログラム側でcreateSelectorQuery で各ノードの top を取得
クラス名 意味
.lh-steps ルートコンテナ(flex)
.lh-steps--{horizontal|vertical} 方向
.lh-steps__item ステップ項目
.lh-steps__item--{wait|process|finish|error} 状態(円の色 + 文字色 + 接続線の色)
.lh-steps__item--clickable クリック可能(hover ハイライト + ポインタ)
.lh-steps__item--line-last 行末項目(行末の空白を指す接続線を隠す)
.lh-steps__circle / __title / __description 円 / タイトル / 説明
.lh-steps__line 接続線(:last-child でグローバルな末項の線を隠す)

状態色:wait --lh-text-placeholder、process --lh-primary-color、finish --lh-success-color、error --lh-danger-color;接続線は通常 --lh-border-color、完了済みの区間は finish 色を使います。

  • lineLast は横向き折り返しのシーンでのみ必要:縦向きステップバーは各項が 1 行を独占し、接続線は自然と垂直に下へ向かうため、行末の問題がありません。固定幅の横向きステップバー(折り返さない)にも必要ありません——定幅 + 折り返し許可のときだけ測定します。
  • 測定のタイミング:offsetTop は DOM のレンダリング後に読まなければなりません(onMounted / useLayoutEffect)、かつウィンドウの resize 時に再計算します。初回フレームは点滅する可能性があります(接続線が一旦表示されてから隠される)が、許容範囲です;厳密なシーンでは visibility で表示を遅らせます。
  • forced は全体上書きであって単ステップではない:status prop に error を渡すとすべてのステップが error になります——完了済みのものも含めて、これが「プロセス全体の失敗」というセマンティクスです。特定の 1 ステップだけを失敗にしたいときは、その LhStep に単独で status を渡します(UI 層が単ステップの status を forced の単ステップ版にマージするか、その項のクラス名を直接上書きします)。
  • current は範囲を超えてもよい:current: 5 でステップが 4 つしかないときはすべて finish になります(index がすべて 5 未満のため)——「プロセス完了」の終了状態表示に使え、特別な判定は不要です。
  • SSR / SSG の末項の罠:これが getStepClassNames が isLast パラメータを受け取らない理由です。setup 期間中に「登録総数」に基づいて末項を算出するいかなる実装も、初回画面ですべての項を誤判定します——:last-child を使ってください。
  • クリック可能な戻りジャンプの業務制約:clickable は通常完了済みのステップに対してのみ有効にします(情報を見直すための戻る)—進行中と未処理のステップにはジャンプできません。任意のステップへのジャンプを許すかは業務で決め、core はクラス名を出力するだけです。
  • description のスペース:横向きステップバーに説明があると高さが増し、同じ行に折り返した際の行の高さは最も高い項で決まります——offsetTop を測定する前に、すべての項のレンダリングが完了していることを確認してください。
@lh-ui/share/core/steps → getStepStatus( )
返回值
(填好参数后点运行)

Copyright © 2024-2026 FengMo WenXue