-
Notifications
You must be signed in to change notification settings - Fork 888
refactor(site): Normalize avatar components #5860
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
27ca826
72bfda0
1cb58da
900622e
454a1f5
61b5f95
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// 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 { cloneElement, FC } from "react" | ||
import { combineClasses } from "util/combineClasses" | ||
import { firstLetter } from "./firstLetter" | ||
|
||
export type AvatarProps = MuiAvatarProps & { | ||
size?: "sm" | "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> | ||
) | ||
} | ||
|
||
export const AvatarIcon: FC<{ children: JSX.Element }> = ({ children }) => { | ||
const styles = useStyles() | ||
return cloneElement(children, { className: styles.avatarIcon }) | ||
} | ||
|
||
const useStyles = makeStyles((theme) => ({ | ||
// Size styles | ||
sm: { | ||
width: theme.spacing(4), | ||
height: theme.spacing(4), | ||
fontSize: theme.spacing(2), | ||
}, | ||
// 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", | ||
}, | ||
}, | ||
})) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
}, | ||
})) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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,12 @@ 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> | ||
<img src={avatarSrc} alt="" /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to check I follow: the difference between There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The difference is: when you pass the image as a child, it does not add the Thinking better, I have no idea why I decided to use a child instead of using the // Instead of
<AvatarIcon>
<img src={avatarSrc} alt="" />
</AvatarIcon>
// it should be
<AvatarIcon src={avatarSrc} /> There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahhh ok yeah that makes sense! |
||
</AvatarIcon> | ||
</Avatar> | ||
) | ||
} | ||
|
||
const useStyles = makeStyles((theme) => ({ | ||
resourceAvatar: { | ||
backgroundColor: theme.palette.divider, | ||
|
||
"& img": { | ||
width: 18, | ||
height: 18, | ||
}, | ||
}, | ||
})) |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not really important, just a thought but would it make sense to make
AvatarData
takeAvatar
props and pass them along rather than acceptavatar
? My reasoning is that it seems unfortunate that there are two ways of doing things like:and
Or alternatively we could always make it so we always have to pass an
avatar
, either way works so there is one way of doing things.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AvatarData
is a bad name for what it does and not a good abstraction. Wondering ifHeading
orDataHeading
would be better with the following interface:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely agree!