PokémonCn

Poké Ball

Preview

Standard (Kanto/Hoenn/Sinnoh)

master
ultra
great
poke
safari
net
dive
nest
repeat
timer
luxury
premier
dusk
heal
quick
cherish

Apricorn family (Johto signature) + Park

fast
level
lure
heavy
love
friend
moon
sport
park

Scale 1 / 2 / 4 / 6 (luxury)

Installation

$pnpm dlx shadcn@latest add https://pokemoncn.dev/r/poke-ball.json

Usage

<PokeBall variant="poke" />
<PokeBall variant="master" scale={4} />
<PokeBall variant="lure" />

Component code

Same source `shadcn add` drops into your project. Multi-file components ship every file separately — auto-generated data files are collapsed by default.

ui/poke-ball.tsx98 lines · 2.7 KB
import * as React from "react"

import { cn } from "@/lib/utils"
import { BALLS, type BallVariant } from "./poke-ball-data"

/**
 * PokeBall — every Poké Ball icon from HG/SS, pixel-perfect.
 *
 * Decoded directly from `pret/pokeheartgold` `files/itemtool/itemdata/item_icon`
 * — each variant pairs a 4bpp NCGR (32×32 native, ~18×18 once cropped) with its
 * own 16-colour BGR555 NCLR. We greedy-RLE the pixels into rectangles, drop
 * palette-0 (NDS transparent), and render as inline `<rect>`s with crispEdges
 * so it stays sharp at any scale.
 *
 * 25 variants total — the standard 16 plus the Apricorn balls and Park Ball
 * that Johto specifically reintroduces. Same files the game ships with, so
 * `<PokeBall variant="lure" />` is exactly what your save file would draw.
 *
 * The icon itself is `aria-hidden`. Children render as a positioned overlay
 * (`position: relative; z-index: 10`), useful for a quantity counter,
 * checkmark, or any badge you want to slap on top.
 */

interface PokeBallProps extends React.HTMLAttributes<HTMLSpanElement> {
  /** Which Poké Ball. Defaults to the basic `"poke"` ball. */
  variant?: BallVariant
  /** Pixel scale multiplier — 1 = native ~18px, 2 = 36px, 3 = 54px… */
  scale?: number
  /** Optional adornment rendered on top (e.g. a quantity counter). */
  children?: React.ReactNode
}

function PokeBall({
  variant = "poke",
  scale = 2,
  className,
  style,
  children,
  ...rest
}: PokeBallProps) {
  const data = BALLS[variant]
  const w = data.w * scale
  const h = data.h * scale
  return (
    <span
      {...rest}
      data-ball={variant}
      className={cn("inline-block", className)}
      style={{
        width: w,
        height: h,
        position: "relative",
        lineHeight: 0,
        ...style,
      }}
    >
      <svg
        width={w}
        height={h}
        viewBox={`0 0 ${data.w} ${data.h}`}
        shapeRendering="crispEdges"
        aria-hidden
        focusable={false}
        style={{ display: "block", pointerEvents: "none" }}
      >
        {data.rects.map(([x, y, rw, rh, p], i) => (
          <rect
            key={i}
            x={x}
            y={y}
            width={rw}
            height={rh}
            fill={data.palette[p - 1]}
          />
        ))}
      </svg>
      {children != null ? (
        <span
          style={{
            position: "absolute",
            inset: 0,
            zIndex: 10,
            display: "flex",
            alignItems: "flex-end",
            justifyContent: "flex-end",
            pointerEvents: "auto",
          }}
        >
          {children}
        </span>
      ) : null}
    </span>
  )
}

export { PokeBall, BALLS }
export type { PokeBallProps, BallVariant }
ui/poke-ball-data.ts148 lines · 39.9 KB