{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "toggle-chip",
  "type": "registry:ui",
  "dependencies": [
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "toggle-chip.tsx",
      "target": "components/ui/toggle-chip.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type ToggleChipGroupType = \"single\" | \"multiple\";\n\ninterface GroupContext {\n  values: string[];\n  disabled: boolean;\n  toggle: (value: string) => void;\n  isLocked: (value: string) => boolean;\n}\n\nconst ToggleChipGroupContext = React.createContext<GroupContext | null>(null);\n\ninterface ToggleChipGroupBaseProps\n  extends Omit<React.HTMLAttributes<HTMLFieldSetElement>, \"onChange\"> {\n  /** Names the group for assistive tech. Falls back to a string `label`. */\n  ariaLabel?: string;\n  /** Mono eyebrow rendered before the chips. */\n  label?: React.ReactNode;\n  disabled?: boolean;\n}\n\nexport interface ToggleChipGroupSingleProps extends ToggleChipGroupBaseProps {\n  type: \"single\";\n  /** The pressed id. Controlled. */\n  value: string;\n  onValueChange?: (value: string) => void;\n}\n\nexport interface ToggleChipGroupMultipleProps extends ToggleChipGroupBaseProps {\n  type?: \"multiple\";\n  /** The pressed ids. Controlled. */\n  value: string[];\n  onValueChange?: (value: string[]) => void;\n  /** Refuse to deselect below this many. */\n  min?: number;\n  /** Refuse to select beyond this many. */\n  max?: number;\n}\n\nexport type ToggleChipGroupProps =\n  | ToggleChipGroupSingleProps\n  | ToggleChipGroupMultipleProps;\n\nexport const ToggleChipGroup = React.forwardRef<\n  HTMLFieldSetElement,\n  ToggleChipGroupProps\n>((allProps, ref) => {\n  const {\n    className,\n    ariaLabel,\n    label,\n    disabled = false,\n    children,\n    type: _type,\n    value: _value,\n    onValueChange: _onValueChange,\n    min: _min,\n    max: _max,\n    ...props\n  } = allProps as ToggleChipGroupMultipleProps;\n\n  const values = allProps.type === \"single\" ? [allProps.value] : allProps.value;\n\n  const context: GroupContext = {\n    values,\n    disabled,\n    isLocked: (id) => {\n      if (allProps.type === \"single\") return false;\n      const { min, max } = allProps;\n      if (values.includes(id)) return min != null && values.length <= min;\n      return max != null && values.length >= max;\n    },\n    toggle: (id) => {\n      if (allProps.type === \"single\") {\n        allProps.onValueChange?.(id);\n        return;\n      }\n      allProps.onValueChange?.(\n        values.includes(id)\n          ? values.filter((current) => current !== id)\n          : [...values, id],\n      );\n    },\n  };\n\n  return (\n    <ToggleChipGroupContext.Provider value={context}>\n      <fieldset\n        ref={ref}\n        disabled={disabled}\n        aria-label={\n          ariaLabel ?? (typeof label === \"string\" ? label : undefined)\n        }\n        className={cn(\n          \"flex min-w-0 flex-wrap items-center gap-2 rounded-xl border border-border bg-sunken/60 p-2 backdrop-blur-md\",\n          className,\n        )}\n        {...props}\n      >\n        {label != null && (\n          <span className=\"px-1 font-mono text-[0.625rem] font-semibold uppercase tracking-[0.12em] text-muted\">\n            {label}\n          </span>\n        )}\n        {children}\n      </fieldset>\n    </ToggleChipGroupContext.Provider>\n  );\n});\nToggleChipGroup.displayName = \"ToggleChipGroup\";\n\nexport interface ToggleChipProps\n  extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"value\"> {\n  value: string;\n}\n\nexport const ToggleChip = React.forwardRef<HTMLButtonElement, ToggleChipProps>(\n  ({ className, value, disabled, onClick, children, ...props }, ref) => {\n    const group = React.useContext(ToggleChipGroupContext);\n    if (!group)\n      throw new Error(\"ToggleChip must be rendered inside a ToggleChipGroup\");\n\n    const selected = group.values.includes(value);\n    const isDisabled = disabled || group.disabled || group.isLocked(value);\n\n    return (\n      <button\n        ref={ref}\n        type=\"button\"\n        aria-pressed={selected}\n        disabled={isDisabled}\n        onClick={(event) => {\n          onClick?.(event);\n          if (!event.defaultPrevented) group.toggle(value);\n        }}\n        className={cn(\n          \"relative inline-flex min-h-9 items-center gap-2 rounded-lg px-2.5 py-1.5 text-xs font-semibold outline-none\",\n          \"transition-control duration-fast ease-soft focus-visible:ring-focus\",\n          \"active:scale-[0.96] motion-reduce:transform-none motion-reduce:transition-none\",\n          // The chip is 36px tall to keep the rows dense, so it borrows the 44px\n          // target from a pseudo-element rather than from its own box.\n          \"before:absolute before:-inset-y-1 before:inset-x-0 before:content-['']\",\n          selected\n            ? \"bg-accent/15 text-accent\"\n            : \"bg-ink/[0.06] text-muted hover:text-ink\",\n          isDisabled\n            ? \"cursor-not-allowed opacity-55\"\n            : \"cursor-pointer hover:bg-ink/10\",\n          className,\n        )}\n        {...props}\n      >\n        <span className=\"grid size-4 shrink-0 place-items-center rounded-full bg-sunken/70\">\n          {selected && <CheckIcon />}\n        </span>\n        {children}\n      </button>\n    );\n  },\n);\nToggleChip.displayName = \"ToggleChip\";\n\nfunction CheckIcon() {\n  return (\n    <svg\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2.4\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      className=\"size-3\"\n      aria-hidden=\"true\"\n    >\n      <path d=\"m5 13 4 4L19 7\" />\n    </svg>\n  );\n}\n"
    }
  ]
}