{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "combobox",
  "title": "UI Combobox",
  "description": "A combobox whose opened/closed state is a pure function of the timeline; the panel fade/scale/lift are keyframed presets like select. The trigger reuses the Input presets (typed query + caret reveal via an injected inputStyle), the list is filtered by a pure case-insensitive substring on the visible query prefix, and rows reuse the SelectItem row.",
  "dependencies": ["remotion"],
  "registryDependencies": [
    "@remocn/remocn-ui",
    "@remocn/input",
    "@remocn/select-item"
  ],
  "files": [
    {
      "path": "registry/remocn-ui/combobox/index.tsx",
      "content": "\"use client\";\n\nimport {\n  type InputStyle,\n  type InputStyleContext,\n  inputStyle,\n  inputStyleContext,\n} from \"@/components/remocn/input\";\nimport {\n  SelectItemRow,\n  type SelectItemState,\n  type SelectItemStyle,\n  type SelectItemStyleContext,\n  selectItemStyle,\n  selectItemStyleContext,\n} from \"@/components/remocn/select-item\";\nimport {\n  type RemocnTheme,\n  revealedText,\n  useRemocnTheme,\n} from \"@/lib/remocn-ui\";\n\nexport type ComboboxState = \"opened\" | \"closed\";\n\nexport interface ComboboxProps {\n  state?: ComboboxState;\n  style?: ComboboxStyle;\n  query?: string;\n  revealCount?: number;\n  placeholder?: string;\n  items?: string[];\n  selectedIndex?: number;\n  highlightedIndex?: number;\n  pressedIndex?: number;\n  itemStyles?: (SelectItemStyle | undefined)[];\n  inputStyle?: InputStyle;\n  theme?: Partial<RemocnTheme>;\n  className?: string;\n}\n\nconst WIDTH = 280;\n\nexport function filterComboboxItems(\n  items: string[],\n  query: string,\n  revealCount?: number,\n): string[] {\n  const visible = (\n    revealCount === undefined ? query : revealedText(query, revealCount)\n  )\n    .trim()\n    .toLowerCase();\n  if (visible === \"\") return items;\n  return items.filter((item) => item.toLowerCase().includes(visible));\n}\n\nexport interface ComboboxStyle {\n  panelOpacity: number;\n  panelScale: number;\n  panelTranslateY: number;\n}\n\nexport interface ComboboxStyleContext {\n  triggerCtx: InputStyleContext;\n  panelBg: string;\n  panelBorder: string;\n  mutedFg: string;\n  radius: number;\n  itemCtx: SelectItemStyleContext;\n}\n\nexport function comboboxStyleContext(theme: RemocnTheme): ComboboxStyleContext {\n  return {\n    triggerCtx: inputStyleContext(theme),\n    panelBg: theme.popover,\n    panelBorder: theme.border,\n    mutedFg: theme.mutedForeground,\n    radius: theme.radius,\n    itemCtx: selectItemStyleContext(theme),\n  };\n}\n\nexport function comboboxStyle(\n  state: ComboboxState,\n  _ctx: ComboboxStyleContext,\n): ComboboxStyle {\n  switch (state) {\n    case \"opened\":\n      return { panelOpacity: 1, panelScale: 1, panelTranslateY: 0 };\n    default:\n      return { panelOpacity: 0, panelScale: 0.96, panelTranslateY: -4 };\n  }\n}\n\nfunction rowState(\n  i: number,\n  selectedIndex: number,\n  highlightedIndex: number,\n  pressedIndex: number,\n): SelectItemState {\n  if (i === pressedIndex) return \"press\";\n  if (i === selectedIndex) return \"selected\";\n  if (i === highlightedIndex) return \"hover\";\n  return \"idle\";\n}\n\nexport function Combobox({\n  state = \"closed\",\n  style,\n  query = \"\",\n  revealCount,\n  placeholder = \"Select a fruit…\",\n  items = [\"Apple\", \"Banana\", \"Orange\", \"Grape\"],\n  selectedIndex = -1,\n  highlightedIndex = -1,\n  pressedIndex = -1,\n  itemStyles,\n  inputStyle: inputStyleOverride,\n  theme: themeOverride,\n  className,\n}: ComboboxProps) {\n  const theme = useRemocnTheme(themeOverride, \"light\");\n  const ctx = comboboxStyleContext(theme);\n  const v = style ?? comboboxStyle(state, ctx);\n\n  const visibleQuery =\n    revealCount === undefined ? query : revealedText(query, revealCount);\n  const filtered = filterComboboxItems(items, query, revealCount);\n\n  const trigger: InputStyle =\n    inputStyleOverride ??\n    inputStyle(visibleQuery ? \"typing\" : \"idle\", ctx.triggerCtx);\n\n  const valueWidth = visibleQuery.length * 8;\n\n  return (\n    <div\n      className={className}\n      style={{\n        position: \"absolute\",\n        inset: 0,\n        display: \"flex\",\n        alignItems: \"center\",\n        justifyContent: \"center\",\n        background: \"transparent\",\n        fontFamily:\n          \"var(--font-geist-sans), -apple-system, BlinkMacSystemFont, sans-serif\",\n      }}\n    >\n      <div style={{ position: \"relative\", width: WIDTH }}>\n        {}\n        <div\n          style={{\n            position: \"relative\",\n            display: \"flex\",\n            alignItems: \"center\",\n            width: WIDTH,\n            boxSizing: \"border-box\",\n            height: 40,\n            padding: \"0 14px\",\n            fontSize: 15,\n            letterSpacing: \"-0.01em\",\n            background: trigger.background,\n            border: `1px solid ${trigger.borderColor}`,\n            borderRadius: theme.radius,\n            boxShadow: `0 0 0 ${trigger.ringWidth}px ${trigger.ringColor}`,\n          }}\n        >\n          {}\n          <span\n            style={{\n              position: \"absolute\",\n              left: 14,\n              color: ctx.triggerCtx.mutedForeground,\n              opacity: trigger.valueReveal > 0 ? 0 : trigger.placeholderOpacity,\n              pointerEvents: \"none\",\n              whiteSpace: \"nowrap\",\n            }}\n          >\n            {placeholder}\n          </span>\n          {}\n          <div style={{ display: \"flex\", alignItems: \"center\", minWidth: 0 }}>\n            <span\n              style={{\n                display: \"inline-block\",\n                width: valueWidth * trigger.valueReveal,\n                overflow: \"hidden\",\n                whiteSpace: \"nowrap\",\n                color: ctx.triggerCtx.foreground,\n              }}\n            >\n              {visibleQuery}\n            </span>\n            <span\n              style={{\n                flexShrink: 0,\n                width: 2,\n                height: 17,\n                borderRadius: 1,\n                background: ctx.triggerCtx.foreground,\n                opacity: trigger.caretOpacity,\n              }}\n            />\n          </div>\n        </div>\n        {}\n        <div\n          style={{\n            position: \"absolute\",\n            top: \"calc(100% + 6px)\",\n            left: 0,\n            width: WIDTH,\n            boxSizing: \"border-box\",\n            transformOrigin: \"top\",\n            transform: `translateY(${v.panelTranslateY}px) scale(${v.panelScale})`,\n            opacity: v.panelOpacity,\n            background: ctx.panelBg,\n            border: `1px solid ${ctx.panelBorder}`,\n            borderRadius: ctx.radius,\n            padding: 4,\n            display: \"flex\",\n            flexDirection: \"column\",\n            gap: 2,\n            boxShadow: \"0 16px 32px -12px rgba(0,0,0,0.25)\",\n          }}\n        >\n          {filtered.length === 0 ? (\n            <div\n              style={{\n                padding: \"12px\",\n                textAlign: \"center\",\n                fontSize: 14,\n                color: ctx.mutedFg,\n              }}\n            >\n              No results found.\n            </div>\n          ) : (\n            filtered.map((item, i) => {\n              const override = itemStyles?.[i];\n              return (\n                <SelectItemRow\n                  key={item}\n                  style={\n                    override ??\n                    selectItemStyle(\n                      rowState(\n                        i,\n                        selectedIndex,\n                        highlightedIndex,\n                        pressedIndex,\n                      ),\n                      ctx.itemCtx,\n                    )\n                  }\n                  ctx={ctx.itemCtx}\n                  label={item}\n                  width={WIDTH - 8}\n                  radius={theme.radius}\n                  check={ctx.itemCtx.check}\n                />\n              );\n            })\n          )}\n        </div>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/combobox.tsx"
    },
    {
      "path": "registry/remocn-ui/combobox/use-combobox-transition.ts",
      "content": "\"use client\";\n\nimport {\n  type ComboboxState,\n  type ComboboxStyle,\n  comboboxStyle,\n  comboboxStyleContext,\n} from \"@/components/remocn/combobox\";\nimport {\n  easings,\n  type RemocnTheme,\n  type Step,\n  useRemocnTheme,\n  useStateTransition,\n} from \"@/lib/remocn-ui\";\n\nexport const DEFAULT_DURATION = 12;\n\nexport function tweenComboboxStyle(\n  a: ComboboxStyle,\n  b: ComboboxStyle,\n  t: number,\n): ComboboxStyle {\n  return {\n    panelOpacity: a.panelOpacity + (b.panelOpacity - a.panelOpacity) * t,\n    panelScale: a.panelScale + (b.panelScale - a.panelScale) * t,\n    panelTranslateY:\n      a.panelTranslateY + (b.panelTranslateY - a.panelTranslateY) * t,\n  };\n}\n\nexport interface ComboboxTransitionOptions {\n  theme?: Partial<RemocnTheme>;\n  mode?: \"light\" | \"dark\";\n  speed?: number;\n  defaultDuration?: number;\n}\n\nexport function useComboboxTransition(\n  steps: Step<ComboboxState>[],\n  opts: ComboboxTransitionOptions = {},\n): ComboboxStyle {\n  const {\n    theme: themeOverride,\n    mode,\n    speed = 1,\n    defaultDuration = DEFAULT_DURATION,\n  } = opts;\n  const theme = useRemocnTheme(themeOverride, mode);\n  const ctx = comboboxStyleContext(theme);\n  const { from, to, progress } = useStateTransition(\n    steps,\n    \"closed\",\n    speed,\n    defaultDuration,\n  );\n  const t = easings.out(progress);\n  return tweenComboboxStyle(\n    comboboxStyle(from, ctx),\n    comboboxStyle(to, ctx),\n    t,\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/use-combobox-transition.ts"
    }
  ],
  "type": "registry:component"
}
