Skip to content

Commit 27ca826

Browse files
committed
refactor(site): Normalize avatar components
1 parent 5c5ddc6 commit 27ca826

File tree

24 files changed

+157
-305
lines changed

24 files changed

+157
-305
lines changed

site/.eslintrc.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ rules:
9696
message:
9797
"Use path imports to avoid pulling in unused modules. See:
9898
https://material-ui.com/guides/minimizing-bundle-size/"
99+
- name: "@material-ui/core/Avatar"
100+
message:
101+
"You should use the Avatar component provided on components/Avatar/Avatar"
99102
no-unused-vars: "off"
100103
"object-curly-spacing": "off"
101104
react-hooks/exhaustive-deps: warn

site/src/components/Avatar/Avatar.tsx

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// This is the only place MuiAvatar can be used
2+
// eslint-disable-next-line no-restricted-imports -- Read above
3+
import MuiAvatar, {
4+
AvatarProps as MuiAvatarProps,
5+
} from "@material-ui/core/Avatar"
6+
import { makeStyles } from "@material-ui/core/styles"
7+
import { cloneElement, FC } from "react"
8+
import { combineClasses } from "util/combineClasses"
9+
import { firstLetter } from "./firstLetter"
10+
11+
export type AvatarProps = MuiAvatarProps & {
12+
size?: "sm" | "md" | "xl"
13+
colorScheme?: "light" | "darken"
14+
fitImage?: boolean
15+
}
16+
17+
export const Avatar: FC<AvatarProps> = ({
18+
size = "md",
19+
colorScheme = "light",
20+
fitImage,
21+
className,
22+
children,
23+
...muiProps
24+
}) => {
25+
const styles = useStyles()
26+
27+
return (
28+
<MuiAvatar
29+
{...muiProps}
30+
className={combineClasses([
31+
className,
32+
styles[size],
33+
styles[colorScheme],
34+
fitImage && styles.fitImage,
35+
])}
36+
>
37+
{/* If the children is a string, we always want to render the first letter */}
38+
{typeof children === "string" ? firstLetter(children) : children}
39+
</MuiAvatar>
40+
)
41+
}
42+
43+
export const AvatarIcon: FC<{ children: JSX.Element }> = ({ children }) => {
44+
const styles = useStyles()
45+
return cloneElement(children, { className: styles.avatarIcon })
46+
}
47+
48+
const useStyles = makeStyles((theme) => ({
49+
// Size styles
50+
sm: {
51+
width: theme.spacing(4),
52+
height: theme.spacing(4),
53+
fontSize: theme.spacing(2),
54+
},
55+
// Just use the default value from theme
56+
md: {},
57+
xl: {
58+
width: theme.spacing(6),
59+
height: theme.spacing(6),
60+
fontSize: theme.spacing(3),
61+
},
62+
// Colors
63+
// Just use the default value from theme
64+
light: {},
65+
darken: {
66+
background: theme.palette.divider,
67+
color: theme.palette.text.primary,
68+
},
69+
// Avatar icon
70+
avatarIcon: {
71+
maxWidth: "50%",
72+
},
73+
// Fit image
74+
fitImage: {
75+
"& .MuiAvatar-img": {
76+
objectFit: "contain",
77+
},
78+
},
79+
}))
Lines changed: 23 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,50 @@
1-
import Avatar from "@material-ui/core/Avatar"
2-
import Link from "@material-ui/core/Link"
3-
import { makeStyles } from "@material-ui/core/styles"
1+
import { Avatar } from "components/Avatar/Avatar"
42
import { FC, PropsWithChildren } from "react"
5-
import { Link as RouterLink } from "react-router-dom"
6-
import { firstLetter } from "../../util/firstLetter"
7-
import {
8-
TableCellData,
9-
TableCellDataPrimary,
10-
TableCellDataSecondary,
11-
} from "../TableCellData/TableCellData"
3+
import { Stack } from "components/Stack/Stack"
4+
import { makeStyles } from "@material-ui/core/styles"
125

136
export interface AvatarDataProps {
147
title: string
158
subtitle?: string
16-
highlightTitle?: boolean
17-
link?: string
9+
src?: string
1810
avatar?: React.ReactNode
1911
}
2012

