@cloud-ru/uikit-product-fields-predefined 0.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/CHANGELOG.md +935 -0
  2. package/LICENSE +201 -0
  3. package/README.md +151 -0
  4. package/package.json +63 -0
  5. package/src/components/AIDisclaimer/AIDisclaimer.tsx +18 -0
  6. package/src/components/AIDisclaimer/index.ts +1 -0
  7. package/src/components/AIDisclaimer/styles.module.scss +19 -0
  8. package/src/components/FieldAi/FieldAi.tsx +145 -0
  9. package/src/components/FieldAi/components/CheckItem/CheckItem.tsx +44 -0
  10. package/src/components/FieldAi/components/CheckItem/index.ts +1 -0
  11. package/src/components/FieldAi/components/CheckItem/styles.module.scss +28 -0
  12. package/src/components/FieldAi/components/MobileFieldAi/MobileFieldAi.tsx +57 -0
  13. package/src/components/FieldAi/components/MobileFieldAi/index.ts +1 -0
  14. package/src/components/FieldAi/components/MobileFieldAi/styles.module.scss +81 -0
  15. package/src/components/FieldAi/components/PasswordValidation/PasswordValidation.tsx +78 -0
  16. package/src/components/FieldAi/components/PasswordValidation/index.ts +1 -0
  17. package/src/components/FieldAi/components/PasswordValidation/styles.module.scss +32 -0
  18. package/src/components/FieldAi/components/TextArea/TextArea.tsx +113 -0
  19. package/src/components/FieldAi/components/TextArea/index.ts +1 -0
  20. package/src/components/FieldAi/components/TextArea/styles.module.scss +35 -0
  21. package/src/components/FieldAi/components/WithPasswordValidation/WithPasswordValidation.tsx +51 -0
  22. package/src/components/FieldAi/components/WithPasswordValidation/index.ts +1 -0
  23. package/src/components/FieldAi/components/WithPasswordValidation/styles.module.scss +7 -0
  24. package/src/components/FieldAi/index.ts +1 -0
  25. package/src/components/FieldAi/styles.module.scss +33 -0
  26. package/src/components/FieldAi/utils.ts +25 -0
  27. package/src/components/FieldChat/FieldChat.tsx +101 -0
  28. package/src/components/FieldChat/components/Attachments/Attachments.tsx +32 -0
  29. package/src/components/FieldChat/components/Attachments/index.ts +1 -0
  30. package/src/components/FieldChat/components/Attachments/styles.module.scss +9 -0
  31. package/src/components/FieldChat/index.ts +1 -0
  32. package/src/components/FieldChat/styles.module.scss +13 -0
  33. package/src/components/FieldPhone/FieldPhone.tsx +226 -0
  34. package/src/components/FieldPhone/__tests__/constants.ts +26 -0
  35. package/src/components/FieldPhone/__tests__/formatPhoneNumber.spec.ts +15 -0
  36. package/src/components/FieldPhone/__tests__/matchMedia.ts +15 -0
  37. package/src/components/FieldPhone/constants.ts +1 -0
  38. package/src/components/FieldPhone/countries.tsx +1755 -0
  39. package/src/components/FieldPhone/hooks/index.ts +2 -0
  40. package/src/components/FieldPhone/hooks/useCountries.ts +40 -0
  41. package/src/components/FieldPhone/hooks/useMapCountryToOptions.ts +25 -0
  42. package/src/components/FieldPhone/index.ts +7 -0
  43. package/src/components/FieldPhone/styles.module.scss +9 -0
  44. package/src/components/FieldPhone/types.ts +32 -0
  45. package/src/components/FieldPhone/utils.ts +71 -0
  46. package/src/components/SelectCreate/SelectCreate.tsx +122 -0
  47. package/src/components/SelectCreate/SelectFooter/SelectFooter.tsx +29 -0
  48. package/src/components/SelectCreate/SelectFooter/index.ts +1 -0
  49. package/src/components/SelectCreate/SelectFooter/styles.module.scss +9 -0
  50. package/src/components/SelectCreate/index.ts +1 -0
  51. package/src/components/SelectCreate/types.ts +35 -0
  52. package/src/components/SelectCreate/useSelectDataStates.tsx +53 -0
  53. package/src/components/index.ts +5 -0
  54. package/src/helperComponents/FieldSubmitButton/FieldSubmitButton.tsx +42 -0
  55. package/src/helperComponents/FieldSubmitButton/index.ts +1 -0
  56. package/src/helperComponents/TextAreaActionsFooter/TextAreaActionsFooter.tsx +18 -0
  57. package/src/helperComponents/TextAreaActionsFooter/index.ts +1 -0
  58. package/src/helperComponents/TextAreaActionsFooter/styles.module.scss +23 -0
  59. package/src/helpers/capitalize.ts +3 -0
  60. package/src/helpers/getSymbolsRangeFromMask.ts +17 -0
  61. package/src/helpers/index.ts +3 -0
  62. package/src/helpers/isTouchDevice.ts +5 -0
  63. package/src/hooks/index.ts +1 -0
  64. package/src/hooks/useOpen.ts +22 -0
  65. package/src/index.ts +3 -0
