@boxcustodia/library 2.0.0-alpha.12 → 2.0.0-alpha.14

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 (174) hide show
  1. package/dist/index.cjs.js +1 -138
  2. package/dist/index.d.ts +1087 -720
  3. package/dist/index.es.js +7011 -56097
  4. package/dist/theme.css +1 -1
  5. package/package.json +34 -26
  6. package/src/__doc__/Examples.tsx +1 -1
  7. package/src/__doc__/Intro.mdx +3 -3
  8. package/src/__doc__/Tabs.mdx +112 -0
  9. package/src/__doc__/V2.mdx +1246 -0
  10. package/src/components/accordion/accordion.stories.tsx +143 -0
  11. package/src/components/accordion/accordion.tsx +135 -0
  12. package/src/components/accordion/index.ts +1 -0
  13. package/src/components/alert/alert.stories.tsx +24 -4
  14. package/src/components/alert/alert.tsx +17 -9
  15. package/src/components/alert-dialog/alert-dialog.stories.tsx +24 -0
  16. package/src/components/alert-dialog/alert-dialog.test.tsx +1 -1
  17. package/src/components/alert-dialog/alert-dialog.tsx +58 -10
  18. package/src/components/auto-complete/auto-complete.stories.tsx +616 -200
  19. package/src/components/auto-complete/auto-complete.tsx +420 -68
  20. package/src/components/auto-complete/index.ts +0 -1
  21. package/src/components/avatar/avatar.stories.tsx +162 -21
  22. package/src/components/avatar/avatar.tsx +79 -20
  23. package/src/components/button/button.stories.tsx +219 -294
  24. package/src/components/button/button.test.tsx +10 -17
  25. package/src/components/button/button.tsx +78 -19
  26. package/src/components/button/components/base-button.tsx +30 -53
  27. package/src/components/button/index.ts +0 -1
  28. package/src/components/calendar/calendar.stories.tsx +1 -1
  29. package/src/components/calendar/calendar.tsx +4 -4
  30. package/src/components/card/card.stories.tsx +141 -69
  31. package/src/components/card/card.tsx +155 -54
  32. package/src/components/center/center.stories.tsx +22 -39
  33. package/src/components/checkbox/checkbox.stories.tsx +25 -5
  34. package/src/components/checkbox/checkbox.tsx +76 -15
  35. package/src/components/checkbox-group/checkbox-group.stories.tsx +116 -28
  36. package/src/components/checkbox-group/checkbox-group.tsx +84 -3
  37. package/src/components/combobox/combobox.stories.tsx +33 -23
  38. package/src/components/combobox/combobox.tsx +99 -77
  39. package/src/components/date-picker/date-input.stories.tsx +14 -6
  40. package/src/components/date-picker/date-input.tsx +2 -2
  41. package/src/components/date-picker/date-picker.model.ts +13 -4
  42. package/src/components/date-picker/date-picker.stories.tsx +38 -12
  43. package/src/components/date-picker/date-picker.tsx +28 -14
  44. package/src/components/dialog/dialog.stories.tsx +18 -0
  45. package/src/components/dialog/dialog.test.tsx +1 -1
  46. package/src/components/dialog/dialog.tsx +51 -20
  47. package/src/components/divider/divider.stories.tsx +126 -51
  48. package/src/components/divider/divider.tsx +16 -16
  49. package/src/components/dropzone/dropzone.stories.tsx +71 -90
  50. package/src/components/dropzone/dropzone.tsx +383 -105
  51. package/src/components/dropzone/index.ts +0 -1
  52. package/src/components/empty/empty.stories.tsx +165 -0
  53. package/src/components/empty/empty.tsx +156 -0
  54. package/src/components/empty/index.ts +1 -0
  55. package/src/components/field/field.stories.tsx +227 -4
  56. package/src/components/field/field.tsx +77 -42
  57. package/src/components/form/form.stories.tsx +320 -197
  58. package/src/components/form/form.tsx +3 -23
  59. package/src/components/index.ts +2 -6
  60. package/src/components/input/input.stories.tsx +5 -5
  61. package/src/components/input/input.tsx +4 -4
  62. package/src/components/kbd/kbd.stories.tsx +1 -0
  63. package/src/components/label/label.stories.tsx +16 -0
  64. package/src/components/label/label.tsx +13 -2
  65. package/src/components/loader/loader.stories.tsx +7 -5
  66. package/src/components/loader/loader.tsx +8 -3
  67. package/src/components/menu/menu-primitives.tsx +207 -196
  68. package/src/components/menu/menu.stories.tsx +276 -146
  69. package/src/components/menu/menu.tsx +146 -54
  70. package/src/components/number-input/number-input.stories.tsx +27 -4
  71. package/src/components/number-input/number-input.test.tsx +2 -2
  72. package/src/components/number-input/number-input.tsx +31 -33
  73. package/src/components/otp/index.ts +1 -0
  74. package/src/components/otp/otp.stories.tsx +209 -0
  75. package/src/components/otp/otp.tsx +100 -0
  76. package/src/components/pagination/index.ts +1 -0
  77. package/src/components/pagination/pagination.model.ts +2 -0
  78. package/src/components/pagination/pagination.stories.tsx +154 -59
  79. package/src/components/pagination/pagination.test.tsx +122 -57
  80. package/src/components/pagination/pagination.tsx +575 -77
  81. package/src/components/password/password.stories.tsx +18 -3
  82. package/src/components/password/password.tsx +29 -9
  83. package/src/components/popover/popover.stories.tsx +26 -5
  84. package/src/components/popover/popover.tsx +15 -23
  85. package/src/components/progress/progress.stories.tsx +1 -0
  86. package/src/components/radio-group/index.ts +1 -0
  87. package/src/components/radio-group/radio-group.stories.tsx +251 -0
  88. package/src/components/radio-group/radio-group.tsx +212 -0
  89. package/src/components/scroll-area/scroll-area.stories.tsx +1 -0
  90. package/src/components/select/select.stories.tsx +118 -19
  91. package/src/components/select/select.tsx +67 -62
  92. package/src/components/skeleton/skeleton.stories.tsx +1 -0
  93. package/src/components/stack/stack.stories.tsx +179 -89
  94. package/src/components/stack/stack.tsx +2 -2
  95. package/src/components/stepper/index.ts +1 -1
  96. package/src/components/stepper/stepper.stories.tsx +767 -83
  97. package/src/components/stepper/stepper.test.tsx +18 -18
  98. package/src/components/stepper/stepper.tsx +554 -0
  99. package/src/components/switch/switch.stories.tsx +15 -1
  100. package/src/components/switch/switch.tsx +17 -4
  101. package/src/components/table/index.ts +0 -2
  102. package/src/components/table/table.stories.tsx +131 -18
  103. package/src/components/table/table.test.tsx +1 -1
  104. package/src/components/table/table.tsx +183 -77
  105. package/src/components/tabs/tabs.stories.tsx +373 -155
  106. package/src/components/tabs/tabs.test.tsx +12 -12
  107. package/src/components/tabs/tabs.tsx +72 -149
  108. package/src/components/tag/index.ts +0 -1
  109. package/src/components/tag/tag.stories.tsx +155 -120
  110. package/src/components/tag/tag.tsx +47 -95
  111. package/src/components/textarea/textarea.stories.tsx +8 -22
  112. package/src/components/textarea/textarea.tsx +17 -79
  113. package/src/components/timeline/timeline.stories.tsx +323 -42
  114. package/src/components/timeline/timeline.tsx +359 -132
  115. package/src/components/toast/toast.stories.tsx +1 -0
  116. package/src/components/tooltip/tooltip.tsx +11 -9
  117. package/src/components/tree/index.ts +0 -1
  118. package/src/components/tree/tree.stories.tsx +365 -408
  119. package/src/components/tree/tree.test.tsx +163 -0
  120. package/src/components/tree/tree.tsx +212 -36
  121. package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +5 -5
  122. package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +1 -3
  123. package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +6 -6
  124. package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +1 -1
  125. package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +1 -1
  126. package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +1 -1
  127. package/src/hooks/usePagination/usePagination.tsx +36 -24
  128. package/src/styles/theme.css +1 -1
  129. package/src/utils/form.tsx +67 -37
  130. package/src/utils/index.ts +1 -1
  131. package/src/__doc__/Migration.mdx +0 -475
  132. package/src/components/auto-complete/auto-complete-primitives.tsx +0 -155
  133. package/src/components/background-image/background-image.stories.tsx +0 -21
  134. package/src/components/background-image/background-image.test.tsx +0 -29
  135. package/src/components/background-image/background-image.tsx +0 -23
  136. package/src/components/background-image/index.ts +0 -1
  137. package/src/components/button/button.variants.ts +0 -44
  138. package/src/components/button/components/loader-overlay.tsx +0 -21
  139. package/src/components/button/components/loading-icon.tsx +0 -47
  140. package/src/components/dropzone/upload-primitives.tsx +0 -310
  141. package/src/components/dropzone/use-dropzone.ts +0 -122
  142. package/src/components/empty-state/empty-state.stories.tsx +0 -56
  143. package/src/components/empty-state/empty-state.tsx +0 -39
  144. package/src/components/empty-state/index.ts +0 -1
  145. package/src/components/heading/heading.stories.tsx +0 -74
  146. package/src/components/heading/heading.tsx +0 -28
  147. package/src/components/heading/heading.variants.ts +0 -27
  148. package/src/components/heading/index.ts +0 -1
  149. package/src/components/kbd/kbd.variants.ts +0 -26
  150. package/src/components/menu/util/render-menu-item.tsx +0 -54
  151. package/src/components/multi-select/hooks/use-multi-select.ts +0 -66
  152. package/src/components/multi-select/index.ts +0 -1
  153. package/src/components/multi-select/multi-select.stories.tsx +0 -294
  154. package/src/components/multi-select/multi-select.tsx +0 -300
  155. package/src/components/multi-select/multi-select.variants.ts +0 -22
  156. package/src/components/pagination/components/pagination-option.tsx +0 -27
  157. package/src/components/show/index.ts +0 -1
  158. package/src/components/show/show.stories.tsx +0 -197
  159. package/src/components/show/show.test.tsx +0 -41
  160. package/src/components/show/show.tsx +0 -16
  161. package/src/components/stepper/Stepper.tsx +0 -190
  162. package/src/components/stepper/context/stepper-context.tsx +0 -11
  163. package/src/components/table/table-primitives.tsx +0 -122
  164. package/src/components/table/table.model.ts +0 -20
  165. package/src/components/table-pagination/index.ts +0 -2
  166. package/src/components/table-pagination/table-pagination.model.ts +0 -2
  167. package/src/components/table-pagination/table-pagination.stories.tsx +0 -23
  168. package/src/components/table-pagination/table-pagination.test.tsx +0 -32
  169. package/src/components/table-pagination/table-pagination.tsx +0 -108
  170. package/src/components/tabs/context/tabs-context.tsx +0 -14
  171. package/src/components/tag/tag.variants.ts +0 -31
  172. package/src/components/timeline/timeline-status.ts +0 -5
  173. package/src/components/tree/hooks/use-controllable-tree-state.ts +0 -80
  174. package/src/components/tree/tree-primitives.tsx +0 -126
