@conatel-sa/react-ui 0.2.4 → 0.2.6

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.umd.js CHANGED
@@ -21,10 +21,12 @@ var ReactLibraryForms = (() => {
21
21
  // src/forms-entry.ts
22
22
  var forms_entry_exports = {};
23
23
  __export(forms_entry_exports, {
24
+ EmailInput: () => EmailInput,
24
25
  MultiSelectDropdown: () => MultiSelectDropdown,
25
26
  PASSWORD_HINT: () => PASSWORD_HINT,
26
27
  PASSWORD_RULES: () => PASSWORD_RULES,
27
28
  PasswordInput: () => PasswordInput,
29
+ SingleSelectDropdown: () => SingleSelectDropdown,
28
30
  getPasswordValid: () => getPasswordValid,
29
31
  isPasswordValid: () => isPasswordValid,
30
32
  validatePassword: () => validatePassword
@@ -36,6 +38,7 @@ var ReactLibraryForms = (() => {
36
38
  var {
37
39
  useContext,
38
40
  useEffect,
41
+ useLayoutEffect,
39
42
  useMemo,
40
43
  useState,
41
44
  useRef,
@@ -82,25 +85,6 @@ var ReactLibraryForms = (() => {
82
85
  var Fragment2 = react_shim_default.Fragment;
83
86
 
84
87
  // src/components/forms/PasswordInput.tsx
85
- function inputBaseStyle(focused, disabled, error) {
86
- return {
87
- fontFamily: "var(--font-sans, system-ui)",
88
- fontSize: "var(--text-base, 14px)",
89
- fontWeight: "var(--weight-regular, 400)",
90
- color: "var(--color-text, #1a1a1a)",
91
- background: "var(--input-bg, #ffffff)",
92
- 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))"}`,
93
- borderRadius: "var(--input-radius, var(--radius-md, 6px))",
94
- padding: "var(--space-2, 8px) var(--space-3, 12px)",
95
- paddingRight: "3rem",
96
- outline: "none",
97
- transition: "box-shadow var(--duration-fast, .1s) var(--ease-default, ease), border-color var(--duration-fast, .1s) var(--ease-default, ease)",
98
- boxShadow: focused ? "var(--focus-ring, 0 0 0 3px rgba(204, 3, 46, 0.12))" : "none",
99
- opacity: disabled ? 0.6 : 1,
100
- width: "100%",
101
- boxSizing: "border-box"
102
- };
103
- }
104
88
  function PasswordInput({
105
89
  value,
106
90
  onChange,
@@ -113,261 +97,146 @@ var ReactLibraryForms = (() => {
113
97
  ariaLabel,
114
98
  showValidation = false,
115
99
  id: idProp,
116
- inputClassName,
117
- className,
100
+ inputClassName = "form-input",
101
+ className = "form-group",
118
102
  minLength = 8,
119
- "data-testid": dataTestId
103
+ "data-testid": dataTestId,
104
+ leftIcon = "lock"
120
105
  }) {
121
106
  const [revealed, setRevealed] = useState(false);
122
- const [focused, setFocused] = useState(false);
123
107
  const genId = useId();
124
108
  const id = idProp ?? genId;
125
- const appChrome = showValidation || inputClassName !== void 0 || className !== void 0;
126
- const effectiveInputClass = inputClassName ?? (showValidation ? "form-input" : void 0);
127
- if (appChrome) {
128
- const checks = validatePassword(value);
129
- return /* @__PURE__ */ jsxs("div", { className: className ?? "form-group", children: [
130
- label ? /* @__PURE__ */ jsx("label", { className: "text-xs block mb-2 font-semibold", htmlFor: id, children: label }) : null,
131
- /* @__PURE__ */ jsxs("div", { style: { position: "relative", display: "flex", alignItems: "center" }, children: [
132
- /* @__PURE__ */ jsx(
133
- "input",
134
- {
135
- type: revealed ? "text" : "password",
136
- id,
137
- "data-testid": dataTestId,
138
- value,
139
- disabled,
140
- onChange: (e) => onChange(e.target.value),
141
- placeholder,
142
- title: PASSWORD_HINT,
143
- className: effectiveInputClass,
144
- minLength,
145
- required,
146
- "aria-label": ariaLabel ?? label ?? "Password",
147
- style: { paddingRight: "2.5rem", flex: 1 }
148
- }
149
- ),
150
- /* @__PURE__ */ jsx(
151
- "button",
152
- {
153
- type: "button",
154
- onClick: () => setRevealed((v) => !v),
155
- tabIndex: -1,
156
- style: {
157
- position: "absolute",
158
- right: "0.5rem",
159
- background: "none",
160
- border: "none",
161
- padding: "0.25rem",
162
- cursor: disabled ? "not-allowed" : "pointer",
163
- color: "var(--color-text-muted)",
164
- display: "flex",
165
- alignItems: "center"
166
- },
167
- title: revealed ? "Ocultar contrase\xF1a" : "Mostrar contrase\xF1a",
168
- disabled,
169
- children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", style: { fontSize: "1.25rem" }, children: revealed ? "visibility_off" : "visibility" })
170
- }
171
- )
172
- ] }),
173
- showValidation ? /* @__PURE__ */ jsxs("p", { style: { marginTop: "0.5rem", fontSize: "0.75rem", color: "var(--color-text-secondary)" }, children: [
174
- "* ",
175
- PASSWORD_HINT
176
- ] }) : null,
177
- showValidation && value.length > 0 ? /* @__PURE__ */ jsx(
178
- "ul",
179
- {
180
- style: {
181
- marginTop: "0.5rem",
182
- paddingLeft: "1.25rem",
183
- fontSize: "0.75rem",
184
- color: "var(--color-text-secondary)"
185
- },
186
- children: PASSWORD_RULES.map((rule) => /* @__PURE__ */ jsxs(
187
- "li",
188
- {
189
- style: {
190
- listStyle: "none",
191
- marginLeft: "-1.25rem",
192
- marginBottom: "0.25rem",
193
- display: "flex",
194
- alignItems: "center",
195
- gap: "0.35rem"
196
- },
197
- children: [
198
- /* @__PURE__ */ jsx(
199
- "span",
200
- {
201
- style: {
202
- color: rule.ok(checks) ? "var(--color-success)" : "var(--color-text-muted)",
203
- fontWeight: rule.ok(checks) ? 600 : 400
204
- },
205
- children: rule.ok(checks) ? "\u2713" : "\u25CB"
206
- }
207
- ),
208
- /* @__PURE__ */ jsx("span", { style: rule.ok(checks) ? { color: "var(--color-success)" } : void 0, children: rule.label })
209
- ]
210
- },
211
- rule.key
212
- ))
213
- }
214
- ) : null,
215
- 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
216
- ] });
217
- }
218
- const inputStyle = inputBaseStyle(focused, disabled, Boolean(error));
219
- const buttonStyle = {
220
- position: "absolute",
221
- right: "var(--space-2, 8px)",
222
- top: "50%",
223
- transform: "translateY(-50%)",
224
- background: "transparent",
225
- border: "none",
226
- cursor: disabled ? "not-allowed" : "pointer",
227
- padding: 0,
228
- display: "inline-flex",
229
- alignItems: "center",
230
- justifyContent: "center",
231
- color: "var(--color-text-muted, #6b6b6b)",
232
- opacity: disabled ? 0.6 : 1
233
- };
234
- const wrapStyle = {
235
- position: "relative"
236
- };
237
- const labelStyle = {
238
- fontFamily: "var(--font-sans, system-ui)",
239
- fontSize: "var(--text-sm, 13px)",
240
- fontWeight: "var(--weight-medium, 500)",
241
- color: "var(--color-text, #1a1a1a)",
242
- marginBottom: "var(--space-1, .25rem)",
243
- display: "flex",
244
- gap: "6px",
245
- alignItems: "baseline"
246
- };
247
- const metaStyle = {
248
- fontFamily: "var(--font-sans, system-ui)",
249
- fontSize: "var(--text-sm, 13px)",
250
- margin: "var(--space-1, .25rem) 0 0",
251
- lineHeight: 1.2,
252
- color: error ? "var(--color-error, #dc2626)" : "var(--color-text-muted, #757578)"
253
- };
254
- return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", width: "100%" }, children: [
255
- label ? /* @__PURE__ */ jsxs("label", { htmlFor: id, style: labelStyle, children: [
109
+ const checks = validatePassword(value);
110
+ const wrapClass = `${inputClassName} input-password-wrap`.trim();
111
+ return /* @__PURE__ */ jsxs("div", { className, children: [
112
+ label ? /* @__PURE__ */ jsxs("label", { className: "form-label-app", htmlFor: id, children: [
256
113
  label,
257
- required ? /* @__PURE__ */ jsxs("span", { style: { color: "var(--color-error, #dc2626)" }, "aria-hidden": true, children: [
114
+ required ? /* @__PURE__ */ jsxs("span", { className: "required-mark", "aria-hidden": true, children: [
258
115
  " ",
259
116
  "*"
260
117
  ] }) : null
261
118
  ] }) : null,
262
- /* @__PURE__ */ jsxs("div", { style: wrapStyle, children: [
263
- /* @__PURE__ */ jsx(
264
- "input",
265
- {
266
- id,
267
- type: revealed ? "text" : "password",
268
- value,
269
- disabled,
270
- onChange: (e) => onChange(e.target.value),
271
- placeholder,
272
- "aria-label": ariaLabel ?? label ?? "Password",
273
- onFocus: () => setFocused(true),
274
- onBlur: () => setFocused(false),
275
- style: inputStyle,
276
- "data-testid": dataTestId
277
- }
278
- ),
279
- /* @__PURE__ */ jsx(
280
- "button",
281
- {
282
- type: "button",
283
- style: buttonStyle,
284
- disabled,
285
- "aria-label": revealed ? "Ocultar password" : "Mostrar password",
286
- onClick: () => setRevealed((v) => !v),
287
- children: /* @__PURE__ */ jsx(
119
+ /* @__PURE__ */ jsxs(
120
+ "div",
121
+ {
122
+ className: wrapClass,
123
+ style: {
124
+ position: "relative",
125
+ display: "flex",
126
+ alignItems: "center",
127
+ padding: "0.75rem 2.5rem 0.75rem 3rem"
128
+ },
129
+ children: [
130
+ /* @__PURE__ */ jsx(
288
131
  "span",
289
132
  {
290
133
  className: "material-symbols-outlined",
291
- style: { fontSize: "1.1rem", lineHeight: 1 },
292
- "aria-hidden": "true",
293
- children: revealed ? "visibility_off" : "visibility"
134
+ style: {
135
+ position: "absolute",
136
+ left: "1rem",
137
+ color: "var(--color-text-muted)",
138
+ fontSize: "1.25rem",
139
+ pointerEvents: "none"
140
+ },
141
+ "aria-hidden": true,
142
+ children: leftIcon
143
+ }
144
+ ),
145
+ /* @__PURE__ */ jsx(
146
+ "input",
147
+ {
148
+ type: revealed ? "text" : "password",
149
+ id,
150
+ "data-testid": dataTestId,
151
+ value,
152
+ disabled,
153
+ onChange: (e) => onChange(e.target.value),
154
+ placeholder,
155
+ title: PASSWORD_HINT,
156
+ minLength,
157
+ required,
158
+ "aria-label": ariaLabel ?? label ?? "Password",
159
+ style: {
160
+ border: "none",
161
+ background: "transparent",
162
+ flex: 1,
163
+ minWidth: 0,
164
+ padding: 0,
165
+ fontSize: "inherit",
166
+ outline: "none"
167
+ }
168
+ }
169
+ ),
170
+ /* @__PURE__ */ jsx(
171
+ "button",
172
+ {
173
+ type: "button",
174
+ onClick: () => setRevealed((v) => !v),
175
+ tabIndex: -1,
176
+ title: revealed ? "Ocultar contrase\xF1a" : "Mostrar contrase\xF1a",
177
+ disabled,
178
+ style: {
179
+ position: "absolute",
180
+ right: "0.5rem",
181
+ background: "none",
182
+ border: "none",
183
+ padding: "0.25rem",
184
+ cursor: disabled ? "not-allowed" : "pointer",
185
+ color: "var(--color-text-muted)"
186
+ },
187
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", style: { fontSize: "1.25rem" }, children: revealed ? "visibility_off" : "visibility" })
294
188
  }
295
189
  )
296
- }
297
- )
298
- ] }),
299
- error ? /* @__PURE__ */ jsx("p", { role: "alert", style: metaStyle, children: error }) : hint ? /* @__PURE__ */ jsx("p", { style: metaStyle, children: hint }) : null
190
+ ]
191
+ }
192
+ ),
193
+ showValidation ? /* @__PURE__ */ jsxs("p", { style: { marginTop: "0.5rem", fontSize: "0.75rem", color: "var(--color-text-muted)" }, children: [
194
+ "* ",
195
+ PASSWORD_HINT
196
+ ] }) : null,
197
+ showValidation && value.length > 0 ? /* @__PURE__ */ jsx(
198
+ "ul",
199
+ {
200
+ style: {
201
+ marginTop: "0.5rem",
202
+ paddingLeft: "1.25rem",
203
+ fontSize: "0.75rem",
204
+ color: "var(--color-text-muted)"
205
+ },
206
+ children: PASSWORD_RULES.map((rule) => /* @__PURE__ */ jsxs(
207
+ "li",
208
+ {
209
+ style: {
210
+ listStyle: "none",
211
+ marginLeft: "-1.25rem",
212
+ marginBottom: "0.25rem",
213
+ display: "flex",
214
+ alignItems: "center",
215
+ gap: "0.35rem"
216
+ },
217
+ children: [
218
+ /* @__PURE__ */ jsx(
219
+ "span",
220
+ {
221
+ style: {
222
+ color: rule.ok(checks) ? "var(--color-success)" : "var(--color-text-muted)",
223
+ fontWeight: rule.ok(checks) ? 600 : 400
224
+ },
225
+ children: rule.ok(checks) ? "\u2713" : "\u25CB"
226
+ }
227
+ ),
228
+ /* @__PURE__ */ jsx("span", { style: rule.ok(checks) ? { color: "var(--color-success)" } : void 0, children: rule.label })
229
+ ]
230
+ },
231
+ rule.key
232
+ ))
233
+ }
234
+ ) : null,
235
+ 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
300
236
  ] });
301
237
  }
302
238
 
303
239
  // src/components/forms/MultiSelectDropdown.tsx
304
- function baseControlStyle(disabled, focused, error) {
305
- return {
306
- width: "100%",
307
- display: "flex",
308
- alignItems: "center",
309
- justifyContent: "space-between",
310
- gap: "var(--space-2, 8px)",
311
- padding: "var(--space-2, 8px) var(--space-3, 12px)",
312
- borderRadius: "var(--input-radius, var(--radius-md, 6px))",
313
- 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))"}`,
314
- background: "var(--input-bg, #ffffff)",
315
- color: "var(--color-text, #1a1a1a)",
316
- cursor: disabled ? "not-allowed" : "pointer",
317
- opacity: disabled ? 0.6 : 1,
318
- boxShadow: focused ? "var(--focus-ring, 0 0 0 3px rgba(204, 3, 46, 0.12))" : "none",
319
- outline: "none",
320
- userSelect: "none"
321
- };
322
- }
323
- var appButtonStyle = (disabled) => ({
324
- width: "100%",
325
- textAlign: "left",
326
- appearance: "none",
327
- cursor: disabled ? "not-allowed" : "pointer",
328
- display: "flex",
329
- alignItems: "center",
330
- justifyContent: "space-between",
331
- gap: "0.5rem"
332
- });
333
- function popoverStyle() {
334
- return {
335
- position: "absolute",
336
- left: 0,
337
- right: 0,
338
- top: "calc(100% + 6px)",
339
- zIndex: "var(--z-dropdown, 100)",
340
- background: "var(--color-surface, #ffffff)",
341
- border: "1.5px solid var(--color-border, #e0e0e0)",
342
- borderRadius: "var(--radius-md, 6px)",
343
- boxShadow: "var(--shadow-md, 0 4px 6px -1px rgba(0,0,0,.1))",
344
- padding: "var(--space-2, 8px)",
345
- maxHeight: "240px",
346
- overflowY: "auto"
347
- };
348
- }
349
- function itemRowStyle(active) {
350
- return {
351
- width: "100%",
352
- display: "flex",
353
- alignItems: "center",
354
- justifyContent: "flex-start",
355
- gap: "var(--space-2, 8px)",
356
- padding: "var(--space-1, .25rem) var(--space-2, 8px)",
357
- borderRadius: "var(--radius-sm, 4px)",
358
- cursor: "pointer",
359
- background: active ? "var(--color-primary-light, rgba(204, 3, 46, 0.08))" : "transparent",
360
- color: active ? "var(--color-primary, #CC032E)" : "var(--color-text, #1a1a1a)"
361
- };
362
- }
363
- var defaultLabelStyle = {
364
- fontFamily: "var(--font-sans, system-ui)",
365
- fontSize: "var(--text-sm, 13px)",
366
- fontWeight: "var(--weight-medium, 500)",
367
- color: "var(--color-text, #1a1a1a)",
368
- marginBottom: "var(--space-1, .25rem)",
369
- display: "block"
370
- };
371
240
  function MultiSelectDropdown({
372
241
  label,
373
242
  options,
@@ -380,27 +249,16 @@ var ReactLibraryForms = (() => {
380
249
  allowAllNone = true,
381
250
  allLabel = "Todos",
382
251
  noneLabel = "Ninguno",
383
- maxLabels = 2,
384
- labelStyle,
385
252
  style,
386
- buttonClassName
253
+ buttonClassName = "form-select",
254
+ className = "form-group"
387
255
  }) {
388
256
  const [open, setOpen] = useState(false);
389
- const [focused, setFocused] = useState(false);
390
- const id = useId();
391
257
  const rootRef = useRef(null);
392
258
  const selectedValues = value ?? selected ?? [];
393
259
  const effectivePlaceholder = emptyLabel ?? placeholder;
394
- const selectedSet = useMemo(() => new Set(selectedValues), [selectedValues]);
395
- const selectedLabels = useMemo(() => {
396
- const byValue = new Map(options.map((o) => [o.value, o.label]));
397
- return selectedValues.map((v) => byValue.get(v) ?? v);
398
- }, [options, selectedValues]);
399
- const allValues = useMemo(() => options.map((o) => o.value), [options]);
400
- const isAllSelected = useMemo(
401
- () => selectedValues.length > 0 && selectedValues.length === allValues.length,
402
- [selectedValues, allValues]
403
- );
260
+ const isAllSelected = options.length > 0 && selectedValues.length === options.length;
261
+ const isNoneSelected = selectedValues.length === 0;
404
262
  useEffect(() => {
405
263
  if (!open) return;
406
264
  const onDown = (e) => {
@@ -415,148 +273,354 @@ var ReactLibraryForms = (() => {
415
273
  }, [open]);
416
274
  const toggleValue = (v) => {
417
275
  if (disabled) return;
418
- const next = selectedSet.has(v) ? selectedValues.filter((x) => x !== v) : [...selectedValues, v];
419
- onChange(next);
420
- };
421
- const applyAllNone = (mode) => {
422
- if (disabled) return;
423
- if (mode === "all") onChange(allValues);
424
- else onChange([]);
276
+ if (selectedValues.includes(v)) onChange(selectedValues.filter((x) => x !== v));
277
+ else onChange([...selectedValues, v]);
425
278
  };
426
- const summary = (() => {
427
- if (selectedValues.length === 0) return effectivePlaceholder;
428
- if (isAllSelected) return allLabel;
429
- const shown = selectedLabels.slice(0, Math.max(1, maxLabels));
430
- const extra = selectedLabels.length - shown.length;
431
- return extra > 0 ? `${shown.join(", ")} +${extra} mas` : shown.join(", ");
432
- })();
433
- const checkboxStyle = {
434
- accentColor: "var(--color-primary, #CC032E)",
435
- margin: 0
436
- };
437
- const chevron = open ? "expand_less" : "expand_more";
438
- const triggerStyle = buttonClassName ? appButtonStyle(disabled) : baseControlStyle(disabled, focused);
439
- const mergedLabelStyle = label ? { ...defaultLabelStyle, ...labelStyle } : void 0;
440
- return /* @__PURE__ */ jsxs(
441
- "div",
442
- {
443
- style: { display: "flex", flexDirection: "column", width: "100%", position: "relative", ...style },
444
- ref: rootRef,
445
- children: [
446
- label ? /* @__PURE__ */ jsx("label", { htmlFor: id, style: mergedLabelStyle, children: label }) : null,
447
- /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
448
- /* @__PURE__ */ jsxs(
449
- "button",
279
+ const buttonLabel = selectedValues.length === 0 ? effectivePlaceholder : selectedValues.map((v) => options.find((o) => o.value === v)?.label ?? v).join(", ");
280
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, className, style: { position: "relative", ...style ?? {} }, children: [
281
+ label ? /* @__PURE__ */ jsx("label", { className: "form-label-dropdown", children: label }) : null,
282
+ /* @__PURE__ */ jsxs(
283
+ "button",
284
+ {
285
+ type: "button",
286
+ className: buttonClassName,
287
+ onClick: () => {
288
+ if (disabled) return;
289
+ setOpen((o) => !o);
290
+ },
291
+ disabled,
292
+ style: {
293
+ width: "100%",
294
+ textAlign: "left",
295
+ overflow: "hidden",
296
+ textOverflow: "ellipsis",
297
+ whiteSpace: "nowrap",
298
+ display: "flex",
299
+ alignItems: "center",
300
+ justifyContent: "space-between",
301
+ gap: "0.5rem",
302
+ cursor: disabled ? "not-allowed" : "pointer",
303
+ opacity: disabled ? 0.6 : 1
304
+ },
305
+ "aria-haspopup": "listbox",
306
+ "aria-expanded": open,
307
+ children: [
308
+ /* @__PURE__ */ jsx(
309
+ "span",
450
310
  {
451
- id,
452
- type: "button",
453
- className: buttonClassName,
454
- disabled,
455
- style: triggerStyle,
456
- onClick: () => setOpen((v) => !v),
457
- onFocus: () => setFocused(true),
458
- onBlur: () => setFocused(false),
459
- "aria-haspopup": "listbox",
460
- "aria-expanded": open,
311
+ style: {
312
+ overflow: "hidden",
313
+ textOverflow: "ellipsis",
314
+ whiteSpace: "nowrap",
315
+ color: selectedValues.length === 0 ? "var(--color-text-muted)" : "var(--color-text-primary)"
316
+ },
317
+ children: buttonLabel
318
+ }
319
+ ),
320
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", style: { fontSize: "1rem", color: "var(--color-text-secondary)", flexShrink: 0 }, children: open ? "expand_less" : "expand_more" })
321
+ ]
322
+ }
323
+ ),
324
+ open && /* @__PURE__ */ jsxs(
325
+ "div",
326
+ {
327
+ className: "form-dropdown-panel",
328
+ style: {
329
+ position: "absolute",
330
+ top: "100%",
331
+ left: 0,
332
+ minWidth: "100%",
333
+ maxHeight: "220px",
334
+ overflowY: "auto",
335
+ border: "1px solid var(--color-border)",
336
+ borderRadius: "var(--radius-md)",
337
+ boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
338
+ marginTop: "2px"
339
+ },
340
+ children: [
341
+ allowAllNone && options.length > 0 && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.35rem", padding: "0.35rem 0.6rem", borderBottom: "1px solid var(--color-border)", fontSize: "0.75rem" }, children: [
342
+ /* @__PURE__ */ jsxs(
343
+ "button",
344
+ {
345
+ type: "button",
346
+ onClick: () => onChange(options.map((o) => o.value)),
347
+ style: {
348
+ background: "none",
349
+ border: "none",
350
+ color: isAllSelected ? "var(--color-success)" : "var(--color-text-secondary)",
351
+ cursor: "pointer",
352
+ display: "inline-flex",
353
+ alignItems: "center",
354
+ gap: "0.2rem",
355
+ padding: 0,
356
+ fontWeight: isAllSelected ? 600 : 500
357
+ },
358
+ children: [
359
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", style: { fontSize: "0.9rem" }, children: "check" }),
360
+ /* @__PURE__ */ jsx("span", { children: allLabel })
361
+ ]
362
+ }
363
+ ),
364
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--color-text-muted)" }, children: "|" }),
365
+ /* @__PURE__ */ jsxs(
366
+ "button",
367
+ {
368
+ type: "button",
369
+ onClick: () => onChange([]),
370
+ style: {
371
+ background: "none",
372
+ border: "none",
373
+ color: isNoneSelected ? "var(--color-danger)" : "var(--color-text-secondary)",
374
+ cursor: "pointer",
375
+ display: "inline-flex",
376
+ alignItems: "center",
377
+ gap: "0.2rem",
378
+ padding: 0,
379
+ fontWeight: isNoneSelected ? 600 : 500
380
+ },
381
+ children: [
382
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", style: { fontSize: "0.9rem" }, children: "close" }),
383
+ /* @__PURE__ */ jsx("span", { children: noneLabel })
384
+ ]
385
+ }
386
+ )
387
+ ] }),
388
+ options.length === 0 ? /* @__PURE__ */ jsx("div", { style: { padding: "0.5rem 0.75rem", fontSize: "0.875rem", color: "var(--color-text-muted)" }, children: "Sin opciones" }) : null,
389
+ options.map((opt) => /* @__PURE__ */ jsxs(
390
+ "label",
391
+ {
392
+ style: {
393
+ display: "flex",
394
+ alignItems: "center",
395
+ gap: "0.5rem",
396
+ padding: "0.4rem 0.75rem",
397
+ cursor: "pointer",
398
+ fontSize: "0.875rem",
399
+ color: selectedValues.includes(opt.value) ? "var(--color-text-primary)" : "var(--color-text-secondary)",
400
+ background: selectedValues.includes(opt.value) ? "var(--color-accent-bg)" : "transparent"
401
+ },
402
+ onMouseEnter: (e) => {
403
+ e.currentTarget.style.background = selectedValues.includes(opt.value) ? "var(--color-accent-bg)" : "var(--color-bg-hover)";
404
+ },
405
+ onMouseLeave: (e) => {
406
+ e.currentTarget.style.background = selectedValues.includes(opt.value) ? "var(--color-accent-bg)" : "transparent";
407
+ },
461
408
  children: [
462
- /* @__PURE__ */ jsx("span", { style: { flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: summary }),
463
409
  /* @__PURE__ */ jsx(
464
- "span",
410
+ "input",
465
411
  {
466
- className: "material-symbols-outlined",
467
- style: { fontSize: "1.1rem", color: "var(--color-text-muted, #6b6b6b)", opacity: buttonClassName ? 0.7 : void 0 },
468
- "aria-hidden": "true",
469
- children: chevron
412
+ type: "checkbox",
413
+ checked: selectedValues.includes(opt.value),
414
+ onChange: () => toggleValue(opt.value),
415
+ style: { width: "auto", margin: 0, accentColor: "var(--color-primary)" }
470
416
  }
471
- )
417
+ ),
418
+ opt.label
472
419
  ]
473
- }
474
- ),
475
- open ? /* @__PURE__ */ jsx("div", { style: popoverStyle(), role: "listbox", "aria-multiselectable": "true", children: options.length === 0 ? /* @__PURE__ */ jsx(
476
- "div",
420
+ },
421
+ opt.value
422
+ ))
423
+ ]
424
+ }
425
+ )
426
+ ] });
427
+ }
428
+
429
+ // src/components/forms/SingleSelectDropdown.tsx
430
+ function SingleSelectDropdown({
431
+ options,
432
+ value,
433
+ selected,
434
+ onChange,
435
+ placeholder = "Seleccionar",
436
+ emptyValue = "",
437
+ allowEmptyOption = true,
438
+ disabled,
439
+ label,
440
+ style,
441
+ buttonClassName = "form-select",
442
+ dataTestId,
443
+ title,
444
+ ariaLabel,
445
+ buttonStyle,
446
+ className = "form-group"
447
+ }) {
448
+ const [open, setOpen] = useState(false);
449
+ const rootRef = useRef(null);
450
+ const currentValue = value ?? selected ?? emptyValue;
451
+ const selectedOption = options.find((o) => o.value === currentValue);
452
+ const buttonLabel = selectedOption?.label ?? placeholder;
453
+ const renderedOptions = allowEmptyOption ? [{ value: emptyValue, label: placeholder }, ...options] : options;
454
+ useEffect(() => {
455
+ if (!open) return;
456
+ const onDown = (e) => {
457
+ const el = rootRef.current;
458
+ if (!el) return;
459
+ const target = e.target;
460
+ if (target && el.contains(target)) return;
461
+ setOpen(false);
462
+ };
463
+ document.addEventListener("mousedown", onDown);
464
+ return () => document.removeEventListener("mousedown", onDown);
465
+ }, [open]);
466
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, className, style: { position: "relative", ...style ?? {} }, children: [
467
+ label ? /* @__PURE__ */ jsx("label", { className: "form-label-dropdown", children: label }) : null,
468
+ /* @__PURE__ */ jsxs(
469
+ "button",
470
+ {
471
+ type: "button",
472
+ className: buttonClassName,
473
+ "data-testid": dataTestId,
474
+ title,
475
+ "aria-label": ariaLabel,
476
+ onClick: () => {
477
+ if (disabled) return;
478
+ setOpen((o) => !o);
479
+ },
480
+ disabled,
481
+ style: {
482
+ width: "100%",
483
+ textAlign: "left",
484
+ overflow: "hidden",
485
+ textOverflow: "ellipsis",
486
+ whiteSpace: "nowrap",
487
+ display: "flex",
488
+ alignItems: "center",
489
+ justifyContent: "space-between",
490
+ gap: "0.5rem",
491
+ cursor: disabled ? "not-allowed" : "pointer",
492
+ opacity: disabled ? 0.6 : 1,
493
+ ...buttonStyle ?? {}
494
+ },
495
+ "aria-haspopup": "listbox",
496
+ "aria-expanded": open,
497
+ children: [
498
+ /* @__PURE__ */ jsx(
499
+ "span",
477
500
  {
478
501
  style: {
479
- padding: "0.75rem",
480
- fontSize: "0.875rem",
481
- color: "var(--color-text-muted, #6b6b6b)"
502
+ overflow: "hidden",
503
+ textOverflow: "ellipsis",
504
+ whiteSpace: "nowrap",
505
+ color: currentValue === emptyValue ? "var(--color-text-muted)" : "var(--color-text-primary)"
482
506
  },
483
- children: effectivePlaceholder
507
+ children: buttonLabel
484
508
  }
485
- ) : /* @__PURE__ */ jsxs(Fragment2, { children: [
486
- allowAllNone ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "var(--space-2, 8px)", marginBottom: "var(--space-2, 8px)" }, children: [
487
- /* @__PURE__ */ jsx(
488
- "button",
489
- {
490
- type: "button",
491
- style: {
492
- padding: "var(--space-1, .25rem) var(--space-2, 8px)",
493
- borderRadius: "var(--radius-md, 6px)",
494
- border: "1.5px solid var(--color-border, #e0e0e0)",
495
- background: "var(--color-neutral-100, #f5f5f5)",
496
- cursor: disabled ? "not-allowed" : "pointer",
497
- color: "var(--color-text, #1a1a1a)",
498
- fontFamily: "var(--font-sans, system-ui)",
499
- fontSize: "var(--text-sm, 13px)",
500
- fontWeight: "var(--weight-medium, 500)"
501
- },
502
- onClick: () => applyAllNone("all"),
503
- disabled,
504
- children: allLabel
505
- }
506
- ),
507
- /* @__PURE__ */ jsx(
508
- "button",
509
- {
510
- type: "button",
511
- style: {
512
- padding: "var(--space-1, .25rem) var(--space-2, 8px)",
513
- borderRadius: "var(--radius-md, 6px)",
514
- border: "1.5px solid var(--color-border, #e0e0e0)",
515
- background: "var(--color-neutral-100, #f5f5f5)",
516
- cursor: disabled ? "not-allowed" : "pointer",
517
- color: "var(--color-text, #1a1a1a)",
518
- fontFamily: "var(--font-sans, system-ui)",
519
- fontSize: "var(--text-sm, 13px)",
520
- fontWeight: "var(--weight-medium, 500)"
521
- },
522
- onClick: () => applyAllNone("none"),
523
- disabled,
524
- children: noneLabel
525
- }
526
- )
527
- ] }) : null,
528
- options.map((opt) => {
529
- const checked = selectedSet.has(opt.value);
530
- return /* @__PURE__ */ jsxs(
531
- "div",
532
- {
533
- style: itemRowStyle(checked),
534
- role: "option",
535
- "aria-selected": checked,
536
- onClick: () => toggleValue(opt.value),
537
- children: [
538
- /* @__PURE__ */ jsx("input", { type: "checkbox", checked, readOnly: true, style: checkboxStyle, "aria-label": opt.label }),
539
- /* @__PURE__ */ jsx(
540
- "span",
541
- {
542
- style: {
543
- fontFamily: "var(--font-sans, system-ui)",
544
- fontSize: "var(--text-sm, 13px)",
545
- fontWeight: "var(--weight-medium, 500)"
546
- },
547
- children: opt.label
548
- }
549
- )
550
- ]
509
+ ),
510
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", style: { fontSize: "1rem", color: "var(--color-text-secondary)", flexShrink: 0 }, children: open ? "expand_less" : "expand_more" })
511
+ ]
512
+ }
513
+ ),
514
+ open ? /* @__PURE__ */ jsxs(
515
+ "div",
516
+ {
517
+ className: "form-dropdown-panel",
518
+ style: {
519
+ position: "absolute",
520
+ top: "100%",
521
+ left: 0,
522
+ minWidth: "100%",
523
+ maxHeight: "220px",
524
+ overflowY: "auto",
525
+ border: "1px solid var(--color-border)",
526
+ borderRadius: "var(--radius-md)",
527
+ boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
528
+ marginTop: "2px"
529
+ },
530
+ role: "listbox",
531
+ children: [
532
+ renderedOptions.length === 0 ? /* @__PURE__ */ jsx("div", { style: { padding: "0.5rem 0.75rem", fontSize: "0.875rem", color: "var(--color-text-muted)" }, children: "Sin opciones" }) : null,
533
+ renderedOptions.map((opt) => {
534
+ const isSelected = opt.value === currentValue;
535
+ return /* @__PURE__ */ jsxs(
536
+ "div",
537
+ {
538
+ role: "option",
539
+ "aria-selected": isSelected,
540
+ onClick: () => {
541
+ onChange(opt.value);
542
+ setOpen(false);
543
+ },
544
+ onMouseEnter: (e) => {
545
+ e.currentTarget.style.background = isSelected ? "var(--color-accent-bg)" : "var(--color-bg-hover)";
551
546
  },
552
- opt.value
553
- );
554
- })
555
- ] }) }) : null
556
- ] })
557
- ]
558
- }
559
- );
547
+ onMouseLeave: (e) => {
548
+ e.currentTarget.style.background = isSelected ? "var(--color-accent-bg)" : "transparent";
549
+ },
550
+ style: {
551
+ display: "flex",
552
+ alignItems: "center",
553
+ gap: "0.5rem",
554
+ padding: "0.4rem 0.75rem",
555
+ cursor: "pointer",
556
+ fontSize: "0.875rem",
557
+ color: isSelected ? "var(--color-text-primary)" : "var(--color-text-secondary)",
558
+ background: isSelected ? "var(--color-accent-bg)" : "transparent",
559
+ userSelect: "none"
560
+ },
561
+ children: [
562
+ isSelected ? /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", style: { fontSize: "0.95rem" }, children: "check" }) : null,
563
+ opt.label
564
+ ]
565
+ },
566
+ opt.value
567
+ );
568
+ })
569
+ ]
570
+ }
571
+ ) : null
572
+ ] });
573
+ }
574
+
575
+ // src/components/forms/EmailInput.tsx
576
+ function EmailInput({
577
+ value,
578
+ onChange,
579
+ placeholder = "correo@ejemplo.com",
580
+ label = "Correo electr\xF3nico",
581
+ hint,
582
+ error,
583
+ disabled,
584
+ required,
585
+ ariaLabel,
586
+ id: idProp,
587
+ leftIcon = "mail",
588
+ className = "form-group",
589
+ inputClassName = "form-input",
590
+ autoComplete = "email",
591
+ "data-testid": dataTestId
592
+ }) {
593
+ const genId = useId();
594
+ const id = idProp ?? genId;
595
+ return /* @__PURE__ */ jsxs("div", { className, children: [
596
+ label ? /* @__PURE__ */ jsxs("label", { className: "form-label-app", htmlFor: id, children: [
597
+ label,
598
+ required ? /* @__PURE__ */ jsxs("span", { className: "required-mark", "aria-hidden": true, children: [
599
+ " ",
600
+ "*"
601
+ ] }) : null
602
+ ] }) : null,
603
+ /* @__PURE__ */ jsxs("div", { className: "input-with-icon", children: [
604
+ /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": true, children: leftIcon }),
605
+ /* @__PURE__ */ jsx(
606
+ "input",
607
+ {
608
+ id,
609
+ "data-testid": dataTestId,
610
+ type: "email",
611
+ value,
612
+ onChange: (e) => onChange(e.target.value),
613
+ placeholder,
614
+ className: inputClassName,
615
+ required,
616
+ disabled,
617
+ autoComplete,
618
+ "aria-label": ariaLabel ?? label ?? "Email"
619
+ }
620
+ )
621
+ ] }),
622
+ 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
623
+ ] });
560
624
  }
561
625
  return __toCommonJS(forms_entry_exports);
562
626
  })();