@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
package/package.json
CHANGED
package/src/App.tsx
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
FormBuilderShowcase,
|
|
45
45
|
WysiwygShowcase,
|
|
46
46
|
FieldShowcase,
|
|
47
|
+
MaskedInputShowcase,
|
|
47
48
|
MultiSelectShowcase,
|
|
48
49
|
ChipInputShowcase,
|
|
49
50
|
} from "./showcases/FormShowcases";
|
|
@@ -114,6 +115,7 @@ const categories = [
|
|
|
114
115
|
{ id: "field", label: "ITField" },
|
|
115
116
|
{ id: "datepicker", label: "ITDatePicker" },
|
|
116
117
|
{ id: "timepicker", label: "ITTimePicker" },
|
|
118
|
+
{ id: "maskedinput", label: "ITMaskedInput" },
|
|
117
119
|
{ id: "calendar", label: "ITCalendar" },
|
|
118
120
|
{ id: "slidetoggle", label: "ITSlideToggle" },
|
|
119
121
|
{ id: "dropfile", label: "ITDropfile" },
|
|
@@ -294,6 +296,8 @@ function App() {
|
|
|
294
296
|
return <DatePickerShowcase />;
|
|
295
297
|
case "timepicker":
|
|
296
298
|
return <TimePickerShowcase />;
|
|
299
|
+
case "maskedinput":
|
|
300
|
+
return <MaskedInputShowcase />;
|
|
297
301
|
case "calendar":
|
|
298
302
|
return <CalendarShowcase />;
|
|
299
303
|
case "slidetoggle":
|
|
@@ -61,7 +61,8 @@ import { ITCalendar } from '@axzydev/axzy_ui_system';
|
|
|
61
61
|
- **month** — Traditional month grid with date picker, single and range selection modes.
|
|
62
62
|
- **week** — 7-column time grid from 6 AM to 10 PM with event overlays and drag-to-select range.
|
|
63
63
|
- **day** — Single-column time grid for focused daily scheduling.
|
|
64
|
-
- **years** — Click the
|
|
64
|
+
- **years** — Click the year in the header to switch to a year selection view (12-year grid).
|
|
65
|
+
- **months** — Click the month name in the header (or pick a year) to switch to a month selection view (12-month grid, same style as the year grid). Flow: header → year → month → days.
|
|
65
66
|
- Uses `date-fns` with Spanish locale (`es`) for month and weekday names.
|
|
66
67
|
- Month view supports `minDate`/`maxDate` boundaries — out-of-range dates are disabled and dimmed.
|
|
67
68
|
- Events render as colored bars in week/day views with customizable `color` prop.
|
|
@@ -78,7 +78,7 @@ export const ITCalendar: React.FC<ITCalendarProps> = ({
|
|
|
78
78
|
const mode = modeProp || (onChange ? 'month' : 'week');
|
|
79
79
|
|
|
80
80
|
const [currentDate, setCurrentDate] = useState(value || new Date());
|
|
81
|
-
const [view, setView] = useState<'calendar' | 'years'>('calendar');
|
|
81
|
+
const [view, setView] = useState<'calendar' | 'years' | 'months'>('calendar');
|
|
82
82
|
|
|
83
83
|
// Selection/Accent colors based on variant
|
|
84
84
|
const getVariantStyles = () => {
|
|
@@ -96,6 +96,25 @@ export const ITCalendar: React.FC<ITCalendarProps> = ({
|
|
|
96
96
|
if (value) setCurrentDate(value);
|
|
97
97
|
}, [value]);
|
|
98
98
|
|
|
99
|
+
// Shift the view year while keeping the day-of-month safe (clamped to target month).
|
|
100
|
+
const shiftYear = (yearDelta: number) => {
|
|
101
|
+
setCurrentDate((d) => {
|
|
102
|
+
const year = d.getFullYear() + yearDelta;
|
|
103
|
+
const daysInTarget = new Date(year, d.getMonth() + 1, 0).getDate();
|
|
104
|
+
return new Date(year, d.getMonth(), Math.min(d.getDate(), daysInTarget));
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Jump to a month of the currently viewed year (day-of-month clamped).
|
|
109
|
+
const pickMonth = (monthIndex: number) => {
|
|
110
|
+
setCurrentDate((d) => {
|
|
111
|
+
const year = d.getFullYear();
|
|
112
|
+
const daysInTarget = new Date(year, monthIndex + 1, 0).getDate();
|
|
113
|
+
return new Date(year, monthIndex, Math.min(d.getDate(), daysInTarget));
|
|
114
|
+
});
|
|
115
|
+
setView('calendar');
|
|
116
|
+
};
|
|
117
|
+
|
|
99
118
|
// Navigation handlers
|
|
100
119
|
const handleNext = () => {
|
|
101
120
|
if (view === 'years') {
|
|
@@ -104,6 +123,8 @@ export const ITCalendar: React.FC<ITCalendarProps> = ({
|
|
|
104
123
|
newDate.setFullYear(d.getFullYear() + 12);
|
|
105
124
|
return newDate;
|
|
106
125
|
});
|
|
126
|
+
} else if (view === 'months') {
|
|
127
|
+
shiftYear(1);
|
|
107
128
|
} else if (mode === 'month') {
|
|
108
129
|
setCurrentDate((d) => addMonths(d, 1));
|
|
109
130
|
} else if (mode === 'day') {
|
|
@@ -120,7 +141,9 @@ export const ITCalendar: React.FC<ITCalendarProps> = ({
|
|
|
120
141
|
newDate.setFullYear(d.getFullYear() - 12);
|
|
121
142
|
return newDate;
|
|
122
143
|
});
|
|
123
|
-
} else if (
|
|
144
|
+
} else if (view === 'months') {
|
|
145
|
+
shiftYear(-1);
|
|
146
|
+
} else if (mode === 'month') {
|
|
124
147
|
setCurrentDate((d) => addMonths(d, -1));
|
|
125
148
|
} else if (mode === 'day') {
|
|
126
149
|
setCurrentDate((d) => addDays(d, -1));
|
|
@@ -249,6 +272,7 @@ export const ITCalendar: React.FC<ITCalendarProps> = ({
|
|
|
249
272
|
// ----------------------------------------------------------------------
|
|
250
273
|
const startYear = currentDate.getFullYear() - 6;
|
|
251
274
|
const years = Array.from({ length: 12 }, (_, i) => startYear + i);
|
|
275
|
+
const months = Array.from({ length: 12 }, (_, i) => i);
|
|
252
276
|
|
|
253
277
|
// ----------------------------------------------------------------------
|
|
254
278
|
// Render
|
|
@@ -277,25 +301,50 @@ export const ITCalendar: React.FC<ITCalendarProps> = ({
|
|
|
277
301
|
borderBottomColor: 'var(--it-calendar-border, #e2e8f0)'
|
|
278
302
|
}}
|
|
279
303
|
>
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
304
|
+
{view === 'years' ? (
|
|
305
|
+
<ITText
|
|
306
|
+
as="h2"
|
|
307
|
+
className="text-sm font-bold select-none px-2 py-1"
|
|
308
|
+
style={{
|
|
309
|
+
color: 'var(--it-calendar-header-text, #1e293b)',
|
|
310
|
+
}}
|
|
311
|
+
>
|
|
312
|
+
{`${years[0]} - ${years[years.length - 1]}`}
|
|
313
|
+
</ITText>
|
|
314
|
+
) : (
|
|
315
|
+
<div className="flex items-center">
|
|
316
|
+
{view !== 'months' && (
|
|
317
|
+
<button
|
|
318
|
+
type="button"
|
|
319
|
+
onClick={() => setView('months')}
|
|
320
|
+
className="text-sm font-bold capitalize select-none px-2 py-1 rounded transition-colors"
|
|
321
|
+
style={{ color: 'var(--it-calendar-header-text, #1e293b)' }}
|
|
322
|
+
onMouseEnter={(e) => {
|
|
323
|
+
e.currentTarget.style.backgroundColor = 'var(--it-calendar-header-hover, #f1f5f9)';
|
|
324
|
+
}}
|
|
325
|
+
onMouseLeave={(e) => {
|
|
326
|
+
e.currentTarget.style.backgroundColor = 'transparent';
|
|
327
|
+
}}
|
|
328
|
+
>
|
|
329
|
+
{format(currentDate, 'MMMM', { locale: es })}
|
|
330
|
+
</button>
|
|
331
|
+
)}
|
|
332
|
+
<button
|
|
333
|
+
type="button"
|
|
334
|
+
onClick={() => setView('years')}
|
|
335
|
+
className="text-sm font-bold select-none px-2 py-1 rounded transition-colors"
|
|
336
|
+
style={{ color: 'var(--it-calendar-header-text, #1e293b)' }}
|
|
337
|
+
onMouseEnter={(e) => {
|
|
338
|
+
e.currentTarget.style.backgroundColor = 'var(--it-calendar-header-hover, #f1f5f9)';
|
|
339
|
+
}}
|
|
340
|
+
onMouseLeave={(e) => {
|
|
341
|
+
e.currentTarget.style.backgroundColor = 'transparent';
|
|
342
|
+
}}
|
|
343
|
+
>
|
|
344
|
+
{format(currentDate, 'yyyy')}
|
|
345
|
+
</button>
|
|
346
|
+
</div>
|
|
347
|
+
)}
|
|
299
348
|
<div className="flex items-center gap-1">
|
|
300
349
|
<button
|
|
301
350
|
onClick={handlePrev}
|
|
@@ -359,13 +408,41 @@ export const ITCalendar: React.FC<ITCalendarProps> = ({
|
|
|
359
408
|
newDate.setFullYear(year);
|
|
360
409
|
return newDate;
|
|
361
410
|
});
|
|
362
|
-
setView('
|
|
411
|
+
setView('months');
|
|
363
412
|
}}
|
|
364
413
|
>
|
|
365
414
|
<ITText as="span">{year}</ITText>
|
|
366
415
|
</button>
|
|
367
416
|
))}
|
|
368
417
|
</div>
|
|
418
|
+
) : view === 'months' ? (
|
|
419
|
+
<div className="p-4 grid grid-cols-4 gap-2">
|
|
420
|
+
{months.map(monthIndex => {
|
|
421
|
+
const isActive = monthIndex === currentDate.getMonth();
|
|
422
|
+
return (
|
|
423
|
+
<button
|
|
424
|
+
key={monthIndex}
|
|
425
|
+
type="button"
|
|
426
|
+
className={cn(
|
|
427
|
+
"h-10 rounded-md text-sm font-medium transition-colors border border-transparent",
|
|
428
|
+
isActive
|
|
429
|
+
? "bg-[var(--it-calendar-selected-bg)] text-[var(--it-calendar-selected-text)]"
|
|
430
|
+
: "hover:bg-[var(--it-calendar-today-bg)] hover:text-[var(--it-calendar-today-text)]"
|
|
431
|
+
)}
|
|
432
|
+
style={{
|
|
433
|
+
color: isActive
|
|
434
|
+
? 'var(--it-calendar-selected-text, #ffffff)'
|
|
435
|
+
: 'var(--it-calendar-days-text, #334155)'
|
|
436
|
+
}}
|
|
437
|
+
onClick={() => pickMonth(monthIndex)}
|
|
438
|
+
>
|
|
439
|
+
<ITText as="span" className="capitalize">
|
|
440
|
+
{format(new Date(2000, monthIndex, 1), 'MMM', { locale: es })}
|
|
441
|
+
</ITText>
|
|
442
|
+
</button>
|
|
443
|
+
);
|
|
444
|
+
})}
|
|
445
|
+
</div>
|
|
369
446
|
) : mode === 'month' ? (
|
|
370
447
|
<div className="p-4">
|
|
371
448
|
{/* Weekday Headers */}
|
|
@@ -396,6 +473,7 @@ export const ITCalendar: React.FC<ITCalendarProps> = ({
|
|
|
396
473
|
onClick={() => onChange && onChange(day)}
|
|
397
474
|
className={cn(
|
|
398
475
|
"h-10 w-full flex items-center justify-center rounded-md text-sm transition-colors relative",
|
|
476
|
+
"text-[var(--it-calendar-days-text)] hover:bg-[var(--it-calendar-today-bg)] hover:text-[var(--it-calendar-today-text)]",
|
|
399
477
|
!isCurrentMonth && "opacity-40",
|
|
400
478
|
isDisabled && "opacity-20 cursor-not-allowed",
|
|
401
479
|
)}
|
|
@@ -406,12 +484,12 @@ export const ITCalendar: React.FC<ITCalendarProps> = ({
|
|
|
406
484
|
? 'var(--it-calendar-range-bg, #eff6ff)'
|
|
407
485
|
: isToday(day)
|
|
408
486
|
? 'var(--it-calendar-today-bg, #eff6ff)'
|
|
409
|
-
:
|
|
487
|
+
: undefined,
|
|
410
488
|
color: isSelected || isRangeStart || isRangeEnd
|
|
411
489
|
? 'var(--it-calendar-selected-text, #ffffff)'
|
|
412
490
|
: isToday(day)
|
|
413
491
|
? 'var(--it-calendar-today-text, #2563eb)'
|
|
414
|
-
:
|
|
492
|
+
: undefined,
|
|
415
493
|
fontWeight: isSelected || isRangeStart || isRangeEnd || isToday(day) ? '700' : '400',
|
|
416
494
|
}}
|
|
417
495
|
>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Meta, Title, Description, Primary, Controls, Stories } from '@storybook/blocks';
|
|
2
|
+
import * as StoriesData from './masked-input.stories';
|
|
3
|
+
|
|
4
|
+
<Meta of={StoriesData} />
|
|
5
|
+
|
|
6
|
+
# ITMaskedInput
|
|
7
|
+
|
|
8
|
+
Input con máscara de formato: defines un patrón y el usuario solo escribe los caracteres útiles; los separadores se insertan solos y los huecos se pintan con un carácter de relleno.
|
|
9
|
+
|
|
10
|
+
## Props
|
|
11
|
+
|
|
12
|
+
| Prop | Type | Default | Description |
|
|
13
|
+
|------|------|---------|-------------|
|
|
14
|
+
| `name` | `string` | required | Nombre del campo para formularios. |
|
|
15
|
+
| `mask` | `string` | required | Patrón: `9`=dígito, `A`=letra (mayúscula), `a`=letra, `x`=alfanumérico, `*`=cualquiera. Cualquier otro carácter es un separador literal auto-insertado. |
|
|
16
|
+
| `value` | `string` | — | Valor limpio (raw): solo caracteres útiles, sin separadores ni relleno. |
|
|
17
|
+
| `onChange` | `(e) => void` | required | `e.target.value` es el valor raw. |
|
|
18
|
+
| `onBlur` | `(e) => void` | — | Blur del input. |
|
|
19
|
+
| `onComplete` | `(value) => void` | — | Dispara cuando la máscara queda llena. |
|
|
20
|
+
| `pattern` | `RegExp` | — | Regex validada con full-match contra el raw en blur; si no cumple muestra error. |
|
|
21
|
+
| `placeholderChar` | `string` | `"_"` | Carácter pintado en slots vacíos. |
|
|
22
|
+
| `variant` | `ColorsTypes` | `"primary"` | Color de acento del tema. |
|
|
23
|
+
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Tamaño del input. |
|
|
24
|
+
| `label` | `string` | — | Etiqueta sobre el input. |
|
|
25
|
+
| `disabled` | `boolean` | `false` | Desactiva el input. |
|
|
26
|
+
| `required` | `boolean` | `false` | Marca el campo como requerido. |
|
|
27
|
+
| `touched` | `boolean` | `false` | Indica si el campo fue tocado. |
|
|
28
|
+
| `error` | `string \| boolean` | — | Estado de error. |
|
|
29
|
+
| `className` | `string` | — | Clases del wrapper externo. |
|
|
30
|
+
| `containerClassName` | `string` | — | Clases del contenedor del input. |
|
|
31
|
+
| `iconLeft` / `iconRight` | `ReactNode` | — | Iconos laterales. |
|
|
32
|
+
| `autoFocus` | `boolean` | `false` | Enfoca al montar. |
|
|
33
|
+
|
|
34
|
+
## Tokens del mask
|
|
35
|
+
|
|
36
|
+
| Token | Acepta |
|
|
37
|
+
|-------|--------|
|
|
38
|
+
| `9` | un dígito |
|
|
39
|
+
| `A` | una letra (se guarda en mayúsculas) |
|
|
40
|
+
| `a` | una letra (tal cual) |
|
|
41
|
+
| `x` | un carácter alfanumérico |
|
|
42
|
+
| `*` | cualquier carácter |
|
|
43
|
+
| cualquier otro | literal insertado solo (p.ej. `-`, `/`, `(`, `)`, espacio) |
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { ITMaskedInput } from '@axzydev/axzy_ui_system';
|
|
49
|
+
|
|
50
|
+
// Código de activación
|
|
51
|
+
<ITMaskedInput
|
|
52
|
+
name="code"
|
|
53
|
+
mask="xxxx-xxxx-xxxx"
|
|
54
|
+
label="Código"
|
|
55
|
+
onChange={(e) => setCode(e.target.value)}
|
|
56
|
+
/>
|
|
57
|
+
|
|
58
|
+
// Teléfono con validación regex sobre el valor limpio
|
|
59
|
+
<ITMaskedInput
|
|
60
|
+
name="phone"
|
|
61
|
+
mask="(999) 999-9999"
|
|
62
|
+
pattern={/^\d{10}$/}
|
|
63
|
+
onChange={(e) => setPhone(e.target.value)}
|
|
64
|
+
/>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Notes
|
|
68
|
+
|
|
69
|
+
- ¿El valor? **Solo caracteres útiles**: escribir `ABC1-2345-6789` devuelve `ABC123456789` en `onChange`.
|
|
70
|
+
- El cursor salta a huecos vacíos con selección automática; borrar un separador lo vuelve a insertar.
|
|
71
|
+
- `pattern` valida contra el raw completo (full-match); el mensaje de error genérico es "Formato inválido" salvo que pases `error` como string.
|
|
72
|
+
- `onComplete` es ideal para tarjetas, CVV o códigos de un solo uso.
|
|
73
|
+
|
|
74
|
+
## Stories
|
|
75
|
+
|
|
76
|
+
<Primary />
|
|
77
|
+
<Controls />
|
|
78
|
+
<Stories />
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ColorsTypes } from "@/types/colors.types";
|
|
2
|
+
import { SizesTypes } from "@/types/sizes.types";
|
|
3
|
+
import { ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
export interface ITMaskedInputProps {
|
|
6
|
+
/** Unique name attribute for the underlying input element. */
|
|
7
|
+
name: string;
|
|
8
|
+
/**
|
|
9
|
+
* Mask pattern. Tokens are filled by the user and everything else is an
|
|
10
|
+
* auto-inserted literal.
|
|
11
|
+
* - `9` — digit
|
|
12
|
+
* - `A` — letter (stored uppercase)
|
|
13
|
+
* - `a` — letter
|
|
14
|
+
* - `x` — alphanumeric
|
|
15
|
+
* - `*` — any character
|
|
16
|
+
* - any other character ( `-`, `/`, `(`, `)`, ` `, `.` ) is a literal separator
|
|
17
|
+
* @example "xxxx-xxxx-xxxx"
|
|
18
|
+
* @example "(999) 999-9999"
|
|
19
|
+
* @example "9999-9999-9999-9999"
|
|
20
|
+
*/
|
|
21
|
+
mask: string;
|
|
22
|
+
/** Clean value containing only the useful characters (no separators, no placeholders). */
|
|
23
|
+
value?: string;
|
|
24
|
+
/** Called when the masked value changes. `event.target.value` is the raw value without separators. */
|
|
25
|
+
onChange: (event: { target: { name: string; value: string } }) => void;
|
|
26
|
+
/** Called when the input loses focus. */
|
|
27
|
+
onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
|
|
28
|
+
/** Fired when all token slots are filled with valid characters. */
|
|
29
|
+
onComplete?: (value: string) => void;
|
|
30
|
+
/** Optional regex full-matched against the raw value to validate the field (e.g. `/^\\d{16}$/`). */
|
|
31
|
+
pattern?: RegExp;
|
|
32
|
+
/** Character shown for empty token slots. @default "_" */
|
|
33
|
+
placeholderChar?: string;
|
|
34
|
+
/** Color variant matching the design system (ColorsTypes). @default "primary" */
|
|
35
|
+
variant?: ColorsTypes;
|
|
36
|
+
/** Size preset: "sm" | "md" | "lg". @default "md" */
|
|
37
|
+
size?: SizesTypes;
|
|
38
|
+
/** Label displayed above the input. */
|
|
39
|
+
label?: string;
|
|
40
|
+
/** Disables the input. @default false */
|
|
41
|
+
disabled?: boolean;
|
|
42
|
+
/** Marks the field as required. */
|
|
43
|
+
required?: boolean;
|
|
44
|
+
/** Visually indicates the field has been interacted with. */
|
|
45
|
+
touched?: boolean;
|
|
46
|
+
/** Error state — pass a string message or `true` for default styling. */
|
|
47
|
+
error?: string | boolean;
|
|
48
|
+
/** Additional CSS classes for the outer wrapper element. */
|
|
49
|
+
className?: string;
|
|
50
|
+
/** Additional CSS classes for the underlying input container. */
|
|
51
|
+
containerClassName?: string;
|
|
52
|
+
/** Icon element rendered on the left side of the input. */
|
|
53
|
+
iconLeft?: ReactNode;
|
|
54
|
+
/** Icon element rendered on the right side of the input. */
|
|
55
|
+
iconRight?: ReactNode;
|
|
56
|
+
/** Auto-focus the input on mount. @default false */
|
|
57
|
+
autoFocus?: boolean;
|
|
58
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { Meta } from '@storybook/react';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import ITMaskedInput from './masked-input';
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Components/Form Elements/ITMaskedInput',
|
|
7
|
+
component: ITMaskedInput,
|
|
8
|
+
parameters: { layout: 'padded' },
|
|
9
|
+
argTypes: {
|
|
10
|
+
mask: { control: 'text' },
|
|
11
|
+
placeholderChar: { control: 'text' },
|
|
12
|
+
onChange: { action: 'changed' },
|
|
13
|
+
onComplete: { action: 'completed' },
|
|
14
|
+
disabled: { control: 'boolean' },
|
|
15
|
+
error: { control: 'text' },
|
|
16
|
+
variant: {
|
|
17
|
+
control: 'select',
|
|
18
|
+
options: ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'purple', 'gray'],
|
|
19
|
+
},
|
|
20
|
+
size: { control: 'select', options: ['sm', 'md', 'lg'] },
|
|
21
|
+
},
|
|
22
|
+
} satisfies Meta<typeof ITMaskedInput>;
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
|
|
26
|
+
function CodeMaskedWrapper(args: any) {
|
|
27
|
+
const [value, setValue] = useState<string>('');
|
|
28
|
+
return (
|
|
29
|
+
<div className="w-full max-w-xs">
|
|
30
|
+
<ITMaskedInput
|
|
31
|
+
{...args}
|
|
32
|
+
value={value}
|
|
33
|
+
onChange={(e) => {
|
|
34
|
+
setValue(e.target.value);
|
|
35
|
+
args.onChange(e);
|
|
36
|
+
}}
|
|
37
|
+
/>
|
|
38
|
+
<p className="mt-2 text-xs font-mono text-slate-500">
|
|
39
|
+
raw: {value || '(vacío)'}
|
|
40
|
+
</p>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const Alphanumeric: any = {
|
|
46
|
+
render: CodeMaskedWrapper,
|
|
47
|
+
args: {
|
|
48
|
+
name: 'code',
|
|
49
|
+
label: 'Código único',
|
|
50
|
+
mask: 'xxxx-xxxx-xxxx',
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const Phone: any = {
|
|
55
|
+
render: CodeMaskedWrapper,
|
|
56
|
+
args: {
|
|
57
|
+
name: 'phone',
|
|
58
|
+
label: 'Teléfono',
|
|
59
|
+
mask: '(999) 999-9999',
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const CreditCard: any = {
|
|
64
|
+
render: CodeMaskedWrapper,
|
|
65
|
+
args: {
|
|
66
|
+
name: 'card',
|
|
67
|
+
label: 'Número de tarjeta',
|
|
68
|
+
mask: '9999-9999-9999-9999',
|
|
69
|
+
onComplete: undefined,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const PhoneValidatedWrapper = (args: any) => {
|
|
74
|
+
const [value, setValue] = useState<string>('5512345678');
|
|
75
|
+
const [complete, setComplete] = useState<string | null>(null);
|
|
76
|
+
return (
|
|
77
|
+
<div className="w-full max-w-xs">
|
|
78
|
+
<ITMaskedInput
|
|
79
|
+
{...args}
|
|
80
|
+
value={value}
|
|
81
|
+
onChange={(e) => {
|
|
82
|
+
setValue(e.target.value);
|
|
83
|
+
args.onChange(e);
|
|
84
|
+
setComplete(null);
|
|
85
|
+
}}
|
|
86
|
+
onComplete={(v) => setComplete(v)}
|
|
87
|
+
/>
|
|
88
|
+
{complete && (
|
|
89
|
+
<p className="mt-2 text-xs font-mono text-emerald-600">
|
|
90
|
+
¡Completo! raw: {complete}
|
|
91
|
+
</p>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const WithPattern: any = {
|
|
98
|
+
render: PhoneValidatedWrapper,
|
|
99
|
+
args: {
|
|
100
|
+
name: 'validated_phone',
|
|
101
|
+
label: 'Teléfono válido (regex)',
|
|
102
|
+
mask: '999-999-9999',
|
|
103
|
+
pattern: /^\d{9}$/,
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const Sizes: any = {
|
|
108
|
+
render: () => (
|
|
109
|
+
<div className="flex flex-col gap-4 w-full max-w-xs">
|
|
110
|
+
<ITMaskedInput name="sz_sm" label="Small" mask="xxxx-xxxx" size="sm" onChange={() => {}} />
|
|
111
|
+
<ITMaskedInput name="sz_md" label="Medium" mask="xxxx-xxxx" size="md" onChange={() => {}} />
|
|
112
|
+
<ITMaskedInput name="sz_lg" label="Large" mask="xxxx-xxxx" size="lg" onChange={() => {}} />
|
|
113
|
+
</div>
|
|
114
|
+
),
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const Disabled: any = {
|
|
118
|
+
args: {
|
|
119
|
+
name: 'disabled_code',
|
|
120
|
+
label: 'Deshabilitado',
|
|
121
|
+
mask: 'xxxx-xxxx',
|
|
122
|
+
value: 'abcd',
|
|
123
|
+
disabled: true,
|
|
124
|
+
onChange: () => {},
|
|
125
|
+
},
|
|
126
|
+
};
|