Skip to content

Commit

Permalink
feat: add a new smile type avatar
Browse files Browse the repository at this point in the history
  • Loading branch information
ajhenry committed Sep 27, 2023
1 parent ba4502c commit 09bab69
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 76 deletions.
30 changes: 14 additions & 16 deletions packages/prettyavatars/src/components/avatar.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from 'react' // Do not remove line
import React from 'react'
import AvatarBauhaus from './bauhaus'
import AvatarBeam from './beam'
import AvatarLetter from './letter'
import AvatarMarble from './marble'
import AvatarPixel from './pixel'
import AvatarPixelArt from './pixel-art'
import AvatarRing from './ring'
import AvatarSmile from './smile'
import AvatarSunset from './sunset'

const variants = [
Expand All @@ -18,13 +19,9 @@ const variants = [
'marble',
'letter-plain',
'pixel-art',
'smile',
] as const

const deprecatedVariants: Record<string, string> = {
geometric: 'beam',
abstract: 'bauhaus',
}

export type Variant = (typeof variants)[number]

export interface AvatarProps {
Expand All @@ -42,6 +39,13 @@ export const defaultProps = {
size: 40,
}

const checkedVariant = (variant: string) => {
if (variants.includes(variant as Variant)) {
return variant
}
return 'beam'
}

const Avatar = ({
variant = 'marble',
colors = ['#92A1C6', '#146A7C', '#F0AB3D', '#C271B4', '#C20D90'],
Expand All @@ -51,15 +55,7 @@ const Avatar = ({
...props
}: AvatarProps & { variant: Variant }) => {
const avatarProps = { colors, name, size, square, ...props }
const checkedVariant = () => {
if (Object.keys(deprecatedVariants).includes(variant)) {
return deprecatedVariants[variant]
}
if (variants.includes(variant)) {
return variant
}
return 'marble'
}

const avatars: Record<string, React.ReactElement> = {
letter: <AvatarLetter {...avatarProps} />,
pixel: <AvatarPixel {...avatarProps} />,
Expand All @@ -70,8 +66,10 @@ const Avatar = ({
marble: <AvatarMarble {...avatarProps} />,
'letter-plain': <AvatarLetter {...avatarProps} plain />,
'pixel-art': <AvatarPixelArt {...avatarProps} />,
smile: <AvatarSmile {...avatarProps} />,
}
return avatars[checkedVariant()]

return avatars[checkedVariant(variant)]
}

export default Avatar
155 changes: 155 additions & 0 deletions packages/prettyavatars/src/components/dynamic-letter__unstable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import Color from 'color'
import * as React from 'react'
import ReactHtmlParser from 'react-html-parser'
import TextToSVG from 'text-to-svg'
import { letterMap } from '../lib/letters'
import { getInitials, getRandomColor, hashCode, sfc } from '../lib/utils'
import { AvatarProps } from './avatar'

const SIZE = 80

export interface AvatarDynamicLetterProps extends AvatarProps {
fontUrl?: string
unstable?: boolean
plain?: boolean
}

const getInterLetters = (letter: string, fill: string) => {
return `<path d="${letterMap[letter]}" fill="${fill}" />`
}

const AvatarDynamicLetter: React.FC<AvatarDynamicLetterProps> = (props) => {
const { name, colors, fontUrl, unstable, plain } = props
const [loader, setLoader] = React.useState<TextToSVG | null>(null)
const [initials, setInitials] = React.useState<string>(getInitials(name))
const [seed, setSeed] = React.useState(hashCode(props.name))
const [color, setColor] = React.useState<string>(
getRandomColor(seed, colors, colors.length)
)
const lightened = Color(color)
.lighten(0.2 + sfc(unstable ? Math.random() : seed)() * 0.5)
.hex()
const darkened = Color(color)
.darken(0.2 + sfc(unstable ? Math.random() : seed + 1)() * 0.5)
.hex()

React.useEffect(() => {
TextToSVG.load(
fontUrl ?? '/fonts/Inter-Black.ttf',
function (err, textToSVG) {
if (!textToSVG) {
if (window === undefined) {
// eslint-disable-next-line no-console
console.log(err)
}
return
}
if (!loader) {
setLoader(textToSVG)
}
}
)
}, [loader, fontUrl])

React.useEffect(() => {
setInitials(getInitials(name))
setSeed(hashCode(name))
}, [name])

// Needed for SSR to work
React.useEffect(() => {
setColor(getRandomColor(seed, colors, colors.length))
}, [seed, colors, name])

const generatePath = (letters: string) => {
if (!fontUrl) {
return getInterLetters(letters, darkened)
}

// This is still a work in progress for dynamic fonts
if (!loader) {
return
}

return loader.getPath(letters, {
x: SIZE / 2,
y: SIZE / 2,
anchor: 'center middle',
fontSize: letters.length === 2 ? SIZE - 48 : SIZE - 32,
attributes: {
fill: darkened,
},
})
}

const SQUARE_CONST = 0.05
const SCALE_CONSTANT = 0.8

return (
<svg
width={props.size}
height={props.size}
viewBox={`0 0 ${SIZE} ${SIZE}`}
fill='none'
xmlns='http://www.w3.org/2000/svg'
>
{props.square ? (
<>
<rect
width={SIZE}
height={SIZE}
fill={!plain ? darkened : lightened}
/>
{!plain && (
<rect
width={SIZE * (1 - SQUARE_CONST * 2)}
height={SIZE * (1 - SQUARE_CONST * 2)}
fill={lightened}
x={SIZE - SIZE * (1 - SQUARE_CONST)}
y={SIZE - SIZE * (1 - SQUARE_CONST)}
/>
)}
</>
) : (
<>
<circle
cx={SIZE / 2}
cy={SIZE / 2}
r={SIZE / 2}
fill={!plain ? darkened : lightened}
/>
{!plain && (
<circle
cx={SIZE / 2}
cy={SIZE / 2}
r={SIZE / 2 - SIZE * 0.05}
fill={lightened}
/>
)}
</>
)}
{initials.length === 2 ? (
<>
<g
transform={`translate(-${8 * SCALE_CONSTANT}, ${
10 * SCALE_CONSTANT
}) scale(${SCALE_CONSTANT})`}
>
{ReactHtmlParser(generatePath(initials.split('')[0])!)}
</g>
<g
transform={`translate(${26 * SCALE_CONSTANT}, ${
10 * SCALE_CONSTANT
}) scale(${SCALE_CONSTANT})`}
>
{ReactHtmlParser(generatePath(initials.split('')[1])!)}
</g>
</>
) : (
ReactHtmlParser(generatePath(initials)!)
)}
</svg>
)
}

export default AvatarDynamicLetter
66 changes: 8 additions & 58 deletions packages/prettyavatars/src/components/letter.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import Color from 'color'
import * as React from 'react'
import ReactHtmlParser from 'react-html-parser'
import TextToSVG from 'text-to-svg'
import { letterMap } from '../lib/letters'
import { getInitials, getRandomColor, hashCode, sfc } from '../lib/utils'
import { AvatarProps } from './avatar'

const SIZE = 80

export interface AvatarLetterProps extends AvatarProps {
fontUrl?: string
unstable?: boolean
plain?: boolean
}

Expand All @@ -19,67 +16,20 @@ const getInterLetters = (letter: string, fill: string) => {
}

const AvatarLetter: React.FC<AvatarLetterProps> = (props) => {
const { name, colors, fontUrl, unstable, plain } = props
const [loader, setLoader] = React.useState<TextToSVG | null>(null)
const [initials, setInitials] = React.useState<string>(getInitials(name))
const [seed, setSeed] = React.useState(hashCode(props.name))
const [color, setColor] = React.useState<string>(
getRandomColor(seed, colors, colors.length)
)
const { name, colors, plain } = props
const seed = hashCode(name)
const initials = getInitials(name)
const color = getRandomColor(seed, colors, colors.length)

const lightened = Color(color)
.lighten(0.2 + sfc(unstable ? Math.random() : seed)() * 0.5)
.lighten(0.2 + sfc(seed)() * 0.5)
.hex()
const darkened = Color(color)
.darken(0.2 + sfc(unstable ? Math.random() : seed + 1)() * 0.5)
.darken(0.2 + sfc(seed + 1)() * 0.5)
.hex()

React.useEffect(() => {
TextToSVG.load(
fontUrl ?? '/fonts/Inter-Black.ttf',
function (err, textToSVG) {
if (!textToSVG) {
if (window === undefined) {
// eslint-disable-next-line no-console
console.log(err)
}
return
}
if (!loader) {
setLoader(textToSVG)
}
}
)
}, [loader, fontUrl])

React.useEffect(() => {
setInitials(getInitials(name))
setSeed(hashCode(name))
}, [name])

// Needed for SSR to work
React.useEffect(() => {
setColor(getRandomColor(seed, colors, colors.length))
}, [seed, colors, name])

const generatePath = (letters: string) => {
if (!fontUrl) {
return getInterLetters(letters, darkened)
}

// This is still a work in progress for dynamic fonts
if (!loader) {
return
}

return loader.getPath(letters, {
x: SIZE / 2,
y: SIZE / 2,
anchor: 'center middle',
fontSize: letters.length === 2 ? SIZE - 48 : SIZE - 32,
attributes: {
fill: darkened,
},
})
return getInterLetters(letters, darkened)
}

const SQUARE_CONST = 0.05
Expand Down
Loading

0 comments on commit 09bab69

Please sign in to comment.