diff --git a/apps/sim/app/workspace/[workspaceId]/components/presence/presence-avatars.tsx b/apps/sim/app/workspace/[workspaceId]/components/presence/presence-avatars.tsx
index ff53ee14325..386c026a551 100644
--- a/apps/sim/app/workspace/[workspaceId]/components/presence/presence-avatars.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/components/presence/presence-avatars.tsx
@@ -37,7 +37,7 @@ function UserAvatar({ user, index }: UserAvatarProps) {
)}
{initials}
@@ -98,7 +98,7 @@ export function PresenceAvatars({
style={{ zIndex: 0 }}
aria-label={`${overflowCount} more ${overflowCount === 1 ? 'user' : 'users'}`}
>
-
+
+{overflowCount}
diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource/components/owner-cell/owner-cell.tsx b/apps/sim/app/workspace/[workspaceId]/components/resource/components/owner-cell/owner-cell.tsx
index ebbf3bb9e9b..ad14698ae52 100644
--- a/apps/sim/app/workspace/[workspaceId]/components/resource/components/owner-cell/owner-cell.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/components/resource/components/owner-cell/owner-cell.tsx
@@ -1,4 +1,5 @@
import { memo } from 'react'
+import { Avatar, AvatarFallback, AvatarImage } from '@sim/emcn'
import type { ResourceCell } from '@/app/workspace/[workspaceId]/components/resource/resource'
import type { WorkspaceMember } from '@/hooks/queries/workspace'
@@ -13,21 +14,11 @@ export interface OwnerAvatarProps {
* owner/uploaded-by filter options on every list.
*/
export const OwnerAvatar = memo(function OwnerAvatar({ name, image }: OwnerAvatarProps) {
- if (image) {
- return (
-
- )
- }
-
return (
-
- {name.charAt(0).toUpperCase()}
-
+
+ {image && }
+ {name.charAt(0).toUpperCase()}
+
)
})
diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/member-list/member-list.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/member-list/member-list.tsx
index aa11d37b50f..97c5be6db21 100644
--- a/apps/sim/app/workspace/[workspaceId]/settings/components/member-list/member-list.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/settings/components/member-list/member-list.tsx
@@ -1,7 +1,7 @@
'use client'
import type { ReactNode } from 'react'
-import { OverflowText } from '@sim/emcn'
+import { Avatar, AvatarFallback, AvatarImage, OverflowText } from '@sim/emcn'
import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state'
import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section'
@@ -18,21 +18,11 @@ interface MemberAvatarProps {
* the member's name when no image is available.
*/
export function MemberAvatar({ name, image }: MemberAvatarProps) {
- if (image) {
- return (
-
- )
- }
-
return (
-
- {name.charAt(0).toUpperCase()}
-
+
+ {image && }
+ {name.charAt(0).toUpperCase()}
+
)
}
diff --git a/packages/emcn/src/components/avatar/avatar.test.tsx b/packages/emcn/src/components/avatar/avatar.test.tsx
new file mode 100644
index 00000000000..474d9242536
--- /dev/null
+++ b/packages/emcn/src/components/avatar/avatar.test.tsx
@@ -0,0 +1,49 @@
+/** @vitest-environment jsdom */
+import { act } from 'react'
+import { createRoot, type Root } from 'react-dom/client'
+import { afterEach, describe, expect, it } from 'vitest'
+import { Avatar, AvatarFallback } from './avatar'
+
+let root: Root | undefined
+let container: HTMLDivElement | undefined
+
+afterEach(() => {
+ act(() => root?.unmount())
+ container?.remove()
+})
+
+describe('Avatar fallback sizing', () => {
+ it('scopes xs sizing to its avatar while preserving explicit overrides', () => {
+ Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true })
+ container = document.createElement('div')
+ document.body.appendChild(container)
+ root = createRoot(container)
+ act(() =>
+ root?.render(
+ <>
+
+ A
+
+
+ BC
+
+
+
+ D
+
+
+ >
+ )
+ )
+ const xs = container.querySelector('[data-testid="xs"]')!
+ const regular = container.querySelector('[data-testid="default"]')!
+ const overridden = container.querySelector('[data-testid="override"]')!
+ expect(xs.classList.contains('text-[8px]')).toBe(true)
+ expect(xs.classList.contains('text-xs')).toBe(false)
+ expect(regular.classList.contains('text-xs')).toBe(true)
+ expect(regular.classList.contains('text-[8px]')).toBe(false)
+ expect(overridden.classList.contains('text-[7px]')).toBe(true)
+ expect(overridden.classList.contains('text-[8px]')).toBe(false)
+ expect(overridden.classList.contains('text-xs')).toBe(false)
+ })
+})
diff --git a/packages/emcn/src/components/avatar/avatar.tsx b/packages/emcn/src/components/avatar/avatar.tsx
index 85ebaa522c8..f2dc07acc56 100644
--- a/packages/emcn/src/components/avatar/avatar.tsx
+++ b/packages/emcn/src/components/avatar/avatar.tsx
@@ -50,6 +50,8 @@ const avatarStatusVariants = cva(
}
)
+const AvatarSizeContext = React.createContext['size']>('md')
+
type AvatarStatus = 'online' | 'offline' | 'busy' | 'away'
interface AvatarProps
@@ -93,22 +95,24 @@ interface AvatarProps
*/
const Avatar = React.forwardRef, AvatarProps>(
({ className, size, status, children, ...props }, ref) => (
-
-
- {children}
-
- {status && (
-
- )}
-
+
+
+
+ {children}
+
+ {status && (
+
+ )}
+
+
)
)
Avatar.displayName = 'Avatar'
@@ -133,6 +137,7 @@ AvatarImage.displayName = 'AvatarImage'
/**
* Fallback component for Avatar. Displays initials or icon when image is unavailable.
+ * The xs size uses 8px initials; other sizes retain text-xs.
*
* Carries the package's only hardcoded `font-medium`, and deliberately: one or
* two capitals at `text-xs` on a filled disc are a glyph, not running text, and
@@ -142,16 +147,20 @@ AvatarImage.displayName = 'AvatarImage'
const AvatarFallback = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
+>(({ className, ...props }, ref) => {
+ const size = React.useContext(AvatarSizeContext)
+ return (
+
+ )
+})
AvatarFallback.displayName = 'AvatarFallback'
export { Avatar, AvatarImage, AvatarFallback, avatarVariants, avatarStatusVariants }