2113
export const AvatarData: FC<PropsWithChildren<AvatarDataProps>> = ({
2214
title,
2315
subtitle,
24-
link,
25-
highlightTitle,
16+
src,
2617
avatar,
2718
}) => {
2819
const styles = useStyles()
2920

3021
if (!avatar) {
31-
avatar = <Avatar>{firstLetter(title)}</Avatar>
22+
avatar = <Avatar src={src}>{title}</Avatar>
3223
}
3324

3425
return (
35-
<div className={styles.root}>
36-
<div className={styles.avatarWrapper}>{avatar}</div>
26+
<Stack spacing={1.5} direction="row" alignItems="center">
27+
{avatar}
3728

38-
{link ? (
39-
<Link to={link} underline="none" component={RouterLink}>
40-
<TableCellData>
41-
<TableCellDataPrimary highlight={highlightTitle}>
42-
{title}
43-
</TableCellDataPrimary>
44-
{subtitle && (
45-
<TableCellDataSecondary>{subtitle}</TableCellDataSecondary>
46-
)}
47-
</TableCellData>
48-
</Link>
49-
) : (
50-
<TableCellData>
51-
<TableCellDataPrimary highlight={highlightTitle}>
52-
{title}
53-
</TableCellDataPrimary>
54-
{subtitle && (
55-
<TableCellDataSecondary>{subtitle}</TableCellDataSecondary>
56-
)}
57-
</TableCellData>
58-
)}
59-
</div>
29+
<Stack spacing={0}>
30+
<span className={styles.title}>{title}</span>
31+
{subtitle && <span className={styles.subtitle}>{subtitle}</span>}
32+
</Stack>
33+
</Stack>
6034
)
6135
}
6236

