diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 4d204ced02..8773b9f976 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -2,7 +2,7 @@ export * from './useActionHandlers'; export * from './useAsyncActionHandler'; export * from './useBodyScrollLock'; export * from './useControlledState'; -export * from './useGeneratorColor'; +export * from './useColorGenerator'; export * from './useFileInput'; export * from './useFocusWithin'; export * from './useForkRef'; diff --git a/src/hooks/useColorGenerator/README.md b/src/hooks/useColorGenerator/README.md new file mode 100644 index 0000000000..28c077d7a4 --- /dev/null +++ b/src/hooks/useColorGenerator/README.md @@ -0,0 +1,26 @@ + + +# useColorGenerator + + + +```tsx +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. + +## 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[]` | | | | + +## Result + +`useColorGenerator` returns an object with exactly two values: + +1. color - unique color from a token. +2. textColor - text color (dark or light), ensurring higher contrast on generated color. diff --git a/src/hooks/useColorGenerator/__stories__/ColorGenerator.scss b/src/hooks/useColorGenerator/__stories__/ColorGenerator.scss new file mode 100644 index 0000000000..100143912f --- /dev/null +++ b/src/hooks/useColorGenerator/__stories__/ColorGenerator.scss @@ -0,0 +1,12 @@ +@use '../../../components/variables.scss'; + +$block: '.#{variables.$ns}color-generator'; + +#{$block} { + &__color-items { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-block-start: 20px; + } +} diff --git a/src/hooks/useColorGenerator/__stories__/ColoredAvatar.tsx b/src/hooks/useColorGenerator/__stories__/ColoredAvatar.tsx new file mode 100644 index 0000000000..6c9bde9bd8 --- /dev/null +++ b/src/hooks/useColorGenerator/__stories__/ColoredAvatar.tsx @@ -0,0 +1,36 @@ +import React from 'react'; + +import {Avatar} from '../../../components/Avatar'; +import type {AvatarProps} from '../../../components/Avatar'; +import type {UseColorGeneratorProps} from '../types'; +import {useColorGenerator} from '../useColorGenerator'; + +type ColoredAvatarProps = AvatarProps & { + withText: boolean; + mode: UseColorGeneratorProps['mode']; + token: UseColorGeneratorProps['token']; +}; + +export const ColoredAvatar = ({ + mode, + theme, + token, + withText, + ...avatarProps +}: ColoredAvatarProps) => { + const {color, textColor} = useColorGenerator({ + token, + mode, + }); + + return ( + + ); +}; diff --git a/src/hooks/useColorGenerator/__stories__/useColorGenerator.stories.tsx b/src/hooks/useColorGenerator/__stories__/useColorGenerator.stories.tsx new file mode 100644 index 0000000000..b33e997719 --- /dev/null +++ b/src/hooks/useColorGenerator/__stories__/useColorGenerator.stories.tsx @@ -0,0 +1,69 @@ +import React from 'react'; + +import type {Meta, StoryObj} from '@storybook/react'; + +import {Button} from '../../../components/Button'; +import {block} from '../../../components/utils/cn'; +import {randomString} from '../__tests__/utils/randomString'; + +import {ColoredAvatar} from './ColoredAvatar'; + +import './ColorGenerator.scss'; + +const b = block('color-generator'); + +const meta: Meta = { + title: 'Hooks/useColorGenerator', + argTypes: { + mode: { + options: ['unsaturated', 'saturated', 'bright'], + control: {type: 'radio'}, + }, + withText: { + control: 'boolean', + }, + }, +}; + +export default meta; + +type Story = StoryObj; + +const initValues = () => { + const result = Array.from({length: 10}, () => randomString(16)); + + return result; +}; + +const Template = (args: React.ComponentProps) => { + const {mode, withText} = args; + const [tokens, setTokens] = React.useState(initValues); + + const onClick = React.useCallback(() => { + const newToken = randomString(16); + setTokens((prev) => [newToken, ...prev]); + }, []); + + return ( + + +
+ {tokens.map((token) => ( + + ))} +
+
+ ); +}; + +export const Default: Story = { + render: (args) => { + return