@shipfox/react-ui 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/.turbo/turbo-check.log +3 -3
  3. package/.turbo/turbo-type.log +1 -1
  4. package/CHANGELOG.md +12 -0
  5. package/dist/components/alert/alert.d.ts +18 -0
  6. package/dist/components/alert/alert.d.ts.map +1 -0
  7. package/dist/components/alert/alert.js +123 -0
  8. package/dist/components/alert/alert.js.map +1 -0
  9. package/dist/components/alert/alert.stories.js +112 -0
  10. package/dist/components/alert/alert.stories.js.map +1 -0
  11. package/dist/components/alert/index.d.ts +2 -0
  12. package/dist/components/alert/index.d.ts.map +1 -0
  13. package/dist/components/alert/index.js +3 -0
  14. package/dist/components/alert/index.js.map +1 -0
  15. package/dist/components/avatar/avatar-group.d.ts +18 -0
  16. package/dist/components/avatar/avatar-group.d.ts.map +1 -0
  17. package/dist/components/avatar/avatar-group.js +132 -0
  18. package/dist/components/avatar/avatar-group.js.map +1 -0
  19. package/dist/components/avatar/avatar.d.ts +21 -0
  20. package/dist/components/avatar/avatar.d.ts.map +1 -0
  21. package/dist/components/avatar/avatar.js +166 -0
  22. package/dist/components/avatar/avatar.js.map +1 -0
  23. package/dist/components/avatar/avatar.stories.js +255 -0
  24. package/dist/components/avatar/avatar.stories.js.map +1 -0
  25. package/dist/components/avatar/index.d.ts +3 -0
  26. package/dist/components/avatar/index.d.ts.map +1 -0
  27. package/dist/components/avatar/index.js +4 -0
  28. package/dist/components/avatar/index.js.map +1 -0
  29. package/dist/components/icon/custom/index.d.ts +1 -0
  30. package/dist/components/icon/custom/index.d.ts.map +1 -1
  31. package/dist/components/icon/custom/index.js +1 -0
  32. package/dist/components/icon/custom/index.js.map +1 -1
  33. package/dist/components/icon/custom/shipfox-logo.d.ts +8 -0
  34. package/dist/components/icon/custom/shipfox-logo.d.ts.map +1 -0
  35. package/dist/components/icon/custom/shipfox-logo.js +22 -0
  36. package/dist/components/icon/custom/shipfox-logo.js.map +1 -0
  37. package/dist/components/icon/icon.d.ts +4 -1
  38. package/dist/components/icon/icon.d.ts.map +1 -1
  39. package/dist/components/icon/icon.js +6 -3
  40. package/dist/components/icon/icon.js.map +1 -1
  41. package/dist/components/index.d.ts +4 -0
  42. package/dist/components/index.d.ts.map +1 -1
  43. package/dist/components/index.js +4 -0
  44. package/dist/components/index.js.map +1 -1
  45. package/dist/components/inline-tips/index.d.ts +2 -0
  46. package/dist/components/inline-tips/index.d.ts.map +1 -0
  47. package/dist/components/inline-tips/index.js +3 -0
  48. package/dist/components/inline-tips/index.js.map +1 -0
  49. package/dist/components/inline-tips/inline-tips.d.ts +19 -0
  50. package/dist/components/inline-tips/inline-tips.d.ts.map +1 -0
  51. package/dist/components/inline-tips/inline-tips.js +98 -0
  52. package/dist/components/inline-tips/inline-tips.js.map +1 -0
  53. package/dist/components/inline-tips/inline-tips.stories.js +214 -0
  54. package/dist/components/inline-tips/inline-tips.stories.js.map +1 -0
  55. package/dist/components/textarea/index.d.ts +2 -0
  56. package/dist/components/textarea/index.d.ts.map +1 -0
  57. package/dist/components/textarea/index.js +3 -0
  58. package/dist/components/textarea/index.js.map +1 -0
  59. package/dist/components/textarea/textarea.d.ts +10 -0
  60. package/dist/components/textarea/textarea.d.ts.map +1 -0
  61. package/dist/components/textarea/textarea.js +31 -0
  62. package/dist/components/textarea/textarea.js.map +1 -0
  63. package/dist/components/textarea/textarea.stories.js +333 -0
  64. package/dist/components/textarea/textarea.stories.js.map +1 -0
  65. package/dist/components/tooltip/tooltip.d.ts +7 -0
  66. package/dist/components/tooltip/tooltip.d.ts.map +1 -0
  67. package/dist/components/tooltip/tooltip.js +38 -0
  68. package/dist/components/tooltip/tooltip.js.map +1 -0
  69. package/dist/utils/avatar.d.ts +3 -0
  70. package/dist/utils/avatar.d.ts.map +1 -0
  71. package/dist/utils/avatar.js +32 -0
  72. package/dist/utils/avatar.js.map +1 -0
  73. package/dist/utils/index.d.ts +1 -0
  74. package/dist/utils/index.d.ts.map +1 -1
  75. package/dist/utils/index.js +1 -0
  76. package/dist/utils/index.js.map +1 -1
  77. package/index.css +32 -1
  78. package/package.json +5 -5
  79. package/src/components/alert/alert.stories.tsx +77 -0
  80. package/src/components/alert/alert.tsx +144 -0
  81. package/src/components/alert/index.ts +1 -0
  82. package/src/components/avatar/avatar-group.tsx +186 -0
  83. package/src/components/avatar/avatar.stories.tsx +172 -0
  84. package/src/components/avatar/avatar.tsx +215 -0
  85. package/src/components/avatar/index.ts +2 -0
  86. package/src/components/icon/custom/index.ts +1 -0
  87. package/src/components/icon/custom/shipfox-logo.tsx +20 -0
  88. package/src/components/icon/icon.tsx +11 -1
  89. package/src/components/index.ts +4 -0
  90. package/src/components/inline-tips/index.ts +1 -0
  91. package/src/components/inline-tips/inline-tips.stories.tsx +126 -0
  92. package/src/components/inline-tips/inline-tips.tsx +132 -0
  93. package/src/components/textarea/index.ts +1 -0
  94. package/src/components/textarea/textarea.stories.tsx +190 -0
  95. package/src/components/textarea/textarea.tsx +42 -0
  96. package/src/components/tooltip/tooltip.tsx +52 -0
  97. package/src/utils/avatar.ts +27 -0
  98. package/src/utils/index.ts +1 -0