6337
const useStyles = makeStyles((theme) => ({
64-
root: {
65-
display: "flex",
66-
alignItems: "center",
38+
title: {
39+
color: theme.palette.text.primary,
40+
fontWeight: 600,
6741
},
68-
avatarWrapper: {
69-
marginRight: theme.spacing(1.5),
42+
43+
subtitle: {
44+
fontSize: 12,
45+
color: theme.palette.text.secondary,
46+
lineHeight: "140%",
47+
marginTop: 2,
48+
maxWidth: 540,
7049
},
7150
}))

site/src/components/BuildsTable/BuildAvatar.tsx

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import Avatar from "@material-ui/core/Avatar"
21
import Badge from "@material-ui/core/Badge"
32
import { Theme, useTheme, withStyles } from "@material-ui/core/styles"
43
import { FC } from "react"
@@ -8,6 +7,7 @@ import DeleteOutlined from "@material-ui/icons/DeleteOutlined"
87
import { WorkspaceBuild, WorkspaceTransition } from "api/typesGenerated"
98
import { getDisplayWorkspaceBuildStatus } from "util/workspace"
109
import { PaletteIndex } from "theme/palettes"
10+
import { Avatar, AvatarProps } from "components/Avatar/Avatar"
1111

1212
interface StylesBadgeProps {
1313
type: PaletteIndex
@@ -25,27 +25,9 @@ const StyledBadge = withStyles((theme) => ({
2525
},
2626
}))(Badge)
2727

28-
interface StyledAvatarProps {
29-
size?: number
30-
}
31-
32-
const StyledAvatar = withStyles((theme) => ({
33-
root: {
34-
background: theme.palette.divider,
35-
color: theme.palette.text.primary,
36-
border: `2px solid ${theme.palette.divider}`,
37-
width: ({ size }: StyledAvatarProps) => size,
38-
height: ({ size }: StyledAvatarProps) => size,
39-
40-
"& svg": {
41-
width: ({ size }: StyledAvatarProps) => (size ? size / 2 : 18),
42-
height: ({ size }: StyledAvatarProps) => (size ? size / 2 : 18),
43-
},
44-
},
45-
}))(Avatar)
46-
47-
export interface BuildAvatarProps extends StyledAvatarProps {
28+
export interface BuildAvatarProps {
4829
build: WorkspaceBuild
30+
size?: AvatarProps["size"]
4931
}
5032

5133
const iconByTransition: Record<WorkspaceTransition, JSX.Element> = {
@@ -71,9 +53,9 @@ export const BuildAvatar: FC<BuildAvatarProps> = ({ build, size }) => {
7153
}}
7254
badgeContent={<div></div>}
7355
>
74-
<StyledAvatar size={size}>
56+
<Avatar size={size} colorScheme="darken">
7557
{iconByTransition[build.transition]}
76-
</StyledAvatar>
58+
</Avatar>
7759
</StyledBadge>
7860
)
7961
}

site/src/components/GroupAvatar/GroupAvatar.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import Avatar from "@material-ui/core/Avatar"
1+
import { Avatar } from "components/Avatar/Avatar"
22
import Badge from "@material-ui/core/Badge"
33
import { withStyles } from "@material-ui/core/styles"
44
import Group from "@material-ui/icons/Group"
55
import { FC } from "react"
6-
import { firstLetter } from "util/firstLetter"
76

87
const StyledBadge = withStyles((theme) => ({
98
badge: {
@@ -38,7 +37,7 @@ export const GroupAvatar: FC<GroupAvatarProps> = ({ name, avatarURL }) => {
3837
}}
3938
badgeContent={<Group />}
4039
>
41-
<Avatar src={avatarURL}>{firstLetter(name)}</Avatar>
40+
<Avatar src={avatarURL}>{name}</Avatar>
4241
</StyledBadge>
4342
)
4443
}

site/src/components/Resources/ResourceAvatar.tsx

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import Avatar from "@material-ui/core/Avatar"
2-
import { makeStyles } from "@material-ui/core/styles"
1+
import { Avatar, AvatarIcon } from "components/Avatar/Avatar"
32
import { FC } from "react"
43
import { WorkspaceResource } from "../../api/typesGenerated"
54

65
const FALLBACK_ICON = "/icon/widgets.svg"
76

8-
// NOTE @jsjoeio, @BrunoQuaresma
97
// These resources (i.e. docker_image, kubernetes_deployment) map to Terraform
108
// resource types. These are the most used ones and are based on user usage.
119
// We may want to update from time-to-time.
@@ -37,18 +35,12 @@ export type ResourceAvatarProps = { resource: WorkspaceResource }
3735
export const ResourceAvatar: FC<ResourceAvatarProps> = ({ resource }) => {
3836
const hasIcon = resource.icon && resource.icon !== ""
3937
const avatarSrc = hasIcon ? resource.icon : getIconPathResource(resource.type)
40-
const styles = useStyles()
4138

42-
return <Avatar className={styles.resourceAvatar} src={avatarSrc} />
39+
return (
40+
<Avatar colorScheme="darken">
41+
<AvatarIcon>
42+
<img src={avatarSrc} alt="" />
43+
</AvatarIcon>
44+
</Avatar>
45+
)
4346
}
44-
45-
const useStyles = makeStyles((theme) => ({
46-
resourceAvatar: {
47-
backgroundColor: theme.palette.divider,
48-
49-
"& img": {
50-
width: 18,
51-
height: 18,
52-
},
53-
},
54-
}))

site/src/components/TableCellData/TableCellData.tsx

Lines changed: 0 additions & 43 deletions
This file was deleted.

site/src/components/TemplateLayout/TemplateLayout.tsx

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import Avatar from "@material-ui/core/Avatar"
21
import Button from "@material-ui/core/Button"
32
import Link from "@material-ui/core/Link"
43
import { makeStyles } from "@material-ui/core/styles"
@@ -19,7 +18,6 @@ import {
1918
useParams,
2019
} from "react-router-dom"
2120
import { combineClasses } from "util/combineClasses"
22-
import { firstLetter } from "util/firstLetter"
2321
import {
2422
TemplateContext,
2523
templateMachine,
@@ -29,6 +27,7 @@ import { Stack } from "components/Stack/Stack"
2927
import { Permissions } from "xServices/auth/authXService"
3028
import { Loader } from "components/Loader/Loader"
3129
import { usePermissions } from "hooks/usePermissions"
30+
import { Avatar } from "components/Avatar/Avatar"
3231

3332
const Language = {
3433
settingsButton: "Settings",
@@ -139,17 +138,12 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
139138
}
140139
>
141140
<Stack direction="row" spacing={3} className={styles.pageTitle}>
142-
<div>
143-
{hasIcon ? (
144-
<div className={styles.iconWrapper}>
145-
<img src={template.icon} alt="" />
146-
</div>
147-
) : (
148-
<Avatar className={styles.avatar}>
149-
{firstLetter(template.name)}
150-
</Avatar>
151-
)}
152-
</div>
141+
{hasIcon ? (
142+
<Avatar src={template.icon} variant="square" fitImage />
143+
) : (
144+
<Avatar size="xl">{template.name}</Avatar>
145+
)}
146+
153147
<div>
154148
<PageHeaderTitle>
155149
{template.display_name.length > 0
@@ -212,11 +206,6 @@ export const useStyles = makeStyles((theme) => {
212206
pageTitle: {
213207
alignItems: "center",
214208
},
215-
avatar: {
216-
width: theme.spacing(6),
217-
height: theme.spacing(6),
218-
fontSize: theme.spacing(3),
219-
},
220209
iconWrapper: {
221210
width: theme.spacing(6),
222211
height: theme.spacing(6),

0 commit comments

Comments
 (0)