From 45aba44b9cc86edb7d49807d5a82b9f983ee42da Mon Sep 17 00:00:00 2001 From: German Shteinardt Date: Fri, 3 May 2024 11:19:08 +0200 Subject: [PATCH] feat: added a hook for generating a unique color --- src/hooks/index.ts | 1 + src/hooks/useGeneratorColor/README.md | 28 +++++ .../useGeneratorColor/__stories__/Color.tsx | 30 +++++ .../__stories__/GeneratorColor.scss | 18 +++ .../__stories__/UseGeneratorColor.stories.tsx | 57 +++++++++ .../__stories__/constants.ts | 5 + .../__stories__/utils/randomString.ts | 11 ++ src/hooks/useGeneratorColor/index.ts | 2 + src/hooks/useGeneratorColor/types.ts | 23 ++++ .../useGeneratorColor/useGeneratorColor.ts | 41 ++++++ src/hooks/useGeneratorColor/utils/color.ts | 119 ++++++++++++++++++ .../useGeneratorColor/utils/constants.ts | 34 +++++ src/hooks/useGeneratorColor/utils/getHue.ts | 9 ++ .../useGeneratorColor/utils/hashFnv32a.ts | 12 ++ src/hooks/useGeneratorColor/utils/mathFrac.ts | 2 + .../useGeneratorColor/utils/normalizeHash.ts | 7 ++ .../useGeneratorColor/utils/randomIndex.ts | 18 +++ 17 files changed, 417 insertions(+) create mode 100644 src/hooks/useGeneratorColor/README.md create mode 100644 src/hooks/useGeneratorColor/__stories__/Color.tsx create mode 100644 src/hooks/useGeneratorColor/__stories__/GeneratorColor.scss create mode 100644 src/hooks/useGeneratorColor/__stories__/UseGeneratorColor.stories.tsx create mode 100644 src/hooks/useGeneratorColor/__stories__/constants.ts create mode 100644 src/hooks/useGeneratorColor/__stories__/utils/randomString.ts create mode 100644 src/hooks/useGeneratorColor/index.ts create mode 100644 src/hooks/useGeneratorColor/types.ts create mode 100644 src/hooks/useGeneratorColor/useGeneratorColor.ts create mode 100644 src/hooks/useGeneratorColor/utils/color.ts create mode 100644 src/hooks/useGeneratorColor/utils/constants.ts create mode 100644 src/hooks/useGeneratorColor/utils/getHue.ts create mode 100644 src/hooks/useGeneratorColor/utils/hashFnv32a.ts create mode 100644 src/hooks/useGeneratorColor/utils/mathFrac.ts create mode 100644 src/hooks/useGeneratorColor/utils/normalizeHash.ts create mode 100644 src/hooks/useGeneratorColor/utils/randomIndex.ts diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 9a98690749..4d204ced02 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -2,6 +2,7 @@ export * from './useActionHandlers'; export * from './useAsyncActionHandler'; export * from './useBodyScrollLock'; export * from './useControlledState'; +export * from './useGeneratorColor'; export * from './useFileInput'; export * from './useFocusWithin'; export * from './useForkRef'; diff --git a/src/hooks/useGeneratorColor/README.md b/src/hooks/useGeneratorColor/README.md new file mode 100644 index 0000000000..ac3e1bb303 --- /dev/null +++ b/src/hooks/useGeneratorColor/README.md @@ -0,0 +1,28 @@ + + +# useGeneratorColor + + + +```tsx +import {useGeneratorColor} from '@gravity-ui/uikit'; +``` + +The `useGeneratorColor` 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, | string[] | undefined | | +| | an index is generated from the token passed, | | | +| | and the value from the color array at that index is returned | | | + +## Result + +`useGeneratorColor` returns an object with exactly two values: + +1. color - unique color from a token. +2. oppositeColor - inverted color (black or white), ensuring higher text contrast compared to the current unique color, which is usually better for human perception. diff --git a/src/hooks/useGeneratorColor/__stories__/Color.tsx b/src/hooks/useGeneratorColor/__stories__/Color.tsx new file mode 100644 index 0000000000..0228c709aa --- /dev/null +++ b/src/hooks/useGeneratorColor/__stories__/Color.tsx @@ -0,0 +1,30 @@ +import React from 'react'; + +import {Avatar} from '../../../components/Avatar'; +import type {AvatarProps} from '../../../components/Avatar'; +import type {UseGeneratorColorProps} from '../types'; +import {useGeneratorColor} from '../useGeneratorColor'; + +type ColorProps = AvatarProps & { + withText: boolean; + mode: UseGeneratorColorProps['mode']; + token: UseGeneratorColorProps['token']; +}; + +export const Color = ({mode, theme, token, withText, ...avatarProps}: ColorProps) => { + const {color, oppositeColor} = useGeneratorColor({ + token, + mode, + }); + + return ( + + ); +}; diff --git a/src/hooks/useGeneratorColor/__stories__/GeneratorColor.scss b/src/hooks/useGeneratorColor/__stories__/GeneratorColor.scss new file mode 100644 index 0000000000..85f0f846c4 --- /dev/null +++ b/src/hooks/useGeneratorColor/__stories__/GeneratorColor.scss @@ -0,0 +1,18 @@ +@use '../../../components/variables.scss'; + +$block: '.#{variables.$ns}generator-color'; + +#{$block} { + &__actions { + display: flex; + gap: 4px; + margin-block-end: 20px; + align-items: center; + } + + &__color-items { + display: flex; + flex-wrap: wrap; + gap: 10px; + } +} diff --git a/src/hooks/useGeneratorColor/__stories__/UseGeneratorColor.stories.tsx b/src/hooks/useGeneratorColor/__stories__/UseGeneratorColor.stories.tsx new file mode 100644 index 0000000000..34057ed199 --- /dev/null +++ b/src/hooks/useGeneratorColor/__stories__/UseGeneratorColor.stories.tsx @@ -0,0 +1,57 @@ +import React from 'react'; + +import type {Meta, StoryFn} from '@storybook/react'; + +import {Button} from '../../../components/Button'; +import {Checkbox} from '../../../components/Checkbox'; +import {Select} from '../../../components/Select'; +import {block} from '../../../components/utils/cn'; +import type {UseGeneratorColorProps} from '../types'; + +import {Color} from './Color'; +import {colorModes} from './constants'; +import {randomString} from './utils/randomString'; + +import './GeneratorColor.scss'; + +const b = block('generator-color'); + +export default {title: 'Hooks/useGeneratorColor'} as Meta; + +const DefaultTemplate: StoryFn = () => { + const [tokens, setTokens] = React.useState([]); + const [mode, setMode] = React.useState(['unsaturated']); + const [withText, setWithText] = React.useState(false); + + const onClick = React.useCallback(() => { + const newToken = randomString(16); + setTokens((prev) => [newToken, ...prev]); + }, []); + + return ( + +
+ +