Skip to content

Commit

Permalink
fix(AvatarStack): correctly display more than 9 remaining avatars (#1882
Browse files Browse the repository at this point in the history
)
  • Loading branch information
ogonkov authored Oct 3, 2024
1 parent 5ce703f commit 0abd7f1
Show file tree
Hide file tree
Showing 15 changed files with 141 additions and 40 deletions.
47 changes: 43 additions & 4 deletions src/components/AvatarStack/AvatarStack.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,23 +37,62 @@ $block: '.#{variables.$ns}avatar-stack';
}
}

&__more-button {
@include mixins.button-reset;

&__more-button,
&__more {
border-radius: 100%;

width: var(--_--more-button-size);
height: var(--_--more-button-size);

&_size {
@each $size-name, $size-value in avatar-variables.$sizes {
&_#{$size-name} {
--_--more-button-size: #{$size-value};
}
}
}
}

&__more-button {
@include mixins.button-reset;

&:focus-visible {
outline: var(--g-color-line-focus) solid 2px;
outline-offset: 0;
}
}

&__more {
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;

background-color: var(--g-color-base-generic);

&_has-border {
border-width: var(--_--more-button-border-width);
border-style: solid;
}

&_size {
@each $size-name, $size-value in avatar-variables.$sizes {
&_#{$size-name} {
--_--more-button-size: #{$size-value};
@if $size-name == '2xs' or $size-name == 'xs' {
font-size: var(--g-text-caption-1-font-size);
}

@if $size-name == 's' {
font-size: var(--g-text-caption-2-font-size);
}

@if $size-name == 'm' or $size-name == 'l' {
font-size: var(--g-text-body-1-font-size);
}

@if $size-name == 'xl' {
font-size: var(--g-text-body-2-font-size);
}
}
}
}
Expand Down
14 changes: 6 additions & 8 deletions src/components/AvatarStack/AvatarStack.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import React from 'react';

import {Avatar} from '../Avatar';
import {block} from '../utils/cn';

import {AvatarStackItem} from './AvatarStackItem';
import {AvatarStackMore} from './AvatarStackMore';
import {AvatarStackMoreButton} from './AvatarStackMoreButton';
import i18n from './i18n';
import type {AvatarStackProps} from './types';

import './AvatarStack.scss';
Expand Down Expand Up @@ -55,11 +54,7 @@ const AvatarStackComponent = ({
{renderMore ? (
renderMore({count: moreItems})
) : (
<Avatar
text={`+${moreItems}`}
aria-label={i18n('more', {count: moreItems})}
size={size}
/>
<AvatarStackMore count={moreItems} size={size} />
)}
</AvatarStackItem>
) : null}
Expand All @@ -70,4 +65,7 @@ const AvatarStackComponent = ({

AvatarStackComponent.displayName = 'AvatarStack';

export const AvatarStack = Object.assign(AvatarStackComponent, {MoreButton: AvatarStackMoreButton});
export const AvatarStack = Object.assign(AvatarStackComponent, {
More: AvatarStackMore,
MoreButton: AvatarStackMoreButton,
});
36 changes: 36 additions & 0 deletions src/components/AvatarStack/AvatarStackMore.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';

import {DEFAULT_AVATAR_SIZE} from '../Avatar';
import {block} from '../utils/cn';

import i18n from './i18n';
import type {AvatarStackMoreProps} from './types';

const b = block('avatar-stack');

/**
* Badge for displaying count of remaining avatars
*/
export const AvatarStackMore = React.forwardRef<HTMLDivElement, AvatarStackMoreProps>(
(
{
className,
count,
'aria-label': ariaLabel,
borderColor = 'var(--g-color-line-generic-solid)',
size = DEFAULT_AVATAR_SIZE,
},
ref,
) => (
<div
ref={ref}
className={b('more', {size, 'has-border': Boolean(borderColor)}, className)}
aria-label={ariaLabel || i18n('more', {count})}
style={{borderColor}}
>
+{count}
</div>
),
);

AvatarStackMore.displayName = 'AvatarStack.More';
61 changes: 34 additions & 27 deletions src/components/AvatarStack/AvatarStackMoreButton.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,46 @@
import React from 'react';

import type {AvatarSize} from '../Avatar';
import {Avatar, DEFAULT_AVATAR_SIZE} from '../Avatar';
import {DEFAULT_AVATAR_SIZE} from '../Avatar';
import {block} from '../utils/cn';

import i18n from './i18n';
import {AvatarStackMore} from './AvatarStackMore';
import type {AvatarStackMoreButtonProps} from './types';

const b = block('avatar-stack');

export type AvatarStackMoreButtonProps = Pick<
React.HTMLProps<HTMLButtonElement>,
'className' | 'onClick' | 'aria-label'
> & {
size?: AvatarSize;
count: number;
};

export const AvatarStackMoreButton = React.forwardRef<
HTMLButtonElement,
AvatarStackMoreButtonProps
>(({className, size = DEFAULT_AVATAR_SIZE, onClick, count, 'aria-label': ariaLabel}, ref) => {
return (
<button
ref={ref}
type="button"
className={b('more-button', {size}, className)}
onClick={onClick}
>
<Avatar
text={`+${count}`}
size={size}
aria-label={ariaLabel || i18n('more', {count})}
/>
</button>
);
});
>(
(
{
className,
badgeClassName,
size = DEFAULT_AVATAR_SIZE,
onClick,
count,
'aria-label': ariaLabel,
borderColor,
},
ref,
) => {
return (
<button
ref={ref}
type="button"
className={b('more-button', {size}, className)}
onClick={onClick}
>
<AvatarStackMore
className={badgeClassName}
size={size}
count={count}
aria-label={ariaLabel}
borderColor={borderColor}
/>
</button>
);
},
);

AvatarStackMoreButton.displayName = 'AvatarStack.MoreButton';
2 changes: 2 additions & 0 deletions src/components/AvatarStack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,5 @@ Component for overriding more button
<Avatar imgUrl={`https://i.pravatar.cc/150?u=login3`} />
</AvatarStack>
```

Alternatively, `<AvatarStack.More/>` could be used. This component doesn't have `<button/>` wrap, and could be used for integration with some different wrap, like router links component.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 6 additions & 1 deletion src/components/AvatarStack/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
export {AvatarStack} from './AvatarStack';
export type {AvatarStackProps, AvatarStackOverlapSize} from './types';
export type {
AvatarStackProps,
AvatarStackOverlapSize,
AvatarStackMoreProps,
AvatarStackMoreButtonProps,
} from './types';
14 changes: 14 additions & 0 deletions src/components/AvatarStack/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,17 @@ export interface AvatarStackProps {
*/
renderMore?: (options: {count: number}) => React.ReactElement;
}

export type AvatarStackMoreProps = Pick<
React.HTMLProps<HTMLDivElement>,
'className' | 'aria-label'
> & {
count: number;
size?: AvatarSize;
borderColor?: string;
};

export type AvatarStackMoreButtonProps = Pick<React.HTMLProps<HTMLButtonElement>, 'onClick'> &
AvatarStackMoreProps & {
badgeClassName?: string;
};

0 comments on commit 0abd7f1

Please sign in to comment.