Skip to content

Commit 150427f

Browse files
authored
Merge pull request #320 from code0-tech/feat/#271
New Avatar component
2 parents 22e6ce0 + f706266 commit 150427f

File tree

5 files changed

+147
-3
lines changed

5 files changed

+147
-3
lines changed

package-lock.json

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,11 @@
4646
"identity-obj-proxy": "^3.0.0",
4747
"jest-image-snapshot": "^6.4.0",
4848
"merge-props": "^6.0.0",
49+
"overlap-area": "^1.1.0",
4950
"playwright": "1.51.1",
5051
"react": "^19.0.0",
5152
"react-dom": "^19.0.0",
53+
"react-zoom-pan-pinch": "^3.7.0",
5254
"rimraf": "^6.0.1",
5355
"rollup": "^4.34.8",
5456
"rollup-plugin-dts": "^6.1.1",
@@ -61,8 +63,7 @@
6163
"storybook": "^8.6.3",
6264
"style-loader": "^4.0.0",
6365
"typescript": "^5.7.3",
64-
"overlap-area": "^1.1.0",
65-
"react-zoom-pan-pinch": "^3.7.0"
66+
"js-md5": "^0.8.3"
6667
},
6768
"main": "dist/cjs/index.js",
6869
"module": "dist/esm/index.js",
@@ -74,10 +75,11 @@
7475
"@ariakit/react": "^0.4.5",
7576
"@tabler/icons-react": "^3.5.0",
7677
"merge-props": "^6.0.0",
78+
"overlap-area": "^1.1.0",
7779
"react": "^18.0.0 || ^19.0.0",
7880
"react-dom": "^18.0.0 || ^19.0.0",
7981
"react-zoom-pan-pinch": "^3.6.1",
80-
"overlap-area": "^1.1.0"
82+
"js-md5": "^0.8.3"
8183
},
8284
"publishConfig": {
8385
"access": "public"
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import {Meta} from "@storybook/react";
2+
import React from "react";
3+
import Avatar from "./Avatar";
4+
import Flex from "../flex/Flex";
5+
6+
const meta: Meta = {
7+
title: "Avatar",
8+
component: Avatar
9+
}
10+
11+
export default meta
12+
13+
export const AvatarExample = () => {
14+
return <Flex style={{gap: ".35rem"}}>
15+
<Avatar identifier={"nsammito@code0.tech"}/>
16+
<Avatar src={"https://avatars.githubusercontent.com/u/60352747?v=4"}/>
17+
<Avatar identifier={"rgoetz@code0.tech"}/>
18+
</Flex>
19+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@import "../../styles/helpers";
2+
3+
.avatar {
4+
@include box(false);
5+
aspect-ratio: 50 / 50;
6+
padding: $xxs;
7+
display: flex;
8+
align-items: center;
9+
justify-content: center;
10+
overflow: hidden;
11+
12+
&--image {
13+
padding: 0;
14+
}
15+
}

src/components/avatar/Avatar.tsx

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import React from "react";
2+
import {Code0Component} from "../../utils/types";
3+
import {md5} from 'js-md5';
4+
import "./Avatar.style.scss"
5+
import {mergeCode0Props} from "../../utils/utils";
6+
7+
interface AvatarImageProps {
8+
src: string
9+
size: number
10+
}
11+
12+
interface AvatarIdenticonProps {
13+
identifier: string
14+
size: number
15+
}
16+
17+
export interface AvatarProps extends Code0Component<HTMLDivElement> {
18+
identifier?: string
19+
src?: string
20+
size?: number
21+
}
22+
23+
const AvatarImage: React.FC<AvatarImageProps> = ({src, size}) => {
24+
return <img src={src} width={size} height={size} alt="Avatar image"/>
25+
}
26+
27+
const AvatarIdenticon: React.FC<AvatarIdenticonProps> = ({identifier, size}) => {
28+
const canvas = React.useRef<HTMLCanvasElement>(null);
29+
30+
React.useEffect(() => {
31+
updateCanvas()
32+
})
33+
34+
const updateCanvas = () => {
35+
const hash = md5(identifier)
36+
const block = Math.floor(size / 5)
37+
const hashColor = hash.slice(0, 6)
38+
39+
canvas.current!!.width = block * 5
40+
canvas.current!!.height = block * 5
41+
42+
let arr = hash.split('').map((el: number | string) => {
43+
el = parseInt(el as string, 16);
44+
if (el < 8) {
45+
return 0;
46+
} else {
47+
return 1;
48+
}
49+
});
50+
51+
let map = [];
52+
53+
map[0] = map[4] = arr.slice(0, 5);
54+
map[1] = map[3] = arr.slice(5, 10);
55+
map[2] = arr.slice(10, 15);
56+
57+
const ctx = canvas.current!!.getContext('2d');
58+
ctx!!.imageSmoothingEnabled = false;
59+
ctx!!.clearRect(0, 0, canvas.current!!.width, canvas.current!!.height);
60+
61+
map.forEach((row, i) => {
62+
row.forEach((el, j) => {
63+
if (el) {
64+
ctx!!.fillStyle = '#' + hashColor;
65+
ctx!!.fillRect(
66+
block * i,
67+
block * j,
68+
block,
69+
block
70+
);
71+
} else {
72+
ctx!!.fillStyle = "transparent";
73+
ctx!!.fillRect(
74+
block * i,
75+
block * j,
76+
block,
77+
block
78+
);
79+
}
80+
});
81+
});
82+
};
83+
84+
return <canvas ref={canvas}/>
85+
}
86+
87+
const Avatar: React.FC<AvatarProps> = (props: AvatarProps) => {
88+
89+
const {identifier, src, size = 25, ...rest} = props
90+
91+
return <div {...mergeCode0Props(`avatar ${!identifier && src ? "avatar--image" : ""}`, rest)}>
92+
{identifier ?
93+
<AvatarIdenticon identifier={identifier} size={size}/> :
94+
src ? <AvatarImage src={src} size={size + 13}/> : null
95+
}
96+
</div>
97+
}
98+
99+
export default Avatar

0 commit comments

Comments
 (0)