authorgravatar for 69873514+sandersonstabo@users.noreply.github.comss <69873514+sandersonstabo@users.noreply.github.com> 2026-07-18 13:22:37+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-18 13:22:37+02:00
loge63403fe9259b67f8beccdbf8117671ffefaf48f
tree4e10a4b969eb7e9cc5a588c3aa7758c39a594832
parentc0e161af069ec501a44d72c4e59d38d3cf85706d
signature Signed by PGP key B5690EEEBB952194

[fix/feat:ui] Preserve open-in editor brand colors (#3225)

Co-authored-by: Julius Marminge <julius0216@outlook.com>

1 files changed, 42 insertions(+), 5 deletions(-)

apps/web/src/components/chat/OpenInPicker.tsx+42-5
...@@ -31,111 +31,138 @@ import {...@@ -31,111 +31,138 @@ import {
31 RustRoverIcon,31 RustRoverIcon,
32 WebStormIcon,32 WebStormIcon,
33} from "../JetBrainsIcons";33} from "../JetBrainsIcons";
34import { isMacPlatform, isWindowsPlatform } from "~/lib/utils";34import { cn, isMacPlatform, isWindowsPlatform } from "~/lib/utils";
35import { shellEnvironment } from "~/state/shell";35import { shellEnvironment } from "~/state/shell";
36import { useAtomCommand } from "~/state/use-atom-command";36import { useAtomCommand } from "~/state/use-atom-command";
3737
38type OpenInOption = {
39 label: string;
40 Icon: Icon;
41 value: EditorId;
42 kind: "brand" | "generic";
43};
44
38const resolveOptions = (platform: string, availableEditors: ReadonlyArray<EditorId>) => {45const resolveOptions = (platform: string, availableEditors: ReadonlyArray<EditorId>) => {
39 const baseOptions: ReadonlyArray<{ label: string; Icon: Icon; value: EditorId }> = [46 const baseOptions: ReadonlyArray<OpenInOption> = [
40 {47 {
41 label: "Cursor",48 label: "Cursor",
42 Icon: CursorIcon,49 Icon: CursorIcon,
43 value: "cursor",50 value: "cursor",
51 kind: "brand",
44 },52 },
45 {53 {
46 label: "Trae",54 label: "Trae",
47 Icon: TraeIcon,55 Icon: TraeIcon,
48 value: "trae",56 value: "trae",
57 kind: "brand",
49 },58 },
50 {59 {
51 label: "Kiro",60 label: "Kiro",
52 Icon: KiroIcon,61 Icon: KiroIcon,
53 value: "kiro",62 value: "kiro",
63 kind: "brand",
54 },64 },
55 {65 {
56 label: "VS Code",66 label: "VS Code",
57 Icon: VisualStudioCode,67 Icon: VisualStudioCode,
58 value: "vscode",68 value: "vscode",
69 kind: "brand",
59 },70 },
60 {71 {
61 label: "VS Code Insiders",72 label: "VS Code Insiders",
62 Icon: VisualStudioCodeInsiders,73 Icon: VisualStudioCodeInsiders,
63 value: "vscode-insiders",74 value: "vscode-insiders",
75 kind: "brand",
64 },76 },
65 {77 {
66 label: "VSCodium",78 label: "VSCodium",
67 Icon: VSCodium,79 Icon: VSCodium,
68 value: "vscodium",80 value: "vscodium",
81 kind: "brand",
69 },82 },
70 {83 {
71 label: "Zed",84 label: "Zed",
72 Icon: Zed,85 Icon: Zed,
73 value: "zed",86 value: "zed",
87 kind: "brand",
74 },88 },
75 {89 {
76 label: "Antigravity",90 label: "Antigravity",
77 Icon: AntigravityIcon,91 Icon: AntigravityIcon,
78 value: "antigravity",92 value: "antigravity",
93 kind: "brand",
79 },94 },
80 {95 {
81 label: "IntelliJ IDEA",96 label: "IntelliJ IDEA",
82 Icon: IntelliJIdeaIcon,97 Icon: IntelliJIdeaIcon,
83 value: "idea",98 value: "idea",
99 kind: "brand",
84 },100 },
85 {101 {
86 label: "Aqua",102 label: "Aqua",
87 Icon: AquaIcon,103 Icon: AquaIcon,
88 value: "aqua",104 value: "aqua",
105 kind: "brand",
89 },106 },
90 {107 {
91 label: "CLion",108 label: "CLion",
92 Icon: CLionIcon,109 Icon: CLionIcon,
93 value: "clion",110 value: "clion",
111 kind: "brand",
94 },112 },
95 {113 {
96 label: "DataGrip",114 label: "DataGrip",
97 Icon: DataGripIcon,115 Icon: DataGripIcon,
98 value: "datagrip",116 value: "datagrip",
117 kind: "brand",
99 },118 },
100 {119 {
101 label: "DataSpell",120 label: "DataSpell",
102 Icon: DataSpellIcon,121 Icon: DataSpellIcon,
103 value: "dataspell",122 value: "dataspell",
123 kind: "brand",
104 },124 },
105 {125 {
106 label: "GoLand",126 label: "GoLand",
107 Icon: GoLandIcon,127 Icon: GoLandIcon,
108 value: "goland",128 value: "goland",
129 kind: "brand",
109 },130 },
110 {131 {
111 label: "PhpStorm",132 label: "PhpStorm",
112 Icon: PhpStormIcon,133 Icon: PhpStormIcon,
113 value: "phpstorm",134 value: "phpstorm",
135 kind: "brand",
114 },136 },
115 {137 {
116 label: "PyCharm",138 label: "PyCharm",
117 Icon: PyCharmIcon,139 Icon: PyCharmIcon,
118 value: "pycharm",140 value: "pycharm",
141 kind: "brand",
119 },142 },
120 {143 {
121 label: "Rider",144 label: "Rider",
122 Icon: RiderIcon,145 Icon: RiderIcon,
123 value: "rider",146 value: "rider",
147 kind: "brand",
124 },148 },
125 {149 {
126 label: "RubyMine",150 label: "RubyMine",
127 Icon: RubyMineIcon,151 Icon: RubyMineIcon,
128 value: "rubymine",152 value: "rubymine",
153 kind: "brand",
129 },154 },
130 {155 {
131 label: "RustRover",156 label: "RustRover",
132 Icon: RustRoverIcon,157 Icon: RustRoverIcon,
133 value: "rustrover",158 value: "rustrover",
159 kind: "brand",
134 },160 },
135 {161 {
136 label: "WebStorm",162 label: "WebStorm",
137 Icon: WebStormIcon,163 Icon: WebStormIcon,
138 value: "webstorm",164 value: "webstorm",
165 kind: "brand",
139 },166 },
140 {167 {
141 label: isMacPlatform(platform)168 label: isMacPlatform(platform)
...@@ -145,12 +172,17 @@ const resolveOptions = (platform: string, availableEditors: ReadonlyArray<Editor...@@ -145,12 +172,17 @@ const resolveOptions = (platform: string, availableEditors: ReadonlyArray<Editor
145 : "Files",172 : "Files",
146 Icon: FolderClosedIcon,173 Icon: FolderClosedIcon,
147 value: "file-manager",174 value: "file-manager",
175 kind: "generic",
148 },176 },
149 ];177 ];
150 const availableEditorSet = new Set(availableEditors);178 const availableEditorSet = new Set(availableEditors);
151 return baseOptions.filter((option) => availableEditorSet.has(option.value));179 return baseOptions.filter((option) => availableEditorSet.has(option.value));
152};180};
153181
182function getOpenInIconClass(kind: OpenInOption["kind"]) {
183 return cn(kind === "brand" ? "text-foreground opacity-100" : "text-muted-foreground");
184}
185
154export const OpenInPicker = memo(function OpenInPicker({186export const OpenInPicker = memo(function OpenInPicker({
155 environmentId,187 environmentId,
156 keybindings,188 keybindings,
...@@ -233,7 +265,12 @@ export const OpenInPicker = memo(function OpenInPicker({...@@ -233,7 +265,12 @@ export const OpenInPicker = memo(function OpenInPicker({
233 disabled={!preferredEditor || !openInCwd}265 disabled={!preferredEditor || !openInCwd}
234 onClick={() => openInEditor(preferredEditor)}266 onClick={() => openInEditor(preferredEditor)}
235 >267 >
236 {primaryOption?.Icon && <primaryOption.Icon aria-hidden="true" className="size-3.5" />}268 {primaryOption?.Icon && (
269 <primaryOption.Icon
270 aria-hidden="true"
271 className={cn("size-3.5", getOpenInIconClass(primaryOption.kind))}
272 />
273 )}
237 <span274 <span
238 className={275 className={
239 compact276 compact
...@@ -259,9 +296,9 @@ export const OpenInPicker = memo(function OpenInPicker({...@@ -259,9 +296,9 @@ export const OpenInPicker = memo(function OpenInPicker({
259 </MenuTrigger>296 </MenuTrigger>
260 <MenuPopup align="end">297 <MenuPopup align="end">
261 {options.length === 0 && <MenuItem disabled>No installed editors found</MenuItem>}298 {options.length === 0 && <MenuItem disabled>No installed editors found</MenuItem>}
262 {options.map(({ label, Icon, value }) => (299 {options.map(({ label, Icon, value, kind }) => (
263 <MenuItem key={value} onClick={() => openInEditor(value)}>300 <MenuItem key={value} onClick={() => openInEditor(value)}>
264 <Icon aria-hidden="true" className="text-muted-foreground" />301 <Icon aria-hidden="true" className={getOpenInIconClass(kind)} />
265 {label}302 {label}
266 {value === preferredEditor && openFavoriteEditorShortcutLabel && (303 {value === preferredEditor && openFavoriteEditorShortcutLabel && (
267 <MenuShortcut>{openFavoriteEditorShortcutLabel}</MenuShortcut>304 <MenuShortcut>{openFavoriteEditorShortcutLabel}</MenuShortcut>