Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Introducing avatar tooltip #7143

Merged
merged 24 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1024e8a
feat: tooltip component and avatar tooltip
canerakdas Oct 25, 2024
4a82887
chore: metabar story props updated
canerakdas Oct 25, 2024
683fe09
chore: self review
canerakdas Oct 25, 2024
ef4be2f
chore: self review
canerakdas Oct 25, 2024
f87e418
refactor: class names
canerakdas Oct 26, 2024
ed52e02
refactor: horizontal margin added
canerakdas Oct 26, 2024
672e2cb
feat: accessible avatars on mobile
canerakdas Oct 30, 2024
047e3c9
chore: resolve conflict
canerakdas Oct 30, 2024
0f48558
feat: default author url
canerakdas Oct 30, 2024
6ae4ebf
refactor: review updates
canerakdas Oct 31, 2024
728bfde
chore: self review
canerakdas Oct 31, 2024
1d1587d
Merge branch 'main' into feat/avatar-tooltip
ovflowd Oct 31, 2024
b8d9607
refactor: design and review updates
canerakdas Nov 2, 2024
9365d1d
Merge branch 'feat/avatar-tooltip' of https://github.com/canerakdas/n…
canerakdas Nov 2, 2024
667cfda
fix: Avatars in MetaBar story
canerakdas Nov 2, 2024
6611dc0
refactor: review updates
canerakdas Nov 2, 2024
b0012da
fix: opening the tooltip portal within the dialog
canerakdas Nov 5, 2024
17f3be9
Merge branch 'main' into feat/avatar-tooltip
canerakdas Nov 5, 2024
816b7da
fix: adjusting visible avatar count
canerakdas Nov 5, 2024
5a76b83
chore: resolve conflict
canerakdas Nov 16, 2024
a37d2dc
refactor: review updates
canerakdas Nov 16, 2024
b062f39
Update apps/site/util/authorUtils.ts
canerakdas Nov 16, 2024
7c8f95e
refactor: enhancing code readability
canerakdas Nov 16, 2024
912d47d
refactor: review update
canerakdas Nov 18, 2024
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
36 changes: 29 additions & 7 deletions apps/site/components/Common/AvatarGroup/Avatar/index.module.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
.avatar {
@apply flex
size-8
.item {
@apply xs:max-h-10
xs:max-w-10
flex
h-full
max-h-12
w-full
max-w-12
items-center
justify-center
rounded-full
Expand All @@ -15,9 +20,26 @@
dark:text-neutral-300;
}

.avatarRoot {
@apply -ml-2
size-8
flex-shrink-0
.avatar {
@apply size-8
flex-shrink-0;

.wrapper {
@apply max-xs:block
max-xs:py-0;
}
}

.small {
@apply xs:size-8
xs:-ml-2
ml-0.5
size-10
first:ml-0;
}

.medium {
@apply -ml-2.5
size-10
first:ml-0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,23 @@ type Meta = MetaObj<typeof Avatar>;

export const Default: Story = {
args: {
src: getGitHubAvatarUrl('ovflowd'),
alt: 'ovflowd',
image: getGitHubAvatarUrl('ovflowd'),
nickname: 'ovflowd',
},
};

export const NoSquare: Story = {
args: {
src: '/static/images/logos/nodejs.png',
alt: 'SD',
image: '/static/images/logo-hexagon-card.png',
nickname: 'SD',
},
};

export const FallBack: Story = {
args: {
src: 'https://avatars.githubusercontent.com/u/',
alt: 'UA',
image: 'https://avatars.githubusercontent.com/u/',
nickname: 'John Doe',
fallback: 'JD',
},
};

Expand Down
62 changes: 44 additions & 18 deletions apps/site/components/Common/AvatarGroup/Avatar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,53 @@
import * as RadixAvatar from '@radix-ui/react-avatar';
import type { FC } from 'react';
import classNames from 'classnames';
import type { ComponentPropsWithoutRef, ElementRef } from 'react';
import { forwardRef } from 'react';

import Link from '@/components/Link';

import styles from './index.module.css';

export type AvatarProps = {
src: string;
alt: string;
fallback: string;
image?: string;
name?: string;
nickname: string;
canerakdas marked this conversation as resolved.
Show resolved Hide resolved
fallback?: string;
size?: 'small' | 'medium';
url?: string;
};

const Avatar: FC<AvatarProps> = ({ src, alt, fallback }) => (
<RadixAvatar.Root className={styles.avatarRoot}>
<RadixAvatar.Image
loading="lazy"
src={src}
alt={alt}
title={alt}
className={styles.avatar}
/>
<RadixAvatar.Fallback delayMs={500} className={styles.avatar}>
{fallback}
</RadixAvatar.Fallback>
</RadixAvatar.Root>
);
const Avatar = forwardRef<
ElementRef<typeof RadixAvatar.Root>,
ComponentPropsWithoutRef<typeof RadixAvatar.Root> & AvatarProps
>(({ image, nickname, name, fallback, url, size = 'small', ...props }, ref) => {
const Wrapper = url ? Link : 'div';

return (
<RadixAvatar.Root
{...props}
className={classNames(styles.avatar, styles[size], props.className)}
ref={ref}
>
<Wrapper
href={url || undefined}
target={url ? '_blank' : undefined}
className={styles.wrapper}
>
<RadixAvatar.Image
loading="lazy"
src={image}
alt={name || nickname}
className={styles.item}
/>
<RadixAvatar.Fallback
delayMs={500}
className={classNames(styles.item, styles[size])}
>
{fallback}
</RadixAvatar.Fallback>
</Wrapper>
</RadixAvatar.Root>
);
});

export default Avatar;
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.overlay {
@apply flex
min-w-56
items-center
gap-2
p-3;
}

.user {
@apply grow;
}

.name {
@apply font-semibold
text-neutral-900
dark:text-neutral-300;
}

.nickname {
@apply font-medium
text-neutral-700
dark:text-neutral-500;
}

.arrow {
@apply w-3
fill-neutral-600
dark:fill-white;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Meta as MetaObj, StoryObj } from '@storybook/react';

import AvatarOverlay from '@/components/Common/AvatarGroup/Overlay';
import { getAuthorWithId, getAuthorWithName } from '@/util/authorUtils';

type Story = StoryObj<typeof AvatarOverlay>;
type Meta = MetaObj<typeof AvatarOverlay>;

export const Default: Story = {
args: getAuthorWithId(['nodejs'], true)[0],
};

export const FallBack: Story = {
args: getAuthorWithName(['Node.js'], true)[0],
};

export const WithoutName: Story = {
args: getAuthorWithId(['canerakdas'], true)[0],
};

export default { component: AvatarOverlay } as Meta;
36 changes: 36 additions & 0 deletions apps/site/components/Common/AvatarGroup/Overlay/index.tsx
canerakdas marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ArrowUpRightIcon } from '@heroicons/react/24/solid';
import type { ComponentProps, FC } from 'react';

import Avatar from '@/components/Common/AvatarGroup/Avatar';
import Link from '@/components/Link';

import styles from './index.module.css';

export type AvatarOverlayProps = ComponentProps<typeof Avatar> & {
url?: string;
};

const AvatarOverlay: FC<AvatarOverlayProps> = ({
image,
name,
nickname,
fallback,
url,
}) => (
<Link className={styles.overlay} href={url} target="_blank">
<Avatar
image={image}
name={name}
nickname={nickname}
fallback={fallback}
size="medium"
/>
<div className={styles.user}>
{name && <div className={styles.name}>{name}</div>}
{nickname && <div className={styles.nickname}>{nickname}</div>}
</div>
<ArrowUpRightIcon className={styles.arrow} />
</Link>
);

export default AvatarOverlay;
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ const names = [
];

const avatars = names.map(name => ({
src: getGitHubAvatarUrl(name),
alt: name,
image: getGitHubAvatarUrl(name),
nickname: name,
}));

describe('AvatarGroup', () => {
Expand Down
12 changes: 5 additions & 7 deletions apps/site/components/Common/AvatarGroup/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Meta as MetaObj, StoryObj } from '@storybook/react';

import AvatarGroup from '@/components/Common/AvatarGroup';
import { getGitHubAvatarUrl } from '@/util/gitHubUtils';
import { getAuthorWithId } from '@/util/authorUtils';

type Story = StoryObj<typeof AvatarGroup>;
type Meta = MetaObj<typeof AvatarGroup>;
Expand All @@ -24,15 +24,13 @@ const names = [
];

const unknownAvatar = {
src: 'https://avatars.githubusercontent.com/u/',
alt: 'unknown-avatar',
image: 'https://avatars.githubusercontent.com/u/',
nickname: 'unknown-avatar',
fallback: 'UA',
};

const defaultProps = {
avatars: [
unknownAvatar,
...names.map(name => ({ src: getGitHubAvatarUrl(name), alt: name })),
],
avatars: [unknownAvatar, ...getAuthorWithId(names, true)],
};

export const Default: Story = {
Expand Down
47 changes: 33 additions & 14 deletions apps/site/components/Common/AvatarGroup/index.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
'use client';

import classNames from 'classnames';
import type { ComponentProps, FC } from 'react';
import { useState, useMemo } from 'react';
import type { FC } from 'react';
import { useState, useMemo, Fragment } from 'react';

import type { AvatarProps } from '@/components/Common/AvatarGroup/Avatar';
import Avatar from '@/components/Common/AvatarGroup/Avatar';
import avatarstyles from '@/components/Common/AvatarGroup/Avatar/index.module.css';
import { getAcronymFromString } from '@/util/stringUtils';
import AvatarOverlay from '@/components/Common/AvatarGroup/Overlay';
import Tooltip from '@/components/Common/Tooltip';

import styles from './index.module.css';

type AvatarGroupProps = {
avatars: Array<Omit<ComponentProps<typeof Avatar>, 'fallback'>>;
avatars: Array<AvatarProps>;
limit?: number;
isExpandable?: boolean;
size?: AvatarProps['size'];
container?: HTMLElement;
};

const AvatarGroup: FC<AvatarGroupProps> = ({
avatars,
limit = 10,
isExpandable = true,
size = 'small',
container,
}) => {
const [showMore, setShowMore] = useState(false);

Expand All @@ -30,21 +36,34 @@ const AvatarGroup: FC<AvatarGroupProps> = ({

return (
<div className={styles.avatarGroup}>
{renderAvatars.map((avatar, index) => (
<Avatar
src={avatar.src}
alt={avatar.alt}
fallback={getAcronymFromString(avatar.alt)}
key={index}
/>
{renderAvatars.map(({ ...avatar }) => (
<Fragment key={avatar.nickname}>
<Tooltip
asChild
container={container}
content={<AvatarOverlay {...avatar} />}
>
<Avatar
{...avatar}
size={size}
className={classNames({
'cursor-pointer': avatar.url,
'pointer-events-none': !avatar.url,
})}
/>
</Tooltip>
</Fragment>
))}

{avatars.length > limit && (
<span
onClick={isExpandable ? () => setShowMore(prev => !prev) : undefined}
className={classNames(avatarstyles.avatarRoot, 'cursor-pointer')}
className={classNames(
avatarstyles.avatar,
avatarstyles[size],
'cursor-pointer'
)}
>
<span className={avatarstyles.avatar}>
<span className={avatarstyles.item}>
{`${showMore ? '-' : '+'}${avatars.length - limit}`}
</span>
</span>
Expand Down
Loading
Loading