{
  "name": "tag-input",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "tag-input.tsx",
      "content": "\"use client\";\r\n\r\nimport { css, themeVars as theme } from \"@yugnex/core\";\r\nimport { useControllableState } from \"@yugnex/core/client\";\r\nimport { useId, useRef, useState, type KeyboardEvent, type ReactNode } from \"react\";\r\n\r\nconst shellClass = css({\r\n  display: \"flex\",\r\n  flexWrap: \"wrap\",\r\n  alignItems: \"center\",\r\n  gap: theme.space[1.5],\r\n  minHeight: \"2.5rem\",\r\n  padding: theme.space[1.5],\r\n  borderRadius: theme.radius.md,\r\n  border: `1px solid ${theme.color.input}`,\r\n  backgroundColor: theme.color.background,\r\n  cursor: \"text\",\r\n  transitionProperty: \"border-color, box-shadow\",\r\n  transitionDuration: theme.duration.fast,\r\n  \"&:focus-within\": {\r\n    borderColor: theme.color.ring,\r\n    boxShadow: `0 0 0 3px ${theme.color.accent}`,\r\n  },\r\n  '&[data-disabled=\"true\"]': { opacity: 0.5, cursor: \"not-allowed\" },\r\n  '&[data-invalid=\"true\"]': { borderColor: theme.color.destructive },\r\n});\r\n\r\nconst tagClass = css({\r\n  display: \"inline-flex\",\r\n  alignItems: \"center\",\r\n  gap: theme.space[1],\r\n  height: \"1.5rem\",\r\n  padding: `0 ${theme.space[1]} 0 ${theme.space[2]}`,\r\n  borderRadius: theme.radius.sm,\r\n  backgroundColor: theme.color.muted,\r\n  color: theme.color.foreground,\r\n  fontFamily: theme.fontFamily.sans,\r\n  fontSize: theme.fontSize.xs,\r\n  maxWidth: \"100%\",\r\n});\r\n\r\nconst tagLabelClass = css({ overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\" });\r\n\r\nconst removeClass = css({\r\n  display: \"inline-flex\",\r\n  alignItems: \"center\",\r\n  justifyContent: \"center\",\r\n  width: \"1rem\",\r\n  height: \"1rem\",\r\n  borderRadius: theme.radius.sm,\r\n  border: \"none\",\r\n  background: \"transparent\",\r\n  color: theme.color.mutedForeground,\r\n  cursor: \"pointer\",\r\n  flexShrink: 0,\r\n  \"&:hover\": { backgroundColor: theme.color.border, color: theme.color.foreground },\r\n  \"&:focus-visible\": { outline: `2px solid ${theme.color.ring}`, outlineOffset: \"1px\" },\r\n});\r\n\r\nconst inputClass = css({\r\n  flex: 1,\r\n  minWidth: \"6rem\",\r\n  border: \"none\",\r\n  outline: \"none\",\r\n  background: \"transparent\",\r\n  color: theme.color.foreground,\r\n  fontFamily: theme.fontFamily.sans,\r\n  fontSize: theme.fontSize.sm,\r\n  padding: `0 ${theme.space[1]}`,\r\n  height: \"1.5rem\",\r\n  \"&::placeholder\": { color: theme.color.mutedForeground },\r\n});\r\n\r\nconst labelClass = css({\r\n  display: \"block\",\r\n  marginBottom: theme.space[1.5],\r\n  fontSize: theme.fontSize.sm,\r\n  fontWeight: theme.fontWeight.medium,\r\n  color: theme.color.foreground,\r\n});\r\n\r\nconst helpClass = css({\r\n  marginTop: theme.space[1.5],\r\n  fontSize: theme.fontSize.xs,\r\n  color: theme.color.mutedForeground,\r\n});\r\n\r\nexport interface TagInputProps {\r\n  value?: string[];\r\n  defaultValue?: string[];\r\n  onValueChange?: (value: string[]) => void;\r\n  placeholder?: string;\r\n  label?: ReactNode;\r\n  description?: ReactNode;\r\n  disabled?: boolean;\r\n  /** Reject additions past this count. */\r\n  maxTags?: number;\r\n  /** Keys that commit the current draft. Defaults to Enter and comma. */\r\n  commitKeys?: string[];\r\n  /** Reject duplicates (case-insensitive). */\r\n  allowDuplicates?: boolean;\r\n  className?: string;\r\n}\r\n\r\n/**\r\n * Multi-value entry — labels, recipients, stop sequences, allowed tools.\r\n *\r\n * Backspace on an empty field removes the last tag, which is the behavior\r\n * people expect from every tag field they've used. Each tag's remove button is\r\n * individually focusable, so the control is fully keyboard-operable rather than\r\n * relying on Backspace alone.\r\n */\r\nexport function TagInput({\r\n  value,\r\n  defaultValue = [],\r\n  onValueChange,\r\n  placeholder = \"Add and press Enter…\",\r\n  label,\r\n  description,\r\n  disabled,\r\n  maxTags,\r\n  commitKeys = [\"Enter\", \",\"],\r\n  allowDuplicates = false,\r\n  className,\r\n}: TagInputProps) {\r\n  const [tags, setTags] = useControllableState<string[]>({\r\n    value,\r\n    defaultValue,\r\n    onChange: onValueChange,\r\n  });\r\n  const [draft, setDraft] = useState(\"\");\r\n  const inputRef = useRef<HTMLInputElement | null>(null);\r\n  const inputId = useId();\r\n\r\n  const atLimit = maxTags != null && tags.length >= maxTags;\r\n\r\n  function commit() {\r\n    const next = draft.trim();\r\n    if (!next || atLimit) return;\r\n    if (!allowDuplicates && tags.some((t) => t.toLowerCase() === next.toLowerCase())) {\r\n      setDraft(\"\");\r\n      return;\r\n    }\r\n    setTags([...tags, next]);\r\n    setDraft(\"\");\r\n  }\r\n\r\n  function removeAt(index: number) {\r\n    setTags(tags.filter((_, i) => i !== index));\r\n  }\r\n\r\n  function handleKeyDown(event: KeyboardEvent<HTMLInputElement>) {\r\n    if (commitKeys.includes(event.key)) {\r\n      event.preventDefault();\r\n      commit();\r\n    } else if (event.key === \"Backspace\" && draft === \"\" && tags.length > 0) {\r\n      event.preventDefault();\r\n      removeAt(tags.length - 1);\r\n    }\r\n  }\r\n\r\n  return (\r\n    <div className={className}>\r\n      {label ? (\r\n        <label className={labelClass} htmlFor={inputId}>\r\n          {label}\r\n        </label>\r\n      ) : null}\r\n      <div\r\n        className={shellClass}\r\n        data-disabled={disabled || undefined}\r\n        onClick={() => inputRef.current?.focus()}\r\n      >\r\n        {tags.map((tag, index) => (\r\n          <span key={`${tag}-${index}`} className={tagClass}>\r\n            <span className={tagLabelClass}>{tag}</span>\r\n            <button\r\n              type=\"button\"\r\n              className={removeClass}\r\n              aria-label={`Remove ${tag}`}\r\n              disabled={disabled}\r\n              onClick={(event) => {\r\n                event.stopPropagation();\r\n                removeAt(index);\r\n              }}\r\n            >\r\n              <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" aria-hidden=\"true\">\r\n                <path d=\"M2 2L8 8M8 2L2 8\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" />\r\n              </svg>\r\n            </button>\r\n          </span>\r\n        ))}\r\n        <input\r\n          ref={inputRef}\r\n          id={inputId}\r\n          className={inputClass}\r\n          value={draft}\r\n          placeholder={atLimit ? undefined : placeholder}\r\n          disabled={disabled || atLimit}\r\n          onChange={(event) => setDraft(event.target.value)}\r\n          onKeyDown={handleKeyDown}\r\n          onBlur={commit}\r\n        />\r\n      </div>\r\n      {description ? <p className={helpClass}>{description}</p> : null}\r\n      {maxTags != null ? (\r\n        <p className={helpClass}>\r\n          {tags.length}/{maxTags}\r\n        </p>\r\n      ) : null}\r\n    </div>\r\n  );\r\n}\r\n",
      "type": "registry:component"
    }
  ]
}