Skip to content

Commit 60f09e1

Browse files
committed
refactor(wip): general UI improvements
1 parent 1924e10 commit 60f09e1

File tree

7 files changed

+172
-103
lines changed

7 files changed

+172
-103
lines changed

webview-ui/src/components/prompts/PromptsView.tsx

Lines changed: 156 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import React, { useState, useEffect, useMemo, useCallback } from "react"
22
import { Button } from "@/components/ui/button"
33
import {
44
VSCodeTextArea,
5-
VSCodeDropdown,
6-
VSCodeOption,
75
VSCodeTextField,
86
VSCodeCheckbox,
97
VSCodeRadioGroup,
@@ -29,6 +27,25 @@ import { Tab, TabContent, TabHeader } from "../common/Tab"
2927
import i18next from "i18next"
3028
import { useAppTranslation } from "@src/i18n/TranslationContext"
3129
import { Trans } from "react-i18next"
30+
import {
31+
Command,
32+
CommandEmpty,
33+
CommandGroup,
34+
CommandInput,
35+
CommandItem,
36+
CommandList,
37+
Input,
38+
Popover,
39+
PopoverContent,
40+
PopoverTrigger,
41+
Select,
42+
SelectContent,
43+
SelectItem,
44+
SelectTrigger,
45+
SelectValue,
46+
Textarea,
47+
} from "../ui"
48+
import { ChevronsUpDown, X } from "lucide-react"
3249

3350
// Get all available groups that should show in prompts view
3451
const availableGroups = (Object.keys(TOOL_GROUPS) as ToolGroup[]).filter((group) => !TOOL_GROUPS[group].alwaysAvailable)
@@ -73,6 +90,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
7390
const [isCreateModeDialogOpen, setIsCreateModeDialogOpen] = useState(false)
7491
const [activeSupportTab, setActiveSupportTab] = useState<SupportPromptType>("ENHANCE")
7592
const [isSystemPromptDisclosureOpen, setIsSystemPromptDisclosureOpen] = useState(false)
93+
const [searchValue, setSearchValue] = useState("")
94+
const [open, onOpenChange] = useState(false)
7695

7796
// Direct update functions
7897
const updateAgentPrompt = useCallback(
@@ -471,23 +490,80 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
471490
</div>
472491

473492
<div className="flex gap-2 items-center mb-3 flex-wrap py-1">
474-
{modes.map((modeConfig) => {
475-
const isActive = mode === modeConfig.slug
476-
return (
477-
<button
478-
key={modeConfig.slug}
479-
data-testid={`${modeConfig.slug}-tab`}
480-
data-active={isActive ? "true" : "false"}
481-
onClick={() => handleModeSwitch(modeConfig)}
482-
className={`px-2 py-1 border-none rounded cursor-pointer font-bold ${
483-
isActive
484-
? "bg-vscode-button-background text-vscode-button-foreground opacity-100"
485-
: "bg-transparent text-vscode-foreground opacity-80"
486-
}`}>
487-
{modeConfig.name}
488-
</button>
489-
)
490-
})}
493+
<Popover open={open} onOpenChange={onOpenChange}>
494+
<PopoverTrigger asChild>
495+
<Button
496+
variant="combobox"
497+
role="combobox"
498+
aria-expanded={open}
499+
className="grow justify-between"
500+
// Use select-component data-testid for test compatibility
501+
data-testid="select-component">
502+
<div>{getCurrentMode()?.name}</div>
503+
<ChevronsUpDown className="opacity-50" />
504+
</Button>
505+
</PopoverTrigger>
506+
<PopoverContent className="p-0 w-[var(--radix-popover-trigger-width)]">
507+
<Command>
508+
<div className="relative">
509+
<CommandInput
510+
value={searchValue}
511+
onValueChange={setSearchValue}
512+
placeholder={t("settings:providers.searchPlaceholder")}
513+
className="h-9 mr-4"
514+
data-testid="profile-search-input"
515+
/>
516+
{searchValue.length > 0 && (
517+
<div className="absolute right-2 top-0 bottom-0 flex items-center justify-center">
518+
<X
519+
className="text-vscode-input-foreground opacity-50 hover:opacity-100 size-4 p-0.5 cursor-pointer"
520+
onClick={() => setSearchValue("")}
521+
/>
522+
</div>
523+
)}
524+
</div>
525+
<CommandList>
526+
<CommandEmpty>
527+
{searchValue && (
528+
<div className="py-2 px-1 text-sm">
529+
{t("settings:providers.noMatchFound")}
530+
</div>
531+
)}
532+
</CommandEmpty>
533+
<CommandGroup>
534+
{modes
535+
.filter((modeConfig) =>
536+
searchValue
537+
? modeConfig.name
538+
.toLowerCase()
539+
.includes(searchValue.toLowerCase()) ||
540+
modeConfig.slug
541+
.toLowerCase()
542+
.includes(searchValue.toLowerCase())
543+
: true,
544+
)
545+
.map((config) => (
546+
<CommandItem
547+
key={config.name}
548+
value={config.slug}
549+
onSelect={(value) => {
550+
handleModeSwitch(
551+
modes.find((m) => m.slug === value) || modes[0],
552+
)
553+
onOpenChange(false)
554+
}}
555+
data-testid={`profile-option-${config.slug}`}>
556+
<div className="flex-1 flex items-center justify-between">
557+
{config.name}
558+
<span className="text-foreground/70">{config.slug}</span>
559+
</div>
560+
</CommandItem>
561+
))}
562+
</CommandGroup>
563+
</CommandList>
564+
</Command>
565+
</PopoverContent>
566+
</Popover>
491567
</div>
492568
</div>
493569

@@ -498,7 +574,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
498574
<div className="flex-1">
499575
<div className="font-bold mb-1">{t("prompts:createModeDialog.name.label")}</div>
500576
<div className="flex gap-2">
501-
<VSCodeTextField
577+
<Input
502578
value={getModeProperty(findModeBySlug(mode, customModes), "name") ?? ""}
503579
onChange={(e: Event | React.FormEvent<HTMLElement>) => {
504580
const target =
@@ -553,15 +629,15 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
553629
<div className="text-sm text-vscode-descriptionForeground mb-2">
554630
{t("prompts:roleDefinition.description")}
555631
</div>
556-
<VSCodeTextArea
632+
<Textarea
557633
value={(() => {
558634
const customMode = findModeBySlug(mode, customModes)
559635
const prompt = customModePrompts?.[mode] as PromptComponent
560636
return customMode?.roleDefinition ?? prompt?.roleDefinition ?? getRoleDefinition(mode)
561637
})()}
562638
onChange={(e) => {
563639
const value =
564-
(e as CustomEvent)?.detail?.target?.value ||
640+
(e as unknown as CustomEvent)?.detail?.target?.value ||
565641
((e as any).target as HTMLTextAreaElement).value
566642
const customMode = findModeBySlug(mode, customModes)
567643
if (customMode) {
@@ -578,8 +654,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
578654
})
579655
}
580656
}}
657+
className="resize-y"
581658
rows={4}
582-
resize="vertical"
583659
style={{ width: "100%" }}
584660
data-testid={`${getCurrentMode()?.slug || "code"}-prompt-textarea`}
585661
/>
@@ -591,22 +667,25 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
591667
{t("prompts:apiConfiguration.title")}
592668
</div>
593669
<div style={{ marginBottom: "8px" }}>
594-
<VSCodeDropdown
595-
value={currentApiConfigName || ""}
596-
onChange={(e: any) => {
597-
const value = e.detail?.target?.value || e.target?.value
670+
<Select
671+
value={currentApiConfigName}
672+
onValueChange={(value) => {
598673
vscode.postMessage({
599674
type: "loadApiConfiguration",
600675
text: value,
601676
})
602-
}}
603-
className="w-full">
604-
{(listApiConfigMeta || []).map((config) => (
605-
<VSCodeOption key={config.id} value={config.name}>
606-
{config.name}
607-
</VSCodeOption>
608-
))}
609-
</VSCodeDropdown>
677+
}}>
678+
<SelectTrigger className="w-full">
679+
<SelectValue placeholder={t("settings:common.select")} />
680+
</SelectTrigger>
681+
<SelectContent>
682+
{(listApiConfigMeta || []).map((config) => (
683+
<SelectItem key={config.id} value={config.name}>
684+
{config.name}
685+
</SelectItem>
686+
))}
687+
</SelectContent>
688+
</Select>
610689
<div className="text-xs mt-1.5 text-vscode-descriptionForeground">
611690
{t("prompts:apiConfiguration.select")}
612691
</div>
@@ -736,7 +815,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
736815
modeName: getCurrentMode()?.name || "Code",
737816
})}
738817
</div>
739-
<VSCodeTextArea
818+
<Textarea
740819
value={(() => {
741820
const customMode = findModeBySlug(mode, customModes)
742821
const prompt = customModePrompts?.[mode] as PromptComponent
@@ -748,7 +827,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
748827
})()}
749828
onChange={(e) => {
750829
const value =
751-
(e as CustomEvent)?.detail?.target?.value ||
830+
(e as unknown as CustomEvent)?.detail?.target?.value ||
752831
((e as any).target as HTMLTextAreaElement).value
753832
const customMode = findModeBySlug(mode, customModes)
754833
if (customMode) {
@@ -768,8 +847,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
768847
}
769848
}}
770849
rows={4}
771-
resize="vertical"
772-
style={{ width: "100%" }}
850+
className="w-full resize-y"
773851
data-testid={`${getCurrentMode()?.slug || "code"}-custom-instructions-textarea`}
774852
/>
775853
<div
@@ -905,11 +983,11 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
905983
<div className="text-sm text-vscode-descriptionForeground mb-2">
906984
{t("prompts:globalCustomInstructions.description", { language: i18next.language })}
907985
</div>
908-
<VSCodeTextArea
986+
<Textarea
909987
value={customInstructions ?? ""}
910988
onChange={(e) => {
911989
const value =
912-
(e as CustomEvent)?.detail?.target?.value ||
990+
(e as unknown as CustomEvent)?.detail?.target?.value ||
913991
((e as any).target as HTMLTextAreaElement).value
914992
setCustomInstructions(value || undefined)
915993
vscode.postMessage({
@@ -918,8 +996,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
918996
})
919997
}}
920998
rows={4}
921-
resize="vertical"
922-
className="w-full"
999+
className="w-full resize-y"
9231000
data-testid="global-custom-instructions-textarea"
9241001
/>
9251002
<div className="text-xs text-vscode-descriptionForeground mt-1.5 mb-10">
@@ -968,28 +1045,20 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
9681045
flexWrap: "wrap",
9691046
padding: "4px 0",
9701047
}}>
971-
{Object.keys(supportPrompt.default).map((type) => (
972-
<button
973-
key={type}
974-
data-testid={`${type}-tab`}
975-
data-active={activeSupportTab === type ? "true" : "false"}
976-
onClick={() => setActiveSupportTab(type as SupportPromptType)}
977-
style={{
978-
padding: "4px 8px",
979-
border: "none",
980-
background: activeSupportTab === type ? "var(--vscode-button-background)" : "none",
981-
color:
982-
activeSupportTab === type
983-
? "var(--vscode-button-foreground)"
984-
: "var(--vscode-foreground)",
985-
cursor: "pointer",
986-
opacity: activeSupportTab === type ? 1 : 0.8,
987-
borderRadius: "3px",
988-
fontWeight: "bold",
989-
}}>
990-
{t(`prompts:supportPrompts.types.${type}.label`)}
991-
</button>
992-
))}
1048+
<Select
1049+
value={activeSupportTab}
1050+
onValueChange={(type) => setActiveSupportTab(type as SupportPromptType)}>
1051+
<SelectTrigger className="w-full">
1052+
<SelectValue placeholder={t("settings:common.select")} />
1053+
</SelectTrigger>
1054+
<SelectContent>
1055+
{Object.keys(supportPrompt.default).map((type) => (
1056+
<SelectItem key={type} value={type}>
1057+
{t(`prompts:supportPrompts.types.${type}.label`)}
1058+
</SelectItem>
1059+
))}
1060+
</SelectContent>
1061+
</Select>
9931062
</div>
9941063

