{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"usage-bar","title":"Usage Bar","description":"Shows how a whole is shared out — storage, a data plan, a quota — as one row of segments sized by their value, with a legend, an axis and a highlighted segment that a list beside it can follow.","dependencies":["class-variance-authority","cn"],"files":[{"path":"components/ui/usage-bar.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"cn\"\n\ntype UsageBarContextValue = {\n  max: number\n  active: string | null\n  setActive: (name: string | null) => void\n}\n\nconst UsageBarContext = React.createContext<UsageBarContextValue | null>(null)\n\nfunction useUsageBar() {\n  const context = React.useContext(UsageBarContext)\n  if (!context) {\n    throw new Error(\"useUsageBar must be used within a <UsageBar />\")\n  }\n  return context\n}\n\nfunction UsageBar({\n  className,\n  max,\n  active: activeProp,\n  defaultActive = null,\n  onActiveChange,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  /** The whole the segments are measured against. */\n  max: number\n  /** The name of the highlighted segment (controlled). */\n  active?: string | null\n  defaultActive?: string | null\n  onActiveChange?: (name: string | null) => void\n}) {\n  const [uncontrolled, setUncontrolled] = React.useState(defaultActive)\n  const active = activeProp === undefined ? uncontrolled : activeProp\n\n  const setActive = React.useCallback(\n    (name: string | null) => {\n      if (activeProp === undefined) setUncontrolled(name)\n      onActiveChange?.(name)\n    },\n    [activeProp, onActiveChange]\n  )\n\n  const value = React.useMemo(\n    () => ({ max, active, setActive }),\n    [max, active, setActive]\n  )\n\n  return (\n    <UsageBarContext.Provider value={value}>\n      <div\n        data-slot=\"usage-bar\"\n        className={cn(\"flex flex-col gap-3 text-sm\", className)}\n        {...props}\n      />\n    </UsageBarContext.Provider>\n  )\n}\n\nfunction UsageBarHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"usage-bar-header\"\n      className={cn(\n        \"flex flex-wrap items-center justify-between gap-x-6 gap-y-2\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction UsageBarTrack({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      role=\"list\"\n      data-slot=\"usage-bar-track\"\n      className={cn(\"flex h-7 w-full gap-px\", className)}\n      {...props}\n    />\n  )\n}\n\nconst usageBarSegmentVariants = cva(\"rounded-[2px]\", {\n  variants: {\n    variant: {\n      default:\n        \"bg-foreground/30 data-active:ring-1 data-active:ring-border data-active:ring-inset focus-visible:ring-1 focus-visible:ring-border focus-visible:ring-inset\",\n      muted:\n        \"bg-foreground/10 data-active:ring-1 data-active:ring-border data-active:ring-inset focus-visible:ring-1 focus-visible:ring-border focus-visible:ring-inset\",\n      primary:\n        \"bg-chart-4 data-active:ring-1 data-active:ring-border data-active:ring-inset focus-visible:ring-1 focus-visible:ring-border focus-visible:ring-inset\",\n      striped:\n        \"bg-foreground/5 bg-[repeating-linear-gradient(-45deg,var(--border)_0_1.5px,transparent_1.5px_8.5px)] data-active:ring-1 data-active:ring-border data-active:ring-inset focus-visible:ring-1 focus-visible:ring-border focus-visible:ring-inset\",\n      dashed:\n        \"border border-dashed border-foreground/20 data-active:border-solid data-active:border-border focus-visible:border-solid focus-visible:border-border\",\n    },\n  },\n  defaultVariants: {\n    variant: \"default\",\n  },\n})\n\nfunction UsageBarSegment({\n  className,\n  style,\n  value,\n  name,\n  variant = \"default\",\n  onPointerEnter,\n  onPointerLeave,\n  onFocus,\n  onBlur,\n  ...props\n}: React.ComponentProps<\"div\"> &\n  VariantProps<typeof usageBarSegmentVariants> & {\n    /** The segment's size, in the units of the bar's `max`. */\n    value: number\n    /** Makes the segment hoverable and focusable, and the target of `active`. */\n    name?: string\n  }) {\n  const { max, active, setActive } = useUsageBar()\n  const isActive = name !== undefined && active === name\n\n  return (\n    <div\n      role=\"listitem\"\n      data-slot=\"usage-bar-segment\"\n      data-variant={variant}\n      data-active={isActive || undefined}\n      tabIndex={name === undefined ? undefined : 0}\n      aria-label={name}\n      className={cn(\n        usageBarSegmentVariants({ variant }),\n        \"relative min-w-px transition-[flex-basis] outline-none after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] data-active:after:bg-foreground/10 focus-visible:after:bg-foreground/10\",\n        className\n      )}\n      style={{\n        flexBasis: `${max > 0 ? (Math.max(value, 0) / max) * 100 : 0}%`,\n        ...style,\n      }}\n      onPointerEnter={(event) => {\n        if (name !== undefined) setActive(name)\n        onPointerEnter?.(event)\n      }}\n      onPointerLeave={(event) => {\n        if (isActive) setActive(null)\n        onPointerLeave?.(event)\n      }}\n      onFocus={(event) => {\n        if (name !== undefined) setActive(name)\n        onFocus?.(event)\n      }}\n      onBlur={(event) => {\n        if (isActive) setActive(null)\n        onBlur?.(event)\n      }}\n      {...props}\n    />\n  )\n}\n\nfunction UsageBarLegend({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"usage-bar-legend\"\n      className={cn(\n        \"flex flex-wrap items-center gap-x-4 gap-y-1 text-muted-foreground\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction UsageBarLegendItem({\n  className,\n  children,\n  variant = \"default\",\n  swatchClassName,\n  ...props\n}: React.ComponentProps<\"div\"> &\n  VariantProps<typeof usageBarSegmentVariants> & {\n    /** Classes for the swatch — the colour of a segment styled by hand. */\n    swatchClassName?: string\n  }) {\n  return (\n    <div\n      data-slot=\"usage-bar-legend-item\"\n      className={cn(\"flex items-center gap-1.5\", className)}\n      {...props}\n    >\n      <span\n        aria-hidden\n        data-slot=\"usage-bar-swatch\"\n        className={cn(\n          usageBarSegmentVariants({ variant }),\n          \"h-3 w-2 shrink-0\",\n          variant === \"striped\" &&\n            \"bg-[repeating-linear-gradient(-45deg,var(--border)_0_1.5px,transparent_1.5px_4.95px)]\",\n          swatchClassName\n        )}\n      />\n      {children}\n    </div>\n  )\n}\n\nfunction UsageBarAxis({\n  className,\n  ticks,\n  format = (value) => value.toLocaleString(\"en-US\"),\n  ...props\n}: Omit<React.ComponentProps<\"div\">, \"children\"> & {\n  /** Values to mark; defaults to quarters of `max`. */\n  ticks?: number[]\n  format?: (value: number) => React.ReactNode\n}) {\n  const { max } = useUsageBar()\n  const values =\n    max > 0\n      ? (ticks ?? [0, 1, 2, 3, 4].map((quarter) => (max * quarter) / 4))\n      : []\n\n  return (\n    <div\n      aria-hidden\n      data-slot=\"usage-bar-axis\"\n      className={cn(\n        \"relative -mt-2 h-6 text-2xs text-muted-foreground tabular-nums\",\n        className\n      )}\n      {...props}\n    >\n      {values.map((value) => {\n        const position = (value / max) * 100\n        const edge = position <= 0 ? \"start\" : position >= 100 ? \"end\" : null\n        return (\n          <span\n            key={value}\n            className={cn(\n              \"absolute inset-y-0 flex flex-col gap-1 whitespace-nowrap\",\n              edge === \"start\" && \"items-start\",\n              edge === \"end\" && \"-translate-x-full items-end\",\n              !edge && \"-translate-x-1/2 items-center\"\n            )}\n            style={{ left: `${position}%` }}\n          >\n            <span className=\"h-1 w-px bg-border\" />\n            <span className=\"leading-4\">{format(value)}</span>\n          </span>\n        )\n      })}\n    </div>\n  )\n}\n\nexport {\n  UsageBar,\n  UsageBarHeader,\n  UsageBarTrack,\n  UsageBarSegment,\n  UsageBarLegend,\n  UsageBarLegendItem,\n  UsageBarAxis,\n  useUsageBar,\n  usageBarSegmentVariants,\n}\n","type":"registry:ui"}],"cssVars":{"theme":{"text-2xs--letter-spacing":"-0.005em","text-sm--letter-spacing":"-0.005em","text-2xs":"0.75rem","text-2xs--line-height":"calc(1 / 0.75)"}},"categories":["feedback"],"type":"registry:ui"}