Skip to content

Commit 300ad87

Browse files
refactor: replace and remove deprecated Avatar component (coder#15930)
Close coder#14997
1 parent 137dc6e commit 300ad87

File tree

72 files changed

+461
-915
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+461
-915
lines changed

site/e2e/tests/deployment/workspaceProxies.spec.ts

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,12 @@ test("default proxy is online", async ({ page }) => {
2525
`table.MuiTable-root tr[data-testid="primary"]`,
2626
);
2727

28-
const workspaceProxyName = workspaceProxyPrimary.locator("td.name span");
29-
const workspaceProxyURL = workspaceProxyPrimary.locator("td.url");
30-
const workspaceProxyStatus = workspaceProxyPrimary.locator("td.status span");
28+
const summary = workspaceProxyPrimary.locator(".summary");
29+
const status = workspaceProxyPrimary.locator(".status");
3130

32-
await expect(workspaceProxyName).toHaveText("Default");
33-
await expect(workspaceProxyURL).toHaveText(`http://localhost:${coderPort}`);
34-
await expect(workspaceProxyStatus).toHaveText("Healthy");
31+
await expect(summary).toContainText("Default");
32+
await expect(summary).toContainText(`http://localhost:${coderPort}`);
33+
await expect(status).toContainText("Healthy");
3534
});
3635

