座席表SeatMapExperimental

The 2-D selectable seat grid: rows × columns with aisle gaps (columns array with nulls for the aisle — 3-3 / 2-4-2 / 2-2 cabins), per-seat state (空席 / 予約済 / 確保中 / 選択中) and type (非常口 / 足元ゆったり / プレミアム / 窓側 / 通路側), controlled multi-select with a capacity cap (maxSelectable), and grid a11y: role=grid, arrow-key roving focus, aria-selected / aria-disabled, composed seat names (『12番A席、窓側、非常口座席、空席、¥1,500』). The seat/spot picker every booking flow needs — airline / 新幹線 seats, cinema, stadium, event hall, restaurant tables. State never rides on colour alone (selected shows a check, occupied an ×, plus an sr-only label). Owns its horizontal scroll so a wide cabin doesn't push the page on a phone. Controlled — pass selectedIds + onToggle. For a read-only intensity grid use HeatmapChart; this is the interactive picker.

プレビュー

座席を選択(最大 2 席)。選択中: 13E

A
B
C
D
E
F
空席特別席(非常口/足元ゆったり 等)選択中予約済

Props

表は横にスクロールできます
プロパティ初期値説明
columns(string | null)[]-列IDの並び。null は通路(3-3 / 2-4-2 / 2-2)。
seatsSeat[]-座席。各 { id, row, col, state?, type?, position?, fee? }。
Seat.state"available" | "occupied" | "held" | "blocked""available"空席状況。selected は selectedIds から導出。blocked は空きスペース。
Seat.typestring-種別(非常口 / 足元ゆったり / プレミアム)。読み上げ名+特別席の見た目に反映。
Seat.position"window" | "aisle" | "middle"-窓側 / 通路側。読み上げ名に反映。
Seat.feenumber-座席指定料。読み上げ名に反映。
selectedIdsstring[]-選択中の座席ID(controlled)。
maxSelectablenumber-選択上限。到達すると未選択席は操作不可に。
onToggle(seatId: string) => void-座席のトグル(操作可能な空席のみ発火)。
formatFee(fee: number) => string¥1,500読み上げ名の料金整形。
showHeadersbooleantrue列ヘッダ+行番号の表示。
hideLegendboolean-凡例を隠す。

Usage

import { SeatMap, type Seat } from "@gunjo/ui"

const columns = ["A", "B", "C", null, "D", "E", "F"] // null = 通路

<SeatMap
  columns={columns}
  seats={[
    { id: "12A", row: 12, col: "A", position: "window", type: "非常口座席", fee: 1500 },
    { id: "12B", row: 12, col: "B", state: "occupied" },
    // …
  ]}
  selectedIds={selected}
  maxSelectable={2}
  onToggle={(id) => toggle(id)}
/>