{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "usage",
  "type": "registry:component",
  "title": "Usage",
  "description": "How full a model's context or a plan is, as a small indicator that opens into the figures.",
  "dependencies": [
    "@base-ui/react",
    "tailwind-variants"
  ],
  "registryDependencies": [
    "https://panelui.dev/r/web/rolling-number.json"
  ],
  "cssVars": {
    "light": {
      "success": "oklch(0.696 0.17 162.48)",
      "warning": "oklch(0.769 0.188 70.08)",
      "info": "oklch(0.623 0.214 259.815)"
    },
    "dark": {
      "success": "oklch(0.696 0.17 162.48)",
      "warning": "oklch(0.769 0.188 70.08)",
      "info": "oklch(0.623 0.214 259.815)"
    }
  },
  "files": [
    {
      "path": "components/panelui/usage.tsx",
      "type": "registry:component",
      "target": "@components/panelui/usage.tsx",
      "content": "'use client';\n\n/**\n * Usage — how much of a model's context, or of a plan, has been used.\n *\n * ```tsx\n * <Usage limit={200_000} segments={segments} state=\"active\">\n *   <Usage.Trigger />\n *   <Usage.Content>\n *     <Usage.Summary label=\"Context window\" unit=\"tokens\" />\n *     <Usage.Breakdown />\n *     <Usage.Section title=\"Plan\">\n *       <Usage.Meter label=\"Session\" value={62} limit={100} note=\"Resets in 2h 14m\" />\n *     </Usage.Section>\n *   </Usage.Content>\n * </Usage>\n * ```\n *\n * ## An indicator that opens into the figures\n *\n * The trigger is small enough for a composer's footer or a chat header, and\n * says one thing at a glance: how full, and whether that is a problem yet.\n * Pressing it opens the figures behind it: what the space is taken up by,\n * and how much of the plan is left.\n *\n * ## Colour is the state\n *\n * The colour comes from the share used, not from the caller. Below\n * `warning` (70%) it is the info colour, from there to `critical` (90%) the\n * warning colour, and past that the destructive one. `state=\"idle\"` greys it\n * out, for a conversation not yet started, and `state=\"done\"` turns it to\n * the success colour, for a run that finished inside its budget.\n *\n * ## Segments\n *\n * Pass `segments` to say what the space is taken up by: the system prompt,\n * the tools, the files, the messages. The ring and the bar draw them in\n * order with a gap between each, the older ones in neutral steps and the\n * last, the one still growing, in the state's colour, so the eye goes to the\n * part that is changing. A segment's `color` overrides that.\n *\n * ## Six indicators\n *\n * `ring` is a dot inside a ring of arcs. `dots` is the same reading as\n * twelve dots round the dot. `gauge` is an open arc with the percentage in\n * it. `bar` is a short segmented bar with the percentage beside it, for a\n * footer. `cells` fills a small grid a twentieth at a time. `pill` is a\n * chip with a small ring and the count written out.\n */\nimport { Popover } from '@base-ui/react/popover';\nimport {\n  createContext,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n  type CSSProperties,\n  type ComponentProps,\n  type ReactNode,\n} from 'react';\nimport { tv } from 'tailwind-variants';\nimport { RollingNumber } from '@/components/panelui/rolling-number';\n\nexport type UsageState = 'idle' | 'active' | 'done';\nexport type UsageLevel = 'idle' | 'normal' | 'warning' | 'critical' | 'done';\nexport type UsageVariant = 'ring' | 'dots' | 'gauge' | 'bar' | 'cells' | 'pill';\n\nexport interface UsageSegment {\n  /** What takes up this share, as the breakdown names it: `System prompt`, `Messages`. */\n  label: string;\n  /** How much it takes up, in the same unit as `limit`. */\n  value: number;\n  /** A CSS colour for it, in place of the one picked from its place in the order. */\n  color?: string;\n}\n\n/** Each level's colour, as the `--usage-tone` every part draws its state in. */\nconst TONES: Record<UsageLevel, string> = {\n  idle: 'var(--muted-foreground)',\n  normal: 'var(--info)',\n  warning: 'var(--warning)',\n  critical: 'var(--destructive)',\n  done: 'var(--success)',\n};\n\nconst KEYFRAMES =\n  '@keyframes panelui-usage-breathe{0%,100%{transform:scale(1)}50%{transform:scale(.78)}}' +\n  '@keyframes panelui-usage-glow{0%,100%{opacity:1}50%{opacity:.45}}' +\n  // Reduced motion: the indicator jumps to each new reading and holds still.\n  '@media (prefers-reduced-motion:reduce){[data-slot=usage-trigger] *{animation:none!important;transition:none!important}}';\n\nconst EASE = 'cubic-bezier(0.23, 1, 0.32, 1)';\n\nconst usageVariants = tv({\n  slots: {\n    trigger: [\n      'group/usage relative inline-flex shrink-0 cursor-pointer select-none items-center justify-center gap-2 text-foreground outline-none',\n      'transition-[background-color,scale,color] duration-150 ease-out active:scale-[0.97] motion-reduce:active:scale-100',\n      'focus-visible:ring-[3px] focus-visible:ring-ring/30 data-popup-open:bg-accent',\n      'tabular-nums',\n    ],\n    positioner: 'z-50 outline-none',\n    popup: [\n      'w-80 max-w-[calc(100vw-1rem)] origin-(--transform-origin) overflow-hidden rounded-xl border bg-popover text-popover-foreground shadow-lg shadow-black/10 outline-none',\n      'transition-[opacity,scale] duration-[180ms] ease-[cubic-bezier(0.23,1,0.32,1)]',\n      'data-starting-style:scale-[0.96] data-starting-style:opacity-0 data-ending-style:scale-[0.96] data-ending-style:opacity-0',\n      'motion-reduce:transition-none',\n    ],\n    summary: 'flex flex-col gap-3 p-4',\n    summaryHead: 'flex items-end justify-between gap-3',\n    summaryLabel: 'text-xs text-muted-foreground',\n    summaryFigure: 'flex items-baseline gap-1.5 text-foreground',\n    summaryValue: 'text-2xl leading-8 font-semibold tracking-tight tabular-nums',\n    summaryLimit: 'text-sm text-muted-foreground tabular-nums',\n    percent: [\n      'inline-flex h-6 items-center rounded-md px-1.5 text-xs font-medium tabular-nums',\n      'bg-[color-mix(in_oklab,var(--usage-tone)_12%,transparent)] text-[color-mix(in_oklab,var(--usage-tone),var(--foreground)_30%)]',\n    ],\n    stack: 'flex h-2 w-full gap-[3px] overflow-hidden',\n    stackPiece: 'h-full min-w-0 rounded-[3px] transition-[flex-grow] duration-500 ease-[cubic-bezier(0.23,1,0.32,1)] motion-reduce:transition-none',\n    breakdown: 'flex flex-col gap-1.5 px-4 pb-4 text-[0.8125rem]',\n    breakdownRow: 'flex items-center gap-2.5',\n    swatch: 'size-2 shrink-0 rounded-[3px]',\n    breakdownLabel: 'min-w-0 flex-1 truncate text-muted-foreground',\n    breakdownValue: 'tabular-nums text-foreground',\n    breakdownShare: 'w-10 text-end tabular-nums text-muted-foreground',\n    section: 'flex flex-col gap-3 border-t p-4',\n    sectionHead: 'flex items-center justify-between gap-3',\n    sectionTitle: 'text-xs font-medium text-muted-foreground',\n    sectionAside: 'text-xs text-muted-foreground',\n    meter: 'flex flex-col gap-1.5',\n    meterHead: 'flex items-baseline justify-between gap-3 text-[0.8125rem]',\n    meterLabel: 'text-foreground',\n    meterValue: 'tabular-nums text-muted-foreground',\n    ticks: 'flex h-3.5 items-stretch gap-[2px]',\n    tick: 'flex-1 rounded-[1px] transition-colors duration-300',\n    meterNote: 'text-xs text-muted-foreground',\n    footer: 'flex items-center justify-between gap-3 border-t bg-muted/40 px-4 py-2.5 text-xs text-muted-foreground',\n  },\n  variants: {\n    variant: {\n      ring: { trigger: 'size-8 rounded-full hover:bg-accent [&>svg]:size-[22px]' },\n      dots: { trigger: 'size-8 rounded-full hover:bg-accent [&>svg]:size-[22px]' },\n      gauge: { trigger: 'size-11 rounded-full hover:bg-accent [&>svg]:size-10' },\n      bar: { trigger: 'h-8 rounded-full px-2.5 text-xs font-medium hover:bg-accent' },\n      cells: { trigger: 'h-8 rounded-lg px-2 text-xs font-medium hover:bg-accent' },\n      pill: {\n        trigger:\n          'h-8 rounded-full border bg-background px-2.5 text-xs font-medium shadow-xs hover:bg-accent dark:bg-input/30 [&>svg]:size-3.5',\n      },\n    },\n  },\n  defaultVariants: { variant: 'ring' },\n});\n\n/* -------------------------------------------------------------------------- */\n/* Reading the figures                                                        */\n/* -------------------------------------------------------------------------- */\n\nfunction levelOf(ratio: number, state: UsageState, warning: number, critical: number): UsageLevel {\n  if (state === 'idle') return 'idle';\n  if (state === 'done') return 'done';\n  if (ratio >= critical) return 'critical';\n  if (ratio >= warning) return 'warning';\n  return 'normal';\n}\n\n/** The older segments in neutral steps, the last in the state's colour. */\nfunction colourOf(index: number, count: number, own?: string) {\n  if (own) return own;\n  if (index === count - 1) return 'var(--usage-tone)';\n  const strength = Math.max(28, 78 - (count - 2 - index) * 18);\n  return `color-mix(in oklab, var(--muted-foreground) ${strength}%, transparent)`;\n}\n\ninterface Piece {\n  label: string;\n  value: number;\n  share: number;\n  color: string;\n}\n\ninterface UsageContextValue {\n  used: number;\n  limit: number;\n  ratio: number;\n  level: UsageLevel;\n  state: UsageState;\n  label: string;\n  pieces: Piece[];\n  warning: number;\n  critical: number;\n  format: Intl.NumberFormatOptions;\n  locales?: Intl.LocalesArgument;\n}\n\nconst UsageContext = createContext<UsageContextValue | null>(null);\n\nfunction useUsage(part: string) {\n  const usage = useContext(UsageContext);\n  if (!usage) throw new Error(`${part} must be used inside <Usage>.`);\n  return usage;\n}\n\nconst toneStyle = (level: UsageLevel) => ({ '--usage-tone': TONES[level] }) as CSSProperties;\n\n/** The figure written compactly, as the trigger and the breakdown show it. */\nfunction useFormat() {\n  const { format, locales } = useUsage('Usage');\n  return useMemo(() => new Intl.NumberFormat(locales, format), [format, locales]);\n}\n\n/** False for the first frame, so the arcs and bars grow in from nothing once. */\nfunction useDrawn() {\n  const [drawn, setDrawn] = useState(false);\n  useEffect(() => {\n    const frame = requestAnimationFrame(() => setDrawn(true));\n    return () => cancelAnimationFrame(frame);\n  }, []);\n  return drawn;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Root                                                                       */\n/* -------------------------------------------------------------------------- */\n\nexport interface UsageProps extends Omit<Popover.Root.Props, 'children'> {\n  /** How much there is in all, in the unit of the values: tokens, requests, dollars. */\n  limit: number;\n  /** What the used space is taken up by, oldest first. Their sum is the amount used. */\n  segments?: readonly UsageSegment[];\n  /** The amount used, when there are no `segments` to add up. */\n  value?: number;\n  /** `idle` greys it out, `active` breathes, `done` turns it to the success colour. Default `active`. */\n  state?: UsageState;\n  /** The share, from 0 to 1, at which it turns to the warning colour. Default `0.7`. */\n  warning?: number;\n  /** The share, from 0 to 1, at which it turns to the destructive colour. Default `0.9`. */\n  critical?: number;\n  /** What is being measured, as the trigger's accessible name begins. Default `Context`. */\n  label?: string;\n  /** How amounts are written. Default compact, so 124000 is `124K`. */\n  format?: Intl.NumberFormatOptions;\n  /**\n   * The locale the amounts are written in. Default `en-US`, so `124K` reads the same on every device;\n   * pass the reader's locale to write them in their language.\n   */\n  locales?: Intl.LocalesArgument;\n  children?: ReactNode;\n}\n\nconst COMPACT: Intl.NumberFormatOptions = { notation: 'compact', maximumFractionDigits: 1 };\n\n/** Holds the figures, and opens the popover from its trigger. */\nfunction UsageRoot({\n  limit,\n  segments,\n  value,\n  state = 'active',\n  warning = 0.7,\n  critical = 0.9,\n  label = 'Context',\n  format = COMPACT,\n  locales = 'en-US',\n  children,\n  ...props\n}: UsageProps) {\n  const context = useMemo<UsageContextValue>(() => {\n    const parts = segments ?? [];\n    const used = parts.length > 0 ? parts.reduce((sum, part) => sum + Math.max(0, part.value), 0) : Math.max(0, value ?? 0);\n    const safeLimit = Math.max(1, limit);\n    const ratio = Math.min(1, used / safeLimit);\n    const pieces = parts.map((part, index) => ({\n      label: part.label,\n      value: Math.max(0, part.value),\n      share: Math.max(0, part.value) / safeLimit,\n      color: colourOf(index, parts.length, part.color),\n    }));\n    if (pieces.length === 0 && used > 0) {\n      pieces.push({ label, value: used, share: ratio, color: 'var(--usage-tone)' });\n    }\n    return {\n      used,\n      limit: safeLimit,\n      ratio,\n      level: levelOf(ratio, state, warning, critical),\n      state,\n      label,\n      pieces,\n      warning,\n      critical,\n      format,\n      locales,\n    };\n  }, [segments, value, limit, state, warning, critical, label, format, locales]);\n\n  return (\n    <UsageContext.Provider value={context}>\n      <Popover.Root {...props}>{children}</Popover.Root>\n    </UsageContext.Provider>\n  );\n}\nUsageRoot.displayName = 'Usage';\n\n/* -------------------------------------------------------------------------- */\n/* Indicators                                                                 */\n/* -------------------------------------------------------------------------- */\n\n/** A dot that shrinks and grows while the amount is still changing. */\nconst breathe = (on: boolean): CSSProperties | undefined =>\n  on ? { animation: 'panelui-usage-breathe 1.8s ease-in-out infinite', transformBox: 'fill-box', transformOrigin: 'center' } : undefined;\n\n/** The newest bar or cell, fading in and out while the amount is still changing. */\nconst glow = (on: boolean): CSSProperties | undefined =>\n  on ? { animation: 'panelui-usage-glow 1.8s ease-in-out infinite' } : undefined;\n\n/** Arcs round a circle: each piece in turn, then the free track, with a gap between each. */\nfunction arcsOf(pieces: Piece[], ratio: number, circumference: number, gap: number) {\n  const shown = pieces.filter((piece) => piece.share > 0);\n  const free = Math.max(0, 1 - ratio);\n  const all = [...shown.map((piece) => ({ ...piece, track: false })), ...(free > 0.001 ? [{ label: 'Free', value: 0, share: free, color: '', track: true }] : [])];\n  const spacing = all.length > 1 ? gap : 0;\n  let start = 0;\n  return all.map((piece) => {\n    const length = Math.max(0, Math.min(piece.share, 1) * circumference - spacing);\n    const arc = { ...piece, start, length };\n    start += Math.min(piece.share, 1) * circumference;\n    return arc;\n  });\n}\n\nfunction RingIndicator({ compact = false }: { compact?: boolean }) {\n  const { pieces, ratio, level, state } = useUsage('Usage.Trigger');\n  const drawn = useDrawn();\n  const radius = compact ? 9.5 : 9.25;\n  const circumference = 2 * Math.PI * radius;\n  const arcs = arcsOf(pieces, ratio, circumference, compact ? 2.6 : 2.4);\n  const done = level === 'done';\n  return (\n    <svg aria-hidden=\"true\" viewBox=\"0 0 24 24\" fill=\"none\" overflow=\"visible\">\n      <g\n        style={{\n          transformOrigin: '12px 12px',\n          transform: done ? 'scale(0.55)' : 'scale(1)',\n          opacity: done ? 0 : 1,\n          transition: `transform 320ms ${EASE}, opacity 220ms ease-out`,\n        }}\n      >\n        {arcs.map((arc, index) => (\n          <circle\n            key={index}\n            cx=\"12\"\n            cy=\"12\"\n            r={radius}\n            stroke={arc.track ? 'color-mix(in oklab, var(--muted-foreground) 20%, transparent)' : arc.color}\n            strokeWidth={compact ? 3.5 : 3}\n            strokeDasharray={`${drawn ? arc.length : 0} ${circumference}`}\n            strokeDashoffset={-arc.start}\n            transform=\"rotate(-90 12 12)\"\n            style={{ transition: `stroke-dasharray 600ms ${EASE}, stroke-dashoffset 600ms ${EASE}, stroke 300ms ease` }}\n          />\n        ))}\n      </g>\n      {compact ? null : (\n        <circle\n          cx=\"12\"\n          cy=\"12\"\n          r=\"5\"\n          fill=\"var(--usage-tone)\"\n          style={{\n            transformOrigin: '12px 12px',\n            transform: done ? 'scale(1.6)' : 'scale(1)',\n            transition: `transform 320ms ${EASE}, fill 300ms ease`,\n            ...breathe(state === 'active' && !done),\n          }}\n        />\n      )}\n    </svg>\n  );\n}\n\nfunction DotsIndicator() {\n  const { ratio, level, state } = useUsage('Usage.Trigger');\n  const lit = level === 'done' ? 12 : Math.round(ratio * 12);\n  return (\n    <svg aria-hidden=\"true\" viewBox=\"0 0 24 24\" fill=\"none\">\n      {Array.from({ length: 12 }, (_, index) => {\n        const angle = (index / 12) * Math.PI * 2 - Math.PI / 2;\n        const on = index < lit;\n        return (\n          <circle\n            key={index}\n            cx={12 + Math.cos(angle) * 9.5}\n            cy={12 + Math.sin(angle) * 9.5}\n            r={on ? 1.6 : 1.2}\n            fill={on ? 'var(--usage-tone)' : 'color-mix(in oklab, var(--muted-foreground) 30%, transparent)'}\n            style={{ transition: `r 300ms ${EASE}, fill 300ms ease`, transitionDelay: `${index * 25}ms` }}\n          />\n        );\n      })}\n      <circle cx=\"12\" cy=\"12\" r=\"4\" fill=\"var(--usage-tone)\" style={breathe(state === 'active' && level !== 'done')} />\n    </svg>\n  );\n}\n\nfunction GaugeIndicator() {\n  const { ratio, state, level } = useUsage('Usage.Trigger');\n  const drawn = useDrawn();\n  const radius = 15;\n  const sweep = 0.75;\n  const circumference = 2 * Math.PI * radius;\n  const full = circumference * sweep;\n  const shown = drawn ? full * ratio : 0;\n  const endAngle = ((135 + 270 * ratio) * Math.PI) / 180;\n  return (\n    <svg aria-hidden=\"true\" viewBox=\"0 0 40 40\" fill=\"none\">\n      <circle\n        cx=\"20\"\n        cy=\"20\"\n        r={radius}\n        stroke=\"color-mix(in oklab, var(--muted-foreground) 20%, transparent)\"\n        strokeWidth=\"3\"\n        strokeLinecap=\"round\"\n        strokeDasharray={`${full} ${circumference}`}\n        transform=\"rotate(135 20 20)\"\n      />\n      <circle\n        cx=\"20\"\n        cy=\"20\"\n        r={radius}\n        stroke=\"var(--usage-tone)\"\n        strokeWidth=\"3\"\n        strokeLinecap=\"round\"\n        strokeDasharray={`${shown} ${circumference}`}\n        transform=\"rotate(135 20 20)\"\n        style={{ transition: `stroke-dasharray 600ms ${EASE}, stroke 300ms ease` }}\n      />\n      {state === 'active' && level !== 'done' && ratio > 0 ? (\n        <circle\n          cx={20 + Math.cos(endAngle) * radius}\n          cy={20 + Math.sin(endAngle) * radius}\n          r=\"1.5\"\n          fill=\"var(--background)\"\n          style={breathe(true)}\n        />\n      ) : null}\n      <text\n        x=\"20\"\n        y=\"20\"\n        dy=\"0.35em\"\n        textAnchor=\"middle\"\n        fill=\"currentColor\"\n        style={{ fontSize: 10, fontWeight: 600, fontVariantNumeric: 'tabular-nums' }}\n      >\n        {Math.round(ratio * 100)}\n      </text>\n    </svg>\n  );\n}\n\nfunction BarIndicator() {\n  const { pieces, ratio, state, level } = useUsage('Usage.Trigger');\n  const drawn = useDrawn();\n  const shown = pieces.filter((piece) => piece.share > 0);\n  return (\n    <>\n      <span aria-hidden=\"true\" className=\"flex h-1.5 w-12 gap-[2px]\">\n        {shown.map((piece, index) => (\n          <span\n            key={piece.label}\n            className=\"rounded-full\"\n            style={{\n              flexGrow: drawn ? piece.share : 0,\n              flexBasis: 0,\n              background: piece.color,\n              transition: `flex-grow 600ms ${EASE}, background-color 300ms ease`,\n              ...glow(state === 'active' && level !== 'done' && index === shown.length - 1),\n            }}\n          />\n        ))}\n        <span\n          className=\"rounded-full\"\n          style={{\n            flexGrow: drawn ? Math.max(0, 1 - ratio) : 1,\n            flexBasis: 0,\n            background: 'color-mix(in oklab, var(--muted-foreground) 20%, transparent)',\n            transition: `flex-grow 600ms ${EASE}`,\n          }}\n        />\n      </span>\n      <span>{Math.round(ratio * 100)}%</span>\n    </>\n  );\n}\n\nfunction CellsIndicator() {\n  const { used, ratio, state, level } = useUsage('Usage.Trigger');\n  const formatter = useFormat();\n  const count = 20;\n  const lit = ratio * count;\n  return (\n    <>\n      <span aria-hidden=\"true\" className=\"grid grid-flow-col grid-rows-2 gap-[2px]\">\n        {Array.from({ length: count }, (_, index) => {\n          // Filled column by column, top then bottom, like reading a meter.\n          const column = Math.floor(index / 2);\n          const order = column * 2 + (index % 2);\n          const fill = Math.max(0, Math.min(1, lit - order));\n          const last = order === Math.ceil(lit) - 1;\n          return (\n            <span\n              key={index}\n              className=\"size-[5px] rounded-[1.5px]\"\n              style={{\n                // A cell part-way full is the tone at part strength, by opacity, so\n                // the colour itself never has to be mixed.\n                background: fill > 0 ? 'var(--usage-tone)' : 'color-mix(in oklab, var(--muted-foreground) 22%, transparent)',\n                opacity: fill > 0 ? 0.35 + fill * 0.65 : 1,\n                transition: 'background-color 300ms ease, opacity 300ms ease',\n                ...glow(state === 'active' && level !== 'done' && last),\n              }}\n            />\n          );\n        })}\n      </span>\n      <span>{formatter.format(used)}</span>\n    </>\n  );\n}\n\nfunction PillIndicator() {\n  const { used, limit } = useUsage('Usage.Trigger');\n  const formatter = useFormat();\n  return (\n    <>\n      <RingIndicator compact />\n      <span>\n        {formatter.format(used)}\n        <span className=\"text-muted-foreground\"> / {formatter.format(limit)}</span>\n      </span>\n    </>\n  );\n}\n\nconst INDICATORS: Record<UsageVariant, () => ReactNode> = {\n  ring: () => <RingIndicator />,\n  dots: () => <DotsIndicator />,\n  gauge: () => <GaugeIndicator />,\n  bar: () => <BarIndicator />,\n  cells: () => <CellsIndicator />,\n  pill: () => <PillIndicator />,\n};\n\nexport interface UsageTriggerProps extends Omit<Popover.Trigger.Props, 'children'> {\n  /** The indicator: `ring`, `dots`, `gauge`, `bar`, `cells` or `pill`. Default `ring`. */\n  variant?: UsageVariant;\n  className?: string;\n  children?: ReactNode;\n}\n\n/** The indicator, and the button that opens the figures. Its accessible name says the share used. */\nfunction UsageTrigger({ variant = 'ring', className, style, children, ...props }: UsageTriggerProps) {\n  const usage = useUsage('Usage.Trigger');\n  const percent = Math.round(usage.ratio * 100);\n  const name =\n    usage.level === 'idle'\n      ? `${usage.label}: not started`\n      : `${usage.label}: ${percent}% used${usage.level === 'done' ? ', finished' : ''}`;\n  return (\n    <Popover.Trigger\n      data-slot=\"usage-trigger\"\n      data-variant={variant}\n      data-level={usage.level}\n      aria-label={name}\n      className={usageVariants({ variant }).trigger({ className })}\n      style={{ ...toneStyle(usage.level), ...(style as CSSProperties) }}\n      {...props}\n    >\n      <style>{KEYFRAMES}</style>\n      {INDICATORS[variant]()}\n      {children}\n    </Popover.Trigger>\n  );\n}\nUsageTrigger.displayName = 'Usage.Trigger';\n\n/* -------------------------------------------------------------------------- */\n/* The popover                                                                */\n/* -------------------------------------------------------------------------- */\n\nexport interface UsageContentProps extends Popover.Popup.Props {\n  /** Which side of the trigger it opens on. Default `top`, since the trigger usually sits low in a composer. */\n  side?: Popover.Positioner.Props['side'];\n  /** How it lines up with the trigger along that side. Default `start`. */\n  align?: Popover.Positioner.Props['align'];\n  /** The gap between the trigger and the popover, in pixels. Default `8`. */\n  sideOffset?: number;\n  /** Where in the document it is put. Default `document.body`. */\n  container?: Popover.Portal.Props['container'];\n  className?: string;\n}\n\n/** The figures, in a layer that grows out of the trigger. */\nfunction UsageContent({ side = 'top', align = 'start', sideOffset = 8, container, className, style, ...props }: UsageContentProps) {\n  const usage = useUsage('Usage.Content');\n  const slots = usageVariants();\n  return (\n    <Popover.Portal container={container}>\n      <Popover.Positioner\n        side={side}\n        align={align}\n        sideOffset={sideOffset}\n        collisionPadding={8}\n        className={slots.positioner()}\n      >\n        <Popover.Popup\n          data-slot=\"usage-content\"\n          className={slots.popup({ className })}\n          style={{ ...toneStyle(usage.level), ...(style as CSSProperties) }}\n          {...props}\n        />\n      </Popover.Positioner>\n    </Popover.Portal>\n  );\n}\nUsageContent.displayName = 'Usage.Content';\n\nexport interface UsageSummaryProps extends ComponentProps<'div'> {\n  /** What is being measured, over the figure. Default the root's `label`. */\n  label?: ReactNode;\n  /** The unit after the limit: `tokens`. */\n  unit?: ReactNode;\n}\n\n/** The amount used written large, with the limit, the share, and the segments as one bar. */\nfunction UsageSummary({ label, unit, className, ...props }: UsageSummaryProps) {\n  const usage = useUsage('Usage.Summary');\n  const formatter = useFormat();\n  const drawn = useDrawn();\n  const slots = usageVariants();\n  const shown = usage.pieces.filter((piece) => piece.share > 0);\n  return (\n    <div data-slot=\"usage-summary\" className={slots.summary({ className })} {...props}>\n      <div className={slots.summaryHead()}>\n        <div className=\"flex flex-col gap-0.5\">\n          <span className={slots.summaryLabel()}>{label ?? usage.label}</span>\n          <span className={slots.summaryFigure()}>\n            <RollingNumber value={usage.used} format={usage.format} locales={usage.locales} className={slots.summaryValue()} />\n            <span className={slots.summaryLimit()}>\n              / {formatter.format(usage.limit)}\n              {unit ? <> {unit}</> : null}\n            </span>\n          </span>\n        </div>\n        {usage.level === 'idle' ? null : <span className={slots.percent()}>{Math.round(usage.ratio * 100)}%</span>}\n      </div>\n      <div aria-hidden=\"true\" className={slots.stack()}>\n        {shown.map((piece) => (\n          <span\n            key={piece.label}\n            className={slots.stackPiece()}\n            style={{ flexGrow: drawn ? piece.share : 0, flexBasis: 0, background: piece.color }}\n          />\n        ))}\n        <span\n          className={slots.stackPiece()}\n          style={{\n            flexGrow: drawn ? Math.max(0, 1 - usage.ratio) : 1,\n            flexBasis: 0,\n            background: 'color-mix(in oklab, var(--muted-foreground) 16%, transparent)',\n          }}\n        />\n      </div>\n    </div>\n  );\n}\nUsageSummary.displayName = 'Usage.Summary';\n\nexport interface UsageBreakdownProps extends ComponentProps<'ul'> {\n  /** What the unused space is called in the last row. Pass `null` for no such row. Default `Free`. */\n  freeLabel?: ReactNode;\n}\n\n/** One row per segment, in the colour the ring and the bar draw it in, with what is left last. */\nfunction UsageBreakdown({ freeLabel = 'Free', className, ...props }: UsageBreakdownProps) {\n  const usage = useUsage('Usage.Breakdown');\n  const formatter = useFormat();\n  const slots = usageVariants();\n  const free = Math.max(0, usage.limit - usage.used);\n  const share = (value: number) => `${Math.round((value / usage.limit) * 100)}%`;\n  return (\n    <ul data-slot=\"usage-breakdown\" className={slots.breakdown({ className })} {...props}>\n      {usage.pieces.map((piece) => (\n        <li key={piece.label} className={slots.breakdownRow()}>\n          <span aria-hidden=\"true\" className={slots.swatch()} style={{ background: piece.color }} />\n          <span className={slots.breakdownLabel()}>{piece.label}</span>\n          <span className={slots.breakdownValue()}>{formatter.format(piece.value)}</span>\n          <span className={slots.breakdownShare()}>{share(piece.value)}</span>\n        </li>\n      ))}\n      {freeLabel === null ? null : (\n        <li className={slots.breakdownRow()}>\n          <span\n            aria-hidden=\"true\"\n            className={slots.swatch()}\n            style={{ background: 'color-mix(in oklab, var(--muted-foreground) 16%, transparent)' }}\n          />\n          <span className={slots.breakdownLabel()}>{freeLabel}</span>\n          <span className={slots.breakdownValue()}>{formatter.format(free)}</span>\n          <span className={slots.breakdownShare()}>{share(free)}</span>\n        </li>\n      )}\n    </ul>\n  );\n}\nUsageBreakdown.displayName = 'Usage.Breakdown';\n\nexport interface UsageSectionProps extends Omit<ComponentProps<'section'>, 'title'> {\n  /** The section's heading: `Plan`, `This month`. */\n  title?: ReactNode;\n  /** Something at the far end of the heading, such as the plan's name. */\n  aside?: ReactNode;\n}\n\n/** A block of figures under a rule, with a heading. */\nfunction UsageSection({ title, aside, className, children, ...props }: UsageSectionProps) {\n  const slots = usageVariants();\n  return (\n    <section data-slot=\"usage-section\" className={slots.section({ className })} {...props}>\n      {title || aside ? (\n        <div className={slots.sectionHead()}>\n          {title ? <h3 className={slots.sectionTitle()}>{title}</h3> : <span />}\n          {aside ? <span className={slots.sectionAside()}>{aside}</span> : null}\n        </div>\n      ) : null}\n      {children}\n    </section>\n  );\n}\nUsageSection.displayName = 'Usage.Section';\n\nexport interface UsageMeterProps extends ComponentProps<'div'> {\n  /** What it measures: `Session`, `This week`. */\n  label: ReactNode;\n  /** How much is used. */\n  value: number;\n  /** How much there is. Default `100`, for a value that is already a percentage. */\n  limit?: number;\n  /** A line under the bar: when it resets, what happens at the limit. */\n  note?: ReactNode;\n  /** How the value is written beside the label. Default `value / limit` in the root's format, or a percentage when `limit` is 100. */\n  display?: ReactNode;\n}\n\n/** One limit as a row of ticks lit up to the share used, in the colour of its own level. */\nfunction UsageMeter({ label, value, limit = 100, note, display, className, style, ...props }: UsageMeterProps) {\n  const usage = useUsage('Usage.Meter');\n  const formatter = useFormat();\n  const slots = usageVariants();\n  const ratio = Math.min(1, Math.max(0, value / Math.max(1, limit)));\n  const level = levelOf(ratio, usage.state === 'idle' ? 'idle' : 'active', usage.warning, usage.critical);\n  const lit = Math.round(ratio * 32);\n  const shown = display ?? (limit === 100 ? `${Math.round(value)}%` : `${formatter.format(value)} / ${formatter.format(limit)}`);\n  return (\n    <div\n      data-slot=\"usage-meter\"\n      data-level={level}\n      className={slots.meter({ className })}\n      style={{ ...toneStyle(level), ...style }}\n      {...props}\n    >\n      <div className={slots.meterHead()}>\n        <span className={slots.meterLabel()}>{label}</span>\n        <span className={slots.meterValue()}>{shown}</span>\n      </div>\n      <div\n        role=\"meter\"\n        aria-label={typeof label === 'string' ? label : undefined}\n        aria-valuemin={0}\n        aria-valuemax={limit}\n        aria-valuenow={value}\n        className={slots.ticks()}\n      >\n        {Array.from({ length: 32 }, (_, index) => (\n          <span\n            key={index}\n            className={slots.tick()}\n            style={{\n              background:\n                index < lit ? 'var(--usage-tone)' : 'color-mix(in oklab, var(--muted-foreground) 18%, transparent)',\n              transitionDelay: `${index * 12}ms`,\n            }}\n          />\n        ))}\n      </div>\n      {note ? <span className={slots.meterNote()}>{note}</span> : null}\n    </div>\n  );\n}\nUsageMeter.displayName = 'Usage.Meter';\n\nexport interface UsageFooterProps extends ComponentProps<'div'> {}\n\n/** A band at the foot of the popover, for a note and an action. */\nfunction UsageFooter({ className, ...props }: UsageFooterProps) {\n  return <div data-slot=\"usage-footer\" className={usageVariants().footer({ className })} {...props} />;\n}\nUsageFooter.displayName = 'Usage.Footer';\n\nexport const Usage = Object.assign(UsageRoot, {\n  Trigger: UsageTrigger,\n  Content: UsageContent,\n  Summary: UsageSummary,\n  Breakdown: UsageBreakdown,\n  Section: UsageSection,\n  Meter: UsageMeter,\n  Footer: UsageFooter,\n});\n\nexport { UsageTrigger, UsageContent, UsageSummary, UsageBreakdown, UsageSection, UsageMeter, UsageFooter };\n"
    }
  ]
}
