{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "fraction",
  "title": "Fraction",
  "description": "A count set as a display fraction: yours large in italic, the total small beside it, a pen stroke between. A changed number turns over.",
  "registryDependencies": [
    "https://thedirectors.agency/ui/r/0nlytype.json"
  ],
  "files": [
    {
      "path": "registry/0nlytype/ui/fraction.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { useDigitRoll } from \"@/lib/0nlytype/use-digit-roll\"\nimport { cn } from \"@/lib/utils\"\n\ntype FractionProps = Omit<React.ComponentProps<\"span\">, \"children\"> & {\n  /** The part that's yours, in italic. */\n  count: React.ReactNode\n  /** What it counts toward. Leave it out and the count stands alone: a figure of yours that turns over as it changes. */\n  total?: React.ReactNode\n  /** solidus: on a leaning pen stroke. vinculum: stacked over a bar that inks the share. readout: the share hung beside it as a decimal. */\n  variant?: \"solidus\" | \"vinculum\" | \"readout\"\n}\n\nconst plain = (v: React.ReactNode): v is string | number => typeof v === \"string\" || typeof v === \"number\"\n// A number, however it's grouped or pointed (12, 1 284, 0.75): figures, and no letters.\nconst figures = /^[^\\p{L}]*\\d[^\\p{L}]*$/u\nconst digit = /\\d/\n// ponytail: reads the figures and the point only, which is enough to pick the way the wheels turn.\nconst amount = (v: string | number) => (typeof v === \"number\" ? v : Number(v.replace(/[^\\d.]/g, \"\")))\nconst still = () => matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n\n/**\n * One side of the fraction. A number turns over figure by figure, and only the figures that changed:\n * the units first, the carry running leftward one arpeggio step behind, as a counter's wheels do.\n * A number that gains or loses a figure, or a word, turns over whole; anything else just changes.\n */\nfunction Side({ value, className, hidden }: { value: React.ReactNode; className?: string; hidden?: boolean }) {\n  const { shown, whole, figs } = useDigitRoll(value, { order: plain(value) ? amount(value) : 0, numeric: true })\n\n  const text = plain(value) && plain(shown) ? String(shown) : null\n  return (\n    <span ref={whole} className={className} aria-hidden={hidden || undefined}>\n      {text !== null && figures.test(text) ? (\n        <>\n          <span aria-hidden=\"true\">\n            {[...text].map((f, k) => (\n              <span key={text.length - k} ref={(el) => void (figs.current[k] = el)} className={digit.test(f) ? \"ot-fraction-figure\" : undefined}>\n                {f}\n              </span>\n            ))}\n          </span>\n          <span className=\"ot-sr\">{text}</span>\n        </>\n      ) : (\n        (text ?? value)\n      )}\n    </span>\n  )\n}\n\n/**\n * A count set as a display fraction with a leaning hairline. Reads aloud as \"2 of 5\". When a number\n * changes it turns over the way the count moved, and the solidus ticks like a pen. The figures always\n * read left to right, in a right-to-left page too. vinculum stacks the count over a bar that inks its\n * share; readout hangs the share beside it as a decimal, the way Ikeda's posters print 1/3 by 0.13.\n */\nfunction Fraction({ count, total, variant = \"solidus\", className, style, ...props }: FractionProps) {\n  const was = React.useRef([count, total])\n  const slash = React.useRef<HTMLElement>(null)\n\n  React.useEffect(() => {\n    const moved = [count, total].map((v, i) => [was.current[i], v]).find(([a, b]) => !Object.is(a, b) && plain(a) && plain(b))\n    was.current = [count, total]\n    if (!moved || variant !== \"solidus\" || !slash.current?.animate || still()) return\n    const lean = amount(moved[1] as string | number) < amount(moved[0] as string | number) ? -1 : 1\n    slash.current.animate([{ rotate: \"24deg\" }, { rotate: `${24 + lean * 9}deg`, offset: 0.3 }, { rotate: \"24deg\" }], { duration: 560, easing: \"cubic-bezier(0.34, 1.5, 0.5, 1)\" }) // --ot-spiccato\n  }, [count, total, variant])\n\n  const share = plain(count) && plain(total) && Number(total) > 0 ? Math.min(1, Math.max(0, Number(count) / Number(total))) : null\n  return (\n    <span\n      data-slot=\"fraction\"\n      data-variant={variant === \"solidus\" ? undefined : variant}\n      className={cn(\"ot-fraction\", className)}\n      style={variant === \"vinculum\" && share !== null ? ({ \"--share\": share, ...style } as React.CSSProperties) : style}\n      {...props}\n    >\n      {variant === \"readout\" && share !== null ? <Side value={share.toFixed(2)} className=\"ot-fraction-readout\" hidden /> : null}\n      <Side value={count} className=\"ot-yours\" />\n      {total === undefined ? null : (\n        <>\n          <i ref={slash}>\n            <span className=\"ot-sr\"> of </span>\n          </i>\n          <Side value={total} />\n        </>\n      )}\n    </span>\n  )\n}\n\nexport { Fraction, type FractionProps }\n",
      "type": "registry:ui"
    }
  ],
  "meta": {
    "movement": "II",
    "underneath": "native",
    "contract": "ot-fraction"
  },
  "type": "registry:ui"
}
