feat(ui): conditional model selector shadow logic (#681)

* feat(ui): conditional model selector shadow logic (#678)

- Update ModelSelectorList to conditionally render shadow based on scroll state
- Update CommandList to forward ref for scroll detection
- Resolves #678

* Update components/ui/command.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix(ui): update listRef type to match CommandList forwarded ref

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Khairil Rahman Hakiki
2026-02-07 10:13:56 +07:00
committed by GitHub
parent d752524851
commit 7fbc857d3a
2 changed files with 65 additions and 20 deletions

View File

@@ -1,5 +1,6 @@
import { Cloud } from "lucide-react"
import type { ComponentProps, ReactNode } from "react"
import type { ComponentProps, ElementRef, ReactNode } from "react"
import { useEffect, useRef, useState } from "react"
import {
Command,
CommandDialog,
@@ -69,20 +70,62 @@ export type ModelSelectorListProps = ComponentProps<typeof CommandList>
export const ModelSelectorList = ({
className,
...props
}: ModelSelectorListProps) => (
<div className="relative">
<CommandList
className={cn(
// Hide scrollbar on all platforms
"[&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]",
className,
)}
{...props}
/>
{/* Bottom shadow indicator for scrollable content */}
<div className="pointer-events-none absolute bottom-0 left-0 right-0 h-12 bg-gradient-to-t from-muted/80 via-muted/40 to-transparent" />
</div>
)
}: ModelSelectorListProps) => {
const listRef = useRef<ElementRef<typeof CommandList>>(null)
const [showShadow, setShowShadow] = useState(false)
useEffect(() => {
const listElement = listRef.current
if (!listElement) return
const checkScroll = () => {
const { scrollTop, scrollHeight, clientHeight } = listElement
// Show shadow if there is more content below
// Using a small threshold to handle fractional pixel rendering
setShowShadow(
scrollHeight > Math.ceil(scrollTop + clientHeight) + 1,
)
}
// Initial check
checkScroll()
// Event listeners
listElement.addEventListener("scroll", checkScroll)
window.addEventListener("resize", checkScroll)
// Observe content changes (e.g. async loading of items)
const observer = new MutationObserver(checkScroll)
observer.observe(listElement, { childList: true, subtree: true })
return () => {
listElement.removeEventListener("scroll", checkScroll)
window.removeEventListener("resize", checkScroll)
observer.disconnect()
}
}, [])
return (
<div className="relative">
<CommandList
ref={listRef}
className={cn(
// Hide scrollbar on all platforms
"[&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]",
className,
)}
{...props}
/>
{/* Bottom shadow indicator for scrollable content */}
<div
className={cn(
"pointer-events-none absolute bottom-0 left-0 right-0 h-12 bg-gradient-to-t from-muted/80 via-muted/40 to-transparent transition-opacity duration-200",
showShadow ? "opacity-100" : "opacity-0",
)}
/>
</div>
)
}
export type ModelSelectorEmptyProps = ComponentProps<typeof CommandEmpty>