@axzydev/axzy_ui_system 1.3.0 → 1.3.1
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/index.cjs +1652 -1411
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.cts +100 -1
- package/dist/index.d.ts +100 -1
- package/dist/index.js +1465 -1225
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/App.tsx +4 -0
- package/src/components/molecules/calendar/calendar.doc.mdx +2 -1
- package/src/components/molecules/calendar/calendar.tsx +102 -24
- package/src/components/molecules/masked-input/masked-input.doc.mdx +78 -0
- package/src/components/molecules/masked-input/masked-input.props.ts +58 -0
- package/src/components/molecules/masked-input/masked-input.stories.tsx +126 -0
- package/src/components/molecules/masked-input/masked-input.tsx +244 -0
- package/src/index.ts +4 -2
- package/src/showcases/FormShowcases.tsx +178 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import React, { useRef, useState, useMemo } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import ITInput from "@/components/atoms/input/input";
|
|
4
|
+
import { ITMaskedInputProps } from "./masked-input.props";
|
|
5
|
+
|
|
6
|
+
type Token = "9" | "A" | "a" | "x" | "*";
|
|
7
|
+
|
|
8
|
+
type MaskRule =
|
|
9
|
+
| { type: "token"; token: Token }
|
|
10
|
+
| { type: "literal"; char: string };
|
|
11
|
+
|
|
12
|
+
const TOKEN_SET = /[9Aax*]/;
|
|
13
|
+
|
|
14
|
+
function parseMask(mask: string): MaskRule[] {
|
|
15
|
+
return mask.split("").map((ch) =>
|
|
16
|
+
TOKEN_SET.test(ch)
|
|
17
|
+
? { type: "token", token: ch as Token }
|
|
18
|
+
: { type: "literal", char: ch }
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function matchToken(token: Token, ch: string): boolean {
|
|
23
|
+
switch (token) {
|
|
24
|
+
case "9":
|
|
25
|
+
return /\d/.test(ch);
|
|
26
|
+
case "A":
|
|
27
|
+
case "a":
|
|
28
|
+
return /[A-Za-z]/.test(ch);
|
|
29
|
+
case "x":
|
|
30
|
+
return /[A-Za-z0-9]/.test(ch);
|
|
31
|
+
case "*":
|
|
32
|
+
return ch.length > 0;
|
|
33
|
+
default:
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function extract(input: string, rules: MaskRule[], maskChar: string): string {
|
|
39
|
+
let raw = "";
|
|
40
|
+
let slot = 0;
|
|
41
|
+
for (const ch of input) {
|
|
42
|
+
while (slot < rules.length && rules[slot].type === "literal") slot++;
|
|
43
|
+
if (slot >= rules.length) break;
|
|
44
|
+
const rule = rules[slot];
|
|
45
|
+
if (
|
|
46
|
+
rule.type === "token" &&
|
|
47
|
+
ch !== maskChar &&
|
|
48
|
+
matchToken(rule.token, ch)
|
|
49
|
+
) {
|
|
50
|
+
raw += rule.token === "A" ? ch.toUpperCase() : ch;
|
|
51
|
+
slot++;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return raw;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function format(raw: string, rules: MaskRule[], maskChar: string): string {
|
|
58
|
+
let out = "";
|
|
59
|
+
let idx = 0;
|
|
60
|
+
for (const rule of rules) {
|
|
61
|
+
if (rule.type === "literal") {
|
|
62
|
+
out += rule.char;
|
|
63
|
+
} else {
|
|
64
|
+
const ch = raw[idx];
|
|
65
|
+
out += ch !== undefined ? ch : maskChar;
|
|
66
|
+
idx++;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function countConsumed(input: string, rules: MaskRule[], upto: number, maskChar: string): number {
|
|
73
|
+
let slot = 0;
|
|
74
|
+
let n = 0;
|
|
75
|
+
for (let i = 0; i < upto && i < input.length; i++) {
|
|
76
|
+
const ch = input[i];
|
|
77
|
+
while (slot < rules.length && rules[slot].type === "literal") slot++;
|
|
78
|
+
if (slot >= rules.length) break;
|
|
79
|
+
const rule = rules[slot];
|
|
80
|
+
if (rule.type === "token" && ch !== maskChar && matchToken(rule.token, ch)) {
|
|
81
|
+
n++;
|
|
82
|
+
slot++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return n;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function caretAfter(n: number, rules: MaskRule[]): number {
|
|
89
|
+
let pos = 0;
|
|
90
|
+
let consumed = 0;
|
|
91
|
+
for (const rule of rules) {
|
|
92
|
+
if (rule.type === "token") {
|
|
93
|
+
consumed++;
|
|
94
|
+
if (consumed > n) break;
|
|
95
|
+
}
|
|
96
|
+
pos++;
|
|
97
|
+
}
|
|
98
|
+
return pos;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Text input that applies a formatting mask while the user types, exposing only
|
|
103
|
+
* the clean (raw) value through `onChange`.
|
|
104
|
+
*
|
|
105
|
+
* The mask is defined by a pattern string. Tokens are filled by the user and
|
|
106
|
+
* any other character acts as an auto-inserted literal separator. Empty slots
|
|
107
|
+
* are painted with `placeholderChar`, so the mask is always visible.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```tsx
|
|
111
|
+
* <ITMaskedInput
|
|
112
|
+
* name="code"
|
|
113
|
+
* mask="xxxx-xxxx-xxxx"
|
|
114
|
+
* label="Código"
|
|
115
|
+
* onChange={(e) => setCode(e.target.value)}
|
|
116
|
+
* />
|
|
117
|
+
* ```
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```tsx
|
|
121
|
+
* <ITMaskedInput
|
|
122
|
+
* name="phone"
|
|
123
|
+
* mask="(999) 999-9999"
|
|
124
|
+
* pattern={/^\d{10}$/}
|
|
125
|
+
* onChange={(e) => setPhone(e.target.value)}
|
|
126
|
+
* />
|
|
127
|
+
* ```
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```tsx
|
|
131
|
+
* <ITMaskedInput
|
|
132
|
+
* name="card"
|
|
133
|
+
* mask="9999-9999-9999-9999"
|
|
134
|
+
* onComplete={(card) => validateCard(card)}
|
|
135
|
+
* />
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export default function ITMaskedInput({
|
|
139
|
+
name,
|
|
140
|
+
mask,
|
|
141
|
+
value,
|
|
142
|
+
onChange,
|
|
143
|
+
onBlur,
|
|
144
|
+
onComplete,
|
|
145
|
+
pattern,
|
|
146
|
+
placeholderChar = "_",
|
|
147
|
+
variant = "primary",
|
|
148
|
+
size = "md",
|
|
149
|
+
label,
|
|
150
|
+
disabled = false,
|
|
151
|
+
required,
|
|
152
|
+
touched,
|
|
153
|
+
error,
|
|
154
|
+
className,
|
|
155
|
+
containerClassName,
|
|
156
|
+
iconLeft,
|
|
157
|
+
iconRight,
|
|
158
|
+
autoFocus = false,
|
|
159
|
+
}: ITMaskedInputProps) {
|
|
160
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
161
|
+
const rawRef = useRef<string>(value ?? "");
|
|
162
|
+
const isControlled = value !== undefined;
|
|
163
|
+
|
|
164
|
+
const [internalRaw, setInternalRaw] = useState<string>(value ?? "");
|
|
165
|
+
const [invalidPattern, setInvalidPattern] = useState(false);
|
|
166
|
+
|
|
167
|
+
const rules = useMemo(() => parseMask(mask), [mask]);
|
|
168
|
+
const tokenCount = useMemo(() => rules.filter((r) => r.type === "token").length, [rules]);
|
|
169
|
+
|
|
170
|
+
const raw = isControlled ? (value ?? "") : internalRaw;
|
|
171
|
+
const display = useMemo(() => format(raw, rules, placeholderChar), [raw, rules, placeholderChar]);
|
|
172
|
+
|
|
173
|
+
const scheduleCaret = (caret: number) => {
|
|
174
|
+
const inputEl = containerRef.current?.querySelector("input");
|
|
175
|
+
if (!inputEl) return;
|
|
176
|
+
requestAnimationFrame(() => {
|
|
177
|
+
if (document.activeElement === inputEl) {
|
|
178
|
+
const pos = Math.min(caret, inputEl.value.length);
|
|
179
|
+
inputEl.setSelectionRange(pos, pos);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
185
|
+
const el = e.target as HTMLInputElement;
|
|
186
|
+
const inputVal = el.value ?? "";
|
|
187
|
+
const caretBefore = el.selectionStart ?? inputVal.length;
|
|
188
|
+
|
|
189
|
+
const nextRaw = extract(inputVal, rules, placeholderChar);
|
|
190
|
+
const targetCaret = caretAfter(
|
|
191
|
+
countConsumed(inputVal, rules, caretBefore, placeholderChar),
|
|
192
|
+
rules
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
if (nextRaw !== rawRef.current) {
|
|
196
|
+
rawRef.current = nextRaw;
|
|
197
|
+
setInternalRaw(nextRaw);
|
|
198
|
+
onChange?.({ target: { name, value: nextRaw } });
|
|
199
|
+
if (nextRaw.length === tokenCount && nextRaw.length > 0) {
|
|
200
|
+
onComplete?.(nextRaw);
|
|
201
|
+
}
|
|
202
|
+
if (invalidPattern) setInvalidPattern(false);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
scheduleCaret(targetCaret);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
|
209
|
+
if (pattern && rawRef.current.length > 0 && !pattern.test(rawRef.current)) {
|
|
210
|
+
setInvalidPattern(true);
|
|
211
|
+
}
|
|
212
|
+
onBlur?.(e);
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const resolvedError = invalidPattern
|
|
216
|
+
? typeof error === "string"
|
|
217
|
+
? error
|
|
218
|
+
: "Formato inválido"
|
|
219
|
+
: error;
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<div ref={containerRef} className={clsx("w-full", className)}>
|
|
223
|
+
<ITInput
|
|
224
|
+
name={name}
|
|
225
|
+
type="text"
|
|
226
|
+
label={label}
|
|
227
|
+
value={display}
|
|
228
|
+
onChange={handleChange}
|
|
229
|
+
onBlur={(e) => handleBlur(e as React.FocusEvent<HTMLInputElement>)}
|
|
230
|
+
maxLength={mask.length}
|
|
231
|
+
variant={variant}
|
|
232
|
+
size={size}
|
|
233
|
+
disabled={disabled}
|
|
234
|
+
required={required}
|
|
235
|
+
touched={touched}
|
|
236
|
+
error={resolvedError}
|
|
237
|
+
containerClassName={containerClassName}
|
|
238
|
+
iconLeft={iconLeft}
|
|
239
|
+
iconRight={iconRight}
|
|
240
|
+
autoFocus={autoFocus}
|
|
241
|
+
/>
|
|
242
|
+
</div>
|
|
243
|
+
);
|
|
244
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ import ITChip from "./components/atoms/chip/chip";
|
|
|
21
21
|
import ITChipInput from "./components/molecules/chip-input/chip-input";
|
|
22
22
|
import ITDropdownMenu from "./components/molecules/dropdown-menu/dropdown-menu";
|
|
23
23
|
import ITField from "./components/molecules/field/field";
|
|
24
|
+
import ITMaskedInput from "./components/molecules/masked-input/masked-input";
|
|
24
25
|
import ITMultiSelect from "./components/molecules/multi-select/multi-select";
|
|
25
26
|
import ITConfirmDialog from "./components/organisms/confirm-dialog/confirm-dialog";
|
|
26
27
|
import ITDataTable from "./components/organisms/data-table/dataTable";
|
|
@@ -75,6 +76,7 @@ import type { ITConfirmDialogProps } from "./components/organisms/confirm-dialog
|
|
|
75
76
|
import type { ITAccordionProps, ITAccordionItem } from "./components/molecules/accordion/accordion.props";
|
|
76
77
|
import type { ITDropdownMenuProps, ITDropdownMenuItem } from "./components/molecules/dropdown-menu/dropdown-menu.props";
|
|
77
78
|
import type { ITFieldProps } from "./components/molecules/field/field.props";
|
|
79
|
+
import type { ITMaskedInputProps } from "./components/molecules/masked-input/masked-input.props";
|
|
78
80
|
import type { ITMultiSelectProps, ITMultiSelectOption } from "./components/molecules/multi-select/multi-select.props";
|
|
79
81
|
import type { ITDataTableFetchParams, ITDataTableProps, ITDataTableResponse } from "./components/organisms/data-table/dataTable.props";
|
|
80
82
|
import type { ITDatePickerProps } from "./components/molecules/date-picker/date-picker.props";
|
|
@@ -140,7 +142,7 @@ export {
|
|
|
140
142
|
ITAlert, ITAvatar, ITBadget, ITBreadcrumbs, ITButton,
|
|
141
143
|
ITCalendar,
|
|
142
144
|
ITCard, ITCheckbox, ITChip, ITChipInput, ITConfirmDialog, ITDataTable, ITDatePicker,
|
|
143
|
-
ITDropdownMenu, ITField, ITMultiSelect,
|
|
145
|
+
ITDropdownMenu, ITField, ITMaskedInput, ITMultiSelect,
|
|
144
146
|
ITDialog, ITDivider, ITDrawer,
|
|
145
147
|
ITDropfile, ITEmptyState, ITFlex, ITFormBuilder, ITFormHeader, ITGrid, ITImage, ITInput, ITLayout,
|
|
146
148
|
ITLoader, ITNavbar, ITPage, ITPageHeader, ITPagination, ITPopover, ITProgress,
|
|
@@ -161,7 +163,7 @@ export {
|
|
|
161
163
|
ITBreadcrumbItem, ITBreadcrumbsProps, ITButtonProps,
|
|
162
164
|
ITCalendarProps,
|
|
163
165
|
ITCardProps, ITCheckboxProps, ITChipProps, ITChipInputProps, ITConfirmDialogProps,
|
|
164
|
-
ITDropdownMenuItem, ITDropdownMenuProps, ITFieldProps, ITMultiSelectOption, ITMultiSelectProps,
|
|
166
|
+
ITDropdownMenuItem, ITDropdownMenuProps, ITFieldProps, ITMaskedInputProps, ITMultiSelectOption, ITMultiSelectProps,
|
|
165
167
|
ITDataTableFetchParams, ITDataTableProps, ITDataTableResponse, ITDatePickerProps,
|
|
166
168
|
ITDialogProps, ITDividerProps, ITDrawerProps,
|
|
167
169
|
ITEmptyStateProps, ITFlexProps,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
ITFormBuilder,
|
|
14
14
|
ITWysiwyg,
|
|
15
15
|
ITField,
|
|
16
|
+
ITMaskedInput,
|
|
16
17
|
ITMultiSelect,
|
|
17
18
|
ITChipInput,
|
|
18
19
|
ITBadget,
|
|
@@ -1913,3 +1914,180 @@ export const ChipInputShowcase = () => {
|
|
|
1913
1914
|
/>
|
|
1914
1915
|
);
|
|
1915
1916
|
};
|
|
1917
|
+
|
|
1918
|
+
// 15. ITMaskedInput Showcase
|
|
1919
|
+
const MaskedDemo = ({
|
|
1920
|
+
title,
|
|
1921
|
+
mask,
|
|
1922
|
+
pattern,
|
|
1923
|
+
hint,
|
|
1924
|
+
initial,
|
|
1925
|
+
}: {
|
|
1926
|
+
title: string;
|
|
1927
|
+
mask: string;
|
|
1928
|
+
pattern?: RegExp;
|
|
1929
|
+
hint?: string;
|
|
1930
|
+
initial?: string;
|
|
1931
|
+
}) => {
|
|
1932
|
+
const [raw, setRaw] = useState<string>(initial ?? "");
|
|
1933
|
+
const [complete, setComplete] = useState<string | null>(null);
|
|
1934
|
+
return (
|
|
1935
|
+
<div>
|
|
1936
|
+
<h4 className="text-sm font-bold text-slate-700 mb-2">{title}</h4>
|
|
1937
|
+
<ITMaskedInput
|
|
1938
|
+
name={title.toLowerCase().replace(/\s+/g, "_")}
|
|
1939
|
+
label={mask}
|
|
1940
|
+
mask={mask}
|
|
1941
|
+
pattern={pattern}
|
|
1942
|
+
value={raw}
|
|
1943
|
+
onChange={(e) => {
|
|
1944
|
+
setRaw(e.target.value);
|
|
1945
|
+
setComplete(null);
|
|
1946
|
+
}}
|
|
1947
|
+
onComplete={(v) => setComplete(v)}
|
|
1948
|
+
/>
|
|
1949
|
+
<p className="mt-1.5 text-xs font-mono text-slate-500">
|
|
1950
|
+
raw: {raw || "(vacío)"}
|
|
1951
|
+
{complete && <span className="text-emerald-600 ml-2">✓ completo</span>}
|
|
1952
|
+
{hint && <span className="text-slate-400 ml-2">· {hint}</span>}
|
|
1953
|
+
</p>
|
|
1954
|
+
</div>
|
|
1955
|
+
);
|
|
1956
|
+
};
|
|
1957
|
+
|
|
1958
|
+
export const MaskedInputShowcase = () => {
|
|
1959
|
+
const [code, setCode] = useState<string>("");
|
|
1960
|
+
const [variant, setVariant] = useState<any>("primary");
|
|
1961
|
+
const [size, setSize] = useState<any>("md");
|
|
1962
|
+
const [disabled, setDisabled] = useState(false);
|
|
1963
|
+
const [withError, setWithError] = useState(false);
|
|
1964
|
+
|
|
1965
|
+
const codeExample = `<ITMaskedInput\n name="code"\n label="Código de activación"\n mask="xxxx-xxxx-xxxx"\n variant="${variant}"\n size="${size}"\n disabled={${disabled}}\n onChange={(e) => setCode(e.target.value)}\n/>`;
|
|
1966
|
+
|
|
1967
|
+
return (
|
|
1968
|
+
<ShowcaseLayout
|
|
1969
|
+
title="ITMaskedInput"
|
|
1970
|
+
description="Input de texto con máscara de formato: teclea solo caracteres útiles y los separadores se insertan solos (xxxx-xxxx-xxxx, (999) 999-9999, etc.)."
|
|
1971
|
+
code={codeExample}
|
|
1972
|
+
demo={
|
|
1973
|
+
<div className="w-full max-w-sm">
|
|
1974
|
+
<ITMaskedInput
|
|
1975
|
+
name="showcase_masked"
|
|
1976
|
+
label="Código de activación"
|
|
1977
|
+
mask="xxxx-xxxx-xxxx"
|
|
1978
|
+
value={code}
|
|
1979
|
+
variant={variant}
|
|
1980
|
+
size={size}
|
|
1981
|
+
disabled={disabled}
|
|
1982
|
+
error={withError}
|
|
1983
|
+
onChange={(e) => setCode(e.target.value)}
|
|
1984
|
+
/>
|
|
1985
|
+
{code && (
|
|
1986
|
+
<p className="mt-2 text-xs text-slate-500 font-mono">
|
|
1987
|
+
Valor actual (raw): {code || "—"}
|
|
1988
|
+
</p>
|
|
1989
|
+
)}
|
|
1990
|
+
</div>
|
|
1991
|
+
}
|
|
1992
|
+
controls={
|
|
1993
|
+
<>
|
|
1994
|
+
<ITSelect
|
|
1995
|
+
name="masked_variant_ctrl"
|
|
1996
|
+
label="Tema de Color"
|
|
1997
|
+
value={variant}
|
|
1998
|
+
onChange={(e: any) => setVariant(e.target.value)}
|
|
1999
|
+
options={[
|
|
2000
|
+
{ label: "Primary", value: "primary" },
|
|
2001
|
+
{ label: "Secondary", value: "secondary" },
|
|
2002
|
+
{ label: "Success", value: "success" },
|
|
2003
|
+
{ label: "Danger", value: "danger" },
|
|
2004
|
+
{ label: "Warning", value: "warning" },
|
|
2005
|
+
{ label: "Info", value: "info" },
|
|
2006
|
+
]}
|
|
2007
|
+
/>
|
|
2008
|
+
<ITSelect
|
|
2009
|
+
name="masked_size_ctrl"
|
|
2010
|
+
label="Tamaño"
|
|
2011
|
+
value={size}
|
|
2012
|
+
onChange={(e: any) => setSize(e.target.value)}
|
|
2013
|
+
options={[
|
|
2014
|
+
{ label: "Small", value: "sm" },
|
|
2015
|
+
{ label: "Medium", value: "md" },
|
|
2016
|
+
{ label: "Large", value: "lg" },
|
|
2017
|
+
]}
|
|
2018
|
+
/>
|
|
2019
|
+
<div className="flex items-center justify-between pt-2">
|
|
2020
|
+
<span className="text-sm font-semibold text-gray-700">Deshabilitado</span>
|
|
2021
|
+
<ITSlideToggle isOn={disabled} onToggle={setDisabled} activeColor="danger" size="sm" />
|
|
2022
|
+
</div>
|
|
2023
|
+
<div className="flex items-center justify-between">
|
|
2024
|
+
<span className="text-sm font-semibold text-gray-700">Error</span>
|
|
2025
|
+
<ITSlideToggle isOn={withError} onToggle={setWithError} activeColor="danger" size="sm" />
|
|
2026
|
+
</div>
|
|
2027
|
+
</>
|
|
2028
|
+
}
|
|
2029
|
+
gallery={
|
|
2030
|
+
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
|
|
2031
|
+
<MaskedDemo
|
|
2032
|
+
title="Código alfanumérico"
|
|
2033
|
+
mask="xxxx-xxxx-xxxx"
|
|
2034
|
+
hint="Escribe: ABC1-2345"
|
|
2035
|
+
/>
|
|
2036
|
+
<MaskedDemo
|
|
2037
|
+
title="Número de tarjeta"
|
|
2038
|
+
mask="9999-9999-9999-9999"
|
|
2039
|
+
hint="Escribe 16 dígitos"
|
|
2040
|
+
/>
|
|
2041
|
+
<MaskedDemo
|
|
2042
|
+
title="Teléfono"
|
|
2043
|
+
mask="(999) 999-9999"
|
|
2044
|
+
pattern={/^\d{10}$/}
|
|
2045
|
+
hint="Regex /^\\d{10}$/"
|
|
2046
|
+
/>
|
|
2047
|
+
<MaskedDemo
|
|
2048
|
+
title="Código postal"
|
|
2049
|
+
mask="99999"
|
|
2050
|
+
hint="Ejemplo: 44100"
|
|
2051
|
+
/>
|
|
2052
|
+
<MaskedDemo
|
|
2053
|
+
title="CVV / OTP"
|
|
2054
|
+
mask="999"
|
|
2055
|
+
hint="3 dígitos, onComplete"
|
|
2056
|
+
/>
|
|
2057
|
+
<MaskedDemo
|
|
2058
|
+
title="Clave con letras en MAYÚSCULA"
|
|
2059
|
+
mask="A-A-A-999"
|
|
2060
|
+
hint="Token A auto-mayúscula"
|
|
2061
|
+
initial="ABC123"
|
|
2062
|
+
/>
|
|
2063
|
+
</div>
|
|
2064
|
+
}
|
|
2065
|
+
doc={{
|
|
2066
|
+
summary: "Input con máscara: value/onChange entregan solo los caracteres útiles y los separadores se auto-insertan.",
|
|
2067
|
+
description:
|
|
2068
|
+
"Define el patrón con tokens (9=dígito, A=letra mayúscula, a=letra, x=alfanumérico, *=cualquiera) y literales (todo lo demás). Los huecos vacíos se pintan con placeholderChar ('_').",
|
|
2069
|
+
examples: [
|
|
2070
|
+
'<ITMaskedInput name="code" mask="xxxx-xxxx-xxxx" onChange={(e) => setCode(e.target.value)} />',
|
|
2071
|
+
'<ITMaskedInput name="phone" mask="(999) 999-9999" pattern={/^\\d{10}$/} onChange={setPhone} />',
|
|
2072
|
+
'<ITMaskedInput name="card" mask="9999-9999-9999-9999" onComplete={validateCard} />',
|
|
2073
|
+
],
|
|
2074
|
+
props: [
|
|
2075
|
+
{ name: "mask", type: "string", required: true, description: "Patrón de la máscara (tokens + literales)." },
|
|
2076
|
+
{ name: "value", type: "string", description: "Valor limpio (raw): solo caracteres útiles." },
|
|
2077
|
+
{ name: "onChange", type: "(e) => void", description: "e.target.value = raw sin separadores." },
|
|
2078
|
+
{ name: "onComplete", type: "(value) => void", description: "Dispara al llenar toda la máscara." },
|
|
2079
|
+
{ name: "pattern", type: "RegExp", description: "Validación full-match del raw en blur." },
|
|
2080
|
+
{ name: "placeholderChar", type: "string", default: '"_"', description: "Relleno de slots vacíos." },
|
|
2081
|
+
{ name: "variant", type: "ColorsTypes", default: '"primary"', description: "Color de acento." },
|
|
2082
|
+
{ name: "size", type: '"sm" | "md" | "lg"', default: '"md"', description: "Tamaño del input." },
|
|
2083
|
+
{ name: "error", type: "string | boolean", description: "Estado de error." },
|
|
2084
|
+
],
|
|
2085
|
+
notes: [
|
|
2086
|
+
"Los tokens A guardan letras en mayúsculas automáticamente.",
|
|
2087
|
+
"Borrar un separador lo vuelve a insertar; el cursor se conserva en la posición correcta.",
|
|
2088
|
+
"El cursor se preserva también al editar en medio de la máscara.",
|
|
2089
|
+
],
|
|
2090
|
+
}}
|
|
2091
|
+
/>
|
|
2092
|
+
);
|
|
2093
|
+
};
|