{
  "$schema": "https://cubixflow.ir/schema/registry-item.json",
  "name": "chips",
  "type": "registry:ui",
  "title": "Chips",
  "description": "A row of selectable pills for filtering, tagging, or picking one or more options.",
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/cubix/chips.tsx",
      "type": "registry:ui",
      "target": "components/cubix/chips.tsx",
      "content": "\"use client\"\n\n/*\n  Cubix Chips - a row of selectable pills built on a toggle group.\n\n  Single mode keeps one chip selected and lets the selected chip deselect\n  itself; multiple mode keeps any number selected. Both modes take string\n  arrays for value and defaultValue, matching Cubix Accordion. Arrow keys\n  move focus between the enabled chips of the same group (wrapping at the\n  ends, vertical orientation swaps to ArrowUp/ArrowDown) and follow the\n  reading direction, so they are mirrored in RTL. Home and End jump to the\n  first and last one. Backspace or Delete removes a focused removable chip.\n*/\nimport * as React from \"react\"\nimport { DirectionProvider } from \"@base-ui/react/direction-provider\"\nimport { Toggle as TogglePrimitive } from \"@base-ui/react/toggle\"\nimport { ToggleGroup as ToggleGroupPrimitive } from \"@base-ui/react/toggle-group\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype ChipsProps = Omit<\n  ToggleGroupPrimitive.Props<string>,\n  \"loopFocus\" | \"value\" | \"defaultValue\" | \"onValueChange\"\n> & {\n  value?: string[]\n  defaultValue?: string[]\n  onValueChange?: (value: string[]) => void\n  dir?: \"ltr\" | \"rtl\"\n}\n\n/*\n  Cubix is Persian-first, so the row starts right-to-left and then follows\n  the closest dir on the page. An explicit dir prop wins.\n*/\nfunction usePageDir(dir: ChipsProps[\"dir\"]) {\n  const nodeRef = React.useRef<HTMLDivElement | null>(null)\n  const [pageDir, setPageDir] = React.useState<\"ltr\" | \"rtl\">(\"rtl\")\n\n  React.useLayoutEffect(() => {\n    const closest = nodeRef.current?.parentElement?.closest(\"[dir]\")?.getAttribute(\"dir\")\n    if (closest === \"ltr\" || closest === \"rtl\") {\n      setPageDir(closest)\n    }\n  }, [])\n\n  return { nodeRef, resolvedDir: dir ?? pageDir }\n}\n\nfunction Chips({\n  className,\n  multiple = false,\n  value,\n  defaultValue,\n  onValueChange,\n  dir,\n  ref,\n  ...props\n}: ChipsProps) {\n  const { nodeRef, resolvedDir } = usePageDir(dir)\n  const setRef = (node: HTMLDivElement | null) => {\n    nodeRef.current = node\n    if (typeof ref === \"function\") {\n      ref(node)\n    } else if (ref) {\n      ref.current = node\n    }\n  }\n\n  return (\n    <DirectionProvider direction={resolvedDir}>\n      <ToggleGroupPrimitive<string>\n        data-slot=\"chips\"\n        dir={resolvedDir}\n        ref={setRef}\n        multiple={multiple}\n        value={value}\n        defaultValue={defaultValue}\n        onValueChange={onValueChange ? (next) => onValueChange([...next]) : undefined}\n        className={cn(\"flex w-full flex-wrap items-center gap-2\", className)}\n        {...props}\n      />\n    </DirectionProvider>\n  )\n}\n\n/*\n  Removing the focused chip would drop focus to the page body. Move it to\n  the next enabled chip first, or to the previous one when removing the last.\n*/\nfunction focusSiblingChip(chip: HTMLElement) {\n  const group = chip.closest(\"[data-slot=chips]\")\n  if (!group) return\n  const chips = Array.from(\n    group.querySelectorAll<HTMLElement>(\"[data-slot=chip]:not(:disabled):not([data-disabled])\")\n  )\n  const index = chips.indexOf(chip)\n  const sibling = chips[index + 1] ?? chips[index - 1]\n  sibling?.focus()\n}\n\n/*\n  Same filled circle-X as Cubix Text Field's clear button.\n*/\nfunction ChipRemoveIcon({ className }: { className?: string }) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"currentColor\"\n      aria-hidden=\"true\"\n      className={className}\n    >\n      <path d=\"M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z\" />\n    </svg>\n  )\n}\n\n/*\n  The variant sets how a selected chip looks, using the same colors as the\n  matching Cubix Button variant. Unselected chips always stay outlined.\n*/\nconst chipVariants = {\n  default: {\n    chip: \"aria-pressed:border-transparent aria-pressed:bg-primary aria-pressed:text-primary-foreground aria-pressed:hover:bg-[color-mix(in_oklch,var(--primary),black_10%)] aria-pressed:group-hover/chip:bg-[color-mix(in_oklch,var(--primary),black_10%)]\",\n    remove: \"peer-aria-pressed/chip:text-primary-foreground\",\n  },\n  secondary: {\n    chip: \"aria-pressed:border-transparent aria-pressed:bg-primary/10 aria-pressed:text-primary dark:aria-pressed:bg-primary/20 aria-pressed:hover:bg-primary/20 aria-pressed:group-hover/chip:bg-primary/20\",\n    remove: \"peer-aria-pressed/chip:text-primary\",\n  },\n  gray: {\n    chip: \"aria-pressed:border-transparent aria-pressed:bg-secondary aria-pressed:text-secondary-foreground aria-pressed:hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-pressed:group-hover/chip:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]\",\n    remove: \"peer-aria-pressed/chip:text-secondary-foreground\",\n  },\n  outline: {\n    chip: \"aria-pressed:border-border aria-pressed:bg-transparent aria-pressed:text-foreground aria-pressed:hover:bg-muted aria-pressed:group-hover/chip:bg-muted dark:aria-pressed:border-input dark:aria-pressed:hover:bg-input/50 dark:aria-pressed:group-hover/chip:bg-input/50\",\n    remove: \"peer-aria-pressed/chip:text-foreground\",\n  },\n} as const\n\ntype ChipVariant = keyof typeof chipVariants\n\n/*\n  Sizes follow Cubix Button heights. The remove button keeps the same gap\n  to the chip's top, bottom and end edges: (height - icon size) / 2.\n*/\nconst chipSizes = {\n  xs: {\n    chip: \"h-7 gap-1 px-2.5 text-caption [&_svg:not([class*='size-'])]:size-3.5\",\n    removable: \"pe-6.5\",\n    remove: \"end-1 [&_svg]:size-3.5\",\n    avatar: \"size-5\",\n  },\n  sm: {\n    chip: \"h-8 gap-1.5 px-3 text-label [&_svg:not([class*='size-'])]:size-4\",\n    removable: \"pe-7.5\",\n    remove: \"end-1.5 [&_svg]:size-4\",\n    avatar: \"size-6\",\n  },\n  default: {\n    chip: \"h-10 gap-2 px-4 text-label [&_svg:not([class*='size-'])]:size-5\",\n    removable: \"pe-9\",\n    remove: \"end-2.5 [&_svg]:size-4\",\n    avatar: \"size-8\",\n  },\n  lg: {\n    chip: \"h-12 gap-2.5 px-6 text-description [&_svg:not([class*='size-'])]:size-6\",\n    removable: \"pe-10.5\",\n    remove: \"end-3.5 [&_svg]:size-4\",\n    avatar: \"size-10\",\n  },\n} as const\n\ntype ChipSize = keyof typeof chipSizes\n\ntype ChipProps = Omit<TogglePrimitive.Props<string>, \"value\"> & {\n  value: string\n  icon?: React.ReactNode\n  avatar?: React.ReactNode\n  variant?: ChipVariant\n  size?: ChipSize\n  removeLabel?: string\n  onRemove?: () => void\n}\n\nfunction Chip({\n  variant = \"default\",\n  size = \"sm\",\n  className,\n  children,\n  icon,\n  avatar,\n  removeLabel = \"حذف\",\n  onRemove,\n  onKeyDown,\n  ...props\n}: ChipProps) {\n  const chip = (\n    <TogglePrimitive\n      data-slot=\"chip\"\n      onKeyDown={(event) => {\n        onKeyDown?.(event)\n        if (!onRemove || event.defaultPrevented) return\n        if (event.key !== \"Backspace\" && event.key !== \"Delete\") return\n        event.preventDefault()\n        focusSiblingChip(event.currentTarget)\n        onRemove()\n      }}\n      className={cn(\n        \"peer/chip inline-flex shrink-0 items-center rounded-full has-data-[slot=chip-avatar]:ps-[3px] border border-border bg-background font-normal whitespace-nowrap text-foreground outline-none transition-colors hover:bg-muted group-hover/chip:bg-muted focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        chipSizes[size].chip,\n        chipVariants[variant].chip,\n        onRemove && chipSizes[size].removable,\n        className\n      )}\n      {...props}\n    >\n      {avatar ? (\n        <span\n          data-slot=\"chip-avatar\"\n          aria-hidden=\"true\"\n          className={cn(\n            \"inline-flex shrink-0 [&>[data-slot=avatar]]:size-full\",\n            chipSizes[size].avatar\n          )}\n        >\n          {avatar}\n        </span>\n      ) : icon ? (\n        <span\n          data-slot=\"chip-icon\"\n          aria-hidden=\"true\"\n          className=\"inline-flex shrink-0 items-center\"\n        >\n          {icon}\n        </span>\n      ) : null}\n      {children}\n    </TogglePrimitive>\n  )\n\n  if (!onRemove) {\n    return chip\n  }\n\n  /*\n    The remove button sits beside the chip instead of inside it, because\n    HTML does not allow a button inside another button. It is positioned\n    over the chip's end padding so it looks the same. It stays out of the\n    tab order; keyboard users remove the focused chip with Backspace or\n    Delete.\n  */\n  return (\n    <span data-slot=\"chip-root\" className=\"group/chip relative inline-flex shrink-0\">\n      {chip}\n      <button\n        type=\"button\"\n        data-slot=\"chip-remove\"\n        aria-label={removeLabel}\n        tabIndex={-1}\n        onClick={(event) => {\n          event.stopPropagation()\n          const root = event.currentTarget.parentElement\n          const chipElement = root?.querySelector<HTMLElement>(\"[data-slot=chip]\")\n          if (chipElement && root?.contains(document.activeElement)) {\n            focusSiblingChip(chipElement)\n          }\n          onRemove()\n        }}\n        className={cn(\n          \"absolute top-1/2 inline-flex size-5 -translate-y-1/2 cursor-default items-center justify-center rounded-full text-foreground opacity-70 outline-none transition-opacity duration-200 ease-out hover:opacity-100 focus-visible:opacity-100 peer-disabled/chip:pointer-events-none peer-disabled/chip:opacity-35 peer-data-disabled/chip:pointer-events-none peer-data-disabled/chip:opacity-35 [&_svg]:pointer-events-none\",\n          chipSizes[size].remove,\n          chipVariants[variant].remove\n        )}\n      >\n        <ChipRemoveIcon />\n      </button>\n    </span>\n  )\n}\n\nexport { Chips, Chip, type ChipsProps, type ChipProps, type ChipVariant, type ChipSize }\n"
    }
  ]
}

