Skip to content

Commit c288b11

Browse files
committed
make skill picker a searchable combobox
1 parent 86efb61 commit c288b11

1 file changed

Lines changed: 97 additions & 5 deletions

File tree

packages/ui/src/features/loops/components/LoopSkillFields.tsx

Lines changed: 97 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
1+
import { CaretDown } from "@phosphor-icons/react";
12
import { isUploadableSkillSource } from "@posthog/core/message-editor/skillTags";
23
import { useHostTRPC } from "@posthog/host-router/react";
4+
import {
5+
Combobox,
6+
ComboboxContent,
7+
ComboboxEmpty,
8+
ComboboxInput,
9+
ComboboxItem,
10+
ComboboxList,
11+
ComboboxTrigger,
12+
Button as QuillButton,
13+
} from "@posthog/quill";
314
import { SettingsOptionSelect } from "@posthog/ui/features/settings/SettingsOptionSelect";
415
import { useHostCapabilities } from "@posthog/ui/shell/useHostCapabilities";
516
import { TextArea } from "@radix-ui/themes";
617
import { useQuery } from "@tanstack/react-query";
7-
import { useRef } from "react";
18+
import { useRef, useState } from "react";
819
import type { LoopFormValues } from "../loopFormTypes";
920
import type { LoopSkillDraft } from "../loopSkill";
1021
import { Field } from "./LoopFormPrimitives";
@@ -31,6 +42,89 @@ function pickableSkillLabel(
3142
return `${skill.name} (${origin})`;
3243
}
3344

45+
interface SkillOption {
46+
value: string;
47+
label: string;
48+
}
49+
50+
/** Searchable skill picker, same quill Combobox shape as the repository picker
51+
* on the new-task page. Items are the (unique) labels so cmdk's filter matches
52+
* what the user sees. */
53+
function SkillCombobox({
54+
options,
55+
selectedValue,
56+
disabled,
57+
onValueChange,
58+
}: {
59+
options: SkillOption[];
60+
selectedValue: string;
61+
disabled: boolean;
62+
onValueChange: (value: string) => void;
63+
}) {
64+
const triggerRef = useRef<HTMLButtonElement>(null);
65+
const [open, setOpen] = useState(false);
66+
const [searchQuery, setSearchQuery] = useState("");
67+
const selectedLabel =
68+
options.find((option) => option.value === selectedValue)?.label ?? null;
69+
70+
return (
71+
<Combobox
72+
items={options.map((option) => option.label)}
73+
value={selectedLabel}
74+
onValueChange={(label) => {
75+
const picked = options.find((option) => option.label === label);
76+
if (picked) {
77+
onValueChange(picked.value);
78+
}
79+
}}
80+
open={open}
81+
onOpenChange={(nextOpen) => {
82+
setOpen(nextOpen);
83+
if (!nextOpen) {
84+
setSearchQuery("");
85+
}
86+
}}
87+
inputValue={searchQuery}
88+
onInputValueChange={setSearchQuery}
89+
disabled={disabled}
90+
>
91+
<ComboboxTrigger
92+
render={
93+
<QuillButton
94+
ref={triggerRef}
95+
variant="outline"
96+
size="sm"
97+
disabled={disabled}
98+
aria-label="Skill"
99+
className="w-full justify-between"
100+
>
101+
<span className="min-w-0 truncate">
102+
{selectedLabel ?? "Pick a skill…"}
103+
</span>
104+
<CaretDown size={10} weight="bold" className="shrink-0" />
105+
</QuillButton>
106+
}
107+
/>
108+
<ComboboxContent
109+
anchor={triggerRef}
110+
side="bottom"
111+
sideOffset={6}
112+
className="min-w-[280px]"
113+
>
114+
<ComboboxInput placeholder="Search skills..." />
115+
<ComboboxEmpty>No skills found.</ComboboxEmpty>
116+
<ComboboxList>
117+
{(label: string) => (
118+
<ComboboxItem key={label} value={label}>
119+
{label}
120+
</ComboboxItem>
121+
)}
122+
</ComboboxList>
123+
</ComboboxContent>
124+
</Combobox>
125+
);
126+
}
127+
34128
export function LoopInstructionsFields({
35129
values,
36130
disabled,
@@ -148,12 +242,10 @@ export function LoopInstructionsFields({
148242
required
149243
hint="The skill is snapshotted when you save; every run installs that snapshot into its sandbox."
150244
>
151-
<SettingsOptionSelect
152-
ariaLabel="Skill"
153-
value={selectedValue}
245+
<SkillCombobox
154246
options={skillOptions}
247+
selectedValue={selectedValue}
155248
disabled={disabled}
156-
placeholder="Pick a skill…"
157249
onValueChange={handleSkillChange}
158250
/>
159251
</Field>

0 commit comments

Comments
 (0)