Points Levels Timeline
trophy/points-levels-timelineFreeComponent
Vertical React timeline for level progression. Supports sub-levels, thresholds, and level progress for gamification roadmaps. Complements Points Levels List. Built on shadcn/ui + Tailwind. Open source.
Live preview
live · rendered by the registry
Rendered by trophy on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://ui.trophy.so/r/points-levels-timeline.jsonSource
1"use client"23import * as React from "react"4import { Star } from "lucide-react"56import { cn } from "@/lib/utils"78export interface PointsSubLevelTimeline {9 name: string10 points: number11}1213export interface PointsLevelTimeline {14 id: string15 key?: string16 name: string17 description?: string18 badgeUrl?: string | null19 points: number20 subLevels?: PointsSubLevelTimeline[]21}2223interface PointsLevelsTimelineProps extends React.HTMLAttributes<HTMLDivElement> {24 levels: PointsLevelTimeline[]25 currentPoints?: number26 currentLevelLabel?: string27 formatPoints?: (value: number) => string28}2930function formatRangeLabel(31 points: number,32 nextLevelPoints: number | null | undefined,33 formatPoints?: (value: number) => string34) {35 const format = formatPoints ?? ((value: number) => value.toLocaleString())36 if (typeof nextLevelPoints === "number") {37 return `${format(points)} - ${format(nextLevelPoints - 1)} XP`38 }39 return `${format(points)}+ XP`40}4142const PointsLevelsTimeline = React.forwardRef<43 HTMLDivElement,44 PointsLevelsTimelineProps45>(46 (47 {48 className,49 levels,50 currentPoints,51 currentLevelLabel = "Current",52 formatPoints,53 ...props54 },55 ref56 ) => {57 const hasTracking = typeof currentPoints === "number"58 const safeTotalPoints = hasTracking ? Math.max(0, currentPoints) : null5960 const currentLevelIndex = React.useMemo(() => {61 if (!hasTracking || safeTotalPoints === null || levels.length === 0)62 return -163 for (let i = levels.length - 1; i >= 0; i -= 1) {64 if (safeTotalPoints >= levels[i].points) return i65 }66 return -167 }, [hasTracking, levels, safeTotalPoints])6869 const clampedCurrentProgress = React.useMemo(() => {70 if (currentLevelIndex < 0 || safeTotalPoints === null) return 071 if (levels.length <= 1 || currentLevelIndex >= levels.length - 1)72 return 1007374 const currentPointsThreshold = levels[currentLevelIndex].points75 const nextPointsThreshold = levels[currentLevelIndex + 1].points76 const segmentSize = Math.max(77 1,78 nextPointsThreshold - currentPointsThreshold79 )80 const progressInSegment = safeTotalPoints - currentPointsThreshold81 return Math.max(0, Math.min((progressInSegment / segmentSize) * 100, 100))82 }, [currentLevelIndex, levels, safeTotalPoints])8384 const progressHeightPercent = React.useMemo(() => {85 if (currentLevelIndex < 0) return 086// … truncated
What it pulls in
npm packages
Files it writes
More from trophy
All 18 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Achievement Badgeachievement-badge | trophy | Components | Free | 1 dep | |
| Achievement Cardachievement-card | trophy | Components | Free | no deps | |
| Achievement Gridachievement-grid | trophy | Components | Free | 2 deps | |
| Achievement Listachievement-list | trophy | Components | Free | 2 deps | |
| Achievement Unlockedachievement-unlocked | trophy | Components | Free | 1 dep | |
| Leaderboard Cardleaderboard-card | trophy | Components | Free | no deps | |
| Leaderboard Podiumleaderboard-podium | trophy | Components | Free | 2 deps | |
| Leaderboard Rankingsleaderboard-rankings | trophy | Components | Free | 2 deps |
