Просмотр исходного кода

Simplify model picker, use default @shadcn/ui components (#1782)

* Simplify model picker, use default @shadcn/ui components

* Fix tests
Chris Estreich 9 месяцев назад
Родитель
Сommit
8b368e92c9

+ 42 - 34
webview-ui/src/components/settings/ApiOptions.tsx

@@ -273,7 +273,7 @@ const ApiOptions = ({
 						onInput={handleInputChange("openRouterApiKey")}
 						placeholder="Enter API Key..."
 						className="w-full">
-						<span className="font-medium">{t("settings:providers.openRouterApiKey")}</span>
+						<label className="block font-medium mb-1">{t("settings:providers.openRouterApiKey")}</label>
 					</VSCodeTextField>
 					<div className="text-sm text-vscode-descriptionForeground -mt-2">
 						{t("settings:providers.apiKeyStorageNotice")}
@@ -331,7 +331,7 @@ const ApiOptions = ({
 						onInput={handleInputChange("apiKey")}
 						placeholder="Enter API Key..."
 						className="w-full">
-						<div className="font-medium">{t("settings:providers.anthropicApiKey")}</div>
+						<label className="block font-medium mb-1">{t("settings:providers.anthropicApiKey")}</label>
 					</VSCodeTextField>
 					<div className="text-sm text-vscode-descriptionForeground -mt-2">
 						{t("settings:providers.apiKeyStorageNotice")}
@@ -374,7 +374,7 @@ const ApiOptions = ({
 						onInput={handleInputChange("glamaApiKey")}
 						placeholder="Enter API Key..."
 						className="w-full">
-						<span className="font-medium">{t("settings:providers.glamaApiKey")}</span>
+						<label className="block font-medium mb-1">{t("settings:providers.glamaApiKey")}</label>
 					</VSCodeTextField>
 					<div className="text-sm text-vscode-descriptionForeground -mt-2">
 						{t("settings:providers.apiKeyStorageNotice")}
@@ -395,7 +395,7 @@ const ApiOptions = ({
 						onInput={handleInputChange("requestyApiKey")}
 						placeholder={t("settings:providers.getRequestyApiKey")}
 						className="w-full">
-						<span className="font-medium">{t("settings:providers.requestyApiKey")}</span>
+						<label className="block font-medium mb-1">{t("settings:providers.requestyApiKey")}</label>
 					</VSCodeTextField>
 					<div className="text-sm text-vscode-descriptionForeground -mt-2">
 						{t("settings:providers.apiKeyStorageNotice")}
@@ -411,7 +411,7 @@ const ApiOptions = ({
 						onInput={handleInputChange("openAiNativeApiKey")}
 						placeholder="Enter API Key..."
 						className="w-full">
-						<span className="font-medium">OpenAI API Key</span>
+						<label className="block font-medium mb-1">OpenAI API Key</label>
 					</VSCodeTextField>
 					<div className="text-sm text-vscode-descriptionForeground -mt-2">
 						{t("settings:providers.apiKeyStorageNotice")}
@@ -451,7 +451,9 @@ const ApiOptions = ({
 								onInput={handleInputChange("mistralCodestralUrl")}
 								placeholder="https://codestral.mistral.ai"
 								className="w-full">
-								<span className="font-medium">{t("settings:providers.codestralBaseUrl")}</span>
+								<label className="block font-medium mb-1">
+									{t("settings:providers.codestralBaseUrl")}
+								</label>
 							</VSCodeTextField>
 							<div className="text-sm text-vscode-descriptionForeground -mt-2">
 								{t("settings:providers.codestralBaseUrlDesc")}
@@ -481,7 +483,7 @@ const ApiOptions = ({
 							onInput={handleInputChange("awsProfile")}
 							placeholder="Enter profile name"
 							className="w-full">
-							<span className="font-medium">{t("settings:providers.awsProfileName")}</span>
+							<label className="block font-medium mb-1">{t("settings:providers.awsProfileName")}</label>
 						</VSCodeTextField>
 					) : (
 						<>
@@ -491,7 +493,7 @@ const ApiOptions = ({
 								onInput={handleInputChange("awsAccessKey")}
 								placeholder="Enter Access Key..."
 								className="w-full">
-								<span className="font-medium">{t("settings:providers.awsAccessKey")}</span>
+								<label className="block font-medium mb-1">{t("settings:providers.awsAccessKey")}</label>
 							</VSCodeTextField>
 							<VSCodeTextField
 								value={apiConfiguration?.awsSecretKey || ""}
@@ -499,7 +501,7 @@ const ApiOptions = ({
 								onInput={handleInputChange("awsSecretKey")}
 								placeholder="Enter Secret Key..."
 								className="w-full">
-								<span className="font-medium">{t("settings:providers.awsSecretKey")}</span>
+								<label className="block font-medium mb-1">{t("settings:providers.awsSecretKey")}</label>
 							</VSCodeTextField>
 							<VSCodeTextField
 								value={apiConfiguration?.awsSessionToken || ""}
@@ -507,7 +509,9 @@ const ApiOptions = ({
 								onInput={handleInputChange("awsSessionToken")}
 								placeholder="Enter Session Token..."
 								className="w-full">
-								<span className="font-medium">{t("settings:providers.awsSessionToken")}</span>
+								<label className="block font-medium mb-1">
+									{t("settings:providers.awsSessionToken")}
+								</label>
 							</VSCodeTextField>
 						</>
 					)}
@@ -567,21 +571,23 @@ const ApiOptions = ({
 						onInput={handleInputChange("vertexJsonCredentials")}
 						placeholder="Enter Credentials JSON..."
 						className="w-full">
-						<span className="font-medium">{t("settings:providers.googleCloudCredentials")}</span>
+						<label className="block font-medium mb-1">
+							{t("settings:providers.googleCloudCredentials")}
+						</label>
 					</VSCodeTextField>
 					<VSCodeTextField
 						value={apiConfiguration?.vertexKeyFile || ""}
 						onInput={handleInputChange("vertexKeyFile")}
 						placeholder="Enter Key File Path..."
 						className="w-full">
-						<span className="font-medium">{t("settings:providers.googleCloudKeyFile")}</span>
+						<label className="block font-medium mb-1">{t("settings:providers.googleCloudKeyFile")}</label>
 					</VSCodeTextField>
 					<VSCodeTextField
 						value={apiConfiguration?.vertexProjectId || ""}
 						onInput={handleInputChange("vertexProjectId")}
 						placeholder="Enter Project ID..."
 						className="w-full">
-						<span className="font-medium">{t("settings:providers.googleCloudProjectId")}</span>
+						<label className="block font-medium mb-1">{t("settings:providers.googleCloudProjectId")}</label>
 					</VSCodeTextField>
 					<div>
 						<label className="block font-medium mb-1">{t("settings:providers.googleCloudRegion")}</label>
@@ -611,7 +617,7 @@ const ApiOptions = ({
 						onInput={handleInputChange("geminiApiKey")}
 						placeholder="Enter API Key..."
 						className="w-full">
-						<span className="font-medium">{t("settings:providers.geminiApiKey")}</span>
+						<label className="block font-medium mb-1">{t("settings:providers.geminiApiKey")}</label>
 					</VSCodeTextField>
 					<div className="text-sm text-vscode-descriptionForeground -mt-2">
 						{t("settings:providers.apiKeyStorageNotice")}
@@ -654,7 +660,7 @@ const ApiOptions = ({
 						onInput={handleInputChange("openAiBaseUrl")}
 						placeholder={"Enter base URL..."}
 						className="w-full">
-						<span className="font-medium">{t("settings:providers.openAiBaseUrl")}</span>
+						<label className="block font-medium mb-1">{t("settings:providers.openAiBaseUrl")}</label>
 					</VSCodeTextField>
 					<VSCodeTextField
 						value={apiConfiguration?.openAiApiKey || ""}
@@ -662,7 +668,7 @@ const ApiOptions = ({
 						onInput={handleInputChange("openAiApiKey")}
 						placeholder="Enter API Key..."
 						className="w-full">
-						<span className="font-medium">{t("settings:providers.openAiApiKey")}</span>
+						<label className="block font-medium mb-1">{t("settings:providers.openAiApiKey")}</label>
 					</VSCodeTextField>
 					<ModelPicker
 						apiConfiguration={apiConfiguration}
@@ -744,9 +750,9 @@ const ApiOptions = ({
 								})}
 								placeholder="e.g. 4096"
 								className="w-full">
-								<span className="font-medium">
+								<label className="block font-medium mb-1">
 									{t("settings:providers.customModel.maxTokens.label")}
-								</span>
+								</label>
 							</VSCodeTextField>
 							<div className="text-sm text-vscode-descriptionForeground">
 								{t("settings:providers.customModel.maxTokens.description")}
@@ -788,9 +794,9 @@ const ApiOptions = ({
 								})}
 								placeholder="e.g. 128000"
 								className="w-full">
-								<span className="font-medium">
+								<label className="block font-medium mb-1">
 									{t("settings:providers.customModel.contextWindow.label")}
-								</span>
+								</label>
 							</VSCodeTextField>
 							<div className="text-sm text-vscode-descriptionForeground">
 								{t("settings:providers.customModel.contextWindow.description")}
@@ -908,9 +914,9 @@ const ApiOptions = ({
 								placeholder="e.g. 0.0001"
 								className="w-full">
 								<div className="flex items-center gap-1">
-									<span className="font-medium">
+									<label className="block font-medium mb-1">
 										{t("settings:providers.customModel.pricing.input.label")}
-									</span>
+									</label>
 									<i
 										className="codicon codicon-info text-vscode-descriptionForeground"
 										title={t("settings:providers.customModel.pricing.input.description")}
@@ -953,9 +959,9 @@ const ApiOptions = ({
 								placeholder="e.g. 0.0002"
 								className="w-full">
 								<div className="flex items-center gap-1">
-									<span className="font-medium">
+									<label className="block font-medium mb-1">
 										{t("settings:providers.customModel.pricing.output.label")}
-									</span>
+									</label>
 									<i
 										className="codicon codicon-info text-vscode-descriptionForeground"
 										title={t("settings:providers.customModel.pricing.output.description")}
@@ -1044,9 +1050,9 @@ const ApiOptions = ({
 										placeholder="e.g. 0.00005"
 										className="w-full">
 										<div className="flex items-center gap-1">
-											<span className="font-medium">
+											<label className="block font-medium mb-1">
 												{t("settings:providers.customModel.pricing.cacheWrites.label")}
-											</span>
+											</label>
 											<i
 												className="codicon codicon-info text-vscode-descriptionForeground"
 												title={t(
@@ -1079,14 +1085,14 @@ const ApiOptions = ({
 						onInput={handleInputChange("lmStudioBaseUrl")}
 						placeholder={"Default: http://localhost:1234"}
 						className="w-full">
-						<span className="font-medium">{t("settings:providers.lmStudio.baseUrl")}</span>
+						<label className="block font-medium mb-1">{t("settings:providers.lmStudio.baseUrl")}</label>
 					</VSCodeTextField>
 					<VSCodeTextField
 						value={apiConfiguration?.lmStudioModelId || ""}
 						onInput={handleInputChange("lmStudioModelId")}
 						placeholder={"e.g. meta-llama-3.1-8b-instruct"}
 						className="w-full">
-						<span className="font-medium">{t("settings:providers.lmStudio.modelId")}</span>
+						<label className="block font-medium mb-1">{t("settings:providers.lmStudio.modelId")}</label>
 					</VSCodeTextField>
 					{lmStudioModels.length > 0 && (
 						<VSCodeRadioGroup
@@ -1121,7 +1127,9 @@ const ApiOptions = ({
 									onInput={handleInputChange("lmStudioDraftModelId")}
 									placeholder={"e.g. lmstudio-community/llama-3.2-1b-instruct"}
 									className="w-full">
-									<span className="font-medium">{t("settings:providers.lmStudio.draftModelId")}</span>
+									<label className="block font-medium mb-1">
+										{t("settings:providers.lmStudio.draftModelId")}
+									</label>
 								</VSCodeTextField>
 								<div className="text-sm text-vscode-descriptionForeground">
 									{t("settings:providers.lmStudio.draftModelDesc")}
@@ -1185,7 +1193,7 @@ const ApiOptions = ({
 						onInput={handleInputChange("deepSeekApiKey")}
 						placeholder="Enter API Key..."
 						className="w-full">
-						<span className="font-medium">{t("settings:providers.deepSeekApiKey")}</span>
+						<label className="block font-medium mb-1">{t("settings:providers.deepSeekApiKey")}</label>
 					</VSCodeTextField>
 					<div className="text-sm text-vscode-descriptionForeground -mt-2">
 						{t("settings:providers.apiKeyStorageNotice")}
@@ -1244,14 +1252,14 @@ const ApiOptions = ({
 						onInput={handleInputChange("ollamaBaseUrl")}
 						placeholder={"Default: http://localhost:11434"}
 						className="w-full">
-						<span className="font-medium">{t("settings:providers.ollama.baseUrl")}</span>
+						<label className="block font-medium mb-1">{t("settings:providers.ollama.baseUrl")}</label>
 					</VSCodeTextField>
 					<VSCodeTextField
 						value={apiConfiguration?.ollamaModelId || ""}
 						onInput={handleInputChange("ollamaModelId")}
 						placeholder={"e.g. llama3.1"}
 						className="w-full">
-						<span className="font-medium">{t("settings:providers.ollama.modelId")}</span>
+						<label className="block font-medium mb-1">{t("settings:providers.ollama.modelId")}</label>
 					</VSCodeTextField>
 					{ollamaModels.length > 0 && (
 						<VSCodeRadioGroup
@@ -1288,7 +1296,7 @@ const ApiOptions = ({
 						onInput={handleInputChange("unboundApiKey")}
 						placeholder="Enter API Key..."
 						className="w-full">
-						<span className="font-medium">{t("settings:providers.unboundApiKey")}</span>
+						<label className="block font-medium mb-1">{t("settings:providers.unboundApiKey")}</label>
 					</VSCodeTextField>
 					<div className="text-sm text-vscode-descriptionForeground -mt-2">
 						{t("settings:providers.apiKeyStorageNotice")}
@@ -1458,7 +1466,7 @@ const ApiOptions = ({
 								}}
 								placeholder="Enter ARN (e.g. arn:aws:bedrock:us-east-1:123456789012:foundation-model/my-model)"
 								className="w-full">
-								<span className="font-medium">Custom ARN</span>
+								<label className="block font-medium mb-1">Custom ARN</label>
 							</VSCodeTextField>
 							<div className="text-sm text-vscode-descriptionForeground -mt-2">
 								{t("settings:providers.awsCustomArnUse")}

+ 92 - 18
webview-ui/src/components/settings/ModelPicker.tsx

@@ -1,8 +1,21 @@
 import { useMemo, useState, useCallback, useEffect, useRef } from "react"
 import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
 import { Trans } from "react-i18next"
+import { ChevronsUpDown, Check, X } from "lucide-react"
 
-import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem } from "@/components/ui/combobox"
+import { cn } from "@/lib/utils"
+import {
+	Command,
+	CommandEmpty,
+	CommandGroup,
+	CommandInput,
+	CommandItem,
+	CommandList,
+	Popover,
+	PopoverContent,
+	PopoverTrigger,
+	Button,
+} from "@/components/ui"
 
 import { ApiConfiguration, ModelInfo } from "../../../../src/shared/api"
 
@@ -41,9 +54,10 @@ export const ModelPicker = ({
 	setApiConfigurationField,
 	defaultModelInfo,
 }: ModelPickerProps) => {
+	const [open, setOpen] = useState(false)
 	const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false)
 	const isInitialized = useRef(false)
-
+	const searchInputRef = useRef<HTMLInputElement>(null)
 	const modelIds = useMemo(() => Object.keys(models ?? {}).sort((a, b) => a.localeCompare(b)), [models])
 
 	const { selectedModelId, selectedModelInfo } = useMemo(
@@ -51,43 +65,103 @@ export const ModelPicker = ({
 		[apiConfiguration],
 	)
 
+	const [searchValue, setSearchValue] = useState(selectedModelId)
+
 	const onSelect = useCallback(
 		(modelId: string) => {
+			setOpen(false)
 			const modelInfo = models?.[modelId]
 			setApiConfigurationField(modelIdKey, modelId)
 			setApiConfigurationField(modelInfoKey, modelInfo ?? defaultModelInfo)
+			// Delay to ensure the popover is closed before setting the search value.
+			setTimeout(() => setSearchValue(modelId), 100)
 		},
 		[modelIdKey, modelInfoKey, models, setApiConfigurationField, defaultModelInfo],
 	)
 
-	const inputValue = apiConfiguration[modelIdKey]
+	const onOpenChange = useCallback(
+		(open: boolean) => {
+			setOpen(open)
+
+			// Abandon the current search if the popover is closed.
+			if (!open) {
+				// Delay to ensure the popover is closed before setting the search value.
+				setTimeout(() => setSearchValue(selectedModelId), 100)
+			}
+		},
+		[selectedModelId],
+	)
+
+	const onClearSearch = useCallback(() => {
+		setSearchValue("")
+		searchInputRef.current?.focus()
+	}, [])
 
 	useEffect(() => {
-		if (!inputValue && !isInitialized.current) {
+		if (!selectedModelId && !isInitialized.current) {
 			const initialValue = modelIds.includes(selectedModelId) ? selectedModelId : defaultModelId
 			setApiConfigurationField(modelIdKey, initialValue)
 		}
 
 		isInitialized.current = true
-	}, [inputValue, modelIds, setApiConfigurationField, modelIdKey, selectedModelId, defaultModelId])
+	}, [modelIds, setApiConfigurationField, modelIdKey, selectedModelId, defaultModelId])
 
 	return (
 		<>
 			<div>
-				<div className="font-medium">Model</div>
-				<Combobox type="single" inputValue={inputValue} onInputValueChange={onSelect}>
-					<ComboboxInput placeholder="Search model..." data-testid="model-input" />
-					<ComboboxContent>
-						<ComboboxEmpty>No model found.</ComboboxEmpty>
-						{modelIds.map((model) => (
-							<ComboboxItem key={model} value={model}>
-								{model}
-							</ComboboxItem>
-						))}
-					</ComboboxContent>
-				</Combobox>
+				<label className="block font-medium mb-1">Model</label>
+				<Popover open={open} onOpenChange={onOpenChange}>
+					<PopoverTrigger asChild>
+						<Button
+							variant="outline"
+							role="combobox"
+							aria-expanded={open}
+							className="w-full justify-between">
+							<div>{selectedModelId ?? "Select"}</div>
+							<ChevronsUpDown className="opacity-50" />
+						</Button>
+					</PopoverTrigger>
+					<PopoverContent className="p-0 w-[var(--radix-popover-trigger-width)]">
+						<Command>
+							<div className="relative">
+								<CommandInput
+									ref={searchInputRef}
+									value={searchValue}
+									onValueChange={setSearchValue}
+									placeholder="Search"
+									className="h-9 mr-4"
+									data-testid="model-input"
+								/>
+								{searchValue.length > 0 && (
+									<div className="absolute right-2 top-0 bottom-0 flex items-center justify-center">
+										<X
+											className="opacity-25 hover:opacity-100 cursor-pointer size-4 bg-vscode-button-secondaryBackground rounded-full p-0.5"
+											onClick={onClearSearch}
+										/>
+									</div>
+								)}
+							</div>
+							<CommandList>
+								<CommandEmpty>No model found.</CommandEmpty>
+								<CommandGroup>
+									{modelIds.map((model) => (
+										<CommandItem key={model} value={model} onSelect={onSelect}>
+											{model}
+											<Check
+												className={cn(
+													"ml-auto",
+													model === selectedModelId ? "opacity-100" : "opacity-0",
+												)}
+											/>
+										</CommandItem>
+									))}
+								</CommandGroup>
+							</CommandList>
+						</Command>
+					</PopoverContent>
+				</Popover>
 			</div>
-			{selectedModelId && selectedModelInfo && selectedModelId === inputValue && (
+			{selectedModelId && selectedModelInfo && (
 				<ModelInfoView
 					selectedModelId={selectedModelId}
 					modelInfo={selectedModelInfo}

+ 12 - 4
webview-ui/src/components/settings/__tests__/ModelPicker.test.tsx

@@ -1,7 +1,8 @@
-// cd webview-ui && npx jest src/components/settings/__tests__/ModelPicker.test.ts
+// npx jest src/components/settings/__tests__/ModelPicker.test.ts
 
 import { screen, fireEvent, render } from "@testing-library/react"
 import { act } from "react"
+
 import { ModelPicker } from "../ModelPicker"
 
 jest.mock("../../../context/ExtensionStateContext", () => ({
@@ -69,9 +70,16 @@ describe("ModelPicker", () => {
 		})
 
 		await act(async () => {
-			// Find and click the model item by its value.
-			const modelItem = screen.getByTestId("model-input")
-			fireEvent.input(modelItem, { target: { value: "model2" } })
+			// Find and set the input value
+			const modelInput = screen.getByTestId("model-input")
+			fireEvent.input(modelInput, { target: { value: "model2" } })
+		})
+
+		// Need to find and click the CommandItem to trigger onSelect
+		await act(async () => {
+			// Find the CommandItem for model2 and click it
+			const modelItem = screen.getByText("model2")
+			fireEvent.click(modelItem)
 		})
 
 		// Verify the API config was updated.

+ 1 - 1
webview-ui/src/components/ui/button.tsx

@@ -14,7 +14,7 @@ const buttonVariants = cva(
 				destructive:
 					"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90 cursor-pointer",
 				outline:
-					"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground cursor-pointer",
+					"border border-vscode-input-border bg-background shadow-sm hover:bg-accent hover:text-accent-foreground cursor-pointer",
 				secondary:
 					"border border-vscode-input-border bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80 cursor-pointer",
 				ghost: "hover:bg-accent hover:text-accent-foreground cursor-pointer",

+ 0 - 522
webview-ui/src/components/ui/combobox-primitive.tsx

@@ -1,522 +0,0 @@
-/* eslint-disable react/jsx-pascal-case */
-"use client"
-
-import * as React from "react"
-import { composeEventHandlers } from "@radix-ui/primitive"
-import { useComposedRefs } from "@radix-ui/react-compose-refs"
-import * as PopoverPrimitive from "@radix-ui/react-popover"
-import { Primitive } from "@radix-ui/react-primitive"
-import * as RovingFocusGroupPrimitive from "@radix-ui/react-roving-focus"
-import { useControllableState } from "@radix-ui/react-use-controllable-state"
-import { Command as CommandPrimitive } from "cmdk"
-
-export type ComboboxContextProps = {
-	inputValue: string
-	onInputValueChange: (inputValue: string, reason: "inputChange" | "itemSelect" | "clearClick") => void
-	onInputBlur?: (e: React.FocusEvent<HTMLInputElement, Element>) => void
-	open: boolean
-	onOpenChange: (open: boolean) => void
-	currentTabStopId: string | null
-	onCurrentTabStopIdChange: (currentTabStopId: string | null) => void
-	inputRef: React.RefObject<HTMLInputElement>
-	tagGroupRef: React.RefObject<React.ElementRef<typeof RovingFocusGroupPrimitive.Root>>
-	disabled?: boolean
-	required?: boolean
-} & (
-	| Required<Pick<ComboboxSingleProps, "type" | "value" | "onValueChange">>
-	| Required<Pick<ComboboxMultipleProps, "type" | "value" | "onValueChange">>
-)
-
-const ComboboxContext = React.createContext<ComboboxContextProps>({
-	type: "single",
-	value: "",
-	onValueChange: () => {},
-	inputValue: "",
-	onInputValueChange: () => {},
-	onInputBlur: () => {},
-	open: false,
-	onOpenChange: () => {},
-	currentTabStopId: null,
-	onCurrentTabStopIdChange: () => {},
-	inputRef: { current: null },
-	tagGroupRef: { current: null },
-	disabled: false,
-	required: false,
-})
-
-export const useComboboxContext = () => React.useContext(ComboboxContext)
-
-export type ComboboxType = "single" | "multiple"
-
-export interface ComboboxBaseProps
-	extends React.ComponentProps<typeof PopoverPrimitive.Root>,
-		Omit<React.ComponentProps<typeof CommandPrimitive>, "value" | "defaultValue" | "onValueChange" | "children"> {
-	type?: ComboboxType | undefined
-	inputValue?: string
-	defaultInputValue?: string
-	onInputValueChange?: (inputValue: string, reason: "inputChange" | "itemSelect" | "clearClick") => void
-	onInputBlur?: (e: React.FocusEvent<HTMLInputElement, Element>) => void
-	disabled?: boolean
-	required?: boolean
-}
-
-export type ComboboxValue<T extends ComboboxType = "single"> = T extends "single"
-	? string
-	: T extends "multiple"
-		? string[]
-		: never
-
-export interface ComboboxSingleProps {
-	type: "single"
-	value?: string
-	defaultValue?: string
-	onValueChange?: (value: string) => void
-}
-
-export interface ComboboxMultipleProps {
-	type: "multiple"
-	value?: string[]
-	defaultValue?: string[]
-	onValueChange?: (value: string[]) => void
-}
-
-export type ComboboxProps = ComboboxBaseProps & (ComboboxSingleProps | ComboboxMultipleProps)
-
-export const Combobox = React.forwardRef(
-	<T extends ComboboxType = "single">(
-		{
-			type = "single" as T,
-			open: openProp,
-			onOpenChange,
-			defaultOpen,
-			modal,
-			children,
-			value: valueProp,
-			defaultValue,
-			onValueChange,
-			inputValue: inputValueProp,
-			defaultInputValue,
-			onInputValueChange,
-			onInputBlur,
-			disabled,
-			required,
-			...props
-		}: ComboboxProps,
-		ref: React.ForwardedRef<React.ElementRef<typeof CommandPrimitive>>,
-	) => {
-		const [value = type === "multiple" ? [] : "", setValue] = useControllableState<ComboboxValue<T>>({
-			prop: valueProp as ComboboxValue<T>,
-			defaultProp: defaultValue as ComboboxValue<T>,
-			onChange: onValueChange as (value: ComboboxValue<T>) => void,
-		})
-		const [inputValue = "", setInputValue] = useControllableState({
-			prop: inputValueProp,
-			defaultProp: defaultInputValue,
-		})
-		const [open = false, setOpen] = useControllableState({
-			prop: openProp,
-			defaultProp: defaultOpen,
-			onChange: onOpenChange,
-		})
-		const [currentTabStopId, setCurrentTabStopId] = React.useState<string | null>(null)
-		const inputRef = React.useRef<HTMLInputElement>(null)
-		const tagGroupRef = React.useRef<React.ElementRef<typeof RovingFocusGroupPrimitive.Root>>(null)
-
-		const handleInputValueChange: ComboboxContextProps["onInputValueChange"] = React.useCallback(
-			(inputValue, reason) => {
-				setInputValue(inputValue)
-				onInputValueChange?.(inputValue, reason)
-			},
-			[setInputValue, onInputValueChange],
-		)
-
-		return (
-			<ComboboxContext.Provider
-				value={
-					{
-						type,
-						value,
-						onValueChange: setValue,
-						inputValue,
-						onInputValueChange: handleInputValueChange,
-						onInputBlur,
-						open,
-						onOpenChange: setOpen,
-						currentTabStopId,
-						onCurrentTabStopIdChange: setCurrentTabStopId,
-						inputRef,
-						tagGroupRef,
-						disabled,
-						required,
-					} as ComboboxContextProps
-				}>
-				<PopoverPrimitive.Root open={open} onOpenChange={setOpen} modal={modal}>
-					<CommandPrimitive ref={ref} {...props}>
-						{children}
-						{!open && <CommandPrimitive.List aria-hidden hidden />}
-					</CommandPrimitive>
-				</PopoverPrimitive.Root>
-			</ComboboxContext.Provider>
-		)
-	},
-)
-Combobox.displayName = "Combobox"
-
-export const ComboboxTagGroup = React.forwardRef<
-	React.ElementRef<typeof RovingFocusGroupPrimitive.Root>,
-	React.ComponentPropsWithoutRef<typeof RovingFocusGroupPrimitive.Root>
->((props, ref) => {
-	const { currentTabStopId, onCurrentTabStopIdChange, tagGroupRef, type } = useComboboxContext()
-
-	if (type !== "multiple") {
-		throw new Error('<ComboboxTagGroup> should only be used when type is "multiple"')
-	}
-
-	const composedRefs = useComposedRefs(ref, tagGroupRef)
-
-	return (
-		<RovingFocusGroupPrimitive.Root
-			ref={composedRefs}
-			tabIndex={-1}
-			currentTabStopId={currentTabStopId}
-			onCurrentTabStopIdChange={onCurrentTabStopIdChange}
-			onBlur={() => onCurrentTabStopIdChange(null)}
-			{...props}
-		/>
-	)
-})
-ComboboxTagGroup.displayName = "ComboboxTagGroup"
-
-export interface ComboboxTagGroupItemProps
-	extends React.ComponentPropsWithoutRef<typeof RovingFocusGroupPrimitive.Item> {
-	value: string
-	disabled?: boolean
-}
-
-const ComboboxTagGroupItemContext = React.createContext<Pick<ComboboxTagGroupItemProps, "value" | "disabled">>({
-	value: "",
-	disabled: false,
-})
-
-const useComboboxTagGroupItemContext = () => React.useContext(ComboboxTagGroupItemContext)
-
-export const ComboboxTagGroupItem = React.forwardRef<
-	React.ElementRef<typeof RovingFocusGroupPrimitive.Item>,
-	ComboboxTagGroupItemProps
->(({ onClick, onKeyDown, value: valueProp, disabled, ...props }, ref) => {
-	const { value, onValueChange, inputRef, currentTabStopId, type } = useComboboxContext()
-
-	if (type !== "multiple") {
-		throw new Error('<ComboboxTagGroupItem> should only be used when type is "multiple"')
-	}
-
-	const lastItemValue = value.at(-1)
-
-	return (
-		<ComboboxTagGroupItemContext.Provider value={{ value: valueProp, disabled }}>
-			<RovingFocusGroupPrimitive.Item
-				ref={ref}
-				onKeyDown={composeEventHandlers(onKeyDown, (event) => {
-					if (event.key === "Escape") {
-						inputRef.current?.focus()
-					}
-					if (event.key === "ArrowUp" || event.key === "ArrowDown") {
-						event.preventDefault()
-						inputRef.current?.focus()
-					}
-					if (event.key === "ArrowRight" && currentTabStopId === lastItemValue) {
-						inputRef.current?.focus()
-					}
-					if (event.key === "Backspace" || event.key === "Delete") {
-						onValueChange(value.filter((v) => v !== currentTabStopId))
-						inputRef.current?.focus()
-					}
-				})}
-				onClick={composeEventHandlers(onClick, () => disabled && inputRef.current?.focus())}
-				tabStopId={valueProp}
-				focusable={!disabled}
-				data-disabled={disabled}
-				active={valueProp === lastItemValue}
-				{...props}
-			/>
-		</ComboboxTagGroupItemContext.Provider>
-	)
-})
-ComboboxTagGroupItem.displayName = "ComboboxTagGroupItem"
-
-export const ComboboxTagGroupItemRemove = React.forwardRef<
-	React.ElementRef<typeof Primitive.button>,
-	React.ComponentPropsWithoutRef<typeof Primitive.button>
->(({ onClick, ...props }, ref) => {
-	const { value, onValueChange, type } = useComboboxContext()
-
-	if (type !== "multiple") {
-		throw new Error('<ComboboxTagGroupItemRemove> should only be used when type is "multiple"')
-	}
-
-	const { value: valueProp, disabled } = useComboboxTagGroupItemContext()
-
-	return (
-		<Primitive.button
-			ref={ref}
-			aria-hidden
-			tabIndex={-1}
-			disabled={disabled}
-			onClick={composeEventHandlers(onClick, () => onValueChange(value.filter((v) => v !== valueProp)))}
-			{...props}
-		/>
-	)
-})
-ComboboxTagGroupItemRemove.displayName = "ComboboxTagGroupItemRemove"
-
-export const ComboboxInput = React.forwardRef<
-	React.ElementRef<typeof CommandPrimitive.Input>,
-	Omit<React.ComponentProps<typeof CommandPrimitive.Input>, "value" | "onValueChange">
->(({ onKeyDown, onMouseDown, onFocus, onBlur, ...props }, ref) => {
-	const {
-		type,
-		inputValue,
-		onInputValueChange,
-		onInputBlur,
-		open,
-		onOpenChange,
-		value,
-		onValueChange,
-		inputRef,
-		disabled,
-		required,
-		tagGroupRef,
-	} = useComboboxContext()
-
-	const composedRefs = useComposedRefs(ref, inputRef)
-
-	return (
-		<CommandPrimitive.Input
-			ref={composedRefs}
-			disabled={disabled}
-			required={required}
-			value={inputValue}
-			onValueChange={(search) => {
-				if (!open) {
-					onOpenChange(true)
-				}
-				// Schedule input value change to the next tick.
-				setTimeout(() => onInputValueChange(search, "inputChange"))
-				if (!search && type === "single") {
-					onValueChange("")
-				}
-			}}
-			onKeyDown={composeEventHandlers(onKeyDown, (event) => {
-				if (event.key === "ArrowUp" || event.key === "ArrowDown") {
-					if (!open) {
-						event.preventDefault()
-						onOpenChange(true)
-					}
-				}
-				if (type !== "multiple") {
-					return
-				}
-				if (event.key === "ArrowLeft" && !inputValue && value.length) {
-					tagGroupRef.current?.focus()
-				}
-				if (event.key === "Backspace" && !inputValue) {
-					onValueChange(value.slice(0, -1))
-				}
-			})}
-			onMouseDown={composeEventHandlers(onMouseDown, () => onOpenChange(!!inputValue || !open))}
-			onFocus={composeEventHandlers(onFocus, () => onOpenChange(true))}
-			onBlur={composeEventHandlers(onBlur, (event) => {
-				if (!event.relatedTarget?.hasAttribute("cmdk-list")) {
-					onInputBlur?.(event)
-				}
-			})}
-			{...props}
-		/>
-	)
-})
-ComboboxInput.displayName = "ComboboxInput"
-
-export const ComboboxClear = React.forwardRef<
-	React.ElementRef<typeof Primitive.button>,
-	React.ComponentPropsWithoutRef<typeof Primitive.button>
->(({ onClick, ...props }, ref) => {
-	const { value, onValueChange, inputValue, onInputValueChange, type } = useComboboxContext()
-
-	const isValueEmpty = type === "single" ? !value : !value.length
-
-	return (
-		<Primitive.button
-			ref={ref}
-			disabled={isValueEmpty && !inputValue}
-			onClick={composeEventHandlers(onClick, () => {
-				if (type === "single") {
-					onValueChange("")
-				} else {
-					onValueChange([])
-				}
-				onInputValueChange("", "clearClick")
-			})}
-			{...props}
-		/>
-	)
-})
-ComboboxClear.displayName = "ComboboxClear"
-
-export const ComboboxTrigger = PopoverPrimitive.Trigger
-
-export const ComboboxAnchor = PopoverPrimitive.Anchor
-
-export const ComboboxPortal = PopoverPrimitive.Portal
-
-export const ComboboxContent = React.forwardRef<
-	React.ElementRef<typeof PopoverPrimitive.Content>,
-	React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
->(({ children, onOpenAutoFocus, onInteractOutside, ...props }, ref) => (
-	<PopoverPrimitive.Content
-		asChild
-		ref={ref}
-		onOpenAutoFocus={composeEventHandlers(onOpenAutoFocus, (event) => event.preventDefault())}
-		onCloseAutoFocus={composeEventHandlers(onOpenAutoFocus, (event) => event.preventDefault())}
-		onInteractOutside={composeEventHandlers(onInteractOutside, (event) => {
-			if (event.target instanceof Element && event.target.hasAttribute("cmdk-input")) {
-				event.preventDefault()
-			}
-		})}
-		{...props}>
-		<CommandPrimitive.List>{children}</CommandPrimitive.List>
-	</PopoverPrimitive.Content>
-))
-ComboboxContent.displayName = "ComboboxContent"
-
-export const ComboboxEmpty = CommandPrimitive.Empty
-
-export const ComboboxLoading = CommandPrimitive.Loading
-
-export interface ComboboxItemProps extends Omit<React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>, "value"> {
-	value: string
-}
-
-const ComboboxItemContext = React.createContext({ isSelected: false })
-
-const useComboboxItemContext = () => React.useContext(ComboboxItemContext)
-
-const findComboboxItemText = (children: React.ReactNode) => {
-	let text = ""
-
-	React.Children.forEach(children, (child) => {
-		if (text) {
-			return
-		}
-
-		if (React.isValidElement<{ children: React.ReactNode }>(child)) {
-			if (child.type === ComboboxItemText) {
-				text = child.props.children as string
-			} else {
-				text = findComboboxItemText(child.props.children)
-			}
-		}
-	})
-
-	return text
-}
-
-export const ComboboxItem = React.forwardRef<React.ElementRef<typeof CommandPrimitive.Item>, ComboboxItemProps>(
-	({ value: valueProp, children, onMouseDown, ...props }, ref) => {
-		const { type, value, onValueChange, onInputValueChange, onOpenChange } = useComboboxContext()
-
-		const inputValue = React.useMemo(() => findComboboxItemText(children), [children])
-
-		const isSelected = type === "single" ? value === valueProp : value.includes(valueProp)
-
-		return (
-			<ComboboxItemContext.Provider value={{ isSelected }}>
-				<CommandPrimitive.Item
-					ref={ref}
-					onMouseDown={composeEventHandlers(onMouseDown, (event) => event.preventDefault())}
-					onSelect={() => {
-						if (type === "multiple") {
-							onValueChange(
-								value.includes(valueProp)
-									? value.filter((v) => v !== valueProp)
-									: [...value, valueProp],
-							)
-							onInputValueChange("", "itemSelect")
-						} else {
-							onValueChange(valueProp)
-							onInputValueChange(inputValue, "itemSelect")
-							// Schedule open change to the next tick.
-							setTimeout(() => onOpenChange(false))
-						}
-					}}
-					value={inputValue}
-					{...props}>
-					{children}
-				</CommandPrimitive.Item>
-			</ComboboxItemContext.Provider>
-		)
-	},
-)
-ComboboxItem.displayName = "ComboboxItem"
-
-export const ComboboxItemIndicator = React.forwardRef<
-	React.ElementRef<typeof Primitive.span>,
-	React.ComponentPropsWithoutRef<typeof Primitive.span>
->((props, ref) => {
-	const { isSelected } = useComboboxItemContext()
-
-	if (!isSelected) {
-		return null
-	}
-
-	return <Primitive.span ref={ref} aria-hidden {...props} />
-})
-ComboboxItemIndicator.displayName = "ComboboxItemIndicator"
-
-export interface ComboboxItemTextProps extends React.ComponentPropsWithoutRef<typeof React.Fragment> {
-	children: string
-}
-
-export const ComboboxItemText = (props: ComboboxItemTextProps) => <React.Fragment {...props} />
-ComboboxItemText.displayName = "ComboboxItemText"
-
-export const ComboboxGroup = CommandPrimitive.Group
-
-export const ComboboxSeparator = CommandPrimitive.Separator
-
-const Root = Combobox
-const TagGroup = ComboboxTagGroup
-const TagGroupItem = ComboboxTagGroupItem
-const TagGroupItemRemove = ComboboxTagGroupItemRemove
-const Input = ComboboxInput
-const Clear = ComboboxClear
-const Trigger = ComboboxTrigger
-const Anchor = ComboboxAnchor
-const Portal = ComboboxPortal
-const Content = ComboboxContent
-const Empty = ComboboxEmpty
-const Loading = ComboboxLoading
-const Item = ComboboxItem
-const ItemIndicator = ComboboxItemIndicator
-const ItemText = ComboboxItemText
-const Group = ComboboxGroup
-const Separator = ComboboxSeparator
-
-export {
-	Root,
-	TagGroup,
-	TagGroupItem,
-	TagGroupItemRemove,
-	Input,
-	Clear,
-	Trigger,
-	Anchor,
-	Portal,
-	Content,
-	Empty,
-	Loading,
-	Item,
-	ItemIndicator,
-	ItemText,
-	Group,
-	Separator,
-}

+ 0 - 178
webview-ui/src/components/ui/combobox.tsx

@@ -1,178 +0,0 @@
-"use client"
-
-import * as React from "react"
-import { Slottable } from "@radix-ui/react-slot"
-import { cva } from "class-variance-authority"
-import { Check, ChevronsUpDown, Loader, X } from "lucide-react"
-
-import { cn } from "@/lib/utils"
-
-import * as ComboboxPrimitive from "@/components/ui/combobox-primitive"
-import { badgeVariants } from "@/components/ui/badge"
-
-import {
-	InputBase,
-	InputBaseAdornmentButton,
-	InputBaseControl,
-	InputBaseFlexWrapper,
-	InputBaseInput,
-} from "@/components/ui/input-base"
-
-export const Combobox = ComboboxPrimitive.Root
-
-const ComboboxInputBase = React.forwardRef<
-	React.ElementRef<typeof InputBase>,
-	React.ComponentPropsWithoutRef<typeof InputBase>
->(({ children, ...props }, ref) => (
-	<ComboboxPrimitive.Anchor asChild>
-		<InputBase ref={ref} {...props}>
-			{children}
-			<ComboboxPrimitive.Clear asChild>
-				<InputBaseAdornmentButton>
-					<X />
-				</InputBaseAdornmentButton>
-			</ComboboxPrimitive.Clear>
-			<ComboboxPrimitive.Trigger asChild>
-				<InputBaseAdornmentButton>
-					<ChevronsUpDown />
-				</InputBaseAdornmentButton>
-			</ComboboxPrimitive.Trigger>
-		</InputBase>
-	</ComboboxPrimitive.Anchor>
-))
-ComboboxInputBase.displayName = "ComboboxInputBase"
-
-export const ComboboxInput = React.forwardRef<
-	React.ElementRef<typeof ComboboxPrimitive.Input>,
-	React.ComponentPropsWithoutRef<typeof ComboboxPrimitive.Input>
->((props, ref) => (
-	<ComboboxInputBase>
-		<InputBaseControl>
-			<ComboboxPrimitive.Input asChild>
-				<InputBaseInput ref={ref} {...props} />
-			</ComboboxPrimitive.Input>
-		</InputBaseControl>
-	</ComboboxInputBase>
-))
-ComboboxInput.displayName = "ComboboxInput"
-
-export const ComboboxTagsInput = React.forwardRef<
-	React.ElementRef<typeof ComboboxPrimitive.Input>,
-	React.ComponentPropsWithoutRef<typeof ComboboxPrimitive.Input>
->(({ children, ...props }, ref) => (
-	<ComboboxInputBase>
-		<ComboboxPrimitive.ComboboxTagGroup asChild>
-			<InputBaseFlexWrapper className="flex items-center gap-2">
-				{children}
-				<InputBaseControl>
-					<ComboboxPrimitive.Input asChild>
-						<InputBaseInput ref={ref} {...props} />
-					</ComboboxPrimitive.Input>
-				</InputBaseControl>
-			</InputBaseFlexWrapper>
-		</ComboboxPrimitive.ComboboxTagGroup>
-	</ComboboxInputBase>
-))
-ComboboxTagsInput.displayName = "ComboboxTagsInput"
-
-export const ComboboxTag = React.forwardRef<
-	React.ElementRef<typeof ComboboxPrimitive.ComboboxTagGroupItem>,
-	React.ComponentPropsWithoutRef<typeof ComboboxPrimitive.ComboboxTagGroupItem>
->(({ children, className, ...props }, ref) => (
-	<ComboboxPrimitive.ComboboxTagGroupItem
-		ref={ref}
-		className={cn(
-			badgeVariants({ variant: "outline" }),
-			"group gap-1 pr-1.5 data-[disabled]:opacity-50",
-			className,
-		)}
-		{...props}>
-		<Slottable>{children}</Slottable>
-		<ComboboxPrimitive.ComboboxTagGroupItemRemove className="group-data-[disabled]:pointer-events-none">
-			<X className="size-4" />
-			<span className="sr-only">Remove</span>
-		</ComboboxPrimitive.ComboboxTagGroupItemRemove>
-	</ComboboxPrimitive.ComboboxTagGroupItem>
-))
-ComboboxTag.displayName = "ComboboxTag"
-
-export const ComboboxContent = React.forwardRef<
-	React.ElementRef<typeof ComboboxPrimitive.Content>,
-	React.ComponentPropsWithoutRef<typeof ComboboxPrimitive.Content>
->(({ className, align = "start", alignOffset = 0, ...props }, ref) => (
-	<ComboboxPrimitive.Portal>
-		<ComboboxPrimitive.Content
-			ref={ref}
-			align={align}
-			alignOffset={alignOffset}
-			className={cn(
-				"min-w-72 border-vscode-dropdown-border relative z-50 left-0 max-h-96 w-[--radix-popover-trigger-width] overflow-y-auto overflow-x-hidden rounded-xs border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
-				className,
-			)}
-			{...props}
-		/>
-	</ComboboxPrimitive.Portal>
-))
-ComboboxContent.displayName = "ComboboxContent"
-
-export const ComboboxEmpty = React.forwardRef<
-	React.ElementRef<typeof ComboboxPrimitive.Empty>,
-	React.ComponentPropsWithoutRef<typeof ComboboxPrimitive.Empty>
->(({ className, ...props }, ref) => (
-	<ComboboxPrimitive.Empty ref={ref} className={cn("py-6 text-center text-sm", className)} {...props} />
-))
-ComboboxEmpty.displayName = "ComboboxEmpty"
-
-export const ComboboxLoading = React.forwardRef<
-	React.ElementRef<typeof ComboboxPrimitive.Loading>,
-	React.ComponentPropsWithoutRef<typeof ComboboxPrimitive.Loading>
->(({ className, ...props }, ref) => (
-	<ComboboxPrimitive.Loading
-		ref={ref}
-		className={cn("flex items-center justify-center px-1.5 py-2", className)}
-		{...props}>
-		<Loader className="size-4 animate-spin [mask:conic-gradient(transparent_45deg,_white)]" />
-	</ComboboxPrimitive.Loading>
-))
-ComboboxLoading.displayName = "ComboboxLoading"
-
-export const ComboboxGroup = React.forwardRef<
-	React.ElementRef<typeof ComboboxPrimitive.Group>,
-	React.ComponentPropsWithoutRef<typeof ComboboxPrimitive.Group>
->(({ className, ...props }, ref) => (
-	<ComboboxPrimitive.Group
-		ref={ref}
-		className={cn(
-			"[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-sm [&_[cmdk-group-heading]]:font-semibold",
-			className,
-		)}
-		{...props}
-	/>
-))
-ComboboxGroup.displayName = "ComboboxGroup"
-
-const ComboboxSeparator = React.forwardRef<
-	React.ElementRef<typeof ComboboxPrimitive.Separator>,
-	React.ComponentPropsWithoutRef<typeof ComboboxPrimitive.Separator>
->(({ className, ...props }, ref) => (
-	<ComboboxPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-border", className)} {...props} />
-))
-ComboboxSeparator.displayName = "ComboboxSeparator"
-
-export const comboboxItemStyle = cva(
-	"relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-vscode-list-activeSelectionBackground data-[selected=true]:text-vscode-dropdown-foreground data-[disabled=true]:opacity-50",
-)
-
-export const ComboboxItem = React.forwardRef<
-	React.ElementRef<typeof ComboboxPrimitive.Item>,
-	Omit<React.ComponentPropsWithoutRef<typeof ComboboxPrimitive.Item>, "children"> &
-		Pick<React.ComponentPropsWithoutRef<typeof ComboboxPrimitive.ItemText>, "children">
->(({ className, children, ...props }, ref) => (
-	<ComboboxPrimitive.Item ref={ref} className={cn(comboboxItemStyle(), className)} {...props}>
-		<ComboboxPrimitive.ItemText>{children}</ComboboxPrimitive.ItemText>
-		<ComboboxPrimitive.ItemIndicator className="absolute right-2 flex size-3.5 items-center justify-center">
-			<Check className="size-4" />
-		</ComboboxPrimitive.ItemIndicator>
-	</ComboboxPrimitive.Item>
-))
-ComboboxItem.displayName = "ComboboxItem"

+ 0 - 157
webview-ui/src/components/ui/input-base.tsx

@@ -1,157 +0,0 @@
-/* eslint-disable react/jsx-no-comment-textnodes */
-/* eslint-disable react/jsx-pascal-case */
-"use client"
-
-import * as React from "react"
-import { composeEventHandlers } from "@radix-ui/primitive"
-import { composeRefs } from "@radix-ui/react-compose-refs"
-import { Primitive } from "@radix-ui/react-primitive"
-import { Slot } from "@radix-ui/react-slot"
-
-import { cn } from "@/lib/utils"
-import { Button } from "./button"
-
-export type InputBaseContextProps = Pick<InputBaseProps, "autoFocus" | "disabled"> & {
-	controlRef: React.RefObject<HTMLElement>
-	onFocusedChange: (focused: boolean) => void
-}
-
-const InputBaseContext = React.createContext<InputBaseContextProps>({
-	autoFocus: false,
-	controlRef: { current: null },
-	disabled: false,
-	onFocusedChange: () => {},
-})
-
-const useInputBaseContext = () => React.useContext(InputBaseContext)
-
-export interface InputBaseProps extends React.ComponentPropsWithoutRef<typeof Primitive.div> {
-	autoFocus?: boolean
-	disabled?: boolean
-}
-
-export const InputBase = React.forwardRef<React.ElementRef<typeof Primitive.div>, InputBaseProps>(
-	({ autoFocus, disabled, className, onClick, ...props }, ref) => {
-		// eslint-disable-next-line @typescript-eslint/no-unused-vars
-		const [focused, setFocused] = React.useState(false)
-
-		const controlRef = React.useRef<HTMLElement>(null)
-
-		return (
-			<InputBaseContext.Provider
-				value={{
-					autoFocus,
-					controlRef,
-					disabled,
-					onFocusedChange: setFocused,
-				}}>
-				<Primitive.div
-					ref={ref}
-					onClick={composeEventHandlers(onClick, (event) => {
-						// Based on MUI's <InputBase /> implementation.
-						// https://github.com/mui/material-ui/blob/master/packages/mui-material/src/InputBase/InputBase.js#L458~L460
-						if (controlRef.current && event.currentTarget === event.target) {
-							controlRef.current.focus()
-						}
-					})}
-					className={cn(
-						"flex w-full text-vscode-input-foreground border border-vscode-dropdown-border  bg-vscode-input-background rounded-xs px-3 py-0.5 text-base transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus:outline-0 focus-visible:outline-none focus-visible:border-vscode-focusBorder disabled:cursor-not-allowed disabled:opacity-50",
-						disabled && "cursor-not-allowed opacity-50",
-						className,
-					)}
-					{...props}
-				/>
-			</InputBaseContext.Provider>
-		)
-	},
-)
-InputBase.displayName = "InputBase"
-
-export const InputBaseFlexWrapper = React.forwardRef<
-	React.ElementRef<typeof Primitive.div>,
-	React.ComponentPropsWithoutRef<typeof Primitive.div>
->(({ className, ...props }, ref) => (
-	<Primitive.div ref={ref} className={cn("flex flex-1 flex-wrap", className)} {...props} />
-))
-InputBaseFlexWrapper.displayName = "InputBaseFlexWrapper"
-
-export const InputBaseControl = React.forwardRef<
-	React.ElementRef<typeof Slot>,
-	React.ComponentPropsWithoutRef<typeof Slot>
->(({ onFocus, onBlur, ...props }, ref) => {
-	const { controlRef, autoFocus, disabled, onFocusedChange } = useInputBaseContext()
-
-	return (
-		<Slot
-			ref={composeRefs(controlRef, ref)}
-			autoFocus={autoFocus}
-			onFocus={composeEventHandlers(onFocus, () => onFocusedChange(true))}
-			onBlur={composeEventHandlers(onBlur, () => onFocusedChange(false))}
-			{...{ disabled }}
-			{...props}
-		/>
-	)
-})
-InputBaseControl.displayName = "InputBaseControl"
-
-export interface InputBaseAdornmentProps extends React.ComponentPropsWithoutRef<"div"> {
-	asChild?: boolean
-	disablePointerEvents?: boolean
-}
-
-export const InputBaseAdornment = React.forwardRef<React.ElementRef<"div">, InputBaseAdornmentProps>(
-	({ className, disablePointerEvents, asChild, children, ...props }, ref) => {
-		const Comp = asChild ? Slot : typeof children === "string" ? "p" : "div"
-
-		const isAction = React.isValidElement(children) && children.type === InputBaseAdornmentButton
-
-		return (
-			<Comp
-				ref={ref}
-				className={cn(
-					"flex items-center text-muted-foreground [&_svg]:size-4",
-					(!isAction || disablePointerEvents) && "pointer-events-none",
-					className,
-				)}
-				{...props}>
-				{children}
-			</Comp>
-		)
-	},
-)
-InputBaseAdornment.displayName = "InputBaseAdornment"
-
-export const InputBaseAdornmentButton = React.forwardRef<
-	React.ElementRef<typeof Button>,
-	React.ComponentPropsWithoutRef<typeof Button>
->(({ type = "button", variant = "ghost", size = "icon", disabled: disabledProp, className, ...props }, ref) => {
-	const { disabled } = useInputBaseContext()
-
-	return (
-		<Button
-			ref={ref}
-			type={type}
-			variant={variant}
-			size={size}
-			disabled={disabled || disabledProp}
-			className={cn("size-6", className)}
-			{...props}
-		/>
-	)
-})
-InputBaseAdornmentButton.displayName = "InputBaseAdornmentButton"
-
-export const InputBaseInput = React.forwardRef<
-	React.ElementRef<typeof Primitive.input>,
-	React.ComponentPropsWithoutRef<typeof Primitive.input>
->(({ className, ...props }, ref) => (
-	<Primitive.input
-		ref={ref}
-		className={cn(
-			"w-full flex-1 bg-transparent file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus:outline-none disabled:pointer-events-none",
-			className,
-		)}
-		{...props}
-	/>
-))
-InputBaseInput.displayName = "InputBaseInput"