@jsswift/ui 1.0.54 → 2.0.0
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 +36 -36
- package/dist/css/base.css +374 -374
- package/dist/css/docs.css +54 -54
- package/dist/css/fonts-nunito.css +2 -2
- package/dist/css/min-ui.css +1 -1
- package/dist/css/responsive-base.css +549 -549
- package/dist/css/responsive.css +3225 -3225
- package/dist/css/ui-components.css +2902 -2930
- package/dist/css/ui-light.css +3853 -3881
- package/dist/css/ui-light.min.css +1 -1
- package/dist/css/ui.css +6530 -6558
- package/dist/css/ui.min.css +1 -1
- package/dist/min-ui.js +12 -12
- package/dist/ui.js +4354 -9379
- package/dist/ui.min.js +12 -12
- package/package.json +2 -2
- package/src/00-bootstrap.js +305 -305
- package/src/01-foundation-helpers.js +328 -325
- package/src/10-primitives-layout.js +242 -242
- package/src/20-display-content.js +76 -76
- package/src/21-list-content.js +21 -21
- package/src/22-banner-content.js +376 -376
- package/src/23-tooltip.js +9 -9
- package/src/31-form-advanced.js +141 -141
- package/src/40-navigation.js +81 -81
- package/src/41-tab-panel.js +37 -37
- package/src/50-feedback.js +52 -52
- package/src/51-feedback-service.js +166 -166
- package/src/60-shell-app.js +1082 -1082
- package/src/61-form-service.js +5 -5
- package/src/70-dialog.js +34 -34
- package/src/71-menu-overlays.js +73 -73
- package/src/80-data.js +38 -38
- package/src/css/animation.css +36 -36
- package/src/css/base.css +374 -374
- package/src/css/docs.css +54 -54
- package/src/css/fonts-nunito.css +2 -2
- package/src/css/responsive.css +3225 -3225
- package/src/css/ui-components.css +2902 -2930
package/src/22-banner-content.js
CHANGED
|
@@ -1,376 +1,376 @@
|
|
|
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) ? `
|
|
15
|
-
});
|
|
16
|
-
const actionsPlacementClass = uiComputed([rawProps.actionsPlacement, rawProps.stack], () => {
|
|
17
|
-
const placement = String(uiUnwrap(rawProps.actionsPlacement) || "").toLowerCase();
|
|
18
|
-
return placement === "bottom" || !!uiUnwrap(rawProps.stack) ? "
|
|
19
|
-
});
|
|
20
|
-
const stackClass = uiComputed(rawProps.stack, () => uiUnwrap(rawProps.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
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
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(--
|
|
46
|
-
return s;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
if (rawProps.accent != null || uiIsReactive(rawProps.accent)) {
|
|
50
|
-
p.style["--
|
|
51
|
-
p.style["--
|
|
52
|
-
const tone = toneToCss(rawProps.accent);
|
|
53
|
-
return tone ? `color-mix(in srgb, ${tone} 38%, var(--
|
|
54
|
-
};
|
|
55
|
-
p.style["--
|
|
56
|
-
const tone = toneToCss(rawProps.accent);
|
|
57
|
-
return tone ? `color-mix(in srgb, ${tone} 14%, var(--
|
|
58
|
-
};
|
|
59
|
-
p.style["--
|
|
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: "
|
|
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: "
|
|
133
|
-
_.div(
|
|
134
|
-
{ class: "
|
|
135
|
-
_.div(
|
|
136
|
-
{ class: "
|
|
137
|
-
titleNodes.length ? _.div({ class: "
|
|
138
|
-
messageNodes.length ? _.div({ class: "
|
|
139
|
-
descriptionNodes.length ? _.div({ class: "
|
|
140
|
-
bodyNodes.length ? _.div({ class: "
|
|
141
|
-
metaNodes.length ? _.div({ class: "
|
|
142
|
-
),
|
|
143
|
-
hasBottomActions && actionsNodes.length
|
|
144
|
-
? _.div({ class: "
|
|
145
|
-
: null
|
|
146
|
-
),
|
|
147
|
-
sideNodes.length ? _.div({ class: "
|
|
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
|
-
|
|
159
|
-
// Esempio: JSswift.ui.Banner({ type: "warning", title: "Pagamento in sospeso", message: "Aggiorna il batch entro le 18:00" })
|
|
160
|
-
|
|
161
|
-
UI.Alert = (...args) => {
|
|
162
|
-
const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
|
|
163
|
-
const slots = rawProps.slots || {};
|
|
164
|
-
|
|
165
|
-
const resolveStateValue = () => normalizeState(uiUnwrap(rawProps.type) || uiUnwrap(rawProps.state) || uiUnwrap(rawProps.color) || "warning");
|
|
166
|
-
const stateClass = uiComputed([rawProps.type, rawProps.state, rawProps.color], () => {
|
|
167
|
-
const state = resolveStateValue();
|
|
168
|
-
return state ? `
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
const props = { ...rawProps };
|
|
172
|
-
const p = JSswift.omit(props, [
|
|
173
|
-
"actions", "aside", "closeLabel", "description", "dismiss", "dismissible",
|
|
174
|
-
"icon", "iconSize", "message", "meta", "onDismiss", "slots", "state",
|
|
175
|
-
"subtitle", "title", "type", "variant"
|
|
176
|
-
]);
|
|
177
|
-
p.class = uiClass([
|
|
178
|
-
"
|
|
179
|
-
"
|
|
180
|
-
"
|
|
181
|
-
stateClass,
|
|
182
|
-
uiWhen(rawProps.dismissible, "is-dismissible"),
|
|
183
|
-
props.class
|
|
184
|
-
]);
|
|
185
|
-
p.style = { ...(props.style || {}) };
|
|
186
|
-
|
|
187
|
-
const autoIconMap = {
|
|
188
|
-
success: "check_circle",
|
|
189
|
-
warning: "warning",
|
|
190
|
-
danger: "error",
|
|
191
|
-
info: "info",
|
|
192
|
-
primary: "bolt",
|
|
193
|
-
secondary: "campaign",
|
|
194
|
-
dark: "shield",
|
|
195
|
-
light: "notifications"
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
const iconFallback = (() => {
|
|
199
|
-
if (rawProps.icon === false || rawProps.icon === null) return null;
|
|
200
|
-
if (rawProps.icon != null) {
|
|
201
|
-
return typeof rawProps.icon === "string"
|
|
202
|
-
? UI.Icon({ name: rawProps.icon, size: rawProps.iconSize || "sm" })
|
|
203
|
-
: JSswift.ui.slot(rawProps.icon, { as: "icon" });
|
|
204
|
-
}
|
|
205
|
-
const iconName = autoIconMap[resolveStateValue()] || autoIconMap.warning;
|
|
206
|
-
return UI.Icon({ name: iconName, size: rawProps.iconSize || "sm" });
|
|
207
|
-
})();
|
|
208
|
-
|
|
209
|
-
const titleNodes = renderSlotToArray(slots, "title", {}, rawProps.title);
|
|
210
|
-
const messageNodes = renderSlotToArray(slots, "message", {}, rawProps.message != null ? rawProps.message : children);
|
|
211
|
-
const descriptionNodes = renderSlotToArray(slots, "description", {}, rawProps.description ?? rawProps.subtitle);
|
|
212
|
-
const metaNodes = renderSlotToArray(slots, "meta", {}, rawProps.meta);
|
|
213
|
-
const actionsNodes = renderSlotToArray(slots, "actions", {}, rawProps.actions);
|
|
214
|
-
const asideNodes = renderSlotToArray(slots, "aside", {}, rawProps.aside);
|
|
215
|
-
const iconNodes = renderSlotToArray(slots, "icon", {}, iconFallback);
|
|
216
|
-
const dismissNodes = renderSlotToArray(slots, "dismiss", {}, rawProps.dismiss);
|
|
217
|
-
|
|
218
|
-
let alertEl = null;
|
|
219
|
-
const defaultDismissNode = rawProps.dismissible || rawProps.onDismiss
|
|
220
|
-
? UI.Btn({
|
|
221
|
-
class: "
|
|
222
|
-
outline: true,
|
|
223
|
-
size: "sm",
|
|
224
|
-
"aria-label": rawProps.closeLabel || "Chiudi alert",
|
|
225
|
-
onClick: (e) => {
|
|
226
|
-
rawProps.onDismiss?.(e);
|
|
227
|
-
if (e.defaultPrevented) return;
|
|
228
|
-
alertEl?.remove();
|
|
229
|
-
}
|
|
230
|
-
}, UI.Icon({ name: "close", size: "sm" }))
|
|
231
|
-
: null;
|
|
232
|
-
|
|
233
|
-
alertEl = _.div(
|
|
234
|
-
p,
|
|
235
|
-
iconNodes.length ? _.div({ class: "
|
|
236
|
-
_.div(
|
|
237
|
-
{ class: "
|
|
238
|
-
titleNodes.length ? _.div({ class: "
|
|
239
|
-
messageNodes.length ? _.div({ class: "
|
|
240
|
-
descriptionNodes.length ? _.div({ class: "
|
|
241
|
-
metaNodes.length ? _.div({ class: "
|
|
242
|
-
actionsNodes.length ? _.div({ class: "
|
|
243
|
-
),
|
|
244
|
-
(asideNodes.length || dismissNodes.length || defaultDismissNode)
|
|
245
|
-
? _.div(
|
|
246
|
-
{ class: "
|
|
247
|
-
...asideNodes,
|
|
248
|
-
...(dismissNodes.length ? dismissNodes : (defaultDismissNode ? [defaultDismissNode] : []))
|
|
249
|
-
)
|
|
250
|
-
: null
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
alertEl.setAttribute("role", resolveStateValue() === "danger" || resolveStateValue() === "warning" ? "alert" : "status");
|
|
254
|
-
setPropertyProps(alertEl, rawProps);
|
|
255
|
-
return alertEl;
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
// Esempio: JSswift.ui.Alert({ type: "warning", title: "Review richiesta", message: "Controlla il batch prima del go-live" })
|
|
259
|
-
|
|
260
|
-
UI.EmptyState = (...args) => {
|
|
261
|
-
const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
|
|
262
|
-
const slots = rawProps.slots || {};
|
|
263
|
-
const resolveStateValue = () => normalizeState(uiUnwrap(rawProps.state) || uiUnwrap(rawProps.color) || "");
|
|
264
|
-
const stateClass = uiComputed([rawProps.state, rawProps.color], () => {
|
|
265
|
-
const state = resolveStateValue();
|
|
266
|
-
return state ? `
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
const props = { ...rawProps };
|
|
270
|
-
const p = JSswift.omit(props, [
|
|
271
|
-
"actions", "description", "eyebrow", "icon", "iconSize", "illustration", "media",
|
|
272
|
-
"message", "meta", "slots", "state", "title"
|
|
273
|
-
]);
|
|
274
|
-
p.class = uiClass([
|
|
275
|
-
"
|
|
276
|
-
"
|
|
277
|
-
"
|
|
278
|
-
stateClass,
|
|
279
|
-
uiWhen(rawProps.compact, "compact"),
|
|
280
|
-
uiWhen(rawProps.inline, "inline"),
|
|
281
|
-
props.class
|
|
282
|
-
]);
|
|
283
|
-
p.style = { ...(props.style || {}) };
|
|
284
|
-
|
|
285
|
-
const iconFallback = rawProps.icon === false
|
|
286
|
-
? null
|
|
287
|
-
: (rawProps.icon != null
|
|
288
|
-
? (typeof rawProps.icon === "string"
|
|
289
|
-
? UI.Icon({ name: rawProps.icon, size: rawProps.iconSize || "xl" })
|
|
290
|
-
: JSswift.ui.slot(rawProps.icon, { as: "icon" }))
|
|
291
|
-
: UI.Icon({ name: "inbox", size: rawProps.iconSize || "xl" }));
|
|
292
|
-
|
|
293
|
-
const illustrationNodes = renderSlotToArray(slots, "illustration", {}, rawProps.illustration ?? rawProps.media);
|
|
294
|
-
const iconNodes = renderSlotToArray(slots, "icon", {}, iconFallback);
|
|
295
|
-
const eyebrowNodes = renderSlotToArray(slots, "eyebrow", {}, rawProps.eyebrow);
|
|
296
|
-
const titleNodes = renderSlotToArray(slots, "title", {}, rawProps.title);
|
|
297
|
-
const messageNodes = renderSlotToArray(slots, "message", {}, rawProps.message != null ? rawProps.message : children);
|
|
298
|
-
const descriptionNodes = renderSlotToArray(slots, "description", {}, rawProps.description);
|
|
299
|
-
const metaNodes = renderSlotToArray(slots, "meta", {}, rawProps.meta);
|
|
300
|
-
const actionsNodes = renderSlotToArray(slots, "actions", {}, rawProps.actions);
|
|
301
|
-
const bodyNodes = renderSlotToArray(slots, "default", {}, rawProps.message != null ? children : null);
|
|
302
|
-
|
|
303
|
-
const emptyState = _.section(
|
|
304
|
-
p,
|
|
305
|
-
illustrationNodes.length
|
|
306
|
-
? _.div({ class: "
|
|
307
|
-
: null,
|
|
308
|
-
iconNodes.length ? _.div({ class: "
|
|
309
|
-
eyebrowNodes.length ? _.div({ class: "
|
|
310
|
-
titleNodes.length ? _.div({ class: "
|
|
311
|
-
messageNodes.length ? _.div({ class: "
|
|
312
|
-
descriptionNodes.length ? _.div({ class: "
|
|
313
|
-
metaNodes.length ? _.div({ class: "
|
|
314
|
-
bodyNodes.length ? _.div({ class: "
|
|
315
|
-
actionsNodes.length ? _.div({ class: "
|
|
316
|
-
);
|
|
317
|
-
|
|
318
|
-
setPropertyProps(emptyState, rawProps);
|
|
319
|
-
return emptyState;
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
// Esempio: JSswift.ui.EmptyState({ title: "No results", message: "Prova a cambiare i filtri" })
|
|
323
|
-
|
|
324
|
-
// -------------------------------
|
|
325
|
-
// 3) APP SHELL
|
|
326
|
-
// -------------------------------
|
|
327
|
-
let drawerStateKey = "jsswift:drawer-open";
|
|
328
|
-
const drawerToggleIcons = new Set();
|
|
329
|
-
const drawerElsByKey = new Map();
|
|
330
|
-
const readDrawerOpen = (key = drawerStateKey) => {
|
|
331
|
-
const store = JSswift?.store;
|
|
332
|
-
if (store?.get) {
|
|
333
|
-
const stored = store.get(key, undefined);
|
|
334
|
-
if (typeof stored === "boolean") return stored;
|
|
335
|
-
}
|
|
336
|
-
try {
|
|
337
|
-
return localStorage.getItem(key) === "1";
|
|
338
|
-
} catch {
|
|
339
|
-
return false;
|
|
340
|
-
}
|
|
341
|
-
};
|
|
342
|
-
const writeDrawerOpen = (open, key = drawerStateKey) => {
|
|
343
|
-
const store = JSswift?.store;
|
|
344
|
-
if (store?.set) {
|
|
345
|
-
store.set(key, !!open);
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
try {
|
|
349
|
-
localStorage.setItem(key, open ? "1" : "0");
|
|
350
|
-
} catch {
|
|
351
|
-
// ignore storage errors
|
|
352
|
-
}
|
|
353
|
-
};
|
|
354
|
-
let drawerOpen = readDrawerOpen();
|
|
355
|
-
const setDrawerOpen = (open, key = drawerStateKey) => {
|
|
356
|
-
drawerOpen = !!open;
|
|
357
|
-
const drawerEls = drawerElsByKey.get(key);
|
|
358
|
-
if (drawerEls && drawerEls.size) {
|
|
359
|
-
drawerEls.forEach((el) => el?.classList.toggle("open", drawerOpen));
|
|
360
|
-
} else {
|
|
361
|
-
const drawerEl = document.querySelector(".
|
|
362
|
-
if (drawerEl) drawerEl.classList.toggle("open", drawerOpen);
|
|
363
|
-
}
|
|
364
|
-
drawerToggleIcons.forEach((entry) => {
|
|
365
|
-
if (!entry) return;
|
|
366
|
-
if (typeof entry.update === "function") {
|
|
367
|
-
entry.update(drawerOpen);
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
const { el, openIcon, closeIcon } = entry;
|
|
371
|
-
if (el) el.textContent = drawerOpen ? openIcon : closeIcon;
|
|
372
|
-
});
|
|
373
|
-
writeDrawerOpen(drawerOpen, key);
|
|
374
|
-
return drawerOpen;
|
|
375
|
-
};
|
|
376
|
-
|
|
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) ? `jss-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) ? "jss-banner-actions-bottom" : "";
|
|
19
|
+
});
|
|
20
|
+
const stackClass = uiComputed(rawProps.stack, () => uiUnwrap(rawProps.stack) ? "jss-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
|
+
"jss-clear-set",
|
|
30
|
+
"jss-banner",
|
|
31
|
+
"jss-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(--jss-${s})`;
|
|
46
|
+
return s;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (rawProps.accent != null || uiIsReactive(rawProps.accent)) {
|
|
50
|
+
p.style["--jss-banner-accent"] = () => toneToCss(rawProps.accent);
|
|
51
|
+
p.style["--jss-banner-border"] = () => {
|
|
52
|
+
const tone = toneToCss(rawProps.accent);
|
|
53
|
+
return tone ? `color-mix(in srgb, ${tone} 38%, var(--jss-border-color))` : "";
|
|
54
|
+
};
|
|
55
|
+
p.style["--jss-banner-bg"] = () => {
|
|
56
|
+
const tone = toneToCss(rawProps.accent);
|
|
57
|
+
return tone ? `color-mix(in srgb, ${tone} 14%, var(--jss-panel))` : "";
|
|
58
|
+
};
|
|
59
|
+
p.style["--jss-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: "jss-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: "jss-banner-icon" }, ...iconNodes) : null,
|
|
133
|
+
_.div(
|
|
134
|
+
{ class: "jss-banner-body" },
|
|
135
|
+
_.div(
|
|
136
|
+
{ class: "jss-banner-copy" },
|
|
137
|
+
titleNodes.length ? _.div({ class: "jss-banner-title" }, ...titleNodes) : null,
|
|
138
|
+
messageNodes.length ? _.div({ class: "jss-banner-message" }, ...messageNodes) : null,
|
|
139
|
+
descriptionNodes.length ? _.div({ class: "jss-banner-description" }, ...descriptionNodes) : null,
|
|
140
|
+
bodyNodes.length ? _.div({ class: "jss-banner-extra" }, ...bodyNodes) : null,
|
|
141
|
+
metaNodes.length ? _.div({ class: "jss-banner-meta" }, ...metaNodes) : null
|
|
142
|
+
),
|
|
143
|
+
hasBottomActions && actionsNodes.length
|
|
144
|
+
? _.div({ class: "jss-banner-actions" }, ...actionsNodes)
|
|
145
|
+
: null
|
|
146
|
+
),
|
|
147
|
+
sideNodes.length ? _.div({ class: "jss-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
|
+
|
|
159
|
+
// Esempio: JSswift.ui.Banner({ type: "warning", title: "Pagamento in sospeso", message: "Aggiorna il batch entro le 18:00" })
|
|
160
|
+
|
|
161
|
+
UI.Alert = (...args) => {
|
|
162
|
+
const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
|
|
163
|
+
const slots = rawProps.slots || {};
|
|
164
|
+
|
|
165
|
+
const resolveStateValue = () => normalizeState(uiUnwrap(rawProps.type) || uiUnwrap(rawProps.state) || uiUnwrap(rawProps.color) || "warning");
|
|
166
|
+
const stateClass = uiComputed([rawProps.type, rawProps.state, rawProps.color], () => {
|
|
167
|
+
const state = resolveStateValue();
|
|
168
|
+
return state ? `jss-state-${state}` : "";
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const props = { ...rawProps };
|
|
172
|
+
const p = JSswift.omit(props, [
|
|
173
|
+
"actions", "aside", "closeLabel", "description", "dismiss", "dismissible",
|
|
174
|
+
"icon", "iconSize", "message", "meta", "onDismiss", "slots", "state",
|
|
175
|
+
"subtitle", "title", "type", "variant"
|
|
176
|
+
]);
|
|
177
|
+
p.class = uiClass([
|
|
178
|
+
"jss-clear-set",
|
|
179
|
+
"jss-alert",
|
|
180
|
+
"jss-singularity",
|
|
181
|
+
stateClass,
|
|
182
|
+
uiWhen(rawProps.dismissible, "is-dismissible"),
|
|
183
|
+
props.class
|
|
184
|
+
]);
|
|
185
|
+
p.style = { ...(props.style || {}) };
|
|
186
|
+
|
|
187
|
+
const autoIconMap = {
|
|
188
|
+
success: "check_circle",
|
|
189
|
+
warning: "warning",
|
|
190
|
+
danger: "error",
|
|
191
|
+
info: "info",
|
|
192
|
+
primary: "bolt",
|
|
193
|
+
secondary: "campaign",
|
|
194
|
+
dark: "shield",
|
|
195
|
+
light: "notifications"
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const iconFallback = (() => {
|
|
199
|
+
if (rawProps.icon === false || rawProps.icon === null) return null;
|
|
200
|
+
if (rawProps.icon != null) {
|
|
201
|
+
return typeof rawProps.icon === "string"
|
|
202
|
+
? UI.Icon({ name: rawProps.icon, size: rawProps.iconSize || "sm" })
|
|
203
|
+
: JSswift.ui.slot(rawProps.icon, { as: "icon" });
|
|
204
|
+
}
|
|
205
|
+
const iconName = autoIconMap[resolveStateValue()] || autoIconMap.warning;
|
|
206
|
+
return UI.Icon({ name: iconName, size: rawProps.iconSize || "sm" });
|
|
207
|
+
})();
|
|
208
|
+
|
|
209
|
+
const titleNodes = renderSlotToArray(slots, "title", {}, rawProps.title);
|
|
210
|
+
const messageNodes = renderSlotToArray(slots, "message", {}, rawProps.message != null ? rawProps.message : children);
|
|
211
|
+
const descriptionNodes = renderSlotToArray(slots, "description", {}, rawProps.description ?? rawProps.subtitle);
|
|
212
|
+
const metaNodes = renderSlotToArray(slots, "meta", {}, rawProps.meta);
|
|
213
|
+
const actionsNodes = renderSlotToArray(slots, "actions", {}, rawProps.actions);
|
|
214
|
+
const asideNodes = renderSlotToArray(slots, "aside", {}, rawProps.aside);
|
|
215
|
+
const iconNodes = renderSlotToArray(slots, "icon", {}, iconFallback);
|
|
216
|
+
const dismissNodes = renderSlotToArray(slots, "dismiss", {}, rawProps.dismiss);
|
|
217
|
+
|
|
218
|
+
let alertEl = null;
|
|
219
|
+
const defaultDismissNode = rawProps.dismissible || rawProps.onDismiss
|
|
220
|
+
? UI.Btn({
|
|
221
|
+
class: "jss-alert-close",
|
|
222
|
+
outline: true,
|
|
223
|
+
size: "sm",
|
|
224
|
+
"aria-label": rawProps.closeLabel || "Chiudi alert",
|
|
225
|
+
onClick: (e) => {
|
|
226
|
+
rawProps.onDismiss?.(e);
|
|
227
|
+
if (e.defaultPrevented) return;
|
|
228
|
+
alertEl?.remove();
|
|
229
|
+
}
|
|
230
|
+
}, UI.Icon({ name: "close", size: "sm" }))
|
|
231
|
+
: null;
|
|
232
|
+
|
|
233
|
+
alertEl = _.div(
|
|
234
|
+
p,
|
|
235
|
+
iconNodes.length ? _.div({ class: "jss-alert-icon" }, ...iconNodes) : null,
|
|
236
|
+
_.div(
|
|
237
|
+
{ class: "jss-alert-body" },
|
|
238
|
+
titleNodes.length ? _.div({ class: "jss-alert-title" }, ...titleNodes) : null,
|
|
239
|
+
messageNodes.length ? _.div({ class: "jss-alert-message" }, ...messageNodes) : null,
|
|
240
|
+
descriptionNodes.length ? _.div({ class: "jss-alert-description" }, ...descriptionNodes) : null,
|
|
241
|
+
metaNodes.length ? _.div({ class: "jss-alert-meta" }, ...metaNodes) : null,
|
|
242
|
+
actionsNodes.length ? _.div({ class: "jss-alert-actions" }, ...actionsNodes) : null
|
|
243
|
+
),
|
|
244
|
+
(asideNodes.length || dismissNodes.length || defaultDismissNode)
|
|
245
|
+
? _.div(
|
|
246
|
+
{ class: "jss-alert-side" },
|
|
247
|
+
...asideNodes,
|
|
248
|
+
...(dismissNodes.length ? dismissNodes : (defaultDismissNode ? [defaultDismissNode] : []))
|
|
249
|
+
)
|
|
250
|
+
: null
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
alertEl.setAttribute("role", resolveStateValue() === "danger" || resolveStateValue() === "warning" ? "alert" : "status");
|
|
254
|
+
setPropertyProps(alertEl, rawProps);
|
|
255
|
+
return alertEl;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// Esempio: JSswift.ui.Alert({ type: "warning", title: "Review richiesta", message: "Controlla il batch prima del go-live" })
|
|
259
|
+
|
|
260
|
+
UI.EmptyState = (...args) => {
|
|
261
|
+
const { props: rawProps, children } = JSswift.uiNormalizeArgs(args);
|
|
262
|
+
const slots = rawProps.slots || {};
|
|
263
|
+
const resolveStateValue = () => normalizeState(uiUnwrap(rawProps.state) || uiUnwrap(rawProps.color) || "");
|
|
264
|
+
const stateClass = uiComputed([rawProps.state, rawProps.color], () => {
|
|
265
|
+
const state = resolveStateValue();
|
|
266
|
+
return state ? `jss-state-${state}` : "";
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const props = { ...rawProps };
|
|
270
|
+
const p = JSswift.omit(props, [
|
|
271
|
+
"actions", "description", "eyebrow", "icon", "iconSize", "illustration", "media",
|
|
272
|
+
"message", "meta", "slots", "state", "title"
|
|
273
|
+
]);
|
|
274
|
+
p.class = uiClass([
|
|
275
|
+
"jss-clear-set",
|
|
276
|
+
"jss-empty-state",
|
|
277
|
+
"jss-singularity",
|
|
278
|
+
stateClass,
|
|
279
|
+
uiWhen(rawProps.compact, "compact"),
|
|
280
|
+
uiWhen(rawProps.inline, "inline"),
|
|
281
|
+
props.class
|
|
282
|
+
]);
|
|
283
|
+
p.style = { ...(props.style || {}) };
|
|
284
|
+
|
|
285
|
+
const iconFallback = rawProps.icon === false
|
|
286
|
+
? null
|
|
287
|
+
: (rawProps.icon != null
|
|
288
|
+
? (typeof rawProps.icon === "string"
|
|
289
|
+
? UI.Icon({ name: rawProps.icon, size: rawProps.iconSize || "xl" })
|
|
290
|
+
: JSswift.ui.slot(rawProps.icon, { as: "icon" }))
|
|
291
|
+
: UI.Icon({ name: "inbox", size: rawProps.iconSize || "xl" }));
|
|
292
|
+
|
|
293
|
+
const illustrationNodes = renderSlotToArray(slots, "illustration", {}, rawProps.illustration ?? rawProps.media);
|
|
294
|
+
const iconNodes = renderSlotToArray(slots, "icon", {}, iconFallback);
|
|
295
|
+
const eyebrowNodes = renderSlotToArray(slots, "eyebrow", {}, rawProps.eyebrow);
|
|
296
|
+
const titleNodes = renderSlotToArray(slots, "title", {}, rawProps.title);
|
|
297
|
+
const messageNodes = renderSlotToArray(slots, "message", {}, rawProps.message != null ? rawProps.message : children);
|
|
298
|
+
const descriptionNodes = renderSlotToArray(slots, "description", {}, rawProps.description);
|
|
299
|
+
const metaNodes = renderSlotToArray(slots, "meta", {}, rawProps.meta);
|
|
300
|
+
const actionsNodes = renderSlotToArray(slots, "actions", {}, rawProps.actions);
|
|
301
|
+
const bodyNodes = renderSlotToArray(slots, "default", {}, rawProps.message != null ? children : null);
|
|
302
|
+
|
|
303
|
+
const emptyState = _.section(
|
|
304
|
+
p,
|
|
305
|
+
illustrationNodes.length
|
|
306
|
+
? _.div({ class: "jss-empty-state-illustration" }, ...illustrationNodes)
|
|
307
|
+
: null,
|
|
308
|
+
iconNodes.length ? _.div({ class: "jss-empty-state-icon" }, ...iconNodes) : null,
|
|
309
|
+
eyebrowNodes.length ? _.div({ class: "jss-empty-state-eyebrow" }, ...eyebrowNodes) : null,
|
|
310
|
+
titleNodes.length ? _.div({ class: "jss-empty-state-title" }, ...titleNodes) : null,
|
|
311
|
+
messageNodes.length ? _.div({ class: "jss-empty-state-message" }, ...messageNodes) : null,
|
|
312
|
+
descriptionNodes.length ? _.div({ class: "jss-empty-state-description" }, ...descriptionNodes) : null,
|
|
313
|
+
metaNodes.length ? _.div({ class: "jss-empty-state-meta" }, ...metaNodes) : null,
|
|
314
|
+
bodyNodes.length ? _.div({ class: "jss-empty-state-extra" }, ...bodyNodes) : null,
|
|
315
|
+
actionsNodes.length ? _.div({ class: "jss-empty-state-actions" }, ...actionsNodes) : null
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
setPropertyProps(emptyState, rawProps);
|
|
319
|
+
return emptyState;
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// Esempio: JSswift.ui.EmptyState({ title: "No results", message: "Prova a cambiare i filtri" })
|
|
323
|
+
|
|
324
|
+
// -------------------------------
|
|
325
|
+
// 3) APP SHELL
|
|
326
|
+
// -------------------------------
|
|
327
|
+
let drawerStateKey = "jsswift:drawer-open";
|
|
328
|
+
const drawerToggleIcons = new Set();
|
|
329
|
+
const drawerElsByKey = new Map();
|
|
330
|
+
const readDrawerOpen = (key = drawerStateKey) => {
|
|
331
|
+
const store = JSswift?.store;
|
|
332
|
+
if (store?.get) {
|
|
333
|
+
const stored = store.get(key, undefined);
|
|
334
|
+
if (typeof stored === "boolean") return stored;
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
return localStorage.getItem(key) === "1";
|
|
338
|
+
} catch {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
const writeDrawerOpen = (open, key = drawerStateKey) => {
|
|
343
|
+
const store = JSswift?.store;
|
|
344
|
+
if (store?.set) {
|
|
345
|
+
store.set(key, !!open);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
localStorage.setItem(key, open ? "1" : "0");
|
|
350
|
+
} catch {
|
|
351
|
+
// ignore storage errors
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
let drawerOpen = readDrawerOpen();
|
|
355
|
+
const setDrawerOpen = (open, key = drawerStateKey) => {
|
|
356
|
+
drawerOpen = !!open;
|
|
357
|
+
const drawerEls = drawerElsByKey.get(key);
|
|
358
|
+
if (drawerEls && drawerEls.size) {
|
|
359
|
+
drawerEls.forEach((el) => el?.classList.toggle("open", drawerOpen));
|
|
360
|
+
} else {
|
|
361
|
+
const drawerEl = document.querySelector(".jss-drawer");
|
|
362
|
+
if (drawerEl) drawerEl.classList.toggle("open", drawerOpen);
|
|
363
|
+
}
|
|
364
|
+
drawerToggleIcons.forEach((entry) => {
|
|
365
|
+
if (!entry) return;
|
|
366
|
+
if (typeof entry.update === "function") {
|
|
367
|
+
entry.update(drawerOpen);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const { el, openIcon, closeIcon } = entry;
|
|
371
|
+
if (el) el.textContent = drawerOpen ? openIcon : closeIcon;
|
|
372
|
+
});
|
|
373
|
+
writeDrawerOpen(drawerOpen, key);
|
|
374
|
+
return drawerOpen;
|
|
375
|
+
};
|
|
376
|
+
|