@@ -1,26 +1,31 @@
1
- import { faker } from "@faker-js/faker";
2
1
  import type { Meta, StoryObj } from "@storybook/react-vite";
3
- import { User } from "lucide-react";
2
+ import { Check, User } from "lucide-react";
4
3
  import { action } from "storybook/actions";
5
4
  import {
6
5
  Avatar,
6
+ AvatarBadge,
7
7
  AvatarFallback,
8
+ AvatarGroup,
9
+ AvatarGroupCount,
8
10
  AvatarImage,
9
11
  AvatarRoot,
10
12
  } from "../../components";
11
13
 
12
14
  /**
13
- * Displays a user's profile image with automatic fallback to text initials derived from `alt`.
15
+ * Circular user profile image with automatic fallback to text initials derived from `alt`.
14
16
  * Built on Base UI Avatar — image loading and fallback visibility are handled natively.
15
17
  * Use `delay` to defer the fallback appearance and avoid a flash during image load.
16
- * Exposes `AvatarRoot`, `AvatarImage`, `AvatarFallback` primitives for custom compositions.
18
+ * Pass `badge` to render a status/notification dot via `AvatarBadge`.
19
+ * Exposes `AvatarRoot`, `AvatarImage`, `AvatarFallback`, `AvatarBadge`,
20
+ * `AvatarGroup`, `AvatarGroupCount` primitives for custom compositions.
17
21
  */
