diff --git a/site/e2e/tests/deployment/workspaceProxies.spec.ts b/site/e2e/tests/deployment/workspaceProxies.spec.ts index 0e6edd544cc60..f18ba0a21dde3 100644 --- a/site/e2e/tests/deployment/workspaceProxies.spec.ts +++ b/site/e2e/tests/deployment/workspaceProxies.spec.ts @@ -18,13 +18,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 }) => { @@ -50,19 +49,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); @@ -82,13 +78,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! diff --git a/site/src/components/Avatar/Avatar.stories.tsx b/site/src/components/Avatar/Avatar.stories.tsx index c1e2f590a91d3..55deeb9073dbe 100644 --- a/site/src/components/Avatar/Avatar.stories.tsx +++ b/site/src/components/Avatar/Avatar.stories.tsx @@ -1,11 +1,11 @@ import type { Meta, StoryObj } from "@storybook/react"; -import { Avatar, AvatarFallback, AvatarImage } from "./Avatar"; +import { Avatar } from "./Avatar"; const meta: Meta = { title: "components/Avatar", component: Avatar, args: { - children: , + 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: ( - - ), + 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: ( - - ), + 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: ( - - ), + 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: AR, + fallback: "Adriana Rodrigues", }, }; -export const FallbackDefaultSize: Story = { +export const FallbackMdSize: Story = { args: { - children: AR, + src: "", + fallback: "Adriana Rodrigues", }, }; export const FallbackSmSize: Story = { args: { + src: "", size: "sm", - children: AR, + fallback: "Adriana Rodrigues", }, }; diff --git a/site/src/components/Avatar/Avatar.tsx b/site/src/components/Avatar/Avatar.tsx index 54807455dd030..c09bfaddddf10 100644 --- a/site/src/components/Avatar/Avatar.tsx +++ b/site/src/components/Avatar/Avatar.tsx @@ -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, - VariantProps {} +export type AvatarProps = AvatarPrimitive.AvatarProps & + VariantProps & { + src?: string; + + fallback?: string; + }; const Avatar = React.forwardRef< React.ElementRef, AvatarProps ->(({ className, size, variant, ...props }, ref) => ( - -)); -Avatar.displayName = AvatarPrimitive.Root.displayName; - -const AvatarImage = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AvatarImage.displayName = AvatarPrimitive.Image.displayName; +>(({ className, size, variant, src, fallback, children, ...props }, ref) => { + const theme = useTheme(); -const AvatarFallback = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; + return ( + + + {fallback && ( + + {fallback.charAt(0).toUpperCase()} + + )} + {children} + + ); +}); +Avatar.displayName = AvatarPrimitive.Root.displayName; -export { Avatar, AvatarImage, AvatarFallback }; +export { Avatar }; diff --git a/site/src/components/AvatarCard/AvatarCard.stories.tsx b/site/src/components/Avatar/AvatarCard.stories.tsx similarity index 96% rename from site/src/components/AvatarCard/AvatarCard.stories.tsx rename to site/src/components/Avatar/AvatarCard.stories.tsx index b1202be9d6387..cc8fb56e16c05 100644 --- a/site/src/components/AvatarCard/AvatarCard.stories.tsx +++ b/site/src/components/Avatar/AvatarCard.stories.tsx @@ -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", }, }; diff --git a/site/src/components/AvatarCard/AvatarCard.tsx b/site/src/components/Avatar/AvatarCard.tsx similarity index 86% rename from site/src/components/AvatarCard/AvatarCard.tsx rename to site/src/components/Avatar/AvatarCard.tsx index a98dfc54b519d..97df5c6ee765c 100644 --- a/site/src/components/AvatarCard/AvatarCard.tsx +++ b/site/src/components/Avatar/AvatarCard.tsx @@ -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 = ({ header, imgUrl, - altText, - background, subtitle, maxWidth = "none", }) => { @@ -72,9 +67,7 @@ export const AvatarCard: FC = ({ )} - - {header} - + ); }; diff --git a/site/src/components/AvatarData/AvatarData.stories.tsx b/site/src/components/Avatar/AvatarData.stories.tsx similarity index 100% rename from site/src/components/AvatarData/AvatarData.stories.tsx rename to site/src/components/Avatar/AvatarData.stories.tsx diff --git a/site/src/components/AvatarData/AvatarData.tsx b/site/src/components/Avatar/AvatarData.tsx similarity index 86% rename from site/src/components/AvatarData/AvatarData.tsx rename to site/src/components/Avatar/AvatarData.tsx index 93952bb84d2fc..fab188a234853 100644 --- a/site/src/components/AvatarData/AvatarData.tsx +++ b/site/src/components/Avatar/AvatarData.tsx @@ -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 = ({ const theme = useTheme(); if (!avatar) { avatar = ( - - {(typeof title === "string" ? title : imgFallbackText) || "-"} - + ); } return ( = ({ 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 ( - } - classes={{ badge: cx(classNames.badge, badgeType) }} - > - - - - - ); -}; - -const classNames = { - badge: css({ - borderRadius: "100%", - width: 8, - minWidth: 8, - height: 8, - display: "block", - padding: 0, - }), -}; diff --git a/site/src/components/Filter/SelectFilter.stories.tsx b/site/src/components/Filter/SelectFilter.stories.tsx index ff90bdaa273b3..fcb187c1c098c 100644 --- a/site/src/components/Filter/SelectFilter.stories.tsx +++ b/site/src/components/Filter/SelectFilter.stories.tsx @@ -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: , + startIcon: , label: `Option ${i + 1}`, value: `option-${i + 1}`, })); diff --git a/site/src/components/Filter/UserFilter.tsx b/site/src/components/Filter/UserFilter.tsx index 015bc3d71d439..e1c6d0057d021 100644 --- a/site/src/components/Filter/UserFilter.tsx +++ b/site/src/components/Filter/UserFilter.tsx @@ -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: ( - + ), }, ...filtered, @@ -45,11 +41,7 @@ export const useUserFilterMenu = ({ label: me.username, value: me.username, startIcon: ( - + ), }; } @@ -61,10 +53,10 @@ export const useUserFilterMenu = ({ label: firstUser.username, value: firstUser.username, startIcon: ( - ), }; @@ -77,11 +69,7 @@ export const useUserFilterMenu = ({ label: user.username, value: user.username, startIcon: ( - + ), })); options = addMeAsFirstOption(options); diff --git a/site/src/components/FullPageLayout/Topbar.tsx b/site/src/components/FullPageLayout/Topbar.tsx index ab51e93b395df..8acff9bc6f4c5 100644 --- a/site/src/components/FullPageLayout/Topbar.tsx +++ b/site/src/components/FullPageLayout/Topbar.tsx @@ -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> = (props) => { }; export const TopbarAvatar: FC = (props) => { - return ( - - ); + return ; }; type TopbarIconProps = HTMLAttributes; diff --git a/site/src/components/GroupAvatar/GroupAvatar.stories.tsx b/site/src/components/GroupAvatar/GroupAvatar.stories.tsx deleted file mode 100644 index 7702fcf39ccad..0000000000000 --- a/site/src/components/GroupAvatar/GroupAvatar.stories.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import { GroupAvatar } from "./GroupAvatar"; - -const meta: Meta = { - title: "components/GroupAvatar", - component: GroupAvatar, -}; - -export default meta; -type Story = StoryObj; - -const Example: Story = { - args: { - name: "My Group", - avatarURL: "", - }, -}; - -export { Example as GroupAvatar }; diff --git a/site/src/components/GroupAvatar/GroupAvatar.tsx b/site/src/components/GroupAvatar/GroupAvatar.tsx deleted file mode 100644 index fb65c448aef9e..0000000000000 --- a/site/src/components/GroupAvatar/GroupAvatar.tsx +++ /dev/null @@ -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 = ({ name, avatarURL }) => { - const badge = useClassName(classNames.badge, []); - - return ( - } - classes={{ badge }} - > - - {name} - - - ); -}; - -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; diff --git a/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx b/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx index 6adf28f852f04..348c312ec9fe7 100644 --- a/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx +++ b/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx @@ -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 = ({ ...params.InputProps, onChange: debouncedInputOnChange, startAdornment: value && ( - - {value.name} - + ), endAdornment: ( <> diff --git a/site/src/components/SelectMenu/SelectMenu.stories.tsx b/site/src/components/SelectMenu/SelectMenu.stories.tsx index cae38693899d1..4cbcb465db4bd 100644 --- a/site/src/components/SelectMenu/SelectMenu.stories.tsx +++ b/site/src/components/SelectMenu/SelectMenu.stories.tsx @@ -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 = { } + startIcon={} > {selectedOpt} @@ -36,7 +36,7 @@ const meta: Meta = { {opts.map((o) => ( - + {o} @@ -77,7 +77,7 @@ export const LongButtonText: Story = { } + startIcon={} > {selectedOpt} @@ -88,7 +88,7 @@ export const LongButtonText: Story = { {opts.map((o) => ( - + {o} @@ -115,7 +115,7 @@ export const NoSelectedOption: Story = { {opts.map((o) => ( - + {o} diff --git a/site/src/components/Sidebar/Sidebar.stories.tsx b/site/src/components/Sidebar/Sidebar.stories.tsx index 580d5d59b5be9..6f8d578230b7a 100644 --- a/site/src/components/Sidebar/Sidebar.stories.tsx +++ b/site/src/components/Sidebar/Sidebar.stories.tsx @@ -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 = { @@ -20,7 +20,7 @@ export const Default: Story = { children: ( } + avatar={} title="Jon" subtitle="jon@coder.com" /> diff --git a/site/src/components/Sidebar/Sidebar.tsx b/site/src/components/Sidebar/Sidebar.tsx index b719bb28e83de..987a4b91a3976 100644 --- a/site/src/components/Sidebar/Sidebar.tsx +++ b/site/src/components/Sidebar/Sidebar.tsx @@ -28,7 +28,7 @@ export const SidebarHeader: FC = ({ linkTo, }) => { return ( - + {avatar}
= ({ - template, - ...avatarProps -}) => { - return template.icon ? ( - - ) : ( - {template.display_name || template.name} - ); -}; diff --git a/site/src/components/Timeline/TimelineEntry.tsx b/site/src/components/Timeline/TimelineEntry.tsx index 5c9cedd4cea56..73cdc19295b97 100644 --- a/site/src/components/Timeline/TimelineEntry.tsx +++ b/site/src/components/Timeline/TimelineEntry.tsx @@ -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 ( [ - { - "&: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} ); }); + +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>; diff --git a/site/src/components/UserAutocomplete/UserAutocomplete.tsx b/site/src/components/UserAutocomplete/UserAutocomplete.tsx index cf9c9ea6ecc0b..f5bfd109c4a5c 100644 --- a/site/src/components/UserAutocomplete/UserAutocomplete.tsx +++ b/site/src/components/UserAutocomplete/UserAutocomplete.tsx @@ -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 = ({ ...params.InputProps, onChange: debouncedInputOnChange, startAdornment: value && ( - - {value.username} - + ), endAdornment: ( <> diff --git a/site/src/components/UserAvatar/UserAvatar.stories.tsx b/site/src/components/UserAvatar/UserAvatar.stories.tsx deleted file mode 100644 index 367dcc2efe7c1..0000000000000 --- a/site/src/components/UserAvatar/UserAvatar.stories.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import { UserAvatar } from "./UserAvatar"; - -const meta: Meta = { - title: "components/UserAvatar", - component: UserAvatar, -}; - -export default meta; -type Story = StoryObj; - -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", - }, -}; diff --git a/site/src/components/UserAvatar/UserAvatar.tsx b/site/src/components/UserAvatar/UserAvatar.tsx deleted file mode 100644 index 5244dc6de20e0..0000000000000 --- a/site/src/components/UserAvatar/UserAvatar.tsx +++ /dev/null @@ -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 = ({ - username, - avatarURL, - ...avatarProps -}) => { - return ( - - {username} - - ); -}; diff --git a/site/src/components/deprecated/Avatar/Avatar.stories.tsx b/site/src/components/deprecated/Avatar/Avatar.stories.tsx deleted file mode 100644 index c06390c450d89..0000000000000 --- a/site/src/components/deprecated/Avatar/Avatar.stories.tsx +++ /dev/null @@ -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 = { - title: "components/DeprecatedAvatar", - component: Avatar, -}; - -export default meta; -type Story = StoryObj; - -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: , - }, -}; - -export const WithMuiIconXL = { - args: { - background: true, - children: , - size: "xl", - }, -}; - -export const WithAvatarIcon = { - args: { - background: true, - children: , - }, -}; diff --git a/site/src/components/deprecated/Avatar/Avatar.tsx b/site/src/components/deprecated/Avatar/Avatar.tsx deleted file mode 100644 index 1fcd6208655d2..0000000000000 --- a/site/src/components/deprecated/Avatar/Avatar.tsx +++ /dev/null @@ -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>; - -const fitImageStyles = css` - & .MuiAvatar-img { - object-fit: contain; - } -`; - -/** - * @deprecated Use `Avatar` from `@components/Avatar` instead. - */ -export const Avatar: FC = ({ - size = "md", - fitImage, - children, - background, - ...muiProps -}) => { - const fromName = !muiProps.src && typeof children === "string"; - - return ( - ({ - background: - background || fromName ? theme.palette.divider : undefined, - color: theme.palette.text.primary, - }), - ]} - > - {typeof children === "string" ? firstLetter(children) : children} - - ); -}; - -/** - * @deprecated Use `Avatar` from `@components/Avatar` instead. - */ -export const ExternalAvatar: FC = (props) => { - const theme = useTheme(); - - return ( - - ); -}; - -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 = ({ 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 ( - <> - -
- {alt} -
- - ); -}; - -const firstLetter = (str: string): string => { - if (str.length > 0) { - return str[0].toLocaleUpperCase(); - } - - return ""; -}; diff --git a/site/src/index.css b/site/src/index.css index 1353b200745c0..c97e827b98a0f 100644 --- a/site/src/index.css +++ b/site/src/index.css @@ -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%; diff --git a/site/src/components/BuildAvatar/BuildAvatar.stories.tsx b/site/src/modules/builds/BuildAvatar/BuildAvatar.stories.tsx similarity index 91% rename from site/src/components/BuildAvatar/BuildAvatar.stories.tsx rename to site/src/modules/builds/BuildAvatar/BuildAvatar.stories.tsx index 75cedf10ba5c5..ef7f1c49cc7d4 100644 --- a/site/src/components/BuildAvatar/BuildAvatar.stories.tsx +++ b/site/src/modules/builds/BuildAvatar/BuildAvatar.stories.tsx @@ -13,27 +13,21 @@ const meta: Meta = { export default meta; type Story = StoryObj; -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", }, }; diff --git a/site/src/modules/builds/BuildAvatar/BuildAvatar.tsx b/site/src/modules/builds/BuildAvatar/BuildAvatar.tsx new file mode 100644 index 0000000000000..e1be56e995b3f --- /dev/null +++ b/site/src/modules/builds/BuildAvatar/BuildAvatar.tsx @@ -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 = ({ build, size }) => { + const theme = useTheme(); + const { type } = getDisplayWorkspaceBuildStatus(theme, build); + const iconColor = useClassName( + (css, theme) => css({ color: theme.roles[type].fill.solid }), + [type], + ); + + return ( + + + + ); +}; diff --git a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.tsx b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.tsx index caa01ecce0aa6..6fb7428bb0dc1 100644 --- a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.tsx +++ b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.tsx @@ -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 = ({ }> - - {template.icon !== "" ? ( - - ) : ( - {template.name} - )} + +
diff --git a/site/src/pages/CreateWorkspacePage/SelectedTemplate.tsx b/site/src/pages/CreateWorkspacePage/SelectedTemplate.tsx index 921dd9d173557..13687e4e45fcb 100644 --- a/site/src/pages/CreateWorkspacePage/SelectedTemplate.tsx +++ b/site/src/pages/CreateWorkspacePage/SelectedTemplate.tsx @@ -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 = ({ template }) => { return ( - - + - {template.name} - + fallback={template.name} + /> diff --git a/site/src/pages/DeploymentSettingsPage/OAuth2AppsSettingsPage/OAuth2AppsSettingsPageView.tsx b/site/src/pages/DeploymentSettingsPage/OAuth2AppsSettingsPage/OAuth2AppsSettingsPageView.tsx index 47d19f48d68eb..8f47c288a840c 100644 --- a/site/src/pages/DeploymentSettingsPage/OAuth2AppsSettingsPage/OAuth2AppsSettingsPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/OAuth2AppsSettingsPage/OAuth2AppsSettingsPageView.tsx @@ -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 = ({ app }) => { return ( - - ) - } - /> + + + {app.name} + diff --git a/site/src/pages/ExternalAuthPage/ExternalAuthPageView.tsx b/site/src/pages/ExternalAuthPage/ExternalAuthPageView.tsx index d6e19665cdf92..5ff3b5a626b93 100644 --- a/site/src/pages/ExternalAuthPage/ExternalAuthPageView.tsx +++ b/site/src/pages/ExternalAuthPage/ExternalAuthPageView.tsx @@ -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 = ({

{externalAuth.installations.length > 0 && ( -
+
{externalAuth.installations.map((install) => { if (!install.account) { return; @@ -88,9 +91,10 @@ const ExternalAuthPageView: FC = ({ target="_blank" rel="noreferrer" > - - {install.account.login} - + ); diff --git a/site/src/pages/GroupsPage/GroupPage.tsx b/site/src/pages/GroupsPage/GroupPage.tsx index 1244118aa3840..4af3ce30389fa 100644 --- a/site/src/pages/GroupsPage/GroupPage.tsx +++ b/site/src/pages/GroupsPage/GroupPage.tsx @@ -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 = ({ - } + avatar={} title={member.username} subtitle={member.email} /> diff --git a/site/src/pages/GroupsPage/GroupsPageView.tsx b/site/src/pages/GroupsPage/GroupsPageView.tsx index 8c9f1f8e46601..6188cc09428b9 100644 --- a/site/src/pages/GroupsPage/GroupsPageView.tsx +++ b/site/src/pages/GroupsPage/GroupsPageView.tsx @@ -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 = ({ } title={group.display_name || group.name} @@ -132,13 +131,13 @@ export const GroupsPageView: FC = ({ {group.members.map((member) => ( - ))} diff --git a/site/src/pages/ManagementSettingsPage/GroupsPage/GroupPage.tsx b/site/src/pages/ManagementSettingsPage/GroupsPage/GroupPage.tsx index 42251f5d5965f..6c226a1dba9ff 100644 --- a/site/src/pages/ManagementSettingsPage/GroupsPage/GroupPage.tsx +++ b/site/src/pages/ManagementSettingsPage/GroupsPage/GroupPage.tsx @@ -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 = ({ - } + avatar={} title={member.username} subtitle={member.email} /> diff --git a/site/src/pages/ManagementSettingsPage/GroupsPage/GroupsPageView.tsx b/site/src/pages/ManagementSettingsPage/GroupsPage/GroupsPageView.tsx index 65e565d75133e..fe109d0ea5718 100644 --- a/site/src/pages/ManagementSettingsPage/GroupsPage/GroupsPageView.tsx +++ b/site/src/pages/ManagementSettingsPage/GroupsPage/GroupsPageView.tsx @@ -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 = ({ group }) => { } title={group.display_name || group.name} @@ -139,13 +138,13 @@ const GroupRow: FC = ({ group }) => { {group.members.map((member) => ( - ))} diff --git a/site/src/pages/ManagementSettingsPage/OrganizationMembersPageView.tsx b/site/src/pages/ManagementSettingsPage/OrganizationMembersPageView.tsx index 36002c605e04b..1cc009e1f4de7 100644 --- a/site/src/pages/ManagementSettingsPage/OrganizationMembersPageView.tsx +++ b/site/src/pages/ManagementSettingsPage/OrganizationMembersPageView.tsx @@ -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< } title={member.name || member.username} diff --git a/site/src/pages/ManagementSettingsPage/OrganizationSummaryPageView.tsx b/site/src/pages/ManagementSettingsPage/OrganizationSummaryPageView.tsx index 3c37499f34375..c12b3c13a416c 100644 --- a/site/src/pages/ManagementSettingsPage/OrganizationSummaryPageView.tsx +++ b/site/src/pages/ManagementSettingsPage/OrganizationSummaryPageView.tsx @@ -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, }} > - - + +
{organization.display_name || organization.name} diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx index f205194a1aded..097b8fce513e7 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx @@ -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 = ({ }} >
- +
{row.username}
= ({ }} >
- +
{row.username}
= ({ } > - - {hasIcon ? ( - - ) : ( - {template.name} - )} + +
diff --git a/site/src/pages/TemplatePage/TemplateVersionsPage/VersionRow.tsx b/site/src/pages/TemplatePage/TemplateVersionsPage/VersionRow.tsx index f2cf6bd695fe2..bd8e7e846a011 100644 --- a/site/src/pages/TemplatePage/TemplateVersionsPage/VersionRow.tsx +++ b/site/src/pages/TemplatePage/TemplateVersionsPage/VersionRow.tsx @@ -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 = ({ justifyContent="space-between" > - = ({ template }) => { + } title={template.display_name || template.name} linkTo={getLink( diff --git a/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPageView.tsx b/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPageView.tsx index a69f7657c8bbd..33f1b227e0af2 100644 --- a/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPageView.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/TemplatePermissionsPageView.tsx @@ -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< } title={group.display_name || group.name} diff --git a/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/UserOrGroupAutocomplete.tsx b/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/UserOrGroupAutocomplete.tsx index 07c0c58969848..7b0f643eac39f 100644 --- a/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/UserOrGroupAutocomplete.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplatePermissionsPage/UserOrGroupAutocomplete.tsx @@ -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"; diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx index bb9bbb7c72732..fe02ff909d169 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx @@ -217,7 +217,10 @@ export const TemplateVersionEditor: FC = ({
- + ({ label: org.display_name || org.name, value: org.name, startIcon: ( - + ), }); diff --git a/site/src/pages/TemplatesPage/TemplatesPageView.tsx b/site/src/pages/TemplatesPage/TemplatesPageView.tsx index 34667c437ceee..d936abb781b90 100644 --- a/site/src/pages/TemplatesPage/TemplatesPageView.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPageView.tsx @@ -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 = ({ showOrganizations, template }) => { > 0 - ? template.display_name - : template.name - } + title={template.display_name || template.name} subtitle={template.description} avatar={ - hasIcon && ( - - ) + } /> diff --git a/site/src/pages/UserSettingsPage/AccountPage/AccountUserGroups.tsx b/site/src/pages/UserSettingsPage/AccountPage/AccountUserGroups.tsx index 23df9fa7f3e36..49c30523519a1 100644 --- a/site/src/pages/UserSettingsPage/AccountPage/AccountUserGroups.tsx +++ b/site/src/pages/UserSettingsPage/AccountPage/AccountUserGroups.tsx @@ -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 = ({ {groups.map((group) => ( = ({ ? externalAuth.authenticated : (link?.authenticated ?? false); - let avatar = app.display_icon ? ( - - ) : ( - {name} - ); - - // 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 = ( - - - - } - > - {avatar} - - ); - } - return ( - - {link?.validate_error && ( - <> - + + {name} + {/* + * 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 && ( + - Error:{" "} + + + )} + + {link?.validate_error && ( + + + Error:{" "} + + {link?.validate_error} - {link?.validate_error} - - )} + )} +
= ({ app, revoke }) => { return ( - - ) - } - /> + + + {app.name} + diff --git a/site/src/pages/UserSettingsPage/Sidebar.tsx b/site/src/pages/UserSettingsPage/Sidebar.tsx index 196f34d5ce0e1..5cc8c54dcbda9 100644 --- a/site/src/pages/UserSettingsPage/Sidebar.tsx +++ b/site/src/pages/UserSettingsPage/Sidebar.tsx @@ -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 = ({ user }) => { return ( - } + avatar={} title={user.username} subtitle={user.email} /> diff --git a/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyRow.tsx b/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyRow.tsx index 0005e1fe28a68..7da859a1e0b91 100644 --- a/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyRow.tsx +++ b/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyRow.tsx @@ -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 = ({ proxy, latency }) => { return ( <> - + 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 !== "" && ( - - ) + } /> - - {proxy.path_app_url} - - - {statusBadge} + +
{statusBadge}
= ({ title, messages }) => { css={{ borderBottom: `1px solid ${theme.palette.divider}`, backgroundColor: theme.palette.background.default, - padding: "16px 24px", + padding: "16px 64px", }} >
= ({ - Proxy - URL - Status + Proxy + + Status + Latency diff --git a/site/src/pages/UsersPage/UsersTable/UserGroupsCell.tsx b/site/src/pages/UsersPage/UsersTable/UserGroupsCell.tsx index 03d8bebcd2dbf..00f135abb2bdc 100644 --- a/site/src/pages/UsersPage/UsersTable/UserGroupsCell.tsx +++ b/site/src/pages/UsersPage/UsersTable/UserGroupsCell.tsx @@ -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 = ({ userGroups }) => { alignItems: "center", }} > - - {groupName} - + = ({ return ( - - + +
Build #{build.build_number} {build.initiator_name} diff --git a/site/src/pages/WorkspacePage/BuildRow.tsx b/site/src/pages/WorkspacePage/BuildRow.tsx index 8b4e9e5a564d1..366ff4b04d8f8 100644 --- a/site/src/pages/WorkspacePage/BuildRow.tsx +++ b/site/src/pages/WorkspacePage/BuildRow.tsx @@ -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 { diff --git a/site/src/pages/WorkspacePage/ChangeVersionDialog.tsx b/site/src/pages/WorkspacePage/ChangeVersionDialog.tsx index cd12862097f45..453baca597a2c 100644 --- a/site/src/pages/WorkspacePage/ChangeVersionDialog.tsx +++ b/site/src/pages/WorkspacePage/ChangeVersionDialog.tsx @@ -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 = ({
  • - {option.name} - + } title={ = ({ - + {ownerName} @@ -319,7 +313,7 @@ const OrganizationBreadcrumb: FC = ({ - + {orgName} @@ -345,12 +339,7 @@ const OrganizationBreadcrumb: FC = ({ subtitle="Organization" avatar={ orgIconUrl && ( - + ) } imgFallbackText={orgName} @@ -381,7 +370,7 @@ const WorkspaceBreadcrumb: FC = ({ - + {workspaceName} @@ -412,11 +401,10 @@ const WorkspaceBreadcrumb: FC = ({ } avatar={ - } imgFallbackText={templateDisplayName} @@ -440,6 +428,7 @@ const styles = { breadcrumbSegment: { display: "flex", + alignItems: "center", flexFlow: "row nowrap", gap: "8px", maxWidth: "160px", diff --git a/site/src/pages/WorkspaceSettingsPage/Sidebar.tsx b/site/src/pages/WorkspaceSettingsPage/Sidebar.tsx index b89e6f5b7d887..604fc2ed70d23 100644 --- a/site/src/pages/WorkspaceSettingsPage/Sidebar.tsx +++ b/site/src/pages/WorkspaceSettingsPage/Sidebar.tsx @@ -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 = ({ username, workspace }) => { + } title={workspace.name} linkTo={`/@${username}/${workspace.name}`} diff --git a/site/src/pages/WorkspacesPage/WorkspacesButton.tsx b/site/src/pages/WorkspacesPage/WorkspacesButton.tsx index d97ff9e2a7551..504a8e156ce5f 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesButton.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesButton.tsx @@ -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; interface WorkspacesButtonProps { @@ -141,18 +139,10 @@ const WorkspaceResultsRow: FC = ({ template }) => { }} > - {template.display_name || "-"} - + fallback={template.display_name || template.name} + />
    ({ diff --git a/site/src/pages/WorkspacesPage/WorkspacesEmpty.tsx b/site/src/pages/WorkspacesPage/WorkspacesEmpty.tsx index dcf96ec5c0d8b..80b5602ba2c4c 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesEmpty.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesEmpty.tsx @@ -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 = ({ })} >
    - - {t.name} - +
    diff --git a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx index 2ca1b28ca1632..d3ed0d650e9a6 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx @@ -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 = ({
    } avatar={ - - {workspace.name} - + fallback={workspace.name} + /> } />
    diff --git a/site/src/pages/WorkspacesPage/filter/menus.tsx b/site/src/pages/WorkspacesPage/filter/menus.tsx index 7a2d79c606e1f..52e655914cec5 100644 --- a/site/src/pages/WorkspacesPage/filter/menus.tsx +++ b/site/src/pages/WorkspacesPage/filter/menus.tsx @@ -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: , + startIcon: , } 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: , + startIcon: , })); }, }); }; +const TemplateAvatar: FC<{ template: Template }> = ({ template }) => { + return ( + + ); +}; + export type TemplateFilterMenu = ReturnType; type TemplateMenuProps = Readonly<{