@rovula/ui 0.0.76 → 0.0.78
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/dist/cjs/bundle.css +40 -0
- package/dist/cjs/bundle.js +3 -3
- package/dist/cjs/bundle.js.map +1 -1
- package/dist/cjs/types/components/Dropdown/Dropdown.stories.d.ts +7 -0
- package/dist/cjs/types/components/InputFilter/InputFilter.stories.d.ts +7 -0
- package/dist/cjs/types/components/MaskedTextInput/MaskedTextInput.d.ts +75 -0
- package/dist/cjs/types/components/MaskedTextInput/MaskedTextInput.stories.d.ts +491 -0
- package/dist/cjs/types/components/MaskedTextInput/index.d.ts +3 -0
- package/dist/cjs/types/components/NumberInput/NumberInput.d.ts +39 -0
- package/dist/cjs/types/components/NumberInput/NumberInput.stories.d.ts +18 -0
- package/dist/cjs/types/components/NumberInput/index.d.ts +2 -0
- package/dist/cjs/types/components/RadioGroup/RadioGroup.stories.d.ts +1 -1
- package/dist/cjs/types/components/Search/Search.stories.d.ts +7 -0
- package/dist/cjs/types/components/Slider/Slider.stories.d.ts +1 -1
- package/dist/cjs/types/components/TextInput/TextInput.d.ts +14 -0
- package/dist/cjs/types/components/TextInput/TextInput.stories.d.ts +14 -0
- package/dist/cjs/types/index.d.ts +4 -0
- package/dist/components/MaskedTextInput/MaskedTextInput.js +267 -0
- package/dist/components/MaskedTextInput/MaskedTextInput.stories.js +167 -0
- package/dist/components/MaskedTextInput/index.js +2 -0
- package/dist/components/NumberInput/NumberInput.js +254 -0
- package/dist/components/NumberInput/NumberInput.stories.js +212 -0
- package/dist/components/NumberInput/index.js +1 -0
- package/dist/components/TextInput/TextInput.js +13 -11
- package/dist/components/Toast/Toast.styles.js +1 -1
- package/dist/esm/bundle.css +40 -0
- package/dist/esm/bundle.js +3 -3
- package/dist/esm/bundle.js.map +1 -1
- package/dist/esm/types/components/Dropdown/Dropdown.stories.d.ts +7 -0
- package/dist/esm/types/components/InputFilter/InputFilter.stories.d.ts +7 -0
- package/dist/esm/types/components/MaskedTextInput/MaskedTextInput.d.ts +75 -0
- package/dist/esm/types/components/MaskedTextInput/MaskedTextInput.stories.d.ts +491 -0
- package/dist/esm/types/components/MaskedTextInput/index.d.ts +3 -0
- package/dist/esm/types/components/NumberInput/NumberInput.d.ts +39 -0
- package/dist/esm/types/components/NumberInput/NumberInput.stories.d.ts +18 -0
- package/dist/esm/types/components/NumberInput/index.d.ts +2 -0
- package/dist/esm/types/components/RadioGroup/RadioGroup.stories.d.ts +1 -1
- package/dist/esm/types/components/Search/Search.stories.d.ts +7 -0
- package/dist/esm/types/components/Slider/Slider.stories.d.ts +1 -1
- package/dist/esm/types/components/TextInput/TextInput.d.ts +14 -0
- package/dist/esm/types/components/TextInput/TextInput.stories.d.ts +14 -0
- package/dist/esm/types/index.d.ts +4 -0
- package/dist/index.d.ts +110 -1
- package/dist/index.js +2 -0
- package/dist/src/theme/global.css +51 -0
- package/package.json +1 -1
- package/src/components/MaskedTextInput/MaskedTextInput.stories.tsx +414 -0
- package/src/components/MaskedTextInput/MaskedTextInput.tsx +391 -0
- package/src/components/MaskedTextInput/README.md +202 -0
- package/src/components/MaskedTextInput/index.ts +3 -0
- package/src/components/NumberInput/NumberInput.stories.tsx +350 -0
- package/src/components/NumberInput/NumberInput.tsx +428 -0
- package/src/components/NumberInput/index.ts +2 -0
- package/src/components/TextInput/TextInput.tsx +54 -12
- package/src/components/Toast/Toast.styles.tsx +1 -1
- package/src/index.ts +7 -0
|
@@ -279,9 +279,9 @@ declare const meta: {
|
|
|
279
279
|
inputMode?: "none" | "text" | "tel" | "url" | "email" | "numeric" | "decimal" | "search" | undefined | undefined;
|
|
280
280
|
is?: string | undefined | undefined;
|
|
281
281
|
required?: boolean | undefined;
|
|
282
|
+
onValueChange?: ((value: string) => void) | undefined;
|
|
282
283
|
loop?: boolean | undefined;
|
|
283
284
|
asChild?: boolean | undefined;
|
|
284
|
-
onValueChange?: ((value: string) => void) | undefined;
|
|
285
285
|
ref?: React.LegacyRef<HTMLDivElement> | undefined;
|
|
286
286
|
}>) => import("react/jsx-runtime").JSX.Element)[];
|
|
287
287
|
};
|
|
@@ -314,6 +314,13 @@ declare const meta: {
|
|
|
314
314
|
helperText?: string | undefined;
|
|
315
315
|
errorMessage?: string | undefined;
|
|
316
316
|
labelClassName?: string | undefined;
|
|
317
|
+
classes?: {
|
|
318
|
+
iconWrapper?: string;
|
|
319
|
+
iconSearchWrapper?: string;
|
|
320
|
+
icon?: string;
|
|
321
|
+
startIconWrapper?: string;
|
|
322
|
+
endIconWrapper?: string;
|
|
323
|
+
} | undefined;
|
|
317
324
|
onClickStartIcon?: (() => void) | undefined;
|
|
318
325
|
onClickEndIcon?: (() => void) | undefined;
|
|
319
326
|
renderStartIcon?: (() => React.ReactNode) | undefined;
|
|
@@ -289,10 +289,10 @@ declare const meta: {
|
|
|
289
289
|
unselectable?: "on" | "off" | undefined | undefined;
|
|
290
290
|
inputMode?: "none" | "text" | "tel" | "url" | "email" | "numeric" | "decimal" | "search" | undefined | undefined;
|
|
291
291
|
is?: string | undefined | undefined;
|
|
292
|
+
onValueChange?: ((value: number[]) => void) | undefined;
|
|
292
293
|
asChild?: boolean | undefined;
|
|
293
294
|
inverted?: boolean | undefined;
|
|
294
295
|
minStepsBetweenThumbs?: number | undefined;
|
|
295
|
-
onValueChange?: ((value: number[]) => void) | undefined;
|
|
296
296
|
onValueCommit?: ((value: number[]) => void) | undefined;
|
|
297
297
|
ref?: React.LegacyRef<HTMLSpanElement> | undefined;
|
|
298
298
|
}>) => import("react/jsx-runtime").JSX.Element)[];
|
|
@@ -21,6 +21,13 @@ export type InputProps = {
|
|
|
21
21
|
endIcon?: ReactNode;
|
|
22
22
|
className?: string;
|
|
23
23
|
labelClassName?: string;
|
|
24
|
+
classes?: {
|
|
25
|
+
iconWrapper?: string;
|
|
26
|
+
iconSearchWrapper?: string;
|
|
27
|
+
icon?: string;
|
|
28
|
+
startIconWrapper?: string;
|
|
29
|
+
endIconWrapper?: string;
|
|
30
|
+
};
|
|
24
31
|
onClickStartIcon?: () => void;
|
|
25
32
|
onClickEndIcon?: () => void;
|
|
26
33
|
renderStartIcon?: () => ReactNode;
|
|
@@ -48,6 +55,13 @@ export declare const TextInput: React.ForwardRefExoticComponent<{
|
|
|
48
55
|
endIcon?: ReactNode;
|
|
49
56
|
className?: string;
|
|
50
57
|
labelClassName?: string;
|
|
58
|
+
classes?: {
|
|
59
|
+
iconWrapper?: string;
|
|
60
|
+
iconSearchWrapper?: string;
|
|
61
|
+
icon?: string;
|
|
62
|
+
startIconWrapper?: string;
|
|
63
|
+
endIconWrapper?: string;
|
|
64
|
+
};
|
|
51
65
|
onClickStartIcon?: () => void;
|
|
52
66
|
onClickEndIcon?: () => void;
|
|
53
67
|
renderStartIcon?: () => ReactNode;
|
|
@@ -23,6 +23,13 @@ declare const meta: {
|
|
|
23
23
|
endIcon?: React.ReactNode;
|
|
24
24
|
className?: string;
|
|
25
25
|
labelClassName?: string;
|
|
26
|
+
classes?: {
|
|
27
|
+
iconWrapper?: string;
|
|
28
|
+
iconSearchWrapper?: string;
|
|
29
|
+
icon?: string;
|
|
30
|
+
startIconWrapper?: string;
|
|
31
|
+
endIconWrapper?: string;
|
|
32
|
+
};
|
|
26
33
|
onClickStartIcon?: () => void;
|
|
27
34
|
onClickEndIcon?: () => void;
|
|
28
35
|
renderStartIcon?: () => React.ReactNode;
|
|
@@ -54,6 +61,13 @@ declare const meta: {
|
|
|
54
61
|
endIcon?: React.ReactNode;
|
|
55
62
|
className?: string | undefined;
|
|
56
63
|
labelClassName?: string | undefined;
|
|
64
|
+
classes?: {
|
|
65
|
+
iconWrapper?: string;
|
|
66
|
+
iconSearchWrapper?: string;
|
|
67
|
+
icon?: string;
|
|
68
|
+
startIconWrapper?: string;
|
|
69
|
+
endIconWrapper?: string;
|
|
70
|
+
} | undefined;
|
|
57
71
|
onClickStartIcon?: (() => void) | undefined;
|
|
58
72
|
onClickEndIcon?: (() => void) | undefined;
|
|
59
73
|
renderStartIcon?: (() => React.ReactNode) | undefined;
|
|
@@ -2,6 +2,8 @@ import "./theme/global.css";
|
|
|
2
2
|
import "./icons/iconConfig";
|
|
3
3
|
export { default as Button } from "./components/Button/Button";
|
|
4
4
|
export { default as TextInput } from "./components/TextInput/TextInput";
|
|
5
|
+
export { default as MaskedTextInput } from "./components/MaskedTextInput";
|
|
6
|
+
export { NumberInput } from "./components/NumberInput/NumberInput";
|
|
5
7
|
export { default as TextArea } from "./components/TextArea/TextArea";
|
|
6
8
|
export { default as Text } from "./components/Text/Text";
|
|
7
9
|
export { default as Tabs } from "./components/Tabs/Tabs";
|
|
@@ -38,6 +40,8 @@ export * from "./components/FocusedScrollView/FocusedScrollView";
|
|
|
38
40
|
export * from "./components/RadioGroup/RadioGroup";
|
|
39
41
|
export type { ButtonProps } from "./components/Button/Button";
|
|
40
42
|
export type { InputProps } from "./components/TextInput/TextInput";
|
|
43
|
+
export type { MaskedTextInputProps, MaskRule, } from "./components/MaskedTextInput";
|
|
44
|
+
export type { NumberInputProps } from "./components/NumberInput/NumberInput";
|
|
41
45
|
export type { TextAreaProps } from "./components/TextArea/TextArea";
|
|
42
46
|
export type { DropdownProps, Options } from "./components/Dropdown/Dropdown";
|
|
43
47
|
export type { NavbarProps } from "./components/Navbar/Navbar";
|
|
@@ -0,0 +1,267 @@
|
|
|
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 React, { forwardRef, useCallback, useImperativeHandle, useRef, useState, useEffect, } from "react";
|
|
14
|
+
import TextInput from "../TextInput/TextInput";
|
|
15
|
+
import { cn } from "@/utils/cn";
|
|
16
|
+
// Kendo UI style mask patterns
|
|
17
|
+
export const MASK_PATTERNS = {
|
|
18
|
+
PHONE: "(000) 000-0000",
|
|
19
|
+
PHONE_INTL: "+000 000 000 0000",
|
|
20
|
+
CREDIT_CARD: "0000 0000 0000 0000",
|
|
21
|
+
DATE: "00/00/0000",
|
|
22
|
+
TIME: "00:00",
|
|
23
|
+
SSN: "000-00-0000",
|
|
24
|
+
ZIP_CODE: "00000",
|
|
25
|
+
ZIP_CODE_EXT: "00000-0000",
|
|
26
|
+
CURRENCY: "$000,000.00",
|
|
27
|
+
PERCENTAGE: "000%",
|
|
28
|
+
LICENSE_PLATE: "AAA-0000",
|
|
29
|
+
PRODUCT_CODE: "AA-0000-AA",
|
|
30
|
+
ALPHANUMERIC: "AAAA-0000",
|
|
31
|
+
};
|
|
32
|
+
// Kendo UI mask rules
|
|
33
|
+
const KENDO_RULES = {
|
|
34
|
+
"0": { pattern: /[0-9]/, placeholder: "0" }, // Any digit 0-9
|
|
35
|
+
"9": { pattern: /[0-9\s]/, placeholder: "9" }, // Any digit 0-9 or space
|
|
36
|
+
"#": { pattern: /[0-9\s+\-]/, placeholder: "#" }, // Any digit, space, +, or -
|
|
37
|
+
L: { pattern: /[a-zA-Z]/, placeholder: "L" }, // Any letter
|
|
38
|
+
"?": { pattern: /[a-zA-Z\s]/, placeholder: "?" }, // Any letter or space
|
|
39
|
+
"&": { pattern: /[^\s]/, placeholder: "&" }, // Any character except space
|
|
40
|
+
C: { pattern: /./, placeholder: "C" }, // Any character including space
|
|
41
|
+
A: { pattern: /[a-zA-Z0-9]/, placeholder: "A" }, // Any alphanumeric
|
|
42
|
+
a: { pattern: /[a-zA-Z0-9\s]/, placeholder: "a" }, // Any alphanumeric or space
|
|
43
|
+
};
|
|
44
|
+
// Helper function to create mask pattern from string using Kendo UI rules
|
|
45
|
+
const createMaskPattern = (mask, customRules) => {
|
|
46
|
+
const rules = Object.assign(Object.assign({}, KENDO_RULES), customRules);
|
|
47
|
+
const pattern = [];
|
|
48
|
+
let i = 0;
|
|
49
|
+
while (i < mask.length) {
|
|
50
|
+
const char = mask[i];
|
|
51
|
+
if (char === "\\" && i + 1 < mask.length) {
|
|
52
|
+
// Escaped character - treat as literal
|
|
53
|
+
const nextChar = mask[i + 1];
|
|
54
|
+
pattern.push({
|
|
55
|
+
pattern: new RegExp(`^${nextChar}$`),
|
|
56
|
+
placeholder: nextChar,
|
|
57
|
+
isLiteral: true,
|
|
58
|
+
});
|
|
59
|
+
i += 2;
|
|
60
|
+
}
|
|
61
|
+
else if (rules[char]) {
|
|
62
|
+
// Apply Kendo rule
|
|
63
|
+
const rule = rules[char];
|
|
64
|
+
if (typeof rule === "function") {
|
|
65
|
+
pattern.push({
|
|
66
|
+
pattern: /./, // Accept any character, validate with function
|
|
67
|
+
placeholder: char,
|
|
68
|
+
validator: rule,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
else if (rule instanceof RegExp) {
|
|
72
|
+
pattern.push({
|
|
73
|
+
pattern: rule,
|
|
74
|
+
placeholder: char,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
pattern.push({
|
|
79
|
+
pattern: rule.pattern,
|
|
80
|
+
placeholder: rule.placeholder,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
i++;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// Literal character
|
|
87
|
+
pattern.push({
|
|
88
|
+
pattern: new RegExp(`^${char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`),
|
|
89
|
+
placeholder: char,
|
|
90
|
+
isLiteral: true,
|
|
91
|
+
});
|
|
92
|
+
i++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return pattern;
|
|
96
|
+
};
|
|
97
|
+
// Helper function to apply mask to value using Kendo UI rules
|
|
98
|
+
const applyMask = (value, maskPattern, maskChar = "_", showMask = true, guide = true) => {
|
|
99
|
+
let maskedValue = "";
|
|
100
|
+
let valueIndex = 0;
|
|
101
|
+
for (let i = 0; i < maskPattern.length; i++) {
|
|
102
|
+
const rule = maskPattern[i];
|
|
103
|
+
if (rule.isLiteral) {
|
|
104
|
+
// Literal character - always add
|
|
105
|
+
maskedValue += rule.placeholder;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
// Input character - find next valid character
|
|
109
|
+
let foundValid = false;
|
|
110
|
+
while (valueIndex < value.length) {
|
|
111
|
+
const char = value[valueIndex];
|
|
112
|
+
valueIndex++;
|
|
113
|
+
// Check with validator function first if exists
|
|
114
|
+
const isValid = rule.validator
|
|
115
|
+
? rule.validator(char)
|
|
116
|
+
: rule.pattern.test(char);
|
|
117
|
+
if (isValid) {
|
|
118
|
+
maskedValue += char;
|
|
119
|
+
foundValid = true;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
// Skip invalid characters and continue searching
|
|
123
|
+
}
|
|
124
|
+
if (!foundValid && guide && showMask) {
|
|
125
|
+
// No valid character found, fill with placeholder
|
|
126
|
+
maskedValue += maskChar;
|
|
127
|
+
}
|
|
128
|
+
else if (!foundValid) {
|
|
129
|
+
// No placeholder needed, stop building the mask
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return maskedValue;
|
|
135
|
+
};
|
|
136
|
+
// Helper function to extract raw value from masked value
|
|
137
|
+
const extractRawValue = (maskedValue, maskPattern) => {
|
|
138
|
+
let rawValue = "";
|
|
139
|
+
for (let i = 0; i < maskedValue.length && i < maskPattern.length; i++) {
|
|
140
|
+
const rule = maskPattern[i];
|
|
141
|
+
const char = maskedValue[i];
|
|
142
|
+
if (!rule.isLiteral) {
|
|
143
|
+
// Only include non-literal characters in raw value
|
|
144
|
+
rawValue += char;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return rawValue;
|
|
148
|
+
};
|
|
149
|
+
// Helper function to get cursor position after masking
|
|
150
|
+
const getCursorPosition = (maskedValue, rawInputLength, maskPattern) => {
|
|
151
|
+
let inputCount = 0;
|
|
152
|
+
let cursorPos = 0;
|
|
153
|
+
for (let i = 0; i < maskPattern.length && i < maskedValue.length; i++) {
|
|
154
|
+
const rule = maskPattern[i];
|
|
155
|
+
const char = maskedValue[i];
|
|
156
|
+
if (rule.isLiteral) {
|
|
157
|
+
cursorPos = i + 1;
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
inputCount++;
|
|
161
|
+
cursorPos = i + 1;
|
|
162
|
+
if (inputCount >= rawInputLength) {
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return cursorPos;
|
|
168
|
+
};
|
|
169
|
+
export const MaskedTextInput = forwardRef((_a, ref) => {
|
|
170
|
+
var { mask, maskChar = "_", showMask = true, guide = true, keepCharPositions = false, rules, onMaskedChange, onChange, value, defaultValue } = _a, props = __rest(_a, ["mask", "maskChar", "showMask", "guide", "keepCharPositions", "rules", "onMaskedChange", "onChange", "value", "defaultValue"]);
|
|
171
|
+
const inputRef = useRef(null);
|
|
172
|
+
const [maskedValue, setMaskedValue] = useState("");
|
|
173
|
+
const [rawValue, setRawValue] = useState("");
|
|
174
|
+
// Parse mask pattern using Kendo UI rules
|
|
175
|
+
const maskPattern = React.useMemo(() => {
|
|
176
|
+
if (!mask)
|
|
177
|
+
return null;
|
|
178
|
+
return createMaskPattern(mask, rules);
|
|
179
|
+
}, [mask, rules]);
|
|
180
|
+
// Initialize values
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
const initialValue = value || defaultValue || "";
|
|
183
|
+
if (maskPattern && initialValue) {
|
|
184
|
+
const masked = applyMask(initialValue, maskPattern, maskChar, showMask, guide);
|
|
185
|
+
const raw = extractRawValue(masked, maskPattern);
|
|
186
|
+
setMaskedValue(masked);
|
|
187
|
+
setRawValue(raw);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
setMaskedValue(initialValue);
|
|
191
|
+
setRawValue(initialValue);
|
|
192
|
+
}
|
|
193
|
+
}, [maskPattern, maskChar, showMask, guide, value, defaultValue]);
|
|
194
|
+
useImperativeHandle(ref, () => inputRef === null || inputRef === void 0 ? void 0 : inputRef.current);
|
|
195
|
+
const handleChange = useCallback((event) => {
|
|
196
|
+
const inputValue = event.target.value;
|
|
197
|
+
if (!maskPattern) {
|
|
198
|
+
setMaskedValue(inputValue);
|
|
199
|
+
setRawValue(inputValue);
|
|
200
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(event);
|
|
201
|
+
onMaskedChange === null || onMaskedChange === void 0 ? void 0 : onMaskedChange(inputValue, inputValue);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const newMaskedValue = applyMask(inputValue, maskPattern, maskChar, showMask, guide);
|
|
205
|
+
const newRawValue = extractRawValue(newMaskedValue, maskPattern);
|
|
206
|
+
setMaskedValue(newMaskedValue);
|
|
207
|
+
setRawValue(newRawValue);
|
|
208
|
+
// Create synthetic event with masked value
|
|
209
|
+
const syntheticEvent = Object.assign(Object.assign({}, event), { target: Object.assign(Object.assign({}, event.target), { value: newMaskedValue }) });
|
|
210
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(syntheticEvent);
|
|
211
|
+
onMaskedChange === null || onMaskedChange === void 0 ? void 0 : onMaskedChange(newMaskedValue, newRawValue);
|
|
212
|
+
// Set cursor position after state update
|
|
213
|
+
setTimeout(() => {
|
|
214
|
+
if (inputRef.current) {
|
|
215
|
+
const rawLength = newRawValue.replace(/[_\s]/g, "").length;
|
|
216
|
+
const newCursorPos = getCursorPosition(newMaskedValue, rawLength, maskPattern);
|
|
217
|
+
inputRef.current.setSelectionRange(newCursorPos, newCursorPos);
|
|
218
|
+
}
|
|
219
|
+
}, 0);
|
|
220
|
+
}, [maskPattern, maskChar, showMask, guide, onChange, onMaskedChange]);
|
|
221
|
+
const handleKeyDown = useCallback((event) => {
|
|
222
|
+
var _a, _b, _c;
|
|
223
|
+
if (!maskPattern) {
|
|
224
|
+
(_a = props.onKeyDown) === null || _a === void 0 ? void 0 : _a.call(props, event);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const { key, ctrlKey, metaKey } = event;
|
|
228
|
+
const input = event.target;
|
|
229
|
+
const cursorPos = input.selectionStart || 0;
|
|
230
|
+
// Allow navigation and editing keys
|
|
231
|
+
if (key === "Backspace" ||
|
|
232
|
+
key === "Delete" ||
|
|
233
|
+
key === "ArrowLeft" ||
|
|
234
|
+
key === "ArrowRight" ||
|
|
235
|
+
key === "Home" ||
|
|
236
|
+
key === "End" ||
|
|
237
|
+
key === "Tab" ||
|
|
238
|
+
key.length > 1 || // Allow other special keys
|
|
239
|
+
ctrlKey ||
|
|
240
|
+
metaKey) {
|
|
241
|
+
(_b = props.onKeyDown) === null || _b === void 0 ? void 0 : _b.call(props, event);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// Find the next non-literal position from cursor
|
|
245
|
+
let targetPos = cursorPos;
|
|
246
|
+
while (targetPos < maskPattern.length &&
|
|
247
|
+
maskPattern[targetPos].isLiteral) {
|
|
248
|
+
targetPos++;
|
|
249
|
+
}
|
|
250
|
+
// Check if we have a valid position and the key matches
|
|
251
|
+
if (targetPos < maskPattern.length) {
|
|
252
|
+
const targetRule = maskPattern[targetPos];
|
|
253
|
+
// Check with validator function first if exists
|
|
254
|
+
const isValid = targetRule.validator
|
|
255
|
+
? targetRule.validator(key)
|
|
256
|
+
: targetRule.pattern.test(key);
|
|
257
|
+
if (!isValid) {
|
|
258
|
+
event.preventDefault();
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
(_c = props.onKeyDown) === null || _c === void 0 ? void 0 : _c.call(props, event);
|
|
263
|
+
}, [maskPattern, props]);
|
|
264
|
+
return (_jsx(TextInput, Object.assign({}, props, { ref: inputRef, value: maskedValue, onChange: handleChange, onKeyDown: handleKeyDown, className: cn(props.className) })));
|
|
265
|
+
});
|
|
266
|
+
MaskedTextInput.displayName = "MaskedTextInput";
|
|
267
|
+
export default MaskedTextInput;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import MaskedTextInput, { MASK_PATTERNS } from "./MaskedTextInput";
|
|
4
|
+
// More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction
|
|
5
|
+
const meta = {
|
|
6
|
+
title: "Components/MaskedTextInput",
|
|
7
|
+
component: MaskedTextInput,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
parameters: {
|
|
10
|
+
// More on how to position stories at: https://storybook.js.org/docs/7.0/react/configure/story-layout
|
|
11
|
+
layout: "fullscreen",
|
|
12
|
+
},
|
|
13
|
+
decorators: [
|
|
14
|
+
(Story) => (_jsx("div", { className: "p-5 flex w-full bg-[rgb(var(--base-bg-2))] ", children: _jsx(Story, {}) })),
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
export default meta;
|
|
18
|
+
export const Default = {
|
|
19
|
+
args: {
|
|
20
|
+
label: "Phone Number",
|
|
21
|
+
mask: MASK_PATTERNS.PHONE,
|
|
22
|
+
fullwidth: true,
|
|
23
|
+
},
|
|
24
|
+
render: (args) => {
|
|
25
|
+
return (_jsxs("div", { className: "flex flex-row gap-4 w-full", children: [_jsx(MaskedTextInput, Object.assign({ id: "1", size: "lg" }, args)), _jsx(MaskedTextInput, Object.assign({ id: "2", size: "md" }, args)), _jsx(MaskedTextInput, Object.assign({ id: "3", size: "sm" }, args))] }));
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
export const PhoneNumber = {
|
|
29
|
+
args: {
|
|
30
|
+
label: "Phone Number",
|
|
31
|
+
mask: MASK_PATTERNS.PHONE,
|
|
32
|
+
fullwidth: true,
|
|
33
|
+
},
|
|
34
|
+
render: (args) => {
|
|
35
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 w-full max-w-md", children: [_jsx(MaskedTextInput, Object.assign({}, args)), _jsx(MaskedTextInput, Object.assign({}, args, { label: "International Phone", mask: MASK_PATTERNS.PHONE_INTL }))] }));
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
export const CreditCard = {
|
|
39
|
+
args: {
|
|
40
|
+
label: "Credit Card Number",
|
|
41
|
+
mask: MASK_PATTERNS.CREDIT_CARD,
|
|
42
|
+
fullwidth: true,
|
|
43
|
+
},
|
|
44
|
+
render: (args) => {
|
|
45
|
+
return (_jsx("div", { className: "flex flex-col gap-4 w-full max-w-md", children: _jsx(MaskedTextInput, Object.assign({}, args)) }));
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
export const DateAndTime = {
|
|
49
|
+
args: {
|
|
50
|
+
label: "Date",
|
|
51
|
+
mask: MASK_PATTERNS.DATE,
|
|
52
|
+
fullwidth: true,
|
|
53
|
+
},
|
|
54
|
+
render: (args) => {
|
|
55
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 w-full max-w-md", children: [_jsx(MaskedTextInput, Object.assign({}, args)), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Time", mask: MASK_PATTERNS.TIME }))] }));
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
export const SocialSecurityNumber = {
|
|
59
|
+
args: {
|
|
60
|
+
label: "Social Security Number",
|
|
61
|
+
mask: MASK_PATTERNS.SSN,
|
|
62
|
+
fullwidth: true,
|
|
63
|
+
},
|
|
64
|
+
render: (args) => {
|
|
65
|
+
return (_jsx("div", { className: "flex flex-col gap-4 w-full max-w-md", children: _jsx(MaskedTextInput, Object.assign({}, args)) }));
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
export const ZipCode = {
|
|
69
|
+
args: {
|
|
70
|
+
label: "ZIP Code",
|
|
71
|
+
mask: MASK_PATTERNS.ZIP_CODE,
|
|
72
|
+
fullwidth: true,
|
|
73
|
+
},
|
|
74
|
+
render: (args) => {
|
|
75
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 w-full max-w-md", children: [_jsx(MaskedTextInput, Object.assign({}, args)), _jsx(MaskedTextInput, Object.assign({}, args, { label: "ZIP Code + 4", mask: MASK_PATTERNS.ZIP_CODE_EXT }))] }));
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
export const Currency = {
|
|
79
|
+
args: {
|
|
80
|
+
label: "Amount",
|
|
81
|
+
mask: MASK_PATTERNS.CURRENCY,
|
|
82
|
+
fullwidth: true,
|
|
83
|
+
},
|
|
84
|
+
render: (args) => {
|
|
85
|
+
return (_jsx("div", { className: "flex flex-col gap-4 w-full max-w-md", children: _jsx(MaskedTextInput, Object.assign({}, args)) }));
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
export const CustomMask = {
|
|
89
|
+
args: {
|
|
90
|
+
label: "Custom Pattern",
|
|
91
|
+
mask: "AAA-999-AAA",
|
|
92
|
+
fullwidth: true,
|
|
93
|
+
},
|
|
94
|
+
render: (args) => {
|
|
95
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 w-full max-w-md", children: [_jsx(MaskedTextInput, Object.assign({}, args)), _jsx(MaskedTextInput, Object.assign({}, args, { label: "License Plate", mask: "999-AAA" })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Product Code", mask: "AA-9999-AA" })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Time", mask: "00:00:00" }))] }));
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
export const MaskOptions = {
|
|
99
|
+
args: {
|
|
100
|
+
label: "Phone with Options",
|
|
101
|
+
mask: MASK_PATTERNS.PHONE,
|
|
102
|
+
fullwidth: true,
|
|
103
|
+
},
|
|
104
|
+
render: (args) => {
|
|
105
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 w-full max-w-md", children: [_jsx(MaskedTextInput, Object.assign({}, args, { label: "With Guide", guide: true, showMask: true })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Without Guide", guide: false, showMask: true })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Custom Mask Char", maskChar: "*", guide: true, showMask: true }))] }));
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
export const WithCallbacks = {
|
|
109
|
+
args: {
|
|
110
|
+
label: "Phone with Callbacks",
|
|
111
|
+
mask: MASK_PATTERNS.PHONE,
|
|
112
|
+
fullwidth: true,
|
|
113
|
+
},
|
|
114
|
+
render: (args) => {
|
|
115
|
+
const [maskedValue, setMaskedValue] = useState("");
|
|
116
|
+
const [rawValue, setRawValue] = useState("");
|
|
117
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 w-full max-w-md", children: [_jsx(MaskedTextInput, Object.assign({}, args, { onChange: (e) => {
|
|
118
|
+
setMaskedValue(e.target.value);
|
|
119
|
+
setRawValue(e.target.value);
|
|
120
|
+
}, onMaskedChange: (masked, raw) => {
|
|
121
|
+
setMaskedValue(masked);
|
|
122
|
+
setRawValue(raw);
|
|
123
|
+
} })), _jsxs("div", { className: "p-4 bg-gray-100 rounded", children: [_jsxs("p", { children: [_jsx("strong", { children: "Masked Value:" }), " ", maskedValue] }), _jsxs("p", { children: [_jsx("strong", { children: "Raw Value:" }), " ", rawValue] })] })] }));
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
export const AllPatterns = {
|
|
127
|
+
args: {
|
|
128
|
+
fullwidth: true,
|
|
129
|
+
},
|
|
130
|
+
render: (args) => {
|
|
131
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 w-full max-w-2xl", children: [_jsx("h3", { className: "text-lg font-semibold", children: "All Mask Patterns" }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [_jsx(MaskedTextInput, Object.assign({}, args, { label: "Phone", mask: MASK_PATTERNS.PHONE })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "International Phone", mask: MASK_PATTERNS.PHONE_INTL })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Credit Card", mask: MASK_PATTERNS.CREDIT_CARD })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Date", mask: MASK_PATTERNS.DATE })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Time", mask: MASK_PATTERNS.TIME })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "SSN", mask: MASK_PATTERNS.SSN })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "ZIP Code", mask: MASK_PATTERNS.ZIP_CODE })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "ZIP Code + 4", mask: MASK_PATTERNS.ZIP_CODE_EXT })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Currency", mask: MASK_PATTERNS.CURRENCY })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Percentage", mask: MASK_PATTERNS.PERCENTAGE }))] })] }));
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
export const KendoRules = {
|
|
135
|
+
args: {
|
|
136
|
+
label: "Kendo UI Rules",
|
|
137
|
+
fullwidth: true,
|
|
138
|
+
},
|
|
139
|
+
render: (args) => {
|
|
140
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 w-full max-w-md", children: [_jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Kendo UI Mask Rules Examples" }), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Rule: 0 (Digits Only)", mask: "000-000-0000", helperText: "0 = Required digit (0-9)" })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Rule: L (Letters Only)", mask: "LLL-LLLL", helperText: "L = Required letter (a-z, A-Z)" })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Rule: A (Alphanumeric)", mask: "AAAA-0000", helperText: "A = Letter or digit" })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Rule: # (Number with sign)", mask: "###", helperText: "# = Digit, space, + or -" })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Mixed Pattern Example", mask: "(000) LLL-0000", helperText: "Format: (123) ABC-4567" }))] }));
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
export const OptionalRules = {
|
|
144
|
+
args: {
|
|
145
|
+
label: "Optional Rules (with space)",
|
|
146
|
+
fullwidth: true,
|
|
147
|
+
},
|
|
148
|
+
render: (args) => {
|
|
149
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 w-full max-w-md", children: [_jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Rules that accept space (optional input)" }), _jsx("p", { className: "text-xs text-gray-500", children: "Note: These rules allow space, making the position optional" }), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Rule: 9 (Digit or Space)", mask: "999-999-9999", helperText: "9 = Optional digit (press space to skip)", guide: false })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Rule: ? (Letter or Space)", mask: "???-????", helperText: "? = Optional letter (press space to skip)", guide: false })), _jsx(MaskedTextInput, Object.assign({}, args, { label: "Rule: a (Alphanumeric or Space)", mask: "aaaa-aaaa", helperText: "a = Optional alphanumeric (press space to skip)", guide: false }))] }));
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
export const CustomRules = {
|
|
153
|
+
args: {
|
|
154
|
+
label: "Custom Rules",
|
|
155
|
+
fullwidth: true,
|
|
156
|
+
},
|
|
157
|
+
render: (args) => {
|
|
158
|
+
return (_jsxs("div", { className: "flex flex-col gap-4 w-full max-w-md", children: [_jsx("h3", { className: "text-sm font-semibold text-gray-700", children: "Custom Validation Rules" }), _jsx("div", { className: "space-y-2", children: _jsx(MaskedTextInput, Object.assign({}, args, { label: "Custom Rule: Digits 3-9 only", mask: "~-~-~", rules: {
|
|
159
|
+
"~": /[3-9]/,
|
|
160
|
+
}, helperText: "Type only digits 3, 4, 5, 6, 7, 8, or 9" })) }), _jsx("div", { className: "space-y-2", children: _jsx(MaskedTextInput, Object.assign({}, args, { label: "Custom Rule: Uppercase letters only", mask: "***", rules: {
|
|
161
|
+
"*": (char) => char === char.toUpperCase() && /[A-Z]/.test(char),
|
|
162
|
+
}, helperText: "Type only uppercase letters (A-Z)" })) }), _jsx("div", { className: "space-y-2", children: _jsx(MaskedTextInput, Object.assign({}, args, { label: "Mixed Custom Rules", mask: "~*-~*", rules: {
|
|
163
|
+
"~": /[0-9]/,
|
|
164
|
+
"*": /[A-Z]/,
|
|
165
|
+
}, helperText: "Format: Digit-Letter-Digit-Letter" })) })] }));
|
|
166
|
+
},
|
|
167
|
+
};
|