framepexls-ui-lib 0.1.9 → 0.1.11

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/Button.d.mts CHANGED
@@ -14,7 +14,8 @@ type ButtonProps = {
14
14
  iconOnly?: boolean;
15
15
  icon?: React__default.ReactNode;
16
16
  noPaddingX?: boolean;
17
+ unstyled?: boolean;
17
18
  } & React__default.ButtonHTMLAttributes<HTMLButtonElement>;
18
- declare function Button({ children, variant, size, loading, disabled, leftIcon, rightIcon, block, className, type, active, inverted, iconOnly, icon, noPaddingX, ...rest }: ButtonProps): react_jsx_runtime.JSX.Element;
19
+ declare function Button({ children, variant, size, loading, disabled, leftIcon, rightIcon, block, className, type, active, inverted, iconOnly, icon, noPaddingX, unstyled, ...rest }: ButtonProps): react_jsx_runtime.JSX.Element;
19
20
 
20
21
  export { Button as default };
package/dist/Button.d.ts CHANGED
@@ -14,7 +14,8 @@ type ButtonProps = {
14
14
  iconOnly?: boolean;
15
15
  icon?: React__default.ReactNode;
16
16
  noPaddingX?: boolean;
17
+ unstyled?: boolean;
17
18
  } & React__default.ButtonHTMLAttributes<HTMLButtonElement>;
18
- declare function Button({ children, variant, size, loading, disabled, leftIcon, rightIcon, block, className, type, active, inverted, iconOnly, icon, noPaddingX, ...rest }: ButtonProps): react_jsx_runtime.JSX.Element;
19
+ declare function Button({ children, variant, size, loading, disabled, leftIcon, rightIcon, block, className, type, active, inverted, iconOnly, icon, noPaddingX, unstyled, ...rest }: ButtonProps): react_jsx_runtime.JSX.Element;
19
20
 
20
21
  export { Button as default };
package/dist/Button.js CHANGED
@@ -39,8 +39,22 @@ function Button({
39
39
  iconOnly = false,
40
40
  icon,
41
41
  noPaddingX = false,
42
+ unstyled = false,
42
43
  ...rest
43
44
  }) {
45
+ if (unstyled) {
46
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
47
+ "button",
48
+ {
49
+ type,
50
+ disabled: disabled || loading,
51
+ "aria-pressed": active ? true : void 0,
52
+ className,
53
+ ...rest,
54
+ children
55
+ }
56
+ );
57
+ }
44
58
  const base = "inline-flex items-center justify-center rounded-xl font-medium transition focus:outline-none focus-visible:ring-2 disabled:opacity-60 disabled:cursor-not-allowed";
45
59
  const sizes = size === "sm" ? `h-9 ${noPaddingX ? "" : "px-3"} text-sm` : size === "lg" ? `h-11 ${noPaddingX ? "" : "px-5"} text-base` : `h-10 ${noPaddingX ? "" : "px-4"} text-sm`;
