@cloud-ru/uikit-product-fields-predefined 2.4.7 → 2.4.8-preview-f8642e85.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 (85) hide show
  1. package/README.md +25 -0
  2. package/dist/cjs/components/FieldCode/FieldCode.d.ts +42 -0
  3. package/dist/cjs/components/FieldCode/FieldCode.js +47 -0
  4. package/dist/cjs/components/FieldCode/components/Cell/Cell.d.ts +5 -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 +3 -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 +3 -0
  20. package/dist/cjs/components/FieldCode/hooks/index.js +19 -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/useFieldHelpers.d.ts +13 -0
  24. package/dist/cjs/components/FieldCode/hooks/useFieldHelpers.js +34 -0
  25. package/dist/cjs/components/FieldCode/hooks/useFocusCell.d.ts +5 -0
  26. package/dist/cjs/components/FieldCode/hooks/useFocusCell.js +22 -0
  27. package/dist/cjs/components/FieldCode/index.d.ts +1 -0
  28. package/dist/cjs/components/FieldCode/index.js +17 -0
  29. package/dist/cjs/components/FieldCode/styles.module.css +30 -0
  30. package/dist/cjs/components/FieldCode/utils.d.ts +6 -0
  31. package/dist/cjs/components/FieldCode/utils.js +21 -0
  32. package/dist/cjs/components/index.d.ts +1 -0
  33. package/dist/cjs/components/index.js +1 -0
  34. package/dist/esm/components/FieldCode/FieldCode.d.ts +42 -0
  35. package/dist/esm/components/FieldCode/FieldCode.js +40 -0
  36. package/dist/esm/components/FieldCode/components/Cell/Cell.d.ts +5 -0
  37. package/dist/esm/components/FieldCode/components/Cell/Cell.js +21 -0
  38. package/dist/esm/components/FieldCode/components/Cell/index.d.ts +1 -0
  39. package/dist/esm/components/FieldCode/components/Cell/index.js +1 -0
  40. package/dist/esm/components/FieldCode/components/Cell/styles.module.css +3 -0
  41. package/dist/esm/components/FieldCode/components/ResendCode/ResendCode.d.ts +8 -0
  42. package/dist/esm/components/FieldCode/components/ResendCode/ResendCode.js +25 -0
  43. package/dist/esm/components/FieldCode/components/ResendCode/index.d.ts +1 -0
  44. package/dist/esm/components/FieldCode/components/ResendCode/index.js +1 -0
  45. package/dist/esm/components/FieldCode/components/ResendCode/utils.d.ts +1 -0
  46. package/dist/esm/components/FieldCode/components/ResendCode/utils.js +5 -0
  47. package/dist/esm/components/FieldCode/components/index.d.ts +2 -0
  48. package/dist/esm/components/FieldCode/components/index.js +2 -0
  49. package/dist/esm/components/FieldCode/constants.d.ts +14 -0
  50. package/dist/esm/components/FieldCode/constants.js +7 -0
  51. package/dist/esm/components/FieldCode/hooks/index.d.ts +3 -0
  52. package/dist/esm/components/FieldCode/hooks/index.js +3 -0
  53. package/dist/esm/components/FieldCode/hooks/useCodeInput.d.ts +22 -0
  54. package/dist/esm/components/FieldCode/hooks/useCodeInput.js +95 -0
  55. package/dist/esm/components/FieldCode/hooks/useFieldHelpers.d.ts +13 -0
  56. package/dist/esm/components/FieldCode/hooks/useFieldHelpers.js +31 -0
  57. package/dist/esm/components/FieldCode/hooks/useFocusCell.d.ts +5 -0
  58. package/dist/esm/components/FieldCode/hooks/useFocusCell.js +19 -0
  59. package/dist/esm/components/FieldCode/index.d.ts +1 -0
  60. package/dist/esm/components/FieldCode/index.js +1 -0
  61. package/dist/esm/components/FieldCode/styles.module.css +30 -0
  62. package/dist/esm/components/FieldCode/utils.d.ts +6 -0
  63. package/dist/esm/components/FieldCode/utils.js +13 -0
  64. package/dist/esm/components/index.d.ts +1 -0
  65. package/dist/esm/components/index.js +1 -0
  66. package/dist/tsconfig.cjs.tsbuildinfo +1 -1
  67. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  68. package/package.json +6 -3
  69. package/src/components/FieldCode/FieldCode.tsx +115 -0
  70. package/src/components/FieldCode/components/Cell/Cell.tsx +32 -0
  71. package/src/components/FieldCode/components/Cell/index.ts +1 -0
  72. package/src/components/FieldCode/components/Cell/styles.module.scss +5 -0
  73. package/src/components/FieldCode/components/ResendCode/ResendCode.tsx +33 -0
  74. package/src/components/FieldCode/components/ResendCode/index.ts +1 -0
  75. package/src/components/FieldCode/components/ResendCode/utils.ts +5 -0
  76. package/src/components/FieldCode/components/index.ts +2 -0
  77. package/src/components/FieldCode/constants.ts +20 -0
  78. package/src/components/FieldCode/hooks/index.ts +3 -0
  79. package/src/components/FieldCode/hooks/useCodeInput.ts +147 -0
  80. package/src/components/FieldCode/hooks/useFieldHelpers.ts +52 -0
  81. package/src/components/FieldCode/hooks/useFocusCell.ts +29 -0
  82. package/src/components/FieldCode/index.ts +1 -0
  83. package/src/components/FieldCode/styles.module.scss +40 -0
  84. package/src/components/FieldCode/utils.ts +23 -0
  85. package/src/components/index.ts +1 -0
