@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,489 @@
|
|
|
1
|
+
UI.Banner = (...args) => {
|
|
2
|
+
const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
|
|
3
|
+
const slots = rawProps.slots || {};
|
|
4
|
+
|
|
5
|
+
const resolveStateValue = () => normalizeState(uiUnwrap(rawProps.type) || uiUnwrap(rawProps.state) || "");
|
|
6
|
+
const ctx = {
|
|
7
|
+
state: resolveStateValue,
|
|
8
|
+
dismissible: () => !!uiUnwrap(rawProps.dismissible)
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const typeClass = uiComputed([rawProps.type, rawProps.state], () => resolveStateValue());
|
|
12
|
+
const variantClass = uiComputed(rawProps.variant, () => {
|
|
13
|
+
const variant = String(uiUnwrap(rawProps.variant) || "").toLowerCase();
|
|
14
|
+
return ["solid", "outline", "ghost"].includes(variant) ? `cms-banner-${variant}` : "";
|
|
15
|
+
});
|
|
16
|
+
const actionsPlacementClass = uiComputed([rawProps.actionsPlacement, rawProps.stack], () => {
|
|
17
|
+
const placement = String(uiUnwrap(rawProps.actionsPlacement) || "").toLowerCase();
|
|
18
|
+
return placement === "bottom" || !!uiUnwrap(rawProps.stack) ? "cms-banner-actions-bottom" : "";
|
|
19
|
+
});
|
|
20
|
+
const stackClass = uiComputed(rawProps.stack, () => uiUnwrap(rawProps.stack) ? "cms-banner-stack" : "");
|
|
21
|
+
|
|
22
|
+
const props = { ...rawProps };
|
|
23
|
+
const p = JSswift.omit(props, [
|
|
24
|
+
"actions", "actionsPlacement", "accent", "aside", "body", "closeLabel", "description",
|
|
25
|
+
"dismiss", "dismissible", "icon", "iconSize", "message", "meta", "onDismiss", "slots",
|
|
26
|
+
"stack", "state", "subtitle", "title", "type", "variant"
|
|
27
|
+
]);
|
|
28
|
+
p.class = uiClass([
|
|
29
|
+
"cms-clear-set",
|
|
30
|
+
"cms-banner",
|
|
31
|
+
"cms-singularity",
|
|
32
|
+
typeClass,
|
|
33
|
+
variantClass,
|
|
34
|
+
actionsPlacementClass,
|
|
35
|
+
stackClass,
|
|
36
|
+
props.class
|
|
37
|
+
]);
|
|
38
|
+
p.style = { ...(props.style || {}) };
|
|
39
|
+
|
|
40
|
+
const toneToCss = (value) => {
|
|
41
|
+
if (value == null || value === "") return "";
|
|
42
|
+
const v = uiUnwrap(value);
|
|
43
|
+
if (v == null || v === "") return "";
|
|
44
|
+
const s = String(v);
|
|
45
|
+
if (isTokenCSS(s)) return `var(--cms-${s})`;
|
|
46
|
+
return s;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (rawProps.accent != null || uiIsReactive(rawProps.accent)) {
|
|
50
|
+
p.style["--cms-banner-accent"] = () => toneToCss(rawProps.accent);
|
|
51
|
+
p.style["--cms-banner-border"] = () => {
|
|
52
|
+
const tone = toneToCss(rawProps.accent);
|
|
53
|
+
return tone ? `color-mix(in srgb, ${tone} 38%, var(--cms-border-color))` : "";
|
|
54
|
+
};
|
|
55
|
+
p.style["--cms-banner-bg"] = () => {
|
|
56
|
+
const tone = toneToCss(rawProps.accent);
|
|
57
|
+
return tone ? `color-mix(in srgb, ${tone} 14%, var(--cms-panel))` : "";
|
|
58
|
+
};
|
|
59
|
+
p.style["--cms-banner-color"] = () => {
|
|
60
|
+
const tone = toneToCss(rawProps.accent);
|
|
61
|
+
return tone ? `color-mix(in srgb, ${tone} 78%, white)` : "";
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const autoIconMap = {
|
|
66
|
+
success: "check_circle",
|
|
67
|
+
warning: "warning",
|
|
68
|
+
danger: "error",
|
|
69
|
+
info: "info",
|
|
70
|
+
primary: "bolt",
|
|
71
|
+
secondary: "campaign",
|
|
72
|
+
dark: "shield",
|
|
73
|
+
light: "notifications"
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const iconFallback = (() => {
|
|
77
|
+
if (rawProps.icon === false || rawProps.icon === null) return null;
|
|
78
|
+
if (rawProps.icon != null) {
|
|
79
|
+
return typeof rawProps.icon === "string"
|
|
80
|
+
? UI.Icon({ name: rawProps.icon, size: rawProps.iconSize || rawProps.size || "md" })
|
|
81
|
+
: JSswift.ui.slot(rawProps.icon, { as: "icon" });
|
|
82
|
+
}
|
|
83
|
+
const state = resolveStateValue();
|
|
84
|
+
const iconName = state ? autoIconMap[state] : null;
|
|
85
|
+
return iconName ? UI.Icon({ name: iconName, size: rawProps.iconSize || rawProps.size || "md" }) : null;
|
|
86
|
+
})();
|
|
87
|
+
|
|
88
|
+
const titleNodes = renderSlotToArray(slots, "title", ctx, rawProps.title);
|
|
89
|
+
const messageNodes = renderSlotToArray(slots, "message", ctx, rawProps.message != null ? rawProps.message : children);
|
|
90
|
+
const descriptionNodes = renderSlotToArray(slots, "description", ctx, rawProps.description ?? rawProps.subtitle);
|
|
91
|
+
const metaNodes = renderSlotToArray(slots, "meta", ctx, rawProps.meta);
|
|
92
|
+
const bodyNodes = renderSlotToArray(
|
|
93
|
+
slots,
|
|
94
|
+
"default",
|
|
95
|
+
ctx,
|
|
96
|
+
rawProps.body != null ? [rawProps.body, ...(children || [])] : (rawProps.message != null ? children : null)
|
|
97
|
+
);
|
|
98
|
+
const iconNodes = renderSlotToArray(slots, "icon", ctx, iconFallback);
|
|
99
|
+
const asideNodes = renderSlotToArray(slots, "aside", ctx, rawProps.aside);
|
|
100
|
+
const actionsNodes = renderSlotToArray(slots, "actions", ctx, rawProps.actions);
|
|
101
|
+
const dismissNodes = renderSlotToArray(slots, "dismiss", ctx, rawProps.dismiss);
|
|
102
|
+
|
|
103
|
+
const hasBottomActions = (() => {
|
|
104
|
+
const placement = String(uiUnwrap(rawProps.actionsPlacement) || "").toLowerCase();
|
|
105
|
+
return placement === "bottom" || !!uiUnwrap(rawProps.stack);
|
|
106
|
+
})();
|
|
107
|
+
const isDismissible = !!uiUnwrap(rawProps.dismissible) || !!rawProps.onDismiss;
|
|
108
|
+
|
|
109
|
+
let bannerEl = null;
|
|
110
|
+
const defaultDismissNode = isDismissible
|
|
111
|
+
? UI.Btn({
|
|
112
|
+
class: "cms-banner-close",
|
|
113
|
+
outline: true,
|
|
114
|
+
size: rawProps.size || "sm",
|
|
115
|
+
"aria-label": rawProps.closeLabel || "Chiudi banner",
|
|
116
|
+
onClick: (e) => {
|
|
117
|
+
rawProps.onDismiss?.(e);
|
|
118
|
+
if (e.defaultPrevented) return;
|
|
119
|
+
bannerEl?.remove();
|
|
120
|
+
}
|
|
121
|
+
}, UI.Icon({ name: "close", size: rawProps.size || "sm" }))
|
|
122
|
+
: null;
|
|
123
|
+
|
|
124
|
+
const sideNodes = [
|
|
125
|
+
...asideNodes,
|
|
126
|
+
...(!hasBottomActions ? actionsNodes : []),
|
|
127
|
+
...(dismissNodes.length ? dismissNodes : (defaultDismissNode ? [defaultDismissNode] : []))
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
bannerEl = _.div(
|
|
131
|
+
p,
|
|
132
|
+
iconNodes.length ? _.div({ class: "cms-banner-icon" }, ...iconNodes) : null,
|
|
133
|
+
_.div(
|
|
134
|
+
{ class: "cms-banner-body" },
|
|
135
|
+
_.div(
|
|
136
|
+
{ class: "cms-banner-copy" },
|
|
137
|
+
titleNodes.length ? _.div({ class: "cms-banner-title" }, ...titleNodes) : null,
|
|
138
|
+
messageNodes.length ? _.div({ class: "cms-banner-message" }, ...messageNodes) : null,
|
|
139
|
+
descriptionNodes.length ? _.div({ class: "cms-banner-description" }, ...descriptionNodes) : null,
|
|
140
|
+
bodyNodes.length ? _.div({ class: "cms-banner-extra" }, ...bodyNodes) : null,
|
|
141
|
+
metaNodes.length ? _.div({ class: "cms-banner-meta" }, ...metaNodes) : null
|
|
142
|
+
),
|
|
143
|
+
hasBottomActions && actionsNodes.length
|
|
144
|
+
? _.div({ class: "cms-banner-actions" }, ...actionsNodes)
|
|
145
|
+
: null
|
|
146
|
+
),
|
|
147
|
+
sideNodes.length ? _.div({ class: "cms-banner-side" }, ...sideNodes) : null
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const role = p.role || (() => {
|
|
151
|
+
const state = resolveStateValue();
|
|
152
|
+
return state === "danger" || state === "warning" ? "alert" : "status";
|
|
153
|
+
})();
|
|
154
|
+
bannerEl.setAttribute("role", role);
|
|
155
|
+
setPropertyProps(bannerEl, rawProps);
|
|
156
|
+
return bannerEl;
|
|
157
|
+
};
|
|
158
|
+
if (JSswift.isDev?.()) {
|
|
159
|
+
UI.meta = UI.meta || {};
|
|
160
|
+
UI.meta.Banner = {
|
|
161
|
+
signature: "UI.Banner(...children) | UI.Banner(props, ...children)",
|
|
162
|
+
props: {
|
|
163
|
+
title: "String|Node|Function|Array",
|
|
164
|
+
message: "String|Node|Function|Array",
|
|
165
|
+
description: "String|Node|Function|Array",
|
|
166
|
+
meta: "String|Node|Function|Array",
|
|
167
|
+
icon: "String|Node|Function|Array|false",
|
|
168
|
+
actions: "Node|Function|Array",
|
|
169
|
+
aside: "Node|Function|Array",
|
|
170
|
+
body: "Node|Function|Array",
|
|
171
|
+
dismissible: "boolean",
|
|
172
|
+
dismiss: "Node|Function|Array",
|
|
173
|
+
onDismiss: "function",
|
|
174
|
+
closeLabel: "string",
|
|
175
|
+
type: "success|warning|danger|error|info|primary|secondary|light|dark",
|
|
176
|
+
state: "success|warning|danger|error|info|primary|secondary|light|dark",
|
|
177
|
+
accent: "string",
|
|
178
|
+
variant: "soft|solid|outline|ghost",
|
|
179
|
+
actionsPlacement: "end|bottom",
|
|
180
|
+
dense: "boolean",
|
|
181
|
+
stack: "boolean",
|
|
182
|
+
slots: "{ icon?, title?, message?, description?, meta?, actions?, aside?, dismiss?, default? }",
|
|
183
|
+
class: "string",
|
|
184
|
+
style: "object"
|
|
185
|
+
},
|
|
186
|
+
slots: {
|
|
187
|
+
icon: "Leading visual/icon content",
|
|
188
|
+
title: "Banner title content",
|
|
189
|
+
message: "Primary message content",
|
|
190
|
+
description: "Secondary/supporting text",
|
|
191
|
+
meta: "Meta information content",
|
|
192
|
+
actions: "Actions area content",
|
|
193
|
+
aside: "Right side support content",
|
|
194
|
+
dismiss: "Custom dismiss control",
|
|
195
|
+
default: "Extra banner body content"
|
|
196
|
+
},
|
|
197
|
+
returns: "HTMLDivElement",
|
|
198
|
+
description: "Structured banner with tone, actions, dismiss, and composable slots."
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
// Esempio: JSswift.ui.Banner({ type: "warning", title: "Pagamento in sospeso", message: "Aggiorna il batch entro le 18:00" })
|
|
202
|
+
|
|
203
|
+
UI.Alert = (...args) => {
|
|
204
|
+
const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
|
|
205
|
+
const slots = rawProps.slots || {};
|
|
206
|
+
|
|
207
|
+
const resolveStateValue = () => normalizeState(uiUnwrap(rawProps.type) || uiUnwrap(rawProps.state) || uiUnwrap(rawProps.color) || "warning");
|
|
208
|
+
const stateClass = uiComputed([rawProps.type, rawProps.state, rawProps.color], () => {
|
|
209
|
+
const state = resolveStateValue();
|
|
210
|
+
return state ? `cms-state-${state}` : "";
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const props = { ...rawProps };
|
|
214
|
+
const p = JSswift.omit(props, [
|
|
215
|
+
"actions", "aside", "closeLabel", "description", "dismiss", "dismissible",
|
|
216
|
+
"icon", "iconSize", "message", "meta", "onDismiss", "slots", "state",
|
|
217
|
+
"subtitle", "title", "type", "variant"
|
|
218
|
+
]);
|
|
219
|
+
p.class = uiClass([
|
|
220
|
+
"cms-clear-set",
|
|
221
|
+
"cms-alert",
|
|
222
|
+
"cms-singularity",
|
|
223
|
+
stateClass,
|
|
224
|
+
uiWhen(rawProps.dismissible, "is-dismissible"),
|
|
225
|
+
props.class
|
|
226
|
+
]);
|
|
227
|
+
p.style = { ...(props.style || {}) };
|
|
228
|
+
|
|
229
|
+
const autoIconMap = {
|
|
230
|
+
success: "check_circle",
|
|
231
|
+
warning: "warning",
|
|
232
|
+
danger: "error",
|
|
233
|
+
info: "info",
|
|
234
|
+
primary: "bolt",
|
|
235
|
+
secondary: "campaign",
|
|
236
|
+
dark: "shield",
|
|
237
|
+
light: "notifications"
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const iconFallback = (() => {
|
|
241
|
+
if (rawProps.icon === false || rawProps.icon === null) return null;
|
|
242
|
+
if (rawProps.icon != null) {
|
|
243
|
+
return typeof rawProps.icon === "string"
|
|
244
|
+
? UI.Icon({ name: rawProps.icon, size: rawProps.iconSize || "sm" })
|
|
245
|
+
: JSswift.ui.slot(rawProps.icon, { as: "icon" });
|
|
246
|
+
}
|
|
247
|
+
const iconName = autoIconMap[resolveStateValue()] || autoIconMap.warning;
|
|
248
|
+
return UI.Icon({ name: iconName, size: rawProps.iconSize || "sm" });
|
|
249
|
+
})();
|
|
250
|
+
|
|
251
|
+
const titleNodes = renderSlotToArray(slots, "title", {}, rawProps.title);
|
|
252
|
+
const messageNodes = renderSlotToArray(slots, "message", {}, rawProps.message != null ? rawProps.message : children);
|
|
253
|
+
const descriptionNodes = renderSlotToArray(slots, "description", {}, rawProps.description ?? rawProps.subtitle);
|
|
254
|
+
const metaNodes = renderSlotToArray(slots, "meta", {}, rawProps.meta);
|
|
255
|
+
const actionsNodes = renderSlotToArray(slots, "actions", {}, rawProps.actions);
|
|
256
|
+
const asideNodes = renderSlotToArray(slots, "aside", {}, rawProps.aside);
|
|
257
|
+
const iconNodes = renderSlotToArray(slots, "icon", {}, iconFallback);
|
|
258
|
+
const dismissNodes = renderSlotToArray(slots, "dismiss", {}, rawProps.dismiss);
|
|
259
|
+
|
|
260
|
+
let alertEl = null;
|
|
261
|
+
const defaultDismissNode = rawProps.dismissible || rawProps.onDismiss
|
|
262
|
+
? UI.Btn({
|
|
263
|
+
class: "cms-alert-close",
|
|
264
|
+
outline: true,
|
|
265
|
+
size: "sm",
|
|
266
|
+
"aria-label": rawProps.closeLabel || "Chiudi alert",
|
|
267
|
+
onClick: (e) => {
|
|
268
|
+
rawProps.onDismiss?.(e);
|
|
269
|
+
if (e.defaultPrevented) return;
|
|
270
|
+
alertEl?.remove();
|
|
271
|
+
}
|
|
272
|
+
}, UI.Icon({ name: "close", size: "sm" }))
|
|
273
|
+
: null;
|
|
274
|
+
|
|
275
|
+
alertEl = _.div(
|
|
276
|
+
p,
|
|
277
|
+
iconNodes.length ? _.div({ class: "cms-alert-icon" }, ...iconNodes) : null,
|
|
278
|
+
_.div(
|
|
279
|
+
{ class: "cms-alert-body" },
|
|
280
|
+
titleNodes.length ? _.div({ class: "cms-alert-title" }, ...titleNodes) : null,
|
|
281
|
+
messageNodes.length ? _.div({ class: "cms-alert-message" }, ...messageNodes) : null,
|
|
282
|
+
descriptionNodes.length ? _.div({ class: "cms-alert-description" }, ...descriptionNodes) : null,
|
|
283
|
+
metaNodes.length ? _.div({ class: "cms-alert-meta" }, ...metaNodes) : null,
|
|
284
|
+
actionsNodes.length ? _.div({ class: "cms-alert-actions" }, ...actionsNodes) : null
|
|
285
|
+
),
|
|
286
|
+
(asideNodes.length || dismissNodes.length || defaultDismissNode)
|
|
287
|
+
? _.div(
|
|
288
|
+
{ class: "cms-alert-side" },
|
|
289
|
+
...asideNodes,
|
|
290
|
+
...(dismissNodes.length ? dismissNodes : (defaultDismissNode ? [defaultDismissNode] : []))
|
|
291
|
+
)
|
|
292
|
+
: null
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
alertEl.setAttribute("role", resolveStateValue() === "danger" || resolveStateValue() === "warning" ? "alert" : "status");
|
|
296
|
+
setPropertyProps(alertEl, rawProps);
|
|
297
|
+
return alertEl;
|
|
298
|
+
};
|
|
299
|
+
if (JSswift.isDev?.()) {
|
|
300
|
+
UI.meta = UI.meta || {};
|
|
301
|
+
UI.meta.Alert = {
|
|
302
|
+
signature: "UI.Alert(...children) | UI.Alert(props, ...children)",
|
|
303
|
+
props: {
|
|
304
|
+
title: "String|Node|Function|Array",
|
|
305
|
+
message: "String|Node|Function|Array",
|
|
306
|
+
description: "String|Node|Function|Array",
|
|
307
|
+
meta: "Node|Function|Array",
|
|
308
|
+
icon: "String|Node|Function|Array|false",
|
|
309
|
+
actions: "Node|Function|Array",
|
|
310
|
+
aside: "Node|Function|Array",
|
|
311
|
+
dismissible: "boolean",
|
|
312
|
+
dismiss: "Node|Function|Array",
|
|
313
|
+
onDismiss: "function",
|
|
314
|
+
closeLabel: "string",
|
|
315
|
+
type: "success|warning|danger|error|info|primary|secondary|light|dark",
|
|
316
|
+
state: "success|warning|danger|error|info|primary|secondary|light|dark",
|
|
317
|
+
slots: "{ icon?, title?, message?, description?, meta?, actions?, aside?, dismiss?, default? }",
|
|
318
|
+
class: "string",
|
|
319
|
+
style: "object"
|
|
320
|
+
},
|
|
321
|
+
slots: {
|
|
322
|
+
icon: "Leading alert icon content",
|
|
323
|
+
title: "Alert title content",
|
|
324
|
+
message: "Primary alert copy",
|
|
325
|
+
description: "Secondary/supporting text",
|
|
326
|
+
meta: "Inline meta content",
|
|
327
|
+
actions: "Alert actions cluster",
|
|
328
|
+
aside: "Right side support content",
|
|
329
|
+
dismiss: "Custom dismiss control",
|
|
330
|
+
default: "Fallback alert message content"
|
|
331
|
+
},
|
|
332
|
+
returns: "HTMLDivElement",
|
|
333
|
+
description: "Alert compatto per warning inline, policy note e feedback persistente dentro page o card."
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
// Esempio: JSswift.ui.Alert({ type: "warning", title: "Review richiesta", message: "Controlla il batch prima del go-live" })
|
|
337
|
+
|
|
338
|
+
UI.EmptyState = (...args) => {
|
|
339
|
+
const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
|
|
340
|
+
const slots = rawProps.slots || {};
|
|
341
|
+
const resolveStateValue = () => normalizeState(uiUnwrap(rawProps.state) || uiUnwrap(rawProps.color) || "");
|
|
342
|
+
const stateClass = uiComputed([rawProps.state, rawProps.color], () => {
|
|
343
|
+
const state = resolveStateValue();
|
|
344
|
+
return state ? `cms-state-${state}` : "";
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const props = { ...rawProps };
|
|
348
|
+
const p = JSswift.omit(props, [
|
|
349
|
+
"actions", "description", "eyebrow", "icon", "iconSize", "illustration", "media",
|
|
350
|
+
"message", "meta", "slots", "state", "title"
|
|
351
|
+
]);
|
|
352
|
+
p.class = uiClass([
|
|
353
|
+
"cms-clear-set",
|
|
354
|
+
"cms-empty-state",
|
|
355
|
+
"cms-singularity",
|
|
356
|
+
stateClass,
|
|
357
|
+
uiWhen(rawProps.compact, "compact"),
|
|
358
|
+
uiWhen(rawProps.inline, "inline"),
|
|
359
|
+
props.class
|
|
360
|
+
]);
|
|
361
|
+
p.style = { ...(props.style || {}) };
|
|
362
|
+
|
|
363
|
+
const iconFallback = rawProps.icon === false
|
|
364
|
+
? null
|
|
365
|
+
: (rawProps.icon != null
|
|
366
|
+
? (typeof rawProps.icon === "string"
|
|
367
|
+
? UI.Icon({ name: rawProps.icon, size: rawProps.iconSize || "xl" })
|
|
368
|
+
: JSswift.ui.slot(rawProps.icon, { as: "icon" }))
|
|
369
|
+
: UI.Icon({ name: "inbox", size: rawProps.iconSize || "xl" }));
|
|
370
|
+
|
|
371
|
+
const illustrationNodes = renderSlotToArray(slots, "illustration", {}, rawProps.illustration ?? rawProps.media);
|
|
372
|
+
const iconNodes = renderSlotToArray(slots, "icon", {}, iconFallback);
|
|
373
|
+
const eyebrowNodes = renderSlotToArray(slots, "eyebrow", {}, rawProps.eyebrow);
|
|
374
|
+
const titleNodes = renderSlotToArray(slots, "title", {}, rawProps.title);
|
|
375
|
+
const messageNodes = renderSlotToArray(slots, "message", {}, rawProps.message != null ? rawProps.message : children);
|
|
376
|
+
const descriptionNodes = renderSlotToArray(slots, "description", {}, rawProps.description);
|
|
377
|
+
const metaNodes = renderSlotToArray(slots, "meta", {}, rawProps.meta);
|
|
378
|
+
const actionsNodes = renderSlotToArray(slots, "actions", {}, rawProps.actions);
|
|
379
|
+
const bodyNodes = renderSlotToArray(slots, "default", {}, rawProps.message != null ? children : null);
|
|
380
|
+
|
|
381
|
+
const emptyState = _.section(
|
|
382
|
+
p,
|
|
383
|
+
illustrationNodes.length
|
|
384
|
+
? _.div({ class: "cms-empty-state-illustration" }, ...illustrationNodes)
|
|
385
|
+
: null,
|
|
386
|
+
iconNodes.length ? _.div({ class: "cms-empty-state-icon" }, ...iconNodes) : null,
|
|
387
|
+
eyebrowNodes.length ? _.div({ class: "cms-empty-state-eyebrow" }, ...eyebrowNodes) : null,
|
|
388
|
+
titleNodes.length ? _.div({ class: "cms-empty-state-title" }, ...titleNodes) : null,
|
|
389
|
+
messageNodes.length ? _.div({ class: "cms-empty-state-message" }, ...messageNodes) : null,
|
|
390
|
+
descriptionNodes.length ? _.div({ class: "cms-empty-state-description" }, ...descriptionNodes) : null,
|
|
391
|
+
metaNodes.length ? _.div({ class: "cms-empty-state-meta" }, ...metaNodes) : null,
|
|
392
|
+
bodyNodes.length ? _.div({ class: "cms-empty-state-extra" }, ...bodyNodes) : null,
|
|
393
|
+
actionsNodes.length ? _.div({ class: "cms-empty-state-actions" }, ...actionsNodes) : null
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
setPropertyProps(emptyState, rawProps);
|
|
397
|
+
return emptyState;
|
|
398
|
+
};
|
|
399
|
+
if (JSswift.isDev?.()) {
|
|
400
|
+
UI.meta = UI.meta || {};
|
|
401
|
+
UI.meta.EmptyState = {
|
|
402
|
+
signature: "UI.EmptyState(...children) | UI.EmptyState(props, ...children)",
|
|
403
|
+
props: {
|
|
404
|
+
eyebrow: "String|Node|Function|Array",
|
|
405
|
+
title: "String|Node|Function|Array",
|
|
406
|
+
message: "String|Node|Function|Array",
|
|
407
|
+
description: "String|Node|Function|Array",
|
|
408
|
+
meta: "Node|Function|Array",
|
|
409
|
+
icon: "String|Node|Function|Array|false",
|
|
410
|
+
illustration: "Node|Function|Array",
|
|
411
|
+
media: "Alias of illustration",
|
|
412
|
+
actions: "Node|Function|Array",
|
|
413
|
+
compact: "boolean",
|
|
414
|
+
inline: "boolean",
|
|
415
|
+
state: "primary|secondary|success|warning|danger|info|light|dark",
|
|
416
|
+
slots: "{ illustration?, icon?, eyebrow?, title?, message?, description?, meta?, actions?, default? }",
|
|
417
|
+
class: "string",
|
|
418
|
+
style: "object"
|
|
419
|
+
},
|
|
420
|
+
slots: {
|
|
421
|
+
illustration: "Top visual / illustration content",
|
|
422
|
+
icon: "Leading empty-state icon",
|
|
423
|
+
eyebrow: "Small kicker content",
|
|
424
|
+
title: "Primary empty-state heading",
|
|
425
|
+
message: "Primary message content",
|
|
426
|
+
description: "Secondary supporting text",
|
|
427
|
+
meta: "Meta badges or summary content",
|
|
428
|
+
actions: "Actions cluster",
|
|
429
|
+
default: "Extra empty-state body content"
|
|
430
|
+
},
|
|
431
|
+
returns: "HTMLElement",
|
|
432
|
+
description: "Surface per zero-results, onboarding vuoti e pannelli senza dati con CTA di recupero."
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
// Esempio: JSswift.ui.EmptyState({ title: "No results", message: "Prova a cambiare i filtri" })
|
|
436
|
+
|
|
437
|
+
// -------------------------------
|
|
438
|
+
// 3) APP SHELL
|
|
439
|
+
// -------------------------------
|
|
440
|
+
let drawerStateKey = "jsswift:drawer-open";
|
|
441
|
+
const drawerToggleIcons = new Set();
|
|
442
|
+
const drawerElsByKey = new Map();
|
|
443
|
+
const readDrawerOpen = (key = drawerStateKey) => {
|
|
444
|
+
const store = JSswift?.store;
|
|
445
|
+
if (store?.get) {
|
|
446
|
+
const stored = store.get(key, undefined);
|
|
447
|
+
if (typeof stored === "boolean") return stored;
|
|
448
|
+
}
|
|
449
|
+
try {
|
|
450
|
+
return localStorage.getItem(key) === "1";
|
|
451
|
+
} catch {
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
const writeDrawerOpen = (open, key = drawerStateKey) => {
|
|
456
|
+
const store = JSswift?.store;
|
|
457
|
+
if (store?.set) {
|
|
458
|
+
store.set(key, !!open);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
try {
|
|
462
|
+
localStorage.setItem(key, open ? "1" : "0");
|
|
463
|
+
} catch {
|
|
464
|
+
// ignore storage errors
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
let drawerOpen = readDrawerOpen();
|
|
468
|
+
const setDrawerOpen = (open, key = drawerStateKey) => {
|
|
469
|
+
drawerOpen = !!open;
|
|
470
|
+
const drawerEls = drawerElsByKey.get(key);
|
|
471
|
+
if (drawerEls && drawerEls.size) {
|
|
472
|
+
drawerEls.forEach((el) => el?.classList.toggle("open", drawerOpen));
|
|
473
|
+
} else {
|
|
474
|
+
const drawerEl = document.querySelector(".cms-drawer");
|
|
475
|
+
if (drawerEl) drawerEl.classList.toggle("open", drawerOpen);
|
|
476
|
+
}
|
|
477
|
+
drawerToggleIcons.forEach((entry) => {
|
|
478
|
+
if (!entry) return;
|
|
479
|
+
if (typeof entry.update === "function") {
|
|
480
|
+
entry.update(drawerOpen);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
const { el, openIcon, closeIcon } = entry;
|
|
484
|
+
if (el) el.textContent = drawerOpen ? openIcon : closeIcon;
|
|
485
|
+
});
|
|
486
|
+
writeDrawerOpen(drawerOpen, key);
|
|
487
|
+
return drawerOpen;
|
|
488
|
+
};
|
|
489
|
+
|