46
60
  const variantClass = (() => {
package/dist/Button.mjs CHANGED
@@ -16,8 +16,22 @@ function Button({
16
16
  iconOnly = false,
17
17
  icon,
18
18
  noPaddingX = false,
19
+ unstyled = false,
19
20
  ...rest
20
21
  }) {
22
+ if (unstyled) {
23
+ return /* @__PURE__ */ jsx(
24
+ "button",
25
+ {
26
+ type,
27
+ disabled: disabled || loading,
28
+ "aria-pressed": active ? true : void 0,
29
+ className,
30
+ ...rest,
31
+ children
32
+ }
33
+ );
34
+ }
21
35
  const base = "inline-flex items-center justify-center rounded-xl font-medium transition focus:outline-none focus-visible:ring-2 disabled:opacity-60 disabled:cursor-not-allowed";
22
36
  const sizes = size === "sm" ? `h-9 ${noPaddingX ? "" : "px-3"} text-sm` : size === "lg" ? `h-11 ${noPaddingX ? "" : "px-5"} text-base` : `h-10 ${noPaddingX ? "" : "px-4"} text-sm`;
23
37
  const variantClass = (() => {
@@ -378,7 +378,6 @@ function ComboSelect({
378
378
  onFocus: () => setOpen(true),
379
379
  onKeyDown,
380
380
  placeholder,
381
- readOnly: !searchable,
382
381
  disabled,
383
382
  autoComplete: "off",
384
383
  spellCheck: false,
@@ -345,7 +345,6 @@ function ComboSelect({
345
345
  onFocus: () => setOpen(true),
346
346
  onKeyDown,
347
347
  placeholder,
348
- readOnly: !searchable,
349
348
  disabled,
350
349
  autoComplete: "off",
351
350
  spellCheck: false,
@@ -88,9 +88,18 @@ const withinBounds = (d, min, max) => {
88
88
  }
89
89
  return true;
90
90
  };
91
+ const toAMPM = (hhmm) => {
92
+ const [hStr, mStr] = hhmm.split(":");
93
+ const H = parseInt(hStr != null ? hStr : "0", 10);
94
+ const M = parseInt(mStr != null ? mStr : "0", 10);
95
+ if (Number.isNaN(H) || Number.isNaN(M)) return hhmm;
96
+ const hr12 = H % 12 === 0 ? 12 : H % 12;
97
+ const mm = M < 10 ? `0${M}` : String(M);
98
+ return `${hr12}:${mm} ${H < 12 ? "AM" : "PM"}`;
99
+ };
91
100
  const formatDisplayValue = (value, mode) => {
92
101
  if (!value) return "";
93
- if (mode === "time") return value;
102
+ if (mode === "time") return toAMPM(value);
94
103
  const parsed = parseValueByType(value, mode);
95
104
  if (!parsed) return value;
96
105
  if (mode === "date") {
@@ -106,7 +115,8 @@ const formatDisplayValue = (value, mode) => {
106
115
  month: "short",
107
116
  year: "numeric",
108
117
  hour: "2-digit",
109
- minute: "2-digit"
118
+ minute: "2-digit",
119
+ hour12: true
110
120
  });
111
121
  } catch {
112
122
  return fmtISODateTimeLocal(parsed);
@@ -258,7 +268,7 @@ function DateTimeField({
258
268
  ),
259
269
  (type === "time" || type === "datetime-local") && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between gap-2 border-t border-slate-100 px-3 py-2 text-sm dark:border-white/10", children: [
260
270
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
261
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
271
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
262
272
  "button",
263
273
  {
264
274
  ref: timeBtnRef,
@@ -268,11 +278,7 @@ function DateTimeField({
268
278
  "aria-haspopup": "dialog",
269
279
  "aria-expanded": showTimePop,
270
280
  title: "Editar hora",
271
- children: [
272
- pad2(hh),
273
- ":",
274
- pad2(mm)
275
- ]
281
+ children: toAMPM(`${pad2(hh)}:${pad2(mm)}`)
276
282
  }
277
283
  ),
278
284
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -361,11 +367,11 @@ function DateTimeField({
361
367
  ]
362
368
  }
363
369
  );
370
+ const isReadOnly = !htmlOnChange;
364
371
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { ref: anchorRef, className: "relative", children: [
365
372
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
366
373
  import_Input.default,
367
374
  {
368
- readOnly: true,
369
375
  leftSlot: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CalendarIcon, {}),
370
376
  rightSlot: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
371
377
  "button",
@@ -386,6 +392,7 @@ function DateTimeField({
386
392
  onClear: () => emit(null),
387
393
  "aria-haspopup": "dialog",
388
394
  "aria-expanded": open,
395
+ readOnly: isReadOnly,
389
396
  ...inputProps
390
397
  }
391
398
  ),
@@ -55,9 +55,18 @@ const withinBounds = (d, min, max) => {
55
55
  }
56
56
  return true;
57
57
  };
58
+ const toAMPM = (hhmm) => {
59
+ const [hStr, mStr] = hhmm.split(":");
60
+ const H = parseInt(hStr != null ? hStr : "0", 10);
61
+ const M = parseInt(mStr != null ? mStr : "0", 10);
62
+ if (Number.isNaN(H) || Number.isNaN(M)) return hhmm;
63
+ const hr12 = H % 12 === 0 ? 12 : H % 12;
64
+ const mm = M < 10 ? `0${M}` : String(M);
65
+ return `${hr12}:${mm} ${H < 12 ? "AM" : "PM"}`;
66
+ };
58
67
  const formatDisplayValue = (value, mode) => {
59
68
  if (!value) return "";
60
- if (mode === "time") return value;
69
+ if (mode === "time") return toAMPM(value);
61
70
  const parsed = parseValueByType(value, mode);
62
71
  if (!parsed) return value;
63
72
  if (mode === "date") {
@@ -73,7 +82,8 @@ const formatDisplayValue = (value, mode) => {
73
82
  month: "short",
74
83
  year: "numeric",
75
84
  hour: "2-digit",
76
- minute: "2-digit"
85
+ minute: "2-digit",
86
+ hour12: true
77
87
  });
78
88
  } catch {
79
89
  return fmtISODateTimeLocal(parsed);
@@ -225,7 +235,7 @@ function DateTimeField({
225
235
  ),
226
236
  (type === "time" || type === "datetime-local") && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-t border-slate-100 px-3 py-2 text-sm dark:border-white/10", children: [
227
237
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
228
- /* @__PURE__ */ jsxs(
238
+ /* @__PURE__ */ jsx(
229
239
  "button",
230
240
  {
231
241
  ref: timeBtnRef,
@@ -235,11 +245,7 @@ function DateTimeField({
235
245
  "aria-haspopup": "dialog",
236
246
  "aria-expanded": showTimePop,
237
247
  title: "Editar hora",
238
- children: [
239
- pad2(hh),
240
- ":",
241
- pad2(mm)
242
- ]
248
+ children: toAMPM(`${pad2(hh)}:${pad2(mm)}`)
243
249
  }
244
250
  ),
245
251
  /* @__PURE__ */ jsx(
@@ -328,11 +334,11 @@ function DateTimeField({
328
334
  ]
329
335
  }
330
336
  );
337
+ const isReadOnly = !htmlOnChange;
331
338
  return /* @__PURE__ */ jsxs("div", { ref: anchorRef, className: "relative", children: [
332
339
  /* @__PURE__ */ jsx(
333
340
  Input,
334
341
  {
335
- readOnly: true,
336
342
  leftSlot: /* @__PURE__ */ jsx(CalendarIcon, {}),
337
343
  rightSlot: /* @__PURE__ */ jsx(
338
344
  "button",
@@ -353,6 +359,7 @@ function DateTimeField({
353
359
  onClear: () => emit(null),
354
360
  "aria-haspopup": "dialog",
355
361
  "aria-expanded": open,
362
+ readOnly: isReadOnly,
356
363
  ...inputProps
357
364
  }
358
365
  ),
package/dist/Input.js CHANGED
@@ -41,7 +41,8 @@ const baseControl = "w-full rounded-2xl border border-slate-200 bg-white px-3.5
41
41
  const errorControl = "border-blue-300 focus:border-blue-300 focus:ring-blue-500/15";
42
42
  const Input = import_react.default.forwardRef(
43
43
  ({ className, error, leftSlot, rightSlot, clearable, onClear, tone = "default", value, onChange, ...props }, ref) => {
44
- const showClear = clearable && !!value && !props.readOnly && !props.disabled;
44
+ const showClear = clearable && !!value && !props.disabled;
45
+ const readOnly = !onChange;
45
46
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative content-center", children: [
46
47
  leftSlot && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-slate-400", children: leftSlot }),
47
48
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -50,6 +51,7 @@ const Input = import_react.default.forwardRef(
50
51
  ref,
51
52
  value,
52
53
  onChange,
54
+ readOnly,
53
55
  "aria-invalid": error ? true : void 0,
54
56
  className: cx(
55
57
  baseControl,
package/dist/Input.mjs CHANGED
@@ -8,7 +8,8 @@ const baseControl = "w-full rounded-2xl border border-slate-200 bg-white px-3.5
8
8
  const errorControl = "border-blue-300 focus:border-blue-300 focus:ring-blue-500/15";
9
9
  const Input = React.forwardRef(
10
10
  ({ className, error, leftSlot, rightSlot, clearable, onClear, tone = "default", value, onChange, ...props }, ref) => {
11
- const showClear = clearable && !!value && !props.readOnly && !props.disabled;
11
+ const showClear = clearable && !!value && !props.disabled;
12
+ const readOnly = !onChange;
12
13
  return /* @__PURE__ */ jsxs("div", { className: "relative content-center", children: [
13
14
  leftSlot && /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-slate-400", children: leftSlot }),
14
15
  /* @__PURE__ */ jsx(
@@ -17,6 +18,7 @@ const Input = React.forwardRef(
17
18
  ref,
18
19
  value,
19
20
  onChange,
21
+ readOnly,
20
22
  "aria-invalid": error ? true : void 0,
21
23
  className: cx(
22
24
  baseControl,
@@ -0,0 +1,23 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ComboOption } from './ComboSelect.mjs';
3
+ import 'react';
4
+
5
+ type MultiComboOption = ComboOption & {
6
+ color?: string | null;
7
+ };
8
+ type MultiComboSelectProps = {
9
+ options: MultiComboOption[];
10
+ value: string[];
11
+ onChange: (next: string[]) => void;
12
+ placeholder?: string;
13
+ disabled?: boolean;
14
+ loading?: boolean;
15
+ className?: string;
16
+ noResultsText?: string;
17
+ maxVisibleTags?: number;
18
+ enableDialog?: boolean;
19
+ dialogTitle?: string;
20
+ };
21
+ declare function MultiComboSelect({ options, value, onChange, placeholder, disabled, loading, className, noResultsText, maxVisibleTags, enableDialog, dialogTitle, }: MultiComboSelectProps): react_jsx_runtime.JSX.Element;
22
+
23
+ export { type MultiComboOption, type MultiComboSelectProps, MultiComboSelect as default };
@@ -0,0 +1,23 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ComboOption } from './ComboSelect.js';
3
+ import 'react';
4
+
5
+ type MultiComboOption = ComboOption & {
6
+ color?: string | null;
7
+ };
8
+ type MultiComboSelectProps = {
9
+ options: MultiComboOption[];
10
+ value: string[];
11
+ onChange: (next: string[]) => void;
12
+ placeholder?: string;
13
+ disabled?: boolean;
14
+ loading?: boolean;
15
+ className?: string;
16
+ noResultsText?: string;
17
+ maxVisibleTags?: number;
18
+ enableDialog?: boolean;
19
+ dialogTitle?: string;
20
+ };
21
+ declare function MultiComboSelect({ options, value, onChange, placeholder, disabled, loading, className, noResultsText, maxVisibleTags, enableDialog, dialogTitle, }: MultiComboSelectProps): react_jsx_runtime.JSX.Element;
22
+
23
+ export { type MultiComboOption, type MultiComboSelectProps, MultiComboSelect as default };
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ "use client";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+ var MultiComboSelect_exports = {};
31
+ __export(MultiComboSelect_exports, {
32
+ default: () => MultiComboSelect
33
+ });
34
+ module.exports = __toCommonJS(MultiComboSelect_exports);
35
+ var import_jsx_runtime = require("react/jsx-runtime");
36
+ var import_react = __toESM(require("react"));
37
+ var import_ComboSelect = __toESM(require("./ComboSelect"));
38
+ var import_Dialog = __toESM(require("./Dialog"));
39
+ var import_Badge = __toESM(require("./Badge"));
40
+ function MultiComboSelect({
41
+ options,
42
+ value,
43
+ onChange,
44
+ placeholder = "Selecciona\u2026",
45
+ disabled,
46
+ loading,
47
+ className,
48
+ noResultsText,
49
+ maxVisibleTags = 3,
50
+ enableDialog = true,
51
+ dialogTitle = "Seleccionadas"
52
+ }) {
53
+ const selectedSet = import_react.default.useMemo(() => new Set(value), [value]);
54
+ const [dialogOpen, setDialogOpen] = import_react.default.useState(false);
55
+ const toggle = import_react.default.useCallback(
56
+ (val) => {
57
+ if (val == null) return;
58
+ const next = new Set(selectedSet);
59
+ if (next.has(val)) next.delete(val);
60
+ else next.add(val);
61
+ onChange(Array.from(next));
62
+ },
63
+ [selectedSet, onChange]
64
+ );
65
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
66
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
67
+ import_ComboSelect.default,
68
+ {
69
+ options,
70
+ value: null,
71
+ onChange: (v) => toggle(v),
72
+ placeholder: value.length === 0 ? placeholder : "",
73
+ disabled,
74
+ loading,
75
+ className,
76
+ noResultsText,
77
+ closeOnSelect: false,
78
+ keepFocusOnSelect: true,
79
+ clearQueryOnSelect: false,
80
+ renderTags: null,
81
+ renderOption: (opt) => {
82
+ var _a;
83
+ const isSel = selectedSet.has(opt.value);
84
+ const color = (_a = opt.color) != null ? _a : void 0;
85
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between gap-3", children: [
86
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "min-w-0 flex items-center gap-3", children: [
87
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "inline-block h-2.5 w-2.5 rounded-full", style: { background: color } }),
88
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "truncate", children: opt.label })
89
+ ] }),
90
+ isSel && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m5 12 4 4L19 6" }) })
91
+ ] });
92
+ }
93
+ }
94
+ ),
95
+ value.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mt-2 flex flex-wrap gap-1.5", children: [
96
+ value.slice(0, Math.max(0, maxVisibleTags)).map((v) => {
97
+ const opt = options.find((o) => o.value === v);
98
+ if (!opt) return null;
99
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Badge.default, { tone: "slate", size: "sm", onClick: () => toggle(v), title: "Quitar", children: opt.label }, v);
100
+ }),
101
+ enableDialog && value.length > maxVisibleTags && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
102
+ "button",
103
+ {
104
+ type: "button",
105
+ className: "inline-flex items-center gap-1 rounded-full border border-slate-200 bg-white px-2 py-0.5 text-xs text-slate-700 hover:bg-slate-50 active:scale-95 dark:border-white/10 dark:bg-white/10 dark:text-slate-200",
106
+ onClick: () => setDialogOpen(true),
107
+ title: "Ver todas",
108
+ children: [
109
+ "+",
110
+ value.length - Math.max(0, maxVisibleTags)
111
+ ]
112
+ }
113
+ )
114
+ ] }),
115
+ enableDialog && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_Dialog.default, { open: dialogOpen, onClose: () => setDialogOpen(false), size: "sm", children: [
116
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Dialog.default.Header, { title: dialogTitle, onClose: () => setDialogOpen(false) }),
117
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Dialog.default.Body, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "grid gap-2", children: [
118
+ value.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-sm text-muted", children: "Sin seleccionadas" }),
119
+ value.map((v) => {
120
+ var _a;
121
+ const opt = options.find((o) => o.value === v);
122
+ if (!opt) return null;
123
+ const color = (_a = opt.color) != null ? _a : void 0;
124
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between gap-3 rounded-xl border border-border bg-surface px-3 py-2 text-sm", children: [
125
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "min-w-0 flex items-center gap-2", children: [
126
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "inline-block h-2.5 w-2.5 rounded-full", style: { background: color } }),
127
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "truncate", children: opt.label })
128
+ ] }),
129
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
130
+ "button",
131
+ {
132
+ type: "button",
133
+ className: "inline-flex h-8 items-center justify-center rounded-lg border border-slate-300/80 bg-white px-2 text-xs text-slate-700 hover:bg-slate-50 active:scale-95 dark:border-white/10 dark:bg-white/5 dark:text-slate-200 dark:hover:bg-white/10",
134
+ onClick: () => toggle(v),
135
+ children: "Quitar"
136
+ }
137
+ )
138
+ ] }, v);
139
+ })
140
+ ] }) }),
141
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Dialog.default.Footer, { align: "end", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
142
+ "button",
143
+ {
144
+ type: "button",
145
+ className: "inline-flex h-10 items-center gap-2 rounded-xl border border-slate-300/80 bg-white px-4 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 dark:border-white/10 dark:bg-white/5 dark:text-slate-200 dark:hover:bg-white/10",
146
+ onClick: () => setDialogOpen(false),
147
+ children: "Cerrar"
148
+ }
149
+ ) })
150
+ ] })
151
+ ] });
152
+ }
@@ -0,0 +1,122 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import React from "react";
4
+ import ComboSelect from "./ComboSelect";
5
+ import Dialog from "./Dialog";
6
+ import Badge from "./Badge";
7
+ function MultiComboSelect({
8
+ options,
9
+ value,
10
+ onChange,
11
+ placeholder = "Selecciona\u2026",
12
+ disabled,
13
+ loading,
14
+ className,
15
+ noResultsText,
16
+ maxVisibleTags = 3,
17
+ enableDialog = true,
18
+ dialogTitle = "Seleccionadas"
19
+ }) {
20
+ const selectedSet = React.useMemo(() => new Set(value), [value]);
21
+ const [dialogOpen, setDialogOpen] = React.useState(false);
22
+ const toggle = React.useCallback(
23
+ (val) => {
24
+ if (val == null) return;
25
+ const next = new Set(selectedSet);
26
+ if (next.has(val)) next.delete(val);
27
+ else next.add(val);
28
+ onChange(Array.from(next));
29
+ },
30
+ [selectedSet, onChange]
31
+ );
32
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
33
+ /* @__PURE__ */ jsx(
34
+ ComboSelect,
35
+ {
36
+ options,
37
+ value: null,
38
+ onChange: (v) => toggle(v),
39
+ placeholder: value.length === 0 ? placeholder : "",
40
+ disabled,
41
+ loading,
42
+ className,
43
+ noResultsText,
44
+ closeOnSelect: false,
45
+ keepFocusOnSelect: true,
46
+ clearQueryOnSelect: false,
47
+ renderTags: null,
48
+ renderOption: (opt) => {
49
+ var _a;
50
+ const isSel = selectedSet.has(opt.value);
51
+ const color = (_a = opt.color) != null ? _a : void 0;
52
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
53
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex items-center gap-3", children: [
54
+ /* @__PURE__ */ jsx("span", { className: "inline-block h-2.5 w-2.5 rounded-full", style: { background: color } }),
55
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: opt.label })
56
+ ] }),
57
+ isSel && /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { d: "m5 12 4 4L19 6" }) })
58
+ ] });
59
+ }
60
+ }
61
+ ),
62
+ value.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap gap-1.5", children: [
63
+ value.slice(0, Math.max(0, maxVisibleTags)).map((v) => {
64
+ const opt = options.find((o) => o.value === v);
65
+ if (!opt) return null;
66
+ return /* @__PURE__ */ jsx(Badge, { tone: "slate", size: "sm", onClick: () => toggle(v), title: "Quitar", children: opt.label }, v);
67
+ }),
68
+ enableDialog && value.length > maxVisibleTags && /* @__PURE__ */ jsxs(
69
+ "button",
70
+ {
71
+ type: "button",
72
+ className: "inline-flex items-center gap-1 rounded-full border border-slate-200 bg-white px-2 py-0.5 text-xs text-slate-700 hover:bg-slate-50 active:scale-95 dark:border-white/10 dark:bg-white/10 dark:text-slate-200",
73
+ onClick: () => setDialogOpen(true),
74
+ title: "Ver todas",
75
+ children: [
76
+ "+",
77
+ value.length - Math.max(0, maxVisibleTags)
78
+ ]
79
+ }
80
+ )
81
+ ] }),
82
+ enableDialog && /* @__PURE__ */ jsxs(Dialog, { open: dialogOpen, onClose: () => setDialogOpen(false), size: "sm", children: [
83
+ /* @__PURE__ */ jsx(Dialog.Header, { title: dialogTitle, onClose: () => setDialogOpen(false) }),
84
+ /* @__PURE__ */ jsx(Dialog.Body, { children: /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
85
+ value.length === 0 && /* @__PURE__ */ jsx("div", { className: "text-sm text-muted", children: "Sin seleccionadas" }),
86
+ value.map((v) => {
87
+ var _a;
88
+ const opt = options.find((o) => o.value === v);
89
+ if (!opt) return null;
90
+ const color = (_a = opt.color) != null ? _a : void 0;
91
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 rounded-xl border border-border bg-surface px-3 py-2 text-sm", children: [
92
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex items-center gap-2", children: [
93
+ /* @__PURE__ */ jsx("span", { className: "inline-block h-2.5 w-2.5 rounded-full", style: { background: color } }),
94
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: opt.label })
95
+ ] }),
96
+ /* @__PURE__ */ jsx(
97
+ "button",
98
+ {
99
+ type: "button",
100
+ className: "inline-flex h-8 items-center justify-center rounded-lg border border-slate-300/80 bg-white px-2 text-xs text-slate-700 hover:bg-slate-50 active:scale-95 dark:border-white/10 dark:bg-white/5 dark:text-slate-200 dark:hover:bg-white/10",
101
+ onClick: () => toggle(v),
102
+ children: "Quitar"
103
+ }
104
+ )
105
+ ] }, v);
106
+ })
107
+ ] }) }),
108
+ /* @__PURE__ */ jsx(Dialog.Footer, { align: "end", children: /* @__PURE__ */ jsx(
109
+ "button",
110
+ {
111
+ type: "button",
112
+ className: "inline-flex h-10 items-center gap-2 rounded-xl border border-slate-300/80 bg-white px-4 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 dark:border-white/10 dark:bg-white/5 dark:text-slate-200 dark:hover:bg-white/10",
113
+ onClick: () => setDialogOpen(false),
114
+ children: "Cerrar"
115
+ }
116
+ ) })
117
+ ] })
118
+ ] });
119
+ }
120
+ export {
121
+ MultiComboSelect as default
122
+ };
@@ -1,7 +1,6 @@
1
1
  import * as React from 'react';
