Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added a hook for generating a unique color #1565

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions src/hooks/useColorGenerator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@
import {useColorGenerator} from '@gravity-ui/uikit';
```

The `useColorGenerator` a hook that generates a unique (but consistent) background color based on some unique attribute (e.g., name, id, email). The background color remains unchanged with each update.
The `useColorGenerator` hook generates a unique (but consistent) background color based on some unique attribute (e.g., name, id, email). The background color remains unchanged with each update.

## Properties

| Name | Description | Type | Default | | |
| :-------- | :----------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------- | :---------: | --- | --- |
| mode | Value to control color saturation | `saturated` `unsaturated` `bright` | `saturated` |
| token | Unique attribute of the entity (e.g., name, id, email) | `string` | | | |
| colorKeys | If an array of colors is passed, an index is generated from the token passed, and the value from the color array at that index is returned | `string[]` | | | |
| Name | Description | Type | Default | | |
| :-------- | :----------------------------------------------------- | :----------------------- | :-----: | --- | --- |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type column should also be centered. Also markdown seems broken.

| intensity | Value to control color saturation | `light` `medium` `heavy` | `light` |
| seed | Unique attribute of the entity (e.g., name, id, email) | `string` | | | |

## Result

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@use '../../../components/variables.scss';

$block: '.#{variables.$ns}color-generator';
$block: '.#{variables.$ns}color-generator-stories';

#{$block} {
&__color-items {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import {randomString} from '../../../components/utils/common';

import {ColoredAvatar} from './ColoredAvatar';

import './ColorGenerator.scss';
import './useColorGenerator.stories.scss';

const b = block('color-generator');
const b = block('color-generator-stories');

const meta: Meta = {
title: 'Hooks/useColorGenerator',
Expand All @@ -24,7 +24,7 @@ export default meta;

type Story = StoryObj<typeof ColoredAvatar>;

const views = ['-', 'light', 'medium', 'heavy'] as const;
const views = ['light', 'medium', 'heavy'] as const;
const states = ['view', 'colors'] as const;

const Template = (args: React.ComponentProps<typeof ColoredAvatar>) => {
Expand All @@ -35,24 +35,12 @@ const Template = (args: React.ComponentProps<typeof ColoredAvatar>) => {
for (const state of states) {
const key = `${view}_${state}`;

if (view === '-' && state === 'view') {
items.push(
<div key={key} className={b('grid-cell', {head: 'left'})}>
<strong>view\state</strong>
</div>,
);
} else if (state === 'view') {
if (state === 'view') {
items.push(
<div key={key} className={b('grid-cell', {head: 'left'})}>
<strong>{view}</strong>
</div>,
);
} else if (view === '-') {
items.push(
<div key={key} className={b('grid-cell', {head: 'top'})}>
<strong>{state}</strong>
</div>,
);
} else {
const props = {
...args,
Expand All @@ -77,7 +65,7 @@ const Template = (args: React.ComponentProps<typeof ColoredAvatar>) => {
);
};

export const View: Story = {
export const Default: Story = {
render: (args) => {
return <Template {...args} />;
},
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useColorGenerator/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export {useColorGenerator} from './useColorGenerator';
export type {UseColorGeneratorProps} from './types';
export type {UseColorGeneratorProps, UseColorGeneratorResult} from './types';
13 changes: 9 additions & 4 deletions src/hooks/useColorGenerator/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,26 @@ export type ThemeColorSettings = {
heavy: ColorOptions;
};

export type INTENSITY = 'light' | 'medium' | 'heavy';
export type Intensity = 'light' | 'medium' | 'heavy';

export type ColorProps = {
intensity?: INTENSITY;
intensity?: Intensity;
seed: string;
theme: string;
};

export type UseColorGeneratorProps = {
intensity?: INTENSITY;
intensity?: Intensity;
seed: string;
};

export type UseColorGeneratorResult = {
color: string;
textColor: string;
};

export type HslColorProps = {
hash: number;
intensity: INTENSITY;
intensity: Intensity;
theme: string;
};
74 changes: 44 additions & 30 deletions src/hooks/useColorGenerator/useColorGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,57 @@
/* eslint-disable valid-jsdoc */
import React from 'react';

import {useThemeType} from '../../components/theme/useThemeType';

import {getPersistentColor, getTextColor} from './color';
import type {UseColorGeneratorProps} from './types';
import type {UseColorGeneratorProps, UseColorGeneratorResult} from './types';

/**
* It is used to create a unique color from a token (string) and to obtain an inverted color (black or white),
* ensuring higher text contrast compared to the current color, which is usually better for human perception.
*
* Usage example:
```tsx
import React from 'react';
import {Avatar} from '@gravity-ui/uikit';

const Component = ({ token, text, ...avatarProps }) => {
const {color, textColor} = useColorGenerator({
token,
});
* The `useColorGenerator` hook generates a unique (but consistent) background color based on some unique attribute (e.g., name, id, email).
* The background color remains unchanged with each update.
*
* @param {Object} props
* @param {string} [props.intensity='light'] - value to control color saturation.
* @param {string} props.seed - unique attribute of the entity (e.g., name, id, email).
* @example
*
* import React from 'react';
* import {Avatar} from '@gravity-ui/uikit';
*
* const Component = ({ token, text, ...avatarProps }) => {
* const {color, textColor} = useColorGenerator({
* seed,
* });
*
* return (
* <Avatar
* {...avatarProps}
* text={text}
* color={text ? textColor : undefined}
* backgroundColor={color}
* />
* );
* };

return (
<Avatar
{...avatarProps}
text={text}
color={text ? textColor : undefined}
backgroundColor={color}
/>
);
};
```
@returns {Object} returns an object with exactly two values:
* - color: unique color from a token.
* - textColor: text color (dark or light), ensurring higher contrast on generated color.
*/
export function useColorGenerator(props: UseColorGeneratorProps) {
export function useColorGenerator(props: UseColorGeneratorProps): UseColorGeneratorResult {
const theme = useThemeType();

const {color, hue, saturation, lightness} = getPersistentColor({
...props,
theme,
});
const {color, hue, saturation, lightness} = React.useMemo(
() =>
getPersistentColor({
...props,
theme,
}),
[props, theme],
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

props is an object, better to pass individual props


const textColor = getTextColor(hue, saturation, lightness);
const textColor = React.useMemo(
() => getTextColor(hue, saturation, lightness),
[hue, lightness, saturation],
);

return {color, textColor};
}
Loading