mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
refactor(site): Normalize avatar components (#5860)
This commit is contained in:
@@ -96,6 +96,10 @@ rules:
|
||||
message:
|
||||
"Use path imports to avoid pulling in unused modules. See:
|
||||
https://material-ui.com/guides/minimizing-bundle-size/"
|
||||
- name: "@material-ui/core/Avatar"
|
||||
message:
|
||||
"You should use the Avatar component provided on
|
||||
components/Avatar/Avatar"
|
||||
no-unused-vars: "off"
|
||||
"object-curly-spacing": "off"
|
||||
react-hooks/exhaustive-deps: warn
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Story } from "@storybook/react"
|
||||
import { Avatar, AvatarIcon, AvatarProps } from "./Avatar"
|
||||
import PauseIcon from "@material-ui/icons/PauseOutlined"
|
||||
|
||||
export default {
|
||||
title: "components/Avatar",
|
||||
component: Avatar,
|
||||
}
|
||||
|
||||
const Template: Story<AvatarProps> = (args: AvatarProps) => <Avatar {...args} />
|
||||
|
||||
export const Letter = Template.bind({})
|
||||
Letter.args = {
|
||||
children: "Coder",
|
||||
}
|
||||
|
||||
export const LetterXL = Template.bind({})
|
||||
LetterXL.args = {
|
||||
children: "Coder",
|
||||
size: "xl",
|
||||
}
|
||||
|
||||
export const LetterDarken = Template.bind({})
|
||||
LetterDarken.args = {
|
||||
children: "Coder",
|
||||
colorScheme: "darken",
|
||||
}
|
||||
|
||||
export const Image = Template.bind({})
|
||||
Image.args = {
|
||||
src: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4",
|
||||
}
|
||||
|
||||
export const ImageXL = Template.bind({})
|
||||
ImageXL.args = {
|
||||
src: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4",
|
||||
size: "xl",
|
||||
}
|
||||
|
||||
export const MuiIcon = Template.bind({})
|
||||
MuiIcon.args = {
|
||||
children: <PauseIcon />,
|
||||
}
|
||||
|
||||
export const MuiIconDarken = Template.bind({})
|
||||
MuiIconDarken.args = {
|
||||
children: <PauseIcon />,
|
||||
colorScheme: "darken",
|
||||
}
|
||||
|
||||
export const MuiIconXL = Template.bind({})
|
||||
MuiIconXL.args = {
|
||||
children: <PauseIcon />,
|
||||
size: "xl",
|
||||
}
|
||||
|
||||
export const AvatarIconDarken = Template.bind({})
|
||||
AvatarIconDarken.args = {
|
||||
children: <AvatarIcon src="/icon/database.svg" />,
|
||||
colorScheme: "darken",
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// This is the only place MuiAvatar can be used
|
||||
// eslint-disable-next-line no-restricted-imports -- Read above
|
||||
import MuiAvatar, {
|
||||
AvatarProps as MuiAvatarProps,
|
||||
} from "@material-ui/core/Avatar"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { FC } from "react"
|
||||
import { combineClasses } from "util/combineClasses"
|
||||
import { firstLetter } from "./firstLetter"
|
||||
|
||||
export type AvatarProps = MuiAvatarProps & {
|
||||
size?: "md" | "xl"
|
||||
colorScheme?: "light" | "darken"
|
||||
fitImage?: boolean
|
||||
}
|
||||
|
||||
export const Avatar: FC<AvatarProps> = ({
|
||||
size = "md",
|
||||
colorScheme = "light",
|
||||
fitImage,
|
||||
className,
|
||||
children,
|
||||
...muiProps
|
||||
}) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<MuiAvatar
|
||||
{...muiProps}
|
||||
className={combineClasses([
|
||||
className,
|
||||
styles[size],
|
||||
styles[colorScheme],
|
||||
fitImage && styles.fitImage,
|
||||
])}
|
||||
>
|
||||
{/* If the children is a string, we always want to render the first letter */}
|
||||
{typeof children === "string" ? firstLetter(children) : children}
|
||||
</MuiAvatar>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Use it to make an img element behaves like a MaterialUI Icon component
|
||||
*/
|
||||
export const AvatarIcon: FC<{ src: string }> = ({ src }) => {
|
||||
const styles = useStyles()
|
||||
return <img src={src} alt="" className={styles.avatarIcon} />
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
// Size styles
|
||||
// Just use the default value from theme
|
||||
md: {},
|
||||
xl: {
|
||||
width: theme.spacing(6),
|
||||
height: theme.spacing(6),
|
||||
fontSize: theme.spacing(3),
|
||||
},
|
||||
// Colors
|
||||
// Just use the default value from theme
|
||||
light: {},
|
||||
darken: {
|
||||
background: theme.palette.divider,
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
// Avatar icon
|
||||
avatarIcon: {
|
||||
maxWidth: "50%",
|
||||
},
|
||||
// Fit image
|
||||
fitImage: {
|
||||
"& .MuiAvatar-img": {
|
||||
objectFit: "contain",
|
||||
},
|
||||
},
|
||||
}))
|
||||
@@ -16,16 +16,9 @@ Example.args = {
|
||||
subtitle: "coder@coder.com",
|
||||
}
|
||||
|
||||
export const WithHighlightTitle = Template.bind({})
|
||||
WithHighlightTitle.args = {
|
||||
export const WithImage = Template.bind({})
|
||||
WithImage.args = {
|
||||
title: "coder",
|
||||
subtitle: "coder@coder.com",
|
||||
highlightTitle: true,
|
||||
}
|
||||
|
||||
export const WithLink = Template.bind({})
|
||||
WithLink.args = {
|
||||
title: "coder",
|
||||
subtitle: "coder@coder.com",
|
||||
link: "/users/coder",
|
||||
src: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4",
|
||||
}
|
||||
|
||||
@@ -1,71 +1,50 @@
|
||||
import Avatar from "@material-ui/core/Avatar"
|
||||
import Link from "@material-ui/core/Link"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { Avatar } from "components/Avatar/Avatar"
|
||||
import { FC, PropsWithChildren } from "react"
|
||||
import { Link as RouterLink } from "react-router-dom"
|
||||
import { firstLetter } from "../../util/firstLetter"
|
||||
import {
|
||||
TableCellData,
|
||||
TableCellDataPrimary,
|
||||
TableCellDataSecondary,
|
||||
} from "../TableCellData/TableCellData"
|
||||
import { Stack } from "components/Stack/Stack"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
|
||||
export interface AvatarDataProps {
|
||||
title: string
|
||||
subtitle?: string
|
||||
highlightTitle?: boolean
|
||||
link?: string
|
||||
src?: string
|
||||
avatar?: React.ReactNode
|
||||
}
|
||||
|
||||
export const AvatarData: FC<PropsWithChildren<AvatarDataProps>> = ({
|
||||
title,
|
||||
subtitle,
|
||||
link,
|
||||
highlightTitle,
|
||||
src,
|
||||
avatar,
|
||||
}) => {
|
||||
const styles = useStyles()
|
||||
|
||||
if (!avatar) {
|
||||
avatar = <Avatar>{firstLetter(title)}</Avatar>
|
||||
avatar = <Avatar src={src}>{title}</Avatar>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<div className={styles.avatarWrapper}>{avatar}</div>
|
||||
<Stack spacing={1.5} direction="row" alignItems="center">
|
||||
{avatar}
|
||||
|
||||
{link ? (
|
||||
<Link to={link} underline="none" component={RouterLink}>
|
||||
<TableCellData>
|
||||
<TableCellDataPrimary highlight={highlightTitle}>
|
||||
{title}
|
||||
</TableCellDataPrimary>
|
||||
{subtitle && (
|
||||
<TableCellDataSecondary>{subtitle}</TableCellDataSecondary>
|
||||
)}
|
||||
</TableCellData>
|
||||
</Link>
|
||||
) : (
|
||||
<TableCellData>
|
||||
<TableCellDataPrimary highlight={highlightTitle}>
|
||||
{title}
|
||||
</TableCellDataPrimary>
|
||||
{subtitle && (
|
||||
<TableCellDataSecondary>{subtitle}</TableCellDataSecondary>
|
||||
)}
|
||||
</TableCellData>
|
||||
)}
|
||||
</div>
|
||||
<Stack spacing={0}>
|
||||
<span className={styles.title}>{title}</span>
|
||||
{subtitle && <span className={styles.subtitle}>{subtitle}</span>}
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
title: {
|
||||
color: theme.palette.text.primary,
|
||||
fontWeight: 600,
|
||||
},
|
||||
avatarWrapper: {
|
||||
marginRight: theme.spacing(1.5),
|
||||
|
||||
subtitle: {
|
||||
fontSize: 12,
|
||||
color: theme.palette.text.secondary,
|
||||
lineHeight: "140%",
|
||||
marginTop: 2,
|
||||
maxWidth: 540,
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import Avatar from "@material-ui/core/Avatar"
|
||||
import Badge from "@material-ui/core/Badge"
|
||||
import { Theme, useTheme, withStyles } from "@material-ui/core/styles"
|
||||
import { FC } from "react"
|
||||
@@ -8,6 +7,7 @@ import DeleteOutlined from "@material-ui/icons/DeleteOutlined"
|
||||
import { WorkspaceBuild, WorkspaceTransition } from "api/typesGenerated"
|
||||
import { getDisplayWorkspaceBuildStatus } from "util/workspace"
|
||||
import { PaletteIndex } from "theme/palettes"
|
||||
import { Avatar, AvatarProps } from "components/Avatar/Avatar"
|
||||
|
||||
interface StylesBadgeProps {
|
||||
type: PaletteIndex
|
||||
@@ -25,27 +25,9 @@ const StyledBadge = withStyles((theme) => ({
|
||||
},
|
||||
}))(Badge)
|
||||
|
||||
interface StyledAvatarProps {
|
||||
size?: number
|
||||
}
|
||||
|
||||
const StyledAvatar = withStyles((theme) => ({
|
||||
root: {
|
||||
background: theme.palette.divider,
|
||||
color: theme.palette.text.primary,
|
||||
border: `2px solid ${theme.palette.divider}`,
|
||||
width: ({ size }: StyledAvatarProps) => size,
|
||||
height: ({ size }: StyledAvatarProps) => size,
|
||||
|
||||
"& svg": {
|
||||
width: ({ size }: StyledAvatarProps) => (size ? size / 2 : 18),
|
||||
height: ({ size }: StyledAvatarProps) => (size ? size / 2 : 18),
|
||||
},
|
||||
},
|
||||
}))(Avatar)
|
||||
|
||||
export interface BuildAvatarProps extends StyledAvatarProps {
|
||||
export interface BuildAvatarProps {
|
||||
build: WorkspaceBuild
|
||||
size?: AvatarProps["size"]
|
||||
}
|
||||
|
||||
const iconByTransition: Record<WorkspaceTransition, JSX.Element> = {
|
||||
@@ -71,9 +53,9 @@ export const BuildAvatar: FC<BuildAvatarProps> = ({ build, size }) => {
|
||||
}}
|
||||
badgeContent={<div></div>}
|
||||
>
|
||||
<StyledAvatar size={size}>
|
||||
<Avatar size={size} colorScheme="darken">
|
||||
{iconByTransition[build.transition]}
|
||||
</StyledAvatar>
|
||||
</Avatar>
|
||||
</StyledBadge>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import Avatar from "@material-ui/core/Avatar"
|
||||
import { Avatar } from "components/Avatar/Avatar"
|
||||
import Badge from "@material-ui/core/Badge"
|
||||
import { withStyles } from "@material-ui/core/styles"
|
||||
import Group from "@material-ui/icons/Group"
|
||||
import { FC } from "react"
|
||||
import { firstLetter } from "util/firstLetter"
|
||||
|
||||
const StyledBadge = withStyles((theme) => ({
|
||||
badge: {
|
||||
@@ -38,7 +37,7 @@ export const GroupAvatar: FC<GroupAvatarProps> = ({ name, avatarURL }) => {
|
||||
}}
|
||||
badgeContent={<Group />}
|
||||
>
|
||||
<Avatar src={avatarURL}>{firstLetter(name)}</Avatar>
|
||||
<Avatar src={avatarURL}>{name}</Avatar>
|
||||
</StyledBadge>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import Avatar from "@material-ui/core/Avatar"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { Avatar, AvatarIcon } from "components/Avatar/Avatar"
|
||||
import { FC } from "react"
|
||||
import { WorkspaceResource } from "../../api/typesGenerated"
|
||||
|
||||
const FALLBACK_ICON = "/icon/widgets.svg"
|
||||
|
||||
// NOTE @jsjoeio, @BrunoQuaresma
|
||||
// These resources (i.e. docker_image, kubernetes_deployment) map to Terraform
|
||||
// resource types. These are the most used ones and are based on user usage.
|
||||
// We may want to update from time-to-time.
|
||||
@@ -37,18 +35,10 @@ export type ResourceAvatarProps = { resource: WorkspaceResource }
|
||||
export const ResourceAvatar: FC<ResourceAvatarProps> = ({ resource }) => {
|
||||
const hasIcon = resource.icon && resource.icon !== ""
|
||||
const avatarSrc = hasIcon ? resource.icon : getIconPathResource(resource.type)
|
||||
const styles = useStyles()
|
||||
|
||||
return <Avatar className={styles.resourceAvatar} src={avatarSrc} />
|
||||
return (
|
||||
<Avatar colorScheme="darken">
|
||||
<AvatarIcon src={avatarSrc} />
|
||||
</Avatar>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
resourceAvatar: {
|
||||
backgroundColor: theme.palette.divider,
|
||||
|
||||
"& img": {
|
||||
width: 18,
|
||||
height: 18,
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { ReactNode, FC, PropsWithChildren } from "react"
|
||||
import { Stack } from "../Stack/Stack"
|
||||
|
||||
interface StyleProps {
|
||||
highlight?: boolean
|
||||
}
|
||||
|
||||
export const TableCellData: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
return <Stack spacing={0}>{children}</Stack>
|
||||
}
|
||||
|
||||
export const TableCellDataPrimary: FC<
|
||||
PropsWithChildren<{ highlight?: boolean }>
|
||||
> = ({ children, highlight }) => {
|
||||
const styles = useStyles({ highlight })
|
||||
|
||||
return <span className={styles.primary}>{children}</span>
|
||||
}
|
||||
|
||||
export const TableCellDataSecondary: FC<PropsWithChildren<unknown>> = ({
|
||||
children,
|
||||
}) => {
|
||||
const styles = useStyles({})
|
||||
|
||||
return <span className={styles.secondary}>{children}</span>
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
primary: {
|
||||
color: ({ highlight }: StyleProps) =>
|
||||
highlight ? theme.palette.text.primary : theme.palette.text.secondary,
|
||||
fontWeight: ({ highlight }: StyleProps) => (highlight ? 600 : undefined),
|
||||
},
|
||||
|
||||
secondary: {
|
||||
fontSize: 12,
|
||||
color: theme.palette.text.secondary,
|
||||
lineHeight: "140%",
|
||||
marginTop: 2,
|
||||
maxWidth: 540,
|
||||
},
|
||||
}))
|
||||
@@ -1,4 +1,3 @@
|
||||
import Avatar from "@material-ui/core/Avatar"
|
||||
import Button from "@material-ui/core/Button"
|
||||
import Link from "@material-ui/core/Link"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
@@ -19,7 +18,6 @@ import {
|
||||
useParams,
|
||||
} from "react-router-dom"
|
||||
import { combineClasses } from "util/combineClasses"
|
||||
import { firstLetter } from "util/firstLetter"
|
||||
import {
|
||||
TemplateContext,
|
||||
templateMachine,
|
||||
@@ -29,6 +27,7 @@ import { Stack } from "components/Stack/Stack"
|
||||
import { Permissions } from "xServices/auth/authXService"
|
||||
import { Loader } from "components/Loader/Loader"
|
||||
import { usePermissions } from "hooks/usePermissions"
|
||||
import { Avatar } from "components/Avatar/Avatar"
|
||||
|
||||
const Language = {
|
||||
settingsButton: "Settings",
|
||||
@@ -139,17 +138,12 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
|
||||
}
|
||||
>
|
||||
<Stack direction="row" spacing={3} className={styles.pageTitle}>
|
||||
<div>
|
||||
{hasIcon ? (
|
||||
<div className={styles.iconWrapper}>
|
||||
<img src={template.icon} alt="" />
|
||||
</div>
|
||||
) : (
|
||||
<Avatar className={styles.avatar}>
|
||||
{firstLetter(template.name)}
|
||||
</Avatar>
|
||||
)}
|
||||
</div>
|
||||
{hasIcon ? (
|
||||
<Avatar size="xl" src={template.icon} variant="square" fitImage />
|
||||
) : (
|
||||
<Avatar size="xl">{template.name}</Avatar>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<PageHeaderTitle>
|
||||
{template.display_name.length > 0
|
||||
@@ -212,11 +206,6 @@ export const useStyles = makeStyles((theme) => {
|
||||
pageTitle: {
|
||||
alignItems: "center",
|
||||
},
|
||||
avatar: {
|
||||
width: theme.spacing(6),
|
||||
height: theme.spacing(6),
|
||||
fontSize: theme.spacing(3),
|
||||
},
|
||||
iconWrapper: {
|
||||
width: theme.spacing(6),
|
||||
height: theme.spacing(6),
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import Avatar from "@material-ui/core/Avatar"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { User } from "api/typesGenerated"
|
||||
import { FC } from "react"
|
||||
import { firstLetter } from "../../util/firstLetter"
|
||||
|
||||
export const AutocompleteAvatar: FC<{ user: User }> = ({ user }) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<div className={styles.avatarContainer}>
|
||||
{user.avatar_url ? (
|
||||
<img
|
||||
className={styles.avatar}
|
||||
alt={`${user.username}'s Avatar`}
|
||||
src={user.avatar_url}
|
||||
/>
|
||||
) : (
|
||||
<Avatar>{firstLetter(user.username)}</Avatar>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const useStyles = makeStyles((theme) => {
|
||||
return {
|
||||
avatarContainer: {
|
||||
margin: "0px 10px",
|
||||
},
|
||||
avatar: {
|
||||
width: theme.spacing(4.5),
|
||||
height: theme.spacing(4.5),
|
||||
borderRadius: "100%",
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -4,12 +4,12 @@ import TextField from "@material-ui/core/TextField"
|
||||
import Autocomplete from "@material-ui/lab/Autocomplete"
|
||||
import { useMachine } from "@xstate/react"
|
||||
import { User } from "api/typesGenerated"
|
||||
import { Avatar } from "components/Avatar/Avatar"
|
||||
import { AvatarData } from "components/AvatarData/AvatarData"
|
||||
import debounce from "just-debounce-it"
|
||||
import { ChangeEvent, FC, useEffect, useState } from "react"
|
||||
import { combineClasses } from "util/combineClasses"
|
||||
import { searchUserMachine } from "xServices/users/searchUserXService"
|
||||
import { AutocompleteAvatar } from "./AutocompleteAvatar"
|
||||
|
||||
export type UserAutocompleteProps = {
|
||||
value: User | null
|
||||
@@ -77,16 +77,7 @@ export const UserAutocomplete: FC<UserAutocompleteProps> = ({
|
||||
<AvatarData
|
||||
title={option.username}
|
||||
subtitle={option.email}
|
||||
highlightTitle
|
||||
avatar={
|
||||
option.avatar_url ? (
|
||||
<img
|
||||
className={styles.avatar}
|
||||
alt={`${option.username}'s Avatar`}
|
||||
src={option.avatar_url}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
src={option.avatar_url}
|
||||
/>
|
||||
)}
|
||||
options={searchResults}
|
||||
@@ -103,8 +94,8 @@ export const UserAutocomplete: FC<UserAutocompleteProps> = ({
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
onChange: handleFilterChange,
|
||||
startAdornment: (
|
||||
<>{showAvatar && value && <AutocompleteAvatar user={value} />}</>
|
||||
startAdornment: showAvatar && value && (
|
||||
<Avatar src={value.avatar_url}>{value.username}</Avatar>
|
||||
),
|
||||
endAdornment: (
|
||||
<>
|
||||
@@ -145,12 +136,6 @@ export const useStyles = makeStyles<Theme, styleProps>((theme) => {
|
||||
padding: `${theme.spacing(0, 0.5, 0, 0.5)} !important`,
|
||||
},
|
||||
}),
|
||||
|
||||
avatar: {
|
||||
width: theme.spacing(4.5),
|
||||
height: theme.spacing(4.5),
|
||||
borderRadius: "100%",
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
import Avatar from "@material-ui/core/Avatar"
|
||||
import { Avatar } from "components/Avatar/Avatar"
|
||||
import { FC } from "react"
|
||||
import { firstLetter } from "../../util/firstLetter"
|
||||
|
||||
export interface UserAvatarProps {
|
||||
username: string
|
||||
className?: string
|
||||
avatarURL?: string
|
||||
// It is needed to work with the AvatarGroup so it can pass the
|
||||
// MuiAvatarGroup-avatar className
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const UserAvatar: FC<UserAvatarProps> = ({
|
||||
username,
|
||||
className,
|
||||
avatarURL,
|
||||
className,
|
||||
}) => {
|
||||
return (
|
||||
<Avatar className={className} title={username}>
|
||||
{avatarURL ? (
|
||||
<img alt={`${username}'s Avatar`} src={avatarURL} width="100%" />
|
||||
) : (
|
||||
firstLetter(username)
|
||||
)}
|
||||
<Avatar title={username} src={avatarURL} className={className}>
|
||||
{username}
|
||||
</Avatar>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -77,16 +77,7 @@ export const UserOrGroupAutocomplete: React.FC<
|
||||
<AvatarData
|
||||
title={isOptionGroup ? option.name : option.username}
|
||||
subtitle={isOptionGroup ? getGroupSubtitle(option) : option.email}
|
||||
highlightTitle
|
||||
avatar={
|
||||
!isOptionGroup && option.avatar_url ? (
|
||||
<img
|
||||
className={styles.avatar}
|
||||
alt={`${option.username}'s Avatar`}
|
||||
src={option.avatar_url}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
src={option.avatar_url}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
@@ -137,11 +128,5 @@ export const useStyles = makeStyles((theme) => {
|
||||
padding: `${theme.spacing(0, 0.5, 0, 0.5)} !important`,
|
||||
},
|
||||
},
|
||||
|
||||
avatar: {
|
||||
width: theme.spacing(4.5),
|
||||
height: theme.spacing(4.5),
|
||||
borderRadius: "100%",
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -110,16 +110,7 @@ export const UsersTableBody: FC<
|
||||
<AvatarData
|
||||
title={user.username}
|
||||
subtitle={user.email}
|
||||
highlightTitle
|
||||
avatar={
|
||||
user.avatar_url ? (
|
||||
<img
|
||||
className={styles.avatar}
|
||||
alt={`${user.username}'s Avatar`}
|
||||
src={user.avatar_url}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
src={user.avatar_url}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -216,11 +207,6 @@ const useStyles = makeStyles((theme) => ({
|
||||
suspended: {
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
avatar: {
|
||||
width: theme.spacing(4.5),
|
||||
height: theme.spacing(4.5),
|
||||
borderRadius: "100%",
|
||||
},
|
||||
rolePill: {
|
||||
backgroundColor: theme.palette.background.paperLight,
|
||||
borderColor: theme.palette.divider,
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
WorkspaceBuildProgress,
|
||||
} from "components/WorkspaceBuildProgress/WorkspaceBuildProgress"
|
||||
import { AgentRow } from "components/Resources/AgentRow"
|
||||
import { Avatar } from "components/Avatar/Avatar"
|
||||
|
||||
export enum WorkspaceErrors {
|
||||
GET_RESOURCES_ERROR = "getResourcesError",
|
||||
@@ -151,10 +152,11 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
|
||||
>
|
||||
<Stack direction="row" spacing={3} alignItems="center">
|
||||
{hasTemplateIcon && (
|
||||
<img
|
||||
alt=""
|
||||
<Avatar
|
||||
size="xl"
|
||||
src={workspace.template_icon}
|
||||
className={styles.templateIcon}
|
||||
variant="square"
|
||||
fitImage
|
||||
/>
|
||||
)}
|
||||
<div>
|
||||
@@ -267,11 +269,6 @@ export const useStyles = makeStyles((theme) => {
|
||||
width: "100%",
|
||||
},
|
||||
|
||||
templateIcon: {
|
||||
width: theme.spacing(6),
|
||||
height: theme.spacing(6),
|
||||
},
|
||||
|
||||
timelineContents: {
|
||||
margin: 0,
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ import { getDisplayWorkspaceTemplateName } from "util/workspace"
|
||||
import { LastUsed } from "../LastUsed/LastUsed"
|
||||
import { Workspace } from "api/typesGenerated"
|
||||
import { OutdatedHelpTooltip } from "components/Tooltips/OutdatedHelpTooltip"
|
||||
import { Avatar } from "components/Avatar/Avatar"
|
||||
|
||||
export const WorkspacesRow: FC<{
|
||||
workspace: Workspace
|
||||
@@ -35,15 +36,12 @@ export const WorkspacesRow: FC<{
|
||||
>
|
||||
<TableCell>
|
||||
<AvatarData
|
||||
highlightTitle
|
||||
title={workspace.name}
|
||||
subtitle={workspace.owner_name}
|
||||
avatar={
|
||||
hasTemplateIcon ? (
|
||||
<div className={styles.templateIconWrapper}>
|
||||
<img alt="" src={workspace.template_icon} />
|
||||
</div>
|
||||
) : undefined
|
||||
hasTemplateIcon && (
|
||||
<Avatar src={workspace.template_icon} variant="square" fitImage />
|
||||
)
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import Avatar from "@material-ui/core/Avatar"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { Template, TemplateExample } from "api/typesGenerated"
|
||||
import { Avatar } from "components/Avatar/Avatar"
|
||||
import { Stack } from "components/Stack/Stack"
|
||||
import { FC } from "react"
|
||||
import { firstLetter } from "util/firstLetter"
|
||||
|
||||
export interface SelectedTemplateProps {
|
||||
template: Template | TemplateExample
|
||||
@@ -19,13 +18,8 @@ export const SelectedTemplate: FC<SelectedTemplateProps> = ({ template }) => {
|
||||
className={styles.template}
|
||||
alignItems="center"
|
||||
>
|
||||
<div className={styles.templateIcon}>
|
||||
{template.icon === "" ? (
|
||||
<Avatar>{firstLetter(template.name)}</Avatar>
|
||||
) : (
|
||||
<img src={template.icon} alt="" />
|
||||
)}
|
||||
</div>
|
||||
<Avatar src={template.icon}>{template.name}</Avatar>
|
||||
|
||||
<Stack direction="column" spacing={0.5}>
|
||||
<span className={styles.templateName}>
|
||||
{"display_name" in template && template.display_name.length > 0
|
||||
@@ -58,13 +52,4 @@ const useStyles = makeStyles((theme) => ({
|
||||
fontSize: 14,
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
|
||||
templateIcon: {
|
||||
width: theme.spacing(4),
|
||||
lineHeight: 1,
|
||||
|
||||
"& img": {
|
||||
width: "100%",
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -173,7 +173,6 @@ export const GroupPage: React.FC = () => {
|
||||
<AvatarData
|
||||
title={member.username}
|
||||
subtitle={member.email}
|
||||
highlightTitle
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell width="1%">
|
||||
|
||||
@@ -144,7 +144,6 @@ export const GroupsPageView: FC<GroupsPageViewProps> = ({
|
||||
}
|
||||
title={group.name}
|
||||
subtitle={`${group.members.length} members`}
|
||||
highlightTitle
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
|
||||
+1
-17
@@ -249,7 +249,6 @@ export const TemplatePermissionsPageView: FC<
|
||||
}
|
||||
title={group.name}
|
||||
subtitle={getGroupSubtitle(group)}
|
||||
highlightTitle
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -296,16 +295,7 @@ export const TemplatePermissionsPageView: FC<
|
||||
<AvatarData
|
||||
title={user.username}
|
||||
subtitle={user.email}
|
||||
highlightTitle
|
||||
avatar={
|
||||
user.avatar_url ? (
|
||||
<img
|
||||
className={styles.avatar}
|
||||
alt={`${user.username}'s Avatar`}
|
||||
src={user.avatar_url}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
src={user.avatar_url}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -363,12 +353,6 @@ export const useStyles = makeStyles((theme) => {
|
||||
width: 100,
|
||||
},
|
||||
|
||||
avatar: {
|
||||
width: theme.spacing(4.5),
|
||||
height: theme.spacing(4.5),
|
||||
borderRadius: "100%",
|
||||
},
|
||||
|
||||
updateSelect: {
|
||||
margin: 0,
|
||||
// Set a fixed width for the select. It avoids selects having different sizes
|
||||
|
||||
@@ -41,6 +41,7 @@ import { Template } from "api/typesGenerated"
|
||||
import { combineClasses } from "util/combineClasses"
|
||||
import { colors } from "theme/colors"
|
||||
import ArrowForwardOutlined from "@material-ui/icons/ArrowForwardOutlined"
|
||||
import { Avatar } from "components/Avatar/Avatar"
|
||||
|
||||
export const Language = {
|
||||
developerCount: (activeCount: number): string => {
|
||||
@@ -97,13 +98,8 @@ const TemplateRow: FC<{ template: Template }> = ({ template }) => {
|
||||
: template.name
|
||||
}
|
||||
subtitle={template.description}
|
||||
highlightTitle
|
||||
avatar={
|
||||
hasIcon && (
|
||||
<div className={styles.templateIconWrapper}>
|
||||
<img alt="" src={template.icon} />
|
||||
</div>
|
||||
)
|
||||
hasIcon && <Avatar src={template.icon} variant="square" fitImage />
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
@@ -34,7 +34,7 @@ export const WorkspaceBuildPageView: FC<WorkspaceBuildPageViewProps> = ({
|
||||
{build && (
|
||||
<PageHeader>
|
||||
<Stack direction="row" alignItems="center" spacing={3}>
|
||||
<BuildAvatar build={build} size={48} />
|
||||
<BuildAvatar build={build} size="xl" />
|
||||
<div>
|
||||
<PageHeaderTitle>Build #{build.build_number}</PageHeaderTitle>
|
||||
<PageHeaderSubtitle condensed>
|
||||
|
||||
@@ -30,6 +30,10 @@ export const getOverrides = ({
|
||||
width: 36,
|
||||
height: 36,
|
||||
fontSize: 18,
|
||||
|
||||
"& .MuiSvgIcon-root": {
|
||||
width: "50%",
|
||||
},
|
||||
},
|
||||
colorDefault: {
|
||||
backgroundColor: colors.gray[6],
|
||||
|
||||
Reference in New Issue
Block a user