{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "action-search-bar",
  "type": "registry:component",
  "title": "Action Search Bar",
  "description": "Command-style search with a debounced filtered action list (KokonutUI, MIT). Category icons use the contract chart palette.",
  "dependencies": [
    "framer-motion",
    "lucide-react"
  ],
  "registryDependencies": [
    "https://design.subconscious.ai/r/use-debounce.json",
    "input"
  ],
  "files": [
    {
      "path": "registry/components/action-search-bar.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\n/**\n * @author: @kokonutui\n * @description: A modern search bar component with action buttons and suggestions\n * @version: 1.0.0\n * @date: 2025-06-26\n * @license: MIT\n * @website: https://kokonutui.com\n * @github: https://github.com/kokonut-labs/kokonutui\n *\n * Bridged to the Subconscious design contract: color → theme tokens, redundant\n * dark: variants dropped. Per-action icon hues use the contract chart palette\n * (--chart-1..5) so categorical color theme-tracks.\n */\n\nimport {\n  AudioLines,\n  BarChart2,\n  LayoutGrid,\n  PlaneTakeoff,\n  Search,\n  Send,\n  Video,\n} from \"lucide-react\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { Input } from \"@/components/ui/input\";\nimport useDebounce from \"@/hooks/use-debounce\";\n\ninterface Action {\n  id: string;\n  label: string;\n  icon: React.ReactNode;\n  description?: string;\n  short?: string;\n  end?: string;\n}\n\ninterface SearchResult {\n  actions: Action[];\n}\n\nconst ANIMATION_VARIANTS = {\n  container: {\n    hidden: { opacity: 0, height: 0 },\n    show: {\n      opacity: 1,\n      height: \"auto\",\n      transition: {\n        height: { duration: 0.4 },\n        staggerChildren: 0.1,\n      },\n    },\n    exit: {\n      opacity: 0,\n      height: 0,\n      transition: {\n        height: { duration: 0.3 },\n        opacity: { duration: 0.2 },\n      },\n    },\n  },\n  item: {\n    hidden: { opacity: 0, y: 20 },\n    show: {\n      opacity: 1,\n      y: 0,\n      transition: { duration: 0.3 },\n    },\n    exit: {\n      opacity: 0,\n      y: -10,\n      transition: { duration: 0.2 },\n    },\n  },\n} as const;\n\nconst allActionsSample = [\n  {\n    id: \"1\",\n    label: \"Book tickets\",\n    icon: <PlaneTakeoff className=\"h-4 w-4 text-chart-1\" />,\n    description: \"Operator\",\n    short: \"⌘K\",\n    end: \"Agent\",\n  },\n  {\n    id: \"2\",\n    label: \"Summarize\",\n    icon: <BarChart2 className=\"h-4 w-4 text-chart-3\" />,\n    description: \"gpt-5\",\n    short: \"⌘cmd+p\",\n    end: \"Command\",\n  },\n  {\n    id: \"3\",\n    label: \"Screen Studio\",\n    icon: <Video className=\"h-4 w-4 text-chart-4\" />,\n    description: \"Claude 4.1\",\n    short: \"\",\n    end: \"Application\",\n  },\n  {\n    id: \"4\",\n    label: \"Talk to Jarvis\",\n    icon: <AudioLines className=\"h-4 w-4 text-chart-2\" />,\n    description: \"gpt-5 voice\",\n    short: \"\",\n    end: \"Active\",\n  },\n  {\n    id: \"5\",\n    label: \"Kokonut UI - Pro\",\n    icon: <LayoutGrid className=\"h-4 w-4 text-chart-5\" />,\n    description: \"Components\",\n    short: \"\",\n    end: \"Link\",\n  },\n];\n\nfunction ActionSearchBar({\n  actions = allActionsSample,\n  defaultOpen = false,\n}: {\n  actions?: Action[];\n  defaultOpen?: boolean;\n}) {\n  const [query, setQuery] = useState(\"\");\n  const [result, setResult] = useState<SearchResult | null>(null);\n  const [isFocused, setIsFocused] = useState(defaultOpen);\n  const [isTyping, setIsTyping] = useState(false);\n  const [selectedAction, setSelectedAction] = useState<Action | null>(null);\n  const [activeIndex, setActiveIndex] = useState(-1);\n  const debouncedQuery = useDebounce(query, 200);\n\n  const filteredActions = useMemo(() => {\n    if (!debouncedQuery) return actions;\n\n    const normalizedQuery = debouncedQuery.toLowerCase().trim();\n    return actions.filter((action) => {\n      const searchableText =\n        `${action.label} ${action.description || \"\"}`.toLowerCase();\n      return searchableText.includes(normalizedQuery);\n    });\n  }, [debouncedQuery, actions]);\n\n  useEffect(() => {\n    if (!isFocused) {\n      setResult(null);\n      setActiveIndex(-1);\n      return;\n    }\n\n    setResult({ actions: filteredActions });\n    setActiveIndex(-1);\n  }, [filteredActions, isFocused]);\n\n  const handleInputChange = useCallback(\n    (e: React.ChangeEvent<HTMLInputElement>) => {\n      setQuery(e.target.value);\n      setIsTyping(true);\n      setActiveIndex(-1);\n    },\n    []\n  );\n\n  const handleKeyDown = useCallback(\n    (e: React.KeyboardEvent<HTMLInputElement>) => {\n      if (!result?.actions.length) return;\n\n      switch (e.key) {\n        case \"ArrowDown\":\n          e.preventDefault();\n          setActiveIndex((prev) =>\n            prev < result.actions.length - 1 ? prev + 1 : 0\n          );\n          break;\n        case \"ArrowUp\":\n          e.preventDefault();\n          setActiveIndex((prev) =>\n            prev > 0 ? prev - 1 : result.actions.length - 1\n          );\n          break;\n        case \"Enter\":\n          e.preventDefault();\n          if (activeIndex >= 0 && result.actions[activeIndex]) {\n            setSelectedAction(result.actions[activeIndex]);\n          }\n          break;\n        case \"Escape\":\n          setIsFocused(false);\n          setActiveIndex(-1);\n          break;\n      }\n    },\n    [result?.actions, activeIndex]\n  );\n\n  const handleActionClick = useCallback((action: Action) => {\n    setSelectedAction(action);\n  }, []);\n\n  const handleFocus = useCallback(() => {\n    setSelectedAction(null);\n    setIsFocused(true);\n    setActiveIndex(-1);\n  }, []);\n\n  const handleBlur = useCallback(() => {\n    setTimeout(() => {\n      setIsFocused(false);\n      setActiveIndex(-1);\n    }, 200);\n  }, []);\n\n  return (\n    <div className=\"w-full\">\n      <div\n        className=\"relative flex flex-col items-center justify-start\"\n        style={{ minHeight: \"300px\" }}\n      >\n        <div\n          className=\"sticky top-0 z-10 w-full max-w-sm pt-4 pb-1\"\n          style={{ background: \"var(--backgroundColor)\" }}\n        >\n          <label\n            className=\"mb-1 block font-medium text-xs\"\n            htmlFor=\"search\"\n            style={{\n              color: \"color-mix(in srgb, var(--color) 60%, transparent)\",\n            }}\n          >\n            Search Commands\n          </label>\n          <div className=\"relative\">\n            <Input\n              aria-activedescendant={\n                activeIndex >= 0\n                  ? `action-${result?.actions[activeIndex]?.id}`\n                  : undefined\n              }\n              aria-autocomplete=\"list\"\n              aria-expanded={isFocused && !!result}\n              autoComplete=\"off\"\n              className=\"h-9 rounded-lg py-1.5 pr-9 pl-3 text-sm focus-visible:ring-offset-0\"\n              id=\"search\"\n              onBlur={handleBlur}\n              onChange={handleInputChange}\n              onFocus={handleFocus}\n              onKeyDown={handleKeyDown}\n              placeholder=\"What's up?\"\n              role=\"combobox\"\n              type=\"text\"\n              value={query}\n            />\n            <div className=\"absolute top-1/2 right-3 h-4 w-4 -translate-y-1/2\">\n              <AnimatePresence mode=\"popLayout\">\n                {query.length > 0 ? (\n                  <motion.div\n                    animate={{ y: 0, opacity: 1 }}\n                    exit={{ y: 20, opacity: 0 }}\n                    initial={{ y: -20, opacity: 0 }}\n                    key=\"send\"\n                    transition={{ duration: 0.2 }}\n                  >\n                    <Send\n                      className=\"h-4 w-4\"\n                      style={{\n                        color:\n                          \"color-mix(in srgb, var(--color) 60%, transparent)\",\n                      }}\n                    />\n                  </motion.div>\n                ) : (\n                  <motion.div\n                    animate={{ y: 0, opacity: 1 }}\n                    exit={{ y: 20, opacity: 0 }}\n                    initial={{ y: -20, opacity: 0 }}\n                    key=\"search\"\n                    transition={{ duration: 0.2 }}\n                  >\n                    <Search\n                      className=\"h-4 w-4\"\n                      style={{\n                        color:\n                          \"color-mix(in srgb, var(--color) 60%, transparent)\",\n                      }}\n                    />\n                  </motion.div>\n                )}\n              </AnimatePresence>\n            </div>\n          </div>\n        </div>\n\n        <div className=\"w-full max-w-sm\">\n          <AnimatePresence>\n            {isFocused && result && !selectedAction && (\n              <motion.div\n                animate=\"show\"\n                aria-label=\"Search results\"\n                className=\"mt-1 w-full overflow-hidden rounded-md\"\n                exit=\"exit\"\n                initial=\"hidden\"\n                role=\"listbox\"\n                style={{\n                  background: \"var(--backgroundColor)\",\n                  border:\n                    \"1px solid color-mix(in srgb, var(--color) 15%, transparent)\",\n                }}\n                variants={ANIMATION_VARIANTS.container}\n              >\n                <motion.ul role=\"none\">\n                  {result.actions.map((action) => (\n                    <motion.li\n                      aria-selected={\n                        activeIndex === result.actions.indexOf(action)\n                      }\n                      className=\"flex cursor-pointer items-center justify-between rounded-md px-3 py-2\"\n                      id={`action-${action.id}`}\n                      key={action.id}\n                      layout\n                      onClick={() => handleActionClick(action)}\n                      role=\"option\"\n                      style={{\n                        background:\n                          activeIndex === result.actions.indexOf(action)\n                            ? \"color-mix(in srgb, var(--color) 8%, transparent)\"\n                            : undefined,\n                      }}\n                      variants={ANIMATION_VARIANTS.item}\n                    >\n                      <div className=\"flex items-center justify-between gap-2\">\n                        <div className=\"flex items-center gap-2\">\n                          <span\n                            aria-hidden=\"true\"\n                            style={{\n                              color:\n                                \"color-mix(in srgb, var(--color) 60%, transparent)\",\n                            }}\n                          >\n                            {action.icon}\n                          </span>\n                          <span\n                            className=\"font-medium text-sm\"\n                            style={{ color: \"var(--color)\" }}\n                          >\n                            {action.label}\n                          </span>\n                          {action.description && (\n                            <span\n                              className=\"text-xs\"\n                              style={{\n                                color:\n                                  \"color-mix(in srgb, var(--color) 60%, transparent)\",\n                              }}\n                            >\n                              {action.description}\n                            </span>\n                          )}\n                        </div>\n                      </div>\n                      <div className=\"flex items-center gap-2\">\n                        {action.short && (\n                          <span\n                            aria-label={`Keyboard shortcut: ${action.short}`}\n                            className=\"text-xs\"\n                            style={{\n                              color:\n                                \"color-mix(in srgb, var(--color) 60%, transparent)\",\n                            }}\n                          >\n                            {action.short}\n                          </span>\n                        )}\n                        {action.end && (\n                          <span\n                            className=\"text-right text-xs\"\n                            style={{\n                              color:\n                                \"color-mix(in srgb, var(--color) 60%, transparent)\",\n                            }}\n                          >\n                            {action.end}\n                          </span>\n                        )}\n                      </div>\n                    </motion.li>\n                  ))}\n                </motion.ul>\n                <div\n                  className=\"mt-2 border-t px-3 py-2\"\n                  style={{\n                    borderColor:\n                      \"color-mix(in srgb, var(--color) 15%, transparent)\",\n                  }}\n                >\n                  <div\n                    className=\"flex items-center justify-between text-xs\"\n                    style={{\n                      color:\n                        \"color-mix(in srgb, var(--color) 60%, transparent)\",\n                    }}\n                  >\n                    <span>Press ⌘K to open commands</span>\n                    <span>ESC to cancel</span>\n                  </div>\n                </div>\n              </motion.div>\n            )}\n          </AnimatePresence>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport default ActionSearchBar;\n"
    }
  ]
}
