mirror of
https://github.com/langgenius/dify.git
synced 2026-05-04 09:28:04 +08:00
refactor(web): simplify avatar composition api
This commit is contained in:
@ -1,8 +1,9 @@
|
||||
import type { ImageLoadingStatus } from '@base-ui/react/avatar'
|
||||
import type * as React from 'react'
|
||||
import { Avatar as BaseAvatar } from '@base-ui/react/avatar'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
export const avatarSizeClasses = {
|
||||
const avatarSizeClasses = {
|
||||
'xxs': { root: 'size-4', text: 'text-[7px]' },
|
||||
'xs': { root: 'size-5', text: 'text-[8px]' },
|
||||
'sm': { root: 'size-6', text: 'text-[10px]' },
|
||||
@ -15,8 +16,6 @@ export const avatarSizeClasses = {
|
||||
|
||||
export type AvatarSize = keyof typeof avatarSizeClasses
|
||||
|
||||
export const getAvatarSizeClassNames = (size: AvatarSize) => avatarSizeClasses[size]
|
||||
|
||||
export type AvatarProps = {
|
||||
name: string
|
||||
avatar: string | null
|
||||
@ -25,15 +24,61 @@ export type AvatarProps = {
|
||||
onLoadingStatusChange?: (status: ImageLoadingStatus) => void
|
||||
}
|
||||
|
||||
export const AvatarRoot = BaseAvatar.Root
|
||||
export const AvatarImage = BaseAvatar.Image
|
||||
export const AvatarFallback = BaseAvatar.Fallback
|
||||
export type AvatarRootProps = React.ComponentPropsWithRef<typeof BaseAvatar.Root> & {
|
||||
size?: AvatarSize
|
||||
}
|
||||
|
||||
export const avatarPartClassNames = {
|
||||
root: 'relative inline-flex shrink-0 select-none items-center justify-center overflow-hidden rounded-full bg-primary-600',
|
||||
image: 'absolute inset-0 size-full object-cover',
|
||||
fallback: 'flex size-full items-center justify-center font-medium text-white',
|
||||
} as const
|
||||
export function AvatarRoot({
|
||||
size = 'md',
|
||||
className,
|
||||
...props
|
||||
}: AvatarRootProps) {
|
||||
return (
|
||||
<BaseAvatar.Root
|
||||
className={cn(
|
||||
'relative inline-flex shrink-0 select-none items-center justify-center overflow-hidden rounded-full bg-primary-600',
|
||||
avatarSizeClasses[size].root,
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export type AvatarImageProps = React.ComponentPropsWithRef<typeof BaseAvatar.Image>
|
||||
|
||||
export function AvatarImage({
|
||||
className,
|
||||
...props
|
||||
}: AvatarImageProps) {
|
||||
return (
|
||||
<BaseAvatar.Image
|
||||
className={cn('absolute inset-0 size-full object-cover', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export type AvatarFallbackProps = React.ComponentPropsWithRef<typeof BaseAvatar.Fallback> & {
|
||||
size?: AvatarSize
|
||||
}
|
||||
|
||||
export function AvatarFallback({
|
||||
size = 'md',
|
||||
className,
|
||||
...props
|
||||
}: AvatarFallbackProps) {
|
||||
return (
|
||||
<BaseAvatar.Fallback
|
||||
className={cn(
|
||||
'flex size-full items-center justify-center font-medium text-white',
|
||||
avatarSizeClasses[size].text,
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const Avatar = ({
|
||||
name,
|
||||
@ -42,19 +87,16 @@ export const Avatar = ({
|
||||
className,
|
||||
onLoadingStatusChange,
|
||||
}: AvatarProps) => {
|
||||
const sizeClassNames = getAvatarSizeClassNames(size)
|
||||
|
||||
return (
|
||||
<AvatarRoot className={cn(avatarPartClassNames.root, sizeClassNames.root, className)}>
|
||||
<AvatarRoot size={size} className={className}>
|
||||
{avatar && (
|
||||
<AvatarImage
|
||||
src={avatar}
|
||||
alt={name}
|
||||
className={avatarPartClassNames.image}
|
||||
onLoadingStatusChange={onLoadingStatusChange}
|
||||
/>
|
||||
)}
|
||||
<AvatarFallback className={cn(avatarPartClassNames.fallback, sizeClassNames.text)}>
|
||||
<AvatarFallback size={size}>
|
||||
{name?.[0]?.toLocaleUpperCase()}
|
||||
</AvatarFallback>
|
||||
</AvatarRoot>
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import type { FC } from 'react'
|
||||
import type { AvatarSize } from '@/app/components/base/avatar'
|
||||
import { memo } from 'react'
|
||||
import { AvatarFallback, AvatarImage, avatarPartClassNames, AvatarRoot, getAvatarSizeClassNames } from '@/app/components/base/avatar'
|
||||
import { AvatarFallback, AvatarImage, AvatarRoot } from '@/app/components/base/avatar'
|
||||
import { getUserColor } from '@/app/components/workflow/collaboration/utils/user-color'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
type User = {
|
||||
id: string
|
||||
@ -12,7 +11,6 @@ type User = {
|
||||
avatar_url?: string | null
|
||||
}
|
||||
|
||||
/** Map legacy pixel size to Avatar token (closest; ties favor smaller px, e.g. 28 -> sm). */
|
||||
function numericPxToAvatarSize(px: number): AvatarSize {
|
||||
const candidates: { px: number, size: AvatarSize }[] = [
|
||||
{ px: 16, size: 'xxs' },
|
||||
@ -62,7 +60,6 @@ export const UserAvatarList: FC<UserAvatarListProps> = memo(({
|
||||
|
||||
const currentUserId = userProfile?.id
|
||||
const avatarSize = numericPxToAvatarSize(size)
|
||||
const avatarSizeClassNames = getAvatarSizeClassNames(avatarSize)
|
||||
|
||||
return (
|
||||
<div className={`flex items-center -space-x-1 ${className}`}>
|
||||
@ -75,16 +72,15 @@ export const UserAvatarList: FC<UserAvatarListProps> = memo(({
|
||||
className="relative"
|
||||
style={{ zIndex: visibleUsers.length - index }}
|
||||
>
|
||||
<AvatarRoot className={cn(avatarPartClassNames.root, avatarSizeClassNames.root, 'ring-2 ring-components-panel-bg')}>
|
||||
<AvatarRoot size={avatarSize} className="ring-2 ring-components-panel-bg">
|
||||
{user.avatar_url && (
|
||||
<AvatarImage
|
||||
src={user.avatar_url}
|
||||
alt={user.name}
|
||||
className={avatarPartClassNames.image}
|
||||
/>
|
||||
)}
|
||||
<AvatarFallback
|
||||
className={cn(avatarPartClassNames.fallback, avatarSizeClassNames.text)}
|
||||
size={avatarSize}
|
||||
style={userColor ? { backgroundColor: userColor } : undefined}
|
||||
>
|
||||
{user.name?.[0]?.toLocaleUpperCase()}
|
||||
|
||||
Reference in New Issue
Block a user