mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-07 05:39:05 +08:00
feat: implement select mode menu autoscroll for long mode lists
This commit is contained in:
parent
87bce69a13
commit
3f656ab202
@ -8,7 +8,7 @@ import { getModelUniqId } from '@renderer/services/ModelService'
|
|||||||
import { Model, Provider } from '@renderer/types'
|
import { Model, Provider } from '@renderer/types'
|
||||||
import { Avatar, Dropdown, Tooltip } from 'antd'
|
import { Avatar, Dropdown, Tooltip } from 'antd'
|
||||||
import { first, sortBy } from 'lodash'
|
import { first, sortBy } from 'lodash'
|
||||||
import { FC, useEffect, useMemo, useRef, useState } from 'react'
|
import { FC, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled, { createGlobalStyle } from 'styled-components'
|
import styled, { createGlobalStyle } from 'styled-components'
|
||||||
|
|
||||||
@ -27,6 +27,11 @@ const MentionModelsButton: FC<Props> = ({ mentionModels, onMentionModel: onSelec
|
|||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
const menuRef = useRef<HTMLDivElement>(null)
|
const menuRef = useRef<HTMLDivElement>(null)
|
||||||
const [searchText, setSearchText] = useState('')
|
const [searchText, setSearchText] = useState('')
|
||||||
|
const itemRefs = useRef<Array<HTMLDivElement | null>>([])
|
||||||
|
|
||||||
|
const setItemRef = (index: number, el: HTMLDivElement | null) => {
|
||||||
|
itemRefs.current[index] = el
|
||||||
|
}
|
||||||
|
|
||||||
const togglePin = async (modelId: string) => {
|
const togglePin = async (modelId: string) => {
|
||||||
const newPinnedModels = pinnedModels.includes(modelId)
|
const newPinnedModels = pinnedModels.includes(modelId)
|
||||||
@ -167,9 +172,17 @@ const MentionModelsButton: FC<Props> = ({ mentionModels, onMentionModel: onSelec
|
|||||||
loadPinnedModels()
|
loadPinnedModels()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
// Scroll to the first menu item when the mode selection menu opens
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (isOpen && flatModelItems.length > 0 && itemRefs.current[0]) {
|
||||||
|
itemRefs.current[0].scrollIntoView({ block: 'nearest' })
|
||||||
|
}
|
||||||
|
}, [isOpen, flatModelItems])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const showModelSelector = () => {
|
const showModelSelector = () => {
|
||||||
dropdownRef.current?.click()
|
dropdownRef.current?.click()
|
||||||
|
itemRefs.current = []
|
||||||
setIsOpen(true)
|
setIsOpen(true)
|
||||||
setSelectedIndex(0)
|
setSelectedIndex(0)
|
||||||
setSearchText('')
|
setSearchText('')
|
||||||
@ -180,10 +193,18 @@ const MentionModelsButton: FC<Props> = ({ mentionModels, onMentionModel: onSelec
|
|||||||
|
|
||||||
if (e.key === 'ArrowDown') {
|
if (e.key === 'ArrowDown') {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
setSelectedIndex((prev) => (prev < flatModelItems.length - 1 ? prev + 1 : 0))
|
setSelectedIndex((prev) => {
|
||||||
|
const newIndex = prev < flatModelItems.length - 1 ? prev + 1 : 0
|
||||||
|
itemRefs.current[newIndex]?.scrollIntoView({ block: 'nearest' })
|
||||||
|
return newIndex
|
||||||
|
})
|
||||||
} else if (e.key === 'ArrowUp') {
|
} else if (e.key === 'ArrowUp') {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : flatModelItems.length - 1))
|
setSelectedIndex((prev) => {
|
||||||
|
const newIndex = prev > 0 ? prev - 1 : flatModelItems.length - 1
|
||||||
|
itemRefs.current[newIndex]?.scrollIntoView({ block: 'nearest' })
|
||||||
|
return newIndex
|
||||||
|
})
|
||||||
} else if (e.key === 'Enter') {
|
} else if (e.key === 'Enter') {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (selectedIndex >= 0 && selectedIndex < flatModelItems.length) {
|
if (selectedIndex >= 0 && selectedIndex < flatModelItems.length) {
|
||||||
@ -250,15 +271,20 @@ const MentionModelsButton: FC<Props> = ({ mentionModels, onMentionModel: onSelec
|
|||||||
<div key={group.key} className="ant-dropdown-menu-item-group">
|
<div key={group.key} className="ant-dropdown-menu-item-group">
|
||||||
<div className="ant-dropdown-menu-item-group-title">{group.label}</div>
|
<div className="ant-dropdown-menu-item-group-title">{group.label}</div>
|
||||||
<div>
|
<div>
|
||||||
{group.children.map((item, idx) => (
|
{group.children.map((item, idx) => {
|
||||||
<div
|
// calculate item global idx
|
||||||
key={item.key}
|
const index = startIndex + idx
|
||||||
className={`ant-dropdown-menu-item ${selectedIndex === startIndex + idx ? 'ant-dropdown-menu-item-selected' : ''}`}
|
return (
|
||||||
onClick={item.onClick}>
|
<div
|
||||||
<span className="ant-dropdown-menu-item-icon">{item.icon}</span>
|
key={item.key}
|
||||||
{item.label}
|
ref={(el) => setItemRef(index, el)}
|
||||||
</div>
|
className={`ant-dropdown-menu-item ${selectedIndex === index ? 'ant-dropdown-menu-item-selected' : ''}`}
|
||||||
))}
|
onClick={item.onClick}>
|
||||||
|
<span className="ant-dropdown-menu-item-icon">{item.icon}</span>
|
||||||
|
{item.label}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user