@cloud-ru/uikit-product-fields-predefined 0.14.0 → 0.15.0

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 (29) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +239 -3
  3. package/dist/cjs/components/FieldAi/FieldAi.js +26 -4
  4. package/dist/cjs/components/FieldAi/components/CheckItem/CheckItem.d.ts +2 -1
  5. package/dist/cjs/components/FieldAi/components/CheckItem/CheckItem.js +2 -2
  6. package/dist/cjs/components/FieldAi/components/CheckItem/styles.module.css +14 -0
  7. package/dist/cjs/components/FieldAi/components/PasswordValidation/PasswordValidation.d.ts +3 -2
  8. package/dist/cjs/components/FieldAi/components/PasswordValidation/PasswordValidation.js +2 -2
  9. package/dist/cjs/components/FieldAi/components/WithPasswordValidation/WithPasswordValidation.d.ts +3 -2
  10. package/dist/cjs/components/FieldAi/components/WithPasswordValidation/WithPasswordValidation.js +3 -3
  11. package/dist/cjs/components/FieldAi/utils.d.ts +1 -0
  12. package/dist/esm/components/FieldAi/FieldAi.js +27 -5
  13. package/dist/esm/components/FieldAi/components/CheckItem/CheckItem.d.ts +2 -1
  14. package/dist/esm/components/FieldAi/components/CheckItem/CheckItem.js +2 -2
  15. package/dist/esm/components/FieldAi/components/CheckItem/styles.module.css +14 -0
  16. package/dist/esm/components/FieldAi/components/PasswordValidation/PasswordValidation.d.ts +3 -2
  17. package/dist/esm/components/FieldAi/components/PasswordValidation/PasswordValidation.js +2 -2
  18. package/dist/esm/components/FieldAi/components/WithPasswordValidation/WithPasswordValidation.d.ts +3 -2
  19. package/dist/esm/components/FieldAi/components/WithPasswordValidation/WithPasswordValidation.js +3 -3
  20. package/dist/esm/components/FieldAi/utils.d.ts +1 -0
  21. package/dist/tsconfig.cjs.tsbuildinfo +1 -1
  22. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  23. package/package.json +8 -8
  24. package/src/components/FieldAi/FieldAi.tsx +36 -5
  25. package/src/components/FieldAi/components/CheckItem/CheckItem.tsx +3 -2
  26. package/src/components/FieldAi/components/CheckItem/styles.module.scss +16 -0
  27. package/src/components/FieldAi/components/PasswordValidation/PasswordValidation.tsx +9 -2
  28. package/src/components/FieldAi/components/WithPasswordValidation/WithPasswordValidation.tsx +15 -3
  29. package/src/components/FieldAi/utils.ts +2 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cloud-ru/uikit-product-fields-predefined",
3
3
  "title": "Fields Predefined",
4
- "version": "0.14.0",
4
+ "version": "0.15.0",
5
5
  "sideEffects": [
6
6
  "*.css",
7
7
  "*.woff",
@@ -32,12 +32,12 @@
32
32
  },
33
33
  "scripts": {},
34
34
  "dependencies": {
35
- "@cloud-ru/uikit-product-icons": "16.0.0",
36
- "@cloud-ru/uikit-product-mobile-dropdown": "0.9.28",
37
- "@cloud-ru/uikit-product-mobile-fields": "0.12.0",
38
- "@cloud-ru/uikit-product-mobile-modal": "0.9.25",
39
- "@cloud-ru/uikit-product-mobile-tooltip": "0.5.1",
40
- "@cloud-ru/uikit-product-utils": "8.0.1",
35
+ "@cloud-ru/uikit-product-icons": "16.0.1",
36
+ "@cloud-ru/uikit-product-mobile-dropdown": "0.9.29",
37
+ "@cloud-ru/uikit-product-mobile-fields": "0.12.1",
38
+ "@cloud-ru/uikit-product-mobile-modal": "0.9.26",
39
+ "@cloud-ru/uikit-product-mobile-tooltip": "0.5.2",
40
+ "@cloud-ru/uikit-product-utils": "8.0.2",
41
41
  "@snack-uikit/attachment": "0.4.10",
42
42
  "@snack-uikit/button": "0.19.16",
43
43
  "@snack-uikit/drop-zone": "0.9.6",
@@ -61,5 +61,5 @@
61
61
  "react-hook-form": ">=7.51.0",
62
62
  "yup": ">=0.32.0"
63
63
  },
