Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / packages / pass / components / Form / Field / UrlGroupField.tsx
blob19918bc58d53c6f2463783c6d6b8e9fd09f64b23
1 import type { ReactNode } from 'react';
2 import { type KeyboardEvent, useRef } from 'react';
4 import { FieldArray, type FormikContextType, type FormikErrors } from 'formik';
5 import { c } from 'ttag';
7 import { Button } from '@proton/atoms';
8 import { Icon, InputFieldTwo } from '@proton/components/';
9 import { maybeErrorMessage } from '@proton/pass/hooks/useFieldControl';
10 import type { UrlGroupValues, UrlItem } from '@proton/pass/types';
11 import { isEmptyString } from '@proton/pass/utils/string/is-empty-string';
12 import { uniqueId } from '@proton/pass/utils/string/unique-id';
13 import { sanitizeURL } from '@proton/pass/utils/url/sanitize';
15 import { FieldBox } from './Layout/FieldBox';
17 export type UrlGroupProps<V extends UrlGroupValues = UrlGroupValues> = {
18     form: FormikContextType<V>;
19     renderExtraActions?: (helpers: {
20         handleRemove: (idx: number) => () => void;
21         handleAdd: (url: string) => void;
22         handleReplace: (idx: number) => (url: string) => void;
23     }) => ReactNode;
26 export const createNewUrl = (url: string) => ({ id: uniqueId(), url: sanitizeURL(url).valid ? url : '' });
28 export const UrlGroupField = <T extends UrlGroupValues>({ form, renderExtraActions }: UrlGroupProps<T>) => {
29     const inputRef = useRef<HTMLInputElement>(null);
30     const { values, errors, handleChange } = form;
32     const onKeyEnter = (event: KeyboardEvent<HTMLInputElement>) => {
33         if (event.key === 'Enter') {
34             event.preventDefault(); /* avoid submitting the form */
35             event.currentTarget.blur();
36         }
37     };
39     const hasURL = Boolean(values.url) || values.urls.some(({ url }) => !isEmptyString(url));
41     return (
42         <FieldBox icon="earth">
43             <label
44                 htmlFor="next-url-field"
45                 className="field-two-label text-sm"
46                 style={{ color: hasURL ? 'var(--text-weak)' : 'inherit' }}
47             >
48                 {c('Label').t`Websites`}
49             </label>
51             <FieldArray
52                 name="urls"
53                 render={(helpers) => {
54                     const handleRemove = helpers.handleRemove;
56                     const handleReplace = (index: number) => (url: string) =>
57                         helpers.replace(index, { id: values.urls[index].id, url });
59                     const handleAdd = (url: string) => {
60                         helpers.push(createNewUrl(sanitizeURL(url).url));
61                         return form.setFieldValue('url', '');
62                     };
64                     return (
65                         <>
66                             <ul className="unstyled m-0 mb-1">
67                                 {values.urls.map(({ url, id }, index) => (
68                                     <li key={id} className="flex items-center flex-nowrap">
69                                         <InputFieldTwo
70                                             error={(errors.urls?.[index] as FormikErrors<UrlItem>)?.url}
71                                             onValue={handleReplace(index)}
72                                             onBlur={() => handleReplace(index)(sanitizeURL(url).url)}
73                                             value={url}
74                                             unstyled
75                                             assistContainerClassName="empty:hidden"
76                                             inputClassName="color-norm p-0 rounded-none"
77                                             placeholder="https://"
78                                             onKeyDown={onKeyEnter}
79                                         />
80                                         <Button
81                                             icon
82                                             pill
83                                             className="shrink-0 ml-2"
84                                             color="weak"
85                                             onClick={handleRemove(index)}
86                                             shape="ghost"
87                                             size="small"
88                                             title={c('Action').t`Delete`}
89                                         >
90                                             <Icon name="cross" size={5} className="color-weak" />
91                                         </Button>
92                                     </li>
93                                 ))}
94                             </ul>
96                             <InputFieldTwo
97                                 unstyled
98                                 id="next-url-field"
99                                 assistContainerClassName="empty:hidden"
100                                 inputClassName="color-norm p-0 rounded-none"
101                                 placeholder="https://"
102                                 name="url"
103                                 value={values.url}
104                                 error={maybeErrorMessage(errors.url)}
105                                 onChange={handleChange}
106                                 onBlur={() => values.url && !errors.url && handleAdd(values.url)}
107                                 onKeyDown={onKeyEnter}
108                                 ref={inputRef}
109                             />
111                             <hr className="mt-3 mb-1" />
113                             {renderExtraActions?.({ handleAdd, handleRemove, handleReplace })}
115                             <Button
116                                 icon
117                                 color="norm"
118                                 shape="ghost"
119                                 size="small"
120                                 title={c('Action').t`Add`}
121                                 className="flex items-center gap-1"
122                                 onClick={() => handleAdd(values.url).then(() => inputRef.current?.focus())}
123                             >
124                                 <Icon name="plus" /> {c('Action').t`Add`}
125                             </Button>
126                         </>
127                     );
128                 }}
129             />
130         </FieldBox>
131     );