@@ -0,0 +1,144 @@
1
+ import {cva, type VariantProps} from 'class-variance-authority';
2
+ import {Icon} from 'components/icon';
3
+ import type {ComponentProps} from 'react';
4
+ import {cn} from 'utils/cn';
5
+
6
+ const alertVariants = cva(
7
+ 'relative w-full rounded-l-4 rounded-r-8 px-16 py-12 text-sm flex gap-12 items-start border',
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: 'bg-tag-neutral-bg text-foreground-neutral-base border-tag-neutral-border',
12
+ info: 'bg-tag-blue-bg text-foreground-neutral-base border-tag-blue-border',
13
+ success: 'bg-tag-success-bg text-foreground-neutral-base border-tag-success-border',
14
+ warning: 'bg-tag-warning-bg text-foreground-neutral-base border-tag-warning-border',
15
+ destructive: 'bg-tag-error-bg text-foreground-neutral-base border-tag-error-border',
16
+ },
17
+ },
18
+ defaultVariants: {
19
+ variant: 'default',
20
+ },
21
+ },
22
+ );
23
+
24
+ const alertLineVariants = cva('w-4 self-stretch rounded-full', {
25
+ variants: {
26
+ variant: {
27
+ default: 'bg-tag-neutral-icon',
28
+ info: 'bg-tag-blue-icon',
29
+ success: 'bg-tag-success-icon',
30
+ warning: 'bg-tag-warning-icon',
31
+ destructive: 'bg-tag-error-icon',
32
+ },
33
+ },
34
+ defaultVariants: {
35
+ variant: 'default',
36
+ },
37
+ });
38
+
39
+ const closeIconVariants = cva('w-16 h-16', {
40
+ variants: {
41
+ variant: {
42
+ default: 'text-tag-neutral-icon',
43
+ info: 'text-tag-blue-icon',
44
+ success: 'text-tag-success-icon',
45
+ warning: 'text-tag-warning-icon',
46
+ destructive: 'text-tag-error-icon',
47
+ },
48
+ },
49
+ defaultVariants: {
50
+ variant: 'default',
51
+ },
52
+ });
53
+
54
+ type AlertProps = ComponentProps<'div'> & VariantProps<typeof alertVariants>;
55
+
56
+ function Alert({className, variant, children, ...props}: AlertProps) {
57
+ return (
58
+ <div className="w-full flex items-start gap-4">
59
+ <div data-slot="alert-line" className={cn(alertLineVariants({variant}))} aria-hidden="true" />
60
+ <div
61
+ data-slot="alert"
62
+ role="alert"
63
+ className={cn(alertVariants({variant}), className)}
64
+ {...props}
65
+ >
66
+ {children}
67
+ </div>
68
+ </div>
69
+ );
70
+ }
71
+
72
+ function AlertContent({className, ...props}: ComponentProps<'div'>) {
73
+ return <div data-slot="alert-content" className={cn('flex-1 min-w-0', className)} {...props} />;
74
+ }
75
+
76
+ function AlertTitle({className, ...props}: ComponentProps<'div'>) {
77
+ return (
78
+ <div
79
+ data-slot="alert-title"
80
+ className={cn('font-medium text-sm leading-20 text-foreground-neutral-base mb-4', className)}
81
+ {...props}
82
+ />
83
+ );
84
+ }
85
+
86
+ function AlertDescription({className, ...props}: ComponentProps<'div'>) {
87
+ return (
88
+ <div
89
+ data-slot="alert-description"
90
+ className={cn(
91
+ 'text-xs leading-20 text-foreground-neutral-base [&_p]:leading-relaxed',
92
+ className,
93
+ )}
94
+ {...props}
95
+ />
96
+ );
97
+ }
98
+
99
+ function AlertActions({className, ...props}: ComponentProps<'div'>) {
100
+ return (
101
+ <div
102
+ data-slot="alert-actions"
103
+ className={cn('flex items-center gap-8 mt-8', className)}
104
+ {...props}
105
+ />
106
+ );
107
+ }
108
+
109
+ function AlertAction({className, ...props}: ComponentProps<'button'>) {
110
+ return (
111
+ <button
112
+ data-slot="alert-action"
113
+ type="button"
114
+ className={cn(
115
+ 'bg-transparent border-none p-0 cursor-pointer text-xs font-medium leading-20 text-foreground-neutral-base hover:text-foreground-neutral-subtle transition-colors duration-150 outline-none focus-visible:ring-2 focus-visible:ring-background-accent-blue-base focus-visible:ring-offset-2',
116
+ className,
117
+ )}
118
+ {...props}
119
+ />
120
+ );
121
+ }
122
+
123
+ function AlertClose({
124
+ className,
125
+ variant = 'default',
126
+ ...props
127
+ }: ComponentProps<'button'> & VariantProps<typeof closeIconVariants>) {
128
+ return (
129
+ <button
130
+ data-slot="alert-close"
131
+ type="button"
132
+ className={cn(
133
+ 'absolute cursor-pointer top-12 right-12 rounded-4 p-4 bg-transparent border-none text-foreground-neutral-muted hover:text-foreground-neutral-base hover:bg-background-components-hover transition-colors duration-150 outline-none focus-visible:ring-2 focus-visible:ring-background-accent-blue-base focus-visible:ring-offset-2',
134
+ className,
135
+ )}
136
+ aria-label="Close"
137
+ {...props}
138
+ >
139
+ <Icon name="close" className={cn(closeIconVariants({variant}))} />
140
+ </button>
141
+ );
142
+ }
143
+
144
+ export {Alert, AlertContent, AlertTitle, AlertDescription, AlertActions, AlertAction, AlertClose};
@@ -0,0 +1 @@
1
+ export * from './alert';
@@ -0,0 +1,186 @@
1
+ import * as TooltipPrimitive from '@radix-ui/react-tooltip';
2
+ import {cva, type VariantProps} from 'class-variance-authority';
3
+ import {
4
+ Children,
5
+ type ComponentProps,
6
+ cloneElement,
7
+ type ReactElement,
8
+ type ReactNode,
9
+ useMemo,
10
+ } from 'react';
11
+ import {cn} from 'utils/cn';
12
+ import {TooltipContent, TooltipProvider, TooltipTrigger} from '../tooltip/tooltip';
13
+
14
+ const avatarGroupVariants = cva('flex items-start', {
15
+ variants: {
16
+ size: {
17
+ '3xs': '-space-x-4',
18
+ '2xs': '-space-x-4',
19
+ xs: '-space-x-4',
20
+ sm: '-space-x-4',
21
+ md: '-space-x-4',
22
+ lg: '-space-x-6',
23
+ xl: '-space-x-6',
24
+ '2xl': '-space-x-12',
25
+ '3xl': '-space-x-12',
26
+ },
27
+ },
28
+ defaultVariants: {
29
+ size: 'md',
30
+ },
31
+ });
32
+
33
+ const avatarGroupOverflowVariants = cva(
34
+ 'flex shrink-0 items-center justify-center rounded-full bg-background-components-base text-foreground-neutral-subtle font-medium ring-1 ring-border-neutral-base-component ring-offset-1 ring-offset-background-neutral-base shadow-button-neutral',
35
+ {
36
+ variants: {
37
+ size: {
38
+ '3xs': 'size-[18px] text-[10px] leading-[10px]',
39
+ '2xs': 'size-[20px] text-[11px] leading-[11px]',
40
+ xs: 'size-[24px] text-xs leading-4',
41
+ sm: 'size-[28px] text-xs leading-5',
42
+ md: 'size-[32px] text-sm leading-5',
43
+ lg: 'size-[36px] text-sm leading-5',
44
+ xl: 'size-[40px] text-base leading-6',
45
+ '2xl': 'size-[80px] text-2xl leading-8',
46
+ '3xl': 'size-[120px] text-4xl leading-[56px]',
47
+ },
48
+ },
49
+ defaultVariants: {
50
+ size: 'md',
51
+ },
52
+ },
53
+ );
54
+
55
+ type TooltipContentProps = ComponentProps<typeof TooltipContent>;
56
+
57
+ type AvatarContainerProps = {
58
+ children: ReactNode;
59
+ zIndex: number;
60
+ tooltipContent?: ReactNode;
61
+ tooltipProps?: Partial<TooltipContentProps>;
62
+ animateOnHover?: boolean;
63
+ };
64
+
65
+ function AvatarContainer({
66
+ children,
67
+ zIndex,
68
+ tooltipContent,
69
+ tooltipProps,
70
+ animateOnHover = false,
71
+ }: AvatarContainerProps) {
72
+ return (
73
+ <TooltipPrimitive.Root>
74
+ <TooltipTrigger asChild>
75
+ <div
76
+ data-slot="avatar-container"
77
+ className={cn(
78
+ 'relative',
79
+ animateOnHover && 'transition-transform duration-300 ease-out hover:-translate-y-2',
80
+ )}
81
+ style={{zIndex}}
82
+ >
83
+ {children}
84
+ </div>
85
+ </TooltipTrigger>
86
+ {tooltipContent && (
87
+ <AvatarGroupTooltip {...tooltipProps}>{tooltipContent}</AvatarGroupTooltip>
88
+ )}
89
+ </TooltipPrimitive.Root>
90
+ );
91
+ }
92
+
93
+ function getTooltipContent(children: ReactNode): ReactNode | null {
94
+ const tooltip = Children.toArray(children).find(
95
+ (child) =>
96
+ typeof child === 'object' &&
97
+ child !== null &&
98
+ 'type' in child &&
99
+ child.type === AvatarGroupTooltip,
100
+ ) as ReactElement<ComponentProps<typeof AvatarGroupTooltip>> | undefined;
101
+
102
+ return tooltip?.props.children || null;
103
+ }
104
+
105
+ type AvatarGroupTooltipProps = TooltipContentProps;
106
+
107
+ function AvatarGroupTooltip(props: AvatarGroupTooltipProps) {
108
+ return <TooltipContent {...props} />;
109
+ }
110
+
111
+ type AvatarGroupProps = ComponentProps<'div'> &
112
+ VariantProps<typeof avatarGroupVariants> & {
113
+ children: ReactElement[];
114
+ maxVisible?: number;
115
+ animateOnHover?: boolean;
116
+ tooltipProps?: Partial<TooltipContentProps>;
117
+ };
118
+
119
+ export function AvatarGroup({
120
+ className,
121
+ size = 'md',
122
+ children,
123
+ maxVisible,
124
+ animateOnHover = false,
125
+ tooltipProps = {side: 'top', sideOffset: 8},
126
+ ...props
127
+ }: AvatarGroupProps) {
128
+ const normalizedSize = size ?? 'md';
129
+
130
+ const childrenArray = Children.toArray(children) as ReactElement[];
131
+
132
+ const {visibleCount, visibleAvatars, overflowCount} = useMemo(() => {
133
+ const count =
134
+ maxVisible !== undefined ? Math.min(maxVisible, childrenArray.length) : childrenArray.length;
135
+ return {
136
+ visibleCount: count,
137
+ visibleAvatars: childrenArray.slice(0, count),
138
+ overflowCount: childrenArray.length - count,
139
+ };
140
+ }, [childrenArray, maxVisible]);
141
+
142
+ return (
143
+ <TooltipProvider delayDuration={0}>
144
+ <div
145
+ className={cn(avatarGroupVariants({size: normalizedSize}), className)}
146
+ data-slot="avatar-group"
147
+ {...props}
148
+ >
149
+ {visibleAvatars.map((child, index) => {
150
+ const zIndex = index + 1;
151
+ const childProps = 'props' in child ? (child.props as {children?: ReactNode}) : {};
152
+ const tooltipContent = getTooltipContent(childProps.children);
153
+
154
+ return (
155
+ <AvatarContainer
156
+ key={child.key || index}
157
+ zIndex={zIndex}
158
+ tooltipContent={tooltipContent}
159
+ tooltipProps={tooltipProps}
160
+ animateOnHover={animateOnHover}
161
+ >
162
+ {cloneElement(child, {
163
+ ...childProps,
164
+ children: tooltipContent ? undefined : childProps.children,
165
+ } as Partial<typeof childProps>)}
166
+ </AvatarContainer>
167
+ );
168
+ })}
169
+ {overflowCount > 0 && (
170
+ <div
171
+ className={cn(
172
+ 'relative',
173
+ avatarGroupOverflowVariants({size: normalizedSize}),
174
+ 'rounded-full',
175
+ )}
176
+ style={{zIndex: visibleCount + 1}}
177
+ >
178
+ +{overflowCount}
179
+ </div>
180
+ )}
181
+ </div>
182
+ </TooltipProvider>
183
+ );
184
+ }
185
+
186
+ export {AvatarGroupTooltip, type AvatarGroupTooltipProps};
@@ -0,0 +1,172 @@
1
+ import type {Meta, StoryObj} from '@storybook/react';
2
+ import {Code} from 'components/typography';
3
+ import {Avatar} from './avatar';
4
+ import {AvatarGroup, AvatarGroupTooltip} from './avatar-group';
5
+
6
+ const contentOptions = ['letters', 'logo', 'logoPlaceholder', 'image', 'upload'] as const;
7
+ const radiusOptions = ['full', 'rounded'] as const;
8
+ const sizeOptions = ['3xs', '2xs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'] as const;
9
+
10
+ const meta = {
11
+ title: 'Components/Avatar',
12
+ component: Avatar,
13
+ tags: ['autodocs'],
14
+ argTypes: {
15
+ content: {
16
+ control: 'select',
17
+ options: contentOptions,
18
+ },
19
+ radius: {
20
+ control: 'select',
21
+ options: radiusOptions,
22
+ },
23
+ size: {
24
+ control: 'select',
25
+ options: sizeOptions,
26
+ },
27
+ fallback: {
28
+ control: 'text',
29
+ },
30
+ src: {
31
+ control: 'text',
32
+ },
33
+ alt: {
34
+ control: 'text',
35
+ },
36
+ },
37
+ args: {
38
+ content: 'letters',
39
+ radius: 'full',
40
+ size: 'md',
41
+ fallback: 'John Doe',
42
+ },
43
+ } satisfies Meta<typeof Avatar>;
44
+
45
+ export default meta;
46
+ type Story = StoryObj<typeof meta>;
47
+
48
+ export const Default: Story = {
49
+ args: {
50
+ content: 'upload',
51
+ fallback: 'Kyle Nguyen',
52
+ },
53
+
54
+ render: (args) => (
55
+ <div className="flex flex-wrap items-end gap-16">
56
+ {sizeOptions.map((size) => (
57
+ <div key={size} className="flex flex-col items-center gap-8">
58
+ <Avatar {...args} size={size} />
59
+ <Code variant="label" className="text-foreground-neutral-base">
60
+ {size}
61
+ </Code>
62
+ </div>
63
+ ))}
64
+ </div>
65
+ ),
66
+ };
67
+
68
+ // AvatarGroup Stories
69
+ const avatarGroupMeta = {
70
+ title: 'Components/AvatarGroup',
71
+ component: AvatarGroup,
72
+ tags: ['autodocs'],
73
+ argTypes: {
74
+ size: {
75
+ control: 'select',
76
+ options: sizeOptions,
77
+ },
78
+ maxVisible: {
79
+ control: 'number',
80
+ },
81
+ },
82
+ args: {
83
+ size: 'md',
84
+ children: [],
85
+ },
86
+ } satisfies Meta<typeof AvatarGroup>;
87
+
88
+ export const AvatarGroupDefault: StoryObj<typeof avatarGroupMeta> = {
89
+ args: {
90
+ children: [],
91
+ },
92
+ render: () => {
93
+ const avatars = [
94
+ {name: 'John Doe', content: 'image'},
95
+ {name: 'Jane Smith', content: 'image'},
96
+ {name: 'Bob Johnson', content: 'image'},
97
+ {name: 'Alice Brown', content: 'image'},
98
+ ] as const;
99
+
100
+ return (
101
+ <div className="flex flex-col gap-16">
102
+ <div className="flex flex-col gap-8">
103
+ <Code variant="label" className="text-foreground-neutral-base">
104
+ Default (without tooltips)
105
+ </Code>
106
+ <AvatarGroup size="md">
107
+ {avatars.map((avatar) => (
108
+ <Avatar key={avatar.name} content={avatar.content} fallback={avatar.name} />
109
+ ))}
110
+ </AvatarGroup>
111
+ </div>
112
+ </div>
113
+ );
114
+ },
115
+ };
116
+
117
+ export const AvatarGroupWithTooltips: StoryObj<typeof avatarGroupMeta> = {
118
+ args: {
119
+ children: [],
120
+ },
121
+ render: () => {
122
+ const avatars = [
123
+ {name: 'John Doe', content: 'image'},
124
+ {name: 'Jane Smith', content: 'image'},
125
+ {name: 'Bob Johnson', content: 'image'},
126
+ {name: 'Alice Brown', content: 'image'},
127
+ {name: 'Carlos Vega', content: 'image'},
128
+ {name: 'Linda Tran', content: 'image'},
129
+ ] as const;
130
+
131
+ return (
132
+ <div className="flex flex-col gap-16">
133
+ <div className="flex flex-col gap-8">
134
+ <Code variant="label" className="text-foreground-neutral-base">
135
+ With Tooltips
136
+ </Code>
137
+ <AvatarGroup size="md">
138
+ {avatars.map((avatar) => (
139
+ <Avatar key={avatar.name} content={avatar.content} fallback={avatar.name}>
140
+ <AvatarGroupTooltip>{avatar.name}</AvatarGroupTooltip>
141
+ </Avatar>
142
+ ))}
143
+ </AvatarGroup>
144
+ </div>
145
+ <div className="flex flex-col gap-8">
146
+ <Code variant="label" className="text-foreground-neutral-base">
147
+ With Tooltips (maxVisible: 4)
148
+ </Code>
149
+ <AvatarGroup size="md" maxVisible={4}>
150
+ {avatars.map((avatar) => (
151
+ <Avatar key={avatar.name} content={avatar.content} fallback={avatar.name}>
152
+ <AvatarGroupTooltip>{avatar.name}</AvatarGroupTooltip>
153
+ </Avatar>
154
+ ))}
155
+ </AvatarGroup>
156
+ </div>
157
+ <div className="flex flex-col gap-8">
158
+ <Code variant="label" className="text-foreground-neutral-base">
159
+ With Tooltips and Hover Animation
160
+ </Code>
161
+ <AvatarGroup size="md" maxVisible={4} animateOnHover>
162
+ {avatars.map((avatar) => (
163
+ <Avatar key={avatar.name} content={avatar.content} fallback={avatar.name}>
164
+ <AvatarGroupTooltip>{avatar.name}</AvatarGroupTooltip>
165
+ </Avatar>
166
+ ))}
167
+ </AvatarGroup>
168
+ </div>
169
+ </div>
170
+ );
171
+ },
172
+ };