@@ -0,0 +1,42 @@
1
+ import { FieldDecoratorProps } from '@snack-uikit/fields';
2
+ import { type ResendCodeProps } from './components';
3
+ import { type FieldCodeFocusEffect } from './constants';
4
+ import { UseCodeInputParams } from './hooks';
5
+ export type FieldCodeRef = {
6
+ /** Перенести фокус на ячейку с индексом `index` */
7
+ moveFocus: (index: number) => void;
8
+ /** Убрать фокус со всех ячеек кода */
9
+ blurFields: () => void;
10
+ /** Сбросить значение кода */
11
+ resetCode: () => void;
12
+ };
13
+ export type FieldCodeProps = {
14
+ /** CSS-класс компонента */
15
+ className?: string;
16
+ /** CSS-класс ячейки кода */
17
+ cellClassName?: string;
18
+ /** Позиции, после которых нужно вставить пробел (индексы символов, после которых будет пробел) */
19
+ spacing?: number[];
20
+ /** Подсветить пустые символы кода */
21
+ showEmptyChars?: boolean;
22
+ /** Компонент отправки нового кода */
23
+ resendCode?: ResendCodeProps;
24
+ /** Сценарии автофокуса; по умолчанию — первая ячейка при монтировании и после сброса (см. `FieldCodeFocusEffect`) */
25
+ focusEffects?: readonly FieldCodeFocusEffect[];
26
+ } & Omit<UseCodeInputParams, 'moveFocus'> & Pick<FieldDecoratorProps, 'size' | 'disabled' | 'label' | 'error'>;
27
+ export declare const FieldCode: import("react").ForwardRefExoticComponent<{
28
+ /** CSS-класс компонента */
29
+ className?: string;
30
+ /** CSS-класс ячейки кода */
31
+ cellClassName?: string;
32
+ /** Позиции, после которых нужно вставить пробел (индексы символов, после которых будет пробел) */
33
+ spacing?: number[];
34
+ /** Подсветить пустые символы кода */
35
+ showEmptyChars?: boolean;
36
+ /** Компонент отправки нового кода */
37
+ resendCode?: ResendCodeProps;
38
+ /** Сценарии автофокуса; по умолчанию — первая ячейка при монтировании и после сброса (см. `FieldCodeFocusEffect`) */
39
+ focusEffects?: readonly FieldCodeFocusEffect[];
40
+ } & Omit<UseCodeInputParams, "moveFocus"> & Pick<FieldDecoratorProps, "size" | "label" | "error" | "disabled"> & import("react").RefAttributes<FieldCodeRef>>;
41
+ export type { FieldCodeFocusEffect } from './constants';
42
+ export { FIELD_CODE_DEFAULT_FOCUS_EFFECTS } from './constants';
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import cn from 'classnames';
3
+ import { forwardRef, useImperativeHandle } from 'react';
4
+ import { FieldDecorator } from '@snack-uikit/fields';
5
+ import { Cell, ResendCode } from './components';
6
+ import { FIELD_CODE_DEFAULT_FOCUS_EFFECTS } from './constants';
7
+ import { useCodeInput, useFieldHelpers, useFocusCell } from './hooks';
8
+ import styles from './styles.module.css';
9
+ import { getCellValidationState } from './utils';
10
+ export const FieldCode = forwardRef(function FieldCode(props, ref) {
11
+ var _a;
12
+ const { codeLength, className, cellClassName, value, onChange, spacing, onComplete, size, disabled, label, error, showEmptyChars, resendCode, focusEffects = FIELD_CODE_DEFAULT_FOCUS_EFFECTS, } = props;
13
+ const { inputsRef, moveFocus, blurFields } = useFocusCell(codeLength);
14
+ const { code, cellHandlers, onChangeCode } = useCodeInput({ value, onChange, codeLength, moveFocus, onComplete });
15
+ const { resetCode } = useFieldHelpers({
16
+ onChangeCode,
17
+ focusEffects,
18
+ moveFocus,
19
+ showEmptyChars,
20
+ code,
21
+ codeLength,
22
+ });
23
+ useImperativeHandle(ref, () => ({
24
+ moveFocus,
25
+ blurFields,
26
+ resetCode,
27
+ }), [moveFocus, blurFields, resetCode]);
28
+ const resolvedDecoratorProps = {
29
+ label,
30
+ disabled,
31
+ size,
32
+ error,
33
+ };
34
+ return (_jsxs("div", { className: cn(styles.fieldCode, className), children: [_jsx(FieldDecorator, Object.assign({}, resolvedDecoratorProps, { children: _jsx("div", { className: styles.codeContainer, "data-size": size, children: code.map((char, index) => (_jsx(Cell, { ref: inputRef => {
35
+ if (inputRef) {
36
+ inputsRef.current[index] = inputRef;
37
+ }
38
+ }, className: cn((spacing === null || spacing === void 0 ? void 0 : spacing.includes(index)) && styles.cellSpacing, cellClassName), size: size, value: char, disabled: disabled, autoComplete: index === 0 ? 'one-time-code' : undefined, onKeyDown: e => cellHandlers.onKeyDown(e, index), onPaste: cellHandlers.onPaste, onChange: e => cellHandlers.onChange(e, index), validationState: getCellValidationState(char, showEmptyChars, Boolean(error)) }, index))) }) })), resendCode ? _jsx(ResendCode, Object.assign({}, resendCode, { size: (_a = resendCode.size) !== null && _a !== void 0 ? _a : size })) : null] }));
39
+ });
40
+ export { FIELD_CODE_DEFAULT_FOCUS_EFFECTS } from './constants';
@@ -0,0 +1,5 @@
1
+ import { FieldTextProps } from '@snack-uikit/fields';
2
+ export declare const Cell: import("react").ForwardRefExoticComponent<{
3
+ /** CSS-класс ячейки кода */
4
+ className?: string;
5
+ } & Pick<FieldTextProps, "size" | "onPaste" | "onChange" | "onKeyDown" | "value" | "disabled" | "autoComplete" | "validationState"> & import("react").RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,21 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { jsx as _jsx } from "react/jsx-runtime";
13
+ import cn from 'classnames';
14
+ import { forwardRef } from 'react';
15
+ import { FieldText } from '@snack-uikit/fields';
16
+ import { ZERO_WIDTH_SPACE } from '../../constants';
17
+ import styles from './styles.module.css';
18
+ export const Cell = forwardRef((props, ref) => {
19
+ const { className, size, value } = props, fieldCellProps = __rest(props, ["className", "size", "value"]);
20
+ return (_jsx(FieldText, Object.assign({ inputMode: 'numeric', ref: ref, className: cn(styles.cell, className), "data-size": size, showClearButton: false, value: value === ZERO_WIDTH_SPACE ? '' : value, size: size }, fieldCellProps)));
21
+ });
@@ -0,0 +1 @@
1
+ export * from './Cell';
@@ -0,0 +1 @@
1
+ export * from './Cell';
@@ -0,0 +1,3 @@
1
+ .cell input{
2
+ text-align:center;
3
+ }
@@ -0,0 +1,8 @@
1
+ import { ButtonFunctionProps } from '@snack-uikit/button';
2
+ export type ResendCodeProps = {
3
+ /** Колбек отправки нового кода */
4
+ onResend: () => void;
5
+ /** Количество секунд до следующего отправления кода */
6
+ secondsToNextResend: number;
7
+ } & Pick<ButtonFunctionProps, 'size' | 'disabled'>;
8
+ export declare function ResendCode(props: ResendCodeProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,25 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { jsx as _jsx } from "react/jsx-runtime";
13
+ import { UpdateSVG } from '@cloud-ru/uikit-product-icons';
14
+ import { useLocale } from '@cloud-ru/uikit-product-locale';
15
+ import { ButtonFunction } from '@snack-uikit/button';
16
+ import { formatSecondsAsMmSs } from './utils';
17
+ export function ResendCode(props) {
18
+ const { onResend, secondsToNextResend } = props, buttonProps = __rest(props, ["onResend", "secondsToNextResend"]);
19
+ const { t } = useLocale('FieldsPredefined');
20
+ return (_jsx(ButtonFunction, Object.assign({ label: secondsToNextResend > 0
21
+ ? t('FieldCode.resendCodeButtons.resendCodeWithVia', {
22
+ timer: formatSecondsAsMmSs(secondsToNextResend),
23
+ })
24
+ : t('FieldCode.resendCodeButtons.resendCode'), onClick: onResend, icon: _jsx(UpdateSVG, {}), iconPosition: 'before' }, buttonProps)));
25
+ }
@@ -0,0 +1 @@
1
+ export * from './ResendCode';
@@ -0,0 +1 @@
1
+ export * from './ResendCode';
@@ -0,0 +1 @@
1
+ export declare function formatSecondsAsMmSs(totalSeconds: number): string;
@@ -0,0 +1,5 @@
1
+ export function formatSecondsAsMmSs(totalSeconds) {
2
+ const minutes = Math.floor(totalSeconds / 60);
3
+ const seconds = totalSeconds % 60;
4
+ return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
5
+ }
@@ -0,0 +1,2 @@
1
+ export * from './Cell';
2
+ export * from './ResendCode';
@@ -0,0 +1,2 @@
1
+ export * from './Cell';
2
+ export * from './ResendCode';
@@ -0,0 +1,14 @@
1
+ export declare const ZERO_WIDTH_SPACE = "\u200B";
2
+ /**
3
+ * Когда дергать `moveFocus` у `FieldCode` (массив `focusEffects`, порядок не важен).
4
+ * @see {@link FIELD_CODE_DEFAULT_FOCUS_EFFECTS}
5
+ */
6
+ export type FieldCodeFocusEffect =
7
+ /** После монтирования — ячейка `0`. */
8
+ 'firstCellOnMount'
9
+ /** После `ref.resetCode()` — ячейка `0`. */
10
+ | 'firstCellOnReset'
11
+ /** При включении `showEmptyChars` — первая пустая (все заполнены — последняя); не на каждый ввод. */
12
+ | 'firstCellWhenShowEmptyChars';
13
+ /** Дефолтный набор для `focusEffects`. */
14
+ export declare const FIELD_CODE_DEFAULT_FOCUS_EFFECTS: readonly FieldCodeFocusEffect[];
@@ -0,0 +1,7 @@
1
+ export const ZERO_WIDTH_SPACE = '\u200B';
2
+ /** Дефолтный набор для `focusEffects`. */
3
+ export const FIELD_CODE_DEFAULT_FOCUS_EFFECTS = [
4
+ 'firstCellOnMount',
5
+ 'firstCellOnReset',
6
+ 'firstCellWhenShowEmptyChars',
7
+ ];
@@ -0,0 +1,3 @@
1
+ export * from './useCodeInput';
2
+ export * from './useFocusCell';
3
+ export * from './useFieldHelpers';
@@ -0,0 +1,3 @@
1
+ export * from './useCodeInput';
2
+ export * from './useFocusCell';
3
+ export * from './useFieldHelpers';
@@ -0,0 +1,22 @@
1
+ import { ClipboardEvent, KeyboardEvent } from 'react';
2
+ export type UseCodeInputParams = {
3
+ /** Количество цифр в коде */
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,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 @@
1
+ export * from './FieldCode';
@@ -0,0 +1 @@
1
+ export * from './FieldCode';
@@ -0,0 +1,30 @@
1
+ .fieldCode{
2
+ display:flex;
3
+ flex-direction:column;
4
+ align-items:center;
5
+ gap:8px;
6
+ }
7
+
8
+ .codeContainer{
9
+ display:flex;
10
+ justify-content:center;
11
+ }
12
+ .codeContainer[data-size=s]{
13
+ gap:8px;
14
+ }
15
+ .codeContainer[data-size=m]{
16
+ gap:8px;
17
+ }
18
+ .codeContainer[data-size=l]{
19
+ gap:12px;
20
+ }
21
+
22
+ .cellSpacing[data-size=s]{
23
+ margin-right:2px;
24
+ }
25
+ .cellSpacing[data-size=m]{
26
+ margin-right:8px;
27
+ }
28
+ .cellSpacing[data-size=l]{
29
+ margin-right:12px;
30
+ }
@@ -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
+ };
@@ -6,3 +6,4 @@ export * from './AIDisclaimer';
6
6
  export * from './FieldName';
7
7
  export * from './FieldDescription';
8
8
  export * from './FieldMask';
9
+ export * from './FieldCode';
@@ -6,3 +6,4 @@ export * from './AIDisclaimer';
6
6
  export * from './FieldName';
7
7
  export * from './FieldDescription';
8
8
  export * from './FieldMask';
9
+ export * from './FieldCode';