@cloud-ru/uikit-product-fields-predefined 3.0.0 → 3.0.1-preview-d01a0cca.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 (90) hide show
  1. package/README.md +290 -12
  2. package/dist/cjs/components/FieldCode/FieldCode.d.ts +35 -0
  3. package/dist/cjs/components/FieldCode/FieldCode.js +48 -0
  4. package/dist/cjs/components/FieldCode/components/Cell/Cell.d.ts +7 -0
  5. package/dist/cjs/components/FieldCode/components/Cell/Cell.js +27 -0
  6. package/dist/cjs/components/FieldCode/components/Cell/index.d.ts +1 -0
  7. package/dist/cjs/components/FieldCode/components/Cell/index.js +17 -0
  8. package/dist/cjs/components/FieldCode/components/Cell/styles.module.css +19 -0
  9. package/dist/cjs/components/FieldCode/components/ResendCode/ResendCode.d.ts +8 -0
  10. package/dist/cjs/components/FieldCode/components/ResendCode/ResendCode.js +28 -0
  11. package/dist/cjs/components/FieldCode/components/ResendCode/index.d.ts +1 -0
  12. package/dist/cjs/components/FieldCode/components/ResendCode/index.js +17 -0
  13. package/dist/cjs/components/FieldCode/components/ResendCode/utils.d.ts +1 -0
  14. package/dist/cjs/components/FieldCode/components/ResendCode/utils.js +8 -0
  15. package/dist/cjs/components/FieldCode/components/index.d.ts +2 -0
  16. package/dist/cjs/components/FieldCode/components/index.js +18 -0
  17. package/dist/cjs/components/FieldCode/constants.d.ts +14 -0
  18. package/dist/cjs/components/FieldCode/constants.js +10 -0
  19. package/dist/cjs/components/FieldCode/hooks/index.d.ts +4 -0
  20. package/dist/cjs/components/FieldCode/hooks/index.js +20 -0
  21. package/dist/cjs/components/FieldCode/hooks/useCodeInput.d.ts +22 -0
  22. package/dist/cjs/components/FieldCode/hooks/useCodeInput.js +98 -0
  23. package/dist/cjs/components/FieldCode/hooks/useFieldCodeValidate.d.ts +8 -0
  24. package/dist/cjs/components/FieldCode/hooks/useFieldCodeValidate.js +24 -0
  25. package/dist/cjs/components/FieldCode/hooks/useFieldHelpers.d.ts +13 -0
  26. package/dist/cjs/components/FieldCode/hooks/useFieldHelpers.js +34 -0
  27. package/dist/cjs/components/FieldCode/hooks/useFocusCell.d.ts +5 -0
  28. package/dist/cjs/components/FieldCode/hooks/useFocusCell.js +22 -0
  29. package/dist/cjs/components/FieldCode/index.d.ts +2 -0
  30. package/dist/cjs/components/FieldCode/index.js +20 -0
  31. package/dist/cjs/components/FieldCode/styles.module.css +37 -0
  32. package/dist/cjs/components/FieldCode/utils.d.ts +6 -0
  33. package/dist/cjs/components/FieldCode/utils.js +21 -0
  34. package/dist/cjs/components/index.d.ts +1 -0
  35. package/dist/cjs/components/index.js +1 -0
  36. package/dist/esm/components/FieldCode/FieldCode.d.ts +35 -0
  37. package/dist/esm/components/FieldCode/FieldCode.js +41 -0
  38. package/dist/esm/components/FieldCode/components/Cell/Cell.d.ts +7 -0
  39. package/dist/esm/components/FieldCode/components/Cell/Cell.js +21 -0
  40. package/dist/esm/components/FieldCode/components/Cell/index.d.ts +1 -0
  41. package/dist/esm/components/FieldCode/components/Cell/index.js +1 -0
  42. package/dist/esm/components/FieldCode/components/Cell/styles.module.css +19 -0
  43. package/dist/esm/components/FieldCode/components/ResendCode/ResendCode.d.ts +8 -0
  44. package/dist/esm/components/FieldCode/components/ResendCode/ResendCode.js +25 -0
  45. package/dist/esm/components/FieldCode/components/ResendCode/index.d.ts +1 -0
  46. package/dist/esm/components/FieldCode/components/ResendCode/index.js +1 -0
  47. package/dist/esm/components/FieldCode/components/ResendCode/utils.d.ts +1 -0
  48. package/dist/esm/components/FieldCode/components/ResendCode/utils.js +5 -0
  49. package/dist/esm/components/FieldCode/components/index.d.ts +2 -0
  50. package/dist/esm/components/FieldCode/components/index.js +2 -0
  51. package/dist/esm/components/FieldCode/constants.d.ts +14 -0
  52. package/dist/esm/components/FieldCode/constants.js +7 -0
  53. package/dist/esm/components/FieldCode/hooks/index.d.ts +4 -0
  54. package/dist/esm/components/FieldCode/hooks/index.js +4 -0
  55. package/dist/esm/components/FieldCode/hooks/useCodeInput.d.ts +22 -0
  56. package/dist/esm/components/FieldCode/hooks/useCodeInput.js +95 -0
  57. package/dist/esm/components/FieldCode/hooks/useFieldCodeValidate.d.ts +8 -0
  58. package/dist/esm/components/FieldCode/hooks/useFieldCodeValidate.js +21 -0
  59. package/dist/esm/components/FieldCode/hooks/useFieldHelpers.d.ts +13 -0
  60. package/dist/esm/components/FieldCode/hooks/useFieldHelpers.js +31 -0
  61. package/dist/esm/components/FieldCode/hooks/useFocusCell.d.ts +5 -0
  62. package/dist/esm/components/FieldCode/hooks/useFocusCell.js +19 -0
  63. package/dist/esm/components/FieldCode/index.d.ts +2 -0
  64. package/dist/esm/components/FieldCode/index.js +2 -0
  65. package/dist/esm/components/FieldCode/styles.module.css +37 -0
  66. package/dist/esm/components/FieldCode/utils.d.ts +6 -0
  67. package/dist/esm/components/FieldCode/utils.js +13 -0
  68. package/dist/esm/components/index.d.ts +1 -0
  69. package/dist/esm/components/index.js +1 -0
  70. package/dist/tsconfig.cjs.tsbuildinfo +1 -1
  71. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  72. package/package.json +6 -3
  73. package/src/components/FieldCode/FieldCode.tsx +133 -0
  74. package/src/components/FieldCode/components/Cell/Cell.tsx +35 -0
  75. package/src/components/FieldCode/components/Cell/index.ts +1 -0
  76. package/src/components/FieldCode/components/Cell/styles.module.scss +24 -0
  77. package/src/components/FieldCode/components/ResendCode/ResendCode.tsx +33 -0
  78. package/src/components/FieldCode/components/ResendCode/index.ts +1 -0
  79. package/src/components/FieldCode/components/ResendCode/utils.ts +5 -0
  80. package/src/components/FieldCode/components/index.ts +2 -0
  81. package/src/components/FieldCode/constants.ts +20 -0
  82. package/src/components/FieldCode/hooks/index.ts +4 -0
  83. package/src/components/FieldCode/hooks/useCodeInput.ts +147 -0
  84. package/src/components/FieldCode/hooks/useFieldCodeValidate.ts +35 -0
  85. package/src/components/FieldCode/hooks/useFieldHelpers.ts +52 -0
  86. package/src/components/FieldCode/hooks/useFocusCell.ts +29 -0
  87. package/src/components/FieldCode/index.ts +2 -0
  88. package/src/components/FieldCode/styles.module.scss +48 -0
  89. package/src/components/FieldCode/utils.ts +23 -0
  90. package/src/components/index.ts +1 -0
