feat: message list implements virtual scrolling
This commit is contained in:
parent
89e20a65db
commit
c9388c744e
8 changed files with 45 additions and 29 deletions
|
@ -69,6 +69,7 @@
|
|||
"react-hook-form": "^7.51.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-use": "^17.5.0",
|
||||
"react-virtuoso": "^4.10.4",
|
||||
"remark-breaks": "^4.0.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remesh": "^4.2.2",
|
||||
|
|
|
@ -83,6 +83,9 @@ importers:
|
|||
react-use:
|
||||
specifier: ^17.5.0
|
||||
version: 17.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react-virtuoso:
|
||||
specifier: ^4.10.4
|
||||
version: 4.10.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
remark-breaks:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
|
@ -4796,6 +4799,13 @@ packages:
|
|||
react: '*'
|
||||
react-dom: '*'
|
||||
|
||||
react-virtuoso@4.10.4:
|
||||
resolution: {integrity: sha512-G/gprhTbK+lzMxoo/iStcZxVEGph/cIhc3WANEpt92RuMw+LiCZOmBfKoeoZOHlm/iyftTrDJhGaTCpxyucnkQ==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
react: '>=16 || >=17 || >= 18'
|
||||
react-dom: '>=16 || >=17 || >= 18'
|
||||
|
||||
react@18.3.1:
|
||||
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -11333,6 +11343,11 @@ snapshots:
|
|||
ts-easing: 0.2.0
|
||||
tslib: 2.7.0
|
||||
|
||||
react-virtuoso@4.10.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
react@18.3.1:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
|
|
|
@ -24,10 +24,7 @@ const MessageItem: FC<MessageItemProps> = (props) => {
|
|||
props.onHateChange?.(checked)
|
||||
}
|
||||
return (
|
||||
<div
|
||||
data-index={props.index}
|
||||
className="box-border grid grid-cols-[auto_1fr] gap-x-2 px-4 [content-visibility:auto] first:pt-4 last:pb-4"
|
||||
>
|
||||
<div data-index={props.index} className="box-border grid grid-cols-[auto_1fr] gap-x-2 px-4 first:pt-4 last:pb-4">
|
||||
<Avatar>
|
||||
<AvatarImage src={props.data.userAvatar} alt="avatar" />
|
||||
<AvatarFallback>{props.data.username.at(0)}</AvatarFallback>
|
||||
|
|
|
@ -1,20 +1,26 @@
|
|||
import { type ReactElement } from 'react'
|
||||
import { FC, useRef, type ReactElement } from 'react'
|
||||
|
||||
import React from 'react'
|
||||
import { type MessageItemProps } from './MessageItem'
|
||||
import { ScrollArea } from '@/components/ui/ScrollArea'
|
||||
import { Virtuoso } from 'react-virtuoso'
|
||||
|
||||
export interface MessageListProps {
|
||||
children?: Array<ReactElement<MessageItemProps>>
|
||||
}
|
||||
// [&>div>div]:!block fix word-break: break-word;
|
||||
const MessageList = React.forwardRef<HTMLDivElement, MessageListProps>(({ children }, ref) => {
|
||||
const MessageList: FC<MessageListProps> = ({ children }) => {
|
||||
const scrollParentRef = useRef<HTMLDivElement | null>(null)
|
||||
return (
|
||||
<ScrollArea ref={ref} className="[&>div>div]:!block">
|
||||
{children}
|
||||
<ScrollArea ref={scrollParentRef}>
|
||||
<Virtuoso
|
||||
followOutput={(isAtBottom: boolean) => (isAtBottom ? 'smooth' : 'auto')}
|
||||
initialTopMostItemIndex={{ index: 'LAST', align: 'end' }}
|
||||
data={children}
|
||||
customScrollParent={scrollParentRef.current!}
|
||||
itemContent={(_: any, item: ReactElement<MessageItemProps>) => item}
|
||||
/>
|
||||
</ScrollArea>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
MessageList.displayName = 'MessageList'
|
||||
|
||||
|
|
|
@ -17,9 +17,6 @@ const Main: FC = () => {
|
|||
like: message.likeUsers.some((likeUser) => likeUser.userId === userInfo?.id),
|
||||
hate: message.hateUsers.some((hateUser) => hateUser.userId === userInfo?.id)
|
||||
}))
|
||||
const messageListRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const isUpdate = useRef(false)
|
||||
|
||||
const handleLikeChange = (messageId: string) => {
|
||||
send(roomDomain.command.SendLikeMessageCommand(messageId))
|
||||
|
@ -29,20 +26,20 @@ const Main: FC = () => {
|
|||
send(roomDomain.command.SendHateMessageCommand(messageId))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const lastMessageRef = messageListRef.current?.querySelector('[data-index]:last-child')
|
||||
const timerId = setTimeout(() => {
|
||||
requestAnimationFrame(() => {
|
||||
lastMessageRef?.scrollIntoView({ behavior: isUpdate.current ? 'smooth' : 'instant', block: 'end' })
|
||||
isUpdate.current = true
|
||||
})
|
||||
}, 0)
|
||||
// useEffect(() => {
|
||||
// const lastMessageRef = messageListRef.current?.querySelector('[data-index]:last-child')
|
||||
// const timerId = setTimeout(() => {
|
||||
// requestAnimationFrame(() => {
|
||||
// lastMessageRef?.scrollIntoView({ behavior: isUpdate.current ? 'smooth' : 'instant', block: 'end' })
|
||||
// isUpdate.current = true
|
||||
// })
|
||||
// }, 0)
|
||||
|
||||
return () => clearTimeout(timerId)
|
||||
}, [messageList.length])
|
||||
// return () => clearTimeout(timerId)
|
||||
// }, [messageList.length])
|
||||
|
||||
return (
|
||||
<MessageList ref={messageListRef}>
|
||||
<MessageList>
|
||||
{messageList.map((message, index) => (
|
||||
<MessageItem
|
||||
key={message.id}
|
||||
|
|
|
@ -50,7 +50,7 @@ const AvatarSelect = React.forwardRef<HTMLInputElement, AvatarSelectProps>(
|
|||
<Avatar
|
||||
tabIndex={disabled ? -1 : 1}
|
||||
className={cn(
|
||||
'group h-20 w-20 cursor-pointer border-4 border-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background',
|
||||
'group h-24 w-24 cursor-pointer border-4 border-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background',
|
||||
{
|
||||
'cursor-not-allowed': disabled,
|
||||
'opacity-50': disabled
|
||||
|
|
|
@ -134,7 +134,7 @@ const ProfileForm = () => {
|
|||
onClick={handleRandomAvatar}
|
||||
>
|
||||
<RefreshCcwIcon size={14} />
|
||||
Random Avatar
|
||||
Ugly Avatar
|
||||
</Button>
|
||||
</div>
|
||||
</FormControl>
|
||||
|
|
|
@ -7,8 +7,8 @@ const ScrollArea = React.forwardRef<
|
|||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.Root ref={ref} className={cn('relative overflow-hidden', className)} {...props}>
|
||||
<ScrollAreaPrimitive.Viewport className="size-full overscroll-none rounded-[inherit]">
|
||||
<ScrollAreaPrimitive.Root className={cn('relative overflow-hidden', className)} {...props}>
|
||||
<ScrollAreaPrimitive.Viewport ref={ref} className="size-full overscroll-none rounded-[inherit]">
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
|
|
Loading…
Reference in a new issue