fix: make model selector dropdowns responsive to viewport

This commit is contained in:
Bohdan Triapitsyn
2026-01-04 01:24:29 +02:00
parent d86105fcb2
commit 9d1aceeb6d
4 changed files with 21 additions and 4 deletions
+1 -1
View File
@@ -2847,7 +2847,7 @@ dependencies = [
[[package]] [[package]]
name = "openchamber-desktop" name = "openchamber-desktop"
version = "1.4.1" version = "1.4.3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"axum", "axum",
@@ -1870,7 +1870,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({ className }) => {
</div> </div>
{/* Scrollable content */} {/* Scrollable content */}
<ScrollableOverlay outerClassName="max-h-[400px] flex-1"> <ScrollableOverlay outerClassName="max-h-[min(400px,calc(100dvh-12rem))] flex-1">
<div className="p-1"> <div className="p-1">
{!hasResults && ( {!hasResults && (
<div className="px-2 py-4 text-center typography-meta text-muted-foreground"> <div className="px-2 py-4 text-center typography-meta text-muted-foreground">
@@ -111,8 +111,10 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
const [isOpen, setIsOpen] = React.useState(false); const [isOpen, setIsOpen] = React.useState(false);
const [searchQuery, setSearchQuery] = React.useState(''); const [searchQuery, setSearchQuery] = React.useState('');
const [selectedIndex, setSelectedIndex] = React.useState(0); const [selectedIndex, setSelectedIndex] = React.useState(0);
const [availableHeight, setAvailableHeight] = React.useState<number | null>(null);
const searchInputRef = React.useRef<HTMLInputElement>(null); const searchInputRef = React.useRef<HTMLInputElement>(null);
const dropdownRef = React.useRef<HTMLDivElement>(null); const dropdownRef = React.useRef<HTMLDivElement>(null);
const triggerRef = React.useRef<HTMLButtonElement>(null);
const itemRefs = React.useRef<(HTMLButtonElement | null)[]>([]); const itemRefs = React.useRef<(HTMLButtonElement | null)[]>([]);
// Count occurrences of each model for display purposes // Count occurrences of each model for display purposes
@@ -193,6 +195,17 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
const hasResults = filteredFavorites.length > 0 || filteredRecents.length > 0 || filteredProviders.length > 0; const hasResults = filteredFavorites.length > 0 || filteredRecents.length > 0 || filteredProviders.length > 0;
// Calculate available height when dropdown opens
React.useEffect(() => {
if (isOpen && triggerRef.current) {
const rect = triggerRef.current.getBoundingClientRect();
// Space above trigger minus padding from top edge
const spaceAbove = rect.top - 100;
// Cap at 400px max, minimum 150px
setAvailableHeight(Math.max(150, Math.min(400, spaceAbove)));
}
}, [isOpen]);
// Focus search input when opened // Focus search input when opened
React.useEffect(() => { React.useEffect(() => {
if (isOpen && searchInputRef.current) { if (isOpen && searchInputRef.current) {
@@ -280,6 +293,7 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
{/* Add model button (dropdown trigger) */} {/* Add model button (dropdown trigger) */}
<div className="relative" ref={dropdownRef}> <div className="relative" ref={dropdownRef}>
<Button <Button
ref={triggerRef}
type="button" type="button"
variant="outline" variant="outline"
size="sm" size="sm"
@@ -369,7 +383,10 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
</div> </div>
{/* Models list */} {/* Models list */}
<ScrollableOverlay outerClassName="max-h-[400px] flex-1"> <ScrollableOverlay
outerClassName="flex-1"
style={{ maxHeight: availableHeight ? `${availableHeight}px` : '300px' }}
>
<div className="p-1"> <div className="p-1">
{!hasResults && ( {!hasResults && (
<div className="px-2 py-4 text-center typography-meta text-muted-foreground"> <div className="px-2 py-4 text-center typography-meta text-muted-foreground">
@@ -563,7 +563,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
</div> </div>
{/* Scrollable content */} {/* Scrollable content */}
<ScrollableOverlay outerClassName="max-h-[400px] flex-1"> <ScrollableOverlay outerClassName="max-h-[min(400px,calc(100dvh-12rem))] flex-1">
<div className="p-1"> <div className="p-1">
{/* Not selected option */} {/* Not selected option */}
<div <div