18
22
  const meta: Meta<typeof Avatar> = {
19
23
  title: "components/Avatar",
20
24
  component: Avatar,
25
+ parameters: { layout: "centered" },
21
26
  args: {
22
- alt: faker.person.fullName(),
23
- src: faker.image.avatar(),
27
+ alt: "Sara Lane",
28
+ src: "https://i.pravatar.cc/150?img=47",
24
29
  delay: 1000,
25
30
  },
26
31
  } satisfies Meta<typeof Avatar>;
@@ -30,6 +35,22 @@ type Story = StoryObj<typeof meta>;
30
35
 
31
36
  export const Default: Story = {};
32
37
 
38
+ /**
39
+ * `className` styles the avatar root. `classNames` exposes the `image`,
40
+ * `fallback`, and `badge` slots for fine-grained styling without dropping
41
+ * to primitives.
42
+ */
43
+ export const WithClassNames: Story = {
44
+ args: {
45
+ badge: <Check />,
46
+ className: "ring-2 ring-ring",
47
+ classNames: {
48
+ fallback: "bg-primary text-primary-foreground",
49
+ badge: "bg-emerald-500",
50
+ },
51
+ },
52
+ };
53
+
33
54
  /**
34
55
  * When `src` is empty or the image fails to load, initials are extracted from `alt` automatically.
35
56
  */
