mirror of
https://github.com/langgenius/dify.git
synced 2026-08-28 21:23:22 +08:00
add UserAvatarList component
This commit is contained in:
parent
e8683bf957
commit
088ccf8b8d
65
web/app/components/base/user-avatar-list/index.tsx
Normal file
65
web/app/components/base/user-avatar-list/index.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import type { FC } from 'react'
|
||||||
|
import { memo } from 'react'
|
||||||
|
import Avatar from '@/app/components/base/avatar'
|
||||||
|
|
||||||
|
type User = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
avatar_url?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserAvatarListProps = {
|
||||||
|
users: User[]
|
||||||
|
maxVisible?: number
|
||||||
|
size?: number
|
||||||
|
className?: string
|
||||||
|
showCount?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UserAvatarList: FC<UserAvatarListProps> = memo(({
|
||||||
|
users,
|
||||||
|
maxVisible = 3,
|
||||||
|
size = 24,
|
||||||
|
className = '',
|
||||||
|
showCount = true,
|
||||||
|
}) => {
|
||||||
|
if (!users.length) return null
|
||||||
|
|
||||||
|
const shouldShowCount = showCount && users.length > maxVisible
|
||||||
|
const actualMaxVisible = shouldShowCount ? Math.max(1, maxVisible - 1) : maxVisible
|
||||||
|
const visibleUsers = users.slice(0, actualMaxVisible)
|
||||||
|
const remainingCount = users.length - actualMaxVisible
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`flex items-center -space-x-1 ${className}`}>
|
||||||
|
{visibleUsers.map((user, index) => (
|
||||||
|
<div
|
||||||
|
key={`${user.id}-${index}`}
|
||||||
|
className='relative'
|
||||||
|
style={{ zIndex: visibleUsers.length - index }}
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
name={user.name}
|
||||||
|
avatar={user.avatar_url || null}
|
||||||
|
size={size}
|
||||||
|
className='ring-2 ring-white'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{shouldShowCount && remainingCount > 0 && (
|
||||||
|
<div
|
||||||
|
className={'flex items-center justify-center rounded-full bg-components-panel-on-panel-item-bg text-[10px] leading-none text-text-secondary ring-2 ring-white'}
|
||||||
|
style={{
|
||||||
|
zIndex: 0,
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+{remainingCount}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
UserAvatarList.displayName = 'UserAvatarList'
|
||||||
@ -3,7 +3,7 @@ import { RiCheckLine, RiCheckboxCircleFill, RiCheckboxCircleLine, RiCloseLine, R
|
|||||||
import { useStore } from '@/app/components/workflow/store'
|
import { useStore } from '@/app/components/workflow/store'
|
||||||
import type { WorkflowCommentList } from '@/service/workflow-comment'
|
import type { WorkflowCommentList } from '@/service/workflow-comment'
|
||||||
import { useWorkflowComment } from '@/app/components/workflow/hooks/use-workflow-comment'
|
import { useWorkflowComment } from '@/app/components/workflow/hooks/use-workflow-comment'
|
||||||
import Avatar from '@/app/components/base/avatar'
|
import { UserAvatarList } from '@/app/components/base/user-avatar-list'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import { ControlMode } from '@/app/components/workflow/types'
|
import { ControlMode } from '@/app/components/workflow/types'
|
||||||
import { resolveWorkflowComment } from '@/service/workflow-comment'
|
import { resolveWorkflowComment } from '@/service/workflow-comment'
|
||||||
@ -114,46 +114,12 @@ const CommentsPanel = () => {
|
|||||||
onClick={() => handleSelect(c)}
|
onClick={() => handleSelect(c)}
|
||||||
>
|
>
|
||||||
<div className='min-w-0'>
|
<div className='min-w-0'>
|
||||||
{/* Participants stacked avatars above creator name */}
|
|
||||||
{(() => {
|
|
||||||
const creator = {
|
|
||||||
id: c.created_by,
|
|
||||||
name: c.created_by_account?.name || 'User',
|
|
||||||
avatar_url: c.created_by_account?.avatar_url || null,
|
|
||||||
}
|
|
||||||
const collaborators = (c.participants || []).filter(p => p.id !== creator.id)
|
|
||||||
const all = [creator, ...collaborators]
|
|
||||||
if (!all.length) return null
|
|
||||||
const shouldShowCount = all.length >= 4
|
|
||||||
const maxVisible = shouldShowCount ? 2 : 3
|
|
||||||
const visibleUsers = all.slice(0, maxVisible)
|
|
||||||
const remainingCount = all.length - maxVisible
|
|
||||||
return (
|
|
||||||
<div className='mb-1 flex items-center justify-between'>
|
<div className='mb-1 flex items-center justify-between'>
|
||||||
<div className='flex items-center -space-x-1'>
|
<UserAvatarList
|
||||||
{visibleUsers.map((p, index) => (
|
users={c.participants}
|
||||||
<div
|
maxVisible={3}
|
||||||
key={`${p.id}-${index}`}
|
|
||||||
className='relative'
|
|
||||||
style={{ zIndex: visibleUsers.length - index }}
|
|
||||||
>
|
|
||||||
<Avatar
|
|
||||||
name={p.name}
|
|
||||||
avatar={p.avatar_url || null}
|
|
||||||
size={24}
|
size={24}
|
||||||
className='ring-2 ring-white'
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{remainingCount > 0 && (
|
|
||||||
<div
|
|
||||||
className='flex h-[24px] w-[24px] items-center justify-center rounded-full bg-components-panel-on-panel-item-bg text-[10px] leading-none text-text-secondary ring-2 ring-white'
|
|
||||||
style={{ zIndex: 0 }}
|
|
||||||
>
|
|
||||||
+{remainingCount}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className='ml-2 flex items-center'>
|
<div className='ml-2 flex items-center'>
|
||||||
{c.resolved ? (
|
{c.resolved ? (
|
||||||
<RiCheckboxCircleFill className='h-4 w-4 text-text-secondary'/>
|
<RiCheckboxCircleFill className='h-4 w-4 text-text-secondary'/>
|
||||||
@ -165,8 +131,6 @@ const CommentsPanel = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
|
||||||
})()}
|
|
||||||
{/* Header row: creator + time */}
|
{/* Header row: creator + time */}
|
||||||
<div className='flex items-start'>
|
<div className='flex items-start'>
|
||||||
<div className='flex min-w-0 items-center gap-2'>
|
<div className='flex min-w-0 items-center gap-2'>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user