diff --git a/site/package.json b/site/package.json index 1951d32ad82c2..e4d65d55f7c5f 100644 --- a/site/package.json +++ b/site/package.json @@ -50,6 +50,7 @@ "@mui/system": "5.16.7", "@mui/utils": "5.16.6", "@mui/x-tree-view": "7.18.0", + "@radix-ui/react-avatar": "1.1.2", "@radix-ui/react-dialog": "1.1.2", "@radix-ui/react-label": "2.1.0", "@radix-ui/react-slider": "1.2.1", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index a9218c49473e1..e0d1cfab8b2f3 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -63,6 +63,9 @@ importers: '@mui/x-tree-view': specifier: 7.18.0 version: 7.18.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@mui/material@5.16.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.16.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-avatar': + specifier: 1.1.2 + version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-dialog': specifier: 1.1.2 version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1550,6 +1553,19 @@ packages: '@radix-ui/primitive@1.1.0': resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==} + '@radix-ui/react-avatar@1.1.2': + resolution: {integrity: sha512-GaC7bXQZ5VgZvVvsJ5mu/AEbjYLnhhkoidOboC50Z6FFlLA03wG2ianUoH+zgDQ31/9gCF59bE4+2bBgTyMiig==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-collection@1.1.0': resolution: {integrity: sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==} peerDependencies: @@ -1581,6 +1597,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-compose-refs@1.1.1': + resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-context@1.0.1': resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==} peerDependencies: @@ -1822,6 +1847,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-primitive@2.0.1': + resolution: {integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-slider@1.2.1': resolution: {integrity: sha512-bEzQoDW0XP+h/oGbutF5VMWJPAl/UU8IJjr7h02SOHDIIIxq+cep8nItVNoBV+OMmahCdqdF38FTpmXoqQUGvw==} peerDependencies: @@ -1853,6 +1891,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-slot@1.1.1': + resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-switch@1.1.1': resolution: {integrity: sha512-diPqDDoBcZPSicYoMWdWx+bCPuTRH4QSp9J+65IvtdS0Kuzt67bI6n32vCj8q6NZmYW/ah+2orOtMwcX5eQwIg==} peerDependencies: @@ -3513,7 +3560,6 @@ packages: eslint@8.52.0: resolution: {integrity: sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true espree@9.6.1: @@ -7213,6 +7259,18 @@ snapshots: '@radix-ui/primitive@1.1.0': {} + '@radix-ui/react-avatar@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-context': 1.1.1(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.12)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + '@radix-ui/react-collection@1.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.12)(react@18.3.1) @@ -7238,6 +7296,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.12 + '@radix-ui/react-compose-refs@1.1.1(@types/react@18.3.12)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + '@radix-ui/react-context@1.0.1(@types/react@18.3.12)(react@18.3.1)': dependencies: '@babel/runtime': 7.25.6 @@ -7455,6 +7519,15 @@ snapshots: '@types/react': 18.3.12 '@types/react-dom': 18.3.1 + '@radix-ui/react-primitive@2.0.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.1.1(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + '@radix-ui/react-slider@1.2.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/number': 1.1.0 @@ -7489,6 +7562,13 @@ snapshots: optionalDependencies: '@types/react': 18.3.12 + '@radix-ui/react-slot@1.1.1(@types/react@18.3.12)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.12)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + '@radix-ui/react-switch@1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 diff --git a/site/src/components/Avatar/Avatar.stories.tsx b/site/src/components/Avatar/Avatar.stories.tsx index 944185b8e2350..c1e2f590a91d3 100644 --- a/site/src/components/Avatar/Avatar.stories.tsx +++ b/site/src/components/Avatar/Avatar.stories.tsx @@ -1,59 +1,73 @@ -import PauseIcon from "@mui/icons-material/PauseOutlined"; import type { Meta, StoryObj } from "@storybook/react"; -import { Avatar, AvatarIcon } from "./Avatar"; +import { Avatar, AvatarFallback, AvatarImage } from "./Avatar"; const meta: Meta = { title: "components/Avatar", component: Avatar, + args: { + children: , + }, }; export default meta; type Story = StoryObj; -export const WithLetter: Story = { - args: { - children: "Coder", - }, +export const ImageLgSize: Story = { + args: { size: "lg" }, }; -export const WithLetterXL = { +export const ImageDefaultSize: Story = {}; + +export const ImageSmSize: Story = { + args: { size: "sm" }, +}; + +export const IconLgSize: Story = { args: { - children: "Coder", - size: "xl", + size: "lg", + variant: "icon", + children: ( + + ), }, }; -export const WithImage = { +export const IconDefaultSize: Story = { args: { - src: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4", + variant: "icon", + children: ( + + ), }, }; -export const WithImageXL = { +export const IconSmSize: Story = { args: { - src: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4", - size: "xl", + variant: "icon", + size: "sm", + children: ( + + ), }, }; -export const WithMuiIcon = { +export const FallbackLgSize: Story = { args: { - background: true, - children: , + size: "lg", + + children: AR, }, }; -export const WithMuiIconXL = { +export const FallbackDefaultSize: Story = { args: { - background: true, - children: , - size: "xl", + children: AR, }, }; -export const WithAvatarIcon = { +export const FallbackSmSize: Story = { args: { - background: true, - children: , + size: "sm", + children: AR, }, }; diff --git a/site/src/components/Avatar/Avatar.tsx b/site/src/components/Avatar/Avatar.tsx index 6e0e4c650573d..54807455dd030 100644 --- a/site/src/components/Avatar/Avatar.tsx +++ b/site/src/components/Avatar/Avatar.tsx @@ -1,116 +1,94 @@ -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; -}; +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} + * + * 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} + */ +import * as React from "react"; +import { cn } from "utils/cn"; -const sizeStyles = { - xs: { - width: 16, - height: 16, - fontSize: 8, - fontWeight: 700, +const avatarVariants = cva( + "relative flex shrink-0 overflow-hidden rounded border border-solid bg-surface-secondary text-content-secondary", + { + 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]", + }, + variant: { + default: "", + icon: "", + }, + }, + defaultVariants: { + size: "default", + }, + compoundVariants: [ + { + size: "lg", + variant: "icon", + className: "p-[9px]", + }, + { + size: "default", + variant: "icon", + className: "p-[3px]", + }, + { + size: "sm", + variant: "icon", + className: "p-[2px]", + }, + ], }, - 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; - } -`; +); -export const Avatar: FC = ({ - size = "md", - fitImage, - children, - background, - ...muiProps -}) => { - const fromName = !muiProps.src && typeof children === "string"; +export interface AvatarProps + extends React.ComponentPropsWithoutRef, + VariantProps {} - return ( - ({ - background: - background || fromName ? theme.palette.divider : undefined, - color: theme.palette.text.primary, - }), - ]} - > - {typeof children === "string" ? firstLetter(children) : children} - - ); -}; - -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 - */ -export const AvatarIcon: FC = ({ src, alt }) => { - const hookId = useId(); - const avatarId = `${hookId}-avatar`; +const Avatar = React.forwardRef< + React.ElementRef, + AvatarProps +>(({ className, size, variant, ...props }, ref) => ( + +)); +Avatar.displayName = AvatarPrimitive.Root.displayName; - // 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 AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarImage.displayName = AvatarPrimitive.Image.displayName; -const firstLetter = (str: string): string => { - if (str.length > 0) { - return str[0].toLocaleUpperCase(); - } +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; - return ""; -}; +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/site/src/components/AvatarCard/AvatarCard.tsx b/site/src/components/AvatarCard/AvatarCard.tsx index 5605b4c239732..a98dfc54b519d 100644 --- a/site/src/components/AvatarCard/AvatarCard.tsx +++ b/site/src/components/AvatarCard/AvatarCard.tsx @@ -1,5 +1,5 @@ import { type CSSObject, useTheme } from "@emotion/react"; -import { Avatar } from "components/Avatar/Avatar"; +import { Avatar } from "components/deprecated/Avatar/Avatar"; import type { FC, ReactNode } from "react"; type AvatarCardProps = { diff --git a/site/src/components/AvatarData/AvatarData.tsx b/site/src/components/AvatarData/AvatarData.tsx index eb9fa81d4981d..93952bb84d2fc 100644 --- a/site/src/components/AvatarData/AvatarData.tsx +++ b/site/src/components/AvatarData/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 { diff --git a/site/src/components/BuildAvatar/BuildAvatar.tsx b/site/src/components/BuildAvatar/BuildAvatar.tsx index e9870b4e746be..ae9e9e9af4904 100644 --- a/site/src/components/BuildAvatar/BuildAvatar.tsx +++ b/site/src/components/BuildAvatar/BuildAvatar.tsx @@ -2,8 +2,8 @@ import { css, cx } from "@emotion/css"; import { useTheme } from "@emotion/react"; import Badge from "@mui/material/Badge"; import type { WorkspaceBuild } from "api/typesGenerated"; -import { Avatar, type AvatarProps } from "components/Avatar/Avatar"; 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"; diff --git a/site/src/components/FullPageLayout/Topbar.tsx b/site/src/components/FullPageLayout/Topbar.tsx index 7787b0fe50295..ab51e93b395df 100644 --- a/site/src/components/FullPageLayout/Topbar.tsx +++ b/site/src/components/FullPageLayout/Topbar.tsx @@ -2,7 +2,10 @@ 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/Avatar/Avatar"; +import { + type AvatarProps, + ExternalAvatar, +} from "components/deprecated/Avatar/Avatar"; import { type FC, type ForwardedRef, diff --git a/site/src/components/GroupAvatar/GroupAvatar.tsx b/site/src/components/GroupAvatar/GroupAvatar.tsx index c65e68f4f0dc7..fb65c448aef9e 100644 --- a/site/src/components/GroupAvatar/GroupAvatar.tsx +++ b/site/src/components/GroupAvatar/GroupAvatar.tsx @@ -1,6 +1,6 @@ import Group from "@mui/icons-material/Group"; import Badge from "@mui/material/Badge"; -import { Avatar } from "components/Avatar/Avatar"; +import { Avatar } from "components/deprecated/Avatar/Avatar"; import { type ClassName, useClassName } from "hooks/useClassName"; import type { FC } from "react"; diff --git a/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx b/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx index 2c4fc89f6ea21..6adf28f852f04 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 { Avatar } from "components/Avatar/Avatar"; import { AvatarData } from "components/AvatarData/AvatarData"; +import { Avatar } from "components/deprecated/Avatar/Avatar"; import { useDebouncedFunction } from "hooks/debounce"; import { type ChangeEvent, diff --git a/site/src/components/TemplateAvatar/TemplateAvatar.tsx b/site/src/components/TemplateAvatar/TemplateAvatar.tsx index 1882ae5cbd758..dc0017f459b19 100644 --- a/site/src/components/TemplateAvatar/TemplateAvatar.tsx +++ b/site/src/components/TemplateAvatar/TemplateAvatar.tsx @@ -1,5 +1,5 @@ import type { Template } from "api/typesGenerated"; -import { Avatar, type AvatarProps } from "components/Avatar/Avatar"; +import { Avatar, type AvatarProps } from "components/deprecated/Avatar/Avatar"; import type { FC } from "react"; interface TemplateAvatarProps extends AvatarProps { diff --git a/site/src/components/UserAutocomplete/UserAutocomplete.tsx b/site/src/components/UserAutocomplete/UserAutocomplete.tsx index d80773cb4de16..cf9c9ea6ecc0b 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 { Avatar } from "components/Avatar/Avatar"; import { AvatarData } from "components/AvatarData/AvatarData"; +import { Avatar } from "components/deprecated/Avatar/Avatar"; import { useDebouncedFunction } from "hooks/debounce"; import { type ChangeEvent, diff --git a/site/src/components/UserAvatar/UserAvatar.tsx b/site/src/components/UserAvatar/UserAvatar.tsx index c0815651ea348..5244dc6de20e0 100644 --- a/site/src/components/UserAvatar/UserAvatar.tsx +++ b/site/src/components/UserAvatar/UserAvatar.tsx @@ -1,4 +1,4 @@ -import { Avatar, type AvatarProps } from "components/Avatar/Avatar"; +import { Avatar, type AvatarProps } from "components/deprecated/Avatar/Avatar"; import type { FC } from "react"; export type UserAvatarProps = { diff --git a/site/src/components/deprecated/Avatar/Avatar.stories.tsx b/site/src/components/deprecated/Avatar/Avatar.stories.tsx new file mode 100644 index 0000000000000..c06390c450d89 --- /dev/null +++ b/site/src/components/deprecated/Avatar/Avatar.stories.tsx @@ -0,0 +1,59 @@ +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 new file mode 100644 index 0000000000000..1fcd6208655d2 --- /dev/null +++ b/site/src/components/deprecated/Avatar/Avatar.tsx @@ -0,0 +1,124 @@ +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/modules/resources/ResourceAvatar.tsx b/site/src/modules/resources/ResourceAvatar.tsx index d910bbe0c6678..14d3b3248a882 100644 --- a/site/src/modules/resources/ResourceAvatar.tsx +++ b/site/src/modules/resources/ResourceAvatar.tsx @@ -1,7 +1,7 @@ import { visuallyHidden } from "@mui/utils"; import type { WorkspaceResource } from "api/typesGenerated"; -import { Avatar } from "components/Avatar/Avatar"; import { ExternalImage } from "components/ExternalImage/ExternalImage"; +import { Avatar } from "components/deprecated/Avatar/Avatar"; import { type FC, useId } from "react"; import { getResourceIconPath } from "utils/workspace"; diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx index 1c158b8225e2f..5cda28cb495b5 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx @@ -5,7 +5,6 @@ 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, @@ -22,6 +21,7 @@ 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"; diff --git a/site/src/pages/CreateWorkspacePage/SelectedTemplate.tsx b/site/src/pages/CreateWorkspacePage/SelectedTemplate.tsx index 7d949c3a3ed40..921dd9d173557 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 { ExternalAvatar } 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 { diff --git a/site/src/pages/DeploymentSettingsPage/OAuth2AppsSettingsPage/OAuth2AppsSettingsPageView.tsx b/site/src/pages/DeploymentSettingsPage/OAuth2AppsSettingsPage/OAuth2AppsSettingsPageView.tsx index 7481d8d5c1684..47d19f48d68eb 100644 --- a/site/src/pages/DeploymentSettingsPage/OAuth2AppsSettingsPage/OAuth2AppsSettingsPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/OAuth2AppsSettingsPage/OAuth2AppsSettingsPageView.tsx @@ -10,11 +10,11 @@ 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 { Avatar } from "components/Avatar/Avatar"; import { AvatarData } from "components/AvatarData/AvatarData"; 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"; diff --git a/site/src/pages/ExternalAuthPage/ExternalAuthPageView.tsx b/site/src/pages/ExternalAuthPage/ExternalAuthPageView.tsx index 73248c66fd21d..d6e19665cdf92 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 { diff --git a/site/src/pages/TemplatePage/TemplatePageHeader.tsx b/site/src/pages/TemplatePage/TemplatePageHeader.tsx index 4ed1103194225..4f6d345b0c4fb 100644 --- a/site/src/pages/TemplatePage/TemplatePageHeader.tsx +++ b/site/src/pages/TemplatePage/TemplatePageHeader.tsx @@ -11,7 +11,6 @@ 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"; @@ -30,6 +29,7 @@ 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"; diff --git a/site/src/pages/TemplateSettingsPage/Sidebar.tsx b/site/src/pages/TemplateSettingsPage/Sidebar.tsx index 67294d248e373..b28c27cda005c 100644 --- a/site/src/pages/TemplateSettingsPage/Sidebar.tsx +++ b/site/src/pages/TemplateSettingsPage/Sidebar.tsx @@ -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 { ExternalAvatar } 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"; diff --git a/site/src/pages/TemplatesPage/TemplatesPageView.tsx b/site/src/pages/TemplatesPage/TemplatesPageView.tsx index da6d4e113229b..34667c437ceee 100644 --- a/site/src/pages/TemplatesPage/TemplatesPageView.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPageView.tsx @@ -12,7 +12,6 @@ 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 { ExternalAvatar } from "components/Avatar/Avatar"; import { AvatarData } from "components/AvatarData/AvatarData"; import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton"; import { DeprecatedBadge } from "components/Badges/Badges"; @@ -37,6 +36,7 @@ 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"; diff --git a/site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.tsx b/site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.tsx index cb3334a9a8bf0..09ec684bf56c6 100644 --- a/site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.tsx +++ b/site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.tsx @@ -20,7 +20,6 @@ import type { ListUserExternalAuthResponse, } from "api/typesGenerated"; import { ErrorAlert } from "components/Alert/ErrorAlert"; -import { Avatar, ExternalAvatar } from "components/Avatar/Avatar"; import { AvatarData } from "components/AvatarData/AvatarData"; import { Loader } from "components/Loader/Loader"; import { @@ -31,6 +30,7 @@ import { ThreeDotsButton, } from "components/MoreMenu/MoreMenu"; 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"; diff --git a/site/src/pages/UserSettingsPage/OAuth2ProviderPage/OAuth2ProviderPageView.tsx b/site/src/pages/UserSettingsPage/OAuth2ProviderPage/OAuth2ProviderPageView.tsx index 446f62ee5e922..17e953222c07e 100644 --- a/site/src/pages/UserSettingsPage/OAuth2ProviderPage/OAuth2ProviderPageView.tsx +++ b/site/src/pages/UserSettingsPage/OAuth2ProviderPage/OAuth2ProviderPageView.tsx @@ -7,9 +7,9 @@ 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 { Avatar } from "components/Avatar/Avatar"; import { AvatarData } from "components/AvatarData/AvatarData"; import { TableLoader } from "components/TableLoader/TableLoader"; +import { Avatar } from "components/deprecated/Avatar/Avatar"; import type { FC } from "react"; export type OAuth2ProviderPageViewProps = { diff --git a/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyRow.tsx b/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyRow.tsx index 39f3d4e8ae60a..0005e1fe28a68 100644 --- a/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyRow.tsx +++ b/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyRow.tsx @@ -2,7 +2,6 @@ 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 { Avatar } from "components/Avatar/Avatar"; import { AvatarData } from "components/AvatarData/AvatarData"; import { HealthyBadge, @@ -10,6 +9,7 @@ import { 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"; diff --git a/site/src/pages/UsersPage/UsersTable/UserGroupsCell.tsx b/site/src/pages/UsersPage/UsersTable/UserGroupsCell.tsx index 0400c5ac05bab..f26bcb36c8b13 100644 --- a/site/src/pages/UsersPage/UsersTable/UserGroupsCell.tsx +++ b/site/src/pages/UsersPage/UsersTable/UserGroupsCell.tsx @@ -4,7 +4,6 @@ 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 { Popover, @@ -12,6 +11,7 @@ import { PopoverTrigger, } from "components/Popover/Popover"; import { Stack } from "components/Stack/Stack"; +import { Avatar } from "components/deprecated/Avatar/Avatar"; import type { FC } from "react"; type GroupsCellProps = { diff --git a/site/src/pages/WorkspacePage/ChangeVersionDialog.tsx b/site/src/pages/WorkspacePage/ChangeVersionDialog.tsx index 206f7b743f28f..cd12862097f45 100644 --- a/site/src/pages/WorkspacePage/ChangeVersionDialog.tsx +++ b/site/src/pages/WorkspacePage/ChangeVersionDialog.tsx @@ -6,7 +6,6 @@ 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 { Avatar } from "components/Avatar/Avatar"; import { AvatarData } from "components/AvatarData/AvatarData"; import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"; import type { DialogProps } from "components/Dialogs/Dialog"; @@ -14,6 +13,7 @@ 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"; diff --git a/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx b/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx index 7ca112befb4e5..25d1b0e7a9fab 100644 --- a/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx @@ -6,7 +6,6 @@ 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 { ExternalAvatar } from "components/Avatar/Avatar"; import { AvatarData } from "components/AvatarData/AvatarData"; import { Topbar, @@ -19,6 +18,7 @@ import { import { HelpTooltipContent } from "components/HelpTooltip/HelpTooltip"; import { Popover, PopoverTrigger } from "components/Popover/Popover"; import { UserAvatar } from "components/UserAvatar/UserAvatar"; +import { ExternalAvatar } from "components/deprecated/Avatar/Avatar"; import { useDashboard } from "modules/dashboard/useDashboard"; import { linkToTemplate, useLinks } from "modules/navigation"; import { WorkspaceStatusBadge } from "modules/workspaces/WorkspaceStatusBadge/WorkspaceStatusBadge"; diff --git a/site/src/pages/WorkspaceSettingsPage/Sidebar.tsx b/site/src/pages/WorkspaceSettingsPage/Sidebar.tsx index f10729d2c5c35..b89e6f5b7d887 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 { diff --git a/site/src/pages/WorkspacesPage/WorkspacesButton.tsx b/site/src/pages/WorkspacesPage/WorkspacesButton.tsx index 8723469ad4a7b..2cbca11fcef60 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesButton.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesButton.tsx @@ -3,7 +3,6 @@ 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"; @@ -13,6 +12,7 @@ import { PopoverTrigger, } from "components/Popover/Popover"; import { SearchEmpty, searchStyles } from "components/Search/Search"; +import { Avatar } from "components/deprecated/Avatar/Avatar"; import { linkToTemplate, useLinks } from "modules/navigation"; import { type FC, type ReactNode, useState } from "react"; import type { UseQueryResult } from "react-query"; diff --git a/site/src/pages/WorkspacesPage/WorkspacesEmpty.tsx b/site/src/pages/WorkspacesPage/WorkspacesEmpty.tsx index c2a1ced06800d..dcf96ec5c0d8b 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"; diff --git a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx index cff9873274a55..2ca1b28ca1632 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx @@ -11,7 +11,6 @@ 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 { ExternalAvatar } from "components/Avatar/Avatar"; import { AvatarData } from "components/AvatarData/AvatarData"; import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton"; import { InfoTooltip } from "components/InfoTooltip/InfoTooltip"; @@ -20,6 +19,7 @@ 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"; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy