Introduction
Let us continue building our chakra components using styled-components
& styled-system
. In this tutorial we will be cloning the Chakra UI Avatar
component.
- I would like you to first check the chakra docs for avatar.
- All the code for this tutorial can be found under the atom-avatar branch here.
Prerequisite
Please check the previous post where we have completed the Image
component and useImage
hook. Also please check the Chakra Avatar Component code here. The theme / styles are here In this tutorial we will -
- Create a Avatar component.
- Create story for the Avatar component.
Setup
- First let us create a branch, from the main branch run -
git checkout -b atom-avatar
Under the
components/atoms
folder create a new folder calledavatar
. Underavatar
folder create 3 filesavatar.tsx
,avatar.stories.tsx
andindex.ts
.So our folder structure stands like - src/components/atoms/avatar.
AvatarBadge Component
- First we will need to install the following package -
npm install tinycolor2
npm install --save-dev @types/tinycolor2
- Under the
utils
folder create a new filetheme.ts
and paste the following code -
import Color from "tinycolor2";
export function randomColor() {
return Color.random().toHexString();
}
export function isDark(colorHex: string) {
return Color(colorHex).isDark();
}
export function transparentize(color: string, opacity: number) {
return Color(color).setAlpha(opacity).toRgbString();
}
If you played with the chakra Avatar component on the docs site. You might have noticed that there are 3 components, we need to create
Avatar
,AvatarBadge
andAvatarGroup
. We will createAvatarGroup
in the next tutorial. Internally we have another component calledAvatarName
&AvatarImage
.For the
AvatarBadge
paste the following -
export interface AvatarBadgeProps extends BoxProps {}
const BaseAvatarBadge = styled(Box)<AvatarBadgeProps>`
position: absolute;
display: flex;
align-items: center;
justify-content: center;
bottom: 0;
transform: translate(55%, 35%);
border-radius: 9999px;
`;
export const AvatarBadge = React.forwardRef<HTMLDivElement, AvatarBadgeProps>(
(props, ref) => {
const {
borderWidth = "0.2em",
borderStyle = "solid",
borderColor = "white",
...delegated
} = props;
return (
<BaseAvatarBadge
ref={ref}
borderWidth={borderWidth}
borderStyle={borderStyle}
borderColor={borderColor}
{...delegated}
/>
);
}
);
- We are extending
Box
with some default styles and passing some styles as props with default values.
AvatarName Component
- Paste the following code -
function initials(name: string) {
const [firstName, lastName] = name.split(" ");
return firstName && lastName
? `${firstName.charAt(0)}${lastName.charAt(0)}`
: firstName.charAt(0);
}
interface AvatarNameProps
extends BoxProps,
Pick<AvatarOptions, "name" | "getInitials"> {}
const AvatarName: React.FC<AvatarNameProps> = (props) => {
const { name, getInitials, ...delegated } = props;
return <Box {...delegated}>{name ? getInitials?.(name) : null}</Box>;
};
AvatarOptions Type
- Paste the following code before the
AvatarBadge
component -
interface AvatarOptions {
name?: string;
showBorder?: boolean;
src?: string;
srcSet?: string;
loading?: "eager" | "lazy";
onError?: () => void;
icon?: React.ReactElement;
getInitials?: (name: string) => string;
}
-
Inline with the chakra Avatar component we have the above Props that we will pass to Avatar : -
- name - The name of the person in the avatar. If
src
has loaded, the name will be used as thealt
attribute of theimg
. Ifsrc
is not loaded, the name will be used to create the initials. - showBorder - If
true
, theAvatar
will show a border around it. Best for a group of avatars. - src - The image url of the
Avatar
. - srcSet - List of sources to use for different screen resolutions.
- loading - Defines loading strategy for image either "loading" | "eager".
- onError() - Function called when image failed to load.
- icon - The default avatar used as fallback when
name
, andsrc
is not specified. - getInitials(name: string) - Function to get the initials to display
- name - The name of the person in the avatar. If
Did you find some image related props similar to the
Image
component /useImage
hook params we completed last tutorial, we will be using theuseImage
hook forAvatarImage
.
AvatarImage Component
- Paste the following code below
AvatarName
component -
export type AvatarSizes =
| "2xs"
| "xs"
| "sm"
| "md"
| "lg"
| "xl"
| "2xl"
| "full";
export interface AvatarProps
extends Omit<BoxProps, "onError" | "size">,
AvatarOptions {
iconLabel?: string;
s?: ResponsiveValue<AvatarSizes>;
}
interface AvatarImageProps
extends ImageProps,
Pick<AvatarProps, "getInitials" | "borderRadius" | "icon" | "name"> {
iconLabel?: string;
}
const AvatarImage: React.FC<AvatarImageProps> = (props) => {
const {
src,
onError,
getInitials,
name,
borderRadius,
loading,
iconLabel,
icon = <AvatarFallback />,
} = props;
const status = useImage({ src, onError });
const hasLoaded = status === "loaded";
const showFallback = !src || !hasLoaded;
if (showFallback) {
return name ? (
<AvatarName getInitials={getInitials} name={name} />
) : (
React.cloneElement(icon, {
role: "img",
"aria-label": iconLabel,
})
);
}
return (
<Image
src={src}
alt={name}
loading={loading}
boxSize="100%"
fit="cover"
borderRadius={borderRadius ?? "2px"}
/>
);
};
-
We will apply the fallback Icon (showFallback) under 2 conditions -
- If
src
was passed and the image has not loaded or failed to load. - If
src
wasn't passed.
- If
If
src
was passed and the image has loaded, we'll return the Component.
Avatar Component
- Paste the Following code below the
AvatarImage
component -
const BaseAvatar = styled(Box)<AvatarProps>`
${({ name, bg, backgroundColor, theme: { colors } }) => {
const avatarBg = name ? randomColor() : colors.gray400;
const color = isDark(avatarBg) ? "white" : colors.gray800;
return {
backgroundColor: backgroundColor ?? bg ?? avatarBg,
color,
borderColor: "white",
verticalAlign: "top",
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
textAlign: "center",
textTransform: "uppercase",
fontWeight: 500,
position: "relative",
flexShrink: 0,
};
}}
${variant({
prop: "s",
variants: {
"2xs": {
size: "1rem",
fontSize: "calc(1rem / 2.5)",
},
xs: {
size: "1.5rem",
fontSize: "calc(1.5rem / 2.5)",
},
sm: {
size: "2rem",
fontSize: "calc(2rem / 2.5)",
},
md: {
size: "3rem",
fontSize: "calc(3rem / 2.5)",
},
lg: {
size: "4rem",
fontSize: "calc(4rem / 2.5)",
},
xl: {
size: "6rem",
fontSize: "calc(6rem / 2.5)",
},
"2xl": {
size: "8rem",
fontSize: "calc(8rem / 2.5)",
},
full: {
size: "100%",
fontSize: "calc(100% / 2.5)",
},
},
})}
`;
export const Avatar = React.forwardRef<HTMLDivElement, AvatarProps>(
(props, ref) => {
const {
src,
name,
s = "md",
borderRadius = "9999px",
showBorder,
borderWidth,
onError,
getInitials = initials,
icon = <AvatarFallback />,
iconLabel = " avatar",
loading,
children,
...delegated
} = props;
return (
<BaseAvatar
ref={ref}
s={s}
name={name}
borderWidth={showBorder ? "2px" : borderWidth}
borderRadius={borderRadius}
{...delegated}
>
<AvatarImage
src={src}
loading={loading}
onError={onError}
getInitials={getInitials}
name={name}
borderRadius={borderRadius}
icon={icon}
iconLabel={iconLabel}
/>
{children}
</BaseAvatar>
);
}
);
- The above code is self-explanatory if you are following this series. We created a
s
(size) variant. Handled the light and dark bg colors, we are creating a random color for each avatar.
Story
- With the above our
Avatar
component is completed, let us create a story. - Under the
src/components/atoms/avatar/avatar.stories.tsx
file we add the below story code.
import * as React from "react";
import { Stack } from "../layout";
import { AiOutlineUser } from "../icons";
import { Avatar, AvatarBadge, AvatarProps } from "./avatar";
export default {
title: "Atoms/Avatar",
};
export const Playground = {
argTypes: {
s: {
name: "s",
type: { name: "string", required: false },
defaultValue: "md",
description: "Size for the Avatar",
table: {
type: { summary: "string" },
defaultValue: { summary: "md" },
},
control: {
type: "select",
options: ["xs", "2xs", "sm", "md", "lg", "xl", "2xl", "full"],
},
},
name: {
name: "size",
type: { name: "string", required: false },
defaultValue: "Segun Adebayo",
description: `The name of the person in the avatar.
-If src has loaded, the name will be used as the alt attribute of the img
-If src is not loaded, the name will be used to create the initials`,
table: {
type: { summary: "string" },
defaultValue: { summary: "-" },
},
},
src: {
name: "src",
type: { name: "string", required: false },
defaultValue: "https://bit.ly/sage-adebayo",
description: "The image url of the Avatar",
table: {
type: { summary: "string" },
defaultValue: { summary: "-" },
},
},
},
render: (args: AvatarProps) => <Avatar {...args} />,
};
export const Default = {
render: () => (
<Stack direction="column" spacing="xl">
<Stack>
<Avatar src="https://bit.ly/broken-link" />
<Avatar name="Ryan Florence" src="https://bit.ly/ryan-florence" />
<Avatar name="Segun Adebayo" />
<Avatar name="Kent Dodds" src="https://bit.ly/kent-c-dodds" />
<Avatar name="Prosper Otemuyiwa" src="https://bit.ly/prosper-baba" />
<Avatar name="Christian Nwamba" src="https://bit.ly/code-beast" />
</Stack>
<Stack>
<Avatar>
<AvatarBadge size="1.25em" bg="green500" />
</Avatar>
<Avatar>
<AvatarBadge borderColor="papayawhip" bg="tomato" size="1.25em" />
</Avatar>
</Stack>
<Stack>
<Avatar bg="red500" icon={<AiOutlineUser fontSize="1.5rem" />} />
<Avatar bg="teal500" />
</Stack>
</Stack>
),
};
Build the Library
- Under the
avatar/index.ts
file paste the following -
export * from "./avatar";
- Under the
/atom/index.ts
file paste the following -
export * from "./layout";
export * from "./typography";
export * from "./feedback";
export * from "./icon";
export * from "./icons";
export * from "./form";
export * from "./image";
export * from "./tag";
export * from "./badge";
export * from "./avatar";
Now
npm run build
.Under the folder
example/src/App.tsx
we can test ourAvatar
component. Copy paste the default story code and runnpm run start
from theexample
directory.
Summary
There you go guys in this tutorial we created Avatar
component just like chakra ui. You can find the code for this tutorial under the atom-avatar branch here. In the next tutorial we will create AvatarGroup
component. Until next time PEACE.
Top comments (0)