@conatel-sa/react-ui 0.2.4 → 0.2.5

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 CHANGED
@@ -29,25 +29,6 @@ var PASSWORD_HINT = "M\xEDn. 8 caracteres, may\xFAscula, min\xFAscula, n\xFAmero
29
29
 
30
30
  // src/components/forms/PasswordInput.tsx
31
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
32
  function PasswordInput({
52
33
  value,
53
34
  onChange,
@@ -60,263 +41,148 @@ function PasswordInput({
60
41
  ariaLabel,
61
42
  showValidation = false,
62
43
  id: idProp,
63
- inputClassName,
64
- className,
44
+ inputClassName = "form-input",
45
+ className = "form-group",
65
46
  minLength = 8,
66
- "data-testid": dataTestId
47
+ "data-testid": dataTestId,
48
+ leftIcon = "lock"
67
49
  }) {
68
50
  const [revealed, setRevealed] = useState(false);
69
- const [focused, setFocused] = useState(false);
70
51
  const genId = useId();
71
52
  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: [
53
+ const checks = validatePassword(value);
54
+ const wrapClass = `${inputClassName} input-password-wrap`.trim();
55
+ return /* @__PURE__ */ jsxs("div", { className, children: [
56
+ label ? /* @__PURE__ */ jsxs("label", { className: "form-label-app", htmlFor: id, children: [
203
57
  label,
204
- required ? /* @__PURE__ */ jsxs("span", { style: { color: "var(--color-error, #dc2626)" }, "aria-hidden": true, children: [
58
+ required ? /* @__PURE__ */ jsxs("span", { className: "required-mark", "aria-hidden": true, children: [
205
59
  " ",
206
60
  "*"
207
61
  ] }) : null
208
62
  ] }) : 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(
63
+ /* @__PURE__ */ jsxs(
64
+ "div",
65
+ {
66
+ className: wrapClass,
67
+ style: {
68
+ position: "relative",
69
+ display: "flex",
70
+ alignItems: "center",
71
+ padding: "0.75rem 2.5rem 0.75rem 3rem"
72
+ },
73
+ children: [
74
+ /* @__PURE__ */ jsx(
235
75
  "span",
236
76
  {
237
77
  className: "material-symbols-outlined",
238
- style: { fontSize: "1.1rem", lineHeight: 1 },
239
- "aria-hidden": "true",
240
- children: revealed ? "visibility_off" : "visibility"
78
+ style: {
79
+ position: "absolute",
80
+ left: "1rem",
81
+ color: "var(--color-text-muted)",
82
+ fontSize: "1.25rem",
83
+ pointerEvents: "none"
84
+ },
85
+ "aria-hidden": true,
86
+ children: leftIcon
87
+ }
88
+ ),
89
+ /* @__PURE__ */ jsx(
90
+ "input",
91
+ {
92
+ type: revealed ? "text" : "password",
93
+ id,
94
+ "data-testid": dataTestId,
95
+ value,
96
+ disabled,
97
+ onChange: (e) => onChange(e.target.value),
98
+ placeholder,
99
+ title: PASSWORD_HINT,
100
+ minLength,
101
+ required,
102
+ "aria-label": ariaLabel ?? label ?? "Password",
103
+ style: {
104
+ border: "none",
105
+ background: "transparent",
106
+ flex: 1,
107
+ minWidth: 0,
108
+ padding: 0,
109
+ fontSize: "inherit",
110
+ outline: "none"
111
+ }
112
+ }
113
+ ),
114
+ /* @__PURE__ */ jsx(
115
+ "button",
116
+ {
117
+ type: "button",
118
+ onClick: () => setRevealed((v) => !v),
119
+ tabIndex: -1,
120
+ title: revealed ? "Ocultar contrase\xF1a" : "Mostrar contrase\xF1a",
121
+ disabled,
122
+ style: {
123
+ position: "absolute",
124
+ right: "0.5rem",
125
+ background: "none",
126
+ border: "none",
127
+ padding: "0.25rem",
128
+ cursor: disabled ? "not-allowed" : "pointer",
129
+ color: "var(--color-text-muted)"
130
+ },
131
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", style: { fontSize: "1.25rem" }, children: revealed ? "visibility_off" : "visibility" })
241
132
  }
242
133
  )
243
- }
244
- )
245
- ] }),
246
- error ? /* @__PURE__ */ jsx("p", { role: "alert", style: metaStyle, children: error }) : hint ? /* @__PURE__ */ jsx("p", { style: metaStyle, children: hint }) : null
134
+ ]
135
+ }
136
+ ),
137
+ showValidation ? /* @__PURE__ */ jsxs("p", { style: { marginTop: "0.5rem", fontSize: "0.75rem", color: "var(--color-text-muted)" }, children: [
138
+ "* ",
139
+ PASSWORD_HINT
140
+ ] }) : null,
141
+ showValidation && value.length > 0 ? /* @__PURE__ */ jsx(
142
+ "ul",
143
+ {
144
+ style: {
145
+ marginTop: "0.5rem",
146
+ paddingLeft: "1.25rem",
147
+ fontSize: "0.75rem",
148
+ color: "var(--color-text-muted)"
149
+ },
150
+ children: PASSWORD_RULES.map((rule) => /* @__PURE__ */ jsxs(
151
+ "li",
152
+ {
153
+ style: {
154
+ listStyle: "none",
155
+ marginLeft: "-1.25rem",
156
+ marginBottom: "0.25rem",
157
+ display: "flex",
158
+ alignItems: "center",
159
+ gap: "0.35rem"
160
+ },
161
+ children: [
162
+ /* @__PURE__ */ jsx(
163
+ "span",
164
+ {
165
+ style: {
166
+ color: rule.ok(checks) ? "var(--color-success)" : "var(--color-text-muted)",
167
+ fontWeight: rule.ok(checks) ? 600 : 400
168
+ },
169
+ children: rule.ok(checks) ? "\u2713" : "\u25CB"
170
+ }
171
+ ),
172
+ /* @__PURE__ */ jsx("span", { style: rule.ok(checks) ? { color: "var(--color-success)" } : void 0, children: rule.label })
173
+ ]
174
+ },
175
+ rule.key
176
+ ))
177
+ }
178
+ ) : null,
179
+ error ? /* @__PURE__ */ jsx("p", { role: "alert", style: { marginTop: "0.35rem", fontSize: "0.8125rem", color: "var(--color-error)" }, children: error }) : hint ? /* @__PURE__ */ jsx("p", { style: { marginTop: "0.35rem", fontSize: "0.8125rem", color: "var(--color-text-muted)" }, children: hint }) : null
247
180
  ] });
248
181
  }
249
182
 
250
183
  // 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
- };
184
+ import { useEffect, useRef, useState as useState2 } from "react";
185
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
320
186
  function MultiSelectDropdown({
321
187
  label,
322
188
  options,
@@ -329,27 +195,16 @@ function MultiSelectDropdown({
329
195
  allowAllNone = true,
330
196
  allLabel = "Todos",
331
197
  noneLabel = "Ninguno",
332
- maxLabels = 2,
333
- labelStyle,
334
198
  style,
335
- buttonClassName
199
+ buttonClassName = "form-select",
200
+ className = "form-group"
336
201
  }) {
337
202
  const [open, setOpen] = useState2(false);
338
- const [focused, setFocused] = useState2(false);
339
- const id = useId2();
340
203
  const rootRef = useRef(null);
341
204
  const selectedValues = value ?? selected ?? [];
342
205
  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
- );
206
+ const isAllSelected = options.length > 0 && selectedValues.length === options.length;
207
+ const isNoneSelected = selectedValues.length === 0;
353
208
  useEffect(() => {
354
209
  if (!open) return;
355
210
  const onDown = (e) => {
@@ -364,154 +219,366 @@ function MultiSelectDropdown({
364
219
  }, [open]);
365
220
  const toggleValue = (v) => {
366
221
  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([]);
222
+ if (selectedValues.includes(v)) onChange(selectedValues.filter((x) => x !== v));
223
+ else onChange([...selectedValues, v]);
374
224
  };
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",
225
+ const buttonLabel = selectedValues.length === 0 ? effectivePlaceholder : selectedValues.map((v) => options.find((o) => o.value === v)?.label ?? v).join(", ");
226
+ return /* @__PURE__ */ jsxs2("div", { ref: rootRef, className, style: { position: "relative", ...style ?? {} }, children: [
227
+ label ? /* @__PURE__ */ jsx2("label", { className: "form-label-dropdown", children: label }) : null,
228
+ /* @__PURE__ */ jsxs2(
229
+ "button",
230
+ {
231
+ type: "button",
232
+ className: buttonClassName,
233
+ onClick: () => {
234
+ if (disabled) return;
235
+ setOpen((o) => !o);
236
+ },
237
+ disabled,
238
+ style: {
239
+ width: "100%",
240
+ textAlign: "left",
241
+ overflow: "hidden",
242
+ textOverflow: "ellipsis",
243
+ whiteSpace: "nowrap",
244
+ display: "flex",
245
+ alignItems: "center",
246
+ justifyContent: "space-between",
247
+ gap: "0.5rem",
248
+ cursor: disabled ? "not-allowed" : "pointer",
249
+ opacity: disabled ? 0.6 : 1
250
+ },
251
+ "aria-haspopup": "listbox",
252
+ "aria-expanded": open,
253
+ children: [
254
+ /* @__PURE__ */ jsx2(
255
+ "span",
399
256
  {
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,
257
+ style: {
258
+ overflow: "hidden",
259
+ textOverflow: "ellipsis",
260
+ whiteSpace: "nowrap",
261
+ color: selectedValues.length === 0 ? "var(--color-text-muted)" : "var(--color-text-primary)"
262
+ },
263
+ children: buttonLabel
264
+ }
265
+ ),
266
+ /* @__PURE__ */ jsx2("span", { className: "material-symbols-outlined", style: { fontSize: "1rem", color: "var(--color-text-secondary)", flexShrink: 0 }, children: open ? "expand_less" : "expand_more" })
267
+ ]
268
+ }
269
+ ),
270
+ open && /* @__PURE__ */ jsxs2(
271
+ "div",
272
+ {
273
+ className: "form-dropdown-panel",
274
+ style: {
275
+ position: "absolute",
276
+ top: "100%",
277
+ left: 0,
278
+ minWidth: "100%",
279
+ maxHeight: "220px",
280
+ overflowY: "auto",
281
+ border: "1px solid var(--color-border)",
282
+ borderRadius: "var(--radius-md)",
283
+ boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
284
+ marginTop: "2px"
285
+ },
286
+ children: [
287
+ allowAllNone && options.length > 0 && /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "0.35rem", padding: "0.35rem 0.6rem", borderBottom: "1px solid var(--color-border)", fontSize: "0.75rem" }, children: [
288
+ /* @__PURE__ */ jsxs2(
289
+ "button",
290
+ {
291
+ type: "button",
292
+ onClick: () => onChange(options.map((o) => o.value)),
293
+ style: {
294
+ background: "none",
295
+ border: "none",
296
+ color: isAllSelected ? "var(--color-success)" : "var(--color-text-secondary)",
297
+ cursor: "pointer",
298
+ display: "inline-flex",
299
+ alignItems: "center",
300
+ gap: "0.2rem",
301
+ padding: 0,
302
+ fontWeight: isAllSelected ? 600 : 500
303
+ },
304
+ children: [
305
+ /* @__PURE__ */ jsx2("span", { className: "material-symbols-outlined", style: { fontSize: "0.9rem" }, children: "check" }),
306
+ /* @__PURE__ */ jsx2("span", { children: allLabel })
307
+ ]
308
+ }
309
+ ),
310
+ /* @__PURE__ */ jsx2("span", { style: { color: "var(--color-text-muted)" }, children: "|" }),
311
+ /* @__PURE__ */ jsxs2(
312
+ "button",
313
+ {
314
+ type: "button",
315
+ onClick: () => onChange([]),
316
+ style: {
317
+ background: "none",
318
+ border: "none",
319
+ color: isNoneSelected ? "var(--color-danger)" : "var(--color-text-secondary)",
320
+ cursor: "pointer",
321
+ display: "inline-flex",
322
+ alignItems: "center",
323
+ gap: "0.2rem",
324
+ padding: 0,
325
+ fontWeight: isNoneSelected ? 600 : 500
326
+ },
327
+ children: [
328
+ /* @__PURE__ */ jsx2("span", { className: "material-symbols-outlined", style: { fontSize: "0.9rem" }, children: "close" }),
329
+ /* @__PURE__ */ jsx2("span", { children: noneLabel })
330
+ ]
331
+ }
332
+ )
333
+ ] }),
334
+ options.length === 0 ? /* @__PURE__ */ jsx2("div", { style: { padding: "0.5rem 0.75rem", fontSize: "0.875rem", color: "var(--color-text-muted)" }, children: "Sin opciones" }) : null,
335
+ options.map((opt) => /* @__PURE__ */ jsxs2(
336
+ "label",
337
+ {
338
+ style: {
339
+ display: "flex",
340
+ alignItems: "center",
341
+ gap: "0.5rem",
342
+ padding: "0.4rem 0.75rem",
343
+ cursor: "pointer",
344
+ fontSize: "0.875rem",
345
+ color: selectedValues.includes(opt.value) ? "var(--color-text-primary)" : "var(--color-text-secondary)",
346
+ background: selectedValues.includes(opt.value) ? "var(--color-accent-bg)" : "transparent"
347
+ },
348
+ onMouseEnter: (e) => {
349
+ e.currentTarget.style.background = selectedValues.includes(opt.value) ? "var(--color-accent-bg)" : "var(--color-bg-hover)";
350
+ },
351
+ onMouseLeave: (e) => {
352
+ e.currentTarget.style.background = selectedValues.includes(opt.value) ? "var(--color-accent-bg)" : "transparent";
353
+ },
410
354
  children: [
411
- /* @__PURE__ */ jsx2("span", { style: { flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: summary }),
412
355
  /* @__PURE__ */ jsx2(
413
- "span",
356
+ "input",
414
357
  {
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
358
+ type: "checkbox",
359
+ checked: selectedValues.includes(opt.value),
360
+ onChange: () => toggleValue(opt.value),
361
+ style: { width: "auto", margin: 0, accentColor: "var(--color-primary)" }
419
362
  }
420
- )
363
+ ),
364
+ opt.label
421
365
  ]
422
- }
423
- ),
424
- open ? /* @__PURE__ */ jsx2("div", { style: popoverStyle(), role: "listbox", "aria-multiselectable": "true", children: options.length === 0 ? /* @__PURE__ */ jsx2(
425
- "div",
366
+ },
367
+ opt.value
368
+ ))
369
+ ]
370
+ }
371
+ )
372
+ ] });
373
+ }
374
+
375
+ // src/components/forms/SingleSelectDropdown.tsx
376
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
377
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
378
+ function SingleSelectDropdown({
379
+ options,
380
+ value,
381
+ selected,
382
+ onChange,
383
+ placeholder = "Seleccionar",
384
+ emptyValue = "",
385
+ allowEmptyOption = true,
386
+ disabled,
387
+ label,
388
+ style,
389
+ buttonClassName = "form-select",
390
+ dataTestId,
391
+ title,
392
+ ariaLabel,
393
+ buttonStyle,
394
+ className = "form-group"
395
+ }) {
396
+ const [open, setOpen] = useState3(false);
397
+ const rootRef = useRef2(null);
398
+ const currentValue = value ?? selected ?? emptyValue;
399
+ const selectedOption = options.find((o) => o.value === currentValue);
400
+ const buttonLabel = selectedOption?.label ?? placeholder;
401
+ const renderedOptions = allowEmptyOption ? [{ value: emptyValue, label: placeholder }, ...options] : options;
402
+ useEffect2(() => {
403
+ if (!open) return;
404
+ const onDown = (e) => {
405
+ const el = rootRef.current;
406
+ if (!el) return;
407
+ const target = e.target;
408
+ if (target && el.contains(target)) return;
409
+ setOpen(false);
410
+ };
411
+ document.addEventListener("mousedown", onDown);
412
+ return () => document.removeEventListener("mousedown", onDown);
413
+ }, [open]);
414
+ return /* @__PURE__ */ jsxs3("div", { ref: rootRef, className, style: { position: "relative", ...style ?? {} }, children: [
415
+ label ? /* @__PURE__ */ jsx3("label", { className: "form-label-dropdown", children: label }) : null,
416
+ /* @__PURE__ */ jsxs3(
417
+ "button",
418
+ {
419
+ type: "button",
420
+ className: buttonClassName,
421
+ "data-testid": dataTestId,
422
+ title,
423
+ "aria-label": ariaLabel,
424
+ onClick: () => {
425
+ if (disabled) return;
426
+ setOpen((o) => !o);
427
+ },
428
+ disabled,
429
+ style: {
430
+ width: "100%",
431
+ textAlign: "left",
432
+ overflow: "hidden",
433
+ textOverflow: "ellipsis",
434
+ whiteSpace: "nowrap",
435
+ display: "flex",
436
+ alignItems: "center",
437
+ justifyContent: "space-between",
438
+ gap: "0.5rem",
439
+ cursor: disabled ? "not-allowed" : "pointer",
440
+ opacity: disabled ? 0.6 : 1,
441
+ ...buttonStyle ?? {}
442
+ },
443
+ "aria-haspopup": "listbox",
444
+ "aria-expanded": open,
445
+ children: [
446
+ /* @__PURE__ */ jsx3(
447
+ "span",
426
448
  {
427
449
  style: {
428
- padding: "0.75rem",
429
- fontSize: "0.875rem",
430
- color: "var(--color-text-muted, #6b6b6b)"
450
+ overflow: "hidden",
451
+ textOverflow: "ellipsis",
452
+ whiteSpace: "nowrap",
453
+ color: currentValue === emptyValue ? "var(--color-text-muted)" : "var(--color-text-primary)"
431
454
  },
432
- children: effectivePlaceholder
455
+ children: buttonLabel
433
456
  }
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
- ]
457
+ ),
458
+ /* @__PURE__ */ jsx3("span", { className: "material-symbols-outlined", style: { fontSize: "1rem", color: "var(--color-text-secondary)", flexShrink: 0 }, children: open ? "expand_less" : "expand_more" })
459
+ ]
460
+ }
461
+ ),
462
+ open ? /* @__PURE__ */ jsxs3(
463
+ "div",
464
+ {
465
+ className: "form-dropdown-panel",
466
+ style: {
467
+ position: "absolute",
468
+ top: "100%",
469
+ left: 0,
470
+ minWidth: "100%",
471
+ maxHeight: "220px",
472
+ overflowY: "auto",
473
+ border: "1px solid var(--color-border)",
474
+ borderRadius: "var(--radius-md)",
475
+ boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
476
+ marginTop: "2px"
477
+ },
478
+ role: "listbox",
479
+ children: [
480
+ renderedOptions.length === 0 ? /* @__PURE__ */ jsx3("div", { style: { padding: "0.5rem 0.75rem", fontSize: "0.875rem", color: "var(--color-text-muted)" }, children: "Sin opciones" }) : null,
481
+ renderedOptions.map((opt) => {
482
+ const isSelected = opt.value === currentValue;
483
+ return /* @__PURE__ */ jsxs3(
484
+ "div",
485
+ {
486
+ role: "option",
487
+ "aria-selected": isSelected,
488
+ onClick: () => {
489
+ onChange(opt.value);
490
+ setOpen(false);
491
+ },
492
+ onMouseEnter: (e) => {
493
+ e.currentTarget.style.background = isSelected ? "var(--color-accent-bg)" : "var(--color-bg-hover)";
494
+ },
495
+ onMouseLeave: (e) => {
496
+ e.currentTarget.style.background = isSelected ? "var(--color-accent-bg)" : "transparent";
500
497
  },
501
- opt.value
502
- );
503
- })
504
- ] }) }) : null
505
- ] })
506
- ]
507
- }
508
- );
498
+ style: {
499
+ display: "flex",
500
+ alignItems: "center",
501
+ gap: "0.5rem",
502
+ padding: "0.4rem 0.75rem",
503
+ cursor: "pointer",
504
+ fontSize: "0.875rem",
505
+ color: isSelected ? "var(--color-text-primary)" : "var(--color-text-secondary)",
506
+ background: isSelected ? "var(--color-accent-bg)" : "transparent",
507
+ userSelect: "none"
508
+ },
509
+ children: [
510
+ isSelected ? /* @__PURE__ */ jsx3("span", { className: "material-symbols-outlined", style: { fontSize: "0.95rem" }, children: "check" }) : null,
511
+ opt.label
512
+ ]
513
+ },
514
+ opt.value
515
+ );
516
+ })
517
+ ]
518
+ }
519
+ ) : null
520
+ ] });
521
+ }
522
+
523
+ // src/components/forms/EmailInput.tsx
524
+ import { useId as useId2 } from "react";
525
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
526
+ function EmailInput({
527
+ value,
528
+ onChange,
529
+ placeholder = "correo@ejemplo.com",
530
+ label = "Correo electr\xF3nico",
531
+ hint,
532
+ error,
533
+ disabled,
534
+ required,
535
+ ariaLabel,
536
+ id: idProp,
537
+ leftIcon = "mail",
538
+ className = "form-group",
539
+ inputClassName = "form-input",
540
+ autoComplete = "email",
541
+ "data-testid": dataTestId
542
+ }) {
543
+ const genId = useId2();
544
+ const id = idProp ?? genId;
545
+ return /* @__PURE__ */ jsxs4("div", { className, children: [
546
+ label ? /* @__PURE__ */ jsxs4("label", { className: "form-label-app", htmlFor: id, children: [
547
+ label,
548
+ required ? /* @__PURE__ */ jsxs4("span", { className: "required-mark", "aria-hidden": true, children: [
549
+ " ",
550
+ "*"
551
+ ] }) : null
552
+ ] }) : null,
553
+ /* @__PURE__ */ jsxs4("div", { className: "input-with-icon", children: [
554
+ /* @__PURE__ */ jsx4("span", { className: "material-symbols-outlined", "aria-hidden": true, children: leftIcon }),
555
+ /* @__PURE__ */ jsx4(
556
+ "input",
557
+ {
558
+ id,
559
+ "data-testid": dataTestId,
560
+ type: "email",
561
+ value,
562
+ onChange: (e) => onChange(e.target.value),
563
+ placeholder,
564
+ className: inputClassName,
565
+ required,
566
+ disabled,
567
+ autoComplete,
568
+ "aria-label": ariaLabel ?? label ?? "Email"
569
+ }
570
+ )
571
+ ] }),
572
+ error ? /* @__PURE__ */ jsx4("p", { role: "alert", style: { marginTop: "0.35rem", fontSize: "0.8125rem", color: "var(--color-error)" }, children: error }) : hint ? /* @__PURE__ */ jsx4("p", { style: { marginTop: "0.35rem", fontSize: "0.8125rem", color: "var(--color-text-muted)" }, children: hint }) : null
573
+ ] });
509
574
  }
510
575
  export {
576
+ EmailInput,
511
577
  MultiSelectDropdown,
512
578
  PASSWORD_HINT,
513
579
  PASSWORD_RULES,
514
580
  PasswordInput,
581
+ SingleSelectDropdown,
515
582
  getPasswordValid,
516
583
  isPasswordValid,
517
584
  validatePassword