{"version":3,"sources":["../components/ui/tag-picker.tsx"],"sourcesContent":["\"use client\";\nimport * as React from 'react';\nimport { IconPlus, IconX } from '@tabler/icons-react';\nimport { useMemo, useState } from 'react';\n\nimport { cn } from '../../utils/ui';\nimport { Badge } from './badge';\n\nfunction normalizeTagName(name: string) {\n  return name.replace(/\\s+/g, ' ').trim();\n}\n\nexport interface TagPickerProps {\n  id?: string;\n  value: string[];\n  onChange: (values: string[]) => void;\n  suggestions?: string[];\n  placeholder?: string;\n  disabled?: boolean;\n  className?: string;\n}\n\nexport function TagPicker({\n  id,\n  value,\n  onChange,\n  suggestions = [],\n  placeholder = 'Add tags...',\n  disabled = false,\n  className,\n}: TagPickerProps) {\n  const [inputValue, setInputValue] = useState('');\n  const [isFocused, setIsFocused] = useState(false);\n\n  const selectedKeys = useMemo(() => new Set(value.map((item) => item.toLocaleLowerCase())), [value]);\n  const normalizedInput = normalizeTagName(inputValue);\n  const normalizedInputKey = normalizedInput.toLocaleLowerCase();\n\n  const filteredSuggestions = useMemo(() => {\n    return suggestions\n      .filter((suggestion) => !selectedKeys.has(suggestion.toLocaleLowerCase()))\n      .filter((suggestion) => {\n        if (!normalizedInput) {\n          return true;\n        }\n        return suggestion.toLocaleLowerCase().includes(normalizedInputKey);\n      })\n      .slice(0, 8);\n  }, [normalizedInput, normalizedInputKey, selectedKeys, suggestions]);\n\n  const canCreate =\n    !!normalizedInput &&\n    !selectedKeys.has(normalizedInputKey) &&\n    !suggestions.some((suggestion) => suggestion.toLocaleLowerCase() === normalizedInputKey);\n\n  const addTag = (rawName: string) => {\n    const nextName = normalizeTagName(rawName);\n    if (!nextName) {\n      setInputValue('');\n      return;\n    }\n\n    const nextKey = nextName.toLocaleLowerCase();\n    const matchingSuggestion = suggestions.find((suggestion) => suggestion.toLocaleLowerCase() === nextKey);\n    const finalName = matchingSuggestion ?? nextName;\n\n    if (selectedKeys.has(finalName.toLocaleLowerCase())) {\n      setInputValue('');\n      return;\n    }\n\n    onChange([...value, finalName]);\n    setInputValue('');\n  };\n\n  const removeTag = (tagName: string) => {\n    onChange(value.filter((item) => item.toLocaleLowerCase() !== tagName.toLocaleLowerCase()));\n  };\n\n  const showMenu = !disabled && isFocused && (filteredSuggestions.length > 0 || canCreate);\n\n  return (\n    <div className={cn('space-y-2', className)}>\n      <div className=\"relative\">\n        <div\n          className={cn(\n            'flex min-h-8 flex-wrap items-center gap-1 rounded-lg border bg-background px-2 py-1 text-sm',\n            'focus-within:ring-1 focus-within:ring-ring',\n            disabled && 'cursor-not-allowed opacity-60',\n          )}\n        >\n          {value.map((tagName) => (\n            <Badge key={tagName} variant=\"secondary\" className=\"flex items-center gap-1 rounded-md px-1.5\">\n              <span className=\"text-xs\">{tagName}</span>\n              {!disabled && (\n                <button\n                  type=\"button\"\n                  className=\"inline-flex cursor-pointer items-center justify-center rounded-md p-1 transition-colors hover:bg-red-50 hover:text-red-700 focus-visible:bg-red-100 focus-visible:outline-none\"\n                  onMouseDown={(event) => event.preventDefault()}\n                  onClick={() => removeTag(tagName)}\n                  aria-label={`Remove ${tagName}`}\n                >\n                  <IconX className=\"h-3.5 w-3.5\" />\n                </button>\n              )}\n            </Badge>\n          ))}\n\n          <input\n            id={id}\n            value={inputValue}\n            disabled={disabled}\n            placeholder={value.length === 0 ? placeholder : ''}\n            className=\"min-w-24 flex-1 border-0 bg-transparent p-0 pl-1 text-sm outline-none placeholder:text-muted-foreground\"\n            onFocus={() => setIsFocused(true)}\n            onBlur={() => {\n              window.setTimeout(() => setIsFocused(false), 100);\n            }}\n            onChange={(event) => setInputValue(event.target.value)}\n            onKeyDown={(event) => {\n              if ((event.key === 'Enter' || event.key === ',') && normalizedInput) {\n                event.preventDefault();\n                addTag(normalizedInput);\n              }\n\n              if (event.key === 'Backspace' && !inputValue && value.length > 0) {\n                event.preventDefault();\n                removeTag(value[value.length - 1]);\n              }\n            }}\n          />\n        </div>\n\n        {showMenu && (\n          <div className=\"absolute z-50 mt-1 w-full rounded-md border bg-background p-1 shadow-md\">\n            {filteredSuggestions.map((suggestion) => (\n              <button\n                key={suggestion}\n                type=\"button\"\n                className=\"flex w-full items-center justify-between rounded-sm px-2 py-1.5 text-left text-sm hover:bg-muted\"\n                onMouseDown={(event) => event.preventDefault()}\n                onClick={() => addTag(suggestion)}\n              >\n                <span>{suggestion}</span>\n                <span className=\"text-xs text-muted-foreground\">Existing</span>\n              </button>\n            ))}\n\n            {canCreate && (\n              <button\n                type=\"button\"\n                className=\"flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm hover:bg-muted\"\n                onMouseDown={(event) => event.preventDefault()}\n                onClick={() => addTag(normalizedInput)}\n              >\n                <IconPlus className=\"h-3.5 w-3.5\" />\n                <span>Create &quot;{normalizedInput}&quot;</span>\n              </button>\n            )}\n          </div>\n        )}\n      </div>\n    </div>\n  );\n}\n"],"mappings":"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","names":[]}