Reward track
React 等级奖励列表组件,即赛季通行证或战令页面:每个等级一行,奖励放在格子里,状态分为已领取、可领取与未解锁。
安装
# once per project: where @zzzui items come frompnpm dlx shadcn@latest registry add @zzzui=https://zzzui.catforest.dev/r/{name}.jsonpnpm dlx shadcn@latest add @zzzui/reward-track示例
待领取的等级奖励
- Level 2, claimed.Claimed
- Level 3, ready to claim.
- Level 4, ready to claim, your level.
- Level 5, locked.Reach level 5
- Level 6, locked.Reach level 6
import { useState } from "react";import { Button } from "@/components/zero/button";import { CellIcon } from "@/components/zero/icons/cell";import { ChipIcon } from "@/components/zero/icons/chip";import { CrateIcon } from "@/components/zero/icons/crate";import { KeyIcon } from "@/components/zero/icons/key";import { KitIcon } from "@/components/zero/icons/kit";import { InventorySlot } from "@/components/zero/inventory";import { RewardTier, RewardTrack } from "@/components/zero/reward-track";const current = 4;const levels = [ { level: 2, rewards: [ { name: "Power cell", icon: CellIcon, rarity: "common", count: 2 }, ], }, { level: 3, rewards: [ { name: "Repair kit", icon: KitIcon, rarity: "uncommon", count: 1 }, { name: "Power cell", icon: CellIcon, rarity: "common", count: 3 }, ], }, { level: 4, rewards: [ { name: "Sealed crate", icon: CrateIcon, rarity: "rare", count: 1 }, ], }, { level: 5, rewards: [ { name: "Route chip", icon: ChipIcon, rarity: "epic", count: 1 }, { name: "Repair kit", icon: KitIcon, rarity: "uncommon", count: 2 }, ], }, { level: 6, rewards: [ { name: "Gate key", icon: KeyIcon, rarity: "legendary", count: 1 }, ], },] as const;export default function RewardTrackLevels() { const [claimed, setClaimed] = useState<number[]>([2]); const state = (level: number) => level > current ? "locked" : claimed.includes(level) ? "claimed" : "ready"; const ready = levels.filter((l) => state(l.level) === "ready"); return ( <div className="grid w-full max-w-2xl gap-3"> <div className="flex justify-end"> <Button size="sm" variant="secondary" disabled={ready.length === 0} onClick={() => setClaimed((c) => [...c, ...ready.map((l) => l.level)]) } > Claim all </Button> </div> <RewardTrack aria-label="Courier levels"> {levels.map((l) => ( <RewardTier key={l.level} level={l.level} state={state(l.level)} current={l.level === current} onClaim={() => setClaimed((c) => [...c, l.level])} > {l.rewards.map((r) => ( <InventorySlot key={r.name} rarity={r.rarity} count={r.count} aria-label={`${r.name}, ${r.count}`} > <r.icon /> </InventorySlot> ))} </RewardTier> ))} </RewardTrack> </div> );}行为
每个等级一行:大号斜体的等级数字放在斜切的槽中,奖励(InventorySlots)在中间,行末是可执行的操作。ready 点亮等级数字并加上强调色边缘,显示 Claim 按钮;claimed 将奖励变暗,并以对勾标明已领取;locked 将奖励进一步变暗,并说明解锁条件。current 标记当前所在的等级。
每行开头会以文字向读屏软件说明等级与状态(「Level 5, ready to claim」)。「Claim all」按钮应放在外层页面:只需在外层状态中做一次批量更新。
属性
RewardTier
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| level | number | — | 等级。 |
| state | "claimed" | "ready" | "locked" | — | 领取状态。 |
| current | boolean | — | 是否为当前所在等级。 |
| onClaim | () => void | — | 按下 Claim 时调用。 |
| note | ReactNode | — | 关于该等级本身的一行说明,不随状态变暗。 |
| locked | ReactNode | "Reach level n" | 未解锁时显示的解锁条件。 |
| labels | Partial<RewardTierLabels> | rewardTierLabels | 换成其他语言的文字:读屏前缀、「Claim」「Claimed」,以及未解锁时的条件。 |