sigpro-ui 1.0.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.
Files changed (43) hide show
  1. package/Readme.md +146 -0
  2. package/dist/sigpro-ui.cjs +1677 -0
  3. package/dist/sigpro-ui.esm.js +1621 -0
  4. package/dist/sigpro-ui.umd.js +1680 -0
  5. package/dist/sigpro-ui.umd.min.js +1 -0
  6. package/index.js +40 -0
  7. package/package.json +52 -0
  8. package/src/components/Accordion.js +24 -0
  9. package/src/components/Alert.js +50 -0
  10. package/src/components/Autocomplete.js +95 -0
  11. package/src/components/Badge.js +6 -0
  12. package/src/components/Button.js +39 -0
  13. package/src/components/Checkbox.js +21 -0
  14. package/src/components/Colorpicker.js +81 -0
  15. package/src/components/Datepicker.js +252 -0
  16. package/src/components/Drawer.js +18 -0
  17. package/src/components/Dropdown.js +37 -0
  18. package/src/components/Fab.js +51 -0
  19. package/src/components/Fieldset.js +19 -0
  20. package/src/components/Fileinput.js +113 -0
  21. package/src/components/Indicator.js +9 -0
  22. package/src/components/Input.js +77 -0
  23. package/src/components/List.js +18 -0
  24. package/src/components/Loading.js +13 -0
  25. package/src/components/Menu.js +25 -0
  26. package/src/components/Modal.js +31 -0
  27. package/src/components/Navbar.js +6 -0
  28. package/src/components/Radio.js +25 -0
  29. package/src/components/Range.js +24 -0
  30. package/src/components/Rating.js +34 -0
  31. package/src/components/Select.js +36 -0
  32. package/src/components/Stack.js +6 -0
  33. package/src/components/Stat.js +11 -0
  34. package/src/components/Swap.js +13 -0
  35. package/src/components/Table.js +60 -0
  36. package/src/components/Tabs.js +46 -0
  37. package/src/components/Timeline.js +52 -0
  38. package/src/components/Toast.js +63 -0
  39. package/src/components/Tooltip.js +6 -0
  40. package/src/components/index.js +110 -0
  41. package/src/core/i18n.js +26 -0
  42. package/src/core/icons.js +17 -0
  43. package/src/core/utils.js +5 -0
