有効期限ExpiryBadgeExperimental
期限日を有効、期限間近、失効、未登録の状態に分類して表示する期限バッジです。
プレビュー
30 日以内
適性診断は 22 日後に期限を迎えます。現在の判定は「期限間近」です。
- 普通二種免許
- 2029/03/15残991日有効
- 適性診断(適齢)
- 2026/07/20残22日期限間近
- 健康診断
- 2026/06/1018日超過失効
- 地理試験合格証
- 未登録
状態とバリエーション
4つの状態
valid / expiring / expired / missing を、アイコンと文言を含むチップで表示します。
- 普通二種免許
- 2029/03/15残991日有効
- 適性診断(適齢)
- 2026/07/20残22日期限間近
- 健康診断
- 2026/06/1018日超過失効
- 地理試験合格証
- 未登録
日付と残日数の制御
showDate と hideRemaining で、一覧密度に合わせて表示量を調整します。
期限間近残22日期限間近2026/07/20
いつから知らせるか
warnWithinDays は「期限間近」に入る日数です。同じ期限日でも、書類ごとに知らせ始める時期は違います。
有効2026/08/05残38日既定(30日前から)
有効2026/08/05残38日健診の運用(14日前から)
期限間近2026/08/05残38日車検の運用(60日前から)
プロパティ
表は横にスクロールできます
| プロパティ | 型 | 初期値 | 説明 |
|---|---|---|---|
| value | string | number | Date | null | - | 判定する期限日です。null または未指定は missing になります。 |
| today | string | number | Date | new Date() | 比較基準日です。SSR では明示指定します。 |
| warnWithinDays | number | 30 | この日数以内を expiring と判定します。 |
| showDate | boolean | true | 状態チップの横に日付を表示します。 |
| hideRemaining | boolean | false | 残日数または超過日数を隠します。 |
| formatDate | (date: Date) => string | - | 日付表示の整形関数です。 |
| formatRemaining | (days: number, state: ExpiryState) => ReactNode | - | 残日数または超過日数の表示文言を整形します。 |
| labels | Partial<Record<ExpiryState, ReactNode>> | - | 状態ラベルを上書きします。 |
| stateClassName | string | - | 状態チップ部分だけに追加する className です。表や一覧で幅を揃える時に使います。 |
| statePosition | "start" | "end" | "start" | 状態チップを日付・残日数の前後どちらに置くかを指定します。 |
| classifyExpiry | (value, options) => { state, days } | - | UI なしで同じ期限分類を返す純関数です。 |
使い方
import * as React from "react";
import { ExpiryBadge, MetadataList, Slider, classifyExpiry } from "@gunjo/ui";
const today = "2026-06-28";
const expiryBadgeAlignClass = "w-[244px] justify-end";
const expiryBadgeStateClass = "w-20 shrink-0 justify-start";
const expiryLabels = {
valid: "有効",
expiring: "期限間近",
expired: "失効",
missing: "未登録",
} as const;
function formatExpiryRemaining(days: number) {
if (days < 0) return Math.abs(days) + "日超過";
if (days === 0) return "本日まで";
return "残" + days + "日";
}
export function ComplianceExpiryList() {
const [warnWithinDays, setWarnWithinDays] = React.useState(30);
const rows = [
{ label: "普通二種免許", value: "2029-03-15" },
{ label: "適性診断(適齢)", value: "2026-07-20" },
{ label: "健康診断", value: "2026-06-10" },
{ label: "地理試験合格証", value: null },
];
const assessment = classifyExpiry("2026-07-20", { today, warnWithinDays });
return (
<div className="flex w-full max-w-xl flex-col gap-4 rounded-lg border bg-card p-4">
<Slider
min={7}
max={90}
step={1}
value={warnWithinDays}
onValueChange={setWarnWithinDays}
aria-label="期限間近の判定日数"
/>
<p className="text-xs text-muted-foreground">
適性診断は {assessment.days} 日後に期限を迎えます。現在の判定は「{expiryLabels[assessment.state]}」です。
</p>
<MetadataList
items={rows.map((row) => ({
label: row.label,
value: (
<ExpiryBadge
value={row.value}
today={today}
warnWithinDays={warnWithinDays}
labels={expiryLabels}
formatRemaining={formatExpiryRemaining}
className={expiryBadgeAlignClass}
stateClassName={expiryBadgeStateClass}
statePosition="end"
/>
),
}))}
/>
</div>
);
}設計の判断
- 判定を部品から外に出した。
classifyExpiry()は表示を持たない関数で、日付を渡すと「有効・期限間近・失効・未登録」と残り日数を返します。表の並べ替えや件数の集計は、バッジを描かずにこの関数だけで済みます。 - 色だけに意味を乗せない。4つの状態それぞれに別のアイコンと文字(有効/期限間近/失効/未登録)が付きます。資料も「色だけでステータスを表現しない」を核に挙げています。文字は
labelsで差し替えられます。 - 「期限間近」の線は呼ぶ側が引く。
warnWithinDaysの既定は30日です。車検と資格と保険では警告を出したい時期が違うので、部品の中に固定しませんでした。数のバッジではないので、資料の「0件で非表示」「99件を超えたら99+」の丸めは持ちません。todayも props で、サーバーとブラウザで結果がずれないようにしています。
一般のバッジの設計は UIXHERO の「バッジ」にあります。 UIXHERO: バッジ(Badge)
使用コンポーネント
関連コンポーネント
設計の判断(UIXHERO)
「いつ・なぜ使うか」の判断は、姉妹サイト UIXHERO の記事で解説しています。