mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
refactor: replace and remove deprecated Avatar component (#15930)
Close https://github.com/coder/coder/issues/14997
This commit is contained in:
@@ -25,13 +25,12 @@ test("default proxy is online", async ({ page }) => {
|
||||
`table.MuiTable-root tr[data-testid="primary"]`,
|
||||
);
|
||||
|
||||
const workspaceProxyName = workspaceProxyPrimary.locator("td.name span");
|
||||
const workspaceProxyURL = workspaceProxyPrimary.locator("td.url");
|
||||
const workspaceProxyStatus = workspaceProxyPrimary.locator("td.status span");
|
||||
const summary = workspaceProxyPrimary.locator(".summary");
|
||||
const status = workspaceProxyPrimary.locator(".status");
|
||||
|
||||
await expect(workspaceProxyName).toHaveText("Default");
|
||||
await expect(workspaceProxyURL).toHaveText(`http://localhost:${coderPort}`);
|
||||
await expect(workspaceProxyStatus).toHaveText("Healthy");
|
||||
await expect(summary).toContainText("Default");
|
||||
await expect(summary).toContainText(`http://localhost:${coderPort}`);
|
||||
await expect(status).toContainText("Healthy");
|
||||
});
|
||||
|
||||
test("custom proxy is online", async ({ page }) => {
|
||||
@@ -57,19 +56,16 @@ test("custom proxy is online", async ({ page }) => {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
|
||||
const workspaceProxy = page.locator("table.MuiTable-root tr", {
|
||||
const proxyRow = page.locator("table.MuiTable-root tr", {
|
||||
hasText: proxyName,
|
||||
});
|
||||
|
||||
const workspaceProxyName = workspaceProxy.locator("td.name span");
|
||||
const workspaceProxyURL = workspaceProxy.locator("td.url");
|
||||
const workspaceProxyStatus = workspaceProxy.locator("td.status span");
|
||||
const summary = proxyRow.locator(".summary");
|
||||
const status = proxyRow.locator(".status");
|
||||
|
||||
await expect(workspaceProxyName).toHaveText(proxyName);
|
||||
await expect(workspaceProxyURL).toHaveText(
|
||||
`http://127.0.0.1:${workspaceProxyPort}`,
|
||||
);
|
||||
await expect(workspaceProxyStatus).toHaveText("Healthy");
|
||||
await expect(summary).toContainText(proxyName);
|
||||
await expect(summary).toContainText(`http://127.0.0.1:${workspaceProxyPort}`);
|
||||
await expect(status).toContainText("Healthy");
|
||||
|
||||
// Tear down the proxy
|
||||
await stopWorkspaceProxy(proxyServer);
|
||||
@@ -89,13 +85,13 @@ const waitUntilWorkspaceProxyIsHealthy = async (
|
||||
while (retries < maxRetries) {
|
||||
await page.reload();
|
||||
|
||||
const workspaceProxy = page.locator("table.MuiTable-root tr", {
|
||||
const proxyRow = page.locator("table.MuiTable-root tr", {
|
||||
hasText: proxyName,
|
||||
});
|
||||
const workspaceProxyStatus = workspaceProxy.locator("td.status span");
|
||||
const status = proxyRow.locator(".status");
|
||||
|
||||
try {
|
||||
await expect(workspaceProxyStatus).toHaveText("Healthy", {
|
||||
await expect(status).toContainText("Healthy", {
|
||||
timeout: 1_000,
|
||||
});
|
||||
return; // healthy!
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "./Avatar";
|
||||
import { Avatar } from "./Avatar";
|
||||
|
||||
const meta: Meta<typeof Avatar> = {
|
||||
title: "components/Avatar",
|
||||
component: Avatar,
|
||||
args: {
|
||||
children: <AvatarImage src="https://github.com/kylecarbs.png" />,
|
||||
src: "https://github.com/kylecarbs.png",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@ export const ImageLgSize: Story = {
|
||||
args: { size: "lg" },
|
||||
};
|
||||
|
||||
export const ImageDefaultSize: Story = {};
|
||||
export const ImageMdSize: Story = {};
|
||||
|
||||
export const ImageSmSize: Story = {
|
||||
args: { size: "sm" },
|
||||
@@ -26,18 +26,14 @@ export const IconLgSize: Story = {
|
||||
args: {
|
||||
size: "lg",
|
||||
variant: "icon",
|
||||
children: (
|
||||
<AvatarImage src="https://em-content.zobj.net/source/apple/391/billed-cap_1f9e2.png" />
|
||||
),
|
||||
src: "https://em-content.zobj.net/source/apple/391/billed-cap_1f9e2.png",
|
||||
},
|
||||
};
|
||||
|
||||
export const IconDefaultSize: Story = {
|
||||
export const IconMdSize: Story = {
|
||||
args: {
|
||||
variant: "icon",
|
||||
children: (
|
||||
<AvatarImage src="https://em-content.zobj.net/source/apple/391/billed-cap_1f9e2.png" />
|
||||
),
|
||||
src: "https://em-content.zobj.net/source/apple/391/billed-cap_1f9e2.png",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -45,29 +41,36 @@ export const IconSmSize: Story = {
|
||||
args: {
|
||||
variant: "icon",
|
||||
size: "sm",
|
||||
children: (
|
||||
<AvatarImage src="https://em-content.zobj.net/source/apple/391/billed-cap_1f9e2.png" />
|
||||
),
|
||||
src: "https://em-content.zobj.net/source/apple/391/billed-cap_1f9e2.png",
|
||||
},
|
||||
};
|
||||
|
||||
export const NonSquaredIcon: Story = {
|
||||
args: {
|
||||
variant: "icon",
|
||||
src: "/icon/docker.png",
|
||||
},
|
||||
};
|
||||
|
||||
export const FallbackLgSize: Story = {
|
||||
args: {
|
||||
src: "",
|
||||
size: "lg",
|
||||
|
||||
children: <AvatarFallback>AR</AvatarFallback>,
|
||||
fallback: "Adriana Rodrigues",
|
||||
},
|
||||
};
|
||||
|
||||
export const FallbackDefaultSize: Story = {
|
||||
export const FallbackMdSize: Story = {
|
||||
args: {
|
||||
children: <AvatarFallback>AR</AvatarFallback>,
|
||||
src: "",
|
||||
fallback: "Adriana Rodrigues",
|
||||
},
|
||||
};
|
||||
|
||||
export const FallbackSmSize: Story = {
|
||||
args: {
|
||||
src: "",
|
||||
size: "sm",
|
||||
children: <AvatarFallback>AR</AvatarFallback>,
|
||||
fallback: "Adriana Rodrigues",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
||||
import { type VariantProps, cva } from "class-variance-authority";
|
||||
/**
|
||||
* Copied from shadc/ui on 12/16/2024
|
||||
* @see {@link https://ui.shadcn.com/docs/components/avatar}
|
||||
@@ -7,8 +5,16 @@ import { type VariantProps, cva } from "class-variance-authority";
|
||||
* This component was updated to support the variants and match the styles from
|
||||
* the Figma design:
|
||||
* @see {@link https://www.figma.com/design/WfqIgsTFXN2BscBSSyXWF8/Coder-kit?node-id=711-383&t=xqxOSUk48GvDsjGK-0}
|
||||
*
|
||||
* It was also simplified to make usage easier and reduce boilerplate.
|
||||
* @see {@link https://github.com/coder/coder/pull/15930#issuecomment-2552292440}
|
||||
*/
|
||||
|
||||
import { useTheme } from "@emotion/react";
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
||||
import { type VariantProps, cva } from "class-variance-authority";
|
||||
import * as React from "react";
|
||||
import { getExternalImageStylesFromUrl } from "theme/externalImages";
|
||||
import { cn } from "utils/cn";
|
||||
|
||||
const avatarVariants = cva(
|
||||
@@ -16,79 +22,71 @@ const avatarVariants = cva(
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
lg: "h-10 w-10 rounded-[6px] text-sm font-medium",
|
||||
default: "h-6 w-6 text-2xs",
|
||||
sm: "h-[18px] w-[18px] text-[8px]",
|
||||
lg: "h-[--avatar-lg] w-[--avatar-lg] rounded-[6px] text-sm font-medium",
|
||||
md: "h-[--avatar-default] w-[--avatar-default] text-2xs",
|
||||
sm: "h-[--avatar-sm] w-[--avatar-sm] text-[8px]",
|
||||
},
|
||||
variant: {
|
||||
default: "",
|
||||
icon: "",
|
||||
default: null,
|
||||
icon: null,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: "default",
|
||||
size: "md",
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
size: "lg",
|
||||
variant: "icon",
|
||||
className: "p-[9px]",
|
||||
className: "p-2",
|
||||
},
|
||||
{
|
||||
size: "default",
|
||||
size: "md",
|
||||
variant: "icon",
|
||||
className: "p-[3px]",
|
||||
className: "p-1",
|
||||
},
|
||||
{
|
||||
size: "sm",
|
||||
variant: "icon",
|
||||
className: "p-[2px]",
|
||||
className: "p-[3px]",
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
export interface AvatarProps
|
||||
extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>,
|
||||
VariantProps<typeof avatarVariants> {}
|
||||
export type AvatarProps = AvatarPrimitive.AvatarProps &
|
||||
VariantProps<typeof avatarVariants> & {
|
||||
src?: string;
|
||||
|
||||
fallback?: string;
|
||||
};
|
||||
|
||||
const Avatar = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Root>,
|
||||
AvatarProps
|
||||
>(({ className, size, variant, ...props }, ref) => (
|
||||
<AvatarPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(avatarVariants({ size, variant, className }))}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
>(({ className, size, variant, src, fallback, children, ...props }, ref) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<AvatarPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(avatarVariants({ size, variant, className }))}
|
||||
{...props}
|
||||
>
|
||||
<AvatarPrimitive.Image
|
||||
src={src}
|
||||
className="aspect-square h-full w-full object-contain"
|
||||
css={getExternalImageStylesFromUrl(theme.externalImages, src)}
|
||||
/>
|
||||
{fallback && (
|
||||
<AvatarPrimitive.Fallback className="flex h-full w-full items-center justify-center rounded-full">
|
||||
{fallback.charAt(0).toUpperCase()}
|
||||
</AvatarPrimitive.Fallback>
|
||||
)}
|
||||
{children}
|
||||
</AvatarPrimitive.Root>
|
||||
);
|
||||
});
|
||||
Avatar.displayName = AvatarPrimitive.Root.displayName;
|
||||
|
||||
const AvatarImage = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Image>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Image
|
||||
ref={ref}
|
||||
className={cn("aspect-square h-full w-full", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
|
||||
|
||||
const AvatarFallback = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Fallback
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-full w-full items-center justify-center rounded-full",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
|
||||
|
||||
export { Avatar, AvatarImage, AvatarFallback };
|
||||
export { Avatar };
|
||||
|
||||
-1
@@ -13,7 +13,6 @@ export const WithImage: Story = {
|
||||
args: {
|
||||
header: "Coder",
|
||||
imgUrl: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4",
|
||||
altText: "Coder",
|
||||
subtitle: "56 members",
|
||||
},
|
||||
};
|
||||
+2
-9
@@ -1,13 +1,10 @@
|
||||
import { type CSSObject, useTheme } from "@emotion/react";
|
||||
import { Avatar } from "components/deprecated/Avatar/Avatar";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import type { FC, ReactNode } from "react";
|
||||
|
||||
type AvatarCardProps = {
|
||||
header: string;
|
||||
imgUrl: string;
|
||||
altText: string;
|
||||
background?: boolean;
|
||||
|
||||
subtitle?: ReactNode;
|
||||
maxWidth?: number | "none";
|
||||
};
|
||||
@@ -15,8 +12,6 @@ type AvatarCardProps = {
|
||||
export const AvatarCard: FC<AvatarCardProps> = ({
|
||||
header,
|
||||
imgUrl,
|
||||
altText,
|
||||
background,
|
||||
subtitle,
|
||||
maxWidth = "none",
|
||||
}) => {
|
||||
@@ -72,9 +67,7 @@ export const AvatarCard: FC<AvatarCardProps> = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Avatar background={background} src={imgUrl} alt={altText} size="md">
|
||||
{header}
|
||||
</Avatar>
|
||||
<Avatar size="lg" src={imgUrl} fallback={header} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { Avatar } from "components/deprecated/Avatar/Avatar";
|
||||
import type { FC, ReactNode } from "react";
|
||||
|
||||
export interface AvatarDataProps {
|
||||
@@ -29,17 +29,17 @@ export const AvatarData: FC<AvatarDataProps> = ({
|
||||
const theme = useTheme();
|
||||
if (!avatar) {
|
||||
avatar = (
|
||||
<Avatar background src={src}>
|
||||
{(typeof title === "string" ? title : imgFallbackText) || "-"}
|
||||
</Avatar>
|
||||
<Avatar
|
||||
src={src}
|
||||
fallback={(typeof title === "string" ? title : imgFallbackText) || "-"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
spacing={1.5}
|
||||
spacing={1}
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
css={{
|
||||
minHeight: 40, // Make it predictable for the skeleton
|
||||
width: "100%",
|
||||
@@ -1,50 +0,0 @@
|
||||
import { css, cx } from "@emotion/css";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import Badge from "@mui/material/Badge";
|
||||
import type { WorkspaceBuild } from "api/typesGenerated";
|
||||
import { BuildIcon } from "components/BuildIcon/BuildIcon";
|
||||
import { Avatar, type AvatarProps } from "components/deprecated/Avatar/Avatar";
|
||||
import { useClassName } from "hooks/useClassName";
|
||||
import type { FC } from "react";
|
||||
import { getDisplayWorkspaceBuildStatus } from "utils/workspace";
|
||||
|
||||
export interface BuildAvatarProps {
|
||||
build: WorkspaceBuild;
|
||||
size?: AvatarProps["size"];
|
||||
}
|
||||
|
||||
export const BuildAvatar: FC<BuildAvatarProps> = ({ build, size }) => {
|
||||
const theme = useTheme();
|
||||
const { status, type } = getDisplayWorkspaceBuildStatus(theme, build);
|
||||
const badgeType = useClassName(
|
||||
(css, theme) => css({ backgroundColor: theme.roles[type].fill.solid }),
|
||||
[type],
|
||||
);
|
||||
|
||||
return (
|
||||
<Badge
|
||||
role="status"
|
||||
aria-label={status}
|
||||
title={status}
|
||||
overlap="circular"
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
|
||||
badgeContent={<div></div>}
|
||||
classes={{ badge: cx(classNames.badge, badgeType) }}
|
||||
>
|
||||
<Avatar background size={size}>
|
||||
<BuildIcon transition={build.transition} />
|
||||
</Avatar>
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
const classNames = {
|
||||
badge: css({
|
||||
borderRadius: "100%",
|
||||
width: 8,
|
||||
minWidth: 8,
|
||||
height: 8,
|
||||
display: "block",
|
||||
padding: 0,
|
||||
}),
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { action } from "@storybook/addon-actions";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { expect, userEvent, within } from "@storybook/test";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { useState } from "react";
|
||||
import { withDesktopViewport } from "testHelpers/storybook";
|
||||
import {
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from "./SelectFilter";
|
||||
|
||||
const options: SelectFilterOption[] = Array.from({ length: 50 }, (_, i) => ({
|
||||
startIcon: <UserAvatar username={`username ${i + 1}`} size="xs" />,
|
||||
startIcon: <Avatar fallback={`username ${i + 1}`} size="sm" />,
|
||||
label: `Option ${i + 1}`,
|
||||
value: `option-${i + 1}`,
|
||||
}));
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { API } from "api/api";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import {
|
||||
SelectFilter,
|
||||
type SelectFilterOption,
|
||||
SelectFilterSearch,
|
||||
} from "components/Filter/SelectFilter";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import { useAuthenticated } from "contexts/auth/RequireAuth";
|
||||
import type { FC } from "react";
|
||||
import { type UseFilterMenuOptions, useFilterMenu } from "./menu";
|
||||
@@ -23,11 +23,7 @@ export const useUserFilterMenu = ({
|
||||
label: me.username,
|
||||
value: me.username,
|
||||
startIcon: (
|
||||
<UserAvatar
|
||||
username={me.username}
|
||||
avatarURL={me.avatar_url}
|
||||
size="xs"
|
||||
/>
|
||||
<Avatar fallback={me.username} src={me.avatar_url} size="sm" />
|
||||
),
|
||||
},
|
||||
...filtered,
|
||||
@@ -45,11 +41,7 @@ export const useUserFilterMenu = ({
|
||||
label: me.username,
|
||||
value: me.username,
|
||||
startIcon: (
|
||||
<UserAvatar
|
||||
username={me.username}
|
||||
avatarURL={me.avatar_url}
|
||||
size="xs"
|
||||
/>
|
||||
<Avatar fallback={me.username} src={me.avatar_url} size="sm" />
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -61,10 +53,10 @@ export const useUserFilterMenu = ({
|
||||
label: firstUser.username,
|
||||
value: firstUser.username,
|
||||
startIcon: (
|
||||
<UserAvatar
|
||||
username={firstUser.username}
|
||||
avatarURL={firstUser.avatar_url}
|
||||
size="xs"
|
||||
<Avatar
|
||||
fallback={firstUser.username}
|
||||
src={firstUser.avatar_url}
|
||||
size="sm"
|
||||
/>
|
||||
),
|
||||
};
|
||||
@@ -77,11 +69,7 @@ export const useUserFilterMenu = ({
|
||||
label: user.username,
|
||||
value: user.username,
|
||||
startIcon: (
|
||||
<UserAvatar
|
||||
username={user.username}
|
||||
avatarURL={user.avatar_url}
|
||||
size="xs"
|
||||
/>
|
||||
<Avatar fallback={user.username} src={user.avatar_url} size="sm" />
|
||||
),
|
||||
}));
|
||||
options = addMeAsFirstOption(options);
|
||||
|
||||
@@ -2,10 +2,7 @@ import { css } from "@emotion/css";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import Button, { type ButtonProps } from "@mui/material/Button";
|
||||
import IconButton, { type IconButtonProps } from "@mui/material/IconButton";
|
||||
import {
|
||||
type AvatarProps,
|
||||
ExternalAvatar,
|
||||
} from "components/deprecated/Avatar/Avatar";
|
||||
import { Avatar, type AvatarProps } from "components/Avatar/Avatar";
|
||||
import {
|
||||
type FC,
|
||||
type ForwardedRef,
|
||||
@@ -97,14 +94,7 @@ export const TopbarDivider: FC<HTMLAttributes<HTMLSpanElement>> = (props) => {
|
||||
};
|
||||
|
||||
export const TopbarAvatar: FC<AvatarProps> = (props) => {
|
||||
return (
|
||||
<ExternalAvatar
|
||||
{...props}
|
||||
variant="square"
|
||||
fitImage
|
||||
css={{ width: 16, height: 16 }}
|
||||
/>
|
||||
);
|
||||
return <Avatar {...props} variant="icon" size="sm" />;
|
||||
};
|
||||
|
||||
type TopbarIconProps = HTMLAttributes<HTMLOrSVGElement>;
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { GroupAvatar } from "./GroupAvatar";
|
||||
|
||||
const meta: Meta<typeof GroupAvatar> = {
|
||||
title: "components/GroupAvatar",
|
||||
component: GroupAvatar,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof GroupAvatar>;
|
||||
|
||||
const Example: Story = {
|
||||
args: {
|
||||
name: "My Group",
|
||||
avatarURL: "",
|
||||
},
|
||||
};
|
||||
|
||||
export { Example as GroupAvatar };
|
||||
@@ -1,46 +0,0 @@
|
||||
import Group from "@mui/icons-material/Group";
|
||||
import Badge from "@mui/material/Badge";
|
||||
import { Avatar } from "components/deprecated/Avatar/Avatar";
|
||||
import { type ClassName, useClassName } from "hooks/useClassName";
|
||||
import type { FC } from "react";
|
||||
|
||||
export interface GroupAvatarProps {
|
||||
name: string;
|
||||
avatarURL?: string;
|
||||
}
|
||||
|
||||
export const GroupAvatar: FC<GroupAvatarProps> = ({ name, avatarURL }) => {
|
||||
const badge = useClassName(classNames.badge, []);
|
||||
|
||||
return (
|
||||
<Badge
|
||||
overlap="circular"
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
|
||||
badgeContent={<Group />}
|
||||
classes={{ badge }}
|
||||
>
|
||||
<Avatar background src={avatarURL}>
|
||||
{name}
|
||||
</Avatar>
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
const classNames = {
|
||||
badge: (css, theme) =>
|
||||
css({
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderRadius: "100%",
|
||||
width: 24,
|
||||
height: 24,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
|
||||
"& svg": {
|
||||
width: 14,
|
||||
height: 14,
|
||||
},
|
||||
}),
|
||||
} satisfies Record<string, ClassName>;
|
||||
@@ -5,8 +5,8 @@ import TextField from "@mui/material/TextField";
|
||||
import { checkAuthorization } from "api/queries/authCheck";
|
||||
import { organizations } from "api/queries/organizations";
|
||||
import type { AuthorizationCheck, Organization } from "api/typesGenerated";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { Avatar } from "components/deprecated/Avatar/Avatar";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AvatarData } from "components/Avatar/AvatarData";
|
||||
import { useDebouncedFunction } from "hooks/debounce";
|
||||
import {
|
||||
type ChangeEvent,
|
||||
@@ -132,9 +132,7 @@ export const OrganizationAutocomplete: FC<OrganizationAutocompleteProps> = ({
|
||||
...params.InputProps,
|
||||
onChange: debouncedInputOnChange,
|
||||
startAdornment: value && (
|
||||
<Avatar size="sm" src={value.icon}>
|
||||
{value.name}
|
||||
</Avatar>
|
||||
<Avatar size="sm" src={value.icon} fallback={value.name} />
|
||||
),
|
||||
endAdornment: (
|
||||
<>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { action } from "@storybook/addon-actions";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { userEvent, within } from "@storybook/test";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { withDesktopViewport } from "testHelpers/storybook";
|
||||
import {
|
||||
SelectMenu,
|
||||
@@ -25,7 +25,7 @@ const meta: Meta<typeof SelectMenu> = {
|
||||
<SelectMenu>
|
||||
<SelectMenuTrigger>
|
||||
<SelectMenuButton
|
||||
startIcon={<UserAvatar size="xs" username={selectedOpt} />}
|
||||
startIcon={<Avatar size="sm" fallback={selectedOpt} />}
|
||||
>
|
||||
{selectedOpt}
|
||||
</SelectMenuButton>
|
||||
@@ -36,7 +36,7 @@ const meta: Meta<typeof SelectMenu> = {
|
||||
{opts.map((o) => (
|
||||
<SelectMenuItem key={o} selected={o === selectedOpt}>
|
||||
<SelectMenuIcon>
|
||||
<UserAvatar size="xs" username={o} />
|
||||
<Avatar size="sm" fallback={o} />
|
||||
</SelectMenuIcon>
|
||||
{o}
|
||||
</SelectMenuItem>
|
||||
@@ -77,7 +77,7 @@ export const LongButtonText: Story = {
|
||||
<SelectMenuTrigger>
|
||||
<SelectMenuButton
|
||||
css={{ width: 200 }}
|
||||
startIcon={<UserAvatar size="xs" username={selectedOpt} />}
|
||||
startIcon={<Avatar size="sm" fallback={selectedOpt} />}
|
||||
>
|
||||
{selectedOpt}
|
||||
</SelectMenuButton>
|
||||
@@ -88,7 +88,7 @@ export const LongButtonText: Story = {
|
||||
{opts.map((o) => (
|
||||
<SelectMenuItem key={o} selected={o === selectedOpt}>
|
||||
<SelectMenuIcon>
|
||||
<UserAvatar size="xs" username={o} />
|
||||
<Avatar size="sm" fallback={o} />
|
||||
</SelectMenuIcon>
|
||||
{o}
|
||||
</SelectMenuItem>
|
||||
@@ -115,7 +115,7 @@ export const NoSelectedOption: Story = {
|
||||
{opts.map((o) => (
|
||||
<SelectMenuItem key={o}>
|
||||
<SelectMenuIcon>
|
||||
<UserAvatar size="xs" username={o} />
|
||||
<Avatar size="sm" fallback={o} />
|
||||
</SelectMenuIcon>
|
||||
{o}
|
||||
</SelectMenuItem>
|
||||
|
||||
@@ -4,7 +4,7 @@ import SecurityIcon from "@mui/icons-material/LockOutlined";
|
||||
import AccountIcon from "@mui/icons-material/Person";
|
||||
import VpnKeyOutlined from "@mui/icons-material/VpnKeyOutlined";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { Sidebar, SidebarHeader, SidebarNavItem } from "./Sidebar";
|
||||
|
||||
const meta: Meta<typeof Sidebar> = {
|
||||
@@ -20,7 +20,7 @@ export const Default: Story = {
|
||||
children: (
|
||||
<Sidebar>
|
||||
<SidebarHeader
|
||||
avatar={<UserAvatar username="Jon" />}
|
||||
avatar={<Avatar fallback="Jon" />}
|
||||
title="Jon"
|
||||
subtitle="jon@coder.com"
|
||||
/>
|
||||
|
||||
@@ -28,7 +28,7 @@ export const SidebarHeader: FC<SidebarHeaderProps> = ({
|
||||
linkTo,
|
||||
}) => {
|
||||
return (
|
||||
<Stack direction="row" alignItems="center" css={styles.info}>
|
||||
<Stack direction="row" spacing={1} css={styles.info}>
|
||||
{avatar}
|
||||
<div
|
||||
css={{
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import type { Template } from "api/typesGenerated";
|
||||
import { Avatar, type AvatarProps } from "components/deprecated/Avatar/Avatar";
|
||||
import type { FC } from "react";
|
||||
|
||||
interface TemplateAvatarProps extends AvatarProps {
|
||||
template: Template;
|
||||
}
|
||||
|
||||
export const TemplateAvatar: FC<TemplateAvatarProps> = ({
|
||||
template,
|
||||
...avatarProps
|
||||
}) => {
|
||||
return template.icon ? (
|
||||
<Avatar src={template.icon} variant="square" fitImage {...avatarProps} />
|
||||
) : (
|
||||
<Avatar {...avatarProps}>{template.display_name || template.name}</Avatar>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { Interpolation } from "@emotion/react";
|
||||
import TableRow, { type TableRowProps } from "@mui/material/TableRow";
|
||||
import { forwardRef } from "react";
|
||||
import type { Theme } from "theme";
|
||||
|
||||
interface TimelineEntryProps extends TableRowProps {
|
||||
clickable?: boolean;
|
||||
@@ -12,39 +14,44 @@ export const TimelineEntry = forwardRef<
|
||||
return (
|
||||
<TableRow
|
||||
ref={ref}
|
||||
css={(theme) => [
|
||||
{
|
||||
"&:focus": {
|
||||
outlineStyle: "solid",
|
||||
outlineOffset: -1,
|
||||
outlineWidth: 2,
|
||||
outlineColor: theme.palette.primary.main,
|
||||
},
|
||||
"& td": {
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
},
|
||||
"& td:before": {
|
||||
position: "absolute",
|
||||
left: 49, // 50px - (width / 2)
|
||||
display: "block",
|
||||
content: "''",
|
||||
height: "100%",
|
||||
width: 2,
|
||||
background: theme.palette.divider,
|
||||
},
|
||||
},
|
||||
clickable && {
|
||||
cursor: "pointer",
|
||||
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.action.hover,
|
||||
},
|
||||
},
|
||||
]}
|
||||
css={[styles.row, clickable ? styles.clickable : null]}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</TableRow>
|
||||
);
|
||||
});
|
||||
|
||||
const styles = {
|
||||
row: (theme) => ({
|
||||
"--side-padding": "32px",
|
||||
"&:focus": {
|
||||
outlineStyle: "solid",
|
||||
outlineOffset: -1,
|
||||
outlineWidth: 2,
|
||||
outlineColor: theme.palette.primary.main,
|
||||
},
|
||||
"& td": {
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
},
|
||||
"& td:before": {
|
||||
"--line-width": "2px",
|
||||
position: "absolute",
|
||||
left: "calc((var(--side-padding) + var(--avatar-default)/2) - var(--line-width) / 2)",
|
||||
display: "block",
|
||||
content: "''",
|
||||
height: "100%",
|
||||
width: "var(--line-width)",
|
||||
background: theme.palette.divider,
|
||||
},
|
||||
}),
|
||||
|
||||
clickable: (theme) => ({
|
||||
cursor: "pointer",
|
||||
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.action.hover,
|
||||
},
|
||||
}),
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -6,8 +6,8 @@ import { getErrorMessage } from "api/errors";
|
||||
import { organizationMembers } from "api/queries/organizations";
|
||||
import { users } from "api/queries/users";
|
||||
import type { OrganizationMemberWithUserData, User } from "api/typesGenerated";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { Avatar } from "components/deprecated/Avatar/Avatar";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AvatarData } from "components/Avatar/AvatarData";
|
||||
import { useDebouncedFunction } from "hooks/debounce";
|
||||
import {
|
||||
type ChangeEvent,
|
||||
@@ -170,9 +170,11 @@ const InnerAutocomplete = <T extends SelectedUser>({
|
||||
...params.InputProps,
|
||||
onChange: debouncedInputOnChange,
|
||||
startAdornment: value && (
|
||||
<Avatar size="sm" src={value.avatar_url}>
|
||||
{value.username}
|
||||
</Avatar>
|
||||
<Avatar
|
||||
size="sm"
|
||||
src={value.avatar_url}
|
||||
fallback={value.username}
|
||||
/>
|
||||
),
|
||||
endAdornment: (
|
||||
<>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { UserAvatar } from "./UserAvatar";
|
||||
|
||||
const meta: Meta<typeof UserAvatar> = {
|
||||
title: "components/UserAvatar",
|
||||
component: UserAvatar,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof UserAvatar>;
|
||||
|
||||
export const Jon: Story = {
|
||||
args: {
|
||||
username: "sreya",
|
||||
avatarURL: "https://github.com/sreya.png",
|
||||
},
|
||||
};
|
||||
|
||||
export const Jonjon: Story = {
|
||||
args: {
|
||||
username: "ジョンジョン",
|
||||
avatarURL: "https://github.com/sreya.png",
|
||||
},
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Avatar, type AvatarProps } from "components/deprecated/Avatar/Avatar";
|
||||
import type { FC } from "react";
|
||||
|
||||
export type UserAvatarProps = {
|
||||
username: string;
|
||||
avatarURL?: string;
|
||||
} & AvatarProps;
|
||||
|
||||
export const UserAvatar: FC<UserAvatarProps> = ({
|
||||
username,
|
||||
avatarURL,
|
||||
...avatarProps
|
||||
}) => {
|
||||
return (
|
||||
<Avatar background title={username} src={avatarURL} {...avatarProps}>
|
||||
{username}
|
||||
</Avatar>
|
||||
);
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
import PauseIcon from "@mui/icons-material/PauseOutlined";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { Avatar, AvatarIcon } from "./Avatar";
|
||||
|
||||
const meta: Meta<typeof Avatar> = {
|
||||
title: "components/DeprecatedAvatar",
|
||||
component: Avatar,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Avatar>;
|
||||
|
||||
export const WithLetter: Story = {
|
||||
args: {
|
||||
children: "Coder",
|
||||
},
|
||||
};
|
||||
|
||||
export const WithLetterXL = {
|
||||
args: {
|
||||
children: "Coder",
|
||||
size: "xl",
|
||||
},
|
||||
};
|
||||
|
||||
export const WithImage = {
|
||||
args: {
|
||||
src: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4",
|
||||
},
|
||||
};
|
||||
|
||||
export const WithImageXL = {
|
||||
args: {
|
||||
src: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4",
|
||||
size: "xl",
|
||||
},
|
||||
};
|
||||
|
||||
export const WithMuiIcon = {
|
||||
args: {
|
||||
background: true,
|
||||
children: <PauseIcon />,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithMuiIconXL = {
|
||||
args: {
|
||||
background: true,
|
||||
children: <PauseIcon />,
|
||||
size: "xl",
|
||||
},
|
||||
};
|
||||
|
||||
export const WithAvatarIcon = {
|
||||
args: {
|
||||
background: true,
|
||||
children: <AvatarIcon src="/icon/database.svg" alt="Database" />,
|
||||
},
|
||||
};
|
||||
@@ -1,124 +0,0 @@
|
||||
import { type Interpolation, type Theme, css, useTheme } from "@emotion/react";
|
||||
import MuiAvatar, {
|
||||
type AvatarProps as MuiAvatarProps,
|
||||
// biome-ignore lint/nursery/noRestrictedImports: Used as base component
|
||||
} from "@mui/material/Avatar";
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import { type FC, useId } from "react";
|
||||
import { getExternalImageStylesFromUrl } from "theme/externalImages";
|
||||
|
||||
export type AvatarProps = MuiAvatarProps & {
|
||||
size?: "xs" | "sm" | "md" | "xl";
|
||||
background?: boolean;
|
||||
fitImage?: boolean;
|
||||
};
|
||||
|
||||
const sizeStyles = {
|
||||
xs: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
fontSize: 8,
|
||||
fontWeight: 700,
|
||||
},
|
||||
sm: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
},
|
||||
md: {},
|
||||
xl: {
|
||||
width: 48,
|
||||
height: 48,
|
||||
fontSize: 24,
|
||||
},
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
const fitImageStyles = css`
|
||||
& .MuiAvatar-img {
|
||||
object-fit: contain;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* @deprecated Use `Avatar` from `@components/Avatar` instead.
|
||||
*/
|
||||
export const Avatar: FC<AvatarProps> = ({
|
||||
size = "md",
|
||||
fitImage,
|
||||
children,
|
||||
background,
|
||||
...muiProps
|
||||
}) => {
|
||||
const fromName = !muiProps.src && typeof children === "string";
|
||||
|
||||
return (
|
||||
<MuiAvatar
|
||||
{...muiProps}
|
||||
css={[
|
||||
sizeStyles[size],
|
||||
fitImage && fitImageStyles,
|
||||
(theme) => ({
|
||||
background:
|
||||
background || fromName ? theme.palette.divider : undefined,
|
||||
color: theme.palette.text.primary,
|
||||
}),
|
||||
]}
|
||||
>
|
||||
{typeof children === "string" ? firstLetter(children) : children}
|
||||
</MuiAvatar>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated Use `Avatar` from `@components/Avatar` instead.
|
||||
*/
|
||||
export const ExternalAvatar: FC<AvatarProps> = (props) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Avatar
|
||||
css={getExternalImageStylesFromUrl(theme.externalImages, props.src)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type AvatarIconProps = {
|
||||
src: string;
|
||||
alt: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Use it to make an img element behaves like a MaterialUI Icon component
|
||||
*
|
||||
* @deprecated Use `AvatarIcon` from `@components/Avatar` instead.
|
||||
*/
|
||||
export const AvatarIcon: FC<AvatarIconProps> = ({ src, alt }) => {
|
||||
const hookId = useId();
|
||||
const avatarId = `${hookId}-avatar`;
|
||||
|
||||
// We use a `visuallyHidden` element instead of setting `alt` to avoid
|
||||
// splatting the text out on the screen if the image fails to load.
|
||||
return (
|
||||
<>
|
||||
<img
|
||||
src={src}
|
||||
alt=""
|
||||
css={{ maxWidth: "50%" }}
|
||||
aria-labelledby={avatarId}
|
||||
/>
|
||||
<div id={avatarId} css={{ ...visuallyHidden }}>
|
||||
{alt}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const firstLetter = (str: string): string => {
|
||||
if (str.length > 0) {
|
||||
return str[0].toLocaleUpperCase();
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
@@ -31,6 +31,10 @@
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
--ring: 240 10% 3.9%;
|
||||
|
||||
--avatar-lg: 2.5rem;
|
||||
--avatar-default: 1.5rem;
|
||||
--avatar-sm: 1.125rem;
|
||||
}
|
||||
.dark {
|
||||
--content-primary: 0, 0%, 98%;
|
||||
|
||||
+4
-10
@@ -13,27 +13,21 @@ const meta: Meta<typeof BuildAvatar> = {
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof BuildAvatar>;
|
||||
|
||||
export const XSSize: Story = {
|
||||
args: {
|
||||
size: "xs",
|
||||
},
|
||||
};
|
||||
|
||||
export const SMSize: Story = {
|
||||
export const SmSize: Story = {
|
||||
args: {
|
||||
size: "sm",
|
||||
},
|
||||
};
|
||||
|
||||
export const MDSize: Story = {
|
||||
export const MdSize: Story = {
|
||||
args: {
|
||||
size: "md",
|
||||
},
|
||||
};
|
||||
|
||||
export const XLSize: Story = {
|
||||
export const LgSize: Story = {
|
||||
args: {
|
||||
size: "xl",
|
||||
size: "lg",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import type { WorkspaceBuild } from "api/typesGenerated";
|
||||
import { Avatar, type AvatarProps } from "components/Avatar/Avatar";
|
||||
import { BuildIcon } from "components/BuildIcon/BuildIcon";
|
||||
import { useClassName } from "hooks/useClassName";
|
||||
import type { FC } from "react";
|
||||
import { getDisplayWorkspaceBuildStatus } from "utils/workspace";
|
||||
|
||||
export interface BuildAvatarProps {
|
||||
build: WorkspaceBuild;
|
||||
size?: AvatarProps["size"];
|
||||
}
|
||||
|
||||
export const BuildAvatar: FC<BuildAvatarProps> = ({ build, size }) => {
|
||||
const theme = useTheme();
|
||||
const { type } = getDisplayWorkspaceBuildStatus(theme, build);
|
||||
const iconColor = useClassName(
|
||||
(css, theme) => css({ color: theme.roles[type].fill.solid }),
|
||||
[type],
|
||||
);
|
||||
|
||||
return (
|
||||
<Avatar size={size} variant="icon">
|
||||
<BuildIcon
|
||||
transition={build.transition}
|
||||
className={`w-full h-full ${iconColor}`}
|
||||
/>
|
||||
</Avatar>
|
||||
);
|
||||
};
|
||||
@@ -1,15 +1,15 @@
|
||||
import { type Interpolation, type Theme, css, useTheme } from "@emotion/react";
|
||||
import Badge from "@mui/material/Badge";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "components/deprecated/Popover/Popover";
|
||||
import { type FC, useState } from "react";
|
||||
import { BUTTON_SM_HEIGHT, navHeight } from "theme/constants";
|
||||
import { navHeight } from "theme/constants";
|
||||
import { UserDropdownContent } from "./UserDropdownContent";
|
||||
|
||||
export interface UserDropdownProps {
|
||||
@@ -34,10 +34,10 @@ export const UserDropdown: FC<UserDropdownProps> = ({
|
||||
<button css={styles.button} data-testid="user-dropdown-trigger">
|
||||
<div css={styles.badgeContainer}>
|
||||
<Badge overlap="circular">
|
||||
<UserAvatar
|
||||
css={styles.avatar}
|
||||
username={user.username}
|
||||
avatarURL={user.avatar_url}
|
||||
<Avatar
|
||||
fallback={user.username}
|
||||
src={user.avatar_url}
|
||||
size="lg"
|
||||
/>
|
||||
</Badge>
|
||||
<DropdownArrow
|
||||
@@ -88,10 +88,4 @@ const styles = {
|
||||
minWidth: 0,
|
||||
maxWidth: 300,
|
||||
},
|
||||
|
||||
avatar: {
|
||||
width: BUTTON_SM_HEIGHT,
|
||||
height: BUTTON_SM_HEIGHT,
|
||||
fontSize: 16,
|
||||
},
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { AuthorizationResponse, Organization } from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
@@ -8,7 +9,6 @@ import {
|
||||
BreadcrumbSeparator,
|
||||
} from "components/Breadcrumb/Breadcrumb";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import { useAuthenticated } from "contexts/auth/RequireAuth";
|
||||
import { RequirePermission } from "contexts/auth/RequirePermission";
|
||||
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||
@@ -90,11 +90,11 @@ const OrganizationSettingsLayout: FC = () => {
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage className="text-content-primary">
|
||||
<UserAvatar
|
||||
<Avatar
|
||||
key={organization.id}
|
||||
size="xs"
|
||||
username={organization.display_name}
|
||||
avatarURL={organization.icon}
|
||||
size="sm"
|
||||
fallback={organization.display_name}
|
||||
src={organization.icon}
|
||||
/>
|
||||
{organization?.name}
|
||||
</BreadcrumbPage>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { cx } from "@emotion/css";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import type { AuthorizationResponse, Organization } from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import {
|
||||
Sidebar as BaseSidebar,
|
||||
SettingsSidebarNavItem as SidebarNavSubItem,
|
||||
} from "components/Sidebar/Sidebar";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import type { Permissions } from "contexts/auth/permissions";
|
||||
import { type ClassName, useClassName } from "hooks/useClassName";
|
||||
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||
@@ -128,11 +128,10 @@ const OrganizationSettingsNavigation: FC<
|
||||
active={active}
|
||||
href={urlForSubpage(organization.name)}
|
||||
icon={
|
||||
<UserAvatar
|
||||
key={organization.id}
|
||||
size="sm"
|
||||
username={organization.display_name}
|
||||
avatarURL={organization.icon}
|
||||
<Avatar
|
||||
variant="icon"
|
||||
src={organization.icon}
|
||||
fallback={organization.display_name}
|
||||
/>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -158,12 +158,12 @@ const styles = {
|
||||
backgroundColor: theme.palette.divider,
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 49,
|
||||
left: 43,
|
||||
},
|
||||
}),
|
||||
|
||||
agentStatusWrapper: {
|
||||
width: 36,
|
||||
width: 24,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
flexShrink: 0,
|
||||
|
||||
@@ -1,26 +1,12 @@
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import type { WorkspaceResource } from "api/typesGenerated";
|
||||
import { ExternalImage } from "components/ExternalImage/ExternalImage";
|
||||
import { Avatar } from "components/deprecated/Avatar/Avatar";
|
||||
import { type FC, useId } from "react";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import type { FC } from "react";
|
||||
import { getResourceIconPath } from "utils/workspace";
|
||||
|
||||
export type ResourceAvatarProps = { resource: WorkspaceResource };
|
||||
|
||||
export const ResourceAvatar: FC<ResourceAvatarProps> = ({ resource }) => {
|
||||
const avatarSrc = resource.icon || getResourceIconPath(resource.type);
|
||||
const altId = useId();
|
||||
|
||||
return (
|
||||
<Avatar background>
|
||||
<ExternalImage
|
||||
src={avatarSrc}
|
||||
css={{ maxWidth: "50%" }}
|
||||
aria-labelledby={altId}
|
||||
/>
|
||||
<div id={altId} css={{ ...visuallyHidden }}>
|
||||
{resource.name}
|
||||
</div>
|
||||
</Avatar>
|
||||
);
|
||||
return <Avatar variant="icon" src={avatarSrc} fallback={resource.name} />;
|
||||
};
|
||||
|
||||
@@ -99,11 +99,7 @@ export const ResourceCard: FC<ResourceCardProps> = ({ resource, agentRow }) => {
|
||||
css={styles.resourceCardHeader}
|
||||
spacing={10}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
css={styles.resourceCardProfile}
|
||||
>
|
||||
<Stack direction="row" spacing={1} css={styles.resourceCardProfile}>
|
||||
<div>
|
||||
<ResourceAvatar resource={resource} />
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* the workspaces and audits page that have a risk of getting out of sync.
|
||||
*/
|
||||
import { API } from "api/api";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import {
|
||||
SelectFilter,
|
||||
type SelectFilterOption,
|
||||
@@ -17,7 +18,6 @@ import {
|
||||
type UseFilterMenuOptions,
|
||||
useFilterMenu,
|
||||
} from "components/Filter/menu";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import type { FC } from "react";
|
||||
|
||||
// Organization helpers ////////////////////////////////////////////////////////
|
||||
@@ -39,11 +39,11 @@ export const useOrganizationsFilterMenu = ({
|
||||
label: organization.display_name || organization.name,
|
||||
value: organization.name,
|
||||
startIcon: (
|
||||
<UserAvatar
|
||||
<Avatar
|
||||
key={organization.id}
|
||||
size="xs"
|
||||
username={organization.display_name || organization.name}
|
||||
avatarURL={organization.icon}
|
||||
size="sm"
|
||||
fallback={organization.display_name || organization.name}
|
||||
src={organization.icon}
|
||||
/>
|
||||
),
|
||||
};
|
||||
@@ -74,11 +74,11 @@ export const useOrganizationsFilterMenu = ({
|
||||
label: organization.display_name || organization.name,
|
||||
value: organization.name,
|
||||
startIcon: (
|
||||
<UserAvatar
|
||||
<Avatar
|
||||
key={organization.id}
|
||||
size="xs"
|
||||
username={organization.display_name || organization.name}
|
||||
avatarURL={organization.icon}
|
||||
size="sm"
|
||||
fallback={organization.display_name || organization.name}
|
||||
src={organization.icon}
|
||||
/>
|
||||
),
|
||||
}));
|
||||
|
||||
@@ -5,11 +5,11 @@ import Link from "@mui/material/Link";
|
||||
import TableCell from "@mui/material/TableCell";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type { AuditLog } from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
|
||||
import { Pill } from "components/Pill/Pill";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { TimelineEntry } from "components/Timeline/TimelineEntry";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import { type FC, useState } from "react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import type { ThemeRole } from "theme/roles";
|
||||
@@ -90,9 +90,9 @@ export const AuditLogRow: FC<AuditLogRowProps> = ({
|
||||
css={styles.auditLogHeaderInfo}
|
||||
>
|
||||
<Stack direction="row" alignItems="center" css={styles.fullWidth}>
|
||||
<UserAvatar
|
||||
username={auditLog.user?.username ?? "?"}
|
||||
avatarURL={auditLog.user?.avatar_url}
|
||||
<Avatar
|
||||
fallback={auditLog.user?.username ?? "?"}
|
||||
src={auditLog.user?.avatar_url}
|
||||
/>
|
||||
|
||||
<Stack
|
||||
|
||||
@@ -5,6 +5,7 @@ import TextField from "@mui/material/TextField";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import { Alert } from "components/Alert/Alert";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import {
|
||||
FormFields,
|
||||
FormFooter,
|
||||
@@ -21,7 +22,6 @@ import { Pill } from "components/Pill/Pill";
|
||||
import { RichParameterInput } from "components/RichParameterInput/RichParameterInput";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete";
|
||||
import { Avatar } from "components/deprecated/Avatar/Avatar";
|
||||
import { type FormikContextType, useFormik } from "formik";
|
||||
import { generateWorkspaceName } from "modules/workspaces/generateWorkspaceName";
|
||||
import { type FC, useCallback, useEffect, useMemo, useState } from "react";
|
||||
@@ -147,12 +147,13 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
|
||||
return (
|
||||
<Margins size="medium">
|
||||
<PageHeader actions={<Button onClick={onCancel}>Cancel</Button>}>
|
||||
<Stack direction="row" spacing={3} alignItems="center">
|
||||
{template.icon !== "" ? (
|
||||
<Avatar size="xl" src={template.icon} variant="square" fitImage />
|
||||
) : (
|
||||
<Avatar size="xl">{template.name}</Avatar>
|
||||
)}
|
||||
<Stack direction="row">
|
||||
<Avatar
|
||||
variant="icon"
|
||||
size="lg"
|
||||
src={template.icon}
|
||||
fallback={template.name}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<PageHeaderTitle>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Interpolation, Theme } from "@emotion/react";
|
||||
import type { Template, TemplateExample } from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { ExternalAvatar } from "components/deprecated/Avatar/Avatar";
|
||||
import type { FC } from "react";
|
||||
|
||||
export interface SelectedTemplateProps {
|
||||
@@ -10,19 +10,13 @@ export interface SelectedTemplateProps {
|
||||
|
||||
export const SelectedTemplate: FC<SelectedTemplateProps> = ({ template }) => {
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={3}
|
||||
css={styles.template}
|
||||
alignItems="center"
|
||||
>
|
||||
<ExternalAvatar
|
||||
variant={template.icon ? "square" : undefined}
|
||||
fitImage={Boolean(template.icon)}
|
||||
<Stack direction="row" css={styles.template}>
|
||||
<Avatar
|
||||
variant="icon"
|
||||
size="lg"
|
||||
src={template.icon}
|
||||
>
|
||||
{template.name}
|
||||
</ExternalAvatar>
|
||||
fallback={template.name}
|
||||
/>
|
||||
|
||||
<Stack direction="column" spacing={0}>
|
||||
<span css={styles.templateName}>
|
||||
|
||||
+5
-10
@@ -10,11 +10,10 @@ import TableHead from "@mui/material/TableHead";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { TableLoader } from "components/TableLoader/TableLoader";
|
||||
import { Avatar } from "components/deprecated/Avatar/Avatar";
|
||||
import { useClickableTableRow } from "hooks/useClickableTableRow";
|
||||
import type { FC } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
@@ -98,14 +97,10 @@ const OAuth2AppRow: FC<OAuth2AppRowProps> = ({ app }) => {
|
||||
return (
|
||||
<TableRow key={app.id} data-testid={`app-${app.id}`} {...clickableProps}>
|
||||
<TableCell>
|
||||
<AvatarData
|
||||
title={app.name}
|
||||
avatar={
|
||||
Boolean(app.icon) && (
|
||||
<Avatar src={app.icon} variant="square" fitImage />
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<Avatar variant="icon" src={app.icon} fallback={app.name} />
|
||||
<span className="font-semibold">{app.name}</span>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
|
||||
@@ -8,10 +8,10 @@ import Tooltip from "@mui/material/Tooltip";
|
||||
import type { ApiErrorResponse } from "api/errors";
|
||||
import type { ExternalAuth, ExternalAuthDevice } from "api/typesGenerated";
|
||||
import { Alert, AlertDetail } from "components/Alert/Alert";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { CopyButton } from "components/CopyButton/CopyButton";
|
||||
import { SignInLayout } from "components/SignInLayout/SignInLayout";
|
||||
import { Welcome } from "components/Welcome/Welcome";
|
||||
import { Avatar } from "components/deprecated/Avatar/Avatar";
|
||||
import type { FC, ReactNode } from "react";
|
||||
|
||||
export interface ExternalAuthPageViewProps {
|
||||
@@ -76,7 +76,10 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
|
||||
</p>
|
||||
|
||||
{externalAuth.installations.length > 0 && (
|
||||
<div css={styles.authorizedInstalls}>
|
||||
<div
|
||||
css={styles.authorizedInstalls}
|
||||
className="flex gap-2 items-center"
|
||||
>
|
||||
{externalAuth.installations.map((install) => {
|
||||
if (!install.account) {
|
||||
return;
|
||||
@@ -88,9 +91,10 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Avatar size="sm" src={install.account.avatar_url}>
|
||||
{install.account.login}
|
||||
</Avatar>
|
||||
<Avatar
|
||||
src={install.account.avatar_url}
|
||||
fallback={install.account.login}
|
||||
/>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -20,7 +20,8 @@ import {
|
||||
} from "api/queries/groups";
|
||||
import type { Group, ReducedUser, User } from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AvatarData } from "components/Avatar/AvatarData";
|
||||
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog";
|
||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
|
||||
@@ -45,7 +46,6 @@ import {
|
||||
TableToolbar,
|
||||
} from "components/TableToolbar/TableToolbar";
|
||||
import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import { type FC, useState } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
@@ -288,12 +288,7 @@ const GroupMemberRow: FC<GroupMemberRowProps> = ({
|
||||
<TableRow key={member.id}>
|
||||
<TableCell width="59%">
|
||||
<AvatarData
|
||||
avatar={
|
||||
<UserAvatar
|
||||
username={member.username}
|
||||
avatarURL={member.avatar_url}
|
||||
/>
|
||||
}
|
||||
avatar={<Avatar fallback={member.username} src={member.avatar_url} />}
|
||||
title={member.username}
|
||||
subtitle={member.email}
|
||||
/>
|
||||
|
||||
@@ -11,17 +11,16 @@ import TableContainer from "@mui/material/TableContainer";
|
||||
import TableHead from "@mui/material/TableHead";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import type { Group } from "api/typesGenerated";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AvatarData } from "components/Avatar/AvatarData";
|
||||
import { AvatarDataSkeleton } from "components/Avatar/AvatarDataSkeleton";
|
||||
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
|
||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||
import { GroupAvatar } from "components/GroupAvatar/GroupAvatar";
|
||||
import { Paywall } from "components/Paywall/Paywall";
|
||||
import {
|
||||
TableLoaderSkeleton,
|
||||
TableRowSkeleton,
|
||||
} from "components/TableLoader/TableLoader";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import type { FC } from "react";
|
||||
import { Link as RouterLink, useNavigate } from "react-router-dom";
|
||||
import { docs } from "utils/docs";
|
||||
@@ -117,9 +116,9 @@ export const GroupsPageView: FC<GroupsPageViewProps> = ({
|
||||
<TableCell>
|
||||
<AvatarData
|
||||
avatar={
|
||||
<GroupAvatar
|
||||
name={group.display_name || group.name}
|
||||
avatarURL={group.avatar_url}
|
||||
<Avatar
|
||||
fallback={group.display_name || group.name}
|
||||
src={group.avatar_url}
|
||||
/>
|
||||
}
|
||||
title={group.display_name || group.name}
|
||||
@@ -132,13 +131,13 @@ export const GroupsPageView: FC<GroupsPageViewProps> = ({
|
||||
<AvatarGroup
|
||||
max={10}
|
||||
total={group.members.length}
|
||||
css={{ justifyContent: "flex-end" }}
|
||||
css={{ justifyContent: "flex-end", gap: 4 }}
|
||||
>
|
||||
{group.members.map((member) => (
|
||||
<UserAvatar
|
||||
<Avatar
|
||||
key={member.username}
|
||||
username={member.username}
|
||||
avatarURL={member.avatar_url}
|
||||
fallback={member.username}
|
||||
src={member.avatar_url}
|
||||
/>
|
||||
))}
|
||||
</AvatarGroup>
|
||||
|
||||
@@ -24,7 +24,8 @@ import type {
|
||||
ReducedUser,
|
||||
} from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AvatarData } from "components/Avatar/AvatarData";
|
||||
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog";
|
||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
|
||||
@@ -44,7 +45,6 @@ import {
|
||||
TableToolbar,
|
||||
} from "components/TableToolbar/TableToolbar";
|
||||
import { MemberAutocomplete } from "components/UserAutocomplete/UserAutocomplete";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import { type FC, useState } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
@@ -302,12 +302,7 @@ const GroupMemberRow: FC<GroupMemberRowProps> = ({
|
||||
<TableRow key={member.id}>
|
||||
<TableCell width="59%">
|
||||
<AvatarData
|
||||
avatar={
|
||||
<UserAvatar
|
||||
username={member.username}
|
||||
avatarURL={member.avatar_url}
|
||||
/>
|
||||
}
|
||||
avatar={<Avatar fallback={member.username} src={member.avatar_url} />}
|
||||
title={member.username}
|
||||
subtitle={member.email}
|
||||
/>
|
||||
|
||||
@@ -11,17 +11,16 @@ import TableContainer from "@mui/material/TableContainer";
|
||||
import TableHead from "@mui/material/TableHead";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import type { Group } from "api/typesGenerated";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AvatarData } from "components/Avatar/AvatarData";
|
||||
import { AvatarDataSkeleton } from "components/Avatar/AvatarDataSkeleton";
|
||||
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
|
||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||
import { GroupAvatar } from "components/GroupAvatar/GroupAvatar";
|
||||
import { Paywall } from "components/Paywall/Paywall";
|
||||
import {
|
||||
TableLoaderSkeleton,
|
||||
TableRowSkeleton,
|
||||
} from "components/TableLoader/TableLoader";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import { useClickableTableRow } from "hooks";
|
||||
import type { FC } from "react";
|
||||
import { Link as RouterLink, useNavigate } from "react-router-dom";
|
||||
@@ -124,9 +123,9 @@ const GroupRow: FC<GroupRowProps> = ({ group }) => {
|
||||
<TableCell>
|
||||
<AvatarData
|
||||
avatar={
|
||||
<GroupAvatar
|
||||
name={group.display_name || group.name}
|
||||
avatarURL={group.avatar_url}
|
||||
<Avatar
|
||||
fallback={group.display_name || group.name}
|
||||
src={group.avatar_url}
|
||||
/>
|
||||
}
|
||||
title={group.display_name || group.name}
|
||||
@@ -139,13 +138,13 @@ const GroupRow: FC<GroupRowProps> = ({ group }) => {
|
||||
<AvatarGroup
|
||||
max={10}
|
||||
total={group.members.length}
|
||||
css={{ justifyContent: "flex-end" }}
|
||||
css={{ justifyContent: "flex-end", gap: 8 }}
|
||||
>
|
||||
{group.members.map((member) => (
|
||||
<UserAvatar
|
||||
<Avatar
|
||||
key={member.username}
|
||||
username={member.username}
|
||||
avatarURL={member.avatar_url}
|
||||
fallback={member.username}
|
||||
src={member.avatar_url}
|
||||
/>
|
||||
))}
|
||||
</AvatarGroup>
|
||||
|
||||
@@ -16,7 +16,8 @@ import type {
|
||||
User,
|
||||
} from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AvatarData } from "components/Avatar/AvatarData";
|
||||
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
|
||||
import {
|
||||
MoreMenu,
|
||||
@@ -28,7 +29,6 @@ import {
|
||||
import { SettingsHeader } from "components/SettingsHeader/SettingsHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import { UserGroupsCell } from "pages/UsersPage/UsersTable/UserGroupsCell";
|
||||
import { type FC, useState } from "react";
|
||||
import { TableColumnHelpTooltip } from "./UserTable/TableColumnHelpTooltip";
|
||||
@@ -107,9 +107,9 @@ export const OrganizationMembersPageView: FC<
|
||||
<TableCell>
|
||||
<AvatarData
|
||||
avatar={
|
||||
<UserAvatar
|
||||
username={member.username}
|
||||
avatarURL={member.avatar_url}
|
||||
<Avatar
|
||||
fallback={member.username}
|
||||
src={member.avatar_url}
|
||||
/>
|
||||
}
|
||||
title={member.name || member.username}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { Organization } from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import {
|
||||
PageHeader,
|
||||
PageHeaderSubtitle,
|
||||
PageHeaderTitle,
|
||||
} from "components/PageHeader/PageHeader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import type { FC } from "react";
|
||||
|
||||
interface OrganizationSummaryPageViewProps {
|
||||
@@ -23,13 +23,14 @@ export const OrganizationSummaryPageView: FC<
|
||||
paddingTop: 0,
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" spacing={3} alignItems="center">
|
||||
<UserAvatar
|
||||
key={organization.id}
|
||||
size="xl"
|
||||
username={organization.display_name || organization.name}
|
||||
avatarURL={organization.icon}
|
||||
<Stack direction="row">
|
||||
<Avatar
|
||||
size="lg"
|
||||
variant="icon"
|
||||
src={organization.icon}
|
||||
fallback={organization.display_name || organization.name}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<PageHeaderTitle>
|
||||
{organization.display_name || organization.name}
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
ActiveUserChart,
|
||||
ActiveUsersTitle,
|
||||
} from "components/ActiveUserChart/ActiveUserChart";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import {
|
||||
HelpTooltip,
|
||||
HelpTooltipContent,
|
||||
@@ -35,7 +36,6 @@ import {
|
||||
} from "components/HelpTooltip/HelpTooltip";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import {
|
||||
addHours,
|
||||
addWeeks,
|
||||
@@ -316,10 +316,7 @@ const UsersLatencyPanel: FC<UsersLatencyPanelProps> = ({
|
||||
}}
|
||||
>
|
||||
<div css={{ display: "flex", alignItems: "center", gap: 12 }}>
|
||||
<UserAvatar
|
||||
username={row.username}
|
||||
avatarURL={row.avatar_url}
|
||||
/>
|
||||
<Avatar fallback={row.username} src={row.avatar_url} />
|
||||
<div css={{ fontWeight: 500 }}>{row.username}</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -387,10 +384,7 @@ const UsersActivityPanel: FC<UsersActivityPanelProps> = ({
|
||||
}}
|
||||
>
|
||||
<div css={{ display: "flex", alignItems: "center", gap: 12 }}>
|
||||
<UserAvatar
|
||||
username={row.username}
|
||||
avatarURL={row.avatar_url}
|
||||
/>
|
||||
<Avatar fallback={row.username} src={row.avatar_url} />
|
||||
<div css={{ fontWeight: 500 }}>{row.username}</div>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -11,6 +11,7 @@ import type {
|
||||
Template,
|
||||
TemplateVersion,
|
||||
} from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
|
||||
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog";
|
||||
import { Margins } from "components/Margins/Margins";
|
||||
@@ -29,7 +30,6 @@ import {
|
||||
} from "components/PageHeader/PageHeader";
|
||||
import { Pill } from "components/Pill/Pill";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { Avatar } from "components/deprecated/Avatar/Avatar";
|
||||
import { linkToTemplate, useLinks } from "modules/navigation";
|
||||
import type { FC } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
@@ -201,12 +201,13 @@ export const TemplatePageHeader: FC<TemplatePageHeaderProps> = ({
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Stack direction="row" spacing={3} alignItems="center">
|
||||
{hasIcon ? (
|
||||
<Avatar size="xl" src={template.icon} variant="square" fitImage />
|
||||
) : (
|
||||
<Avatar size="xl">{template.name}</Avatar>
|
||||
)}
|
||||
<Stack direction="row">
|
||||
<Avatar
|
||||
size="lg"
|
||||
variant="icon"
|
||||
src={template.icon}
|
||||
fallback={template.name}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<Stack direction="row" alignItems="center" spacing={1}>
|
||||
|
||||
@@ -2,11 +2,11 @@ import type { CSSObject, Interpolation, Theme } from "@emotion/react";
|
||||
import Button from "@mui/material/Button";
|
||||
import TableCell from "@mui/material/TableCell";
|
||||
import type { TemplateVersion } from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { InfoTooltip } from "components/InfoTooltip/InfoTooltip";
|
||||
import { Pill } from "components/Pill/Pill";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { TimelineEntry } from "components/Timeline/TimelineEntry";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import { useClickableTableRow } from "hooks/useClickableTableRow";
|
||||
import type { FC } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -49,9 +49,9 @@ export const VersionRow: FC<VersionRowProps> = ({
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Stack direction="row" alignItems="center">
|
||||
<UserAvatar
|
||||
username={version.created_by.username}
|
||||
avatarURL={version.created_by.avatar_url}
|
||||
<Avatar
|
||||
fallback={version.created_by.username}
|
||||
src={version.created_by.avatar_url}
|
||||
/>
|
||||
<Stack
|
||||
css={styles.versionSummary}
|
||||
|
||||
@@ -3,12 +3,12 @@ import SecurityIcon from "@mui/icons-material/LockOutlined";
|
||||
import GeneralIcon from "@mui/icons-material/SettingsOutlined";
|
||||
import ScheduleIcon from "@mui/icons-material/TimerOutlined";
|
||||
import type { Template } from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import {
|
||||
Sidebar as BaseSidebar,
|
||||
SidebarHeader,
|
||||
SidebarNavItem,
|
||||
} from "components/Sidebar/Sidebar";
|
||||
import { ExternalAvatar } from "components/deprecated/Avatar/Avatar";
|
||||
import { linkToTemplate, useLinks } from "modules/navigation";
|
||||
import type { FC } from "react";
|
||||
|
||||
@@ -23,7 +23,7 @@ export const Sidebar: FC<SidebarProps> = ({ template }) => {
|
||||
<BaseSidebar>
|
||||
<SidebarHeader
|
||||
avatar={
|
||||
<ExternalAvatar src={template.icon} variant="square" fitImage />
|
||||
<Avatar variant="icon" src={template.icon} fallback={template.name} />
|
||||
}
|
||||
title={template.display_name || template.name}
|
||||
linkTo={getLink(
|
||||
|
||||
+5
-5
@@ -17,10 +17,10 @@ import type {
|
||||
TemplateRole,
|
||||
TemplateUser,
|
||||
} from "api/typesGenerated";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AvatarData } from "components/Avatar/AvatarData";
|
||||
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
|
||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||
import { GroupAvatar } from "components/GroupAvatar/GroupAvatar";
|
||||
import {
|
||||
MoreMenu,
|
||||
MoreMenuContent,
|
||||
@@ -257,9 +257,9 @@ export const TemplatePermissionsPageView: FC<
|
||||
<TableCell>
|
||||
<AvatarData
|
||||
avatar={
|
||||
<GroupAvatar
|
||||
name={group.display_name || group.name}
|
||||
avatarURL={group.avatar_url}
|
||||
<Avatar
|
||||
fallback={group.display_name || group.name}
|
||||
src={group.avatar_url}
|
||||
/>
|
||||
}
|
||||
title={group.display_name || group.name}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import CircularProgress from "@mui/material/CircularProgress";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import { templaceACLAvailable } from "api/queries/templates";
|
||||
import type { Group, ReducedUser } from "api/typesGenerated";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { AvatarData } from "components/Avatar/AvatarData";
|
||||
import { useDebouncedFunction } from "hooks/debounce";
|
||||
import { type ChangeEvent, type FC, useState } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
|
||||
@@ -217,7 +217,10 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
|
||||
</div>
|
||||
|
||||
<TopbarData>
|
||||
<TopbarAvatar src={template.icon} />
|
||||
<TopbarAvatar
|
||||
src={template.icon}
|
||||
fallback={template.display_name || template.name}
|
||||
/>
|
||||
<RouterLink
|
||||
to={templateLink}
|
||||
css={{
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { API } from "api/api";
|
||||
import type { Organization } from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { Filter, MenuSkeleton, type useFilter } from "components/Filter/Filter";
|
||||
import {
|
||||
SelectFilter,
|
||||
type SelectFilterOption,
|
||||
} from "components/Filter/SelectFilter";
|
||||
import { useFilterMenu } from "components/Filter/menu";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import type { FC } from "react";
|
||||
|
||||
interface TemplatesFilterProps {
|
||||
@@ -66,11 +66,6 @@ const orgOption = (org: Organization): SelectFilterOption => ({
|
||||
label: org.display_name || org.name,
|
||||
value: org.name,
|
||||
startIcon: (
|
||||
<UserAvatar
|
||||
key={org.id}
|
||||
size="xs"
|
||||
username={org.display_name}
|
||||
avatarURL={org.icon}
|
||||
/>
|
||||
<Avatar key={org.id} size="sm" fallback={org.display_name} src={org.icon} />
|
||||
),
|
||||
});
|
||||
|
||||
@@ -12,8 +12,9 @@ import TableRow from "@mui/material/TableRow";
|
||||
import { hasError, isApiValidationError } from "api/errors";
|
||||
import type { Template, TemplateExample } from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AvatarData } from "components/Avatar/AvatarData";
|
||||
import { AvatarDataSkeleton } from "components/Avatar/AvatarDataSkeleton";
|
||||
import { DeprecatedBadge } from "components/Badges/Badges";
|
||||
import type { useFilter } from "components/Filter/Filter";
|
||||
import {
|
||||
@@ -36,7 +37,6 @@ import {
|
||||
TableLoaderSkeleton,
|
||||
TableRowSkeleton,
|
||||
} from "components/TableLoader/TableLoader";
|
||||
import { ExternalAvatar } from "components/deprecated/Avatar/Avatar";
|
||||
import { useClickableTableRow } from "hooks/useClickableTableRow";
|
||||
import { linkToTemplate, useLinks } from "modules/navigation";
|
||||
import type { FC } from "react";
|
||||
@@ -110,16 +110,14 @@ const TemplateRow: FC<TemplateRowProps> = ({ showOrganizations, template }) => {
|
||||
>
|
||||
<TableCell>
|
||||
<AvatarData
|
||||
title={
|
||||
template.display_name.length > 0
|
||||
? template.display_name
|
||||
: template.name
|
||||
}
|
||||
title={template.display_name || template.name}
|
||||
subtitle={template.description}
|
||||
avatar={
|
||||
hasIcon && (
|
||||
<ExternalAvatar variant="square" fitImage src={template.icon} />
|
||||
)
|
||||
<Avatar
|
||||
variant="icon"
|
||||
src={template.icon}
|
||||
fallback={template.display_name || template.name}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
@@ -3,7 +3,7 @@ import Grid from "@mui/material/Grid";
|
||||
import { isApiError } from "api/errors";
|
||||
import type { Group } from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { AvatarCard } from "components/AvatarCard/AvatarCard";
|
||||
import { AvatarCard } from "components/Avatar/AvatarCard";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||
import type { FC } from "react";
|
||||
@@ -53,9 +53,7 @@ export const AccountUserGroups: FC<AccountGroupsProps> = ({
|
||||
{groups.map((group) => (
|
||||
<Grid item key={group.id} xs={1}>
|
||||
<AvatarCard
|
||||
background
|
||||
imgUrl={group.avatar_url}
|
||||
altText={group.display_name || group.name}
|
||||
header={group.display_name || group.name}
|
||||
subtitle={
|
||||
showOrganizations ? (
|
||||
|
||||
@@ -20,7 +20,8 @@ import type {
|
||||
ListUserExternalAuthResponse,
|
||||
} from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AvatarData } from "components/Avatar/AvatarData";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import {
|
||||
MoreMenu,
|
||||
@@ -29,8 +30,8 @@ import {
|
||||
MoreMenuTrigger,
|
||||
ThreeDotsButton,
|
||||
} from "components/MoreMenu/MoreMenu";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { TableEmpty } from "components/TableEmpty/TableEmpty";
|
||||
import { Avatar, ExternalAvatar } from "components/deprecated/Avatar/Avatar";
|
||||
import type { ExternalAuthPollingState } from "pages/CreateWorkspacePage/CreateWorkspacePage";
|
||||
import { type FC, useCallback, useEffect, useState } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
@@ -151,60 +152,40 @@ const ExternalAuthRow: FC<ExternalAuthRowProps> = ({
|
||||
? externalAuth.authenticated
|
||||
: (link?.authenticated ?? false);
|
||||
|
||||
let avatar = app.display_icon ? (
|
||||
<ExternalAvatar
|
||||
src={app.display_icon}
|
||||
size="sm"
|
||||
variant="square"
|
||||
fitImage
|
||||
/>
|
||||
) : (
|
||||
<Avatar>{name}</Avatar>
|
||||
);
|
||||
|
||||
// If the link is authenticated and has a refresh token, show that it will automatically
|
||||
// attempt to authenticate when the token expires.
|
||||
if (link?.has_refresh_token && authenticated) {
|
||||
avatar = (
|
||||
<StyledBadge
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "right",
|
||||
}}
|
||||
color="default"
|
||||
overlap="circular"
|
||||
badgeContent={
|
||||
<Tooltip
|
||||
title="Authentication token will automatically refresh when expired."
|
||||
placement="right"
|
||||
>
|
||||
<AutorenewIcon
|
||||
sx={{
|
||||
fontSize: "1em",
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
{avatar}
|
||||
</StyledBadge>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TableRow key={app.id}>
|
||||
<TableCell>
|
||||
<AvatarData title={name} avatar={avatar} />
|
||||
{link?.validate_error && (
|
||||
<>
|
||||
<span
|
||||
css={{ paddingLeft: "1em", color: theme.palette.error.light }}
|
||||
<Stack direction="row" alignItems="center" spacing={1}>
|
||||
<Avatar variant="icon" src={app.display_icon} fallback={name} />
|
||||
<span className="font-semibold">{name}</span>
|
||||
{/*
|
||||
* If the link is authenticated and has a refresh token, show that it will automatically
|
||||
* attempt to authenticate when the token expires.
|
||||
*/}
|
||||
{link?.has_refresh_token && authenticated && (
|
||||
<Tooltip
|
||||
title="Authentication token will automatically refresh when expired."
|
||||
placement="right"
|
||||
>
|
||||
Error:{" "}
|
||||
<AutorenewIcon
|
||||
sx={{
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{link?.validate_error && (
|
||||
<span>
|
||||
<span
|
||||
css={{ paddingLeft: "1em", color: theme.palette.error.light }}
|
||||
>
|
||||
Error:{" "}
|
||||
</span>
|
||||
{link?.validate_error}
|
||||
</span>
|
||||
{link?.validate_error}
|
||||
</>
|
||||
)}
|
||||
)}
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell css={{ textAlign: "right" }}>
|
||||
<LoadingButton
|
||||
|
||||
@@ -7,9 +7,10 @@ import TableHead from "@mui/material/TableHead";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AvatarData } from "components/Avatar/AvatarData";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { TableLoader } from "components/TableLoader/TableLoader";
|
||||
import { Avatar } from "components/deprecated/Avatar/Avatar";
|
||||
import type { FC } from "react";
|
||||
|
||||
export type OAuth2ProviderPageViewProps = {
|
||||
@@ -67,14 +68,10 @@ const OAuth2AppRow: FC<OAuth2AppRowProps> = ({ app, revoke }) => {
|
||||
return (
|
||||
<TableRow key={app.id} data-testid={`app-${app.id}`}>
|
||||
<TableCell>
|
||||
<AvatarData
|
||||
title={app.name}
|
||||
avatar={
|
||||
Boolean(app.icon) && (
|
||||
<Avatar src={app.icon} variant="square" fitImage />
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
<Avatar variant="icon" src={app.icon} fallback={app.name} />
|
||||
<span className="font-semibold">{app.name}</span>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
|
||||
@@ -6,6 +6,7 @@ import NotificationsIcon from "@mui/icons-material/NotificationsNoneOutlined";
|
||||
import AccountIcon from "@mui/icons-material/Person";
|
||||
import VpnKeyOutlined from "@mui/icons-material/VpnKeyOutlined";
|
||||
import type { User } from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { FeatureStageBadge } from "components/FeatureStageBadge/FeatureStageBadge";
|
||||
import { GitIcon } from "components/Icons/GitIcon";
|
||||
import {
|
||||
@@ -13,7 +14,6 @@ import {
|
||||
SidebarHeader,
|
||||
SidebarNavItem,
|
||||
} from "components/Sidebar/Sidebar";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||
import type { FC } from "react";
|
||||
|
||||
@@ -29,9 +29,7 @@ export const Sidebar: FC<SidebarProps> = ({ user }) => {
|
||||
return (
|
||||
<BaseSidebar>
|
||||
<SidebarHeader
|
||||
avatar={
|
||||
<UserAvatar username={user.username} avatarURL={user.avatar_url} />
|
||||
}
|
||||
avatar={<Avatar fallback={user.username} src={user.avatar_url} />}
|
||||
title={user.username}
|
||||
subtitle={user.email}
|
||||
/>
|
||||
|
||||
@@ -2,14 +2,14 @@ import { useTheme } from "@emotion/react";
|
||||
import TableCell from "@mui/material/TableCell";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import type { Region, WorkspaceProxy } from "api/typesGenerated";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AvatarData } from "components/Avatar/AvatarData";
|
||||
import {
|
||||
HealthyBadge,
|
||||
NotHealthyBadge,
|
||||
NotReachableBadge,
|
||||
NotRegisteredBadge,
|
||||
} from "components/Badges/Badges";
|
||||
import { Avatar } from "components/deprecated/Avatar/Avatar";
|
||||
import type { ProxyLatencyReport } from "contexts/useProxyLatency";
|
||||
import type { FC, ReactNode } from "react";
|
||||
import { getLatencyColor } from "utils/latency";
|
||||
@@ -40,31 +40,23 @@ export const ProxyRow: FC<ProxyRowProps> = ({ proxy, latency }) => {
|
||||
return (
|
||||
<>
|
||||
<TableRow key={proxy.name} data-testid={proxy.name}>
|
||||
<TableCell className="name">
|
||||
<TableCell className="summary">
|
||||
<AvatarData
|
||||
title={
|
||||
proxy.display_name && proxy.display_name.length > 0
|
||||
? proxy.display_name
|
||||
: proxy.name
|
||||
}
|
||||
src={proxy.icon_url}
|
||||
title={proxy.display_name || proxy.name}
|
||||
subtitle={proxy.path_app_url}
|
||||
avatar={
|
||||
proxy.icon_url !== "" && (
|
||||
<Avatar
|
||||
size="sm"
|
||||
src={proxy.icon_url}
|
||||
variant="square"
|
||||
fitImage
|
||||
/>
|
||||
)
|
||||
<Avatar
|
||||
variant="icon"
|
||||
src={proxy.icon_url}
|
||||
fallback={proxy.display_name || proxy.name}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
<TableCell css={{ fontSize: 14 }} className="url">
|
||||
{proxy.path_app_url}
|
||||
</TableCell>
|
||||
<TableCell css={{ fontSize: 14 }} className="status">
|
||||
{statusBadge}
|
||||
<TableCell className="status">
|
||||
<div className="flex items-center justify-end">{statusBadge}</div>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
css={{
|
||||
@@ -132,7 +124,7 @@ const ProxyMessagesList: FC<ProxyMessagesListProps> = ({ title, messages }) => {
|
||||
css={{
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
padding: "16px 24px",
|
||||
padding: "16px 64px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -42,9 +42,10 @@ export const WorkspaceProxyView: FC<WorkspaceProxyViewProps> = ({
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell width="40%">Proxy</TableCell>
|
||||
<TableCell width="30%">URL</TableCell>
|
||||
<TableCell width="10%">Status</TableCell>
|
||||
<TableCell width="70%">Proxy</TableCell>
|
||||
<TableCell width="10%" css={{ textAlign: "right" }}>
|
||||
Status
|
||||
</TableCell>
|
||||
<TableCell width="20%" css={{ textAlign: "right" }}>
|
||||
Latency
|
||||
</TableCell>
|
||||
|
||||
@@ -4,9 +4,9 @@ import List from "@mui/material/List";
|
||||
import ListItem from "@mui/material/ListItem";
|
||||
import TableCell from "@mui/material/TableCell";
|
||||
import type { Group } from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { OverflowY } from "components/OverflowY/OverflowY";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { Avatar } from "components/deprecated/Avatar/Avatar";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
@@ -99,9 +99,12 @@ export const UserGroupsCell: FC<GroupsCellProps> = ({ userGroups }) => {
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Avatar size="xs" src={group.avatar_url} alt={groupName}>
|
||||
{groupName}
|
||||
</Avatar>
|
||||
<Avatar
|
||||
size="sm"
|
||||
variant="icon"
|
||||
src={group.avatar_url}
|
||||
fallback={groupName}
|
||||
/>
|
||||
|
||||
<span
|
||||
css={{
|
||||
|
||||
@@ -10,8 +10,8 @@ import TableCell from "@mui/material/TableCell";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import type { GroupsByUserId } from "api/queries/groups";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton";
|
||||
import { AvatarData } from "components/Avatar/AvatarData";
|
||||
import { AvatarDataSkeleton } from "components/Avatar/AvatarDataSkeleton";
|
||||
import { PremiumBadge } from "components/Badges/Badges";
|
||||
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
|
||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||
|
||||
@@ -5,7 +5,6 @@ import type {
|
||||
WorkspaceBuild,
|
||||
} from "api/typesGenerated";
|
||||
import { Alert } from "components/Alert/Alert";
|
||||
import { BuildAvatar } from "components/BuildAvatar/BuildAvatar";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import {
|
||||
FullWidthPageHeader,
|
||||
@@ -16,6 +15,7 @@ import { Stack } from "components/Stack/Stack";
|
||||
import { Stats, StatsItem } from "components/Stats/Stats";
|
||||
import { TAB_PADDING_X, TabLink, Tabs, TabsList } from "components/Tabs/Tabs";
|
||||
import { useSearchParamsKey } from "hooks/useSearchParamsKey";
|
||||
import { BuildAvatar } from "modules/builds/BuildAvatar/BuildAvatar";
|
||||
import { DashboardFullPage } from "modules/dashboard/DashboardLayout";
|
||||
import { AgentLogs } from "modules/resources/AgentLogs/AgentLogs";
|
||||
import { useAgentLogs } from "modules/resources/AgentLogs/useAgentLogs";
|
||||
@@ -74,8 +74,8 @@ export const WorkspaceBuildPageView: FC<WorkspaceBuildPageViewProps> = ({
|
||||
return (
|
||||
<DashboardFullPage>
|
||||
<FullWidthPageHeader sticky={false}>
|
||||
<Stack direction="row" alignItems="center" spacing={3}>
|
||||
<BuildAvatar build={build} />
|
||||
<Stack direction="row">
|
||||
<BuildAvatar build={build} size="lg" />
|
||||
<div>
|
||||
<PageHeaderTitle>Build #{build.build_number}</PageHeaderTitle>
|
||||
<PageHeaderSubtitle>{build.initiator_name}</PageHeaderSubtitle>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { CSSObject, Interpolation, Theme } from "@emotion/react";
|
||||
import TableCell from "@mui/material/TableCell";
|
||||
import type { WorkspaceBuild } from "api/typesGenerated";
|
||||
import { BuildAvatar } from "components/BuildAvatar/BuildAvatar";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { TimelineEntry } from "components/Timeline/TimelineEntry";
|
||||
import { useClickable } from "hooks/useClickable";
|
||||
import { BuildAvatar } from "modules/builds/BuildAvatar/BuildAvatar";
|
||||
import type { FC } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
|
||||
@@ -6,14 +6,14 @@ import CircularProgress from "@mui/material/CircularProgress";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import type { Template, TemplateVersion } from "api/typesGenerated";
|
||||
import { Alert } from "components/Alert/Alert";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AvatarData } from "components/Avatar/AvatarData";
|
||||
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
|
||||
import type { DialogProps } from "components/Dialogs/Dialog";
|
||||
import { FormFields } from "components/Form/Form";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { Pill } from "components/Pill/Pill";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { Avatar } from "components/deprecated/Avatar/Avatar";
|
||||
import { TemplateUpdateMessage } from "modules/templates/TemplateUpdateMessage";
|
||||
import { type FC, useRef, useState } from "react";
|
||||
import { createDayString } from "utils/createDayString";
|
||||
@@ -88,9 +88,10 @@ export const ChangeVersionDialog: FC<ChangeVersionDialogProps> = ({
|
||||
<li {...props}>
|
||||
<AvatarData
|
||||
avatar={
|
||||
<Avatar src={option.created_by.avatar_url}>
|
||||
{option.name}
|
||||
</Avatar>
|
||||
<Avatar
|
||||
src={option.created_by.avatar_url}
|
||||
fallback={option.name}
|
||||
/>
|
||||
}
|
||||
title={
|
||||
<Stack
|
||||
|
||||
@@ -6,7 +6,8 @@ import Link from "@mui/material/Link";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { workspaceQuota } from "api/queries/workspaceQuota";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AvatarData } from "components/Avatar/AvatarData";
|
||||
import {
|
||||
Topbar,
|
||||
TopbarAvatar,
|
||||
@@ -16,8 +17,6 @@ import {
|
||||
TopbarIconButton,
|
||||
} from "components/FullPageLayout/Topbar";
|
||||
import { HelpTooltipContent } from "components/HelpTooltip/HelpTooltip";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import { ExternalAvatar } from "components/deprecated/Avatar/Avatar";
|
||||
import { Popover, PopoverTrigger } from "components/deprecated/Popover/Popover";
|
||||
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||
import { linkToTemplate, useLinks } from "modules/navigation";
|
||||
@@ -25,7 +24,6 @@ import { WorkspaceStatusBadge } from "modules/workspaces/WorkspaceStatusBadge/Wo
|
||||
import type { FC } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { isEmojiUrl } from "utils/appearance";
|
||||
import { displayDormantDeletion } from "utils/dormant";
|
||||
import { WorkspaceActions } from "./WorkspaceActions/WorkspaceActions";
|
||||
import { WorkspaceNotifications } from "./WorkspaceNotifications/WorkspaceNotifications";
|
||||
@@ -285,11 +283,7 @@ const OwnerBreadcrumb: FC<OwnerBreadcrumbProps> = ({
|
||||
<Popover mode="hover">
|
||||
<PopoverTrigger>
|
||||
<span css={styles.breadcrumbSegment}>
|
||||
<UserAvatar
|
||||
size="xs"
|
||||
username={ownerName}
|
||||
avatarURL={ownerAvatarUrl}
|
||||
/>
|
||||
<Avatar size="sm" fallback={ownerName} src={ownerAvatarUrl} />
|
||||
<span css={styles.breadcrumbText}>{ownerName}</span>
|
||||
</span>
|
||||
</PopoverTrigger>
|
||||
@@ -319,7 +313,7 @@ const OrganizationBreadcrumb: FC<OrganizationBreadcrumbProps> = ({
|
||||
<Popover mode="hover">
|
||||
<PopoverTrigger>
|
||||
<span css={styles.breadcrumbSegment}>
|
||||
<UserAvatar size="xs" src={orgIconUrl ?? ""} username={orgName} />
|
||||
<Avatar size="sm" src={orgIconUrl ?? ""} fallback={orgName} />
|
||||
<span css={styles.breadcrumbText}>{orgName}</span>
|
||||
</span>
|
||||
</PopoverTrigger>
|
||||
@@ -345,12 +339,7 @@ const OrganizationBreadcrumb: FC<OrganizationBreadcrumbProps> = ({
|
||||
subtitle="Organization"
|
||||
avatar={
|
||||
orgIconUrl && (
|
||||
<ExternalAvatar
|
||||
src={orgIconUrl}
|
||||
title={orgName}
|
||||
variant={isEmojiUrl(orgIconUrl) ? "square" : "circular"}
|
||||
fitImage
|
||||
/>
|
||||
<Avatar variant="icon" src={orgIconUrl} fallback={orgName} />
|
||||
)
|
||||
}
|
||||
imgFallbackText={orgName}
|
||||
@@ -381,7 +370,7 @@ const WorkspaceBreadcrumb: FC<WorkspaceBreadcrumbProps> = ({
|
||||
<Popover mode="hover">
|
||||
<PopoverTrigger>
|
||||
<span css={styles.breadcrumbSegment}>
|
||||
<TopbarAvatar src={templateIconUrl} />
|
||||
<TopbarAvatar src={templateIconUrl} fallback={templateDisplayName} />
|
||||
<span css={[styles.breadcrumbText, { fontWeight: 500 }]}>
|
||||
{workspaceName}
|
||||
</span>
|
||||
@@ -412,11 +401,10 @@ const WorkspaceBreadcrumb: FC<WorkspaceBreadcrumbProps> = ({
|
||||
</Link>
|
||||
}
|
||||
avatar={
|
||||
<ExternalAvatar
|
||||
<Avatar
|
||||
variant="icon"
|
||||
src={templateIconUrl}
|
||||
title={workspaceName}
|
||||
variant={isEmojiUrl(templateIconUrl) ? "square" : "circular"}
|
||||
fitImage
|
||||
fallback={templateDisplayName}
|
||||
/>
|
||||
}
|
||||
imgFallbackText={templateDisplayName}
|
||||
@@ -440,6 +428,7 @@ const styles = {
|
||||
|
||||
breadcrumbSegment: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexFlow: "row nowrap",
|
||||
gap: "8px",
|
||||
maxWidth: "160px",
|
||||
|
||||
@@ -2,12 +2,12 @@ import ParameterIcon from "@mui/icons-material/CodeOutlined";
|
||||
import GeneralIcon from "@mui/icons-material/SettingsOutlined";
|
||||
import ScheduleIcon from "@mui/icons-material/TimerOutlined";
|
||||
import type { Workspace } from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import {
|
||||
Sidebar as BaseSidebar,
|
||||
SidebarHeader,
|
||||
SidebarNavItem,
|
||||
} from "components/Sidebar/Sidebar";
|
||||
import { Avatar } from "components/deprecated/Avatar/Avatar";
|
||||
import type { FC } from "react";
|
||||
|
||||
interface SidebarProps {
|
||||
@@ -20,7 +20,11 @@ export const Sidebar: FC<SidebarProps> = ({ username, workspace }) => {
|
||||
<BaseSidebar>
|
||||
<SidebarHeader
|
||||
avatar={
|
||||
<Avatar src={workspace.template_icon} variant="square" fitImage />
|
||||
<Avatar
|
||||
variant="icon"
|
||||
src={workspace.template_icon}
|
||||
fallback={workspace.name}
|
||||
/>
|
||||
}
|
||||
title={workspace.name}
|
||||
linkTo={`/@${username}/${workspace.name}`}
|
||||
|
||||
@@ -3,11 +3,11 @@ import OpenIcon from "@mui/icons-material/OpenInNewOutlined";
|
||||
import Button from "@mui/material/Button";
|
||||
import Link from "@mui/material/Link";
|
||||
import type { Template } from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { MenuSearch } from "components/Menu/MenuSearch";
|
||||
import { OverflowY } from "components/OverflowY/OverflowY";
|
||||
import { SearchEmpty, searchStyles } from "components/Search/Search";
|
||||
import { Avatar } from "components/deprecated/Avatar/Avatar";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
@@ -21,8 +21,6 @@ import {
|
||||
type LinkProps as RouterLinkProps,
|
||||
} from "react-router-dom";
|
||||
|
||||
const ICON_SIZE = 18;
|
||||
|
||||
type TemplatesQuery = UseQueryResult<Template[]>;
|
||||
|
||||
interface WorkspacesButtonProps {
|
||||
@@ -141,18 +139,10 @@ const WorkspaceResultsRow: FC<WorkspaceResultsRowProps> = ({ template }) => {
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
variant="icon"
|
||||
src={template.icon}
|
||||
fitImage
|
||||
alt={template.display_name || "Coder template"}
|
||||
css={{
|
||||
width: `${ICON_SIZE}px`,
|
||||
height: `${ICON_SIZE}px`,
|
||||
fontSize: `${ICON_SIZE * 0.5}px`,
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
{template.display_name || "-"}
|
||||
</Avatar>
|
||||
fallback={template.display_name || template.name}
|
||||
/>
|
||||
|
||||
<div
|
||||
css={(theme) => ({
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import ArrowForwardOutlined from "@mui/icons-material/ArrowForwardOutlined";
|
||||
import Button from "@mui/material/Button";
|
||||
import type { Template } from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { TableEmpty } from "components/TableEmpty/TableEmpty";
|
||||
import { Avatar } from "components/deprecated/Avatar/Avatar";
|
||||
import { linkToTemplate, useLinks } from "modules/navigation";
|
||||
import type { FC } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
@@ -122,14 +122,7 @@ export const WorkspacesEmpty: FC<WorkspacesEmptyProps> = ({
|
||||
})}
|
||||
>
|
||||
<div css={{ flexShrink: 0, paddingTop: 4 }}>
|
||||
<Avatar
|
||||
variant={t.icon ? "square" : undefined}
|
||||
fitImage={Boolean(t.icon)}
|
||||
src={t.icon}
|
||||
size="sm"
|
||||
>
|
||||
{t.name}
|
||||
</Avatar>
|
||||
<Avatar variant="icon" src={t.icon} fallback={t.name} />
|
||||
</div>
|
||||
|
||||
<div css={{ width: "100%", minWidth: "0" }}>
|
||||
|
||||
@@ -11,15 +11,15 @@ import TableHead from "@mui/material/TableHead";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
import type { Template, Workspace } from "api/typesGenerated";
|
||||
import { AvatarData } from "components/AvatarData/AvatarData";
|
||||
import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import { AvatarData } from "components/Avatar/AvatarData";
|
||||
import { AvatarDataSkeleton } from "components/Avatar/AvatarDataSkeleton";
|
||||
import { InfoTooltip } from "components/InfoTooltip/InfoTooltip";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import {
|
||||
TableLoaderSkeleton,
|
||||
TableRowSkeleton,
|
||||
} from "components/TableLoader/TableLoader";
|
||||
import { ExternalAvatar } from "components/deprecated/Avatar/Avatar";
|
||||
import { useClickableTableRow } from "hooks/useClickableTableRow";
|
||||
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||
import { WorkspaceDormantBadge } from "modules/workspaces/WorkspaceDormantBadge/WorkspaceDormantBadge";
|
||||
@@ -186,15 +186,11 @@ export const WorkspacesTable: FC<WorkspacesTableProps> = ({
|
||||
</div>
|
||||
}
|
||||
avatar={
|
||||
<ExternalAvatar
|
||||
<Avatar
|
||||
variant="icon"
|
||||
src={workspace.template_icon}
|
||||
variant={
|
||||
workspace.template_icon ? "square" : undefined
|
||||
}
|
||||
fitImage={Boolean(workspace.template_icon)}
|
||||
>
|
||||
{workspace.name}
|
||||
</ExternalAvatar>
|
||||
fallback={workspace.name}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { API } from "api/api";
|
||||
import type { WorkspaceStatus } from "api/typesGenerated";
|
||||
import type { Template, WorkspaceStatus } from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import {
|
||||
SelectFilter,
|
||||
type SelectFilterOption,
|
||||
@@ -10,7 +11,6 @@ import {
|
||||
useFilterMenu,
|
||||
} from "components/Filter/menu";
|
||||
import { StatusIndicator } from "components/StatusIndicator/StatusIndicator";
|
||||
import { TemplateAvatar } from "components/TemplateAvatar/TemplateAvatar";
|
||||
import type { FC } from "react";
|
||||
import { getDisplayWorkspaceStatus } from "utils/workspace";
|
||||
|
||||
@@ -30,7 +30,7 @@ export const useTemplateFilterMenu = ({
|
||||
return {
|
||||
label: template.display_name || template.name,
|
||||
value: template.name,
|
||||
startIcon: <TemplateAvatar size="xs" template={template} />,
|
||||
startIcon: <TemplateAvatar template={template} />,
|
||||
} satisfies SelectFilterOption;
|
||||
}
|
||||
return null;
|
||||
@@ -46,12 +46,23 @@ export const useTemplateFilterMenu = ({
|
||||
return filteredTemplates.map((template) => ({
|
||||
label: template.display_name || template.name,
|
||||
value: template.name,
|
||||
startIcon: <TemplateAvatar size="xs" template={template} />,
|
||||
startIcon: <TemplateAvatar template={template} />,
|
||||
}));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const TemplateAvatar: FC<{ template: Template }> = ({ template }) => {
|
||||
return (
|
||||
<Avatar
|
||||
size="sm"
|
||||
variant="icon"
|
||||
src={template.icon}
|
||||
fallback={template.display_name || template.name}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export type TemplateFilterMenu = ReturnType<typeof useTemplateFilterMenu>;
|
||||
|
||||
type TemplateMenuProps = Readonly<{
|
||||
|
||||
Reference in New Issue
Block a user