@@ -0,0 +1,4 @@
1
+ export * from './useCodeInput';
2
+ export * from './useFieldCodeValidate';
3
+ export * from './useFocusCell';
4
+ export * from './useFieldHelpers';
@@ -0,0 +1,4 @@
1
+ export * from './useCodeInput';
2
+ export * from './useFieldCodeValidate';
3
+ export * from './useFocusCell';
4
+ export * from './useFieldHelpers';
@@ -0,0 +1,22 @@
1
+ import { ClipboardEvent, KeyboardEvent } from 'react';
2
+ export type UseCodeInputParams = {
3
+ /** Количество цифр в коде (ожидается целое ≥ 1) */
4
+ codeLength: number;
5
+ /** Значение кода */
6
+ value?: string;
7
+ /** Колбек изменения значения */
8
+ onChange?: (code: string) => void;
9
+ /** Функция фокуса */
10
+ moveFocus: (index: number) => void;
11
+ /** Колбек достижения ввода всех символов кода */
12
+ onComplete?: (code: string) => void;
13
+ };
14
+ export declare function useCodeInput(params: UseCodeInputParams): {
15
+ code: string[];
16
+ cellHandlers: {
17
+ onKeyDown: (e: KeyboardEvent<HTMLInputElement>, index: number) => void;
18
+ onPaste: (e: ClipboardEvent<HTMLInputElement>) => void;
19
+ onChange: (code: string, index: number) => void;
20
+ };
21
+ onChangeCode: (value: any, ...args: any[]) => any | void;
22
+ };
@@ -0,0 +1,95 @@
1
+ import { useRefState } from '@siberiacancode/reactuse';
2
+ import { useCallback, useEffect } from 'react';
3
+ import { useValueControl } from '@snack-uikit/utils';
4
+ import { ZERO_WIDTH_SPACE } from '../constants';
5
+ import { isNumberChar, isStringCodeLength, isZeroWidthSpace } from '../utils';
6
+ const buildCodeArray = (str, codeLength) => Array.from({ length: codeLength }, (_, idx) => str[idx] || ZERO_WIDTH_SPACE);
7
+ export function useCodeInput(params) {
8
+ const { value: valueProp, onChange: onChangeProp, codeLength, moveFocus, onComplete } = params;
9
+ const [value = '', onChange] = useValueControl({
10
+ value: valueProp,
11
+ onChange: onChangeProp,
12
+ defaultValue: '',
13
+ });
14
+ const codeRef = useRefState(buildCodeArray(value, codeLength));
15
+ const updateCodeByIndex = useCallback((index, newChar) => {
16
+ codeRef.current[index] = newChar;
17
+ onChange === null || onChange === void 0 ? void 0 : onChange(codeRef.current.join(''));
18
+ }, [codeRef, onChange]);
19
+ const updateFullCode = useCallback((newCode) => {
20
+ codeRef.current = newCode.split('');
21
+ onChange === null || onChange === void 0 ? void 0 : onChange(newCode);
22
+ moveFocus(codeLength - 1);
23
+ onComplete === null || onComplete === void 0 ? void 0 : onComplete(newCode);
24
+ }, [codeLength, codeRef, moveFocus, onChange, onComplete]);
25
+ const handleAfterCellUpdate = useCallback((index) => {
26
+ const normalizedCode = codeRef.current.join('');
27
+ const isLastInput = index === codeLength - 1;
28
+ const isAllInputsFilled = isStringCodeLength(normalizedCode, codeLength);
29
+ if (!isLastInput) {
30
+ moveFocus(index + 1);
31
+ }
32
+ else if (isAllInputsFilled) {
33
+ onComplete === null || onComplete === void 0 ? void 0 : onComplete(normalizedCode);
34
+ }
35
+ }, [codeLength, codeRef, moveFocus, onComplete]);
36
+ const deleteChar = useCallback((index) => {
37
+ if (codeRef.current[index] && !isZeroWidthSpace(codeRef.current[index])) {
38
+ updateCodeByIndex(index, ZERO_WIDTH_SPACE);
39
+ }
40
+ else if (index > 0) {
41
+ moveFocus(index - 1);
42
+ }
43
+ }, [codeRef, moveFocus, updateCodeByIndex]);
44
+ const onAutoCompleteInput = useCallback((code, index) => {
45
+ if (isStringCodeLength(code, codeLength)) {
46
+ updateFullCode(code);
47
+ return;
48
+ }
49
+ if (!isNumberChar(code)) {
50
+ return;
51
+ }
52
+ updateCodeByIndex(index, code);
53
+ handleAfterCellUpdate(index);
54
+ }, [codeLength, handleAfterCellUpdate, updateCodeByIndex, updateFullCode]);
55
+ const onKeyDown = useCallback((e, index) => {
56
+ switch (e.key) {
57
+ case 'ArrowLeft':
58
+ moveFocus(index - 1);
59
+ break;
60
+ case 'ArrowRight':
61
+ moveFocus(index + 1);
62
+ break;
63
+ case 'Backspace':
64
+ deleteChar(index);
65
+ break;
66
+ default:
67
+ if (isNumberChar(e.key)) {
68
+ e.preventDefault();
69
+ updateCodeByIndex(index, e.key);
70
+ handleAfterCellUpdate(index);
71
+ }
72
+ break;
73
+ }
74
+ }, [deleteChar, handleAfterCellUpdate, moveFocus, updateCodeByIndex]);
75
+ const onPaste = useCallback((e) => {
76
+ var _a;
77
+ const codeInput = (_a = e === null || e === void 0 ? void 0 : e.clipboardData.getData('text')) !== null && _a !== void 0 ? _a : '';
78
+ if (!isStringCodeLength(codeInput, codeLength)) {
79
+ return;
80
+ }
81
+ updateFullCode(codeInput);
82
+ }, [codeLength, updateFullCode]);
83
+ useEffect(() => {
84
+ codeRef.current = buildCodeArray(value, codeLength);
85
+ }, [codeLength, codeRef, value]);
86
+ return {
87
+ code: codeRef.current,
88
+ cellHandlers: {
89
+ onKeyDown,
90
+ onPaste,
91
+ onChange: onAutoCompleteInput,
92
+ },
93
+ onChangeCode: onChange,
94
+ };
95
+ }
@@ -0,0 +1,8 @@
1
+ export type UseFieldCodeValidateParams = {
2
+ /** Ожидаемая длина кода (цифр) */
3
+ codeLength: number;
4
+ };
5
+ /**
6
+ * Возвращает функцию валидации значения кода (пусто / неполный код).
7
+ */
8
+ export declare function useFieldCodeValidate(params: UseFieldCodeValidateParams): (value?: string | number) => string | undefined;
@@ -0,0 +1,21 @@
1
+ import { useCallback } from 'react';
2
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
3
+ import { isNumberChar } from '../utils';
4
+ /**
5
+ * Возвращает функцию валидации значения кода (пусто / неполный код).
6
+ */
7
+ export function useFieldCodeValidate(params) {
8
+ const { codeLength } = params;
9
+ const { t } = useLocale('FieldsPredefined');
10
+ return useCallback((value) => {
11
+ const str = value != null ? String(value) : '';
12
+ const digits = str.split('').filter(isNumberChar).join('');
13
+ if (digits.length === 0) {
14
+ return t('FieldCode.required');
15
+ }
16
+ if (digits.length < codeLength) {
17
+ return t('FieldCode.minLength', { count: codeLength });
18
+ }
19
+ return undefined;
20
+ }, [codeLength, t]);
21
+ }
@@ -0,0 +1,13 @@
1
+ import { type FieldCodeFocusEffect } from '../constants';
2
+ type UseFieldHelpersParams = {
3
+ onChangeCode: (code: string) => void;
4
+ moveFocus: (index: number) => void;
5
+ focusEffects: readonly FieldCodeFocusEffect[];
6
+ showEmptyChars?: boolean;
7
+ code: readonly string[];
8
+ codeLength: number;
9
+ };
10
+ export declare function useFieldHelpers(params: UseFieldHelpersParams): {
11
+ resetCode: () => void;
12
+ };
13
+ export {};
@@ -0,0 +1,31 @@
1
+ import { useCallback, useEffect, useMemo } from 'react';
2
+ import { getFirstEmptyCellIndex } from '../utils';
3
+ export function useFieldHelpers(params) {
4
+ const { onChangeCode, moveFocus, focusEffects: focusEffectsProp, showEmptyChars, code, codeLength } = params;
5
+ const focusEffectsKey = [...focusEffectsProp].sort().join(',');
6
+ const focusEffects = useMemo(() => [...focusEffectsProp],
7
+ // eslint-disable-next-line react-hooks/exhaustive-deps
8
+ [focusEffectsKey]);
9
+ const resetCode = useCallback(() => {
10
+ onChangeCode('');
11
+ if (focusEffects.includes('firstCellOnReset')) {
12
+ moveFocus(0);
13
+ }
14
+ }, [focusEffects, moveFocus, onChangeCode]);
15
+ useEffect(() => {
16
+ if (focusEffects.includes('firstCellOnMount')) {
17
+ moveFocus(0);
18
+ }
19
+ }, [focusEffects, moveFocus]);
20
+ useEffect(() => {
21
+ if (!focusEffects.includes('firstCellWhenShowEmptyChars') || !showEmptyChars) {
22
+ return;
23
+ }
24
+ const emptyIndex = getFirstEmptyCellIndex(code);
25
+ if (emptyIndex >= 0) {
26
+ moveFocus(emptyIndex);
27
+ }
28
+ // eslint-disable-next-line react-hooks/exhaustive-deps
29
+ }, [showEmptyChars, focusEffects, moveFocus, codeLength]);
30
+ return { resetCode };
31
+ }
@@ -0,0 +1,5 @@
1
+ export declare function useFocusCell(codeLength: number): {
2
+ inputsRef: import("react").MutableRefObject<HTMLInputElement[]>;
3
+ moveFocus: (newIndex: number) => void;
4
+ blurFields: () => void;
5
+ };
@@ -0,0 +1,19 @@
1
+ import { useCallback, useRef } from 'react';
2
+ export function useFocusCell(codeLength) {
3
+ const inputsRef = useRef([]);
4
+ const focusInput = useCallback((index) => {
5
+ var _a;
6
+ (_a = inputsRef.current[index]) === null || _a === void 0 ? void 0 : _a.focus();
7
+ }, [inputsRef]);
8
+ const moveFocus = useCallback((newIndex) => {
9
+ if (newIndex >= 0 && newIndex < codeLength) {
10
+ focusInput(newIndex);
11
+ }
12
+ }, [codeLength, focusInput]);
13
+ const blurFields = useCallback(() => {
14
+ inputsRef.current.forEach(input => {
15
+ input === null || input === void 0 ? void 0 : input.blur();
16
+ });
17
+ }, [inputsRef]);
18
+ return { inputsRef, moveFocus, blurFields };
19
+ }
@@ -0,0 +1,2 @@
1
+ export * from './FieldCode';
2
+ export { useFieldCodeValidate, type UseFieldCodeValidateParams } from './hooks/useFieldCodeValidate';
@@ -0,0 +1,2 @@
1
+ export * from './FieldCode';
2
+ export { useFieldCodeValidate } from './hooks/useFieldCodeValidate';
@@ -0,0 +1,37 @@
1
+ .fieldCode{
2
+ display:flex;
3
+ flex-direction:column;
4
+ align-items:center;
5
+ gap:8px;
6
+ }
7
+ .fieldCode[data-stretch-cells]{
8
+ width:100%;
9
+ }
10
+
11
+ .fieldDecorator{
12
+ width:unset;
13
+ }
14
+
15
+ .codeContainer{
16
+ display:flex;
17
+ justify-content:center;
18
+ }
19
+ .codeContainer[data-size=s]{
20
+ gap:8px;
21
+ }
22
+ .codeContainer[data-size=m]{
23
+ gap:8px;
24
+ }
25
+ .codeContainer[data-size=l]{
26
+ gap:12px;
27
+ }
28
+
29
+ .cellSpacing[data-size=s]{
30
+ margin-right:8px;
31
+ }
32
+ .cellSpacing[data-size=m]{
33
+ margin-right:8px;
34
+ }
35
+ .cellSpacing[data-size=l]{
36
+ margin-right:12px;
37
+ }
@@ -0,0 +1,6 @@
1
+ import { FieldTextProps } from '@snack-uikit/fields';
2
+ export declare const isNumberChar: (char: string) => boolean;
3
+ export declare const isStringCodeLength: (input: string, codeLength: number) => boolean;
4
+ export declare const isZeroWidthSpace: (value: string) => value is "​";
5
+ export declare function getFirstEmptyCellIndex(code: readonly string[]): number;
6
+ export declare const getCellValidationState: (value: string, showEmptyChars?: boolean, error?: boolean) => FieldTextProps["validationState"];
@@ -0,0 +1,13 @@
1
+ import { ZERO_WIDTH_SPACE } from './constants';
2
+ export const isNumberChar = (char) => /^\d$/.test(char);
3
+ export const isStringCodeLength = (input, codeLength) => new RegExp(`^\\d{${codeLength}}$`).test(input);
4
+ export const isZeroWidthSpace = (value) => value === ZERO_WIDTH_SPACE;
5
+ export function getFirstEmptyCellIndex(code) {
6
+ return code.findIndex(isZeroWidthSpace);
7
+ }
8
+ export const getCellValidationState = (value, showEmptyChars, error) => {
9
+ if (showEmptyChars) {
10
+ return isZeroWidthSpace(value) ? 'error' : 'default';
11
+ }
12
+ return error ? 'error' : 'default';
13
+ };
@@ -5,3 +5,4 @@ export * from './AIDisclaimer';
5
5
  export * from './FieldName';
6
6
  export * from './FieldDescription';
7
7
  export * from './FieldMask';
8
+ export * from './FieldCode';
@@ -5,3 +5,4 @@ export * from './AIDisclaimer';
5
5
  export * from './FieldName';
6
6
  export * from './FieldDescription';
7
7
  export * from './FieldMask';
8
+ export * from './FieldCode';