64
- "gitHead": "6366d4a4912c09f95af6ba794df7b5a246893762"
64
+ "gitHead": "a8efe861f537e7d5b0bb7dbadff99c57fd612720"
65
65
  }
@@ -1,5 +1,5 @@
1
1
  import cn from 'classnames';
2
- import { forwardRef, KeyboardEventHandler, useMemo, useState } from 'react';
2
+ import { forwardRef, KeyboardEventHandler, useEffect, useMemo, useRef, useState } from 'react';
3
3
 
4
4
  import { EyeClosedSVG, EyeSVG } from '@cloud-ru/uikit-product-icons';
5
5
  import { useLocale } from '@cloud-ru/uikit-product-locale';
@@ -19,7 +19,7 @@ import { AIDisclaimer } from '../AIDisclaimer/AIDisclaimer';
19
19
  import { MobileFieldAi } from './components/MobileFieldAi';
20
20
  import { WithPasswordValidation } from './components/WithPasswordValidation';
21
21
  import styles from './styles.module.scss';
22
- import { getValidationPassword } from './utils';
22
+ import { getValidationPassword, ValidationPasswordKey } from './utils';
23
23
 
24
24
  export type FieldAiProps = WithLayoutType<
25
25
  Omit<FieldTextAreaProps, 'placeholder' | 'labelTooltip' | 'label' | 'required' | 'size' | 'spellCheck' | 'footer'> & {
@@ -39,6 +39,8 @@ export const FieldAi = forwardRef<HTMLTextAreaElement, FieldAiProps>(
39
39
  const isTouchDevice = isTouchDeviceHelper(layoutType);
40
40
 
41
41
  const [isValueHidden, setIsValueHidden] = useState<boolean>(true);
42
+ const [animatedValidationKey, setAnimatedValidationKey] = useState<ValidationPasswordKey | null>(null);
43
+ const timerRef = useRef<NodeJS.Timeout | null>(null);
42
44
 
43
45
  const isValueValid = typeof value === 'string' && value.trim().length > 0;
44
46
  const isPasswordMode = secure === 'password';
@@ -47,19 +49,47 @@ export const FieldAi = forwardRef<HTMLTextAreaElement, FieldAiProps>(
47
49
  const isPasswordValid = isPasswordMode ? Object.values(passwordValidation).every(Boolean) : true;
48
50
  const showPasswordError = !isPasswordValid && secure && value;
49
51
 
52
+ useEffect(
53
+ () => () => {
54
+ if (timerRef.current) {
55
+ clearTimeout(timerRef.current);
56
+ }
57
+ },
58
+ [],
59
+ );
60
+
50
61
  const handleSubmit = () => {
51
62
  if (isValueValid && isPasswordValid) {
52
63
  handleSubmitProp(value);
53
64
  }
54
65
  };
55
66
 
56
- const handleKeyDown: KeyboardEventHandler<HTMLTextAreaElement> = e => {
67
+ const triggerValidationHighlight = (key: ValidationPasswordKey) => {
68
+ setAnimatedValidationKey(key);
69
+
70
+ timerRef.current = setTimeout(() => {
71
+ setAnimatedValidationKey(null);
72
+ }, 1000);
73
+ };
74
+
75
+ const handleKeyDown: KeyboardEventHandler<HTMLTextAreaElement> = event => {
57
76
  if (isTouchDevice) {
58
77
  return;
59
78
  }
60
79
 
61
- if (e.key === 'Enter' && !e.shiftKey) {
62
- e.preventDefault();
80
+ if (isPasswordMode && event.key.length === 1) {
81
+ const isLetter = /\p{L}/u.test(event.key);
82
+ const isLatinLetter = /^[a-zA-Z]$/.test(event.key);
83
+
84
+ if (isLetter && !isLatinLetter) {
85
+ event.preventDefault();
86
+ triggerValidationHighlight('onlyLatin');
87
+ return;
88
+ }
89
+ }
90
+
91
+ if (event.key === 'Enter' && !event.shiftKey) {
92
+ event.preventDefault();
63
93
 
64
94
  if (!disabled) {
65
95
  handleSubmit();
@@ -88,6 +118,7 @@ export const FieldAi = forwardRef<HTMLTextAreaElement, FieldAiProps>(
88
118
  showValidation={isPasswordMode}
89
119
  passwordValidation={passwordValidation}
90
120
  layoutType={layoutType}
121
+ animatedKey={animatedValidationKey}
91
122
  >
92
123
  <AdaptiveFieldTextArea
93
124
  {...props}
@@ -10,11 +10,12 @@ type CheckItemProps = WithLayoutType<{
10
10
  label: string;
11
11
  checked: boolean;
12
12
  shouldHide?: boolean;
13
+ animated?: boolean;
13
14
  }>;
14
15
 
15
16
  const CHECKED_ITEM_DISAPPEAR_TIMEOUT = 500;
16
17
 
17
- export function CheckItem({ label, checked, layoutType, shouldHide = false }: CheckItemProps) {
18
+ export function CheckItem({ label, checked, layoutType, shouldHide = false, animated = false }: CheckItemProps) {
18
19
  const [visible, setVisible] = useState(checked);
19
20
 
20
21
  useEffect(() => {
@@ -34,7 +35,7 @@ export function CheckItem({ label, checked, layoutType, shouldHide = false }: Ch
34
35
  }
35
36
 
36
37
  return (
37
- <div className={styles.checkItem} data-layout-type={layoutType}>
38
+ <div className={styles.checkItem} data-layout-type={layoutType} data-animated={animated}>
38
39
  <CheckSVG size={20} className={styles.icon} data-checked={checked} />
39
40
  <Typography.SansBodyM className={styles.label} data-checked={checked}>
40
41
  {label}
@@ -1,5 +1,17 @@
1
1
  @use '@sbercloud/figma-tokens-cloud-platform/build/scss/styles-theme-variables' as ste;
2
2
 
3
+ @keyframes shake {
4
+ 0%, 100% {
5
+ transform: translateX(0);
6
+ }
7
+ 10%, 30%, 50%, 70%, 90% {
8
+ transform: translateX(-4px);
9
+ }
10
+ 20%, 40%, 60%, 80% {
11
+ transform: translateX(4px);
12
+ }
13
+ }
14
+
3
15
  .checkItem {
4
16
  display: inline-flex;
5
17
  gap: 10px;
@@ -8,6 +20,10 @@
8
20
  &[data-layout-type='tablet'] {
9
21
  gap: 8px;
10
22
  }
23
+
24
+ &[data-animated='true'] {
25
+ animation: shake 0.5s ease-in-out;
26
+ }
11
27
  }
12
28
 
13
29
  .icon {
@@ -4,15 +4,16 @@ import { useLocale } from '@cloud-ru/uikit-product-locale';
4
4
  import { WithLayoutType } from '@cloud-ru/uikit-product-utils';
5
5
 
6
6
  import { isTouchDevice as isTouchDeviceHelper } from '../../../../helpers';
7
- import { ValidationPassword } from '../../utils';
7
+ import { ValidationPassword, ValidationPasswordKey } from '../../utils';
8
8
  import { CheckItem } from '../CheckItem';
9
9
  import styles from './styles.module.scss';
10
10
 
11
11
  export type WithPasswordTooltipProps = WithLayoutType<{
12
12
  passwordValidation: ValidationPassword;
13
+ animatedKey?: ValidationPasswordKey | null;
13
14
  }>;
14
15
 
15
- export function PasswordValidation({ passwordValidation, layoutType }: WithPasswordTooltipProps) {
16
+ export function PasswordValidation({ passwordValidation, layoutType, animatedKey }: WithPasswordTooltipProps) {
16
17
  const { t } = useLocale('FieldsPredefined');
17
18
 
18
19
  const allCriteriaMet = useMemo(() => Object.values(passwordValidation).every(item => item), [passwordValidation]);
@@ -41,36 +42,42 @@ export function PasswordValidation({ passwordValidation, layoutType }: WithPassw
41
42
  label={t('FieldAi.secret.passwordTooltip.minLength')}
42
43
  layoutType={layoutType}
43
44
  shouldHide={isTouchDevice}
45
+ animated={animatedKey === 'minLength'}
44
46
  />
45
47
  <CheckItem
46
48
  checked={passwordValidation.onlyLatin}
47
49
  label={t('FieldAi.secret.passwordTooltip.onlyLatin')}
48
50
  layoutType={layoutType}
49
51
  shouldHide={isTouchDevice}
52
+ animated={animatedKey === 'onlyLatin'}
50
53
  />
51
54
  <CheckItem
52
55
  checked={passwordValidation.hasLetterCases}
53
56
  label={t('FieldAi.secret.passwordTooltip.hasLetterCases')}
54
57
  layoutType={layoutType}
55
58
  shouldHide={isTouchDevice}
59
+ animated={animatedKey === 'hasLetterCases'}
56
60
  />
57
61
  <CheckItem
58
62
  checked={passwordValidation.hasNumber}
59
63
  label={t('FieldAi.secret.passwordTooltip.hasNumber')}
60
64
  layoutType={layoutType}
61
65
  shouldHide={isTouchDevice}
66
+ animated={animatedKey === 'hasNumber'}
62
67
  />
63
68
  <CheckItem
64
69
  checked={passwordValidation.hasSymbol}
65
70
  label={t('FieldAi.secret.passwordTooltip.hasSymbol')}
66
71
  layoutType={layoutType}
67
72
  shouldHide={isTouchDevice}
73
+ animated={animatedKey === 'hasSymbol'}
68
74
  />
69
75
  <CheckItem
70
76
  checked={passwordValidation.noSpaces}
71
77
  label={t('FieldAi.secret.passwordTooltip.noSpaces')}
72
78
  layoutType={layoutType}
73
79
  shouldHide={isTouchDevice}
80
+ animated={animatedKey === 'noSpaces'}
74
81
  />
75
82
  </div>
76
83
  </div>
@@ -4,7 +4,7 @@ import { AdaptiveTooltip } from '@cloud-ru/uikit-product-mobile-tooltip';
4
4
  import { WithLayoutType } from '@cloud-ru/uikit-product-utils';
5
5
 
6
6
  import { isTouchDevice } from '../../../../helpers';
7
- import { ValidationPassword } from '../../utils';
7
+ import { ValidationPassword, ValidationPasswordKey } from '../../utils';
8
8
  import { PasswordValidation } from '../PasswordValidation';
9
9
  import styles from './styles.module.scss';
10
10
 
@@ -12,6 +12,7 @@ export type WithPasswordTooltipProps = WithLayoutType<{
12
12
  showValidation?: boolean;
13
13
  children: ReactNode;
14
14
  passwordValidation: ValidationPassword;
15
+ animatedKey?: ValidationPasswordKey | null;
15
16
  }>;
16
17
 
17
18
  export function WithPasswordValidation({
@@ -19,12 +20,17 @@ export function WithPasswordValidation({
19
20
  passwordValidation,
20
21
  layoutType,
21
22
  children,
23
+ animatedKey,
22
24
  }: WithPasswordTooltipProps) {
23
25
  if (isTouchDevice(layoutType)) {
24
26
  if (showValidation) {
25
27
  return (
26
28
  <div className={styles.validationContainer}>
27
- <PasswordValidation passwordValidation={passwordValidation} layoutType={layoutType} />
29
+ <PasswordValidation
30
+ passwordValidation={passwordValidation}
31
+ layoutType={layoutType}
32
+ animatedKey={animatedKey}
33
+ />
28
34
 
29
35
  {children}
30
36
  </div>
@@ -39,7 +45,13 @@ export function WithPasswordValidation({
39
45
  <AdaptiveTooltip
40
46
  placement={'left-end'}
41
47
  layoutType={layoutType}
42
- tip={<PasswordValidation passwordValidation={passwordValidation} layoutType={layoutType} />}
48
+ tip={
49
+ <PasswordValidation
50
+ passwordValidation={passwordValidation}
51
+ layoutType={layoutType}
52
+ animatedKey={animatedKey}
53
+ />
54
+ }
43
55
  offset={8}
44
56
  >
45
57
  {children}
@@ -7,6 +7,8 @@ export type ValidationPassword = {
7
7
  noSpaces: boolean;
8
8
  };
9
9
 
10
+ export type ValidationPasswordKey = keyof ValidationPassword;
11
+
10
12
  const MIN_PASSWORD_LENGTH = 8;
11
13
  const NUMBER_REGEX = /[0-9]/;
12
14
  const CAPITAL_REGEX = /\p{Lu}/u;