@@ -39,46 +60,166 @@ export const FallbackText: Story = {
39
60
  },
40
61
  };
41
62
 
63
+ /**
64
+ * Pass any node to `fallback` to override the default initials — useful for generic
65
+ * placeholders, icons, or branded glyphs.
66
+ */
42
67
  export const CustomFallback: Story = {
43
68
  args: {
44
69
  src: "",
45
- fallback: <User />,
70
+ fallback: <User className="size-1/2" />,
46
71
  },
47
72
  };
48
73
 
49
74
  export const Sizes: Story = {
50
75
  render: (args) => (
51
- <div className="flex items-center gap-4">
52
- <Avatar {...args} size="xs" />
53
- <Avatar {...args} size="sm" />
54
- <Avatar {...args} size="md" />
55
- <Avatar {...args} size="lg" />
56
- <Avatar {...args} size="xl" />
76
+ <div className="flex items-end gap-6">
77
+ {(["xs", "sm", "md", "lg", "xl"] as const).map((size) => (
78
+ <div key={size} className="flex flex-col items-center gap-2">
79
+ <Avatar {...args} size={size} />
80
+ <span className="text-xs text-muted-foreground font-mono">
81
+ {size}
82
+ </span>
83
+ </div>
84
+ ))}
57
85
  </div>
58
86
  ),
59
87
  };
60
88
 
