{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "storage-breakdown",
  "title": "StorageBreakdown",
  "description": "Storage composition by volume or by cost — root, child, instant restore, and snapshots as one bar with per-segment shares.",
  "registryDependencies": [
    "utils",
    "https://ui.neon.com/r/neon-tokens.json",
    "https://ui.neon.com/r/skeleton.json"
  ],
  "files": [
    {
      "path": "src/components/storage-breakdown/storage-breakdown.tsx",
      "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport type { ComponentProps, ReactNode } from \"react\";\n\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport { useControllableState } from \"@/hooks/use-controllable-state\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface StorageSegment {\n  /** Stable id, e.g. \"root_branch_bytes_month\". */\n  id: string;\n  /** Row and tooltip label, e.g. \"Root branch\". */\n  label: string;\n  /** Amount in the unit you're displaying, e.g. GB-months. */\n  value: number;\n  /**\n   * USD per unit, e.g. 0.35 for root storage. Storage metrics bill at\n   * different rates, so bytes and dollars rank differently. Give every\n   * segment a rate and the card can show both.\n   */\n  rate?: number;\n  /** Any CSS color; defaults to the --chart-n token for its position. */\n  color?: string;\n  /** One quiet line explaining what this segment is. */\n  hint?: string;\n}\n\n/** Rank by how much storage is held, or by what it costs. */\nexport type StorageBreakdownView = \"volume\" | \"cost\";\n\nexport type StorageBreakdownProps = Omit<\n  ComponentProps<\"section\">,\n  \"children\"\n> & {\n  /** Segments in stacking order, largest first reads best. */\n  segments: StorageSegment[];\n  /** Panel heading. */\n  title?: string;\n  /** Unit for the headline figure, e.g. \"GB-mo\". Sits beside the number. */\n  unit?: string;\n  /** Period readout, e.g. \"Feb 1 – Feb 14\". Sits in the header's right. */\n  period?: string;\n  /**\n   * Denominator for the share percentages. Defaults to the segment sum;\n   * pass a plan allowance to show headroom instead of composition.\n   */\n  total?: number;\n  /**\n   * Controlled view. \"volume\" ranks by the raw amount; \"cost\" ranks by\n   * amount x rate. The switch only appears when every segment has a rate.\n   */\n  view?: StorageBreakdownView;\n  defaultView?: StorageBreakdownView;\n  onViewChange?: (view: StorageBreakdownView) => void;\n  /** Formats every amount shown. Defaults to two decimals. */\n  formatValue?: (value: number) => string;\n  /** Controlled highlight, e.g. driven from a chart legend. */\n  activeSegmentId?: string | null;\n  onActiveSegmentIdChange?: (id: string | null) => void;\n  /** Makes rows clickable, e.g. to filter a chart. */\n  onSelectSegment?: (segment: StorageSegment) => void;\n  isLoading?: boolean;\n  error?: string | null;\n  /** Extra content under the rows, e.g. a link to snapshot settings. */\n  footer?: ReactNode;\n};\n\n/* ─────────────────────────────────────────────────────────\n * BREAKDOWN STORYBOARD\n *\n *  rest      one continuous bar answers \"what is my storage\n *            made of\" at a glance; the rows below answer\n *            \"how much\" and \"what does it cost\" without a\n *            tooltip\n *  volume    the default ranking, by amount held\n *  cost      the same segments weighted by rate. Storage\n *            bills at four different prices, so bytes and\n *            dollars rank differently — instant restore\n *            looks larger than it bills. The switch exists\n *            so the card can't misdirect the one reader\n *            who came here to cut a bill\n *  hover     hovering a row dims the other segments rather\n *            than growing anything — the bar never moves,\n *            so comparison stays honest\n *  zero      a metric the API omitted is zero, not missing;\n *            it keeps its row at 0% instead of vanishing\n *            and making the reader wonder\n *  loading   bar and rows skeleton at their real heights\n * ───────────────────────────────────────────────────────── */\n\nconst PALETTE = [\n  \"var(--chart-1)\",\n  \"var(--chart-2)\",\n  \"var(--chart-3)\",\n  \"var(--chart-4)\",\n  \"var(--chart-5)\",\n] as const;\n\n/**\n * One decimal, always. Without a minimum, 4.0% prints as \"4%\" and the\n * decimal points stop lining up in a right-aligned column.\n */\nconst PERCENT = new Intl.NumberFormat(\"en-US\", {\n  maximumFractionDigits: 1,\n  minimumFractionDigits: 1,\n  style: \"percent\",\n});\n\nconst CURRENCY = new Intl.NumberFormat(\"en-US\", {\n  currency: \"USD\",\n  style: \"currency\",\n});\n\nconst DEFAULT_FORMAT = new Intl.NumberFormat(\"en-US\", {\n  maximumFractionDigits: 2,\n});\n\n/** Hoisted so the default prop keeps a stable identity across renders. */\nconst formatDefault = (value: number) => DEFAULT_FORMAT.format(value);\n\nconst colorOf = (segment: StorageSegment, index: number): string =>\n  segment.color ?? PALETTE[index % PALETTE.length] ?? PALETTE[0];\n\nconst MIN_VISIBLE_SHARE = 0.004;\n\nconst costOf = (segment: StorageSegment) => segment.value * (segment.rate ?? 0);\n\n/**\n * One legend row. A row that does nothing is not a button: rendering one\n * hands keyboard and screen-reader users a dead stop per segment.\n */\nconst SegmentRow = ({\n  color,\n  formatValue,\n  isInteractive,\n  onSelect,\n  segment,\n  setActive,\n  share,\n  showsCost,\n  unit,\n}: {\n  color: string;\n  formatValue: (value: number) => string;\n  isInteractive: boolean;\n  onSelect?: () => void;\n  segment: StorageSegment;\n  setActive: (id: string | null) => void;\n  share: number;\n  showsCost: boolean;\n  unit: string;\n}) => {\n  const Row = isInteractive ? \"button\" : \"div\";\n  const amount = showsCost\n    ? CURRENCY.format(costOf(segment))\n    : formatValue(segment.value);\n  const counterpart = showsCost\n    ? `${formatValue(segment.value)} ${unit}`\n    : `~${CURRENCY.format(costOf(segment))}`;\n\n  return (\n    <Row\n      className={cn(\n        \"flex w-full items-baseline gap-2 rounded-md px-1.5 py-1.5 text-left transition-colors\",\n        isInteractive &&\n          \"hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40\"\n      )}\n      onBlur={isInteractive ? () => setActive(null) : undefined}\n      onClick={onSelect}\n      onFocus={isInteractive ? () => setActive(segment.id) : undefined}\n      onPointerEnter={() => setActive(segment.id)}\n      onPointerLeave={() => setActive(null)}\n      {...(isInteractive ? { type: \"button\" as const } : {})}\n    >\n      <span\n        aria-hidden=\"true\"\n        className=\"size-2.5 shrink-0 translate-y-[1px] rounded-[2px]\"\n        style={{ backgroundColor: color }}\n      />\n      <span className=\"min-w-0 flex-1\">\n        <span className=\"block truncate text-foreground text-sm\">\n          {segment.label}\n        </span>\n        {segment.hint ? (\n          <span className=\"block truncate text-[11px] text-muted-foreground/70\">\n            {segment.hint}\n          </span>\n        ) : null}\n      </span>\n      <span className=\"text-right\">\n        <span className=\"block font-mono text-foreground text-sm tabular-nums\">\n          {amount}\n        </span>\n        {segment.rate === undefined ? null : (\n          <span className=\"block font-mono text-[10px] text-muted-foreground/60 tabular-nums\">\n            {counterpart}\n          </span>\n        )}\n      </span>\n      <span className=\"w-14 text-right font-mono text-[11px] text-muted-foreground tabular-nums\">\n        {PERCENT.format(share)}\n      </span>\n    </Row>\n  );\n};\n\n/** The figure, its unit, and its counterpart in the other currency. */\nconst Headline = ({\n  costSum,\n  formatValue,\n  isPriced,\n  showsCost,\n  sum,\n  unit,\n}: {\n  costSum: number;\n  formatValue: (value: number) => string;\n  isPriced: boolean;\n  showsCost: boolean;\n  sum: number;\n  unit: string;\n}) => {\n  if (showsCost) {\n    return (\n      <p className=\"mt-2 font-medium font-mono text-2xl text-foreground tabular-nums\">\n        {CURRENCY.format(costSum)}\n      </p>\n    );\n  }\n\n  // The unit belongs beside the number, not in the opposite corner.\n  return (\n    <p className=\"mt-2 flex items-baseline gap-1.5\">\n      <span className=\"font-medium font-mono text-2xl text-foreground tabular-nums\">\n        {formatValue(sum)}\n      </span>\n      <span className=\"font-mono text-muted-foreground text-sm\">{unit}</span>\n      {isPriced ? (\n        <span className=\"font-mono text-muted-foreground/60 text-xs tabular-nums\">\n          · ~{CURRENCY.format(costSum)}\n        </span>\n      ) : null}\n    </p>\n  );\n};\n\nconst ViewSwitch = ({\n  value,\n  onChange,\n}: {\n  value: StorageBreakdownView;\n  onChange: (next: StorageBreakdownView) => void;\n}) => (\n  <fieldset\n    aria-label=\"Rank by\"\n    className=\"flex items-center gap-0.5 rounded-full border border-border/60 p-0.5\"\n  >\n    {([\"volume\", \"cost\"] as const).map((option) => (\n      <button\n        aria-pressed={option === value}\n        className={cn(\n          \"rounded-full px-2 py-0.5 font-mono text-[10px] transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40\",\n          option === value\n            ? \"bg-primary/10 text-primary\"\n            : \"text-muted-foreground hover:text-foreground\"\n        )}\n        key={option}\n        onClick={() => onChange(option)}\n        type=\"button\"\n      >\n        {option}\n      </button>\n    ))}\n  </fieldset>\n);\n\nexport const StorageBreakdown = ({\n  activeSegmentId,\n  className,\n  defaultView = \"volume\",\n  error = null,\n  footer,\n  formatValue = formatDefault,\n  isLoading = false,\n  onActiveSegmentIdChange,\n  onSelectSegment,\n  onViewChange,\n  period,\n  segments,\n  title = \"storage\",\n  total,\n  unit = \"GB-mo\",\n  view,\n  ...props\n}: StorageBreakdownProps) => {\n  const [internalActive, setInternalActive] = useState<string | null>(null);\n  const active =\n    activeSegmentId === undefined ? internalActive : activeSegmentId;\n\n  const [activeView, setActiveView] =\n    useControllableState<StorageBreakdownView>({\n      caller: \"StorageBreakdown\",\n      defaultProp: defaultView,\n      onChange: onViewChange,\n      prop: view,\n    });\n\n  const setActive = (id: string | null) => {\n    if (activeSegmentId === undefined) {\n      setInternalActive(id);\n    }\n\n    onActiveSegmentIdChange?.(id);\n  };\n\n  // Rates are what make bytes and dollars disagree. Offer the switch only\n  // when every segment can answer both questions.\n  const isPriced =\n    segments.length > 0 && segments.every((s) => s.rate !== undefined);\n  const showsCost = isPriced && activeView === \"cost\";\n\n  const weightOf = (segment: StorageSegment) =>\n    showsCost ? costOf(segment) : segment.value;\n\n  const sum = segments.reduce((carry, segment) => carry + segment.value, 0);\n  const costSum = segments.reduce(\n    (carry, segment) => carry + costOf(segment),\n    0\n  );\n  const denominator = showsCost ? costSum : (total ?? sum);\n  const shareOf = (segment: StorageSegment) =>\n    denominator > 0 ? weightOf(segment) / denominator : 0;\n\n  const isInteractive = Boolean(onSelectSegment);\n\n  if (isLoading) {\n    return (\n      <section\n        className={cn(\n          \"rounded-lg border border-border/60 bg-card p-4\",\n          className\n        )}\n        data-slot=\"storage-breakdown\"\n        {...props}\n      >\n        <Skeleton className=\"h-4 w-24\" />\n        <Skeleton className=\"mt-3 h-8 w-40\" />\n        <Skeleton className=\"mt-3 h-2.5 w-full rounded-full\" />\n        <div className=\"mt-4 space-y-2.5\">\n          {segments.map((segment) => (\n            <Skeleton className=\"h-8 w-full\" key={segment.id} />\n          ))}\n        </div>\n      </section>\n    );\n  }\n\n  return (\n    <section\n      className={cn(\n        \"rounded-lg border border-border/60 bg-card p-4\",\n        className\n      )}\n      data-slot=\"storage-breakdown\"\n      {...props}\n    >\n      <header className=\"flex items-center justify-between gap-3\">\n        <h3 className=\"font-mono text-muted-foreground text-xs\">{title}</h3>\n        <div className=\"flex items-center gap-2\">\n          {period ? (\n            <span className=\"font-mono text-muted-foreground/70 text-xs tabular-nums\">\n              {period}\n            </span>\n          ) : null}\n          {isPriced ? (\n            <ViewSwitch onChange={setActiveView} value={activeView} />\n          ) : null}\n        </div>\n      </header>\n\n      <Headline\n        costSum={costSum}\n        formatValue={formatValue}\n        isPriced={isPriced}\n        showsCost={showsCost}\n        sum={sum}\n        unit={unit}\n      />\n\n      {error ? (\n        <p\n          className=\"mt-3 flex items-baseline gap-2 text-sm\"\n          data-slot=\"storage-breakdown-error\"\n          role=\"alert\"\n        >\n          <span className=\"font-mono text-destructive text-xs\">error</span>\n          <span className=\"text-foreground\">{error}</span>\n        </p>\n      ) : null}\n\n      {/* No gaps: segments butt against each other so the bar reads as one\n          quantity split up, not as a segmented control. */}\n      <div\n        aria-hidden=\"true\"\n        className=\"mt-3 flex h-2.5 w-full overflow-hidden rounded-full bg-muted\"\n      >\n        {segments.map((segment, index) => {\n          const share = shareOf(segment);\n\n          if (share < MIN_VISIBLE_SHARE) {\n            return null;\n          }\n\n          return (\n            <span\n              className=\"h-full transition-opacity duration-200\"\n              key={segment.id}\n              style={{\n                backgroundColor: colorOf(segment, index),\n                // A hairline of the card's own color separates neighbours,\n                // so the ramp's steps read as distinct bands.\n                borderRight:\n                  index < segments.length - 1\n                    ? \"1.5px solid var(--card)\"\n                    : undefined,\n                opacity: active && active !== segment.id ? 0.25 : 1,\n                width: `${share * 100}%`,\n              }}\n            />\n          );\n        })}\n      </div>\n\n      <ul className=\"mt-4 space-y-0.5\">\n        {segments.map((segment, index) => (\n          <li key={segment.id}>\n            <SegmentRow\n              color={colorOf(segment, index)}\n              formatValue={formatValue}\n              isInteractive={isInteractive}\n              onSelect={\n                isInteractive ? () => onSelectSegment?.(segment) : undefined\n              }\n              segment={segment}\n              setActive={setActive}\n              share={shareOf(segment)}\n              showsCost={showsCost}\n              unit={unit}\n            />\n          </li>\n        ))}\n      </ul>\n\n      {showsCost ? (\n        <p className=\"mt-2 px-1.5 text-[10px] text-muted-foreground/70\">\n          ranked by cost · storage bills at four different rates\n        </p>\n      ) : null}\n\n      {footer ? <div className=\"mt-3\">{footer}</div> : null}\n    </section>\n  );\n};\n",
      "type": "registry:component"
    },
    {
      "path": "src/hooks/use-controllable-state.tsx",
      "content": "/* oxlint-disable func-style, no-empty-function, no-shadow, no-use-before-define, react/react-compiler, max-lines-per-function, max-lines, no-magic-numbers -- vendored from chanhdai.com/r/elastic-slider.json (MIT, @iamncdai); kept diffable against upstream */\nimport * as React from \"react\";\n\n// use-layout-effect.tsx\n// https://github.com/radix-ui/primitives/blob/main/packages/react/use-layout-effect/src/use-layout-effect.tsx\n\n/**\n * On the server, React emits a warning when calling `useLayoutEffect`.\n * This is because neither `useLayoutEffect` nor `useEffect` run on the server.\n * We use this safe version which suppresses the warning by replacing it with a noop on the server.\n *\n * See: https://reactjs.org/docs/hooks-reference.html#uselayouteffect\n */\nconst useLayoutEffect = globalThis?.document ? React.useLayoutEffect : () => {};\n\n// use-controllable-state.tsx\n// https://github.com/radix-ui/primitives/blob/main/packages/react/use-controllable-state/src/use-controllable-state.tsx\n\n// Prevent bundlers from trying to optimize the import\nconst useInsertionEffect: typeof useLayoutEffect =\n  (React as never)[\" useInsertionEffect \".trim().toString()] || useLayoutEffect;\n\ntype ChangeHandler<T> = (state: T) => void;\ntype SetStateFn<T> = React.Dispatch<React.SetStateAction<T>>;\n\ninterface UseControllableStateParams<T> {\n  prop?: T | undefined;\n  defaultProp: T;\n  onChange?: ChangeHandler<T>;\n  caller?: string;\n}\n\nexport function useControllableState<T>({\n  prop,\n  defaultProp,\n  onChange = () => {},\n  caller,\n}: UseControllableStateParams<T>): [T, SetStateFn<T>] {\n  const [uncontrolledProp, setUncontrolledProp, onChangeRef] =\n    useUncontrolledState({\n      defaultProp,\n      onChange,\n    });\n  const isControlled = prop !== undefined;\n  const value = isControlled ? prop : uncontrolledProp;\n\n  // Hooks run unconditionally so Hook order never changes between renders;\n  // only the dev-time warning itself is gated on the environment.\n  // (Neon UI patch on the vendored source.)\n  const isControlledRef = React.useRef(prop !== undefined);\n  React.useEffect(() => {\n    if (process.env.NODE_ENV !== \"production\") {\n      const wasControlled = isControlledRef.current;\n      if (wasControlled !== isControlled) {\n        const from = wasControlled ? \"controlled\" : \"uncontrolled\";\n        const to = isControlled ? \"controlled\" : \"uncontrolled\";\n        console.warn(\n          `${caller} is changing from ${from} to ${to}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`\n        );\n      }\n    }\n    isControlledRef.current = isControlled;\n  }, [isControlled, caller]);\n\n  const setValue = React.useCallback<SetStateFn<T>>(\n    (nextValue) => {\n      if (isControlled) {\n        const value = isFunction(nextValue) ? nextValue(prop) : nextValue;\n        if (value !== prop) {\n          onChangeRef.current?.(value);\n        }\n      } else {\n        // Notify the parent from the event handler instead of a useEffect,\n        // per https://react.dev/learn/you-might-not-need-an-effect — saves\n        // the extra render. (Neon UI patch on the vendored source.)\n        const value = isFunction(nextValue)\n          ? nextValue(uncontrolledProp)\n          : nextValue;\n        setUncontrolledProp(value);\n        if (value !== uncontrolledProp) {\n          onChangeRef.current?.(value);\n        }\n      }\n    },\n    [isControlled, prop, setUncontrolledProp, onChangeRef, uncontrolledProp]\n  );\n\n  return [value, setValue];\n}\n\nfunction useUncontrolledState<T>({\n  defaultProp,\n  onChange,\n}: Omit<UseControllableStateParams<T>, \"prop\">): [\n  Value: T,\n  setValue: React.Dispatch<React.SetStateAction<T>>,\n  OnChangeRef: React.RefObject<ChangeHandler<T> | undefined>,\n] {\n  const [value, setValue] = React.useState(defaultProp);\n\n  const onChangeRef = React.useRef(onChange);\n  useInsertionEffect(() => {\n    onChangeRef.current = onChange;\n  }, [onChange]);\n\n  // onChange is fired from setValue in useControllableState rather than from\n  // an effect here, so parents update in the same render pass.\n  // (Neon UI patch on the vendored source.)\n  return [value, setValue, onChangeRef];\n}\n\nfunction isFunction(value: unknown): value is (...args: never[]) => unknown {\n  return typeof value === \"function\";\n}\n",
      "type": "registry:hook"
    }
  ],
  "type": "registry:component"
}