💄 style: improve editing scroll experience (#7149)

This commit is contained in:
Arvin Xu
2025-03-26 08:26:47 +08:00
committed by GitHub
parent e32c8e7fc3
commit 816331fd8d
6 changed files with 53 additions and 36 deletions
@@ -64,7 +64,7 @@ const MainChatItem = memo<ThreadChatItemProps>(({ id, index }) => {
const placement = displayMode === 'chat' && userRole === 'user' ? 'end' : 'start';
const actionBar = useMemo(() => <ActionsBar id={id} />, [id]);
const actionBar = useMemo(() => <ActionsBar id={id} index={index} />, [id]);
return (
<ChatItem
@@ -1,9 +1,10 @@
import { ActionEvent, ActionIconGroup, type ActionIconGroupProps } from '@lobehub/ui';
import { App } from 'antd';
import isEqual from 'fast-deep-equal';
import { memo, useCallback } from 'react';
import { memo, use, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { VirtuosoContext } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
import { useChatStore } from '@/store/chat';
import { chatSelectors } from '@/store/chat/selectors';
import { MessageRoleType } from '@/types/message';
@@ -29,9 +30,10 @@ const ActionsBar = memo<ActionsBarProps>((props) => {
interface ActionsProps {
id: string;
inPortalThread?: boolean;
index: number;
}
const Actions = memo<ActionsProps>(({ id, inPortalThread }) => {
const Actions = memo<ActionsProps>(({ id, inPortalThread, index }) => {
const item = useChatStore(chatSelectors.getMessageById(id), isEqual);
const { t } = useTranslation('common');
const [
@@ -58,12 +60,15 @@ const Actions = memo<ActionsProps>(({ id, inPortalThread }) => {
s.toggleMessageEditing,
]);
const { message } = App.useApp();
const virtuosoRef = use(VirtuosoContext);
const handleActionClick = useCallback(
async (action: ActionEvent) => {
switch (action.key) {
case 'edit': {
toggleMessageEditing(id, true);
virtuosoRef?.current?.scrollIntoView({ align: 'start', behavior: 'auto', index });
}
}
if (!item) return;
@@ -3,10 +3,11 @@
import { ChatItem } from '@lobehub/ui';
import { createStyles } from 'antd-style';
import isEqual from 'fast-deep-equal';
import { MouseEventHandler, ReactNode, memo, useCallback, useMemo } from 'react';
import { MouseEventHandler, ReactNode, memo, use, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { VirtuosoContext } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
import { useAgentStore } from '@/store/agent';
import { agentChatConfigSelectors } from '@/store/agent/selectors';
import { useChatStore } from '@/store/chat';
@@ -64,6 +65,7 @@ const Item = memo<ChatListItemProps>(
endRender,
disableEditing,
inPortalThread = false,
index,
}) => {
const { t } = useTranslation('common');
const { styles, cx } = useStyles();
@@ -188,6 +190,7 @@ const Item = memo<ChatListItemProps>(
);
const onChange = useCallback((value: string) => updateMessageContent(id, value), [id]);
const virtuosoRef = use(VirtuosoContext);
const onDoubleClick = useCallback<MouseEventHandler<HTMLDivElement>>(
(e) => {
@@ -195,6 +198,8 @@ const Item = memo<ChatListItemProps>(
if (item.id === 'default' || item.error) return;
if (item.role && ['assistant', 'user'].includes(item.role) && e.altKey) {
toggleMessageEditing(id, true);
virtuosoRef?.current?.scrollIntoView({ align: 'start', behavior: 'auto', index });
}
},
[item, disableEditing],
@@ -0,0 +1,4 @@
import { RefObject, createContext } from 'react';
import { VirtuosoHandle } from 'react-virtuoso';
export const VirtuosoContext = createContext<RefObject<VirtuosoHandle | null> | null>(null);
@@ -13,6 +13,7 @@ import { chatSelectors } from '@/store/chat/selectors';
import AutoScroll from '../AutoScroll';
import SkeletonList from '../SkeletonList';
import { VirtuosoContext } from './VirtuosoContext';
interface VirtualizedListProps {
dataSource: string[];
@@ -69,38 +70,40 @@ const VirtualizedList = memo<VirtualizedListProps>(({ mobile, dataSource, itemCo
);
return (
<Flexbox height={'100%'}>
<Virtuoso
atBottomStateChange={setAtBottom}
atBottomThreshold={50 * (mobile ? 2 : 1)}
computeItemKey={(_, item) => item}
data={dataSource}
followOutput={getFollowOutput}
increaseViewportBy={overscan}
initialTopMostItemIndex={dataSource?.length - 1}
isScrolling={setIsScrolling}
itemContent={itemContent}
overscan={overscan}
ref={virtuosoRef}
/>
<AutoScroll
atBottom={atBottom}
isScrolling={isScrolling}
onScrollToBottom={(type) => {
const virtuoso = virtuosoRef.current;
switch (type) {
case 'auto': {
virtuoso?.scrollToIndex({ align: 'end', behavior: 'auto', index: 'LAST' });
break;
<VirtuosoContext value={virtuosoRef}>
<Flexbox height={'100%'}>
<Virtuoso
atBottomStateChange={setAtBottom}
atBottomThreshold={50 * (mobile ? 2 : 1)}
computeItemKey={(_, item) => item}
data={dataSource}
followOutput={getFollowOutput}
increaseViewportBy={overscan}
initialTopMostItemIndex={dataSource?.length - 1}
isScrolling={setIsScrolling}
itemContent={itemContent}
overscan={overscan}
ref={virtuosoRef}
/>
<AutoScroll
atBottom={atBottom}
isScrolling={isScrolling}
onScrollToBottom={(type) => {
const virtuoso = virtuosoRef.current;
switch (type) {
case 'auto': {
virtuoso?.scrollToIndex({ align: 'end', behavior: 'auto', index: 'LAST' });
break;
}
case 'click': {
virtuoso?.scrollToIndex({ align: 'end', behavior: 'smooth', index: 'LAST' });
break;
}
}
case 'click': {
virtuoso?.scrollToIndex({ align: 'end', behavior: 'smooth', index: 'LAST' });
break;
}
}
}}
/>
</Flexbox>
}}
/>
</Flexbox>
</VirtuosoContext>
);
});
+1 -1
View File
@@ -31,7 +31,7 @@ const ThreadChatItem = memo<ThreadChatItemProps>(({ id, index }) => {
const isParentMessage = index <= threadStartMessageIndex;
const actionBar = useMemo(
() => !isParentMessage && <ActionsBar id={id} inPortalThread />,
() => !isParentMessage && <ActionsBar id={id} inPortalThread index={index} />,
[id, isParentMessage],
);