@jsswift/ui 1.0.41
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/css/animation.css +121 -0
- package/dist/css/base.css +621 -0
- package/dist/css/docs.css +216 -0
- package/dist/css/min-ui.css +1 -0
- package/dist/css/responsive.css +3259 -0
- package/dist/css/ui-components.css +8757 -0
- package/dist/css/ui.css +12765 -0
- package/dist/css/ui.min.css +1 -0
- package/dist/fonts/Nunito-Black.ttf +0 -0
- package/dist/fonts/Nunito-BlackItalic.ttf +0 -0
- package/dist/fonts/Nunito-Bold.ttf +0 -0
- package/dist/fonts/Nunito-BoldItalic.ttf +0 -0
- package/dist/fonts/Nunito-ExtraBold.ttf +0 -0
- package/dist/fonts/Nunito-ExtraBoldItalic.ttf +0 -0
- package/dist/fonts/Nunito-ExtraLight.ttf +0 -0
- package/dist/fonts/Nunito-ExtraLightItalic.ttf +0 -0
- package/dist/fonts/Nunito-Italic.ttf +0 -0
- package/dist/fonts/Nunito-Light.ttf +0 -0
- package/dist/fonts/Nunito-LightItalic.ttf +0 -0
- package/dist/fonts/Nunito-Medium.ttf +0 -0
- package/dist/fonts/Nunito-MediumItalic.ttf +0 -0
- package/dist/fonts/Nunito-Regular.ttf +0 -0
- package/dist/fonts/Nunito-SemiBold.ttf +0 -0
- package/dist/fonts/Nunito-SemiBoldItalic.ttf +0 -0
- package/dist/img/logo.svg +131 -0
- package/dist/img/svg/tabler-icons-sprite.svg +39534 -0
- package/dist/min-ui.js +34 -0
- package/dist/ui.js +21027 -0
- package/dist/ui.min.js +34 -0
- package/package.json +40 -0
- package/src/00-bootstrap.js +330 -0
- package/src/01-foundation-helpers.js +1208 -0
- package/src/10-primitives-layout.js +5972 -0
- package/src/20-display-content.js +1014 -0
- package/src/21-list-content.js +341 -0
- package/src/22-banner-content.js +489 -0
- package/src/23-tooltip.js +326 -0
- package/src/31-form-advanced.js +3149 -0
- package/src/40-navigation.js +1454 -0
- package/src/41-tab-panel.js +1090 -0
- package/src/50-feedback.js +1209 -0
- package/src/51-feedback-service.js +166 -0
- package/src/60-shell-app.js +1483 -0
- package/src/61-form-service.js +450 -0
- package/src/70-dialog.js +401 -0
- package/src/71-menu-overlays.js +1442 -0
- package/src/80-data.js +503 -0
- package/src/README.md +159 -0
- package/src/css/README.md +17 -0
- package/src/css/animation.css +121 -0
- package/src/css/base.css +621 -0
- package/src/css/docs.css +216 -0
- package/src/css/responsive.css +3259 -0
- package/src/css/ui-components.css +8757 -0
- package/src/modules.json +176 -0
|
@@ -0,0 +1,1014 @@
|
|
|
1
|
+
UI.Icon = (...args) => {
|
|
2
|
+
let props = {};
|
|
3
|
+
let children = [];
|
|
4
|
+
let name = "home";
|
|
5
|
+
let hasName = false;
|
|
6
|
+
const a = args[0];
|
|
7
|
+
const b = args[1];
|
|
8
|
+
const isPlainObject = (v) => v && typeof v === "object" && !v.nodeType && !Array.isArray(v) && !(v instanceof Function);
|
|
9
|
+
|
|
10
|
+
if (typeof a === "string") {
|
|
11
|
+
name = a;
|
|
12
|
+
hasName = true;
|
|
13
|
+
if (isPlainObject(b)) {
|
|
14
|
+
props = b;
|
|
15
|
+
if (args.length > 2) children = args.slice(2);
|
|
16
|
+
} else if (args.length > 1) {
|
|
17
|
+
children = args.slice(1);
|
|
18
|
+
}
|
|
19
|
+
} else if (isPlainObject(a)) {
|
|
20
|
+
props = a;
|
|
21
|
+
if (a.name != null) {
|
|
22
|
+
name = a.name;
|
|
23
|
+
hasName = true;
|
|
24
|
+
}
|
|
25
|
+
if (args.length > 1) children = args.slice(1);
|
|
26
|
+
} else if (args.length) {
|
|
27
|
+
children = args;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!hasName && children.length) {
|
|
31
|
+
name = children.length === 1 ? children[0] : children;
|
|
32
|
+
}
|
|
33
|
+
applyCommonProps(props);
|
|
34
|
+
|
|
35
|
+
const slots = props.slots || {};
|
|
36
|
+
|
|
37
|
+
const size = uiUnwrap(props.size);
|
|
38
|
+
const color = uiUnwrap(props.color);
|
|
39
|
+
const isFill = String(name).endsWith("-fill");
|
|
40
|
+
const style = { ...(props.style || {}) };
|
|
41
|
+
const sizeClass = uiComputed(props.size, () => {
|
|
42
|
+
const v = uiUnwrap(props.size);
|
|
43
|
+
return (typeof v === "string" && sizeMap[v]) ? v : "";
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const hasVisualSurface = !!(props.color || props.clickable || props.border || props.glossy || props.glow || props.glass || props.shadow || props.outline || props.gradient || props.textGradient || props.lightShadow || props.radius);
|
|
47
|
+
const builtInSpriteIcons = {
|
|
48
|
+
"chevron-down": {
|
|
49
|
+
viewBox: "0 0 24 24",
|
|
50
|
+
paths: [{ d: "M6 9l6 6l6 -6", fill: "none", stroke: "currentColor", "stroke-width": 2, "stroke-linecap": "round", "stroke-linejoin": "round" }]
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const createBuiltInSpriteIcon = (id) => {
|
|
54
|
+
const def = builtInSpriteIcons[id];
|
|
55
|
+
if (!def) return null;
|
|
56
|
+
return _.svg(
|
|
57
|
+
{ width: "100%", height: "100%", viewBox: def.viewBox, fill: "none", "aria-hidden": "true", focusable: "false" },
|
|
58
|
+
...def.paths.map((path) => _.path(path))
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
const wrapIcon = (icon) => {
|
|
62
|
+
const tooltip = uiUnwrap(props.tooltip);
|
|
63
|
+
if (tooltip == null || tooltip === false || !UI.Tooltip) return icon;
|
|
64
|
+
const tooltipProps = isPlainObject(tooltip)
|
|
65
|
+
? { ...tooltip }
|
|
66
|
+
: { text: tooltip };
|
|
67
|
+
if (isPlainObject(props.tooltipProps)) Object.assign(tooltipProps, props.tooltipProps);
|
|
68
|
+
return UI.Tooltip({ ...tooltipProps, target: icon });
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const cls = uiClass([
|
|
72
|
+
"cms-icon",
|
|
73
|
+
"material-icons",
|
|
74
|
+
...(hasVisualSurface ? ["cms-clear-set", "cms-singularity"] : []),
|
|
75
|
+
sizeClass,
|
|
76
|
+
props.class
|
|
77
|
+
]);
|
|
78
|
+
const p = JSswift.omit(props, ["name", "size", "class", "style", "label", "border", "color", "icon", "iconRight", "removable", "onRemove", "dense", "flat", "glossy", "outline", "slots", "spriteUrl", "tooltip", "tooltipProps"]);
|
|
79
|
+
p.class = cls;
|
|
80
|
+
if (Object.keys(style).length) p.style = style;
|
|
81
|
+
|
|
82
|
+
if (typeof name === "function" || (name && typeof name === "object")) {
|
|
83
|
+
const customNode = JSswift.ui.renderSlot(slots, "default", {}, name);
|
|
84
|
+
const content = renderSlotToArray(null, "default", {}, customNode);
|
|
85
|
+
const icon = _.span({ ...p, "data-icon": "custom" }, ...content);
|
|
86
|
+
setPropertyProps(icon, props);
|
|
87
|
+
return wrapIcon(icon);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const nameStr = String(name);
|
|
91
|
+
const useHref = nameStr.includes("#") ? nameStr : "";
|
|
92
|
+
let icon = null;
|
|
93
|
+
if (useHref) {
|
|
94
|
+
const spriteUrl =
|
|
95
|
+
props.spriteUrl ||
|
|
96
|
+
JSswift.config?.iconSpriteUrl ||
|
|
97
|
+
(typeof globalThis !== "undefined" ? globalThis.JSswift_setting?.iconSpriteUrl : null);
|
|
98
|
+
const symbolId = useHref.slice(useHref.indexOf("#") + 1);
|
|
99
|
+
const svg = spriteUrl
|
|
100
|
+
? _.svg(
|
|
101
|
+
{ width: "100%", height: "100%" },
|
|
102
|
+
_.use({ href: spriteUrl + useHref })
|
|
103
|
+
)
|
|
104
|
+
: createBuiltInSpriteIcon(symbolId) || _.svg(
|
|
105
|
+
{ width: "100%", height: "100%" },
|
|
106
|
+
_.use({ href: useHref })
|
|
107
|
+
);
|
|
108
|
+
if (isFill) svg.style.fill = "currentColor";
|
|
109
|
+
icon = _.span({ ...p, "data-icon": nameStr }, svg, ...children);
|
|
110
|
+
} else {
|
|
111
|
+
icon = _.span({ ...p, "data-icon": nameStr }, nameStr, ...children);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (size != null) {
|
|
115
|
+
let v = size;
|
|
116
|
+
if (JSswift.uiSizes.includes(size)) {
|
|
117
|
+
v = `var(--cms-icon-size-${size})`;
|
|
118
|
+
}
|
|
119
|
+
v = typeof v === "number" ? v + "px" : String(v);
|
|
120
|
+
icon.style.setProperty("--set-icon-size", v);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
setPropertyProps(icon, props);
|
|
124
|
+
return wrapIcon(icon);
|
|
125
|
+
};
|
|
126
|
+
if (JSswift.isDev?.()) {
|
|
127
|
+
UI.meta = UI.meta || {};
|
|
128
|
+
UI.meta.Icon = {
|
|
129
|
+
signature: "UI.Icon(name) | UI.Icon(props) | UI.Icon(props, ...children)",
|
|
130
|
+
props: {
|
|
131
|
+
name: "string|Node|Function",
|
|
132
|
+
size: "number|string",
|
|
133
|
+
color: "string",
|
|
134
|
+
shadow: "boolean|string",
|
|
135
|
+
lightShadow: "boolean",
|
|
136
|
+
clickable: "boolean",
|
|
137
|
+
border: "boolean",
|
|
138
|
+
glossy: "boolean",
|
|
139
|
+
glow: "boolean",
|
|
140
|
+
glass: "boolean",
|
|
141
|
+
gradient: "boolean|number",
|
|
142
|
+
outline: "boolean",
|
|
143
|
+
textGradient: "boolean",
|
|
144
|
+
radius: "number|string",
|
|
145
|
+
spriteUrl: "string",
|
|
146
|
+
tooltip: "String|Node|Function|Object",
|
|
147
|
+
tooltipProps: "object",
|
|
148
|
+
slots: "{ default? }",
|
|
149
|
+
class: "string",
|
|
150
|
+
style: "object"
|
|
151
|
+
},
|
|
152
|
+
slots: {
|
|
153
|
+
default: "Custom icon content"
|
|
154
|
+
},
|
|
155
|
+
returns: "HTMLSpanElement",
|
|
156
|
+
description: "Sprite- or text-based icon with configurable size/color."
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// Esempio: JSswift.ui.Icon({ name: "home", size: 18 })
|
|
160
|
+
|
|
161
|
+
UI.Badge = (...args) => {
|
|
162
|
+
const { props, children } = JSswift.uiNormalizeArgs(args);
|
|
163
|
+
const slots = props.slots || {};
|
|
164
|
+
const sizeClass = uiComputed(props.size, () => {
|
|
165
|
+
const v = uiUnwrap(props.size);
|
|
166
|
+
return (typeof v === "string" && sizeMap[v]) ? `cms-size-${v}` : "";
|
|
167
|
+
});
|
|
168
|
+
const cls = uiClass([
|
|
169
|
+
"cms-clear-set",
|
|
170
|
+
"cms-badge",
|
|
171
|
+
"cms-singularity",
|
|
172
|
+
sizeClass,
|
|
173
|
+
uiWhen(props.outline, "outline"),
|
|
174
|
+
props.class
|
|
175
|
+
]);
|
|
176
|
+
const p = JSswift.omit(props, [
|
|
177
|
+
"label",
|
|
178
|
+
"color",
|
|
179
|
+
"outline",
|
|
180
|
+
"size",
|
|
181
|
+
"slots",
|
|
182
|
+
"notification",
|
|
183
|
+
"iconSize",
|
|
184
|
+
"topLeft",
|
|
185
|
+
"topRight",
|
|
186
|
+
"bottomLeft",
|
|
187
|
+
"bottomRight",
|
|
188
|
+
"left",
|
|
189
|
+
"right"
|
|
190
|
+
]);
|
|
191
|
+
p.class = cls;
|
|
192
|
+
|
|
193
|
+
const style = {
|
|
194
|
+
...(props.style || {})
|
|
195
|
+
};
|
|
196
|
+
if (props.size) {
|
|
197
|
+
const size = uiUnwrap(props.size);
|
|
198
|
+
if (!(typeof size === "string" && sizeMap[size])) {
|
|
199
|
+
style.fontSize = toCssSize(props.size);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
p.style = style;
|
|
203
|
+
|
|
204
|
+
const renderNamedSlotToArray = (names, ctx, fallback) => {
|
|
205
|
+
const list = Array.isArray(names) ? names : [names];
|
|
206
|
+
for (const name of list) {
|
|
207
|
+
if (JSswift.ui.getSlot(slots, name) != null) {
|
|
208
|
+
return renderSlotToArray(slots, name, ctx, fallback);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return fallback == null || fallback === false || fallback === ""
|
|
212
|
+
? []
|
|
213
|
+
: renderSlotToArray(null, "default", ctx, fallback);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const resolveIconSize = () => {
|
|
217
|
+
const raw = uiUnwrap(props.iconSize ?? props.size);
|
|
218
|
+
if (typeof raw === "number") return Math.max(10, Math.round(raw * 0.85));
|
|
219
|
+
if (typeof raw === "string") {
|
|
220
|
+
const map = {
|
|
221
|
+
xxs: 10,
|
|
222
|
+
xs: 11,
|
|
223
|
+
sm: 12,
|
|
224
|
+
md: 13,
|
|
225
|
+
lg: 14,
|
|
226
|
+
xl: 16,
|
|
227
|
+
xxl: 18,
|
|
228
|
+
xxxl: 20
|
|
229
|
+
};
|
|
230
|
+
return map[raw] || 14;
|
|
231
|
+
}
|
|
232
|
+
return 14;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const resolveIconFallback = (source, as) => {
|
|
236
|
+
const raw = uiUnwrap(source);
|
|
237
|
+
if (raw == null || raw === false || raw === "") return null;
|
|
238
|
+
if (typeof raw === "string") return UI.Icon({ name: raw, size: resolveIconSize() });
|
|
239
|
+
return JSswift.ui.slot(raw, { as });
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const renderIconAnchor = (slotNames, propKey, position) => _.dynamic(() => {
|
|
243
|
+
const iconFallback = resolveIconFallback(props[propKey], position);
|
|
244
|
+
const nodes = renderNamedSlotToArray(slotNames, { position }, iconFallback);
|
|
245
|
+
if (!nodes.length) return null;
|
|
246
|
+
return _.span({
|
|
247
|
+
class: `cms-badge-anchor cms-badge-anchor-${position}`,
|
|
248
|
+
"data-badge-slot": position
|
|
249
|
+
}, ...nodes);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const content = _.dynamic(() => {
|
|
253
|
+
const labelFallback = uiUnwrap(props.label);
|
|
254
|
+
let labelNodes = renderNamedSlotToArray(["label"], {}, labelFallback);
|
|
255
|
+
if (!labelNodes.length) labelNodes = renderNamedSlotToArray(["default"], {}, children);
|
|
256
|
+
return _.span(
|
|
257
|
+
{ class: "cms-badge-content" },
|
|
258
|
+
_.span({ class: "cms-badge-label" }, ...(labelNodes.length ? labelNodes : [""]))
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const notification = _.dynamic(() => {
|
|
263
|
+
const rawNotification = uiUnwrap(props.notification);
|
|
264
|
+
const fallback = rawNotification == null || rawNotification === false || rawNotification === ""
|
|
265
|
+
? null
|
|
266
|
+
: rawNotification;
|
|
267
|
+
const nodes = renderNamedSlotToArray(["notification"], { notification: rawNotification }, fallback);
|
|
268
|
+
if (!nodes.length) return null;
|
|
269
|
+
return _.span({
|
|
270
|
+
class: "cms-badge-notification",
|
|
271
|
+
"data-badge-slot": "notification",
|
|
272
|
+
"aria-label": typeof rawNotification === "number" ? `${rawNotification} notifications` : null
|
|
273
|
+
}, ...nodes);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const wrap = _.span(
|
|
277
|
+
renderIconAnchor(["left"], "left", "left"),
|
|
278
|
+
renderIconAnchor(["centerLeft", "center-left"], "centerLeft", "center-left"),
|
|
279
|
+
p,
|
|
280
|
+
content,
|
|
281
|
+
renderIconAnchor(["topLeft", "top-left"], "topLeft", "top-left"),
|
|
282
|
+
renderIconAnchor(["topRight", "top-right"], "topRight", "top-right"),
|
|
283
|
+
renderIconAnchor(["bottomLeft", "bottom-left"], "bottomLeft", "bottom-left"),
|
|
284
|
+
renderIconAnchor(["bottomRight", "bottom-right"], "bottomRight", "bottom-right"),
|
|
285
|
+
renderIconAnchor(["centerRight", "center-right"], "centerRight", "center-right"),
|
|
286
|
+
renderIconAnchor(["right"], "right", "right"),
|
|
287
|
+
notification
|
|
288
|
+
);
|
|
289
|
+
// se esiste un props con left o right
|
|
290
|
+
if (props.topLeft || props.centerLeft || props.bottomLeft) {
|
|
291
|
+
wrap.style.setProperty("padding-left", "15px");
|
|
292
|
+
}
|
|
293
|
+
if (props.topRight || props.centerRight || props.bottomRight) {
|
|
294
|
+
wrap.style.setProperty("padding-right", "15px");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
setPropertyProps(wrap, props);
|
|
298
|
+
return wrap;
|
|
299
|
+
};
|
|
300
|
+
if (JSswift.isDev?.()) {
|
|
301
|
+
UI.meta = UI.meta || {};
|
|
302
|
+
UI.meta.Badge = {
|
|
303
|
+
signature: "UI.Badge(...children) | UI.Badge(props, ...children)",
|
|
304
|
+
props: {
|
|
305
|
+
label: "String|Node|Function|Array",
|
|
306
|
+
color: "string",
|
|
307
|
+
size: "string|number",
|
|
308
|
+
outline: "boolean",
|
|
309
|
+
notification: "string|number|Node|Function|[get,set] signal",
|
|
310
|
+
iconSize: "string|number",
|
|
311
|
+
topLeft: "String|Node|Function|Array",
|
|
312
|
+
topRight: "String|Node|Function|Array",
|
|
313
|
+
bottomLeft: "String|Node|Function|Array",
|
|
314
|
+
bottomRight: "String|Node|Function|Array",
|
|
315
|
+
centerLeft: "String|Node|Function|Array",
|
|
316
|
+
centerRight: "String|Node|Function|Array",
|
|
317
|
+
left: "String|Node|Function|Array",
|
|
318
|
+
right: "String|Node|Function|Array",
|
|
319
|
+
slots: "{ label?, default?, notification?, topLeft?, topRight?, bottomLeft?, bottomRight?, left?, right? }",
|
|
320
|
+
class: "string",
|
|
321
|
+
style: "object"
|
|
322
|
+
},
|
|
323
|
+
slots: {
|
|
324
|
+
label: "Badge label content",
|
|
325
|
+
default: "Fallback content",
|
|
326
|
+
notification: "Notification badge content",
|
|
327
|
+
topLeft: "Icon anchored top-left",
|
|
328
|
+
topRight: "Icon anchored top-right",
|
|
329
|
+
bottomLeft: "Icon anchored bottom-left",
|
|
330
|
+
bottomRight: "Icon anchored bottom-right",
|
|
331
|
+
centerLeft: "Icon anchored center-left",
|
|
332
|
+
centerRight: "Icon anchored center-right",
|
|
333
|
+
left: "Icon anchored left",
|
|
334
|
+
right: "Icon anchored right"
|
|
335
|
+
},
|
|
336
|
+
returns: "HTMLSpanElement",
|
|
337
|
+
description: "Inline badge with reactive notification and 6 positionable icon slots."
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
// Esempio: JSswift.ui.Badge({ label: "New" })
|
|
341
|
+
|
|
342
|
+
const avatarGetInitials = (value) => {
|
|
343
|
+
if (value == null || value === false) return "";
|
|
344
|
+
const text = String(value)
|
|
345
|
+
.replace(/[._-]+/g, " ")
|
|
346
|
+
.replace(/\s+/g, " ")
|
|
347
|
+
.trim();
|
|
348
|
+
if (!text) return "";
|
|
349
|
+
const words = text.split(" ").filter(Boolean);
|
|
350
|
+
if (!words.length) return "";
|
|
351
|
+
if (words.length === 1) {
|
|
352
|
+
const token = words[0].replace(/[^a-z0-9]/gi, "");
|
|
353
|
+
return token.slice(0, 2).toUpperCase();
|
|
354
|
+
}
|
|
355
|
+
return words.slice(0, 2).map((part) => part.charAt(0).toUpperCase()).join("");
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const avatarStatusState = (value, fallback) => {
|
|
359
|
+
const raw = value == null || value === true ? fallback : value;
|
|
360
|
+
if (raw == null || raw === false) return normalizeState(fallback);
|
|
361
|
+
const token = String(raw).toLowerCase();
|
|
362
|
+
if (token === "online" || token === "available" || token === "active") return "success";
|
|
363
|
+
if (token === "away" || token === "pending") return "warning";
|
|
364
|
+
if (token === "busy" || token === "dnd" || token === "blocked") return "danger";
|
|
365
|
+
if (token === "offline" || token === "idle") return "secondary";
|
|
366
|
+
return normalizeState(token) || normalizeState(fallback);
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const avatarPrimitiveBadge = (value, className = "cms-avatar-badge") => {
|
|
370
|
+
if (value == null || value === false) return null;
|
|
371
|
+
if (value instanceof Node || Array.isArray(value)) return value;
|
|
372
|
+
if (typeof value === "function") return value;
|
|
373
|
+
if (value === true) return _.span({ class: className });
|
|
374
|
+
return _.span({ class: className }, String(value));
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
const avatarPrimitiveStatus = (value, color) => {
|
|
378
|
+
if (value == null || value === false) {
|
|
379
|
+
if (!color) return null;
|
|
380
|
+
const fallbackState = avatarStatusState(null, color);
|
|
381
|
+
return _.span({ class: uiClass(["cms-avatar-status", fallbackState ? `cms-state-${fallbackState}` : ""]) });
|
|
382
|
+
}
|
|
383
|
+
if (value instanceof Node || Array.isArray(value)) return value;
|
|
384
|
+
if (typeof value === "function") return value;
|
|
385
|
+
const state = avatarStatusState(value, color);
|
|
386
|
+
if (value === true || state) {
|
|
387
|
+
return _.span({ class: uiClass(["cms-avatar-status", state ? `cms-state-${state}` : ""]) });
|
|
388
|
+
}
|
|
389
|
+
return avatarPrimitiveBadge(value, "cms-avatar-badge");
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const avatarRenderAnchor = (className, nodes) => {
|
|
393
|
+
if (!nodes || !nodes.length) return null;
|
|
394
|
+
return _.span({ class: uiClass(["cms-avatar-anchor", className]) }, ...nodes);
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
UI.Avatar = (...args) => {
|
|
398
|
+
const { props, children } = JSswift.uiNormalizeArgs(args);
|
|
399
|
+
const slots = props.slots || {};
|
|
400
|
+
const sizeClass = uiComputed(props.size, () => {
|
|
401
|
+
const v = uiUnwrap(props.size);
|
|
402
|
+
return (typeof v === "string" && JSswift.uiSizes?.includes(v)) ? `cms-size-${v}` : "";
|
|
403
|
+
});
|
|
404
|
+
const stateClass = uiComputed([props.color, props.state], () => {
|
|
405
|
+
const v = uiUnwrap(props.color) || uiUnwrap(props.state) || "";
|
|
406
|
+
const state = normalizeState(v);
|
|
407
|
+
return state ? `cms-state-${state}` : "";
|
|
408
|
+
});
|
|
409
|
+
const cls = uiClass([
|
|
410
|
+
"cms-clear-set",
|
|
411
|
+
"cms-avatar",
|
|
412
|
+
"cms-singularity",
|
|
413
|
+
sizeClass,
|
|
414
|
+
stateClass,
|
|
415
|
+
uiWhen(props.square, "cms-avatar-square"),
|
|
416
|
+
uiWhen(props.elevated, "elevated"),
|
|
417
|
+
props.class
|
|
418
|
+
]);
|
|
419
|
+
|
|
420
|
+
const p = JSswift.omit(props, [
|
|
421
|
+
"src", "srcset", "srcSet", "sizes", "alt", "label", "name", "initials", "text",
|
|
422
|
+
"size", "fontSize", "textSize", "radius", "rounded", "square", "elevated",
|
|
423
|
+
"icon", "iconSize", "media", "fit", "badge", "notification", "status",
|
|
424
|
+
"statusColor", "presence", "topLeft", "topRight", "bottomLeft", "bottomRight",
|
|
425
|
+
"slots"
|
|
426
|
+
]);
|
|
427
|
+
p.class = cls;
|
|
428
|
+
|
|
429
|
+
const style = { ...(props.style || {}) };
|
|
430
|
+
const sizeStyle = uiStyleValue(props.size, (value) => {
|
|
431
|
+
if (typeof value === "string" && JSswift.uiSizes?.includes(value)) return "";
|
|
432
|
+
return toCssSize(value);
|
|
433
|
+
}, "");
|
|
434
|
+
if (sizeStyle != null) {
|
|
435
|
+
style.width = sizeStyle;
|
|
436
|
+
style.height = sizeStyle;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const fontSizeValue = props.fontSize ?? props.textSize;
|
|
440
|
+
const fontSizeStyle = fontSizeValue != null
|
|
441
|
+
? uiStyleValue(fontSizeValue, toCssSize)
|
|
442
|
+
: uiStyleValue(props.size, (value) => {
|
|
443
|
+
if (typeof value === "string" && JSswift.uiSizes?.includes(value)) return "";
|
|
444
|
+
return `clamp(11px, calc(${toCssSize(value)} * 0.34), 28px)`;
|
|
445
|
+
}, "");
|
|
446
|
+
if (fontSizeStyle != null) style.fontSize = fontSizeStyle;
|
|
447
|
+
|
|
448
|
+
const radiusStyle = uiStyleValue(props.radius, (value) => {
|
|
449
|
+
const normalized = normalizeRadius(value);
|
|
450
|
+
if (normalized === "xxxl") return "999px";
|
|
451
|
+
if (normalized) return `var(--cms-r-${normalized})`;
|
|
452
|
+
if (typeof value === "number") return `${value}px`;
|
|
453
|
+
return String(value);
|
|
454
|
+
});
|
|
455
|
+
if (radiusStyle != null) style["--set-border-radius"] = radiusStyle;
|
|
456
|
+
p.style = style;
|
|
457
|
+
|
|
458
|
+
const ctx = { props };
|
|
459
|
+
const altText = uiComputed([props.alt, props.label, props.name, props.text], () => {
|
|
460
|
+
const raw = uiUnwrap(props.alt) || uiUnwrap(props.label) || uiUnwrap(props.name) || uiUnwrap(props.text) || "avatar";
|
|
461
|
+
return String(raw);
|
|
462
|
+
});
|
|
463
|
+
const iconFallback = props.icon != null
|
|
464
|
+
? (typeof props.icon === "string"
|
|
465
|
+
? UI.Icon({ name: props.icon, size: props.iconSize || props.size || "sm" })
|
|
466
|
+
: JSswift.ui.slot(props.icon, { as: "icon" }))
|
|
467
|
+
: null;
|
|
468
|
+
const labelFallback = uiComputed([props.initials, props.text, props.label, props.name], () => {
|
|
469
|
+
const explicit = uiUnwrap(props.initials) ?? uiUnwrap(props.text);
|
|
470
|
+
if (explicit != null) return explicit;
|
|
471
|
+
const rawLabel = uiUnwrap(props.label) ?? uiUnwrap(props.name);
|
|
472
|
+
if (typeof rawLabel === "string") return avatarGetInitials(rawLabel);
|
|
473
|
+
return rawLabel;
|
|
474
|
+
});
|
|
475
|
+
const badgeFallback = avatarPrimitiveBadge(uiUnwrap(props.badge ?? props.notification));
|
|
476
|
+
const statusFallback = avatarPrimitiveStatus(
|
|
477
|
+
uiUnwrap(props.status ?? props.presence),
|
|
478
|
+
uiUnwrap(props.statusColor)
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
let mediaNodes = renderSlotToArray(slots, "media", ctx, props.media);
|
|
482
|
+
if (!mediaNodes.length && props.src) {
|
|
483
|
+
const imgProps = {
|
|
484
|
+
class: "cms-avatar-img",
|
|
485
|
+
src: props.src,
|
|
486
|
+
alt: altText,
|
|
487
|
+
sizes: props.sizes,
|
|
488
|
+
style: {}
|
|
489
|
+
};
|
|
490
|
+
if (props.srcset != null) imgProps.srcset = props.srcset;
|
|
491
|
+
if (props.srcSet != null) imgProps.srcset = props.srcSet;
|
|
492
|
+
const fitStyle = uiStyleValue(props.fit);
|
|
493
|
+
if (fitStyle != null) imgProps.style.objectFit = fitStyle;
|
|
494
|
+
mediaNodes = [_.img(imgProps)];
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const defaultNodes = renderSlotToArray(slots, "default", ctx, children);
|
|
498
|
+
const fallbackNodes = renderSlotToArray(slots, "fallback", ctx, null);
|
|
499
|
+
const labelNodes = renderSlotToArray(slots, "label", ctx, labelFallback);
|
|
500
|
+
const iconNodes = renderSlotToArray(slots, "icon", ctx, iconFallback);
|
|
501
|
+
const mainNodes = mediaNodes.length
|
|
502
|
+
? mediaNodes
|
|
503
|
+
: (defaultNodes.length
|
|
504
|
+
? defaultNodes
|
|
505
|
+
: (fallbackNodes.length
|
|
506
|
+
? fallbackNodes
|
|
507
|
+
: (labelNodes.length
|
|
508
|
+
? labelNodes
|
|
509
|
+
: (iconNodes.length ? iconNodes : ["?"]))));
|
|
510
|
+
|
|
511
|
+
const bodyClass = mediaNodes.length ? "cms-avatar-media" : "cms-avatar-fallback";
|
|
512
|
+
const body = _.span({ class: "cms-avatar-body" },
|
|
513
|
+
_.span({ class: bodyClass }, ...mainNodes)
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
let topLeftNodes = renderSlotToArray(slots, "topLeft", ctx, props.topLeft);
|
|
517
|
+
let topRightNodes = renderSlotToArray(slots, "topRight", ctx, props.topRight);
|
|
518
|
+
let bottomLeftNodes = renderSlotToArray(slots, "bottomLeft", ctx, props.bottomLeft);
|
|
519
|
+
let bottomRightNodes = renderSlotToArray(slots, "bottomRight", ctx, props.bottomRight);
|
|
520
|
+
const badgeNodes = renderSlotToArray(slots, "badge", ctx, badgeFallback);
|
|
521
|
+
const statusNodes = renderSlotToArray(slots, "status", ctx, statusFallback);
|
|
522
|
+
|
|
523
|
+
if (!topRightNodes.length && badgeNodes.length) topRightNodes = badgeNodes;
|
|
524
|
+
if (!bottomRightNodes.length && statusNodes.length) bottomRightNodes = statusNodes;
|
|
525
|
+
|
|
526
|
+
const wrap = _.div(
|
|
527
|
+
p,
|
|
528
|
+
body,
|
|
529
|
+
avatarRenderAnchor("cms-avatar-anchor-top-left", topLeftNodes),
|
|
530
|
+
avatarRenderAnchor("cms-avatar-anchor-top-right", topRightNodes),
|
|
531
|
+
avatarRenderAnchor("cms-avatar-anchor-bottom-left", bottomLeftNodes),
|
|
532
|
+
avatarRenderAnchor("cms-avatar-anchor-bottom-right", bottomRightNodes)
|
|
533
|
+
);
|
|
534
|
+
setPropertyProps(wrap, props);
|
|
535
|
+
return wrap;
|
|
536
|
+
};
|
|
537
|
+
if (JSswift.isDev?.()) {
|
|
538
|
+
UI.meta = UI.meta || {};
|
|
539
|
+
UI.meta.Avatar = {
|
|
540
|
+
signature: "UI.Avatar(...children) | UI.Avatar(props, ...children)",
|
|
541
|
+
props: {
|
|
542
|
+
src: "string",
|
|
543
|
+
srcset: "string",
|
|
544
|
+
sizes: "string",
|
|
545
|
+
alt: "string",
|
|
546
|
+
label: "String|Node|Function|Array",
|
|
547
|
+
name: "string",
|
|
548
|
+
initials: "string|Node|Function|Array",
|
|
549
|
+
text: "string|Node|Function|Array",
|
|
550
|
+
size: "number|string",
|
|
551
|
+
fontSize: "number|string",
|
|
552
|
+
radius: "number|string",
|
|
553
|
+
square: "boolean",
|
|
554
|
+
elevated: "boolean",
|
|
555
|
+
color: "string",
|
|
556
|
+
state: "string",
|
|
557
|
+
icon: "string|Node|Function|Array",
|
|
558
|
+
iconSize: "number|string",
|
|
559
|
+
media: "Node|Function|Array",
|
|
560
|
+
fit: "cover|contain|fill|scale-down|none",
|
|
561
|
+
badge: "string|number|Node|Function|Array",
|
|
562
|
+
notification: "string|number|Node|Function|Array",
|
|
563
|
+
status: "boolean|string|number|Node|Function|Array",
|
|
564
|
+
statusColor: "success|warning|danger|info|primary|secondary|dark|light|string",
|
|
565
|
+
topLeft: "Node|Function|Array",
|
|
566
|
+
topRight: "Node|Function|Array",
|
|
567
|
+
bottomLeft: "Node|Function|Array",
|
|
568
|
+
bottomRight: "Node|Function|Array",
|
|
569
|
+
slots: "{ media?, default?, fallback?, label?, icon?, badge?, status?, topLeft?, topRight?, bottomLeft?, bottomRight? }",
|
|
570
|
+
mobile: "{ width?, height?, minWidth?, maxWidth?, padding?, margin?, radius? }",
|
|
571
|
+
tablet: "{ width?, height?, minWidth?, maxWidth?, padding?, margin?, radius? }",
|
|
572
|
+
pc: "{ width?, height?, minWidth?, maxWidth?, padding?, margin?, radius? }",
|
|
573
|
+
class: "string",
|
|
574
|
+
style: "object"
|
|
575
|
+
},
|
|
576
|
+
slots: {
|
|
577
|
+
media: "Custom main media",
|
|
578
|
+
default: "Custom main content instead of image or fallback",
|
|
579
|
+
fallback: "Fallback custom quando non c'e immagine",
|
|
580
|
+
label: "Fallback testuale / initials",
|
|
581
|
+
icon: "Fallback icon",
|
|
582
|
+
badge: "Badge overlay, di default top-right",
|
|
583
|
+
status: "Presence dot or overlay content, bottom-right by default",
|
|
584
|
+
topLeft: "Top-left overlay content",
|
|
585
|
+
topRight: "Top-right overlay content",
|
|
586
|
+
bottomLeft: "Bottom-left overlay content",
|
|
587
|
+
bottomRight: "Bottom-right overlay content"
|
|
588
|
+
},
|
|
589
|
+
returns: "HTMLDivElement",
|
|
590
|
+
description: "Flexible avatar with image, smart fallbacks, states, badge, and overlay slots."
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
// Esempio: JSswift.ui.Avatar({ label: "CM" })
|
|
594
|
+
|
|
595
|
+
UI.Chip = (...args) => {
|
|
596
|
+
const { props, children } = JSswift.uiNormalizeArgs(args);
|
|
597
|
+
const slots = props.slots || {};
|
|
598
|
+
const sizeClass = uiComputed(props.size, () => {
|
|
599
|
+
const v = uiUnwrap(props.size);
|
|
600
|
+
return (typeof v === "string" && sizeMap[v]) ? v : "";
|
|
601
|
+
});
|
|
602
|
+
const cls = uiClass([
|
|
603
|
+
"cms-clear-set",
|
|
604
|
+
"cms-chip",
|
|
605
|
+
"cms-singularity",
|
|
606
|
+
sizeClass,
|
|
607
|
+
props.class
|
|
608
|
+
]);
|
|
609
|
+
const p = JSswift.omit(props, ["label", "border", "color", "icon", "iconRight", "removable", "onRemove", "dense", "flat", "glossy", "outline", "slots"]);
|
|
610
|
+
p.class = cls;
|
|
611
|
+
p.style = {
|
|
612
|
+
...(props.style || {})
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
const iconFallback = props.icon
|
|
616
|
+
? (typeof props.icon === "string" ? UI.Icon({ name: props.icon, size: props?.size ?? null }) : JSswift.ui.slot(props.icon, { as: "icon" }))
|
|
617
|
+
: null;
|
|
618
|
+
const iconRightFallback = props.iconRight
|
|
619
|
+
? (typeof props.iconRight === "string" ? UI.Icon({ name: props.iconRight, size: props?.size ?? null }) : JSswift.ui.slot(props.iconRight, { as: "iconRight" }))
|
|
620
|
+
: null;
|
|
621
|
+
const iconNode = JSswift.ui.renderSlot(slots, "icon", {}, iconFallback);
|
|
622
|
+
const iconRightNode = JSswift.ui.renderSlot(slots, "iconRight", {}, iconRightFallback);
|
|
623
|
+
const labelNodes = renderSlotToArray(slots, "label", {}, props.label);
|
|
624
|
+
const labelNode = labelNodes.length ? labelNodes : renderSlotToArray(slots, "default", {}, children);
|
|
625
|
+
|
|
626
|
+
const iconNodes = renderSlotToArray(null, "default", {}, iconNode);
|
|
627
|
+
const iconRightNodes = renderSlotToArray(null, "default", {}, iconRightNode);
|
|
628
|
+
const wrap = _.span(p, ...iconNodes, ...(labelNode.length ? labelNode : [""]), ...iconRightNodes);
|
|
629
|
+
if (props.removable) {
|
|
630
|
+
const btn = UI.Btn({ class: "cms-chip-remove", onClick: props.onRemove, size: props?.size ?? null }, UI.Icon({ size: props?.size ?? null, name: "close" }));
|
|
631
|
+
wrap.appendChild(btn);
|
|
632
|
+
btn.onclick = () => {
|
|
633
|
+
if (props.onRemove) {
|
|
634
|
+
props.onRemove();
|
|
635
|
+
}
|
|
636
|
+
wrap.remove();
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
setPropertyProps(wrap, props);
|
|
640
|
+
return wrap;
|
|
641
|
+
};
|
|
642
|
+
if (JSswift.isDev?.()) {
|
|
643
|
+
UI.meta = UI.meta || {};
|
|
644
|
+
UI.meta.Chip = {
|
|
645
|
+
signature: "UI.Chip(...children) | UI.Chip(props, ...children)",
|
|
646
|
+
props: {
|
|
647
|
+
label: "String|Node|Function|Array",
|
|
648
|
+
icon: "String|Node|Function|Array",
|
|
649
|
+
iconRight: "String|Node|Function|Array",
|
|
650
|
+
removable: "boolean",
|
|
651
|
+
onRemove: "function",
|
|
652
|
+
dense: "boolean",
|
|
653
|
+
outline: "boolean|string|number",
|
|
654
|
+
slots: "{ icon?, label?, default? }",
|
|
655
|
+
class: "string",
|
|
656
|
+
style: "object"
|
|
657
|
+
},
|
|
658
|
+
slots: {
|
|
659
|
+
icon: "Chip icon content",
|
|
660
|
+
iconRight: "Chip right icon content",
|
|
661
|
+
label: "Chip label content",
|
|
662
|
+
default: "Fallback content"
|
|
663
|
+
},
|
|
664
|
+
events: {
|
|
665
|
+
onRemove: "MouseEvent"
|
|
666
|
+
},
|
|
667
|
+
returns: "HTMLSpanElement",
|
|
668
|
+
description: "Chip with optional icon and removal."
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
// Esempio: JSswift.ui.Chip({ label: "Tag", removable: true })
|
|
672
|
+
|
|
673
|
+
UI.Stat = (...args) => {
|
|
674
|
+
const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
|
|
675
|
+
const slots = rawProps.slots || {};
|
|
676
|
+
const resolveStateValue = () => normalizeState(uiUnwrap(rawProps.state) || uiUnwrap(rawProps.color) || "");
|
|
677
|
+
const stateClass = uiComputed([rawProps.state, rawProps.color], () => {
|
|
678
|
+
const state = resolveStateValue();
|
|
679
|
+
return state ? `cms-state-${state}` : "";
|
|
680
|
+
});
|
|
681
|
+
const trendClass = uiComputed(rawProps.trend, () => {
|
|
682
|
+
const raw = String(uiUnwrap(rawProps.trend) || "").toLowerCase();
|
|
683
|
+
if (["up", "positive", "success", "gain", "increase"].includes(raw)) return "is-positive";
|
|
684
|
+
if (["down", "negative", "danger", "loss", "decrease"].includes(raw)) return "is-negative";
|
|
685
|
+
if (["flat", "neutral", "steady", "stable"].includes(raw)) return "is-neutral";
|
|
686
|
+
return "";
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
const props = { ...rawProps };
|
|
690
|
+
const p = JSswift.omit(props, [
|
|
691
|
+
"actions",
|
|
692
|
+
"aside",
|
|
693
|
+
"delta",
|
|
694
|
+
"eyebrow",
|
|
695
|
+
"footer",
|
|
696
|
+
"icon",
|
|
697
|
+
"iconSize",
|
|
698
|
+
"label",
|
|
699
|
+
"meta",
|
|
700
|
+
"note",
|
|
701
|
+
"slots",
|
|
702
|
+
"state",
|
|
703
|
+
"subtitle",
|
|
704
|
+
"title",
|
|
705
|
+
"trend",
|
|
706
|
+
"value"
|
|
707
|
+
]);
|
|
708
|
+
p.class = uiClass([
|
|
709
|
+
"cms-clear-set",
|
|
710
|
+
"cms-stat",
|
|
711
|
+
"cms-singularity",
|
|
712
|
+
stateClass,
|
|
713
|
+
trendClass,
|
|
714
|
+
props.class
|
|
715
|
+
]);
|
|
716
|
+
p.style = { ...(props.style || {}) };
|
|
717
|
+
|
|
718
|
+
const renderNamed = (slotName, fallback, ctx = {}) => renderSlotToArray(
|
|
719
|
+
slots,
|
|
720
|
+
slotName,
|
|
721
|
+
ctx,
|
|
722
|
+
fallback
|
|
723
|
+
);
|
|
724
|
+
|
|
725
|
+
const iconFallback = rawProps.icon == null
|
|
726
|
+
? null
|
|
727
|
+
: (typeof rawProps.icon === "string"
|
|
728
|
+
? UI.Icon({ name: rawProps.icon, size: rawProps.iconSize || "md" })
|
|
729
|
+
: JSswift.ui.slot(rawProps.icon, { as: "icon" }));
|
|
730
|
+
|
|
731
|
+
const eyebrowNodes = renderNamed("eyebrow", rawProps.eyebrow);
|
|
732
|
+
const labelNodes = renderNamed("label", rawProps.label ?? rawProps.title);
|
|
733
|
+
const valueNodes = renderNamed("value", rawProps.value);
|
|
734
|
+
const noteNodes = renderNamed("note", rawProps.note ?? rawProps.subtitle);
|
|
735
|
+
const metaNodes = renderNamed("meta", rawProps.meta);
|
|
736
|
+
const deltaNodes = renderNamed("delta", rawProps.delta, { trend: rawProps.trend });
|
|
737
|
+
const iconNodes = renderNamed("icon", iconFallback);
|
|
738
|
+
const asideNodes = renderNamed("aside", rawProps.aside);
|
|
739
|
+
const footerNodes = renderNamed("footer", rawProps.footer);
|
|
740
|
+
const actionsNodes = renderNamed("actions", rawProps.actions);
|
|
741
|
+
const bodyNodes = renderNamed("default", children);
|
|
742
|
+
|
|
743
|
+
const stat = _.article(
|
|
744
|
+
p,
|
|
745
|
+
_.div(
|
|
746
|
+
{ class: "cms-stat-head" },
|
|
747
|
+
_.div(
|
|
748
|
+
{ class: "cms-stat-lead" },
|
|
749
|
+
iconNodes.length ? _.div({ class: "cms-stat-icon" }, ...iconNodes) : null,
|
|
750
|
+
_.div(
|
|
751
|
+
{ class: "cms-stat-copy" },
|
|
752
|
+
eyebrowNodes.length ? _.div({ class: "cms-stat-eyebrow" }, ...eyebrowNodes) : null,
|
|
753
|
+
labelNodes.length ? _.div({ class: "cms-stat-label" }, ...labelNodes) : null
|
|
754
|
+
)
|
|
755
|
+
),
|
|
756
|
+
asideNodes.length ? _.div({ class: "cms-stat-aside" }, ...asideNodes) : null
|
|
757
|
+
),
|
|
758
|
+
_.div(
|
|
759
|
+
{ class: "cms-stat-body" },
|
|
760
|
+
valueNodes.length ? _.div({ class: "cms-stat-value" }, ...valueNodes) : null,
|
|
761
|
+
deltaNodes.length
|
|
762
|
+
? _.div({ class: "cms-stat-delta" }, ...deltaNodes)
|
|
763
|
+
: null,
|
|
764
|
+
noteNodes.length ? _.div({ class: "cms-stat-note" }, ...noteNodes) : null,
|
|
765
|
+
metaNodes.length ? _.div({ class: "cms-stat-meta" }, ...metaNodes) : null,
|
|
766
|
+
bodyNodes.length ? _.div({ class: "cms-stat-extra" }, ...bodyNodes) : null
|
|
767
|
+
),
|
|
768
|
+
(footerNodes.length || actionsNodes.length)
|
|
769
|
+
? _.div(
|
|
770
|
+
{ class: "cms-stat-footer" },
|
|
771
|
+
footerNodes.length ? _.div({ class: "cms-stat-footer-copy" }, ...footerNodes) : null,
|
|
772
|
+
actionsNodes.length ? _.div({ class: "cms-stat-actions" }, ...actionsNodes) : null
|
|
773
|
+
)
|
|
774
|
+
: null
|
|
775
|
+
);
|
|
776
|
+
|
|
777
|
+
setPropertyProps(stat, rawProps);
|
|
778
|
+
return stat;
|
|
779
|
+
};
|
|
780
|
+
if (JSswift.isDev?.()) {
|
|
781
|
+
UI.meta = UI.meta || {};
|
|
782
|
+
UI.meta.Stat = {
|
|
783
|
+
signature: "UI.Stat(...children) | UI.Stat(props, ...children)",
|
|
784
|
+
props: {
|
|
785
|
+
eyebrow: "String|Node|Function|Array",
|
|
786
|
+
label: "String|Node|Function|Array",
|
|
787
|
+
title: "Alias of label",
|
|
788
|
+
value: "String|Number|Node|Function|Array",
|
|
789
|
+
delta: "String|Node|Function|Array",
|
|
790
|
+
trend: "up|down|flat|positive|negative|neutral",
|
|
791
|
+
note: "String|Node|Function|Array",
|
|
792
|
+
subtitle: "Alias of note",
|
|
793
|
+
meta: "Node|Function|Array",
|
|
794
|
+
icon: "String|Node|Function|Array",
|
|
795
|
+
aside: "Node|Function|Array",
|
|
796
|
+
footer: "Node|Function|Array",
|
|
797
|
+
actions: "Node|Function|Array",
|
|
798
|
+
state: "primary|secondary|success|warning|danger|info|light|dark",
|
|
799
|
+
slots: "{ eyebrow?, label?, value?, delta?, note?, meta?, icon?, aside?, footer?, actions?, default? }",
|
|
800
|
+
class: "string",
|
|
801
|
+
style: "object"
|
|
802
|
+
},
|
|
803
|
+
slots: {
|
|
804
|
+
eyebrow: "Small kicker above the stat label",
|
|
805
|
+
label: "Main stat label",
|
|
806
|
+
value: "Primary metric value",
|
|
807
|
+
delta: "Trend / delta content",
|
|
808
|
+
note: "Supporting note",
|
|
809
|
+
meta: "Extra meta badges or info",
|
|
810
|
+
icon: "Leading icon or avatar",
|
|
811
|
+
aside: "Right-side support content",
|
|
812
|
+
footer: "Footer/supporting bottom content",
|
|
813
|
+
actions: "Footer actions cluster",
|
|
814
|
+
default: "Extra body content"
|
|
815
|
+
},
|
|
816
|
+
returns: "HTMLElement",
|
|
817
|
+
description: "Compact surface for single metrics, trends, and operational metadata."
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
// Esempio: JSswift.ui.Stat({ label: "Revenue", value: "€ 128k", delta: "+18%" })
|
|
821
|
+
|
|
822
|
+
UI.Kpi = (...args) => {
|
|
823
|
+
const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
|
|
824
|
+
const slots = rawProps.slots || {};
|
|
825
|
+
const resolveStateValue = () => normalizeState(uiUnwrap(rawProps.state) || uiUnwrap(rawProps.color) || "");
|
|
826
|
+
const stateClass = uiComputed([rawProps.state, rawProps.color], () => {
|
|
827
|
+
const state = resolveStateValue();
|
|
828
|
+
return state ? `cms-state-${state}` : "";
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
const props = { ...rawProps };
|
|
832
|
+
const p = JSswift.omit(props, [
|
|
833
|
+
"actions",
|
|
834
|
+
"aside",
|
|
835
|
+
"delta",
|
|
836
|
+
"eyebrow",
|
|
837
|
+
"footer",
|
|
838
|
+
"icon",
|
|
839
|
+
"iconSize",
|
|
840
|
+
"label",
|
|
841
|
+
"media",
|
|
842
|
+
"meta",
|
|
843
|
+
"note",
|
|
844
|
+
"slots",
|
|
845
|
+
"state",
|
|
846
|
+
"subtitle",
|
|
847
|
+
"title",
|
|
848
|
+
"trend",
|
|
849
|
+
"value"
|
|
850
|
+
]);
|
|
851
|
+
p.class = uiClass([
|
|
852
|
+
"cms-clear-set",
|
|
853
|
+
"cms-kpi",
|
|
854
|
+
"cms-singularity",
|
|
855
|
+
stateClass,
|
|
856
|
+
props.class
|
|
857
|
+
]);
|
|
858
|
+
p.style = { ...(props.style || {}) };
|
|
859
|
+
|
|
860
|
+
const renderNamed = (slotName, fallback, ctx = {}) => renderSlotToArray(
|
|
861
|
+
slots,
|
|
862
|
+
slotName,
|
|
863
|
+
ctx,
|
|
864
|
+
fallback
|
|
865
|
+
);
|
|
866
|
+
|
|
867
|
+
const iconFallback = rawProps.icon == null
|
|
868
|
+
? null
|
|
869
|
+
: (typeof rawProps.icon === "string"
|
|
870
|
+
? UI.Icon({ name: rawProps.icon, size: rawProps.iconSize || "lg" })
|
|
871
|
+
: JSswift.ui.slot(rawProps.icon, { as: "icon" }));
|
|
872
|
+
|
|
873
|
+
const eyebrowNodes = renderNamed("eyebrow", rawProps.eyebrow);
|
|
874
|
+
const titleNodes = renderNamed("title", rawProps.title ?? rawProps.label);
|
|
875
|
+
const valueNodes = renderNamed("value", rawProps.value);
|
|
876
|
+
const deltaNodes = renderNamed("delta", rawProps.delta, { trend: rawProps.trend });
|
|
877
|
+
const noteNodes = renderNamed("note", rawProps.note ?? rawProps.subtitle);
|
|
878
|
+
const metaNodes = renderNamed("meta", rawProps.meta);
|
|
879
|
+
const iconNodes = renderNamed("icon", iconFallback);
|
|
880
|
+
const asideNodes = renderNamed("aside", rawProps.aside);
|
|
881
|
+
const mediaNodes = renderNamed("media", rawProps.media);
|
|
882
|
+
const footerNodes = renderNamed("footer", rawProps.footer);
|
|
883
|
+
const actionsNodes = renderNamed("actions", rawProps.actions);
|
|
884
|
+
const bodyNodes = renderNamed("default", children);
|
|
885
|
+
|
|
886
|
+
const kpi = _.section(
|
|
887
|
+
p,
|
|
888
|
+
_.div(
|
|
889
|
+
{ class: "cms-kpi-head" },
|
|
890
|
+
_.div(
|
|
891
|
+
{ class: "cms-kpi-title-wrap" },
|
|
892
|
+
iconNodes.length ? _.div({ class: "cms-kpi-icon" }, ...iconNodes) : null,
|
|
893
|
+
_.div(
|
|
894
|
+
{ class: "cms-kpi-copy" },
|
|
895
|
+
eyebrowNodes.length ? _.div({ class: "cms-kpi-eyebrow" }, ...eyebrowNodes) : null,
|
|
896
|
+
titleNodes.length ? _.div({ class: "cms-kpi-title" }, ...titleNodes) : null
|
|
897
|
+
)
|
|
898
|
+
),
|
|
899
|
+
asideNodes.length ? _.div({ class: "cms-kpi-aside" }, ...asideNodes) : null
|
|
900
|
+
),
|
|
901
|
+
_.div(
|
|
902
|
+
{ class: "cms-kpi-core" },
|
|
903
|
+
_.div(
|
|
904
|
+
{ class: "cms-kpi-value-wrap" },
|
|
905
|
+
valueNodes.length ? _.div({ class: "cms-kpi-value" }, ...valueNodes) : null,
|
|
906
|
+
deltaNodes.length ? _.div({ class: "cms-kpi-delta" }, ...deltaNodes) : null
|
|
907
|
+
),
|
|
908
|
+
noteNodes.length ? _.div({ class: "cms-kpi-note" }, ...noteNodes) : null,
|
|
909
|
+
metaNodes.length ? _.div({ class: "cms-kpi-meta" }, ...metaNodes) : null,
|
|
910
|
+
mediaNodes.length ? _.div({ class: "cms-kpi-media" }, ...mediaNodes) : null,
|
|
911
|
+
bodyNodes.length ? _.div({ class: "cms-kpi-extra" }, ...bodyNodes) : null
|
|
912
|
+
),
|
|
913
|
+
(footerNodes.length || actionsNodes.length)
|
|
914
|
+
? _.div(
|
|
915
|
+
{ class: "cms-kpi-footer" },
|
|
916
|
+
footerNodes.length ? _.div({ class: "cms-kpi-footer-copy" }, ...footerNodes) : null,
|
|
917
|
+
actionsNodes.length ? _.div({ class: "cms-kpi-actions" }, ...actionsNodes) : null
|
|
918
|
+
)
|
|
919
|
+
: null
|
|
920
|
+
);
|
|
921
|
+
|
|
922
|
+
setPropertyProps(kpi, rawProps);
|
|
923
|
+
return kpi;
|
|
924
|
+
};
|
|
925
|
+
if (JSswift.isDev?.()) {
|
|
926
|
+
UI.meta = UI.meta || {};
|
|
927
|
+
UI.meta.Kpi = {
|
|
928
|
+
signature: "UI.Kpi(...children) | UI.Kpi(props, ...children)",
|
|
929
|
+
props: {
|
|
930
|
+
eyebrow: "String|Node|Function|Array",
|
|
931
|
+
title: "String|Node|Function|Array",
|
|
932
|
+
label: "Alias of title",
|
|
933
|
+
value: "String|Number|Node|Function|Array",
|
|
934
|
+
delta: "String|Node|Function|Array",
|
|
935
|
+
trend: "up|down|flat|positive|negative|neutral",
|
|
936
|
+
note: "String|Node|Function|Array",
|
|
937
|
+
subtitle: "Alias of note",
|
|
938
|
+
meta: "Node|Function|Array",
|
|
939
|
+
media: "Node|Function|Array",
|
|
940
|
+
icon: "String|Node|Function|Array",
|
|
941
|
+
aside: "Node|Function|Array",
|
|
942
|
+
footer: "Node|Function|Array",
|
|
943
|
+
actions: "Node|Function|Array",
|
|
944
|
+
state: "primary|secondary|success|warning|danger|info|light|dark",
|
|
945
|
+
slots: "{ eyebrow?, title?, value?, delta?, note?, meta?, media?, icon?, aside?, footer?, actions?, default? }",
|
|
946
|
+
class: "string",
|
|
947
|
+
style: "object"
|
|
948
|
+
},
|
|
949
|
+
slots: {
|
|
950
|
+
eyebrow: "Kicker content",
|
|
951
|
+
title: "KPI title content",
|
|
952
|
+
value: "Primary KPI value",
|
|
953
|
+
delta: "Delta/trend content",
|
|
954
|
+
note: "Supporting note",
|
|
955
|
+
meta: "Extra meta badges or inline info",
|
|
956
|
+
media: "Secondary data view or mini-visual content",
|
|
957
|
+
icon: "Leading icon or avatar",
|
|
958
|
+
aside: "Top-right support content",
|
|
959
|
+
footer: "Bottom support content",
|
|
960
|
+
actions: "Footer actions cluster",
|
|
961
|
+
default: "Extra body content"
|
|
962
|
+
},
|
|
963
|
+
returns: "HTMLElement",
|
|
964
|
+
description: "Richer surface for KPIs, headline metrics, and mini dashboard summaries."
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
// Esempio: JSswift.ui.Kpi({ title: "Orders", value: "342", delta: "+12%" })
|
|
968
|
+
|
|
969
|
+
const overlayAnimDuration = (el, fallback = 180) => {
|
|
970
|
+
const d = getComputedStyle(el).transitionDuration;
|
|
971
|
+
if (!d) return fallback;
|
|
972
|
+
const first = d.split(",")[0].trim();
|
|
973
|
+
if (!first) return fallback;
|
|
974
|
+
const ms = first.includes("ms") ? parseFloat(first) : parseFloat(first) * 1000;
|
|
975
|
+
return Number.isFinite(ms) ? ms : fallback;
|
|
976
|
+
};
|
|
977
|
+
|
|
978
|
+
const overlayEnter = (entry) => {
|
|
979
|
+
if (!entry?.overlay || !entry?.panel) return;
|
|
980
|
+
entry.overlay.classList.add("enter");
|
|
981
|
+
entry.panel.classList.add("enter");
|
|
982
|
+
requestAnimationFrame(() => {
|
|
983
|
+
if (!entry?.overlay || !entry?.panel) return;
|
|
984
|
+
entry.overlay.classList.add("entered");
|
|
985
|
+
entry.panel.classList.add("entered");
|
|
986
|
+
entry.overlay.classList.remove("enter");
|
|
987
|
+
entry.panel.classList.remove("enter");
|
|
988
|
+
});
|
|
989
|
+
};
|
|
990
|
+
|
|
991
|
+
const overlayLeave = (entry, done) => {
|
|
992
|
+
if (!entry?.overlay || !entry?.panel) {
|
|
993
|
+
done?.();
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
if (entry._closing) return;
|
|
997
|
+
entry._closing = true;
|
|
998
|
+
entry.overlay.classList.add("leave");
|
|
999
|
+
entry.panel.classList.add("leave");
|
|
1000
|
+
const finish = () => {
|
|
1001
|
+
if (entry._closed) return;
|
|
1002
|
+
entry._closed = true;
|
|
1003
|
+
done?.();
|
|
1004
|
+
};
|
|
1005
|
+
const onEnd = (e) => {
|
|
1006
|
+
if (e.target !== entry.panel) return;
|
|
1007
|
+
entry.panel.removeEventListener("transitionend", onEnd);
|
|
1008
|
+
finish();
|
|
1009
|
+
};
|
|
1010
|
+
entry.panel.addEventListener("transitionend", onEnd);
|
|
1011
|
+
const ms = overlayAnimDuration(entry.panel, 180);
|
|
1012
|
+
setTimeout(finish, ms + 40);
|
|
1013
|
+
};
|
|
1014
|
+
|