9951064
{/* Support prompt description */}
@@ -1021,18 +1090,17 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
10211090
</Button>
10221091
</div>
10231092

1024-
<VSCodeTextArea
1093+
<Textarea
10251094
value={getSupportPromptValue(activeSupportTab)}
10261095
onChange={(e) => {
10271096
const value =
1028-
(e as CustomEvent)?.detail?.target?.value ||
1097+
(e as unknown as CustomEvent)?.detail?.target?.value ||
10291098
((e as any).target as HTMLTextAreaElement).value
10301099
const trimmedValue = value.trim()
10311100
updateSupportPrompt(activeSupportTab, trimmedValue || undefined)
10321101
}}
10331102
rows={6}
1034-
resize="vertical"
1035-
style={{ width: "100%" }}
1103+
className="resize-y w-full"
10361104
/>
10371105

10381106
{activeSupportTab === "ENHANCE" && (
@@ -1058,27 +1126,31 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
10581126
{t("prompts:supportPrompts.enhance.apiConfigDescription")}
10591127
</div>
10601128
</div>
1061-
<VSCodeDropdown
1129+
<Select
10621130
value={enhancementApiConfigId || ""}
1063-
data-testid="api-config-dropdown"
1064-
onChange={(e: any) => {
1065-
const value = e.detail?.target?.value || e.target?.value
1131+
onValueChange={(value) => {
10661132
setEnhancementApiConfigId(value)
10671133
vscode.postMessage({
10681134
type: "enhancementApiConfigId",
10691135
text: value,
10701136
})
1071-
}}
1072-
style={{ width: "300px" }}>
1073-
<VSCodeOption value="">
1074-
{t("prompts:supportPrompts.enhance.useCurrentConfig")}
1075-
</VSCodeOption>
1076-
{(listApiConfigMeta || []).map((config) => (
1077-
<VSCodeOption key={config.id} value={config.id}>
1078-
{config.name}
1079-
</VSCodeOption>
1080-
))}
1081-
</VSCodeDropdown>
1137+
}}>
1138+
<SelectTrigger data-testid="api-config-select">
1139+
<SelectValue
1140+
placeholder={t("prompts:supportPrompts.enhance.useCurrentConfig")}
1141+
/>
1142+
</SelectTrigger>
1143+
<SelectContent>
1144+
<SelectItem value="">
1145+
{t("prompts:supportPrompts.enhance.useCurrentConfig")}
1146+
</SelectItem>
1147+
{(listApiConfigMeta || []).map((config) => (
1148+
<SelectItem key={config.id} value={config.id}>
1149+
{config.name}
1150+
</SelectItem>
1151+
))}
1152+
</SelectContent>
1153+
</Select>
10821154
</div>
10831155
</div>
10841156

0 commit comments

Comments
 (0)