2
2
 
3
3
  type Props = {
4
- /** ancla (el botón/chip que abriste) */
5
4
  anchorEl: HTMLElement | null;
6
5
  hh: number;
7
6
  mm: number;
@@ -1,7 +1,6 @@
1
1
  import * as React from 'react';
2
2
 
3
3
  type Props = {
4
- /** ancla (el botón/chip que abriste) */
5
4
  anchorEl: HTMLElement | null;
6
5
  hh: number;
7
6
  mm: number;
@@ -129,6 +129,14 @@ function TimePopover({
129
129
  className: "w-64 rounded-2xl border border-slate-200 bg-white p-3 shadow-xl dark:border-white/10 dark:bg-[#0e0d0e]",
130
130
  children: [
131
131
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mb-2 text-sm font-medium text-slate-700 dark:text-slate-200", children: "Selecciona hora" }),
132
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mb-2 text-xs text-slate-500 dark:text-slate-300", children: [
133
+ "Vista: ",
134
+ (() => {
135
+ const hr12 = H % 12 === 0 ? 12 : H % 12;
136
+ const mm2 = M < 10 ? `0${M}` : String(M);
137
+ return `${hr12}:${mm2} ${H < 12 ? "AM" : "PM"}`;
138
+ })()
139
+ ] }),
132
140
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
133
141
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col items-stretch gap-2", children: [
134
142
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -94,6 +94,14 @@ function TimePopover({
94
94
  className: "w-64 rounded-2xl border border-slate-200 bg-white p-3 shadow-xl dark:border-white/10 dark:bg-[#0e0d0e]",
95
95
  children: [
96
96
  /* @__PURE__ */ jsx("div", { className: "mb-2 text-sm font-medium text-slate-700 dark:text-slate-200", children: "Selecciona hora" }),
97
+ /* @__PURE__ */ jsxs("div", { className: "mb-2 text-xs text-slate-500 dark:text-slate-300", children: [
98
+ "Vista: ",
99
+ (() => {
100
+ const hr12 = H % 12 === 0 ? 12 : H % 12;
101
+ const mm2 = M < 10 ? `0${M}` : String(M);
102
+ return `${hr12}:${mm2} ${H < 12 ? "AM" : "PM"}`;
103
+ })()
104
+ ] }),
97
105
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
98
106
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-stretch gap-2", children: [
99
107
  /* @__PURE__ */ jsx(
@@ -78,10 +78,15 @@ function TimeRangeField({ value, onValueChange, portal = true, portalId, clearab
78
78
  const el = portalId ? document.getElementById(portalId) : null;
79
79
  setPortalRoot(el != null ? el : document.body);
80
80
  }, [portal, portalId]);
81
+ const toAMPM = (hh, mm) => {
82
+ const hr12 = hh % 12 === 0 ? 12 : hh % 12;
83
+ const mm2 = mm < 10 ? `0${mm}` : String(mm);
84
+ return `${hr12}:${mm2} ${hh < 12 ? "AM" : "PM"}`;
85
+ };
81
86
  const display = (0, import_react.useMemo)(() => {
82
87
  var _a2;
83
- const a = from ? fmtHHmm(from.hh, from.mm) : null;
84
- const b = to ? fmtHHmm(to.hh, to.mm) : null;
88
+ const a = from ? toAMPM(from.hh, from.mm) : null;
89
+ const b = to ? toAMPM(to.hh, to.mm) : null;
85
90
  if (!a && !b) return "";
86
91
  if (a && b) return `${a} \u2013 ${b}`;
87
92
  return (_a2 = a != null ? a : b) != null ? _a2 : "";
@@ -163,7 +168,7 @@ function TimeRangeField({ value, onValueChange, portal = true, portalId, clearab
163
168
  className: "rounded-xl ring-1 ring-slate-200 px-2.5 py-1.5 font-medium tracking-wide hover:bg-slate-50 active:scale-[0.98] dark:ring-white/10 dark:hover:bg-white/10",
164
169
  onClick: () => setShowFromPop((v) => !v),
165
170
  ref: fromBtnRef,
166
- children: from ? fmtHHmm(from.hh, from.mm) : "--:--"
171
+ children: from ? toAMPM(from.hh, from.mm) : "--:--"
167
172
  }
168
173
  ),
169
174
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -189,7 +194,7 @@ function TimeRangeField({ value, onValueChange, portal = true, portalId, clearab
189
194
  className: "rounded-xl ring-1 ring-slate-200 px-2.5 py-1.5 font-medium tracking-wide hover:bg-slate-50 active:scale-[0.98] dark:ring-white/10 dark:hover:bg-white/10",
190
195
  onClick: () => setShowToPop((v) => !v),
191
196
  ref: toBtnRef,
192
- children: to ? fmtHHmm(to.hh, to.mm) : "--:--"
197
+ children: to ? toAMPM(to.hh, to.mm) : "--:--"
193
198
  }
194
199
  ),
195
200
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -213,7 +218,6 @@ function TimeRangeField({ value, onValueChange, portal = true, portalId, clearab
213
218
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
214
219
  import_Input.default,
215
220
  {
216
- readOnly: true,
217
221
  value: display,
218
222
  placeholder,
219
223
  onClick: openPopover,
@@ -45,10 +45,15 @@ function TimeRangeField({ value, onValueChange, portal = true, portalId, clearab
45
45
  const el = portalId ? document.getElementById(portalId) : null;
46
46
  setPortalRoot(el != null ? el : document.body);
47
47
  }, [portal, portalId]);
48
+ const toAMPM = (hh, mm) => {
49
+ const hr12 = hh % 12 === 0 ? 12 : hh % 12;
50
+ const mm2 = mm < 10 ? `0${mm}` : String(mm);
51
+ return `${hr12}:${mm2} ${hh < 12 ? "AM" : "PM"}`;
52
+ };
48
53
  const display = useMemo(() => {
49
54
  var _a2;
50
- const a = from ? fmtHHmm(from.hh, from.mm) : null;
51
- const b = to ? fmtHHmm(to.hh, to.mm) : null;
55
+ const a = from ? toAMPM(from.hh, from.mm) : null;
56
+ const b = to ? toAMPM(to.hh, to.mm) : null;
52
57
  if (!a && !b) return "";
53
58
  if (a && b) return `${a} \u2013 ${b}`;
54
59
  return (_a2 = a != null ? a : b) != null ? _a2 : "";
@@ -130,7 +135,7 @@ function TimeRangeField({ value, onValueChange, portal = true, portalId, clearab
130
135
  className: "rounded-xl ring-1 ring-slate-200 px-2.5 py-1.5 font-medium tracking-wide hover:bg-slate-50 active:scale-[0.98] dark:ring-white/10 dark:hover:bg-white/10",
131
136
  onClick: () => setShowFromPop((v) => !v),
132
137
  ref: fromBtnRef,
133
- children: from ? fmtHHmm(from.hh, from.mm) : "--:--"
138
+ children: from ? toAMPM(from.hh, from.mm) : "--:--"
134
139
  }
135
140
  ),
136
141
  /* @__PURE__ */ jsx(
@@ -156,7 +161,7 @@ function TimeRangeField({ value, onValueChange, portal = true, portalId, clearab
156
161
  className: "rounded-xl ring-1 ring-slate-200 px-2.5 py-1.5 font-medium tracking-wide hover:bg-slate-50 active:scale-[0.98] dark:ring-white/10 dark:hover:bg-white/10",
157
162
  onClick: () => setShowToPop((v) => !v),
158
163
  ref: toBtnRef,
159
- children: to ? fmtHHmm(to.hh, to.mm) : "--:--"
164
+ children: to ? toAMPM(to.hh, to.mm) : "--:--"
160
165
  }
161
166
  ),
162
167
  /* @__PURE__ */ jsx(
@@ -180,7 +185,6 @@ function TimeRangeField({ value, onValueChange, portal = true, portalId, clearab
180
185
  /* @__PURE__ */ jsx(
181
186
  Input,
182
187
  {
183
- readOnly: true,
184
188
  value: display,
185
189
  placeholder,
186
190
  onClick: openPopover,
package/dist/index.d.mts CHANGED
@@ -24,6 +24,7 @@ export { default as AppTopbar } from './AppTopbar.mjs';
24
24
  export { default as OrderButton } from './OrderButton.mjs';
25
25
  export { default as SearchInput } from './SearchInput.mjs';
26
26
  export { default as ReviewHistory, ReviewHistoryDialog } from './ReviewHistory.mjs';
27
+ export { MultiComboOption, default as MultiComboSelect, MultiComboSelectProps } from './MultiComboSelect.mjs';
27
28
  export { default as Sidebar } from './Sidebar.mjs';
28
29
  export { default as CalendarPanel, CalendarPanelProps } from './CalendarPanel.mjs';
29
30
  export { MonthPopover, default as TimePopover, WeekPopover } from './TimePopover.mjs';
package/dist/index.d.ts CHANGED
@@ -24,6 +24,7 @@ export { default as AppTopbar } from './AppTopbar.js';
24
24
  export { default as OrderButton } from './OrderButton.js';
25
25
  export { default as SearchInput } from './SearchInput.js';
26
26
  export { default as ReviewHistory, ReviewHistoryDialog } from './ReviewHistory.js';
27
+ export { MultiComboOption, default as MultiComboSelect, MultiComboSelectProps } from './MultiComboSelect.js';
27
28
  export { default as Sidebar } from './Sidebar.js';
28
29
  export { default as CalendarPanel, CalendarPanelProps } from './CalendarPanel.js';
29
30
  export { MonthPopover, default as TimePopover, WeekPopover } from './TimePopover.js';
package/dist/index.js CHANGED
@@ -49,6 +49,7 @@ __export(index_exports, {
49
49
  Input: () => import_Input.default,
50
50
  Money: () => import_Money.default,
51
51
  MonthPopover: () => import_TimePopover2.MonthPopover,
52
+ MultiComboSelect: () => import_MultiComboSelect.default,
52
53
  OrderButton: () => import_OrderButton.default,
53
54
  Pagination: () => import_Pagination.default,
54
55
  ReviewHistory: () => import_ReviewHistory.default,
@@ -98,6 +99,7 @@ var import_OrderButton = __toESM(require("./OrderButton"));
98
99
  var import_SearchInput = __toESM(require("./SearchInput"));
99
100
  var import_ReviewHistory = __toESM(require("./ReviewHistory"));
100
101
  var import_ReviewHistory2 = require("./ReviewHistory");
102
+ var import_MultiComboSelect = __toESM(require("./MultiComboSelect"));
101
103
  var import_Sidebar = __toESM(require("./Sidebar"));
102
104
  var import_CalendarPanel = __toESM(require("./CalendarPanel"));
103
105
  var import_TimePopover = __toESM(require("./TimePopover"));
@@ -129,6 +131,7 @@ __reExport(index_exports, require("./iconos"), module.exports);
129
131
  Input,
130
132
  Money,
131
133
  MonthPopover,
134
+ MultiComboSelect,
132
135
  OrderButton,
133
136
  Pagination,
134
137
  ReviewHistory,
package/dist/index.mjs CHANGED
@@ -27,13 +27,14 @@ import { default as default24 } from "./OrderButton";
27
27
  import { default as default25 } from "./SearchInput";
28
28
  import { default as default26 } from "./ReviewHistory";
29
29
  import { ReviewHistoryDialog } from "./ReviewHistory";
30
- import { default as default27 } from "./Sidebar";
31
- import { default as default28 } from "./CalendarPanel";
32
- import { default as default29 } from "./TimePopover";
30
+ import { default as default27 } from "./MultiComboSelect";
31
+ import { default as default28 } from "./Sidebar";
32
+ import { default as default29 } from "./CalendarPanel";
33
+ import { default as default30 } from "./TimePopover";
33
34
  import { WeekPopover, MonthPopover } from "./TimePopover";
34
- import { default as default30 } from "./TimePanel";
35
- import { default as default31 } from "./TimeRangeField";
36
- import { default as default32 } from "./Steps";
35
+ import { default as default31 } from "./TimePanel";
36
+ import { default as default32 } from "./TimeRangeField";
37
+ import { default as default33 } from "./Steps";
37
38
  import { StepsNav } from "./Steps";
38
39
  export * from "./iconos";
39
40
  export {
@@ -44,7 +45,7 @@ export {
44
45
  default17 as BadgeCluster,
45
46
  default18 as Breadcrumb,
46
47
  default2 as Button,
47
- default28 as CalendarPanel,
48
+ default29 as CalendarPanel,
48
49
  default14 as ChartCard,
49
50
  default5 as CheckboxPillsGroup,
50
51
  default8 as ColumnSelector,
@@ -57,22 +58,23 @@ export {
57
58
  default4 as Input,
58
59
  default20 as Money,
59
60
  MonthPopover,
61
+ default27 as MultiComboSelect,
60
62
  default24 as OrderButton,
61
63
  default12 as Pagination,
62
64
  default26 as ReviewHistory,
63
65
  ReviewHistoryDialog,
64
66
  default25 as SearchInput,
65
67
  default6 as Select,
66
- default27 as Sidebar,
68
+ default28 as Sidebar,
67
69
  SortTh,
68
70
  default15 as StatCard,
69
- default32 as Steps,
71
+ default33 as Steps,
70
72
  StepsNav,
71
73
  Td,
72
74
  Th,
73
75
  default21 as TimeAgo,
74
- default30 as TimePanel,
75
- default29 as TimePopover,
76
- default31 as TimeRangeField,
76
+ default31 as TimePanel,
77
+ default30 as TimePopover,
78
+ default32 as TimeRangeField,
77
79
  WeekPopover
78
80
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "framepexls-ui-lib",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "description": "Componentes UI de Framepexls para React/Next.",