Update selected item color in Pass menu
[ProtonMail-WebClient.git] / packages / pass / hooks / usePasswordGenerator.tsx
blob20beba813641be782e745167ba1fd0c564b3cea4
1 import { useCallback, useEffect, useState } from 'react';
3 import {
4     DEFAULT_MEMORABLE_PW_OPTIONS,
5     DEFAULT_RANDOM_PW_OPTIONS,
6     alphabeticChars,
7     digitChars,
8 } from '@proton/pass/lib/password/constants';
9 import type { GeneratePasswordConfig, GeneratePasswordMode } from '@proton/pass/lib/password/generator';
10 import { generatePassword } from '@proton/pass/lib/password/generator';
11 import type { MaybeNull } from '@proton/pass/types';
12 import { merge } from '@proton/pass/utils/object/merge';
13 import debounce from '@proton/utils/debounce';
15 export enum CharType {
16     Alphabetic,
17     Digit,
18     Special,
21 /* Designers mixed the colors of different ui-${type}
22  * sub-themes for the character colors.. */
23 export const charTypeToClassName = {
24     [CharType.Alphabetic]: '',
25     [CharType.Digit]: 'ui-violet pass-password-generator--char-digit',
26     [CharType.Special]: 'ui-teal pass-password-generator--char-special',
29 export const getTypeFromChar = (char: string) => {
30     if (alphabeticChars.includes(char)) return CharType.Alphabetic;
31     if (digitChars.includes(char)) return CharType.Digit;
33     return CharType.Special;
36 export const getCharsGroupedByColor = (password: string) => {
37     if (password.length === 0) return [];
39     const [head, ...chars] = Array.from(password);
40     const startType = getTypeFromChar(head);
42     return chars
43         .reduce(
44             (state, currentChar) => {
45                 const currentElement = state[state.length - 1];
46                 const previousType = currentElement.color;
47                 const currentType = getTypeFromChar(currentChar);
49                 return previousType !== currentType
50                     ? [...state, { color: currentType, content: currentChar }]
51                     : [...state.slice(0, -1), { color: previousType, content: currentElement.content + currentChar }];
52             },
53             [{ color: startType, content: head }]
54         )
55         .map(({ color, content }, index) => (
56             <span className={charTypeToClassName[color]} key={index}>
57                 {content}
58             </span>
59         ));
62 type UsePasswordGeneratorOptions = {
63     initial: MaybeNull<GeneratePasswordConfig>;
64     onConfigChange: (options: GeneratePasswordConfig) => void;
67 export const usePasswordGenerator = ({ initial, onConfigChange }: UsePasswordGeneratorOptions) => {
68     const [config, setConfig] = useState<GeneratePasswordConfig>(initial ?? DEFAULT_MEMORABLE_PW_OPTIONS);
69     const [password, setPassword] = useState(() => generatePassword(config));
70     const regeneratePassword = () => setPassword(generatePassword(config));
72     /** debounce the pw options dispatch in order to avoid swarming the
73      * store with updates when using the length slider */
74     const savePasswordOptions = useCallback(debounce(onConfigChange, 250), []);
76     const setPasswordOptions = <T extends GeneratePasswordConfig['type']>(
77         type: T,
78         update?: Partial<Extract<GeneratePasswordConfig, { type: T }>['options']>
79     ) => {
80         setConfig((options) => {
81             const newOptions = (() => {
82                 if (update) return merge(options, { options: update });
83                 if (type === 'memorable') return DEFAULT_MEMORABLE_PW_OPTIONS;
84                 if (type === 'random') return DEFAULT_RANDOM_PW_OPTIONS;
85                 return options;
86             })();
88             savePasswordOptions(newOptions);
89             return newOptions;
90         });
91     };
93     /* regenerate the password on each options change */
94     useEffect(() => regeneratePassword(), Object.values(config.options));
96     return {
97         config,
98         password,
99         setPassword,
100         setPasswordOptions,
101         regeneratePassword,
102     };
105 export type UsePasswordGeneratorResult<T extends GeneratePasswordMode = GeneratePasswordMode> = Omit<
106     ReturnType<typeof usePasswordGenerator>,
107     'config'
108 > & { config: GeneratePasswordConfig<T> };
110 export const isUsingRandomPassword = (
111     result: UsePasswordGeneratorResult
112 ): result is UsePasswordGeneratorResult<'random'> => result.config.type === 'random';
114 export const isUsingMemorablePassword = (
115     result: UsePasswordGeneratorResult
116 ): result is UsePasswordGeneratorResult<'memorable'> => result.config.type === 'memorable';