@cloud-ru/uikit-product-fields-predefined 3.0.3 → 3.1.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.
- package/CHANGELOG.md +11 -0
- package/README.md +28 -0
- package/dist/cjs/components/FieldCode/FieldCode.d.ts +48 -0
- package/dist/cjs/components/FieldCode/FieldCode.js +47 -0
- package/dist/cjs/components/FieldCode/components/Cell/Cell.d.ts +7 -0
- package/dist/cjs/components/FieldCode/components/Cell/Cell.js +27 -0
- package/dist/cjs/components/FieldCode/components/Cell/index.d.ts +1 -0
- package/dist/cjs/components/FieldCode/components/Cell/index.js +17 -0
- package/dist/cjs/components/FieldCode/components/Cell/styles.module.css +19 -0
- package/dist/cjs/components/FieldCode/components/ResendCode/ResendCode.d.ts +8 -0
- package/dist/cjs/components/FieldCode/components/ResendCode/ResendCode.js +29 -0
- package/dist/cjs/components/FieldCode/components/ResendCode/index.d.ts +1 -0
- package/dist/cjs/components/FieldCode/components/ResendCode/index.js +17 -0
- package/dist/cjs/components/FieldCode/components/ResendCode/utils.d.ts +1 -0
- package/dist/cjs/components/FieldCode/components/ResendCode/utils.js +8 -0
- package/dist/cjs/components/FieldCode/components/index.d.ts +2 -0
- package/dist/cjs/components/FieldCode/components/index.js +18 -0
- package/dist/cjs/components/FieldCode/constants.d.ts +1 -0
- package/dist/cjs/components/FieldCode/constants.js +4 -0
- package/dist/cjs/components/FieldCode/hooks/index.d.ts +5 -0
- package/dist/cjs/components/FieldCode/hooks/index.js +21 -0
- package/dist/cjs/components/FieldCode/hooks/useCodeInput.d.ts +22 -0
- package/dist/cjs/components/FieldCode/hooks/useCodeInput.js +98 -0
- package/dist/cjs/components/FieldCode/hooks/useFieldCodeOverflow.d.ts +6 -0
- package/dist/cjs/components/FieldCode/hooks/useFieldCodeOverflow.js +43 -0
- package/dist/cjs/components/FieldCode/hooks/useFieldCodeValidate.d.ts +8 -0
- package/dist/cjs/components/FieldCode/hooks/useFieldCodeValidate.js +24 -0
- package/dist/cjs/components/FieldCode/hooks/useFieldHelpers.d.ts +12 -0
- package/dist/cjs/components/FieldCode/hooks/useFieldHelpers.js +30 -0
- package/dist/cjs/components/FieldCode/hooks/useFocusCell.d.ts +5 -0
- package/dist/cjs/components/FieldCode/hooks/useFocusCell.js +22 -0
- package/dist/cjs/components/FieldCode/index.d.ts +2 -0
- package/dist/cjs/components/FieldCode/index.js +20 -0
- package/dist/cjs/components/FieldCode/styles.module.css +41 -0
- package/dist/cjs/components/FieldCode/utils.d.ts +6 -0
- package/dist/cjs/components/FieldCode/utils.js +21 -0
- package/dist/cjs/components/index.d.ts +1 -0
- package/dist/cjs/components/index.js +1 -0
- package/dist/esm/components/FieldCode/FieldCode.d.ts +48 -0
- package/dist/esm/components/FieldCode/FieldCode.js +41 -0
- package/dist/esm/components/FieldCode/components/Cell/Cell.d.ts +7 -0
- package/dist/esm/components/FieldCode/components/Cell/Cell.js +21 -0
- package/dist/esm/components/FieldCode/components/Cell/index.d.ts +1 -0
- package/dist/esm/components/FieldCode/components/Cell/index.js +1 -0
- package/dist/esm/components/FieldCode/components/Cell/styles.module.css +19 -0
- package/dist/esm/components/FieldCode/components/ResendCode/ResendCode.d.ts +8 -0
- package/dist/esm/components/FieldCode/components/ResendCode/ResendCode.js +26 -0
- package/dist/esm/components/FieldCode/components/ResendCode/index.d.ts +1 -0
- package/dist/esm/components/FieldCode/components/ResendCode/index.js +1 -0
- package/dist/esm/components/FieldCode/components/ResendCode/utils.d.ts +1 -0
- package/dist/esm/components/FieldCode/components/ResendCode/utils.js +5 -0
- package/dist/esm/components/FieldCode/components/index.d.ts +2 -0
- package/dist/esm/components/FieldCode/components/index.js +2 -0
- package/dist/esm/components/FieldCode/constants.d.ts +1 -0
- package/dist/esm/components/FieldCode/constants.js +1 -0
- package/dist/esm/components/FieldCode/hooks/index.d.ts +5 -0
- package/dist/esm/components/FieldCode/hooks/index.js +5 -0
- package/dist/esm/components/FieldCode/hooks/useCodeInput.d.ts +22 -0
- package/dist/esm/components/FieldCode/hooks/useCodeInput.js +95 -0
- package/dist/esm/components/FieldCode/hooks/useFieldCodeOverflow.d.ts +6 -0
- package/dist/esm/components/FieldCode/hooks/useFieldCodeOverflow.js +40 -0
- package/dist/esm/components/FieldCode/hooks/useFieldCodeValidate.d.ts +8 -0
- package/dist/esm/components/FieldCode/hooks/useFieldCodeValidate.js +21 -0
- package/dist/esm/components/FieldCode/hooks/useFieldHelpers.d.ts +12 -0
- package/dist/esm/components/FieldCode/hooks/useFieldHelpers.js +27 -0
- package/dist/esm/components/FieldCode/hooks/useFocusCell.d.ts +5 -0
- package/dist/esm/components/FieldCode/hooks/useFocusCell.js +19 -0
- package/dist/esm/components/FieldCode/index.d.ts +2 -0
- package/dist/esm/components/FieldCode/index.js +2 -0
- package/dist/esm/components/FieldCode/styles.module.css +41 -0
- package/dist/esm/components/FieldCode/utils.d.ts +6 -0
- package/dist/esm/components/FieldCode/utils.js +13 -0
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/index.js +1 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +6 -3
- package/src/components/FieldCode/FieldCode.tsx +138 -0
- package/src/components/FieldCode/components/Cell/Cell.tsx +35 -0
- package/src/components/FieldCode/components/Cell/index.ts +1 -0
- package/src/components/FieldCode/components/Cell/styles.module.scss +24 -0
- package/src/components/FieldCode/components/ResendCode/ResendCode.tsx +36 -0
- package/src/components/FieldCode/components/ResendCode/index.ts +1 -0
- package/src/components/FieldCode/components/ResendCode/utils.ts +5 -0
- package/src/components/FieldCode/components/index.ts +2 -0
- package/src/components/FieldCode/constants.ts +1 -0
- package/src/components/FieldCode/hooks/index.ts +5 -0
- package/src/components/FieldCode/hooks/useCodeInput.ts +147 -0
- package/src/components/FieldCode/hooks/useFieldCodeOverflow.ts +54 -0
- package/src/components/FieldCode/hooks/useFieldCodeValidate.ts +35 -0
- package/src/components/FieldCode/hooks/useFieldHelpers.ts +44 -0
- package/src/components/FieldCode/hooks/useFocusCell.ts +29 -0
- package/src/components/FieldCode/index.ts +2 -0
- package/src/components/FieldCode/styles.module.scss +46 -0
- package/src/components/FieldCode/utils.ts +23 -0
- package/src/components/index.ts +1 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useFocusCell = useFocusCell;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
function useFocusCell(codeLength) {
|
|
6
|
+
const inputsRef = (0, react_1.useRef)([]);
|
|
7
|
+
const focusInput = (0, react_1.useCallback)((index) => {
|
|
8
|
+
var _a;
|
|
9
|
+
(_a = inputsRef.current[index]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
10
|
+
}, [inputsRef]);
|
|
11
|
+
const moveFocus = (0, react_1.useCallback)((newIndex) => {
|
|
12
|
+
if (newIndex >= 0 && newIndex < codeLength) {
|
|
13
|
+
focusInput(newIndex);
|
|
14
|
+
}
|
|
15
|
+
}, [codeLength, focusInput]);
|
|
16
|
+
const blurFields = (0, react_1.useCallback)(() => {
|
|
17
|
+
inputsRef.current.forEach(input => {
|
|
18
|
+
input === null || input === void 0 ? void 0 : input.blur();
|
|
19
|
+
});
|
|
20
|
+
}, [inputsRef]);
|
|
21
|
+
return { inputsRef, moveFocus, blurFields };
|
|
22
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.useFieldCodeValidate = void 0;
|
|
18
|
+
__exportStar(require("./FieldCode"), exports);
|
|
19
|
+
var useFieldCodeValidate_1 = require("./hooks/useFieldCodeValidate");
|
|
20
|
+
Object.defineProperty(exports, "useFieldCodeValidate", { enumerable: true, get: function () { return useFieldCodeValidate_1.useFieldCodeValidate; } });
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
.fieldCodeScrollable{
|
|
12
|
+
overflow-x:auto;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.fieldDecorator{
|
|
16
|
+
width:unset;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.codeContainer{
|
|
20
|
+
display:flex;
|
|
21
|
+
justify-content:safe center;
|
|
22
|
+
}
|
|
23
|
+
.codeContainer[data-size=s]{
|
|
24
|
+
gap:8px;
|
|
25
|
+
}
|
|
26
|
+
.codeContainer[data-size=m]{
|
|
27
|
+
gap:8px;
|
|
28
|
+
}
|
|
29
|
+
.codeContainer[data-size=l]{
|
|
30
|
+
gap:12px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.cellSpacing[data-size=s]{
|
|
34
|
+
margin-right:8px;
|
|
35
|
+
}
|
|
36
|
+
.cellSpacing[data-size=m]{
|
|
37
|
+
margin-right:8px;
|
|
38
|
+
}
|
|
39
|
+
.cellSpacing[data-size=l]{
|
|
40
|
+
margin-right:12px;
|
|
41
|
+
}
|
|
@@ -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,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getCellValidationState = exports.isZeroWidthSpace = exports.isStringCodeLength = exports.isNumberChar = void 0;
|
|
4
|
+
exports.getFirstEmptyCellIndex = getFirstEmptyCellIndex;
|
|
5
|
+
const constants_1 = require("./constants");
|
|
6
|
+
const isNumberChar = (char) => /^\d$/.test(char);
|
|
7
|
+
exports.isNumberChar = isNumberChar;
|
|
8
|
+
const isStringCodeLength = (input, codeLength) => new RegExp(`^\\d{${codeLength}}$`).test(input);
|
|
9
|
+
exports.isStringCodeLength = isStringCodeLength;
|
|
10
|
+
const isZeroWidthSpace = (value) => value === constants_1.ZERO_WIDTH_SPACE;
|
|
11
|
+
exports.isZeroWidthSpace = isZeroWidthSpace;
|
|
12
|
+
function getFirstEmptyCellIndex(code) {
|
|
13
|
+
return code.findIndex(exports.isZeroWidthSpace);
|
|
14
|
+
}
|
|
15
|
+
const getCellValidationState = (value, showEmptyChars, error) => {
|
|
16
|
+
if (showEmptyChars) {
|
|
17
|
+
return (0, exports.isZeroWidthSpace)(value) ? 'error' : 'default';
|
|
18
|
+
}
|
|
19
|
+
return error ? 'error' : 'default';
|
|
20
|
+
};
|
|
21
|
+
exports.getCellValidationState = getCellValidationState;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { WithLayoutType } from '@cloud-ru/uikit-product-utils';
|
|
2
|
+
import { FieldDecoratorProps } from '@snack-uikit/fields';
|
|
3
|
+
import { type ResendCodeProps } from './components';
|
|
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
|
+
/** Собственные пропсы `FieldCode` */
|
|
14
|
+
export type FieldCodeOwnProps = WithLayoutType<{
|
|
15
|
+
/** CSS-класс компонента */
|
|
16
|
+
className?: string;
|
|
17
|
+
/** CSS-класс ячейки кода */
|
|
18
|
+
cellClassName?: string;
|
|
19
|
+
/** Позиции, после которых нужно вставить пробел (индексы символов, после которых будет пробел) */
|
|
20
|
+
spacing?: number[];
|
|
21
|
+
/** Подсветить пустые символы кода */
|
|
22
|
+
showEmptyChars?: boolean;
|
|
23
|
+
/** Компонент отправки нового кода */
|
|
24
|
+
resendCode?: ResendCodeProps;
|
|
25
|
+
/** Сообщение при неверном коде, если не передан свой `error` */
|
|
26
|
+
invalidCode?: string;
|
|
27
|
+
/** Растягивать ячейки на всю доступную ширину; иначе фиксированная ширина по `size` */
|
|
28
|
+
stretchCells?: boolean;
|
|
29
|
+
}>;
|
|
30
|
+
export type FieldCodeProps = FieldCodeOwnProps & Omit<UseCodeInputParams, 'moveFocus'> & Pick<FieldDecoratorProps, 'size' | 'disabled' | 'label' | 'error' | 'data-test-id'>;
|
|
31
|
+
export declare const FieldCode: import("react").ForwardRefExoticComponent<{
|
|
32
|
+
/** CSS-класс компонента */
|
|
33
|
+
className?: string;
|
|
34
|
+
/** CSS-класс ячейки кода */
|
|
35
|
+
cellClassName?: string;
|
|
36
|
+
/** Позиции, после которых нужно вставить пробел (индексы символов, после которых будет пробел) */
|
|
37
|
+
spacing?: number[];
|
|
38
|
+
/** Подсветить пустые символы кода */
|
|
39
|
+
showEmptyChars?: boolean;
|
|
40
|
+
/** Компонент отправки нового кода */
|
|
41
|
+
resendCode?: ResendCodeProps;
|
|
42
|
+
/** Сообщение при неверном коде, если не передан свой `error` */
|
|
43
|
+
invalidCode?: string;
|
|
44
|
+
/** Растягивать ячейки на всю доступную ширину; иначе фиксированная ширина по `size` */
|
|
45
|
+
stretchCells?: boolean;
|
|
46
|
+
} & {
|
|
47
|
+
layoutType: import("@cloud-ru/uikit-product-utils/.").LayoutType;
|
|
48
|
+
} & Omit<UseCodeInputParams, "moveFocus"> & Pick<FieldDecoratorProps, "size" | "label" | "error" | "disabled" | "data-test-id"> & import("react").RefAttributes<FieldCodeRef>>;
|
|
@@ -0,0 +1,41 @@
|
|
|
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 { useCodeInput, useFieldCodeOverflow, useFieldHelpers, useFocusCell } from './hooks';
|
|
7
|
+
import styles from './styles.module.css';
|
|
8
|
+
import { getCellValidationState } from './utils';
|
|
9
|
+
export const FieldCode = forwardRef(function FieldCode(props, ref) {
|
|
10
|
+
var _a;
|
|
11
|
+
const { codeLength, className, cellClassName, value, onChange, spacing, onComplete, size, disabled, label, error, invalidCode, showEmptyChars, resendCode, layoutType, stretchCells = false, 'data-test-id': dataTestId, } = props;
|
|
12
|
+
const isMobile = layoutType === 'mobile';
|
|
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
|
+
isMobile,
|
|
18
|
+
moveFocus,
|
|
19
|
+
showEmptyChars,
|
|
20
|
+
code,
|
|
21
|
+
codeLength,
|
|
22
|
+
});
|
|
23
|
+
const { rootRef, codeContainerRef, hasOverflow } = useFieldCodeOverflow();
|
|
24
|
+
useImperativeHandle(ref, () => ({
|
|
25
|
+
moveFocus,
|
|
26
|
+
blurFields,
|
|
27
|
+
resetCode,
|
|
28
|
+
}), [moveFocus, blurFields, resetCode]);
|
|
29
|
+
const resolvedError = error !== null && error !== void 0 ? error : invalidCode;
|
|
30
|
+
const resolvedDecoratorProps = {
|
|
31
|
+
label,
|
|
32
|
+
disabled,
|
|
33
|
+
size,
|
|
34
|
+
error: resolvedError,
|
|
35
|
+
};
|
|
36
|
+
return (_jsxs("div", Object.assign({ ref: rootRef, className: cn(styles.fieldCode, hasOverflow && styles.fieldCodeScrollable, className), "data-stretch-cells": stretchCells || undefined }, (dataTestId ? { 'data-test-id': dataTestId } : undefined), { children: [_jsx(FieldDecorator, Object.assign({ className: cn(!hasOverflow && styles.fieldDecorator) }, resolvedDecoratorProps, { children: _jsx("div", { ref: codeContainerRef, className: styles.codeContainer, "data-size": size, "data-stretch-cells": stretchCells || undefined, children: code.map((char, index) => (_jsx(Cell, { ref: inputRef => {
|
|
37
|
+
if (inputRef) {
|
|
38
|
+
inputsRef.current[index] = inputRef;
|
|
39
|
+
}
|
|
40
|
+
}, className: cn((spacing === null || spacing === void 0 ? void 0 : spacing.includes(index)) && styles.cellSpacing, cellClassName), stretchCells: stretchCells, 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(resolvedError)) }, index))) }) })), resendCode ? _jsx(ResendCode, Object.assign({}, resendCode, { size: (_a = resendCode.size) !== null && _a !== void 0 ? _a : size })) : null] })));
|
|
41
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { FieldTextProps } from '@snack-uikit/fields';
|
|
2
|
+
export declare const Cell: import("react").ForwardRefExoticComponent<{
|
|
3
|
+
/** CSS-класс ячейки кода */
|
|
4
|
+
className?: string;
|
|
5
|
+
/** Растягивать ячейку на всю доступную ширину */
|
|
6
|
+
stretchCells?: boolean;
|
|
7
|
+
} & 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, stretchCells, value } = props, fieldCellProps = __rest(props, ["className", "size", "stretchCells", "value"]);
|
|
20
|
+
return (_jsx(FieldText, Object.assign({ inputMode: 'numeric', ref: ref, className: cn(styles.cell, className), "data-size": size, "data-stretch-cells": stretchCells || undefined, 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,19 @@
|
|
|
1
|
+
.cell{
|
|
2
|
+
flex-shrink:0;
|
|
3
|
+
}
|
|
4
|
+
.cell input{
|
|
5
|
+
text-align:center;
|
|
6
|
+
}
|
|
7
|
+
.cell[data-size=s]:not([data-stretch-cells]){
|
|
8
|
+
width:32px;
|
|
9
|
+
}
|
|
10
|
+
.cell[data-size=m]:not([data-stretch-cells]){
|
|
11
|
+
width:40px;
|
|
12
|
+
}
|
|
13
|
+
.cell[data-size=l]:not([data-stretch-cells]){
|
|
14
|
+
width:52px;
|
|
15
|
+
}
|
|
16
|
+
.cell[data-stretch-cells]{
|
|
17
|
+
flex:1 1 0;
|
|
18
|
+
min-width:0;
|
|
19
|
+
}
|
|
@@ -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,26 @@
|
|
|
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
|
+
const isResendCodeWithVia = secondsToNextResend > 0;
|
|
21
|
+
return (_jsx(ButtonFunction, Object.assign({ label: isResendCodeWithVia
|
|
22
|
+
? t('FieldCode.resendCodeButtons.resendCodeWithVia', {
|
|
23
|
+
timer: formatSecondsAsMmSs(secondsToNextResend),
|
|
24
|
+
})
|
|
25
|
+
: t('FieldCode.resendCodeButtons.resendCode'), onClick: onResend, icon: _jsx(UpdateSVG, {}), iconPosition: 'before', disabled: isResendCodeWithVia !== null && isResendCodeWithVia !== void 0 ? isResendCodeWithVia : buttonProps.disabled }, buttonProps)));
|
|
26
|
+
}
|
|
@@ -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 @@
|
|
|
1
|
+
export declare const ZERO_WIDTH_SPACE = "\u200B";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const ZERO_WIDTH_SPACE = '\u200B';
|
|
@@ -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,6 @@
|
|
|
1
|
+
/** Fallback на крайний случай: включает горизонтальный скролл, если ряд ячеек шире контейнера. */
|
|
2
|
+
export declare function useFieldCodeOverflow(): {
|
|
3
|
+
rootRef: import("react").RefObject<HTMLDivElement>;
|
|
4
|
+
codeContainerRef: import("react").RefObject<HTMLDivElement>;
|
|
5
|
+
hasOverflow: boolean;
|
|
6
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useCallback, useRef, useState } from 'react';
|
|
2
|
+
import { useLayoutEffect } from '@snack-uikit/utils';
|
|
3
|
+
function hasCodeOverflow(root, codeContainer) {
|
|
4
|
+
return codeContainer.scrollWidth > root.clientWidth;
|
|
5
|
+
}
|
|
6
|
+
/** Fallback на крайний случай: включает горизонтальный скролл, если ряд ячеек шире контейнера. */
|
|
7
|
+
export function useFieldCodeOverflow() {
|
|
8
|
+
const [hasOverflow, setHasOverflow] = useState(false);
|
|
9
|
+
const rootRef = useRef(null);
|
|
10
|
+
const codeContainerRef = useRef(null);
|
|
11
|
+
const measure = useCallback(() => {
|
|
12
|
+
const root = rootRef.current;
|
|
13
|
+
const codeContainer = codeContainerRef.current;
|
|
14
|
+
if (!root || !codeContainer) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
setHasOverflow(prev => {
|
|
18
|
+
const next = hasCodeOverflow(root, codeContainer);
|
|
19
|
+
return prev === next ? prev : next;
|
|
20
|
+
});
|
|
21
|
+
}, []);
|
|
22
|
+
useLayoutEffect(() => {
|
|
23
|
+
const root = rootRef.current;
|
|
24
|
+
const codeContainer = codeContainerRef.current;
|
|
25
|
+
if (!root || !codeContainer) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
measure();
|
|
29
|
+
const resizeObserver = new ResizeObserver(measure);
|
|
30
|
+
resizeObserver.observe(root);
|
|
31
|
+
resizeObserver.observe(codeContainer);
|
|
32
|
+
const mutationObserver = new MutationObserver(measure);
|
|
33
|
+
mutationObserver.observe(codeContainer, { childList: true });
|
|
34
|
+
return () => {
|
|
35
|
+
resizeObserver.disconnect();
|
|
36
|
+
mutationObserver.disconnect();
|
|
37
|
+
};
|
|
38
|
+
}, [measure]);
|
|
39
|
+
return { rootRef, codeContainerRef, hasOverflow };
|
|
40
|
+
}
|
|
@@ -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,12 @@
|
|
|
1
|
+
type UseFieldHelpersParams = {
|
|
2
|
+
onChangeCode: (code: string) => void;
|
|
3
|
+
moveFocus: (index: number) => void;
|
|
4
|
+
isMobile?: boolean;
|
|
5
|
+
showEmptyChars?: boolean;
|
|
6
|
+
code: readonly string[];
|
|
7
|
+
codeLength: number;
|
|
8
|
+
};
|
|
9
|
+
export declare function useFieldHelpers(params: UseFieldHelpersParams): {
|
|
10
|
+
resetCode: () => void;
|
|
11
|
+
};
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useCallback, useEffect } from 'react';
|
|
2
|
+
import { getFirstEmptyCellIndex } from '../utils';
|
|
3
|
+
export function useFieldHelpers(params) {
|
|
4
|
+
const { onChangeCode, moveFocus, isMobile = false, showEmptyChars, code, codeLength } = params;
|
|
5
|
+
const resetCode = useCallback(() => {
|
|
6
|
+
onChangeCode('');
|
|
7
|
+
if (!isMobile) {
|
|
8
|
+
moveFocus(0);
|
|
9
|
+
}
|
|
10
|
+
}, [isMobile, moveFocus, onChangeCode]);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (!isMobile) {
|
|
13
|
+
moveFocus(0);
|
|
14
|
+
}
|
|
15
|
+
}, [isMobile, moveFocus]);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (isMobile || !showEmptyChars) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const emptyIndex = getFirstEmptyCellIndex(code);
|
|
21
|
+
if (emptyIndex >= 0) {
|
|
22
|
+
moveFocus(emptyIndex);
|
|
23
|
+
}
|
|
24
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
25
|
+
}, [showEmptyChars, isMobile, moveFocus, codeLength]);
|
|
26
|
+
return { resetCode };
|
|
27
|
+
}
|
|
@@ -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
|
+
}
|