Skip to content

Commit

Permalink
Merge pull request #196 from brionmario/feat-random-color-avatars
Browse files Browse the repository at this point in the history
feat(react): add support to generate random colors for `Avatars`
  • Loading branch information
brionmario authored Nov 25, 2023
2 parents 77c1778 + 6c466f9 commit 04dbb0a
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 8 deletions.
65 changes: 65 additions & 0 deletions packages/react/src/components/Avatar/Avatar.stories.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {ArgsTable, Source, Story, Canvas, Meta} from '@storybook/addon-docs';
import dedent from 'ts-dedent';
import StoryConfig from '../../../.storybook/story-config.ts';
import Stack from '@mui/material/Stack';
import Avatar from './Avatar.tsx';
import {BoltIcon, BriefcaseIcon, CameraIcon, CloudNodesIcon, DatabaseIcon, EclipseIcon} from "@oxygen-ui/react-icons";

export const meta = {
component: Avatar,
Expand All @@ -17,6 +19,9 @@ export const Template = args => <Avatar {...args} />;
- [Overview](#overview)
- [Props](#props)
- [Usage](#usage)
- [Variants](#variants)
- [Random Colors](#random-colors)
- [Random Colors with a supplied randomizer](#random-colors-with-a-supplied-randomizer)

## Overview

Expand Down Expand Up @@ -45,3 +50,63 @@ Import and use the `Avatar` component in your components as follows.
format
code={dedent`import Avatar from '@oxygen-ui/react/Avatar';\n`}
/>

## Variants

### Random Colors

The Avatar component can be used with random colors as follows. Use the `randomBackgroundColor` prop to get a random color for the Avatar.

<Canvas>
<Story name="Random">
<Stack direction="row" spacing={2}>
<Avatar randomBackgroundColor>XZ</Avatar>
<Avatar randomBackgroundColor>Q4</Avatar>
<Avatar randomBackgroundColor>H</Avatar>
<Avatar randomBackgroundColor>OT</Avatar>
<Avatar randomBackgroundColor>OT</Avatar>
<Avatar randomBackgroundColor>OT</Avatar>
</Stack>
</Story>
</Canvas>

### Random Colors with a supplied randomizer

You can pass in a string to the `backgroundColorRandomizer` prop to get a consistent random color for the same string.

<Canvas>
<Story name="Random Sections">
<Stack direction="column" spacing={2}>
<Typography>Icon Avatars</Typography>
<Stack direction="row" spacing={2}>
<Avatar randomBackgroundColor backgroundColorRandomizer="Icon Avatar">
<BoltIcon />
</Avatar>
<Avatar randomBackgroundColor backgroundColorRandomizer="Icon Avatar">
<BriefcaseIcon />
</Avatar>
<Avatar randomBackgroundColor backgroundColorRandomizer="Icon Avatar">
<CameraIcon />
</Avatar>
<Avatar randomBackgroundColor backgroundColorRandomizer="Icon Avatar">
<CloudNodesIcon />
</Avatar>
<Avatar randomBackgroundColor backgroundColorRandomizer="Icon Avatar">
<DatabaseIcon />
</Avatar>
<Avatar randomBackgroundColor backgroundColorRandomizer="Icon Avatar">
<EclipseIcon />
</Avatar>
</Stack>
<Typography>Text Avatars</Typography>
<Stack direction="row" spacing={2}>
<Avatar randomBackgroundColor backgroundColorRandomizer="Text Avatar">XZ</Avatar>
<Avatar randomBackgroundColor backgroundColorRandomizer="Text Avatar">Q4</Avatar>
<Avatar randomBackgroundColor backgroundColorRandomizer="Text Avatar">H</Avatar>
<Avatar randomBackgroundColor backgroundColorRandomizer="Text Avatar">OT</Avatar>
<Avatar randomBackgroundColor backgroundColorRandomizer="Text Avatar">OT</Avatar>
<Avatar randomBackgroundColor backgroundColorRandomizer="Text Avatar">OT</Avatar>
</Stack>
</Stack>
</Story>
</Canvas>
47 changes: 42 additions & 5 deletions packages/react/src/components/Avatar/Avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,58 @@

import MuiAvatar, {AvatarProps as MuiAvatarProps} from '@mui/material/Avatar';
import clsx from 'clsx';
import {FC, ReactElement} from 'react';
import {ElementType, FC, ReactElement, useMemo} from 'react';
import usePastelColorGenerator from 'src/hooks/use-pastel-color-generator';
import {WithWrapperProps} from '../../models';
import {composeComponentDisplayName} from '../../utils';
import './avatar.scss';

export type AvatarProps = MuiAvatarProps;
export type AvatarProps<C extends ElementType = ElementType> = {
/**
* Text for the random background color generator.
*/
backgroundColorRandomizer?: string;
/**
* The component used for the root node. Either a string to use a HTML element or a component.
*/
component?: C;
/**
* If `true`, the background color will be randomly generated.
*/
randomBackgroundColor?: boolean;
} & Omit<MuiAvatarProps<C>, 'component'>;

const COMPONENT_NAME: string = 'Avatar';

const Avatar: FC<AvatarProps> & WithWrapperProps = (props: AvatarProps): ReactElement => {
const {className, ...rest} = props;
const Avatar: FC<AvatarProps> & WithWrapperProps = <C extends ElementType>(props: AvatarProps<C>): ReactElement => {
const {className, children, component, randomBackgroundColor, backgroundColorRandomizer, ...rest} = props;

const colorRandomizer: string = useMemo(() => {
if (backgroundColorRandomizer) {
return backgroundColorRandomizer;
}

if (typeof children === 'string') {
return children;
}

return '';
}, [children, backgroundColorRandomizer]);

const {color} = usePastelColorGenerator(colorRandomizer);

const classes: string = clsx('oxygen-avatar', className);

return <MuiAvatar className={classes} {...rest} />;
return (
<MuiAvatar
component={component}
className={classes}
sx={{bgcolor: randomBackgroundColor ? color : undefined}}
{...rest}
>
{children}
</MuiAvatar>
);
};

Avatar.displayName = composeComponentDisplayName(COMPONENT_NAME);
Expand Down
3 changes: 0 additions & 3 deletions packages/react/src/components/Switch/switch.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@

.MuiSwitch-thumb {
background: var(--oxygen-palette-common-white);
transition-property: transform;
transition-duration: 200ms;
border-radius: inherit;
width: var(--oxygen-switch-thumb-width);
height: var(--oxygen-switch-thumb-width);
border-width: 0.125rem;
Expand Down
62 changes: 62 additions & 0 deletions packages/react/src/hooks/use-pastel-color-generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). All Rights Reserved.
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import {useEffect, useState} from 'react';
import generatePastelColor from '../utils/generate-pastel-color';

export interface UsePastelColorGenerator {
/**
* Generated color.
*/
color: string;
/**
* Function to update the text.
* @param newText - New text to generate the color from.
* @returns void
*/
updateText: (newText: string) => void;
}

/**
* Hook to generate a pastel color based on the given text.
*
* @example
* const {color, updateText} = usePastelColorGenerator('John Doe');
* console.log(color); // hsl(0 70% 80% / 50%)
* updateText('Jane Doe');
* console.log(color); // hsl(240 70% 80% / 50%)
*
* @param initialText - Text to generate the color from.
* @returns Generated color and a function to update the text.
*/
const usePastelColorGenerator = (initialText: string): UsePastelColorGenerator => {
const [text, setText] = useState<string>(initialText);
const [color, setColor] = useState<string>(generatePastelColor(initialText));

useEffect(() => {
setColor(generatePastelColor(text));
}, [text]);

const updateText = (newText: string): void => {
setText(newText);
};

return {color, updateText};
};

export default usePastelColorGenerator;
74 changes: 74 additions & 0 deletions packages/react/src/utils/generate-pastel-color.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). All Rights Reserved.
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

const hues: {
[key: string]: number;
} = {
A: 0,
B: 60,
C: 120,
D: 180,
E: 240,
F: 300,
};

type GenerateColor = (display: string) => string;

// Simple cache to store generated colors
const colorCache: {[key: string]: string} = {};

/**
* Generate a pastel color based on the given text.
*
* @example
* const color = generatePastelColor('John Doe');
* console.log(color); // hsl(0 70% 80% / 50%)
*
* @param text - Text to generate the color from.
* @returns Generated color.
*/
const generatePastelColor: GenerateColor = (text: string): string | null => {
// Check if the color is already in the cache
if (colorCache[text]) {
return colorCache[text];
}

// Check if the text is a non-empty string and return `null` if it is.
if (typeof text !== 'string' || text.trim() === '') {
return null;
}

const hash: number = text.split('').reduce((acc: number, char: string) => acc + char.charCodeAt(0), 0);
const baseHue: number = hash % 360;

const firstChar: string = text[0].toUpperCase();
const predefinedHue: number = hues[firstChar] || baseHue;

const saturation: number = Math.random() * (80 - 70) + 70;
const divisor: number = Math.random() * (100 - 90) + 90;

// Generate the color
const color: string = `hsl(${predefinedHue} 80% ${saturation}% / ${divisor}%)`;

// Cache the color for future use
colorCache[text] = color;

return color;
};

export default generatePastelColor;

0 comments on commit 04dbb0a

Please sign in to comment.