{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "grade-badge",
  "type": "registry:ui",
  "title": "Grade Badge",
  "description": "Visual grade pill — A+ / A / A- / B+ / B / … / F. The atomic surface for scorecard pages: a single chip that announces a tier without making the reader compute. Five tones (excellent/good/fair/poor/fail) map onto the 13-grade ladder so the colour story…",
  "author": "ofri-peretz <https://github.com/ofri-peretz>",
  "categories": [
    "feedback",
    "primitive"
  ],
  "dependencies": [
    "class-variance-authority"
  ],
  "registryDependencies": [
    "https://ds.interlace.tools/r/theme.json",
    "https://ds.interlace.tools/r/cn.json"
  ],
  "files": [
    {
      "path": "registry/interlace-ui/grade-badge.tsx",
      "target": "components/ui/grade-badge.tsx",
      "type": "registry:ui",
      "content": "import * as React from 'react';\n\n// @interlace/grade-badge v1.1.0 — Interlace design system.\n// Docs, props and live preview: https://ds.interlace.tools/c/grade-badge\n// What changed since: https://ds.interlace.tools/c/grade-badge#history\n// Generated banner — keep it, the upgrade diff reads this version.\n\n/**\n * @interlace/ui — GradeBadge\n *\n * Visual grade pill — A+ / A / A- / B+ / B / … / F. The atomic surface for\n * scorecard pages: a single chip that announces a tier without making the\n * reader compute. Five tones (excellent/good/fair/poor/fail) map onto the\n * 13-grade ladder so the colour story stays manageable.\n *\n * ## Anatomy\n *\n *   <span data-slot=\"grade-badge\" data-grade=\"A+\" data-tone=\"excellent\">\n *     A+\n *   </span>\n *\n * ## MIN_VIEWPORT — 320\n *\n * | Rule | Concept                          | Where in this file                                          |\n * | ---- | -------------------------------- | ----------------------------------------------------------- |\n * | R4   | Extends native el + VariantProps | `React.ComponentProps<'span'> & VariantProps<...>`          |\n * | R6   | data-slot + data-grade           | `data-slot=\"grade-badge\"` + `data-grade` + `data-tone`      |\n * | R7   | className merged + ...rest       | `cn(gradeBadgeVariants(...), className)` + `{...props}`     |\n * | R8   | No isXxx; closed grade enum      | `grade` is the 13-tier `'A+' | 'A' | 'A-' | ... | 'F'`        |\n * | R14  | Declares min viewport            | `data-min-viewport={String(MIN_VIEWPORT)}` + exported const |\n * | R19  | Tokens only                      | bg-success/warning/caution/destructive + foreground tokens  |\n * | R20  | AA contrast                      | tone-foreground on tone-background clears AA in both modes  |\n * | R25  | Server component                 | No hooks                                                    |\n * | R26  | A11y                             | aria-label=\"Grade: A+\" so SRs announce the score in words   |\n */\n\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@/lib/utils';\n\nexport const MIN_VIEWPORT = 320 as const;\n\nconst GRADE_VALUES = [\n  'A+', 'A', 'A-',\n  'B+', 'B', 'B-',\n  'C+', 'C', 'C-',\n  'D+', 'D', 'D-',\n  'F',\n] as const;\n\nexport type GradeValue = (typeof GRADE_VALUES)[number];\n\ntype GradeTone = 'excellent' | 'good' | 'fair' | 'poor' | 'fail';\n\nconst GRADE_TONE_MAP: Record<GradeValue, GradeTone> = {\n  'A+': 'excellent', 'A': 'excellent', 'A-': 'excellent',\n  'B+': 'good',       'B': 'good',       'B-': 'good',\n  'C+': 'fair',       'C': 'fair',       'C-': 'fair',\n  'D+': 'poor',       'D': 'poor',       'D-': 'poor',\n  'F':  'fail',\n};\n\nconst gradeBadgeVariants = cva(\n  // Base — sits inline, tabular numerals so digits don't jiggle.\n  'inline-flex items-center justify-center rounded-full font-mono font-bold tabular-nums whitespace-nowrap',\n  {\n    variants: {\n      tone: {\n        excellent: 'bg-success text-success-foreground',\n        good:      'bg-primary text-primary-foreground',\n        fair:      'bg-warning text-warning-foreground',\n        // `poor` is the rung between warning and destructive. It rides the\n        // `caution` semantic token (interlace-theme.css) rather than a raw\n        // palette class — the DS now ships the intermediate orange the\n        // five-tone ladder needs. 9.0:1 light / 10.9:1 dark.\n        poor:      'bg-caution text-caution-foreground',\n        fail:      'bg-destructive text-destructive-foreground',\n      },\n      size: {\n        sm: 'h-5 min-w-8 px-xs text-xs',\n        md: 'h-7 min-w-10 px-sm text-sm',\n        lg: 'h-12 min-w-16 px-md text-xl',\n      },\n    },\n    defaultVariants: {\n      tone: 'good',\n      size: 'md',\n    },\n  },\n);\n\ninterface GradeBadgeProps\n  extends Omit<React.ComponentProps<'span'>, 'children'>,\n    Pick<VariantProps<typeof gradeBadgeVariants>, 'size'> {\n  /** The grade itself. Drives the tone automatically (A* = excellent, etc.). */\n  grade: GradeValue;\n}\n\nfunction GradeBadge({\n  grade,\n  size = 'md',\n  className,\n  ...props\n}: GradeBadgeProps) {\n  const tone = GRADE_TONE_MAP[grade];\n  return (\n    <span\n      data-slot=\"grade-badge\"\n      data-min-viewport={String(MIN_VIEWPORT)}\n      data-grade={grade}\n      data-tone={tone}\n      aria-label={`Grade: ${grade}`}\n      className={cn(gradeBadgeVariants({ tone, size }), className)}\n      {...props}\n    >\n      {grade}\n    </span>\n  );\n}\nGradeBadge.displayName = 'GradeBadge';\n\nexport { GradeBadge, gradeBadgeVariants, GRADE_VALUES };\nexport type { GradeBadgeProps };\n"
    }
  ],
  "meta": {
    "tier": "primitive",
    "client": false,
    "minViewport": 320,
    "loading": false,
    "version": "1.1.0",
    "since": "1.0.0"
  },
  "docs": "## @interlace/grade-badge\n\nInstalled to `components/ui/grade-badge.tsx`.\n\n```tsx\nimport { /* … */ } from '@/components/ui/grade-badge';\n```\n\nProps, a11y contract, live preview and source: https://ds.interlace.tools/c/grade-badge\n\nRequires the `@interlace/theme` CSS baseline (installed automatically as a registry dependency)."
}
