スケジュールグリッドScheduleGridExperimental
A 2-D matrix grid: a row axis × a column axis of rich content cells with a frozen first column + sticky header row, role=grid semantics (rowheaders / columnheaders / gridcells with composed accessible names), roving-tabindex arrow-key navigation, per-cell tone (a destructive flag ring), an unavailable slot treatment, and a contained horizontal scroll that does not push the page on mobile. For any rows×columns matrix of rich navigable/editable cells — timetables (periods×days), gradebooks (students×subjects), shift rosters, comparison/cohort matrices, availability and room/resource booking grids. (Not for sortable list data — that is DataTable; not for value-by-color heatmaps — that is HeatmapChart.)
プレビュー
Props
表は横にスクロールできます
| プロパティ | 型 | 初期値 | 説明 |
|---|---|---|---|
| rows | ScheduleAxisItem[] | - | Row axis (e.g. periods) — rendered as rowheaders down the left. { id, label, sublabel?, ariaLabel? }. |
| columns | ScheduleAxisItem[] | - | Column axis (e.g. days) — rendered as columnheaders across the top. |
| cells | ScheduleCell[] | - | Cells addressed by rowId + colId. Slots with no cell render renderEmpty. |
| ScheduleCell | { rowId; colId; content?; tone?; description?; ariaLabel?; onSelect?; unavailable? } | - | content = cell body; tone (destructive adds a conflict ring); onSelect makes it an activatable button; unavailable = dashed non-interactive; description is appended to the auto '<column> <row>' accessible name; ariaLabel overrides it fully. |
| label | ReactNode | - | Accessible name for the whole grid (required). |
| cornerLabel | ReactNode | - | Top-left corner header. |
| minColumnWidth | number | 112 | Min px per column before horizontal scroll kicks in. |
| rowHeaderWidth | number | 72 | Width (px) of the sticky row-header column. |
| renderEmpty | (row, column) => ReactNode | - | Render a slot that has no cell (available/empty). Default a muted dash. |
| unavailableLabel | string | "利用不可" | Announced (and shown) for an unavailable slot. |
Usage
import { ScheduleGrid, type ScheduleAxisItem, type ScheduleCell } from "@gunjo/ui"
const periods: ScheduleAxisItem[] = [
{ id: "p1", label: "1限", sublabel: "8:50" },
{ id: "p2", label: "2限", sublabel: "9:50" },
]
const days: ScheduleAxisItem[] = [
{ id: "mon", label: "月", ariaLabel: "月曜" },
{ id: "tue", label: "火", ariaLabel: "火曜" },
]
const cells: ScheduleCell[] = [
{
rowId: "p1", colId: "mon",
content: <LessonCard subject="数学" teacher="佐藤" room="2-A" />,
description: "数学、佐藤、2-A", // appended to the "<col> <row>" a11y name
onSelect: () => openEditor("p1", "mon"),
},
{
rowId: "p2", colId: "tue",
tone: "destructive", // conflict → ring + tone
description: "数学、佐藤、競合あり",
content: <LessonCard subject="数学" teacher="佐藤" conflict />,
onSelect: () => openEditor("p2", "tue"),
},
{ rowId: "p2", colId: "mon", unavailable: true }, // e.g. no class this slot
]
<ScheduleGrid label="2年A組 週間時間割" cornerLabel="時限" rows={periods} columns={days} cells={cells} />