{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tag-select",
  "version": "2.1.0",
  "category": "selection",
  "meta": {
    "preview": "https://ui.manifest.build/previews/tag-select.png",
    "version": "2.1.0",
    "changelog": {
      "1.0.0": "Initial release with color variants and multi-select",
      "1.0.2": "Added comprehensive JSDoc documentation",
      "1.1.0": "Moved to selection category for better organization",
      "1.1.1": "Updated Props interface with decorative header and sub-parameter JSDoc documentation",
      "1.1.2": "Fixed defensive property access to handle empty objects and null values",
      "1.1.3": "Additional defensive property access improvements",
      "2.0.0": "BREAKING: Removed onSelectTags action. Tag toggling is now internal.",
      "2.0.1": "Removed default content data - component only renders explicitly provided data",
      "2.0.2": "Fixed stale controlled state sync for selectedTagIds",
      "2.0.3": "Fixed infinite re-render loop when control prop is not provided",
      "2.1.0": "Added demo data defaults - component renders demo content when no data prop is provided"
    }
  },
  "changelog": {
    "1.0.0": "Initial release with color variants and multi-select",
    "1.0.2": "Added comprehensive JSDoc documentation",
    "1.1.0": "Moved to selection category for better organization",
    "1.1.1": "Updated Props interface with decorative header and sub-parameter JSDoc documentation",
    "1.1.2": "Fixed defensive property access to handle empty objects and null values",
    "1.1.3": "Additional defensive property access improvements",
    "2.0.0": "BREAKING: Removed onSelectTags action. Tag toggling is now internal.",
    "2.0.1": "Removed default content data - component only renders explicitly provided data",
    "2.0.2": "Fixed stale controlled state sync for selectedTagIds",
    "2.0.3": "Fixed infinite re-render loop when control prop is not provided",
    "2.1.0": "Added demo data defaults - component renders demo content when no data prop is provided"
  },
  "title": "Tag Select",
  "author": "MNFST, Inc",
  "description": "Colored tag selector with single or multiple selection and color variants.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "button"
  ],
  "files": [
    {
      "path": "registry/selection/tag-select.tsx",
      "content": "'use client'\n\nimport { Button } from '@/components/ui/button'\nimport { cn } from '@/lib/utils'\nimport { Check, X } from 'lucide-react'\nimport { useEffect, useState } from 'react'\nimport { demoTags } from './demo/selection'\n\n/**\n * Represents an individual tag option.\n * @interface Tag\n * @property {string} id - Unique identifier for the tag\n * @property {string} label - Display text for the tag\n * @property {\"default\" | \"blue\" | \"green\" | \"red\" | \"yellow\" | \"purple\"} [color] - Optional color theme\n */\nexport interface Tag {\n  id?: string\n  label?: string\n  color?: 'default' | 'blue' | 'green' | 'red' | 'yellow' | 'purple'\n}\n\n/**\n * ═══════════════════════════════════════════════════════════════════════════\n * TagSelectProps\n * ═══════════════════════════════════════════════════════════════════════════\n *\n * Props for the TagSelect component, which provides tag-based filtering with\n * single or multiple selection modes and validation support.\n */\nexport interface TagSelectProps {\n  data?: {\n    /** Array of tags to display for selection. */\n    tags?: Tag[]\n  }\n  actions?: {\n    /** Called when the user clicks the validate button with the selected tag IDs. */\n    onValidate?: (tagIds: string[]) => void\n  }\n  appearance?: {\n    /**\n     * Selection mode: single allows one tag, multiple allows many.\n     * @default \"multiple\"\n     */\n    mode?: 'single' | 'multiple'\n    /**\n     * Whether to show the clear selection button.\n     * @default true\n     */\n    showClear?: boolean\n    /**\n     * Whether to show the validate button.\n     * @default true\n     */\n    showValidate?: boolean\n    /**\n     * Custom label for the validate button.\n     * @default \"Validate selection\"\n     */\n    validateLabel?: string\n  }\n  control?: {\n    /** Array of pre-selected tag IDs for controlled mode. */\n    selectedTagIds?: string[]\n  }\n}\n\n\n// ChatGPT-compliant: all tags use neutral system colors\nconst tagClasses = {\n  selected: 'bg-foreground text-background border-foreground',\n  unselected: 'bg-background text-foreground border-border hover:bg-muted'\n}\n\n/**\n * A tag selection component with single or multiple selection modes.\n * Features clear selection and validate buttons for user actions.\n *\n * Features:\n * - Single or multiple selection modes\n * - Check icon on selected tags\n * - Clear selection button with count\n * - Validate button with selection count\n * - Neutral system color scheme\n *\n * @component\n * @example\n * ```tsx\n * <TagSelect\n *   data={{\n *     tags: [\n *       { id: \"1\", label: \"Electronics\" },\n *       { id: \"2\", label: \"Audio\" },\n *       { id: \"3\", label: \"Wireless\" }\n *     ]\n *   }}\n *   actions={{\n *     onSelectTags: (ids) => console.log(\"Selected:\", ids),\n *     onValidate: (ids) => console.log(\"Validated:\", ids)\n *   }}\n *   appearance={{\n *     mode: \"multiple\",\n *     showClear: true,\n *     showValidate: true,\n *     validateLabel: \"Apply filters\"\n *   }}\n * />\n * ```\n */\nexport function TagSelect({ data, actions, appearance, control }: TagSelectProps) {\n  const resolved: NonNullable<TagSelectProps['data']> = data ?? { tags: demoTags }\n  const tags = resolved.tags ?? []\n  const onValidate = actions?.onValidate\n  const mode = appearance?.mode ?? 'multiple'\n  const showClear = appearance?.showClear ?? true\n  const showValidate = appearance?.showValidate ?? true\n  const validateLabel = appearance?.validateLabel ?? 'Validate selection'\n  const selectedTagIds = control?.selectedTagIds\n  const [selected, setSelected] = useState<string[]>(selectedTagIds ?? [])\n\n  // Sync internal state when controlled prop changes\n  useEffect(() => {\n    setSelected(selectedTagIds ?? [])\n  }, [selectedTagIds])\n\n  const handleToggle = (tagId: string) => {\n    let newSelected: string[]\n\n    if (mode === 'single') {\n      newSelected = selected.includes(tagId) ? [] : [tagId]\n    } else {\n      newSelected = selected.includes(tagId)\n        ? selected.filter((id) => id !== tagId)\n        : [...selected, tagId]\n    }\n\n    setSelected(newSelected)\n  }\n\n  const handleClear = () => {\n    setSelected([])\n  }\n\n  const handleValidate = () => {\n    onValidate?.(selected)\n  }\n\n  const isSelected = (tagId: string) => selected.includes(tagId)\n\n  return (\n    <div className=\"w-full space-y-2 bg-card rounded-lg p-4\">\n      <div className=\"flex flex-wrap gap-2\">\n        {tags.map((tag, index) => {\n          const tagId = tag.id ?? `tag-${index}`\n          return (\n            <button\n              key={tagId}\n              onClick={() => handleToggle(tagId)}\n              className={cn(\n                'inline-flex items-center gap-1 sm:gap-1.5 rounded-full border px-2.5 sm:px-3 py-0.5 sm:py-1 text-xs sm:text-sm transition-colors cursor-pointer',\n                isSelected(tagId) ? tagClasses.selected : tagClasses.unselected\n              )}\n            >\n              {isSelected(tagId) && (\n                <Check className=\"h-3 w-3 sm:h-3.5 sm:w-3.5\" />\n              )}\n              {tag.label && <span>{tag.label}</span>}\n            </button>\n          )\n        })}\n      </div>\n\n      <div className=\"flex items-center justify-between\">\n        {showClear && selected.length > 0 ? (\n          <button\n            onClick={handleClear}\n            className=\"inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer\"\n          >\n            <X className=\"h-3 w-3\" />\n            Clear selection ({selected.length})\n          </button>\n        ) : (\n          <div />\n        )}\n\n        {showValidate && (\n          <Button\n            onClick={handleValidate}\n            disabled={selected.length === 0}\n            size=\"sm\"\n          >\n            {validateLabel}\n            {selected.length > 0 && ` (${selected.length})`}\n          </Button>\n        )}\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "components/ui/tag-select.tsx"
    },
    {
      "path": "registry/selection/demo/selection.ts",
      "content": "// Demo data for Selection category components\n// This file contains sample data used for component previews and documentation\n\nimport type { Option } from '../types'\n\n// Default options for OptionList\nexport const demoOptions: Option[] = [\n  { label: 'Standard shipping', description: '3-5 business days' },\n  { label: 'Express shipping', description: '1-2 business days' },\n  { label: 'Store pickup', description: 'Available in 2h' },\n]\n\n// Quick reply options\nexport const demoQuickReplies = [\n  { label: 'Yes, please' },\n  { label: 'No, thanks' },\n  { label: 'Tell me more' },\n]\n\n// Tag select options\nexport const demoTags: { id: string; label: string; color: 'red' | 'yellow' | 'green' }[] = [\n  { id: '1', label: 'Important', color: 'red' },\n  { id: '2', label: 'In Progress', color: 'yellow' },\n  { id: '3', label: 'Done', color: 'green' },\n]\n",
      "type": "registry:lib",
      "target": "components/ui/demo/selection.ts"
    }
  ],
  "categories": [
    "selection"
  ],
  "type": "registry:block"
}