リストカードListCardBeta
一覧の1項目を、先頭アクセサリ、本文、状態、メタ情報、選択状態付きで表示します。
プレビュー
状態とバリエーション
選択可能
onSelect を渡すとカード全体がボタンになります。
表示専用
onSelect を省略すると button ではなく表示用 div になります。
JC
中央線快速
人身事故の影響
運転見合わせ7:42 更新
10:42 → 11:14
32分・乗換0回・34.1km
最速最安
¥580
重さで面を変える
severity が変えるのは境界線と淡い背景だけで、行の意味そのものは status の文字が持ちます。色が見えなくても「停止中」と読めるので、重さは色に載せません。neutral は面を変えません。
配送ラインA
停止から18分
停止中
配送ラインB
処理待ちが積み上がっています
遅れ
配送ラインC
10:00 から保守の予定
予定あり
配送ラインD
所要時間は平常どおり
正常
配送ラインE
本日の稼働はありません
停止(予定)
プロパティ
表は横にスクロールできます
| プロパティ | 型 | 初期値 | 説明 |
|---|---|---|---|
| title | ReactNode | - | 主要行です。 |
| leading | ReactNode | - | 先頭アクセサリです。アイコン、アバター、路線チップなどを置きます。 |
| description | ReactNode | - | タイトル下の副次行です。 |
| tags | ReactNode | - | タイトル下に折り返すチップ列です。 |
| status | ReactNode | - | 右側の状態 Badge です。色だけに依存しない文言を含めます。 |
| meta | ReactNode | - | 右側の補足値です。 |
| trailing | ReactNode | - | 末尾アクセサリです。onSelect 時は既定で chevron です。 |
| severity | "critical" | "warning" | "info" | "success" | "neutral" | - | 重大度に応じた境界線と淡い背景です。状態の意味は status の文言で伝えます。 |
| selected | boolean | - | 選択中の見た目です。 |
| onSelect | () => void | - | 渡すとカード全体が44px以上のボタンになります。 |
使い方
import * as React from "react";
import { Badge, LineChip, ListCard, Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@gunjo/ui";
export function RouteResults() {
const rootRef = React.useRef<HTMLDivElement | null>(null);
const [portalContainer, setPortalContainer] = React.useState<HTMLElement | null>(null);
const [selectedId, setSelectedId] = React.useState<"incident" | "route-1" | null>(null);
const [open, setOpen] = React.useState(false);
const [isLargeViewport, setIsLargeViewport] = React.useState(false);
React.useEffect(() => {
setPortalContainer(rootRef.current?.closest<HTMLElement>("[data-doc-component-preview-surface]") ?? rootRef.current);
}, []);
React.useEffect(() => {
const media = window.matchMedia("(min-width: 1024px)");
const update = () => setIsLargeViewport(media.matches);
update();
media.addEventListener("change", update);
return () => media.removeEventListener("change", update);
}, []);
const detail = selectedId === "incident"
? { title: "中央線快速", status: "運転見合わせ", summary: "人身事故の影響で上下線の運転を見合わせています。", variant: "destructive" as const }
: { title: "10:42 → 11:14", status: "推奨経路", summary: "乗換なしで到着する最速・最安の経路です。", variant: "secondary" as const };
const select = (id: "incident" | "route-1") => {
setSelectedId(id);
setOpen(true);
};
return (
<div ref={rootRef} className="relative grid w-full max-w-2xl gap-4 overflow-hidden rounded-lg border bg-card p-4">
<div className="grid content-start gap-3">
<ListCard
leading={<LineChip label="JC" color="#f15a24" />}
title="中央線快速"
description="人身事故の影響"
status={<Badge variant="destructive">運転見合わせ</Badge>}
meta="7:42 更新"
severity="critical"
selected={selectedId === "incident"}
onSelect={() => select("incident")}
/>
<ListCard
title="10:42 → 11:14"
description="32分・乗換0回・34.1km"
tags={<><Badge variant="secondary">最速</Badge><Badge variant="secondary">最安</Badge></>}
meta="¥580"
selected={selectedId === "route-1"}
onSelect={() => select("route-1")}
/>
</div>
<Sheet open={open && selectedId != null} onOpenChange={setOpen} modal={false}>
<SheetContent
side={isLargeViewport ? "right" : "bottom"}
portalContainer={portalContainer}
overlayClassName="rounded-md"
closeLabel="閉じる"
className={isLargeViewport ? "grid w-[300px] max-w-[calc(100%-2rem)] p-4" : "max-h-[72%] overflow-y-auto p-4"}
>
<SheetHeader>
<SheetTitle>詳細</SheetTitle>
<SheetDescription>{detail.title}</SheetDescription>
</SheetHeader>
<div className="grid content-start gap-3 rounded-lg border bg-card p-3">
<p className="text-sm font-semibold text-foreground">{detail.title}</p>
<Badge variant={detail.variant} className="w-fit">{detail.status}</Badge>
<p className="text-sm leading-6 text-muted-foreground">{detail.summary}</p>
</div>
</SheetContent>
</Sheet>
</div>
);
}設計の判断
- 押せるようにするかどうかを、既定では決めない。
onSelectを渡したときだけ 44px 以上のbuttonになり、渡さなければただのdivです。資料は「カード全体をリンクにするか、部分だけにするか」を2番目の判断に挙げています。GUNJO はこれを既定で決めず、呼ぶ側の宣言にしました。 - 状態は色だけに乗せない。
severityが変えるのは全周の境界線と淡い背景だけで、状態そのものはstatusに渡す Badge の文字が持ちます。色を見分けられない人にも同じ情報が届くようにするためです。 - 右側は「状態」と「補足」の2段に固定した。
status(Badge)とmeta(価格・時刻・件数)は右上と右下に縦に積み、metaは折り返しません。行を何十も並べたときに、値の位置が行ごとにずれないようにするためです。
一般のカードの設計は UIXHERO の「カード」にあります。 UIXHERO: カード(Card)