This document outlines the steps to create Avatar
component styled withTailwind CSS and using some npm dependency libraries.
Node.js
and npm
installed on your machine.Tailwind CSS
installed in your project.CVA(class-variance-authority)
is a utility for managing CSS class names based on various conditions.clsx
is a tiny utility for constructing className strings conditionally.tinycolor2
is a lightweight library helping get contrast text color based on background.1// helpers.ts
2import { cva } from "class-variance-authority";
3import tinycolor from "tinycolor2";
4
5export const getRandomHexColor = () => {
6 return "#" + Math.floor(Math.random() * 16777215).toString(16);
7};
8
9export const getTextColor = (hex: string) => {
10 return tinycolor(hex).isDark() ? "white" : "black";
11};
12
13export const radiusVariants = cva("", {
14 variants: {
15 radius: {
16 small: "rounded-md",
17 large: "rounded-xl",
18 full: "rounded-full",
19 },
20 },
21 defaultVariants: {
22 radius: "full",
23 },
24});
25
26export const wrapperVariants = cva(
27 "overflow-hidden p-[1px] data-[bordered=true]:border-[3px] border-solid data-[disabled=true]:opacity-60",
28 {
29 variants: {
30 size: {
31 small: "w-8 h-8",
32 medium: "w-10 h-10",
33 large: "w-14 h-14",
34 },
35 },
36 defaultVariants: {
37 size: "medium",
38 },
39 },
40);
41
42export const textVariants = cva("font-bold !leading-[0]", {
43 variants: {
44 size: {
45 small: "text-lg",
46 medium: "text-xl",
47 large: "text-3xl",
48 },
49 },
50 defaultVariants: {
51 size: "medium",
52 },
53});
54
1// avatar.component.tsx
2import { VariantProps } from "class-variance-authority";
3import clsx from "clsx";
4import { useMemo, useState } from "react";
5import {
6 getRandomHexColor,
7 getTextColor,
8 radiusVariants,
9 textVariants,
10 wrapperVariants,
11} from "./helpers";
12
13interface Props
14 extends VariantProps<typeof wrapperVariants>,
15 VariantProps<typeof radiusVariants> {
16 src?: string;
17 name: string;
18 bordered?: boolean;
19 disabled?: boolean;
20 randomFallbackColor?: boolean;
21 className?: string;
22 imageClassName?: string;
23 fallbackClassName?: string;
24}
25
26const Avatar: React.FC<AvatarProps> = ({
27 src,
28 name,
29 radius,
30 size,
31 hideImage,
32 bordered,
33 disabled,
34 randomFallbackColor,
35 className,
36 imageClassName,
37 fallbackClassName,
38}) => {
39 const [showingFallback, setShowingFallback] = useState<boolean>(!src);
40
41 const randomColor = useMemo(() => {
42 const bgColor = getRandomHexColor();
43 const textColor = getTextColor(bgColor);
44 return { bgColor, textColor };
45 }, []);
46
47 return (
48 <div
49 data-bordered={Boolean(bordered)}
50 data-disabled={Boolean(disabled)}
51 style={{
52 borderColor:
53 (hideImage || showingFallback) && randomFallbackColor
54 ? randomColor.bgColor
55 : "#a3a3a3",
56 }}
57 className={clsx(
58 wrapperVariants({ size }),
59 radiusVariants({ radius }),
60 className
61 )}
62 >
63 {hideImage || showingFallback ? (
64 <div
65 style={{
66 backgroundColor: randomFallbackColor
67 ? randomColor.bgColor
68 : "#a3a3a3",
69 color: randomFallbackColor ? randomColor.textColor : "white",
70 }}
71 className={clsx(
72 "flex h-full w-full items-center justify-center",
73 radiusVariants({ radius }),
74 fallbackClassName
75 )}
76 >
77 <span className={textVariants({ size })}>
78 {name.charAt(0).toUpperCase()}
79 </span>
80 </div>
81 ) : (
82 <img
83 src={src}
84 alt={name}
85 className={clsx(
86 "h-full w-full",
87 radiusVariants({ radius }),
88 imageClassName
89 )}
90 onError={() => setShowingFallback(true)}
91 />
92 )}
93 </div>
94 );
95};
96
97export default Avatar;
98