61
- export const Shapes: Story = {
62
- render: (args) => (
63
- <div className="flex items-center gap-4">
64
- <Avatar {...args} shape="circle" />
65
- <Avatar {...args} shape="square" />
89
+ /**
90
+ * `badge` mounts an `AvatarBadge` inside the root. The badge auto-scales with the
91
+ * avatar's `size` via `group-data-[size=*]/avatar` selectors and clips icons on
92
+ * the smallest sizes to keep the dot legible.
93
+ *
94
+ * For a plain status indicator, leave the badge empty (`<span />`) and override
95
+ * its color via `className`.
96
+ */
97
+ export const Badge: Story = {
98
+ render: () => (
99
+ <div className="flex items-end gap-6">
100
+ {(["sm", "md", "lg", "xl"] as const).map((size) => (
101
+ <div key={size} className="flex flex-col items-center gap-2">
102
+ <Avatar
103
+ src="https://i.pravatar.cc/150?img=47"
104
+ alt="Sara Lane"
105
+ size={size}
106
+ badge={<Check />}
107
+ />
108
+ <span className="text-xs text-muted-foreground font-mono">
109
+ {size}
110
+ </span>
111
+ </div>
112
+ ))}
113
+ </div>
114
+ ),
115
+ };
116
+
117
+ /**
118
+ * Status pattern via `AvatarBadge` on the primitive. The badge's color comes
119
+ * from `className`, the position and ring from the primitive itself.
120
+ */
121
+ export const Status: Story = {
122
+ render: () => (
123
+ <div className="flex items-center gap-6">
124
+ {(
125
+ [
126
+ { label: "online", color: "bg-emerald-500" },
127
+ { label: "away", color: "bg-amber-500" },
128
+ { label: "offline", color: "bg-zinc-400" },
129
+ ] as const
130
+ ).map(({ label, color }) => (
131
+ <div key={label} className="flex flex-col items-center gap-2">
132
+ <AvatarRoot size="lg">
133
+ <AvatarImage
134
+ src={`https://i.pravatar.cc/150?u=${label}`}
135
+ alt={label}
136
+ />
137
+ <AvatarFallback>{label.slice(0, 2).toUpperCase()}</AvatarFallback>
138
+ <AvatarBadge className={color} />
139
+ </AvatarRoot>
140
+ <span className="text-xs text-muted-foreground font-mono">
141
+ {label}
142
+ </span>
143
+ </div>
144
+ ))}
66
145
  </div>
67
146
  ),
68
147
  };
69
148
 
149
+ /**
150
+ * `AvatarGroup` clusters avatars with negative spacing and applies a background
151
+ * ring to every direct `Avatar` child so they read as overlapping discs.
152
+ * Combine with `AvatarGroupCount` for the trailing `+N` slot. Match `size`
153
+ * across children and the count to scale the cluster.
154
+ */
155
+ export const Group: Story = {
156
+ render: () => (
157
+ <AvatarGroup>
158
+ <Avatar src="https://i.pravatar.cc/150?img=47" alt="Sara Lane" />
159
+ <Avatar src="https://i.pravatar.cc/150?img=12" alt="John Doe" />
160
+ <Avatar src="https://i.pravatar.cc/150?img=32" alt="Alex Kim" />
161
+ <Avatar src="https://i.pravatar.cc/150?img=68" alt="Mia Chen" />
162
+ <AvatarGroupCount>+12</AvatarGroupCount>
163
+ </AvatarGroup>
164
+ ),
165
+ };
166
+
167
+ /**
168
+ * Direct composition with `AvatarRoot`, `AvatarImage`, `AvatarFallback`,
169
+ * `AvatarBadge` for full structural control. Use when the composite API
170
+ * doesn't expose what you need — custom children inside the fallback,
171
+ * layered overlays, etc.
172
+ *
173
+ * ```tsx
174
+ * <AvatarRoot size="lg">
175
+ * <AvatarImage src={src} alt="John Doe" />
176
+ * <AvatarFallback delay={1000}>JD</AvatarFallback>
177
+ * <AvatarBadge className="bg-emerald-500" />
178
+ * </AvatarRoot>
179
+ * ```
180
+ */
70
181
  export const Primitive: Story = {
71
182
  render: () => (
72
- <AvatarRoot shape="square" size="lg">
73
- <AvatarImage src={faker.image.avatar()} alt="John Doe" />
183
+ <AvatarRoot size="lg">
184
+ <AvatarImage src="https://i.pravatar.cc/150?img=13" alt="John Doe" />
74
185
  <AvatarFallback delay={1000}>JD</AvatarFallback>
186
+ <AvatarBadge className="bg-emerald-500" />
75
187
  </AvatarRoot>
76
188
  ),
77
189
  };
78
190
 
191
+ /**
192
+ * Avatars accept any `div` prop including `onClick`. Pair with `cursor-pointer`
193
+ * and a ring/hover style for interactive profile triggers.
194
+ */
79
195
  export const Clickable: Story = {
80
196
  args: {
81
197
  onClick: action("avatar:click"),
82
- className: "cursor-pointer",
198
+ className:
199
+ "cursor-pointer ring-2 ring-transparent hover:ring-ring transition-shadow",
83
200
  },
84
201
  };
202
+
203
+ /**
204
+ * Real-world usage: avatar paired with name, role, and an `AvatarBadge`
205
+ * showing online status — canonical card layout for user lists, mentions,
206
+ * and team rosters.
207
+ */
208
+ export const UserCard: Story = {
209
+ render: () => (
210
+ <div className="flex items-center gap-3 rounded-lg border bg-background p-4 shadow-sm">
211
+ <AvatarRoot size="lg">
212
+ <AvatarImage src="https://i.pravatar.cc/150?img=47" alt="Sara Lane" />
213
+ <AvatarFallback>SL</AvatarFallback>
214
+ <AvatarBadge className="bg-emerald-500" />
215
+ </AvatarRoot>
216
+ <div className="flex flex-col">
217
+ <span className="font-semibold leading-tight">Sara Lane</span>
218
+ <span className="text-sm text-muted-foreground leading-tight">
219
+ Senior Product Designer
220
+ </span>
221
+ <span className="text-xs text-muted-foreground mt-1">Active now</span>
222
+ </div>
223
+ </div>
224
+ ),
225
+ };
@@ -1,17 +1,13 @@
1
1
  import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
2
2
  import { cva, type VariantProps } from "class-variance-authority";
3
- import { type ReactNode } from "react";
3
+ import { type ComponentProps, type ReactNode } from "react";
4
4
  import { cn } from "../../lib";
5
5
  import { extractInitials } from "../../utils";
6
6
 
7
7
  const avatarVariants = cva(
8
- "overflow-hidden aspect-square bg-muted grid place-items-center font-semibold shrink-0 select-none",
8
+ "group/avatar relative flex aspect-square shrink-0 select-none rounded-full bg-muted items-center justify-center font-medium",
9
9
  {
10
10
  variants: {
11
- shape: {
12
- circle: "rounded-full",
13
- square: "rounded-xl",
14
- },
15
11
  size: {
16
12
  xs: "size-6 text-[10px]",
17
13
  sm: "size-8 text-xs",
@@ -21,7 +17,6 @@ const avatarVariants = cva(
21
17
  },
22
18
  },
23
19
  defaultVariants: {
24
- shape: "circle",
25
20
  size: "md",
26
21
  },
27
22
  },
@@ -29,14 +24,14 @@ const avatarVariants = cva(
29
24
 
30
25
  export function AvatarRoot({
31
26
  className,
32
- shape,
33
27
  size,
34
28
  ...props
35
29
  }: AvatarPrimitive.Root.Props & VariantProps<typeof avatarVariants>) {
36
30
  return (
37
31
  <AvatarPrimitive.Root
38
- className={cn(avatarVariants({ shape, size }), className)}
32
+ className={cn(avatarVariants({ size }), className)}
39
33
  data-slot="avatar"
34
+ data-size={size ?? "md"}
40
35
  {...props}
41
36
  />
42
37
  );
@@ -48,7 +43,10 @@ export function AvatarImage({
48
43
  }: AvatarPrimitive.Image.Props) {
49
44
  return (
50
45
  <AvatarPrimitive.Image
51
- className={cn("size-full object-cover", className)}
46
+ className={cn(
47
+ "aspect-square size-full rounded-full object-cover",
48
+ className,
49
+ )}
52
50
  data-slot="avatar-image"
53
51
  {...props}
54
52
  />
@@ -61,42 +59,103 @@ export function AvatarFallback({
61
59
  }: AvatarPrimitive.Fallback.Props) {
62
60
  return (
63
61
  <AvatarPrimitive.Fallback
64
- className={cn("flex size-full items-center justify-center", className)}
62
+ className={cn(
63
+ "flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground",
64
+ className,
65
+ )}
65
66
  data-slot="avatar-fallback"
66
67
  {...props}
67
68
  />
68
69
  );
69
70
  }
70
71
 
72
+ export function AvatarBadge({ className, ...props }: ComponentProps<"span">) {
73
+ return (
74
+ <span
75
+ data-slot="avatar-badge"
76
+ className={cn(
77
+ "absolute bottom-0 right-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground ring-2 ring-background select-none",
78
+ "group-data-[size=xs]/avatar:size-1.5 group-data-[size=xs]/avatar:[&>svg]:hidden",
79
+ "group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
80
+ "group-data-[size=md]/avatar:size-2.5 group-data-[size=md]/avatar:[&>svg]:size-2",
81
+ "group-data-[size=lg]/avatar:size-3.5 group-data-[size=lg]/avatar:[&>svg]:size-2.5",
82
+ "group-data-[size=xl]/avatar:size-4 group-data-[size=xl]/avatar:[&>svg]:size-3",
83
+ className,
84
+ )}
85
+ {...props}
86
+ />
87
+ );
88
+ }
89
+
90
+ export function AvatarGroup({ className, ...props }: ComponentProps<"div">) {
91
+ return (
92
+ <div
93
+ data-slot="avatar-group"
94
+ className={cn(
95
+ "group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
96
+ className,
97
+ )}
98
+ {...props}
99
+ />
100
+ );
101
+ }
102
+
103
+ export function AvatarGroupCount({
104
+ className,
105
+ size,
106
+ ...props
107
+ }: ComponentProps<"div"> & VariantProps<typeof avatarVariants>) {
108
+ return (
109
+ <div
110
+ data-slot="avatar-group-count"
111
+ className={cn(
112
+ avatarVariants({ size }),
113
+ "ring-2 ring-background bg-muted text-muted-foreground",
114
+ className,
115
+ )}
116
+ {...props}
117
+ />
118
+ );
119
+ }
120
+
71
121
  interface AvatarProps extends Omit<AvatarPrimitive.Root.Props, "children"> {
72
122
  src?: string;
73
123
  alt: string;
74
124
  fallback?: ReactNode;
125
+ badge?: ReactNode;
75
126
  delay?: AvatarPrimitive.Fallback.Props["delay"];
76
- shape?: VariantProps<typeof avatarVariants>["shape"];
77
127
  size?: VariantProps<typeof avatarVariants>["size"];
78
- imageProps?: AvatarPrimitive.Image.Props;
79
- fallbackProps?: AvatarPrimitive.Fallback.Props;
128
+ /** Styles applied to each internal slot. */
129
+ classNames?: {
130
+ /** Image element rendered when `src` loads successfully. */
131
+ image?: string;
132
+ /** Fallback rendered when no image is available. */
133
+ fallback?: string;
134
+ /** Status/notification badge rendered over the avatar. */
135
+ badge?: string;
136
+ };
80
137
  }
81
138
 
82
139
  export function Avatar({
83
140
  src,
84
141
  alt,
85
142
  fallback,
143
+ badge,
86
144
  delay,
87
- shape,
88
145
  size,
89
146
  className,
90
- imageProps,
91
- fallbackProps,
147
+ classNames,
92
148
  ...props
93
149
  }: AvatarProps) {
94
150
  return (
95
- <AvatarRoot shape={shape} size={size} className={className} {...props}>
96
- <AvatarImage src={src} alt={alt} {...imageProps} />
97
- <AvatarFallback delay={delay} {...fallbackProps}>
151
+ <AvatarRoot size={size} className={className} {...props}>
152
+ <AvatarImage src={src} alt={alt} className={classNames?.image} />
153
+ <AvatarFallback delay={delay} className={classNames?.fallback}>
98
154
  {fallback ?? extractInitials(alt)}
99
155
  </AvatarFallback>
156
+ {badge ? (
157
+ <AvatarBadge className={classNames?.badge}>{badge}</AvatarBadge>
158
+ ) : null}
100
159
  </AvatarRoot>
101
160
  );
102
161
  }