@@ -0,0 +1,28 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables' as ste;
2
+
3
+ .checkItem {
4
+ display: inline-flex;
5
+ gap: 10px;
6
+
7
+ &[data-layout-type='mobile'],
8
+ &[data-layout-type='tablet'] {
9
+ gap: 8px;
10
+ }
11
+ }
12
+
13
+ .icon {
14
+ flex-shrink: 0;
15
+ color: ste.$sys-neutral-accent-default;
16
+
17
+ &[data-checked='true'] {
18
+ color: ste.$sys-primary-text-light;
19
+ }
20
+ }
21
+
22
+ .label {
23
+ color: ste.$sys-neutral-text-light;
24
+
25
+ &[data-checked='true'] {
26
+ color: ste.$sys-primary-text-light;
27
+ }
28
+ }
@@ -0,0 +1,57 @@
1
+ import { forwardRef } from 'react';
2
+
3
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
4
+ import { FieldTextAreaProps } from '@cloud-ru/uikit-product-mobile-fields';
5
+ import { Scroll } from '@snack-uikit/scroll';
6
+
7
+ import { FieldSubmitButton } from '../../../../helperComponents/FieldSubmitButton';
8
+ import { TextArea } from '../TextArea';
9
+ import styles from './styles.module.scss';
10
+
11
+ type MobileFieldAiProps = Omit<
12
+ FieldTextAreaProps,
13
+ 'placeholder' | 'labelTooltip' | 'label' | 'required' | 'size' | 'spellCheck' | 'footer'
14
+ > & {
15
+ onSubmit(): void;
16
+ submitEnabled: boolean;
17
+ };
18
+
19
+ const MIN_ROWS = 1;
20
+ const MAX_ROWS = 6;
21
+
22
+ export const MobileFieldAi = forwardRef<HTMLTextAreaElement, MobileFieldAiProps>(
23
+ ({ onSubmit, value, submitEnabled, ...props }, ref) => {
24
+ const { t } = useLocale('FieldsPredefined');
25
+
26
+ return (
27
+ <div
28
+ className={styles.mobileInputWrapper}
29
+ style={{ '--max-rows': MAX_ROWS, '--min-rows': MIN_ROWS }}
30
+ data-size='m'
31
+ >
32
+ <Scroll className={styles.scrollContainer} size='s' barHideStrategy='never'>
33
+ <TextArea
34
+ {...props}
35
+ className={styles.textarea}
36
+ ref={ref}
37
+ value={value}
38
+ minRows={MIN_ROWS}
39
+ placeholder={t('FieldAi.regular.placeholder')}
40
+ spellCheck={true}
41
+ />
42
+ </Scroll>
43
+
44
+ <div className={styles.mobileSubmitButtonWrapper}>
45
+ <FieldSubmitButton
46
+ showTooltip={false}
47
+ className={styles.mobileSubmitButton}
48
+ fullWidth={true}
49
+ active={submitEnabled}
50
+ handleClick={onSubmit}
51
+ size='s'
52
+ />
53
+ </div>
54
+ </div>
55
+ );
56
+ },
57
+ );
@@ -0,0 +1 @@
1
+ export * from './MobileFieldAi';
@@ -0,0 +1,81 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/components/styles-tokens-fields' as ste;
2
+
3
+ $padding-right: (
4
+ 's': ste.$fields-buttons-s,
5
+ 'm': ste.$fields-buttons-m,
6
+ 'l': ste.$fields-buttons-m,
7
+ );
8
+ $scroll-bar-heights: (
9
+ 's': ste.$dimension-050m,
10
+ 'm': calc(#{ste.$dimension-025m} + #{ste.$dimension-050m}),
11
+ 'l': ste.$dimension-1m,
12
+ );
13
+ $sizes: 's', 'm', 'l';
14
+
15
+ .scrollContainer {
16
+ border-radius: 4px;
17
+ padding: 6px 0;
18
+
19
+ [data-overlayscrollbars-contents] {
20
+ display: flex;
21
+ }
22
+ }
23
+
24
+ .textarea {
25
+ overflow: hidden;
26
+ }
27
+
28
+ .mobileInputWrapper {
29
+ --max-rows: 1000;
30
+ --min-rows: 3;
31
+
32
+ display: flex;
33
+ position: relative;
34
+ gap: 8px;
35
+
36
+ .scrollContainer {
37
+ min-height: calc(var(--min-rows) * var(--row-height) + var(--horizontal-scroll-bar-height));
38
+ max-height: calc(var(--max-rows) * var(--row-height) + var(--horizontal-scroll-bar-height));
39
+ }
40
+
41
+ @each $size in $sizes {
42
+ &[data-size='#{$size}'] {
43
+ --row-height: #{ste.simple-var(ste.$theme-variables, 'sans', 'body', $size, 'line-height')};
44
+ --horizontal-scroll-bar-height: #{ste.simple-var($scroll-bar-heights, $size)};
45
+
46
+ .textarea {
47
+ @include ste.composite-var(ste.$theme-variables, 'sans', 'body', $size);
48
+
49
+ padding-right: calc(42px + ste.simple-var(ste.$fields-scroll-bar-width, 'width'));
50
+ }
51
+ }
52
+ }
53
+ }
54
+
55
+ .mobileSubmitButtonWrapper {
56
+ width: 32px;
57
+ height: 32px;
58
+ display: flex;
59
+ flex-shrink: 0;
60
+ position: absolute;
61
+ bottom: 0;
62
+ right: 10px;
63
+ }
64
+
65
+ .mobileSubmitButton {
66
+ /* stylelint-disable-next-line declaration-no-important */
67
+ position: relative !important;
68
+ /* stylelint-disable-next-line declaration-no-important */
69
+ flex-shrink: 0 !important;
70
+
71
+ &:after {
72
+ content: '';
73
+ display: flex;
74
+ width: 48px;
75
+ height: 48px;
76
+ position: absolute;
77
+ top: 50%;
78
+ left: 50%;
79
+ transform: translate(-50%, -50%);
80
+ }
81
+ }
@@ -0,0 +1,78 @@
1
+ import { useMemo } from 'react';
2
+
3
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
4
+ import { WithLayoutType } from '@cloud-ru/uikit-product-utils';
5
+
6
+ import { isTouchDevice as isTouchDeviceHelper } from '../../../../helpers';
7
+ import { ValidationPassword } from '../../utils';
8
+ import { CheckItem } from '../CheckItem';
9
+ import styles from './styles.module.scss';
10
+
11
+ export type WithPasswordTooltipProps = WithLayoutType<{
12
+ passwordValidation: ValidationPassword;
13
+ }>;
14
+
15
+ export function PasswordValidation({ passwordValidation, layoutType }: WithPasswordTooltipProps) {
16
+ const { t } = useLocale('FieldsPredefined');
17
+
18
+ const allCriteriaMet = useMemo(() => Object.values(passwordValidation).every(item => item), [passwordValidation]);
19
+ const isTouchDevice = isTouchDeviceHelper(layoutType);
20
+
21
+ if (isTouchDevice) {
22
+ if (allCriteriaMet) {
23
+ return (
24
+ <div className={styles.validationItemsContainer} data-layout-type={layoutType}>
25
+ <CheckItem
26
+ checked={true}
27
+ label={t('FieldAi.secret.passwordTooltip.titleSuccess')}
28
+ layoutType={layoutType}
29
+ shouldHide={false}
30
+ />
31
+ </div>
32
+ );
33
+ }
34
+ }
35
+
36
+ return (
37
+ <div className={styles.validationItemsContainer} data-layout-type={layoutType}>
38
+ <div className={styles.validationList}>
39
+ <CheckItem
40
+ checked={passwordValidation.minLength}
41
+ label={t('FieldAi.secret.passwordTooltip.minLength')}
42
+ layoutType={layoutType}
43
+ shouldHide={isTouchDevice}
44
+ />
45
+ <CheckItem
46
+ checked={passwordValidation.onlyLatin}
47
+ label={t('FieldAi.secret.passwordTooltip.onlyLatin')}
48
+ layoutType={layoutType}
49
+ shouldHide={isTouchDevice}
50
+ />
51
+ <CheckItem
52
+ checked={passwordValidation.hasLetterCases}
53
+ label={t('FieldAi.secret.passwordTooltip.hasLetterCases')}
54
+ layoutType={layoutType}
55
+ shouldHide={isTouchDevice}
56
+ />
57
+ <CheckItem
58
+ checked={passwordValidation.hasNumber}
59
+ label={t('FieldAi.secret.passwordTooltip.hasNumber')}
60
+ layoutType={layoutType}
61
+ shouldHide={isTouchDevice}
62
+ />
63
+ <CheckItem
64
+ checked={passwordValidation.hasSymbol}
65
+ label={t('FieldAi.secret.passwordTooltip.hasSymbol')}
66
+ layoutType={layoutType}
67
+ shouldHide={isTouchDevice}
68
+ />
69
+ <CheckItem
70
+ checked={passwordValidation.noSpaces}
71
+ label={t('FieldAi.secret.passwordTooltip.noSpaces')}
72
+ layoutType={layoutType}
73
+ shouldHide={isTouchDevice}
74
+ />
75
+ </div>
76
+ </div>
77
+ );
78
+ }
@@ -0,0 +1 @@
1
+ export * from './PasswordValidation';
@@ -0,0 +1,32 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables' as ste;
2
+
3
+ .tooltipText {
4
+ display: flex;
5
+ flex-direction: column;
6
+ }
7
+
8
+ .validationList {
9
+ display: flex;
10
+ flex-direction: column;
11
+ gap: 12px;
12
+ }
13
+
14
+ .validationItemsContainer {
15
+ @include ste.composite-var(ste.$sans-body-m);
16
+ color: ste.$sys-neutral-text-main;
17
+
18
+ display: flex;
19
+ flex-direction: column;
20
+ gap: 12px;
21
+
22
+ &[data-layout-type='mobile'],
23
+ &[data-layout-type='tablet'] {
24
+ gap: 8px;
25
+ @include ste.composite-var(ste.$sans-body-s);
26
+
27
+ .validationList {
28
+ gap: 2px;
29
+ }
30
+ }
31
+ }
32
+
@@ -0,0 +1,113 @@
1
+ import cn from 'classnames';
2
+ import {
3
+ ChangeEvent,
4
+ ChangeEventHandler,
5
+ FocusEventHandler,
6
+ forwardRef,
7
+ KeyboardEventHandler,
8
+ MouseEventHandler,
9
+ RefAttributes,
10
+ useState,
11
+ } from 'react';
12
+ import TextareaAutosize from 'react-textarea-autosize';
13
+
14
+ import { InputPrivateProps } from '@snack-uikit/input-private';
15
+ import { extractSupportProps, useLayoutEffect, WithSupportProps } from '@snack-uikit/utils';
16
+
17
+ import styles from './styles.module.scss';
18
+
19
+ export type TextAreaProps = RefAttributes<HTMLTextAreaElement> &
20
+ WithSupportProps<{
21
+ /** HTML-аттрибут name */
22
+ name?: string;
23
+ /** HTML-аттрибут value */
24
+ value?: string;
25
+ /** Колбек смены значения */
26
+ onChange?(value: string, e: ChangeEvent<HTMLTextAreaElement>): void;
27
+ /** HTML-аттрибут id */
28
+ id?: string;
29
+ className?: string;
30
+ /** Плейсхолдер */
31
+ placeholder?: string;
32
+ /** Является ли поле доступным только на чтение */
33
+ readonly?: boolean;
34
+ /** Является ли поле деактивированным */
35
+ disabled?: boolean;
36
+ /** Включен ли автокомплит */
37
+ autoComplete?: boolean;
38
+ /** Максимальное кол-во символов */
39
+ maxLength?: number;
40
+ /** Колбек получения фокуса */
41
+ onFocus?: FocusEventHandler<HTMLTextAreaElement>;
42
+ /** Колбек потери фокуса */
43
+ onBlur?: FocusEventHandler<HTMLTextAreaElement>;
44
+ /** Колбек нажатия клавиши клавиатуры */
45
+ onKeyDown?: KeyboardEventHandler<HTMLTextAreaElement>;
46
+ /** HTML-аттрибут tab-index */
47
+ tabIndex?: number;
48
+ /** Минимальное кол-во строк, до которого размер поля может быть увеличен @default 3*/
49
+ minRows?: number;
50
+ /** Включение проверки орфографии @default true*/
51
+ spellCheck?: boolean;
52
+ /** Режим работы экранной клавиатуры */
53
+ inputMode?: InputPrivateProps['inputMode'];
54
+ }>;
55
+
56
+ export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
57
+ (
58
+ {
59
+ name,
60
+ value = '',
61
+ onChange,
62
+ placeholder,
63
+ id,
64
+ className,
65
+ disabled = false,
66
+ readonly = false,
67
+ autoComplete = false,
68
+ maxLength,
69
+ onFocus,
70
+ onBlur,
71
+ onKeyDown,
72
+ tabIndex,
73
+ minRows = 3,
74
+ spellCheck,
75
+ inputMode,
76
+ ...rest
77
+ },
78
+ ref,
79
+ ) => {
80
+ // fix of height on the initial render
81
+ // see https://github.com/Andarist/react-textarea-autosize/issues/337#issuecomment-1024980737
82
+ const [, setIsRerendered] = useState(false);
83
+ useLayoutEffect(() => setIsRerendered(true), []);
84
+
85
+ const onChangeHandler: ChangeEventHandler<HTMLTextAreaElement> = e => onChange?.(e.target.value, e);
86
+ const stopPropagation: MouseEventHandler<HTMLTextAreaElement> = e => e.stopPropagation();
87
+
88
+ return (
89
+ <TextareaAutosize
90
+ id={id}
91
+ name={name}
92
+ value={value}
93
+ ref={ref}
94
+ className={cn(className, styles.textarea)}
95
+ autoComplete={autoComplete ? 'on' : 'off'}
96
+ placeholder={placeholder}
97
+ disabled={disabled}
98
+ readOnly={readonly}
99
+ maxLength={maxLength}
100
+ onChange={onChangeHandler}
101
+ onClick={stopPropagation}
102
+ onFocus={onFocus}
103
+ onBlur={onBlur}
104
+ onKeyDown={onKeyDown}
105
+ tabIndex={tabIndex}
106
+ minRows={minRows}
107
+ spellCheck={spellCheck}
108
+ inputMode={inputMode}
109
+ {...extractSupportProps(rest)}
110
+ />
111
+ );
112
+ },
113
+ );
@@ -0,0 +1 @@
1
+ export * from './TextArea';
@@ -0,0 +1,35 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/components/styles-tokens-element' as ste;
2
+
3
+ .textarea {
4
+ resize: none;
5
+
6
+ box-sizing: border-box;
7
+ width: 100%;
8
+ max-width: 100%;
9
+ margin: 0;
10
+ padding: 0;
11
+
12
+ color: ste.simple-var(ste.$sys-neutral-text-main);
13
+
14
+ background-color: transparent;
15
+ border: none;
16
+ border-radius: 0;
17
+ outline: 0;
18
+
19
+ &::placeholder {
20
+ color: ste.simple-var(ste.$sys-neutral-text-disabled);
21
+ }
22
+
23
+ &::-webkit-scrollbar {
24
+ width: 0;
25
+ max-width: 0;
26
+ }
27
+
28
+ &:read-only {
29
+ color: ste.simple-var(ste.$sys-neutral-text-support);
30
+ }
31
+
32
+ &[disabled] {
33
+ color: ste.simple-var(ste.$sys-neutral-text-disabled);
34
+ }
35
+ }
@@ -0,0 +1,51 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ import { AdaptiveTooltip } from '@cloud-ru/uikit-product-mobile-tooltip';
4
+ import { WithLayoutType } from '@cloud-ru/uikit-product-utils';
5
+
6
+ import { isTouchDevice } from '../../../../helpers';
7
+ import { ValidationPassword } from '../../utils';
8
+ import { PasswordValidation } from '../PasswordValidation';
9
+ import styles from './styles.module.scss';
10
+
11
+ export type WithPasswordTooltipProps = WithLayoutType<{
12
+ showValidation?: boolean;
13
+ children: ReactNode;
14
+ passwordValidation: ValidationPassword;
15
+ }>;
16
+
17
+ export function WithPasswordValidation({
18
+ showValidation,
19
+ passwordValidation,
20
+ layoutType,
21
+ children,
22
+ }: WithPasswordTooltipProps) {
23
+ if (isTouchDevice(layoutType)) {
24
+ if (showValidation) {
25
+ return (
26
+ <div className={styles.validationContainer}>
27
+ <PasswordValidation passwordValidation={passwordValidation} layoutType={layoutType} />
28
+
29
+ {children}
30
+ </div>
31
+ );
32
+ }
33
+
34
+ return children;
35
+ }
36
+
37
+ if (showValidation) {
38
+ return (
39
+ <AdaptiveTooltip
40
+ placement={'left-end'}
41
+ layoutType={layoutType}
42
+ tip={<PasswordValidation passwordValidation={passwordValidation} layoutType={layoutType} />}
43
+ offset={8}
44
+ >
45
+ {children}
46
+ </AdaptiveTooltip>
47
+ );
48
+ }
49
+
50
+ return children;
51
+ }
@@ -0,0 +1 @@
1
+ export * from './WithPasswordValidation';
@@ -0,0 +1,7 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables' as ste;
2
+
3
+ .validationContainer {
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: 8px;
7
+ }
@@ -0,0 +1 @@
1
+ export * from './FieldAi';
@@ -0,0 +1,33 @@
1
+ @use '@sbercloud/figma-tokens-cloud-platform/build/scss/components/styles-tokens-fields' as ste;
2
+
3
+ .wrapper {
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: ste.$dimension-050m;
7
+ }
8
+
9
+ .secured {
10
+ textarea {
11
+ -webkit-text-security: disc;
12
+ /* stylelint-disable-next-line property-no-unknown */
13
+ text-security: disc;
14
+ }
15
+ }
16
+
17
+ .mobileSubmitButton {
18
+ /* stylelint-disable-next-line declaration-no-important */
19
+ position: relative !important;
20
+ /* stylelint-disable-next-line declaration-no-important */
21
+ flex-shrink: 0 !important;
22
+
23
+ &:after {
24
+ content: '';
25
+ display: flex;
26
+ width: 48px;
27
+ height: 48px;
28
+ position: absolute;
29
+ top: 50%;
30
+ left: 50%;
31
+ transform: translate(-50%, -50%);
32
+ }
33
+ }
@@ -0,0 +1,25 @@
1
+ export type ValidationPassword = {
2
+ onlyLatin: boolean;
3
+ minLength: boolean;
4
+ hasLetterCases: boolean;
5
+ hasNumber: boolean;
6
+ hasSymbol: boolean;
7
+ noSpaces: boolean;
8
+ };
9
+
10
+ const MIN_PASSWORD_LENGTH = 8;
11
+ const NUMBER_REGEX = /[0-9]/;
12
+ const CAPITAL_REGEX = /\p{Lu}/u;
13
+ const LOWER_REGEX = /\p{Ll}/u;
14
+ const SYMBOLS_REGEX = /[\p{P}\p{S}]/u;
15
+ const NO_SPACES = /^\S*$/u;
16
+ const LATIN_REGEX = /^[a-zA-Z0-9\p{P}\p{S}]+$/u;
17
+
18
+ export const getValidationPassword = (password: string = ''): ValidationPassword => ({
19
+ onlyLatin: Boolean(password.match(LATIN_REGEX)),
20
+ minLength: password.length >= MIN_PASSWORD_LENGTH,
21
+ hasLetterCases: Boolean(password.match(CAPITAL_REGEX)) && Boolean(password.match(LOWER_REGEX)),
22
+ hasNumber: Boolean(password.match(NUMBER_REGEX)),
23
+ hasSymbol: Boolean(password.match(SYMBOLS_REGEX)),
24
+ noSpaces: Boolean(password.match(NO_SPACES)),
25
+ });
@@ -0,0 +1,101 @@
1
+ import { KeyboardEventHandler, useMemo } from 'react';
2
+
3
+ import { AttachmentSVG } from '@cloud-ru/uikit-product-icons';
4
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
5
+ import { AdaptiveFieldTextArea, FieldTextAreaProps } from '@cloud-ru/uikit-product-mobile-fields';
6
+ import { WithLayoutType } from '@cloud-ru/uikit-product-utils';
7
+ import { AttachmentSquareProps } from '@snack-uikit/attachment';
8
+ import { ButtonFunction } from '@snack-uikit/button';
9
+ import { FileUpload, FileUploadProps } from '@snack-uikit/drop-zone';
10
+ import { Tooltip } from '@snack-uikit/tooltip';
11
+
12
+ import { FieldSubmitButton } from '../../helperComponents/FieldSubmitButton';
13
+ import { TextAreaActionsFooter } from '../../helperComponents/TextAreaActionsFooter';
14
+ import { Attachments } from './components/Attachments';
15
+ import styles from './styles.module.scss';
16
+
17
+ export type FieldChatProps = WithLayoutType<
18
+ Omit<
19
+ FieldTextAreaProps,
20
+ 'placeholder' | 'hint' | 'labelTooltip' | 'label' | 'required' | 'size' | 'spellCheck' | 'footer'
21
+ > & {
22
+ /** Колбек действия при отправке */
23
+ handleSubmit(value: string): void;
24
+ attachment?: Pick<FileUploadProps, 'onFilesUpload' | 'accept'> & {
25
+ /** Список загруженных файлов */
26
+ files?: File[];
27
+ /** Колбек действия при удалении прикрепленного файла */
28
+ onFileDelete: AttachmentSquareProps['onDelete'];
29
+ };
30
+ }
31
+ >;
32
+
33
+ export function FieldChat({ handleSubmit: handleSubmitProp, value, layoutType, attachment, ...props }: FieldChatProps) {
34
+ const { t } = useLocale('FieldsPredefined');
35
+
36
+ const isMobile = layoutType === 'mobile';
37
+
38
+ const files = useMemo<AttachmentSquareProps[]>(
39
+ () =>
40
+ attachment?.files?.map(file => ({
41
+ file,
42
+ onDelete: attachment?.onFileDelete,
43
+ })) ?? [],
44
+ [attachment?.files, attachment?.onFileDelete],
45
+ );
46
+
47
+ const isValueValid = (typeof value === 'string' && value.trim().length > 0) || files.length > 0;
48
+
49
+ const handleSubmit = () => {
50
+ if (isValueValid) {
51
+ handleSubmitProp(value ?? '');
52
+ }
53
+ };
54
+
55
+ const handleKeyDown: KeyboardEventHandler<HTMLTextAreaElement> = e => {
56
+ if (e.key === 'Enter' && !e.shiftKey) {
57
+ e.preventDefault();
58
+ handleSubmit();
59
+ return;
60
+ }
61
+ };
62
+
63
+ return (
64
+ <div className={styles.fieldChat} data-layout-type={layoutType}>
65
+ {isMobile && <Attachments files={files} isMobile={isMobile} />}
66
+
67
+ <AdaptiveFieldTextArea
68
+ {...props}
69
+ layoutType={layoutType}
70
+ size={isMobile ? 'l' : 'm'}
71
+ minRows={1}
72
+ maxRows={4}
73
+ placeholder={t('FieldChat.placeholder')}
74
+ onKeyDown={handleKeyDown}
75
+ footer={
76
+ <TextAreaActionsFooter
77
+ right={
78
+ <>
79
+ {attachment && (
80
+ <Tooltip
81
+ tip={t('FieldChat.attachFileTooltip')}
82
+ hoverDelayOpen={600}
83
+ triggerClassName={styles.uploadTooltip}
84
+ >
85
+ <FileUpload mode='multiple' onFilesUpload={attachment.onFilesUpload} accept={attachment.accept}>
86
+ <ButtonFunction size={isMobile ? 's' : 'xs'} icon={<AttachmentSVG />} />
87
+ </FileUpload>
88
+ </Tooltip>
89
+ )}
90
+
91
+ <FieldSubmitButton active={isValueValid} handleClick={handleSubmit} size={isMobile ? 's' : 'xs'} />
92
+ </>
93
+ }
94
+ />
95
+ }
96
+ />
97
+
98
+ {!isMobile && <Attachments files={files} />}
99
+ </div>
100
+ );
101
+ }