Skip to content

Commit 5cc32e9

Browse files
authored
fix(amazonq): add custom modal dropdown for model selection (#6136)
* fix: remove modal overlay from dropdown, use direct positioning * fix: use generic fallback and reduce dropdown size * fix: consistent 11px font, reduced padding for balanced dropdown trigger * fix: remove debug console.log statements from dropdown implementation Removed all console.log statements from custom dropdown code as they are not needed in production. * trigger CI checks
1 parent 801f907 commit 5cc32e9

File tree

1 file changed

+296
-0
lines changed
  • plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview

1 file changed

+296
-0
lines changed

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/Browser.kt

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,14 +246,124 @@ class Browser(parent: Disposable, private val mynahAsset: Path, val project: Pro
246246
box-shadow: none !important;
247247
border-radius: 0 !important;
248248
}
249+
/* Hide native select - will be replaced with custom dropdown */
249250
select.mynah-form-input {
250251
-webkit-appearance: menulist !important;
251252
appearance: menulist !important;
252253
padding: 0 !important;
253254
}
255+
select.mynah-form-input[data-jb-replaced="true"] {
256+
position: absolute !important;
257+
width: 1px !important;
258+
height: 1px !important;
259+
opacity: 0 !important;
260+
pointer-events: none !important;
261+
}
254262
.mynah-select-handle {
255263
visibility: hidden;
256264
}
265+
266+
/* Custom dropdown styles with modal overlay */
267+
.jb-custom-select {
268+
position: absolute !important;
269+
width: 100%% !important;
270+
top: 0 !important;
271+
left: 0 !important;
272+
right: 0 !important;
273+
font-family: var(--mynah-font-family) !important;
274+
}
275+
276+
/* Ensure the parent wrapper has fixed width and relative positioning */
277+
.mynah-form-input-wrapper {
278+
position: relative !important;
279+
width: 100%% !important;
280+
min-height: 40px !important;
281+
}
282+
283+
.jb-custom-select-trigger {
284+
display: flex !important;
285+
align-items: center !important;
286+
justify-content: space-between !important;
287+
padding: 8px 8px 8px 6px !important;
288+
background: var(--mynah-color-bg) !important;
289+
border: 1px solid var(--mynah-color-border-default) !important;
290+
border-radius: 4px !important;
291+
cursor: pointer !important;
292+
color: var(--mynah-color-text-default) !important;
293+
width: 100%% !important;
294+
box-sizing: border-box !important;
295+
}
296+
297+
.jb-custom-select-trigger > span:first-child {
298+
white-space: nowrap !important;
299+
}
300+
301+
.jb-custom-select-trigger:hover {
302+
border-color: var(--mynah-color-border-hover, var(--mynah-color-border-default)) !important;
303+
}
304+
305+
.jb-custom-select-arrow {
306+
flex-shrink: 0 !important;
307+
width: 0 !important;
308+
height: 0 !important;
309+
border-left: 4px solid transparent !important;
310+
border-right: 4px solid transparent !important;
311+
border-top: 4px solid var(--mynah-color-text-default) !important;
312+
margin-left: 6px !important;
313+
transition: transform 0.2s !important;
314+
}
315+
316+
.jb-custom-select-arrow.open {
317+
transform: rotate(180deg) !important;
318+
}
319+
320+
/* Dropdown menu - floats with high z-index */
321+
.jb-custom-select-dropdown {
322+
position: fixed !important;
323+
background: var(--mynah-card-bg) !important;
324+
border: 1px solid var(--mynah-color-border-default) !important;
325+
border-radius: 6px !important;
326+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4) !important;
327+
min-width: 200px !important;
328+
max-width: 300px !important;
329+
overflow: hidden !important;
330+
z-index: 2147483647 !important;
331+
display: none !important;
332+
}
333+
334+
.jb-custom-select-dropdown.open {
335+
display: block !important;
336+
}
337+
338+
.jb-custom-select-option {
339+
padding: 6px 14px !important;
340+
cursor: pointer !important;
341+
font-size: 11px !important;
342+
color: var(--mynah-color-text-default) !important;
343+
background: var(--mynah-card-bg) !important;
344+
border-bottom: 1px solid var(--mynah-color-border-default) !important;
345+
transition: background 0.15s !important;
346+
}
347+
348+
.jb-custom-select-option:last-child {
349+
border-bottom: none !important;
350+
}
351+
352+
.jb-custom-select-option:hover {
353+
background: var(--mynah-card-bg-alternate) !important;
354+
color: var(--mynah-color-text-strong) !important;
355+
}
356+
357+
.jb-custom-select-option.selected {
358+
background: var(--mynah-color-button) !important;
359+
color: var(--mynah-color-button-reverse) !important;
360+
font-weight: 500 !important;
361+
}
362+
363+
.jb-custom-select-option.selected:hover {
364+
background: var(--mynah-color-button) !important;
365+
filter: brightness(1.1) !important;
366+
}
257367
.mynah-ui-spinner-container > span.mynah-ui-spinner-logo-part > .mynah-ui-spinner-logo-mask.text {
258368
will-change: transform !important;
259369
transform: translateZ(0) !important;
@@ -269,6 +379,192 @@ class Browser(parent: Disposable, private val mynahAsset: Path, val project: Pro
269379
<script type="text/javascript">
270380
${ThemeBrowserAdapter().buildJsCodeToUpdateTheme(EditorThemeAdapter.getThemeFromIde())}
271381
</script>
382+
<script type="text/javascript">
383+
// Custom dropdown for JetBrains
384+
(function() {
385+
let currentOpenDropdown = null;
386+
387+
function replaceSelectWithCustomDropdown() {
388+
const selectElements = document.querySelectorAll('select.mynah-form-input');
389+
390+
selectElements.forEach(function(select) {
391+
// Skip if already replaced
392+
if (select.hasAttribute('data-jb-replaced')) {
393+
return;
394+
}
395+
396+
select.setAttribute('data-jb-replaced', 'true');
397+
398+
// Create custom dropdown container
399+
const container = document.createElement('div');
400+
container.className = 'jb-custom-select';
401+
402+
// Create trigger button
403+
const trigger = document.createElement('div');
404+
trigger.className = 'jb-custom-select-trigger';
405+
406+
const selectedText = document.createElement('span');
407+
selectedText.textContent = select.options[select.selectedIndex]?.text || '';
408+
409+
function adjustFontSize(text, element) {
410+
element.style.fontSize = '11px';
411+
}
412+
413+
// Set initial font size
414+
adjustFontSize(selectedText.textContent, trigger);
415+
416+
const arrow = document.createElement('span');
417+
arrow.className = 'jb-custom-select-arrow';
418+
419+
trigger.appendChild(selectedText);
420+
trigger.appendChild(arrow);
421+
422+
// Create dropdown menu
423+
const dropdown = document.createElement('div');
424+
dropdown.className = 'jb-custom-select-dropdown';
425+
426+
// Add options
427+
if (select.options.length === 0) {
428+
const debugOption = document.createElement('div');
429+
debugOption.className = 'jb-custom-select-option';
430+
debugOption.textContent = 'No models available';
431+
debugOption.style.color = '#ff6b6b';
432+
dropdown.appendChild(debugOption);
433+
}
434+
435+
for (let i = 0; i < select.options.length; i++) {
436+
const option = document.createElement('div');
437+
option.className = 'jb-custom-select-option';
438+
option.textContent = select.options[i].text || ('Option ' + i);
439+
option.setAttribute('data-value', select.options[i].value);
440+
441+
if (i === select.selectedIndex) {
442+
option.classList.add('selected');
443+
}
444+
445+
option.addEventListener('click', function(e) {
446+
e.stopPropagation();
447+
448+
// Update selected value
449+
select.selectedIndex = i;
450+
451+
// Trigger change event on original select
452+
const event = new Event('change', { bubbles: true });
453+
select.dispatchEvent(event);
454+
455+
// Update UI
456+
selectedText.textContent = this.textContent;
457+
458+
// Adjust font size for new text
459+
adjustFontSize(this.textContent, trigger);
460+
461+
// Update selected class
462+
dropdown.querySelectorAll('.jb-custom-select-option').forEach(function(opt) {
463+
opt.classList.remove('selected');
464+
});
465+
this.classList.add('selected');
466+
467+
// Close dropdown
468+
dropdown.classList.remove('open');
469+
arrow.classList.remove('open');
470+
currentOpenDropdown = null;
471+
});
472+
473+
dropdown.appendChild(option);
474+
}
475+
476+
// Toggle dropdown on trigger click
477+
trigger.addEventListener('click', function(e) {
478+
e.stopPropagation();
479+
480+
// Close any other open dropdown
481+
if (currentOpenDropdown && currentOpenDropdown !== dropdown) {
482+
currentOpenDropdown.classList.remove('open');
483+
}
484+
485+
// Toggle this dropdown
486+
const isOpening = !dropdown.classList.contains('open');
487+
488+
if (isOpening) {
489+
// Position dropdown near trigger (smart positioning)
490+
const rect = trigger.getBoundingClientRect();
491+
dropdown.style.left = rect.left + 'px';
492+
493+
// Check if more space above or below
494+
const spaceAbove = rect.top;
495+
const spaceBelow = window.innerHeight - rect.bottom;
496+
497+
if (spaceAbove > spaceBelow) {
498+
// More space above - position above trigger
499+
dropdown.style.bottom = (window.innerHeight - rect.top) + 'px';
500+
dropdown.style.top = 'auto';
501+
} else {
502+
// More space below - position below trigger
503+
dropdown.style.top = rect.bottom + 'px';
504+
dropdown.style.bottom = 'auto';
505+
}
506+
507+
// Show dropdown
508+
dropdown.classList.add('open');
509+
arrow.classList.add('open');
510+
currentOpenDropdown = dropdown;
511+
} else {
512+
// Close dropdown
513+
dropdown.classList.remove('open');
514+
arrow.classList.remove('open');
515+
currentOpenDropdown = null;
516+
}
517+
});
518+
519+
// Build custom dropdown
520+
container.appendChild(trigger);
521+
522+
// Append dropdown to body for proper positioning
523+
document.body.appendChild(dropdown);
524+
525+
// Hide original select and insert custom dropdown trigger
526+
select.style.display = 'none';
527+
select.parentElement.insertBefore(container, select);
528+
});
529+
}
530+
531+
// Close dropdown when clicking outside
532+
document.addEventListener('click', function(e) {
533+
if (currentOpenDropdown) {
534+
currentOpenDropdown.classList.remove('open');
535+
// Also update arrow state
536+
const arrows = document.querySelectorAll('.jb-custom-select-arrow.open');
537+
arrows.forEach(function(arrow) {
538+
arrow.classList.remove('open');
539+
});
540+
currentOpenDropdown = null;
541+
}
542+
});
543+
544+
// Try to replace immediately
545+
replaceSelectWithCustomDropdown();
546+
547+
// Also watch for new selects being added (mynah-ui might add them dynamically)
548+
const observer = new MutationObserver(function() {
549+
replaceSelectWithCustomDropdown();
550+
});
551+
552+
// Start observing once DOM is ready
553+
if (document.body) {
554+
observer.observe(document.body, {
555+
childList: true,
556+
subtree: true
557+
});
558+
} else {
559+
document.addEventListener('DOMContentLoaded', function() {
560+
observer.observe(document.body, {
561+
childList: true,
562+
subtree: true
563+
});
564+
});
565+
}
566+
})();
567+
</script>
272568
</body>
273569
</html>
274570
""".trimIndent()

0 commit comments

Comments
 (0)