@conatel-sa/react-ui 0.2.3 → 0.2.4

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/forms.mjs ADDED
@@ -0,0 +1,518 @@
1
+ // src/components/forms/PasswordInput.tsx
2
+ import { useId, useState } from "react";
3
+
4
+ // src/components/forms/passwordValidation.ts
5
+ var SPECIAL_REGEX = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?~`]/;
6
+ function validatePassword(password) {
7
+ return {
8
+ minLength: password.length >= 8,
9
+ hasLower: /[a-z]/.test(password),
10
+ hasUpper: /[A-Z]/.test(password),
11
+ hasDigit: /[0-9]/.test(password),
12
+ hasSpecial: SPECIAL_REGEX.test(password)
13
+ };
14
+ }
15
+ function isPasswordValid(checks) {
16
+ return checks.minLength && checks.hasLower && checks.hasUpper && checks.hasDigit && checks.hasSpecial;
17
+ }
18
+ function getPasswordValid(password) {
19
+ return isPasswordValid(validatePassword(password));
20
+ }
21
+ var PASSWORD_RULES = [
22
+ { key: "minLength", label: "M\xEDnimo 8 caracteres", ok: (c) => c.minLength },
23
+ { key: "hasLower", label: "Al menos una min\xFAscula", ok: (c) => c.hasLower },
24
+ { key: "hasUpper", label: "Al menos una may\xFAscula", ok: (c) => c.hasUpper },
25
+ { key: "hasDigit", label: "Al menos un n\xFAmero", ok: (c) => c.hasDigit },
26
+ { key: "hasSpecial", label: "Al menos un car\xE1cter especial (!@#$%^&* etc.)", ok: (c) => c.hasSpecial }
27
+ ];
28
+ var PASSWORD_HINT = "M\xEDn. 8 caracteres, may\xFAscula, min\xFAscula, n\xFAmero y un car\xE1cter especial.";
29
+
30
+ // src/components/forms/PasswordInput.tsx
31
+ import { jsx, jsxs } from "react/jsx-runtime";
32
+ function inputBaseStyle(focused, disabled, error) {
33
+ return {
34
+ fontFamily: "var(--font-sans, system-ui)",
35
+ fontSize: "var(--text-base, 14px)",
36
+ fontWeight: "var(--weight-regular, 400)",
37
+ color: "var(--color-text, #1a1a1a)",
38
+ background: "var(--input-bg, #ffffff)",
39
+ border: `1.5px solid ${error ? "var(--color-error, #dc2626)" : focused ? "var(--input-border-focus, var(--color-primary, #CC032E))" : "var(--input-border, var(--color-border, #e0e0e0))"}`,
40
+ borderRadius: "var(--input-radius, var(--radius-md, 6px))",
41
+ padding: "var(--space-2, 8px) var(--space-3, 12px)",
42
+ paddingRight: "3rem",
43
+ outline: "none",
44
+ transition: "box-shadow var(--duration-fast, .1s) var(--ease-default, ease), border-color var(--duration-fast, .1s) var(--ease-default, ease)",
45
+ boxShadow: focused ? "var(--focus-ring, 0 0 0 3px rgba(204, 3, 46, 0.12))" : "none",
46
+ opacity: disabled ? 0.6 : 1,
47
+ width: "100%",
48
+ boxSizing: "border-box"
49
+ };
50
+ }
51
+ function PasswordInput({
52
+ value,
53
+ onChange,
54
+ placeholder = "Contrase\xF1a",
55
+ label,
56
+ hint,
57
+ error,
58
+ disabled,
59
+ required,
60
+ ariaLabel,
61
+ showValidation = false,
62
+ id: idProp,
63
+ inputClassName,
64
+ className,
65
+ minLength = 8,
66
+ "data-testid": dataTestId
67
+ }) {
68
+ const [revealed, setRevealed] = useState(false);
69
+ const [focused, setFocused] = useState(false);
70
+ const genId = useId();
71
+ const id = idProp ?? genId;
72
+ const appChrome = showValidation || inputClassName !== void 0 || className !== void 0;
73
+ const effectiveInputClass = inputClassName ?? (showValidation ? "form-input" : void 0);
74
+ if (appChrome) {
75
+ const checks = validatePassword(value);
76
+ return /* @__PURE__ */ jsxs("div", { className: className ?? "form-group", children: [
77
+ label ? /* @__PURE__ */ jsx("label", { className: "text-xs block mb-2 font-semibold", htmlFor: id, children: label }) : null,
78
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative", display: "flex", alignItems: "center" }, children: [
79
+ /* @__PURE__ */ jsx(
80
+ "input",
81
+ {
82
+ type: revealed ? "text" : "password",
83
+ id,
84
+ "data-testid": dataTestId,
85
+ value,
86
+ disabled,
87
+ onChange: (e) => onChange(e.target.value),
88
+ placeholder,
89
+ title: PASSWORD_HINT,
90
+ className: effectiveInputClass,
91
+ minLength,
92
+ required,
93
+ "aria-label": ariaLabel ?? label ?? "Password",
94
+ style: { paddingRight: "2.5rem", flex: 1 }
95
+ }
96
+ ),
97
+ /* @__PURE__ */ jsx(
98
+ "button",
99
+ {
100
+ type: "button",
101
+ onClick: () => setRevealed((v) => !v),
102
+ tabIndex: -1,
103
+ style: {
104
+ position: "absolute",
105
+ right: "0.5rem",
106
+ background: "none",
107
+ border: "none",
108
+ padding: "0.25rem",
109
+ cursor: disabled ? "not-allowed" : "pointer",
110
+ color: "var(--color-text-muted)",
111
+ display: "flex",
112
+ alignItems: "center"
113
+ },
114
+ title: revealed ? "Ocultar contrase\xF1a" : "Mostrar contrase\xF1a",
115
+ disabled,
116
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", style: { fontSize: "1.25rem" }, children: revealed ? "visibility_off" : "visibility" })
117
+ }
118
+ )
119
+ ] }),
120
+ showValidation ? /* @__PURE__ */ jsxs("p", { style: { marginTop: "0.5rem", fontSize: "0.75rem", color: "var(--color-text-secondary)" }, children: [
121
+ "* ",
122
+ PASSWORD_HINT
123
+ ] }) : null,
124
+ showValidation && value.length > 0 ? /* @__PURE__ */ jsx(
125
+ "ul",
126
+ {
127
+ style: {
128
+ marginTop: "0.5rem",
129
+ paddingLeft: "1.25rem",
130
+ fontSize: "0.75rem",
131
+ color: "var(--color-text-secondary)"
132
+ },
133
+ children: PASSWORD_RULES.map((rule) => /* @__PURE__ */ jsxs(
134
+ "li",
135
+ {
136
+ style: {
137
+ listStyle: "none",
138
+ marginLeft: "-1.25rem",
139
+ marginBottom: "0.25rem",
140
+ display: "flex",
141
+ alignItems: "center",
142
+ gap: "0.35rem"
143
+ },
144
+ children: [
145
+ /* @__PURE__ */ jsx(
146
+ "span",
147
+ {
148
+ style: {
149
+ color: rule.ok(checks) ? "var(--color-success)" : "var(--color-text-muted)",
150
+ fontWeight: rule.ok(checks) ? 600 : 400
151
+ },
152
+ children: rule.ok(checks) ? "\u2713" : "\u25CB"
153
+ }
154
+ ),
155
+ /* @__PURE__ */ jsx("span", { style: rule.ok(checks) ? { color: "var(--color-success)" } : void 0, children: rule.label })
156
+ ]
157
+ },
158
+ rule.key
159
+ ))
160
+ }
161
+ ) : null,
162
+ error ? /* @__PURE__ */ jsx("p", { role: "alert", style: { marginTop: "0.35rem", fontSize: "0.8125rem", color: "var(--color-error, #dc2626)" }, children: error }) : hint ? /* @__PURE__ */ jsx("p", { style: { marginTop: "0.35rem", fontSize: "0.8125rem", color: "var(--color-text-muted, #757578)" }, children: hint }) : null
163
+ ] });
164
+ }
165
+ const inputStyle = inputBaseStyle(focused, disabled, Boolean(error));
166
+ const buttonStyle = {
167
+ position: "absolute",
168
+ right: "var(--space-2, 8px)",
169
+ top: "50%",
170
+ transform: "translateY(-50%)",
171
+ background: "transparent",
172
+ border: "none",
173
+ cursor: disabled ? "not-allowed" : "pointer",
174
+ padding: 0,
175
+ display: "inline-flex",
176
+ alignItems: "center",
177
+ justifyContent: "center",
178
+ color: "var(--color-text-muted, #6b6b6b)",
179
+ opacity: disabled ? 0.6 : 1
180
+ };
181
+ const wrapStyle = {
182
+ position: "relative"
183
+ };
184
+ const labelStyle = {
185
+ fontFamily: "var(--font-sans, system-ui)",
186
+ fontSize: "var(--text-sm, 13px)",
187
+ fontWeight: "var(--weight-medium, 500)",
188
+ color: "var(--color-text, #1a1a1a)",
189
+ marginBottom: "var(--space-1, .25rem)",
190
+ display: "flex",
191
+ gap: "6px",
192
+ alignItems: "baseline"
193
+ };
194
+ const metaStyle = {
195
+ fontFamily: "var(--font-sans, system-ui)",
196
+ fontSize: "var(--text-sm, 13px)",
197
+ margin: "var(--space-1, .25rem) 0 0",
198
+ lineHeight: 1.2,
199
+ color: error ? "var(--color-error, #dc2626)" : "var(--color-text-muted, #757578)"
200
+ };
201
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", width: "100%" }, children: [
202
+ label ? /* @__PURE__ */ jsxs("label", { htmlFor: id, style: labelStyle, children: [
203
+ label,
204
+ required ? /* @__PURE__ */ jsxs("span", { style: { color: "var(--color-error, #dc2626)" }, "aria-hidden": true, children: [
205
+ " ",
206
+ "*"
207
+ ] }) : null
208
+ ] }) : null,
209
+ /* @__PURE__ */ jsxs("div", { style: wrapStyle, children: [
210
+ /* @__PURE__ */ jsx(
211
+ "input",
212
+ {
213
+ id,
214
+ type: revealed ? "text" : "password",
215
+ value,
216
+ disabled,
217
+ onChange: (e) => onChange(e.target.value),
218
+ placeholder,
219
+ "aria-label": ariaLabel ?? label ?? "Password",
220
+ onFocus: () => setFocused(true),
221
+ onBlur: () => setFocused(false),
222
+ style: inputStyle,
223
+ "data-testid": dataTestId
224
+ }
225
+ ),
226
+ /* @__PURE__ */ jsx(
227
+ "button",
228
+ {
229
+ type: "button",
230
+ style: buttonStyle,
231
+ disabled,
232
+ "aria-label": revealed ? "Ocultar password" : "Mostrar password",
233
+ onClick: () => setRevealed((v) => !v),
234
+ children: /* @__PURE__ */ jsx(
235
+ "span",
236
+ {
237
+ className: "material-symbols-outlined",
238
+ style: { fontSize: "1.1rem", lineHeight: 1 },
239
+ "aria-hidden": "true",
240
+ children: revealed ? "visibility_off" : "visibility"
241
+ }
242
+ )
243
+ }
244
+ )
245
+ ] }),
246
+ error ? /* @__PURE__ */ jsx("p", { role: "alert", style: metaStyle, children: error }) : hint ? /* @__PURE__ */ jsx("p", { style: metaStyle, children: hint }) : null
247
+ ] });
248
+ }
249
+
250
+ // src/components/forms/MultiSelectDropdown.tsx
251
+ import { useEffect, useId as useId2, useMemo, useRef, useState as useState2 } from "react";
252
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
253
+ function baseControlStyle(disabled, focused, error) {
254
+ return {
255
+ width: "100%",
256
+ display: "flex",
257
+ alignItems: "center",
258
+ justifyContent: "space-between",
259
+ gap: "var(--space-2, 8px)",
260
+ padding: "var(--space-2, 8px) var(--space-3, 12px)",
261
+ borderRadius: "var(--input-radius, var(--radius-md, 6px))",
262
+ border: `1.5px solid ${error ? "var(--color-error, #dc2626)" : focused ? "var(--input-border-focus, var(--color-primary, #CC032E))" : "var(--input-border, var(--color-border, #e0e0e0))"}`,
263
+ background: "var(--input-bg, #ffffff)",
264
+ color: "var(--color-text, #1a1a1a)",
265
+ cursor: disabled ? "not-allowed" : "pointer",
266
+ opacity: disabled ? 0.6 : 1,
267
+ boxShadow: focused ? "var(--focus-ring, 0 0 0 3px rgba(204, 3, 46, 0.12))" : "none",
268
+ outline: "none",
269
+ userSelect: "none"
270
+ };
271
+ }
272
+ var appButtonStyle = (disabled) => ({
273
+ width: "100%",
274
+ textAlign: "left",
275
+ appearance: "none",
276
+ cursor: disabled ? "not-allowed" : "pointer",
277
+ display: "flex",
278
+ alignItems: "center",
279
+ justifyContent: "space-between",
280
+ gap: "0.5rem"
281
+ });
282
+ function popoverStyle() {
283
+ return {
284
+ position: "absolute",
285
+ left: 0,
286
+ right: 0,
287
+ top: "calc(100% + 6px)",
288
+ zIndex: "var(--z-dropdown, 100)",
289
+ background: "var(--color-surface, #ffffff)",
290
+ border: "1.5px solid var(--color-border, #e0e0e0)",
291
+ borderRadius: "var(--radius-md, 6px)",
292
+ boxShadow: "var(--shadow-md, 0 4px 6px -1px rgba(0,0,0,.1))",
293
+ padding: "var(--space-2, 8px)",
294
+ maxHeight: "240px",
295
+ overflowY: "auto"
296
+ };
297
+ }
298
+ function itemRowStyle(active) {
299
+ return {
300
+ width: "100%",
301
+ display: "flex",
302
+ alignItems: "center",
303
+ justifyContent: "flex-start",
304
+ gap: "var(--space-2, 8px)",
305
+ padding: "var(--space-1, .25rem) var(--space-2, 8px)",
306
+ borderRadius: "var(--radius-sm, 4px)",
307
+ cursor: "pointer",
308
+ background: active ? "var(--color-primary-light, rgba(204, 3, 46, 0.08))" : "transparent",
309
+ color: active ? "var(--color-primary, #CC032E)" : "var(--color-text, #1a1a1a)"
310
+ };
311
+ }
312
+ var defaultLabelStyle = {
313
+ fontFamily: "var(--font-sans, system-ui)",
314
+ fontSize: "var(--text-sm, 13px)",
315
+ fontWeight: "var(--weight-medium, 500)",
316
+ color: "var(--color-text, #1a1a1a)",
317
+ marginBottom: "var(--space-1, .25rem)",
318
+ display: "block"
319
+ };
320
+ function MultiSelectDropdown({
321
+ label,
322
+ options,
323
+ value,
324
+ selected,
325
+ onChange,
326
+ placeholder = "Seleccionar\u2026",
327
+ emptyLabel,
328
+ disabled,
329
+ allowAllNone = true,
330
+ allLabel = "Todos",
331
+ noneLabel = "Ninguno",
332
+ maxLabels = 2,
333
+ labelStyle,
334
+ style,
335
+ buttonClassName
336
+ }) {
337
+ const [open, setOpen] = useState2(false);
338
+ const [focused, setFocused] = useState2(false);
339
+ const id = useId2();
340
+ const rootRef = useRef(null);
341
+ const selectedValues = value ?? selected ?? [];
342
+ const effectivePlaceholder = emptyLabel ?? placeholder;
343
+ const selectedSet = useMemo(() => new Set(selectedValues), [selectedValues]);
344
+ const selectedLabels = useMemo(() => {
345
+ const byValue = new Map(options.map((o) => [o.value, o.label]));
346
+ return selectedValues.map((v) => byValue.get(v) ?? v);
347
+ }, [options, selectedValues]);
348
+ const allValues = useMemo(() => options.map((o) => o.value), [options]);
349
+ const isAllSelected = useMemo(
350
+ () => selectedValues.length > 0 && selectedValues.length === allValues.length,
351
+ [selectedValues, allValues]
352
+ );
353
+ useEffect(() => {
354
+ if (!open) return;
355
+ const onDown = (e) => {
356
+ const el = rootRef.current;
357
+ if (!el) return;
358
+ const target = e.target;
359
+ if (target && el.contains(target)) return;
360
+ setOpen(false);
361
+ };
362
+ document.addEventListener("mousedown", onDown);
363
+ return () => document.removeEventListener("mousedown", onDown);
364
+ }, [open]);
365
+ const toggleValue = (v) => {
366
+ if (disabled) return;
367
+ const next = selectedSet.has(v) ? selectedValues.filter((x) => x !== v) : [...selectedValues, v];
368
+ onChange(next);
369
+ };
370
+ const applyAllNone = (mode) => {
371
+ if (disabled) return;
372
+ if (mode === "all") onChange(allValues);
373
+ else onChange([]);
374
+ };
375
+ const summary = (() => {
376
+ if (selectedValues.length === 0) return effectivePlaceholder;
377
+ if (isAllSelected) return allLabel;
378
+ const shown = selectedLabels.slice(0, Math.max(1, maxLabels));
379
+ const extra = selectedLabels.length - shown.length;
380
+ return extra > 0 ? `${shown.join(", ")} +${extra} mas` : shown.join(", ");
381
+ })();
382
+ const checkboxStyle = {
383
+ accentColor: "var(--color-primary, #CC032E)",
384
+ margin: 0
385
+ };
386
+ const chevron = open ? "expand_less" : "expand_more";
387
+ const triggerStyle = buttonClassName ? appButtonStyle(disabled) : baseControlStyle(disabled, focused);
388
+ const mergedLabelStyle = label ? { ...defaultLabelStyle, ...labelStyle } : void 0;
389
+ return /* @__PURE__ */ jsxs2(
390
+ "div",
391
+ {
392
+ style: { display: "flex", flexDirection: "column", width: "100%", position: "relative", ...style },
393
+ ref: rootRef,
394
+ children: [
395
+ label ? /* @__PURE__ */ jsx2("label", { htmlFor: id, style: mergedLabelStyle, children: label }) : null,
396
+ /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
397
+ /* @__PURE__ */ jsxs2(
398
+ "button",
399
+ {
400
+ id,
401
+ type: "button",
402
+ className: buttonClassName,
403
+ disabled,
404
+ style: triggerStyle,
405
+ onClick: () => setOpen((v) => !v),
406
+ onFocus: () => setFocused(true),
407
+ onBlur: () => setFocused(false),
408
+ "aria-haspopup": "listbox",
409
+ "aria-expanded": open,
410
+ children: [
411
+ /* @__PURE__ */ jsx2("span", { style: { flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: summary }),
412
+ /* @__PURE__ */ jsx2(
413
+ "span",
414
+ {
415
+ className: "material-symbols-outlined",
416
+ style: { fontSize: "1.1rem", color: "var(--color-text-muted, #6b6b6b)", opacity: buttonClassName ? 0.7 : void 0 },
417
+ "aria-hidden": "true",
418
+ children: chevron
419
+ }
420
+ )
421
+ ]
422
+ }
423
+ ),
424
+ open ? /* @__PURE__ */ jsx2("div", { style: popoverStyle(), role: "listbox", "aria-multiselectable": "true", children: options.length === 0 ? /* @__PURE__ */ jsx2(
425
+ "div",
426
+ {
427
+ style: {
428
+ padding: "0.75rem",
429
+ fontSize: "0.875rem",
430
+ color: "var(--color-text-muted, #6b6b6b)"
431
+ },
432
+ children: effectivePlaceholder
433
+ }
434
+ ) : /* @__PURE__ */ jsxs2(Fragment, { children: [
435
+ allowAllNone ? /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: "var(--space-2, 8px)", marginBottom: "var(--space-2, 8px)" }, children: [
436
+ /* @__PURE__ */ jsx2(
437
+ "button",
438
+ {
439
+ type: "button",
440
+ style: {
441
+ padding: "var(--space-1, .25rem) var(--space-2, 8px)",
442
+ borderRadius: "var(--radius-md, 6px)",
443
+ border: "1.5px solid var(--color-border, #e0e0e0)",
444
+ background: "var(--color-neutral-100, #f5f5f5)",
445
+ cursor: disabled ? "not-allowed" : "pointer",
446
+ color: "var(--color-text, #1a1a1a)",
447
+ fontFamily: "var(--font-sans, system-ui)",
448
+ fontSize: "var(--text-sm, 13px)",
449
+ fontWeight: "var(--weight-medium, 500)"
450
+ },
451
+ onClick: () => applyAllNone("all"),
452
+ disabled,
453
+ children: allLabel
454
+ }
455
+ ),
456
+ /* @__PURE__ */ jsx2(
457
+ "button",
458
+ {
459
+ type: "button",
460
+ style: {
461
+ padding: "var(--space-1, .25rem) var(--space-2, 8px)",
462
+ borderRadius: "var(--radius-md, 6px)",
463
+ border: "1.5px solid var(--color-border, #e0e0e0)",
464
+ background: "var(--color-neutral-100, #f5f5f5)",
465
+ cursor: disabled ? "not-allowed" : "pointer",
466
+ color: "var(--color-text, #1a1a1a)",
467
+ fontFamily: "var(--font-sans, system-ui)",
468
+ fontSize: "var(--text-sm, 13px)",
469
+ fontWeight: "var(--weight-medium, 500)"
470
+ },
471
+ onClick: () => applyAllNone("none"),
472
+ disabled,
473
+ children: noneLabel
474
+ }
475
+ )
476
+ ] }) : null,
477
+ options.map((opt) => {
478
+ const checked = selectedSet.has(opt.value);
479
+ return /* @__PURE__ */ jsxs2(
480
+ "div",
481
+ {
482
+ style: itemRowStyle(checked),
483
+ role: "option",
484
+ "aria-selected": checked,
485
+ onClick: () => toggleValue(opt.value),
486
+ children: [
487
+ /* @__PURE__ */ jsx2("input", { type: "checkbox", checked, readOnly: true, style: checkboxStyle, "aria-label": opt.label }),
488
+ /* @__PURE__ */ jsx2(
489
+ "span",
490
+ {
491
+ style: {
492
+ fontFamily: "var(--font-sans, system-ui)",
493
+ fontSize: "var(--text-sm, 13px)",
494
+ fontWeight: "var(--weight-medium, 500)"
495
+ },
496
+ children: opt.label
497
+ }
498
+ )
499
+ ]
500
+ },
501
+ opt.value
502
+ );
503
+ })
504
+ ] }) }) : null
505
+ ] })
506
+ ]
507
+ }
508
+ );
509
+ }
510
+ export {
511
+ MultiSelectDropdown,
512
+ PASSWORD_HINT,
513
+ PASSWORD_RULES,
514
+ PasswordInput,
515
+ getPasswordValid,
516
+ isPasswordValid,
517
+ validatePassword
518
+ };