Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/forty-ants-promise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@nextui-org/avatar": patch
"@nextui-org/theme": patch
---

Support slots in AvatarGroup
23 changes: 12 additions & 11 deletions apps/docs/content/docs/components/avatar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,15 @@ You can customize any part of the avatar by using the `classNames` prop, each `s

### Avatar Group Props

| Attribute | Type | Description | Default |
| ----------- | ------------------------------ | --------------------------------------------------- | ------- |
| max | `number` | The maximum number of visible avatars | `5` |
| total | `number` | Control the number of avatar not visible | - |
| size | `AvatarProps['size']` | Size of the avatars | - |
| color | `AvatarProps['color']` | Color of the avatars | - |
| radius | `AvatarProps['radius']` | Radius of the avatars | - |
| isGrid | `boolean` | Whether the avatars should be displayed in a grid | `false` |
| isDisabled | `boolean` | Whether the avatars are disabled | - |
| isBordered | `boolean` | Whether the avatars have a border | - |
| renderCount | `(count: number) => ReactNode` | This allows you to render a custom count component. | - |
| Attribute | Type | Description | Default |
| ----------- | ---------------------------------- | --------------------------------------------------- | ------- |
| max | `number` | The maximum number of visible avatars | `5` |
| total | `number` | Control the number of avatar not visible | - |
| size | `AvatarProps['size']` | Size of the avatars | - |
| color | `AvatarProps['color']` | Color of the avatars | - |
| radius | `AvatarProps['radius']` | Radius of the avatars | - |
| isGrid | `boolean` | Whether the avatars should be displayed in a grid | `false` |
| isDisabled | `boolean` | Whether the avatars are disabled | - |
| isBordered | `boolean` | Whether the avatars have a border | - |
| renderCount | `(count: number) => ReactNode` | This allows you to render a custom count component. | - |
| classNames | `Record<"base"| "count", string>` | Allows to set custom class names for the avatar group slots. | - |
3 changes: 2 additions & 1 deletion packages/components/avatar/src/avatar-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ const AvatarGroup = forwardRef<"div", AvatarGroupProps>((props, ref) => {
clones,
context,
remainingCount,
renderCount = (count) => <Avatar className="hover:-translate-x-0" name={`+${count}`} />,
getAvatarGroupCountProps,
getAvatarGroupProps,
renderCount = (count) => <Avatar {...getAvatarGroupCountProps()} name={`+${count}`} />,
} = useAvatarGroup({
...props,
ref,
Expand Down
31 changes: 29 additions & 2 deletions packages/components/avatar/src/use-avatar-group.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {ReactNode} from "react";
import type {SlotsToClasses, AvatarGroupSlots, AvatarGroupVariantProps} from "@nextui-org/theme";

import {avatarGroup} from "@nextui-org/theme";
import {HTMLNextUIProps, PropGetter} from "@nextui-org/system";
Expand Down Expand Up @@ -31,9 +32,23 @@ interface Props extends HTMLNextUIProps<"div"> {
* This allows you to render a custom count component.
*/
renderCount?: (count: number) => ReactNode;
/**
* Classname or List of classes to change the classNames of the avatar group.
* if `className` is passed, it will be added to the base slot.
*
* @example
* ```ts
* <AvatarGroup classNames={{
* base: "base-classes",
* count: "count-classes"
* }} />
* ```
*/
classNames?: SlotsToClasses<AvatarGroupSlots>;
}

export type UseAvatarGroupProps = Props &
Omit<AvatarGroupVariantProps, "children" | "isGrid"> &
Partial<Pick<AvatarProps, "size" | "color" | "radius" | "isDisabled" | "isBordered">>;

export type ContextType = {
Expand All @@ -60,6 +75,7 @@ export function useAvatarGroup(props: UseAvatarGroupProps = {}) {
isGrid,
renderCount,
className,
classNames,
...otherProps
} = props;

Expand All @@ -78,7 +94,7 @@ export function useAvatarGroup(props: UseAvatarGroupProps = {}) {
}),
[size, color, radius, isGrid, isBordered, isDisabled],
);
const classNames = useMemo(() => avatarGroup({className, isGrid}), [className, isGrid]);
const slots = useMemo(() => avatarGroup({className, isGrid}), [className, isGrid]);

const validChildren = getValidChildren(children);
const childrenWithinMax = max ? validChildren.slice(0, max) : validChildren;
Expand All @@ -102,19 +118,30 @@ export function useAvatarGroup(props: UseAvatarGroupProps = {}) {
const getAvatarGroupProps: PropGetter = () => {
return {
ref: domRef,
className: classNames,
className: slots.base({
class: clsx(classNames?.base, className),
}),
role: "group",
...otherProps,
};
};

const getAvatarGroupCountProps = () => {
return {
className: slots.count({
class: classNames?.count,
}),
} as AvatarProps;
};

return {
Component,
context,
remainingCount,
clones,
renderCount,
getAvatarGroupProps,
getAvatarGroupCountProps,
};
}

Expand Down
52 changes: 52 additions & 0 deletions packages/components/avatar/stories/avatar-group.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,47 @@ const Template = (args: AvatarGroupProps) => (
</AvatarGroup>
);

const CustomSlotsTemplate = (args: AvatarGroupProps) => (
<AvatarGroup {...args}>
<Avatar
classNames={{base: "border-2 border-yellow-400"}}
radius="sm"
size="sm"
src="https://i.pravatar.cc/150?u=a042581f4e29026024d"
/>
<Avatar
classNames={{base: "border-2 border-yellow-500"}}
radius="sm"
size="sm"
src="https://i.pravatar.cc/150?u=a04258a2462d826712d"
/>
<Avatar
classNames={{base: "border-2 border-yellow-600"}}
radius="sm"
size="sm"
src="https://i.pravatar.cc/150?u=a042581f4e29026704d"
/>
<Avatar
classNames={{base: "border-2 border-yellow-700"}}
radius="sm"
size="sm"
src="https://i.pravatar.cc/150?u=a04258114e29026302d"
/>
<Avatar
classNames={{base: "border-2 border-yellow-500"}}
radius="sm"
size="sm"
src="https://i.pravatar.cc/150?u=a04258114e29026702d"
/>
<Avatar
classNames={{base: "border-2 border-yellow-500"}}
radius="sm"
size="sm"
src="https://i.pravatar.cc/150?u=a04258114e29026708c"
/>
</AvatarGroup>
);

export const Default = {
render: Template,

Expand Down Expand Up @@ -106,3 +147,14 @@ export const CustomCount = {
),
},
};

export const CustomSlots = {
render: CustomSlotsTemplate,

args: {
classNames: {count: "border-2 border-yellow-400"},
max: 3,
radius: "sm",
size: "sm",
},
};
2 changes: 1 addition & 1 deletion packages/core/react/src/scripts/postbuild.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function generateComponents() {
version: componentVersion,
docs: componentDocs,
description: componentDesc,
status: (routeComponent.updated && 'updated') || (routeComponent.newPost && 'newPost') || 'stable',
status: (routeComponent.updated && 'updated') || (routeComponent.newPost && 'new') || 'stable',
style: style || '',
}

Expand Down
6 changes: 5 additions & 1 deletion packages/core/theme/src/components/avatar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,10 @@ const avatar = tv({
* </div>
*/
const avatarGroup = tv({
base: "flex items-center justify-center h-auto w-max-content",
slots: {
base: "flex items-center justify-center h-auto w-max-content",
count: "hover:-translate-x-0",
},
variants: {
isGrid: {
true: "inline-grid grid-cols-4 gap-3",
Expand All @@ -200,6 +203,7 @@ const avatarGroup = tv({
// -ms-2 hover:-translate-x-0 ms-0

export type AvatarGroupVariantProps = VariantProps<typeof avatarGroup>;
export type AvatarGroupSlots = keyof ReturnType<typeof avatarGroup>;
export type AvatarVariantProps = VariantProps<typeof avatar>;
export type AvatarSlots = keyof ReturnType<typeof avatar>;

Expand Down