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 6 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
21 changes: 18 additions & 3 deletions apps/site/components/Common/AvatarGroup/Avatar/index.module.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
.avatar {
.item {
@apply flex
size-8
h-full
max-h-10
w-full
max-w-10
items-center
justify-center
rounded-full
Expand All @@ -15,9 +18,21 @@
dark:text-neutral-300;
}

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

&:hover {
@apply z-10;
}
}

.small {
@apply size-8;
}

.medium {
@apply size-10;
}
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
37 changes: 25 additions & 12 deletions apps/site/components/Common/AvatarGroup/Avatar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,40 @@
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 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';
};

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

export default Avatar;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.overlay {
@apply flex
min-w-56
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;
}
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> & {
website?: string;
canerakdas marked this conversation as resolved.
Show resolved Hide resolved
};

const AvatarOverlay: FC<AvatarOverlayProps> = ({
image,
name,
nickname,
fallback,
website,
}) => (
<Link className={styles.overlay} href={website} 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
21 changes: 18 additions & 3 deletions apps/site/components/Common/AvatarGroup/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,25 @@ const names = [
];

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

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

const avatarOverlay = {
avatars: [
{
image: getGitHubAvatarUrl('nodejs'),
name: 'Node.js',
nickname: 'nodejs',
website: 'https://nodejs.org',
},
],
};

Expand All @@ -46,6 +57,10 @@ export const WithCustomLimit: Story = {
},
};

export const WithOverlay: Story = {
args: avatarOverlay,
};

export const InSmallContainer: Story = {
decorators: [
Story => (
Expand Down
39 changes: 24 additions & 15 deletions apps/site/components/Common/AvatarGroup/index.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
'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'>>;
export type AvatarGroupProps = {
avatars: Array<AvatarProps & { website?: string }>;
limit?: number;
isExpandable?: boolean;
size?: AvatarProps['size'];
};

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

Expand All @@ -30,21 +34,26 @@ 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(({ website, ...avatar }) => (
<Fragment key={avatar.nickname}>
{website ? (
<Tooltip
canerakdas marked this conversation as resolved.
Show resolved Hide resolved
content={<AvatarOverlay {...avatar} website={website} />}
asChild
>
<Avatar {...avatar} size={size} className="cursor-pointer" />
</Tooltip>
) : (
<Avatar {...avatar} size={size} />
)}
</Fragment>
))}

{avatars.length > limit && (
<span
onClick={isExpandable ? () => setShowMore(prev => !prev) : undefined}
className={classNames(avatarstyles.avatarRoot, 'cursor-pointer')}
className={classNames(avatarstyles.avatar, 'cursor-pointer')}
>
<span className={avatarstyles.avatar}>
<span className={avatarstyles.item}>
{`${showMore ? '-' : '+'}${avatars.length - limit}`}
</span>
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,38 +65,32 @@ describe('BlogPostCard', () => {
);

it('Renders all passed authors fullName(s), comma-separated', () => {
const authors = [
{ fullName: 'Jane Doe', src: '' },
{ fullName: 'John Doe', src: '' },
];
const authors = ['Jane Doe', 'John Doe'];

renderBlogPostCard({ authors });

const fullNames = authors.reduce((prev, curr, index) => {
if (index === 0) {
return curr.fullName;
return curr;
}

return `${prev}, ${curr.fullName}`;
return `${prev}, ${curr}`;
}, '');

expect(screen.getByText(fullNames)).toBeVisible();
});

it('Renders all passed authors fullName(s), comma-separated', () => {
const authors = [
{ fullName: 'Jane Doe', src: '' },
{ fullName: 'John Doe', src: '' },
];
const authors = ['Jane Doe', 'John Doe'];

renderBlogPostCard({ authors });

const fullNames = authors.reduce((prev, curr, index) => {
if (index === 0) {
return curr.fullName;
return curr;
}

return `${prev}, ${curr.fullName}`;
return `${prev}, ${curr}`;
}, '');

expect(screen.getByText(fullNames)).toBeVisible();
Expand Down
15 changes: 2 additions & 13 deletions apps/site/components/Common/BlogPostCard/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,7 @@ export const Default: Story = {
category: 'vulnerability',
description:
'Starting on March 15th and going through to March 17th (with much of the issue being mitigated on the 16th), users were receiving intermittent 404 responses when trying to download Node.js from nodejs.org, or even accessing parts of the website.',
authors: [
{
fullName: 'Hayden Bleasel',
src: 'https://avatars.githubusercontent.com/u/',
},
],
authors: ['Hayden Bleasel'],
canerakdas marked this conversation as resolved.
Show resolved Hide resolved
slug: '/blog/vulnerability/something',
date: new Date('17 October 2023'),
},
Expand All @@ -33,13 +28,7 @@ export const MoreThanOneAuthor: Story = {
...Default,
args: {
...Default.args,
authors: [
...(Default.args?.authors ?? []),
{
fullName: 'Jane Doe',
src: 'https://avatars.githubusercontent.com/u/',
},
],
authors: [...(Default.args?.authors ?? []), 'Jane Doe'],
},
};

Expand Down
Loading
Loading