3736
test("custom proxy is online", async ({ page }) => {
@@ -57,19 +56,16 @@ test("custom proxy is online", async ({ page }) => {
5756
waitUntil: "domcontentloaded",
5857
});
5958

60-
const workspaceProxy = page.locator("table.MuiTable-root tr", {
59+
const proxyRow = page.locator("table.MuiTable-root tr", {
6160
hasText: proxyName,
6261
});
6362

64-
const workspaceProxyName = workspaceProxy.locator("td.name span");
65-
const workspaceProxyURL = workspaceProxy.locator("td.url");
66-
const workspaceProxyStatus = workspaceProxy.locator("td.status span");
63+
const summary = proxyRow.locator(".summary");
64+
const status = proxyRow.locator(".status");
6765

68-
await expect(workspaceProxyName).toHaveText(proxyName);
69-
await expect(workspaceProxyURL).toHaveText(
70-
`http://127.0.0.1:${workspaceProxyPort}`,
71-
);
72-
await expect(workspaceProxyStatus).toHaveText("Healthy");
66+
await expect(summary).toContainText(proxyName);
67+
await expect(summary).toContainText(`http://127.0.0.1:${workspaceProxyPort}`);
68+
await expect(status).toContainText("Healthy");
7369

7470
// Tear down the proxy
7571
await stopWorkspaceProxy(proxyServer);
@@ -89,13 +85,13 @@ const waitUntilWorkspaceProxyIsHealthy = async (
8985
while (retries < maxRetries) {
9086
await page.reload();
9187

92-
const workspaceProxy = page.locator("table.MuiTable-root tr", {
88+
const proxyRow = page.locator("table.MuiTable-root tr", {
9389
hasText: proxyName,
9490
});
95-
const workspaceProxyStatus = workspaceProxy.locator("td.status span");
91+
const status = proxyRow.locator(".status");
9692

9793
try {
98-
await expect(workspaceProxyStatus).toHaveText("Healthy", {
94+
await expect(status).toContainText("Healthy", {
9995
timeout: 1_000,
10096
});
10197
return; // healthy!
Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import type { Meta, StoryObj } from "@storybook/react";
2-
import { Avatar, AvatarFallback, AvatarImage } from "./Avatar";
2+
import { Avatar } from "./Avatar";
33

44
const meta: Meta<typeof Avatar> = {
55
title: "components/Avatar",
66
component: Avatar,
77
args: {
8-
children: <AvatarImage src="https://github.com/kylecarbs.png" />,
8+
src: "https://github.com/kylecarbs.png",
99
},
1010
};
1111

@@ -16,7 +16,7 @@ export const ImageLgSize: Story = {
1616
args: { size: "lg" },
1717
};
1818

19-
export const ImageDefaultSize: Story = {};
19+
export const ImageMdSize: Story = {};
2020

2121
export const ImageSmSize: Story = {
2222
args: { size: "sm" },
@@ -26,48 +26,51 @@ export const IconLgSize: Story = {
2626
args: {
2727
size: "lg",
2828
variant: "icon",
29-
children: (
30-
<AvatarImage src="https://em-content.zobj.net/source/apple/391/billed-cap_1f9e2.png" />
31-
),
29+
src: "https://em-content.zobj.net/source/apple/391/billed-cap_1f9e2.png",
3230
},
3331
};
3432

35-
export const IconDefaultSize: Story = {
33+
export const IconMdSize: Story = {
3634
args: {
3735
variant: "icon",
38-
children: (
39-
<AvatarImage src="https://em-content.zobj.net/source/apple/391/billed-cap_1f9e2.png" />
40-
),
36+
src: "https://em-content.zobj.net/source/apple/391/billed-cap_1f9e2.png",
4137
},
4238
};
4339

4440
export const IconSmSize: Story = {
4541
args: {
4642
variant: "icon",
4743
size: "sm",
48-
children: (
49-
<AvatarImage src="https://em-content.zobj.net/source/apple/391/billed-cap_1f9e2.png" />
50-
),
44+
src: "https://em-content.zobj.net/source/apple/391/billed-cap_1f9e2.png",
45+
},
46+
};
47+
48+
export const NonSquaredIcon: Story = {
49+
args: {
50+
variant: "icon",
51+
src: "/icon/docker.png",
5152
},
5253
};
5354

5455
export const FallbackLgSize: Story = {
5556
args: {
57+
src: "",
5658
size: "lg",
57-
58-
children: <AvatarFallback>AR</AvatarFallback>,
59+
fallback: "Adriana Rodrigues",
5960
},
6061
};
6162

62-
export const FallbackDefaultSize: Story = {
63+
export const FallbackMdSize: Story = {
6364
args: {
64-
children: <AvatarFallback>AR</AvatarFallback>,
65+
src: "",
66+
fallback: "Adriana Rodrigues",
6567
},
6668
};
6769

6870
export const FallbackSmSize: Story = {
6971
args: {
72+
src: "",
7073
size: "sm",
71-
children: <AvatarFallback>AR</AvatarFallback>,
74+
fallback: "Adriana Rodrigues",
7275
},
7376
};

site/src/components/Avatar/Avatar.tsx

Lines changed: 48 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,92 @@
1-
import * as AvatarPrimitive from "@radix-ui/react-avatar";
2-
import { type VariantProps, cva } from "class-variance-authority";
31
/**
42
* Copied from shadc/ui on 12/16/2024
53
* @see {@link https://ui.shadcn.com/docs/components/avatar}
64
*
75
* This component was updated to support the variants and match the styles from
86
* the Figma design:
97
* @see {@link https://www.figma.com/design/WfqIgsTFXN2BscBSSyXWF8/Coder-kit?node-id=711-383&t=xqxOSUk48GvDsjGK-0}
8+
*
9+
* It was also simplified to make usage easier and reduce boilerplate.
10+
* @see {@link https://github.com/coder/coder/pull/15930#issuecomment-2552292440}
1011
*/
12+
13+
import { useTheme } from "@emotion/react";
14+
import * as AvatarPrimitive from "@radix-ui/react-avatar";
15+
import { type VariantProps, cva } from "class-variance-authority";
1116
import * as React from "react";
17+
import { getExternalImageStylesFromUrl } from "theme/externalImages";
1218
import { cn } from "utils/cn";
1319

1420
const avatarVariants = cva(
1521
"relative flex shrink-0 overflow-hidden rounded border border-solid bg-surface-secondary text-content-secondary",
1622
{
1723
variants: {
1824
size: {
19-
lg: "h-10 w-10 rounded-[6px] text-sm font-medium",
20-
default: "h-6 w-6 text-2xs",
21-
sm: "h-[18px] w-[18px] text-[8px]",
25+
lg: "h-[--avatar-lg] w-[--avatar-lg] rounded-[6px] text-sm font-medium",
26+
md: "h-[--avatar-default] w-[--avatar-default] text-2xs",
27+
sm: "h-[--avatar-sm] w-[--avatar-sm] text-[8px]",
2228
},
2329
variant: {
24-
default: "",
25-
icon: "",
30+
default: null,
31+
icon: null,
2632
},
2733
},
2834
defaultVariants: {
29-
size: "default",
35+
size: "md",
3036
},
3137
compoundVariants: [
3238
{
3339
size: "lg",
3440
variant: "icon",
35-
className: "p-[9px]",
41+
className: "p-2",
3642
},
3743
{
38-
size: "default",
44+
size: "md",
3945
variant: "icon",
40-
className: "p-[3px]",
46+
className: "p-1",
4147
},
4248
{
4349
size: "sm",
4450
variant: "icon",
45-
className: "p-[2px]",
51+
className: "p-[3px]",
4652
},
4753
],
4854
},
4955
);
5056

51-
export interface AvatarProps
52-
extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>,
53-
VariantProps<typeof avatarVariants> {}
57+
export type AvatarProps = AvatarPrimitive.AvatarProps &
58+
VariantProps<typeof avatarVariants> & {
59+
src?: string;
60+
61+
fallback?: string;
62+
};
5463

5564
const Avatar = React.forwardRef<
5665
React.ElementRef<typeof AvatarPrimitive.Root>,
5766
AvatarProps
58-
>(({ className, size, variant, ...props }, ref) => (
59-
<AvatarPrimitive.Root
60-
ref={ref}
61-
className={cn(avatarVariants({ size, variant, className }))}
62-
{...props}
63-
/>
64-
));
65-
Avatar.displayName = AvatarPrimitive.Root.displayName;
66-
67-
const AvatarImage = React.forwardRef<
68-
React.ElementRef<typeof AvatarPrimitive.Image>,
69-
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
70-
>(({ className, ...props }, ref) => (
71-
<AvatarPrimitive.Image
72-
ref={ref}
73-
className={cn("aspect-square h-full w-full", className)}
74-
{...props}
75-
/>
76-
));
77-
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
67+
>(({ className, size, variant, src, fallback, children, ...props }, ref) => {
68+
const theme = useTheme();
7869

79-
const AvatarFallback = React.forwardRef<
80-
React.ElementRef<typeof AvatarPrimitive.Fallback>,
81-
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
82-
>(({ className, ...props }, ref) => (
83-
<AvatarPrimitive.Fallback
84-
ref={ref}
85-
className={cn(
86-
"flex h-full w-full items-center justify-center rounded-full",
87-
className,
88-
)}
89-
{...props}
90-
/>
91-
));
92-
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
70+
return (
71+
<AvatarPrimitive.Root
72+
ref={ref}
73+
className={cn(avatarVariants({ size, variant, className }))}
74+
{...props}
75+
>
76+
<AvatarPrimitive.Image
77+
src={src}
78+
className="aspect-square h-full w-full object-contain"
79+
css={getExternalImageStylesFromUrl(theme.externalImages, src)}
80+
/>
81+
{fallback && (
82+
<AvatarPrimitive.Fallback className="flex h-full w-full items-center justify-center rounded-full">
83+
{fallback.charAt(0).toUpperCase()}
84+
</AvatarPrimitive.Fallback>
85+
)}
86+
{children}
87+
</AvatarPrimitive.Root>
88+
);
89+
});
90+
Avatar.displayName = AvatarPrimitive.Root.displayName;
9391

94-
export { Avatar, AvatarImage, AvatarFallback };
92+
export { Avatar };

site/src/components/AvatarCard/AvatarCard.stories.tsx renamed to site/src/components/Avatar/AvatarCard.stories.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export const WithImage: Story = {
1313
args: {
1414
header: "Coder",
1515
imgUrl: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4",
16-
altText: "Coder",
1716
subtitle: "56 members",
1817
},
1918
};

site/src/components/AvatarCard/AvatarCard.tsx renamed to site/src/components/Avatar/AvatarCard.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
11
import { type CSSObject, useTheme } from "@emotion/react";
2-
import { Avatar } from "components/deprecated/Avatar/Avatar";
2+
import { Avatar } from "components/Avatar/Avatar";
33
import type { FC, ReactNode } from "react";
44

55
type AvatarCardProps = {
66
header: string;
77
imgUrl: string;
8-
altText: string;
9-
background?: boolean;
10-
118
subtitle?: ReactNode;
129
maxWidth?: number | "none";
1310
};
1411

1512
export const AvatarCard: FC<AvatarCardProps> = ({
1613
header,
1714
imgUrl,
18-
altText,
19-
background,
2015
subtitle,
2116
maxWidth = "none",
2217
}) => {
@@ -72,9 +67,7 @@ export const AvatarCard: FC<AvatarCardProps> = ({
7267
)}
7368
</div>
7469

75-
<Avatar background={background} src={imgUrl} alt={altText} size="md">
76-
{header}
77-
</Avatar>
70+
<Avatar size="lg" src={imgUrl} fallback={header} />
7871
</div>
7972
);
8073
};

site/src/components/AvatarData/AvatarData.tsx renamed to site/src/components/Avatar/AvatarData.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useTheme } from "@emotion/react";
2+
import { Avatar } from "components/Avatar/Avatar";
23
import { Stack } from "components/Stack/Stack";
3-
import { Avatar } from "components/deprecated/Avatar/Avatar";
44
import type { FC, ReactNode } from "react";
55

66
export interface AvatarDataProps {
@@ -29,17 +29,17 @@ export const AvatarData: FC<AvatarDataProps> = ({
2929
const theme = useTheme();
3030
if (!avatar) {
3131
avatar = (
32-
<Avatar background src={src}>
33-
{(typeof title === "string" ? title : imgFallbackText) || "-"}
34-
</Avatar>
32+
<Avatar
33+
src={src}
34+
fallback={(typeof title === "string" ? title : imgFallbackText) || "-"}
35+
/>
3536
);
3637
}
3738

3839
return (
3940
<Stack
40-
spacing={1.5}
41+
spacing={1}
4142
direction="row"
42-
alignItems="center"
4343
css={{
4444
minHeight: 40, // Make it predictable for the skeleton
4545
width: "100%",

0 commit comments

Comments
 (0)