💄 style: center active model on open in model switch panel (#12215)

* 🐛 fix: correct eslint command syntax in lint-ts.sh script

*  feat: add scroll functionality for active model in List component

*  feat: improve scroll behavior for active model in List component
This commit is contained in:
sxjeru
2026-02-27 20:12:18 +08:00
committed by GitHub
parent c9b243ca31
commit 02f2498140
2 changed files with 62 additions and 21 deletions
+1 -1
View File
@@ -2,4 +2,4 @@
set -o pipefail
eslint src/ tests/ --fix --concurrency=auto --prune-suppressions
eslint src/ tests/ --concurrency=auto
eslint src/ tests/ --concurrency=auto
@@ -1,6 +1,6 @@
import { Flexbox } from '@lobehub/ui';
import { type FC, type ReactNode } from 'react';
import { useMemo } from 'react';
import { useCallback, useLayoutEffect, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useEnabledChatModels } from '@/hooks/useEnabledChatModels';
@@ -54,28 +54,69 @@ export const List: FC<ListProps> = ({
const activeKey = menuKey(provider, model);
// Set initial scroll position to keep active model centered
const listRef = useRef<HTMLDivElement | null>(null);
const activeNodeRef = useRef<HTMLDivElement | null>(null);
const hasInitializedPositionRef = useRef(false);
const activeItemRef = useCallback((node: HTMLDivElement | null) => {
activeNodeRef.current = node;
}, []);
const listHeight = panelHeight - TOOLBAR_HEIGHT - FOOTER_HEIGHT;
useLayoutEffect(() => {
if (hasInitializedPositionRef.current) return;
const container = listRef.current;
const activeNode = activeNodeRef.current;
if (!container || !activeNode) return;
const targetScrollTop =
activeNode.offsetTop - (container.clientHeight - activeNode.offsetHeight) / 2;
container.scrollTop = Math.max(0, targetScrollTop);
hasInitializedPositionRef.current = true;
}, [listHeight, activeKey]);
return (
<Flexbox className={styles.list} flex={1} style={{ height: listHeight }}>
{listItems.map((item, index) => (
<ListItemRenderer
activeKey={activeKey}
extraControls={extraControls}
item={item}
newLabel={newLabel}
key={menuKey(
'provider' in item && item.provider ? item.provider.id : '',
'model' in item && item.model
? item.model.id
: 'data' in item && item.data
? item.data.displayName
: `${item.type}-${index}`,
)}
onClose={handleClose}
onModelChange={handleModelChange}
/>
))}
<Flexbox className={styles.list} flex={1} ref={listRef} style={{ height: listHeight }}>
{listItems.map((item, index) => {
const itemKey = menuKey(
'provider' in item && item.provider ? item.provider.id : '',
'model' in item && item.model
? item.model.id
: 'data' in item && item.data
? item.data.displayName
: `${item.type}-${index}`,
);
const isActive =
(item.type === 'provider-model-item' &&
menuKey(item.provider.id, item.model.id) === activeKey) ||
(item.type === 'model-item-single' &&
menuKey(item.data.providers[0].id, item.data.model.id) === activeKey) ||
(item.type === 'model-item-multiple' &&
item.data.providers.some((p) => menuKey(p.id, item.data.model.id) === activeKey));
const renderItem = (key?: string) => (
<ListItemRenderer
activeKey={activeKey}
extraControls={extraControls}
item={item}
key={key}
newLabel={newLabel}
onClose={handleClose}
onModelChange={handleModelChange}
/>
);
return isActive ? (
<div key={itemKey} ref={activeItemRef}>
{renderItem()}
</div>
) : (
renderItem(itemKey)
);
})}
</Flexbox>
);
};