@@ -0,0 +1,252 @@
1
+ import { $, $html, $if } from "sigpro";
2
+ import { val } from "../core/utils.js";
3
+ import {
4
+ iconCalendar,
5
+ iconLeft,
6
+ iconRight,
7
+ iconLLeft,
8
+ iconRRight
9
+ } from "../core/icons.js";
10
+ import { Input } from "./Input.js";
11
+
12
+ /** DATEPICKER */
13
+ export const Datepicker = (props) => {
14
+ const { value, range, label, placeholder, hour = false, ...rest } = props;
15
+
16
+ const isOpen = $(false);
17
+ const internalDate = $(new Date());
18
+ const hoverDate = $(null);
19
+ const startHour = $(0);
20
+ const endHour = $(0);
21
+ const isRangeMode = () => val(range) === true;
22
+
23
+ const now = new Date();
24
+ const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
25
+
26
+ const formatDate = (d) => {
27
+ const year = d.getFullYear();
28
+ const month = String(d.getMonth() + 1).padStart(2, "0");
29
+ const day = String(d.getDate()).padStart(2, "0");
30
+ return `${year}-${month}-${day}`;
31
+ };
32
+
33
+ const selectDate = (date) => {
34
+ const dateStr = formatDate(date);
35
+ const current = val(value);
36
+
37
+ if (isRangeMode()) {
38
+ if (!current?.start || (current.start && current.end)) {
39
+ if (typeof value === "function") {
40
+ value({
41
+ start: dateStr,
42
+ end: null,
43
+ ...(hour && { startHour: startHour() }),
44
+ });
45
+ }
46
+ } else {
47
+ const start = current.start;
48
+ if (typeof value === "function") {
49
+ const newValue = dateStr < start ? { start: dateStr, end: start } : { start, end: dateStr };
50
+ if (hour) {
51
+ newValue.startHour = current.startHour || startHour();
52
+ newValue.endHour = current.endHour || endHour();
53
+ }
54
+ value(newValue);
55
+ }
56
+ isOpen(false);
57
+ }
58
+ } else {
59
+ if (typeof value === "function") {
60
+ value(hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr);
61
+ }
62
+ isOpen(false);
63
+ }
64
+ };
65
+
66
+ const displayValue = $(() => {
67
+ const v = val(value);
68
+ if (!v) return "";
69
+ if (typeof v === "string") {
70
+ if (hour && v.includes("T")) return v.replace("T", " ");
71
+ return v;
72
+ }
73
+ if (v.start && v.end) {
74
+ const startStr = hour && v.startHour ? `${v.start} ${String(v.startHour).padStart(2, "0")}:00` : v.start;
75
+ const endStr = hour && v.endHour ? `${v.end} ${String(v.endHour).padStart(2, "0")}:00` : v.end;
76
+ return `${startStr} - ${endStr}`;
77
+ }
78
+ if (v.start) {
79
+ const startStr = hour && v.startHour ? `${v.start} ${String(v.startHour).padStart(2, "0")}:00` : v.start;
80
+ return `${startStr}...`;
81
+ }
82
+ return "";
83
+ });
84
+
85
+ const move = (m) => {
86
+ const d = internalDate();
87
+ internalDate(new Date(d.getFullYear(), d.getMonth() + m, 1));
88
+ };
89
+
90
+ const moveYear = (y) => {
91
+ const d = internalDate();
92
+ internalDate(new Date(d.getFullYear() + y, d.getMonth(), 1));
93
+ };
94
+
95
+ const HourSlider = ({ value: hVal, onChange }) => {
96
+ return $html("div", { class: "flex-1" }, [
97
+ $html("div", { class: "flex gap-2 items-center" }, [
98
+ $html("input", {
99
+ type: "range",
100
+ min: 0,
101
+ max: 23,
102
+ value: hVal,
103
+ class: "range range-xs flex-1",
104
+ oninput: (e) => {
105
+ const newHour = parseInt(e.target.value);
106
+ onChange(newHour);
107
+ },
108
+ }),
109
+ $html("span", { class: "text-sm font-mono min-w-[48px] text-center" },
110
+ () => String(val(hVal)).padStart(2, "0") + ":00"
111
+ ),
112
+ ]),
113
+ ]);
114
+ };
115
+
116
+ return $html("div", { class: "relative w-full" }, [
117
+ Input({
118
+ label,
119
+ placeholder: placeholder || (isRangeMode() ? "Seleccionar rango..." : "Seleccionar fecha..."),
120
+ value: displayValue,
121
+ readonly: true,
122
+ icon: $html("img", { src: iconCalendar, class: "opacity-40" }),
123
+ onclick: (e) => {
124
+ e.stopPropagation();
125
+ isOpen(!isOpen());
126
+ },
127
+ ...rest,
128
+ }),
129
+
130
+ $if(isOpen, () =>
131
+ $html(
132
+ "div",
133
+ {
134
+ class: "absolute left-0 mt-2 p-4 bg-base-100 border border-base-300 shadow-2xl rounded-box z-[100] w-80 select-none",
135
+ onclick: (e) => e.stopPropagation(),
136
+ },
137
+ [
138
+ $html("div", { class: "flex justify-between items-center mb-4 gap-1" }, [
139
+ $html("div", { class: "flex gap-0.5" }, [
140
+ $html("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(-1) },
141
+ $html("img", { src: iconLLeft, class: "opacity-40" })
142
+ ),
143
+ $html("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(-1) },
144
+ $html("img", { src: iconLeft, class: "opacity-40" })
145
+ ),
146
+ ]),
147
+ $html("span", { class: "font-bold uppercase flex-1 text-center" }, [
148
+ () => internalDate().toLocaleString("es-ES", { month: "short", year: "numeric" }),
149
+ ]),
150
+ $html("div", { class: "flex gap-0.5" }, [
151
+ $html("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(1) },
152
+ $html("img", { src: iconRight, class: "opacity-40" })
153
+ ),
154
+ $html("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(1) },
155
+ $html("img", { src: iconRRight, class: "opacity-40" })
156
+ ),
157
+ ]),
158
+ ]),
159
+
160
+ $html("div", { class: "grid grid-cols-7 gap-1", onmouseleave: () => hoverDate(null) }, [
161
+ ...["L", "M", "X", "J", "V", "S", "D"].map((d) => $html("div", { class: "text-[10px] opacity-40 font-bold text-center" }, d)),
162
+ () => {
163
+ const d = internalDate();
164
+ const year = d.getFullYear();
165
+ const month = d.getMonth();
166
+ const firstDay = new Date(year, month, 1).getDay();
167
+ const offset = firstDay === 0 ? 6 : firstDay - 1;
168
+ const daysInMonth = new Date(year, month + 1, 0).getDate();
169
+
170
+ const nodes = [];
171
+ for (let i = 0; i < offset; i++) nodes.push($html("div"));
172
+
173
+ for (let i = 1; i <= daysInMonth; i++) {
174
+ const date = new Date(year, month, i);
175
+ const dStr = formatDate(date);
176
+
177
+ nodes.push(
178
+ $html(
179
+ "button",
180
+ {
181
+ type: "button",
182
+ class: () => {
183
+ const v = val(value);
184
+ const h = hoverDate();
185
+ const isStart = typeof v === "string" ? v.split("T")[0] === dStr : v?.start === dStr;
186
+ const isEnd = v?.end === dStr;
187
+ let inRange = false;
188
+
189
+ if (isRangeMode() && v?.start) {
190
+ const start = v.start;
191
+ if (!v.end && h) {
192
+ inRange = (dStr > start && dStr <= h) || (dStr < start && dStr >= h);
193
+ } else if (v.end) {
194
+ inRange = dStr > start && dStr < v.end;
195
+ }
196
+ }
197
+
198
+ const base = "btn btn-xs p-0 aspect-square min-h-0 h-auto font-normal relative";
199
+ const state = isStart || isEnd ? "btn-primary z-10" : inRange ? "bg-primary/20 border-none rounded-none" : "btn-ghost";
200
+ const today = dStr === todayStr ? "ring-1 ring-primary ring-inset font-black text-primary" : "";
201
+
202
+ return `${base} ${state} ${today}`;
203
+ },
204
+ onmouseenter: () => { if (isRangeMode()) hoverDate(dStr); },
205
+ onclick: () => selectDate(date),
206
+ },
207
+ [i.toString()],
208
+ ),
209
+ );
210
+ }
211
+ return nodes;
212
+ },
213
+ ]),
214
+
215
+ hour ? $html("div", { class: "mt-3 pt-2 border-t border-base-300" }, [
216
+ isRangeMode()
217
+ ? $html("div", { class: "flex gap-4" }, [
218
+ HourSlider({
219
+ value: startHour,
220
+ onChange: (newHour) => {
221
+ startHour(newHour);
222
+ const currentVal = val(value);
223
+ if (currentVal?.start) value({ ...currentVal, startHour: newHour });
224
+ },
225
+ }),
226
+ HourSlider({
227
+ value: endHour,
228
+ onChange: (newHour) => {
229
+ endHour(newHour);
230
+ const currentVal = val(value);
231
+ if (currentVal?.end) value({ ...currentVal, endHour: newHour });
232
+ },
233
+ }),
234
+ ])
235
+ : HourSlider({
236
+ value: startHour,
237
+ onChange: (newHour) => {
238
+ startHour(newHour);
239
+ const currentVal = val(value);
240
+ if (currentVal && typeof currentVal === "string" && currentVal.includes("-")) {
241
+ value(currentVal.split("T")[0] + "T" + String(newHour).padStart(2, "0") + ":00:00");
242
+ }
243
+ },
244
+ }),
245
+ ]) : null,
246
+ ],
247
+ ),
248
+ ),
249
+
250
+ $if(isOpen, () => $html("div", { class: "fixed inset-0 z-[90]", onclick: () => isOpen(false) })),
251
+ ]);
252
+ };
@@ -0,0 +1,18 @@
1
+ import { $html } from "sigpro";
2
+ import { joinClass } from "../core/utils.js";
3
+
4
+ /** DRAWER */
5
+ export const Drawer = (props) =>
6
+ $html("div", { class: joinClass("drawer", props.class) }, [
7
+ $html("input", {
8
+ id: props.id,
9
+ type: "checkbox",
10
+ class: "drawer-toggle",
11
+ checked: props.open,
12
+ }),
13
+ $html("div", { class: "drawer-content" }, props.content),
14
+ $html("div", { class: "drawer-side" }, [
15
+ $html("label", { for: props.id, class: "drawer-overlay", onclick: () => props.open?.(false) }),
16
+ $html("div", { class: "min-h-full bg-base-200 w-80" }, props.side),
17
+ ]),
18
+ ]);
@@ -0,0 +1,37 @@
1
+ import { $html } from "sigpro";
2
+ import { val } from "../core/utils.js";
3
+
4
+ /** DROPDOWN */
5
+ export const Dropdown = (props, children) => {
6
+ const { label, icon, ...rest } = props;
7
+
8
+ return $html(
9
+ "div",
10
+ {
11
+ ...rest,
12
+ class: () => `dropdown ${val(props.class) || props.class || ""}`,
13
+ },
14
+ [
15
+ $html(
16
+ "div",
17
+ {
18
+ tabindex: 0,
19
+ role: "button",
20
+ class: "btn m-1 flex items-center gap-2",
21
+ },
22
+ [
23
+ icon ? (typeof icon === "function" ? icon() : icon) : null,
24
+ label ? (typeof label === "function" ? label() : label) : null
25
+ ],
26
+ ),
27
+ $html(
28
+ "ul",
29
+ {
30
+ tabindex: 0,
31
+ class: "dropdown-content z-[50] menu p-2 shadow bg-base-100 rounded-box min-w-max border border-base-300",
32
+ },
33
+ [typeof children === "function" ? children() : children],
34
+ ),
35
+ ],
36
+ );
37
+ };
@@ -0,0 +1,51 @@
1
+ import { $html } from "sigpro";
2
+ import { val } from "../core/utils.js";
3
+
4
+ /** FAB (Floating Action Button) */
5
+ export const Fab = (props) => {
6
+ const { icon, label, actions = [], position = "bottom-6 right-6", ...rest } = props;
7
+
8
+ return $html(
9
+ "div",
10
+ {
11
+ ...rest,
12
+ class: () => `fab fixed ${val(position)} flex flex-col-reverse items-end gap-3 z-[100] ${
13
+ props.class || ""
14
+ }`,
15
+ },
16
+ [
17
+ // Botón principal
18
+ $html(
19
+ "div",
20
+ {
21
+ tabindex: 0,
22
+ role: "button",
23
+ class: "btn btn-lg btn-circle btn-primary shadow-2xl",
24
+ },
25
+ [
26
+ icon ? (typeof icon === "function" ? icon() : icon) : null,
27
+ !icon && label ? label : null
28
+ ],
29
+ ),
30
+
31
+ // Acciones secundarias (se despliegan hacia arriba)
32
+ ...val(actions).map((act) =>
33
+ $html("div", { class: "flex items-center gap-3 transition-all duration-300" }, [
34
+ act.label ? $html("span", { class: "badge badge-ghost shadow-sm whitespace-nowrap" }, act.label) : null,
35
+ $html(
36
+ "button",
37
+ {
38
+ type: "button",
39
+ class: `btn btn-circle shadow-lg ${act.class || ""}`,
40
+ onclick: (e) => {
41
+ e.stopPropagation();
42
+ act.onclick?.(e);
43
+ },
44
+ },
45
+ [act.icon ? (typeof act.icon === "function" ? act.icon() : act.icon) : act.text || ""],
46
+ ),
47
+ ]),
48
+ ),
49
+ ],
50
+ );
51
+ };
@@ -0,0 +1,19 @@
1
+ import { $html } from "sigpro";
2
+ import { val, joinClass } from "../core/utils.js";
3
+
4
+ /** FIELDSET */
5
+ export const Fieldset = (props, children) =>
6
+ $html(
7
+ "fieldset",
8
+ {
9
+ ...props,
10
+ class: joinClass("fieldset bg-base-200 border border-base-300 p-4 rounded-lg", props.class),
11
+ },
12
+ [
13
+ () => {
14
+ const legendText = val(props.legend);
15
+ return legendText ? $html("legend", { class: "fieldset-legend font-bold" }, [legendText]) : null;
16
+ },
17
+ children,
18
+ ],
19
+ );
@@ -0,0 +1,113 @@
1
+ import { $, $html, $if, $for } from "sigpro";
2
+ import { iconUpload, iconClose } from "../core/icons.js";
3
+
4
+ /** FILEINPUT */
5
+ export const Fileinput = (props) => {
6
+ const { tooltip, max = 2, accept = "*", onSelect } = props;
7
+
8
+ const selectedFiles = $([]);
9
+ const isDragging = $(false);
10
+ const error = $(null);
11
+ const MAX_BYTES = max * 1024 * 1024;
12
+
13
+ const handleFiles = (files) => {
14
+ const fileList = Array.from(files);
15
+ error(null);
16
+ const oversized = fileList.find((f) => f.size > MAX_BYTES);
17
+
18
+ if (oversized) {
19
+ error(`Máx ${max}MB`);
20
+ return;
21
+ }
22
+
23
+ selectedFiles([...selectedFiles(), ...fileList]);
24
+ onSelect?.(selectedFiles());
25
+ };
26
+
27
+ const removeFile = (index) => {
28
+ const updated = selectedFiles().filter((_, i) => i !== index);
29
+ selectedFiles(updated);
30
+ onSelect?.(updated);
31
+ };
32
+
33
+ return $html("fieldset", { class: "fieldset w-full p-0" }, [
34
+ $html(
35
+ "div",
36
+ {
37
+ class: () => `w-full ${tooltip ? "tooltip tooltip-top before:z-50 after:z-50" : ""}`,
38
+ "data-tip": tooltip,
39
+ },
40
+ [
41
+ $html(
42
+ "label",
43
+ {
44
+ class: () => `
45
+ relative flex items-center justify-between w-full h-12 px-4
46
+ border-2 border-dashed rounded-lg cursor-pointer
47
+ transition-all duration-200
48
+ ${isDragging() ? "border-primary bg-primary/10" : "border-base-content/20 bg-base-100 hover:bg-base-200"}
49
+ `,
50
+ ondragover: (e) => {
51
+ e.preventDefault();
52
+ isDragging(true);
53
+ },
54
+ ondragleave: () => isDragging(false),
55
+ ondrop: (e) => {
56
+ e.preventDefault();
57
+ isDragging(false);
58
+ handleFiles(e.dataTransfer.files);
59
+ },
60
+ },
61
+ [
62
+ $html("div", { class: "flex items-center gap-3 w-full" }, [
63
+ $html("img", { src: iconUpload, class: "w-5 h-5 opacity-50 shrink-0" }),
64
+ $html("span", { class: "text-sm opacity-70 truncate grow text-left" }, "Arrastra o selecciona archivos..."),
65
+ $html("span", { class: "text-[10px] opacity-40 shrink-0" }, `Máx ${max}MB`),
66
+ ]),
67
+ $html("input", {
68
+ type: "file",
69
+ multiple: true,
70
+ accept: accept,
71
+ class: "hidden",
72
+ onchange: (e) => handleFiles(e.target.files),
73
+ }),
74
+ ],
75
+ ),
76
+ ],
77
+ ),
78
+
79
+ () => (error() ? $html("span", { class: "text-[10px] text-error mt-1 px-1 font-medium" }, error()) : null),
80
+
81
+ $if(
82
+ () => selectedFiles().length > 0,
83
+ () =>
84
+ $html("ul", { class: "mt-2 space-y-1" }, [
85
+ $for(
86
+ selectedFiles,
87
+ (file, index) =>
88
+ $html("li", { class: "flex items-center justify-between p-1.5 pl-3 text-xs bg-base-200/50 rounded-md border border-base-300" }, [
89
+ $html("div", { class: "flex items-center gap-2 truncate" }, [
90
+ $html("span", { class: "opacity-50" }, "📄"),
91
+ $html("span", { class: "truncate font-medium max-w-[200px]" }, file.name),
92
+ $html("span", { class: "text-[9px] opacity-40" }, `(${(file.size / 1024).toFixed(0)} KB)`),
93
+ ]),
94
+ $html(
95
+ "button",
96
+ {
97
+ type: "button",
98
+ class: "btn btn-ghost btn-xs btn-circle",
99
+ onclick: (e) => {
100
+ e.preventDefault();
101
+ e.stopPropagation();
102
+ removeFile(index);
103
+ },
104
+ },
105
+ [$html("img", { src: iconClose, class: "w-3 h-3 opacity-70" })],
106
+ ),
107
+ ]),
108
+ (file) => file.name + file.lastModified,
109
+ ),
110
+ ]),
111
+ ),
112
+ ]);
113
+ };
@@ -0,0 +1,9 @@
1
+ import { $html } from "sigpro";
2
+ import { joinClass } from "../core/utils.js";
3
+
4
+ /** INDICATOR */
5
+ export const Indicator = (props, children) =>
6
+ $html("div", { class: joinClass("indicator", props.class) }, [
7
+ children,
8
+ $html("span", { class: joinClass("indicator-item badge", props.badgeClass) }, props.badge),
9
+ ]);
@@ -0,0 +1,77 @@
1
+ import { $, $html } from "sigpro";
2
+ import { val, joinClass } from "../core/utils.js";
3
+ import { tt } from "../core/i18n.js";
4
+ import {
5
+ iconAbc,
6
+ iconLock,
7
+ iconCalendar,
8
+ icon123,
9
+ iconMail,
10
+ iconShow,
11
+ iconHide
12
+ } from "../core/icons.js";
13
+
14
+ /** INPUT */
15
+ export const Input = (props) => {
16
+ const { label, tip, value, error, isSearch, icon, type = "text", ...rest } = props;
17
+ const isPassword = type === "password";
18
+ const visible = $(false);
19
+
20
+ const iconsByType = {
21
+ text: iconAbc,
22
+ password: iconLock,
23
+ date: iconCalendar,
24
+ number: icon123,
25
+ email: iconMail,
26
+ };
27
+
28
+ const inputEl = $html("input", {
29
+ ...rest,
30
+ type: () => (isPassword ? (visible() ? "text" : "password") : type),
31
+ placeholder: props.placeholder || label || (isSearch ? tt("search")() : " "),
32
+ class: joinClass("grow order-2 focus:outline-none", props.class),
33
+ value: value,
34
+ oninput: (e) => props.oninput?.(e),
35
+ disabled: () => val(props.disabled),
36
+ });
37
+
38
+ const leftIcon = icon ? icon : iconsByType[type] ? $html("img", { src: iconsByType[type], class: "opacity-50", alt: type }) : null;
39
+
40
+ return $html(
41
+ "label",
42
+ {
43
+ class: () => joinClass("input input-bordered floating-label flex items-center gap-2 w-full relative", val(error) ? "input-error" : ""),
44
+ },
45
+ [
46
+ leftIcon ? $html("div", { class: "order-1 shrink-0" }, leftIcon) : null,
47
+ label ? $html("span", { class: "text-base-content/60 order-0" }, label) : null,
48
+ inputEl,
49
+ isPassword
50
+ ? $html(
51
+ "button",
52
+ {
53
+ type: "button",
54
+ class: "order-3 btn btn-ghost btn-xs btn-circle opacity-50 hover:opacity-100",
55
+ onclick: (e) => {
56
+ e.preventDefault();
57
+ visible(!visible());
58
+ },
59
+ },
60
+ () =>
61
+ $html("img", {
62
+ class: "w-5 h-5",
63
+ src: visible() ? iconShow : iconHide,
64
+ }),
65
+ )
66
+ : null,
67
+ tip
68
+ ? $html(
69
+ "div",
70
+ { class: "tooltip tooltip-left order-4", "data-tip": tip },
71
+ $html("span", { class: "badge badge-ghost badge-xs cursor-help" }, "?"),
72
+ )
73
+ : null,
74
+ () => (val(error) ? $html("span", { class: "text-error text-[10px] absolute -bottom-5 left-2" }, val(error)) : null),
75
+ ],
76
+ );
77
+ };
@@ -0,0 +1,18 @@
1
+ import { $html, $if, $for } from "sigpro";
2
+ import { joinClass, val } from "../core/utils.js";
3
+
4
+ /** LIST */
5
+ export const List = (props) => {
6
+ const { items, header, render, keyFn, class: className } = props;
7
+
8
+ return $html(
9
+ "ul",
10
+ {
11
+ class: joinClass("list bg-base-100 rounded-box shadow-md", className),
12
+ },
13
+ [
14
+ $if(header, () => $html("li", { class: "p-4 pb-2 text-xs opacity-60 tracking-wide" }, [val(header)])),
15
+ $for(items, (item, index) => $html("li", { class: "list-row" }, [render(item, index)]), keyFn),
16
+ ],
17
+ );
18
+ };
@@ -0,0 +1,13 @@
1
+ import { $html, $if } from "sigpro";
2
+
3
+ /** LOADING (Overlay Component) */
4
+ export const Loading = (props) => {
5
+ // Se espera un signal props.$show para controlar la visibilidad
6
+ return $if(props.$show, () =>
7
+ $html("div", {
8
+ class: "fixed inset-0 z-[100] flex items-center justify-center backdrop-blur-sm bg-base-100/30"
9
+ }, [
10
+ $html("span", { class: "loading loading-spinner loading-lg text-primary" }),
11
+ ]),
12
+ );
13
+ };
@@ -0,0 +1,25 @@
1
+ import { $html, $for } from "sigpro";
2
+ import { val, joinClass } from "../core/utils.js";
3
+
4
+ /** MENU */
5
+ export const Menu = (props) => {
6
+ const renderItems = (items) =>
7
+ $for(
8
+ () => items || [],
9
+ (it) =>
10
+ $html("li", {}, [
11
+ it.children
12
+ ? $html("details", { open: it.open }, [
13
+ $html("summary", {}, [it.icon && $html("span", { class: "mr-2" }, it.icon), it.label]),
14
+ $html("ul", {}, renderItems(it.children)),
15
+ ])
16
+ : $html("a", { class: () => (val(it.active) ? "active" : ""), onclick: it.onclick }, [
17
+ it.icon && $html("span", { class: "mr-2" }, it.icon),
18
+ it.label,
19
+ ]),
20
+ ]),
21
+ (it, i) => it.label || i,
22
+ );
23
+
24
+ return $html("ul", { ...props, class: joinClass("menu bg-base-200 rounded-box", props.class) }, renderItems(props.items));
25
+ };
@@ -0,0 +1,31 @@
1
+ import { $html, $if } from "sigpro";
2
+ import { tt } from "../core/i18n.js";
3
+ import { Button } from "./Button.js";
4
+
5
+ /** MODAL */
6
+ export const Modal = (props, children) => {
7
+ const { title, buttons, open, ...rest } = props;
8
+ const close = () => open(false);
9
+
10
+ return $if(open, () =>
11
+ $html("dialog", { ...rest, class: "modal modal-open" }, [
12
+ $html("div", { class: "modal-box" }, [
13
+ title ? $html("h3", { class: "text-lg font-bold mb-4" }, title) : null,
14
+ typeof children === "function" ? children() : children,
15
+ $html("div", { class: "modal-action flex gap-2" }, [
16
+ ...(Array.isArray(buttons) ? buttons : [buttons]).filter(Boolean),
17
+ Button({ onclick: close }, tt("close")()),
18
+ ]),
19
+ ]),
20
+ $html(
21
+ "form",
22
+ {
23
+ method: "dialog",
24
+ class: "modal-backdrop",
25
+ onclick: (e) => (e.preventDefault(), close()),
26
+ },
27
+ [$html("button", {}, "close")],
28
+ ),
29
+ ]),
30
+ );
31
+ };
@@ -0,0 +1,6 @@
1
+ import { $html } from "sigpro";
2
+ import { joinClass } from "../core/utils.js";
3
+
4
+ /** NAVBAR */
5
+ export const Navbar = (props, children) =>
6
+ $html("div", { ...props, class: joinClass("navbar bg-base-100 shadow-sm px-4", props.class) }, children);